Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c989297
Add PLAN-Integration: consumer patterns, agent discoverability, SageFs
claude Sep 24, 2026
6c55e98
Add exit code categories and an argv-free Command.invoke returning Ru…
claude Sep 24, 2026
4c64bb7
Correct RootCommandDefinition doc: concurrent invocations are unsuppo…
claude Sep 24, 2026
f9d20e4
Reach every consumer-named model type from Partas.Build (Task 14b)
claude Sep 24, 2026
41069b5
Hygiene from PLAN-Integration §7 row 1
claude Sep 24, 2026
59cb334
Abbreviate BuildStageIsActive and BuildConditions in ConsumerTypes
claude Sep 24, 2026
d5a79a3
Add --json, --report and --schema, and a static side-effect-free --ex…
claude Sep 24, 2026
54a456f
Declare a reserved option once and count JSON step positions from zero
claude Sep 24, 2026
56fa8f6
Mask the default of a sensitive option in --schema
claude Sep 24, 2026
19b2de5
Make the library safe in a long-lived host: console, argv, environmen…
claude Sep 24, 2026
11d9c12
Keep each Console.Out writer's console; pin the test host's console t…
claude Sep 24, 2026
df2f3e9
Close the PLAN-Integration §3.2 API traps
claude Sep 24, 2026
6c36bef
Reword when' skip-reason xmldoc as a contract
claude Sep 24, 2026
0334f13
Add Baked prefab stages and rebuild Build/Program.fs on them (PLAN-In…
claude Sep 24, 2026
79da81f
Omit nested directories from Clean.directories; restate expectoWith's…
claude Sep 24, 2026
bc11753
Stop Clean from following symbolic links
claude Sep 24, 2026
89f85ac
Migrate merged code off Input.desc; read --json's result from the las…
claude Sep 24, 2026
6d0e8f7
Record the integration roll-up in PLAN-Integration and CLAUDE.md
claude Sep 24, 2026
abe7e80
Load Terminal.fs in the docs pages; label the process tests as integr…
claude Sep 24, 2026
c814fa7
Add XML examples to the entry points agents reach first
claude Sep 24, 2026
f672d8c
Document hosting a build in a long-lived session, and ship an agent s…
claude Sep 24, 2026
30ac473
Fix final review findings on the integration branch
claude Sep 24, 2026
46f6899
Preserve hosted reporting and CI fixes after rebasing onto master
shayanhabibi Oct 7, 2026
469c55b
Default JSON output on for detected AI agent environments
shayanhabibi Oct 7, 2026
89c714f
Bump packages for hosted APIs and agent-aware output
shayanhabibi Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"fslangmcp": {
"type": "stdio",
"command": "fslangmcp",
"args": ["--project", "Partas.Testing.slnx"],
"args": ["--project", "Partas.Build.slnx"],
"env": {}
}
}
Expand Down
6 changes: 0 additions & 6 deletions Build.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@
`Update` modifies the existing implicit reference instead of
adding a duplicate (which would trigger NU1504). -->
<PackageReference Update="FSharp.Core" Version="10.1.401"/>
<PackageReference Include="Fake.Core.Target" Version="6.1.4"/>
<PackageReference Include="Fake.Core.UserInput" Version="6.1.4"/>
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.4"/>
<PackageReference Include="Fake.DotNet.Testing.Expecto" Version="6.1.4"/>
<PackageReference Include="Fake.Tools.Git" Version="6.1.4"/>
<PackageReference Include="FsToolkit.ErrorHandling" Version="5.2.0"/>
<PackageReference Include="Fake.Core.ReleaseNotes" Version="6.1.4"/>
<PackageReference Include="Partas.GitNet" Version="3.0.0"/>
<PackageReference Include="Partas.TypeProvider.BuildHelper" Version="0.2.5" />
</ItemGroup>
Expand Down
184 changes: 55 additions & 129 deletions Build/Program.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <summary>
/// <summary>
/// The build CLI, written against Partas.Build itself.
///
/// A step is a stage of a pipeline, and a stage that needs a flag binds it in an
Expand All @@ -11,16 +11,9 @@ module Build

open System
open System.IO
open Fake.Core.Context
open Fake.IO
open Fake.IO.Globbing.Operators
open Partas.Build
open Partas.Build.Internal
open Partas.TypeProvider.BuildHelper

let execContext = FakeExecutionContext.Create false "build.fsx" []
setExecutionContext (RuntimeContext.Fake execContext)

[<Literal>]
let __REPOSITORY_DIRECTORY__ =
__SOURCE_DIRECTORY__
Expand All @@ -35,31 +28,11 @@ type Repo =

let private root = Repo.FileSystem.``.``.ToString()

let formatFiles =
!! "**/*.fs"
-- "**/obj/**/*.*"
-- "**/AssemblyInfo.fs"

module Options =
let quick =
Input.option<bool> "--quick"
|> Input.alias "-q"
|> Input.desc "Skips restores, installations, formatting etc"
let skipTests =
Input.option<bool> "--skip-tests"
|> Input.desc "Skips running tests"
let nugetSource =
Input.option<string> "--nuget-source"
|> Input.def "https://api.nuget.org/v3/index.json"
|> Input.desc "NuGet feed URL for authenticated publishing"
let watch =
Input.option<bool> "--watch"
|> Input.desc "Runs the operation in watch mode."

let config =
Baked.Dotnet.config.option
|> InputSpec.ofInput
|> InputSpec.map (Option.defaultValue "Release")
|> Input.description "NuGet feed URL for authenticated publishing"

module Project =
let projects = Repo.Project.AllProjects()
Expand Down Expand Up @@ -88,7 +61,7 @@ module Project =
Input.option<string list> "--project"
|> Input.alias "-p"
|> Input.arity Arity.OneOrMore
|> Input.desc "The project(s) to target"
|> Input.description "The project(s) to target"
|> Input.allowMultipleArgumentsPerToken
|> Input.mapFromManyWith StringComparer.OrdinalIgnoreCase [
yield! allProjects
Expand All @@ -97,124 +70,77 @@ module Project =

/// <summary>Stages every command opens with. All are skipped by <c>--quick</c>.</summary>
module Prelude =
let restore = input {
let! quick = Options.quick
return stage "restore" {
when' (not quick)
run "dotnet tool restore --verbosity q"
run (cmd $"dotnet restore {Repo.Project.SolutionFile}")
}
}
let clean = input {
let! quick = Options.quick

return stage "clean" {
when' (not quick)

run (fun (_: StageContext) ->
Repo.VirtualFileSystem.bin.``.``.EnumerateFiles("*.nupkg", SearchOption.AllDirectories)
|> Seq.iter (_.ToString() >> Shell.rm)
!! "**/**/bin"
++ Repo.VirtualFileSystem.tmp.ToString()
-- Repo.VirtualFileSystem.bin.ToString()
|> Shell.cleanDirs
)
}
}
let restore = Baked.Stages.restore Repo.Project.SolutionFile
/// Empties every <c>bin</c> directory except the root's, and deletes the packages in the root's.
let clean = Baked.Stages.clean [ "**/bin"; "!bin"; "tmp" ] [ "bin/**/*.nupkg" ]

module ProjectManagement =
let build (project: InputSpec<string>) = input {
let! config = Options.config
and! project = project
return stage $"build {project}" {
run (cmd $"dotnet build {project} -c {config}")
let private packages = Repo.VirtualFileSystem.bin.ToString()

// These projects share references and output paths; independent dotnet builds must not race.
let buildAll =
Baked.Stages.build (Project.allProjects |> List.map snd)
|> InputSpec.map (StageContext.toggleParallel false)
let packAll = Baked.Stages.pack packages (Project.allProjects |> List.map snd)
let publishAll = input {
let! source = Options.nugetSource
and! apiKey = Baked.NuGet.apiKey.option
return stage "push" {
let packageGlob = Path.Combine(packages, "*.nupkg")
match apiKey with
| Some key ->
stage "push to source" {
run (cmd $"dotnet nuget push {packageGlob} --source {source}"
|> Cmd.secretOption "--api-key" key
|> Cmd.arg "--skip-duplicate")
}
| None -> stage "push to local feed" { run (cmd $"dotnet nuget push {packageGlob} --source local --skip-duplicate") }
}
}
let buildAll = stage "build" {
parallel'
for _, project in Project.allProjects do
build (InputSpec.ret project)
}

let pack (project: InputSpec<string>) = input {
let! project = project
return stage $"pack {project}" {
run (cmd $"dotnet pack {project} --no-restore -o {Repo.VirtualFileSystem.bin.ToString()}")
}
}
let packAll = stage "pack" {
parallel'
for _, project in Project.allProjects do
InputSpec.ret project
|> pack
}
let publish (project: InputSpec<string>) = input {
let! key = Baked.NuGet.apiKey.option
and! source = Options.nugetSource
and! project = project
return stage $"publish {project}" {
stage "local publish" {
when' key.IsNone
echo "Publishing to local feed"
run $"dotnet nuget push {project} --source local --skip-duplicate"
}
whenSome key (fun key ->
stage "nuget publish" {
echo $"Publishing to {source}"
runSensitive
$"dotnet nuget push {project} --source {source} --api-key {key} --skip-duplicate"
})
}
}
let publishAll = stage "publish" {
Path.Combine(Repo.VirtualFileSystem.bin.ToString(), "*.nupkg")
|> InputSpec.ret
|> publish
}
let bumpArgument =
Baked.SemVer.Stages.bumpArgument (InputSpec.ofInput Project.target)

module Tests =
let buildAll = input {
let! skipTests = Options.skipTests
and! projects =
Project.testProjects
|> List.map (_.Path >> InputSpec.ret >> ProjectManagement.build)
|> InputSpec.sequence
return stage "build tests" {
when' (not skipTests)
projects
}
}
/// Builds the test projects one at a time; skipped by <c>--skip-tests</c>.
let buildAll =
Baked.Stages.buildWith Baked.Dotnet.configOrRelease (Project.testProjects |> List.map _.Path)
|> InputSpec.map (fun build -> { StageContext.toggleParallel false build with Name = "build tests" })
|> InputSpec.map2 (fun skipTests -> StageContext.addPredicateBecause (ValueSome "--skip-tests is set") (fun _ -> not skipTests))
(InputSpec.ofInput Baked.Common.skipTests)

let private expectoArguments = [ "--colours"; "256"; "--sequenced" ]

let execute = input {
let! skipTests = Options.skipTests
and! config = Options.config
let! skipTests = Baked.Common.skipTests
and! ci = Baked.Common.isCI
and! suites =
[ Repo.Project.``Partas.Build.Cmd.NetStandard.Tests``.Path
Repo.Project.``Partas.Build.ExternalAnnotations.Tests``.Path
Repo.Project.``Partas.Build.Tests``.Path
Repo.Project.``Partas.ExternalAnnotations.Tests``.Path ]
|> InputSpec.traverse (fun project ->
Baked.Stages.expecto project expectoArguments
|> InputSpec.map (StageContext.setOutput (ValueSome StageOutput.Console)))
return stage "test" {
when' (not skipTests)
// Expecto reports assertions on stdout; keep them visible in CI and uploaded logs.
when' (not skipTests) "--skip-tests is set"
outputTo StageOutput.Console
// Commands exercised by tests must not append fixture reports to the real job summary.
envVars [ "GITHUB_STEP_SUMMARY", "" ]
for project in [
Repo.Project.``Partas.Build.Cmd.NetStandard.Tests``.Path
Repo.Project.``Partas.Build.ExternalAnnotations.Tests``.Path
Repo.Project.``Partas.Build.Tests``.Path
Repo.Project.``Partas.ExternalAnnotations.Tests``.Path
] do stage $"test {project}" {
run (Cmd.ofString $"""dotnet run --project {project} --no-build -c {config} -- {if ci then "--summary" else null} --colours 256 --sequenced""")
}
// Runs in both configurations regardless of `--configuration`: Release is what catches FS1118.
suites
// Built by the run, in both configurations regardless of `--configuration`: Release is what catches FS1118.
for probeConfig in [ "Debug"; "Release" ] do stage $"compiler probe ({probeConfig})" {
run (Cmd.ofString $"""dotnet run --project {Repo.Project.``Partas.Build.CompilerProbe``.Path} -c {probeConfig} -- {if ci then "--summary" else null} --colours 256 --sequenced""")
run (
cmd $"dotnet run --project {Repo.Project.``Partas.Build.CompilerProbe``.Path} -c {probeConfig} --"
|> Cmd.argIf ci [ "--summary" ]
|> Cmd.args expectoArguments
)
}
}
}

module Documentation =
/// Serves under --watch, builds otherwise.
let generate = input {
let! watch = Options.watch
let! watch = Baked.Common.watch
and! noHotReload = Input.option<bool> "--no-hot-reload"
return stage "docs" {
run (
Expand All @@ -232,9 +158,9 @@ module Documentation =
/// Written for fsdocs, which generated both files at the site root as a link inventory under such a heading.
/// </remarks>
let llms = input {
let! watch = Options.watch
let! watch = Baked.Common.watch
return stage "llms" {
when' (not watch)
when' (not watch) "--watch is set"
run (fun ctx ->
let header = File.ReadAllText(Path.Combine(root, "docs", "static", "llms.txt")).TrimEnd()
for name in [ "llms.txt"; "llms-full.txt" ] do
Expand Down
Loading