@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.
- package/README.md +24 -3
- package/dist/commands/change.js +6 -6
- package/dist/core/archive.d.ts +1 -0
- package/dist/core/archive.js +3 -2
- package/dist/core/config.js +7 -0
- package/dist/core/configurators/cline.d.ts +8 -0
- package/dist/core/configurators/cline.js +15 -0
- package/dist/core/configurators/codebuddy.d.ts +8 -0
- package/dist/core/configurators/codebuddy.js +15 -0
- package/dist/core/configurators/costrict.d.ts +8 -0
- package/dist/core/configurators/costrict.js +15 -0
- package/dist/core/configurators/qoder.d.ts +30 -0
- package/dist/core/configurators/qoder.js +42 -0
- package/dist/core/configurators/qwen.d.ts +24 -0
- package/dist/core/configurators/qwen.js +37 -0
- package/dist/core/configurators/registry.js +15 -0
- package/dist/core/configurators/slash/auggie.d.ts +9 -0
- package/dist/core/configurators/slash/auggie.js +31 -0
- package/dist/core/configurators/slash/cline.d.ts +9 -0
- package/dist/core/configurators/slash/cline.js +23 -0
- package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
- package/dist/core/configurators/slash/codebuddy.js +37 -0
- package/dist/core/configurators/slash/costrict.d.ts +9 -0
- package/dist/core/configurators/slash/costrict.js +31 -0
- package/dist/core/configurators/slash/crush.d.ts +9 -0
- package/dist/core/configurators/slash/crush.js +37 -0
- package/dist/core/configurators/slash/opencode.d.ts +3 -0
- package/dist/core/configurators/slash/opencode.js +41 -2
- package/dist/core/configurators/slash/qoder.d.ts +35 -0
- package/dist/core/configurators/slash/qoder.js +76 -0
- package/dist/core/configurators/slash/qwen.d.ts +37 -0
- package/dist/core/configurators/slash/qwen.js +74 -0
- package/dist/core/configurators/slash/registry.js +21 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.js +68 -17
- package/dist/core/parsers/requirement-blocks.d.ts +6 -0
- package/dist/core/parsers/requirement-blocks.js +28 -5
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +5 -5
- package/dist/core/templates/cline-template.d.ts +2 -0
- package/dist/core/templates/cline-template.js +2 -0
- package/dist/core/templates/costrict-template.d.ts +2 -0
- package/dist/core/templates/costrict-template.js +2 -0
- package/dist/core/templates/index.d.ts +2 -0
- package/dist/core/templates/index.js +8 -0
- package/dist/core/templates/slash-command-templates.js +10 -4
- package/dist/core/validation/validator.d.ts +1 -0
- package/dist/core/validation/validator.js +57 -6
- 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
|
|
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
|
|
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
|
|
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.
|
|
@@ -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.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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'),
|
|
@@ -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
|
-
|
|
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;
|
|
344
|
-
|
|
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
|
-
|
|
347
|
-
return
|
|
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.
|
|
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.
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
47
|
"vitest": "^3.2.4"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|