@fission-ai/openspec 1.10.0 → 1.12.0

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.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/index.js +12 -2
  3. package/dist/commands/change.d.ts +24 -0
  4. package/dist/commands/change.js +229 -5
  5. package/dist/commands/schema.d.ts +5 -0
  6. package/dist/commands/schema.js +201 -35
  7. package/dist/commands/show.js +1 -1
  8. package/dist/commands/validate.d.ts +57 -0
  9. package/dist/commands/validate.js +99 -13
  10. package/dist/commands/workflow/index.d.ts +1 -1
  11. package/dist/commands/workflow/index.js +1 -1
  12. package/dist/commands/workflow/status.d.ts +2 -0
  13. package/dist/commands/workflow/status.js +76 -12
  14. package/dist/core/artifact-graph/resolver.js +15 -9
  15. package/dist/core/available-tools.js +16 -1
  16. package/dist/core/command-generation/adapters/antigravity.d.ts +1 -1
  17. package/dist/core/command-generation/adapters/antigravity.js +2 -2
  18. package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
  19. package/dist/core/command-generation/adapters/codeassistant.js +29 -0
  20. package/dist/core/command-generation/adapters/index.d.ts +1 -0
  21. package/dist/core/command-generation/adapters/index.js +1 -0
  22. package/dist/core/command-generation/registry.js +2 -0
  23. package/dist/core/completions/command-registry.js +21 -0
  24. package/dist/core/completions/generators/fish-generator.d.ts +12 -0
  25. package/dist/core/completions/generators/fish-generator.js +79 -47
  26. package/dist/core/completions/templates/fish-templates.d.ts +1 -1
  27. package/dist/core/completions/templates/fish-templates.js +107 -10
  28. package/dist/core/completions/types.d.ts +4 -0
  29. package/dist/core/config.js +7 -1
  30. package/dist/core/init.d.ts +1 -0
  31. package/dist/core/init.js +71 -65
  32. package/dist/core/legacy-cleanup.js +6 -0
  33. package/dist/core/migration.js +42 -12
  34. package/dist/core/openspec-root.d.ts +1 -0
  35. package/dist/core/openspec-root.js +10 -2
  36. package/dist/core/parsers/requirement-blocks.d.ts +1 -0
  37. package/dist/core/parsers/requirement-blocks.js +2 -0
  38. package/dist/core/shared/ide-restart.d.ts +26 -0
  39. package/dist/core/shared/ide-restart.js +42 -0
  40. package/dist/core/shared/index.d.ts +1 -0
  41. package/dist/core/shared/index.js +1 -0
  42. package/dist/core/shared/tool-detection.js +9 -3
  43. package/dist/core/shared-skill-target.d.ts +13 -0
  44. package/dist/core/shared-skill-target.js +43 -1
  45. package/dist/core/specs-apply.js +22 -9
  46. package/dist/core/templates/workflows/explore.js +108 -73
  47. package/dist/core/templates/workflows/ff-change.js +8 -0
  48. package/dist/core/templates/workflows/propose.js +8 -0
  49. package/dist/core/update.js +28 -31
  50. package/dist/core/validation/constants.d.ts +3 -0
  51. package/dist/core/validation/constants.js +12 -0
  52. package/dist/core/validation/purpose-placeholder.d.ts +45 -0
  53. package/dist/core/validation/purpose-placeholder.js +117 -0
  54. package/dist/core/validation/validator.d.ts +12 -3
  55. package/dist/core/validation/validator.js +90 -4
  56. package/dist/utils/command-references.js +7 -8
  57. package/dist/utils/requirement-diff.d.ts +42 -0
  58. package/dist/utils/requirement-diff.js +61 -0
  59. package/package.json +4 -3
@@ -1,11 +1,32 @@
1
1
  import { STORE_SELECTION_GUIDANCE } from './store-selection.js';
2
+ const PLANNING_GUIDANCE = `## Planning a Change
3
+
4
+ When the user is planning a change, guide them toward shared understanding with focused discovery questions. For open-ended discussion, follow the conversation without imposing an interview or a required output.
5
+
6
+ Before asking a factual question, follow the context discovery below and inspect relevant OpenSpec artifacts, source, tests, docs, and configuration. Do not ask the user to repeat facts you can verify. Summarize relevant findings without reproducing private context or rules. If evidence is missing, conflicting, or inaccessible, state that limitation and ask only for the clarification needed to proceed.
7
+
8
+ - **Follow dependencies** - Resolve the next blocking decision before its dependent details. For example, clarify the user's outcome and scope before choosing an API or data model. Revisit downstream assumptions when an earlier answer changes. Skip branches that do not matter to this goal.
9
+ - **Keep questions focused** - Ask one focused question at a time, and briefly explain why it matters and which decision it unlocks. Batch questions only if the user asks for a batch; keep them small and group related decisions.
10
+ - **Offer grounded recommendations** - When evidence supports a recommendation, state your preferred option and why it fits the user's goals, with alternatives and their tradeoffs when useful. Do not invent intent, priorities, or external constraints: ask the user when only they can answer. Avoid a fixed question format.
11
+ - **Keep a conversational record** - Track decisions in the conversation, not in files. Separate confirmed decisions from proposed defaults and unresolved questions. Silence is not acceptance. Accepting an answer or a batch of recommendations is not permission to write. Keep file-write confirmation separate from discovery questions and follow the guardrails below.
12
+
13
+ Stop asking when the user has enough clarity. Let them pause, pivot, or defer a decision; do not exhaust every branch or force a proposal.
14
+
15
+ For example, after inspecting the relevant code:
16
+
17
+ \`\`\`text
18
+ The CLI already uses SQLite and has no remote service. Is sharing state
19
+ across devices in scope? That determines whether local storage is enough.
20
+ If this stays a single-device tool, I recommend keeping SQLite to avoid
21
+ adding a service to operate; shared state would need a separate sync design.
22
+ \`\`\``;
2
23
  export function getExploreSkillTemplate() {
3
24
  return {
4
25
  name: 'openspec-explore',
5
26
  description: 'Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.',
6
27
  instructions: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
7
28
 
8
- **IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. For a new change, scaffold it first as described below.
29
+ **IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, investigate the codebase, and run read-only commands or tools without confirmation, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create or update OpenSpec change artifacts (proposals, designs, specs) within a confirmed scope—that's capturing thinking, not implementing. Answering design or clarifying questions is never consent to write. Before the first write-capable action, name the artifacts or files you would change and what you would do, ask a direct yes/no question, and wait for the user's confirmation in a separate message. Confirmation covers only the scope you described; ask again before expanding it. For a new change, scaffold it first as described below.
9
30
 
10
31
  **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
11
32
 
@@ -24,6 +45,10 @@ ${STORE_SELECTION_GUIDANCE}
24
45
 
25
46
  ---
26
47
 
48
+ ${PLANNING_GUIDANCE}
49
+
50
+ ---
51
+
27
52
  ## What You Might Do
28
53
 
29
54
  Depending on what the user brings, you might:
@@ -48,22 +73,25 @@ Depending on what the user brings, you might:
48
73
 
49
74
  **Visualize**
50
75
  \`\`\`
51
- ┌─────────────────────────────────────────┐
52
- │ Use ASCII diagrams liberally │
53
- ├─────────────────────────────────────────┤
54
- │ │
55
- │ ┌────────┐ ┌────────┐ │
56
- │ │ State │────────▶│ State │ │
57
- │ │ A │ │ B │ │
58
- │ └────────┘ └────────┘ │
59
- │ │
60
- │ System diagrams, state machines, │
61
- │ data flows, architecture sketches, │
62
- │ dependency graphs, comparison tables │
63
- │ │
64
- └─────────────────────────────────────────┘
76
+ +------------------------------------------+
77
+ | Use ASCII diagrams liberally |
78
+ +------------------------------------------+
79
+ | |
80
+ | [State A] -------> [State B] |
81
+ | | |
82
+ | v |
83
+ | [State C] |
84
+ | |
85
+ | System diagrams, state machines, |
86
+ | data flows, architecture sketches, |
87
+ | dependency graphs, comparison tables |
88
+ | |
89
+ +------------------------------------------+
65
90
  \`\`\`
66
91
 
92
+ **Draw with plain ASCII only** — borders \`+\` \`-\` \`|\`, arrows \`-->\` \`<--\` \`^\` \`v\`, markers \`*\` \`x\`.
93
+ Unicode diagram glyphs can render at different widths across terminals, fonts, and locales, so padded boxes and aligned tables can drift. Keep every diagram character ASCII.
94
+
67
95
  **Surface risks and unknowns**
68
96
  - Identify what could go wrong
69
97
  - Find gaps in understanding
@@ -164,17 +192,17 @@ User: I'm thinking about adding real-time collaboration
164
192
  You: Real-time collab is a big space. Let me think about this...
165
193
 
166
194
  COLLABORATION SPECTRUM
167
- ════════════════════════════════════════════
195
+ ============================================
168
196
 
169
197
  Awareness Coordination Sync
170
- │ │ │
171
- ▼ ▼ ▼
172
- ┌────────┐ ┌────────┐ ┌────────┐
173
- │Presence│ │Cursors │ │ CRDT │
174
- │ "3 │ │ Multi │ │Conflict│
175
- │online" │ │ select │ │ free │
176
- └────────┘ └────────┘ └────────┘
177
- │ │ │
198
+ | | |
199
+ v v v
200
+ +--------+ +--------+ +--------+
201
+ |Presence| |Cursors | | CRDT |
202
+ | "3 | | Multi | |Conflict|
203
+ |online" | | select | | free |
204
+ +--------+ +--------+ +--------+
205
+ | | |
178
206
  trivial moderate complex
179
207
 
180
208
  Where's your head at?
@@ -186,27 +214,27 @@ User: The auth system is a mess
186
214
 
187
215
  You: [reads codebase]
188
216
 
189
- ┌─────────────────────────────────────────────┐
190
- │ CURRENT AUTH FLOW │
191
- └─────────────────────────────────────────────┘
192
- │
193
- ┌─────────────┼─────────────┐
194
- ▼ ▼ ▼
195
- ┌─────────┐ ┌─────────┐ ┌─────────┐
196
- │ Google │ │ GitHub │ │ Email │
197
- │ OAuth │ │ OAuth │ │ Magic │
198
- └────┬────┘ └────┬────┘ └────┬────┘
199
- │ │ │
200
- └─────────────┼─────────────┘
201
- ▼
202
- ┌───────────┐
203
- │ Session │
204
- └─────┬─────┘
205
- │
206
- ▼
207
- ┌───────────┐
208
- │ Perms │
209
- └───────────┘
217
+ +---------------------------------------------+
218
+ | CURRENT AUTH FLOW |
219
+ +---------------------------------------------+
220
+ |
221
+ +-------------+-------------+
222
+ v v v
223
+ +---------+ +---------+ +---------+
224
+ | Google | | GitHub | | Email |
225
+ | OAuth | | OAuth | | Magic |
226
+ +----+----+ +----+----+ +----+----+
227
+ | | |
228
+ +-------------+-------------+
229
+ v
230
+ +-----------+
231
+ | Session |
232
+ +-----+-----+
233
+ |
234
+ v
235
+ +-----------+
236
+ | Perms |
237
+ +-----------+
210
238
 
211
239
  I see three tangles. Which one's burning?
212
240
  \`\`\`
@@ -238,19 +266,19 @@ User: A CLI tool that tracks local dev environments
238
266
 
239
267
  You: That changes everything.
240
268
 
241
- ┌─────────────────────────────────────────────────┐
242
- │ CLI TOOL DATA STORAGE │
243
- └─────────────────────────────────────────────────┘
269
+ +-------------------------------------------------+
270
+ | CLI TOOL DATA STORAGE |
271
+ +-------------------------------------------------+
244
272
 
245
273
  Key constraints:
246
- • No daemon running
247
- • Must work offline
248
- • Single user
274
+ - No daemon running
275
+ - Must work offline
276
+ - Single user
249
277
 
250
278
  SQLite Postgres
251
- Deployment embedded ✓ needs server ✗
252
- Offline yes ✓ no ✗
253
- Single file yes ✓ no ✗
279
+ Deployment embedded needs server
280
+ Offline yes no
281
+ Single file yes no
254
282
 
255
283
  SQLite. Not even close.
256
284
 
@@ -290,11 +318,11 @@ But this summary is optional. Sometimes the thinking IS the value.
290
318
 
291
319
  ## Guardrails
292
320
 
293
- - **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
321
+ - **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not.
294
322
  - **Don't fake understanding** - If something is unclear, dig deeper
295
323
  - **Don't rush** - Discovery is thinking time, not task time
296
324
  - **Don't force structure** - Let patterns emerge naturally
297
- - **Don't auto-capture** - Offer to save insights, don't just do it
325
+ - **Don't auto-capture** - Offer to save insights, don't just do it. Read-only commands and tools need no confirmation. Before the first write-capable action—including \`openspec new change\` or another command that writes files—name the artifacts or files and proposed changes, ask a direct yes/no question, and wait for explicit confirmation in a separate user message. That confirmation covers only the described scope; ask again before expanding it. Answers to design or clarifying questions are never consent to write.
298
326
  - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change "<name>"\` (with \`--store <id>\` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts.
299
327
  - **Do visualize** - A good diagram is worth many paragraphs
300
328
  - **Do explore the codebase** - Ground discussions in reality
@@ -312,7 +340,7 @@ export function getOpsxExploreCommandTemplate() {
312
340
  tags: ['workflow', 'explore', 'experimental', 'thinking'],
313
341
  content: `Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
314
342
 
315
- **IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. For a new change, scaffold it first as described below.
343
+ **IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, investigate the codebase, and run read-only commands or tools without confirmation, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create or update OpenSpec change artifacts (proposals, designs, specs) within a confirmed scope—that's capturing thinking, not implementing. Answering design or clarifying questions is never consent to write. Before the first write-capable action, name the artifacts or files you would change and what you would do, ask a direct yes/no question, and wait for the user's confirmation in a separate message. Confirmation covers only the scope you described; ask again before expanding it. For a new change, scaffold it first as described below.
316
344
 
317
345
  **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
318
346
 
@@ -338,6 +366,10 @@ ${STORE_SELECTION_GUIDANCE}
338
366
 
339
367
  ---
340
368
 
369
+ ${PLANNING_GUIDANCE}
370
+
371
+ ---
372
+
341
373
  ## What You Might Do
342
374
 
343
375
  Depending on what the user brings, you might:
@@ -362,22 +394,25 @@ Depending on what the user brings, you might:
362
394
 
363
395
  **Visualize**
364
396
  \`\`\`
365
- ┌─────────────────────────────────────────┐
366
- │ Use ASCII diagrams liberally │
367
- ├─────────────────────────────────────────┤
368
- │ │
369
- │ ┌────────┐ ┌────────┐ │
370
- │ │ State │────────▶│ State │ │
371
- │ │ A │ │ B │ │
372
- │ └────────┘ └────────┘ │
373
- │ │
374
- │ System diagrams, state machines, │
375
- │ data flows, architecture sketches, │
376
- │ dependency graphs, comparison tables │
377
- │ │
378
- └─────────────────────────────────────────┘
397
+ +------------------------------------------+
398
+ | Use ASCII diagrams liberally |
399
+ +------------------------------------------+
400
+ | |
401
+ | [State A] -------> [State B] |
402
+ | | |
403
+ | v |
404
+ | [State C] |
405
+ | |
406
+ | System diagrams, state machines, |
407
+ | data flows, architecture sketches, |
408
+ | dependency graphs, comparison tables |
409
+ | |
410
+ +------------------------------------------+
379
411
  \`\`\`
380
412
 
413
+ **Draw with plain ASCII only** — borders \`+\` \`-\` \`|\`, arrows \`-->\` \`<--\` \`^\` \`v\`, markers \`*\` \`x\`.
414
+ Unicode diagram glyphs can render at different widths across terminals, fonts, and locales, so padded boxes and aligned tables can drift. Keep every diagram character ASCII.
415
+
381
416
  **Surface risks and unknowns**
382
417
  - Identify what could go wrong
383
418
  - Find gaps in understanding
@@ -486,11 +521,11 @@ When things crystallize, you might offer a summary - but it's optional. Sometime
486
521
 
487
522
  ## Guardrails
488
523
 
489
- - **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
524
+ - **Don't implement** - Never write code or implement features. Workflow configuration counts too: creating or editing schemas, templates, or \`openspec/config.yaml\` is a change, not thinking. Creating or updating OpenSpec change artifacts within the confirmed scope is fine, writing anything else is not.
490
525
  - **Don't fake understanding** - If something is unclear, dig deeper
491
526
  - **Don't rush** - Discovery is thinking time, not task time
492
527
  - **Don't force structure** - Let patterns emerge naturally
493
- - **Don't auto-capture** - Offer to save insights, don't just do it
528
+ - **Don't auto-capture** - Offer to save insights, don't just do it. Read-only commands and tools need no confirmation. Before the first write-capable action—including \`openspec new change\` or another command that writes files—name the artifacts or files and proposed changes, ask a direct yes/no question, and wait for explicit confirmation in a separate user message. That confirmation covers only the described scope; ask again before expanding it. Answers to design or clarifying questions are never consent to write.
494
529
  - **Don't manually scaffold changes** - Never create a new change directory under \`openspec/changes/\` by hand. Always use \`openspec new change "<name>"\` (with \`--store <id>\` when applicable) so required metadata such as \`.openspec.yaml\` is created before writing artifacts.
495
530
  - **Do visualize** - A good diagram is worth many paragraphs
496
531
  - **Do explore the codebase** - Ground discussions in reality
@@ -55,6 +55,10 @@ ${STORE_SELECTION_GUIDANCE}
55
55
  - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact
56
56
  - \`dependencies\`: Completed artifacts to read for context
57
57
  - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
58
+ - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed.
59
+ - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
60
+ - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
61
+ - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
58
62
  - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\`
59
63
  - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path
60
64
  - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
@@ -167,6 +171,10 @@ ${STORE_SELECTION_GUIDANCE}
167
171
  - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact
168
172
  - \`dependencies\`: Completed artifacts to read for context
169
173
  - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
174
+ - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed.
175
+ - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
176
+ - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
177
+ - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
170
178
  - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\`
171
179
  - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path
172
180
  - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
@@ -90,6 +90,10 @@ ${STORE_SELECTION_GUIDANCE}
90
90
  - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact
91
91
  - \`dependencies\`: Completed artifacts to read for context
92
92
  - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
93
+ - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed.
94
+ - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
95
+ - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
96
+ - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
93
97
  - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\`
94
98
  - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path
95
99
  - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
@@ -238,6 +242,10 @@ ${STORE_SELECTION_GUIDANCE}
238
242
  - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact
239
243
  - \`dependencies\`: Completed artifacts to read for context
240
244
  - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
245
+ - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed.
246
+ - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan.
247
+ - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct.
248
+ - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question.
241
249
  - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\`
242
250
  - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path
243
251
  - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file
@@ -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, } from './shared/index.js';
16
+ import { 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';
@@ -23,7 +23,7 @@ import { getAvailableTools } from './available-tools.js';
23
23
  import { WORKFLOW_TO_SKILL_DIR, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
24
24
  import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, findLegacyToolMigrations, migrateLegacyToolDirs, describeLegacyMigration, legacyMigrationNotice, keptInPlaceNotice, hasMovableContent, } from './migration.js';
25
25
  import { resolveCommandSurfaceCapability, resolveCommandInvocation, shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
26
- import { writeSharedSkillTarget, sharedSkillRootOwner } from './shared-skill-target.js';
26
+ import { resolveSharedSkillWriters, writeSharedSkillTarget, } from './shared-skill-target.js';
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');
@@ -154,6 +154,9 @@ export class UpdateCommand {
154
154
  const deliveryIncludesCommands = delivery !== 'skills';
155
155
  // 10. Update tools (all if force, otherwise only those needing update)
156
156
  const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet];
157
+ const sharedSkillWriters = resolveSharedSkillWriters(resolvedProjectPath, configuredAndNewTools
158
+ .map((toolId) => AI_TOOLS.find((tool) => tool.value === toolId))
159
+ .filter((tool) => tool !== undefined));
157
160
  const updatedTools = [];
158
161
  const updatedToolIds = [];
159
162
  const failedTools = [];
@@ -173,11 +176,12 @@ export class UpdateCommand {
173
176
  const skillsRoot = hasGlobalSkillTarget(tool) ? skillsDir : resolvedProjectPath;
174
177
  const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
175
178
  const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
179
+ const writesSkills = !tool.skillsDir || sharedSkillWriters.has(tool.value);
176
180
  const toolWorkflows = legacyWorkflowOverrides[tool.value] ?? desiredWorkflows;
177
181
  const skillTemplates = getSkillTemplates(toolWorkflows);
178
182
  const commandContents = getCommandContents(toolWorkflows);
179
183
  // Generate skill files if delivery includes skills
180
- if (shouldGenerateSkills) {
184
+ if (shouldGenerateSkills && writesSkills) {
181
185
  for (const { template, dirName } of skillTemplates) {
182
186
  const skillDir = path.join(skillsDir, dirName);
183
187
  const skillFile = path.join(skillDir, 'SKILL.md');
@@ -190,7 +194,9 @@ export class UpdateCommand {
190
194
  removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsRoot, skillsDir, toolWorkflows);
191
195
  }
192
196
  // Delete skill directories if delivery is commands-only
193
- if (shouldRemoveSkillsForTool(tool.value, delivery) && !hasGlobalSkillTarget(tool)) {
197
+ if (shouldRemoveSkillsForTool(tool.value, delivery) &&
198
+ writesSkills &&
199
+ !hasGlobalSkillTarget(tool)) {
194
200
  removedSkillCount += await this.removeSkillDirs(skillsRoot, skillsDir);
195
201
  // Persist the selected owner even when commands-only delivery leaves
196
202
  // this target with no generated skills.
@@ -321,14 +327,9 @@ export class UpdateCommand {
321
327
  }
322
328
  console.log();
323
329
  const affectedToolIds = [...new Set([...newlyConfiguredTools, ...updatedToolIds])];
324
- const shouldRestartIde = affectedToolIds.some((toolId) => {
325
- const tool = AI_TOOLS.find((candidate) => candidate.value === toolId);
326
- return Boolean(tool?.requiresIdeRestart &&
327
- (shouldGenerateCommandsForTool(toolId, delivery) ||
328
- shouldGenerateSkillsForTool(toolId, delivery)));
329
- });
330
- if (shouldRestartIde) {
331
- console.log(chalk.dim('Restart your IDE for changes to take effect.'));
330
+ const restartHint = formatIdeRestart(affectedToolIds, delivery);
331
+ if (restartHint) {
332
+ console.log(chalk.dim(restartHint));
332
333
  }
333
334
  if (failedTools.length > 0) {
334
335
  throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`);
@@ -803,6 +804,10 @@ export class UpdateCommand {
803
804
  const newlyConfigured = [];
804
805
  const skippedSharedSkillTools = [];
805
806
  const workflowOverrides = {};
807
+ const arbitrationTools = [...new Set([...configuredTools, ...selectedTools])]
808
+ .map((toolId) => AI_TOOLS.find((tool) => tool.value === toolId))
809
+ .filter((tool) => tool !== undefined);
810
+ const sharedSkillWriters = resolveSharedSkillWriters(projectPath, arbitrationTools);
806
811
  for (const toolId of selectedTools) {
807
812
  const tool = AI_TOOLS.find((t) => t.value === toolId);
808
813
  if (!tool || !toolSupportsSkills(tool))
@@ -813,6 +818,7 @@ export class UpdateCommand {
813
818
  const skillsRoot = hasGlobalSkillTarget(tool) ? skillsDir : projectPath;
814
819
  const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
815
820
  const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
821
+ const writesSkills = !tool.skillsDir || sharedSkillWriters.has(tool.value);
816
822
  const toolWorkflows = (tool.value === 'codex' && inferredCodexWorkflows.length > 0
817
823
  ? inferredCodexWorkflows
818
824
  : desiredWorkflows);
@@ -821,32 +827,23 @@ export class UpdateCommand {
821
827
  }
822
828
  const skillTemplates = getSkillTemplates(toolWorkflows);
823
829
  const commandContents = getCommandContents(toolWorkflows);
824
- // A shared skills root (e.g. `.agents`) already owned by another tool
825
- // must not be overwritten by a tool inferred from legacy artifacts: a
826
- // Codex install detected only from global `~/.codex/prompts` would
827
- // otherwise rewrite an existing vendor-neutral `agents` tree with
828
- // Codex-specific syntax and flip its ownership marker `agents → codex`.
829
- // Leave the established owner in place. (init applies the same
830
- // one-writer rule up front when both targets are selected.)
831
- //
832
- // Skipping here means the tool is never recorded as configured, so a
833
- // persistent legacy signal re-offers it on later runs. Because no
834
- // replacement is written, this tool is also exempted from immediate
835
- // legacy cleanup (see skippedSharedSkillTools) — otherwise a repo-local
836
- // `.codex/prompts` would be deleted with nothing put in its place. That
837
- // repeat is idempotent and harmless — the alternative is the silent
838
- // hijack this prevents.
839
- const sharedOwner = shouldGenerateSkills
840
- ? sharedSkillRootOwner(projectPath, tool.value)
830
+ // Never overwrite a shared skills root owned by another tool. A tool
831
+ // with its own command surface can still install those commands: this
832
+ // is how a legacy Antigravity install gains `.agents/workflows` beside
833
+ // Codex-owned `.agents/skills`. A skills-only tool has no safe artifact
834
+ // to install, so preserve its legacy files and re-offer it later.
835
+ const sharedOwner = shouldGenerateSkills && !writesSkills
836
+ ? arbitrationTools.find((candidate) => candidate.skillsDir === tool.skillsDir &&
837
+ sharedSkillWriters.has(candidate.value))?.value
841
838
  : undefined;
842
- if (sharedOwner) {
839
+ if (sharedOwner && !shouldGenerateCommands) {
843
840
  const ownerName = AI_TOOLS.find((candidate) => candidate.value === sharedOwner)?.name ?? sharedOwner;
844
841
  spinner.info(`Skipped ${tool.name}: ${tool.skillsDir}/skills is already managed by another tool (${ownerName}).`);
845
842
  skippedSharedSkillTools.push(tool.value);
846
843
  continue;
847
844
  }
848
845
  // Create skill files when delivery includes skills
849
- if (shouldGenerateSkills) {
846
+ if (shouldGenerateSkills && writesSkills) {
850
847
  for (const { template, dirName } of skillTemplates) {
851
848
  const skillDir = path.join(skillsDir, dirName);
852
849
  const skillFile = path.join(skillDir, 'SKILL.md');
@@ -6,6 +6,8 @@ export declare const MIN_PURPOSE_LENGTH = 50;
6
6
  export declare const MAX_WHY_SECTION_LENGTH = 1000;
7
7
  export declare const MAX_REQUIREMENT_TEXT_LENGTH = 500;
8
8
  export declare const MAX_DELTAS_PER_CHANGE = 10;
9
+ export declare const PURPOSE_PLACEHOLDER_PREFIX = "TBD - created by archiving change ";
10
+ export declare const PURPOSE_PLACEHOLDER_SUFFIX = ". Update Purpose after archive.";
9
11
  export declare const VALIDATION_MESSAGES: {
10
12
  readonly SCENARIO_EMPTY: "Scenario text cannot be empty";
11
13
  readonly REQUIREMENT_EMPTY: "Requirement text cannot be empty";
@@ -26,6 +28,7 @@ export declare const VALIDATION_MESSAGES: {
26
28
  readonly DELTA_SPEC_EMPTY: "Spec name cannot be empty";
27
29
  readonly DELTA_DESCRIPTION_EMPTY: "Delta description cannot be empty";
28
30
  readonly PURPOSE_TOO_BRIEF: "Purpose section is too brief (less than 50 characters)";
31
+ readonly PURPOSE_IS_PLACEHOLDER: string;
29
32
  readonly REQUIREMENT_TOO_LONG: "Requirement text is very long (>500 characters). Consider breaking it down.";
30
33
  readonly DELTA_DESCRIPTION_TOO_BRIEF: "Delta description is too brief";
31
34
  readonly DELTA_MISSING_REQUIREMENTS: "Delta should include requirements";
@@ -8,6 +8,14 @@ export const MIN_PURPOSE_LENGTH = 50;
8
8
  export const MAX_WHY_SECTION_LENGTH = 1000;
9
9
  export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
10
10
  export const MAX_DELTAS_PER_CHANGE = 10;
11
+ // The Purpose `openspec archive` writes into a main spec it creates when the
12
+ // delta introduced the capability without a usable `## Purpose`. Named here, and
13
+ // composed from these two halves at the write site, so validation recognises the
14
+ // placeholder through the same definition that produces it: a second, hand-copied
15
+ // spelling would stop matching the day the wording changed, and a check that
16
+ // matches nothing looks exactly like a check that found nothing.
17
+ export const PURPOSE_PLACEHOLDER_PREFIX = 'TBD - created by archiving change ';
18
+ export const PURPOSE_PLACEHOLDER_SUFFIX = '. Update Purpose after archive.';
11
19
  // Validation messages
12
20
  export const VALIDATION_MESSAGES = {
13
21
  // Required content
@@ -31,6 +39,10 @@ export const VALIDATION_MESSAGES = {
31
39
  DELTA_DESCRIPTION_EMPTY: 'Delta description cannot be empty',
32
40
  // Warnings
33
41
  PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters)`,
42
+ PURPOSE_IS_PLACEHOLDER: 'Purpose section is still a placeholder rather than a Purpose anyone wrote (the sentence `openspec archive` ' +
43
+ 'writes for a new capability, or a `TBD`/`TODO` marker left in its place). Replace it with what this ' +
44
+ 'capability is for, editing the main spec directly: a `## Purpose` in a delta is read only when the ' +
45
+ 'capability is created, so it cannot replace this one.',
34
46
  REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
35
47
  DELTA_DESCRIPTION_TOO_BRIEF: 'Delta description is too brief',
36
48
  DELTA_MISSING_REQUIREMENTS: 'Delta should include requirements',
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Detects a `## Purpose` that is still a placeholder, rather than one somebody
3
+ * wrote.
4
+ *
5
+ * When a delta introduces a capability with no usable `## Purpose`, archive
6
+ * stamps the placeholder into the new main spec. That text is over
7
+ * `MIN_PURPOSE_LENGTH`, so the brevity check cannot reach it: the one rule that
8
+ * exists to catch a Purpose nobody wrote is satisfied by the exact string
9
+ * meaning nobody wrote one. Nothing else reads it afterwards, so the capability
10
+ * keeps a to-do in it while every command reports success.
11
+ *
12
+ * Two things count, and deliberately nothing else:
13
+ *
14
+ * - the placeholder this tool generates, recognised through the same constants
15
+ * the writer composes it from, wherever it sits in the Purpose - nobody types
16
+ * that sentence by accident;
17
+ * - a `TBD` or `TODO` **opening** the Purpose, which is the marker left behind
18
+ * when someone is told to leave "a brief TBD placeholder" and never comes
19
+ * back. Which of the two words got typed says nothing about whether the
20
+ * Purpose was written, so both are read the same way.
21
+ *
22
+ * A marker inside a sentence is left alone. "The retry budget is TBD pending
23
+ * benchmarks" is a real Purpose with an open question in it, and reporting it
24
+ * would teach people to ignore the warning - which costs more than the findings
25
+ * it would add.
26
+ *
27
+ * Fenced code inside the Purpose is quoted material rather than the Purpose
28
+ * speaking, so it is read out first. Without that, a Purpose documenting the
29
+ * sentence archive writes is reported as being that sentence: a document about
30
+ * the placeholder, failing for carrying one.
31
+ */
32
+ export interface PurposePlaceholderIssue {
33
+ /** 1-based line of the placeholder text, when it can be located. */
34
+ line?: number;
35
+ }
36
+ /**
37
+ * Reports the Purpose of a main spec as an unwritten placeholder, or null when
38
+ * it reads as authored content.
39
+ *
40
+ * An empty Purpose is not reported here - `SPEC_PURPOSE_EMPTY` already covers
41
+ * it, and reporting both would put two findings on one line. That falls out of
42
+ * the two rules rather than needing a case of its own.
43
+ */
44
+ export declare function findPurposePlaceholderIssue(overview: string, content?: string): PurposePlaceholderIssue | null;
45
+ //# sourceMappingURL=purpose-placeholder.d.ts.map