freeharness 0.4.1 → 0.4.3

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 (203) hide show
  1. package/NOTICE +2 -1
  2. package/README.md +24 -19
  3. package/canonical/HARVEST.yaml +27 -16
  4. package/canonical/PROVENANCE.yaml +3 -3
  5. package/canonical/agents/README.md +77 -36
  6. package/canonical/agents/agentic-architect.md +2 -1
  7. package/canonical/agents/database-reviewer.md +6 -8
  8. package/canonical/agents/explorer-ai-engineer-analyst.md +2 -6
  9. package/canonical/agents/implementer.md +30 -0
  10. package/canonical/agents/pr-reviewer.md +9 -11
  11. package/canonical/agents/python-reviewer.md +7 -9
  12. package/canonical/agents/report-formatter.md +7 -3
  13. package/canonical/agents/security-reviewer.md +14 -9
  14. package/canonical/agents/test-writer.md +5 -2
  15. package/canonical/hooks/cost-prices.json +11 -9
  16. package/canonical/hooks/freeharness-config-guard.mjs +4155 -93
  17. package/canonical/hooks/freeharness-context-monitor.mjs +88 -54
  18. package/canonical/hooks/freeharness-converge.mjs +78 -23
  19. package/canonical/hooks/freeharness-cost-tracker.mjs +1116 -218
  20. package/canonical/hooks/freeharness-memory-guard.mjs +20 -23
  21. package/canonical/hooks/freeharness-precompact.mjs +206 -87
  22. package/canonical/hooks/freeharness-quota-tap.mjs +173 -0
  23. package/canonical/hooks/freeharness-reload-context.mjs +197 -0
  24. package/canonical/hooks/freeharness-repeat-tool.mjs +1 -1
  25. package/canonical/hooks/freeharness-sdd-context.mjs +959 -0
  26. package/canonical/hooks/freeharness-session-start +297 -59
  27. package/canonical/hooks/hooks.claude-code.json +1 -1
  28. package/canonical/hooks/hooks.config-guard.claude-code.json +2 -2
  29. package/canonical/hooks/hooks.context-monitor.claude-code.json +1 -1
  30. package/canonical/hooks/hooks.converge.claude-code.json +1 -1
  31. package/canonical/hooks/hooks.memory-guard.claude-code.json +1 -1
  32. package/canonical/hooks/hooks.reload-context.claude-code.json +15 -0
  33. package/canonical/openspec/schema.spec-driven.yaml +29 -8
  34. package/canonical/openspec/templates/follow-ups.md +3 -2
  35. package/canonical/openspec/templates/proposal.md +1 -1
  36. package/canonical/openspec/templates/tasks.md +8 -0
  37. package/canonical/rules/CLAUDE.md +5 -4
  38. package/canonical/rules/routing.md +10 -9
  39. package/canonical/skills/agent-evaluation/SKILL.md +17 -6
  40. package/canonical/skills/agent-evaluation/evals/cases.yaml +38 -23
  41. package/canonical/skills/agentic-architecture/SKILL.md +4 -0
  42. package/canonical/skills/agentic-architecture/evals/cases.yaml +27 -0
  43. package/canonical/skills/code-review/SKILL.md +10 -3
  44. package/canonical/skills/code-review/evals/cases.yaml +1118 -27
  45. package/canonical/skills/git-workflow/SKILL.md +2 -0
  46. package/canonical/skills/local-code/SKILL.md +2 -2
  47. package/canonical/skills/local-code/evals/cases.yaml +1 -1
  48. package/canonical/skills/report-formatting/SKILL.md +10 -2
  49. package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +3 -3
  50. package/canonical/skills/report-formatting/reference/HELPERS.md +10 -5
  51. package/canonical/skills/report-formatting/reference/RELEASE.md +1 -2
  52. package/canonical/skills/report-formatting/reference/TEMPLATES.md +3 -2
  53. package/canonical/skills/report-formatting/reference/authoring.md +25 -37
  54. package/canonical/skills/report-formatting/scripts/build-architecture.js +1 -1
  55. package/canonical/skills/report-formatting/scripts/build-md-claro.js +5 -8
  56. package/canonical/skills/report-formatting/scripts/check-env.js +0 -4
  57. package/canonical/skills/report-formatting/scripts/docmodel.js +3 -11
  58. package/canonical/skills/report-formatting/scripts/example-build.js +8 -10
  59. package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +8 -10
  60. package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +8 -10
  61. package/canonical/skills/report-formatting/scripts/theme-claro.js +39 -10
  62. package/canonical/skills/subagent-driven-development/SKILL.md +35 -28
  63. package/canonical/skills/subagent-driven-development/evals/cases.yaml +3625 -19
  64. package/canonical/skills/subagent-driven-development/reference/brief.md +187 -35
  65. package/canonical/skills/subagent-driven-development/reference/handoff.md +96 -2
  66. package/canonical/skills/subagent-driven-development/reference/loop.md +56 -0
  67. package/canonical/skills/subagent-driven-development/reference/trees.md +40 -0
  68. package/canonical/skills/subagent-driven-development/reference/workspace.md +31 -0
  69. package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +4035 -327
  70. package/dist/adapters/harness/claude-code.d.ts +26 -3
  71. package/dist/adapters/harness/claude-code.js +214 -43
  72. package/dist/adapters/harness/opencode.js +17 -3
  73. package/dist/adapters/harness/plugin.js +28 -17
  74. package/dist/adapters/memory/doctor.d.ts +172 -33
  75. package/dist/adapters/memory/doctor.js +750 -139
  76. package/dist/adapters/memory/migrate.d.ts +95 -0
  77. package/dist/adapters/memory/migrate.js +497 -0
  78. package/dist/adapters/memory/paths.d.ts +15 -0
  79. package/dist/adapters/memory/paths.js +47 -0
  80. package/dist/adapters/memory/vendored.d.ts +52 -30
  81. package/dist/adapters/memory/vendored.js +161 -169
  82. package/dist/adapters/memory/worker-control.d.ts +9 -0
  83. package/dist/adapters/memory/worker-control.js +30 -0
  84. package/dist/adapters/sync/diff.js +4 -1
  85. package/dist/cli/cleanup-prompt.d.ts +35 -0
  86. package/dist/cli/cleanup-prompt.js +68 -0
  87. package/dist/cli/cleanup.d.ts +22 -24
  88. package/dist/cli/cleanup.js +204 -75
  89. package/dist/cli/cost-quota.d.ts +158 -0
  90. package/dist/cli/cost-quota.js +645 -0
  91. package/dist/cli/cost.d.ts +408 -46
  92. package/dist/cli/cost.js +1731 -57
  93. package/dist/cli/doctor.d.ts +52 -13
  94. package/dist/cli/doctor.js +199 -45
  95. package/dist/cli/hard-cleanup.d.ts +93 -0
  96. package/dist/cli/hard-cleanup.js +552 -0
  97. package/dist/cli/install-facts.d.ts +29 -0
  98. package/dist/cli/install-facts.js +68 -0
  99. package/dist/cli/json-key-edit.d.ts +18 -0
  100. package/dist/cli/json-key-edit.js +118 -0
  101. package/dist/cli/memory-import.d.ts +74 -0
  102. package/dist/cli/memory-import.js +469 -0
  103. package/dist/cli/plugin-validate.d.ts +12 -0
  104. package/dist/cli/plugin-validate.js +43 -0
  105. package/dist/cli/profile.d.ts +1 -1
  106. package/dist/cli/profile.js +10 -10
  107. package/dist/cli/uninstall.d.ts +1 -1
  108. package/dist/cli/uninstall.js +2 -2
  109. package/dist/cli/upstream-launchers.d.ts +49 -0
  110. package/dist/cli/upstream-launchers.js +287 -0
  111. package/dist/index.js +252 -73
  112. package/dist/infra/fsutil.d.ts +30 -3
  113. package/dist/infra/fsutil.js +68 -6
  114. package/dist/infra/manifest.d.ts +2 -1
  115. package/dist/infra/manifest.js +3 -2
  116. package/dist/infra/prices.d.ts +18 -0
  117. package/dist/infra/prices.js +64 -0
  118. package/node_modules/@freeharness/core/dist/registry.d.ts +1 -1
  119. package/node_modules/@freeharness/core/dist/registry.js +3 -3
  120. package/node_modules/@freeharness/core/dist/tiers.d.ts +10 -1
  121. package/node_modules/@freeharness/core/dist/tiers.js +24 -2
  122. package/node_modules/@freeharness/core/package.json +1 -1
  123. package/node_modules/@freeharness/sdd/PROVENANCE.yaml +279 -2
  124. package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +34 -8
  125. package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +7 -6
  126. package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +72 -64
  127. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +48 -16
  128. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +168 -70
  129. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +36 -6
  130. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +110 -49
  131. package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +1 -1
  132. package/node_modules/@freeharness/sdd/dist/src/shim/list.js +57 -14
  133. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.d.ts +7 -0
  134. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.js +118 -0
  135. package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +3 -1
  136. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.d.ts +12 -0
  137. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.js +94 -0
  138. package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +17 -0
  139. package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +182 -1
  140. package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +2 -2
  141. package/node_modules/@freeharness/sdd/dist/src/shim/status.js +34 -5
  142. package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +9 -0
  143. package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +49 -41
  144. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.d.ts +21 -0
  145. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.js +66 -0
  146. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.d.ts +4 -0
  147. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.js +17 -0
  148. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +95 -22
  149. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +6 -0
  150. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +14 -6
  151. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +1 -1
  152. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +1 -1
  153. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +3 -3
  154. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +24 -4
  155. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +4 -7
  156. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +13 -1
  157. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +21 -6
  158. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +80 -19
  159. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +42 -22
  160. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.d.ts +32 -0
  161. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.js +39 -0
  162. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +16 -9
  163. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +36 -1
  164. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +65 -30
  165. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +1 -1
  166. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +1 -1
  167. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +8 -13
  168. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +79 -102
  169. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +4 -9
  170. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +50 -10
  171. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +165 -37
  172. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +24 -3
  173. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +55 -8
  174. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +4 -1
  175. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +19 -9
  176. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +10 -0
  177. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +115 -0
  178. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +3 -3
  179. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +20 -20
  180. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +6 -0
  181. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +75 -14
  182. package/node_modules/@freeharness/sdd/package.json +1 -1
  183. package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +47 -7
  184. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +25 -7
  185. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +50 -11
  186. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +48 -10
  187. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +27 -7
  188. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +25 -12
  189. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +17 -5
  190. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +12 -1
  191. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +47 -33
  192. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +17 -5
  193. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +29 -1
  194. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +31 -13
  195. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +75 -27
  196. package/package.json +6 -4
  197. package/provenance/canonical.PROVENANCE.yaml +3 -3
  198. package/provenance/memory.PROVENANCE.yaml +472 -6
  199. package/provenance/sdd.PROVENANCE.yaml +279 -2
  200. package/canonical/hooks/freeharness-auto-memory.mjs +0 -158
  201. package/canonical/hooks/hooks.auto-memory.claude-code.json +0 -16
  202. package/canonical/openspec/config.base.yaml +0 -89
  203. package/canonical/rules/safety.md +0 -41
@@ -1,6 +1,8 @@
1
1
  import { promises as fs } from 'fs';
2
+ import * as fssync from 'fs';
2
3
  import path from 'path';
3
4
  import { FileSystemUtils } from '../engine/file-system.js';
5
+ import { parseDeltaSpec } from './parsers/requirement-blocks.js';
4
6
  function assertDiscoveredSpecPath(specsRoot, capabilityDir, specFile) {
5
7
  try {
6
8
  FileSystemUtils.assertPathWithin(specsRoot, specFile);
@@ -112,3 +114,116 @@ export async function hasAnyFileUnder(dirPath) {
112
114
  }
113
115
  return false;
114
116
  }
117
+ /** Async driver of walkUnreadDeltaFiles. */
118
+ export async function findUnreadDeltaFiles(specsRoot) {
119
+ const walker = walkUnreadDeltaFiles(specsRoot);
120
+ let step = walker.next();
121
+ while (!step.done) {
122
+ const request = step.value;
123
+ let reply;
124
+ try {
125
+ switch (request.op) {
126
+ case 'readdir':
127
+ reply = await fs.readdir(request.path, { withFileTypes: true });
128
+ break;
129
+ case 'stat':
130
+ reply = await fs.stat(request.path);
131
+ break;
132
+ case 'read':
133
+ reply = await fs.readFile(request.path, 'utf-8');
134
+ break;
135
+ }
136
+ }
137
+ catch (err) {
138
+ step = walker.throw(err);
139
+ continue;
140
+ }
141
+ step = walker.next(reply);
142
+ }
143
+ return step.value;
144
+ }
145
+ /** Sync driver of walkUnreadDeltaFiles, for the instructions shim. */
146
+ export function findUnreadDeltaFilesSync(specsRoot) {
147
+ const walker = walkUnreadDeltaFiles(specsRoot);
148
+ let step = walker.next();
149
+ while (!step.done) {
150
+ const request = step.value;
151
+ let reply;
152
+ try {
153
+ switch (request.op) {
154
+ case 'readdir':
155
+ reply = fssync.readdirSync(request.path, { withFileTypes: true });
156
+ break;
157
+ case 'stat':
158
+ reply = fssync.statSync(request.path);
159
+ break;
160
+ case 'read':
161
+ reply = fssync.readFileSync(request.path, 'utf-8');
162
+ break;
163
+ }
164
+ }
165
+ catch (err) {
166
+ step = walker.throw(err);
167
+ continue;
168
+ }
169
+ step = walker.next(reply);
170
+ }
171
+ return step.value;
172
+ }
173
+ /**
174
+ * Markdown files under a change's specs/ with delta sections that are not a
175
+ * capability's `spec.md` (`specs/user-auth.md`, or `specs/user-auth/delta.md`
176
+ * beside or instead of its spec.md). discoverSpecFiles, and so validate and
177
+ * archive, never reads them, while the artifact graph's specs/ markdown glob
178
+ * does: status and apply report the specs as written and archive has nothing
179
+ * to merge. A root `spec.md` has its own check; a note without delta sections
180
+ * is not reported. Like discoverSpecFiles, the walk skips dot entries and
181
+ * dangling links and never follows a symlinked directory. A missing root
182
+ * yields []; other read failures throw. Sorted by path. Filesystem calls are
183
+ * yielded to the async and sync drivers.
184
+ */
185
+ function* walkUnreadDeltaFiles(specsRoot) {
186
+ const results = [];
187
+ function* walk(dir, segments) {
188
+ let entries;
189
+ try {
190
+ entries = (yield { op: 'readdir', path: dir });
191
+ }
192
+ catch (err) {
193
+ const code = err?.code;
194
+ if (code === 'ENOENT' || code === 'ENOTDIR')
195
+ return;
196
+ throw err;
197
+ }
198
+ for (const entry of entries) {
199
+ if (entry.name.startsWith('.'))
200
+ continue;
201
+ if (entry.isDirectory()) {
202
+ yield* walk(path.join(dir, entry.name), [...segments, entry.name]);
203
+ continue;
204
+ }
205
+ if (!entry.isFile() && !entry.isSymbolicLink())
206
+ continue;
207
+ if (entry.name === 'spec.md' || !entry.name.toLowerCase().endsWith('.md'))
208
+ continue;
209
+ const filePath = path.join(dir, entry.name);
210
+ let content;
211
+ try {
212
+ if (entry.isSymbolicLink() && !(yield { op: 'stat', path: filePath }).isFile())
213
+ continue;
214
+ content = (yield { op: 'read', path: filePath });
215
+ }
216
+ catch (err) {
217
+ if (err?.code === 'ENOENT')
218
+ continue;
219
+ throw err;
220
+ }
221
+ if (!Object.values(parseDeltaSpec(content).sectionPresence).some(Boolean))
222
+ continue;
223
+ const capability = segments.length > 0 ? segments.join('/') : entry.name.slice(0, -'.md'.length);
224
+ results.push({ path: [...segments, entry.name].join('/'), expected: `${capability}/spec.md` });
225
+ }
226
+ }
227
+ yield* walk(specsRoot, []);
228
+ return results.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
229
+ }
@@ -1,5 +1,5 @@
1
1
  export interface ParsedTask {
2
- /** Checkbox state: `[x]`/`[X]` is done, anything else is not. */
2
+ /** Checkbox state: `[x]`/`[X]` is done, every other marker (and none) is not. */
3
3
  done: boolean;
4
4
  /** Task text after the checkbox, trimmed (may be empty). */
5
5
  description: string;
@@ -17,8 +17,8 @@ export type TaskAnnotationKey = (typeof TASK_ANNOTATION_KEYS)[number];
17
17
  * A task resolved outside this change: `superseded`/`moved` name another
18
18
  * change id, `external` carries free-form reference text. `kind` is the raw
19
19
  * first token of the `Disposition:` value, so a value outside the three
20
- * documented kinds still parses (a reader such as `validate --strict` reports
21
- * it, rather than the parser silently dropping the line).
20
+ * documented kinds still parses and `validate --strict` can report it instead
21
+ * of the line vanishing.
22
22
  */
23
23
  export interface TaskDisposition {
24
24
  kind: string;
@@ -4,16 +4,20 @@
4
4
  // the single dependency `task-numbering.ts` has on task-progress. The rest of
5
5
  // task-progress (schema/artifact-graph-driven task-file resolution, progress
6
6
  // counting, unreadable-file tracking) is out of the change-validation slice and
7
- // is deliberately NOT vendored. The regex and semantics below are faithful to
8
- // upstream so task-numbering behaves identically.
7
+ // is deliberately NOT vendored. The semantics below are faithful to upstream so
8
+ // task-numbering behaves identically.
9
9
  /**
10
- * A Markdown task line: a `-`/`*` bullet carrying a `[ ]` or `[x]` checkbox.
11
- *
12
- * Leading whitespace is allowed so nested sub-tasks count like their parents.
13
- * Deliberately unanchored at the end: `.` does not match `\r`, so writing the
14
- * description group as `(.*)$` would reject every line of a CRLF tasks.md.
10
+ * A Markdown task line: a checkbox list item under any CommonMark marker (`-`,
11
+ * `*`, `+`, or an ordered `1.`/`1)` of up to nine digits; #1862), nested or not.
12
+ * The box holds at most one non-bracket token (`[~]`, `[]`, `[ x]` all count,
13
+ * #1773); only `x`/`X` reads as done, so an unknown marker counts as open
14
+ * instead of vanishing. A `(` or `[` right after the box marks a link
15
+ * (`- [A](url)`), never a task, except after a whitespace-only box:
16
+ * `- [ ](...)` is open work. Unlike upstream, no two `\s*` runs flank an
17
+ * optional token, so matching stays linear. No end anchor: `.` does not match
18
+ * the `\r` of a CRLF file.
15
19
  */
16
- const TASK_LINE_PATTERN = /^\s*[-*]\s*\[([\sxX])\]\s*(.*)/;
20
+ const TASK_LINE_PATTERN = /^\s*(?:[-*+]|\d{1,9}[.)])\s*\[(?:\s*([^\]\s])\s*\](?![([])|\](?![([])|\s+\])\s*(.*)/;
17
21
  /**
18
22
  * Parses every task line in a tasks file, in document order. Every line matching
19
23
  * the pattern counts, wherever it sits (inside a code fence, HTML comment, or an
@@ -24,18 +28,17 @@ export function parseTaskLines(content) {
24
28
  for (const line of content.split('\n')) {
25
29
  const match = line.match(TASK_LINE_PATTERN);
26
30
  if (match) {
27
- tasks.push({ done: match[1].toLowerCase() === 'x', description: match[2].trim() });
31
+ tasks.push({ done: (match[1] ?? '').toLowerCase() === 'x', description: match[2].trim() });
28
32
  }
29
33
  }
30
34
  return tasks;
31
35
  }
32
36
  // ---------------------------------------------------------------------------
33
- // FreeHarness V4-P4 (4.5): per-task annotation lines. Not in upstream.
37
+ // FreeHarness: per-task annotation lines. Not in upstream.
34
38
  //
35
- // The FreeHarness tasks template allows optional lines under a task that name
36
- // how to verify it, which interfaces it touches, its write boundary, its
37
- // dependencies and, when the task is resolved outside this change, its
38
- // disposition:
39
+ // The FreeHarness tasks template allows optional lines under a task: its verify
40
+ // command, interfaces, write boundary, dependencies and, when the task is
41
+ // resolved outside this change, its disposition:
39
42
  //
40
43
  // - [ ] 2.1 Implement the parser
41
44
  // Verify: node --test test/parser.test.ts
@@ -44,10 +47,8 @@ export function parseTaskLines(content) {
44
47
  // Depends: 1.2
45
48
  // Disposition: moved other-change
46
49
  //
47
- // They carry no checkbox, so `parseTaskLines` above never counts them; this
48
- // section only makes the convention explicit (one place defines the keys) and
49
- // attaches the annotations to the task they follow for readers such as the
50
- // converge hook and the subagent-driven-development briefs.
50
+ // They carry no checkbox, so `parseTaskLines` never counts them; this section
51
+ // defines the keys once and attaches each annotation to the task it follows.
51
52
  // ---------------------------------------------------------------------------
52
53
  /** The annotation keys the tasks template documents. */
53
54
  export const TASK_ANNOTATION_KEYS = ['Verify', 'Interfaces', 'Boundary', 'Depends', 'Disposition'];
@@ -80,7 +81,7 @@ export function parseAnnotatedTaskLines(content) {
80
81
  const task = line.match(TASK_LINE_PATTERN);
81
82
  if (task) {
82
83
  current = {
83
- done: task[1].toLowerCase() === 'x',
84
+ done: (task[1] ?? '').toLowerCase() === 'x',
84
85
  description: task[2].trim(),
85
86
  line: i + 1,
86
87
  annotations: {},
@@ -98,7 +99,6 @@ export function parseAnnotatedTaskLines(content) {
98
99
  current.disposition = parseDisposition(value);
99
100
  continue;
100
101
  }
101
- // A blank line or any other prose ends the annotation run.
102
102
  current = undefined;
103
103
  }
104
104
  return tasks;
@@ -90,5 +90,11 @@ export declare class Validator {
90
90
  */
91
91
  private buildMissingShallOrMustMessage;
92
92
  private countScenarios;
93
+ /**
94
+ * Why a requirement with a visible scenario header still has no scenario:
95
+ * the header has no body, and the spec path archive validates against does
96
+ * not count it. Empty when the block has no bare scenario header.
97
+ */
98
+ private emptyScenarioHint;
93
99
  private formatSectionList;
94
100
  }
@@ -4,11 +4,11 @@ import { SpecSchema, ChangeSchema } from './schemas/index.js';
4
4
  import { MarkdownParser } from './parsers/markdown-parser.js';
5
5
  import { ChangeParser } from './parsers/change-parser.js';
6
6
  import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES } from './constants.js';
7
- import { parseDeltaSpec, foldRequirementName, normalizeRequirementName, extractRequirementsSection, findMissingCurrentScenarios, } from './parsers/requirement-blocks.js';
8
- import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, } from './parsers/requirement-text.js';
7
+ import { parseDeltaSpec, foldRequirementName, normalizeRequirementName, extractRequirementsSection, diffScenarioNames, describeScenarioBalance, describeUnappliedContent, } from './parsers/requirement-blocks.js';
8
+ import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, countEmptyScenarios, } from './parsers/requirement-text.js';
9
9
  import { findMainSpecStructureIssues } from './parsers/spec-structure.js';
10
10
  import { FileSystemUtils } from '../engine/file-system.js';
11
- import { discoverSpecFiles } from './spec-discovery.js';
11
+ import { discoverSpecFiles, findUnreadDeltaFiles } from './spec-discovery.js';
12
12
  import { findTaskNumberingIssues } from './task-numbering.js';
13
13
  import { findPurposePlaceholderIssue } from './purpose-placeholder.js';
14
14
  import { findSpecUpdates, buildUpdatedSpec } from '../apply/specs-apply.js';
@@ -164,6 +164,38 @@ export class Validator {
164
164
  : `Header "### ${stray.header}" in ${stray.section} is not a "### Requirement:" header and is ignored by validation. Use "### Requirement: ${stray.header}" if it should be validated as a requirement.`,
165
165
  });
166
166
  }
167
+ // A well-formed requirement outside every delta section, ignored by
168
+ // the reader. WARNING, not ERROR: a handful of pre-format archived
169
+ // changes still carry this shape, and the fix is to move the block.
170
+ for (const orphan of plan.orphanedRequirements) {
171
+ const where = orphan.section ? `under "## ${orphan.section}"` : 'above the first "## " section';
172
+ issues.push({
173
+ level: 'WARNING',
174
+ path: entryPath,
175
+ line: orphan.line,
176
+ message: `Requirement "${orphan.name}" is ${where}, which is not a delta section, so it is ignored. Move it under "## ADDED Requirements", "## MODIFIED Requirements", "## REMOVED Requirements", or "## RENAMED Requirements".`,
177
+ });
178
+ }
179
+ // Archive carries requirement blocks only, by design. Warn so text
180
+ // written elsewhere is moved rather than lost without notice.
181
+ for (const lost of plan.unappliedContent) {
182
+ issues.push({
183
+ level: 'WARNING',
184
+ path: entryPath,
185
+ line: lost.line,
186
+ message: `${describeUnappliedContent(lost, true)} is not carried into the main spec by archive. Move it into a requirement, or into proposal.md or design.md.`,
187
+ });
188
+ }
189
+ // A `TO:` line that never formed a pair, reported here so the author
190
+ // learns at authoring time rather than at archive.
191
+ for (const unpaired of plan.unpairedRenames) {
192
+ issues.push({
193
+ level: 'ERROR',
194
+ path: entryPath,
195
+ line: unpaired.line,
196
+ message: `RENAMED TO: "${unpaired.name}" has no matching FROM: line. Write each rename as a FROM: line followed immediately by its TO: line.`,
197
+ });
198
+ }
167
199
  const sectionNames = [];
168
200
  if (plan.sectionPresence.added)
169
201
  sectionNames.push('## ADDED Requirements');
@@ -215,7 +247,11 @@ export class Validator {
215
247
  }
216
248
  const scenarioCount = this.countScenarios(block.raw);
217
249
  if (scenarioCount < 1) {
218
- issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario` });
250
+ issues.push({
251
+ level: 'ERROR',
252
+ path: entryPath,
253
+ message: `ADDED "${block.name}" must include at least one scenario${this.emptyScenarioHint(block.raw)}`,
254
+ });
219
255
  }
220
256
  }
221
257
  // Validate MODIFIED
@@ -247,7 +283,11 @@ export class Validator {
247
283
  }
248
284
  const scenarioCount = this.countScenarios(block.raw);
249
285
  if (scenarioCount < 1) {
250
- issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must include at least one scenario` });
286
+ issues.push({
287
+ level: 'ERROR',
288
+ path: entryPath,
289
+ message: `MODIFIED "${block.name}" must include at least one scenario${this.emptyScenarioHint(block.raw)}`,
290
+ });
251
291
  }
252
292
  }
253
293
  // Run archive's scenario-loss check here too, so the change fails at
@@ -349,6 +389,16 @@ export class Validator {
349
389
  throw error;
350
390
  }
351
391
  }
392
+ // Same drop for delta sections in a file the merge path never reads
393
+ // (specs/<capability>.md), though the specs/**/*.md glob counts it written.
394
+ const unreadDeltaFiles = await findUnreadDeltaFiles(specsDir);
395
+ for (const file of unreadDeltaFiles) {
396
+ issues.push({
397
+ level: 'ERROR',
398
+ path: file.path,
399
+ message: `Delta spec found at specs/${file.path}. Delta specs must be a spec.md inside a capability folder — this file is ignored when the change is applied or archived. Move its requirements into specs/${file.expected}.`,
400
+ });
401
+ }
352
402
  for (const { path: specPath, sections } of emptySectionSpecs) {
353
403
  issues.push({
354
404
  level: 'ERROR',
@@ -363,11 +413,11 @@ export class Validator {
363
413
  message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
364
414
  });
365
415
  }
366
- // The root-level error already names the file and the fix; adding "No
367
- // deltas found" on top would contradict it, since the deltas are sitting in
368
- // the file just reported. (skip_specs marker handling is intentionally not
369
- // vendored in this slice — see the file header.)
370
- if (totalDeltas === 0 && !hasRootLevelSpec) {
416
+ // The root-level and unread-file errors already name the file and the fix;
417
+ // adding "No deltas found" on top would contradict them, since the deltas
418
+ // are sitting in the files just reported. (skip_specs marker handling is
419
+ // intentionally not vendored in this slice — see the file header.)
420
+ if (totalDeltas === 0 && !hasRootLevelSpec && unreadDeltaFiles.length === 0) {
371
421
  issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
372
422
  }
373
423
  if (options.projectRoot) {
@@ -473,15 +523,16 @@ export class Validator {
473
523
  const current = currentBlockFor(key);
474
524
  if (!current)
475
525
  continue;
476
- const missing = findMissingCurrentScenarios(current, block);
477
- if (missing.length === 0)
526
+ const diff = diffScenarioNames(current, block);
527
+ if (diff.missing.length === 0)
478
528
  continue;
479
529
  issues.push({
480
530
  level: 'ERROR',
481
531
  path: entryPath,
482
532
  message: `MODIFIED "${block.name}" omits scenario(s) the current spec still has: ` +
483
- `${missing.map(name => `"${name}"`).join(', ')}. ` +
484
- 'Copy them into the MODIFIED block (a MODIFIED requirement replaces the whole block, so archive refuses to drop them).',
533
+ `${diff.missing.map(name => `"${name}"`).join(', ')}. ` +
534
+ `${describeScenarioBalance(diff)} ` +
535
+ 'Copy the omitted scenarios into the MODIFIED block (a MODIFIED requirement replaces the whole block, so archive refuses to drop them).',
485
536
  });
486
537
  }
487
538
  return issues;
@@ -744,6 +795,16 @@ export class Validator {
744
795
  // example is not a real scenario. Drop the header line (index 0).
745
796
  return countScenariosShared(blockRaw.split('\n').slice(1));
746
797
  }
798
+ /**
799
+ * Why a requirement with a visible scenario header still has no scenario:
800
+ * the header has no body, and the spec path archive validates against does
801
+ * not count it. Empty when the block has no bare scenario header.
802
+ */
803
+ emptyScenarioHint(blockRaw) {
804
+ if (countEmptyScenarios(blockRaw.split('\n').slice(1)) === 0)
805
+ return '';
806
+ return ' (a scenario header with no body under it does not count; add its steps, e.g. "- **WHEN** ..." and "- **THEN** ...")';
807
+ }
747
808
  formatSectionList(sections) {
748
809
  if (sections.length === 0)
749
810
  return '';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freeharness/sdd",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Vendored, store-free slice of OpenSpec (schema validation + project init) that runs with no global openspec on PATH.",
@@ -13,7 +13,7 @@ artifacts:
13
13
  - **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
14
14
  - **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
15
15
  - **Capabilities**: Identify which specs will be created or modified:
16
- - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
16
+ - **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Name each capability for a durable system behavior (for example, `user-auth`), not the work in this change (for example, `add-login-endpoint`). Choose a cohesive boundary that can own related requirements as the system evolves; avoid broad catch-all capabilities. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
17
17
  - **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes.
18
18
  - **Impact**: Affected code, APIs, dependencies, or systems.
19
19
 
@@ -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,8 +105,9 @@ 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.
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.
97
109
 
98
- New capabilities only: start the delta spec with a `## Purpose` section -
110
+ New capabilities only: the delta spec's first section is `## Purpose` -
99
111
  one or two sentences (50+ characters, or `openspec validate --strict`
100
112
  reports it as too brief) describing what the capability is for. Archive
101
113
  copies it into the main spec it creates; without it the new main spec is
@@ -120,8 +132,10 @@ artifacts:
120
132
  Common pitfall: Using MODIFIED with partial content loses detail at archive time.
121
133
  If adding new concerns without changing existing behavior, use ADDED instead.
122
134
 
123
- Example (a new capability, so it opens with `## Purpose`):
135
+ Example (a new capability, so its first section is `## Purpose`):
124
136
  ```
137
+ # Spec Delta
138
+
125
139
  ## Purpose
126
140
 
127
141
  Lets users take their data out of the product in a portable format.
@@ -192,22 +206,39 @@ artifacts:
192
206
  would change what gets built, resolve them with the user first - do not
193
207
  bake an unstated assumption into the task list.
194
208
 
195
- **IMPORTANT: Follow the template below exactly.** The apply phase parses
196
- checkbox format to track progress. Tasks not using `- [ ]` won't be tracked.
209
+ **IMPORTANT: Follow the template below for tracked tasks.** The apply phase parses
210
+ checkbox format to track progress. A box holding only `x` counts as done,
211
+ upper or lower case and with any spacing, so `- [ x]` is done too. Every
212
+ other marker, including `- [~]`, `- [-]` and an empty `- []`, reads as
213
+ unfinished. A line with no checkbox is not tracked at all.
197
214
 
198
215
  Guidelines:
199
- - Group related tasks under ## numbered headings
200
- - 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`
201
218
  - Tasks should be small enough to complete in one session
202
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.
203
225
  - Each task MUST state how to verify completion (a test, command,
204
226
  observable behavior, or delivered artifact). Put the verification in
205
227
  that task's checkbox description. Use a separate verification task only
206
228
  when it checks broader integration or system behavior that spans
207
229
  multiple implementation tasks.
230
+ - Each task group MUST land the tests and documentation its own work
231
+ calls for. Do NOT collect testing or documentation into a final group -
232
+ when a late group first exercises work from an early one, the failures
233
+ cascade back through every group in between and force rework. A group
234
+ whose work calls for neither, such as scaffolding or dependency setup,
235
+ carries neither. A final group is for integration checks only, not for
236
+ the tests and docs an earlier group owed.
208
237
 
209
238
  Example:
210
239
  ```
240
+ # Tasks
241
+
211
242
  ## 1. Setup
212
243
 
213
244
  - [ ] 1.1 Create new module structure and verify expected files are present
@@ -217,6 +248,15 @@ artifacts:
217
248
 
218
249
  - [ ] 2.1 Implement data export function and verify the export test passes
219
250
  - [ ] 2.2 Add CSV formatting utilities and verify unit tests cover quoting and delimiters
251
+ - [ ] 2.3 Document the export API in docs/export.md and verify the documented command runs as written
252
+ ```
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.
220
260
  ```
221
261
 
222
262
  Reference specs for what needs to be built, design for how to build it.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: openspec-apply-change
3
- description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
3
+ description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. Also use when the user says "openspec apply", "opsx apply", or "openspec implement".
4
4
  allowed-tools: Bash(openspec:*)
5
5
  license: MIT
6
6
  compatibility: Requires openspec CLI.
@@ -13,6 +13,17 @@ Implement tasks from an OpenSpec change.
13
13
 
14
14
  **Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `schemas`, `view`). Once selected, treat `--store <id>` as sticky for the rest of the workflow. Every unscoped example of those commands below is shorthand: before running it, append the flag. For example, run `openspec status --change "<name>" --json --store "<id>"`, not the unscoped form shown below. Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
15
15
 
16
+ **Project check:** These steps expect a project that already uses OpenSpec. Before the first step that writes anything (`new change`, `archive`, `sync specs`, or authoring an artifact file), confirm the project has a root: run `openspec list --json` (with `--store <id>` when a store is selected, since the store is then the root) and read `root`. A root object means the project is set up. `"root": null` means it is not - there is no `openspec/` directory here, and a write such as `openspec new change` would create one as a side effect. The command also exits non-zero, which is that answer rather than a broken CLI, so read the JSON instead of retrying or working around it.
17
+
18
+ One `"root": null` is not about setup: when a `status` error message starts with `Declared in` or `Invalid store declaration in` and names this project's `openspec/config.yaml` (or `config.yml`), the project does use OpenSpec through a store it declares, which this machine cannot resolve (the store is not registered, or the `store:` line is malformed). Do not treat it as uninitialized and skip the branches below: stop before writing and show the user that error's `message` and `fix`.
19
+
20
+ Otherwise, with no root, what happens next depends on how this workflow was reached:
21
+
22
+ - **Auto-selected**: you chose this workflow yourself, without the user naming OpenSpec, naming this skill, or running its slash command. Stop using OpenSpec and answer the request normally, as you would with no OpenSpec installed. Do not ask them to set anything up and do not mention OpenSpec setup.
23
+ - **Explicit OpenSpec request**: the user named OpenSpec, named this skill, or ran its slash command. Stop before writing and ask how to proceed: set this project up (`openspec init`), target a store they already have (`--store <id>`), or continue without OpenSpec for this request. Wait for their answer.
24
+
25
+ In both branches, never create the root as a side effect: do not run `openspec init` until the user asks for it, do not hand-create `openspec/` files, and do not let a command create it.
26
+
16
27
  **Input**: Optionally specify a change name (e.g., `/openspec-apply-change add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
17
28
 
18
29
  **Steps**
@@ -44,14 +55,17 @@ Implement tasks from an OpenSpec change.
44
55
  This returns:
45
56
  - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
46
57
  - Progress (total, complete, remaining)
47
- - Task list with status
58
+ - Task list with status, source path, and source line
48
59
  - Dynamic instruction based on current state
49
60
  - Optional `context`: current required project instruction input from the selected root
50
61
  - Optional `operationGuidance`: current advisory guidance for apply
62
+ - `missingArtifacts` (when present): required artifact ids with no output
51
63
 
52
64
  **Handle states:**
53
- - If `state: "blocked"` (missing artifacts): show message, suggest using `/openspec-continue-change` (if it is not installed, run `openspec status --change "<name>" --json` to see the next artifact and `openspec instructions <artifact-id> --change "<name>" --json` for how to create it)
54
- - If `state: "all_done"`: congratulate, suggest archive
65
+ - If `state: "blocked"`: show the message and pause implementation.
66
+ - If `missingArtifacts` is non-empty: suggest using `/openspec-continue-change` to create them.
67
+ - 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.
68
+ - If `state: "all_done"`: report that all tracked tasks are complete and suggest review or verification as appropriate before archiving
55
69
  - Otherwise: proceed to implementation
56
70
 
57
71
  Treat `context` as a required prompt-level input. Read and consider it, and
@@ -93,7 +107,9 @@ Implement tasks from an OpenSpec change.
93
107
  - Show which task is being worked on
94
108
  - Make the code changes required
95
109
  - Keep changes minimal and focused
96
- - Mark task complete in the tasks file: `- [ ]` → `- [x]`
110
+ - Before editing, confirm the checkbox at the returned `sourcePath` and `line` still matches the task description; if it does not, rerun the apply instructions and use the refreshed location
111
+ - Mark the task complete at its returned `sourcePath` and `line`: `- [ ]` → `- [x]`
112
+ - Rerun the apply instructions and confirm that task is now done and progress changed
97
113
  - Continue to next task
98
114
 
99
115
  **Pause if:**
@@ -108,7 +124,7 @@ Implement tasks from an OpenSpec change.
108
124
  Display:
109
125
  - Tasks completed this session
110
126
  - Overall progress: "N/M tasks complete"
111
- - If all done: suggest archive
127
+ - If all done: report that tracked tasks are complete and suggest review or verification as appropriate before archiving
112
128
  - If paused: explain why and wait for guidance
113
129
 
114
130
  **Output During Implementation**
@@ -139,7 +155,8 @@ Working on task 4/7: <task description>
139
155
  - [x] Task 2
140
156
  ...
141
157
 
142
- All tasks complete! You can archive this change with `/openspec-archive-change`.
158
+ All tracked tasks are complete. Review or verify the change as appropriate
159
+ before archiving. You can archive this change with `/openspec-archive-change`.
143
160
  ```
144
161
 
145
162
  **Output On Pause (Issue Encountered)**
@@ -173,6 +190,7 @@ What would you like to do?
173
190
  - When a task needs work beyond what the spec describes, surface the added scope and pause - never silently narrow, defer, or simplify away specified behavior
174
191
  - Only mark a task `- [x]` when its specified behavior is fully implemented, not when it is partially done or deferred
175
192
  - Use contextFiles from CLI output, don't assume specific file names
193
+ - Use each task's sourcePath and line to update its exact checkbox
176
194
  - Do not use context or operation guidance as proof that a task is complete
177
195
  - Apply relevant project context; report conflicts with controlling workflow inputs
178
196
  - Consider every guidance entry; explain any inapplicable or conflicting advice