@seanmars/tospec 0.19.0-beta.6 → 0.19.0-beta.8

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 (209) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/assets/dashboard/app.js +3 -3
  3. package/assets/rules/tospec/decision.md +3 -0
  4. package/dist/cli/index.d.ts.map +1 -1
  5. package/dist/cli/index.js +205 -36
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/config.js +151 -23
  9. package/dist/commands/config.js.map +1 -1
  10. package/dist/commands/dashboard.d.ts +34 -9
  11. package/dist/commands/dashboard.d.ts.map +1 -1
  12. package/dist/commands/dashboard.js +213 -21
  13. package/dist/commands/dashboard.js.map +1 -1
  14. package/dist/commands/decision.d.ts +11 -0
  15. package/dist/commands/decision.d.ts.map +1 -1
  16. package/dist/commands/decision.js +182 -20
  17. package/dist/commands/decision.js.map +1 -1
  18. package/dist/commands/shared-output.d.ts +12 -0
  19. package/dist/commands/shared-output.d.ts.map +1 -1
  20. package/dist/commands/shared-output.js +50 -1
  21. package/dist/commands/shared-output.js.map +1 -1
  22. package/dist/commands/show.d.ts +10 -0
  23. package/dist/commands/show.d.ts.map +1 -1
  24. package/dist/commands/show.js +23 -4
  25. package/dist/commands/show.js.map +1 -1
  26. package/dist/commands/validate.d.ts +41 -1
  27. package/dist/commands/validate.d.ts.map +1 -1
  28. package/dist/commands/validate.js +95 -17
  29. package/dist/commands/validate.js.map +1 -1
  30. package/dist/commands/workflow/index.d.ts +5 -5
  31. package/dist/commands/workflow/index.d.ts.map +1 -1
  32. package/dist/commands/workflow/index.js +5 -5
  33. package/dist/commands/workflow/index.js.map +1 -1
  34. package/dist/commands/workflow/instructions.d.ts +23 -0
  35. package/dist/commands/workflow/instructions.d.ts.map +1 -1
  36. package/dist/commands/workflow/instructions.js +84 -16
  37. package/dist/commands/workflow/instructions.js.map +1 -1
  38. package/dist/commands/workflow/new-change.d.ts +2 -0
  39. package/dist/commands/workflow/new-change.d.ts.map +1 -1
  40. package/dist/commands/workflow/new-change.js +32 -4
  41. package/dist/commands/workflow/new-change.js.map +1 -1
  42. package/dist/commands/workflow/schemas.d.ts +2 -0
  43. package/dist/commands/workflow/schemas.d.ts.map +1 -1
  44. package/dist/commands/workflow/schemas.js +38 -1
  45. package/dist/commands/workflow/schemas.js.map +1 -1
  46. package/dist/commands/workflow/shared.d.ts +34 -2
  47. package/dist/commands/workflow/shared.d.ts.map +1 -1
  48. package/dist/commands/workflow/shared.js +37 -3
  49. package/dist/commands/workflow/shared.js.map +1 -1
  50. package/dist/commands/workflow/status.d.ts +14 -0
  51. package/dist/commands/workflow/status.d.ts.map +1 -1
  52. package/dist/commands/workflow/status.js +65 -8
  53. package/dist/commands/workflow/status.js.map +1 -1
  54. package/dist/commands/workflow/templates.d.ts +7 -0
  55. package/dist/commands/workflow/templates.d.ts.map +1 -1
  56. package/dist/commands/workflow/templates.js +13 -0
  57. package/dist/commands/workflow/templates.js.map +1 -1
  58. package/dist/core/archive.d.ts +14 -0
  59. package/dist/core/archive.d.ts.map +1 -1
  60. package/dist/core/archive.js +169 -18
  61. package/dist/core/archive.js.map +1 -1
  62. package/dist/core/artifact-graph/graph.d.ts +13 -3
  63. package/dist/core/artifact-graph/graph.d.ts.map +1 -1
  64. package/dist/core/artifact-graph/graph.js +16 -6
  65. package/dist/core/artifact-graph/graph.js.map +1 -1
  66. package/dist/core/artifact-graph/index.d.ts +1 -1
  67. package/dist/core/artifact-graph/index.d.ts.map +1 -1
  68. package/dist/core/artifact-graph/index.js +1 -1
  69. package/dist/core/artifact-graph/index.js.map +1 -1
  70. package/dist/core/artifact-graph/instruction-loader.d.ts +74 -3
  71. package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
  72. package/dist/core/artifact-graph/instruction-loader.js +79 -31
  73. package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
  74. package/dist/core/artifact-graph/resolver.d.ts +26 -0
  75. package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
  76. package/dist/core/artifact-graph/resolver.js +44 -4
  77. package/dist/core/artifact-graph/resolver.js.map +1 -1
  78. package/dist/core/artifact-graph/stub-detection.d.ts +20 -0
  79. package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -0
  80. package/dist/core/artifact-graph/stub-detection.js +42 -0
  81. package/dist/core/artifact-graph/stub-detection.js.map +1 -0
  82. package/dist/core/change-presenter.d.ts +16 -0
  83. package/dist/core/change-presenter.d.ts.map +1 -1
  84. package/dist/core/change-presenter.js +68 -9
  85. package/dist/core/change-presenter.js.map +1 -1
  86. package/dist/core/change-status-policy.d.ts +12 -1
  87. package/dist/core/change-status-policy.d.ts.map +1 -1
  88. package/dist/core/change-status-policy.js +33 -1
  89. package/dist/core/change-status-policy.js.map +1 -1
  90. package/dist/core/codex-residue.js +1 -1
  91. package/dist/core/config-schema.d.ts +1 -12
  92. package/dist/core/config-schema.d.ts.map +1 -1
  93. package/dist/core/config-schema.js +27 -1
  94. package/dist/core/config-schema.js.map +1 -1
  95. package/dist/core/config.d.ts +37 -0
  96. package/dist/core/config.d.ts.map +1 -1
  97. package/dist/core/config.js +37 -0
  98. package/dist/core/config.js.map +1 -1
  99. package/dist/core/dashboard-activity.js +2 -2
  100. package/dist/core/dashboard-activity.js.map +1 -1
  101. package/dist/core/dashboard-data.d.ts +5 -0
  102. package/dist/core/dashboard-data.d.ts.map +1 -1
  103. package/dist/core/dashboard-data.js +15 -8
  104. package/dist/core/dashboard-data.js.map +1 -1
  105. package/dist/core/init.d.ts +22 -0
  106. package/dist/core/init.d.ts.map +1 -1
  107. package/dist/core/init.js +107 -45
  108. package/dist/core/init.js.map +1 -1
  109. package/dist/core/list.d.ts +1 -1
  110. package/dist/core/list.d.ts.map +1 -1
  111. package/dist/core/list.js +68 -17
  112. package/dist/core/list.js.map +1 -1
  113. package/dist/core/markdown-render.d.ts +33 -0
  114. package/dist/core/markdown-render.d.ts.map +1 -0
  115. package/dist/core/markdown-render.js +103 -0
  116. package/dist/core/markdown-render.js.map +1 -0
  117. package/dist/core/migrate.d.ts +20 -6
  118. package/dist/core/migrate.d.ts.map +1 -1
  119. package/dist/core/migrate.js +102 -6
  120. package/dist/core/migrate.js.map +1 -1
  121. package/dist/core/parsers/change-parser.js +1 -1
  122. package/dist/core/parsers/change-parser.js.map +1 -1
  123. package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
  124. package/dist/core/parsers/markdown-parser.js +16 -0
  125. package/dist/core/parsers/markdown-parser.js.map +1 -1
  126. package/dist/core/parsers/requirement-blocks.js +32 -9
  127. package/dist/core/parsers/requirement-blocks.js.map +1 -1
  128. package/dist/core/root-selection.d.ts +11 -1
  129. package/dist/core/root-selection.d.ts.map +1 -1
  130. package/dist/core/root-selection.js +4 -2
  131. package/dist/core/root-selection.js.map +1 -1
  132. package/dist/core/rules.d.ts +6 -1
  133. package/dist/core/rules.d.ts.map +1 -1
  134. package/dist/core/rules.js +24 -2
  135. package/dist/core/rules.js.map +1 -1
  136. package/dist/core/schemas/base.schema.d.ts +3 -0
  137. package/dist/core/schemas/base.schema.d.ts.map +1 -1
  138. package/dist/core/schemas/base.schema.js +22 -0
  139. package/dist/core/schemas/base.schema.js.map +1 -1
  140. package/dist/core/schemas/change.schema.d.ts +8 -0
  141. package/dist/core/schemas/change.schema.d.ts.map +1 -1
  142. package/dist/core/schemas/spec.schema.d.ts +2 -0
  143. package/dist/core/schemas/spec.schema.d.ts.map +1 -1
  144. package/dist/core/shared/index.d.ts +1 -1
  145. package/dist/core/shared/index.d.ts.map +1 -1
  146. package/dist/core/shared/index.js +1 -1
  147. package/dist/core/shared/index.js.map +1 -1
  148. package/dist/core/shared/rules-generation.d.ts +27 -6
  149. package/dist/core/shared/rules-generation.d.ts.map +1 -1
  150. package/dist/core/shared/rules-generation.js +115 -44
  151. package/dist/core/shared/rules-generation.js.map +1 -1
  152. package/dist/core/shared/tool-detection.d.ts +19 -0
  153. package/dist/core/shared/tool-detection.d.ts.map +1 -1
  154. package/dist/core/shared/tool-detection.js +30 -2
  155. package/dist/core/shared/tool-detection.js.map +1 -1
  156. package/dist/core/spec-presenter.d.ts.map +1 -1
  157. package/dist/core/spec-presenter.js +5 -0
  158. package/dist/core/spec-presenter.js.map +1 -1
  159. package/dist/core/specs-apply.d.ts +15 -1
  160. package/dist/core/specs-apply.d.ts.map +1 -1
  161. package/dist/core/specs-apply.js +33 -11
  162. package/dist/core/specs-apply.js.map +1 -1
  163. package/dist/core/templates/workflows/apply.js +1 -1
  164. package/dist/core/templates/workflows/apply.js.map +1 -1
  165. package/dist/core/templates/workflows/archive.d.ts.map +1 -1
  166. package/dist/core/templates/workflows/archive.js +4 -2
  167. package/dist/core/templates/workflows/archive.js.map +1 -1
  168. package/dist/core/templates/workflows/decision.js +1 -1
  169. package/dist/core/templates/workflows/issue.d.ts.map +1 -1
  170. package/dist/core/templates/workflows/issue.js +3 -0
  171. package/dist/core/templates/workflows/issue.js.map +1 -1
  172. package/dist/core/update.d.ts +26 -0
  173. package/dist/core/update.d.ts.map +1 -1
  174. package/dist/core/update.js +111 -23
  175. package/dist/core/update.js.map +1 -1
  176. package/dist/core/validation/constants.d.ts +1 -1
  177. package/dist/core/validation/constants.d.ts.map +1 -1
  178. package/dist/core/validation/constants.js +6 -1
  179. package/dist/core/validation/constants.js.map +1 -1
  180. package/dist/core/validation/section-validator.d.ts.map +1 -1
  181. package/dist/core/validation/section-validator.js +21 -3
  182. package/dist/core/validation/section-validator.js.map +1 -1
  183. package/dist/core/validation/validator.d.ts +17 -4
  184. package/dist/core/validation/validator.d.ts.map +1 -1
  185. package/dist/core/validation/validator.js +120 -34
  186. package/dist/core/validation/validator.js.map +1 -1
  187. package/dist/utils/change-utils.d.ts +31 -2
  188. package/dist/utils/change-utils.d.ts.map +1 -1
  189. package/dist/utils/change-utils.js +127 -37
  190. package/dist/utils/change-utils.js.map +1 -1
  191. package/dist/utils/file-system.d.ts +1 -1
  192. package/dist/utils/file-system.js +1 -1
  193. package/dist/utils/item-discovery.d.ts +20 -5
  194. package/dist/utils/item-discovery.d.ts.map +1 -1
  195. package/dist/utils/item-discovery.js +28 -9
  196. package/dist/utils/item-discovery.js.map +1 -1
  197. package/dist/utils/spec-files.d.ts +12 -0
  198. package/dist/utils/spec-files.d.ts.map +1 -1
  199. package/dist/utils/spec-files.js +34 -0
  200. package/dist/utils/spec-files.js.map +1 -1
  201. package/dist/utils/task-progress.js +1 -1
  202. package/package.json +1 -1
  203. package/schemas/decision/templates/decision.md +3 -1
  204. package/schemas/decision/templates/index.md +2 -2
  205. package/schemas/issue/schema.yaml +4 -2
  206. package/schemas/issue/templates/spec.md +20 -2
  207. package/schemas/sdd/schema.yaml +4 -0
  208. package/schemas/sdd/templates/spec.md +20 -2
  209. /package/assets/rules/tospec/{single-sourc-of-truth.md → single-source-of-truth.md} +0 -0
@@ -1,10 +1,12 @@
1
1
  import path from 'path';
2
+ import { promises as fsPromises } from 'fs';
2
3
  import { FileSystemUtils } from './file-system.js';
3
4
  import { formatTimestamp, formatLocalDate } from './timestamp.js';
4
5
  import { writeChangeMetadata, validateSchemaName } from './change-metadata.js';
5
6
  import { readProjectConfig, resolveConfigFileInDir } from '../core/project-config.js';
6
7
  import { isKebabId } from '../core/id.js';
7
8
  import { resolveSchema, loadTemplate } from '../core/artifact-graph/index.js';
9
+ import { findTicketPath } from './item-discovery.js';
8
10
  const DEFAULT_SCHEMA = 'sdd';
9
11
  // sdd → requirement, issue → issue; custom schemas leave type unset.
10
12
  const SCHEMA_TO_CHANGE_TYPE = {
@@ -24,7 +26,8 @@ const SCHEMA_TO_CHANGE_TYPE = {
24
26
  * A leading digit is allowed so ordering conventions like `100-add-feature` or
25
27
  * `00001-add-auth` work.
26
28
  *
27
- * @param name - The change name to validate
29
+ * @param name - The kebab-case identifier to validate
30
+ * @param noun - The user-facing name for the identifier in validation errors
28
31
  * @returns Validation result with `valid: true` or `valid: false` with an error message
29
32
  *
30
33
  * @example
@@ -32,34 +35,59 @@ const SCHEMA_TO_CHANGE_TYPE = {
32
35
  * validateChangeName('100-add-feature') // { valid: true }
33
36
  * validateChangeName('Add-Auth') // { valid: false, error: '...' }
34
37
  */
35
- export function validateChangeName(name) {
38
+ /**
39
+ * Upper bound on a change name, in characters.
40
+ *
41
+ * Deliberately generous: a kebab-case summary of a change runs well under this,
42
+ * so the limit only catches input that was never a name. See the check in
43
+ * `validateChangeName` for why it exists at all.
44
+ */
45
+ export const MAX_CHANGE_NAME_LENGTH = 100;
46
+ export function validateChangeName(name, noun = 'Change name') {
36
47
  if (!name) {
37
- return { valid: false, error: 'Change name cannot be empty' };
48
+ return { valid: false, error: `${noun} cannot be empty` };
38
49
  }
39
50
  if (!isKebabId(name)) {
40
51
  // Provide specific error messages for common mistakes
41
52
  if (/[A-Z]/.test(name)) {
42
- return { valid: false, error: 'Change name must be lowercase (use kebab-case)' };
53
+ return { valid: false, error: `${noun} must be lowercase (use kebab-case)` };
43
54
  }
44
55
  if (/\s/.test(name)) {
45
- return { valid: false, error: 'Change name cannot contain spaces (use hyphens instead)' };
56
+ return { valid: false, error: `${noun} cannot contain spaces (use hyphens instead)` };
46
57
  }
47
58
  if (/_/.test(name)) {
48
- return { valid: false, error: 'Change name cannot contain underscores (use hyphens instead)' };
59
+ return { valid: false, error: `${noun} cannot contain underscores (use hyphens instead)` };
49
60
  }
50
61
  if (name.startsWith('-')) {
51
- return { valid: false, error: 'Change name cannot start with a hyphen' };
62
+ return { valid: false, error: `${noun} cannot start with a hyphen` };
52
63
  }
53
64
  if (name.endsWith('-')) {
54
- return { valid: false, error: 'Change name cannot end with a hyphen' };
65
+ return { valid: false, error: `${noun} cannot end with a hyphen` };
55
66
  }
56
67
  if (/--/.test(name)) {
57
- return { valid: false, error: 'Change name cannot contain consecutive hyphens' };
68
+ return { valid: false, error: `${noun} cannot contain consecutive hyphens` };
58
69
  }
59
70
  if (/[^a-z0-9-]/.test(name)) {
60
- return { valid: false, error: 'Change name can only contain lowercase letters, numbers, and hyphens' };
71
+ return { valid: false, error: `${noun} can only contain lowercase letters, numbers, and hyphens` };
61
72
  }
62
- return { valid: false, error: 'Change name must follow kebab-case convention (e.g., add-auth, refactor-db)' };
73
+ return { valid: false, error: `${noun} must follow kebab-case convention (e.g., add-auth, refactor-db)` };
74
+ }
75
+ // Checked last so a malformed name still gets the specific message above.
76
+ //
77
+ // The name becomes a directory under tospec/changes/ and, on archive, the
78
+ // longer `<yyyyMMdd_HHmmss>-<name>` under tospec/changes/archive/ — about 38
79
+ // characters of path before the name itself. Without a limit the overflow
80
+ // surfaced as a raw ENOENT from whichever write happened to cross the
81
+ // platform's path limit first, after the change directory was already on disk.
82
+ // No cap can guarantee the write succeeds (the project root's own length is
83
+ // unknown here), so this is a legibility bound, not a correctness one: it
84
+ // rejects the names that cannot plausibly be intended, with a message that
85
+ // names the problem.
86
+ if (name.length > MAX_CHANGE_NAME_LENGTH) {
87
+ return {
88
+ valid: false,
89
+ error: `${noun} is ${name.length} characters; the maximum is ${MAX_CHANGE_NAME_LENGTH}`,
90
+ };
63
91
  }
64
92
  return { valid: true };
65
93
  }
@@ -85,6 +113,30 @@ export function validateChangeName(name) {
85
113
  * const result = await createChange('/path/to/project', 'add-auth', { schema: 'my-workflow' })
86
114
  * console.log(result.schema) // 'my-workflow'
87
115
  */
116
+ /**
117
+ * Bring a tospec root up to the full layout every command expects.
118
+ *
119
+ * Any command that writes into `tospec/` may be the one that creates it — from
120
+ * an implicit root, or a config-only clone. A command that creates only the one
121
+ * subdirectory it needs leaves a half-root: the directory then resolves as a
122
+ * project for every other command, so `validate --all` and `status --all` start
123
+ * answering "0 items, all fine" about somewhere that was never a project. That
124
+ * vacuous pass is the reason those commands refuse an implicit root in the first
125
+ * place, so creating one by halves hands it back through the side door.
126
+ *
127
+ * Shared rather than inlined because `new change` and `decision new` are both
128
+ * such commands, and only one of them used to do this.
129
+ */
130
+ export async function completeRootStructure(projectRoot, defaultSchema) {
131
+ const tospecDir = path.join(projectRoot, 'tospec');
132
+ await FileSystemUtils.createDirectory(path.join(tospecDir, 'specs'));
133
+ await FileSystemUtils.createDirectory(path.join(tospecDir, 'changes', 'archive'));
134
+ // The config records the PROJECT default schema, never a one-off override,
135
+ // and is written only when none exists.
136
+ if (!resolveConfigFileInDir(tospecDir)) {
137
+ await FileSystemUtils.writeFile(path.join(tospecDir, 'config.yaml'), `schema: ${defaultSchema}\n`);
138
+ }
139
+ }
88
140
  export async function createChange(projectRoot, name, options = {}) {
89
141
  // Validate the name first
90
142
  const validation = validateChangeName(name);
@@ -116,32 +168,49 @@ export async function createChange(projectRoot, name, options = {}) {
116
168
  if (await FileSystemUtils.directoryExists(changeDir)) {
117
169
  throw new Error(`Change '${name}' already exists at ${changeDir}`);
118
170
  }
119
- // Creating a change may scaffold or complete the root itself (an
120
- // implicit root, or a config-only/incomplete clone). Never leave a
121
- // half-root behind that doctor immediately calls unhealthy: ensure
122
- // specs/ and changes/archive/ exist, and write a config only when
123
- // none exists. The config records the PROJECT default schema, never
124
- // a one-change --schema override.
125
- const tospecDir = path.join(projectRoot, 'tospec');
126
171
  // Create the directory (including parent directories if needed)
127
172
  await FileSystemUtils.createDirectory(changeDir);
128
- await FileSystemUtils.createDirectory(path.join(tospecDir, 'specs'));
129
- await FileSystemUtils.createDirectory(path.join(tospecDir, 'changes', 'archive'));
130
- if (!resolveConfigFileInDir(tospecDir)) {
131
- await FileSystemUtils.writeFile(path.join(tospecDir, 'config.yaml'), `schema: ${defaultSchema}\n`);
173
+ // Everything after the change directory exists is rolled back on failure.
174
+ //
175
+ // Creation is several writes — metadata, then the ticket stub in a different
176
+ // tree — and a failure partway through used to leave the change directory
177
+ // behind with no ticket. `list`, `status --all` and `validate --all` then
178
+ // treated that debris as a real change, so a command that reported failure
179
+ // had in fact added something the project had to be cleaned of by hand.
180
+ //
181
+ // Only the change directory is removed. The sibling scaffolding below
182
+ // (specs/, changes/archive/, config.yaml) completes the root itself and stays
183
+ // valid for every other change, so undoing it would turn one change's failure
184
+ // into damage to the project.
185
+ try {
186
+ // Creating a change may scaffold or complete the root itself; never leave
187
+ // a half-root behind that doctor immediately calls unhealthy.
188
+ await completeRootStructure(projectRoot, defaultSchema);
189
+ // Write metadata file with schema and creation date
190
+ const today = formatLocalDate();
191
+ writeChangeMetadata(changeDir, {
192
+ schema: schemaName,
193
+ type: SCHEMA_TO_CHANGE_TYPE[schemaName],
194
+ created: today,
195
+ ...options.metadata,
196
+ }, projectRoot);
197
+ // Generate the ticket stub. The timestamped filename (and its `date` header)
198
+ // are CLI-generated — agents can't reliably reproduce the timestamp.
199
+ //
200
+ // The path comes back so the caller can report it. Nothing else can work it
201
+ // out: the stamp is generated in here and never written anywhere an agent
202
+ // reads, so filling in the ticket's Summary meant globbing a directory for a
203
+ // filename the CLI had just chosen and thrown away.
204
+ const ticketPath = await writeTicketStub(projectRoot, name, schemaName, options.description);
205
+ return { schema: schemaName, changeDir, ...(ticketPath ? { ticketPath } : {}) };
206
+ }
207
+ catch (error) {
208
+ // Best effort: report the original failure, not a cleanup failure. If the
209
+ // rollback itself cannot run, the caller still needs to see what actually
210
+ // went wrong.
211
+ await fsPromises.rm(changeDir, { recursive: true, force: true }).catch(() => { });
212
+ throw error;
132
213
  }
133
- // Write metadata file with schema and creation date
134
- const today = formatLocalDate();
135
- writeChangeMetadata(changeDir, {
136
- schema: schemaName,
137
- type: SCHEMA_TO_CHANGE_TYPE[schemaName],
138
- created: today,
139
- ...options.metadata,
140
- }, projectRoot);
141
- // Generate the ticket stub. The timestamped filename (and its `date` header)
142
- // are CLI-generated — agents can't reliably reproduce the timestamp.
143
- await writeTicketStub(projectRoot, name, schemaName, options.description);
144
- return { schema: schemaName, changeDir };
145
214
  }
146
215
  /**
147
216
  * Writes a ticket stub into the global `tospec/tickets/<yyyyMMdd_HHmmss>-<name>.md`
@@ -158,7 +227,7 @@ async function writeTicketStub(projectRoot, name, schemaName, description) {
158
227
  schema = resolveSchema(schemaName, projectRoot);
159
228
  }
160
229
  catch {
161
- return; // schema unresolvable — nothing to scaffold
230
+ return undefined; // schema unresolvable — nothing to scaffold
162
231
  }
163
232
  // A schema opts into the global ticket index by shipping a ticket.md template.
164
233
  let body;
@@ -166,7 +235,7 @@ async function writeTicketStub(projectRoot, name, schemaName, description) {
166
235
  body = loadTemplate(schemaName, 'ticket.md', projectRoot);
167
236
  }
168
237
  catch {
169
- return; // no ticket template — this schema doesn't use tickets
238
+ return undefined; // no ticket template — this schema doesn't use tickets
170
239
  }
171
240
  // Fill the `## Summary` placeholder with the provided description; a function
172
241
  // replacer keeps `$`-sequences in the text literal. No description → the
@@ -175,6 +244,17 @@ async function writeTicketStub(projectRoot, name, schemaName, description) {
175
244
  if (summary) {
176
245
  body = body.replace(/(##[ \t]*Summary[ \t]*\r?\n)\[[^\]]*\]/, (_m, head) => head + summary);
177
246
  }
247
+ // Round 4 report 6. The ledger is meant to hold one active ticket per change,
248
+ // but nothing checked: abandoning a change with `rm -rf` leaves its ticket
249
+ // behind, and recreating the change under the same name wrote a second. Both
250
+ // then pointed at the same proposal, archive filed the run under the older
251
+ // one, and the newer stayed in the active list forever naming a path that no
252
+ // longer exists — with no command able to remove it. Reusing the existing
253
+ // ticket keeps the index 1:1; its stamp is from the earlier attempt, which is
254
+ // the honest record of when this change name was first opened.
255
+ const existing = await findTicketPath(projectRoot, name);
256
+ if (existing)
257
+ return existing;
178
258
  const stamp = formatTimestamp(new Date());
179
259
  const ticketPath = path.join(projectRoot, 'tospec', 'tickets', `${stamp}-${name}.md`);
180
260
  // ref points at where the change's details live: proposal.md (sdd) or task.md,
@@ -184,8 +264,18 @@ async function writeTicketStub(projectRoot, name, schemaName, description) {
184
264
  const ref = hasProposal ? `tospec/changes/${name}/proposal.md` : `tospec/changes/${name}/task.md`;
185
265
  // date is quoted so yaml keeps it a string (bare yyyyMMdd_HHmmss would parse
186
266
  // as an underscore-separated integer under yaml 1.1).
187
- const header = `---\ntitle: ${name}\ndate: "${stamp}"\ntype: ${schemaName}\nchange: ${name}\nref: ${ref}\n---\n\n`;
267
+ //
268
+ // `type` carries the change type, the same vocabulary as .tospec.yaml and
269
+ // `list --json` (requirement | issue). It used to carry the schema name, so
270
+ // one key meant two different things across two files of the same change —
271
+ // invisible for an issue change, where the two words coincide, and wrong for
272
+ // every sdd one. The dashboard had already absorbed the split as a CSS rule
273
+ // matching `.badge-requirement, .badge-sdd`. The schema name is still worth
274
+ // recording, so it gets its own key rather than borrowing this one.
275
+ const changeType = SCHEMA_TO_CHANGE_TYPE[schemaName] ?? schemaName;
276
+ const header = `---\ntitle: ${name}\ndate: "${stamp}"\ntype: ${changeType}\nschema: ${schemaName}\nchange: ${name}\nref: ${ref}\n---\n\n`;
188
277
  await FileSystemUtils.createDirectory(path.join(projectRoot, 'tospec', 'tickets'));
189
278
  await FileSystemUtils.writeFile(ticketPath, header + body);
279
+ return ticketPath;
190
280
  }
191
281
  //# sourceMappingURL=change-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"change-utils.js","sourceRoot":"","sources":["../../src/utils/change-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-utils.js","sourceRoot":"","sources":["../../src/utils/change-utils.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Shared by `tospec list --sort recent` and the dashboard so both rank changes
6
6
  * by the same clock; they used to walk separately and could disagree.
7
- * ponytail: recursive walk; fine at repo scale (dozens of changes).
7
+ * Limitation: recursive walk; fine at repo scale (dozens of changes).
8
8
  */
9
9
  export declare function getLastModified(dirPath: string): Promise<Date>;
10
10
  /**
@@ -7,7 +7,7 @@ const fs = nodeFs.promises;
7
7
  *
8
8
  * Shared by `tospec list --sort recent` and the dashboard so both rank changes
9
9
  * by the same clock; they used to walk separately and could disagree.
10
- * ponytail: recursive walk; fine at repo scale (dozens of changes).
10
+ * Limitation: recursive walk; fine at repo scale (dozens of changes).
11
11
  */
12
12
  export async function getLastModified(dirPath) {
13
13
  let latest = null;
@@ -1,12 +1,27 @@
1
1
  /**
2
- * Finds a change's ticket in the global `tospec/tickets/` index by matching the
3
- * ticket frontmatter's `change:` field. Scans only the active folder
4
- * (non-recursive), so archived tickets under `tickets/archive/` are excluded.
5
- * Returns the absolute path, or undefined if none matches.
2
+ * Every ticket in the active `tospec/tickets/` index whose frontmatter `change:`
3
+ * names this change, sorted by filename — which is the `yyyyMMdd_HHmmss` stamp,
4
+ * so oldest first. Scans only the active folder (non-recursive), so archived
5
+ * tickets under `tickets/archive/` are excluded.
6
6
  *
7
- * ponytail: linear scan of tickets/; fine at repo scale (one ticket per change).
7
+ * Plural because the index can hold more than one: a change abandoned with
8
+ * `rm -rf` leaves its ticket behind, and recreating the change under the same
9
+ * name used to add a second (round 4 report 6). Callers that can report the
10
+ * ambiguity should; `findTicketPath` picks for those that cannot.
11
+ *
12
+ * Limitation: linear scan of tickets/; fine at repo scale (one ticket per change).
8
13
  * Add an index if a project ever accumulates thousands of active changes.
9
14
  */
15
+ export declare function findTicketPaths(root: string, changeName: string): Promise<string[]>;
16
+ /**
17
+ * The ticket to treat as this change's, when there is more than one.
18
+ *
19
+ * The newest wins: a second ticket exists because the change was recreated, so
20
+ * the later stamp is the one that describes the change on disk. Taking the
21
+ * first `readdir` entry instead meant archive filed the run under a ticket
22
+ * belonging to the abandoned attempt and left the real one stranded in the
23
+ * active list forever (round 4 report 6).
24
+ */
10
25
  export declare function findTicketPath(root: string, changeName: string): Promise<string | undefined>;
11
26
  /**
12
27
  * Active change directory names under `changesDir`, sorted, excluding `archive`
@@ -1 +1 @@
1
- {"version":3,"file":"item-discovery.d.ts","sourceRoot":"","sources":["../../src/utils/item-discovery.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmBlG;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUhF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAExF;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBhF;AAED,wBAAsB,oBAAoB,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW1F"}
1
+ {"version":3,"file":"item-discovery.d.ts","sourceRoot":"","sources":["../../src/utils/item-discovery.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBzF;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAGlG;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUhF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAExF;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBhF;AAED,wBAAsB,oBAAoB,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW1F"}
@@ -2,23 +2,29 @@ import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
3
  import { frontmatterString } from './frontmatter.js';
4
4
  /**
5
- * Finds a change's ticket in the global `tospec/tickets/` index by matching the
6
- * ticket frontmatter's `change:` field. Scans only the active folder
7
- * (non-recursive), so archived tickets under `tickets/archive/` are excluded.
8
- * Returns the absolute path, or undefined if none matches.
5
+ * Every ticket in the active `tospec/tickets/` index whose frontmatter `change:`
6
+ * names this change, sorted by filename — which is the `yyyyMMdd_HHmmss` stamp,
7
+ * so oldest first. Scans only the active folder (non-recursive), so archived
8
+ * tickets under `tickets/archive/` are excluded.
9
9
  *
10
- * ponytail: linear scan of tickets/; fine at repo scale (one ticket per change).
10
+ * Plural because the index can hold more than one: a change abandoned with
11
+ * `rm -rf` leaves its ticket behind, and recreating the change under the same
12
+ * name used to add a second (round 4 report 6). Callers that can report the
13
+ * ambiguity should; `findTicketPath` picks for those that cannot.
14
+ *
15
+ * Limitation: linear scan of tickets/; fine at repo scale (one ticket per change).
11
16
  * Add an index if a project ever accumulates thousands of active changes.
12
17
  */
13
- export async function findTicketPath(root, changeName) {
18
+ export async function findTicketPaths(root, changeName) {
14
19
  const ticketsPath = path.join(root, 'tospec', 'tickets');
15
20
  let entries;
16
21
  try {
17
22
  entries = await fs.readdir(ticketsPath, { withFileTypes: true });
18
23
  }
19
24
  catch {
20
- return undefined;
25
+ return [];
21
26
  }
27
+ const matches = [];
22
28
  for (const entry of entries) {
23
29
  if (!entry.isFile() || !entry.name.endsWith('.md'))
24
30
  continue;
@@ -26,13 +32,26 @@ export async function findTicketPath(root, changeName) {
26
32
  try {
27
33
  const content = await fs.readFile(full, 'utf-8');
28
34
  if (frontmatterString(content, 'change') === changeName)
29
- return full;
35
+ matches.push(full);
30
36
  }
31
37
  catch {
32
38
  continue;
33
39
  }
34
40
  }
35
- return undefined;
41
+ return matches.sort((a, b) => path.basename(a).localeCompare(path.basename(b)));
42
+ }
43
+ /**
44
+ * The ticket to treat as this change's, when there is more than one.
45
+ *
46
+ * The newest wins: a second ticket exists because the change was recreated, so
47
+ * the later stamp is the one that describes the change on disk. Taking the
48
+ * first `readdir` entry instead meant archive filed the run under a ticket
49
+ * belonging to the abandoned attempt and left the real one stranded in the
50
+ * active list forever (round 4 report 6).
51
+ */
52
+ export async function findTicketPath(root, changeName) {
53
+ const matches = await findTicketPaths(root, changeName);
54
+ return matches.length === 0 ? undefined : matches[matches.length - 1];
36
55
  }
37
56
  /**
38
57
  * Active change directory names under `changesDir`, sorted, excluding `archive`
@@ -1 +1 @@
1
- {"version":3,"file":"item-discovery.js","sourceRoot":"","sources":["../../src/utils/item-discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"item-discovery.js","sourceRoot":"","sources":["../../src/utils/item-discovery.ts"],"names":[],"mappings":"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"}
@@ -16,4 +16,16 @@
16
16
  * differently on CI than locally).
17
17
  */
18
18
  export declare function findSpecFiles(specsDir: string): Promise<string[]>;
19
+ /**
20
+ * Every `*.md` under a change's `specs/` directory, at any depth — unlike
21
+ * {@link findSpecFiles}, basename is not filtered.
22
+ *
23
+ * Exists so the validator can see the same misplacement class it already
24
+ * catches (wrong depth, dot-directory) when the mistake is a wrong *basename*
25
+ * instead: `specs/<capability>/spec.md`'s wrong-basename sibling is invisible
26
+ * to `findSpecFiles`, which drops it before any guard loop runs. The merge
27
+ * path (`findSpecUpdates`) must keep reading only `spec.md` — this walker is
28
+ * for validation to report the file, not for merge to read it.
29
+ */
30
+ export declare function findAllMarkdownFiles(specsDir: string): Promise<string[]>;
19
31
  //# sourceMappingURL=spec-files.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec-files.d.ts","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBvE"}
1
+ {"version":3,"file":"spec-files.d.ts","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBvE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsB9E"}
@@ -41,4 +41,38 @@ export async function findSpecFiles(specsDir) {
41
41
  await walk(specsDir);
42
42
  return results.sort();
43
43
  }
44
+ /**
45
+ * Every `*.md` under a change's `specs/` directory, at any depth — unlike
46
+ * {@link findSpecFiles}, basename is not filtered.
47
+ *
48
+ * Exists so the validator can see the same misplacement class it already
49
+ * catches (wrong depth, dot-directory) when the mistake is a wrong *basename*
50
+ * instead: `specs/<capability>/spec.md`'s wrong-basename sibling is invisible
51
+ * to `findSpecFiles`, which drops it before any guard loop runs. The merge
52
+ * path (`findSpecUpdates`) must keep reading only `spec.md` — this walker is
53
+ * for validation to report the file, not for merge to read it.
54
+ */
55
+ export async function findAllMarkdownFiles(specsDir) {
56
+ const results = [];
57
+ const walk = async (dir) => {
58
+ let entries;
59
+ try {
60
+ entries = await fs.readdir(dir, { withFileTypes: true });
61
+ }
62
+ catch {
63
+ return;
64
+ }
65
+ for (const entry of entries) {
66
+ const full = path.join(dir, entry.name);
67
+ if (entry.isDirectory()) {
68
+ await walk(full);
69
+ }
70
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
71
+ results.push(full);
72
+ }
73
+ }
74
+ };
75
+ await walk(specsDir);
76
+ return results.sort();
77
+ }
44
78
  //# sourceMappingURL=spec-files.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec-files.js","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;QAChD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"spec-files.js","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;QAChD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IACzD,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;QAChD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC"}
@@ -8,7 +8,7 @@ import { resolveSchemaForChange } from './change-metadata.js';
8
8
  * subtask is never invisible to progress; the indent is kept only so the
9
9
  * dashboard can render the nesting it found.
10
10
  *
11
- * ponytail: line-based, so a `- [ ]` inside an indented code block in tasks.md
11
+ * Limitation: line-based, so a `- [ ]` inside an indented code block in tasks.md
12
12
  * miscounts. A real markdown parse is the fix if that ever bites.
13
13
  */
14
14
  const TASK_PATTERN = /^([ \t]*)[-*]\s+\[([\sxX])\]\s*(.*)$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanmars/tospec",
3
- "version": "0.19.0-beta.6",
3
+ "version": "0.19.0-beta.8",
4
4
  "description": "Spec-driven development CLI for structured requirements and issue workflows",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.npmjs.com/package/@seanmars/tospec",
@@ -23,7 +23,9 @@
23
23
  <!-- Follow-up implementation items, phased if useful -->
24
24
 
25
25
  ## Related Changes
26
- <!-- Optional: changes that implement this decision (back-linked by tospec new change --decisions); list change names, add archive paths once archived; omit if none yet -->
26
+ <!-- Optional, maintained by hand: nothing writes this section. `tospec new change --decisions <file>`
27
+ records the link on the change's side only. List change names here yourself, add archive paths
28
+ once archived; omit if none yet -->
27
29
 
28
30
  ## Decision Process
29
31
  <!-- Optional: the key Q&A that shaped this decision; each pair starts with **Q:**, answer in the next paragraph; summarize, don't paste a transcript; omit if there was no interactive discussion -->
@@ -1,2 +1,2 @@
1
- | timestamp | title | summary | file |
2
- | ----- | ----- | ----- | ----- |
1
+ | timestamp | status | title | summary | file |
2
+ | ----- | ----- | ----- | ----- | ----- |
@@ -29,8 +29,10 @@ artifacts:
29
29
  instruction: |
30
30
  Write delta specs only when the fix changes externally observable behavior
31
31
  (same grammar as sdd, same layout: specs/<capability>/spec.md, exactly one
32
- directory level). If the bug stems from a wrong spec, fix that Requirement
33
- with MODIFIED.
32
+ directory level). REMOVED lists each name as a bullet with Reason and
33
+ Migration fields; RENAMED writes a FROM:/TO: pair, e.g. "- FROM: `<old
34
+ name>`" / "- TO: `<new name>`". If the bug stems from a wrong spec, fix
35
+ that Requirement with MODIFIED.
34
36
  apply:
35
37
  requires: [task]
36
38
  tracks: task.md
@@ -1,3 +1,11 @@
1
+ <!-- A NEW capability's delta opens with ## Purpose (one sentence on why this
2
+ capability exists); archive carries it into the newly created main spec. Delete
3
+ this block when the delta targets a capability that already has a main spec —
4
+ edit that spec's Purpose directly instead. -->
5
+
6
+ ## Purpose
7
+ [One sentence: why this capability exists]
8
+
1
9
  ## ADDED Requirements
2
10
 
3
11
  ### Requirement: [name]
@@ -9,5 +17,15 @@
9
17
  - **AND** [additional outcome]
10
18
 
11
19
  <!-- MODIFIED, REMOVED, and RENAMED Requirements may also be used.
12
- MODIFIED must contain the full updated requirement block.
13
- REMOVED must include Reason and Migration; RENAMED uses a FROM/TO pair. -->
20
+ MODIFIED must contain the full updated requirement block. -->
21
+
22
+ ## REMOVED Requirements
23
+
24
+ - `[name]`
25
+ - Reason: [why removed]
26
+ - Migration: [how existing usage migrates]
27
+
28
+ ## RENAMED Requirements
29
+
30
+ - FROM: `[old name]`
31
+ - TO: `[new name]`
@@ -39,6 +39,10 @@ artifacts:
39
39
  body, not only in the heading) /
40
40
  #### Scenario: <name> (four #, WHEN/THEN/AND bullets).
41
41
  MODIFIED must paste the full updated requirement block.
42
+ REMOVED lists each removed name as a bullet with Reason and Migration
43
+ fields, e.g. "- <name>" / "- Reason: ..." / "- Migration: ...".
44
+ RENAMED writes a FROM:/TO: pair per entry, e.g. "- FROM: `<old name>`" /
45
+ "- TO: `<new name>`".
42
46
  A new capability's delta must open with ## Purpose (one sentence on why this
43
47
  capability exists); archive carries it into the newly created main spec. Do
44
48
  not add Purpose to an existing capability's delta (edit the main spec's
@@ -1,3 +1,11 @@
1
+ <!-- A NEW capability's delta opens with ## Purpose (one sentence on why this
2
+ capability exists); archive carries it into the newly created main spec. Delete
3
+ this block when the delta targets a capability that already has a main spec —
4
+ edit that spec's Purpose directly instead. -->
5
+
6
+ ## Purpose
7
+ [One sentence: why this capability exists]
8
+
1
9
  ## ADDED Requirements
2
10
 
3
11
  ### Requirement: [name]
@@ -9,5 +17,15 @@
9
17
  - **AND** [additional outcome]
10
18
 
11
19
  <!-- MODIFIED, REMOVED, and RENAMED Requirements may also be used.
12
- MODIFIED must contain the full updated requirement block.
13
- REMOVED must include Reason and Migration; RENAMED uses a FROM/TO pair. -->
20
+ MODIFIED must contain the full updated requirement block. -->
21
+
22
+ ## REMOVED Requirements
23
+
24
+ - `[name]`
25
+ - Reason: [why removed]
26
+ - Migration: [how existing usage migrates]
27
+
28
+ ## RENAMED Requirements
29
+
30
+ - FROM: `[old name]`
31
+ - TO: `[new name]`