Stores: keep your planning in its own repo and work with it from anywhere#1190
Stores: keep your planning in its own repo and work with it from anywhere#1190TabishB wants to merge 111 commits into
Conversation
|
Important Review skippedToo many files! This PR contains 221 files, which is 71 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 (221)
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.
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).
The fixture was a shebang-less text file: macOS posix_spawn rejects it with ENOEXEC (the spawn error the test wants), but glibc execvp retries ENOEXEC via /bin/sh, so on Linux the child runs and exits 127 instead of erroring. A shebang pointing at a missing interpreter fails ENOENT on every POSIX libc with no shell fallback; a garbage claude.exe covers the win32 matrix leg the same way. Verified in a node:20 Linux container (old fixture reproduces exit 127; full workset file passes with the fix) and on macOS (full suite, 1799 tests).
A problem-first guide for the new surface (stores, references, targets, repo map, doctor, context, worksets) under docs/stores-beta/, mirroring the old workspaces-beta layout. Built around two team stories — one team sharing a planning repo, and requirements crossing team lines — with every command output captured from a live walk of the current build in isolated scratch state. Carries the beta notice (shapes may change), the verified resolution-precedence table, known limitations including the one-checkout-per-store-id rule and the commands that stay cwd-based, and the real on-disk state locations. Linked from the README, getting-started, and the cli.md stores section, which gains the same beta note.
The note at the top of the Stores section names worksets but is invisible to a reader deep-linking straight to Personal worksets.
What this is
This PR replaces OpenSpec's old workspace/initiative model with one simple idea: a store — a standalone repo that holds your specs and changes, registered on your machine by name. Everything else here makes that idea practical: your code repos can point at a store, OpenSpec can tell you whether your setup is healthy, show you the working context it assembles for you, and reopen the folders you work on together in your editor or agent.
It is the complete
simplify-context-and-workspace-modelroadmap (Phases 1–7), built slice by slice with per-slice specs, plans, and reviews, and proven as a whole by a final acceptance capstone.What you can do now
Start planning in five minutes. Two commands take you from install to a working, store-scoped change:
openspec store setup team-plans --path ~/openspec/team-plans openspec new change my-first-change --store team-plansEvery step prints the exact next command, so neither you nor your agent needs insider knowledge. The whole lifecycle —
status,instructions,archive— works the same way.Share planning across a team. A store is an ordinary git repo: push it, and a teammate clones it and runs
openspec store registerto continue the same work from their machine.Connect your code repos. One line in a code repo's
openspec/config.yaml(store: team-plans) and every OpenSpec command works from inside that repo with no flags. Stores can also declare the upstream context they draw on (references:) and the repos the work is about (targets:), withopenspec repo registermapping those names to local checkouts. Declarations are just declarations — nothing clones, syncs, or fences your edits.Ask "is my setup healthy?"
openspec doctorchecks the whole picture — store identity, pointers, references, targets, repo map — and every finding comes with a pasteable fix.Ask "what am I working with?"
openspec contextassembles your working set as a human listing, an agent brief, or a.code-workspacefile for your editor.Keep personal worksets. Name a group of folders you like open together (a store plus some repos) and reopen them with one command in VS Code, Cursor, Claude Code, or codex:
openspec workset create→openspec workset open. Worksets are personal and local — your view of the work, never shared state.Drive it all from an agent. Every command has
--jsonbehind a documented contract (docs/agent-contract.md: every shape and 100+ diagnostic codes).What's gone
The
workspaceandinitiativecommand groups — their state model, schema, accepted specs, and generated guidance — are deleted, not hidden. "Context store" became simply store everywhere (commands, output, docs, machine tokens). Despite everything added above, the branch is net −4,478 lines ofsrc.How it was proven
--helpoutput, which assembled the full setup and self-verified withdoctor/context/validate.origin/main...HEAD: four independent mechanisms; all P1/P2 findings fixed and re-verified.Full evidence lives in
openspec/work/simplify-context-and-workspace-model/— the roadmap with its decision changelog (including everyDecided autonomouslyentry for review), per-slice specs and plans, capstone reports, and the release-readiness verdict.🤖 Generated with Claude Code