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,483 @@
1
+ // `openspec archive <id>` — folds a change's delta specs into openspec/specs/
2
+ // and moves the change into openspec/changes/archive/.
3
+ //
4
+ // Non-interactive and store-free. Built on the vendored specs-apply slice
5
+ // (vendor/openspec/apply/specs-apply.ts) and the vendored Validator. This is the
6
+ // one command that writes the spec source of truth, so it (1) refuses to archive
7
+ // an invalid change and (2) folds every delta before moving the change, never a
8
+ // move-without-fold that would leave openspec/specs/ stale.
9
+ //
10
+ // V4-P4 additions:
11
+ // - Archive blockers (#1710 findArchiveBlockers, reported INFO by the validator)
12
+ // and unchecked tasks are listed before anything is written; without `--yes`
13
+ // they refuse the archive (upstream prompts; this shim is non-interactive).
14
+ // - Supersession: a `Supersedes: <change-id>` line in the change's design.md
15
+ // names an archived change this one replaces. Archive prints the link, writes
16
+ // the reverse link (`Superseded-by: <id>`) into the archived record, and
17
+ // refuses a MODIFIED requirement that carries a superseded requirement's text
18
+ // unchanged (an in-place edit of a published decision).
19
+ // - Unresolved delta headers: a MODIFIED / REMOVED / RENAMED whose header is not
20
+ // in openspec/specs/<cap>/spec.md is listed with the blockers and refuses the
21
+ // archive unless `--force`, which folds the rest and skips those deltas
22
+ // (vendor/openspec/validation/delta-resolution.ts).
23
+ // - Metadata `depends_on` / `supersedes`: an unarchived `depends_on` change is
24
+ // listed among the blockers (refusal, same as an unchecked task); `supersedes`
25
+ // cross-links archived changes the same way the design.md `Supersedes:` line
26
+ // does, as the union of both sources.
27
+ import { promises as fs } from 'node:fs';
28
+ import * as fssync from 'node:fs';
29
+ import * as path from 'node:path';
30
+ import { Validator } from '../../vendor/openspec/validation/validator.js';
31
+ import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from '../../vendor/openspec/apply/specs-apply.js';
32
+ import { parseDeltaSpec } from '../../vendor/openspec/validation/parsers/requirement-blocks.js';
33
+ import { discoverSpecFiles } from '../../vendor/openspec/validation/spec-discovery.js';
34
+ import { findUnresolvedDeltaHeaders, } from '../../vendor/openspec/validation/delta-resolution.js';
35
+ import { parseAnnotatedTaskLines } from '../../vendor/openspec/validation/task-lines.js';
36
+ import { readChangeMetadata } from '../../vendor/openspec/engine/change-metadata.js';
37
+ import { changesDirFor, resolveChangeId, taskLabel, validateChangeExists } from './shared.js';
38
+ import { readFollowUps } from './follow-up.js';
39
+ const SUPERSEDES_LINE = /^\s*Supersedes:\s*(.+?)\s*$/gm;
40
+ const SUPERSEDED_BY_PREFIX = 'Superseded-by:';
41
+ /**
42
+ * Reads the `Supersedes: <change-id>[, <change-id>...]` lines of a design.md.
43
+ * Convention: one or more lines, each `Supersedes:` followed by archived change
44
+ * ids separated by commas. Absent lines -> empty list. Template placeholders
45
+ * live in HTML comments; a line left as `Supersedes: <!-- ... -->` names nothing.
46
+ */
47
+ function parseDesignSupersedes(designText) {
48
+ const content = designText.replace(/<!--[\s\S]*?-->/g, '');
49
+ const ids = [];
50
+ for (const match of content.matchAll(SUPERSEDES_LINE)) {
51
+ for (const id of match[1].split(',')) {
52
+ const trimmed = id.trim().replace(/^`|`$/g, '');
53
+ if (trimmed && !ids.includes(trimmed))
54
+ ids.push(trimmed);
55
+ }
56
+ }
57
+ return ids;
58
+ }
59
+ /**
60
+ * Resolves the ids a change supersedes: the deduplicated union of design.md's
61
+ * `Supersedes:` lines and the metadata `supersedes` list, design.md first.
62
+ */
63
+ export function readSupersedes(designText, metadata) {
64
+ const ids = parseDesignSupersedes(designText);
65
+ for (const id of metadata?.supersedes ?? []) {
66
+ if (!ids.includes(id))
67
+ ids.push(id);
68
+ }
69
+ return ids;
70
+ }
71
+ /** Collapses whitespace so two requirement blocks compare on their words. */
72
+ function normalizeBlockText(raw) {
73
+ return raw.replace(/\s+/g, ' ').trim();
74
+ }
75
+ /** Every requirement block (any delta section) declared by a change's specs. */
76
+ async function collectRequirementBlocks(changeDir) {
77
+ const specsDir = path.join(changeDir, 'specs');
78
+ const out = [];
79
+ for (const file of await safeDiscover(specsDir)) {
80
+ const content = await fs.readFile(file.specFile, 'utf-8');
81
+ const plan = parseDeltaSpec(content);
82
+ for (const block of [...plan.added, ...plan.modified])
83
+ out.push({ spec: file.id, block });
84
+ }
85
+ return out;
86
+ }
87
+ /**
88
+ * Resolves the change's `Supersedes:` lines and enforces the supersession
89
+ * rules: every id names an archived change, and no MODIFIED requirement of this
90
+ * change repeats a superseded requirement's text unchanged.
91
+ */
92
+ export async function checkSupersession(changeName, changeDir, changesDir, metadata) {
93
+ const designPath = path.join(changeDir, 'design.md');
94
+ const designText = fssync.existsSync(designPath) ? fssync.readFileSync(designPath, 'utf-8') : '';
95
+ const ids = readSupersedes(designText, metadata);
96
+ if (ids.length === 0)
97
+ return [];
98
+ const links = [];
99
+ // Only MODIFIED blocks are in-place edits; ADDED blocks are new decisions.
100
+ const modifiedOnly = [];
101
+ for (const file of await safeDiscover(path.join(changeDir, 'specs'))) {
102
+ const plan = parseDeltaSpec(await fs.readFile(file.specFile, 'utf-8'));
103
+ for (const block of plan.modified)
104
+ modifiedOnly.push({ spec: file.id, block });
105
+ }
106
+ for (const id of ids) {
107
+ if (id === changeName) {
108
+ throw new Error(`Change '${changeName}' declares 'Supersedes: ${id}' naming itself. Fix: name the archived change it replaces.`);
109
+ }
110
+ const archived = path.join(changesDir, 'archive', id);
111
+ if (!fssync.existsSync(archived) || !fssync.statSync(archived).isDirectory()) {
112
+ throw new Error(`Change '${changeName}' declares 'Supersedes: ${id}' but openspec/changes/archive/${id} does not exist. ` +
113
+ `Fix: supersede only archived changes (check the id with 'ls openspec/changes/archive'), or archive '${id}' first.`);
114
+ }
115
+ const superseded = await collectRequirementBlocks(archived);
116
+ for (const { spec, block } of modifiedOnly) {
117
+ const same = superseded.find((s) => normalizeBlockText(s.block.raw) === normalizeBlockText(block.raw));
118
+ if (same) {
119
+ throw new Error(`Change '${changeName}' supersedes '${id}' but MODIFIED "${block.name}" (specs/${spec}) repeats the ` +
120
+ `superseded requirement text unchanged: a published decision is never edited in place. ` +
121
+ `Fix: state the new decision as ADDED (or RENAMED) with its own text and leave the superseded text in ` +
122
+ `openspec/changes/archive/${id}; when only part of the decision changes, keep the original requirement ` +
123
+ `active and cross-link it.`);
124
+ }
125
+ }
126
+ links.push({ change: id, path: archived });
127
+ }
128
+ return links;
129
+ }
130
+ async function safeDiscover(specsDir) {
131
+ try {
132
+ return await discoverSpecFiles(specsDir);
133
+ }
134
+ catch (error) {
135
+ if (error.code === 'ENOENT')
136
+ return [];
137
+ throw error;
138
+ }
139
+ }
140
+ /** Appends the reverse link to the archived record's design.md (idempotent). */
141
+ async function writeSupersededBy(link, changeName) {
142
+ const designPath = path.join(link.path, 'design.md');
143
+ const line = `${SUPERSEDED_BY_PREFIX} ${changeName}`;
144
+ const existing = fssync.existsSync(designPath) ? await fs.readFile(designPath, 'utf-8') : '';
145
+ if (existing.split('\n').some((l) => l.trim() === line))
146
+ return;
147
+ const separator = existing.length === 0 ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
148
+ await fs.writeFile(designPath, `${existing}${separator}${line}\n`, 'utf-8');
149
+ }
150
+ /**
151
+ * Lines that describe what archive would refuse or what is still open. The
152
+ * unresolved headers are listed here as well so one refusal names everything.
153
+ */
154
+ export function listArchiveBlockers(changeDir, issues, unresolved = []) {
155
+ const blockers = [];
156
+ for (const issue of issues) {
157
+ if (issue.level === 'INFO' && /^Archive would refuse this delta/.test(issue.message)) {
158
+ blockers.push(`[INFO] ${issue.path}: ${issue.message}`);
159
+ }
160
+ }
161
+ for (const u of unresolved)
162
+ blockers.push(`[WARNING] ${u.entryPath}: ${u.message}`);
163
+ const tasksPath = path.join(changeDir, 'tasks.md');
164
+ if (fssync.existsSync(tasksPath)) {
165
+ // A dispositioned task is resolved outside this change (see
166
+ // dispositionedTasks below), so it is never a blocker; only a plain
167
+ // unchecked task refuses the archive.
168
+ const open = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8')).filter((t) => !t.done && !t.disposition);
169
+ if (open.length > 0) {
170
+ blockers.push(`[INFO] tasks.md: ${open.length} unchecked task(s); first: ${open[0].description || '(no description)'}`);
171
+ }
172
+ }
173
+ return blockers;
174
+ }
175
+ const DISPOSITION_KINDS = new Set(['superseded', 'moved', 'external']);
176
+ /**
177
+ * Why a task's disposition does not resolve it, or null when it does: a kind
178
+ * outside the three documented ones, an `external` line without a reference,
179
+ * or a `superseded`/`moved` target `resolveChangeId` cannot place in flight or
180
+ * archived. Re-checked here (not merely trusted from an earlier `validate`
181
+ * run) the same way `listDependencyBlockers` below re-checks `depends_on`.
182
+ */
183
+ function dispositionBlockerReason(projectRoot, disposition) {
184
+ const { kind, target } = disposition;
185
+ if (!DISPOSITION_KINDS.has(kind)) {
186
+ return `disposition kind '${kind}' is none of superseded, moved, external`;
187
+ }
188
+ if (kind === 'external') {
189
+ return target.trim() === '' ? 'an external disposition needs a reference' : null;
190
+ }
191
+ return resolveChangeId(projectRoot, target) === null
192
+ ? `${kind} disposition names unresolved change '${target}'`
193
+ : null;
194
+ }
195
+ /**
196
+ * The change's dispositioned tasks (unchecked, carrying a `Disposition:`
197
+ * line), for the archive result to print grouped by kind. `listArchiveBlockers`
198
+ * above already excludes these from the unchecked-task blocker; a task whose
199
+ * disposition does not resolve is still listed here for display, but also
200
+ * comes back as a blocker from `listDispositionBlockers`.
201
+ */
202
+ function dispositionedTasks(changeDir) {
203
+ const tasksPath = path.join(changeDir, 'tasks.md');
204
+ if (!fssync.existsSync(tasksPath))
205
+ return [];
206
+ const tasks = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8'));
207
+ const out = [];
208
+ for (const task of tasks) {
209
+ if (task.done || !task.disposition)
210
+ continue;
211
+ out.push({
212
+ kind: task.disposition.kind,
213
+ task: taskLabel(task),
214
+ target: task.disposition.target,
215
+ });
216
+ }
217
+ return out;
218
+ }
219
+ /**
220
+ * Lists every unchecked task whose disposition does not resolve it (an
221
+ * unknown kind, an unresolved `superseded`/`moved` target, or an empty
222
+ * `external` reference), naming the task and the reason. Such a task stays a
223
+ * blocker exactly like a plain unchecked task; a resolving disposition never
224
+ * appears here.
225
+ */
226
+ function listDispositionBlockers(projectRoot, changeDir) {
227
+ const tasksPath = path.join(changeDir, 'tasks.md');
228
+ if (!fssync.existsSync(tasksPath))
229
+ return [];
230
+ const tasks = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8'));
231
+ const blockers = [];
232
+ for (const task of tasks) {
233
+ if (task.done || !task.disposition)
234
+ continue;
235
+ const reason = dispositionBlockerReason(projectRoot, task.disposition);
236
+ if (reason) {
237
+ blockers.push(`[INFO] tasks.md: task ${taskLabel(task)} disposition does not resolve it (${reason})`);
238
+ }
239
+ }
240
+ return blockers;
241
+ }
242
+ /**
243
+ * Lists every `depends_on` change that is not archived, naming it and its
244
+ * current state (in flight, or unresolved). Has the same effect as an
245
+ * unchecked task: archiving without `--yes` is refused while any are open.
246
+ */
247
+ function listDependencyBlockers(projectRoot, metadata) {
248
+ const blockers = [];
249
+ for (const id of metadata?.depends_on ?? []) {
250
+ const resolution = resolveChangeId(projectRoot, id);
251
+ if (resolution !== 'archived') {
252
+ blockers.push(`[INFO] depends_on: '${id}' is not archived (${resolution ?? 'unresolved'})`);
253
+ }
254
+ }
255
+ return blockers;
256
+ }
257
+ /**
258
+ * The validator's "Archive would refuse this delta" pass re-run the way a
259
+ * `--force` fold runs (unresolved headers skipped), so a waived header is not
260
+ * still listed as a refusal while a real conflict (an ADDED that already
261
+ * exists) still is.
262
+ */
263
+ async function forcedFoldBlockers(changeDir, specsDir, changeName) {
264
+ const out = [];
265
+ let updates;
266
+ try {
267
+ updates = await findSpecUpdates(changeDir, specsDir);
268
+ }
269
+ catch (error) {
270
+ return [`[INFO] specs: Could not check archive merge conflicts: ${error instanceof Error ? error.message : String(error)}`];
271
+ }
272
+ for (const update of updates) {
273
+ try {
274
+ await buildUpdatedSpec(update, changeName, { silent: true, skipUnresolved: true });
275
+ }
276
+ catch (error) {
277
+ if (error?.code !== undefined)
278
+ continue;
279
+ out.push(`[INFO] ${update.id}/spec.md: Archive would refuse this delta: ${error instanceof Error ? error.message : String(error)}`);
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+ /**
285
+ * Archive a change: validate it, fold its delta specs into openspec/specs/, then
286
+ * move openspec/changes/<name>/ to openspec/changes/archive/<name>/.
287
+ *
288
+ * Throws (with an actionable message) rather than partially archiving when the
289
+ * change is invalid, a fold fails, or the archive destination is already taken.
290
+ */
291
+ export async function archiveChange(changeName, { projectRoot, yes, force }) {
292
+ // Resolve + guard the change name against the active changes directory.
293
+ const name = validateChangeExists(changeName, projectRoot);
294
+ const changesDir = changesDirFor(projectRoot);
295
+ const changeDir = path.join(changesDir, name);
296
+ const specsDir = path.join(projectRoot, 'openspec', 'specs');
297
+ // Metadata (.openspec.yaml) drives the depends_on blockers and the supersedes
298
+ // union below. Metadata that cannot be read degrades to "no dependency/supersedes
299
+ // data" rather than aborting the archive, but the lost gate is listed among the
300
+ // blockers: without that line the archive would run with its dependency check
301
+ // silently skipped.
302
+ let metadata;
303
+ let metadataBlockers = [];
304
+ try {
305
+ metadata = readChangeMetadata(changeDir, projectRoot);
306
+ }
307
+ catch (error) {
308
+ metadata = null;
309
+ const reason = error instanceof Error ? error.message : String(error);
310
+ metadataBlockers = [`[INFO] metadata unreadable: ${reason}; depends_on and supersedes not checked`];
311
+ }
312
+ // 1. Validate the change. This is the authoritative delta-spec pass — the same
313
+ // verdict `openspec validate <change>` uses. Abort on any ERROR; an empty
314
+ // change (nothing to validate) reports valid and proceeds.
315
+ const validator = new Validator(false);
316
+ const report = await validator.validateChangeDeltaSpecs(changeDir, {
317
+ mainSpecsDir: specsDir,
318
+ projectRoot,
319
+ });
320
+ if (!report.valid) {
321
+ const details = report.issues
322
+ .filter((i) => i.level === 'ERROR')
323
+ .map((i) => ` [${i.level}] ${i.line ? `${i.path}:${i.line}` : i.path}: ${i.message}`)
324
+ .join('\n');
325
+ throw new Error(`Change '${name}' is not valid; refusing to archive:\n${details}`);
326
+ }
327
+ // 1b. Blockers: deltas archive would refuse (validator INFO, #1710),
328
+ // unresolved delta headers (unless --force already waives them) and
329
+ // unchecked tasks. Listed together; without --yes they refuse the archive
330
+ // with nothing written (upstream asks; this shim cannot).
331
+ const unresolved = await findUnresolvedDeltaHeaders(changeDir, specsDir, changesDir);
332
+ const dependencyBlockers = listDependencyBlockers(projectRoot, metadata);
333
+ const dispositionBlockers = listDispositionBlockers(projectRoot, changeDir);
334
+ const blockers = force
335
+ ? [
336
+ ...(await forcedFoldBlockers(changeDir, specsDir, name)),
337
+ ...listArchiveBlockers(changeDir, report.issues.filter((i) => !/^Archive would refuse this delta/.test(i.message))),
338
+ ...metadataBlockers,
339
+ ...dependencyBlockers,
340
+ ...dispositionBlockers,
341
+ ]
342
+ : [
343
+ ...listArchiveBlockers(changeDir, report.issues, unresolved),
344
+ ...metadataBlockers,
345
+ ...dependencyBlockers,
346
+ ...dispositionBlockers,
347
+ ];
348
+ if (blockers.length > 0 && !yes) {
349
+ throw new Error(`Change '${name}' has archive blockers; nothing was written:\n ${blockers.join('\n ')}\n` +
350
+ `Fix the blockers, or pass --yes to archive with open tasks (a refused delta still aborts the fold` +
351
+ `${unresolved.length > 0 ? '; an unresolved header needs --force' : ''}).`);
352
+ }
353
+ // 1c. Unresolved headers are never waived by --yes: the fold would drop them
354
+ // silently and the published spec would keep the old requirement. --force
355
+ // archives anyway and skips them, each named below.
356
+ if (unresolved.length > 0 && !force) {
357
+ throw new Error(`Change '${name}' has ${unresolved.length} unresolved delta header(s); nothing was written:\n ` +
358
+ `${unresolved.map((u) => `[WARNING] ${u.entryPath}: ${u.message}`).join('\n ')}\n` +
359
+ `Pass --force to archive anyway (those deltas are skipped; the change record keeps their text).`);
360
+ }
361
+ // 1d. Supersession links (design.md `Supersedes:`), checked before any write.
362
+ const supersedes = await checkSupersession(name, changeDir, changesDir, metadata);
363
+ // 1e. Dispositioned tasks, read while the change still sits at changeDir (the
364
+ // move below relocates it).
365
+ const dispositions = dispositionedTasks(changeDir);
366
+ // 2. Fold the change's delta specs into openspec/specs/. buildUpdatedSpec does
367
+ // the ADDED/MODIFIED/REMOVED/RENAMED merge; writeUpdatedSpec writes it in
368
+ // place. A fold that resolves to zero operations is an already-synced
369
+ // (early-sync) delta and is skipped, matching upstream's write gate.
370
+ //
371
+ // Capability RETIREMENT (deleting a main spec whose last requirement a
372
+ // delta removed) is gated upstream behind an explicit retire_capabilities
373
+ // marker and is out of this vendored slice; here every fold rebuilds and
374
+ // writes the spec, never deletes one.
375
+ // Build every fold BEFORE writing any: buildUpdatedSpec throws on an
376
+ // unapplicable delta, so planning the whole change first means one bad
377
+ // delta aborts the archive with openspec/specs/ untouched, rather than
378
+ // leaving earlier specs half-written.
379
+ const updates = await findSpecUpdates(changeDir, specsDir);
380
+ const plan = [];
381
+ const skipUnresolved = force === true && unresolved.length > 0;
382
+ for (const update of updates) {
383
+ const built = await buildUpdatedSpec(update, name, { silent: true, skipUnresolved });
384
+ plan.push({ update, rebuilt: built.rebuilt, counts: built.counts });
385
+ }
386
+ const applied = [];
387
+ for (const { update, rebuilt, counts } of plan) {
388
+ const total = counts.added + counts.modified + counts.removed + counts.renamed;
389
+ if (total === 0)
390
+ continue; // already in sync — nothing to write
391
+ await writeUpdatedSpec(update, rebuilt, counts, { silent: true });
392
+ applied.push(update.id);
393
+ }
394
+ // 3. Move the change into changes/archive/<name>/. Refuse to clobber an
395
+ // existing archived change of the same name.
396
+ const archiveDir = path.join(changesDir, 'archive');
397
+ const archivedTo = path.join(archiveDir, name);
398
+ await fs.mkdir(archiveDir, { recursive: true });
399
+ if (fssync.existsSync(archivedTo)) {
400
+ throw new Error(`An archived change already exists at ${archivedTo}; move or remove it before archiving '${name}'.`);
401
+ }
402
+ await fs.rename(changeDir, archivedTo);
403
+ // 4. Reverse links: the superseded archived record names its successor.
404
+ for (const link of supersedes)
405
+ await writeSupersededBy(link, name);
406
+ // 5. Open follow-ups, read from the archived location (the file moved with the
407
+ // directory in step 3): printed as a notice, never a blocker. An item is open
408
+ // while its checkbox is unchecked, the rule `follow-up list`, `follow-up claim`
409
+ // and the session-start pointer read it by, so the four never disagree.
410
+ const followUps = readFollowUps(archivedTo).filter((item) => !item.done);
411
+ return { applied, archivedTo, supersedes, skipped: skipUnresolved ? unresolved : [], dispositions, followUps };
412
+ }
413
+ /**
414
+ * CLI wrapper for `openspec archive <id>`. Returns the process exit code and
415
+ * emits either the single-document `--json` result or human-readable lines.
416
+ */
417
+ export async function archiveHandler(projectRoot, changeName, options) {
418
+ try {
419
+ const result = await archiveChange(changeName, {
420
+ projectRoot,
421
+ ...(options.yes !== undefined ? { yes: options.yes } : {}),
422
+ ...(options.force !== undefined ? { force: options.force } : {}),
423
+ });
424
+ if (options.json) {
425
+ console.log(JSON.stringify({
426
+ archived: true,
427
+ change: changeName,
428
+ applied: result.applied,
429
+ archivedTo: result.archivedTo,
430
+ supersedes: result.supersedes,
431
+ skipped: result.skipped.map((u) => ({ operation: u.operation, header: u.header, spec: u.specId, ...(u.addedBy ? { addedBy: u.addedBy } : {}) })),
432
+ dispositions: result.dispositions,
433
+ followUps: result.followUps,
434
+ }, null, 2));
435
+ }
436
+ else {
437
+ if (result.applied.length > 0) {
438
+ console.log(`Applied spec updates: ${result.applied.join(', ')}`);
439
+ }
440
+ else {
441
+ console.log('No spec updates to apply (already in sync).');
442
+ }
443
+ for (const link of result.supersedes) {
444
+ console.log(`Supersedes ${link.change} (${link.path}); reverse link written to its design.md`);
445
+ }
446
+ for (const u of result.skipped) {
447
+ console.log(`Warning (--force): skipped ${u.operation} "${u.header}" in specs/${u.entryPath} — ${u.message}`);
448
+ }
449
+ if (result.dispositions.length > 0) {
450
+ console.log('Dispositions:');
451
+ const byKind = new Map();
452
+ for (const d of result.dispositions) {
453
+ const group = byKind.get(d.kind) ?? [];
454
+ group.push(d);
455
+ byKind.set(d.kind, group);
456
+ }
457
+ for (const group of byKind.values()) {
458
+ for (const d of group)
459
+ console.log(` ${d.kind}: ${d.task} -> ${d.target}`);
460
+ }
461
+ }
462
+ if (result.followUps.length > 0) {
463
+ console.log(`follow-ups open: ${result.followUps.length}`);
464
+ for (const item of result.followUps) {
465
+ const suffix = item.from ? ` (from ${item.from})` : '';
466
+ console.log(` ${item.index}. ${item.text}${suffix}`);
467
+ }
468
+ }
469
+ console.log(`Archived change to ${result.archivedTo}`);
470
+ }
471
+ return 0;
472
+ }
473
+ catch (error) {
474
+ const message = error instanceof Error ? error.message : String(error);
475
+ if (options.json) {
476
+ console.log(JSON.stringify({ archived: false, status: [{ severity: 'error', code: 'archive_error', message }] }, null, 2));
477
+ }
478
+ else {
479
+ console.error(message);
480
+ }
481
+ return 1;
482
+ }
483
+ }
@@ -0,0 +1,4 @@
1
+ export interface ContextOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function contextHandler(projectRoot: string | null, options: ContextOptions): number;
@@ -0,0 +1,48 @@
1
+ // `openspec context [--json]` — store-free minimal working set.
2
+ //
3
+ // Upstream `context` renders the multi-repo working set (root + referenced
4
+ // stores) from the store registry. This slice dropped the store, so the working
5
+ // set is always "this root alone": a `{ root: { path }, members: [] }` brief.
6
+ // When no openspec/ root is found, it reports the `no_openspec_root` diagnostic
7
+ // the /opsx propose workflow checks for (then it falls back to `schemas --json`
8
+ // from the cwd). The `--store` flag, if passed, is accepted and ignored.
9
+ import { planningHomeStub, printJson } from './shared.js';
10
+ export function contextHandler(projectRoot, options) {
11
+ if (!projectRoot) {
12
+ const payload = {
13
+ root: null,
14
+ members: [],
15
+ status: [
16
+ {
17
+ severity: 'error',
18
+ code: 'no_openspec_root',
19
+ message: 'No OpenSpec root found from the current directory.',
20
+ },
21
+ ],
22
+ };
23
+ if (options.json) {
24
+ printJson(payload);
25
+ }
26
+ else {
27
+ console.error('No OpenSpec root found from the current directory.');
28
+ }
29
+ return 1;
30
+ }
31
+ const workingSet = {
32
+ root: { path: projectRoot },
33
+ planningHome: planningHomeStub(projectRoot),
34
+ members: [],
35
+ status: [],
36
+ };
37
+ if (options.json) {
38
+ printJson(workingSet);
39
+ return 0;
40
+ }
41
+ console.log(`Working context for ${projectRoot}`);
42
+ console.log();
43
+ console.log('OpenSpec root');
44
+ console.log(` ${projectRoot}`);
45
+ console.log();
46
+ console.log('No references declared; the working set is this root alone.');
47
+ return 0;
48
+ }
@@ -0,0 +1,57 @@
1
+ /** One follow-up item, its 1-based position among item lines in file order. */
2
+ export interface FollowUpItem {
3
+ index: number;
4
+ text: string;
5
+ from: string | null;
6
+ /** Checkbox state: a checked item is settled, whether or not it names a claimant. */
7
+ done: boolean;
8
+ /** The claiming change id, null for an item checked by hand with no `-> claimed by` suffix. */
9
+ claimedBy: string | null;
10
+ }
11
+ /**
12
+ * Reads every item line of a change's follow-ups.md, ignoring any other line
13
+ * (heading, blank, comment). Index is the 1-based position among item lines in
14
+ * file order. Absent file -> empty list.
15
+ */
16
+ export declare function readFollowUps(dir: string): FollowUpItem[];
17
+ export interface AddFollowUpResult {
18
+ change: string;
19
+ index: number;
20
+ }
21
+ /**
22
+ * Appends an unclaimed item to a change's follow-ups.md, creating the file from
23
+ * the template when absent. Throws when the change resolves neither in flight
24
+ * nor archived.
25
+ */
26
+ export declare function addFollowUp(projectRoot: string, changeId: string, text: string, from?: string): AddFollowUpResult;
27
+ export interface ListedFollowUp {
28
+ change: string;
29
+ index: number;
30
+ text: string;
31
+ from: string | null;
32
+ state: 'open';
33
+ }
34
+ /** Every open (unchecked) item of every in-flight and archived change. */
35
+ export declare function listFollowUps(projectRoot: string): ListedFollowUp[];
36
+ export interface ClaimFollowUpResult {
37
+ change: string;
38
+ index: number;
39
+ by: string;
40
+ }
41
+ /**
42
+ * Rewrites an item as claimed. Refuses an index without an item, an item
43
+ * already claimed, or a `--by` id that resolves neither in flight nor
44
+ * archived; every other line of the file is left byte-identical.
45
+ */
46
+ export declare function claimFollowUp(projectRoot: string, changeId: string, index: number, byId: string): ClaimFollowUpResult;
47
+ export interface FollowUpCommandOptions {
48
+ json?: boolean;
49
+ from?: string;
50
+ by?: string;
51
+ }
52
+ /** CLI wrapper for `openspec follow-up add <change-id> "<text>" [--from <task>]`. */
53
+ export declare function followUpAddHandler(projectRoot: string, changeId: string | undefined, text: string | undefined, options: FollowUpCommandOptions): number;
54
+ /** CLI wrapper for `openspec follow-up list [--json]`. */
55
+ export declare function followUpListHandler(projectRoot: string, options: FollowUpCommandOptions): number;
56
+ /** CLI wrapper for `openspec follow-up claim <change-id> <index> --by <change-id>`. */
57
+ export declare function followUpClaimHandler(projectRoot: string, changeId: string | undefined, indexArg: string | undefined, options: FollowUpCommandOptions): number;