[codex] Standalone OpenSpec repos: store root parity and --store root selection#1190
[codex] Standalone OpenSpec repos: store root parity and --store root selection#1190TabishB wants to merge 108 commits into
Conversation
|
Important Review skippedToo many files! This PR contains 218 files, which is 68 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (218)
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughOpenSpec commands now resolve an explicit root context from ChangesOpenSpec root and context-store foundation
CLI command root-awareness and workflow changes
Documentation and roadmap reframing
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI commands
participant Root as resolveRootForCommand
participant Store as context-store registry
participant OS as inspectOpenSpecRoot
participant Ops as context-store operations
CLI->>Root: resolve with store / nearest / implicit inputs
Root->>Store: read registry and identity metadata
Root->>OS: inspect candidate OpenSpec root
Root->>CLI: return ResolvedOpenSpecRoot / diagnostic payload
Ops->>OS: ensureOpenSpecRoot / inspectOpenSpecRoot
Ops->>Store: commit registration state
Ops->>Ops: rollbackCreatedPaths on failure
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
Implements the store-root-selection slice (1.2, with 2.1 pulled forward): - Add a shared OpenSpec-root resolver (src/core/root-selection.ts) behind new change, status, instructions, list, show, validate, and archive. --store <id> resolves a registered context store to an ordinary OpenSpec root; identity and root-health failures point to context-store doctor. - Leftover workspace view state never wins root resolution for these commands, and a no-root directory with registered stores errors with a store-selection hint instead of scaffolding an implicit root. - Selected-store runs print "Using OpenSpec root: <id> (<abs path>)" to stderr and JSON successes carry an additive shared root block. - --store-path is rejected deliberately with context-store register guidance, including on show despite allowUnknownOption. - new change is root selection only: initiative-link creation is removed, --initiative and --areas reject before any writes, --goal stays ordinary metadata. openspec set change is removed along with initiative-link.ts. - archive gains --json: non-interactive, machine-readable diagnostics for blocked paths, and no prose or blank lines on stdout. - list gains minimal --specs --json support so specs listing participates in the root reporting contract. - context-store setup/register next steps show --store usage.
- archive --json: silence the REMOVED-deltas-on-new-spec warning from buildUpdatedSpec so the JSON payload stays pure. - Resolver: wrap registry reads so a corrupt registry surfaces as a RootSelectionError; JSON mode now emits a machine-readable diagnostic instead of a blank stdout line. - archive --store (human): per-spec update lines use the absolute store path, matching the cross-root absolute-paths contract. - Noun-form spec show keeps its forward-slash relative not-found message on all platforms; root-aware show reports the absolute path. - Tests: archive --json purity for REMOVED-delta and spec-update-failure paths, corrupt-registry JSON diagnostics, and running inside the standalone store repo without --store.
The archive spec-update phase validated and wrote each rebuilt spec in a single loop, so a later validation failure could leave earlier specs already modified while reporting "No files were changed". Split it into two passes: validate every rebuilt spec first, then write only after all pass. Regression test covers a two-spec change where one rebuilt spec fails validation and asserts no target spec was created or modified.
Rewrites the opening sections of the old initiative and workspace reimplementation artifacts as transition evidence and beta history, and adds the direction-git-native-work transition note. Readers are pointed to openspec/work/simplify-context-and-workspace-model/ for the active direction.
Adds the slice 1.2 spec, plan, and decision-review evidence, and updates the roadmap: 1.2 is implemented and tested on this branch, with review follow-up and merge remaining.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@openspec/work/simplify-context-and-workspace-model/slice-1.2-decision-review.html`:
- Around line 341-353: The clickable DIVs used for cards and options
(onclick="setActive('${d.id}')" on the card and
onclick="event.stopPropagation(); choose('${d.id}','${k}')" on the opt) are not
keyboard-accessible; change those interactive elements to semantic <button>
elements (or, if you must keep DIVs, add role="button", tabindex="0" and keydown
handlers that call setActive and choose on Enter/Space and stopPropagation where
needed) and ensure focus styles and ARIA labels are preserved; update the card
container (currently class "card") and option elements (class "opt") to use the
new buttons or key handlers so keyboard users can activate setActive and choose
reliably.
In `@src/commands/workflow/instructions.ts`:
- Around line 64-70: The spinner started with "const spinner = options.json ?
undefined : ora('Generating instructions...').start();" is not stopped on
early-return after calling resolveRootForCommand; update the early-return paths
in the instructions command (where resolveRootForCommand(...) returns falsy) to
call spinner?.stop() before returning, and apply the same change to the other
early-return block later in the same file (the second unresolved-root return
around lines 383-389) so the ora spinner is always stopped in interactive mode.
In `@src/commands/workflow/status.ts`:
- Line 46: The spinner started with const spinner = options.json ? undefined :
ora('Loading change status...').start() may be left running on early returns for
unresolved root; before every return in the unresolved-root branches (and any
other early return paths in this function), stop the spinner by calling
spinner?.stop() (or spinner && spinner.stop()) so non-JSON flows don't leave a
stale spinner; update the unresolved-root return sites to call spinner?.stop()
immediately before returning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e3f0630-136b-4989-82d5-399ef1f67035
📒 Files selected for processing (61)
docs/cli.mdopenspec/changes/workspace-agent-guidance/proposal.mdopenspec/changes/workspace-apply-repo-slice/proposal.mdopenspec/changes/workspace-reimplementation-roadmap/HISTORICAL_DIRECTION.mdopenspec/changes/workspace-reimplementation-roadmap/POC_REFERENCE_GUIDE.mdopenspec/changes/workspace-reimplementation-roadmap/README.mdopenspec/changes/workspace-reimplementation-roadmap/START_HERE.mdopenspec/changes/workspace-reimplementation-roadmap/proposal.mdopenspec/changes/workspace-verify-and-archive/proposal.mdopenspec/initiatives/context-store-and-initiatives/README.mdopenspec/initiatives/context-store-and-initiatives/decisions.mdopenspec/initiatives/context-store-and-initiatives/direction-git-native-work.mdopenspec/initiatives/context-store-and-initiatives/direction.mdopenspec/initiatives/context-store-and-initiatives/roadmap.mdopenspec/initiatives/context-store-and-initiatives/tasks.mdopenspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/evidence.mdopenspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/plan.mdopenspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/tasks.mdopenspec/work/AGENTS.mdopenspec/work/README.mdopenspec/work/simplify-context-and-workspace-model/goal.mdopenspec/work/simplify-context-and-workspace-model/roadmap.mdopenspec/work/simplify-context-and-workspace-model/slice-1.2-decision-review.htmlopenspec/work/simplify-context-and-workspace-model/slices/store-root-parity/plan.mdopenspec/work/simplify-context-and-workspace-model/slices/store-root-parity/spec.mdopenspec/work/simplify-context-and-workspace-model/slices/store-root-parity/user-facing-review.htmlopenspec/work/simplify-context-and-workspace-model/slices/store-root-selection/plan.mdopenspec/work/simplify-context-and-workspace-model/slices/store-root-selection/spec.mdsrc/cli/index.tssrc/commands/change.tssrc/commands/context-store.tssrc/commands/show.tssrc/commands/spec.tssrc/commands/validate.tssrc/commands/workflow/index.tssrc/commands/workflow/initiative-link.tssrc/commands/workflow/instructions.tssrc/commands/workflow/new-change.tssrc/commands/workflow/set-change.tssrc/commands/workflow/shared.tssrc/commands/workflow/status.tssrc/core/archive.tssrc/core/completions/command-registry.tssrc/core/completions/shared-flags.tssrc/core/context-store/errors.tssrc/core/context-store/operations.tssrc/core/context-store/registry.tssrc/core/index.tssrc/core/list.tssrc/core/openspec-root.tssrc/core/root-selection.tssrc/core/specs-apply.tstest/commands/artifact-workflow.test.tstest/commands/change-initiative-link.test.tstest/commands/context-store.test.tstest/commands/store-root-selection.test.tstest/core/archive.test.tstest/core/completions/command-registry.test.tstest/core/context-store/registry.test.tstest/core/openspec-root.test.tstest/core/root-selection.test.ts
💤 Files with no reviewable changes (3)
- src/commands/workflow/set-change.ts
- src/commands/workflow/initiative-link.ts
- src/commands/workflow/index.ts
| <div class="card ${state.active===d.id?'active':''}" onclick="setActive('${d.id}')"> | ||
| <div class="card-top"> | ||
| <span class="dot ${chosen?'decided':''}"></span> | ||
| <span class="card-title">${d.num}. ${esc(d.title)}</span> | ||
| </div> | ||
| <div class="card-stake">${esc(d.stake)}</div> | ||
| <div class="opts"> | ||
| ${['a','b'].map(k => ` | ||
| <div class="opt ${chosen===k?'selected':''}" onclick="event.stopPropagation(); choose('${d.id}','${k}')"> | ||
| <span class="radio"></span> | ||
| <span>${esc(d.options[k].label)}</span> | ||
| ${d.rec===k?'<span class="rec-badge">rec</span>':''} | ||
| </div>`).join('')} |
There was a problem hiding this comment.
Make interactive choices keyboard-accessible.
Line 349 and Line 372 use click-only div controls, so keyboard users cannot reliably choose options/panes. Please switch to semantic buttons (or add role="button", tabindex="0", and Enter/Space key handlers) for all actionable items.
Suggested minimal patch
- <div class="opt ${chosen===k?'selected':''}" onclick="event.stopPropagation(); choose('${d.id}','${k}')">
+ <div
+ class="opt ${chosen===k?'selected':''}"
+ role="button"
+ tabindex="0"
+ onclick="event.stopPropagation(); choose('${d.id}','${k}')"
+ onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();event.stopPropagation();choose('${d.id}','${k}');}"
+ >
...
- <div class="pane ${chosen===k?'selected':''}" onclick="choose('${d.id}','${k}')">
+ <div
+ class="pane ${chosen===k?'selected':''}"
+ role="button"
+ tabindex="0"
+ onclick="choose('${d.id}','${k}')"
+ onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();choose('${d.id}','${k}');}"
+ >Also applies to: 372-374
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@openspec/work/simplify-context-and-workspace-model/slice-1.2-decision-review.html`
around lines 341 - 353, The clickable DIVs used for cards and options
(onclick="setActive('${d.id}')" on the card and
onclick="event.stopPropagation(); choose('${d.id}','${k}')" on the opt) are not
keyboard-accessible; change those interactive elements to semantic <button>
elements (or, if you must keep DIVs, add role="button", tabindex="0" and keydown
handlers that call setActive and choose on Enter/Space and stopPropagation where
needed) and ensure focus styles and ARIA labels are preserved; update the card
container (currently class "card") and option elements (class "opt") to use the
new buttons or key handlers so keyboard users can activate setActive and choose
reliably.
Spec and plan for slice 1.3 (prove the standalone repo lifecycle end to end), with two review rounds folded in. Adds slice 1.4 to the roadmap, parks archive browsability as L11, and records the single-branch workflow for the whole roadmap.
Implements slice 1.3 (store-lifecycle-proof): - Setup defaults to Git with a pathspec-limited initial commit of exactly the files it created, writes store.yaml before committing, anchors empty directories with .gitkeep, preflights commit identity via git var before creating anything, and requires an explicit --path (interactive setup prompts with a visible user path). - Doctor reports read-only Git facts (commits, uncommitted changes, remote) and warns on commitless repos and clone-fragile directories. - Register errors are terminal: one-checkout-per-id with the unregister escape, registration-aware id-mismatch fix text, and an empty-clone explanation on unhealthy roots. - Selected-store hints carry --store, the root banner prints at resolution time so post-resolution failures keep it, new change names its next command, and status drops the workspace-era Planning home line. - Adds the two-checkout journey e2e test (machine A lifecycle, machine B clone/register/continue) with fully isolated Git config and XDG state.
Two adversarial subagent reviews of the slice 1.3 implementation found one spec violation and several correctness risks; all are fixed: - Hints carry --store everywhere: validate/show non-interactive hints, archive blocked-path fix texts, and status JSON nextSteps now thread the selected store. Status JSON also drops the workspace-era planningHome field. - Reruns of an already-registered store no longer git-init it (the CLI default is resolved against the registry via resolveSetupGitEnabled), keeping reruns strict no-ops. - Failed initial commits unstage setup's files so a user repo is not left with a dirty index; once the commit lands, cleanup no longer deletes the committed files; fresh-dir cleanup is non-recursive again so it can never delete content setup did not create. - Corrupt or fake .git dirs report Git facts as unknown instead of commitless, avoiding misleading empty-clone advice. - The sharing next-step line only prints for actual repositories. - Journey test: Windows-safe path assertions, telemetry opt-out, machine B now runs the full enumerated command set (instructions, validate), asserts register creates no commits, covers the banner-on-failure and store-carrying-hint contract, and doctor human output. Unit tests gain isolated git config, register error-text coverage for both mismatch branches, and a default-flags rerun no-op regression test. Full suite: 93 files, 1729 tests, green.
Follow-up review findings: the invalid-report next step pointed at the deprecated cwd-based 'openspec change show <id>' and dropped --store; it now names the supported top-level 'openspec show <id> --json --deltas-only' with the actual change id and the store flag. The nothing-to-show fallback hints and the ambiguous-item advice in validate and show no longer suggest noun-form commands when a store is selected, since those commands cannot reach a store root; store mode gets --type-scoped top-level equivalents instead. No-store output is unchanged.
Code-quality review follow-up: - The initial commit was built from the rollback ledger, which is the wrong concept: for a converted (existing, non-Git) root it committed only the new anchors and identity file, leaving config and specs uncommitted and clones unhealthy. When setup initializes the repo itself, it now commits the full store shape (openspec/ plus .openspec-store/), while pre-existing repos keep the only-what-setup-created commit that protects user history and staged files. Old beta files outside the store shape are never swept in. - Identity-file creation is now owned solely by setup; registration runs with writeMetadataIfMissing: false and verifies instead of writing, removing the split ownership that made the commit plan leaky. - Git probing, init, identity preflight, and commit mechanics moved from operations.ts (1204 lines) into src/core/context-store/git.ts; operations.ts is back to 1077 lines and owns only the lifecycles. - Git lifecycle tests split into test/commands/context-store-git.test.ts with shared fixtures in test/helpers/context-store-git.ts, including a new conversion test that proves a clone of a converted root is immediately healthy. Spec and plan updated to lock the two commit modes. Full suite: 94 files, 1730 tests, green.
Fresh-eyes review outcome, settled in discussion: the layered PM/architect-to-dev use case (high-level requirements in a standalone store, implementation work in the app repo's own OpenSpec root) replaced the rejected project-to-store binding idea with declared relationships between roots and a fixed resolution precedence — explicit --store, then nearest local root, then a declared default only when no local root exists, then error with hint. References never change where commands act. - Slice 1.4 becomes one guidance pass (absorbs old 2.2; ~13 surfaces from research) gated on the context-store terminology decision promoted from L7. - Phase 2 is fully absorbed: 2.1 shipped in 1.2, 2.2 into 1.4, 2.3 into 4.1 (initiative selection is hardcoded into ~5,500 lines of opening machinery that 4.1 rebuilds; refactoring first is wasted motion). - Phase 3 rewritten around relationships in both directions, references first: repo-references-stores, declared-store fallback, canonical remote in store identity, then store-level target declarations, local repo map, and relationship health reporting. - Phase 4 reframed as context assembly; editor opening is one consumer, an agent session brief is another. - New guardrails: references are repo-level config, never per-change lifecycle links; one change lives in one root. - goal.md gains the layered reference experience.
Decisions settled after parallel product-level and staff-engineer analyses: - Naming: the noun is 'store', defined as 'a standalone OpenSpec repo you've registered'. The context-store → store group rename plus the full machine-token rename (diagnostic codes, JSON keys, data dir) land first in slice 1.4; --store stays; committed store-repo formats are already aligned and stay. openspec repo/--repo rejected: the --repo prior means the code repo being operated on, colliding with target project repos. - Phase 3: index-not-inline reference injection; references: and the fallback store: pointer both live in openspec/config.yaml (top-level marker rejected — .openspec.yaml is taken by change metadata); one typed id namespace with the kebab grammar locked for all id kinds; relationships are location, declaration, or citation — never managed per-artifact links, which is what initiative links were. - Phase 5 criteria agreed: delete rather than hide, sequenced across 1.4, a small command-group deletion slice, and 4.1; never auto-delete user data.
No pause gates: unlocked decisions are made autonomously and recorded as 'Decided autonomously (review me)' changelog lines; Phase 5 deletions proceed without confirmation. Review phases run as parallel multi-agent Workflows plus the /code-review skill (high effort) and codex CLI; /simplify runs serially after correctness fixes.
Serial across slices (single branch, shared junction files, mass rename/deletion commits make cross-track rebases the riskiest unattended operation); Workflow fan-outs within slices for mechanical sweeps; read-only lookahead research for the next slice's code map.
The docs position /loop as interval-based and /goal as the condition-based counterpart: turns fire back-to-back until a verifiable completion condition is met, with full main-loop tool and skill access per turn and persistence across resume. That matches the queue's semantics (next unit when the previous finishes, stop when done), so loop.md becomes runbook.md, reframed around goal-driven turns with an explicit per-turn status block for the goal evaluator and a declared completion signal.
The goal condition previously checked activity (boxes ticked, suite green); it now checks the product claim. Phase 6 / capstone 6.1: four persona journeys including a cold-start agent dogfood, usability audits (error catalog, vocabulary sweep, time-to-first-success), technical audits (single-resolver invariant, dependency direction, dead code, module sizes, agent-contract inventory, net LOC delta vs origin/main), a whole-delta review gauntlet, and a committed release-readiness report. Runbook gains standing per-slice quality bars: locked vocabulary only, pasteable store-carrying errors, consistent agent contracts, ~600-line module budget, no speculative abstractions, one resolver.
Two parallel adversarial reviews (subagent, codex CLI) converged on the same flaw in the first draft: exempting the legacy groups from the token rename contradicted the locked machine-token decision. The spec now states one rule - total mechanical token rename, surgical prose rewrite, behavior changes limited to the two riders - and folds the corrected 45-code token inventory, the missed guidance surfaces, and a sweep-as-test acceptance criterion.
alfred-openspec
left a comment
There was a problem hiding this comment.
Reviewed root parity and the --store command paths. I do not see a blocking runtime issue: root shape checks, setup/register idempotency, rollback of created OpenSpec artifacts and metadata, existing-store compatibility, CI, and the full local test suite all look good.
Only cleanup ask before merge: I would drop or convert the generated HTML review artifacts unless we intentionally want static review prototypes under openspec/work; if they stay, please fix the accessibility nits CodeRabbit flagged.
Four green checkpoints: mechanical rename, the two riders, guidance regeneration (three disjoint streams), and sweep/guards/dogfood. Both parallel reviews (subagent, codex CLI) approved with fixes, all folded: exact rider-1 deletion list with persisted path-bound views preserved, Commander command:* error ownership, docs/concepts.md and beta-doc runtime fixes, sweep roots excluding openspec/ history, old-data-dir negative fixtures, pinned non-interactive dogfood init flags.
The working set a root's declarations describe, in one command: the JSON agent brief (root + members with roles, absolute paths, fetch recipes on available stores, and the existing fixes verbatim on unavailable members), the human listing with the Not-available section, and the --code-workspace editor view (available members only; ref:/repo: folder prefixes; the pinned write matrix - typed context_file_exists refusal, --force, no implicit mkdir, stderr confirmation under --json; stale mapped paths excluded - reported, not guessed). Assembly is presentation over the 3.6 composition through the new shared command gather (doctor refactored onto it, behavior-identical); fetchRecipe exported as the one recipe source. STORE_SELECTION_GUIDANCE gains context with the parity hashes and completions pins updated deliberately; docs add the section and the project-context vs working-context disambiguation. Full suite green (95 files, 1711 tests).
Three review mechanisms converged; all fixed with regression tests:
the --json + --code-workspace failure path now leaves exactly one JSON
document on stdout (the write runs before the brief is printed; both
failure modes pinned); context mirrors doctor's self-reference honesty
('Declared references all resolve to this root' instead of the false
'nothing declared'); the registry degradation is selected by
diagnostic code, never by array position (the fragile health.status[0]
coupling and the redundant boolean+diagnostic pair are gone); the
write summary names the skipped member ids instead of pointing JSON
users at a listing that is not there, with the count arithmetic in
plain form; the dead planningHome params on
buildNextSteps/buildActionContext inputs and their loader threading
are removed; the leftover binding imports in registry.test.ts and
three pieces of edit debris are swept; the ledger's Surviving-tokens
section is pruned; the doctor docs section cross-links context; and
the spec's working-set/builder unit-test bullet is fulfilled
(test/core/working-set.test.ts - the mapping table, ordering,
availability rule, by-code selection, and builder shape).
Skipped with reasoning: suppressing the resolver's both-shapes stderr
warning for context runs (codex P3) - that warning is 3.2 family
behavior for every command at resolution time; forking it per command
would fragment the one-resolver contract. Recorded for the capstone.
Full suite green (96 files, 1715 tests).
Simplify: the stale-path stat sweep moves into shared-gather as missingDeclaredRepoPaths (doctor and context both consume it; the header comment now tells the truth); the dead Windows-path machinery in planning-home dies with the stale workspace-kind test that was its only exerciser (formatChangeLocation collapses to path.relative); the garbled vocabulary-sweep comment is repaired; doctor's dead fs import removed; the context_output_dir_missing code recorded as a plan amendment instead of silent drift. Skipped with reasoning: the printEntryDiagnostics extraction (net-zero lines, couples two surfaces' voices); the three filter passes (readability beats a one-pass accumulator at single-digit N); PlanningHomeSummary identity (recorded for the capstone). Roadmap: 4.1 boxes ticked, Phase 4 complete on the branch, pointer moved to the Phase 5 remainder. Full suite green (96 files, 1714 tests).
Per the locked delete-don't-hide criteria, after 4.1 as queued (decision record: slices/delete-legacy-command-groups/remainder.md): schemas/workspace-planning/ deleted (openspec schemas still advertised the dead workflow); the four workspace-* beta change folders deleted (unimplemented relics - archiving would assert completion; git preserves); L2 decided - the four wholly-workspace accepted specs deleted (capability gone = spec gone) and the workspace requirements excised from cli-config and cli-artifact-workflow (two requirements, eight scenarios - bounded short of the docs rewrite the roadmap forbids). Incidental mentions in five other specs recorded for the capstone vocabulary audit. All 36 remaining accepted specs validate; full suite green untouched (96 files, 1714 tests).
Journeys 2 and 3 land as standing e2e in test/cli-e2e/capstone-journeys.test.ts - the layered PM-to-dev flow (an app-repo agent discovers the reference from config via openspec context, cites the upstream spec by following the fetch recipe verbatim, and writes its design change in the app repo's own root while the store stays read-only) and externalized planning (a code repo with only a store: pointer runs new-change through archive with zero --store flags and never grows planning state). Journey 1 is the standing store-lifecycle e2e. Journey 4 ran as a live cold-start headless dogfood: a fresh codex session given only a vague prompt and --help output assembled the full intended topology - store setup, targets declaration, pointer config, repo mapping, and doctor/context/validate self-verification. Results recorded in capstone/journeys.md. Full suite green (97 files, 1716 tests).
Error-catalog walk: 55 wrong turns exercised live across 13 families (human + JSON) against the actionable/store-carrying/correct-exit/ honest bar - 46 pass. The resolution-layer taxonomy held (differentiated no-root hints, single-document JSON failures, shell-parseable clone fixes, bidirectional namespace collisions). Nine failures recorded and queued for the capstone fix round: 1 P1 (raw YAML stack trace on unparseable real-root configs), 4 P2 (pathless corrupt-registry fix that dead-ends through store doctor, instructions dropping its Fix line, validate summaries without drill-down, implicit scaffolding creating doctor-unhealthy roots), 4 P3. Vocabulary sweep incl. docs/cli.md: clean except the legacy ChangeStatus.initiative JSON passthrough (queued; the schema keeps parsing user data). Time-to-first-success measured live: 2 commands, 2 concepts, each step printing the next command.
All nine error-catalog failures plus the vocabulary finding, with the test pins updated deliberately: P1 - unparseable real-root configs no longer dump a YAMLParseError stack trace: readProjectConfig warns with one line naming the file and the first error line only (pinned: single line, no node_modules). P2 - the corrupt-registry fix names the actual registry file path; the CLI's shared error wrapper (17 catch sites) now prints the diagnostic fix line it used to drop, so instructions and every sibling carry the pasteable next step; validate failure summaries print a drill-down command carrying --store (derived from the resolved root); implicit scaffolding (new change in a bare dir, non-interactive init) now creates the complete healthy shape - specs/, changes/archive/, and a minimal config.yaml - so doctor calls the result ok instead of unhealthy. P3 - the malformed-pointer warning on real roots names the file; the declared-pointer unknown-store fix is reshaped for the actual mistake (register the store or edit the named config - the user never passed --store); the store-register-at-code-repo fix offers repo register; archive not-found lists available changes like its status sibling. Vocabulary - the legacy ChangeStatus.initiative passthrough is gone from every surface (status JSON/human, instructions XML, apply text); the metadata schema still PARSES stored links (user-data tolerance, pinned by the flipped legacy tests: tolerated, not re-emitted). Full suite green (97 files, 1716 tests).
Single-resolver invariant HOLDS: one precedence implementation, nine command entry points through it, doctor/init extra walks verified as post-resolution diagnostics and scaffold guards; one latent unreachable fallback queued for deletion. Dependency direction HOLDS: zero core->commands/cli imports. Dead-code sweep over the 213-file delta: no P2s, five P3s queued, four notes recorded (incl. the ext:: transport status: zero occurrences, the shell-safe gate and team-committed trust boundary hold). Module sizes bounded (largest 1,160 lines). docs/agent-contract.md committed - every JSON shape, the diagnostic envelope, failure payloads, the exit-code contract, and the full diagnostic-code catalog verified against emitting code, with 14 consistency findings; the gauntlet-grade one (several --json failure paths emit no JSON document) is queued for the gauntlet fix round. Net LOC vs origin/main: src -4,478, test -325 - net-negative as the roadmap expected.
Four mechanisms over origin/main...HEAD: /code-review at max effort (all 12 verified candidates CONFIRMED, most live-reproduced), a 32-agent adversarial Workflow (six lenses, refute-style verification: 25 confirmed + 7 completeness gaps), a codex whole-delta review (FIX-FIRST), and the audits' queued items. Consolidated: 2 P1 (the ~/openspec layout turning $HOME into a phantom nearest root that captures every lifecycle command under the home tree; status/ instructions --json errors emitting no JSON document), 13 P2 (the JSON-failure-contract family, the --store-path seam, doctor's up-walking origin probe, the stale registry lock, config-only half-scaffolds, prompt-injection via verbatim hostile strings, five more accepted specs requiring deleted behavior, stale planningHome guidance in generated skills, a syntactically-broken zsh completion script, store-remove delete-before-commit, the setup TOCTOU pair, the orphaned-.git empty-clone path, the metadata rollback race), and a triaged P3 set split into queued-cheap vs recorded-for-report. The gauntlet box ticks only when every P1/P2 is fixed and re-verified.
P1: the nearest-root walk now skips openspec/ directories that are neither planning-shaped nor configured - the recommended ~/openspec store layout no longer turns $HOME into a phantom root that captures every command under the home tree (regression test: the registered-store hint fires instead). status/instructions/list/show/ validate --json failures all emit exactly one JSON status document (JSON-aware shared failure helper; the stray blank stdout lines are gone; store <unknown subcommand> --json emits a typed document; list carries its null-shape). P2: doctor/context gain the --store-path rejection seam; doctor's origin probe is guarded by isGitRepositoryAtRoot (no more enclosing- repo origins or spurious divergence notes); the registry lock steals orphans older than 30s, names the lock path in the busy fix, and reports permission problems as what they are; change scaffolding completes the root shape for config-only roots and records the project default schema, never a one-change --schema override; hostile-content renders are sanitized at the index/render boundary (spec ids, summaries at index time, remotes in targets and divergence messages - control characters can no longer forge instruction lines); the five remaining workspace-requiring accepted specs got the bounded excision (all 36 validate); status JSON carries planningHome again (the generated skills' published archive contract - restored rather than rewriting eleven template references); the zsh completion generator uses the correct quote idiom (generated script now passes zsh -n); store remove commits the registry removal BEFORE deleting files (a failed deletion degrades to a store_files_left_on_disk warning, never a phantom registration); setup re-asserts directory facts at execute (store_setup_path_changed) killing the stale-kind recursive-rm TOCTOU; the half-made .git cleanup no longer hides behind the created-paths ledger (no more commitless-store reruns); the metadata rollback re-reads the registry and never deletes metadata a committed registration depends on. P3 (cheap set): CommonMark-correct fence tracking in purpose extraction; the stale-target sweep requires a DIRECTORY; pretty JSON for empty list; the declared-pointer repo-id fix names the config file; absolute change location when the root is not the cwd; docs fixes (affected_areas legacy wording, --remote in the setup table, vibe in --tools, the real list output example); agent-contract.md updated to match (planningHome restored, the failure-contract claim now true). Test pins updated deliberately: the store --json hint, the remove ordering contract, the zsh escaper, the truncation corpus (summaries now cap at index time, so the budget trips on count). Full suite green (97 files, 1717 tests).
…d (6.1) All 15 gauntlet P1/P2 fixes re-verified live (the JSON-contract codes on show/validate/status/instructions/store, the --store-path seam on doctor, the stale-lock steal, the config-only scaffold completion, the phantom-root regression). The gauntlet ledger marks every finding fixed. The release-readiness report lands with the five-minute new-user story (2 commands, 2 concepts, proven cold by a headless agent), the full audit results, the 18-entry autonomous-decision ledger, and known gaps mapped to Later Ideas - no open P1/P2 findings anywhere. Every queue item's roadmap boxes are ticked except Merged to main, which this run deliberately does not perform. Full suite green (97 files, 1717 tests); 36 accepted specs validate.
The G1 test omitted globalDataDir and never registered a store, so it passed locally only by accident (it saw this machine's REAL registry) and failed on the clean CI runner, where the empty registry correctly fell through to the implicit root. The test now registers a store in its isolated registry and passes globalDataDir, making the no_root_with_registered_stores expectation deterministic everywhere. Full suite green (97 files, 1717 tests).
Evidence base for the spec: the f858c19^ opener archaeology (two-style launch split, PATH/PATHEXT scan, cross-spawn handoff mechanics, the not-to-inherit ledger), current-tree idioms (registry lock/atomic-write, the pure .code-workspace builder, the @InQuirer house rules, JSON contracts), and live CLI verification of code/cursor/claude/codex flag spellings and hazards.
A two-sided audit of the whole delta (production code and tests)
against the cross-platform rules, with every finding fixed:
Production: extractFirstPurposeLine splits on \r?\n (CRLF checkouts -
the Git-for-Windows default - previously got empty summaries for every
referenced spec); the clone-recipe fix quotes for the rendering
platform (single quotes are literal characters in cmd/PowerShell -
win32 now gets double quotes); the registry path-comparison fallback
resolves nonexistent paths instead of raw string-comparing them; the
manual-deletion fix drops its POSIX-only rm -rf; repo register expands
~ via the same expandUserPath every store command uses.
Tests: the onboarding e2e no longer depends on HOME (USERPROFILE set
alongside), declares its local remote in shell-safe forward-slash
form, pins the platform-correct quote style, and executes the fix via
argv arrays instead of split(' ') re-tokenization (paths with spaces);
the store-references normalizer matches the JSON-escaped needle
(serialized Windows paths double their backslashes); the
metadata-path assertion uses path.join; snapshot keys are
POSIX-normalized in the shared helper and both local copies.
Audited clean: registry/conflict path identity (canonicalized both
sides via realpathSync.native), cross-drive path.relative guards,
getGlobalDataDir's win32 branches, git invocations (argv arrays),
the lock and atomic-write semantics, XDG isolation, fetch-recipe
splits (no paths), and the deliberate-POSIX display literals.
CI: the OS test matrix (linux/macos/windows) previously ran ONLY on
push to main - it now also runs on workflow_dispatch so branches can
get a real Windows verification before merge.
Full suite green locally (97 files, 1717 tests).
The references.test.ts pin asserted the POSIX single-quote form; the implementation now deliberately renders double quotes on win32 - the one remaining windows-pwsh matrix failure. The doctor/context pins are quote-agnostic (stringContaining on the unquoted prefix) and the onboarding e2e was already platform-aware.
Subagent: approve-with-fixes; codex: reject (converging). The P1 - attach-dirs argv now carries one attach pair per member, primary included, per the locked FR2 wording. Folded: no-tool open path, stale-saved-tool rule, signal exit contract, the hand-edit parse contract, pinned JSON envelopes (incl. the open --json typed rejection), derived-file locking with ENOENT-tolerant remove, the teammate scenario, the win32 availability matrix, and opener-config touchpoints. Research+spec roadmap box ticked; changelog entries added.
Subagent: approve-with-fixes; codex: reject (converging). The shared P1: open now regenerates the .code-workspace under the lock BEFORE tool resolution, so every fallback names an existing current file. Also folded: real busy-error factory sites with new byte-shape pins (the suite never covered the lock mechanics), withWorksetsLock, cross-spawn import shape, the --member collector, injectable-spawn units for SIGINT/launch-failed, in-process cancellation coverage with enumerated capstone carve-outs, the win32 stat-seam fixture strategy, the recorded TOCTOU, anchor drift fixes, and the spec d12 amendment dropping the dead workset_create_cancelled code.
src/core/file-state.ts extracts writeFileAtomically and the lock-acquire loop from store foundation (errors stay caller-owned via the injected factory; store shapes pinned byte-identical by new tests - the suite never covered the lock mechanics before). src/core/worksets.ts: the saved-views file under <dataDir>/worksets/ on the registry idiom (strict zod + version 1, hand-edit parse contract, withWorksetsLock read-without-write, pure rebuilds, the .code-workspace builder). src/core/openers.ts: the locked built-in table, per-field config merge over built-ins, the PATH/PATHEXT availability scan with an injectable stat seam, and the pure two-style launch-command builder (one attach pair per member, never a positional). GlobalConfig gains the openers key. 42 new unit tests; full suite green (99 files, 1759 tests).
src/commands/workset.ts: create (guided 3-step wizard / non-interactive --member collector with name=path labels), list, open (regenerate the .code-workspace under the lock before any tool resolution so every fallback names a current file; cross-spawn handoff with honest exit-code and 128+signal propagation; the Open manually: block on every cannot-drive failure; hidden --json rejected as one typed JSON document), remove (plan-then-confirm, --yes, ENOENT-tolerant derived cleanup under the lock), and the command:* unknown-subcommand handler. isPromptCancellationError extracted to shared-output (third copy). CLI + completions registration, the docs/cli.md section and table rows, the resurrected path-env helper, the fake-tool recorder, 34 command tests (incl. in-process launch mechanics and interactive-cancellation coverage via mocked prompts), and the two e2e journeys (no-footprint + teammate isolation). Full suite green (101 files, 1795 tests).
Spec-compliance (compliant-with-fixes), /code-review seven-angle fan-out, and codex (approve-with-fixes) converged with no P1s. Behavioral: structural open-fallback rule (surviving members, every post-regeneration failure except cancellation), the primary- reassignment note, honest zero-tools message, pasteable launch-failed alternative, post-save Ctrl-C declines instead of cancelling, parent signal guard during launch (the 128+n contract was unreachable for tty SIGINT), sync spawn throws wrapped, tool.cmd PATHEXT double-append removed, bare workset --json keeps the one-document contract, deadline-bounded lock stat failures, remove cleanup after the durable write, early flag-member validation, lazy cross-spawn (~6ms per CLI invocation). Structure: command layer split (workset / prompts / input); shared homes for formatZodIssues, folderStyleNameProblem, KEBAB_ID_FIX, pathIs*; cancellation lifted into emitFailure with store collapsed onto it. Tests: +6 cases, controlled PATH for the in-process interactive suite, win32 path-env key fix. Spec amended to the shipped contracts. Full suite green (101 files, 1799 tests).
makeLockErrorFactory in file-state (both lock-error factories were data-twins; store shapes stay byte-pinned), optsWithGlobals over the hand-rolled group-option merge, the prompt preview ladder flattened with one assertKnownTool spelling, asErrorMessage hoisted to shared-output, formatMemberRows deduping three renderers, per-branch opener resolution in open (dead branch + redundant re-scan gone), serialize emits validated entries directly, toWorkset dedup, remove --yes skips the duplicate pre-read, KEBAB_ID_FIX adopted, dead exports trimmed. Skips recorded (store-group fallback convergence queued for the next store touch). Full suite green (101 files, 1799 tests).
Scripted walk (both launch styles, exact argv incl. the no-prompt rule, strand-test fallback, missing-member skip, safe remove, byte-untouched members), the interactive wizard from a real pty, live cancellation, and the cold-start headless agent reaching an opened workset from --help alone. No product findings. Full suite green (101 files, 1799 tests).
Summary
Implements Phase 1 slices 1.1 and 1.2 of the simplify-context-and-workspace-model roadmap, plus slice 1.2's pulled-forward item 2.1 (initiative-link retirement). Context stores become ordinary standalone OpenSpec roots with thin identity metadata, and normal OpenSpec commands can act on a registered store by name with
--store <id>.Slice 1.1 — store root parity
context-store setupcreates or preservesopenspec/config.yaml,openspec/specs/,openspec/changes/, andopenspec/changes/archive/alongside.openspec-store/store.yaml.context-store registerrequires an existing healthy OpenSpec root, with--yesfor non-interactive identity creation.context-store doctor --jsonreportsopenspec_rootseparately frommetadataandgit.Slice 1.2 — --store root selection for normal commands
src/core/root-selection.ts) behindnew change,status,instructions,list,show,validate, andarchive.--store <id>resolves a registered store to its root; unknown ids list registered stores; unhealthy or identity-mismatched stores point tocontext-store doctor.Using OpenSpec root: <id> (<abs path>)to stderr; JSON successes carry an additive sharedrootblock (path,source,store_id).--store-pathis rejected deliberately withcontext-store registerguidance, including onshowdespiteallowUnknownOption.new changeis root selection only: initiative-link creation removed,--initiativeand--areasreject before any writes,--goalstays ordinary metadata.openspec set changeis removed (initiative linking was its only behavior).archivegains a non-interactive--jsonmode with machine-readable diagnostics and pure-JSON stdout; rebuilt specs are now all validated before any are written, so a late validation failure leaves every target untouched.listgains minimal--specs --jsonsupport;context-store setup/registernext steps show--storeusage.openspec/work/simplify-context-and-workspace-model/; old beta initiative and workspace docs are marked as transition history.Deprecated noun-form commands (
openspec change show,openspec spec show, …) stay cwd-based and do not gain--store. Updating generated agent skills/guidance to mention--storeis tracked separately.Validation
pnpm run buildpnpm exec vitest run test/core/root-selection.test.ts test/commands/store-root-selection.test.ts test/commands/artifact-workflow.test.ts test/commands/context-store.test.ts test/commands/completion.test.ts test/core/archive.test.tspnpm test(92 files, 1717 passed)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
--store <id>flag to select registered context stores as OpenSpec roots across commands./workdirectory structure for organizing goals, roadmaps, and slices.Removed
set changecommand.--initiativeand--areasflags fromnew change.Improvements