@fission-ai/openspec 1.13.0 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/commands/change.js +12 -1
- package/dist/commands/config.d.ts +9 -0
- package/dist/commands/config.js +161 -22
- package/dist/commands/feedback.js +6 -4
- package/dist/commands/schema.js +30 -9
- package/dist/commands/store.js +5 -2
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.js +63 -1
- package/dist/commands/workflow/instructions.js +69 -22
- package/dist/commands/workflow/new-change.js +20 -0
- package/dist/commands/workflow/shared.d.ts +5 -0
- package/dist/commands/workflow/shared.js +8 -0
- package/dist/commands/workflow/status.d.ts +5 -1
- package/dist/commands/workflow/status.js +42 -5
- package/dist/core/archive.js +260 -77
- package/dist/core/artifact-graph/outputs.d.ts +1 -1
- package/dist/core/artifact-graph/outputs.js +38 -8
- package/dist/core/artifact-graph/schema.d.ts +14 -0
- package/dist/core/artifact-graph/schema.js +52 -0
- package/dist/core/artifact-graph/types.js +12 -1
- package/dist/core/change-status-policy.d.ts +14 -0
- package/dist/core/change-status-policy.js +14 -6
- package/dist/core/command-generation/adapters/continue.js +4 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +1 -1
- package/dist/core/command-generation/adapters/kilocode.js +2 -2
- package/dist/core/command-generation/generator.js +2 -0
- package/dist/core/completion-tip.js +7 -7
- package/dist/core/completions/installers/bash-installer.js +15 -7
- package/dist/core/completions/installers/shell-quote.d.ts +11 -0
- package/dist/core/completions/installers/shell-quote.js +13 -0
- package/dist/core/completions/installers/zsh-installer.js +3 -2
- package/dist/core/config-schema.d.ts +1 -1
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +27 -1
- package/dist/core/global-config.d.ts +30 -2
- package/dist/core/global-config.js +57 -3
- package/dist/core/init.js +22 -6
- package/dist/core/legacy-cleanup.d.ts +5 -1
- package/dist/core/legacy-cleanup.js +207 -14
- package/dist/core/list.js +55 -11
- package/dist/core/migration.js +6 -1
- package/dist/core/parsers/change-parser.d.ts +12 -1
- package/dist/core/parsers/change-parser.js +85 -87
- package/dist/core/parsers/markdown-parser.js +4 -3
- package/dist/core/parsers/requirement-blocks.d.ts +78 -6
- package/dist/core/parsers/requirement-blocks.js +155 -38
- package/dist/core/parsers/requirement-text.d.ts +13 -2
- package/dist/core/parsers/requirement-text.js +34 -7
- package/dist/core/parsers/spec-structure.js +4 -1
- package/dist/core/project-config.d.ts +9 -1
- package/dist/core/project-config.js +11 -2
- package/dist/core/references.d.ts +20 -0
- package/dist/core/references.js +56 -0
- package/dist/core/shared/skill-content-equivalence.js +5 -1
- package/dist/core/shared/skill-generation.js +36 -8
- package/dist/core/shared/tool-detection.js +9 -3
- package/dist/core/specs-apply.d.ts +20 -0
- package/dist/core/specs-apply.js +101 -17
- package/dist/core/store/git.d.ts +33 -0
- package/dist/core/store/git.js +60 -6
- package/dist/core/store/operations.d.ts +1 -1
- package/dist/core/store/operations.js +32 -10
- package/dist/core/store/registry.d.ts +2 -1
- package/dist/core/store/registry.js +5 -1
- package/dist/core/templates/optional-workflow.d.ts +69 -0
- package/dist/core/templates/optional-workflow.js +140 -0
- package/dist/core/templates/workflows/apply-change.js +18 -3
- package/dist/core/templates/workflows/archive-change.js +70 -21
- package/dist/core/templates/workflows/bulk-archive-change.js +85 -21
- package/dist/core/templates/workflows/continue-change.js +14 -4
- package/dist/core/templates/workflows/explore.js +34 -16
- package/dist/core/templates/workflows/ff-change.js +21 -5
- package/dist/core/templates/workflows/new-change.js +15 -3
- package/dist/core/templates/workflows/onboard.js +76 -41
- package/dist/core/templates/workflows/project-root.d.ts +29 -0
- package/dist/core/templates/workflows/project-root.js +38 -0
- package/dist/core/templates/workflows/propose.js +22 -5
- package/dist/core/templates/workflows/sync-specs.js +40 -1
- package/dist/core/templates/workflows/update-change.js +66 -27
- package/dist/core/templates/workflows/verify-change.js +121 -49
- package/dist/core/update.d.ts +12 -0
- package/dist/core/update.js +78 -10
- package/dist/core/validation/purpose-placeholder.js +24 -9
- package/dist/core/validation/task-checkboxes.d.ts +34 -0
- package/dist/core/validation/task-checkboxes.js +181 -0
- package/dist/core/validation/validator.d.ts +53 -1
- package/dist/core/validation/validator.js +160 -45
- package/dist/core/version-check.d.ts +14 -7
- package/dist/core/version-check.js +63 -17
- package/dist/core/worksets.js +6 -4
- package/dist/prompts/searchable-multi-select.d.ts +7 -0
- package/dist/prompts/searchable-multi-select.js +35 -6
- package/dist/telemetry/config.js +14 -2
- package/dist/telemetry/index.d.ts +4 -3
- package/dist/telemetry/index.js +22 -10
- package/dist/telemetry/opt-out.d.ts +17 -0
- package/dist/telemetry/opt-out.js +27 -0
- package/dist/utils/file-system.js +20 -3
- package/dist/utils/line-endings.d.ts +34 -0
- package/dist/utils/line-endings.js +46 -0
- package/dist/utils/nested-change.d.ts +35 -0
- package/dist/utils/nested-change.js +186 -0
- package/dist/utils/spec-discovery.d.ts +20 -0
- package/dist/utils/spec-discovery.js +59 -0
- package/dist/utils/task-progress.d.ts +1 -1
- package/dist/utils/task-progress.js +38 -6
- package/package.json +5 -5
- package/schemas/spec-driven/schema.yaml +18 -3
- package/schemas/spec-driven/templates/design.md +2 -0
- package/schemas/spec-driven/templates/proposal.md +2 -0
- package/schemas/spec-driven/templates/spec.md +2 -0
- package/schemas/spec-driven/templates/tasks.md +2 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
|
|
2
|
+
import { PROJECT_ROOT_GUARD } from './project-root.js';
|
|
2
3
|
export function getVerifyChangeSkillTemplate() {
|
|
3
4
|
return {
|
|
4
5
|
name: 'openspec-verify-change',
|
|
5
|
-
description: 'Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.',
|
|
6
|
+
description: 'Verify implementation matches OpenSpec change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving. Also use when the user says "openspec verify" or "opsx verify".',
|
|
6
7
|
instructions: `Verify that an implementation matches the change artifacts (specs, tasks, design).
|
|
7
8
|
|
|
8
9
|
${STORE_SELECTION_GUIDANCE}
|
|
9
10
|
|
|
11
|
+
${PROJECT_ROOT_GUARD}
|
|
12
|
+
|
|
10
13
|
**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.
|
|
11
14
|
|
|
12
15
|
**Steps**
|
|
@@ -18,7 +21,7 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
18
21
|
- Auto-select if only one active change exists
|
|
19
22
|
- If ambiguous, run \`openspec list --json\` to get available changes and ask the user to select one
|
|
20
23
|
|
|
21
|
-
When prompting, show changes
|
|
24
|
+
When prompting, show all active changes returned by the list, including changes with \`status: "no-tasks"\`.
|
|
22
25
|
Include the schema used for each change if available.
|
|
23
26
|
Mark changes with incomplete tasks as "(In Progress)".
|
|
24
27
|
|
|
@@ -39,7 +42,9 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
39
42
|
openspec instructions apply --change "<name>" --json
|
|
40
43
|
\`\`\`
|
|
41
44
|
|
|
42
|
-
This returns the change directory
|
|
45
|
+
This returns the change directory, \`contextFiles\` (artifact ID -> array of concrete file paths), \`taskTrackingConfigured\`, and top-level \`tasks\` and \`progress\` aggregated from every concrete file matched by the schema's \`apply.tracks\` configuration that could be read. Read all available artifacts from \`contextFiles\`.
|
|
46
|
+
|
|
47
|
+
Treat apply \`state\` and \`instruction\` as context, not a verification verdict. Do not implement tasks or archive the change during verification.
|
|
43
48
|
|
|
44
49
|
4. **Initialize verification report structure**
|
|
45
50
|
|
|
@@ -50,30 +55,59 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
50
55
|
|
|
51
56
|
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
|
52
57
|
|
|
58
|
+
Verification is advisory. Respect intentional omissions such as \`skip_specs: true\`, optional design documents, and schemas without task tracking. Do not require or invent optional or intentionally omitted artifacts to obtain a clean report. \`Not verified\` describes a limit of this report, not a new archive prerequisite. Archive retains its own checks and user-confirmation behavior.
|
|
59
|
+
|
|
60
|
+
Mark checks the schema does not define, or artifacts the status reports as intentionally skipped, as **Not applicable**. The correctness checks of a change whose readable delta specs contain REMOVED or RENAMED requirements but no ADDED or MODIFIED requirements are also **Not applicable** (see step 6). Exclude them from skipped-check counts and the archive-readiness assessment. Reserve **Not verified** for applicable checks whose evidence is missing or unusable.
|
|
61
|
+
|
|
62
|
+
If only task evidence is available for applicable checks, verify task completion only and mark the remaining applicable checks, including **Code Pattern Consistency**, as not verified with the reason "Only task evidence available".
|
|
63
|
+
|
|
64
|
+
If artifacts cannot be read or contain no usable requirements, scenarios, or design decisions, mark the affected checks as not verified with the specific reason. Continue checks supported by the remaining evidence, but a partially checked input set is not a fully verified check. Missing requirements affect Spec Coverage and Requirement Implementation Mapping; missing scenarios affect Scenario Coverage; missing design decisions affect Design Adherence.
|
|
65
|
+
|
|
53
66
|
5. **Verify Completeness**
|
|
54
67
|
|
|
55
68
|
**Task Completion**:
|
|
56
|
-
- If \`
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
- If
|
|
60
|
-
|
|
69
|
+
- If \`taskTrackingConfigured\` is false, report **Task Completion** as not applicable. Do not treat empty \`tasks\` as missing evidence.
|
|
70
|
+
- Otherwise, use the top-level \`tasks\` and \`progress\` fields. They already aggregate every readable concrete file matched by \`apply.tracks\`, regardless of the tracked artifact's ID; do not infer tracking from a \`contextFiles\` key.
|
|
71
|
+
- If \`unavailableTrackingFiles\` is nonempty, mark **Task Completion** as not verified and include every unavailable path and reason. Continue using any readable task evidence, but do not infer completion from the partial \`tasks\` and \`progress\` fields.
|
|
72
|
+
- If \`taskTrackingConfigured\` is true and \`tasks\` is empty, mark **Task Completion** as not verified and record the reason from apply \`state\` and \`instruction\`. Nonzero totals alone do not establish evaluable task descriptions.
|
|
73
|
+
- Report complete vs total tasks from \`progress\`.
|
|
74
|
+
- If \`progress.remaining\` is greater than 0:
|
|
75
|
+
- Add CRITICAL issue for each listed incomplete task. If the remaining count exceeds the listed incomplete tasks, also report the incomplete checkboxes without descriptions and recommend adding descriptions and completing them. Do not infer completion from the listed tasks alone.
|
|
61
76
|
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
|
62
77
|
|
|
63
78
|
**Spec Coverage**:
|
|
79
|
+
- If status marks the spec artifact skipped by \`skip_specs: true\`, or the schema defines no spec artifact, report the spec-dependent checks as not applicable.
|
|
80
|
+
- Otherwise, \`contextFiles\` is keyed by artifact id, and artifact ids come from the active schema. If \`contextFiles.specs\` is absent or empty, mark **Spec Coverage**, **Requirement Implementation Mapping**, and **Scenario Coverage** as not verified; do not treat any of them as clean.
|
|
64
81
|
- If delta specs exist in \`contextFiles.specs\`:
|
|
65
|
-
- Extract all requirements (marked with "### Requirement:")
|
|
66
|
-
- For each requirement:
|
|
82
|
+
- Extract all requirements (marked with "### Requirement:", or listed as \`FROM:\`/\`TO:\` pairs under \`## RENAMED Requirements\`) and note the delta section each one sits under: \`## ADDED\`, \`## MODIFIED\`, \`## REMOVED\`, or \`## RENAMED Requirements\`. The section decides what the check looks for.
|
|
83
|
+
- For each ADDED or MODIFIED requirement (for MODIFIED, check the text in the delta, not the old wording):
|
|
67
84
|
- Search codebase for keywords related to the requirement
|
|
68
85
|
- Assess if implementation likely exists
|
|
69
|
-
- If requirements appear unimplemented:
|
|
86
|
+
- If ADDED or MODIFIED requirements appear unimplemented:
|
|
70
87
|
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
|
71
88
|
- Recommendation: "Implement requirement X: <description>"
|
|
89
|
+
- For each REMOVED requirement, the change asks for the behavior to be gone, so invert the check:
|
|
90
|
+
- Search codebase for the removed behavior. Matches in \`openspec/\` artifacts or docs, or in code that serves only the Migration note or an ADDED requirement, are not evidence by themselves. Report any code path that still delivers the removed behavior, including one shared with an ADDED requirement.
|
|
91
|
+
- Finding no implementation is the expected result. Never report a REMOVED requirement as "Requirement not found" or recommend implementing it.
|
|
92
|
+
- If the behavior is still present:
|
|
93
|
+
- Add CRITICAL issue: "Removed requirement still implemented: <requirement name>"
|
|
94
|
+
- Recommendation: "Remove the remaining implementation at <file>:<lines>, following the requirement's Migration note if it has one"
|
|
95
|
+
- For each RENAMED entry (\`FROM:\`/\`TO:\`), the name changes but the behavior stays, so check the TO requirement for that unchanged behavior:
|
|
96
|
+
- Do not report the FROM name as missing, and do not require code symbols, identifiers, or file names to be renamed.
|
|
97
|
+
- If the TO name also appears under MODIFIED, its behavior is checked there against the MODIFIED text; skip it here.
|
|
98
|
+
- Otherwise, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec: the requirement under the FROM name, or under the TO name only when the FROM name is absent because the main spec is already synced. Its body and scenarios are the evidence for the behavior the TO requirement keeps.
|
|
99
|
+
- Search codebase for that behavior and assess if it is still implemented.
|
|
100
|
+
- If it appears unimplemented:
|
|
101
|
+
- Add CRITICAL issue: "Renamed requirement not found: <TO name>"
|
|
102
|
+
- Recommendation: "Restore the behavior of <TO name> (renamed from <FROM name>); a rename must not change behavior"
|
|
103
|
+
- If the baseline requirement cannot be found or read, mark **Spec Coverage** as not verified for that entry with the reason. Never count an unchecked rename as passing.
|
|
72
104
|
|
|
73
105
|
6. **Verify Correctness**
|
|
74
106
|
|
|
107
|
+
If the delta specs are readable and contain at least one REMOVED or RENAMED requirement but no ADDED or MODIFIED requirements (the change only removes or renames requirements), report **Requirement Implementation Mapping** and **Scenario Coverage** as **Not applicable**. The REMOVED and RENAMED checks under Spec Coverage are the evidence for such a change (each RENAMED entry is checked there against its baseline behavior), so do not mark these two checks as not verified. A delta spec with no parseable requirements at all is unusable evidence, not a removal-only change: mark these checks as not verified.
|
|
108
|
+
|
|
75
109
|
**Requirement Implementation Mapping**:
|
|
76
|
-
- For each requirement from delta specs:
|
|
110
|
+
- For each ADDED or MODIFIED requirement from delta specs (REMOVED entries, and RENAMED entries without a MODIFIED block, were settled under Spec Coverage):
|
|
77
111
|
- Search codebase for implementation evidence
|
|
78
112
|
- If found, note file paths and line ranges
|
|
79
113
|
- Assess if implementation matches requirement intent
|
|
@@ -82,26 +116,29 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
82
116
|
- Recommendation: "Review <file>:<lines> against requirement X"
|
|
83
117
|
|
|
84
118
|
**Scenario Coverage**:
|
|
85
|
-
- For each scenario in delta specs (marked with "#### Scenario:"):
|
|
119
|
+
- For each scenario under an ADDED or MODIFIED requirement in delta specs (marked with "#### Scenario:"):
|
|
86
120
|
- Check if conditions are handled in code
|
|
87
121
|
- Check if tests exist covering the scenario
|
|
88
122
|
- If scenario appears uncovered:
|
|
89
123
|
- Add WARNING: "Scenario not covered: <scenario name>"
|
|
90
124
|
- Recommendation: "Add test or implementation for scenario: <description>"
|
|
125
|
+
- Skip scenarios under a REMOVED requirement; that behavior is meant to be gone.
|
|
91
126
|
|
|
92
127
|
7. **Verify Coherence**
|
|
93
128
|
|
|
94
129
|
**Design Adherence**:
|
|
130
|
+
- If the schema defines no design artifact, report **Design Adherence** as not applicable.
|
|
95
131
|
- If \`contextFiles.design\` exists:
|
|
96
132
|
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
|
97
133
|
- Verify implementation follows those decisions
|
|
98
134
|
- If contradiction detected:
|
|
99
135
|
- Add WARNING: "Design decision not followed: <decision>"
|
|
100
136
|
- Recommendation: "Update implementation or revise design.md to match reality"
|
|
101
|
-
-
|
|
137
|
+
- Otherwise, if \`contextFiles.design\` is absent or empty: mark **Design Adherence** as not verified. With other supporting artifacts, **Code Pattern Consistency** still runs; the task-only case remains limited to task completion.
|
|
102
138
|
|
|
103
139
|
**Code Pattern Consistency**:
|
|
104
|
-
-
|
|
140
|
+
- If implementation changes cannot be identified, mark **Code Pattern Consistency** as not verified and explain the missing evidence.
|
|
141
|
+
- Otherwise, review new code for consistency with project patterns
|
|
105
142
|
- Check file naming, directory structure, coding style
|
|
106
143
|
- If significant deviations found:
|
|
107
144
|
- Add SUGGESTION: "Code pattern deviation: <details>"
|
|
@@ -121,11 +158,15 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
121
158
|
| Coherence | Followed/Issues |
|
|
122
159
|
\`\`\`
|
|
123
160
|
|
|
161
|
+
In each Status cell, report the results of checks that ran and \`Not verified (<reason>)\` for every skipped check. If all checks in a dimension were skipped, start the cell with \`Not verified\`. Never score a skipped check as passing. Treat every not verified or partially verified check as skipped in the final assessment. Count only ADDED and MODIFIED requirements in N, and report REMOVED and RENAMED requirements separately (for example, "1 removal confirmed, 1 rename verified"). For a change that only removes or renames requirements, the Correctness cell reads \`Not applicable (no ADDED or MODIFIED requirements)\`.
|
|
162
|
+
|
|
124
163
|
**Issues by Priority**:
|
|
125
164
|
|
|
126
165
|
1. **CRITICAL** (Must fix before archive):
|
|
127
166
|
- Incomplete tasks
|
|
128
167
|
- Missing requirement implementations
|
|
168
|
+
- Removed requirements still implemented
|
|
169
|
+
- Renamed requirements whose behavior is no longer implemented
|
|
129
170
|
- Each with specific, actionable recommendation
|
|
130
171
|
|
|
131
172
|
2. **WARNING** (Should fix):
|
|
@@ -139,9 +180,12 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
139
180
|
- Each with specific recommendation
|
|
140
181
|
|
|
141
182
|
**Final Assessment**:
|
|
142
|
-
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
|
143
|
-
- If
|
|
144
|
-
- If
|
|
183
|
+
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving." If any check was skipped, also name every skipped check and its reason.
|
|
184
|
+
- If no CRITICAL issues, one or more warnings, and no checks were skipped: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
|
185
|
+
- If only suggestions and no checks were skipped: "No critical issues or warnings. Z suggestion(s) to consider. Ready for archive (with noted improvements)."
|
|
186
|
+
- If no issues and no checks were skipped: "All checks passed. Ready for archive."
|
|
187
|
+
- If any check was skipped and there are no CRITICAL issues: do not claim readiness. Say "No critical issues found in the checks that ran. <check(s)> not verified: <reason>." Include the warning count when nonzero.
|
|
188
|
+
- Include the suggestion count when nonzero in every final assessment.
|
|
145
189
|
|
|
146
190
|
**Verification Heuristics**
|
|
147
191
|
|
|
@@ -151,13 +195,6 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
151
195
|
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
|
152
196
|
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
|
153
197
|
|
|
154
|
-
**Graceful Degradation**
|
|
155
|
-
|
|
156
|
-
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
|
157
|
-
- If tasks + specs exist: verify completeness and correctness, skip design
|
|
158
|
-
- If full artifacts: verify all three dimensions
|
|
159
|
-
- Always note which checks were skipped and why
|
|
160
|
-
|
|
161
198
|
**Output Format**
|
|
162
199
|
|
|
163
200
|
Use clear markdown with:
|
|
@@ -181,6 +218,8 @@ export function getOpsxVerifyCommandTemplate() {
|
|
|
181
218
|
|
|
182
219
|
${STORE_SELECTION_GUIDANCE}
|
|
183
220
|
|
|
221
|
+
${PROJECT_ROOT_GUARD}
|
|
222
|
+
|
|
184
223
|
**Input**: Optionally specify a change name after \`/opsx:verify\` (e.g., \`/opsx:verify add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
|
185
224
|
|
|
186
225
|
**Steps**
|
|
@@ -192,7 +231,7 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
192
231
|
- Auto-select if only one active change exists
|
|
193
232
|
- If ambiguous, run \`openspec list --json\` to get available changes and ask the user to select one
|
|
194
233
|
|
|
195
|
-
When prompting, show changes
|
|
234
|
+
When prompting, show all active changes returned by the list, including changes with \`status: "no-tasks"\`.
|
|
196
235
|
Include the schema used for each change if available.
|
|
197
236
|
Mark changes with incomplete tasks as "(In Progress)".
|
|
198
237
|
|
|
@@ -213,7 +252,9 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
213
252
|
openspec instructions apply --change "<name>" --json
|
|
214
253
|
\`\`\`
|
|
215
254
|
|
|
216
|
-
This returns the change directory
|
|
255
|
+
This returns the change directory, \`contextFiles\` (artifact ID -> array of concrete file paths), \`taskTrackingConfigured\`, and top-level \`tasks\` and \`progress\` aggregated from every concrete file matched by the schema's \`apply.tracks\` configuration that could be read. Read all available artifacts from \`contextFiles\`.
|
|
256
|
+
|
|
257
|
+
Treat apply \`state\` and \`instruction\` as context, not a verification verdict. Do not implement tasks or archive the change during verification.
|
|
217
258
|
|
|
218
259
|
4. **Initialize verification report structure**
|
|
219
260
|
|
|
@@ -224,30 +265,58 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
224
265
|
|
|
225
266
|
Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.
|
|
226
267
|
|
|
268
|
+
Verification is advisory. Respect intentional omissions such as \`skip_specs: true\`, optional design documents, and schemas without task tracking. Do not require or invent optional or intentionally omitted artifacts to obtain a clean report. \`Not verified\` describes a limit of this report, not a new archive prerequisite. Archive retains its own checks and user-confirmation behavior.
|
|
269
|
+
Mark checks the schema does not define, or artifacts the status reports as intentionally skipped, as **Not applicable**. The correctness checks of a change whose readable delta specs contain REMOVED or RENAMED requirements but no ADDED or MODIFIED requirements are also **Not applicable** (see step 6). Exclude them from skipped-check counts and the archive-readiness assessment. Reserve **Not verified** for applicable checks whose evidence is missing or unusable.
|
|
270
|
+
|
|
271
|
+
If only task evidence is available for applicable checks, verify task completion only and mark the remaining applicable checks, including **Code Pattern Consistency**, as not verified with the reason "Only task evidence available".
|
|
272
|
+
|
|
273
|
+
If artifacts cannot be read or contain no usable requirements, scenarios, or design decisions, mark the affected checks as not verified with the specific reason. Continue checks supported by the remaining evidence, but a partially checked input set is not a fully verified check. Missing requirements affect Spec Coverage and Requirement Implementation Mapping; missing scenarios affect Scenario Coverage; missing design decisions affect Design Adherence.
|
|
274
|
+
|
|
227
275
|
5. **Verify Completeness**
|
|
228
276
|
|
|
229
277
|
**Task Completion**:
|
|
230
|
-
- If \`
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
- If
|
|
234
|
-
|
|
278
|
+
- If \`taskTrackingConfigured\` is false, report **Task Completion** as not applicable. Do not treat empty \`tasks\` as missing evidence.
|
|
279
|
+
- Otherwise, use the top-level \`tasks\` and \`progress\` fields. They already aggregate every readable concrete file matched by \`apply.tracks\`, regardless of the tracked artifact's ID; do not infer tracking from a \`contextFiles\` key.
|
|
280
|
+
- If \`unavailableTrackingFiles\` is nonempty, mark **Task Completion** as not verified and include every unavailable path and reason. Continue using any readable task evidence, but do not infer completion from the partial \`tasks\` and \`progress\` fields.
|
|
281
|
+
- If \`taskTrackingConfigured\` is true and \`tasks\` is empty, mark **Task Completion** as not verified and record the reason from apply \`state\` and \`instruction\`. Nonzero totals alone do not establish evaluable task descriptions.
|
|
282
|
+
- Report complete vs total tasks from \`progress\`.
|
|
283
|
+
- If \`progress.remaining\` is greater than 0:
|
|
284
|
+
- Add CRITICAL issue for each listed incomplete task. If the remaining count exceeds the listed incomplete tasks, also report the incomplete checkboxes without descriptions and recommend adding descriptions and completing them. Do not infer completion from the listed tasks alone.
|
|
235
285
|
- Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
|
|
236
286
|
|
|
237
287
|
**Spec Coverage**:
|
|
288
|
+
- If status marks the spec artifact skipped by \`skip_specs: true\`, or the schema defines no spec artifact, report the spec-dependent checks as not applicable.
|
|
289
|
+
- Otherwise, \`contextFiles\` is keyed by artifact id, and artifact ids come from the active schema. If \`contextFiles.specs\` is absent or empty, mark **Spec Coverage**, **Requirement Implementation Mapping**, and **Scenario Coverage** as not verified; do not treat any of them as clean.
|
|
238
290
|
- If delta specs exist in \`contextFiles.specs\`:
|
|
239
|
-
- Extract all requirements (marked with "### Requirement:")
|
|
240
|
-
- For each requirement:
|
|
291
|
+
- Extract all requirements (marked with "### Requirement:", or listed as \`FROM:\`/\`TO:\` pairs under \`## RENAMED Requirements\`) and note the delta section each one sits under: \`## ADDED\`, \`## MODIFIED\`, \`## REMOVED\`, or \`## RENAMED Requirements\`. The section decides what the check looks for.
|
|
292
|
+
- For each ADDED or MODIFIED requirement (for MODIFIED, check the text in the delta, not the old wording):
|
|
241
293
|
- Search codebase for keywords related to the requirement
|
|
242
294
|
- Assess if implementation likely exists
|
|
243
|
-
- If requirements appear unimplemented:
|
|
295
|
+
- If ADDED or MODIFIED requirements appear unimplemented:
|
|
244
296
|
- Add CRITICAL issue: "Requirement not found: <requirement name>"
|
|
245
297
|
- Recommendation: "Implement requirement X: <description>"
|
|
298
|
+
- For each REMOVED requirement, the change asks for the behavior to be gone, so invert the check:
|
|
299
|
+
- Search codebase for the removed behavior. Matches in \`openspec/\` artifacts or docs, or in code that serves only the Migration note or an ADDED requirement, are not evidence by themselves. Report any code path that still delivers the removed behavior, including one shared with an ADDED requirement.
|
|
300
|
+
- Finding no implementation is the expected result. Never report a REMOVED requirement as "Requirement not found" or recommend implementing it.
|
|
301
|
+
- If the behavior is still present:
|
|
302
|
+
- Add CRITICAL issue: "Removed requirement still implemented: <requirement name>"
|
|
303
|
+
- Recommendation: "Remove the remaining implementation at <file>:<lines>, following the requirement's Migration note if it has one"
|
|
304
|
+
- For each RENAMED entry (\`FROM:\`/\`TO:\`), the name changes but the behavior stays, so check the TO requirement for that unchanged behavior:
|
|
305
|
+
- Do not report the FROM name as missing, and do not require code symbols, identifiers, or file names to be renamed.
|
|
306
|
+
- If the TO name also appears under MODIFIED, its behavior is checked there against the MODIFIED text; skip it here.
|
|
307
|
+
- Otherwise, read the baseline requirement in the main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`, using the same capability path as the delta spec: the requirement under the FROM name, or under the TO name only when the FROM name is absent because the main spec is already synced. Its body and scenarios are the evidence for the behavior the TO requirement keeps.
|
|
308
|
+
- Search codebase for that behavior and assess if it is still implemented.
|
|
309
|
+
- If it appears unimplemented:
|
|
310
|
+
- Add CRITICAL issue: "Renamed requirement not found: <TO name>"
|
|
311
|
+
- Recommendation: "Restore the behavior of <TO name> (renamed from <FROM name>); a rename must not change behavior"
|
|
312
|
+
- If the baseline requirement cannot be found or read, mark **Spec Coverage** as not verified for that entry with the reason. Never count an unchecked rename as passing.
|
|
246
313
|
|
|
247
314
|
6. **Verify Correctness**
|
|
248
315
|
|
|
316
|
+
If the delta specs are readable and contain at least one REMOVED or RENAMED requirement but no ADDED or MODIFIED requirements (the change only removes or renames requirements), report **Requirement Implementation Mapping** and **Scenario Coverage** as **Not applicable**. The REMOVED and RENAMED checks under Spec Coverage are the evidence for such a change (each RENAMED entry is checked there against its baseline behavior), so do not mark these two checks as not verified. A delta spec with no parseable requirements at all is unusable evidence, not a removal-only change: mark these checks as not verified.
|
|
317
|
+
|
|
249
318
|
**Requirement Implementation Mapping**:
|
|
250
|
-
- For each requirement from delta specs:
|
|
319
|
+
- For each ADDED or MODIFIED requirement from delta specs (REMOVED entries, and RENAMED entries without a MODIFIED block, were settled under Spec Coverage):
|
|
251
320
|
- Search codebase for implementation evidence
|
|
252
321
|
- If found, note file paths and line ranges
|
|
253
322
|
- Assess if implementation matches requirement intent
|
|
@@ -256,26 +325,29 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
256
325
|
- Recommendation: "Review <file>:<lines> against requirement X"
|
|
257
326
|
|
|
258
327
|
**Scenario Coverage**:
|
|
259
|
-
- For each scenario in delta specs (marked with "#### Scenario:"):
|
|
328
|
+
- For each scenario under an ADDED or MODIFIED requirement in delta specs (marked with "#### Scenario:"):
|
|
260
329
|
- Check if conditions are handled in code
|
|
261
330
|
- Check if tests exist covering the scenario
|
|
262
331
|
- If scenario appears uncovered:
|
|
263
332
|
- Add WARNING: "Scenario not covered: <scenario name>"
|
|
264
333
|
- Recommendation: "Add test or implementation for scenario: <description>"
|
|
334
|
+
- Skip scenarios under a REMOVED requirement; that behavior is meant to be gone.
|
|
265
335
|
|
|
266
336
|
7. **Verify Coherence**
|
|
267
337
|
|
|
268
338
|
**Design Adherence**:
|
|
339
|
+
- If the schema defines no design artifact, report **Design Adherence** as not applicable.
|
|
269
340
|
- If \`contextFiles.design\` exists:
|
|
270
341
|
- Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
|
|
271
342
|
- Verify implementation follows those decisions
|
|
272
343
|
- If contradiction detected:
|
|
273
344
|
- Add WARNING: "Design decision not followed: <decision>"
|
|
274
345
|
- Recommendation: "Update implementation or revise design.md to match reality"
|
|
275
|
-
-
|
|
346
|
+
- Otherwise, if \`contextFiles.design\` is absent or empty: mark **Design Adherence** as not verified. With other supporting artifacts, **Code Pattern Consistency** still runs; the task-only case remains limited to task completion.
|
|
276
347
|
|
|
277
348
|
**Code Pattern Consistency**:
|
|
278
|
-
-
|
|
349
|
+
- If implementation changes cannot be identified, mark **Code Pattern Consistency** as not verified and explain the missing evidence.
|
|
350
|
+
- Otherwise, review new code for consistency with project patterns
|
|
279
351
|
- Check file naming, directory structure, coding style
|
|
280
352
|
- If significant deviations found:
|
|
281
353
|
- Add SUGGESTION: "Code pattern deviation: <details>"
|
|
@@ -295,11 +367,15 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
295
367
|
| Coherence | Followed/Issues |
|
|
296
368
|
\`\`\`
|
|
297
369
|
|
|
370
|
+
In each Status cell, report the results of checks that ran and \`Not verified (<reason>)\` for every skipped check. If all checks in a dimension were skipped, start the cell with \`Not verified\`. Never score a skipped check as passing. Treat every not verified or partially verified check as skipped in the final assessment. Count only ADDED and MODIFIED requirements in N, and report REMOVED and RENAMED requirements separately (for example, "1 removal confirmed, 1 rename verified"). For a change that only removes or renames requirements, the Correctness cell reads \`Not applicable (no ADDED or MODIFIED requirements)\`.
|
|
371
|
+
|
|
298
372
|
**Issues by Priority**:
|
|
299
373
|
|
|
300
374
|
1. **CRITICAL** (Must fix before archive):
|
|
301
375
|
- Incomplete tasks
|
|
302
376
|
- Missing requirement implementations
|
|
377
|
+
- Removed requirements still implemented
|
|
378
|
+
- Renamed requirements whose behavior is no longer implemented
|
|
303
379
|
- Each with specific, actionable recommendation
|
|
304
380
|
|
|
305
381
|
2. **WARNING** (Should fix):
|
|
@@ -313,9 +389,12 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
313
389
|
- Each with specific recommendation
|
|
314
390
|
|
|
315
391
|
**Final Assessment**:
|
|
316
|
-
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving."
|
|
317
|
-
- If
|
|
318
|
-
- If
|
|
392
|
+
- If CRITICAL issues: "X critical issue(s) found. Fix before archiving." If any check was skipped, also name every skipped check and its reason.
|
|
393
|
+
- If no CRITICAL issues, one or more warnings, and no checks were skipped: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."
|
|
394
|
+
- If only suggestions and no checks were skipped: "No critical issues or warnings. Z suggestion(s) to consider. Ready for archive (with noted improvements)."
|
|
395
|
+
- If no issues and no checks were skipped: "All checks passed. Ready for archive."
|
|
396
|
+
- If any check was skipped and there are no CRITICAL issues: do not claim readiness. Say "No critical issues found in the checks that ran. <check(s)> not verified: <reason>." Include the warning count when nonzero.
|
|
397
|
+
- Include the suggestion count when nonzero in every final assessment.
|
|
319
398
|
|
|
320
399
|
**Verification Heuristics**
|
|
321
400
|
|
|
@@ -325,13 +404,6 @@ ${STORE_SELECTION_GUIDANCE}
|
|
|
325
404
|
- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL
|
|
326
405
|
- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable
|
|
327
406
|
|
|
328
|
-
**Graceful Degradation**
|
|
329
|
-
|
|
330
|
-
- If only tasks.md exists: verify task completion only, skip spec/design checks
|
|
331
|
-
- If tasks + specs exist: verify completeness and correctness, skip design
|
|
332
|
-
- If full artifacts: verify all three dimensions
|
|
333
|
-
- Always note which checks were skipped and why
|
|
334
|
-
|
|
335
407
|
**Output Format**
|
|
336
408
|
|
|
337
409
|
Use clear markdown with:
|
package/dist/core/update.d.ts
CHANGED
|
@@ -29,6 +29,18 @@ export declare class UpdateCommand {
|
|
|
29
29
|
*/
|
|
30
30
|
execute(projectPath: string): Promise<void>;
|
|
31
31
|
private syncCopilotCloudFiles;
|
|
32
|
+
/**
|
|
33
|
+
* Tools whose SKILL.md bodies no longer match what this CLI generates.
|
|
34
|
+
*
|
|
35
|
+
* Skill freshness was decided solely by the `generatedBy:` line, so a body
|
|
36
|
+
* edited after generation — a "helpful" PR touching `.claude/skills/**`, a
|
|
37
|
+
* dotfile sync, another agent — left `update` reporting the install healthy.
|
|
38
|
+
* Command files never had that gap: `areCommandFilesUpToDate` content-
|
|
39
|
+
* compares them, and the same comparison belongs on the higher-authority
|
|
40
|
+
* surface. A missing skill file is left to the profile-sync check, which
|
|
41
|
+
* already knows what a partial install means.
|
|
42
|
+
*/
|
|
43
|
+
private findToolsWithDriftedSkills;
|
|
32
44
|
/**
|
|
33
45
|
* Display message when all tools are up to date.
|
|
34
46
|
*/
|
package/dist/core/update.js
CHANGED
|
@@ -13,7 +13,7 @@ import { FileSystemUtils } from '../utils/file-system.js';
|
|
|
13
13
|
import { getSkillReferenceTransformer, getTransformerForTool, transformToSkillReferences } from '../utils/command-references.js';
|
|
14
14
|
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
|
|
15
15
|
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
16
|
-
import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, formatIdeRestart, } from './shared/index.js';
|
|
16
|
+
import { getToolSkillStatus, getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, formatIdeRestart, } from './shared/index.js';
|
|
17
17
|
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatDeferredGlobalPromptSummary, formatCleanupSummary, formatDetectionSummary, getLegacyGlobalPromptMatches, getLegacyWorkflowIdsForTool, getToolsFromLegacyArtifacts, omitGlobalLegacyPromptFiles, omitToolLegacyArtifacts, pickGlobalLegacyPromptFiles, } from './legacy-cleanup.js';
|
|
18
18
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
19
|
import { getGlobalConfig } from './global-config.js';
|
|
@@ -27,6 +27,19 @@ import { resolveSharedSkillWriters, writeSharedSkillTarget, } from './shared-ski
|
|
|
27
27
|
import { includesGitHubCopilot, writeCopilotCloudFiles, removeCopilotCloudFiles, isCopilotCloudEnabled, readCopilotCloudOptIn, findUnmanagedCloudFiles } from './github-copilot/cloud-agent.js';
|
|
28
28
|
const require = createRequire(import.meta.url);
|
|
29
29
|
const { version: OPENSPEC_VERSION } = require('../../package.json');
|
|
30
|
+
function throwIfUpdateFailed(failedTools) {
|
|
31
|
+
if (failedTools.length > 0) {
|
|
32
|
+
throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checkout artifacts that are not real content drift: a UTF-8 BOM and the CRLF
|
|
37
|
+
* line endings a Windows clone with `core.autocrlf` reintroduces on every
|
|
38
|
+
* checkout of committed generated files.
|
|
39
|
+
*/
|
|
40
|
+
function normalizeGeneratedFile(content) {
|
|
41
|
+
return content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
|
42
|
+
}
|
|
30
43
|
/**
|
|
31
44
|
* Scans installed workflow artifacts (skills and managed commands) across all configured tools.
|
|
32
45
|
* Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
|
|
@@ -79,7 +92,7 @@ export class UpdateCommand {
|
|
|
79
92
|
const desiredWorkflows = profileWorkflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
80
93
|
// 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config
|
|
81
94
|
const legacyUpgrade = await this.handleLegacyCleanup(resolvedProjectPath, desiredWorkflows, delivery);
|
|
82
|
-
const { newlyConfiguredTools, workflowOverrides: legacyWorkflowOverrides, deferredGlobalCleanup, } = legacyUpgrade;
|
|
95
|
+
const { newlyConfiguredTools, workflowOverrides: legacyWorkflowOverrides, deferredGlobalCleanup, failedTools: legacyUpgradeFailures = [], } = legacyUpgrade;
|
|
83
96
|
// 5. Find configured tools
|
|
84
97
|
const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath);
|
|
85
98
|
const configuredAndNewTools = [...new Set([...configuredTools, ...newlyConfiguredTools])];
|
|
@@ -87,6 +100,7 @@ export class UpdateCommand {
|
|
|
87
100
|
if (deferredGlobalCleanup) {
|
|
88
101
|
await this.performDeferredGlobalPromptCleanup(resolvedProjectPath, deferredGlobalCleanup);
|
|
89
102
|
}
|
|
103
|
+
throwIfUpdateFailed(legacyUpgradeFailures);
|
|
90
104
|
if (declinedMigrations.length > 0) {
|
|
91
105
|
// Not an unconfigured project — a configured one the user chose to
|
|
92
106
|
// leave in its former directory. Saying "run init" would be wrong.
|
|
@@ -120,9 +134,11 @@ export class UpdateCommand {
|
|
|
120
134
|
})
|
|
121
135
|
.map((s) => s.toolId);
|
|
122
136
|
const toolsNeedingConfigSync = getToolsNeedingProfileSync(resolvedProjectPath, desiredWorkflows, delivery, configuredTools);
|
|
137
|
+
const toolsWithDriftedSkills = this.findToolsWithDriftedSkills(resolvedProjectPath, configuredTools, delivery, (toolId) => legacyWorkflowOverrides[toolId] ?? desiredWorkflows);
|
|
123
138
|
const toolsToUpdateSet = new Set([
|
|
124
139
|
...toolsNeedingVersionUpdate,
|
|
125
140
|
...toolsNeedingConfigSync,
|
|
141
|
+
...toolsWithDriftedSkills,
|
|
126
142
|
]);
|
|
127
143
|
const toolsUpToDate = toolStatuses.filter((s) => !toolsToUpdateSet.has(s.toolId));
|
|
128
144
|
if (!this.force && toolsToUpdateSet.size === 0 && newlyConfiguredTools.length === 0) {
|
|
@@ -136,6 +152,7 @@ export class UpdateCommand {
|
|
|
136
152
|
this.detectNewTools(resolvedProjectPath, configuredTools);
|
|
137
153
|
this.displayProfileNotes(resolvedProjectPath, configuredTools, desiredWorkflows, profile, delivery);
|
|
138
154
|
this.displaySetupNotes(configuredTools);
|
|
155
|
+
throwIfUpdateFailed(legacyUpgradeFailures);
|
|
139
156
|
return;
|
|
140
157
|
}
|
|
141
158
|
// 8. Display update plan
|
|
@@ -146,7 +163,7 @@ export class UpdateCommand {
|
|
|
146
163
|
console.log('No additional refresh needed after legacy migration.');
|
|
147
164
|
}
|
|
148
165
|
else {
|
|
149
|
-
this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate);
|
|
166
|
+
this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate, new Set(toolsWithDriftedSkills));
|
|
150
167
|
}
|
|
151
168
|
console.log();
|
|
152
169
|
// 9. Determine what to generate based on delivery
|
|
@@ -158,7 +175,7 @@ export class UpdateCommand {
|
|
|
158
175
|
.filter((tool) => tool !== undefined));
|
|
159
176
|
const updatedTools = [];
|
|
160
177
|
const updatedToolIds = [];
|
|
161
|
-
const failedTools = [];
|
|
178
|
+
const failedTools = [...legacyUpgradeFailures];
|
|
162
179
|
const skillsInvocableCommandSkips = [];
|
|
163
180
|
const zeroArtifactTools = [];
|
|
164
181
|
let removedCommandCount = 0;
|
|
@@ -329,9 +346,7 @@ export class UpdateCommand {
|
|
|
329
346
|
if (restartHint) {
|
|
330
347
|
console.log(chalk.dim(restartHint));
|
|
331
348
|
}
|
|
332
|
-
|
|
333
|
-
throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`);
|
|
334
|
-
}
|
|
349
|
+
throwIfUpdateFailed(failedTools);
|
|
335
350
|
}
|
|
336
351
|
async syncCopilotCloudFiles(projectPath, configuredTools) {
|
|
337
352
|
try {
|
|
@@ -375,6 +390,45 @@ export class UpdateCommand {
|
|
|
375
390
|
console.warn(`Warning: failed to sync Copilot cloud agent files: ${message}`);
|
|
376
391
|
}
|
|
377
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Tools whose SKILL.md bodies no longer match what this CLI generates.
|
|
395
|
+
*
|
|
396
|
+
* Skill freshness was decided solely by the `generatedBy:` line, so a body
|
|
397
|
+
* edited after generation — a "helpful" PR touching `.claude/skills/**`, a
|
|
398
|
+
* dotfile sync, another agent — left `update` reporting the install healthy.
|
|
399
|
+
* Command files never had that gap: `areCommandFilesUpToDate` content-
|
|
400
|
+
* compares them, and the same comparison belongs on the higher-authority
|
|
401
|
+
* surface. A missing skill file is left to the profile-sync check, which
|
|
402
|
+
* already knows what a partial install means.
|
|
403
|
+
*/
|
|
404
|
+
findToolsWithDriftedSkills(projectPath, toolIds, delivery, workflowsForTool) {
|
|
405
|
+
return toolIds.filter((toolId) => {
|
|
406
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
407
|
+
if (!tool || !toolSupportsSkills(tool))
|
|
408
|
+
return false;
|
|
409
|
+
if (!shouldGenerateSkillsForTool(tool.value, delivery))
|
|
410
|
+
return false;
|
|
411
|
+
// A shared skills root is generated with its owner's transformer, so
|
|
412
|
+
// only the owner may compare it. getToolSkillStatus settles ownership.
|
|
413
|
+
if (!getToolSkillStatus(projectPath, tool.value).configured)
|
|
414
|
+
return false;
|
|
415
|
+
const skillsDir = resolveToolSkillsDir(projectPath, tool);
|
|
416
|
+
const transformer = getTransformerForTool(tool.value, delivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
417
|
+
return getSkillTemplates(workflowsForTool(toolId)).some(({ template, dirName }) => {
|
|
418
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
419
|
+
if (!fs.existsSync(skillFile))
|
|
420
|
+
return false;
|
|
421
|
+
try {
|
|
422
|
+
const existing = fs.readFileSync(skillFile, 'utf-8');
|
|
423
|
+
const generated = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
424
|
+
return normalizeGeneratedFile(existing) !== normalizeGeneratedFile(generated);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
378
432
|
/**
|
|
379
433
|
* Display message when all tools are up to date.
|
|
380
434
|
*/
|
|
@@ -388,13 +442,18 @@ export class UpdateCommand {
|
|
|
388
442
|
/**
|
|
389
443
|
* Display the update plan showing which tools need updating.
|
|
390
444
|
*/
|
|
391
|
-
displayUpdatePlan(toolsToUpdate, statusByTool, upToDate) {
|
|
445
|
+
displayUpdatePlan(toolsToUpdate, statusByTool, upToDate, driftedSkills = new Set()) {
|
|
392
446
|
const updates = toolsToUpdate.map((toolId) => {
|
|
393
447
|
const status = statusByTool.get(toolId);
|
|
394
448
|
if (status?.needsUpdate) {
|
|
395
449
|
const fromVersion = status.generatedByVersion ?? 'unknown';
|
|
396
450
|
return `${status.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
|
|
397
451
|
}
|
|
452
|
+
// Say why: a user who edited a SKILL.md on purpose is owed the reason
|
|
453
|
+
// their edit is about to be overwritten.
|
|
454
|
+
if (driftedSkills.has(toolId)) {
|
|
455
|
+
return `${toolId} (skill files differ from the generated content)`;
|
|
456
|
+
}
|
|
398
457
|
return `${toolId} (config sync)`;
|
|
399
458
|
});
|
|
400
459
|
console.log(`Updating ${toolsToUpdate.length} tool(s): ${updates.join(', ')}`);
|
|
@@ -826,6 +885,7 @@ export class UpdateCommand {
|
|
|
826
885
|
return {
|
|
827
886
|
name: tool?.name || toolId,
|
|
828
887
|
value: toolId,
|
|
888
|
+
searchAliases: tool?.searchAliases,
|
|
829
889
|
configured: false,
|
|
830
890
|
preSelected: true, // Pre-select all detected legacy tools
|
|
831
891
|
};
|
|
@@ -846,6 +906,7 @@ export class UpdateCommand {
|
|
|
846
906
|
// Create skills/commands for selected tools using effective profile+delivery.
|
|
847
907
|
const newlyConfigured = [];
|
|
848
908
|
const skippedSharedSkillTools = [];
|
|
909
|
+
const failedTools = [];
|
|
849
910
|
const workflowOverrides = {};
|
|
850
911
|
const arbitrationTools = [...new Set([...configuredTools, ...selectedTools])]
|
|
851
912
|
.map((toolId) => AI_TOOLS.find((tool) => tool.value === toolId))
|
|
@@ -919,13 +980,20 @@ export class UpdateCommand {
|
|
|
919
980
|
}
|
|
920
981
|
catch (error) {
|
|
921
982
|
spinner.fail(`Failed to set up ${tool.name}`);
|
|
922
|
-
|
|
983
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
984
|
+
console.log(chalk.red(` ${message}`));
|
|
985
|
+
failedTools.push({ name: tool.name, error: message });
|
|
923
986
|
}
|
|
924
987
|
}
|
|
925
988
|
if (newlyConfigured.length > 0) {
|
|
926
989
|
console.log();
|
|
927
990
|
}
|
|
928
|
-
return {
|
|
991
|
+
return {
|
|
992
|
+
newlyConfiguredTools: newlyConfigured,
|
|
993
|
+
workflowOverrides,
|
|
994
|
+
skippedSharedSkillTools,
|
|
995
|
+
failedTools,
|
|
996
|
+
};
|
|
929
997
|
}
|
|
930
998
|
}
|
|
931
999
|
//# sourceMappingURL=update.js.map
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { buildCodeFenceMask } from '../parsers/code-fence.js';
|
|
2
2
|
import { PURPOSE_PLACEHOLDER_PREFIX, PURPOSE_PLACEHOLDER_SUFFIX } from './constants.js';
|
|
3
3
|
/**
|
|
4
|
-
* A `TBD` or `TODO` opening the Purpose.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* A `TBD` or `TODO` opening the Purpose.
|
|
5
|
+
*
|
|
6
|
+
* `WORD_END` keeps the marker off a longer word that merely begins with those
|
|
7
|
+
* letters, like "TBDs" or "TODOs", while still allowing the punctuation a
|
|
8
|
+
* marker is usually written with: `TODO:`, `TBD -`. It rejects any letter,
|
|
9
|
+
* digit or combining mark rather than only the ASCII ones `\b` knows about,
|
|
10
|
+
* because a Purpose is prose and prose is not always written in Latin script -
|
|
11
|
+
* `TBD` followed by an Arabic-Indic digit is as much a longer word as `TBDs`.
|
|
12
|
+
*
|
|
13
|
+
* Case is what separates the marker from the word. `TODO` shouted in capitals
|
|
14
|
+
* is the marker whatever follows it, so `TODO write this later` is still an
|
|
15
|
+
* unwritten Purpose. Written in any other case it is only a marker when
|
|
16
|
+
* punctuation or the end of the line says so, because `todo` is an extremely
|
|
17
|
+
* frequent sentence opener in Spanish ("Todo el...") and Portuguese ("Todo
|
|
18
|
+
* o..."), and ordinary prose in those languages is not a placeholder. That
|
|
19
|
+
* keeps the lowercase forms an agent really does leave behind - `todo - write
|
|
20
|
+
* this later`, `tbd.` - reported, without reading a Spanish sentence as one.
|
|
11
21
|
*/
|
|
12
|
-
const
|
|
22
|
+
const WORD_END = '(?![\\p{L}\\p{N}\\p{M}_])';
|
|
23
|
+
const MARKER_PUNCTUATION = '(?=[ \\t]*(?:$|\\n|[:\\-\u2013\u2014.,;()\\[\\]{}]))';
|
|
24
|
+
/** `TBD`/`TODO` in capitals: the marker, whatever follows it. */
|
|
25
|
+
const LEADING_MARKER_SHOUTED = new RegExp(`^(?:TBD|TODO)${WORD_END}`, 'u');
|
|
26
|
+
/** Any other case: a marker only when punctuation or the line end says so. */
|
|
27
|
+
const LEADING_MARKER_PUNCTUATED = new RegExp(`^(?:TBD|TODO)${WORD_END}${MARKER_PUNCTUATION}`, 'iu');
|
|
13
28
|
const PURPOSE_HEADER = /^ {0,3}##(?!#)[ \t]+Purpose[ \t]*$/i;
|
|
14
29
|
const TOP_LEVEL_HEADER = /^ {0,3}#{1,2}(?!#)[ \t]+/;
|
|
15
30
|
/**
|
|
@@ -60,7 +75,7 @@ export function findPurposePlaceholderIssue(overview, content) {
|
|
|
60
75
|
// that is nothing but a fenced block reduces to the same empty text here, and
|
|
61
76
|
// is left to the brevity and empty-Purpose rules for the same reason.
|
|
62
77
|
const prose = unfencedLines(overview).join('\n').trim();
|
|
63
|
-
const leading =
|
|
78
|
+
const leading = LEADING_MARKER_SHOUTED.test(prose) || LEADING_MARKER_PUNCTUATED.test(prose);
|
|
64
79
|
if (!leading && generatedPlaceholderPrefixIndex(prose) === undefined)
|
|
65
80
|
return null;
|
|
66
81
|
// Which rule matched decides where the placeholder is, so the locator is told.
|