@fission-ai/openspec 1.14.0 → 1.14.1

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 (44) hide show
  1. package/dist/cli/commands/config.d.ts +8 -0
  2. package/dist/cli/commands/config.js +88 -0
  3. package/dist/cli/commands/context.d.ts +3 -0
  4. package/dist/cli/commands/context.js +20 -0
  5. package/dist/cli/commands/doctor.d.ts +3 -0
  6. package/dist/cli/commands/doctor.js +18 -0
  7. package/dist/cli/commands/schema.d.ts +6 -0
  8. package/dist/cli/commands/schema.js +57 -0
  9. package/dist/cli/commands/spec.d.ts +3 -0
  10. package/dist/cli/commands/spec.js +42 -0
  11. package/dist/cli/commands/store.d.ts +3 -0
  12. package/dist/cli/commands/store.js +130 -0
  13. package/dist/cli/commands/workset.d.ts +3 -0
  14. package/dist/cli/commands/workset.js +96 -0
  15. package/dist/cli/index.js +68 -43
  16. package/dist/commands/config.d.ts +16 -7
  17. package/dist/commands/config.js +342 -395
  18. package/dist/commands/context.d.ts +8 -2
  19. package/dist/commands/context.js +22 -37
  20. package/dist/commands/doctor.d.ts +6 -7
  21. package/dist/commands/doctor.js +14 -27
  22. package/dist/commands/schema.d.ts +19 -5
  23. package/dist/commands/schema.js +662 -702
  24. package/dist/commands/spec.d.ts +13 -4
  25. package/dist/commands/spec.js +91 -120
  26. package/dist/commands/store.d.ts +27 -2
  27. package/dist/commands/store.js +1 -120
  28. package/dist/commands/workflow/default-schema.d.ts +2 -0
  29. package/dist/commands/workflow/default-schema.js +4 -0
  30. package/dist/commands/workflow/instructions.js +1 -1
  31. package/dist/commands/workflow/shared.d.ts +1 -1
  32. package/dist/commands/workflow/shared.js +1 -1
  33. package/dist/commands/workset.d.ts +22 -2
  34. package/dist/commands/workset.js +1 -88
  35. package/dist/core/archive.js +13 -5
  36. package/dist/core/completion-tip.js +3 -1
  37. package/dist/core/config-schema.d.ts +1 -1
  38. package/dist/core/templates/workflows/apply-change.js +4 -3
  39. package/dist/core/templates/workflows/continue-change.js +17 -0
  40. package/dist/core/templates/workflows/verify-change.js +12 -12
  41. package/dist/core/validation/validator.js +12 -1
  42. package/dist/core/view.js +2 -26
  43. package/package.json +1 -1
  44. package/schemas/spec-driven/schema.yaml +28 -4
@@ -247,6 +247,14 @@ function toArchiveDiagnostic(error) {
247
247
  if (isRootSelectionError(error)) {
248
248
  return error.diagnostic;
249
249
  }
250
+ if (error instanceof RetirementCleanupError) {
251
+ return {
252
+ severity: 'error',
253
+ code: 'archive_retirement_cleanup_failed',
254
+ message: error.message,
255
+ fix: 'Inspect the archived change and all recovery paths in this diagnostic; preserve any needed content before cleanup.',
256
+ };
257
+ }
250
258
  return {
251
259
  severity: 'error',
252
260
  code: 'archive_error',
@@ -379,7 +387,7 @@ async function assertCopiedDirectoryUnchanged(stagedSource, destination, expecte
379
387
  */
380
388
  class MoveDestinationRetainedError extends Error {
381
389
  }
382
- class RetirementBackupsRetainedError extends Error {
390
+ class RetirementCleanupError extends Error {
383
391
  }
384
392
  function isFallbackRenameCode(code) {
385
393
  return code === 'EPERM' || code === 'EXDEV';
@@ -1012,12 +1020,12 @@ async function finalizeRetirementBackups(snapshots, mainSpecsDir) {
1012
1020
  snapshot.displacedPath = undefined;
1013
1021
  }
1014
1022
  catch (error) {
1015
- errors.push(`Could not remove the committed retirement backup at ${displacedPath} ` +
1023
+ errors.push(`Could not finalize the retirement backup at ${displacedPath} ` +
1016
1024
  `(${error instanceof Error ? error.message : String(error)}).`);
1017
1025
  }
1018
1026
  }
1019
1027
  if (errors.length > 0) {
1020
- throw new RetirementBackupsRetainedError(`${errors.join(' ')} The change remains archived and each listed backup was retained for recovery.`);
1028
+ throw new RetirementCleanupError(`${errors.join(' ')} The change was archived, but retirement cleanup did not complete. Inspect all reported recovery paths before recovery or cleanup.`);
1021
1029
  }
1022
1030
  }
1023
1031
  export class ArchiveCommand {
@@ -1777,11 +1785,11 @@ export class ArchiveCommand {
1777
1785
  await finalizeRetirementBackups(specSnapshots, mainSpecsDir);
1778
1786
  }
1779
1787
  catch (cleanupError) {
1780
- throw new RetirementBackupsRetainedError(`${error.message} ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
1788
+ throw new RetirementCleanupError(`${error.message} ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
1781
1789
  }
1782
1790
  throw error;
1783
1791
  }
1784
- if (error instanceof RetirementBackupsRetainedError)
1792
+ if (error instanceof RetirementCleanupError)
1785
1793
  throw error;
1786
1794
  const rollbackErrors = [];
1787
1795
  try {
@@ -22,7 +22,6 @@ import { getGlobalConfigPath } from './global-config.js';
22
22
  import { writeFileAtomically } from './file-state.js';
23
23
  import { isCiEnvironment } from '../utils/ci.js';
24
24
  import { detectShell } from '../utils/shell-detection.js';
25
- import { CompletionFactory } from './completions/factory.js';
26
25
  export const COMPLETION_TIP_MESSAGE = "Tip: Run 'openspec completion install' for shell completions";
27
26
  function isSuppressedByEnv() {
28
27
  // isCiEnvironment, not a CI==='true' string check: providers set CI to "True",
@@ -54,6 +53,9 @@ async function decideTip() {
54
53
  if (!shell) {
55
54
  return 'retire';
56
55
  }
56
+ // Loaded only here: every shell's generator and installer, needed only on
57
+ // the rare run that still owes the tip.
58
+ const { CompletionFactory } = await import('./completions/factory.js');
57
59
  return (await CompletionFactory.createInstaller(shell).isInstalled())
58
60
  ? 'retire'
59
61
  : 'show';
@@ -10,9 +10,9 @@ export declare const GlobalConfigSchema: z.ZodObject<{
10
10
  core: "core";
11
11
  }>>>;
12
12
  delivery: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
13
- commands: "commands";
14
13
  both: "both";
15
14
  skills: "skills";
15
+ commands: "commands";
16
16
  }>>>;
17
17
  workflows: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
18
  defaultStore: z.ZodOptional<z.ZodString>;
@@ -66,7 +66,7 @@ ${PROJECT_ROOT_GUARD}
66
66
  - If \`state: "blocked"\`: show the message and pause implementation.
67
67
  - If \`missingArtifacts\` is non-empty: ${BLOCKED_STATE_HANDOFF}
68
68
  - Otherwise, follow the CLI instruction to create or repair the schema-configured tracking file from existing planning artifacts. Do not assume another artifact is ready or start implementation while blocked.
69
- - If \`state: "all_done"\`: congratulate, suggest archive
69
+ - If \`state: "all_done"\`: report that all tracked tasks are complete and suggest review or verification as appropriate before archiving
70
70
  - Otherwise: proceed to implementation
71
71
 
72
72
  Treat \`context\` as a required prompt-level input. Read and consider it, and
@@ -125,7 +125,7 @@ ${PROJECT_ROOT_GUARD}
125
125
  Display:
126
126
  - Tasks completed this session
127
127
  - Overall progress: "N/M tasks complete"
128
- - If all done: suggest archive
128
+ - If all done: report that tracked tasks are complete and suggest review or verification as appropriate before archiving
129
129
  - If paused: explain why and wait for guidance
130
130
 
131
131
  **Output During Implementation**
@@ -156,7 +156,8 @@ Working on task 4/7: <task description>
156
156
  - [x] Task 2
157
157
  ...
158
158
 
159
- All tasks complete! ${ARCHIVE_HANDOFF}
159
+ All tracked tasks are complete. Review or verify the change as appropriate
160
+ before archiving. ${ARCHIVE_HANDOFF}
160
161
  \`\`\`
161
162
 
162
163
  **Output On Pause (Issue Encountered)**
@@ -7,6 +7,15 @@ import { PROJECT_ROOT_GUARD } from './project-root.js';
7
7
  * optional-workflow.ts).
8
8
  */
9
9
  const PLANNING_COMPLETE_HANDOFF = optionalWorkflow('apply', 'You can now implement this change with `/opsx:apply`.', 'You can now implement this change - `openspec instructions apply --change "<name>" --json` returns the tasks and how to work them.') + ' ' + optionalWorkflow('archive', 'Once implementation and any tracked work are complete, archive it with `/opsx:archive`.', 'Once implementation and any tracked work are complete, archive it with `openspec archive "<name>"`.');
10
+ /**
11
+ * A glob artifact reads `done` once one matching file exists, so a run
12
+ * interrupted while writing several files leaves it `done` with files missing,
13
+ * and the next run would move on without reading its instruction again (#2034).
14
+ */
15
+ const PARTIAL_ARTIFACT_CHECK = `**Before anything else, finish a partly written artifact**:
16
+ - An artifact whose output is a glob pattern (e.g. \`specs/**/*.md\`) reads \`done\` as soon as one matching file exists, so a run interrupted while writing its files leaves it \`done\` with some still missing
17
+ - For each such \`done\` artifact, first get its instructions and read any completed dependencies needed to determine the expected files. Then compare those files with \`artifactPaths.<id>.existingOutputPaths\`. For spec-driven's \`specs\`, read the proposal and check for one \`specs/<capability-path>/spec.md\` per capability it lists; resolve each expected path against \`changeRoot\` before comparing path identity
18
+ - If any are missing, write only the missing files, and STOP - that counts as this invocation's ONE artifact. If you cannot tell whether a file was left out on purpose, ask the user`;
10
19
  export function getContinueChangeSkillTemplate() {
11
20
  return {
12
21
  name: 'openspec-continue-change',
@@ -51,6 +60,10 @@ ${PROJECT_ROOT_GUARD}
51
60
 
52
61
  ---
53
62
 
63
+ ${PARTIAL_ARTIFACT_CHECK}
64
+
65
+ ---
66
+
54
67
  **If all planning artifacts are complete (\`isPlanningComplete: true\`, or legacy \`isComplete: true\`)**:
55
68
  - Congratulate the user
56
69
  - Show final status including the schema used
@@ -169,6 +182,10 @@ ${PROJECT_ROOT_GUARD}
169
182
 
170
183
  ---
171
184
 
185
+ ${PARTIAL_ARTIFACT_CHECK}
186
+
187
+ ---
188
+
172
189
  **If all planning artifacts are complete (\`isPlanningComplete: true\`, or legacy \`isComplete: true\`)**:
173
190
  - Congratulate the user
174
191
  - Show final status including the schema used
@@ -76,9 +76,9 @@ ${PROJECT_ROOT_GUARD}
76
76
  - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
77
77
 
78
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.
81
- - If delta specs exist in \`contextFiles.specs\`:
79
+ - If status marks the spec artifact skipped by \`skip_specs: true\`, or the schema defines no spec artifact (no artifact whose \`artifactPaths.<id>.outputPath\` is under \`specs/\`), report the spec-dependent checks as not applicable.
80
+ - Otherwise, \`contextFiles\` is keyed by artifact id, and artifact ids come from the active schema, so do not assume an id such as \`specs\`. The spec artifacts are those whose \`artifactPaths.<id>.outputPath\` is under \`specs/\`; read their files from \`contextFiles.<id>\`. If those spec files are absent or empty, mark **Spec Coverage**, **Requirement Implementation Mapping**, and **Scenario Coverage** as not verified; do not treat any of them as clean.
81
+ - If delta specs exist in those spec files:
82
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
83
  - For each ADDED or MODIFIED requirement (for MODIFIED, check the text in the delta, not the old wording):
84
84
  - Search codebase for keywords related to the requirement
@@ -127,14 +127,14 @@ ${PROJECT_ROOT_GUARD}
127
127
  7. **Verify Coherence**
128
128
 
129
129
  **Design Adherence**:
130
- - If the schema defines no design artifact, report **Design Adherence** as not applicable.
131
- - If \`contextFiles.design\` exists:
130
+ - If the schema defines no design artifact (no artifact with id \`design\`, and none whose \`artifactPaths.<id>.outputPath\` is or ends in \`design.md\`), report **Design Adherence** as not applicable.
131
+ - If the design artifact's \`contextFiles.<id>\` file exists:
132
132
  - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
133
133
  - Verify implementation follows those decisions
134
134
  - If contradiction detected:
135
135
  - Add WARNING: "Design decision not followed: <decision>"
136
136
  - Recommendation: "Update implementation or revise design.md to match reality"
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.
137
+ - Otherwise, if the design artifact's \`contextFiles.<id>\` file 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.
138
138
 
139
139
  **Code Pattern Consistency**:
140
140
  - If implementation changes cannot be identified, mark **Code Pattern Consistency** as not verified and explain the missing evidence.
@@ -285,9 +285,9 @@ ${PROJECT_ROOT_GUARD}
285
285
  - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"
286
286
 
287
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.
290
- - If delta specs exist in \`contextFiles.specs\`:
288
+ - If status marks the spec artifact skipped by \`skip_specs: true\`, or the schema defines no spec artifact (no artifact whose \`artifactPaths.<id>.outputPath\` is under \`specs/\`), report the spec-dependent checks as not applicable.
289
+ - Otherwise, \`contextFiles\` is keyed by artifact id, and artifact ids come from the active schema, so do not assume an id such as \`specs\`. The spec artifacts are those whose \`artifactPaths.<id>.outputPath\` is under \`specs/\`; read their files from \`contextFiles.<id>\`. If those spec files are absent or empty, mark **Spec Coverage**, **Requirement Implementation Mapping**, and **Scenario Coverage** as not verified; do not treat any of them as clean.
290
+ - If delta specs exist in those spec files:
291
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
292
  - For each ADDED or MODIFIED requirement (for MODIFIED, check the text in the delta, not the old wording):
293
293
  - Search codebase for keywords related to the requirement
@@ -336,14 +336,14 @@ ${PROJECT_ROOT_GUARD}
336
336
  7. **Verify Coherence**
337
337
 
338
338
  **Design Adherence**:
339
- - If the schema defines no design artifact, report **Design Adherence** as not applicable.
340
- - If \`contextFiles.design\` exists:
339
+ - If the schema defines no design artifact (no artifact with id \`design\`, and none whose \`artifactPaths.<id>.outputPath\` is or ends in \`design.md\`), report **Design Adherence** as not applicable.
340
+ - If the design artifact's \`contextFiles.<id>\` file exists:
341
341
  - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")
342
342
  - Verify implementation follows those decisions
343
343
  - If contradiction detected:
344
344
  - Add WARNING: "Design decision not followed: <decision>"
345
345
  - Recommendation: "Update implementation or revise design.md to match reality"
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.
346
+ - Otherwise, if the design artifact's \`contextFiles.<id>\` file 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.
347
347
 
348
348
  **Code Pattern Consistency**:
349
349
  - If implementation changes cannot be identified, mark **Code Pattern Consistency** as not verified and explain the missing evidence.
@@ -252,6 +252,17 @@ export class Validator {
252
252
  message: this.buildMissingShallOrMustMessage(`ADDED "${block.name}"`, block.name, true),
253
253
  });
254
254
  }
255
+ // Same limit the main spec enforces after archive (#1976), so
256
+ // `validate <change> --strict` catches a new overlong requirement
257
+ // before it lands. MODIFIED is left alone: its text is the existing
258
+ // requirement, which the specs instruction says to keep whole.
259
+ if (requirementText && requirementText.length > MAX_REQUIREMENT_TEXT_LENGTH) {
260
+ issues.push({
261
+ level: 'WARNING',
262
+ path: entryPath,
263
+ message: `ADDED "${block.name}": ${VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG}`,
264
+ });
265
+ }
255
266
  const scenarioCount = this.countScenarios(block.raw);
256
267
  if (scenarioCount < 1) {
257
268
  issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario${this.emptyScenarioHint(block.raw)}` });
@@ -711,7 +722,7 @@ export class Validator {
711
722
  spec.requirements.forEach((req, index) => {
712
723
  if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
713
724
  issues.push({
714
- level: 'INFO',
725
+ level: 'WARNING',
715
726
  path: `requirements[${index}]`,
716
727
  message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
717
728
  });
package/dist/core/view.js CHANGED
@@ -52,14 +52,6 @@ export class ViewCommand {
52
52
  console.log(` ${chalk.green('✓')} ${change.name}`);
53
53
  });
54
54
  }
55
- // Display archived changes
56
- if (changesData.archived.length > 0) {
57
- console.log(chalk.bold.gray('\nArchived Changes'));
58
- console.log('─'.repeat(60));
59
- changesData.archived.forEach((change) => {
60
- console.log(chalk.gray(` ◦ ${change.name}`));
61
- });
62
- }
63
55
  // Display specifications
64
56
  if (specsData.length > 0) {
65
57
  console.log(chalk.bold.blue('\nSpecifications'));
@@ -78,25 +70,11 @@ export class ViewCommand {
78
70
  const changesDir = path.join(openspecDir, 'changes');
79
71
  const projectRoot = path.dirname(openspecDir);
80
72
  if (!fs.existsSync(changesDir)) {
81
- return { draft: [], active: [], completed: [], archived: [] };
73
+ return { draft: [], active: [], completed: [] };
82
74
  }
83
75
  const draft = [];
84
76
  const active = [];
85
77
  const completed = [];
86
- let archived = [];
87
- try {
88
- archived = fs.readdirSync(path.join(changesDir, 'archive'), { withFileTypes: true })
89
- .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
90
- .map((entry) => ({ name: entry.name }));
91
- }
92
- catch (error) {
93
- // A missing archive, or an `archive` path that is a file, has no archived
94
- // changes to show; neither should break the rest of the dashboard.
95
- const code = error.code;
96
- if (code !== 'ENOENT' && code !== 'ENOTDIR') {
97
- throw error;
98
- }
99
- }
100
78
  const entries = fs.readdirSync(changesDir, { withFileTypes: true });
101
79
  for (const entry of entries) {
102
80
  if (entry.isDirectory() && entry.name !== 'archive') {
@@ -136,8 +114,7 @@ export class ViewCommand {
136
114
  return a.name.localeCompare(b.name);
137
115
  });
138
116
  completed.sort((a, b) => a.name.localeCompare(b.name));
139
- archived.sort((a, b) => a.name.localeCompare(b.name));
140
- return { draft, active, completed, archived };
117
+ return { draft, active, completed };
141
118
  }
142
119
  async getSpecsData(openspecDir) {
143
120
  const specsDir = path.join(openspecDir, 'specs');
@@ -182,7 +159,6 @@ export class ViewCommand {
182
159
  }
183
160
  console.log(` ${chalk.yellow('●')} Active Changes: ${chalk.bold(changesData.active.length)} in progress`);
184
161
  console.log(` ${chalk.green('●')} Completed Changes: ${chalk.bold(changesData.completed.length)}`);
185
- console.log(` ${chalk.gray('●')} Archived Changes: ${chalk.bold(changesData.archived.length)}`);
186
162
  if (totalTasks > 0) {
187
163
  const overallProgress = Math.round((completedTasks / totalTasks) * 100);
188
164
  console.log(` ${chalk.magenta('●')} Task Progress: ${chalk.bold(`${completedTasks}/${totalTasks}`)} (${overallProgress}% complete)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fission-ai/openspec",
3
- "version": "1.14.0",
3
+ "version": "1.14.1",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",
@@ -77,6 +77,17 @@ artifacts:
77
77
  - New capabilities: use the exact path from the proposal at `specs/<capability-path>/spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout.
78
78
  - Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Run `openspec list --specs` to confirm that path before writing the delta, appending `--store "<id>"` only for a registered standalone store - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability.
79
79
 
80
+ Write the spec files one at a time, so the user sees progress and an
81
+ interrupted run keeps the files it already wrote:
82
+ - Before writing anything, compare the capabilities the proposal lists
83
+ with the `specs/<capability-path>/spec.md` files that already exist,
84
+ and start with the first capability that has no file. Do not rewrite
85
+ an existing file unless the user asks you to.
86
+ - Before each file, say in one line which capability you are writing.
87
+ - Write each file as soon as you have drafted it. Do not plan every file
88
+ before writing the first.
89
+ - Stop when every capability the proposal lists has its file.
90
+
80
91
  There must be at least one spec file unless the change's `.openspec.yaml`
81
92
  sets `skip_specs: true` (no spec-level behavior change) - `openspec validate`
82
93
  rejects a zero-delta change without that marker. If the proposal lists no
@@ -94,7 +105,7 @@ artifacts:
94
105
  - Each scenario: `#### Scenario: <name>` with WHEN/THEN format
95
106
  - **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
96
107
  - Every requirement MUST have at least one scenario.
97
- - Keep each requirement's description (the text between `### Requirement:` and its first scenario) to 500 characters or fewer. `openspec validate` flags longer descriptions once they reach the main spec. This is an informational hint, not an error. When writing a new requirement, state one behavior per requirement: move examples and edge cases into scenarios, and split a requirement that covers several behaviors into separate `### Requirement:` blocks, each with its own scenarios. Under MODIFIED, keep the existing requirement block whole; never split, trim or rewrite existing text just to meet the length.
108
+ - Keep each requirement's description (the text between `### Requirement:` and its first scenario) to 500 characters or fewer. `openspec validate` flags longer descriptions in ADDED requirements and in the main spec. This is a warning: normal validation still passes, but `openspec validate --strict` fails on it. When writing a new requirement, state one behavior per requirement: move examples and edge cases into scenarios, and split a requirement that covers several behaviors into separate `### Requirement:` blocks, each with its own scenarios. Under MODIFIED, keep the existing requirement block whole; never split, trim or rewrite existing text just to meet the length. Split an existing long requirement only when the user asks for it, in a change made for that purpose: under MODIFIED, keep its header and every scenario and cut its description down to one behavior without changing its meaning, then add each behavior you removed as its own ADDED requirement with its own scenarios.
98
109
 
99
110
  New capabilities only: the delta spec's first section is `## Purpose` -
100
111
  one or two sentences (50+ characters, or `openspec validate --strict`
@@ -195,17 +206,22 @@ artifacts:
195
206
  would change what gets built, resolve them with the user first - do not
196
207
  bake an unstated assumption into the task list.
197
208
 
198
- **IMPORTANT: Follow the template below exactly.** The apply phase parses
209
+ **IMPORTANT: Follow the template below for tracked tasks.** The apply phase parses
199
210
  checkbox format to track progress. A box holding only `x` counts as done,
200
211
  upper or lower case and with any spacing, so `- [ x]` is done too. Every
201
212
  other marker, including `- [~]`, `- [-]` and an empty `- []`, reads as
202
213
  unfinished. A line with no checkbox is not tracked at all.
203
214
 
204
215
  Guidelines:
205
- - Group related tasks under ## numbered headings
206
- - Each task MUST be a checkbox: `- [ ] X.Y Task description`
216
+ - Group related tracked tasks under ## numbered headings
217
+ - Each tracked task MUST be a checkbox: `- [ ] X.Y Task description`
207
218
  - Tasks should be small enough to complete in one session
208
219
  - Order tasks by dependency (what must be done first?)
220
+ - Track implementation and verification work that can be completed before
221
+ archive. If the requested workflow includes archive or work that requires
222
+ this change to be archived, preserve those steps as plain bullets in an
223
+ optional `## Workflow follow-up` section at the end of tasks.md. These
224
+ bullets are reference information outside tracked task progress.
209
225
  - Each task MUST state how to verify completion (a test, command,
210
226
  observable behavior, or delivered artifact). Put the verification in
211
227
  that task's checkbox description. Use a separate verification task only
@@ -235,6 +251,14 @@ artifacts:
235
251
  - [ ] 2.3 Document the export API in docs/export.md and verify the documented command runs as written
236
252
  ```
237
253
 
254
+ When applicable, append workflow follow-up as plain bullets, for example:
255
+ ```
256
+ ## Workflow follow-up
257
+
258
+ - Archive the change after the project's review requirements are satisfied.
259
+ - Verify the archived result.
260
+ ```
261
+
238
262
  Reference specs for what needs to be built, design for how to build it.
239
263
  requires:
240
264
  - specs