Skip to content

merge program command decomposition - #162

Merged
IanFrelinger merged 35 commits into
masterfrom
cursor/sprint1-cli-decomp-integration-e574
Jun 14, 2026
Merged

merge program command decomposition#162
IanFrelinger merged 35 commits into
masterfrom
cursor/sprint1-cli-decomp-integration-e574

Conversation

@IanFrelinger

Copy link
Copy Markdown
Owner

Summary

Changes

Testing

Testing strategy (blast radius)

See Testing strategy pivot v1.

Change typeMinimum proof (check what applies)
Nexo.Core.Domaindotnet test src/Nexo.Tests.Domain · make kernel-coverage-gate
Infrastructure adapter (small / branchy)Focused unit or gap test in touched file · coverage gate
Hosting / API / routing / barriers / AddNexomake test-prod-style and/or make application-gate-tier-c
Docker / mesh / fleet / trustmake mesh-lab-e2e or relevant *-gate tier (see pivot doc)
Megaclass (ProviderFactory, Docker provisioners, …)ProdStyle / virtual host — do not add new *GapCoverageTests files
  • make kernel-coverage-gate (if touching src/Nexo.Core.* or src/Nexo.Infrastructure)
  • make kernel-gate (if touching kernel hosting / pipeline / profiles)
  • make test-prod-style (if touching production DI / API / routing)

Checklist

  • make test passes locally
  • Documentation updated (if applicable)
  • No TODO or NotImplementedException left unresolved
  • Breaking changes are documented

Release (only when this PR ships a versioned NuGet/GHCR release)

  • Not a versioned release — skip
  • Preflight:dotnet run --project application/src/Nexo.CLI -- release preflight <semver> (or make release-preflight VERSION=<semver>)
  • Track: open a Release checklist issue (GitHub → New issue → Release checklist form) or link an existing release issue
  • After merge: tag v<semver> and push (runs release.yml) — see docs/RELEASE_RUNBOOK.md

cursoragentand others added 30 commits June 13, 2026 21:44
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
cursoragentand others added 5 commits June 14, 2026 00:57
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
Co-authored-by: IanFrelinger <IanFrelinger@users.noreply.github.com>
@IanFrelinger
IanFrelinger merged commit ae6c6bf into masterJun 14, 2026
67 of 73 checks passed
@cursor
cursorBot deleted the cursor/sprint1-cli-decomp-integration-e574 branch June 14, 2026 21:41
IanFrelinger added a commit that referenced this pull request Aug 17, 2026
fix(cli): register the trust command on the root (dropped in #162)
IanFrelinger added a commit that referenced this pull request Aug 23, 2026
Wires the tested ProjectScaffold (merged in #365) to `ashlar init <name>`. The command
is deliberately thin: it chooses where files go and refuses to overwrite; every content
and validity decision lives kernel-side in ProjectScaffold, which round-trips its own
output through the loaders and refuses a non-sealed default by code.
Split from the kernel half per the layer-boundary rule (master is kernel-first; this
targets the application/studio-cli integration branch, mirroring application/mcp-a2a).
Also adds a RootCommandRegistrationTests assertion that `init` stays registered — the
front door of the product loop must never silently vanish from the root the way `trust`
did in #162, which is exactly the failure mode that test file exists to guard.
Exercised in the container: `ashlar init demo-app` writes both documents with the
policy-review guidance; rerun refuses with exit 1. Registration tests 3/3.
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
IanFrelinger added a commit that referenced this pull request Aug 23, 2026
* feat(cli): the init verb — application half of the scaffold slice (#366)
Wires the tested ProjectScaffold (merged in #365) to `ashlar init <name>`. The command
is deliberately thin: it chooses where files go and refuses to overwrite; every content
and validity decision lives kernel-side in ProjectScaffold, which round-trips its own
output through the loaders and refuses a non-sealed default by code.
Split from the kernel half per the layer-boundary rule (master is kernel-first; this
targets the application/studio-cli integration branch, mirroring application/mcp-a2a).
Also adds a RootCommandRegistrationTests assertion that `init` stays registered — the
front door of the product loop must never silently vanish from the root the way `trust`
did in #162, which is exactly the failure mode that test file exists to guard.
Exercised in the container: `ashlar init demo-app` writes both documents with the
policy-review guidance; rerun refuses with exit 1. Registration tests 3/3.
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(cli): ashlar verify — the wall, rendered for real (#368)
The verb behind the motion study. Thin over ProjectVerifier (#367): reads the two
documents, renders each course with the fixed vocabulary, maps outcomes to SPEC-009
exit codes (0 verified · 65 verification failed · 1 not-a-project), honours NO_COLOR
and redirected output.
The verdict word is VERIFIED, not CERTIFIED — certified means signed, and nothing
signs yet, so the gold line says exactly that: "unsigned — signing arrives with the
ledger". When real keys land, the provenance course and signature appear here; until
then this command prints the truth.
Exercised in the container:
ashlar init invoice-triage && ashlar verify
✓ course 1 · contract ✓ course 2 · composition ✓ course 3 · envelope
✓ VERIFIED 3 courses · unsigned
(flip policy to proposing with budget 0)
× FAILED course 'envelope': ... seal it or fund it exit 65
Registration guard added alongside init's. Registration tests 4/4.
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Portability gate: the loop on Linux, Windows, and macOS (#370)
* ci: portability gate — the product loop on Linux, Windows, and macOS
"Fully portable" is a product requirement, and until now nothing enforced it:
every green check runs on ubuntu. This gate runs the REAL loop on all three OSes —
ashlar init, ashlar verify (exit 0, verdict present and honest about being
unsigned), the broken-envelope path (exit 65 exactly, per SPEC-009, with the
"seal it or fund it" reason), and the overwrite refusal (exit 1).
Path-filtered to the CLI, the manifest contract, and itself, so the 3-OS matrix
only spends runners when the loop's surface changes. Portability that is not
gated regresses; this is the cheap insurance that it cannot regress silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(ci): capture verify's exit code -e-safely in the portability gate
All three OS legs failed at the same line — and the log is the good news: init and
verify ran and printed VERIFIED on every runner, so the loop IS portable. The
failure was this script: GitHub invokes bash with -e, and `out=$(cmd); rc=$?`
aborts at the assignment when cmd deliberately exits 65, before the assertion that
expects 65 can run. Captures now use `&& rc=0 || rc=$?`, which -e cannot interrupt.
The gate failing on its own first run, on the exact step that tests the failure
path, is the gate working — just one layer higher than intended.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* ashlar gates + the 44-scenario sweep (#372)
* feat(cli): ashlar gates — the human half of admission
The 2 AM approval, in the terminal. Thin over the kernel (#371): every rule — who
may transition what, refusals require reasons, immutable history — lives in
GateStore and is pinned by kernel tests; this class reads .ashlar/gates and renders
in the fixed vocabulary. Gold appears only when a stone is seated.
ashlar gates list what is held
ashlar gates --show <id> courses, provenance, diff, state
ashlar gates --admit <id> --as me seat the stone (actor recorded)
ashlar gates --refuse <id> --reason "..." refusal teaches: recorded, fed back
Plus a HIDDEN `gates propose --file p.json` — the runtime's entry, used by tests
and demos until the SelfExtendRunner wires in. Hidden because users never propose;
applications do. It loads the policy, counts admissions in the budget window, runs
AdmissionGate.Decide, records the outcome, exit 65 on rejection.
Exercised end to end in the container — the full thesis loop, real for the first
time: init -> verify -> propose (HELD, "a person seats the stone") -> gates lists
it -> --admit seats it ("admitted by ian.f · recorded") -> --show renders the
record -> a tool proposal rejects with "never the application's", exit 65.
Registration guard added (5/5).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(e2e): 44-scenario behavioural sweep for the loop, wired into the 3-OS gate
scripts/e2e-loop.sh exercises the shipped CLI against every behavioural claim the
loop makes — no mocks, every invocation a fresh process (so durability is inherent
to the assertions, not a separate test). Coverage: init (creation, refusals leave
files untouched, name validation writes nothing, paths with spaces), verify (all
courses, every failure path with its exit code, no fabricated signatures, no raw
ANSI when redirected), and gates (hold/admit/refuse/show across processes, sealed
rejection, envelope-beats-mode-and-budget, failed and missing courses, budget
degrade-to-held, append-once, illegal ids fail closed, malformed JSON).
The first run scored 28/44 and earned its keep on the spot: all 16 failures traced
to ONE real CLI bug the hand-run demo missed — --path was declared on `gates` but
System.CommandLine does not inherit parent options into subcommands, so
`gates propose --path` failed to parse. The manual demo used the cwd default,
which is exactly why manual testing never caught it. Fixed by making --path a
global option of the gates command. Second run: 44/44.
The sweep now runs as a second step of the portability gate, so every claim is
enforced on Linux, Windows, and macOS — "fully works" as a gate, not a status
report. (Harness itself kept portable: awk over grep -P, which macOS lacks.)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* refactor(cli): gates propose uses the store's atomic transaction (#374)
Follow-up to #373: the CLI's count-decide-record sequence is replaced by one
ProposeAsync call. The transaction lives kernel-side under the store lock precisely
so no caller can reassemble the budget race by invoking the pieces — this command
was the last such caller.
Black-box regression: the full 44-scenario sweep passes unchanged.
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* ashlar run: the loop's last verb (#379)
* feat(cli): ashlar run — the loop's last verb, verified first, manifest-driven
The product rule this verb enforces: YOU CANNOT RUN WHAT DOES NOT VERIFY. The
courses execute first; a failing wall refuses the run at exit 65 naming the course,
with the fix pointer. Only a verified project reaches the orchestrator.
v0 semantics, stated honestly in the class doc: the request runs through the
existing orchestration machinery with provider/model taken from the manifest's
first modelled agent — the scaffold ships mock, so a fresh project runs offline
with zero setup (ASHLAR_ALLOW_MOCK mirrored from the CI gates when the manifest
opts into mock). Mapping each manifest agent onto its own orchestration role is
the M1 integration; this slice makes the manifest the thing that decides HOW a
run executes. Provider comes from the manifest, not from flags.
Orchestrator is factory-injected (the ChatCommand pattern) since ServiceProvider
is private to Program.
Observed end to end in the container — including our own earlier honesty fix
firing in the wild: the mock run's fallback agent logged "GenericAgent performed
no work" exactly as the production-grade pass designed.
Sweep grows to 47 scenarios (run-refuses-unverified at 65, run-mock-completes,
run-not-a-project): 47/47. Registration guard added (6/6).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: restore the run verb's registration, guard, and sweep scenarios
The first commit of this PR carried only RunCommand.cs: a background CI watcher
did a checkout + reset --hard on the shared working tree while these three edits
sat uncommitted, and silently discarded them. The verb compiled but was never
registered — exactly the built-but-never-added failure mode the guard test
exists to catch, which is also why the guard is in this commit.
Process fix adopted alongside: background watchers now only merge PRs; branch
syncs that touch the working tree happen in the foreground, where uncommitted
work is visible before anything resets it.
Registration guard 6/6; sweep at 47 scenarios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: PlzTouchGrass <130718671+PlzTouchGrass@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
IanFrelinger added a commit that referenced this pull request Sep 1, 2026
…446)
* fix(cli): register unity-dev in root command tree + guard test
UnityDevCommand ships 9 subcommands (init/generate/iterate/list/assets/
qa/fullstack/pin/compose) and a full test suite, but was never added to
the root tree in Program.CommandRegistration.cs, so `ashlar unity-dev`
(and the `unity-dev generate` hint it prints) were unreachable from the
CLI. This is the same "built but never registered" failure as `trust`
in #162; RootCommandRegistrationTests did not cover unity-dev, so nothing
caught it.
Register the command with the same SelfExtendRunnerAdapter factory as
SelfExtendCommand, and add RootCommand_RegistersUnityDevCommand asserting
the verb and all 9 subcommands stay registered so it cannot silently drop
again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* slim: remove everything that is not natively Ashlar's responsibility
A six-area audit classified every piece of the tree against the runtime's
native responsibilities (kernel + trust surfaces; runtime/SDK/hosting;
CLI/API/MCP/transport surfaces; federation + the commercial fleet-governance
tier; distribution for the above). Everything else was a vertical riding
along, and it leaves — preserved intact on archive/verticals-2026-08-31 for
extraction to its own repositories.
Removed (712 files, -44k lines):
- the applications/ product layer: physical-atom certification, provenance
graph (as a product; kernel provenance primitives stay), six spatial/AR
projects, their tests, samples, demo tool, compose file, gate workflow,
coverage-ratchet script, and docs (8 physical-atom phase docs, spatial doc)
- the game vertical: nine commercial GameDirector/GameDomain projects,
ForgeMapHostSample, apps/game-director, apps/ashlar-forge, game data
(balance/maps), game compose + Dockerfile, engine-bridge/forge/aesthetic/
portal docs, smoke scripts, _handoff/game-layer (48 non-compiling files)
- the Unity dev vertical inside the CLI: `ashlar unity-dev`, nine handlers,
twelve constraint records, ~1,300 lines of tests, its registration, global
using, guard test, and the unity rows in the multi-platform test matrix
- orphans and residue: src/Ashlar.API stub, ValidationUtilities (zero
consumers), tag-scan sample objective (subject was the removed product),
extract-game-layer.sh (its target left), docs/planning, docs/cleanup,
docs/bricks/unknown.md, stale RESUME-PROMPT
Kept deliberately: Fleet.*/MeshDirector (Ashlar's own commercial scale tier —
native), apps/release-manager + apps/runtime-studio (scheduled extractions;
the open setup lane still seeds from them), cloud ingress adapters + SMS
middleware + terraform (generic ingress seam), Bricks.Owasp/SqlProfile
(authoring exemplars), business/GTM docs, readiness archives.
Coupled edits: Ashlar.sln (-19 projects), PrimeTime.slnf, Makefile,
ci/test-ownership.tsv, dependency-boundary/security-gate path filters,
readiness-gate-local applications layer, tier-d GameDomain block,
LICENSING.md (Tier 3 now empty in-repo; grant scope shrinks to commercial/;
not a licensing event - covenant 2 untouched), ProjectTiers, OpenCoreBoundary
(+ tracked follow-up for game/spatial vocabulary still compiled into core),
DocsIndex, README, certification-evidence. Severed a stale GameDirector.Mcp
reference in Fleet.Host in passing.
Validation: cert-gate 391/391; dependency-boundary PASS; full Ashlar.sln
builds 0 errors; Fleet.Host + FirstFlight build; Tests.BackgroundAgents
526/526; Tests.CLI 188/189 where the single failure
(UnitTestBridge/BackgroundAgentCommandTests) reproduces identically on
untouched master in the same container - pre-existing environmental flake.
[coordinated-integration]
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@IanFrelinger@cursoragent