@fission-ai/openspec 0.12.0 → 0.14.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 (49) hide show
  1. package/README.md +24 -3
  2. package/dist/commands/change.js +6 -6
  3. package/dist/core/archive.d.ts +1 -0
  4. package/dist/core/archive.js +3 -2
  5. package/dist/core/config.js +7 -0
  6. package/dist/core/configurators/cline.d.ts +8 -0
  7. package/dist/core/configurators/cline.js +15 -0
  8. package/dist/core/configurators/codebuddy.d.ts +8 -0
  9. package/dist/core/configurators/codebuddy.js +15 -0
  10. package/dist/core/configurators/costrict.d.ts +8 -0
  11. package/dist/core/configurators/costrict.js +15 -0
  12. package/dist/core/configurators/qoder.d.ts +30 -0
  13. package/dist/core/configurators/qoder.js +42 -0
  14. package/dist/core/configurators/qwen.d.ts +24 -0
  15. package/dist/core/configurators/qwen.js +37 -0
  16. package/dist/core/configurators/registry.js +15 -0
  17. package/dist/core/configurators/slash/auggie.d.ts +9 -0
  18. package/dist/core/configurators/slash/auggie.js +31 -0
  19. package/dist/core/configurators/slash/cline.d.ts +9 -0
  20. package/dist/core/configurators/slash/cline.js +23 -0
  21. package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
  22. package/dist/core/configurators/slash/codebuddy.js +37 -0
  23. package/dist/core/configurators/slash/costrict.d.ts +9 -0
  24. package/dist/core/configurators/slash/costrict.js +31 -0
  25. package/dist/core/configurators/slash/crush.d.ts +9 -0
  26. package/dist/core/configurators/slash/crush.js +37 -0
  27. package/dist/core/configurators/slash/opencode.d.ts +3 -0
  28. package/dist/core/configurators/slash/opencode.js +41 -2
  29. package/dist/core/configurators/slash/qoder.d.ts +35 -0
  30. package/dist/core/configurators/slash/qoder.js +76 -0
  31. package/dist/core/configurators/slash/qwen.d.ts +37 -0
  32. package/dist/core/configurators/slash/qwen.js +74 -0
  33. package/dist/core/configurators/slash/registry.js +21 -0
  34. package/dist/core/init.d.ts +2 -0
  35. package/dist/core/init.js +68 -17
  36. package/dist/core/parsers/requirement-blocks.d.ts +6 -0
  37. package/dist/core/parsers/requirement-blocks.js +28 -5
  38. package/dist/core/templates/agents-template.d.ts +1 -1
  39. package/dist/core/templates/agents-template.js +5 -5
  40. package/dist/core/templates/cline-template.d.ts +2 -0
  41. package/dist/core/templates/cline-template.js +2 -0
  42. package/dist/core/templates/costrict-template.d.ts +2 -0
  43. package/dist/core/templates/costrict-template.js +2 -0
  44. package/dist/core/templates/index.d.ts +2 -0
  45. package/dist/core/templates/index.js +8 -0
  46. package/dist/core/templates/slash-command-templates.js +10 -4
  47. package/dist/core/validation/validator.d.ts +1 -0
  48. package/dist/core/validation/validator.js +57 -6
  49. package/package.json +2 -2
@@ -60,7 +60,7 @@ Track these steps as TODOs and complete them one by one.
60
60
  After deployment, create separate PR to:
61
61
  - Move \`changes/[name]/\` → \`changes/archive/YYYY-MM-DD-[name]/\`
62
62
  - Update \`specs/\` if capabilities changed
63
- - Use \`openspec archive [change] --skip-specs --yes\` for tooling-only changes
63
+ - Use \`openspec archive <change-id> --skip-specs --yes\` for tooling-only changes (always pass the change ID explicitly)
64
64
  - Run \`openspec validate --strict\` to confirm the archived change passes checks
65
65
 
66
66
  ## Before Any Task
@@ -95,9 +95,8 @@ After deployment, create separate PR to:
95
95
  openspec list # List active changes
96
96
  openspec list --specs # List specifications
97
97
  openspec show [item] # Display change or spec
98
- openspec diff [change] # Show spec differences
99
98
  openspec validate [item] # Validate changes or specs
100
- openspec archive [change] [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
99
+ openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
101
100
 
102
101
  # Project management
103
102
  openspec init [path] # Initialize OpenSpec
@@ -161,6 +160,8 @@ New request?
161
160
 
162
161
  2. **Write proposal.md:**
163
162
  \`\`\`markdown
163
+ # Change: [Brief description of change]
164
+
164
165
  ## Why
165
166
  [1-2 sentences on problem/opportunity]
166
167
 
@@ -448,9 +449,8 @@ Only add complexity with:
448
449
  \`\`\`bash
449
450
  openspec list # What's in progress?
450
451
  openspec show [item] # View details
451
- openspec diff [change] # What's changing?
452
452
  openspec validate --strict # Is it correct?
453
- openspec archive [change] [--yes|-y] # Mark complete (add --yes for automation)
453
+ openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
454
454
  \`\`\`
455
455
 
456
456
  Remember: Specs are truth. Changes are proposals. Keep them in sync.
@@ -0,0 +1,2 @@
1
+ export { agentsRootStubTemplate as clineTemplate } from './agents-root-stub.js';
2
+ //# sourceMappingURL=cline-template.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { agentsRootStubTemplate as clineTemplate } from './agents-root-stub.js';
2
+ //# sourceMappingURL=cline-template.js.map
@@ -0,0 +1,2 @@
1
+ export { agentsRootStubTemplate as costrictTemplate } from './agents-root-stub.js';
2
+ //# sourceMappingURL=costrict-template.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { agentsRootStubTemplate as costrictTemplate } from './agents-root-stub.js';
2
+ //# sourceMappingURL=costrict-template.js.map
@@ -7,6 +7,8 @@ export interface Template {
7
7
  export declare class TemplateManager {
8
8
  static getTemplates(context?: ProjectContext): Template[];
9
9
  static getClaudeTemplate(): string;
10
+ static getClineTemplate(): string;
11
+ static getCostrictTemplate(): string;
10
12
  static getAgentsStandardTemplate(): string;
11
13
  static getSlashCommandBody(id: SlashCommandId): string;
12
14
  }
@@ -1,6 +1,8 @@
1
1
  import { agentsTemplate } from './agents-template.js';
2
2
  import { projectTemplate } from './project-template.js';
3
3
  import { claudeTemplate } from './claude-template.js';
4
+ import { clineTemplate } from './cline-template.js';
5
+ import { costrictTemplate } from './costrict-template.js';
4
6
  import { agentsRootStubTemplate } from './agents-root-stub.js';
5
7
  import { getSlashCommandBody } from './slash-command-templates.js';
6
8
  export class TemplateManager {
@@ -19,6 +21,12 @@ export class TemplateManager {
19
21
  static getClaudeTemplate() {
20
22
  return claudeTemplate;
21
23
  }
24
+ static getClineTemplate() {
25
+ return clineTemplate;
26
+ }
27
+ static getCostrictTemplate() {
28
+ return costrictTemplate;
29
+ }
22
30
  static getAgentsStandardTemplate() {
23
31
  return agentsRootStubTemplate;
24
32
  }
@@ -25,11 +25,17 @@ Track these steps as TODOs and complete them one by one.
25
25
  const applyReferences = `**Reference**
26
26
  - Use \`openspec show <id> --json --deltas-only\` if you need additional context from the proposal while implementing.`;
27
27
  const archiveSteps = `**Steps**
28
- 1. Identify the requested change ID (via the prompt or \`openspec list\`).
29
- 2. Run \`openspec archive <id> --yes\` to let the CLI move the change and apply spec updates without prompts (use \`--skip-specs\` only for tooling-only work).
30
- 3. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
31
- 4. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
28
+ 1. Determine the change ID to archive:
29
+ - If this prompt already includes a specific change ID (for example inside a \`<ChangeId>\` block populated by slash-command arguments), use that value after trimming whitespace.
30
+ - If the conversation references a change loosely (for example by title or summary), run \`openspec list\` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
31
+ - Otherwise, review the conversation, run \`openspec list\`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
32
+ - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
33
+ 2. Validate the change ID by running \`openspec list\` (or \`openspec show <id>\`) and stop if the change is missing, already archived, or otherwise not ready to archive.
34
+ 3. Run \`openspec archive <id> --yes\` so the CLI moves the change and applies spec updates without prompts (use \`--skip-specs\` only for tooling-only work).
35
+ 4. Review the command output to confirm the target specs were updated and the change landed in \`changes/archive/\`.
36
+ 5. Validate with \`openspec validate --strict\` and inspect with \`openspec show <id>\` if anything looks off.`;
32
37
  const archiveReferences = `**Reference**
38
+ - Use \`openspec list\` to confirm change IDs before archiving.
33
39
  - Inspect refreshed specs with \`openspec list --specs\` and address any validation issues before handing off.`;
34
40
  export const slashCommandBodies = {
35
41
  proposal: [proposalGuardrails, proposalSteps, proposalReferences].join('\n\n'),
@@ -28,5 +28,6 @@ export declare class Validator {
28
28
  private extractRequirementText;
29
29
  private containsShallOrMust;
30
30
  private countScenarios;
31
+ private formatSectionList;
31
32
  }
32
33
  //# sourceMappingURL=validator.d.ts.map
@@ -93,6 +93,8 @@ export class Validator {
93
93
  const issues = [];
94
94
  const specsDir = path.join(changeDir, 'specs');
95
95
  let totalDeltas = 0;
96
+ const missingHeaderSpecs = [];
97
+ const emptySectionSpecs = [];
96
98
  try {
97
99
  const entries = await fs.readdir(specsDir, { withFileTypes: true });
98
100
  for (const entry of entries) {
@@ -109,6 +111,23 @@ export class Validator {
109
111
  }
110
112
  const plan = parseDeltaSpec(content);
111
113
  const entryPath = `${specName}/spec.md`;
114
+ const sectionNames = [];
115
+ if (plan.sectionPresence.added)
116
+ sectionNames.push('## ADDED Requirements');
117
+ if (plan.sectionPresence.modified)
118
+ sectionNames.push('## MODIFIED Requirements');
119
+ if (plan.sectionPresence.removed)
120
+ sectionNames.push('## REMOVED Requirements');
121
+ if (plan.sectionPresence.renamed)
122
+ sectionNames.push('## RENAMED Requirements');
123
+ const hasSections = sectionNames.length > 0;
124
+ const hasEntries = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
125
+ if (!hasEntries) {
126
+ if (hasSections)
127
+ emptySectionSpecs.push({ path: entryPath, sections: sectionNames });
128
+ else
129
+ missingHeaderSpecs.push(entryPath);
130
+ }
112
131
  const addedNames = new Set();
113
132
  const modifiedNames = new Set();
114
133
  const removedNames = new Set();
@@ -216,6 +235,20 @@ export class Validator {
216
235
  catch {
217
236
  // If no specs dir, treat as no deltas
218
237
  }
238
+ for (const { path: specPath, sections } of emptySectionSpecs) {
239
+ issues.push({
240
+ level: 'ERROR',
241
+ path: specPath,
242
+ message: `Delta sections ${this.formatSectionList(sections)} were found, but no requirement entries parsed. Ensure each section includes at least one "### Requirement:" block (REMOVED may use bullet list syntax).`,
243
+ });
244
+ }
245
+ for (const path of missingHeaderSpecs) {
246
+ issues.push({
247
+ level: 'ERROR',
248
+ path,
249
+ message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
250
+ });
251
+ }
219
252
  if (totalDeltas === 0) {
220
253
  issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
221
254
  }
@@ -334,17 +367,26 @@ export class Validator {
334
367
  }
335
368
  extractRequirementText(blockRaw) {
336
369
  const lines = blockRaw.split('\n');
337
- // Skip header
370
+ // Skip header line (index 0)
338
371
  let i = 1;
339
- const bodyLines = [];
372
+ // Find the first substantial text line, skipping metadata and blank lines
340
373
  for (; i < lines.length; i++) {
341
374
  const line = lines[i];
375
+ // Stop at scenario headers
342
376
  if (/^####\s+/.test(line))
343
- break; // scenarios start
344
- bodyLines.push(line);
377
+ break;
378
+ const trimmed = line.trim();
379
+ // Skip blank lines
380
+ if (trimmed.length === 0)
381
+ continue;
382
+ // Skip metadata lines (lines starting with ** like **ID**, **Priority**, etc.)
383
+ if (/^\*\*[^*]+\*\*:/.test(trimmed))
384
+ continue;
385
+ // Found first non-metadata, non-blank line - this is the requirement text
386
+ return trimmed;
345
387
  }
346
- const text = bodyLines.join('\n').split('\n').map(l => l.trim()).find(l => l.length > 0);
347
- return text;
388
+ // No requirement text found
389
+ return undefined;
348
390
  }
349
391
  containsShallOrMust(text) {
350
392
  return /\b(SHALL|MUST)\b/.test(text);
@@ -353,5 +395,14 @@ export class Validator {
353
395
  const matches = blockRaw.match(/^####\s+/gm);
354
396
  return matches ? matches.length : 0;
355
397
  }
398
+ formatSectionList(sections) {
399
+ if (sections.length === 0)
400
+ return '';
401
+ if (sections.length === 1)
402
+ return sections[0];
403
+ const head = sections.slice(0, -1);
404
+ const last = sections[sections.length - 1];
405
+ return `${head.join(', ')} and ${last}`;
406
+ }
356
407
  }
357
408
  //# sourceMappingURL=validator.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fission-ai/openspec",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",
@@ -43,7 +43,7 @@
43
43
  "@changesets/cli": "^2.27.7",
44
44
  "@types/node": "^24.2.0",
45
45
  "@vitest/ui": "^3.2.4",
46
- "typescript": "^5.9.2",
46
+ "typescript": "^5.9.3",
47
47
  "vitest": "^3.2.4"
48
48
  },
49
49
  "dependencies": {