From f43e35657e970fdb5582d434cc9ac892e3eaf588 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 11:02:39 -0500 Subject: [PATCH 1/5] fix(workflows): preserve nested spec paths --- docs/opsx.md | 2 +- docs/troubleshooting.md | 2 +- docs/writing-specs.md | 2 +- openspec/specs/cli-validate/spec.md | 4 +- openspec/specs/openspec-conventions/spec.md | 6 +- openspec/specs/specs-sync-skill/spec.md | 2 +- schemas/spec-driven/schema.yaml | 14 ++-- schemas/spec-driven/templates/proposal.md | 10 +-- skills/openspec-archive-change/SKILL.md | 4 +- skills/openspec-bulk-archive-change/SKILL.md | 6 +- skills/openspec-explore/SKILL.md | 18 ++--- skills/openspec-propose/SKILL.md | 4 +- skills/openspec-sync-specs/SKILL.md | 6 +- src/core/parsers/spec-structure.ts | 2 +- .../templates/workflows/archive-change.ts | 8 ++- .../workflows/bulk-archive-change.ts | 12 ++-- src/core/templates/workflows/explore.ts | 36 +++++----- src/core/templates/workflows/propose.ts | 8 ++- src/core/templates/workflows/sync-specs.ts | 12 ++-- src/core/validation/validator.ts | 2 +- .../workflow.integration.test.ts | 16 +++++ .../templates/skill-templates-parity.test.ts | 67 ++++++++++++++++++- 22 files changed, 178 insertions(+), 65 deletions(-) diff --git a/docs/opsx.md b/docs/opsx.md index 123eb68fc9..095f159d27 100644 --- a/docs/opsx.md +++ b/docs/opsx.md @@ -478,7 +478,7 @@ Artifacts form a directed acyclic graph (DAG). Dependencies are **enablers**, no │ • Create proposal.md │ │ • Create tasks.md │ │ • Create design.md │ - │ • Create specs//spec.md │ + │ • Create delta spec files │ │ │ │ No awareness of what exists or │ │ dependencies between artifacts │ diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 2c489aee14..7579c19128 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -102,7 +102,7 @@ One message deserves its own note: MODIFIED "" omits scenario(s) the current spec still has: "" ``` -A `MODIFIED` requirement replaces the whole requirement block, so it has to carry every scenario that survives the change, not only the ones you edited. Copy the named scenarios from `openspec/specs//spec.md` back into the delta. This often appears on an older change after someone else's change added a scenario to the same requirement — archive refuses that change either way, and validation now says so before you implement it. +A `MODIFIED` requirement replaces the whole requirement block, so it has to carry every scenario that survives the change, not only the ones you edited. Copy the named scenarios from `openspec/specs//spec.md` back into the delta, preserving any domain directories in the path. This often appears on an older change after someone else's change added a scenario to the same requirement — archive refuses that change either way, and validation now says so before you implement it. ### The AI created incomplete or wrong artifacts diff --git a/docs/writing-specs.md b/docs/writing-specs.md index 501c129cd1..a9ff921caf 100644 --- a/docs/writing-specs.md +++ b/docs/writing-specs.md @@ -58,7 +58,7 @@ A change describes its edits to the specs with three section types. Using the ri On archive, ADDED gets appended to the main spec, MODIFIED replaces the old version, and REMOVED is dropped from it. Remove the last requirement a capability has and you retire it: rather than leave a spec with nothing in it, archive deletes `openspec/specs//spec.md`. Because that is the one archive step that removes a file, it has to be asked for — add `retire_capabilities: true` to the change's `.openspec.yaml`, alongside the `schema:` that file already needs. Without it the archive aborts and tells you so. For a spec in the caller's checkout, the archive output also names the `git checkout` that restores a committed file; selected stores receive checkout-scoped recovery guidance instead. If you mark a real change as ADDED, you end up with two competing requirements; if you describe new behavior as MODIFIED, there's nothing to replace. When in doubt, open the current spec and see whether the requirement is already there. -One more section is worth knowing about. When your delta creates a capability that doesn't exist yet, open it with `## Purpose` — a sentence or two on what the capability is for. Archive uses it as the Purpose of the main spec it creates; skip it and you get a `TBD` placeholder to fill in by hand. An existing spec already has a Purpose, so a delta's is ignored there — edit `openspec/specs//spec.md` directly to change one. +One more section is worth knowing about. When your delta creates a capability that doesn't exist yet, open it with `## Purpose` — a sentence or two on what the capability is for. Archive uses it as the Purpose of the main spec it creates; skip it and you get a `TBD` placeholder to fill in by hand. An existing spec already has a Purpose, so a delta's is ignored there — edit `openspec/specs//spec.md` directly to change one. Here, `` is the directory relative to `specs/`, such as `user-auth` in a flat project or `identity/user-auth` in a project organized by domain. ## Right-size the change diff --git a/openspec/specs/cli-validate/spec.md b/openspec/specs/cli-validate/spec.md index 4c904bb0ab..3f04425af0 100644 --- a/openspec/specs/cli-validate/spec.md +++ b/openspec/specs/cli-validate/spec.md @@ -11,7 +11,7 @@ Validation output SHALL include specific guidance to fix each error, including e - **WHEN** validating a change with zero parsed deltas - **THEN** show error "No deltas found" with guidance: - Explain that change specs must include `## ADDED Requirements`, `## MODIFIED Requirements`, `## REMOVED Requirements`, or `## RENAMED Requirements` - - Remind authors that files must live under `openspec/changes/{id}/specs//spec.md` + - Remind authors that files must live under `openspec/changes/{id}/specs//spec.md` - Include an explicit note: "Spec delta files cannot start with titles before the operation headers" - Suggest running `openspec change show {id} --json --deltas-only` for debugging @@ -163,7 +163,7 @@ The validate command SHALL support flags for bulk validation (--all) and filtere - **AND** exclude the `openspec/changes/archive/` directory - **WHEN** validating with `--specs` -- **THEN** include all specs that have a `spec.md` under `openspec/specs//spec.md` +- **THEN** include all specs that have a `spec.md` under `openspec/specs//spec.md` #### Scenario: Validate all changes diff --git a/openspec/specs/openspec-conventions/spec.md b/openspec/specs/openspec-conventions/spec.md index 85ad36d619..5b94d05131 100644 --- a/openspec/specs/openspec-conventions/spec.md +++ b/openspec/specs/openspec-conventions/spec.md @@ -47,7 +47,7 @@ openspec/ ├── project.md # Project-specific context ├── AGENTS.md # AI assistant instructions ├── specs/ # Current deployed capabilities -│ └── [capability]/ # Single, focused capability +│ └── / # One or more directories for a focused capability │ ├── spec.md # WHAT and WHY │ └── design.md # HOW (optional, for established patterns) └── changes/ # Proposed changes @@ -56,7 +56,7 @@ openspec/ │ ├── tasks.md # Implementation checklist │ ├── design.md # Technical decisions (optional) │ └── specs/ # Complete future state - │ └── [capability]/ + │ └── / │ └── spec.md # Clean markdown (no diff syntax) └── archive/ # Completed changes └── YYYY-MM-DD-[name]/ @@ -224,7 +224,7 @@ The system SHALL support multiple methods for reviewing proposed changes. - **WHEN** reviewing proposed changes - **THEN** reviewers can compare using: - GitHub PR diff view when changes are committed -- Command line: `diff -u specs/[capability]/spec.md changes/[name]/specs/[capability]/spec.md` +- Command line: `diff -u specs/[capability-path]/spec.md changes/[name]/specs/[capability-path]/spec.md` - Any visual diff tool comparing current vs future state ### Requirement: Structured Format Adoption diff --git a/openspec/specs/specs-sync-skill/spec.md b/openspec/specs/specs-sync-skill/spec.md index a69263a634..3d14288802 100644 --- a/openspec/specs/specs-sync-skill/spec.md +++ b/openspec/specs/specs-sync-skill/spec.md @@ -71,7 +71,7 @@ The agent SHALL reconcile main specs with delta specs using the delta operation #### Scenario: New capability spec - **WHEN** delta spec exists for a capability not in main specs -- **THEN** create new main spec file at `openspec/specs//spec.md` +- **THEN** create new main spec file at `openspec/specs//spec.md`, preserving the delta's path relative to `specs/` - **AND** copy the delta's `## Purpose` body into it when the delta has one, matching what `openspec archive` does - **AND** write a brief TBD placeholder Purpose only when the delta has none diff --git a/schemas/spec-driven/schema.yaml b/schemas/spec-driven/schema.yaml index 3f94206079..ae4d9eb336 100644 --- a/schemas/spec-driven/schema.yaml +++ b/schemas/spec-driven/schema.yaml @@ -13,8 +13,8 @@ artifacts: - **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now? - **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**. - **Capabilities**: Identify which specs will be created or modified: - - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs//spec.md`. Use kebab-case names (e.g., `user-auth`, `data-export`). - - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Check `openspec/specs/` for existing spec names. Leave empty if no requirement changes. + - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs//spec.md`. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization. + - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes. - **Impact**: Affected code, APIs, dependencies, or systems. IMPORTANT: The Capabilities section is critical. It creates the contract between @@ -60,8 +60,10 @@ artifacts: visible behavior, it likely does not belong in the spec. Create one spec file per capability listed in the proposal's Capabilities section. - - New capabilities: use the exact kebab-case name from the proposal (specs//spec.md). - - Modified capabilities: use the existing spec folder name from openspec/specs// when creating the delta spec at specs//spec.md. + `` is the spec directory relative to `specs/` (for example, + `user-auth` or `identity/user-auth`). Preserve the full path: + - New capabilities: use the exact path from the proposal at `specs//spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout. + - Modified capabilities: use the exact existing path from `openspec/specs//` when creating the delta at `specs//spec.md`. Do not move or rename the capability. There must be at least one spec file unless the change's `.openspec.yaml` sets `skip_specs: true` (no spec-level behavior change) - `openspec validate` @@ -89,10 +91,10 @@ artifacts: by hand. Do NOT add `## Purpose` to a delta for an existing capability - that spec already has one and the delta's is ignored. To change an existing capability's Purpose - including a leftover `TBD` placeholder - - edit `openspec/specs//spec.md` directly. + edit `openspec/specs//spec.md` directly. MODIFIED requirements workflow: - 1. Locate the existing requirement in openspec/specs//spec.md + 1. Locate the existing requirement in openspec/specs//spec.md 2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios) 3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior 4. Ensure header text matches exactly (whitespace-insensitive) diff --git a/schemas/spec-driven/templates/proposal.md b/schemas/spec-driven/templates/proposal.md index fb8d99c3f8..fe1aeb6acb 100644 --- a/schemas/spec-driven/templates/proposal.md +++ b/schemas/spec-driven/templates/proposal.md @@ -9,18 +9,20 @@ ## Capabilities ### New Capabilities - -- ``: + +- ``: ### Modified Capabilities -- ``: +- ``: ## Impact diff --git a/skills/openspec-archive-change/SKILL.md b/skills/openspec-archive-change/SKILL.md index 41cf89e93a..80991b7fe9 100644 --- a/skills/openspec-archive-change/SKILL.md +++ b/skills/openspec-archive-change/SKILL.md @@ -13,6 +13,8 @@ Archive a completed change in the experimental workflow. **Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store ` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Once selected, treat `--store ` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "" --json --store ""`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. +`` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -91,7 +93,7 @@ Archive a completed change in the experimental workflow. delta specs from other artifacts. **If delta specs exist:** - - Compare each delta spec with its corresponding main spec at `/openspec/specs//spec.md` (use the store-aware `planningHome.root` from step 2, not a hardcoded repo path) + - Compare each delta spec with its corresponding main spec at `/openspec/specs//spec.md` (use the store-aware `planningHome.root` from step 2, not a hardcoded repo path) - Determine what changes would be applied (adds, modifications, removals, renames) - Show a combined summary before prompting diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index b0df1f7e9f..0d95b8dc01 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -15,6 +15,8 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig **Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store ` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Once selected, treat `--store ` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "" --json --store ""`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. +`` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: None required (prompts for selection) **Steps** @@ -186,7 +188,7 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig - If a change has no included delta specs, do not run the sync workflow for it. b. **Verify included delta specs before moving changeRoot**: - - Re-run the comparison only for delta specs in `includedDeltas` against main spec at `/openspec/specs//spec.md` (use the store-aware `planningHome.root` from step 3 status JSON, not a hardcoded repo path). + - Re-run the comparison only for delta specs in `includedDeltas` against main spec at `/openspec/specs//spec.md` (use the store-aware `planningHome.root` from step 3 status JSON, not a hardcoded repo path). - Verify that main specs are updated: - ADDED requirements present - MODIFIED requirements carrying scenario and description changes named in the delta, with their other scenarios intact @@ -323,7 +325,7 @@ No active changes found. Create a new change to get started. - If sync is requested, run the `openspec-sync-specs` workflow inline (agent-driven) for each change with included delta specs - Carry the per-delta `includedDeltas` and `excludedDeltas` decisions into execution; sync and verify only included deltas - Report every excluded delta as `sync skipped` without treating the archive itself as skipped -- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at `/openspec/specs//spec.md` before moving `changeRoot` +- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at `/openspec/specs//spec.md` before moving `changeRoot` - Fetch archive inputs once per selected root before spec inspection or moves - Fetch all required specs-rule snapshots before the batch's first main-spec write or move - A failed archive-inputs lookup never blocks the batch; it proceeds with no context or guidance diff --git a/skills/openspec-explore/SKILL.md b/skills/openspec-explore/SKILL.md index 8633e4d255..b886a44dcb 100644 --- a/skills/openspec-explore/SKILL.md +++ b/skills/openspec-explore/SKILL.md @@ -130,14 +130,16 @@ If the user mentions a change or you detect one is relevant: 3. **Offer to capture when decisions are made** - | Insight Type | Where to Capture | - |----------------------------|--------------------------------| - | New requirement discovered | `specs//spec.md` | - | Requirement changed | `specs//spec.md` | - | Design decision made | `design.md` | - | Scope changed | `proposal.md` | - | New work identified | `tasks.md` | - | Assumption invalidated | Relevant artifact | + `` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + + | Insight Type | Where to Capture | + |----------------------------|-------------------------------------| + | New requirement discovered | `specs//spec.md` | + | Requirement changed | `specs//spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | Example offers: - "That's a design decision. Capture it in design.md?" diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index 29677ac457..0f4ec8a0c5 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -15,10 +15,12 @@ Propose a new change - create the change and generate all artifacts in one step. I'll create a change with the artifacts your schema defines. With the default spec-driven schema that is: - proposal.md (what & why) -- `specs//spec.md` (what the system must do - a delta, not the main spec) +- `specs//spec.md` (what the system must do - a delta, not the main spec) - design.md (how) - tasks.md (implementation steps) +`` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + When the user is ready to implement, they must start the apply workflow explicitly. --- diff --git a/skills/openspec-sync-specs/SKILL.md b/skills/openspec-sync-specs/SKILL.md index c3289ed5cb..c2dc11a626 100644 --- a/skills/openspec-sync-specs/SKILL.md +++ b/skills/openspec-sync-specs/SKILL.md @@ -15,6 +15,8 @@ This is an **agent-driven** operation - you will read delta specs and directly e **Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store ` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Once selected, treat `--store ` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "" --json --store ""`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. +`` is the spec directory relative to `specs/` (for example, `user-auth` or `identity/user-auth`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -89,7 +91,7 @@ This is an **agent-driven** operation - you will read delta specs and directly e a. **Read the delta spec** to understand the intended changes - b. **Read the main spec** at `/openspec/specs//spec.md` (may not exist yet) + b. **Read the main spec** at `/openspec/specs//spec.md` (may not exist yet) c. **Apply changes intelligently**: @@ -138,7 +140,7 @@ This is an **agent-driven** operation - you will read delta specs and directly e (this is what `openspec archive` does; it warns and moves on) d. **Create new main spec** if capability doesn't exist yet: - - Create `/openspec/specs//spec.md` + - Create `/openspec/specs//spec.md` - Add Purpose section: copy the delta's `## Purpose` body verbatim when it has one (this is what `openspec archive` does); only write a brief TBD placeholder when it does not - Add Requirements section with the ADDED requirements diff --git a/src/core/parsers/spec-structure.ts b/src/core/parsers/spec-structure.ts index 3443836f74..efbe13f39f 100644 --- a/src/core/parsers/spec-structure.ts +++ b/src/core/parsers/spec-structure.ts @@ -45,7 +45,7 @@ export function findMainSpecStructureIssues(content: string): MainSpecStructureI header: trimmed, message: `Main spec contains delta header "${trimmed}". ` + - 'Delta headers are only valid inside openspec/changes//specs//spec.md ' + + 'Delta headers are only valid inside openspec/changes//specs//spec.md ' + 'and truncate the parsed ## Requirements section.', }); continue; diff --git a/src/core/templates/workflows/archive-change.ts b/src/core/templates/workflows/archive-change.ts index 8c09666b4e..2dae74d436 100644 --- a/src/core/templates/workflows/archive-change.ts +++ b/src/core/templates/workflows/archive-change.ts @@ -15,6 +15,8 @@ export function getArchiveChangeSkillTemplate(): SkillTemplate { ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -93,7 +95,7 @@ ${STORE_SELECTION_GUIDANCE} delta specs from other artifacts. **If delta specs exist:** - - Compare each delta spec with its corresponding main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path) + - Compare each delta spec with its corresponding main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path) - Determine what changes would be applied (adds, modifications, removals, renames) - Show a combined summary before prompting @@ -195,6 +197,8 @@ export function getOpsxArchiveCommandTemplate(): CommandTemplate { ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name after \`/opsx:archive\` (e.g., \`/opsx:archive add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -273,7 +277,7 @@ ${STORE_SELECTION_GUIDANCE} delta specs from other artifacts. **If delta specs exist:** - - Compare each delta spec with its corresponding main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path) + - Compare each delta spec with its corresponding main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path) - Determine what changes would be applied (adds, modifications, removals, renames) - Show a combined summary before prompting diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index 5585fa77ba..0c181170dc 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -17,6 +17,8 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: None required (prompts for selection) **Steps** @@ -188,7 +190,7 @@ ${STORE_SELECTION_GUIDANCE} - If a change has no included delta specs, do not run the sync workflow for it. b. **Verify included delta specs before moving changeRoot**: - - Re-run the comparison only for delta specs in \`includedDeltas\` against main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 3 status JSON, not a hardcoded repo path). + - Re-run the comparison only for delta specs in \`includedDeltas\` against main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 3 status JSON, not a hardcoded repo path). - Verify that main specs are updated: - ADDED requirements present - MODIFIED requirements carrying scenario and description changes named in the delta, with their other scenarios intact @@ -325,7 +327,7 @@ No active changes found. Create a new change to get started. - If sync is requested, run the \`openspec-sync-specs\` workflow inline (agent-driven) for each change with included delta specs - Carry the per-delta \`includedDeltas\` and \`excludedDeltas\` decisions into execution; sync and verify only included deltas - Report every excluded delta as \`sync skipped\` without treating the archive itself as skipped -- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at \`/openspec/specs//spec.md\` before moving \`changeRoot\` +- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at \`/openspec/specs//spec.md\` before moving \`changeRoot\` - Fetch archive inputs once per selected root before spec inspection or moves - Fetch all required specs-rule snapshots before the batch's first main-spec write or move - A failed archive-inputs lookup never blocks the batch; it proceeds with no context or guidance @@ -354,6 +356,8 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: None required (prompts for selection) **Steps** @@ -526,7 +530,7 @@ ${STORE_SELECTION_GUIDANCE} - If a change has no included delta specs, do not run the sync workflow for it. b. **Verify included delta specs before moving changeRoot**: - - Re-run the comparison only for delta specs in \`includedDeltas\` against main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 3 status JSON, not a hardcoded repo path). + - Re-run the comparison only for delta specs in \`includedDeltas\` against main spec at \`/openspec/specs//spec.md\` (use the store-aware \`planningHome.root\` from step 3 status JSON, not a hardcoded repo path). - Verify that main specs are updated: - ADDED requirements present - MODIFIED requirements carrying scenario and description changes named in the delta, with their other scenarios intact @@ -663,7 +667,7 @@ No active changes found. Create a new change to get started. - If sync is requested, run the \`/opsx:sync\` workflow inline (agent-driven) for each change with included delta specs - Carry the per-delta \`includedDeltas\` and \`excludedDeltas\` decisions into execution; sync and verify only included deltas - Report every excluded delta as \`sync skipped\` without treating the archive itself as skipped -- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at \`/openspec/specs//spec.md\` before moving \`changeRoot\` +- Never archive a change while a spec sync is still in flight — run the sync inline and verify main specs at \`/openspec/specs//spec.md\` before moving \`changeRoot\` - Fetch archive inputs once per selected root before spec inspection or moves - Fetch all required specs-rule snapshots before the batch's first main-spec write or move - A failed archive-inputs lookup never blocks the batch; it proceeds with no context or guidance diff --git a/src/core/templates/workflows/explore.ts b/src/core/templates/workflows/explore.ts index d3a9b5c518..211de65646 100644 --- a/src/core/templates/workflows/explore.ts +++ b/src/core/templates/workflows/explore.ts @@ -132,14 +132,16 @@ If the user mentions a change or you detect one is relevant: 3. **Offer to capture when decisions are made** - | Insight Type | Where to Capture | - |----------------------------|--------------------------------| - | New requirement discovered | \`specs//spec.md\` | - | Requirement changed | \`specs//spec.md\` | - | Design decision made | \`design.md\` | - | Scope changed | \`proposal.md\` | - | New work identified | \`tasks.md\` | - | Assumption invalidated | Relevant artifact | + \`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + + | Insight Type | Where to Capture | + |----------------------------|-------------------------------------| + | New requirement discovered | \`specs//spec.md\` | + | Requirement changed | \`specs//spec.md\` | + | Design decision made | \`design.md\` | + | Scope changed | \`proposal.md\` | + | New work identified | \`tasks.md\` | + | Assumption invalidated | Relevant artifact | Example offers: - "That's a design decision. Capture it in design.md?" @@ -447,14 +449,16 @@ If the user mentions a change or you detect one is relevant: 3. **Offer to capture when decisions are made** - | Insight Type | Where to Capture | - |----------------------------|--------------------------------| - | New requirement discovered | \`specs//spec.md\` | - | Requirement changed | \`specs//spec.md\` | - | Design decision made | \`design.md\` | - | Scope changed | \`proposal.md\` | - | New work identified | \`tasks.md\` | - | Assumption invalidated | Relevant artifact | + \`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + + | Insight Type | Where to Capture | + |----------------------------|-------------------------------------| + | New requirement discovered | \`specs//spec.md\` | + | Requirement changed | \`specs//spec.md\` | + | Design decision made | \`design.md\` | + | Scope changed | \`proposal.md\` | + | New work identified | \`tasks.md\` | + | Assumption invalidated | Relevant artifact | Example offers: - "That's a design decision. Capture it in design.md?" diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index e82594906a..a47c8ea64c 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -17,10 +17,12 @@ export function getOpsxProposeSkillTemplate(): SkillTemplate { I'll create a change with the artifacts your schema defines. With the default spec-driven schema that is: - proposal.md (what & why) -- \`specs//spec.md\` (what the system must do - a delta, not the main spec) +- \`specs//spec.md\` (what the system must do - a delta, not the main spec) - design.md (how) - tasks.md (implementation steps) +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + When the user is ready to implement, they must start the apply workflow explicitly. --- @@ -164,10 +166,12 @@ export function getOpsxProposeCommandTemplate(): CommandTemplate { I'll create a change with the artifacts your schema defines. With the default spec-driven schema that is: - proposal.md (what & why) -- \`specs//spec.md\` (what the system must do - a delta, not the main spec) +- \`specs//spec.md\` (what the system must do - a delta, not the main spec) - design.md (how) - tasks.md (implementation steps) +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve an existing capability's full path and follow the project's established organization for new capabilities. + When the user is ready to implement, they must start the apply workflow explicitly. --- diff --git a/src/core/templates/workflows/sync-specs.ts b/src/core/templates/workflows/sync-specs.ts index 092959db45..9c1790a3e2 100644 --- a/src/core/templates/workflows/sync-specs.ts +++ b/src/core/templates/workflows/sync-specs.ts @@ -17,6 +17,8 @@ This is an **agent-driven** operation - you will read delta specs and directly e ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -91,7 +93,7 @@ ${STORE_SELECTION_GUIDANCE} a. **Read the delta spec** to understand the intended changes - b. **Read the main spec** at \`/openspec/specs//spec.md\` (may not exist yet) + b. **Read the main spec** at \`/openspec/specs//spec.md\` (may not exist yet) c. **Apply changes intelligently**: @@ -140,7 +142,7 @@ ${STORE_SELECTION_GUIDANCE} (this is what \`openspec archive\` does; it warns and moves on) d. **Create new main spec** if capability doesn't exist yet: - - Create \`/openspec/specs//spec.md\` + - Create \`/openspec/specs//spec.md\` - Add Purpose section: copy the delta's \`## Purpose\` body verbatim when it has one (this is what \`openspec archive\` does); only write a brief TBD placeholder when it does not - Add Requirements section with the ADDED requirements @@ -275,6 +277,8 @@ This is an **agent-driven** operation - you will read delta specs and directly e ${STORE_SELECTION_GUIDANCE} +\`\` is the spec directory relative to \`specs/\` (for example, \`user-auth\` or \`identity/user-auth\`). Preserve the full path from each delta spec when resolving its main spec. + **Input**: Optionally specify a change name after \`/opsx:sync\` (e.g., \`/opsx:sync add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. **Steps** @@ -349,7 +353,7 @@ ${STORE_SELECTION_GUIDANCE} a. **Read the delta spec** to understand the intended changes - b. **Read the main spec** at \`/openspec/specs//spec.md\` (may not exist yet) + b. **Read the main spec** at \`/openspec/specs//spec.md\` (may not exist yet) c. **Apply changes intelligently**: @@ -398,7 +402,7 @@ ${STORE_SELECTION_GUIDANCE} (this is what \`openspec archive\` does; it warns and moves on) d. **Create new main spec** if capability doesn't exist yet: - - Create \`/openspec/specs//spec.md\` + - Create \`/openspec/specs//spec.md\` - Add Purpose section: copy the delta's \`## Purpose\` body verbatim when it has one (this is what \`openspec archive\` does); only write a brief TBD placeholder when it does not - Add Requirements section with the ADDED requirements diff --git a/src/core/validation/validator.ts b/src/core/validation/validator.ts index 6beb944338..24bd7fe7eb 100644 --- a/src/core/validation/validator.ts +++ b/src/core/validation/validator.ts @@ -178,7 +178,7 @@ export class Validator { level: 'ERROR', path: 'spec.md', message: - 'Delta spec found at specs/spec.md. Delta specs must live in a capability folder (e.g. specs//spec.md) — a file at the specs/ root is ignored when the change is applied or archived.', + 'Delta spec found at specs/spec.md. Delta specs must live under a capability path (e.g. specs//spec.md) — a file at the specs/ root is ignored when the change is applied or archived.', }); } diff --git a/test/core/artifact-graph/workflow.integration.test.ts b/test/core/artifact-graph/workflow.integration.test.ts index b126801fcc..06a12529cc 100644 --- a/test/core/artifact-graph/workflow.integration.test.ts +++ b/test/core/artifact-graph/workflow.integration.test.ts @@ -35,6 +35,22 @@ describe('artifact-graph workflow integration', () => { }); describe('spec-driven workflow', () => { + it('preserves existing flat or nested capability organization in its instructions (#1459)', () => { + const schema = resolveSchema('spec-driven'); + const proposal = schema.artifacts.find(artifact => artifact.id === 'proposal'); + const specs = schema.artifacts.find(artifact => artifact.id === 'specs'); + + expect(proposal?.instruction).toContain('`user-auth` or `identity/user-auth`'); + expect(proposal?.instruction).toContain('follow the project\'s existing spec organization'); + expect(specs?.instruction).toContain( + '`` is the spec directory relative to `specs/`' + ); + expect(specs?.instruction).toContain( + 'do not add a new domain level when the project uses a flat layout' + ); + expect(specs?.instruction).toContain('Do not move or rename the capability'); + }); + it('should progress through complete workflow', () => { // 1. Resolve the real built-in schema const schema = resolveSchema('spec-driven'); diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 3d2a11d3ca..c8beb978bc 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -240,6 +240,69 @@ describe('skill templates split parity', () => { } }); + it('preserves nested capability paths in spec-aware workflow guidance (#1459)', () => { + const capabilityPathDefinition = + '`` is the spec directory relative to `specs/`'; + const pathAwareTemplates: Array<[string, string, string]> = [ + [ + 'propose skill', + generateSkillContent(getOpsxProposeSkillTemplate(), 'PARITY-BASELINE'), + 'specs//spec.md', + ], + [ + 'propose command', + getOpsxProposeCommandTemplate().content, + 'specs//spec.md', + ], + [ + 'explore skill', + generateSkillContent(getExploreSkillTemplate(), 'PARITY-BASELINE'), + 'specs//spec.md', + ], + [ + 'explore command', + getOpsxExploreCommandTemplate().content, + 'specs//spec.md', + ], + [ + 'sync skill', + generateSkillContent(getSyncSpecsSkillTemplate(), 'PARITY-BASELINE'), + '/openspec/specs//spec.md', + ], + [ + 'sync command', + getOpsxSyncCommandTemplate().content, + '/openspec/specs//spec.md', + ], + [ + 'archive skill', + generateSkillContent(getArchiveChangeSkillTemplate(), 'PARITY-BASELINE'), + '/openspec/specs//spec.md', + ], + [ + 'archive command', + getOpsxArchiveCommandTemplate().content, + '/openspec/specs//spec.md', + ], + [ + 'bulk archive skill', + generateSkillContent(getBulkArchiveChangeSkillTemplate(), 'PARITY-BASELINE'), + '/openspec/specs//spec.md', + ], + [ + 'bulk archive command', + getOpsxBulkArchiveCommandTemplate().content, + '/openspec/specs//spec.md', + ], + ]; + + for (const [label, content, destination] of pathAwareTemplates) { + expect(content, label).toContain(capabilityPathDefinition); + expect(content, label).toContain(destination); + expect(content, label).not.toContain('specs//spec.md'); + } + }); + it('generates no workspace-planning residue in any workflow template (4.1)', () => { const allSkills: Array<[string, () => SkillTemplate]> = [ ['openspec-apply-change', getApplyChangeSkillTemplate], @@ -301,7 +364,7 @@ describe('skill templates split parity', () => { expect(content, variant).toContain('not only the ones the sync reports it touched'); // Main spec paths are store-root aware - expect(content, variant).toContain('/openspec/specs//spec.md'); + expect(content, variant).toContain('/openspec/specs//spec.md'); } }); @@ -329,7 +392,7 @@ describe('skill templates split parity', () => { expect(content, variant).toContain('RENAMED requirements present under the new name and absent under the old one'); // Main spec paths are store-root aware - expect(content, variant).toContain('/openspec/specs//spec.md'); + expect(content, variant).toContain('/openspec/specs//spec.md'); } }); From 867fd9026713d9be1c933b4b7743348d25a7828d Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 11:21:11 -0500 Subject: [PATCH 2/5] fix(workflows): key conflicts by capability path --- openspec/specs/openspec-conventions/spec.md | 2 +- skills/openspec-bulk-archive-change/SKILL.md | 10 ++++---- .../workflows/bulk-archive-change.ts | 20 ++++++++-------- .../templates/skill-templates-parity.test.ts | 23 +++++++++++++++++++ 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/openspec/specs/openspec-conventions/spec.md b/openspec/specs/openspec-conventions/spec.md index 5b94d05131..54fc0e0f3b 100644 --- a/openspec/specs/openspec-conventions/spec.md +++ b/openspec/specs/openspec-conventions/spec.md @@ -224,7 +224,7 @@ The system SHALL support multiple methods for reviewing proposed changes. - **WHEN** reviewing proposed changes - **THEN** reviewers can compare using: - GitHub PR diff view when changes are committed -- Command line: `diff -u specs/[capability-path]/spec.md changes/[name]/specs/[capability-path]/spec.md` +- Command line: `diff -u "specs//spec.md" "changes//specs//spec.md"` - Any visual diff tool comparing current vs future state ### Requirement: Structured Format Adoption diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index 0d95b8dc01..cea1abda61 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -83,14 +83,14 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig batches where some schemas have no `specs` artifact. 4. **Detect spec conflicts** - Build a map of `capability -> [changes that touch it]`: + Build a map keyed by ``, the exact path relative to `specs/`: ```text - auth -> [change-a, change-b] <- CONFLICT (2+ changes) - api -> [change-c] <- OK (only 1 change) + identity/user-auth -> [change-a, change-b] <- CONFLICT (2+ changes) + billing/user-auth -> [change-c] <- OK (different full path) ``` - A conflict exists when 2+ selected changes have delta specs for the same capability. + A conflict exists when 2+ selected changes have delta specs for the exact same ``. 5. **Resolve conflicts agentically** @@ -108,7 +108,7 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig - If neither implemented -> skip spec sync, warn user d. **Record resolution** for each conflict: - - An inclusion or exclusion decision for every delta spec, keyed by change and capability + - An inclusion or exclusion decision for every delta spec, keyed by change and `` - Which included delta specs to apply and in what order - Which delta specs to exclude from sync because their implementation is missing - Rationale (what was found in codebase) diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index 0c181170dc..d1ecb44352 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -85,14 +85,14 @@ ${STORE_SELECTION_GUIDANCE} batches where some schemas have no \`specs\` artifact. 4. **Detect spec conflicts** - Build a map of \`capability -> [changes that touch it]\`: + Build a map keyed by \`\`, the exact path relative to \`specs/\`: \`\`\`text - auth -> [change-a, change-b] <- CONFLICT (2+ changes) - api -> [change-c] <- OK (only 1 change) + identity/user-auth -> [change-a, change-b] <- CONFLICT (2+ changes) + billing/user-auth -> [change-c] <- OK (different full path) \`\`\` - A conflict exists when 2+ selected changes have delta specs for the same capability. + A conflict exists when 2+ selected changes have delta specs for the exact same \`\`. 5. **Resolve conflicts agentically** @@ -110,7 +110,7 @@ ${STORE_SELECTION_GUIDANCE} - If neither implemented -> skip spec sync, warn user d. **Record resolution** for each conflict: - - An inclusion or exclusion decision for every delta spec, keyed by change and capability + - An inclusion or exclusion decision for every delta spec, keyed by change and \`\` - Which included delta specs to apply and in what order - Which delta specs to exclude from sync because their implementation is missing - Rationale (what was found in codebase) @@ -425,14 +425,14 @@ ${STORE_SELECTION_GUIDANCE} 4. **Detect spec conflicts** - Build a map of \`capability -> [changes that touch it]\`: + Build a map keyed by \`\`, the exact path relative to \`specs/\`: \`\`\`text - auth -> [change-a, change-b] <- CONFLICT (2+ changes) - api -> [change-c] <- OK (only 1 change) + identity/user-auth -> [change-a, change-b] <- CONFLICT (2+ changes) + billing/user-auth -> [change-c] <- OK (different full path) \`\`\` - A conflict exists when 2+ selected changes have delta specs for the same capability. + A conflict exists when 2+ selected changes have delta specs for the exact same \`\`. 5. **Resolve conflicts agentically** @@ -450,7 +450,7 @@ ${STORE_SELECTION_GUIDANCE} - If neither implemented -> skip spec sync, warn user d. **Record resolution** for each conflict: - - An inclusion or exclusion decision for every delta spec, keyed by change and capability + - An inclusion or exclusion decision for every delta spec, keyed by change and \`\` - Which included delta specs to apply and in what order - Which delta specs to exclude from sync because their implementation is missing - Rationale (what was found in codebase) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index c8beb978bc..b8951206ac 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -301,6 +301,29 @@ describe('skill templates split parity', () => { expect(content, label).toContain(destination); expect(content, label).not.toContain('specs//spec.md'); } + + const bulkArchiveVariants: Array<[string, string]> = [ + [ + 'bulk archive skill', + generateSkillContent(getBulkArchiveChangeSkillTemplate(), 'PARITY-BASELINE'), + ], + ['bulk archive command', getOpsxBulkArchiveCommandTemplate().content], + ]; + + for (const [label, content] of bulkArchiveVariants) { + expect(content, label).toContain( + 'Build a map keyed by ``, the exact path relative to `specs/`' + ); + expect(content, label).toContain( + 'billing/user-auth -> [change-c] <- OK (different full path)' + ); + expect(content, label).toContain( + 'the exact same ``' + ); + expect(content, label).toContain( + 'keyed by change and ``' + ); + } }); it('generates no workspace-planning residue in any workflow template (4.1)', () => { From 6357f7d28862dad13e35e10d0c067b8bc3e01344 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 11:38:58 -0500 Subject: [PATCH 3/5] fix(workflows): preserve full paths in examples --- skills/openspec-bulk-archive-change/SKILL.md | 10 +++++----- skills/openspec-sync-specs/SKILL.md | 5 +++-- .../workflows/bulk-archive-change.ts | 20 +++++++++---------- src/core/templates/workflows/sync-specs.ts | 10 ++++++---- .../templates/skill-templates-parity.test.ts | 15 ++++++++++++-- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/skills/openspec-bulk-archive-change/SKILL.md b/skills/openspec-bulk-archive-change/SKILL.md index cea1abda61..415b40396e 100644 --- a/skills/openspec-bulk-archive-change/SKILL.md +++ b/skills/openspec-bulk-archive-change/SKILL.md @@ -122,14 +122,14 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig |---------------------|-----------|-------|---------|-----------|--------| | schema-management | Done | 5/5 | 2 delta | None | Ready | | project-config | Done | 3/3 | 1 delta | None | Ready | - | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-oauth | Done | 4/4 | 1 delta | identity/user-auth (!) | Ready* | | add-verify-skill | 1 left | 2/5 | None | None | Warn | ``` For conflicts, show the resolution: ```text * Conflict resolution: - - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + - identity/user-auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) ``` For incomplete changes, show warnings: @@ -210,7 +210,7 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig - Success: archived successfully - Failed: error during archive or spec verification (record error) - Skipped: user chose not to archive (if applicable) - - Sync skipped: for every delta in `excludedDeltas`, report `sync skipped` with the change, capability, and recorded reason. This is distinct from skipping the archive. + - Sync skipped: for every delta in `excludedDeltas`, report `sync skipped` with the change, ``, and recorded reason. This is distinct from skipping the archive. 9. **Display summary** @@ -229,8 +229,8 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig Spec sync summary: - 4 delta specs synced to main specs - - 1 delta spec sync skipped (add-jwt/auth: implementation not found) - - 1 conflict resolved (auth: synced add-oauth, skipped add-jwt) + - 1 delta spec sync skipped (add-jwt, identity/user-auth: implementation not found) + - 1 conflict resolved (identity/user-auth: synced add-oauth, skipped add-jwt) ``` If any failures: diff --git a/skills/openspec-sync-specs/SKILL.md b/skills/openspec-sync-specs/SKILL.md index c2dc11a626..1e0de70fc0 100644 --- a/skills/openspec-sync-specs/SKILL.md +++ b/skills/openspec-sync-specs/SKILL.md @@ -50,8 +50,9 @@ This is an **agent-driven** operation - you will read delta specs and directly e instructions or writing a main spec. Sync every path in `existingOutputPaths` unless the caller narrowed the set. - A caller narrows it by naming an explicit list of delta spec paths to sync — - archive does this inline, and a user can too ("only sync the billing delta"). + A caller narrows it by naming an explicit list of complete entries from + `existingOutputPaths` — archive does this inline, and a user can too + ("only sync `specs/billing/invoices/spec.md`"). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. diff --git a/src/core/templates/workflows/bulk-archive-change.ts b/src/core/templates/workflows/bulk-archive-change.ts index d1ecb44352..cacede2543 100644 --- a/src/core/templates/workflows/bulk-archive-change.ts +++ b/src/core/templates/workflows/bulk-archive-change.ts @@ -124,14 +124,14 @@ ${STORE_SELECTION_GUIDANCE} |---------------------|-----------|-------|---------|-----------|--------| | schema-management | Done | 5/5 | 2 delta | None | Ready | | project-config | Done | 3/3 | 1 delta | None | Ready | - | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-oauth | Done | 4/4 | 1 delta | identity/user-auth (!) | Ready* | | add-verify-skill | 1 left | 2/5 | None | None | Warn | \`\`\` For conflicts, show the resolution: \`\`\`text * Conflict resolution: - - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + - identity/user-auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) \`\`\` For incomplete changes, show warnings: @@ -212,7 +212,7 @@ ${STORE_SELECTION_GUIDANCE} - Success: archived successfully - Failed: error during archive or spec verification (record error) - Skipped: user chose not to archive (if applicable) - - Sync skipped: for every delta in \`excludedDeltas\`, report \`sync skipped\` with the change, capability, and recorded reason. This is distinct from skipping the archive. + - Sync skipped: for every delta in \`excludedDeltas\`, report \`sync skipped\` with the change, \`\`, and recorded reason. This is distinct from skipping the archive. 9. **Display summary** @@ -231,8 +231,8 @@ ${STORE_SELECTION_GUIDANCE} Spec sync summary: - 4 delta specs synced to main specs - - 1 delta spec sync skipped (add-jwt/auth: implementation not found) - - 1 conflict resolved (auth: synced add-oauth, skipped add-jwt) + - 1 delta spec sync skipped (add-jwt, identity/user-auth: implementation not found) + - 1 conflict resolved (identity/user-auth: synced add-oauth, skipped add-jwt) \`\`\` If any failures: @@ -464,14 +464,14 @@ ${STORE_SELECTION_GUIDANCE} |---------------------|-----------|-------|---------|-----------|--------| | schema-management | Done | 5/5 | 2 delta | None | Ready | | project-config | Done | 3/3 | 1 delta | None | Ready | - | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-oauth | Done | 4/4 | 1 delta | identity/user-auth (!) | Ready* | | add-verify-skill | 1 left | 2/5 | None | None | Warn | \`\`\` For conflicts, show the resolution: \`\`\`text * Conflict resolution: - - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + - identity/user-auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) \`\`\` For incomplete changes, show warnings: @@ -552,7 +552,7 @@ ${STORE_SELECTION_GUIDANCE} - Success: archived successfully - Failed: error during archive or spec verification (record error) - Skipped: user chose not to archive (if applicable) - - Sync skipped: for every delta in \`excludedDeltas\`, report \`sync skipped\` with the change, capability, and recorded reason. This is distinct from skipping the archive. + - Sync skipped: for every delta in \`excludedDeltas\`, report \`sync skipped\` with the change, \`\`, and recorded reason. This is distinct from skipping the archive. 9. **Display summary** @@ -571,8 +571,8 @@ ${STORE_SELECTION_GUIDANCE} Spec sync summary: - 4 delta specs synced to main specs - - 1 delta spec sync skipped (add-jwt/auth: implementation not found) - - 1 conflict resolved (auth: synced add-oauth, skipped add-jwt) + - 1 delta spec sync skipped (add-jwt, identity/user-auth: implementation not found) + - 1 conflict resolved (identity/user-auth: synced add-oauth, skipped add-jwt) \`\`\` If any failures: diff --git a/src/core/templates/workflows/sync-specs.ts b/src/core/templates/workflows/sync-specs.ts index 9c1790a3e2..e99e771291 100644 --- a/src/core/templates/workflows/sync-specs.ts +++ b/src/core/templates/workflows/sync-specs.ts @@ -52,8 +52,9 @@ ${STORE_SELECTION_GUIDANCE} instructions or writing a main spec. Sync every path in \`existingOutputPaths\` unless the caller narrowed the set. - A caller narrows it by naming an explicit list of delta spec paths to sync — - archive does this inline, and a user can too ("only sync the billing delta"). + A caller narrows it by naming an explicit list of complete entries from + \`existingOutputPaths\` — archive does this inline, and a user can too + ("only sync \`specs/billing/invoices/spec.md\`"). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. @@ -312,8 +313,9 @@ ${STORE_SELECTION_GUIDANCE} instructions or writing a main spec. Sync every path in \`existingOutputPaths\` unless the caller narrowed the set. - A caller narrows it by naming an explicit list of delta spec paths to sync — - archive does this inline, and a user can too ("only sync the billing delta"). + A caller narrows it by naming an explicit list of complete entries from + \`existingOutputPaths\` — archive does this inline, and a user can too + ("only sync \`specs/billing/invoices/spec.md\`"). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index b8951206ac..7fa6bf1f7e 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -323,6 +323,13 @@ describe('skill templates split parity', () => { expect(content, label).toContain( 'keyed by change and ``' ); + expect(content, label).toContain( + 'identity/user-auth spec: Will apply add-oauth then add-jwt' + ); + expect(content, label).toContain( + 'add-jwt, identity/user-auth: implementation not found' + ); + expect(content, label).not.toContain('add-jwt/auth:'); } }); @@ -468,7 +475,7 @@ describe('skill templates split parity', () => { // The worked example must show the skip, or the agent has no model of // what a partially-synced batch report looks like. expect(content, variant).toContain( - '1 delta spec sync skipped (add-jwt/auth: implementation not found)' + '1 delta spec sync skipped (add-jwt, identity/user-auth: implementation not found)' ); } }); @@ -486,7 +493,7 @@ describe('skill templates split parity', () => { for (const [variant, content] of variants) { expect(content, variant).toContain( - 'A caller narrows it by naming an explicit list of delta spec paths to sync' + 'A caller narrows it by naming an explicit list of complete entries from' ); expect(content, variant).toContain( 'sync only the named paths and leave the remaining delta specs untouched' @@ -497,6 +504,10 @@ describe('skill templates split parity', () => { expect(content, variant).toContain( 'Honor a caller-supplied subset of `existingOutputPaths`' ); + expect(content, variant).toContain( + 'only sync `specs/billing/invoices/spec.md`' + ); + expect(content, variant).not.toContain('only sync the billing delta'); // Step 4 is the operative loop. Narrowing step 3 alone left the loop // still iterating "each path returned by the CLI", which re-widens the From 3447231c7b6adec20c7ac3bce43bbb6488cb7033 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 11:56:50 -0500 Subject: [PATCH 4/5] fix(workflows): clarify nested path inputs --- skills/openspec-onboard/SKILL.md | 8 ++- skills/openspec-sync-specs/SKILL.md | 5 +- src/core/templates/workflows/onboard.ts | 8 ++- src/core/templates/workflows/sync-specs.ts | 10 ++-- .../artifact-graph/instruction-loader.test.ts | 3 + .../templates/skill-templates-parity.test.ts | 57 ++++++++++++++++++- test/core/validation.test.ts | 16 ++++-- 7 files changed, 90 insertions(+), 17 deletions(-) diff --git a/skills/openspec-onboard/SKILL.md b/skills/openspec-onboard/SKILL.md index a6e6fd26c9..0d78693254 100644 --- a/skills/openspec-onboard/SKILL.md +++ b/skills/openspec-onboard/SKILL.md @@ -210,6 +210,11 @@ I'll draft one based on our task. **DO:** Draft the proposal content (don't save yet): +`` is the spec directory relative to `specs/` (for example, +`user-auth` or `identity/user-auth`). Use the exact existing path for modified +capabilities. For new capabilities, follow the project's established spec +organization. + ``` Here's a draft proposal: @@ -226,10 +231,11 @@ Here's a draft proposal: ## Capabilities ### New Capabilities -- ``: [brief description] +- ``: [brief description] ### Modified Capabilities +- ``: [brief description] ## Impact diff --git a/skills/openspec-sync-specs/SKILL.md b/skills/openspec-sync-specs/SKILL.md index 1e0de70fc0..e0a36880a0 100644 --- a/skills/openspec-sync-specs/SKILL.md +++ b/skills/openspec-sync-specs/SKILL.md @@ -51,8 +51,9 @@ This is an **agent-driven** operation - you will read delta specs and directly e Sync every path in `existingOutputPaths` unless the caller narrowed the set. A caller narrows it by naming an explicit list of complete entries from - `existingOutputPaths` — archive does this inline, and a user can too - ("only sync `specs/billing/invoices/spec.md`"). + `existingOutputPaths` — copy those absolute values verbatim. Archive does + this inline, and a user can too (for example, by selecting the entry ending + in `/specs/billing/invoices/spec.md`). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. diff --git a/src/core/templates/workflows/onboard.ts b/src/core/templates/workflows/onboard.ts index f085efda4d..743c71ff8d 100644 --- a/src/core/templates/workflows/onboard.ts +++ b/src/core/templates/workflows/onboard.ts @@ -220,6 +220,11 @@ I'll draft one based on our task. **DO:** Draft the proposal content (don't save yet): +\`\` is the spec directory relative to \`specs/\` (for example, +\`user-auth\` or \`identity/user-auth\`). Use the exact existing path for modified +capabilities. For new capabilities, follow the project's established spec +organization. + \`\`\` Here's a draft proposal: @@ -236,10 +241,11 @@ Here's a draft proposal: ## Capabilities ### New Capabilities -- \`\`: [brief description] +- \`\`: [brief description] ### Modified Capabilities +- \`\`: [brief description] ## Impact diff --git a/src/core/templates/workflows/sync-specs.ts b/src/core/templates/workflows/sync-specs.ts index e99e771291..bedbaa7164 100644 --- a/src/core/templates/workflows/sync-specs.ts +++ b/src/core/templates/workflows/sync-specs.ts @@ -53,8 +53,9 @@ ${STORE_SELECTION_GUIDANCE} Sync every path in \`existingOutputPaths\` unless the caller narrowed the set. A caller narrows it by naming an explicit list of complete entries from - \`existingOutputPaths\` — archive does this inline, and a user can too - ("only sync \`specs/billing/invoices/spec.md\`"). + \`existingOutputPaths\` — copy those absolute values verbatim. Archive does + this inline, and a user can too (for example, by selecting the entry ending + in \`/specs/billing/invoices/spec.md\`). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. @@ -314,8 +315,9 @@ ${STORE_SELECTION_GUIDANCE} Sync every path in \`existingOutputPaths\` unless the caller narrowed the set. A caller narrows it by naming an explicit list of complete entries from - \`existingOutputPaths\` — archive does this inline, and a user can too - ("only sync \`specs/billing/invoices/spec.md\`"). + \`existingOutputPaths\` — copy those absolute values verbatim. Archive does + this inline, and a user can too (for example, by selecting the entry ending + in \`/specs/billing/invoices/spec.md\`). Then sync only the named paths and leave the remaining delta specs untouched: bulk archive excludes a delta whose implementation it could not find, and syncing it anyway would write a main spec the caller deliberately withheld. diff --git a/test/core/artifact-graph/instruction-loader.test.ts b/test/core/artifact-graph/instruction-loader.test.ts index 134e52716e..ce3e153255 100644 --- a/test/core/artifact-graph/instruction-loader.test.ts +++ b/test/core/artifact-graph/instruction-loader.test.ts @@ -18,6 +18,9 @@ describe('instruction-loader', () => { expect(template).toContain('## Why'); expect(template).toContain('## What Changes'); + expect(template).toContain('specs//spec.md'); + expect(template).toContain(''); + expect(template).toContain('exact existing path under openspec/specs/'); }); it('should throw TemplateLoadError for non-existent template', () => { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 7fa6bf1f7e..22a9c4a4f3 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -243,65 +243,103 @@ describe('skill templates split parity', () => { it('preserves nested capability paths in spec-aware workflow guidance (#1459)', () => { const capabilityPathDefinition = '`` is the spec directory relative to `specs/`'; - const pathAwareTemplates: Array<[string, string, string]> = [ + const pathAwareTemplates: Array<[string, string, string, string]> = [ [ 'propose skill', generateSkillContent(getOpsxProposeSkillTemplate(), 'PARITY-BASELINE'), 'specs//spec.md', + "Preserve an existing capability's full path", ], [ 'propose command', getOpsxProposeCommandTemplate().content, 'specs//spec.md', + "Preserve an existing capability's full path", ], [ 'explore skill', generateSkillContent(getExploreSkillTemplate(), 'PARITY-BASELINE'), 'specs//spec.md', + "Preserve an existing capability's full path", ], [ 'explore command', getOpsxExploreCommandTemplate().content, 'specs//spec.md', + "Preserve an existing capability's full path", + ], + [ + 'onboard skill', + generateSkillContent(getOnboardSkillTemplate(), 'PARITY-BASELINE'), + '', + 'Use the exact existing path for modified', + ], + [ + 'onboard command', + getOpsxOnboardCommandTemplate().content, + '', + 'Use the exact existing path for modified', ], [ 'sync skill', generateSkillContent(getSyncSpecsSkillTemplate(), 'PARITY-BASELINE'), '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], [ 'sync command', getOpsxSyncCommandTemplate().content, '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], [ 'archive skill', generateSkillContent(getArchiveChangeSkillTemplate(), 'PARITY-BASELINE'), '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], [ 'archive command', getOpsxArchiveCommandTemplate().content, '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], [ 'bulk archive skill', generateSkillContent(getBulkArchiveChangeSkillTemplate(), 'PARITY-BASELINE'), '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], [ 'bulk archive command', getOpsxBulkArchiveCommandTemplate().content, '/openspec/specs//spec.md', + 'Preserve the full path from each delta spec', ], ]; - for (const [label, content, destination] of pathAwareTemplates) { + for (const [label, content, destination, preservationGuidance] of pathAwareTemplates) { expect(content, label).toContain(capabilityPathDefinition); expect(content, label).toContain(destination); + expect(content, label).toContain(preservationGuidance); expect(content, label).not.toContain('specs//spec.md'); } + const onboardVariants: Array<[string, string]> = [ + [ + 'onboard skill', + generateSkillContent(getOnboardSkillTemplate(), 'PARITY-BASELINE'), + ], + ['onboard command', getOpsxOnboardCommandTemplate().content], + ]; + + for (const [label, content] of onboardVariants) { + expect(content, label).toContain( + '- ``: [brief description]' + ); + expect(content, label).not.toContain(''); + } + const bulkArchiveVariants: Array<[string, string]> = [ [ 'bulk archive skill', @@ -317,6 +355,10 @@ describe('skill templates split parity', () => { expect(content, label).toContain( 'billing/user-auth -> [change-c] <- OK (different full path)' ); + expect(content, label).toContain( + 'identity/user-auth -> [change-a, change-b] <- CONFLICT' + ); + expect(content, label).toContain('identity/user-auth (!)'); expect(content, label).toContain( 'the exact same ``' ); @@ -329,6 +371,12 @@ describe('skill templates split parity', () => { expect(content, label).toContain( 'add-jwt, identity/user-auth: implementation not found' ); + expect(content, label).toContain( + '1 conflict resolved (identity/user-auth: synced add-oauth, skipped add-jwt)' + ); + expect(content, label).not.toContain('\n auth -> [change-a'); + expect(content, label).not.toContain('| auth (!)'); + expect(content, label).not.toContain('(auth: synced'); expect(content, label).not.toContain('add-jwt/auth:'); } }); @@ -505,9 +553,12 @@ describe('skill templates split parity', () => { 'Honor a caller-supplied subset of `existingOutputPaths`' ); expect(content, variant).toContain( - 'only sync `specs/billing/invoices/spec.md`' + 'copy those absolute values verbatim' ); + expect(content, variant).toContain('selecting the entry ending'); + expect(content, variant).toContain('/specs/billing/invoices/spec.md'); expect(content, variant).not.toContain('only sync the billing delta'); + expect(content, variant).not.toContain('only sync `specs/billing/invoices/spec.md`'); // Step 4 is the operative loop. Narrowing step 3 alone left the loop // still iterating "each path returned by the CLI", which re-widens the diff --git a/test/core/validation.test.ts b/test/core/validation.test.ts index e00d3c851a..284f6b5ae7 100644 --- a/test/core/validation.test.ts +++ b/test/core/validation.test.ts @@ -279,9 +279,11 @@ The system SHALL do B. const report = await new Validator().validateSpec(specPath); expect(report.valid).toBe(false); - expect( - report.issues.some(i => i.level === 'ERROR' && i.message.includes('Main spec contains delta header')) - ).toBe(true); + const deltaHeaderIssue = report.issues.find( + i => i.level === 'ERROR' && i.message.includes('Main spec contains delta header') + ); + expect(deltaHeaderIssue).toBeDefined(); + expect(deltaHeaderIssue?.message).toContain('specs//spec.md'); expect( report.issues.some(i => i.level === 'ERROR' && i.message.includes('Requirement header "### Requirement: B" appears outside')) ).toBe(true); @@ -586,9 +588,11 @@ The system SHALL record request metrics. const report = await validator.validateChangeDeltaSpecs(changeDir); expect(report.valid).toBe(false); - expect( - report.issues.some(i => i.message.includes('Delta spec found at specs/spec.md')) - ).toBe(true); + const rootDeltaIssue = report.issues.find( + i => i.message.includes('Delta spec found at specs/spec.md') + ); + expect(rootDeltaIssue).toBeDefined(); + expect(rootDeltaIssue?.message).toContain('specs//spec.md'); // The precise error replaces the generic one, which would otherwise say // "No deltas found" about a file it just named. expect(report.issues.some(i => i.message.includes('No deltas found'))).toBe(false); From 3a5ee27567b5c4f1c667ddafcb0367167c24890c Mon Sep 17 00:00:00 2001 From: Clay Good Date: Tue, 4 Aug 2026 16:37:04 -0500 Subject: [PATCH 5/5] test(workflows): align parity hashes after rebase --- .../templates/skill-templates-parity.test.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index 22a9c4a4f3..b20737d87e 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -37,45 +37,45 @@ import { import { STORE_SELECTION_GUIDANCE } from '../../../src/core/templates/workflows/store-selection.js'; const EXPECTED_FUNCTION_HASHES: Record = { - getExploreSkillTemplate: 'ecfbf91e9e448cd760d02c710b93a41eaeab4a2f26316704afa4ca43859990b6', + getExploreSkillTemplate: 'fec38ba01c5c20695aca0ec7eff78c26e278ead21459cab8ec1562af51053427', getNewChangeSkillTemplate: '935f6335e2d4b7d1bd4f0538c88386350c25e8b16e11b627556262229583ca51', getContinueChangeSkillTemplate: 'ed41e2356af7aad6ef760f60fad19c6843cefe436d8f90084dcba4dbc6bf7272', getApplyChangeSkillTemplate: 'e5fc093637d3100a61acf934553002a5e9f5bccab5110136d7680af4133f7351', getFfChangeSkillTemplate: 'fc2a45a08533ee9c7ab30fdab5f832b7d440070048e2a153f03db1620dc379bb', - getSyncSpecsSkillTemplate: 'f90032dbeb3a647b451139e12624753057018986df000159499dadc2c3d0965a', - getOnboardSkillTemplate: '0b0f9559e21e73a7acfb7e61b403b20080f10ba169d2330c6d55618ce1759a42', - getOpsxExploreCommandTemplate: '1893639d2e95ae41ed537da97e9bbcc0f5f7c95626c94beec58ebb508f05f3b5', + getSyncSpecsSkillTemplate: 'd43b112a3c74bc951b094d220c8e75cca26bb00640d404b78af0752af1ff7bd9', + getOnboardSkillTemplate: 'a9f6134b187ec4f3a5aa6c7c181e51a15fec11b7ac1044a076fdfe79b47fbc80', + getOpsxExploreCommandTemplate: 'e2d470148708a9070675edddd1e783f1c71c96625d08cff4fe7a9994e0d292c0', getOpsxNewCommandTemplate: '08e784e52ac2c146975a874257c589d88e93efbd83dc4d79253c8525f5c3064f', getOpsxContinueCommandTemplate: 'ae964cd00f6ca332fd7f9428a577ade75be279f50431d5f60ece8172e8d1a4b1', getOpsxApplyCommandTemplate: 'd879b0430f756b9dbc5a1a1348a34409b2fcd453eeae7add4bf9f421616c2ad1', getOpsxFfCommandTemplate: '012610f85576a7055dfec2aaabba6bfc245454ce91fb6214587ae9316dc2b864', - getArchiveChangeSkillTemplate: 'b6dac476db882d5e2afea237e298c2aa98ed9f9cacbcf1a5000f00e67e8ca524', - getBulkArchiveChangeSkillTemplate: 'da2bd729048acb64fbac46ab6a45b51174b1b1486f53cfb365499247f0cd4e18', - getOpsxSyncCommandTemplate: '2361cb11e0da0f3ecfded43441edaba8dab6c88ffeb0a217e60c9a3d446bef93', + getArchiveChangeSkillTemplate: '5ef19163f73997fdda1c69dc8bca710c16c50b052b481821d916f4084bb42a64', + getBulkArchiveChangeSkillTemplate: '03cc44a0ce9bdb3ba2668a9d43946596308901600aa29a728c4a71fc76e86de3', + getOpsxSyncCommandTemplate: '361c9e6e063116ae454ecbc9fac90dc44d876f909e2bdd9c4904580a73ce790c', getVerifyChangeSkillTemplate: 'eb2c0f1b46c1be12750965a3a122efd5944d2b25781d714224c6e62a0efdc7fd', - getOpsxArchiveCommandTemplate: 'ce4f2863463a49e206cc6e51ca74e779a36c714e0b9a5233ac4d99535cb29101', - getOpsxOnboardCommandTemplate: 'e04e4ab6c2f25122e6840212b4c22708812c36ceff9ec529c2bb1d1d035429e3', - getOpsxBulkArchiveCommandTemplate: 'fbb4de58ed00861badd93cde9bdd3d7c52f966158a18a660152060076ea9723e', + getOpsxArchiveCommandTemplate: 'e94cbee572231c4a876177bc1cd88b326beeb989c51ee662c703e7b59166f5bb', + getOpsxOnboardCommandTemplate: '3e0da93fb03cec2a8583c47d05359ffefce5e88cb0148ac3686c2ec49a289045', + getOpsxBulkArchiveCommandTemplate: '7d415e6b1ebb5da93bf74bc3d667cf7a5e7f3ec7031d7a61d525b7950ef91863', getOpsxVerifyCommandTemplate: 'ce0ee05b7a6b332e29db2298b9d5a928a1932caf516e35fd88f163154ffd43f4', - getOpsxProposeSkillTemplate: '416200ae0277061405d17d5506243657ee26f7b883abe063844126c497d88f94', - getOpsxProposeCommandTemplate: '8de5ce5fe15c0b13ee1801b6b18cb86dc16ddea66c34223fafb4360232d8424d', + getOpsxProposeSkillTemplate: '16822ea0f2405962a585ebc2ef470cbe7f6990f7fbcd553ad68b145580d393ff', + getOpsxProposeCommandTemplate: '69e1d017765695612bdeb9b3e0ae10986d18f5c3f9305014b79720eef797a951', getFeedbackSkillTemplate: 'd7d83c5f7fc2b92fe8f4588a5bf2d9cb315e4c73ec19bcd5ef28270906319a0d', getUpdateChangeSkillTemplate: 'e50b6cd5d38f0d8974172fd7ebd6e2139f3fe3782c71584d8a61cfdb54edff8e', getOpsxUpdateCommandTemplate: '4f1530486fbe118d9d7d469083c5517b8ec341ed8e92282e0b6c5155fb945bfe', }; const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { - 'openspec-explore': '13c6e3a671258606c43a6c698344a1c77abcd7a8d174193d816043c3451b758f', + 'openspec-explore': '80109dec3abf1505ab1037f7196baac4fcdf175ca954411e8d439e5da881bf62', 'openspec-new-change': '579d432771703f947a331a6ed288bf9c6660ca015fcd376d76f19b6ac7683082', 'openspec-continue-change': '5c34be8194cdb4c5158335e47aece71143e8a22bfb4179dba47fd8aaf436d395', 'openspec-apply-change': '1726319cd4305a47f9c827acaeb84a9de57f7e44aba9ed60869c1758338e18ae', 'openspec-ff-change': '19315644df7c582d920acfb67f3c500ca4e06fccc900265b3ac39621d85f7cdb', - 'openspec-sync-specs': 'dbdc0528c5d59c1a9b3c8b3df01ab2bcf325ad2cb5d47e061c7a65106c058a3e', - 'openspec-archive-change': 'b7432016dd7f56e75da6c21945fa68f6946a4b20abb92788fe633850061e791c', - 'openspec-bulk-archive-change': 'c58e1d319a6587b52202434d5d769c94718aafc0f019276cef04cf8be473b6ce', + 'openspec-sync-specs': '6e85521de10858bb020885eb657aa843e5746b2f09c846aa44545694f456cda9', + 'openspec-archive-change': '019d580a13eee5892cc9233a899919b572a3abfc6a05c1f0aabf9c4ba9bf3d4d', + 'openspec-bulk-archive-change': '6082df91e91fa57fbb88f05ca7834437bfad51561e72657a67a41c355d557646', 'openspec-verify-change': '7cd65897d126f7c948620c0672ca62418620dbcb82ee73d890f758fb666a4ff8', - 'openspec-onboard': '80f39cf33a138aac8e508db25d7af2c9e9bd482f90e414770e806f966dd58c9c', - 'openspec-propose': '48b06cf0fa53be06c84fc3e79729fb16b7b9d8549cbed6d89616eb6ba1f7e325', + 'openspec-onboard': 'c104afb286e7c274a6914cb2042047705e42468a2df16246ff6337692828e12a', + 'openspec-propose': '2414a289c9541b233b80e4a5dcfe75a128bd4c37db421a1f066bf54788afaa97', 'openspec-update-change': '8654fc3ea1eb2f03e1dba3eaf1e8c884b1c71cc949294a070c2f966fb13c8e2a', };