@codewalla_india/openspec 1.3.1 → 1.3.2

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 (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +272 -191
  3. package/bin/openspec.js +5 -5
  4. package/dist/cli/index.js +99 -62
  5. package/dist/commands/change.d.ts +0 -2
  6. package/dist/commands/change.js +107 -86
  7. package/dist/commands/config.js +14 -16
  8. package/dist/commands/doctor.js +9 -4
  9. package/dist/commands/feedback.js +79 -30
  10. package/dist/commands/modify.d.ts +26 -0
  11. package/dist/commands/modify.js +147 -0
  12. package/dist/commands/schema.js +170 -91
  13. package/dist/commands/show.js +5 -2
  14. package/dist/commands/spec.js +49 -29
  15. package/dist/commands/store.js +15 -21
  16. package/dist/commands/validate.d.ts +8 -0
  17. package/dist/commands/validate.js +43 -20
  18. package/dist/commands/workflow/index.d.ts +2 -2
  19. package/dist/commands/workflow/index.js +1 -1
  20. package/dist/commands/workflow/instructions.d.ts +6 -26
  21. package/dist/commands/workflow/instructions.js +183 -376
  22. package/dist/commands/workflow/new-change.d.ts +0 -4
  23. package/dist/commands/workflow/new-change.js +10 -28
  24. package/dist/commands/workflow/shared.d.ts +34 -19
  25. package/dist/commands/workflow/shared.js +34 -4
  26. package/dist/commands/workflow/status.js +10 -32
  27. package/dist/commands/workflow/templates.js +15 -5
  28. package/dist/commands/workset.d.ts +2 -2
  29. package/dist/commands/workset.js +19 -22
  30. package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
  31. package/dist/comprehension-quiz/fingerprint.js +84 -0
  32. package/dist/comprehension-quiz/index.d.ts +42 -0
  33. package/dist/comprehension-quiz/index.js +47 -0
  34. package/dist/comprehension-quiz/pass-record.d.ts +44 -0
  35. package/dist/comprehension-quiz/pass-record.js +98 -0
  36. package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +53 -0
  38. package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
  39. package/dist/comprehension-quiz/providers/factory.js +174 -0
  40. package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
  41. package/dist/comprehension-quiz/providers/interface.js +63 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +34 -0
  43. package/dist/comprehension-quiz/providers/local.js +53 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +53 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
  47. package/dist/comprehension-quiz/providers/openai.js +53 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +69 -0
  49. package/dist/comprehension-quiz/question-generator.js +141 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +165 -0
  52. package/dist/comprehension-quiz/types.d.ts +115 -0
  53. package/dist/comprehension-quiz/types.js +11 -0
  54. package/dist/core/archive.d.ts +14 -1
  55. package/dist/core/archive.js +1350 -190
  56. package/dist/core/artifact-graph/graph.d.ts +28 -9
  57. package/dist/core/artifact-graph/graph.js +94 -43
  58. package/dist/core/artifact-graph/index.d.ts +1 -1
  59. package/dist/core/artifact-graph/index.js +1 -1
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
  61. package/dist/core/artifact-graph/instruction-loader.js +70 -12
  62. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  63. package/dist/core/artifact-graph/outputs.js +67 -5
  64. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  65. package/dist/core/artifact-graph/resolver.js +69 -15
  66. package/dist/core/artifact-graph/types.js +21 -3
  67. package/dist/core/available-tools.d.ts +2 -2
  68. package/dist/core/available-tools.js +14 -3
  69. package/dist/core/change-metadata/schema.d.ts +2 -0
  70. package/dist/core/change-metadata/schema.js +13 -0
  71. package/dist/core/change-status-policy.d.ts +10 -1
  72. package/dist/core/change-status-policy.js +30 -2
  73. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  74. package/dist/core/command-generation/adapters/amazon-q.js +11 -5
  75. package/dist/core/command-generation/adapters/antigravity.js +6 -5
  76. package/dist/core/command-generation/adapters/auggie.js +7 -6
  77. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  78. package/dist/core/command-generation/adapters/bob.js +11 -10
  79. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  80. package/dist/core/command-generation/adapters/claude.js +12 -17
  81. package/dist/core/command-generation/adapters/cline.js +5 -5
  82. package/dist/core/command-generation/adapters/codebuddy.js +8 -7
  83. package/dist/core/command-generation/adapters/continue.js +8 -7
  84. package/dist/core/command-generation/adapters/costrict.js +7 -6
  85. package/dist/core/command-generation/adapters/crush.js +9 -9
  86. package/dist/core/command-generation/adapters/cursor.js +8 -8
  87. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  88. package/dist/core/command-generation/adapters/devin.js +36 -0
  89. package/dist/core/command-generation/adapters/factory.js +7 -6
  90. package/dist/core/command-generation/adapters/gemini.js +40 -5
  91. package/dist/core/command-generation/adapters/github-copilot.js +6 -5
  92. package/dist/core/command-generation/adapters/iflow.js +9 -8
  93. package/dist/core/command-generation/adapters/index.d.ts +4 -2
  94. package/dist/core/command-generation/adapters/index.js +4 -2
  95. package/dist/core/command-generation/adapters/junie.js +6 -5
  96. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  97. package/dist/core/command-generation/adapters/kiro.js +6 -5
  98. package/dist/core/command-generation/adapters/lingma.js +9 -9
  99. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  101. package/dist/core/command-generation/adapters/opencode.js +6 -8
  102. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  103. package/dist/core/command-generation/adapters/pi.js +7 -10
  104. package/dist/core/command-generation/adapters/qoder.js +9 -9
  105. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  106. package/dist/core/command-generation/adapters/qwen.js +14 -9
  107. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  108. package/dist/core/command-generation/adapters/roocode.js +9 -9
  109. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  110. package/dist/core/command-generation/adapters/trae.js +28 -0
  111. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  112. package/dist/core/command-generation/adapters/zcode.js +33 -0
  113. package/dist/core/command-generation/generator.d.ts +8 -0
  114. package/dist/core/command-generation/generator.js +15 -1
  115. package/dist/core/command-generation/index.d.ts +1 -1
  116. package/dist/core/command-generation/index.js +1 -1
  117. package/dist/core/command-generation/invocation.d.ts +73 -0
  118. package/dist/core/command-generation/invocation.js +77 -0
  119. package/dist/core/command-generation/registry.js +8 -4
  120. package/dist/core/command-generation/types.d.ts +9 -2
  121. package/dist/core/command-generation/yaml.d.ts +20 -6
  122. package/dist/core/command-generation/yaml.js +31 -21
  123. package/dist/core/command-surface.d.ts +15 -0
  124. package/dist/core/command-surface.js +33 -0
  125. package/dist/core/completions/command-registry.js +16 -61
  126. package/dist/core/completions/generators/bash-generator.js +41 -41
  127. package/dist/core/completions/generators/fish-generator.js +7 -7
  128. package/dist/core/completions/generators/powershell-generator.js +44 -37
  129. package/dist/core/completions/generators/zsh-generator.js +33 -33
  130. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  131. package/dist/core/completions/installers/zsh-installer.js +20 -4
  132. package/dist/core/completions/templates/bash-templates.js +24 -24
  133. package/dist/core/completions/templates/fish-templates.js +38 -38
  134. package/dist/core/completions/templates/powershell-templates.js +28 -28
  135. package/dist/core/completions/templates/zsh-templates.js +39 -39
  136. package/dist/core/config-prompts.d.ts +1 -1
  137. package/dist/core/config-prompts.js +14 -1
  138. package/dist/core/config-schema.d.ts +10 -1
  139. package/dist/core/config-schema.js +67 -1
  140. package/dist/core/config.d.ts +15 -0
  141. package/dist/core/config.js +46 -5
  142. package/dist/core/file-state.js +56 -24
  143. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  144. package/dist/core/github-copilot/cloud-agent.js +497 -0
  145. package/dist/core/global-config.d.ts +16 -0
  146. package/dist/core/init.d.ts +58 -0
  147. package/dist/core/init.js +474 -73
  148. package/dist/core/legacy-cleanup.d.ts +59 -0
  149. package/dist/core/legacy-cleanup.js +253 -24
  150. package/dist/core/list.js +23 -16
  151. package/dist/core/migration.d.ts +73 -1
  152. package/dist/core/migration.js +360 -11
  153. package/dist/core/modify/conflict-detector.d.ts +26 -0
  154. package/dist/core/modify/conflict-detector.js +62 -0
  155. package/dist/core/modify/dependency-propagator.d.ts +38 -0
  156. package/dist/core/modify/dependency-propagator.js +103 -0
  157. package/dist/core/modify/history-tracker.d.ts +34 -0
  158. package/dist/core/modify/history-tracker.js +114 -0
  159. package/dist/core/modify/index.d.ts +7 -0
  160. package/dist/core/modify/index.js +7 -0
  161. package/dist/core/onboarding-commands.d.ts +30 -0
  162. package/dist/core/onboarding-commands.js +38 -0
  163. package/dist/core/openspec-root.js +18 -15
  164. package/dist/core/parsers/change-parser.d.ts +17 -2
  165. package/dist/core/parsers/change-parser.js +35 -21
  166. package/dist/core/parsers/code-fence.d.ts +15 -0
  167. package/dist/core/parsers/code-fence.js +49 -0
  168. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  169. package/dist/core/parsers/markdown-parser.js +7 -60
  170. package/dist/core/parsers/requirement-blocks.d.ts +29 -0
  171. package/dist/core/parsers/requirement-blocks.js +150 -31
  172. package/dist/core/parsers/requirement-text.d.ts +48 -0
  173. package/dist/core/parsers/requirement-text.js +98 -0
  174. package/dist/core/parsers/spec-structure.d.ts +1 -1
  175. package/dist/core/parsers/spec-structure.js +20 -30
  176. package/dist/core/profile-sync-drift.d.ts +0 -8
  177. package/dist/core/profile-sync-drift.js +49 -50
  178. package/dist/core/profiles.d.ts +2 -2
  179. package/dist/core/profiles.js +2 -2
  180. package/dist/core/project-config.d.ts +27 -18
  181. package/dist/core/project-config.js +101 -34
  182. package/dist/core/references.js +42 -4
  183. package/dist/core/relationship-health.d.ts +8 -0
  184. package/dist/core/relationship-health.js +13 -0
  185. package/dist/core/root-selection.d.ts +9 -6
  186. package/dist/core/root-selection.js +42 -5
  187. package/dist/core/schemas/base.schema.js +7 -2
  188. package/dist/core/shared/allowed-tools.d.ts +12 -0
  189. package/dist/core/shared/allowed-tools.js +12 -0
  190. package/dist/core/shared/index.d.ts +1 -0
  191. package/dist/core/shared/index.js +1 -0
  192. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  193. package/dist/core/shared/skill-content-equivalence.js +41 -0
  194. package/dist/core/shared/skill-generation.js +17 -13
  195. package/dist/core/shared/skill-paths.d.ts +13 -0
  196. package/dist/core/shared/skill-paths.js +23 -0
  197. package/dist/core/shared/tool-detection.d.ts +27 -7
  198. package/dist/core/shared/tool-detection.js +167 -37
  199. package/dist/core/shared-skill-target.d.ts +16 -0
  200. package/dist/core/shared-skill-target.js +156 -0
  201. package/dist/core/specs-apply.d.ts +80 -33
  202. package/dist/core/specs-apply.js +667 -145
  203. package/dist/core/store/git.d.ts +13 -0
  204. package/dist/core/store/git.js +24 -0
  205. package/dist/core/store/operations.js +20 -0
  206. package/dist/core/templates/skill-templates.d.ts +3 -2
  207. package/dist/core/templates/skill-templates.js +3 -2
  208. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  209. package/dist/core/templates/workflows/apply-change.js +186 -224
  210. package/dist/core/templates/workflows/archive-change.js +387 -260
  211. package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
  212. package/dist/core/templates/workflows/continue-change.js +210 -219
  213. package/dist/core/templates/workflows/explore.js +479 -443
  214. package/dist/core/templates/workflows/feedback.js +97 -97
  215. package/dist/core/templates/workflows/ff-change.js +200 -191
  216. package/dist/core/templates/workflows/modify-change.d.ts +4 -1
  217. package/dist/core/templates/workflows/modify-change.js +237 -115
  218. package/dist/core/templates/workflows/new-change.js +127 -134
  219. package/dist/core/templates/workflows/onboard.js +548 -542
  220. package/dist/core/templates/workflows/propose.js +273 -217
  221. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  222. package/dist/core/templates/workflows/store-selection.js +1 -1
  223. package/dist/core/templates/workflows/sync-specs.js +500 -273
  224. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  225. package/dist/core/templates/workflows/update-change.js +177 -0
  226. package/dist/core/templates/workflows/verify-change.js +324 -326
  227. package/dist/core/update.d.ts +40 -5
  228. package/dist/core/update.js +411 -96
  229. package/dist/core/validation/constants.d.ts +4 -1
  230. package/dist/core/validation/constants.js +4 -1
  231. package/dist/core/validation/plan-validator.d.ts +8 -0
  232. package/dist/core/validation/plan-validator.js +45 -0
  233. package/dist/core/validation/task-numbering.d.ts +16 -0
  234. package/dist/core/validation/task-numbering.js +57 -0
  235. package/dist/core/validation/validator.d.ts +27 -3
  236. package/dist/core/validation/validator.js +337 -48
  237. package/dist/core/version-check.d.ts +152 -0
  238. package/dist/core/version-check.js +731 -0
  239. package/dist/core/view.js +13 -18
  240. package/dist/prompts/searchable-multi-select.js +1 -1
  241. package/dist/telemetry/caller-detection.d.ts +27 -0
  242. package/dist/telemetry/caller-detection.js +85 -0
  243. package/dist/telemetry/caller-detection.test.d.ts +5 -0
  244. package/dist/telemetry/config.d.ts +48 -2
  245. package/dist/telemetry/config.js +113 -11
  246. package/dist/telemetry/config.test.d.ts +5 -0
  247. package/dist/telemetry/deduplication.d.ts +35 -0
  248. package/dist/telemetry/deduplication.js +82 -0
  249. package/dist/telemetry/deduplication.test.d.ts +5 -0
  250. package/dist/telemetry/identity.d.ts +62 -22
  251. package/dist/telemetry/identity.js +147 -102
  252. package/dist/telemetry/index.d.ts +126 -15
  253. package/dist/telemetry/index.js +569 -37
  254. package/dist/telemetry/sanitization.d.ts +40 -0
  255. package/dist/telemetry/sanitization.js +114 -0
  256. package/dist/telemetry/sanitization.test.d.ts +5 -0
  257. package/dist/telemetry/throttling.d.ts +20 -0
  258. package/dist/telemetry/throttling.js +70 -0
  259. package/dist/ui/welcome-screen.d.ts +9 -1
  260. package/dist/ui/welcome-screen.js +91 -38
  261. package/dist/utils/change-metadata.d.ts +43 -0
  262. package/dist/utils/change-metadata.js +123 -7
  263. package/dist/utils/change-utils.d.ts +10 -4
  264. package/dist/utils/change-utils.js +20 -13
  265. package/dist/utils/ci.d.ts +12 -0
  266. package/dist/utils/ci.js +16 -0
  267. package/dist/utils/command-references.d.ts +92 -6
  268. package/dist/utils/command-references.js +180 -7
  269. package/dist/utils/date.d.ts +8 -0
  270. package/dist/utils/date.js +13 -0
  271. package/dist/utils/file-system.d.ts +10 -0
  272. package/dist/utils/file-system.js +70 -0
  273. package/dist/utils/index.d.ts +1 -1
  274. package/dist/utils/index.js +1 -1
  275. package/dist/utils/interactive.d.ts +23 -0
  276. package/dist/utils/interactive.js +32 -0
  277. package/dist/utils/item-discovery.d.ts +19 -0
  278. package/dist/utils/item-discovery.js +30 -48
  279. package/dist/utils/shell-detection.d.ts +1 -1
  280. package/dist/utils/shell-detection.js +64 -11
  281. package/dist/utils/spec-discovery.d.ts +40 -0
  282. package/dist/utils/spec-discovery.js +115 -0
  283. package/dist/utils/task-progress.d.ts +29 -1
  284. package/dist/utils/task-progress.js +109 -15
  285. package/package.json +96 -84
  286. package/schemas/spec-driven/schema.yaml +252 -184
  287. package/schemas/spec-driven/templates/design.md +19 -19
  288. package/schemas/spec-driven/templates/plan-template.md +91 -0
  289. package/schemas/spec-driven/templates/proposal.md +29 -24
  290. package/schemas/spec-driven/templates/spec.md +11 -8
  291. package/schemas/spec-driven/templates/tasks.md +9 -9
  292. package/scripts/postinstall.js +83 -83
  293. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  294. package/dist/core/command-generation/adapters/codex.js +0 -39
  295. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  296. package/dist/core/command-generation/adapters/windsurf.js +0 -38
  297. package/dist/core/comprehension/config.d.ts +0 -20
  298. package/dist/core/comprehension/config.js +0 -23
  299. package/dist/core/comprehension/fingerprint.d.ts +0 -14
  300. package/dist/core/comprehension/fingerprint.js +0 -33
  301. package/dist/core/comprehension/index.d.ts +0 -57
  302. package/dist/core/comprehension/index.js +0 -87
  303. package/dist/core/comprehension/pass-record.d.ts +0 -29
  304. package/dist/core/comprehension/pass-record.js +0 -64
  305. package/dist/core/comprehension/stats.d.ts +0 -35
  306. package/dist/core/comprehension/stats.js +0 -133
  307. package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
  308. package/dist/core/templates/workflows/comprehension-guidance.js +0 -73
  309. package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
  310. package/dist/core/templates/workflows/mcp-guidance.js +0 -131
  311. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
  312. package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
  313. package/dist/telemetry/caller.d.ts +0 -5
  314. package/dist/telemetry/caller.js +0 -29
  315. package/dist/telemetry/client.d.ts +0 -27
  316. package/dist/telemetry/client.js +0 -127
  317. package/dist/telemetry/command-context.d.ts +0 -13
  318. package/dist/telemetry/command-context.js +0 -59
  319. package/dist/telemetry/comprehension.d.ts +0 -44
  320. package/dist/telemetry/comprehension.js +0 -105
  321. package/dist/telemetry/content.d.ts +0 -10
  322. package/dist/telemetry/content.js +0 -56
  323. package/dist/telemetry/git-stats.d.ts +0 -12
  324. package/dist/telemetry/git-stats.js +0 -69
  325. package/dist/telemetry/identify-cache.d.ts +0 -7
  326. package/dist/telemetry/identify-cache.js +0 -47
  327. package/dist/telemetry/input.d.ts +0 -17
  328. package/dist/telemetry/input.js +0 -68
  329. package/dist/telemetry/marker.d.ts +0 -37
  330. package/dist/telemetry/marker.js +0 -67
  331. package/dist/telemetry/workflow.d.ts +0 -85
  332. package/dist/telemetry/workflow.js +0 -318
  333. package/schemas/spec-driven/templates/plan.md +0 -19
@@ -0,0 +1,114 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Tracks modification history in the change marker (.openspec.yaml).
5
+ */
6
+ export class HistoryTracker {
7
+ changeRoot;
8
+ constructor(changeRoot) {
9
+ this.changeRoot = changeRoot;
10
+ }
11
+ /**
12
+ * Records a modification event in the change marker.
13
+ */
14
+ async recordModification(changeName, modifiedArtifacts, intent) {
15
+ const markerPath = join(this.changeRoot, changeName, '.openspec.yaml');
16
+ try {
17
+ const content = await readFile(markerPath, 'utf-8');
18
+ const marker = this.parseMarker(content);
19
+ // Add new modification record
20
+ marker.modifyHistory.push({
21
+ timestamp: new Date().toISOString(),
22
+ modifiedArtifacts,
23
+ intent,
24
+ });
25
+ // Write back
26
+ await writeFile(markerPath, this.stringifyMarker(marker), 'utf-8');
27
+ }
28
+ catch (error) {
29
+ // If marker doesn't exist, create it
30
+ if (error.code === 'ENOENT') {
31
+ const marker = {
32
+ modifyHistory: [{
33
+ timestamp: new Date().toISOString(),
34
+ modifiedArtifacts,
35
+ intent,
36
+ }],
37
+ };
38
+ await writeFile(markerPath, this.stringifyMarker(marker), 'utf-8');
39
+ }
40
+ else {
41
+ throw error;
42
+ }
43
+ }
44
+ }
45
+ /**
46
+ * Gets the modification history for a change.
47
+ */
48
+ async getHistory(changeName) {
49
+ const markerPath = join(this.changeRoot, changeName, '.openspec.yaml');
50
+ try {
51
+ const content = await readFile(markerPath, 'utf-8');
52
+ const marker = this.parseMarker(content);
53
+ return marker.modifyHistory || [];
54
+ }
55
+ catch (error) {
56
+ // If marker doesn't exist, return empty history
57
+ if (error.code === 'ENOENT') {
58
+ return [];
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ /**
64
+ * Parses the change marker YAML content.
65
+ * In a full implementation, this would use a proper YAML parser.
66
+ */
67
+ parseMarker(content) {
68
+ // Simplified parsing - in production, use a YAML library
69
+ const lines = content.split('\n');
70
+ const marker = { modifyHistory: [] };
71
+ let inHistory = false;
72
+ let currentRecord = {};
73
+ for (const line of lines) {
74
+ if (line.trim() === 'modifyHistory:') {
75
+ inHistory = true;
76
+ continue;
77
+ }
78
+ if (inHistory) {
79
+ if (line.trim().startsWith('- timestamp:')) {
80
+ if (currentRecord.timestamp && currentRecord.modifiedArtifacts && currentRecord.intent) {
81
+ marker.modifyHistory.push(currentRecord);
82
+ }
83
+ currentRecord = { timestamp: line.split(':')[1].trim() };
84
+ }
85
+ else if (line.trim().startsWith('modifiedArtifacts:')) {
86
+ currentRecord.modifiedArtifacts = line.split(':')[1].trim().split(',').map(s => s.trim());
87
+ }
88
+ else if (line.trim().startsWith('intent:')) {
89
+ currentRecord.intent = line.split(':')[1].trim();
90
+ }
91
+ }
92
+ }
93
+ // Add the last record
94
+ if (currentRecord.timestamp && currentRecord.modifiedArtifacts && currentRecord.intent) {
95
+ marker.modifyHistory.push(currentRecord);
96
+ }
97
+ return marker;
98
+ }
99
+ /**
100
+ * Stringifies the change marker to YAML format.
101
+ * In a full implementation, this would use a proper YAML stringifier.
102
+ */
103
+ stringifyMarker(marker) {
104
+ // Simplified YAML generation - in production, use a YAML library
105
+ let yaml = 'modifyHistory:\n';
106
+ for (const record of marker.modifyHistory) {
107
+ yaml += ` - timestamp: ${record.timestamp}\n`;
108
+ yaml += ` modifiedArtifacts: [${record.modifiedArtifacts.join(', ')}]\n`;
109
+ yaml += ` intent: ${record.intent}\n`;
110
+ }
111
+ return yaml;
112
+ }
113
+ }
114
+ //# sourceMappingURL=history-tracker.js.map
@@ -0,0 +1,7 @@
1
+ export { DependencyPropagator } from './dependency-propagator.js';
2
+ export type { DependencyPropagationResult } from './dependency-propagator.js';
3
+ export { ConflictDetector } from './conflict-detector.js';
4
+ export type { ConflictInfo } from './conflict-detector.js';
5
+ export { HistoryTracker } from './history-tracker.js';
6
+ export type { ModificationRecord, ChangeMarker } from './history-tracker.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ // Dependency Propagator
2
+ export { DependencyPropagator } from './dependency-propagator.js';
3
+ // Conflict Detector
4
+ export { ConflictDetector } from './conflict-detector.js';
5
+ // History Tracker
6
+ export { HistoryTracker } from './history-tracker.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Onboarding command hints.
3
+ *
4
+ * The commands shown to a user after setup must be limited to the workflows
5
+ * their profile actually installs, otherwise we advertise slash commands that
6
+ * were correctly never generated.
7
+ *
8
+ * This module decides WHICH hints to show. How each one is spelled for a given
9
+ * tool — command, skill, or a tool-specific skill prefix — is decided by
10
+ * src/utils/command-references.ts at the call site.
11
+ */
12
+ import type { WorkflowId } from './profiles.js';
13
+ export type OnboardingCommand = {
14
+ workflow: WorkflowId;
15
+ command: string;
16
+ description: string;
17
+ };
18
+ /**
19
+ * Longest description the welcome screen can render. It shows these beside a
20
+ * 24-column art column and only animates at MIN_WIDTH (60) columns or wider; a
21
+ * longer line wraps, and the animation's cursor-up count assumes unwrapped
22
+ * lines. See src/ui/welcome-screen.ts.
23
+ */
24
+ export declare const DESCRIPTION_BUDGET = 17;
25
+ /**
26
+ * Returns the onboarding hints for the installed workflows, in lifecycle order.
27
+ * Returns an empty array when none of the onboarding workflows are installed.
28
+ */
29
+ export declare function getOnboardingCommands(workflows: readonly string[]): OnboardingCommand[];
30
+ //# sourceMappingURL=onboarding-commands.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Onboarding command hints.
3
+ *
4
+ * The commands shown to a user after setup must be limited to the workflows
5
+ * their profile actually installs, otherwise we advertise slash commands that
6
+ * were correctly never generated.
7
+ *
8
+ * This module decides WHICH hints to show. How each one is spelled for a given
9
+ * tool — command, skill, or a tool-specific skill prefix — is decided by
10
+ * src/utils/command-references.ts at the call site.
11
+ */
12
+ /**
13
+ * Longest description the welcome screen can render. It shows these beside a
14
+ * 24-column art column and only animates at MIN_WIDTH (60) columns or wider; a
15
+ * longer line wraps, and the animation's cursor-up count assumes unwrapped
16
+ * lines. See src/ui/welcome-screen.ts.
17
+ */
18
+ export const DESCRIPTION_BUDGET = 17;
19
+ /**
20
+ * Ordered onboarding hints. Each entry is shown only when its workflow is
21
+ * installed, so the list follows the change lifecycle: start, then build,
22
+ * then implement.
23
+ */
24
+ const ONBOARDING_COMMANDS = [
25
+ { workflow: 'propose', command: '/opsx:propose', description: 'Start a change' },
26
+ { workflow: 'new', command: '/opsx:new', description: 'Scaffold a change' },
27
+ { workflow: 'continue', command: '/opsx:continue', description: 'Next artifact' },
28
+ { workflow: 'apply', command: '/opsx:apply', description: 'Implement tasks' },
29
+ ];
30
+ /**
31
+ * Returns the onboarding hints for the installed workflows, in lifecycle order.
32
+ * Returns an empty array when none of the onboarding workflows are installed.
33
+ */
34
+ export function getOnboardingCommands(workflows) {
35
+ const installed = new Set(workflows);
36
+ return ONBOARDING_COMMANDS.filter((entry) => installed.has(entry.workflow));
37
+ }
38
+ //# sourceMappingURL=onboarding-commands.js.map
@@ -11,8 +11,8 @@ export const OPENSPEC_CHANGES_DIR = 'openspec/changes';
11
11
  export const OPENSPEC_ARCHIVE_DIR = 'openspec/changes/archive';
12
12
  export const DEFAULT_OPENSPEC_SCHEMA = 'spec-driven';
13
13
  export const DIRECTORY_ANCHOR_FILE_NAME = '.gitkeep';
14
- // Git cannot track empty directories, so clones of a fresh store would lose
15
- // these and fail root-health checks. Anchored at setup time.
14
+ // Git cannot track empty directories, so setup anchors otherwise-empty
15
+ // conventional store directories for teammates who clone the repo later.
16
16
  export const ANCHORED_OPENSPEC_DIRS = [OPENSPEC_SPECS_DIR, OPENSPEC_ARCHIVE_DIR];
17
17
  async function pathKind(targetPath) {
18
18
  try {
@@ -51,6 +51,14 @@ function unresolvedInspection() {
51
51
  function missingDirectoryDiagnostic(code, message, target) {
52
52
  return makeStoreDiagnostic('error', code, message, { target });
53
53
  }
54
+ async function inspectOptionalPlanningDirectory(inspection, storeRoot, key, relativePath, notDirectoryCode, target) {
55
+ const kind = await pathKind(path.join(storeRoot, relativePath));
56
+ inspection[key] = { present: kind === 'directory' };
57
+ if (kind === 'directory' || kind === 'missing')
58
+ return kind;
59
+ inspection.diagnostics.push(missingDirectoryDiagnostic(notDirectoryCode, `${relativePath}/ exists but is not a directory.`, target));
60
+ return kind;
61
+ }
54
62
  export async function inspectOpenSpecRoot(storeRoot) {
55
63
  const rootKind = await pathKind(storeRoot);
56
64
  const inspection = unresolvedInspection();
@@ -90,23 +98,18 @@ export async function inspectOpenSpecRoot(storeRoot) {
90
98
  inspection.diagnostics.push(missingDirectoryDiagnostic('openspec_config_missing', 'Missing openspec/config.yaml or openspec/config.yml.', 'openspec.config'));
91
99
  }
92
100
  }
93
- for (const [key, relativePath, code, message, target] of [
94
- ['specs', OPENSPEC_SPECS_DIR, 'openspec_specs_missing', 'Missing openspec/specs/.', 'openspec.specs'],
95
- ['changes', OPENSPEC_CHANGES_DIR, 'openspec_changes_missing', 'Missing openspec/changes/.', 'openspec.changes'],
96
- ['archive', OPENSPEC_ARCHIVE_DIR, 'openspec_archive_missing', 'Missing openspec/changes/archive/.', 'openspec.archive'],
97
- ]) {
98
- const kind = await pathKind(path.join(storeRoot, relativePath));
99
- inspection[key] = { present: kind === 'directory' };
100
- if (kind === 'directory')
101
- continue;
102
- inspection.diagnostics.push(missingDirectoryDiagnostic(kind === 'missing' ? code : code.replace('_missing', '_not_directory'), kind === 'missing' ? message : `${relativePath}/ exists but is not a directory.`, target));
101
+ await inspectOptionalPlanningDirectory(inspection, storeRoot, 'specs', OPENSPEC_SPECS_DIR, 'openspec_specs_not_directory', 'openspec.specs');
102
+ const changesKind = await inspectOptionalPlanningDirectory(inspection, storeRoot, 'changes', OPENSPEC_CHANGES_DIR, 'openspec_changes_not_directory', 'openspec.changes');
103
+ if (changesKind === 'directory') {
104
+ await inspectOptionalPlanningDirectory(inspection, storeRoot, 'archive', OPENSPEC_ARCHIVE_DIR, 'openspec_archive_not_directory', 'openspec.archive');
105
+ }
106
+ else {
107
+ inspection.archive = { present: false };
103
108
  }
104
109
  inspection.healthy =
105
110
  inspection.present === true &&
106
111
  inspection.config.present === true &&
107
- inspection.specs.present === true &&
108
- inspection.changes.present === true &&
109
- inspection.archive.present === true;
112
+ inspection.diagnostics.length === 0;
110
113
  return inspection;
111
114
  }
112
115
  async function ensureDirectory(storeRoot, relativePath, ledger) {
@@ -1,10 +1,25 @@
1
- import { MarkdownParser } from './markdown-parser.js';
2
- import { Change } from '../schemas/index.js';
1
+ import { MarkdownParser, Section } from './markdown-parser.js';
2
+ import { Change, Requirement } from '../schemas/index.js';
3
3
  export declare class ChangeParser extends MarkdownParser {
4
4
  private changeDir;
5
5
  constructor(content: string, changeDir: string);
6
6
  parseChangeWithDeltas(name: string): Promise<Change>;
7
7
  private parseDeltaSpecs;
8
+ /**
9
+ * Read requirements from a delta section, ignoring headers that are not
10
+ * `### Requirement: <name>`.
11
+ *
12
+ * A delta section often carries divider headers such as
13
+ * `### Documentation Requirements`. The base parser treats every child header
14
+ * as a requirement, which invented a scenario-less requirement that does not
15
+ * exist (#498): archive warned about a missing scenario, and `show --json`
16
+ * reported an extra delta. The delta reader already skips these headers and
17
+ * notes them, so this keeps the two readers in agreement.
18
+ *
19
+ * Overriding here rather than in MarkdownParser keeps main spec parsing —
20
+ * `view`, `list`, `spec --json`, spec validation — untouched.
21
+ */
22
+ protected parseRequirements(section: Section): Requirement[];
8
23
  private parseSpecDeltas;
9
24
  private parseRenames;
10
25
  private parseSectionsFromContent;
@@ -1,6 +1,8 @@
1
1
  import { MarkdownParser } from './markdown-parser.js';
2
+ import { buildCodeFenceMask } from './requirement-text.js';
2
3
  import path from 'path';
3
4
  import { promises as fs } from 'fs';
5
+ import { discoverSpecFiles } from '../../utils/spec-discovery.js';
4
6
  export class ChangeParser extends MarkdownParser {
5
7
  changeDir;
6
8
  constructor(content, changeDir) {
@@ -37,30 +39,42 @@ export class ChangeParser extends MarkdownParser {
37
39
  }
38
40
  async parseDeltaSpecs(specsDir) {
39
41
  const deltas = [];
40
- try {
41
- const specDirs = await fs.readdir(specsDir, { withFileTypes: true });
42
- for (const dir of specDirs) {
43
- if (!dir.isDirectory())
44
- continue;
45
- const specName = dir.name;
46
- const specFile = path.join(specsDir, specName, 'spec.md');
47
- try {
48
- const content = await fs.readFile(specFile, 'utf-8');
49
- const specDeltas = this.parseSpecDeltas(specName, content);
50
- deltas.push(...specDeltas);
51
- }
52
- catch (error) {
53
- // Spec file might not exist, which is okay
54
- continue;
55
- }
42
+ // Discover delta specs recursively so nested layouts like
43
+ // specs/<area>/<capability>/spec.md are parsed too (#1353)
44
+ const specFiles = await discoverSpecFiles(specsDir);
45
+ for (const { id, specFile } of specFiles) {
46
+ try {
47
+ const content = await fs.readFile(specFile, 'utf-8');
48
+ const specDeltas = this.parseSpecDeltas(id, content);
49
+ deltas.push(...specDeltas);
50
+ }
51
+ catch (error) {
52
+ // Spec file might not be readable, which is okay
53
+ continue;
56
54
  }
57
- }
58
- catch (error) {
59
- // Specs directory might not exist, which is okay
60
- return [];
61
55
  }
62
56
  return deltas;
63
57
  }
58
+ /**
59
+ * Read requirements from a delta section, ignoring headers that are not
60
+ * `### Requirement: <name>`.
61
+ *
62
+ * A delta section often carries divider headers such as
63
+ * `### Documentation Requirements`. The base parser treats every child header
64
+ * as a requirement, which invented a scenario-less requirement that does not
65
+ * exist (#498): archive warned about a missing scenario, and `show --json`
66
+ * reported an extra delta. The delta reader already skips these headers and
67
+ * notes them, so this keeps the two readers in agreement.
68
+ *
69
+ * Overriding here rather than in MarkdownParser keeps main spec parsing —
70
+ * `view`, `list`, `spec --json`, spec validation — untouched.
71
+ */
72
+ parseRequirements(section) {
73
+ return super.parseRequirements({
74
+ ...section,
75
+ children: section.children.filter((child) => /^Requirement:\s*\S/i.test(child.title.trim())),
76
+ });
77
+ }
64
78
  parseSpecDeltas(specName, content) {
65
79
  const deltas = [];
66
80
  const sections = this.parseSectionsFromContent(content);
@@ -148,7 +162,7 @@ export class ChangeParser extends MarkdownParser {
148
162
  parseSectionsFromContent(content) {
149
163
  const normalizedContent = ChangeParser.normalizeContent(content);
150
164
  const lines = normalizedContent.split('\n');
151
- const codeFenceLineMask = ChangeParser.buildCodeFenceMask(lines);
165
+ const codeFenceLineMask = buildCodeFenceMask(lines);
152
166
  const sections = [];
153
167
  const stack = [];
154
168
  for (let i = 0; i < lines.length; i++) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared fenced-code-block detection for the Markdown parsers.
3
+ *
4
+ * Several parsers need to ignore Markdown structure (headers, requirement
5
+ * blocks, scenarios, delta sections) that appears inside fenced code blocks.
6
+ * Keeping this logic in one place avoids the drift that previously left
7
+ * `requirement-blocks.ts` treating fenced `### Requirement:` lines as real
8
+ * requirements during validation and archiving.
9
+ */
10
+ /**
11
+ * Builds a per-line mask where `true` marks a line that is part of a fenced
12
+ * code block (including the opening and closing fence lines themselves).
13
+ */
14
+ export declare function buildCodeFenceMask(lines: string[]): boolean[];
15
+ //# sourceMappingURL=code-fence.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared fenced-code-block detection for the Markdown parsers.
3
+ *
4
+ * Several parsers need to ignore Markdown structure (headers, requirement
5
+ * blocks, scenarios, delta sections) that appears inside fenced code blocks.
6
+ * Keeping this logic in one place avoids the drift that previously left
7
+ * `requirement-blocks.ts` treating fenced `### Requirement:` lines as real
8
+ * requirements during validation and archiving.
9
+ */
10
+ function getFenceMarker(line) {
11
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
12
+ if (!fenceMatch) {
13
+ return null;
14
+ }
15
+ return {
16
+ marker: fenceMatch[1][0],
17
+ length: fenceMatch[1].length,
18
+ };
19
+ }
20
+ function isClosingFence(line, activeFence) {
21
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
22
+ return Boolean(fenceMatch &&
23
+ fenceMatch[1][0] === activeFence.marker &&
24
+ fenceMatch[1].length >= activeFence.length);
25
+ }
26
+ /**
27
+ * Builds a per-line mask where `true` marks a line that is part of a fenced
28
+ * code block (including the opening and closing fence lines themselves).
29
+ */
30
+ export function buildCodeFenceMask(lines) {
31
+ const mask = new Array(lines.length).fill(false);
32
+ let activeFence = null;
33
+ for (let i = 0; i < lines.length; i++) {
34
+ if (!activeFence) {
35
+ const fence = getFenceMarker(lines[i]);
36
+ if (fence) {
37
+ activeFence = fence;
38
+ mask[i] = true;
39
+ }
40
+ continue;
41
+ }
42
+ mask[i] = true;
43
+ if (isClosingFence(lines[i], activeFence)) {
44
+ activeFence = null;
45
+ }
46
+ }
47
+ return mask;
48
+ }
49
+ //# sourceMappingURL=code-fence.js.map
@@ -11,9 +11,6 @@ export declare class MarkdownParser {
11
11
  private currentLine;
12
12
  constructor(content: string);
13
13
  protected static normalizeContent(content: string): string;
14
- protected static buildCodeFenceMask(lines: string[]): boolean[];
15
- private static getFenceMarker;
16
- private static isClosingFence;
17
14
  parseSpec(name: string): Spec;
18
15
  parseChange(name: string): Change;
19
16
  protected parseSections(): Section[];
@@ -1,3 +1,4 @@
1
+ import { buildCodeFenceMask, extractRequirementText } from './requirement-text.js';
1
2
  export class MarkdownParser {
2
3
  lines;
3
4
  codeFenceLineMask;
@@ -5,46 +6,12 @@ export class MarkdownParser {
5
6
  constructor(content) {
6
7
  const normalized = MarkdownParser.normalizeContent(content);
7
8
  this.lines = normalized.split('\n');
8
- this.codeFenceLineMask = MarkdownParser.buildCodeFenceMask(this.lines);
9
+ this.codeFenceLineMask = buildCodeFenceMask(this.lines);
9
10
  this.currentLine = 0;
10
11
  }
11
12
  static normalizeContent(content) {
12
- return content.replace(/\r\n?/g, '\n');
13
- }
14
- static buildCodeFenceMask(lines) {
15
- const mask = new Array(lines.length).fill(false);
16
- let activeFence = null;
17
- for (let i = 0; i < lines.length; i++) {
18
- const fence = MarkdownParser.getFenceMarker(lines[i]);
19
- if (!activeFence) {
20
- if (fence) {
21
- activeFence = fence;
22
- mask[i] = true;
23
- }
24
- continue;
25
- }
26
- mask[i] = true;
27
- if (MarkdownParser.isClosingFence(lines[i], activeFence)) {
28
- activeFence = null;
29
- }
30
- }
31
- return mask;
32
- }
33
- static getFenceMarker(line) {
34
- const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
35
- if (!fenceMatch) {
36
- return null;
37
- }
38
- return {
39
- marker: fenceMatch[1][0],
40
- length: fenceMatch[1].length,
41
- };
42
- }
43
- static isClosingFence(line, activeFence) {
44
- const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
45
- return Boolean(fenceMatch &&
46
- fenceMatch[1][0] === activeFence.marker &&
47
- fenceMatch[1].length >= activeFence.length);
13
+ // Strip a UTF-8 BOM so a header on the first line still matches.
14
+ return content.replace(/^/, '').replace(/\r\n?/g, '\n');
48
15
  }
49
16
  parseSpec(name) {
50
17
  const sections = this.parseSections();
@@ -149,29 +116,9 @@ export class MarkdownParser {
149
116
  parseRequirements(section) {
150
117
  const requirements = [];
151
118
  for (const child of section.children) {
152
- // Extract requirement text from first non-empty content line, fall back to heading
153
- let text = child.title;
154
- // Get content before any child sections (scenarios)
155
- if (child.content.trim()) {
156
- // Split content into lines and find content before any child headers
157
- const lines = child.content.split('\n');
158
- const contentBeforeChildren = [];
159
- for (const line of lines) {
160
- // Stop at child headers (scenarios start with ####)
161
- if (line.trim().startsWith('#')) {
162
- break;
163
- }
164
- contentBeforeChildren.push(line);
165
- }
166
- // Find first non-empty line
167
- const directContent = contentBeforeChildren.join('\n').trim();
168
- if (directContent) {
169
- const firstLine = directContent.split('\n').find(l => l.trim());
170
- if (firstLine) {
171
- text = firstLine.trim();
172
- }
173
- }
174
- }
119
+ // Read the requirement text via the shared reader (multi-line, fence- and
120
+ // metadata-aware, with the shared header-title fallback for empty bodies).
121
+ const text = extractRequirementText(child.title, child.content.split('\n'));
175
122
  const scenarios = this.parseScenarios(child);
176
123
  requirements.push({
177
124
  text,
@@ -11,10 +11,28 @@ export interface RequirementsSectionParts {
11
11
  after: string;
12
12
  }
13
13
  export declare function normalizeRequirementName(name: string): string;
14
+ /**
15
+ * Case- and whitespace-insensitive fold of a requirement name. Requirement
16
+ * matching itself is case-sensitive (normalizeRequirementName); this fold
17
+ * exists only for typo detection - near-miss REMOVED headers and the
18
+ * RENAMED+REMOVED cross-section conflict - where two spellings that differ
19
+ * only in case or interior whitespace mean a mistake, never two requirements.
20
+ */
21
+ export declare function foldRequirementName(name: string): string;
14
22
  /**
15
23
  * Extracts the Requirements section from a spec file and parses requirement blocks.
16
24
  */
17
25
  export declare function extractRequirementsSection(content: string): RequirementsSectionParts;
26
+ /**
27
+ * A level-3 header inside `## ADDED`/`## MODIFIED Requirements` that is not a
28
+ * canonical `### Requirement:` header, recorded at the moment the delta reader
29
+ * skips over it. Surfaced as an INFO note by `validate <change>` (#498).
30
+ */
31
+ export interface SkippedHeader {
32
+ header: string;
33
+ section: string;
34
+ line: number;
35
+ }
18
36
  export interface DeltaPlan {
19
37
  added: RequirementBlock[];
20
38
  modified: RequirementBlock[];
@@ -23,6 +41,7 @@ export interface DeltaPlan {
23
41
  from: string;
24
42
  to: string;
25
43
  }>;
44
+ skippedHeaders: SkippedHeader[];
26
45
  sectionPresence: {
27
46
  added: boolean;
28
47
  modified: boolean;
@@ -34,4 +53,14 @@ export interface DeltaPlan {
34
53
  * Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
35
54
  */
36
55
  export declare function parseDeltaSpec(content: string): DeltaPlan;
56
+ /**
57
+ * Scenario names the current requirement block has and the incoming
58
+ * (MODIFIED) block does not. A MODIFIED requirement replaces the whole block,
59
+ * so every name reported here would be dropped from the main spec.
60
+ *
61
+ * Shared by archive (which refuses to apply the block) and validate (which
62
+ * reports the same loss at authoring time, #1477), so the two cannot disagree
63
+ * about what counts as a dropped scenario.
64
+ */
65
+ export declare function findMissingCurrentScenarios(current: RequirementBlock, incoming: RequirementBlock): string[];
37
66
  //# sourceMappingURL=requirement-blocks.d.ts.map