@codewalla_india/openspec 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +214 -133
  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 +35 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +69 -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 +98 -0
  41. package/dist/comprehension-quiz/providers/interface.js +150 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +35 -0
  43. package/dist/comprehension-quiz/providers/local.js +69 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +35 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +69 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +35 -0
  47. package/dist/comprehension-quiz/providers/openai.js +72 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +117 -0
  49. package/dist/comprehension-quiz/question-generator.js +203 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +75 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +181 -0
  52. package/dist/comprehension-quiz/types.d.ts +137 -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 +193 -71
  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
@@ -1,6 +1,18 @@
1
+ import { buildCodeFenceMask } from './requirement-text.js';
1
2
  export function normalizeRequirementName(name) {
2
3
  return name.trim();
3
4
  }
5
+ /**
6
+ * Case- and whitespace-insensitive fold of a requirement name. Requirement
7
+ * matching itself is case-sensitive (normalizeRequirementName); this fold
8
+ * exists only for typo detection - near-miss REMOVED headers and the
9
+ * RENAMED+REMOVED cross-section conflict - where two spellings that differ
10
+ * only in case or interior whitespace mean a mistake, never two requirements.
11
+ */
12
+ export function foldRequirementName(name) {
13
+ return normalizeRequirementName(name).toLowerCase().replace(/\s+/g, ' ');
14
+ }
15
+ /** The canonical requirement header the delta reader recognizes. */
4
16
  const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;
5
17
  /**
6
18
  * Extracts the Requirements section from a spec file and parses requirement blocks.
@@ -8,7 +20,8 @@ const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;
8
20
  export function extractRequirementsSection(content) {
9
21
  const normalized = normalizeLineEndings(content);
10
22
  const lines = normalized.split('\n');
11
- const reqHeaderIndex = lines.findIndex(l => /^##\s+Requirements\s*$/i.test(l));
23
+ const fenceMask = buildCodeFenceMask(lines);
24
+ const reqHeaderIndex = lines.findIndex((l, i) => !fenceMask[i] && /^##\s+Requirements\s*$/i.test(l));
12
25
  if (reqHeaderIndex === -1) {
13
26
  // No requirements section; create an empty one at the end
14
27
  const before = content.trimEnd();
@@ -24,7 +37,7 @@ export function extractRequirementsSection(content) {
24
37
  // Find end of this section: next line that starts with '## ' at same or higher level
25
38
  let endIndex = lines.length;
26
39
  for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
27
- if (/^##\s+/.test(lines[i])) {
40
+ if (!fenceMask[i] && /^##\s+/.test(lines[i])) {
28
41
  endIndex = i;
29
42
  break;
30
43
  }
@@ -32,29 +45,31 @@ export function extractRequirementsSection(content) {
32
45
  const before = lines.slice(0, reqHeaderIndex).join('\n');
33
46
  const headerLine = lines[reqHeaderIndex];
34
47
  const sectionBodyLines = lines.slice(reqHeaderIndex + 1, endIndex);
48
+ const sectionBodyMask = fenceMask.slice(reqHeaderIndex + 1, endIndex);
49
+ const isRequirementHeader = (cursor) => !sectionBodyMask[cursor] && REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor]);
50
+ const isTopLevelHeader = (cursor) => !sectionBodyMask[cursor] && /^##\s+/.test(sectionBodyLines[cursor]);
35
51
  // Parse requirement blocks within section body
36
52
  const blocks = [];
37
53
  let cursor = 0;
38
54
  let preambleLines = [];
39
55
  // Collect preamble lines until first requirement header
40
- while (cursor < sectionBodyLines.length && !REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor])) {
56
+ while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor)) {
41
57
  preambleLines.push(sectionBodyLines[cursor]);
42
58
  cursor++;
43
59
  }
44
60
  while (cursor < sectionBodyLines.length) {
45
- const headerStart = cursor;
46
61
  const headerLineCandidate = sectionBodyLines[cursor];
47
- const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX);
48
- if (!headerMatch) {
62
+ if (!isRequirementHeader(cursor)) {
49
63
  // Not a requirement header; skip line defensively
50
64
  cursor++;
51
65
  continue;
52
66
  }
67
+ const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX);
53
68
  const name = normalizeRequirementName(headerMatch[1]);
54
69
  cursor++;
55
70
  // Gather lines until next requirement header or end of section
56
71
  const bodyLines = [headerLineCandidate];
57
- while (cursor < sectionBodyLines.length && !REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor]) && !/^##\s+/.test(sectionBodyLines[cursor])) {
72
+ while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor) && !isTopLevelHeader(cursor)) {
58
73
  bodyLines.push(sectionBodyLines[cursor]);
59
74
  cursor++;
60
75
  }
@@ -72,27 +87,42 @@ export function extractRequirementsSection(content) {
72
87
  };
73
88
  }
74
89
  function normalizeLineEndings(content) {
75
- return content.replace(/\r\n?/g, '\n');
90
+ // Strip a UTF-8 BOM: Windows editors and PowerShell redirects prepend one,
91
+ // and it would keep the first line's `## ADDED Requirements` from matching.
92
+ return content.replace(/^/, '').replace(/\r\n?/g, '\n');
76
93
  }
77
94
  /**
78
95
  * Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
79
96
  */
80
97
  export function parseDeltaSpec(content) {
81
98
  const normalized = normalizeLineEndings(content);
82
- const sections = splitTopLevelSections(normalized);
99
+ const lines = normalized.split('\n');
100
+ const fenceMask = buildCodeFenceMask(lines);
101
+ const sections = splitTopLevelSections(lines, fenceMask);
83
102
  const addedLookup = getSectionCaseInsensitive(sections, 'ADDED Requirements');
84
103
  const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
85
104
  const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
86
105
  const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
87
- const added = parseRequirementBlocksFromSection(addedLookup.body);
88
- const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
106
+ const skippedHeaders = [];
107
+ const added = parseRequirementBlocksFromSection(addedLookup.body, {
108
+ section: addedLookup.title,
109
+ bodyStartLine: addedLookup.bodyStartLine,
110
+ sink: skippedHeaders,
111
+ });
112
+ const modified = parseRequirementBlocksFromSection(modifiedLookup.body, {
113
+ section: modifiedLookup.title,
114
+ bodyStartLine: modifiedLookup.bodyStartLine,
115
+ sink: skippedHeaders,
116
+ });
89
117
  const removedNames = parseRemovedNames(removedLookup.body);
90
118
  const renamedPairs = parseRenamedPairs(renamedLookup.body);
119
+ skippedHeaders.sort((a, b) => a.line - b.line);
91
120
  return {
92
121
  added,
93
122
  modified,
94
123
  removed: removedNames,
95
124
  renamed: renamedPairs,
125
+ skippedHeaders,
96
126
  sectionPresence: {
97
127
  added: addedLookup.found,
98
128
  modified: modifiedLookup.found,
@@ -101,43 +131,65 @@ export function parseDeltaSpec(content) {
101
131
  },
102
132
  };
103
133
  }
104
- function splitTopLevelSections(content) {
105
- const lines = content.split('\n');
134
+ function splitTopLevelSections(lines, fenceMask) {
106
135
  const result = {};
107
136
  const indices = [];
108
137
  for (let i = 0; i < lines.length; i++) {
138
+ if (fenceMask[i])
139
+ continue;
109
140
  const m = lines[i].match(/^(##)\s+(.+)$/);
110
141
  if (m) {
111
- const level = m[1].length; // only care for '##'
112
- indices.push({ title: m[2].trim(), index: i, level });
142
+ indices.push({ title: m[2].trim(), index: i });
113
143
  }
114
144
  }
115
145
  for (let i = 0; i < indices.length; i++) {
116
146
  const current = indices[i];
117
147
  const next = indices[i + 1];
118
- const body = lines.slice(current.index + 1, next ? next.index : lines.length).join('\n');
119
- result[current.title] = body;
148
+ const end = next ? next.index : lines.length;
149
+ result[current.title] = {
150
+ lines: lines.slice(current.index + 1, end),
151
+ fenceMask: fenceMask.slice(current.index + 1, end),
152
+ bodyStartLine: current.index + 2,
153
+ };
120
154
  }
121
155
  return result;
122
156
  }
157
+ const EMPTY_SECTION_BODY = { lines: [], fenceMask: [], bodyStartLine: 0 };
123
158
  function getSectionCaseInsensitive(sections, desired) {
124
159
  const target = desired.toLowerCase();
125
160
  for (const [title, body] of Object.entries(sections)) {
126
- if (title.toLowerCase() === target)
127
- return { body, found: true };
161
+ if (title.toLowerCase() === target) {
162
+ return { title, body, bodyStartLine: body.bodyStartLine, found: true };
163
+ }
128
164
  }
129
- return { body: '', found: false };
165
+ return { title: desired, body: EMPTY_SECTION_BODY, bodyStartLine: 0, found: false };
130
166
  }
131
- function parseRequirementBlocksFromSection(sectionBody) {
132
- if (!sectionBody)
167
+ function parseRequirementBlocksFromSection(sectionBody, skipped) {
168
+ const { lines, fenceMask } = sectionBody;
169
+ if (lines.length === 0)
133
170
  return [];
134
- const lines = normalizeLineEndings(sectionBody).split('\n');
171
+ const isRequirementHeader = (i) => !fenceMask[i] && REQUIREMENT_HEADER_REGEX.test(lines[i]);
172
+ const isTopLevelHeader = (i) => !fenceMask[i] && /^##\s+/.test(lines[i]);
173
+ const recordIfSkippedHeader = (index) => {
174
+ if (!skipped || fenceMask[index])
175
+ return;
176
+ const h3 = lines[index].match(/^###\s+(.+?)\s*$/);
177
+ if (h3 && !REQUIREMENT_HEADER_REGEX.test(lines[index])) {
178
+ skipped.sink.push({
179
+ header: h3[1].trim(),
180
+ section: skipped.section,
181
+ line: skipped.bodyStartLine + index,
182
+ });
183
+ }
184
+ };
135
185
  const blocks = [];
136
186
  let i = 0;
137
187
  while (i < lines.length) {
138
188
  // Seek next requirement header
139
- while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]))
189
+ while (i < lines.length && !isRequirementHeader(i)) {
190
+ recordIfSkippedHeader(i);
140
191
  i++;
192
+ }
141
193
  if (i >= lines.length)
142
194
  break;
143
195
  const headerLine = lines[i];
@@ -149,7 +201,8 @@ function parseRequirementBlocksFromSection(sectionBody) {
149
201
  const name = normalizeRequirementName(m[1]);
150
202
  const buf = [headerLine];
151
203
  i++;
152
- while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]) && !/^##\s+/.test(lines[i])) {
204
+ while (i < lines.length && !isRequirementHeader(i) && !isTopLevelHeader(i)) {
205
+ recordIfSkippedHeader(i);
153
206
  buf.push(lines[i]);
154
207
  i++;
155
208
  }
@@ -158,11 +211,14 @@ function parseRequirementBlocksFromSection(sectionBody) {
158
211
  return blocks;
159
212
  }
160
213
  function parseRemovedNames(sectionBody) {
161
- if (!sectionBody)
214
+ const { lines, fenceMask } = sectionBody;
215
+ if (lines.length === 0)
162
216
  return [];
163
217
  const names = [];
164
- const lines = normalizeLineEndings(sectionBody).split('\n');
165
- for (const line of lines) {
218
+ for (let i = 0; i < lines.length; i++) {
219
+ if (fenceMask[i])
220
+ continue;
221
+ const line = lines[i];
166
222
  const m = line.match(REQUIREMENT_HEADER_REGEX);
167
223
  if (m) {
168
224
  names.push(normalizeRequirementName(m[1]));
@@ -177,12 +233,15 @@ function parseRemovedNames(sectionBody) {
177
233
  return names;
178
234
  }
179
235
  function parseRenamedPairs(sectionBody) {
180
- if (!sectionBody)
236
+ const { lines, fenceMask } = sectionBody;
237
+ if (lines.length === 0)
181
238
  return [];
182
239
  const pairs = [];
183
- const lines = normalizeLineEndings(sectionBody).split('\n');
184
240
  let current = {};
185
- for (const line of lines) {
241
+ for (let i = 0; i < lines.length; i++) {
242
+ if (fenceMask[i])
243
+ continue;
244
+ const line = lines[i];
186
245
  const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
187
246
  const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
188
247
  if (fromMatch) {
@@ -198,4 +257,64 @@ function parseRenamedPairs(sectionBody) {
198
257
  }
199
258
  return pairs;
200
259
  }
260
+ /**
261
+ * Scenario names the current requirement block has and the incoming
262
+ * (MODIFIED) block does not. A MODIFIED requirement replaces the whole block,
263
+ * so every name reported here would be dropped from the main spec.
264
+ *
265
+ * Shared by archive (which refuses to apply the block) and validate (which
266
+ * reports the same loss at authoring time, #1477), so the two cannot disagree
267
+ * about what counts as a dropped scenario.
268
+ */
269
+ export function findMissingCurrentScenarios(current, incoming) {
270
+ // Multiplicity-aware: a name present N times in current and M times in
271
+ // incoming means max(0, N - M) instances are missing. Set membership would
272
+ // treat N>M as fully covered and let archive silently drop duplicates
273
+ // (residual #1246 / duplicate-scenario-name blind spot).
274
+ const remainingIncoming = new Map();
275
+ for (const scenario of parseScenarioBlocks(incoming.raw)) {
276
+ const name = scenario.name;
277
+ remainingIncoming.set(name, (remainingIncoming.get(name) ?? 0) + 1);
278
+ }
279
+ const missing = [];
280
+ for (const scenario of parseScenarioBlocks(current.raw)) {
281
+ const name = scenario.name;
282
+ const remaining = remainingIncoming.get(name) ?? 0;
283
+ if (remaining > 0) {
284
+ remainingIncoming.set(name, remaining - 1);
285
+ }
286
+ else {
287
+ missing.push(name);
288
+ }
289
+ }
290
+ return missing;
291
+ }
292
+ function parseScenarioBlocks(requirementRaw) {
293
+ const lines = requirementRaw.replace(/\r\n?/g, '\n').split('\n');
294
+ // A `#### Scenario:` inside a fenced example is not a real scenario. The
295
+ // validator's countScenarios already ignores fenced lines; the drift check
296
+ // must agree with it, or a fenced sample can false-abort an archive (or
297
+ // mask a genuinely dropped scenario).
298
+ const mask = buildCodeFenceMask(lines);
299
+ const scenarios = [];
300
+ let index = 0;
301
+ while (index < lines.length) {
302
+ const headerMatch = mask[index] ? null : lines[index].match(/^####\s*Scenario:\s*(.+)\s*$/);
303
+ if (!headerMatch) {
304
+ index++;
305
+ continue;
306
+ }
307
+ const start = index;
308
+ const name = headerMatch[1].trim();
309
+ index++;
310
+ while (index < lines.length && (mask[index] || !/^####\s*Scenario:\s*(.+)\s*$/.test(lines[index]))) {
311
+ index++;
312
+ }
313
+ scenarios.push({
314
+ name,
315
+ raw: lines.slice(start, index).join('\n').trimEnd(),
316
+ });
317
+ }
318
+ return scenarios;
319
+ }
201
320
  //# sourceMappingURL=requirement-blocks.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared, fence-aware requirement-reading helpers.
3
+ *
4
+ * The requirement reader used to be implemented twice — once for main specs
5
+ * (`MarkdownParser.parseRequirements`) and once for change deltas
6
+ * (`Validator.extractRequirementText` / `countScenarios`) — and the two drifted
7
+ * apart. These helpers are the single source of truth for requirement-body
8
+ * extraction, scenario counting, and `SHALL`/`MUST` detection in
9
+ * `validate <change>`, `validate <spec>`, and `archive`.
10
+ */
11
+ export { buildCodeFenceMask } from './code-fence.js';
12
+ /**
13
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
14
+ * as whole words so the change-delta reader and the schema-based reader accept
15
+ * and reject identical text.
16
+ */
17
+ export declare function containsShallOrMust(text: string): boolean;
18
+ /**
19
+ * Extract the full requirement body from the lines that follow a
20
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
21
+ *
22
+ * Captures every body line from the start up to the first header found on a
23
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
24
+ * divider the delta reader absorbed into the block — skipping blank lines and
25
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
26
+ * when other body text remains: a requirement written entirely as
27
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
28
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
29
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
30
+ * full.
31
+ */
32
+ export declare function extractRequirementBody(bodyLines: string[]): string;
33
+ /**
34
+ * Parser/display fallback for a requirement block with no body text. This is
35
+ * what lets a bare `### The system SHALL ...` header remain readable on the
36
+ * spec path (the title is the requirement). Validator body-keyword checks for
37
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
38
+ * a keyword that appears only in the header still receives the #1156/#1280
39
+ * body-keyword hint.
40
+ */
41
+ export declare function extractRequirementText(headerTitle: string, bodyLines: string[]): string;
42
+ /**
43
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
44
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
45
+ * scenario and is not counted.
46
+ */
47
+ export declare function countScenarios(bodyLines: string[]): number;
48
+ //# sourceMappingURL=requirement-text.d.ts.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Shared, fence-aware requirement-reading helpers.
3
+ *
4
+ * The requirement reader used to be implemented twice — once for main specs
5
+ * (`MarkdownParser.parseRequirements`) and once for change deltas
6
+ * (`Validator.extractRequirementText` / `countScenarios`) — and the two drifted
7
+ * apart. These helpers are the single source of truth for requirement-body
8
+ * extraction, scenario counting, and `SHALL`/`MUST` detection in
9
+ * `validate <change>`, `validate <spec>`, and `archive`.
10
+ */
11
+ // Re-exported so existing importers keep working; the single implementation
12
+ // lives in code-fence.ts.
13
+ export { buildCodeFenceMask } from './code-fence.js';
14
+ import { buildCodeFenceMask } from './code-fence.js';
15
+ /** Lines that look like `**ID**: ...` / `**Priority**: ...` metadata. */
16
+ const METADATA_LINE = /^\*\*[^*]+\*\*:/;
17
+ /** Any markdown header line — the boundary where a requirement body ends. */
18
+ const HEADER_LINE = /^#{1,6}\s/;
19
+ /**
20
+ * A level-4 header. Deliberately matches ANY `####` header, not only
21
+ * `#### Scenario:` — the spec path treats every level-4 child of a requirement
22
+ * as a scenario, so the delta counter must too (parity). Don't tighten this to
23
+ * `Scenario:` without changing both paths together.
24
+ */
25
+ const SCENARIO_HEADER = /^####\s+/;
26
+ /**
27
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
28
+ * as whole words so the change-delta reader and the schema-based reader accept
29
+ * and reject identical text.
30
+ */
31
+ export function containsShallOrMust(text) {
32
+ return /\b(SHALL|MUST)\b/.test(text);
33
+ }
34
+ /**
35
+ * Extract the full requirement body from the lines that follow a
36
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
37
+ *
38
+ * Captures every body line from the start up to the first header found on a
39
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
40
+ * divider the delta reader absorbed into the block — skipping blank lines and
41
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
42
+ * when other body text remains: a requirement written entirely as
43
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
44
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
45
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
46
+ * full.
47
+ */
48
+ export function extractRequirementBody(bodyLines) {
49
+ const mask = buildCodeFenceMask(bodyLines);
50
+ const captured = [];
51
+ const metadata = [];
52
+ for (let i = 0; i < bodyLines.length; i++) {
53
+ if (mask[i])
54
+ continue; // inside a fenced code block
55
+ const line = bodyLines[i];
56
+ if (HEADER_LINE.test(line))
57
+ break; // first scenario or stray divider
58
+ const trimmed = line.trim();
59
+ if (trimmed.length === 0)
60
+ continue; // blank
61
+ if (METADATA_LINE.test(trimmed)) {
62
+ metadata.push(trimmed); // **ID**: / **Priority**: ...
63
+ continue;
64
+ }
65
+ captured.push(trimmed);
66
+ }
67
+ if (captured.length > 0)
68
+ return captured.join('\n');
69
+ return metadata.join('\n'); // metadata-only body: the metadata IS the body
70
+ }
71
+ /**
72
+ * Parser/display fallback for a requirement block with no body text. This is
73
+ * what lets a bare `### The system SHALL ...` header remain readable on the
74
+ * spec path (the title is the requirement). Validator body-keyword checks for
75
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
76
+ * a keyword that appears only in the header still receives the #1156/#1280
77
+ * body-keyword hint.
78
+ */
79
+ export function extractRequirementText(headerTitle, bodyLines) {
80
+ return extractRequirementBody(bodyLines) || headerTitle.trim();
81
+ }
82
+ /**
83
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
84
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
85
+ * scenario and is not counted.
86
+ */
87
+ export function countScenarios(bodyLines) {
88
+ const mask = buildCodeFenceMask(bodyLines);
89
+ let count = 0;
90
+ for (let i = 0; i < bodyLines.length; i++) {
91
+ if (mask[i])
92
+ continue;
93
+ if (SCENARIO_HEADER.test(bodyLines[i]))
94
+ count++;
95
+ }
96
+ return count;
97
+ }
98
+ //# sourceMappingURL=requirement-text.js.map
@@ -1,5 +1,5 @@
1
1
  export interface MainSpecStructureIssue {
2
- kind: 'delta-header' | 'requirement-outside-requirements';
2
+ kind: 'delta-header' | 'requirement-outside-requirements' | 'duplicate-requirement';
3
3
  line: number;
4
4
  header: string;
5
5
  message: string;
@@ -1,3 +1,4 @@
1
+ import { buildCodeFenceMask } from './code-fence.js';
1
2
  const REQUIREMENTS_SECTION_HEADER = /^##\s+Requirements\s*$/i;
2
3
  const TOP_LEVEL_SECTION_HEADER = /^##\s+/;
3
4
  const DELTA_HEADER = /^##\s+(ADDED|MODIFIED|REMOVED|RENAMED)\s+Requirements\s*$/i;
@@ -7,6 +8,7 @@ export function findMainSpecStructureIssues(content) {
7
8
  const stripped = stripFencedCodeBlocksPreservingLines(normalized);
8
9
  const lines = stripped.split('\n');
9
10
  const issues = [];
11
+ const requirementLines = new Map();
10
12
  const requirementsHeaderIndex = lines.findIndex(line => REQUIREMENTS_SECTION_HEADER.test(line));
11
13
  let requirementsEndIndex = lines.length;
12
14
  if (requirementsHeaderIndex !== -1) {
@@ -29,7 +31,7 @@ export function findMainSpecStructureIssues(content) {
29
31
  line: i + 1,
30
32
  header: trimmed,
31
33
  message: `Main spec contains delta header "${trimmed}". ` +
32
- 'Delta headers are only valid inside openspec/changes/<name>/specs/<capability>/spec.md ' +
34
+ 'Delta headers are only valid inside openspec/changes/<name>/specs/<capability-path>/spec.md ' +
33
35
  'and truncate the parsed ## Requirements section.',
34
36
  });
35
37
  continue;
@@ -49,40 +51,28 @@ export function findMainSpecStructureIssues(content) {
49
51
  message: `Requirement header "${trimmed}" appears outside the main ## Requirements section. ` +
50
52
  'Main specs only parse requirements inside that section, so this requirement is currently invisible to validate, list, and archive.',
51
53
  });
54
+ continue;
55
+ }
56
+ const requirementName = requirementMatch[1].trim();
57
+ const previousLine = requirementLines.get(requirementName);
58
+ if (previousLine !== undefined) {
59
+ issues.push({
60
+ kind: 'duplicate-requirement',
61
+ line: i + 1,
62
+ header: trimmed,
63
+ message: `Requirement header "${trimmed}" duplicates the requirement declared on line ${previousLine}. ` +
64
+ 'Requirement names must be unique so spec updates cannot discard one block while updating another.',
65
+ });
66
+ }
67
+ else {
68
+ requirementLines.set(requirementName, i + 1);
52
69
  }
53
70
  }
54
71
  return issues;
55
72
  }
56
73
  export function stripFencedCodeBlocksPreservingLines(content) {
57
74
  const lines = content.split('\n');
58
- const output = [];
59
- let activeFence = null;
60
- for (const line of lines) {
61
- const fenceMatch = line.match(/^\s*(`{3,}|~{3,})(.*)$/);
62
- if (!activeFence) {
63
- if (fenceMatch) {
64
- activeFence = {
65
- marker: fenceMatch[1][0],
66
- length: fenceMatch[1].length,
67
- };
68
- output.push('');
69
- }
70
- else {
71
- output.push(line);
72
- }
73
- continue;
74
- }
75
- output.push('');
76
- if (isClosingFence(line, activeFence)) {
77
- activeFence = null;
78
- }
79
- }
80
- return output.join('\n');
81
- }
82
- function isClosingFence(line, activeFence) {
83
- const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
84
- return Boolean(fenceMatch &&
85
- fenceMatch[1][0] === activeFence.marker &&
86
- fenceMatch[1].length >= activeFence.length);
75
+ const mask = buildCodeFenceMask(lines);
76
+ return lines.map((line, i) => (mask[i] ? '' : line)).join('\n');
87
77
  }
88
78
  //# sourceMappingURL=spec-structure.js.map
@@ -5,14 +5,6 @@ type WorkflowId = (typeof ALL_WORKFLOWS)[number];
5
5
  * Maps workflow IDs to their skill directory names.
6
6
  */
7
7
  export declare const WORKFLOW_TO_SKILL_DIR: Record<WorkflowId, string>;
8
- /**
9
- * Checks whether a tool has at least one generated OpenSpec command file.
10
- */
11
- export declare function toolHasAnyConfiguredCommand(projectPath: string, toolId: string): boolean;
12
- /**
13
- * Returns tools with at least one generated command file on disk.
14
- */
15
- export declare function getCommandConfiguredTools(projectPath: string): string[];
16
8
  /**
17
9
  * Returns tools that are configured via either skills or commands.
18
10
  */