Repository navigation
Add hosted build APIs and agent-aware JSON output - #4
Merged
Merged
Conversation
Survey of Xantham's scripts and Build/Program.fs: shared boilerplate that Baked prefab stages could absorb, API traps, machine-readable output and exit codes for agents, and what must change for pipelines to run inside a long-lived SageFs FSI session (argv-free invoke, console and cancellation hygiene). Records consumer-side findings separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…nResult
Implements PLAN-Integration §4.2 and §5.3.1.
- Exit codes: 0 success (help, --version, --explain), 1 stage failure,
2 usage error (parse error, missing subcommand, failed dependency
validation), 130 cancelled; named by the ExitCode module.
- Command.root { ... } builds a RootCommandDefinition without parsing or
running; Command.invoke args / RootCommandDefinition.Invoke(args,
?output, ?error, ?cancellationToken) parse, run and return a RunResult
(ExitCode, RunOutcome, a PipelineRun per pipeline with its ScopeReport
and StageTiming snapshots). rootCommand's Run goes through Invoke, so
the exit-code mapping lives in one place.
- PipelineContext.runWith links an external cancellation token into the
pipeline's timeout source; run is runWith CancellationToken.None.
- --explain, the timing summary, the grouping --explain and the
dependency diagnostic write to the invocation configuration's
Output/Error instead of Console.
- Records the §8 Q4 decision and section statuses in PLAN-Integration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…rted The remarks read as a serialisation guarantee that Invoke does not enforce. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
An [<AutoOpen>] ConsumerTypes module abbreviates StageContext, PipelineContext, CommandSpec, StageParent, RuntimeContext, StepIndex and the Build* aliases, and the public StageContext module re-exports writeLine, getOutput, getVerbosity and getNamePath. The docs pages drop `open Partas.Build.Internal`, and ConsumerSurfaceTests compiles consumer-style code without it. Baked's bump stage reports through StageContext.writeLine instead of printfn, so quiet and captured stages route it; BakedTests covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
- Build/Program.fs: drop `open Partas.Build.Internal`, build the Expecto arguments with Cmd.argIf/Cmd.args instead of a null-holed Cmd.ofString, remove the unused formatFiles glob. - .mcp.json: point fslangmcp at Partas.Build.slnx. - PLAN-Execution.md: status header reflects the implementation. - PLAN-Integration.md: record what is implemented and how it was verified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
Public condition functions return BuildStageIsActive, so a consumer writing a reusable condition in the same style can name it without opening Internal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…plain PLAN-Integration §4.1 and §4.3. - MachineOutput.fs: `--json` and `--schema` on every command, `--report <path>` on every command that runs pipelines, each skipped when the command already declares the name or an alias. `--schema` prints the command tree as JSON: options with aliases, type, default, accepted values, description, arity. - Under `--json`, `--explain` prints the stage tree as JSON (statically), and a run prints its RunResult as one line of JSON in place of the timing table; `--report` writes it indented to a file. Every document carries formatVersion 1. System.Text.Json is referenced on netstandard2.0 only. - Explain builds a model (ExplainedPipeline/Stage/Step/Condition) under an ExplainMode. It walks StageContext.Conditions instead of calling IsActive, so each condition runs at most once (a failing `when' stage` ran twice) and a throwing condition renders as its message. ExplainMode.Static leaves conditions marked Conditions.effectful unevaluated: whenBranch(es), whenStage / `when' stage`, and whenAll/whenAny/whenNot over any of them. - Conditions.whenBranches takes #seq<string>, keeping the call a plain application the mark survives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
A stage that reads MachineOutput.json or MachineOutput.report registers the option through its pipeline's inputs; reserve now treats that same instance as taken, so --help and --schema list it once. The explain document's step `index` now counts from zero, matching the run result's `step` and `path`. The text form still prints an unlabelled step as `step 1`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
Input.sensitive marks an option or argument as carrying a secret; --schema writes its present default as "***" and reports "sensitive": true. Baked.NuGet.apiKey, whose default is read from NUGET_API_KEY, is marked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…t, reentrancy, interrupts Implements PLAN-Integration §5.3.2-§5.3.6 and answers §8 Q5. - Spike (§8 Q5): Spectre's static AnsiConsole stays bound to the Console.Out current at its first use; without a terminal every console reports width -1 and renders nothing. Recorded in the plan. - Terminal.fs is the only route to the console: Terminal.ansi () rebinds AnsiConsole.Console when Console.Out changes (an explicit assignment is kept), falls back to 80 columns, and under Terminal.withOutput (an AsyncLocal run writer) writes plain text to that writer. Invoke's output now receives the run's console output, child processes included. - Console encodings are set at most once per process, only for a stream that is not redirected, and never throw. - rootCommandOfScript reads argv when the command runs: RootCommandBuilder takes unit -> string array, with an array constructor for rootCommand. - PipelineContext.create starts EnvVars empty; runWith layers the pipeline's own variables over the environment as the run starts. applyDefaults' per-key rule follows. - runWith fails fast with InvalidOperationException on a concurrent second run of one pipeline value; PipelineContext.withRunState gives a copy that runs independently. - RootCommandDefinition.Invoke runs on a worker; a ThreadInterruptedException on the waiting thread cancels the invocation's token source, killing the process tree through the existing registration, then rethrows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f Fix host-safety review findings: refused runs, run writer threading, cts disposal - A command invocation refused a running pipeline value no longer records or prints the other run's reports and timings: runReportingTimings enters the reentrancy guard before the try whose finally reads the collections, and runs through the new internal PipelineContext.runEntered. - Terminal.withOutput installs the caller's writer through TextWriter.Synchronized, since parallel' stages and a redirected child's reader callbacks write to it concurrently. - runInterruptibly disposes the linked CancellationTokenSource in a finally, so failed or interrupted invocations leave no registration on the caller's token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…o stdout Terminal.ansi() rebinding to the current Console.Out routed library output through Expecto's FuncTextWriter. On Unix the runtime locks Console.Out for every terminal write, while Expecto's writer takes Expecto's lock first, so a pipeline thread and Expecto's logger took the two locks in opposite orders and the Release suite could hang under a pseudo-terminal. - Terminal.ansi() remembers the console each Console.Out writer was first seen with, so a writer restored after a swap gets its console back instead of a new console over itself. - The test host pins AnsiConsole.Console to the real stdout before Expecto installs its writer. - Document the hazard and the host-side remedies in Terminal.fs, CLAUDE.md and PLAN-Integration.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
- `runLine` runs a command line derived from the stage context (string, Async/Task, and their option forms). The same six `run` overloads are now [<Obsolete>]: a lambda returning a message would start a process. `run`'s overload set is otherwise unchanged, so the FS0041 balance around `run (build, buildStep)` holds (spiked over every call-site shape; CompilerProbe Debug and Release). - `Input.desc` is [<Obsolete>] in favour of `Input.description`; `inputs` already was, in favour of `input`. Usages in src/, Build/, Partas.Build.ExternalAnnotations/, README and docs/content migrated. - `when' value "reason"` on `stage` (and its InputSpec mirror) records the reason through addPredicateBecause, so --explain prints `(skipped: --quick is set)`. Build/Program.fs gives each `when'` one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…tegration §3.1) Partas.Build.Baked gains Common.quick/skipTests/watch, Dotnet.configOrRelease, and Stages: restore, clean, build, pack, expecto, nugetPush, fantomas and npmInstall, each a function answering InputSpec<StageContext> with a ...With counterpart taking its options as InputSpecs (PLAN §8 Q3: functions). Skips carry an --explain reason. clean is a System.IO glob clean (Clean.fs), so Build/Program.fs and Build.fsproj no longer need Fake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
… remark Clean.directories now drops any selected directory nested inside another selected one, literal or matched, so Clean.run no longer empties a parent and then re-creates a literal child under it. The doc remark states that contract; a test covers literal-under-match, literal-under-literal, match-under-literal and a shared name prefix. expectoWith's remark states what runs the suite instead of arguing for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
A linked directory is neither walked nor selected, a literal directory reached through a link is dropped, and emptying a directory removes the links in it without touching their targets. Previously **/bin could empty a directory outside the root, and a cyclic link made the walk recurse without end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…t output line - Baked/Common.fs and MachineOutput.fs used the now-obsolete `Input.desc` (the json and baked-stages branches predate api-traps' obsoletion); both use `Input.description`, and the build is warning-free again. Common.fs's `--ci` line had lost api-traps' migration in the baked-stages merge. - The machine-output tests assumed the given `output` writer receives only the JSON. Since host routes the run's own console lines to that writer too, they now read the result from the last line, as the documented `--json` contract states, and check `--report` keeps the JSON (not every line) out of the output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
§7 rows 1-10 are done on this branch; the status header and a note under §7 say what the merges decided. CLAUDE.md's test count reflects the merged suite (362 in Partas.Build.Tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…ation The four .fsx pages under docs/content #load the library in compile order and stopped type-checking when Terminal.fs joined it. They load it now, and Baked's Clean.fs and Stages.fs too. The cmd, operations and CompilerTests lists end in testLabel "integration", which SageFs live testing reads from the test name to run them on demand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
<example> blocks on the run overloads, runLine, runSensitive, input, whenSome, cmd, Cmd.argIf/argWhenSome, stage, pipeline, command, rootCommand, rootCommandOfScript, Command.root/invoke, RootCommandDefinition.Invoke, RunResult and its JSON form, the --json flag and Baked.Stages. The run (string) remark claimed an interpolated string keeps each hole one argument; it binds to the string overload and is re-split, and the remark now says so. AGENTS-snippet.md is packed into the Partas.Build package root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
…nippet docs/content/Build/hosting.md covers the build.defs.fsx/build.fsx split, the SageFs init script binding Invoke with the eval's writer, mounting tool scripts with addCommand, cancellation and what a session shares between calls. docs/static/AGENTS-snippet.md tells agents to use --help, --explain (and --json), the exit codes and the JSON run result; llms.txt and README link it. CAPABILITIES.md gains exit codes, Command.root/invoke/RunResult, Input.sensitive and the Baked prefabs. PLAN-Integration records 4.4, 4.5 and 5.4 as done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
- ConsumerTypes: move the stepIndex measure into a nested AutoOpen Measures module so the docs site no longer hits nacara/duplicate-route, and abbreviate Step, StageCondition, StageIndex and BuildEnvInfo so the public StageContext.addStep/addSteps/setSteps and Steps need no Internal. - invoke: under --explain a failed dependency validation exits 2 without a run result or --report file; an exception other than a pipeline failure or cancellation writes the run result as failed, then propagates. - MachineOutput: the --schema input is `schema`, the renderer `commandSchema`. - Docs: Ctrl+C is not an ExitCode.Cancelled path; test totals, Explain.fs dependencies and Terminal.fs in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LK3n2hgMAMxKAmuKyZL8f
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Partas.Build currently requires agents to parse human console output and offers no reusable root-command definition for long-lived hosts. This branch adds hosted invocation and structured discovery/results, with JSON output enabled automatically when the invocation environment identifies an AI coding harness.
Command.root,Command.invoke,RunResult, cancellation and exit-code categories; make console routing, environment refresh and repeated invocation safe in a long-lived host.--json,--report,--schemaand static JSON--explain. JSON run results remain the last output line; step output is unchanged, sensitive values remain masked, and consumer-declared options retain precedence.AiEnvironment.detect/detectWith, returning lazy boolean detections using the is-ai-agent README's environment predicates. JSON defaults evaluate a fresh detection per invocation. Cursor's legacy trace/pager signals must occur together; credentials and configuration are excluded.PARTAS_BUILD_DISABLE_AI=1forces detection false. Nonblank override values enable it except trimmed, case-insensitive0,false,noandoff. Explicit--jsonor--json falsewins;--schemaand--explainare never automatically enabled. Detection uses environment signals only, without the upstream filesystem probe.Validation:
dotnet run --project Build.fsproj -- test: exit 0; 540 suite tests passed, 9 skipped, plus 11 Debug and 11 Release compiler probes passed. Library builds cover net10.0, net8.0 and netstandard2.0 where declared.dotnet fsi index.fsxfromdocs/content/Build: exit 0, no warnings or errors. Updated every documentation source load list that includes MachineOutput.git diff --checkpassed.The clean test command retains an existing docs restore warning (NU1608: FSharp.Compiler.Service 43.12.400 requests FSharp.Core 10.1.400, while 10.1.401 is resolved). Existing platform skips include the symlink test on this Windows machine, which lacks Developer Mode/symlink privileges, and annotation tests unavailable under the current SDK. Environment markers identify inherited harness context, not proof that an AI initiated the command; they affect output defaults only.