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,171 @@
1
+ // FreeHarness addition (not upstream OpenSpec): resolution of MODIFIED / REMOVED /
2
+ // RENAMED delta headers against the current main spec.
3
+ //
4
+ // A MODIFIED or REMOVED block whose header is not in openspec/specs/<cap>/spec.md
5
+ // has nothing to act on at archive time: the fold either aborts (MODIFIED,
6
+ // RENAMED) or treats the removal as already synced (REMOVED) and the old
7
+ // requirement survives in the published spec. The validator reports every such
8
+ // header (ERROR under --strict, WARNING otherwise) and `openspec archive` refuses
9
+ // on them unless --force. When the header exists only in another in-flight
10
+ // change's ADDED block, the finding names that change: the fix is to archive it
11
+ // first.
12
+ import { promises as fs } from 'fs';
13
+ import path from 'path';
14
+ import { parseDeltaSpec, extractRequirementsSection, normalizeRequirementName, } from './parsers/requirement-blocks.js';
15
+ import { discoverSpecFiles } from './spec-discovery.js';
16
+ import { FileSystemUtils } from '../engine/file-system.js';
17
+ /**
18
+ * Builds the printed message for one unresolved header.
19
+ * @param operation Delta operation.
20
+ * @param header Requirement header.
21
+ * @param specId Capability id.
22
+ * @param addedBy In-flight change that ADDs the header, when known.
23
+ * @returns Message text.
24
+ */
25
+ export function unresolvedHeaderMessage(operation, header, specId, addedBy) {
26
+ const where = `openspec/specs/${specId}/spec.md${addedBy ? ` (ADDED by ${addedBy}, not archived yet)` : ''}`;
27
+ return (`${operation} "${header}" does not resolve in ${where} — archive would silently skip it and ` +
28
+ 'the old requirement would survive; fix the header or archive the change that ADDs it first');
29
+ }
30
+ /**
31
+ * Reads the requirement names of a main spec; empty when the spec is absent.
32
+ * @param mainSpecFile Absolute spec.md path.
33
+ * @returns Normalized requirement names.
34
+ */
35
+ async function currentRequirementNames(mainSpecFile) {
36
+ let content;
37
+ try {
38
+ content = await fs.readFile(mainSpecFile, 'utf-8');
39
+ }
40
+ catch (error) {
41
+ const code = error?.code;
42
+ if (code === 'ENOENT' || code === 'ENOTDIR')
43
+ return new Set();
44
+ throw error;
45
+ }
46
+ const names = new Set();
47
+ for (const block of extractRequirementsSection(content).bodyBlocks) {
48
+ names.add(normalizeRequirementName(block.name));
49
+ }
50
+ return names;
51
+ }
52
+ /**
53
+ * Indexes the ADDED headers of every other in-flight change: `specId` ->
54
+ * normalized header -> change id (first change wins).
55
+ * @param changesDir The openspec/changes directory (may be absent).
56
+ * @param selfChange Change id to exclude.
57
+ * @returns Index.
58
+ */
59
+ async function addedElsewhere(changesDir, selfChange) {
60
+ const index = new Map();
61
+ if (!changesDir)
62
+ return index;
63
+ let entries;
64
+ try {
65
+ entries = await fs.readdir(changesDir, { withFileTypes: true });
66
+ }
67
+ catch {
68
+ return index;
69
+ }
70
+ for (const entry of entries) {
71
+ if (!entry.isDirectory() || entry.name === 'archive' || entry.name.startsWith('.') || entry.name === selfChange)
72
+ continue;
73
+ let specs;
74
+ try {
75
+ specs = await discoverSpecFiles(path.join(changesDir, entry.name, 'specs'));
76
+ }
77
+ catch {
78
+ continue;
79
+ }
80
+ for (const { id, specFile } of specs) {
81
+ let content;
82
+ try {
83
+ content = await fs.readFile(specFile, 'utf-8');
84
+ }
85
+ catch {
86
+ continue;
87
+ }
88
+ const bySpec = index.get(id) ?? new Map();
89
+ for (const block of parseDeltaSpec(content).added) {
90
+ const key = normalizeRequirementName(block.name);
91
+ if (!bySpec.has(key))
92
+ bySpec.set(key, entry.name);
93
+ }
94
+ index.set(id, bySpec);
95
+ }
96
+ }
97
+ return index;
98
+ }
99
+ /**
100
+ * Lists the MODIFIED / REMOVED / RENAMED headers of a change that do not exist
101
+ * in the current main specs. A RENAMED whose FROM is gone but whose TO exists is
102
+ * an already-synced rename and resolves.
103
+ * @param changeDir Absolute change directory.
104
+ * @param mainSpecsDir Absolute openspec/specs directory.
105
+ * @param changesDir Absolute openspec/changes directory, for the ADDED-elsewhere
106
+ * attribution; omitted = no attribution.
107
+ * @returns Unresolved headers in delta-file order.
108
+ */
109
+ export async function findUnresolvedDeltaHeaders(changeDir, mainSpecsDir, changesDir) {
110
+ const specsDir = path.join(changeDir, 'specs');
111
+ let discovered;
112
+ try {
113
+ discovered = await discoverSpecFiles(specsDir);
114
+ }
115
+ catch (error) {
116
+ const code = error?.code;
117
+ if (code === 'ENOENT' || code === 'ENOTDIR')
118
+ return [];
119
+ throw error;
120
+ }
121
+ const out = [];
122
+ let elsewhere;
123
+ const changeName = path.basename(changeDir);
124
+ for (const { id: specId, specFile } of discovered) {
125
+ let content;
126
+ try {
127
+ content = await fs.readFile(specFile, 'utf-8');
128
+ }
129
+ catch {
130
+ continue;
131
+ }
132
+ const plan = parseDeltaSpec(content);
133
+ if (plan.modified.length + plan.removed.length + plan.renamed.length === 0)
134
+ continue;
135
+ const mainSpecFile = path.join(mainSpecsDir, ...specId.split('/'), 'spec.md');
136
+ FileSystemUtils.assertPathWithin(path.dirname(mainSpecFile), mainSpecFile);
137
+ const current = await currentRequirementNames(mainSpecFile);
138
+ const entryPath = FileSystemUtils.toPosixPath(path.relative(specsDir, specFile));
139
+ const report = async (operation, header) => {
140
+ elsewhere ??= await addedElsewhere(changesDir, changeName);
141
+ const addedBy = elsewhere.get(specId)?.get(normalizeRequirementName(header));
142
+ out.push({
143
+ operation,
144
+ header,
145
+ specId,
146
+ entryPath,
147
+ ...(addedBy ? { addedBy } : {}),
148
+ message: unresolvedHeaderMessage(operation, header, specId, addedBy),
149
+ });
150
+ };
151
+ const renamedTo = new Set(plan.renamed.map((r) => normalizeRequirementName(r.to)));
152
+ for (const block of plan.modified) {
153
+ const key = normalizeRequirementName(block.name);
154
+ // Archive applies RENAMED first, so a MODIFIED naming a rename target resolves.
155
+ if (current.has(key) || renamedTo.has(key))
156
+ continue;
157
+ await report('MODIFIED', block.name);
158
+ }
159
+ for (const name of plan.removed) {
160
+ if (current.has(normalizeRequirementName(name)))
161
+ continue;
162
+ await report('REMOVED', name);
163
+ }
164
+ for (const { from, to } of plan.renamed) {
165
+ if (current.has(normalizeRequirementName(from)) || current.has(normalizeRequirementName(to)))
166
+ continue;
167
+ await report('RENAMED', from);
168
+ }
169
+ }
170
+ return out;
171
+ }
@@ -0,0 +1,27 @@
1
+ import { MarkdownParser, Section } from './markdown-parser.js';
2
+ import { Change, Requirement } from '../schemas/index.js';
3
+ export declare class ChangeParser extends MarkdownParser {
4
+ private changeDir;
5
+ constructor(content: string, changeDir: string);
6
+ parseChangeWithDeltas(name: string): Promise<Change>;
7
+ private parseDeltaSpecs;
8
+ /**
9
+ * Read requirements from a delta section, ignoring headers that are not
10
+ * `### Requirement: <name>`.
11
+ *
12
+ * A delta section often carries divider headers such as
13
+ * `### Documentation Requirements`. The base parser treats every child header
14
+ * as a requirement, which invented a scenario-less requirement that does not
15
+ * exist (#498): archive warned about a missing scenario, and `show --json`
16
+ * reported an extra delta. The delta reader already skips these headers and
17
+ * notes them, so this keeps the two readers in agreement.
18
+ *
19
+ * Overriding here rather than in MarkdownParser keeps main spec parsing —
20
+ * `view`, `list`, `spec --json`, spec validation — untouched.
21
+ */
22
+ protected parseRequirements(section: Section): Requirement[];
23
+ private parseSpecDeltas;
24
+ private parseRenames;
25
+ private parseSectionsFromContent;
26
+ private getContentUntilNextHeaderFromLines;
27
+ }
@@ -0,0 +1,210 @@
1
+ import { MarkdownParser } from './markdown-parser.js';
2
+ import { buildCodeFenceMask } from './requirement-text.js';
3
+ import path from 'path';
4
+ import { promises as fs } from 'fs';
5
+ import { discoverSpecFiles } from '../spec-discovery.js';
6
+ export class ChangeParser extends MarkdownParser {
7
+ changeDir;
8
+ constructor(content, changeDir) {
9
+ super(content);
10
+ this.changeDir = changeDir;
11
+ }
12
+ async parseChangeWithDeltas(name) {
13
+ const sections = this.parseSections();
14
+ const why = this.findSection(sections, 'Why')?.content || '';
15
+ const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
16
+ if (!why) {
17
+ throw new Error('Change must have a Why section');
18
+ }
19
+ if (!whatChanges) {
20
+ throw new Error('Change must have a What Changes section');
21
+ }
22
+ // Parse deltas from the What Changes section (simple format)
23
+ const simpleDeltas = this.parseDeltas(whatChanges);
24
+ // Check if there are spec files with delta format
25
+ const specsDir = path.join(this.changeDir, 'specs');
26
+ const deltaDeltas = await this.parseDeltaSpecs(specsDir);
27
+ // Combine both types of deltas, preferring delta format if available
28
+ const deltas = deltaDeltas.length > 0 ? deltaDeltas : simpleDeltas;
29
+ return {
30
+ name,
31
+ why: why.trim(),
32
+ whatChanges: whatChanges.trim(),
33
+ deltas,
34
+ metadata: {
35
+ version: '1.0.0',
36
+ format: 'openspec-change',
37
+ },
38
+ };
39
+ }
40
+ async parseDeltaSpecs(specsDir) {
41
+ const deltas = [];
42
+ // Discover delta specs recursively so nested layouts like
43
+ // specs/<area>/<capability>/spec.md are parsed too (#1353)
44
+ const specFiles = await discoverSpecFiles(specsDir);
45
+ for (const { id, specFile } of specFiles) {
46
+ try {
47
+ const content = await fs.readFile(specFile, 'utf-8');
48
+ const specDeltas = this.parseSpecDeltas(id, content);
49
+ deltas.push(...specDeltas);
50
+ }
51
+ catch (error) {
52
+ // Spec file might not be readable, which is okay
53
+ continue;
54
+ }
55
+ }
56
+ return deltas;
57
+ }
58
+ /**
59
+ * Read requirements from a delta section, ignoring headers that are not
60
+ * `### Requirement: <name>`.
61
+ *
62
+ * A delta section often carries divider headers such as
63
+ * `### Documentation Requirements`. The base parser treats every child header
64
+ * as a requirement, which invented a scenario-less requirement that does not
65
+ * exist (#498): archive warned about a missing scenario, and `show --json`
66
+ * reported an extra delta. The delta reader already skips these headers and
67
+ * notes them, so this keeps the two readers in agreement.
68
+ *
69
+ * Overriding here rather than in MarkdownParser keeps main spec parsing —
70
+ * `view`, `list`, `spec --json`, spec validation — untouched.
71
+ */
72
+ parseRequirements(section) {
73
+ return super.parseRequirements({
74
+ ...section,
75
+ children: section.children.filter((child) => /^Requirement:\s*\S/i.test(child.title.trim())),
76
+ });
77
+ }
78
+ parseSpecDeltas(specName, content) {
79
+ const deltas = [];
80
+ const sections = this.parseSectionsFromContent(content);
81
+ // Parse ADDED requirements
82
+ const addedSection = this.findSection(sections, 'ADDED Requirements');
83
+ if (addedSection) {
84
+ const requirements = this.parseRequirements(addedSection);
85
+ requirements.forEach(req => {
86
+ deltas.push({
87
+ spec: specName,
88
+ operation: 'ADDED',
89
+ description: `Add requirement: ${req.text}`,
90
+ // Provide both single and plural forms for compatibility
91
+ requirement: req,
92
+ requirements: [req],
93
+ });
94
+ });
95
+ }
96
+ // Parse MODIFIED requirements
97
+ const modifiedSection = this.findSection(sections, 'MODIFIED Requirements');
98
+ if (modifiedSection) {
99
+ const requirements = this.parseRequirements(modifiedSection);
100
+ requirements.forEach(req => {
101
+ deltas.push({
102
+ spec: specName,
103
+ operation: 'MODIFIED',
104
+ description: `Modify requirement: ${req.text}`,
105
+ requirement: req,
106
+ requirements: [req],
107
+ });
108
+ });
109
+ }
110
+ // Parse REMOVED requirements
111
+ const removedSection = this.findSection(sections, 'REMOVED Requirements');
112
+ if (removedSection) {
113
+ const requirements = this.parseRequirements(removedSection);
114
+ requirements.forEach(req => {
115
+ deltas.push({
116
+ spec: specName,
117
+ operation: 'REMOVED',
118
+ description: `Remove requirement: ${req.text}`,
119
+ requirement: req,
120
+ requirements: [req],
121
+ });
122
+ });
123
+ }
124
+ // Parse RENAMED requirements
125
+ const renamedSection = this.findSection(sections, 'RENAMED Requirements');
126
+ if (renamedSection) {
127
+ const renames = this.parseRenames(renamedSection.content);
128
+ renames.forEach(rename => {
129
+ deltas.push({
130
+ spec: specName,
131
+ operation: 'RENAMED',
132
+ description: `Rename requirement from "${rename.from}" to "${rename.to}"`,
133
+ rename,
134
+ });
135
+ });
136
+ }
137
+ return deltas;
138
+ }
139
+ parseRenames(content) {
140
+ const renames = [];
141
+ const lines = ChangeParser.normalizeContent(content).split('\n');
142
+ let currentRename = {};
143
+ for (const line of lines) {
144
+ const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
145
+ const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
146
+ if (fromMatch) {
147
+ currentRename.from = fromMatch[1].trim();
148
+ }
149
+ else if (toMatch) {
150
+ currentRename.to = toMatch[1].trim();
151
+ if (currentRename.from && currentRename.to) {
152
+ renames.push({
153
+ from: currentRename.from,
154
+ to: currentRename.to,
155
+ });
156
+ currentRename = {};
157
+ }
158
+ }
159
+ }
160
+ return renames;
161
+ }
162
+ parseSectionsFromContent(content) {
163
+ const normalizedContent = ChangeParser.normalizeContent(content);
164
+ const lines = normalizedContent.split('\n');
165
+ const codeFenceLineMask = buildCodeFenceMask(lines);
166
+ const sections = [];
167
+ const stack = [];
168
+ for (let i = 0; i < lines.length; i++) {
169
+ const line = lines[i];
170
+ if (codeFenceLineMask[i]) {
171
+ continue;
172
+ }
173
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
174
+ if (headerMatch) {
175
+ const level = headerMatch[1].length;
176
+ const title = headerMatch[2].trim();
177
+ const contentLines = this.getContentUntilNextHeaderFromLines(lines, codeFenceLineMask, i + 1, level);
178
+ const section = {
179
+ level,
180
+ title,
181
+ content: contentLines.join('\n').trim(),
182
+ children: [],
183
+ };
184
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
185
+ stack.pop();
186
+ }
187
+ if (stack.length === 0) {
188
+ sections.push(section);
189
+ }
190
+ else {
191
+ stack[stack.length - 1].children.push(section);
192
+ }
193
+ stack.push(section);
194
+ }
195
+ }
196
+ return sections;
197
+ }
198
+ getContentUntilNextHeaderFromLines(lines, codeFenceLineMask, startLine, currentLevel) {
199
+ const contentLines = [];
200
+ for (let i = startLine; i < lines.length; i++) {
201
+ const line = lines[i];
202
+ const headerMatch = codeFenceLineMask[i] ? null : line.match(/^(#{1,6})\s+/);
203
+ if (headerMatch && headerMatch[1].length <= currentLevel) {
204
+ break;
205
+ }
206
+ contentLines.push(line);
207
+ }
208
+ return contentLines;
209
+ }
210
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared fenced-code-block detection for the Markdown parsers.
3
+ *
4
+ * Several parsers need to ignore Markdown structure (headers, requirement
5
+ * blocks, scenarios, delta sections) that appears inside fenced code blocks.
6
+ * Keeping this logic in one place avoids the drift that previously left
7
+ * `requirement-blocks.ts` treating fenced `### Requirement:` lines as real
8
+ * requirements during validation and archiving.
9
+ */
10
+ /**
11
+ * Builds a per-line mask where `true` marks a line that is part of a fenced
12
+ * code block (including the opening and closing fence lines themselves).
13
+ */
14
+ export declare function buildCodeFenceMask(lines: string[]): boolean[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared fenced-code-block detection for the Markdown parsers.
3
+ *
4
+ * Several parsers need to ignore Markdown structure (headers, requirement
5
+ * blocks, scenarios, delta sections) that appears inside fenced code blocks.
6
+ * Keeping this logic in one place avoids the drift that previously left
7
+ * `requirement-blocks.ts` treating fenced `### Requirement:` lines as real
8
+ * requirements during validation and archiving.
9
+ */
10
+ function getFenceMarker(line) {
11
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
12
+ if (!fenceMatch) {
13
+ return null;
14
+ }
15
+ return {
16
+ marker: fenceMatch[1][0],
17
+ length: fenceMatch[1].length,
18
+ };
19
+ }
20
+ function isClosingFence(line, activeFence) {
21
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
22
+ return Boolean(fenceMatch &&
23
+ fenceMatch[1][0] === activeFence.marker &&
24
+ fenceMatch[1].length >= activeFence.length);
25
+ }
26
+ /**
27
+ * Builds a per-line mask where `true` marks a line that is part of a fenced
28
+ * code block (including the opening and closing fence lines themselves).
29
+ */
30
+ export function buildCodeFenceMask(lines) {
31
+ const mask = new Array(lines.length).fill(false);
32
+ let activeFence = null;
33
+ for (let i = 0; i < lines.length; i++) {
34
+ if (!activeFence) {
35
+ const fence = getFenceMarker(lines[i]);
36
+ if (fence) {
37
+ activeFence = fence;
38
+ mask[i] = true;
39
+ }
40
+ continue;
41
+ }
42
+ mask[i] = true;
43
+ if (isClosingFence(lines[i], activeFence)) {
44
+ activeFence = null;
45
+ }
46
+ }
47
+ return mask;
48
+ }
@@ -0,0 +1,22 @@
1
+ import { Spec, Change, Requirement, Scenario, Delta } from '../schemas/index.js';
2
+ export interface Section {
3
+ level: number;
4
+ title: string;
5
+ content: string;
6
+ children: Section[];
7
+ }
8
+ export declare class MarkdownParser {
9
+ private lines;
10
+ private codeFenceLineMask;
11
+ private currentLine;
12
+ constructor(content: string);
13
+ protected static normalizeContent(content: string): string;
14
+ parseSpec(name: string): Spec;
15
+ parseChange(name: string): Change;
16
+ protected parseSections(): Section[];
17
+ protected getContentUntilNextHeader(startLine: number, currentLevel: number): string;
18
+ protected findSection(sections: Section[], title: string): Section | undefined;
19
+ protected parseRequirements(section: Section): Requirement[];
20
+ protected parseScenarios(requirementSection: Section): Scenario[];
21
+ protected parseDeltas(content: string): Delta[];
22
+ }
@@ -0,0 +1,186 @@
1
+ import { buildCodeFenceMask, extractRequirementText } from './requirement-text.js';
2
+ /**
3
+ * EARS scenario title: `Scenario: WHEN <event> THE SYSTEM SHALL <behavior>`
4
+ * (also WHILE/WHERE/IF ... SHALL). Case-insensitive; the title is the text
5
+ * after `#### ` so it still starts with `Scenario:`.
6
+ */
7
+ const EARS_SCENARIO_TITLE = /^Scenario:\s*(?:WHEN|WHILE|WHERE|IF)\b[\s\S]*\b(?:SHALL|MUST)\b/i;
8
+ export class MarkdownParser {
9
+ lines;
10
+ codeFenceLineMask;
11
+ currentLine;
12
+ constructor(content) {
13
+ const normalized = MarkdownParser.normalizeContent(content);
14
+ this.lines = normalized.split('\n');
15
+ this.codeFenceLineMask = buildCodeFenceMask(this.lines);
16
+ this.currentLine = 0;
17
+ }
18
+ static normalizeContent(content) {
19
+ // Strip a UTF-8 BOM so a header on the first line still matches.
20
+ return content.replace(/^/, '').replace(/\r\n?/g, '\n');
21
+ }
22
+ parseSpec(name) {
23
+ const sections = this.parseSections();
24
+ const purpose = this.findSection(sections, 'Purpose')?.content || '';
25
+ const requirementsSection = this.findSection(sections, 'Requirements');
26
+ if (!purpose) {
27
+ throw new Error('Spec must have a Purpose section');
28
+ }
29
+ if (!requirementsSection) {
30
+ throw new Error('Spec must have a Requirements section');
31
+ }
32
+ const requirements = this.parseRequirements(requirementsSection);
33
+ return {
34
+ name,
35
+ overview: purpose.trim(),
36
+ requirements,
37
+ metadata: {
38
+ version: '1.0.0',
39
+ format: 'openspec',
40
+ },
41
+ };
42
+ }
43
+ parseChange(name) {
44
+ const sections = this.parseSections();
45
+ const why = this.findSection(sections, 'Why')?.content || '';
46
+ const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
47
+ if (!why) {
48
+ throw new Error('Change must have a Why section');
49
+ }
50
+ if (!whatChanges) {
51
+ throw new Error('Change must have a What Changes section');
52
+ }
53
+ const deltas = this.parseDeltas(whatChanges);
54
+ return {
55
+ name,
56
+ why: why.trim(),
57
+ whatChanges: whatChanges.trim(),
58
+ deltas,
59
+ metadata: {
60
+ version: '1.0.0',
61
+ format: 'openspec-change',
62
+ },
63
+ };
64
+ }
65
+ parseSections() {
66
+ const sections = [];
67
+ const stack = [];
68
+ for (let i = 0; i < this.lines.length; i++) {
69
+ const line = this.lines[i];
70
+ if (this.codeFenceLineMask[i]) {
71
+ continue;
72
+ }
73
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
74
+ if (headerMatch) {
75
+ const level = headerMatch[1].length;
76
+ const title = headerMatch[2].trim();
77
+ const content = this.getContentUntilNextHeader(i + 1, level);
78
+ const section = {
79
+ level,
80
+ title,
81
+ content,
82
+ children: [],
83
+ };
84
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
85
+ stack.pop();
86
+ }
87
+ if (stack.length === 0) {
88
+ sections.push(section);
89
+ }
90
+ else {
91
+ stack[stack.length - 1].children.push(section);
92
+ }
93
+ stack.push(section);
94
+ }
95
+ }
96
+ return sections;
97
+ }
98
+ getContentUntilNextHeader(startLine, currentLevel) {
99
+ const contentLines = [];
100
+ for (let i = startLine; i < this.lines.length; i++) {
101
+ const line = this.lines[i];
102
+ const headerMatch = this.codeFenceLineMask[i] ? null : line.match(/^(#{1,6})\s+/);
103
+ if (headerMatch && headerMatch[1].length <= currentLevel) {
104
+ break;
105
+ }
106
+ contentLines.push(line);
107
+ }
108
+ return contentLines.join('\n').trim();
109
+ }
110
+ findSection(sections, title) {
111
+ for (const section of sections) {
112
+ if (section.title.toLowerCase() === title.toLowerCase()) {
113
+ return section;
114
+ }
115
+ const child = this.findSection(section.children, title);
116
+ if (child) {
117
+ return child;
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ parseRequirements(section) {
123
+ const requirements = [];
124
+ for (const child of section.children) {
125
+ // Read the requirement text via the shared reader (multi-line, fence- and
126
+ // metadata-aware, with the shared header-title fallback for empty bodies).
127
+ const text = extractRequirementText(child.title, child.content.split('\n'));
128
+ const scenarios = this.parseScenarios(child);
129
+ requirements.push({
130
+ text,
131
+ scenarios,
132
+ });
133
+ }
134
+ return requirements;
135
+ }
136
+ parseScenarios(requirementSection) {
137
+ const scenarios = [];
138
+ for (const scenarioSection of requirementSection.children) {
139
+ // Store the raw text content of the scenario section
140
+ if (scenarioSection.content.trim()) {
141
+ scenarios.push({
142
+ rawText: scenarioSection.content
143
+ });
144
+ }
145
+ else if (EARS_SCENARIO_TITLE.test(scenarioSection.title)) {
146
+ // FreeHarness V4-P4 (4.5): an EARS scenario carries its whole behavior
147
+ // in the header (`#### Scenario: WHEN <event> THE SYSTEM SHALL <behavior>`)
148
+ // and may have no body. Upstream drops body-less scenarios, which would
149
+ // leave the requirement without one; the header text is the scenario.
150
+ scenarios.push({ rawText: scenarioSection.title });
151
+ }
152
+ }
153
+ return scenarios;
154
+ }
155
+ parseDeltas(content) {
156
+ const deltas = [];
157
+ const lines = content.split('\n');
158
+ for (const line of lines) {
159
+ // Match both formats: **spec:** and **spec**:
160
+ const deltaMatch = line.match(/^\s*-\s*\*\*([^*:]+)(?::\*\*|\*\*:)\s*(.+)$/);
161
+ if (deltaMatch) {
162
+ const specName = deltaMatch[1].trim();
163
+ const description = deltaMatch[2].trim();
164
+ let operation = 'MODIFIED';
165
+ const lowerDesc = description.toLowerCase();
166
+ // Use word boundaries to avoid false matches (e.g., "address" matching "add")
167
+ // Check RENAMED first since it's more specific than patterns containing "new"
168
+ if (/\brename(s|d|ing)?\b/.test(lowerDesc) || /\brenamed\s+(to|from)\b/.test(lowerDesc)) {
169
+ operation = 'RENAMED';
170
+ }
171
+ else if (/\badd(s|ed|ing)?\b/.test(lowerDesc) || /\bcreate(s|d|ing)?\b/.test(lowerDesc) || /\bnew\b/.test(lowerDesc)) {
172
+ operation = 'ADDED';
173
+ }
174
+ else if (/\bremove(s|d|ing)?\b/.test(lowerDesc) || /\bdelete(s|d|ing)?\b/.test(lowerDesc)) {
175
+ operation = 'REMOVED';
176
+ }
177
+ deltas.push({
178
+ spec: specName,
179
+ operation,
180
+ description,
181
+ });
182
+ }
183
+ }
184
+ return deltas;
185
+ }
186
+ }