freeharness 0.4.1

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 (375) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +108 -0
  3. package/README.md +134 -0
  4. package/canonical/HARVEST.yaml +858 -0
  5. package/canonical/PROVENANCE.yaml +740 -0
  6. package/canonical/agents/README.md +95 -0
  7. package/canonical/agents/agentic-architect.md +18 -0
  8. package/canonical/agents/database-reviewer.md +41 -0
  9. package/canonical/agents/explorer-ai-engineer-analyst.md +65 -0
  10. package/canonical/agents/pr-reviewer.md +96 -0
  11. package/canonical/agents/python-reviewer.md +55 -0
  12. package/canonical/agents/report-formatter.md +19 -0
  13. package/canonical/agents/security-reviewer.md +53 -0
  14. package/canonical/agents/test-writer.md +34 -0
  15. package/canonical/commands/fh-context.md +37 -0
  16. package/canonical/evals/promptfoo.redteam.yaml +108 -0
  17. package/canonical/hooks/cost-prices.json +14 -0
  18. package/canonical/hooks/freeharness-auto-memory.mjs +158 -0
  19. package/canonical/hooks/freeharness-config-guard.mjs +162 -0
  20. package/canonical/hooks/freeharness-context-monitor.mjs +198 -0
  21. package/canonical/hooks/freeharness-converge.mjs +227 -0
  22. package/canonical/hooks/freeharness-cost-tracker.mjs +1027 -0
  23. package/canonical/hooks/freeharness-memory-guard.mjs +57 -0
  24. package/canonical/hooks/freeharness-output-pruner.mjs +333 -0
  25. package/canonical/hooks/freeharness-precompact.mjs +323 -0
  26. package/canonical/hooks/freeharness-repeat-tool.mjs +208 -0
  27. package/canonical/hooks/freeharness-security-guidance.mjs +164 -0
  28. package/canonical/hooks/freeharness-session-start +583 -0
  29. package/canonical/hooks/hooks.auto-memory.claude-code.json +16 -0
  30. package/canonical/hooks/hooks.claude-code.json +17 -0
  31. package/canonical/hooks/hooks.config-guard.claude-code.json +16 -0
  32. package/canonical/hooks/hooks.context-monitor.claude-code.json +15 -0
  33. package/canonical/hooks/hooks.converge.claude-code.json +15 -0
  34. package/canonical/hooks/hooks.cost-tracker.claude-code.json +37 -0
  35. package/canonical/hooks/hooks.memory-guard.claude-code.json +16 -0
  36. package/canonical/hooks/hooks.output-pruner.claude-code.json +16 -0
  37. package/canonical/hooks/hooks.precompact.claude-code.json +15 -0
  38. package/canonical/hooks/hooks.repeat-tool.claude-code.json +25 -0
  39. package/canonical/hooks/hooks.security-guidance.claude-code.json +16 -0
  40. package/canonical/mcp/servers.json +20 -0
  41. package/canonical/openspec/config.base.yaml +89 -0
  42. package/canonical/openspec/schema.spec-driven.yaml +277 -0
  43. package/canonical/openspec/templates/data-contract.md +36 -0
  44. package/canonical/openspec/templates/design.md +84 -0
  45. package/canonical/openspec/templates/eval-plan.md +33 -0
  46. package/canonical/openspec/templates/follow-ups.md +2 -0
  47. package/canonical/openspec/templates/proposal.md +45 -0
  48. package/canonical/openspec/templates/spec.md +42 -0
  49. package/canonical/openspec/templates/tasks.md +38 -0
  50. package/canonical/profiles/local-llamacpp.LOCAL-MODELS.md +47 -0
  51. package/canonical/profiles/local-llamacpp.json +27 -0
  52. package/canonical/profiles/local-ollama.json +20 -0
  53. package/canonical/profiles/mcp.json +91 -0
  54. package/canonical/rules/CLAUDE.md +78 -0
  55. package/canonical/rules/project-CLAUDE.template.md +62 -0
  56. package/canonical/rules/routing.md +20 -0
  57. package/canonical/rules/safety.md +41 -0
  58. package/canonical/settings/settings.cache-hygiene.claude-code.json +5 -0
  59. package/canonical/skills/agent-evaluation/SKILL.md +102 -0
  60. package/canonical/skills/agent-evaluation/assets/evaluation_report_template.md +206 -0
  61. package/canonical/skills/agent-evaluation/evals/cases.yaml +106 -0
  62. package/canonical/skills/agent-evaluation/references/dataset-preparation.md +351 -0
  63. package/canonical/skills/agent-evaluation/references/scorers-constraints.md +165 -0
  64. package/canonical/skills/agent-evaluation/references/scorers.md +431 -0
  65. package/canonical/skills/agent-evaluation/references/setup-guide.md +252 -0
  66. package/canonical/skills/agent-evaluation/references/tracing-integration.md +164 -0
  67. package/canonical/skills/agent-evaluation/references/troubleshooting.md +514 -0
  68. package/canonical/skills/agent-evaluation/scripts/analyze_results.py +462 -0
  69. package/canonical/skills/agent-evaluation/scripts/create_dataset_template.py +382 -0
  70. package/canonical/skills/agent-evaluation/scripts/list_datasets.py +273 -0
  71. package/canonical/skills/agent-evaluation/scripts/run_evaluation_template.py +309 -0
  72. package/canonical/skills/agent-evaluation/scripts/setup_mlflow.py +353 -0
  73. package/canonical/skills/agent-evaluation/scripts/utils/__init__.py +27 -0
  74. package/canonical/skills/agent-evaluation/scripts/utils/env_validation.py +123 -0
  75. package/canonical/skills/agent-evaluation/scripts/utils/tracing_utils.py +108 -0
  76. package/canonical/skills/agent-evaluation/scripts/validate_agent_tracing.py +148 -0
  77. package/canonical/skills/agent-evaluation/scripts/validate_auth.py +240 -0
  78. package/canonical/skills/agent-evaluation/scripts/validate_environment.py +185 -0
  79. package/canonical/skills/agent-evaluation/scripts/validate_tracing_runtime.py +318 -0
  80. package/canonical/skills/agentic-architecture/SKILL.md +41 -0
  81. package/canonical/skills/agentic-architecture/evals/cases.yaml +94 -0
  82. package/canonical/skills/agentic-architecture/reference/context-engineering.md +75 -0
  83. package/canonical/skills/agentic-architecture/reference/patterns.md +76 -0
  84. package/canonical/skills/agentic-architecture/reference/reliability-observability.md +94 -0
  85. package/canonical/skills/agentic-architecture/reference/review-framework.md +59 -0
  86. package/canonical/skills/code-review/SKILL.md +52 -0
  87. package/canonical/skills/code-review/evals/cases.yaml +197 -0
  88. package/canonical/skills/git-workflow/SKILL.md +41 -0
  89. package/canonical/skills/git-workflow/evals/cases.yaml +92 -0
  90. package/canonical/skills/local-code/SKILL.md +24 -0
  91. package/canonical/skills/local-code/evals/cases.yaml +85 -0
  92. package/canonical/skills/local-code/scripts/query_llama.py +68 -0
  93. package/canonical/skills/report-formatting/SKILL.md +62 -0
  94. package/canonical/skills/report-formatting/evals/cases.yaml +83 -0
  95. package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +135 -0
  96. package/canonical/skills/report-formatting/reference/HELPERS.md +120 -0
  97. package/canonical/skills/report-formatting/reference/PPTX.md +136 -0
  98. package/canonical/skills/report-formatting/reference/RELEASE.md +99 -0
  99. package/canonical/skills/report-formatting/reference/TEMPLATES.md +48 -0
  100. package/canonical/skills/report-formatting/reference/authoring.md +66 -0
  101. package/canonical/skills/report-formatting/scripts/architecture-template.md +271 -0
  102. package/canonical/skills/report-formatting/scripts/assets/claro-logo.png +0 -0
  103. package/canonical/skills/report-formatting/scripts/assets/claro-wordmark.svg +1 -0
  104. package/canonical/skills/report-formatting/scripts/build-architecture.js +57 -0
  105. package/canonical/skills/report-formatting/scripts/build-md-claro.js +224 -0
  106. package/canonical/skills/report-formatting/scripts/build-release.js +51 -0
  107. package/canonical/skills/report-formatting/scripts/check-env.js +58 -0
  108. package/canonical/skills/report-formatting/scripts/docmodel.js +283 -0
  109. package/canonical/skills/report-formatting/scripts/example-build.js +119 -0
  110. package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +195 -0
  111. package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +114 -0
  112. package/canonical/skills/report-formatting/scripts/package.json +17 -0
  113. package/canonical/skills/report-formatting/scripts/pptx/example_build_StatusReport_pptx.py +223 -0
  114. package/canonical/skills/report-formatting/scripts/pptx/example_build_WeeklyStatus_pptx.py +245 -0
  115. package/canonical/skills/report-formatting/scripts/pptx/pptx_office.py +144 -0
  116. package/canonical/skills/report-formatting/scripts/pptx/requirements.txt +7 -0
  117. package/canonical/skills/report-formatting/scripts/pptx/status_report_blocks.py +193 -0
  118. package/canonical/skills/report-formatting/scripts/pptx/theme_claro_pptx.py +473 -0
  119. package/canonical/skills/report-formatting/scripts/release-template.md +80 -0
  120. package/canonical/skills/report-formatting/scripts/render-mermaid.js +105 -0
  121. package/canonical/skills/report-formatting/scripts/theme-claro.js +403 -0
  122. package/canonical/skills/subagent-driven-development/SKILL.md +60 -0
  123. package/canonical/skills/subagent-driven-development/evals/cases.yaml +208 -0
  124. package/canonical/skills/subagent-driven-development/reference/brief.md +91 -0
  125. package/canonical/skills/subagent-driven-development/reference/handoff.md +118 -0
  126. package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +850 -0
  127. package/canonical/skills/systematic-debugging/SKILL.md +44 -0
  128. package/canonical/skills/systematic-debugging/evals/cases.yaml +90 -0
  129. package/canonical/skills/test-driven-development/SKILL.md +42 -0
  130. package/canonical/skills/test-driven-development/evals/cases.yaml +86 -0
  131. package/canonical/skills/test-driven-development/reference/writing-good-tests.md +52 -0
  132. package/canonical/skills/verification-before-completion/SKILL.md +53 -0
  133. package/canonical/skills/verification-before-completion/evals/cases.yaml +107 -0
  134. package/dist/adapters/content/language-lint.d.ts +46 -0
  135. package/dist/adapters/content/language-lint.js +191 -0
  136. package/dist/adapters/harness/claude-code.d.ts +57 -0
  137. package/dist/adapters/harness/claude-code.js +385 -0
  138. package/dist/adapters/harness/index.d.ts +3 -0
  139. package/dist/adapters/harness/index.js +17 -0
  140. package/dist/adapters/harness/opencode.d.ts +7 -0
  141. package/dist/adapters/harness/opencode.js +166 -0
  142. package/dist/adapters/harness/plugin.d.ts +22 -0
  143. package/dist/adapters/harness/plugin.js +257 -0
  144. package/dist/adapters/memory/doctor.d.ts +480 -0
  145. package/dist/adapters/memory/doctor.js +1217 -0
  146. package/dist/adapters/memory/vendored.d.ts +150 -0
  147. package/dist/adapters/memory/vendored.js +487 -0
  148. package/dist/adapters/package/node.d.ts +9 -0
  149. package/dist/adapters/package/node.js +20 -0
  150. package/dist/adapters/prompt/readline.d.ts +19 -0
  151. package/dist/adapters/prompt/readline.js +53 -0
  152. package/dist/adapters/sync/diff.d.ts +43 -0
  153. package/dist/adapters/sync/diff.js +380 -0
  154. package/dist/cli/cleanup.d.ts +86 -0
  155. package/dist/cli/cleanup.js +382 -0
  156. package/dist/cli/cost.d.ts +304 -0
  157. package/dist/cli/cost.js +501 -0
  158. package/dist/cli/doctor.d.ts +205 -0
  159. package/dist/cli/doctor.js +654 -0
  160. package/dist/cli/executor.d.ts +5 -0
  161. package/dist/cli/executor.js +48 -0
  162. package/dist/cli/plugin-validate.d.ts +37 -0
  163. package/dist/cli/plugin-validate.js +256 -0
  164. package/dist/cli/prefix.d.ts +68 -0
  165. package/dist/cli/prefix.js +218 -0
  166. package/dist/cli/profile.d.ts +170 -0
  167. package/dist/cli/profile.js +285 -0
  168. package/dist/cli/runner.d.ts +10 -0
  169. package/dist/cli/runner.js +27 -0
  170. package/dist/cli/uninstall.d.ts +65 -0
  171. package/dist/cli/uninstall.js +147 -0
  172. package/dist/index.d.ts +8 -0
  173. package/dist/index.js +989 -0
  174. package/dist/infra/dogfood.d.ts +31 -0
  175. package/dist/infra/dogfood.js +51 -0
  176. package/dist/infra/fsutil.d.ts +40 -0
  177. package/dist/infra/fsutil.js +143 -0
  178. package/dist/infra/manifest.d.ts +76 -0
  179. package/dist/infra/manifest.js +92 -0
  180. package/dist/infra/paths.d.ts +19 -0
  181. package/dist/infra/paths.js +74 -0
  182. package/dist/infra/routing.d.ts +4 -0
  183. package/dist/infra/routing.js +7 -0
  184. package/node_modules/@freeharness/core/dist/context.d.ts +30 -0
  185. package/node_modules/@freeharness/core/dist/context.js +1 -0
  186. package/node_modules/@freeharness/core/dist/index.d.ts +5 -0
  187. package/node_modules/@freeharness/core/dist/index.js +6 -0
  188. package/node_modules/@freeharness/core/dist/planner.d.ts +13 -0
  189. package/node_modules/@freeharness/core/dist/planner.js +18 -0
  190. package/node_modules/@freeharness/core/dist/ports.d.ts +90 -0
  191. package/node_modules/@freeharness/core/dist/ports.js +1 -0
  192. package/node_modules/@freeharness/core/dist/registry.d.ts +41 -0
  193. package/node_modules/@freeharness/core/dist/registry.js +124 -0
  194. package/node_modules/@freeharness/core/dist/tiers.d.ts +30 -0
  195. package/node_modules/@freeharness/core/dist/tiers.js +99 -0
  196. package/node_modules/@freeharness/core/package.json +34 -0
  197. package/node_modules/@freeharness/sdd/PROVENANCE.yaml +175 -0
  198. package/node_modules/@freeharness/sdd/dist/src/bin/openspec.d.ts +2 -0
  199. package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +166 -0
  200. package/node_modules/@freeharness/sdd/dist/src/index.d.ts +14 -0
  201. package/node_modules/@freeharness/sdd/dist/src/index.js +11 -0
  202. package/node_modules/@freeharness/sdd/dist/src/init.d.ts +39 -0
  203. package/node_modules/@freeharness/sdd/dist/src/init.js +190 -0
  204. package/node_modules/@freeharness/sdd/dist/src/root-resolver.d.ts +8 -0
  205. package/node_modules/@freeharness/sdd/dist/src/root-resolver.js +32 -0
  206. package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +81 -0
  207. package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +483 -0
  208. package/node_modules/@freeharness/sdd/dist/src/shim/context.d.ts +4 -0
  209. package/node_modules/@freeharness/sdd/dist/src/shim/context.js +48 -0
  210. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +57 -0
  211. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +279 -0
  212. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +67 -0
  213. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +460 -0
  214. package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +5 -0
  215. package/node_modules/@freeharness/sdd/dist/src/shim/list.js +95 -0
  216. package/node_modules/@freeharness/sdd/dist/src/shim/new-change.d.ts +6 -0
  217. package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +150 -0
  218. package/node_modules/@freeharness/sdd/dist/src/shim/schemas.d.ts +4 -0
  219. package/node_modules/@freeharness/sdd/dist/src/shim/schemas.js +23 -0
  220. package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +56 -0
  221. package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +140 -0
  222. package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +8 -0
  223. package/node_modules/@freeharness/sdd/dist/src/shim/status.js +85 -0
  224. package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +6 -0
  225. package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +229 -0
  226. package/node_modules/@freeharness/sdd/dist/src/validate-change.d.ts +40 -0
  227. package/node_modules/@freeharness/sdd/dist/src/validate-change.js +114 -0
  228. package/node_modules/@freeharness/sdd/dist/src/validate.d.ts +24 -0
  229. package/node_modules/@freeharness/sdd/dist/src/validate.js +72 -0
  230. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.d.ts +130 -0
  231. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +981 -0
  232. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-metadata.d.ts +46 -0
  233. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-metadata.js +128 -0
  234. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +26 -0
  235. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +32 -0
  236. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/file-system.d.ts +50 -0
  237. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/file-system.js +394 -0
  238. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/global-config.d.ts +15 -0
  239. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/global-config.js +49 -0
  240. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/graph.d.ts +71 -0
  241. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/graph.js +164 -0
  242. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +11 -0
  243. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +21 -0
  244. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/instruction-loader.d.ts +102 -0
  245. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/instruction-loader.js +230 -0
  246. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +18 -0
  247. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +138 -0
  248. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/resolver.d.ts +82 -0
  249. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/resolver.js +333 -0
  250. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/schema.d.ts +12 -0
  251. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/schema.js +110 -0
  252. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/state.d.ts +11 -0
  253. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/state.js +32 -0
  254. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.d.ts +33 -0
  255. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +149 -0
  256. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.d.ts +39 -0
  257. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +49 -0
  258. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/claude.d.ts +12 -0
  259. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/claude.js +31 -0
  260. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/index.d.ts +10 -0
  261. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/index.js +10 -0
  262. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/opencode.d.ts +12 -0
  263. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/opencode.js +26 -0
  264. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/generator.d.ts +28 -0
  265. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/generator.js +41 -0
  266. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/index.d.ts +23 -0
  267. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/index.js +25 -0
  268. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/invocation.d.ts +72 -0
  269. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/invocation.js +76 -0
  270. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/registry.d.ts +39 -0
  271. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/registry.js +53 -0
  272. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/types.d.ts +62 -0
  273. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/types.js +7 -0
  274. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/yaml.d.ts +35 -0
  275. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/yaml.js +47 -0
  276. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-references.d.ts +33 -0
  277. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-references.js +55 -0
  278. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/allowed-tools.d.ts +11 -0
  279. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/allowed-tools.js +11 -0
  280. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/skill-generation.d.ts +48 -0
  281. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/skill-generation.js +99 -0
  282. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/skill-templates.d.ts +19 -0
  283. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/skill-templates.js +18 -0
  284. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/types.d.ts +18 -0
  285. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/types.js +4 -0
  286. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.d.ts +19 -0
  287. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +207 -0
  288. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.d.ts +12 -0
  289. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +409 -0
  290. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/bulk-archive-change.d.ts +9 -0
  291. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/bulk-archive-change.js +673 -0
  292. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/continue-change.d.ts +9 -0
  293. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/continue-change.js +230 -0
  294. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.d.ts +12 -0
  295. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +550 -0
  296. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/feedback.d.ts +8 -0
  297. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/feedback.js +107 -0
  298. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/ff-change.d.ts +12 -0
  299. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/ff-change.js +228 -0
  300. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/new-change.d.ts +9 -0
  301. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/new-change.js +147 -0
  302. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/onboard.d.ts +9 -0
  303. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/onboard.js +571 -0
  304. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.d.ts +12 -0
  305. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +322 -0
  306. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/store-selection.d.ts +7 -0
  307. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/store-selection.js +7 -0
  308. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.d.ts +9 -0
  309. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +518 -0
  310. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.d.ts +9 -0
  311. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +176 -0
  312. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/verify-change.d.ts +9 -0
  313. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/verify-change.js +344 -0
  314. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +39 -0
  315. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +54 -0
  316. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/delta-resolution.d.ts +35 -0
  317. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/delta-resolution.js +171 -0
  318. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +27 -0
  319. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +210 -0
  320. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/code-fence.d.ts +14 -0
  321. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/code-fence.js +48 -0
  322. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.d.ts +22 -0
  323. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +186 -0
  324. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +65 -0
  325. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +409 -0
  326. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +55 -0
  327. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +98 -0
  328. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.d.ts +8 -0
  329. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +77 -0
  330. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.d.ts +44 -0
  331. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +116 -0
  332. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/base.schema.d.ts +12 -0
  333. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/base.schema.js +17 -0
  334. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/change.schema.d.ts +72 -0
  335. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/change.schema.js +30 -0
  336. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/index.d.ts +3 -0
  337. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/index.js +3 -0
  338. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/spec.schema.d.ts +17 -0
  339. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/spec.schema.js +14 -0
  340. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +39 -0
  341. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +114 -0
  342. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +45 -0
  343. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +105 -0
  344. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-numbering.d.ts +15 -0
  345. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-numbering.js +56 -0
  346. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/types.d.ts +17 -0
  347. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/types.js +1 -0
  348. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +94 -0
  349. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +756 -0
  350. package/node_modules/@freeharness/sdd/package.json +57 -0
  351. package/node_modules/@freeharness/sdd/vendor/openspec/LICENSE.upstream +22 -0
  352. package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +232 -0
  353. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/README.md +19 -0
  354. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +187 -0
  355. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +181 -0
  356. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +338 -0
  357. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +117 -0
  358. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +342 -0
  359. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +116 -0
  360. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +76 -0
  361. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +560 -0
  362. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +162 -0
  363. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +261 -0
  364. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +90 -0
  365. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +174 -0
  366. package/package.json +70 -0
  367. package/provenance/canonical.PROVENANCE.yaml +740 -0
  368. package/provenance/claude-mem.LICENSE.upstream +202 -0
  369. package/provenance/memory.PROVENANCE.yaml +399 -0
  370. package/provenance/sdd.PROVENANCE.yaml +175 -0
  371. package/vendor-licenses/caveman.LICENSE +27 -0
  372. package/vendor-licenses/deepseek-harness.LICENSE +21 -0
  373. package/vendor-licenses/ecc.LICENSE +21 -0
  374. package/vendor-licenses/pi.LICENSE +21 -0
  375. package/vendor-licenses/superpowers.LICENSE +21 -0
@@ -0,0 +1,65 @@
1
+ export interface RequirementBlock {
2
+ headerLine: string;
3
+ name: string;
4
+ raw: string;
5
+ }
6
+ export interface RequirementsSectionParts {
7
+ before: string;
8
+ headerLine: string;
9
+ preamble: string;
10
+ bodyBlocks: RequirementBlock[];
11
+ after: string;
12
+ }
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;
22
+ /**
23
+ * Extracts the Requirements section from a spec file and parses requirement blocks.
24
+ */
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
+ }
36
+ export interface DeltaPlan {
37
+ added: RequirementBlock[];
38
+ modified: RequirementBlock[];
39
+ removed: string[];
40
+ renamed: Array<{
41
+ from: string;
42
+ to: string;
43
+ }>;
44
+ skippedHeaders: SkippedHeader[];
45
+ sectionPresence: {
46
+ added: boolean;
47
+ modified: boolean;
48
+ removed: boolean;
49
+ renamed: boolean;
50
+ };
51
+ }
52
+ /**
53
+ * Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
54
+ */
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[];
@@ -0,0 +1,409 @@
1
+ import { buildCodeFenceMask, SCENARIO_HEADER } from './requirement-text.js';
2
+ export function normalizeRequirementName(name) {
3
+ return name.trim();
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. */
16
+ const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;
17
+ /**
18
+ * Extracts the Requirements section from a spec file and parses requirement blocks.
19
+ */
20
+ export function extractRequirementsSection(content) {
21
+ const normalized = normalizeLineEndings(content);
22
+ const lines = normalized.split('\n');
23
+ const fenceMask = buildCodeFenceMask(lines);
24
+ const reqHeaderIndex = lines.findIndex((l, i) => !fenceMask[i] && /^##\s+Requirements\s*$/i.test(l));
25
+ if (reqHeaderIndex === -1) {
26
+ // No requirements section; create an empty one at the end
27
+ const before = content.trimEnd();
28
+ const headerLine = '## Requirements';
29
+ return {
30
+ before: before ? before + '\n\n' : '',
31
+ headerLine,
32
+ preamble: '',
33
+ bodyBlocks: [],
34
+ after: '\n',
35
+ };
36
+ }
37
+ // Find end of this section: next line that starts with '## ' at same or higher level
38
+ let endIndex = lines.length;
39
+ for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
40
+ if (!fenceMask[i] && /^##\s+/.test(lines[i])) {
41
+ endIndex = i;
42
+ break;
43
+ }
44
+ }
45
+ const before = lines.slice(0, reqHeaderIndex).join('\n');
46
+ const headerLine = lines[reqHeaderIndex];
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]);
51
+ // Parse requirement blocks within section body
52
+ const blocks = [];
53
+ let cursor = 0;
54
+ let preambleLines = [];
55
+ // Collect preamble lines until first requirement header
56
+ while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor)) {
57
+ preambleLines.push(sectionBodyLines[cursor]);
58
+ cursor++;
59
+ }
60
+ while (cursor < sectionBodyLines.length) {
61
+ const headerLineCandidate = sectionBodyLines[cursor];
62
+ if (!isRequirementHeader(cursor)) {
63
+ // Not a requirement header; skip line defensively
64
+ cursor++;
65
+ continue;
66
+ }
67
+ const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX);
68
+ const name = normalizeRequirementName(headerMatch[1]);
69
+ cursor++;
70
+ // Gather lines until next requirement header or end of section
71
+ const bodyLines = [headerLineCandidate];
72
+ while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor) && !isTopLevelHeader(cursor)) {
73
+ bodyLines.push(sectionBodyLines[cursor]);
74
+ cursor++;
75
+ }
76
+ const raw = bodyLines.join('\n').trimEnd();
77
+ blocks.push({ headerLine: headerLineCandidate, name, raw });
78
+ }
79
+ const after = lines.slice(endIndex).join('\n');
80
+ const preamble = preambleLines.join('\n').trimEnd();
81
+ return {
82
+ before: before.trimEnd() ? before + '\n' : before,
83
+ headerLine,
84
+ preamble,
85
+ bodyBlocks: blocks,
86
+ after: after.startsWith('\n') ? after : '\n' + after,
87
+ };
88
+ }
89
+ function normalizeLineEndings(content) {
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');
93
+ }
94
+ /**
95
+ * Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
96
+ */
97
+ export function parseDeltaSpec(content) {
98
+ const normalized = normalizeLineEndings(content);
99
+ const lines = normalized.split('\n');
100
+ const fenceMask = buildCodeFenceMask(lines);
101
+ const sections = splitTopLevelSections(lines, fenceMask);
102
+ const addedLookup = getSectionsCaseInsensitive(sections, 'ADDED Requirements');
103
+ const modifiedLookup = getSectionsCaseInsensitive(sections, 'MODIFIED Requirements');
104
+ const removedLookup = getSectionsCaseInsensitive(sections, 'REMOVED Requirements');
105
+ const renamedLookup = getSectionsCaseInsensitive(sections, 'RENAMED Requirements');
106
+ const skippedHeaders = [];
107
+ const added = addedLookup.bodies.flatMap((body) => parseRequirementBlocksFromSection(body, {
108
+ section: addedLookup.title,
109
+ bodyStartLine: body.bodyStartLine,
110
+ sink: skippedHeaders,
111
+ }));
112
+ const modified = modifiedLookup.bodies.flatMap((body) => parseRequirementBlocksFromSection(body, {
113
+ section: modifiedLookup.title,
114
+ bodyStartLine: body.bodyStartLine,
115
+ sink: skippedHeaders,
116
+ }));
117
+ const removedNames = removedLookup.bodies.flatMap((body) => parseRemovedNames(body));
118
+ // Pairs are read per section, so a FROM in one copy of the header can never
119
+ // pair with a TO written under a different copy.
120
+ const renamedPairs = renamedLookup.bodies.flatMap((body) => parseRenamedPairs(body, { section: renamedLookup.title, sink: skippedHeaders }));
121
+ skippedHeaders.sort((a, b) => a.line - b.line);
122
+ return {
123
+ added,
124
+ modified,
125
+ removed: removedNames,
126
+ renamed: renamedPairs,
127
+ skippedHeaders,
128
+ sectionPresence: {
129
+ added: addedLookup.found,
130
+ modified: modifiedLookup.found,
131
+ removed: removedLookup.found,
132
+ renamed: renamedLookup.found,
133
+ },
134
+ };
135
+ }
136
+ /**
137
+ * Every `## ` section of a delta file, as a list rather than a title-keyed
138
+ * record.
139
+ *
140
+ * A title-keyed record keeps only the last body under a repeated title, so a
141
+ * delta that writes `## ADDED Requirements` twice would silently drop every
142
+ * requirement under the first copy before any validation or merge rule sees
143
+ * it. A list keeps each occurrence in document order; `getSectionsCaseInsensitive`
144
+ * below merges the ones that share a title.
145
+ */
146
+ function splitTopLevelSections(lines, fenceMask) {
147
+ const sections = [];
148
+ const indices = [];
149
+ for (let i = 0; i < lines.length; i++) {
150
+ if (fenceMask[i])
151
+ continue;
152
+ const m = lines[i].match(/^(##)\s+(.+)$/);
153
+ if (m) {
154
+ indices.push({ title: m[2].trim(), index: i });
155
+ }
156
+ }
157
+ for (let i = 0; i < indices.length; i++) {
158
+ const current = indices[i];
159
+ const next = indices[i + 1];
160
+ const end = next ? next.index : lines.length;
161
+ sections.push({
162
+ title: current.title,
163
+ body: {
164
+ lines: lines.slice(current.index + 1, end),
165
+ fenceMask: fenceMask.slice(current.index + 1, end),
166
+ bodyStartLine: current.index + 2,
167
+ },
168
+ });
169
+ }
170
+ return sections;
171
+ }
172
+ /**
173
+ * Every section body whose title folds to `desired`, in document order.
174
+ *
175
+ * Returning all matches - not just the first - is what lets a repeated header
176
+ * (`## ADDED Requirements` twice) or a case variant (`## ADDED Requirements`
177
+ * next to `## Added Requirements`) apply in full: each body keeps its own
178
+ * `bodyStartLine`, so a diagnostic points at the copy the header actually came
179
+ * from. `title` is the first spelling the author used, which diagnostics quote.
180
+ */
181
+ function getSectionsCaseInsensitive(sections, desired) {
182
+ const target = desired.toLowerCase();
183
+ const matches = sections.filter((section) => section.title.toLowerCase() === target);
184
+ if (matches.length === 0) {
185
+ return { title: desired, bodies: [], found: false };
186
+ }
187
+ return {
188
+ title: matches[0].title,
189
+ bodies: matches.map((section) => section.body),
190
+ found: true,
191
+ };
192
+ }
193
+ function parseRequirementBlocksFromSection(sectionBody, skipped) {
194
+ const { lines, fenceMask } = sectionBody;
195
+ if (lines.length === 0)
196
+ return [];
197
+ const isRequirementHeader = (i) => !fenceMask[i] && REQUIREMENT_HEADER_REGEX.test(lines[i]);
198
+ const isTopLevelHeader = (i) => !fenceMask[i] && /^##\s+/.test(lines[i]);
199
+ const recordIfSkippedHeader = (index) => {
200
+ if (!skipped || fenceMask[index])
201
+ return;
202
+ const h3 = lines[index].match(/^###\s+(.+?)\s*$/);
203
+ if (h3 && !REQUIREMENT_HEADER_REGEX.test(lines[index])) {
204
+ skipped.sink.push({
205
+ header: h3[1].trim(),
206
+ section: skipped.section,
207
+ line: skipped.bodyStartLine + index,
208
+ });
209
+ }
210
+ };
211
+ const blocks = [];
212
+ let i = 0;
213
+ while (i < lines.length) {
214
+ // Seek next requirement header
215
+ while (i < lines.length && !isRequirementHeader(i)) {
216
+ recordIfSkippedHeader(i);
217
+ i++;
218
+ }
219
+ if (i >= lines.length)
220
+ break;
221
+ const headerLine = lines[i];
222
+ const m = headerLine.match(REQUIREMENT_HEADER_REGEX);
223
+ if (!m) {
224
+ i++;
225
+ continue;
226
+ }
227
+ const name = normalizeRequirementName(m[1]);
228
+ const buf = [headerLine];
229
+ i++;
230
+ while (i < lines.length && !isRequirementHeader(i) && !isTopLevelHeader(i)) {
231
+ recordIfSkippedHeader(i);
232
+ buf.push(lines[i]);
233
+ i++;
234
+ }
235
+ blocks.push({ headerLine, name, raw: buf.join('\n').trimEnd() });
236
+ }
237
+ return blocks;
238
+ }
239
+ /**
240
+ * Requirement names listed in `## REMOVED Requirements`, in document order.
241
+ *
242
+ * Two spellings are accepted: a plain `### Requirement:` header, and a bullet
243
+ * carrying one. Every CommonMark bullet marker (`-`, `*`, `+`) counts for the
244
+ * bullet form: accepting only `-` turns a removal written with `*` or `+`
245
+ * into a silent no-op - archive reports success while the requirement stays
246
+ * in the spec.
247
+ */
248
+ function parseRemovedNames(sectionBody) {
249
+ const { lines, fenceMask } = sectionBody;
250
+ if (lines.length === 0)
251
+ return [];
252
+ const names = [];
253
+ for (let i = 0; i < lines.length; i++) {
254
+ if (fenceMask[i])
255
+ continue;
256
+ const line = lines[i];
257
+ const m = line.match(REQUIREMENT_HEADER_REGEX);
258
+ if (m) {
259
+ names.push(normalizeRequirementName(m[1]));
260
+ continue;
261
+ }
262
+ // Also support bullet list of headers
263
+ const bullet = line.match(/^\s*[-*+]\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
264
+ if (bullet) {
265
+ names.push(normalizeRequirementName(bullet[1]));
266
+ }
267
+ }
268
+ return names;
269
+ }
270
+ /**
271
+ * `FROM:`/`TO:` rename pairs from `## RENAMED Requirements`, in document
272
+ * order.
273
+ *
274
+ * The leading bullet is optional, and every CommonMark bullet marker (`-`,
275
+ * `*`, `+`) is accepted: accepting only `-` turns a rename written with `*`
276
+ * or `+` into a silent no-op - archive reports success while the rename never
277
+ * happens.
278
+ */
279
+ function parseRenamedPairs(sectionBody, skipped) {
280
+ const { lines, fenceMask, bodyStartLine } = sectionBody;
281
+ if (lines.length === 0)
282
+ return [];
283
+ const pairs = [];
284
+ let current = {};
285
+ const flushUnpairedFrom = () => {
286
+ if (current.from === undefined || !skipped)
287
+ return;
288
+ skipped.sink.push({
289
+ header: `${current.from} (FROM without a matching TO)`,
290
+ section: skipped.section,
291
+ line: bodyStartLine + current.fromLine,
292
+ });
293
+ };
294
+ for (let i = 0; i < lines.length; i++) {
295
+ if (fenceMask[i])
296
+ continue;
297
+ const line = lines[i];
298
+ const fromMatch = line.match(/^\s*[-*+]?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
299
+ const toMatch = line.match(/^\s*[-*+]?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
300
+ if (fromMatch) {
301
+ // A prior FROM that never got its TO is about to be overwritten and
302
+ // discarded - the only chance to name it before it is gone.
303
+ flushUnpairedFrom();
304
+ current.from = normalizeRequirementName(fromMatch[1]);
305
+ current.fromLine = i;
306
+ }
307
+ else if (toMatch) {
308
+ current.to = normalizeRequirementName(toMatch[1]);
309
+ if (current.from && current.to) {
310
+ pairs.push({ from: current.from, to: current.to });
311
+ current = {};
312
+ }
313
+ }
314
+ }
315
+ flushUnpairedFrom();
316
+ return pairs;
317
+ }
318
+ /**
319
+ * Scenario names the current requirement block has and the incoming
320
+ * (MODIFIED) block does not. A MODIFIED requirement replaces the whole block,
321
+ * so every name reported here would be dropped from the main spec.
322
+ *
323
+ * Shared by archive (which refuses to apply the block) and validate (which
324
+ * reports the same loss at authoring time, #1477), so the two cannot disagree
325
+ * about what counts as a dropped scenario.
326
+ */
327
+ export function findMissingCurrentScenarios(current, incoming) {
328
+ // Multiplicity-aware: a name present N times in current and M times in
329
+ // incoming means max(0, N - M) instances are missing. Set membership would
330
+ // treat N>M as fully covered and let archive silently drop duplicates
331
+ // (residual #1246 / duplicate-scenario-name blind spot).
332
+ const remainingIncoming = new Map();
333
+ for (const scenario of parseScenarioBlocks(incoming.raw)) {
334
+ const name = scenario.name;
335
+ remainingIncoming.set(name, (remainingIncoming.get(name) ?? 0) + 1);
336
+ }
337
+ const missing = [];
338
+ for (const scenario of parseScenarioBlocks(current.raw)) {
339
+ const name = scenario.name;
340
+ const remaining = remainingIncoming.get(name) ?? 0;
341
+ if (remaining > 0) {
342
+ remainingIncoming.set(name, remaining - 1);
343
+ }
344
+ else {
345
+ missing.push(name);
346
+ }
347
+ }
348
+ return missing;
349
+ }
350
+ /**
351
+ * Any non-fenced level-4 header on the given (masked) line. Reuses the spec
352
+ * path's SCENARIO_HEADER so the two counters cannot drift apart.
353
+ */
354
+ function scenarioHeaderAt(lines, mask, index) {
355
+ return !mask[index] && SCENARIO_HEADER.test(lines[index]);
356
+ }
357
+ /**
358
+ * The scenario name for a `#### ` header, matching the label the author reads:
359
+ * the header text with the leading `####`, an optional CommonMark closing `#`
360
+ * run (`#### Foo ####` renders as `Foo`), and an optional `Scenario:` prefix
361
+ * stripped. Both the current and incoming blocks run through here, so the
362
+ * comparison in findMissingCurrentScenarios stays internally consistent
363
+ * regardless of label — and two headers that render to the same title (one
364
+ * ATX-closed, one not) are not mistaken for a dropped scenario.
365
+ */
366
+ function scenarioNameAt(line) {
367
+ return line
368
+ .replace(SCENARIO_HEADER, '')
369
+ // Optional ATX closing sequence. CommonMark only treats a trailing `#` run
370
+ // as a close when it is preceded by a space or tab — not any Unicode space —
371
+ // so this uses `[ \t]`, not `\s`. A looser `\s` could strip a `#` run after
372
+ // an exotic space (e.g. NBSP) that CommonMark keeps, folding two distinct
373
+ // scenario names into one and masking a real loss. `[ \t]` keeps the fold
374
+ // faithful to how the header actually renders.
375
+ .replace(/[ \t]+#+[ \t]*$/, '')
376
+ .replace(/^Scenario:\s*/i, '')
377
+ .trim();
378
+ }
379
+ function parseScenarioBlocks(requirementRaw) {
380
+ const lines = requirementRaw.replace(/\r\n?/g, '\n').split('\n');
381
+ // A scenario is ANY non-fenced `#### ` header, matching the spec path's
382
+ // SCENARIO_HEADER / countScenarios (requirement-text.ts) exactly — not only
383
+ // `#### Scenario:`. The two MUST agree: a level-4 child whose header is not
384
+ // literally `Scenario:` (e.g. `#### Edge case`) is still a scenario the spec
385
+ // path counts, so a MODIFIED block that drops it would otherwise slip past
386
+ // this loss check and be deleted by archive with no error (the parity the
387
+ // SCENARIO_HEADER comment warns not to break). A `####` inside a fenced
388
+ // example is masked out, matching countScenarios.
389
+ const mask = buildCodeFenceMask(lines);
390
+ const scenarios = [];
391
+ let index = 0;
392
+ while (index < lines.length) {
393
+ if (!scenarioHeaderAt(lines, mask, index)) {
394
+ index++;
395
+ continue;
396
+ }
397
+ const start = index;
398
+ const name = scenarioNameAt(lines[index]);
399
+ index++;
400
+ while (index < lines.length && !scenarioHeaderAt(lines, mask, index)) {
401
+ index++;
402
+ }
403
+ scenarios.push({
404
+ name,
405
+ raw: lines.slice(start, index).join('\n').trimEnd(),
406
+ });
407
+ }
408
+ return scenarios;
409
+ }
@@ -0,0 +1,55 @@
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
+ * A level-4 header. Deliberately matches ANY `####` header, not only
14
+ * `#### Scenario:` — the spec path treats every level-4 child of a requirement
15
+ * as a scenario, so the delta counter must too (parity). The delta/loss path
16
+ * reuses this exact constant via `scenarioHeaderAt` in requirement-blocks.ts;
17
+ * keep both paths on it rather than reintroducing a separate `Scenario:` regex.
18
+ */
19
+ export declare const SCENARIO_HEADER: RegExp;
20
+ /**
21
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
22
+ * as whole words so the change-delta reader and the schema-based reader accept
23
+ * and reject identical text.
24
+ */
25
+ export declare function containsShallOrMust(text: string): boolean;
26
+ /**
27
+ * Extract the full requirement body from the lines that follow a
28
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
29
+ *
30
+ * Captures every body line from the start up to the first header found on a
31
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
32
+ * divider the delta reader absorbed into the block — skipping blank lines and
33
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
34
+ * when other body text remains: a requirement written entirely as
35
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
36
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
37
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
38
+ * full.
39
+ */
40
+ export declare function extractRequirementBody(bodyLines: string[]): string;
41
+ /**
42
+ * Parser/display fallback for a requirement block with no body text. This is
43
+ * what lets a bare `### The system SHALL ...` header remain readable on the
44
+ * spec path (the title is the requirement). Validator body-keyword checks for
45
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
46
+ * a keyword that appears only in the header still receives the #1156/#1280
47
+ * body-keyword hint.
48
+ */
49
+ export declare function extractRequirementText(headerTitle: string, bodyLines: string[]): string;
50
+ /**
51
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
52
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
53
+ * scenario and is not counted.
54
+ */
55
+ export declare function countScenarios(bodyLines: string[]): number;
@@ -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). The delta/loss path
23
+ * reuses this exact constant via `scenarioHeaderAt` in requirement-blocks.ts;
24
+ * keep both paths on it rather than reintroducing a separate `Scenario:` regex.
25
+ */
26
+ export const SCENARIO_HEADER = /^####\s+/;
27
+ /**
28
+ * The one predicate for normative-keyword detection. Matches `SHALL` or `MUST`
29
+ * as whole words so the change-delta reader and the schema-based reader accept
30
+ * and reject identical text.
31
+ */
32
+ export function containsShallOrMust(text) {
33
+ return /\b(SHALL|MUST)\b/.test(text);
34
+ }
35
+ /**
36
+ * Extract the full requirement body from the lines that follow a
37
+ * `### Requirement:` header (the lines may include scenarios and fenced code).
38
+ *
39
+ * Captures every body line from the start up to the first header found on a
40
+ * non-fenced line — usually the first `#### Scenario:`, but also a stray `###`
41
+ * divider the delta reader absorbed into the block — skipping blank lines and
42
+ * any line inside a fenced code block. `**metadata**:` lines are skipped only
43
+ * when other body text remains: a requirement written entirely as
44
+ * `**Constraint**: The system MUST ...` keeps that line as its body. Captured
45
+ * lines are trimmed and joined with newlines so a requirement whose text wraps
46
+ * across lines — or whose `SHALL`/`MUST` lands on a later line — is read in
47
+ * full.
48
+ */
49
+ export function extractRequirementBody(bodyLines) {
50
+ const mask = buildCodeFenceMask(bodyLines);
51
+ const captured = [];
52
+ const metadata = [];
53
+ for (let i = 0; i < bodyLines.length; i++) {
54
+ if (mask[i])
55
+ continue; // inside a fenced code block
56
+ const line = bodyLines[i];
57
+ if (HEADER_LINE.test(line))
58
+ break; // first scenario or stray divider
59
+ const trimmed = line.trim();
60
+ if (trimmed.length === 0)
61
+ continue; // blank
62
+ if (METADATA_LINE.test(trimmed)) {
63
+ metadata.push(trimmed); // **ID**: / **Priority**: ...
64
+ continue;
65
+ }
66
+ captured.push(trimmed);
67
+ }
68
+ if (captured.length > 0)
69
+ return captured.join('\n');
70
+ return metadata.join('\n'); // metadata-only body: the metadata IS the body
71
+ }
72
+ /**
73
+ * Parser/display fallback for a requirement block with no body text. This is
74
+ * what lets a bare `### The system SHALL ...` header remain readable on the
75
+ * spec path (the title is the requirement). Validator body-keyword checks for
76
+ * canonical `### Requirement:` blocks use `extractRequirementBody` directly so
77
+ * a keyword that appears only in the header still receives the #1156/#1280
78
+ * body-keyword hint.
79
+ */
80
+ export function extractRequirementText(headerTitle, bodyLines) {
81
+ return extractRequirementBody(bodyLines) || headerTitle.trim();
82
+ }
83
+ /**
84
+ * Count the real scenarios in a requirement block: `#### ` headers on non-fenced
85
+ * lines. A `#### Scenario:` that lives inside a fenced example is not a real
86
+ * scenario and is not counted.
87
+ */
88
+ export function countScenarios(bodyLines) {
89
+ const mask = buildCodeFenceMask(bodyLines);
90
+ let count = 0;
91
+ for (let i = 0; i < bodyLines.length; i++) {
92
+ if (mask[i])
93
+ continue;
94
+ if (SCENARIO_HEADER.test(bodyLines[i]))
95
+ count++;
96
+ }
97
+ return count;
98
+ }
@@ -0,0 +1,8 @@
1
+ export interface MainSpecStructureIssue {
2
+ kind: 'delta-header' | 'requirement-outside-requirements' | 'duplicate-requirement';
3
+ line: number;
4
+ header: string;
5
+ message: string;
6
+ }
7
+ export declare function findMainSpecStructureIssues(content: string): MainSpecStructureIssue[];
8
+ export declare function stripFencedCodeBlocksPreservingLines(content: string): string;