@seanmars/tospec 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +10 -6
  3. package/assets/dashboard/lib/derive.js +11 -5
  4. package/assets/dashboard/lib/store.js +4 -2
  5. package/assets/dashboard/views/change.js +5 -3
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +42 -7
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/commands/config.d.ts.map +1 -1
  10. package/dist/commands/config.js +213 -100
  11. package/dist/commands/config.js.map +1 -1
  12. package/dist/commands/dashboard.d.ts +9 -0
  13. package/dist/commands/dashboard.d.ts.map +1 -1
  14. package/dist/commands/dashboard.js +77 -21
  15. package/dist/commands/dashboard.js.map +1 -1
  16. package/dist/commands/decision.d.ts +38 -1
  17. package/dist/commands/decision.d.ts.map +1 -1
  18. package/dist/commands/decision.js +303 -27
  19. package/dist/commands/decision.js.map +1 -1
  20. package/dist/commands/metrics.d.ts +3 -4
  21. package/dist/commands/metrics.d.ts.map +1 -1
  22. package/dist/commands/metrics.js +3 -5
  23. package/dist/commands/metrics.js.map +1 -1
  24. package/dist/commands/shared-output.d.ts +24 -0
  25. package/dist/commands/shared-output.d.ts.map +1 -1
  26. package/dist/commands/shared-output.js +21 -2
  27. package/dist/commands/shared-output.js.map +1 -1
  28. package/dist/commands/show.d.ts.map +1 -1
  29. package/dist/commands/show.js +12 -9
  30. package/dist/commands/show.js.map +1 -1
  31. package/dist/commands/validate.d.ts +19 -0
  32. package/dist/commands/validate.d.ts.map +1 -1
  33. package/dist/commands/validate.js +243 -64
  34. package/dist/commands/validate.js.map +1 -1
  35. package/dist/commands/workflow/instructions.d.ts +7 -1
  36. package/dist/commands/workflow/instructions.d.ts.map +1 -1
  37. package/dist/commands/workflow/instructions.js +299 -81
  38. package/dist/commands/workflow/instructions.js.map +1 -1
  39. package/dist/commands/workflow/new-change.d.ts.map +1 -1
  40. package/dist/commands/workflow/new-change.js +26 -17
  41. package/dist/commands/workflow/new-change.js.map +1 -1
  42. package/dist/commands/workflow/prompt-escape.d.ts +25 -0
  43. package/dist/commands/workflow/prompt-escape.d.ts.map +1 -0
  44. package/dist/commands/workflow/prompt-escape.js +62 -0
  45. package/dist/commands/workflow/prompt-escape.js.map +1 -0
  46. package/dist/commands/workflow/schemas.d.ts.map +1 -1
  47. package/dist/commands/workflow/schemas.js +34 -16
  48. package/dist/commands/workflow/schemas.js.map +1 -1
  49. package/dist/commands/workflow/shared.d.ts +46 -10
  50. package/dist/commands/workflow/shared.d.ts.map +1 -1
  51. package/dist/commands/workflow/shared.js +66 -30
  52. package/dist/commands/workflow/shared.js.map +1 -1
  53. package/dist/commands/workflow/status.d.ts +5 -2
  54. package/dist/commands/workflow/status.d.ts.map +1 -1
  55. package/dist/commands/workflow/status.js +62 -28
  56. package/dist/commands/workflow/status.js.map +1 -1
  57. package/dist/core/archive.d.ts +38 -0
  58. package/dist/core/archive.d.ts.map +1 -1
  59. package/dist/core/archive.js +492 -75
  60. package/dist/core/archive.js.map +1 -1
  61. package/dist/core/artifact-graph/index.d.ts +2 -2
  62. package/dist/core/artifact-graph/index.d.ts.map +1 -1
  63. package/dist/core/artifact-graph/index.js +2 -2
  64. package/dist/core/artifact-graph/index.js.map +1 -1
  65. package/dist/core/artifact-graph/instruction-loader.d.ts +8 -0
  66. package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
  67. package/dist/core/artifact-graph/instruction-loader.js +58 -36
  68. package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
  69. package/dist/core/artifact-graph/resolver.d.ts +39 -12
  70. package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
  71. package/dist/core/artifact-graph/resolver.js +82 -36
  72. package/dist/core/artifact-graph/resolver.js.map +1 -1
  73. package/dist/core/artifact-graph/schema.d.ts +24 -0
  74. package/dist/core/artifact-graph/schema.d.ts.map +1 -1
  75. package/dist/core/artifact-graph/schema.js +32 -1
  76. package/dist/core/artifact-graph/schema.js.map +1 -1
  77. package/dist/core/artifact-graph/stub-detection.d.ts +10 -2
  78. package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
  79. package/dist/core/artifact-graph/stub-detection.js +18 -11
  80. package/dist/core/artifact-graph/stub-detection.js.map +1 -1
  81. package/dist/core/artifact-graph/types.d.ts +2 -2
  82. package/dist/core/artifact-graph/types.d.ts.map +1 -1
  83. package/dist/core/artifact-graph/types.js +10 -1
  84. package/dist/core/artifact-graph/types.js.map +1 -1
  85. package/dist/core/change-metadata/schema.d.ts.map +1 -1
  86. package/dist/core/change-metadata/schema.js +18 -4
  87. package/dist/core/change-metadata/schema.js.map +1 -1
  88. package/dist/core/change-presenter.d.ts.map +1 -1
  89. package/dist/core/change-presenter.js +13 -8
  90. package/dist/core/change-presenter.js.map +1 -1
  91. package/dist/core/change-status-policy.d.ts.map +1 -1
  92. package/dist/core/change-status-policy.js +3 -1
  93. package/dist/core/change-status-policy.js.map +1 -1
  94. package/dist/core/codex-metrics.d.ts +1 -1
  95. package/dist/core/codex-metrics.js +1 -1
  96. package/dist/core/config-schema.d.ts +8 -0
  97. package/dist/core/config-schema.d.ts.map +1 -1
  98. package/dist/core/config-schema.js +10 -0
  99. package/dist/core/config-schema.js.map +1 -1
  100. package/dist/core/dashboard-activity.d.ts +4 -4
  101. package/dist/core/dashboard-activity.js +4 -4
  102. package/dist/core/dashboard-data.d.ts +11 -5
  103. package/dist/core/dashboard-data.d.ts.map +1 -1
  104. package/dist/core/dashboard-data.js +54 -28
  105. package/dist/core/dashboard-data.js.map +1 -1
  106. package/dist/core/dashboard-hub.d.ts +3 -3
  107. package/dist/core/dashboard-hub.d.ts.map +1 -1
  108. package/dist/core/dashboard-hub.js +16 -4
  109. package/dist/core/dashboard-hub.js.map +1 -1
  110. package/dist/core/dashboard-routes.d.ts +2 -1
  111. package/dist/core/dashboard-routes.d.ts.map +1 -1
  112. package/dist/core/dashboard-routes.js +102 -33
  113. package/dist/core/dashboard-routes.js.map +1 -1
  114. package/dist/core/global-config.d.ts +71 -2
  115. package/dist/core/global-config.d.ts.map +1 -1
  116. package/dist/core/global-config.js +149 -4
  117. package/dist/core/global-config.js.map +1 -1
  118. package/dist/core/id.d.ts +9 -0
  119. package/dist/core/id.d.ts.map +1 -1
  120. package/dist/core/id.js +37 -0
  121. package/dist/core/id.js.map +1 -1
  122. package/dist/core/init.d.ts +9 -2
  123. package/dist/core/init.d.ts.map +1 -1
  124. package/dist/core/init.js +116 -33
  125. package/dist/core/init.js.map +1 -1
  126. package/dist/core/list.d.ts.map +1 -1
  127. package/dist/core/list.js +69 -45
  128. package/dist/core/list.js.map +1 -1
  129. package/dist/core/local-server.d.ts +9 -2
  130. package/dist/core/local-server.d.ts.map +1 -1
  131. package/dist/core/local-server.js +24 -4
  132. package/dist/core/local-server.js.map +1 -1
  133. package/dist/core/markdown-render.d.ts.map +1 -1
  134. package/dist/core/markdown-render.js +7 -2
  135. package/dist/core/markdown-render.js.map +1 -1
  136. package/dist/core/migrate.d.ts +24 -2
  137. package/dist/core/migrate.d.ts.map +1 -1
  138. package/dist/core/migrate.js +335 -59
  139. package/dist/core/migrate.js.map +1 -1
  140. package/dist/core/parsers/change-parser.d.ts +20 -1
  141. package/dist/core/parsers/change-parser.d.ts.map +1 -1
  142. package/dist/core/parsers/change-parser.js +76 -73
  143. package/dist/core/parsers/change-parser.js.map +1 -1
  144. package/dist/core/parsers/markdown-parser.d.ts +24 -0
  145. package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
  146. package/dist/core/parsers/markdown-parser.js +65 -10
  147. package/dist/core/parsers/markdown-parser.js.map +1 -1
  148. package/dist/core/parsers/requirement-blocks.d.ts +127 -6
  149. package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
  150. package/dist/core/parsers/requirement-blocks.js +349 -56
  151. package/dist/core/parsers/requirement-blocks.js.map +1 -1
  152. package/dist/core/parsers/requirement-text.d.ts +25 -1
  153. package/dist/core/parsers/requirement-text.d.ts.map +1 -1
  154. package/dist/core/parsers/requirement-text.js +62 -6
  155. package/dist/core/parsers/requirement-text.js.map +1 -1
  156. package/dist/core/parsers/spec-structure.d.ts.map +1 -1
  157. package/dist/core/parsers/spec-structure.js +36 -24
  158. package/dist/core/parsers/spec-structure.js.map +1 -1
  159. package/dist/core/project-config.d.ts +9 -2
  160. package/dist/core/project-config.d.ts.map +1 -1
  161. package/dist/core/project-config.js +30 -7
  162. package/dist/core/project-config.js.map +1 -1
  163. package/dist/core/project-layout.d.ts +26 -0
  164. package/dist/core/project-layout.d.ts.map +1 -1
  165. package/dist/core/project-layout.js +51 -1
  166. package/dist/core/project-layout.js.map +1 -1
  167. package/dist/core/schema-names.d.ts +17 -0
  168. package/dist/core/schema-names.d.ts.map +1 -1
  169. package/dist/core/schema-names.js +24 -0
  170. package/dist/core/schema-names.js.map +1 -1
  171. package/dist/core/shared/index.d.ts +1 -1
  172. package/dist/core/shared/index.d.ts.map +1 -1
  173. package/dist/core/shared/index.js +1 -1
  174. package/dist/core/shared/index.js.map +1 -1
  175. package/dist/core/shared/rules-generation.d.ts +12 -0
  176. package/dist/core/shared/rules-generation.d.ts.map +1 -1
  177. package/dist/core/shared/rules-generation.js +26 -2
  178. package/dist/core/shared/rules-generation.js.map +1 -1
  179. package/dist/core/shared/skill-generation.d.ts +7 -0
  180. package/dist/core/shared/skill-generation.d.ts.map +1 -1
  181. package/dist/core/shared/skill-generation.js +19 -0
  182. package/dist/core/shared/skill-generation.js.map +1 -1
  183. package/dist/core/shared/tool-detection.d.ts +10 -1
  184. package/dist/core/shared/tool-detection.d.ts.map +1 -1
  185. package/dist/core/shared/tool-detection.js +37 -6
  186. package/dist/core/shared/tool-detection.js.map +1 -1
  187. package/dist/core/spec-presenter.d.ts.map +1 -1
  188. package/dist/core/spec-presenter.js +23 -1
  189. package/dist/core/spec-presenter.js.map +1 -1
  190. package/dist/core/specs-apply.d.ts +98 -1
  191. package/dist/core/specs-apply.d.ts.map +1 -1
  192. package/dist/core/specs-apply.js +426 -63
  193. package/dist/core/specs-apply.js.map +1 -1
  194. package/dist/core/templates/fragments/change-diff.d.ts +37 -0
  195. package/dist/core/templates/fragments/change-diff.d.ts.map +1 -0
  196. package/dist/core/templates/fragments/change-diff.js +37 -0
  197. package/dist/core/templates/fragments/change-diff.js.map +1 -0
  198. package/dist/core/templates/fragments/sync-fail.d.ts +8 -0
  199. package/dist/core/templates/fragments/sync-fail.d.ts.map +1 -0
  200. package/dist/core/templates/fragments/sync-fail.js +8 -0
  201. package/dist/core/templates/fragments/sync-fail.js.map +1 -0
  202. package/dist/core/templates/fragments/validate.d.ts +14 -1
  203. package/dist/core/templates/fragments/validate.d.ts.map +1 -1
  204. package/dist/core/templates/fragments/validate.js +14 -1
  205. package/dist/core/templates/fragments/validate.js.map +1 -1
  206. package/dist/core/templates/fragments/verify.d.ts +2 -2
  207. package/dist/core/templates/fragments/verify.d.ts.map +1 -1
  208. package/dist/core/templates/fragments/verify.js +8 -5
  209. package/dist/core/templates/fragments/verify.js.map +1 -1
  210. package/dist/core/templates/workflows/apply.d.ts +1 -1
  211. package/dist/core/templates/workflows/apply.d.ts.map +1 -1
  212. package/dist/core/templates/workflows/apply.js +29 -10
  213. package/dist/core/templates/workflows/apply.js.map +1 -1
  214. package/dist/core/templates/workflows/archive.d.ts.map +1 -1
  215. package/dist/core/templates/workflows/archive.js +27 -9
  216. package/dist/core/templates/workflows/archive.js.map +1 -1
  217. package/dist/core/templates/workflows/decision.d.ts.map +1 -1
  218. package/dist/core/templates/workflows/decision.js +10 -4
  219. package/dist/core/templates/workflows/decision.js.map +1 -1
  220. package/dist/core/templates/workflows/explore.d.ts +2 -2
  221. package/dist/core/templates/workflows/explore.d.ts.map +1 -1
  222. package/dist/core/templates/workflows/explore.js +7 -3
  223. package/dist/core/templates/workflows/explore.js.map +1 -1
  224. package/dist/core/templates/workflows/grill.d.ts +2 -2
  225. package/dist/core/templates/workflows/grill.d.ts.map +1 -1
  226. package/dist/core/templates/workflows/grill.js +5 -4
  227. package/dist/core/templates/workflows/grill.js.map +1 -1
  228. package/dist/core/templates/workflows/issue.d.ts.map +1 -1
  229. package/dist/core/templates/workflows/issue.js +9 -3
  230. package/dist/core/templates/workflows/issue.js.map +1 -1
  231. package/dist/core/templates/workflows/propose.d.ts.map +1 -1
  232. package/dist/core/templates/workflows/propose.js +4 -1
  233. package/dist/core/templates/workflows/propose.js.map +1 -1
  234. package/dist/core/templates/workflows/sync.d.ts.map +1 -1
  235. package/dist/core/templates/workflows/sync.js +25 -11
  236. package/dist/core/templates/workflows/sync.js.map +1 -1
  237. package/dist/core/templates/workflows/update.d.ts.map +1 -1
  238. package/dist/core/templates/workflows/update.js +17 -10
  239. package/dist/core/templates/workflows/update.js.map +1 -1
  240. package/dist/core/update.d.ts +5 -0
  241. package/dist/core/update.d.ts.map +1 -1
  242. package/dist/core/update.js +66 -29
  243. package/dist/core/update.js.map +1 -1
  244. package/dist/core/validation/constants.d.ts +7 -1
  245. package/dist/core/validation/constants.d.ts.map +1 -1
  246. package/dist/core/validation/constants.js +16 -2
  247. package/dist/core/validation/constants.js.map +1 -1
  248. package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
  249. package/dist/core/validation/purpose-placeholder.js +35 -11
  250. package/dist/core/validation/purpose-placeholder.js.map +1 -1
  251. package/dist/core/validation/section-validator.d.ts +7 -0
  252. package/dist/core/validation/section-validator.d.ts.map +1 -1
  253. package/dist/core/validation/section-validator.js +22 -2
  254. package/dist/core/validation/section-validator.js.map +1 -1
  255. package/dist/core/validation/sister-changes.d.ts +93 -0
  256. package/dist/core/validation/sister-changes.d.ts.map +1 -0
  257. package/dist/core/validation/sister-changes.js +180 -0
  258. package/dist/core/validation/sister-changes.js.map +1 -0
  259. package/dist/core/validation/task-markers.d.ts +9 -0
  260. package/dist/core/validation/task-markers.d.ts.map +1 -0
  261. package/dist/core/validation/task-markers.js +71 -0
  262. package/dist/core/validation/task-markers.js.map +1 -0
  263. package/dist/core/validation/types.d.ts +58 -0
  264. package/dist/core/validation/types.d.ts.map +1 -1
  265. package/dist/core/validation/types.js +22 -0
  266. package/dist/core/validation/types.js.map +1 -1
  267. package/dist/core/validation/validator.d.ts +79 -2
  268. package/dist/core/validation/validator.d.ts.map +1 -1
  269. package/dist/core/validation/validator.js +565 -84
  270. package/dist/core/validation/validator.js.map +1 -1
  271. package/dist/utils/change-metadata.d.ts +62 -5
  272. package/dist/utils/change-metadata.d.ts.map +1 -1
  273. package/dist/utils/change-metadata.js +193 -31
  274. package/dist/utils/change-metadata.js.map +1 -1
  275. package/dist/utils/change-utils.d.ts +21 -0
  276. package/dist/utils/change-utils.d.ts.map +1 -1
  277. package/dist/utils/change-utils.js +168 -53
  278. package/dist/utils/change-utils.js.map +1 -1
  279. package/dist/utils/command-line.d.ts +11 -0
  280. package/dist/utils/command-line.d.ts.map +1 -0
  281. package/dist/utils/command-line.js +43 -0
  282. package/dist/utils/command-line.js.map +1 -0
  283. package/dist/utils/display-width.d.ts +10 -0
  284. package/dist/utils/display-width.d.ts.map +1 -0
  285. package/dist/utils/display-width.js +15 -0
  286. package/dist/utils/display-width.js.map +1 -0
  287. package/dist/utils/file-system.d.ts +16 -0
  288. package/dist/utils/file-system.d.ts.map +1 -1
  289. package/dist/utils/file-system.js +49 -2
  290. package/dist/utils/file-system.js.map +1 -1
  291. package/dist/utils/frontmatter.d.ts.map +1 -1
  292. package/dist/utils/frontmatter.js +11 -5
  293. package/dist/utils/frontmatter.js.map +1 -1
  294. package/dist/utils/interactive.d.ts.map +1 -1
  295. package/dist/utils/interactive.js +3 -1
  296. package/dist/utils/interactive.js.map +1 -1
  297. package/dist/utils/item-discovery.d.ts +3 -2
  298. package/dist/utils/item-discovery.d.ts.map +1 -1
  299. package/dist/utils/item-discovery.js +34 -16
  300. package/dist/utils/item-discovery.js.map +1 -1
  301. package/dist/utils/link.d.ts +0 -1
  302. package/dist/utils/link.d.ts.map +1 -1
  303. package/dist/utils/link.js +0 -1
  304. package/dist/utils/link.js.map +1 -1
  305. package/dist/utils/match.d.ts.map +1 -1
  306. package/dist/utils/match.js +10 -1
  307. package/dist/utils/match.js.map +1 -1
  308. package/dist/utils/option-flags.d.ts +9 -0
  309. package/dist/utils/option-flags.d.ts.map +1 -0
  310. package/dist/utils/option-flags.js +12 -0
  311. package/dist/utils/option-flags.js.map +1 -0
  312. package/dist/utils/sentence.d.ts +9 -0
  313. package/dist/utils/sentence.d.ts.map +1 -0
  314. package/dist/utils/sentence.js +12 -0
  315. package/dist/utils/sentence.js.map +1 -0
  316. package/dist/utils/spec-files.d.ts +84 -1
  317. package/dist/utils/spec-files.d.ts.map +1 -1
  318. package/dist/utils/spec-files.js +192 -3
  319. package/dist/utils/spec-files.js.map +1 -1
  320. package/dist/utils/task-progress.d.ts.map +1 -1
  321. package/dist/utils/task-progress.js +32 -9
  322. package/dist/utils/task-progress.js.map +1 -1
  323. package/dist/utils/timestamp.d.ts +27 -0
  324. package/dist/utils/timestamp.d.ts.map +1 -1
  325. package/dist/utils/timestamp.js +37 -0
  326. package/dist/utils/timestamp.js.map +1 -1
  327. package/package.json +4 -1
  328. package/schemas/decision/templates/decision.md +2 -2
  329. package/schemas/issue/schema.yaml +12 -6
  330. package/schemas/issue/templates/task.md +1 -1
  331. package/schemas/sdd/schema.yaml +21 -10
  332. package/schemas/sdd/templates/proposal.md +1 -1
@@ -1,14 +1,24 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { hasDeltaSection } from '../core/parsers/requirement-blocks.js';
4
+ import { FileSystemUtils, enterOnce, isDirectoryEntry } from './file-system.js';
5
+ import { findDeltaSpecSources } from '../core/specs-apply.js';
3
6
  /**
4
7
  * Absolute paths of every accepted file under `dir`, at any depth, sorted by
5
8
  * code point — not localeCompare, which follows the process ICU locale and
6
9
  * would order issues differently on CI than locally. A missing or unreadable
7
10
  * directory means "no files".
11
+ *
12
+ * Links are followed, as the merge path follows them: a delta the merge reads
13
+ * through a linked capability folder or a linked `spec.md` must be one validate
14
+ * reads too, or validate reports "no deltas" for a change archive merges.
8
15
  */
9
16
  async function findFiles(dir, accept) {
10
17
  const results = [];
18
+ const visited = new Set();
11
19
  const walk = async (current) => {
20
+ if (!(await enterOnce(visited, current)))
21
+ return;
12
22
  let entries;
13
23
  try {
14
24
  entries = await fs.readdir(current, { withFileTypes: true });
@@ -18,10 +28,10 @@ async function findFiles(dir, accept) {
18
28
  }
19
29
  for (const entry of entries) {
20
30
  const full = path.join(current, entry.name);
21
- if (entry.isDirectory()) {
31
+ if (isDirectoryEntry(current, entry)) {
22
32
  await walk(full);
23
33
  }
24
- else if (entry.isFile() && accept(entry.name)) {
34
+ else if (accept(entry.name) && (entry.isFile() || (await isLinkedFile(full, entry)))) {
25
35
  results.push(full);
26
36
  }
27
37
  }
@@ -29,6 +39,11 @@ async function findFiles(dir, accept) {
29
39
  await walk(dir);
30
40
  return results.sort();
31
41
  }
42
+ async function isLinkedFile(full, entry) {
43
+ if (!entry.isSymbolicLink())
44
+ return false;
45
+ return fs.stat(full).then((stat) => stat.isFile(), () => false);
46
+ }
32
47
  /**
33
48
  * Every `spec.md` under a change's `specs/` directory, at any depth.
34
49
  *
@@ -36,11 +51,67 @@ async function findFiles(dir, accept) {
36
51
  * `specs/<capability>/spec.md`: the merge path reads one level, so anything
37
52
  * deeper would be applied by nobody, and finding it lets the validator report it
38
53
  * and archive refuse.
39
- * tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
40
54
  */
41
55
  export async function findSpecFiles(specsDir) {
42
56
  return findFiles(specsDir, (name) => name === 'spec.md');
43
57
  }
58
+ /**
59
+ * The spec files a declared `skip_specs: true` contradicts, relative to
60
+ * `specs/` in POSIX form; none when the marker is not declared. validate's
61
+ * ERROR and `instructions apply`'s warning both read this list, so the two
62
+ * cannot disagree about which change carries the contradiction.
63
+ *
64
+ * Counts only the files the merge would apply, picked by the merge's own
65
+ * selection. A root `specs/<cap>.md` is applied by nobody, so it does not
66
+ * contradict "this change merges no deltas"; validate warns about it instead.
67
+ * Calling it a contradiction would make the same file an ERROR under
68
+ * skip_specs and a WARNING everywhere else, and would block a root notes file
69
+ * that merely quotes a delta header — the case the WARNING level exists for.
70
+ * A nested or root `spec.md` is no contradiction either, for the same reason:
71
+ * validate's layout ERROR names it whatever the marker says, and counted here
72
+ * it was also the one spec file apply's "Not present in this change: specs"
73
+ * told the agent to ignore.
74
+ */
75
+ export async function findSkipSpecsContradictions(changeDir, marker) {
76
+ if (!marker.declared)
77
+ return [];
78
+ const specsDir = path.join(changeDir, 'specs');
79
+ return (await findDeltaSpecSources(specsDir)).map(({ source }) => FileSystemUtils.toPosixPath(path.relative(specsDir, source)));
80
+ }
81
+ /**
82
+ * Why validate refuses a `.md` file below the `specs/` root by its path alone,
83
+ * or null for `<capability>/spec.md`. `entryPath` is relative to `specs/`, in
84
+ * POSIX form, and at least one folder deep: `specs/spec.md` has its own rule
85
+ * ({@link findRootSpecDelta}) and any other root file only warns
86
+ * ({@link findRootDeltaFiles}). validate's layout ERRORs and
87
+ * {@link findMisplacedSpecFiles} both judge by it, so apply cannot call a change
88
+ * ready that validate refuses for its layout.
89
+ */
90
+ export function specLayoutFault(entryPath) {
91
+ const segments = entryPath.split('/');
92
+ if (segments.length > 2)
93
+ return 'nested';
94
+ // fast-glob (which computes artifact status) skips dot-directories while the
95
+ // merge path's readdir does not.
96
+ if (segments[0].startsWith('.'))
97
+ return 'dot-folder';
98
+ return segments[1] === 'spec.md' ? null : 'misnamed';
99
+ }
100
+ /**
101
+ * The files validate refuses for where they sit, relative to `specs/` in POSIX
102
+ * form: `specs/spec.md` by {@link findRootSpecDelta}, and every `.md` below the
103
+ * root by {@link specLayoutFault}. `skip_specs` does not change the list, as it
104
+ * does not change those ERRORs.
105
+ */
106
+ export async function findMisplacedSpecFiles(specsDir, options) {
107
+ const relative = (file) => FileSystemUtils.toPosixPath(path.relative(specsDir, file));
108
+ const rootSpec = await findRootSpecDelta(specsDir, options);
109
+ const below = (await findAllMarkdownFiles(specsDir))
110
+ .filter((file) => path.dirname(file) !== specsDir)
111
+ .map(relative)
112
+ .filter((entryPath) => specLayoutFault(entryPath) !== null);
113
+ return [...(rootSpec === undefined ? [] : [relative(rootSpec)]), ...below].sort();
114
+ }
44
115
  /**
45
116
  * Every `*.md` under a change's `specs/` directory, at any depth. Unlike
46
117
  * {@link findSpecFiles} the basename is not filtered, so the validator can
@@ -50,4 +121,122 @@ export async function findSpecFiles(specsDir) {
50
121
  export async function findAllMarkdownFiles(specsDir) {
51
122
  return findFiles(specsDir, (name) => name.toLowerCase().endsWith('.md'));
52
123
  }
124
+ /**
125
+ * `*.md` files directly under `specs/` (other than the file `specs/spec.md`
126
+ * resolves to, which has its own ERROR) that carry a delta section header — a delta written as
127
+ * `specs/<capability>.md` instead of `specs/<capability>/spec.md`, which the
128
+ * merge never reads.
129
+ *
130
+ * Only these: a root `.md` without a delta header is notes, and stays clutter
131
+ * rather than a lost delta. Validate and archive both read this one list and
132
+ * both only warn, so neither refuses what the other accepts.
133
+ *
134
+ * `includeRootSpec` keeps `specs/spec.md` in the list, for a caller where
135
+ * nothing else names it: archive wherever the merge-empty backstop does not run
136
+ * (under `--skip-specs`, or beside a capability delta).
137
+ */
138
+ export async function findRootDeltaFiles(specsDir, options = {}) {
139
+ let entries;
140
+ try {
141
+ entries = await fs.readdir(specsDir, { withFileTypes: true });
142
+ }
143
+ catch {
144
+ return [];
145
+ }
146
+ const rootSpecName = options.includeRootSpec
147
+ ? undefined
148
+ : await resolveRootSpecName(specsDir, entries.map((entry) => entry.name));
149
+ const found = [];
150
+ for (const entry of entries) {
151
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md') || entry.name === rootSpecName)
152
+ continue;
153
+ const full = path.join(specsDir, entry.name);
154
+ let content;
155
+ try {
156
+ content = await fs.readFile(full, 'utf-8');
157
+ }
158
+ catch {
159
+ continue;
160
+ }
161
+ if (hasDeltaSection(content))
162
+ found.push(full);
163
+ }
164
+ return found.sort();
165
+ }
166
+ /**
167
+ * `*.md` files below the `specs/` root, other than a `spec.md`, that carry a
168
+ * delta section header: a delta written as `specs/<capability>/notes.md` or
169
+ * `SPEC.md`, which the merge never reads (it reads `spec.md`, spelled exactly).
170
+ * Without a delta header the file is notes, the rule {@link findRootDeltaFiles}
171
+ * applies at the root. Root files are left to that function.
172
+ */
173
+ export async function findMisnamedDeltaFiles(specsDir) {
174
+ const found = [];
175
+ for (const file of await findAllMarkdownFiles(specsDir)) {
176
+ if (path.dirname(file) === specsDir || path.basename(file) === 'spec.md')
177
+ continue;
178
+ let content;
179
+ try {
180
+ content = await fs.readFile(file, 'utf-8');
181
+ }
182
+ catch {
183
+ continue;
184
+ }
185
+ if (hasDeltaSection(content))
186
+ found.push(file);
187
+ }
188
+ return found;
189
+ }
190
+ /**
191
+ * The file the validator's `specs/spec.md` ERROR lands on, under its name on
192
+ * disk, for a caller that names that file without running that check: the
193
+ * archive backstop. {@link findSpecFiles} matches the basename exactly, so on a
194
+ * case-insensitive file system a lone `SPEC.md` was left out of both that
195
+ * backstop and {@link findRootDeltaFiles}, and archived under `--no-validate`
196
+ * with nothing naming it.
197
+ */
198
+ export async function findRootSpecFile(specsDir) {
199
+ let names;
200
+ try {
201
+ names = await fs.readdir(specsDir);
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ const name = await resolveRootSpecName(specsDir, names);
207
+ return name === undefined ? undefined : path.join(specsDir, name);
208
+ }
209
+ /**
210
+ * The file `specs/spec.md` resolves to, when it is a delta the merge cannot
211
+ * read: always where the change must carry a delta, and elsewhere only with a
212
+ * delta header, since without one it is notes. validate's ERROR and the
213
+ * archive backstop both pick it here: the backstop counted the file whatever
214
+ * the schema demanded, so it refused notes validate had passed and sent the
215
+ * user back to validate.
216
+ */
217
+ export async function findRootSpecDelta(specsDir, options) {
218
+ const file = await findRootSpecFile(specsDir);
219
+ if (file === undefined || options.requireDelta)
220
+ return file;
221
+ const content = await fs.readFile(file, 'utf-8').catch(() => '');
222
+ return hasDeltaSection(content) ? file : undefined;
223
+ }
224
+ /**
225
+ * The entry name the validator's `specs/spec.md` ERROR lands on. That check
226
+ * stats the path, so the file system decides the case: on a case-insensitive
227
+ * one (Windows, default macOS) it fires for `SPEC.md` too. A plain
228
+ * case-insensitive exclusion would instead hide a `SPEC.md` delta on a
229
+ * case-sensitive file system, where that ERROR never fires for it.
230
+ *
231
+ * Only a regular file, as in that check: a directory named `spec.md` is a
232
+ * capability folder.
233
+ */
234
+ async function resolveRootSpecName(specsDir, names) {
235
+ const stat = await fs.stat(path.join(specsDir, 'spec.md')).catch(() => null);
236
+ if (!stat?.isFile())
237
+ return undefined;
238
+ if (names.includes('spec.md'))
239
+ return 'spec.md';
240
+ return names.find((name) => name.toLowerCase() === 'spec.md');
241
+ }
53
242
  //# 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;;;;;GAKG;AACH,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,MAAiC;IACrE,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QACpD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IACzD,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"spec-files.js","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"task-progress.d.ts","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAoBnE;AA8BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAarB;AAED,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAGvB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB1F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAI/D"}
1
+ {"version":3,"file":"task-progress.d.ts","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAoBnE;AA8BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAarB;AAED,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAGvB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB1F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAI/D"}
@@ -12,13 +12,30 @@ import { resolveSchemaForChange } from './change-metadata.js';
12
12
  * GitHub's rendering is the only view of tasks.md the author can check against,
13
13
  * so "tasks tospec sees" must equal "tasks GitHub draws". Too narrow and
14
14
  * `+ [ ] 1.2 …` is a box nobody can count; too wide and `- [ ]2.6 …` is a task
15
- * nobody can tick.
15
+ * nobody can tick. Ordered-list markers (`1.` / `1)`, up to 9 digits as in
16
+ * CommonMark) are GFM list items too: left out, `1. [ ] 1.2 …` was a drawn box
17
+ * the archive gate never saw, so an unfinished change passed it.
16
18
  *
17
19
  * Fences are masked by the callers (`buildCodeFenceMask`), not here: a tasks.md
18
20
  * documenting the task format puts real `- [ ]` lines in a fence, which would
19
21
  * leave the archive gate permanently incomplete.
22
+ *
23
+ * The text is `[^\r\n]*`, not `.*`: `.` also stops at U+2028/U+2029, but
24
+ * CommonMark ends a line only at LF, CR or CRLF, so GitHub draws
25
+ * `- [ ] a<U+2028>b` as a checkbox. Under `.*` that box went uncounted and an
26
+ * unfinished change passed the archive gate.
27
+ *
28
+ * `(?![ \t])` stops the text from re-splitting the blanks after `]`. Without it,
29
+ * on a line the text cannot finish, `[ \t]+` gave its blanks back one at a time
30
+ * and the text rescanned to the terminator after each: `- [ ]` + 80k blanks +
31
+ * U+2028 took 7.5 s under `.*`, and a bare CR that setTaskDone's old `\n` split
32
+ * left inside a line took 5.8 s. Both callers now end a line at every CR, so no
33
+ * line reaches the text holding one; the lookahead stays so a caller that splits
34
+ * otherwise cannot bring the cost back. Only the longest run could ever match,
35
+ * since a shorter one leaves the text the same terminator to cross, so the
36
+ * groups are unchanged.
20
37
  */
21
- const TASK_PATTERN = /^([ \t]*)[-*+][ \t]+\[([ \txX])\](?:[ \t]+(.*))?$/;
38
+ const TASK_PATTERN = /^([ \t]*)(?:[-*+]|\d{1,9}[.)])[ \t]+\[([ \txX])\](?:[ \t]+(?![ \t])([^\r\n]*))?$/;
22
39
  export function parseTasksFromContent(content) {
23
40
  const tasks = [];
24
41
  // normalizeDocument so a leading BOM cannot hide the first line:
@@ -116,22 +133,28 @@ export async function getTaskProgressForChange(changesDir, changeName, projectRo
116
133
  */
117
134
  export async function setTaskDone(file, line, done) {
118
135
  const content = await fs.readFile(file, 'utf-8');
119
- // BOM stripped so `line` means the same line it did in `parseTasksFromContent`,
120
- // which numbers from BOM-free text. Splitting on '\n' leaves each line's '\r'
121
- // attached, which is what preserves CRLF through the rejoin below.
122
- const lines = stripBom(content).split('\n');
136
+ // Split where `parseTasksFromContent` splits (BOM stripped; CRLF, LF and a
137
+ // bare CR each end a line), so `line` means the line it numbered: a '\n'-only
138
+ // split joined the lines around a bare CR and shifted every later one. A bare
139
+ // CR ends a line rather than staying text because CommonMark ends one there,
140
+ // and a task GitHub draws after it would otherwise go uncounted. The endings
141
+ // are rejoined as they were.
142
+ const text = stripBom(content);
143
+ const lines = text.split(/\r\n?|\n/);
144
+ const endings = text.match(/\r\n?|\n/g) ?? [];
123
145
  const raw = lines[line - 1];
124
146
  // A fenced line is "not a task" for the same reason it is not counted: it is a
125
147
  // quoted example, and writing to it edits documentation. The UI draws no
126
148
  // checkbox there, so reaching here means a stale client — hence the caller's 409.
127
149
  const fenced = buildCodeFenceMask(lines);
128
- if (raw === undefined || fenced[line - 1] || !TASK_PATTERN.test(raw.replace(/\r$/, ''))) {
150
+ if (raw === undefined || fenced[line - 1] || !TASK_PATTERN.test(raw)) {
129
151
  throw new Error(`Line ${line} of ${path.basename(file)} is not a task`);
130
152
  }
131
153
  // Same box class as TASK_PATTERN, so this rewrites exactly the marker the line
132
- // matched on; the first `[…]` on a task line is always it.
154
+ // matched on; the first `[…]` on a task line is always it, ordered list
155
+ // included (`1.` / `1)` carry no bracket).
133
156
  lines[line - 1] = raw.replace(/\[[ \txX]\]/, done ? '[x]' : '[ ]');
134
- await fs.writeFile(file, lines.join('\n'), 'utf-8');
157
+ await fs.writeFile(file, lines.map((l, i) => l + (endings[i] ?? '')).join(''), 'utf-8');
135
158
  }
136
159
  /**
137
160
  * "Tasks done", not "Complete": this counts checkboxes, while `tospec status`
@@ -1 +1 @@
1
- {"version":3,"file":"task-progress.js","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-progress.js","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"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"}
@@ -3,10 +3,37 @@
3
3
  * archive dirs and ticket files — agents must never assemble one by hand.
4
4
  */
5
5
  export declare function formatTimestamp(date: Date): string;
6
+ /**
7
+ * Splits `<yyyyMMdd_HHmmss>-<name>` (an archive dir, a ticket file's base), or
8
+ * null when the value does not open with a stamp. Splitting on the first hyphen
9
+ * alone read `no-date-change` as stamp `no`, name `date-change`.
10
+ */
11
+ export declare function parseStampedName(value: string): {
12
+ stamp: string;
13
+ name: string;
14
+ } | null;
6
15
  /**
7
16
  * A Date as `yyyy-MM-dd` in local time. For date-only metadata, instead of
8
17
  * `toISOString().split('T')[0]`, which truncates in UTC and records the wrong
9
18
  * calendar day near local midnight.
10
19
  */
11
20
  export declare function formatLocalDate(date?: Date): string;
21
+ /**
22
+ * A Date as `yyyy-MM-ddTHH:mm:ss` in local time, with no zone: the `created`
23
+ * a new change records. The day alone could not order two changes made on one
24
+ * day, and validate needs that order to say which of two changes rewriting one
25
+ * requirement archives first.
26
+ */
27
+ export declare function formatLocalDateTime(date?: Date): string;
28
+ /** Both spellings of `created`: the datetime a change records now, and the day older changes recorded. */
29
+ export declare const CREATED_PATTERN: RegExp;
30
+ /**
31
+ * A `created` value as the local Date it names, and whether it carried a time;
32
+ * null when it matches neither spelling or names no real moment (`2026-13-45`
33
+ * and `25:00:00` would roll over into another one).
34
+ */
35
+ export declare function parseCreated(value: string): {
36
+ date: Date;
37
+ hasTime: boolean;
38
+ } | null;
12
39
  //# sourceMappingURL=timestamp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"timestamp.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CASlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG/D"}
1
+ {"version":3,"file":"timestamp.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CASlD;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAGnE;AAED,0GAA0G;AAC1G,eAAO,MAAM,eAAe,QAA2D,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAQnF"}
@@ -12,6 +12,16 @@ export function formatTimestamp(date) {
12
12
  const seconds = pad(date.getSeconds());
13
13
  return `${year}${month}${day}_${hours}${minutes}${seconds}`;
14
14
  }
15
+ const STAMPED_NAME = /^(\d{8}_\d{6})-(.+)$/;
16
+ /**
17
+ * Splits `<yyyyMMdd_HHmmss>-<name>` (an archive dir, a ticket file's base), or
18
+ * null when the value does not open with a stamp. Splitting on the first hyphen
19
+ * alone read `no-date-change` as stamp `no`, name `date-change`.
20
+ */
21
+ export function parseStampedName(value) {
22
+ const match = STAMPED_NAME.exec(value);
23
+ return match ? { stamp: match[1], name: match[2] } : null;
24
+ }
15
25
  /**
16
26
  * A Date as `yyyy-MM-dd` in local time. For date-only metadata, instead of
17
27
  * `toISOString().split('T')[0]`, which truncates in UTC and records the wrong
@@ -21,4 +31,31 @@ export function formatLocalDate(date = new Date()) {
21
31
  const pad = (n) => String(n).padStart(2, '0');
22
32
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
23
33
  }
34
+ /**
35
+ * A Date as `yyyy-MM-ddTHH:mm:ss` in local time, with no zone: the `created`
36
+ * a new change records. The day alone could not order two changes made on one
37
+ * day, and validate needs that order to say which of two changes rewriting one
38
+ * requirement archives first.
39
+ */
40
+ export function formatLocalDateTime(date = new Date()) {
41
+ const pad = (n) => String(n).padStart(2, '0');
42
+ return `${formatLocalDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
43
+ }
44
+ /** Both spellings of `created`: the datetime a change records now, and the day older changes recorded. */
45
+ export const CREATED_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}))?$/;
46
+ /**
47
+ * A `created` value as the local Date it names, and whether it carried a time;
48
+ * null when it matches neither spelling or names no real moment (`2026-13-45`
49
+ * and `25:00:00` would roll over into another one).
50
+ */
51
+ export function parseCreated(value) {
52
+ const match = CREATED_PATTERN.exec(value);
53
+ if (!match)
54
+ return null;
55
+ const [, y, mo, d, h, mi, s] = match;
56
+ const hasTime = h !== undefined;
57
+ const date = hasTime ? new Date(+y, +mo - 1, +d, +h, +mi, +s) : new Date(+y, +mo - 1, +d);
58
+ const roundTrip = hasTime ? formatLocalDateTime(date) : formatLocalDate(date);
59
+ return roundTrip === value ? { date, hasTime } : null;
60
+ }
24
61
  //# sourceMappingURL=timestamp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAU;IACxC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACvC,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,GAAG,IAAI,KAAK,GAAG,OAAO,GAAG,OAAO,EAAE,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAI,GAAS,IAAI,IAAI,EAAE;IACrD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;AACpF,CAAC"}
1
+ {"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanmars/tospec",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
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",
@@ -34,6 +34,7 @@
34
34
  "node": ">=22.22.0"
35
35
  },
36
36
  "devDependencies": {
37
+ "@types/cross-spawn": "^6.0.6",
37
38
  "@types/node": "^24.13.4",
38
39
  "typescript": "^7.0.2",
39
40
  "vitest": "^5.0.0"
@@ -42,8 +43,10 @@
42
43
  "@inquirer/core": "^12.0.3",
43
44
  "@inquirer/prompts": "^8.7.2",
44
45
  "commander": "^15.0.0",
46
+ "cross-spawn": "^7.0.6",
45
47
  "diff": "^9.0.0",
46
48
  "fast-glob": "^3.3.3",
49
+ "fast-string-width": "^3.0.2",
47
50
  "marked": "^18.0.12",
48
51
  "yaml": "^2.9.0",
49
52
  "zod": "^4.6.2"
@@ -24,8 +24,8 @@
24
24
 
25
25
  ## Related Changes
26
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
+ and `tospec decision new --change <name>` record the link on the change's side only. List change
28
+ names here yourself, add archive paths once archived; omit if none yet -->
29
29
 
30
30
  ## Decision Process
31
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 -->
@@ -12,15 +12,19 @@ artifacts:
12
12
  requires: []
13
13
  instruction: |
14
14
  Diagnose and write the issue task: first build a tight feedback loop (one
15
- command that turns this bug red), list 3-5 falsifiable hypotheses and rank
16
- them, verify each, then write down the root cause. Contents: root-cause
17
- analysis, fix plan, test plan (why existing tests missed it / which edge
15
+ command that turns this bug red), list as many falsifiable hypotheses as
16
+ the evidence supports (one or two is fine when the loop already points at
17
+ one cause) and rank them, verify each, then write down the root cause.
18
+ Redact secrets as <REDACTED> in everything pasted here: task.md is
19
+ committed and kept in the archive. Quote only the lines that carry the
20
+ signal, and do not redact the error message itself. A step that needs
21
+ the user's login is theirs to run; do not capture its output.
22
+ Contents: root-cause analysis, fix plan, test plan (why existing tests missed it / which edge
18
23
  cases are missing), and a task checkbox list.
19
24
  validation:
20
25
  requiredSections: [Feedback Loop, Root Cause, Fix Plan, Test Plan, Tasks]
21
26
  - id: specs
22
- # Exactly one directory level — same constraint as sdd. See
23
- # tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
27
+ # Exactly one directory level — same constraint as sdd.
24
28
  generates: specs/*/spec.md
25
29
  description: Optional behavior-changing delta specifications
26
30
  template: spec.md
@@ -29,7 +33,9 @@ artifacts:
29
33
  instruction: |
30
34
  Write delta specs only when the fix changes externally observable behavior
31
35
  (same grammar as sdd, same layout: specs/<capability>/spec.md, exactly one
32
- directory level). REMOVED lists each name as a bullet with Reason and
36
+ directory level). Use the capability's exact name from tospec/specs/,
37
+ letter case included; a new capability gets a kebab-case name, same rule
38
+ as sdd. REMOVED lists each name as a bullet with Reason and
33
39
  Migration fields; RENAMED writes a FROM:/TO: pair, e.g. "- FROM: `<old
34
40
  name>`" / "- TO: `<new name>`". If the bug stems from a wrong spec, fix
35
41
  that Requirement with MODIFIED — pasting the full updated block, since a
@@ -15,7 +15,7 @@
15
15
 
16
16
  ## Tasks
17
17
  ### 1. Fix
18
- - [ ] 1.1 Write a regression test (red)
18
+ - [ ] 1.1 Regression test is red (the one from diagnosis, or write it now)
19
19
  - [ ] 1.2 Implement the fix until the test passes (green)
20
20
  - [ ] 1.3 Add edge-case tests: [...]
21
21
  - [ ] 1.4 Run the full test suite to confirm no regressions
@@ -15,9 +15,14 @@ artifacts:
15
15
  add/modify/remove), Capabilities (New/Modified capabilities and their specs),
16
16
  Impact (affected code/APIs/dependencies/systems). This is what specs and
17
17
  design expand from; never write file paths or code snippets (they go stale).
18
- If the change has no externally observable effect at all (tooling, CI,
19
- docs), say so in Impact and set `skip_specs: true` in the change's
20
- .tospec.yaml — see the specs artifact below.
18
+ Name a capability after the long-lived functional area it covers (`auth`),
19
+ not after this change (`add-login-endpoint`), in kebab-case; a Modified
20
+ capability keeps its exact existing name. When the change falls inside
21
+ an existing capability's area, list that one under Modified Capabilities;
22
+ create a new capability only when the area is genuinely different.
23
+ If the user explicitly says this change needs no delta spec, say so in
24
+ Impact and set `skip_specs: true` in the change's .tospec.yaml — see the
25
+ specs artifact below.
21
26
  validation:
22
27
  requiredSections: [Why, What Changes, Impact]
23
28
  minSectionLength:
@@ -29,8 +34,7 @@ artifacts:
29
34
  - id: specs
30
35
  # Exactly one directory level. A deeper path (specs/<area>/<capability>/spec.md)
31
36
  # is rejected by the validator: archive's merge only reads one level, so a nested
32
- # file would validate clean and then vanish on archive. See
33
- # tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
37
+ # file would validate clean and then vanish on archive.
34
38
  generates: specs/*/spec.md
35
39
  description: Delta specifications for affected capabilities
36
40
  template: spec.md
@@ -38,6 +42,13 @@ artifacts:
38
42
  instruction: |
39
43
  Write delta specs from the proposal. One file per capability, at
40
44
  specs/<capability>/spec.md — exactly one directory level, never nested.
45
+ An existing capability's folder uses its exact name from tospec/specs/,
46
+ letter case included: a name differing only in case is rejected. A new
47
+ capability's folder name becomes its main spec's name, so write it in
48
+ kebab-case (lowercase letters, numbers, single hyphens; letters with no
49
+ case, such as 中文, are fine); validate warns otherwise. A name Windows
50
+ cannot hold (con, aux, nul, prn, com1-9, lpt1-9, any of <>:"/\|?*, or a
51
+ trailing dot or space) is rejected.
41
52
  Grammar: ## ADDED|MODIFIED|REMOVED|RENAMED Requirements /
42
53
  ### Requirement: <name> (state the obligation with SHALL or MUST in the
43
54
  body, not only in the heading) /
@@ -60,11 +71,11 @@ artifacts:
60
71
  capability exists); archive carries it into the newly created main spec. Do
61
72
  not add Purpose to an existing capability's delta (edit the main spec's
62
73
  Purpose directly instead).
63
- A change with genuinely no behavioral effect (infrastructure, tooling,
64
- doc-only) writes no delta and declares `skip_specs: true` in its
65
- .tospec.yaml instead. Use it only when that is actually true — never
66
- invent a Requirement to get past validation, and never set the marker on a
67
- change that does have delta specs (validation rejects that contradiction).
74
+ When the user explicitly says this change needs no delta spec, write no
75
+ delta and declare `skip_specs: true` in its .tospec.yaml instead. Never
76
+ set the marker on your own judgment, never invent a Requirement to get
77
+ past validation, and never set the marker on a change that does have
78
+ delta specs (validation rejects that contradiction).
68
79
  - id: design
69
80
  generates: design.md
70
81
  description: Technical design and testing seams
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Capabilities
8
8
  ### New Capabilities
9
- - `<name>`: <description> (maps to specs/<name>/spec.md)
9
+ - `<name>`: <summary> (maps to specs/<name>/spec.md)
10
10
 
11
11
  ### Modified Capabilities
12
12
  - `<existing-name>`: <the requirement that changes>