SpecsCLIIP-318 Govern Project Settings and Scaffold State Safely
Implementation Notes
Implementation Notes
Summary
- T1 established the CLI-owned
project-settingsboundary for IP-327. - The boundary decodes current and legacy settings, reports safe upgrades, rejects missing/invalid/ambiguous state with typed errors, and never writes during reads.
- Initialization and discriminated writes enforce the three ownership classes while preserving unknown fields.
- Independent-review repairs prove legacy writes do not materialize inferred fields, Git config inference observes every repository-manager remote, and the live filesystem distinguishes only
ENOENTfrom typed access failures. - T1/T2 targeted readonly rereview is complete; the combined focused lifecycle suite passes 131/131 tests and CLI typecheck passes.
- T3 added one pure deterministic desired-state compiler for IP-328. Its public result models managed files, dependencies, structured fields/keys, projections, provenance, degradation, omission, and projection failure without receipt or runtime authority.
- T4 routes init, check, and update through that shared desired state while keeping observation and application outside the compiler. All focused, lifecycle, public/projection, type, build, format, diff, and independent-rereview gates pass.
- T5 adds pure deterministic reconciliation for managed files, dependencies, and structured keys. Update stages the plan but does not apply it or advance receipts.
- T6 applies independent safe actions with typed outcomes, refreshes dependency lockfiles through an injected package-manager process boundary, and advances only truthful receipt state in a final persistence step.
- T7 validates T1-T6 through packaged
hi/hintonly, adds no product or test surface, and leaves the canonical repository unchanged. - T8 routes the implemented lifecycle into the operator runbook and canonical CLI flow pages, closes independent review, synchronizes Linear evidence, and verifies the draft PR/stack gate. The existing pages were bounded factual patches, so the public writer fragment/beat/shape stages were intentionally skipped.
Execution Mode
- Sequential implementation workers; T1 followed by T2 followed by T3 followed by T4 followed by T5 followed by T6. T7 was validation-only. T4-T7 landed in the convergence worktree for draft PR #87; original IP-318 draft PR #80 remains a sibling source PR.
Stack and Draft PR Checkpoint
- Live GitHub evidence on 2026-07-20 shows PR #76 at
71af6c8249ea05ec46519471bc56da772f80d061, draft/open/unmerged, based onmain. - PRs #77 through #83 and #87 are draft/open/unmerged sibling children based directly on
team/stefan/refactor-cli-architecture: #77facf1ba10a1caec39e1602dfb23e28c0e1669a03; #787b14739996d5b297088f0ff102bebb6e3c8f7eb7; #795c96dfd109a60285dca77ceb8093367e23e33356; #80b302005fb55791b6225a946e54931ab6638a33cc; #81fc30dd72954f080c66c85cd892a66a964d950169; #82f24dad1893e14a856d6aef7b86e438aabad83c8b; #83b487fd83dbbf296132310be3bb1dab0041fc6c40; #87d129c9ffabd3f43f70fbefd751d14520c16d7b18. - The restored
stack statusshowed that exact fan-out. The post-restoration and post-#87-updatestack sync --dry-runpreviews reportedStack is current, so no further sync ran from those previews. - No
stack track, PR-base retarget, manual rebase/force-push, sibling linearization, merge, or deployment was used for this checkpoint. In particular, #83 is a sibling and is not assumed to contain #77 through #82. - At tracker sync, PR #87 head was
adec65792416c561857623b0978b07e8929f7c72, still open/draft/unmerged and based onteam/stefan/refactor-cli-architecture. The required post-pushstack sync --dry-runreportedStack is current.
Tracker Closeout
- IP-328 moved to Done at
2026-07-20T01:02:45Z; evidence comment0697ea59-892c-42c9-b303-17ad87a48d99. - IP-329 moved to Done at
2026-07-20T01:02:51Z; evidence comment033824f7-107c-4239-a5c6-a236bc8bd751. - IP-318 moved to Done at
2026-07-20T01:02:52Z; evidence comment39c3facb-2696-4ac8-8721-ce2beb5c0a8a.
Deviations From the Plan
- None for T1-T6. T2 moved every lifecycle settings mutation to
ProjectSettings; the synchronous helpers remain only for the separate tools command and compatibility tests, not init/ensure/check/scaffold/update. - T7's first init attempt submitted an empty required backlog URL; the CLI rejected it before writes and the same still-empty run root was reused. Direct update could not infer a missing repository manager from the isolated Git remote, so the manual flow restored the already-ensured
gitlabchoice after read-onlyhi checkhad proven the safe migration.hi ensureintentionally changed settings only; the run-owned wiki provider marker was reconciled separately before the final cleanhi check. - The default-concurrency full CLI command twice exposed an existing cross-file temp cleanup race: another worker removed a pre-existing
hi-projection-plan-*directory between snapshot and assertion. The exact assertion passed alone, and the full suite passed 446/446 with one worker. T7 did not change production or test code. - T8 changed documentation and evidence only. The writer flow was skipped because it corrected existing lifecycle pages from shipped contracts and accepted evidence rather than creating a new public guide or performing a major rewrite.
Review and Repair Ledger
- T1 review repaired inferred-field materialization during legacy writes, incomplete Git-remote inference, and filesystem reads that collapsed access failures into absence. T1/T2 readonly rereview accepted the corrected lifecycle authority with no residual HIGH/MEDIUM finding.
- T3 review REDs exposed non-canonical projection-summary ordering and dotted structured-path identity collisions. Repairs use locale-free ordering and segment-preserving identities.
- T4 review REDs exposed ambient projection-plan inheritance, run-owned temporary-plan leakage, permissive explicit-plan validation, malformed-context fallback, and false duplicate rejection for legitimate cross-kind trace reuse. Final rereview reported no residual HIGH/MEDIUM finding.
- T5's first review reported three HIGH and two MEDIUM ownership/planning defects. The next rereview reported four MEDIUM defects covering desired duplicate equivalence, symlink mode evidence, cross-family overlap, and duplicate invalid-observation determinism. The final repair added full-payload duplicate comparison, exact type-aware modes, cross-family failures, and deterministic invalid evidence; the exact gate passed 78/78.
- T6 review REDs covered mode-mismatch authorization, missing receipt-only dependency/key observation, premature receipt staging, aggregate-layer exit mutation, absent lockfile refresh, dangling-symlink hashing, and unsafe family correlation. Repairs passed the exact 97/97 gate plus the full serial regression suite.
- T7 added no implementation finding. Its only deviation was the existing default-concurrency temporary-directory race recorded above; isolated proof and the serial full suite passed.
T7 Packaged Lifecycle Acceptance
- Run root
/tmp/ip318-t7.sbgydHisolated HOME, TMPDIR, XDG cache/config/data, and global/system Git configuration. It contained no secrets and was the only manual resource removed. - Packaged bundled
hi initcreated GitHub backlog/asset/repository settings at version 2.6.2. Packagedhi ensurechanged them to Linear backlog, GitLab assets/repository, the Linear project URL, and the correspondingglabtool while preserving managed versions. - A legacy settings document without repository manager or required tools was checked read-only after the isolated repository gained a GitLab origin.
hi check --jsonreturnedSafeSettingsUpgradefor exactly those fields; settings SHA-256 stayed447fa84d…before and after. - Preview classified a user-edited managed hook and modified receipt-only
tooling.jsonkey as separateScaffoldConflictPreservedoutcomes. Write returned exit 1,applied: true,partial, andreceiptPersisted: true, preserved both user values, and completed 30 independent actions. - Receipt evidence remained truthful: hook actual hash
29aaba45…retained prior receipt hash6fd24a7c…; tooling actual valuefalseretained receipt valuetrue; updated lint actual and receipt hashes both became3ccb6881…; a safely removed Claude agent was absent from disk and receipt. - Retry returned the same two conflicts with zero successes. Manifest
904f96b4…, hook29aaba45…, and tooling7cd62f60…hashes were unchanged. After resolving only the run-owned conflicts, structured-key removal succeeded and its receipt disappeared. - Packaged
hiandhintboth reported 2.6.2. Finalhi check --jsonandhint update --check --jsonexited zero with current settings and no changes. Final settings/manifest hashes were2a7935dc…and58685ce5…. - Canonical repository fingerprints were identical before and after: HEAD
a2f65aea…, tree1a3b5680…, and empty diff SHA-256e3b0c442…. The temporary root was removed and its absence confirmed.
T6 Truthful Application and Receipts
applyScaffoldReconciliationexecutes actions in deterministic family/identity order, captures success or failure per action, and continues independent safe work.- A pure mapped-family fold combines the prior receipt with unchanged evidence and successful actions. Conflicts, planning failures, and application failures never advance ownership.
- Update observes receipt-only dependencies and structured keys, so safe stale entries are removed while user-modified values remain conflicts with retained receipt evidence.
- Dependency mutations refresh the detected Bun, pnpm, npm, or Yarn lockfile through an injected process adapter. Configured Yarn 1 and unconfigured
# yarn lockfile v1repositories use noninteractive, scripts-disabled Classic install flags; modern Yarn uses Berry's update-lockfile mode. A process failure restores the prior package manifest and every recognized lockfile snapshot, keeps the prior receipt, and leaves the action detectable so a successful retry reruns the process before advancing ownership. - The staged manifest preserves prior receipt fields, and receipt persistence runs last. The injected pre-write rejection test keeps the prior on-disk receipt intact without rolling back independent successful actions. The default direct manifest write is not atomic; a low-level write failure can require receipt inspection or repair.
- File receipt matching requires exact content, type, and type-aware mode evidence. Dangling symlinks hash through
lstat/readlink, persist a non-empty receipt hash, and retry cleanly. runUpdatereturns status as data.update-commandowns nonzero exit selection, and the shippedhi update --json --writewitness proves partial outcome serialization and exit 1.
T5 Pure Reconciliation Planning
planScaffoldReconciliationkeeps desired state, observed repository state, and prior successful receipt evidence distinct.- One deterministic policy plans per-entry file, dependency, and structured-key creates, updates, removals, unchanged skips, user-modification conflicts, stale-ownership conflicts, invalid-observation failures, and known degradation/omission evidence.
- A conflict or invalid observation suppresses only its own unsafe action; independent safe actions remain in the plan. Structured identities encode the file, entry kind, and full nested path, so unrelated keys remain outside the action.
- Review repairs make tuple identities collision-free, compare full desired action payloads before deterministic duplicate collapse, reject duplicate desired/observed/receipt/invalid-observation identities, and surface desired projection failures.
- Structured observation treats traversal through a primitive or array as invalid instead of missing. Whole-file ownership cannot overlap structured keys or package dependencies. Exact duplicate desired entries deduplicate; unequal values or metadata become scoped planning failures instead of last-write-wins replacement.
- Receipt mode evidence is type-aware: regular files require the exact numeric mode, while symlinks require exact
nullmode evidence. - The update observer reports malformed dependency and structured-config values as typed invalid entries. At the T5 checkpoint, existing manifest data adapted only successfully managed file receipts because dependency and structured-entry receipt fields had not landed yet; T6 adds and consumes those fields.
- Update computes and carries the plan without consuming its actions. T6 owns application outcomes and truthful receipt advancement.
T4 Shared Lifecycle Stages
compileScaffoldLifecycleowns the shared presentation-neutral lifecycle result. Equivalent init, check, and update inputs expose the same desired state while preserving distinct apply, compare, and reconcile stages.- Init applies desired output from missing managed state. Check observes and compares without a write-capable adapter or writable temporary directory. Update stages the same desired state before its existing reconciliation path.
- Desired output carries the compiled context plan and projection evidence explicitly. The T3 compiler still has no filesystem, environment, process, receipt, observation, conflict-policy, or application input.
- Projection subprocesses clear ambient
DP_HARNESS_PROJECTION_PLAN. When a caller supplies a plan, it is serialized into a run-owned temporary directory, passed explicitly, validated against canonical context, and cleaned on success or failure. - Explicit plans reject malformed canonical context, unknown operations, unsafe paths, mismatched action traces, inconsistent summary traces, and invalid outcome tags. They never fall back to implicit context projection.
- Projection summaries may legitimately share one trace identity across different contribution kinds. Validation matches the full trace semantics instead of treating that cross-kind reuse as a duplicate conflict.
- Future-use hook: lifecycle or projection changes should consult this section and rerun the three T4 gates below before changing compiler inputs, process environment handling, explicit-plan validation, or temporary-plan ownership.
T3 Deterministic Desired State
compileDesiredScaffoldStateaccepts onlyContextPlan, validatedProjectSettingsValue, baseline/catalog data, and explicit IP-326 capability/projection results.- The compiler canonicalizes scopes, packs, contributions, compatibility, references, required tools, files, dependencies, structured entries, capability rows, projection order, and action order. Equivalent semantic values produce one stable public result independently of input object identity and caller label.
- Baseline files, package dependency entries, structured field/key ownership, and harness actions retain explicit provenance and identities for later observation/reconciliation work.
- Degraded applied projections, known omissions, and projection failures remain distinct evidence. Duplicate managed identities return
DesiredScaffoldStateCompilationFailureinstead of silently applying last-write-wins behavior. - Review repairs canonicalize every projection summary array and its derived evidence with locale-free ordering. Structured path identities preserve segment boundaries, including dotted segments.
- Receipt, repository state, filesystem, environment, process, conflict policy, and application state are absent from the input type. T3 performs no effects or adapter calls.
- T3 does not migrate init/check/update. T4 owns lifecycle staging, and later IP-320 convergence will migrate the aggregate runtime to the shared seam.
T2 Lifecycle Authority
- Root
hi initowns first settings initialization and wiki/onboarding seed output. - Root
hi scaffoldretains repo-aware setup. It preserves user choices and unknown fields while updating only calculated tools and managed versions. hi ensureupdates user choices and reconciled tools without changing managed versions or running scaffold behavior.hi checkreads upgrade assessment through the authority and never writes settings.hi updatepreserves user choices and unknown fields, computes expected settings bytes in its temporary tree through calculated-tool and managed-version authority changes, and applies only the resulting settings fingerprint. Missing legacy settings are inferred through the authority before initialization; conflicting hints remain typed validation failures.- Current command help, generated init handoff copy, report examples, executable fixtures, CLI README, and normative repository docs use only
hi initandhi scaffold. Canonical shared skills,apps/cli/skillsmirrors, and bundled baselines were explicitly excluded by user instruction; their stale nested-route guidance remains known and was not investigated or synchronized. IP-328 desired-state/reconciliation work remains untouched.
Surprises and Decisions
- A present
requiredToolsarray can still require a safe upgrade when it omits the selected repository manager's required CLI. Upgrade assessment compares canonical derived tools, not field presence alone. - Legacy provider evidence that cannot determine a repository manager is an
AmbiguousSettingsUpgrade, not invalid or missing settings. - Writer output starts from the parsed raw document. Validated/inferred settings inform calculations and the returned read result but are not merged into another ownership class's persistence request.
- User-choice writes validate after applying their authoritative fields, so they can repair a legacy missing repository manager without guessing or materializing calculated/managed fields. Other writers still require readable, unambiguous settings before persistence.
- Git config contributes zero, one, or many repository-manager hints. Zero remains unresolved, one is a safe inference, and many return typed evidence containing repository-manager IDs such as
gitlabandbitbucket. - The live read adapter attempts the read directly.
ENOENTbecomes absent; permission and other read errors becomeProjectSettingsAccessFailure. - The public setup contract does not persist project settings or expose the raw typed projection inputs. Its T3 fixture is explicitly compiler-only evidence and supplies validated settings directly; representative partial/failure projection integration lives in
compile.test.tsthrough the public compiler seam. - Environment isolation belongs at the process boundary. Omitting an environment override is different from explicitly deleting it; the runtime helper removes keys whose supplied value is
undefinedbefore spawning the child. - Projection-plan cleanup is ownership-scoped. The caller removes only the temporary directory it created and leaves unrelated temporary entries untouched.
Sanity Checks
| Check | Result | Notes |
|---|---|---|
bun run --cwd apps/cli test -- src/features/project-settings/project-settings.test.ts src/core/tools.test.ts | Passed | 26 tests across the public feature, live adapter, and core compatibility surface |
bun run --cwd apps/cli check-types | Passed | No TypeScript errors |
bun run --cwd apps/cli build | Passed | Packaged CLI bundled 1,039 modules |
| Complete T2 lifecycle suite | Passed | 131/131 tests across 11 settings, lifecycle, generated-copy, context, and executable public-output files |
Targeted oxfmt --check | Passed | All nine owned implementation/evidence files are formatted |
Targeted oxlint | Blocked | Existing Effect plugin load fails because @typescript-eslint/utils is absent from the installed workspace graph |
autoreview --mode local | Blocked | Installed Codex CLI rejects configured gpt-5.6-sol and requires a newer CLI |
| Targeted readonly rereview | Passed | Corrected topology and completed T1/T2 evidence accepted |
| T3 exact RED | Passed | Review regressions failed 2/26 for summary ordering and dotted path-segment identity; public context stayed green |
| T3 exact GREEN | Passed | Compiler plus public context passed 26/26 tests; CLI tsc --noEmit passed |
| T3 focused format/diff | Passed | Oxfmt and git diff --check passed all nine owned implementation, fixture, and evidence files |
| T4 focused suite | Passed | bun run --cwd apps/cli test -- src/runtime/scripts.test.ts src/scaffold/output.test.ts src/data/scripts/sync-subagents.test.ts passed 48/48 across 3 files |
| T4 exact lifecycle suite | Passed | bun run --cwd apps/cli test -- src/features/scaffold-state/lifecycle.test.ts src/scaffold/stage.test.ts src/scaffold/run.test.ts src/cli/check-command.test.ts src/update/run.test.ts passed 121/121 across 5 files |
| T4 public/projection suite | Passed | bun run --cwd apps/cli test -- src/cli/check-command.test.ts src/features/scaffold-state/lifecycle.test.ts src/features/scaffold-state/compile.test.ts src/features/scaffold-state/desired-output.test.ts src/scaffold/output.test.ts src/context/public-context-contract.test.ts src/cli/public-output-contract.test.ts src/data/scripts/harness-projection/contract.test.ts src/data/scripts/sync-subagents.test.ts passed 122/122 across 9 files |
| T4 static and build gates | Passed | bun run --cwd apps/cli check-types, bun run --cwd apps/cli build, focused Oxfmt across all 29 implementation files, and git diff --check passed |
| T4 independent rereview | Passed | PASS — no residual HIGH/MEDIUM findings. Reviewer validation passed 66 tests across 4 files; the readonly reviewer made no edits |
| T5 exact reconciliation suite | Passed | bun run --cwd apps/cli test -- src/features/scaffold-state/reconcile.test.ts src/update/run.test.ts passed 78/78 across 2 files |
| T5 regression suites | Passed | T1/T2 passed 65/65, T3 passed 94/94, T4 passed 48/48, lifecycle passed 59/59, and packages/scaffold passed 47/47 |
| T5 static and build gates | Passed | CLI and packages/scaffold typechecks passed; CLI build bundled 1,056 modules |
| T6 exact application suite | Passed | Exact four-file gate passes 97/97; full CLI passes 446/446 serially; CLI build bundles 1,058 modules and CLI typecheck passes |
| T7 packaged lifecycle | Passed | Isolated packaged init/ensure/check/update preview/apply/retry/final-clean flow proved settings ownership, two scoped conflicts, 30 independent successes, truthful receipt retention/removal, both aliases, unchanged canonical fingerprints, and owned cleanup |
| T7 exact gates | Passed | Build bundled 1,058 modules; CLI typecheck, root behavior 4/4, and diff check passed; full CLI passed 446/446 with one worker after the existing concurrency-sensitive temp assertion passed 1/1 alone |
| T8 wiki-safe gate | Passed | Wiki tests passed 6/6; direct fumadocs-mdx, next typegen, and native TypeScript checks passed without running the mutating sync wrapper |
| T8 CLI/root gate | Passed | Full CLI passed 446/446 with one worker; build bundled 1,058 modules; CLI typecheck and root behavior 4/4 passed |
| T8 sync preflight | Blocked | node apps/wiki/scripts/sync-content.mjs --check reported existing project-owned routing drift that would delete canonical routed specs and update projected docs. The command made no writes; the mutating wrapper was intentionally skipped |
| T8 focused format/diff | Passed | Exact targeted Oxfmt and scoped git diff --check passed all 13 T8 paths |
| T8 first autoreview | Repaired | Accepted three P2 findings: replace the mutating recorded wiki command, remove stale nested/alias command names, and qualify non-atomic receipt-write durability |
| T8 second autoreview | Repaired | Accepted the stale manifest pack-drift invariant and routed runbook findings. The manifest now matches automatic detected-pack adoption, and explicit ownership expansion allowed the routed runbook to be refreshed from its source without applying unrelated sync drift |
| T8 third autoreview | Repaired | Accepted the remaining finalPacks wording contradiction. The manifest now treats recorded packs as prior accepted evidence augmented by current default/detected resolution in write mode |
| T8 fourth autoreview | Repaired | Accepted the stale baseline environment variable and overbroad recorded-extra-pack classification. Source and routed runbooks now use DP_BASELINE and retain recorded extras as prior accepted selections pending explicit removal |
| T8 parent independent review | Repaired | Accepted two MEDIUM stale environment-name findings. Source and routed runbooks now document DP_SCRIPT_RUNTIME; root docs use the shipped HI_NO_UPDATE_CHECK, HI_NO_SKILL_UPDATE_CHECK, HI_UPDATE_TAG, and HI_STARTUP_CHECK_INTERVAL_MS startup variables |
Acceptance Criteria Status
| Criterion | Status | Notes |
|---|---|---|
| Shared validated settings component | Met for T1 | Public Effect service owns decode, assessment, initialization, and ownership-scoped mutation |
| Safe legacy upgrades preserve user choices | Met for T1 | Tests cover safe conversion, ambiguous evidence, no read writes, and unknown-field preservation |
| Three authoritative field classes | Met for T1 | Raw-document regressions prove each writer changes only its class and user choices can repair their own ambiguity |
| Every lifecycle command uses the boundary | Met for T2 | Init, ensure, check, scaffold, and update use explicit authority reads/initialization/ownership-scoped writes |
| One deterministic desired state | Met for T3 | Pure public compiler owns canonical desired state and explicit IP-326 capability/projection evidence |
| IP-328 lifecycle integration | Met for T4 | Init/check/update share desired state through distinct apply/compare/reconcile stages |
| IP-329 reconciliation | Met for T6 | Scoped planning, mixed application outcomes, lockfile refresh, and truthful final receipt advancement are complete |
Manual Review Checklist
| Area | Check | How to perform | Expected result |
|---|---|---|---|
| Project settings boundary | Re-run focused public contracts | bun run --cwd apps/cli test -- src/features/project-settings/project-settings.test.ts src/core/tools.test.ts | Current/missing/invalid/safe/ambiguous reads, legacy writer permissions, multi-remote inference, adapter failures, preservation, and real temp cleanup all pass |
| Static contract | Re-run CLI type validation | bun run --cwd apps/cli check-types | Command exits zero |
| Desired state | Re-run T3 public contracts | bun run --cwd apps/cli test -- src/features/scaffold-state/compile.test.ts src/context/public-context-contract.test.ts | Identical semantic inputs compile identically; projection degradation/omission remains explicit; public fixture stays exact |
| Lifecycle staging | Re-run focused T4 contracts | Run the focused, exact lifecycle, and public/projection commands in the T4 sanity-check rows above | Init/check/update share desired state; check stays effect-free; projection plan validation and owned cleanup remain strict |
| Truthful application | Re-run exact T6 contract | bun run --cwd apps/cli test -- src/features/scaffold-state/apply.test.ts src/update/run.test.ts src/cli/check-command.test.ts src/cli/public-output-contract.test.ts | Mixed outcomes remain typed, independent actions continue, and only successful receipt state persists |
Remaining Work
- None inside IP-318. T1-T8, independent review, Linear reconciliation, draft-PR verification, and the stack dry-run gate are complete. The wiki sync preflight blocker is pre-existing routing drift; direct wiki tests and type generation pass without mutating canonical content.