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,46 @@
1
+ import { z } from 'zod';
2
+ export declare const METADATA_FILENAME = ".openspec.yaml";
3
+ /**
4
+ * Per-change metadata, trimmed to the fields this store-free slice consumes.
5
+ * Extra keys are allowed (upstream carries goal / affected_areas / initiative /
6
+ * retire_capabilities); they are simply ignored here.
7
+ */
8
+ export declare const ChangeMetadataSchema: z.ZodObject<{
9
+ schema: z.ZodString;
10
+ created: z.ZodOptional<z.ZodString>;
11
+ skip_specs: z.ZodOptional<z.ZodBoolean>;
12
+ depends_on: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ supersedes: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$loose>;
15
+ export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
16
+ /**
17
+ * Error thrown when change metadata validation fails.
18
+ */
19
+ export declare class ChangeMetadataError extends Error {
20
+ readonly metadataPath: string;
21
+ readonly cause?: Error | undefined;
22
+ constructor(message: string, metadataPath: string, cause?: Error | undefined);
23
+ }
24
+ /**
25
+ * Reads change metadata from `.openspec.yaml` in the change directory.
26
+ * Returns null when no metadata file exists; throws when it exists but is
27
+ * invalid or names an unknown schema.
28
+ */
29
+ export declare function readChangeMetadata(changeDir: string, projectRoot?: string): ChangeMetadata | null;
30
+ export interface ResolveSchemaForChangeOptions {
31
+ metadata?: ChangeMetadata | null;
32
+ }
33
+ /**
34
+ * Resolves the schema for a change.
35
+ *
36
+ * Resolution order (store-free slice):
37
+ * 1. Explicit schema (if provided)
38
+ * 2. Schema from `.openspec.yaml` metadata (if present)
39
+ * 3. Default 'spec-driven'
40
+ */
41
+ export declare function resolveSchemaForChange(changeDir: string, explicitSchema?: string, projectRootOverride?: string, options?: ResolveSchemaForChangeOptions): string;
42
+ /**
43
+ * Writes change metadata to `.openspec.yaml`. Used by the store-free
44
+ * `new change` shim. Validates the schema name against available schemas.
45
+ */
46
+ export declare function writeChangeMetadata(changeDir: string, metadata: ChangeMetadata, projectRoot?: string): void;
@@ -0,0 +1,128 @@
1
+ // RECONSTRUCTED (store-free) from @fission-ai/openspec@1.8.0
2
+ // src/utils/change-metadata.ts + src/core/change-metadata/schema.ts
3
+ //
4
+ // The upstream module couples change metadata to project-config (config.yaml
5
+ // schema fallback) and the multi-repo store. This slice keeps only what
6
+ // status/instructions need: reading `.openspec.yaml`, validating it, and
7
+ // resolving the governing schema. Resolution order is trimmed to
8
+ // 1. explicit schema 2. metadata.schema 3. 'spec-driven'
9
+ // (the project-config fallback is intentionally dropped — it is store/config
10
+ // plumbing outside this slice). The ChangeMetadata zod schema is inlined
11
+ // (only the fields this slice reads: schema + skip_specs) instead of vendoring
12
+ // the id-grammar and initiative-link machinery.
13
+ // FreeHarness addition: `depends_on` / `supersedes` (change-id lists), resolved
14
+ // and enforced by the shim's validate/archive commands, not by this slice.
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import * as yaml from 'yaml';
18
+ import { z } from 'zod';
19
+ import { listSchemas } from './resolver.js';
20
+ export const METADATA_FILENAME = '.openspec.yaml';
21
+ /**
22
+ * Per-change metadata, trimmed to the fields this store-free slice consumes.
23
+ * Extra keys are allowed (upstream carries goal / affected_areas / initiative /
24
+ * retire_capabilities); they are simply ignored here.
25
+ */
26
+ export const ChangeMetadataSchema = z
27
+ .object({
28
+ schema: z.string().min(1, { message: 'schema is required' }),
29
+ created: z
30
+ .string()
31
+ .regex(/^\d{4}-\d{2}-\d{2}$/, { message: 'created must be YYYY-MM-DD format' })
32
+ .optional(),
33
+ skip_specs: z.boolean().optional(),
34
+ depends_on: z.array(z.string()).optional(),
35
+ supersedes: z.array(z.string()).optional(),
36
+ })
37
+ .passthrough();
38
+ /**
39
+ * Error thrown when change metadata validation fails.
40
+ */
41
+ export class ChangeMetadataError extends Error {
42
+ metadataPath;
43
+ cause;
44
+ constructor(message, metadataPath, cause) {
45
+ super(message);
46
+ this.metadataPath = metadataPath;
47
+ this.cause = cause;
48
+ this.name = 'ChangeMetadataError';
49
+ }
50
+ }
51
+ /**
52
+ * Reads change metadata from `.openspec.yaml` in the change directory.
53
+ * Returns null when no metadata file exists; throws when it exists but is
54
+ * invalid or names an unknown schema.
55
+ */
56
+ export function readChangeMetadata(changeDir, projectRoot) {
57
+ const metaPath = path.join(changeDir, METADATA_FILENAME);
58
+ if (!fs.existsSync(metaPath)) {
59
+ return null;
60
+ }
61
+ let content;
62
+ try {
63
+ content = fs.readFileSync(metaPath, 'utf-8');
64
+ }
65
+ catch (err) {
66
+ const ioError = err instanceof Error ? err : new Error(String(err));
67
+ throw new ChangeMetadataError(`Failed to read metadata: ${ioError.message}`, metaPath, ioError);
68
+ }
69
+ let parsed;
70
+ try {
71
+ parsed = yaml.parse(content);
72
+ }
73
+ catch (err) {
74
+ const parseError = err instanceof Error ? err : new Error(String(err));
75
+ throw new ChangeMetadataError(`Invalid YAML in metadata file: ${parseError.message}`, metaPath, parseError);
76
+ }
77
+ const parseResult = ChangeMetadataSchema.safeParse(parsed);
78
+ if (!parseResult.success) {
79
+ throw new ChangeMetadataError(`Invalid metadata: ${parseResult.error.message}`, metaPath);
80
+ }
81
+ const availableSchemas = listSchemas(projectRoot);
82
+ if (!availableSchemas.includes(parseResult.data.schema)) {
83
+ throw new ChangeMetadataError(`Unknown schema '${parseResult.data.schema}'. Available: ${availableSchemas.join(', ')}`, metaPath);
84
+ }
85
+ return parseResult.data;
86
+ }
87
+ /**
88
+ * Resolves the schema for a change.
89
+ *
90
+ * Resolution order (store-free slice):
91
+ * 1. Explicit schema (if provided)
92
+ * 2. Schema from `.openspec.yaml` metadata (if present)
93
+ * 3. Default 'spec-driven'
94
+ */
95
+ export function resolveSchemaForChange(changeDir, explicitSchema, projectRootOverride, options = {}) {
96
+ const projectRoot = projectRootOverride ?? path.resolve(changeDir, '../../..');
97
+ if (explicitSchema) {
98
+ return explicitSchema;
99
+ }
100
+ const metadata = options.metadata !== undefined ? options.metadata : readChangeMetadata(changeDir, projectRoot);
101
+ if (metadata?.schema) {
102
+ return metadata.schema;
103
+ }
104
+ return 'spec-driven';
105
+ }
106
+ /**
107
+ * Writes change metadata to `.openspec.yaml`. Used by the store-free
108
+ * `new change` shim. Validates the schema name against available schemas.
109
+ */
110
+ export function writeChangeMetadata(changeDir, metadata, projectRoot) {
111
+ const metaPath = path.join(changeDir, METADATA_FILENAME);
112
+ const parseResult = ChangeMetadataSchema.safeParse(metadata);
113
+ if (!parseResult.success) {
114
+ throw new ChangeMetadataError(`Invalid metadata: ${parseResult.error.message}`, metaPath);
115
+ }
116
+ const availableSchemas = listSchemas(projectRoot);
117
+ if (!availableSchemas.includes(parseResult.data.schema)) {
118
+ throw new ChangeMetadataError(`Unknown schema '${parseResult.data.schema}'. Available: ${availableSchemas.join(', ')}`, metaPath);
119
+ }
120
+ const content = yaml.stringify(parseResult.data);
121
+ try {
122
+ fs.writeFileSync(metaPath, content, 'utf-8');
123
+ }
124
+ catch (err) {
125
+ const ioError = err instanceof Error ? err : new Error(String(err));
126
+ throw new ChangeMetadataError(`Failed to write metadata: ${ioError.message}`, metaPath, ioError);
127
+ }
128
+ }
@@ -0,0 +1,26 @@
1
+ export interface ActionContext {
2
+ mode: 'repo-local';
3
+ sourceOfTruth: 'repo';
4
+ planningArtifacts: string[];
5
+ linkedContext: Array<{
6
+ name: string;
7
+ }>;
8
+ allowedEditRoots: string[];
9
+ requiresAffectedAreaSelection: boolean;
10
+ constraints: string[];
11
+ }
12
+ export interface ChangeStatusPolicyArtifact {
13
+ id: string;
14
+ status: 'done' | 'skipped' | 'ready' | 'blocked';
15
+ }
16
+ export interface ChangeNextStepsInput {
17
+ changeName: string;
18
+ artifactStatuses: ChangeStatusPolicyArtifact[];
19
+ allArtifactsComplete: boolean;
20
+ }
21
+ export interface ActionContextInput {
22
+ projectRoot: string;
23
+ artifactIds: string[];
24
+ }
25
+ export declare function buildActionContext(input: ActionContextInput): ActionContext;
26
+ export declare function buildNextSteps(input: ChangeNextStepsInput): string[];
@@ -0,0 +1,32 @@
1
+ // RECONSTRUCTED (store-free) from @fission-ai/openspec@1.8.0
2
+ // src/core/change-status-policy.ts
3
+ //
4
+ // buildActionContext and buildNextSteps are vendored faithfully. The only
5
+ // change is that the store-aware pieces are dropped: PlanningHome /
6
+ // summarizePlanningHome (multi-repo store home) and the `--store` flag that
7
+ // buildNextSteps threaded into its command hints. Next-step commands are
8
+ // emitted in their plain, store-free form.
9
+ export function buildActionContext(input) {
10
+ return {
11
+ mode: 'repo-local',
12
+ sourceOfTruth: 'repo',
13
+ planningArtifacts: input.artifactIds,
14
+ linkedContext: [],
15
+ allowedEditRoots: [input.projectRoot],
16
+ requiresAffectedAreaSelection: false,
17
+ constraints: [
18
+ 'Repo-local change artifacts and implementation edits are scoped to this project.',
19
+ ],
20
+ };
21
+ }
22
+ export function buildNextSteps(input) {
23
+ const readyArtifact = input.artifactStatuses.find((artifact) => artifact.status === 'ready');
24
+ const steps = [];
25
+ if (readyArtifact) {
26
+ steps.push(`Run openspec instructions ${readyArtifact.id} --change "${input.changeName}" --json before writing that artifact.`);
27
+ }
28
+ else if (input.allArtifactsComplete) {
29
+ steps.push(`All planning artifacts are complete. Run openspec instructions apply --change "${input.changeName}" --json to inspect implementation progress.`);
30
+ }
31
+ return steps;
32
+ }
@@ -0,0 +1,50 @@
1
+ export declare class FileSystemUtils {
2
+ /**
3
+ * Converts a path to use forward slashes (POSIX style).
4
+ * Essential for cross-platform compatibility with glob libraries like fast-glob.
5
+ */
6
+ static toPosixPath(p: string): string;
7
+ /**
8
+ * Returns a canonical absolute path when the target exists.
9
+ * Falls back to path.resolve() so callers can still produce a stable absolute path.
10
+ */
11
+ static canonicalizeExistingPath(targetPath: string): string;
12
+ /**
13
+ * Refuses a target that leaves an allowed directory, including through an
14
+ * existing symlink in either the target or one of its parent directories.
15
+ * Missing suffixes are resolved from their nearest existing ancestor.
16
+ */
17
+ static assertPathWithin(allowedDirectory: string, targetPath: string): void;
18
+ static resolveProjectArtifactPath(projectPath: string, artifactPath: string): string;
19
+ static assertProjectArtifactPath(projectPath: string, targetPath: string): void;
20
+ private static isPathWithin;
21
+ private static canonicalizePotentialPath;
22
+ private static isWindowsBasePath;
23
+ private static normalizeSegments;
24
+ static joinPath(basePath: string, ...segments: string[]): string;
25
+ static createDirectory(dirPath: string): Promise<void>;
26
+ static fileExists(filePath: string): Promise<boolean>;
27
+ /**
28
+ * Finds the first existing parent directory by walking up the directory tree.
29
+ * @param dirPath Starting directory path
30
+ * @returns The first existing directory path, or null if root is reached without finding one
31
+ */
32
+ private static findFirstExistingDirectory;
33
+ static canWriteFile(filePath: string): Promise<boolean>;
34
+ static directoryExists(dirPath: string): Promise<boolean>;
35
+ static writeFile(filePath: string, content: string): Promise<void>;
36
+ static readFile(filePath: string): Promise<string>;
37
+ static updateFileWithMarkers(filePath: string, content: string, startMarker: string, endMarker: string): Promise<void>;
38
+ static ensureWritePermissions(dirPath: string): Promise<boolean>;
39
+ }
40
+ /**
41
+ * Removes a marker block from file content.
42
+ * Only removes markers that are on their own lines (ignores inline mentions).
43
+ * Cleans up double blank lines that may result from removal.
44
+ *
45
+ * @param content - File content with markers
46
+ * @param startMarker - The start marker string
47
+ * @param endMarker - The end marker string
48
+ * @returns Content with marker block removed, or original content if markers not found/invalid
49
+ */
50
+ export declare function removeMarkerBlock(content: string, startMarker: string, endMarker: string): string;
@@ -0,0 +1,394 @@
1
+ // Vendored faithfully from @fission-ai/openspec@1.8.0
2
+ // src/utils/file-system.ts
3
+ // No behavioural changes. The schema-validation slice only uses
4
+ // FileSystemUtils.assertPathWithin (called from resolver.ts), but the full
5
+ // utility is kept intact to stay faithful to upstream.
6
+ import * as nodeFs from 'fs';
7
+ import path from 'path';
8
+ const fs = nodeFs.promises;
9
+ const { constants: fsConstants } = nodeFs;
10
+ function hasOwnerGroupOrOtherWriteBit(stats) {
11
+ return (stats.mode & 0o222) !== 0;
12
+ }
13
+ function hasOwnerGroupOrOtherExecuteBit(stats) {
14
+ return (stats.mode & 0o111) !== 0;
15
+ }
16
+ async function hasWritableModeAndAccess(targetPath) {
17
+ try {
18
+ const stats = await fs.stat(targetPath);
19
+ // POSIX root can often write despite mode bits, but OpenSpec should respect
20
+ // explicit read-only file/directory modes when deciding whether an install
21
+ // path is user-writable. This also keeps permission checks deterministic in
22
+ // root-run CI containers. On Windows, chmod mode bits are not authoritative,
23
+ // so rely on fs.access below.
24
+ if (process.platform !== 'win32' && !hasOwnerGroupOrOtherWriteBit(stats)) {
25
+ return false;
26
+ }
27
+ if (process.platform !== 'win32' && stats.isDirectory() && !hasOwnerGroupOrOtherExecuteBit(stats)) {
28
+ return false;
29
+ }
30
+ const accessMode = stats.isDirectory()
31
+ ? fsConstants.W_OK | fsConstants.X_OK
32
+ : fsConstants.W_OK;
33
+ await fs.access(targetPath, accessMode);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function isMarkerOnOwnLine(content, markerIndex, markerLength) {
41
+ let leftIndex = markerIndex - 1;
42
+ while (leftIndex >= 0 && content[leftIndex] !== '\n') {
43
+ const char = content[leftIndex];
44
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
45
+ return false;
46
+ }
47
+ leftIndex--;
48
+ }
49
+ let rightIndex = markerIndex + markerLength;
50
+ while (rightIndex < content.length && content[rightIndex] !== '\n') {
51
+ const char = content[rightIndex];
52
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
53
+ return false;
54
+ }
55
+ rightIndex++;
56
+ }
57
+ return true;
58
+ }
59
+ function findMarkerIndex(content, marker, fromIndex = 0) {
60
+ let currentIndex = content.indexOf(marker, fromIndex);
61
+ while (currentIndex !== -1) {
62
+ if (isMarkerOnOwnLine(content, currentIndex, marker.length)) {
63
+ return currentIndex;
64
+ }
65
+ currentIndex = content.indexOf(marker, currentIndex + marker.length);
66
+ }
67
+ return -1;
68
+ }
69
+ export class FileSystemUtils {
70
+ /**
71
+ * Converts a path to use forward slashes (POSIX style).
72
+ * Essential for cross-platform compatibility with glob libraries like fast-glob.
73
+ */
74
+ static toPosixPath(p) {
75
+ return p.replace(/\\/g, '/');
76
+ }
77
+ /**
78
+ * Returns a canonical absolute path when the target exists.
79
+ * Falls back to path.resolve() so callers can still produce a stable absolute path.
80
+ */
81
+ static canonicalizeExistingPath(targetPath) {
82
+ try {
83
+ // Prefer the native resolver so Windows short-path aliases are expanded.
84
+ return nodeFs.realpathSync.native(targetPath);
85
+ }
86
+ catch {
87
+ try {
88
+ return nodeFs.realpathSync(targetPath);
89
+ }
90
+ catch {
91
+ return path.resolve(targetPath);
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * Refuses a target that leaves an allowed directory, including through an
97
+ * existing symlink in either the target or one of its parent directories.
98
+ * Missing suffixes are resolved from their nearest existing ancestor.
99
+ */
100
+ static assertPathWithin(allowedDirectory, targetPath) {
101
+ const resolvedDirectory = path.resolve(allowedDirectory);
102
+ const resolvedTarget = path.resolve(targetPath);
103
+ if (!this.isPathWithin(resolvedDirectory, resolvedTarget)) {
104
+ throw new Error(`Path is outside the allowed directory: ${targetPath}`);
105
+ }
106
+ const canonicalDirectory = this.canonicalizePotentialPath(resolvedDirectory);
107
+ const canonicalTarget = this.canonicalizePotentialPath(resolvedTarget);
108
+ if (!this.isPathWithin(canonicalDirectory, canonicalTarget)) {
109
+ throw new Error(`Path is outside the allowed directory: ${targetPath}`);
110
+ }
111
+ }
112
+ static resolveProjectArtifactPath(projectPath, artifactPath) {
113
+ if (path.isAbsolute(artifactPath)) {
114
+ throw new Error(`Refusing to manage an artifact outside the project: ${artifactPath}`);
115
+ }
116
+ const targetPath = path.join(projectPath, artifactPath);
117
+ this.assertPathWithin(projectPath, targetPath);
118
+ return targetPath;
119
+ }
120
+ static assertProjectArtifactPath(projectPath, targetPath) {
121
+ this.assertPathWithin(projectPath, targetPath);
122
+ }
123
+ static isPathWithin(allowedDirectory, targetPath) {
124
+ const relative = path.relative(allowedDirectory, targetPath);
125
+ return (relative === '' ||
126
+ (relative !== '..' &&
127
+ !relative.startsWith(`..${path.sep}`) &&
128
+ !path.isAbsolute(relative)));
129
+ }
130
+ static canonicalizePotentialPath(targetPath) {
131
+ let existingPath = targetPath;
132
+ const missingSegments = [];
133
+ while (true) {
134
+ try {
135
+ // lstat distinguishes a missing path from a dangling symlink. A
136
+ // dangling link cannot be proven confined, so realpath must fail it.
137
+ nodeFs.lstatSync(existingPath);
138
+ const canonicalExisting = nodeFs.realpathSync.native(existingPath);
139
+ return path.resolve(canonicalExisting, ...missingSegments);
140
+ }
141
+ catch (error) {
142
+ const code = error.code;
143
+ if (code !== 'ENOENT') {
144
+ throw error;
145
+ }
146
+ try {
147
+ if (nodeFs.lstatSync(existingPath).isSymbolicLink()) {
148
+ throw new Error(`Cannot verify dangling symbolic link: ${existingPath}`);
149
+ }
150
+ }
151
+ catch (lstatError) {
152
+ if (lstatError.code !== 'ENOENT') {
153
+ throw lstatError;
154
+ }
155
+ }
156
+ const parent = path.dirname(existingPath);
157
+ if (parent === existingPath) {
158
+ throw new Error(`Cannot resolve an existing parent for ${targetPath}`);
159
+ }
160
+ missingSegments.unshift(path.basename(existingPath));
161
+ existingPath = parent;
162
+ }
163
+ }
164
+ }
165
+ static isWindowsBasePath(basePath) {
166
+ return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
167
+ }
168
+ static normalizeSegments(segments) {
169
+ return segments
170
+ .flatMap((segment) => segment.split(/[\\/]+/u))
171
+ .filter((part) => part.length > 0);
172
+ }
173
+ static joinPath(basePath, ...segments) {
174
+ const normalizedSegments = this.normalizeSegments(segments);
175
+ if (this.isWindowsBasePath(basePath)) {
176
+ const normalizedBasePath = path.win32.normalize(basePath);
177
+ return normalizedSegments.length
178
+ ? path.win32.join(normalizedBasePath, ...normalizedSegments)
179
+ : normalizedBasePath;
180
+ }
181
+ const posixBasePath = basePath.replace(/\\/g, '/');
182
+ return normalizedSegments.length
183
+ ? path.posix.join(posixBasePath, ...normalizedSegments)
184
+ : path.posix.normalize(posixBasePath);
185
+ }
186
+ static async createDirectory(dirPath) {
187
+ await fs.mkdir(dirPath, { recursive: true });
188
+ }
189
+ static async fileExists(filePath) {
190
+ try {
191
+ await fs.access(filePath);
192
+ return true;
193
+ }
194
+ catch (error) {
195
+ if (error.code !== 'ENOENT') {
196
+ console.debug(`Unable to check if file exists at ${filePath}: ${error.message}`);
197
+ }
198
+ return false;
199
+ }
200
+ }
201
+ /**
202
+ * Finds the first existing parent directory by walking up the directory tree.
203
+ * @param dirPath Starting directory path
204
+ * @returns The first existing directory path, or null if root is reached without finding one
205
+ */
206
+ static async findFirstExistingDirectory(dirPath) {
207
+ let currentDir = dirPath;
208
+ while (true) {
209
+ try {
210
+ const stats = await fs.stat(currentDir);
211
+ if (stats.isDirectory()) {
212
+ return currentDir;
213
+ }
214
+ // Path component exists but is not a directory (edge case)
215
+ console.debug(`Path component ${currentDir} exists but is not a directory`);
216
+ return null;
217
+ }
218
+ catch (error) {
219
+ if (error.code === 'ENOENT') {
220
+ // Directory doesn't exist, move up one level
221
+ const parentDir = path.dirname(currentDir);
222
+ if (parentDir === currentDir) {
223
+ // Reached filesystem root without finding existing directory
224
+ return null;
225
+ }
226
+ currentDir = parentDir;
227
+ }
228
+ else {
229
+ // Unexpected error (permissions, I/O error, etc.)
230
+ console.debug(`Error checking directory ${currentDir}: ${error.message}`);
231
+ return null;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ static async canWriteFile(filePath) {
237
+ try {
238
+ const stats = await fs.stat(filePath);
239
+ if (stats.isDirectory()) {
240
+ return hasWritableModeAndAccess(filePath);
241
+ }
242
+ if (!stats.isFile()) {
243
+ return true;
244
+ }
245
+ return hasWritableModeAndAccess(filePath);
246
+ }
247
+ catch (error) {
248
+ if (error.code === 'ENOENT') {
249
+ // File doesn't exist - find first existing parent directory and check its permissions
250
+ const parentDir = path.dirname(filePath);
251
+ const existingDir = await this.findFirstExistingDirectory(parentDir);
252
+ if (existingDir === null) {
253
+ // No existing parent directory found (edge case)
254
+ return false;
255
+ }
256
+ // Check if the existing parent directory is writable.
257
+ return hasWritableModeAndAccess(existingDir);
258
+ }
259
+ console.debug(`Unable to determine write permissions for ${filePath}: ${error.message}`);
260
+ return false;
261
+ }
262
+ }
263
+ static async directoryExists(dirPath) {
264
+ try {
265
+ const stats = await fs.stat(dirPath);
266
+ return stats.isDirectory();
267
+ }
268
+ catch (error) {
269
+ if (error.code !== 'ENOENT') {
270
+ console.debug(`Unable to check if directory exists at ${dirPath}: ${error.message}`);
271
+ }
272
+ return false;
273
+ }
274
+ }
275
+ static async writeFile(filePath, content) {
276
+ const dir = path.dirname(filePath);
277
+ await this.createDirectory(dir);
278
+ await fs.writeFile(filePath, content, 'utf-8');
279
+ }
280
+ static async readFile(filePath) {
281
+ return await fs.readFile(filePath, 'utf-8');
282
+ }
283
+ static async updateFileWithMarkers(filePath, content, startMarker, endMarker) {
284
+ let existingContent = '';
285
+ if (await this.fileExists(filePath)) {
286
+ existingContent = await this.readFile(filePath);
287
+ const startIndex = findMarkerIndex(existingContent, startMarker);
288
+ const endIndex = startIndex !== -1
289
+ ? findMarkerIndex(existingContent, endMarker, startIndex + startMarker.length)
290
+ : findMarkerIndex(existingContent, endMarker);
291
+ if (startIndex !== -1 && endIndex !== -1) {
292
+ if (endIndex < startIndex) {
293
+ throw new Error(`Invalid marker state in ${filePath}. End marker appears before start marker.`);
294
+ }
295
+ const before = existingContent.substring(0, startIndex);
296
+ const after = existingContent.substring(endIndex + endMarker.length);
297
+ existingContent = before + startMarker + '\n' + content + '\n' + endMarker + after;
298
+ }
299
+ else if (startIndex === -1 && endIndex === -1) {
300
+ existingContent = startMarker + '\n' + content + '\n' + endMarker + '\n\n' + existingContent;
301
+ }
302
+ else {
303
+ throw new Error(`Invalid marker state in ${filePath}. Found start: ${startIndex !== -1}, Found end: ${endIndex !== -1}`);
304
+ }
305
+ }
306
+ else {
307
+ existingContent = startMarker + '\n' + content + '\n' + endMarker;
308
+ }
309
+ await this.writeFile(filePath, existingContent);
310
+ }
311
+ static async ensureWritePermissions(dirPath) {
312
+ try {
313
+ // If directory doesn't exist, check parent directory permissions
314
+ if (!await this.directoryExists(dirPath)) {
315
+ const parentDir = path.dirname(dirPath);
316
+ if (!await this.directoryExists(parentDir)) {
317
+ await this.createDirectory(parentDir);
318
+ }
319
+ return await this.ensureWritePermissions(parentDir);
320
+ }
321
+ const testFile = path.join(dirPath, '.openspec-test-' + Date.now() + '-' + Math.random().toString(36).slice(2));
322
+ await fs.writeFile(testFile, '');
323
+ // On Windows, file may be temporarily locked by antivirus or indexing services.
324
+ // Retry unlink with a small delay if it fails.
325
+ const maxRetries = 3;
326
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
327
+ try {
328
+ await fs.unlink(testFile);
329
+ break;
330
+ }
331
+ catch (unlinkError) {
332
+ if (attempt === maxRetries - 1) {
333
+ // Last attempt failed, but we successfully wrote the file, so permissions are OK
334
+ // Just log and continue - the temp file will be cleaned up eventually
335
+ console.debug(`Could not clean up test file ${testFile}: ${unlinkError.message}`);
336
+ }
337
+ else {
338
+ // Wait briefly before retrying (Windows file lock release)
339
+ await new Promise((resolve) => setTimeout(resolve, 50));
340
+ }
341
+ }
342
+ }
343
+ return true;
344
+ }
345
+ catch (error) {
346
+ console.debug(`Insufficient permissions to write to ${dirPath}: ${error.message}`);
347
+ return false;
348
+ }
349
+ }
350
+ }
351
+ /**
352
+ * Removes a marker block from file content.
353
+ * Only removes markers that are on their own lines (ignores inline mentions).
354
+ * Cleans up double blank lines that may result from removal.
355
+ *
356
+ * @param content - File content with markers
357
+ * @param startMarker - The start marker string
358
+ * @param endMarker - The end marker string
359
+ * @returns Content with marker block removed, or original content if markers not found/invalid
360
+ */
361
+ export function removeMarkerBlock(content, startMarker, endMarker) {
362
+ const startIndex = findMarkerIndex(content, startMarker);
363
+ const endIndex = startIndex !== -1
364
+ ? findMarkerIndex(content, endMarker, startIndex + startMarker.length)
365
+ : findMarkerIndex(content, endMarker);
366
+ if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
367
+ return content;
368
+ }
369
+ // Find the start of the line containing the start marker
370
+ let lineStart = startIndex;
371
+ while (lineStart > 0 && content[lineStart - 1] !== '\n') {
372
+ lineStart--;
373
+ }
374
+ // Find the end of the line containing the end marker
375
+ let lineEnd = endIndex + endMarker.length;
376
+ while (lineEnd < content.length && content[lineEnd] !== '\n') {
377
+ lineEnd++;
378
+ }
379
+ // Include the trailing newline if present
380
+ if (lineEnd < content.length && content[lineEnd] === '\n') {
381
+ lineEnd++;
382
+ }
383
+ const before = content.substring(0, lineStart);
384
+ const after = content.substring(lineEnd);
385
+ // Clean up double blank lines (handle both Unix \n and Windows \r\n)
386
+ let result = before + after;
387
+ result = result.replace(/(\r?\n){3,}/g, '\n\n');
388
+ // Trim trailing whitespace but preserve leading whitespace and original newline style
389
+ if (result.trimEnd() === '') {
390
+ return '';
391
+ }
392
+ const newline = content.includes('\r\n') ? '\r\n' : '\n';
393
+ return result.trimEnd() + newline;
394
+ }