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,382 @@
1
+ // Cleanup removes leftovers of earlier installs from a fixed, enumerated list; nothing is
2
+ // discovered by heuristics. User scope: parked upstream claude-mem + data backups, upstream
3
+ // claude-mem hooks in settings.json (once the plugin is registered), legacy /openspec commands,
4
+ // the global @fission-ai/openspec package, a global npm `freeharness` link, retired names
5
+ // (RETIRED) still on disk after a rename or a fold when the manifest records them. Project
6
+ // scope: legacy /openspec commands, project copies of canonical agents/skills, duplicate
7
+ // FreeHarness hook entries. Never listed: the schema override, non-canonical skills/agents,
8
+ // `openspec-*` skills, `.claude/commands/opsx/` (the user's or ours).
9
+ //
10
+ // Two-pass: `planCleanup` computes the list untouched; `applyCleanup` removes the confirmed items.
11
+ import fs from "node:fs";
12
+ import os from "node:os";
13
+ import path from "node:path";
14
+ import { spawnSync } from "node:child_process";
15
+ import { PLUGIN_KEY, PLUGIN_NAME, UPSTREAM_MARKETPLACE_OWNER, UPSTREAM_PLUGIN_KEY, UPSTREAM_PLUGIN_NAME } from "../adapters/memory/vendored.js";
16
+ import { manifestPath, readManifest } from "../infra/manifest.js";
17
+ import { configDir, dataDir, diagnoseMemory } from "../adapters/memory/doctor.js";
18
+ import { canonicalNames, memoryPluginInstalled } from "./doctor.js";
19
+ import { isClaudeMemHook, isOurHook, pruneHooksMatching } from "./uninstall.js";
20
+ /** The vendored `openspec` bin replaces it. */
21
+ export const LEGACY_OPENSPEC_PACKAGE = "@fission-ai/openspec";
22
+ /** A global link of it shadows the ~/.local/bin symlink. */
23
+ export const CLI_PACKAGE = "freeharness";
24
+ /** Retired by a rename or a fold, bare names under `~/.claude`. */
25
+ export const RETIRED = {
26
+ skills: ["local-code-intel"],
27
+ agents: ["mle-reviewer", "mle-evaluator"],
28
+ };
29
+ /** Sums the size (bytes) of every file under a directory. */
30
+ export function dirBytes(dir) {
31
+ let total = 0;
32
+ const walk = (d) => {
33
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
34
+ const p = path.join(d, e.name);
35
+ if (e.isDirectory())
36
+ walk(p);
37
+ else if (e.isFile())
38
+ total += fs.statSync(p).size;
39
+ }
40
+ };
41
+ try {
42
+ walk(dir);
43
+ }
44
+ catch {
45
+ /* unreadable subtree: report what was counted */
46
+ }
47
+ return total;
48
+ }
49
+ /** Renders a byte count for the preview (e.g. "12.3 MB"). */
50
+ export function humanBytes(n) {
51
+ if (n >= 1073741824)
52
+ return `${(n / 1073741824).toFixed(1)} GB`;
53
+ if (n >= 1048576)
54
+ return `${(n / 1048576).toFixed(1)} MB`;
55
+ if (n >= 1024)
56
+ return `${(n / 1024).toFixed(1)} KB`;
57
+ return `${n} B`;
58
+ }
59
+ function npmProbeEnv() {
60
+ return { ...process.env, NPM_CONFIG_LOGS_MAX: "0", NPM_CONFIG_UPDATE_NOTIFIER: "false", NPM_CONFIG_CACHE: path.join(os.tmpdir(), "freeharness-npm-probe") }; // no trace left in HOME
61
+ }
62
+ /** Top-level packages of the global npm install: name -> version ("?" when unreported), null when npm is unavailable. */
63
+ export function globalNpmPackages() {
64
+ const r = spawnSync("npm", ["ls", "-g", "--depth=0", "--json"], { encoding: "utf8", timeout: 20000, env: npmProbeEnv() });
65
+ if (r.error || typeof r.stdout !== "string" || r.stdout.trim() === "")
66
+ return null;
67
+ try {
68
+ // `npm ls` exits non-zero on unmet peers but still prints the tree.
69
+ const tree = JSON.parse(r.stdout);
70
+ return Object.fromEntries(Object.entries(tree.dependencies ?? {}).map(([name, dep]) => [name, dep.version ?? "?"]));
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ /** The `<npm prefix -g>/bin/freeharness` shadowing ours on PATH (an old `npm link -g`); undefined when absent, identical to ours, or npm is unavailable. */
77
+ export function globalNpmFreeharnessBin() {
78
+ const r = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8", timeout: 20000, env: npmProbeEnv() });
79
+ if (r.error || r.status !== 0 || typeof r.stdout !== "string")
80
+ return undefined;
81
+ const prefix = r.stdout.trim();
82
+ if (!prefix)
83
+ return undefined;
84
+ const bin = path.join(prefix, "bin", CLI_PACKAGE);
85
+ const ours = path.join(os.homedir(), ".local", "bin", CLI_PACKAGE);
86
+ if (path.resolve(bin) === path.resolve(ours))
87
+ return undefined;
88
+ try {
89
+ fs.lstatSync(bin); // a dangling link still shadows and still needs `npm unlink`
90
+ }
91
+ catch {
92
+ return undefined;
93
+ }
94
+ return bin;
95
+ }
96
+ /** Claude-mem data backups written by `freeharness memory backup` (`<dataDir>-backup-<stamp>`), oldest first. */
97
+ export function memoryBackupDirs() {
98
+ const data = dataDir();
99
+ const parent = path.dirname(data);
100
+ const prefix = `${path.basename(data)}-backup-`;
101
+ if (!fs.existsSync(parent))
102
+ return [];
103
+ return fs
104
+ .readdirSync(parent, { withFileTypes: true })
105
+ .filter((e) => e.isDirectory() && e.name.startsWith(prefix))
106
+ .map((e) => path.join(parent, e.name))
107
+ .sort();
108
+ }
109
+ /** Plugin keys registered in Claude Code's installed_plugins.json (e.g. "name@marketplace"); empty when absent or unreadable. */
110
+ function installedPluginKeys(cfg) {
111
+ try {
112
+ const raw = JSON.parse(fs.readFileSync(path.join(cfg, "plugins", "installed_plugins.json"), "utf8"));
113
+ return Object.keys(raw.plugins ?? {});
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ }
119
+ /** Drops install-manifest entries whose file no longer exists (else every removal is "drift"); 0 without a manifest. */
120
+ export function pruneManifest(scope, projectRoot) {
121
+ const file = manifestPath(scope, projectRoot);
122
+ const m = readManifest(file);
123
+ if (!m)
124
+ return 0;
125
+ const kept = m.files.filter((f) => fs.existsSync(f.path));
126
+ const dropped = m.files.length - kept.length;
127
+ if (dropped > 0)
128
+ fs.writeFileSync(file, JSON.stringify({ ...m, files: kept, updatedAt: new Date().toISOString() }, null, 2) + "\n");
129
+ return dropped;
130
+ }
131
+ /** Retired skill/agent names still present under a `.claude` dir: `update`'s additive merge never deletes them; an absent path is skipped. */
132
+ export function retiredFindings(claudeDir, manifest) {
133
+ const groups = [
134
+ { kind: "skill", dir: "skills", names: RETIRED.skills },
135
+ { kind: "agent", dir: "agents", names: RETIRED.agents },
136
+ ];
137
+ const found = [];
138
+ for (const { kind, dir, names } of groups) {
139
+ for (const name of names) {
140
+ const fileName = kind === "agent" ? `${name}.md` : name;
141
+ const p = path.join(claudeDir, dir, fileName);
142
+ if (!fs.existsSync(p))
143
+ continue;
144
+ const under = `${p}${path.sep}`;
145
+ const recorded = manifest?.files.some((f) => f.path === p || f.path.startsWith(under)) ?? false;
146
+ found.push({ kind, name, path: p, recorded });
147
+ }
148
+ }
149
+ return found;
150
+ }
151
+ /** Computes the user-scope cleanup list; nothing is touched. */
152
+ async function planUser(deps) {
153
+ const plan = { scope: "user", items: [], notes: [] };
154
+ const cfg = configDir();
155
+ // `memory activate` parks foreign versions under the upstream root (doctor.ts's
156
+ // disabledRoot): an upstream install or a pre-rename FreeHarness build, never ours.
157
+ const disabled = path.join(cfg, "plugins", "cache", UPSTREAM_MARKETPLACE_OWNER, `${UPSTREAM_PLUGIN_NAME}.disabled`);
158
+ if (fs.existsSync(disabled)) {
159
+ const d = deps.memory ?? (await diagnoseMemory());
160
+ const item = { kind: "dir", target: disabled, reason: "upstream claude-mem parked by `memory activate`", group: "memory", bytes: dirBytes(disabled) };
161
+ if (d.verdict !== "ours") {
162
+ const verdictLine = d.lines.find((l) => l.includes("VERDICT")) ?? `verdict ${d.verdict}`;
163
+ item.refused = `the FreeHarness build is not the only active memory plugin yet (${verdictLine.replace(/^(OK|WARN)\s+/, "")}); run \`freeharness memory activate\` first`;
164
+ }
165
+ plan.items.push(item);
166
+ }
167
+ // Superseded FreeHarness builds: our own cache versions (.install-version present) that are
168
+ // not the registered installPath, left behind by a version bump. A legacy-root build that
169
+ // is also `ours` is excluded: the migration item below already covers that whole tree.
170
+ {
171
+ const d = deps.memory ?? (await diagnoseMemory());
172
+ if (d.activeInstallPath) {
173
+ for (const c of d.cache.filter((c) => c.ours && !c.legacy && path.resolve(c.dir) !== path.resolve(d.activeInstallPath))) {
174
+ plan.items.push({ kind: "dir", target: c.dir, reason: `superseded FreeHarness memory build ${c.version} (registered: ${d.activeVersion ?? "?"})`, group: "memory", bytes: dirBytes(c.dir) });
175
+ }
176
+ }
177
+ }
178
+ for (const b of memoryBackupDirs()) {
179
+ plan.items.push({ kind: "dir", target: b, reason: "memory data backup written by `memory backup`", group: "backups", bytes: dirBytes(b) });
180
+ }
181
+ // Upstream claude-mem wired via settings.json: once the plugin (its own hooks.json) is
182
+ // registered, these entries run every hook twice.
183
+ const userSettings = path.join(cfg, "settings.json");
184
+ if (fs.existsSync(userSettings) && memoryPluginInstalled(cfg) && pruneHooksMatching(userSettings, isClaudeMemHook, true) > 0) {
185
+ plan.items.push({ kind: "hooks", target: userSettings, reason: `upstream claude-mem hook entries (the ${PLUGIN_KEY} plugin ships its own hooks.json; these run every hook twice)`, group: "memory", hookOwner: UPSTREAM_PLUGIN_NAME });
186
+ }
187
+ // Leftovers of a `registerHooks` migration: the pre-rename cache dir (gated on its own
188
+ // existence, not the key's) and the disabled key (listed only while present). Both stay
189
+ // refused while the old key is enabled, or (same VERDICT check as above) while the build is
190
+ // not verified as sole active plugin — installed_plugins.json alone can register the
191
+ // upstream key here with no enabledPlugins entry at all.
192
+ {
193
+ const d = deps.memory ?? (await diagnoseMemory());
194
+ const oldCacheDir = path.join(cfg, "plugins", "cache", UPSTREAM_MARKETPLACE_OWNER, UPSTREAM_PLUGIN_NAME);
195
+ let oldKeyValue;
196
+ try {
197
+ const settings = JSON.parse(fs.readFileSync(userSettings, "utf8"));
198
+ oldKeyValue = settings.enabledPlugins?.[UPSTREAM_PLUGIN_KEY];
199
+ }
200
+ catch {
201
+ oldKeyValue = undefined;
202
+ }
203
+ let refused;
204
+ if (oldKeyValue === true) {
205
+ refused = `the ${UPSTREAM_PLUGIN_KEY} key is still enabled — migrate first: \`freeharness init --scope user --only memory\``;
206
+ }
207
+ else if (d.verdict !== "ours") {
208
+ const verdictLine = d.lines.find((l) => l.includes("VERDICT")) ?? `verdict ${d.verdict}`;
209
+ refused = `the FreeHarness build is not the only active memory plugin yet (${verdictLine.replace(/^(OK|WARN)\s+/, "")}); run \`freeharness memory activate\` first`;
210
+ }
211
+ if (fs.existsSync(oldCacheDir)) {
212
+ plan.items.push({ kind: "dir", target: oldCacheDir, reason: `previous registration, migrated to ${PLUGIN_NAME}`, group: "memory", bytes: dirBytes(oldCacheDir), refused });
213
+ }
214
+ if (oldKeyValue !== undefined) {
215
+ plan.items.push({ kind: "settings-key", target: userSettings, reason: `disabled ${UPSTREAM_PLUGIN_KEY} key left in enabledPlugins after migrating to ${PLUGIN_KEY}`, group: "memory", settingsKey: UPSTREAM_PLUGIN_KEY, refused });
216
+ }
217
+ }
218
+ const legacy = path.join(cfg, "commands", "openspec");
219
+ if (fs.existsSync(legacy)) {
220
+ plan.items.push({ kind: "dir", target: legacy, reason: "legacy OpenSpec /openspec:* commands (replaced by /opsx)", group: "legacy", bytes: dirBytes(legacy) });
221
+ }
222
+ // A user-level `openspec init` leaves OpenSpec skills/commands in ~/.claude; FreeHarness
223
+ // emits those per project only, so a global copy shows them twice everywhere.
224
+ const globalSkills = path.join(cfg, "skills");
225
+ if (fs.existsSync(globalSkills)) {
226
+ for (const n of fs.readdirSync(globalSkills).filter((n) => n.startsWith("openspec-")).sort()) {
227
+ const d = path.join(globalSkills, n);
228
+ plan.items.push({ kind: "dir", target: d, reason: "global copy of an OpenSpec skill (FreeHarness emits openspec-* skills per project)", group: "legacy", bytes: dirBytes(d) });
229
+ }
230
+ }
231
+ const globalOpsx = path.join(cfg, "commands", "opsx");
232
+ if (fs.existsSync(globalOpsx)) {
233
+ plan.items.push({ kind: "dir", target: globalOpsx, reason: "global copy of the /opsx commands (FreeHarness emits them per project)", group: "legacy", bytes: dirBytes(globalOpsx) });
234
+ }
235
+ // Claude Code plugins are removed by Claude Code itself; report only.
236
+ for (const key of installedPluginKeys(cfg).filter((k) => /openspec|claude-mem/i.test(k) && k !== PLUGIN_KEY)) {
237
+ plan.notes.push(`Claude Code plugin ${key} is installed (not removed here): run \`claude plugin uninstall ${key}\` if it duplicates FreeHarness`);
238
+ }
239
+ const pkgs = globalNpmPackages();
240
+ if (pkgs === null)
241
+ plan.notes.push(`npm not available: global ${LEGACY_OPENSPEC_PACKAGE} and ${CLI_PACKAGE} not checked`);
242
+ else {
243
+ const v = pkgs[LEGACY_OPENSPEC_PACKAGE];
244
+ if (v !== undefined)
245
+ plan.items.push({ kind: "npm-global", target: LEGACY_OPENSPEC_PACKAGE, reason: `global npm package ${v} (shadowed by the vendored openspec bin)`, group: "npm" });
246
+ // A global `npm link` of the CLI resolves before ~/.local/bin on most PATHs and may
247
+ // point at an older build.
248
+ const linkedBin = globalNpmFreeharnessBin();
249
+ if (pkgs[CLI_PACKAGE] !== undefined || linkedBin) {
250
+ plan.items.push({ kind: "npm-global", target: CLI_PACKAGE, reason: `npm -g ${CLI_PACKAGE} (old npm link; shadows ~/.local/bin)${linkedBin ? ` at ${linkedBin}` : ""}`, group: "npm", unlink: true });
251
+ }
252
+ }
253
+ // Retired names: listed only when the manifest records the path, so a same-named
254
+ // user-owned skill/agent stays untouched (doctor reports that case as INFO, never here).
255
+ const manifest = readManifest(manifestPath("user", ""));
256
+ for (const f of retiredFindings(path.join(os.homedir(), ".claude"), manifest)) {
257
+ if (!f.recorded)
258
+ continue;
259
+ plan.items.push({
260
+ kind: f.kind === "skill" ? "dir" : "file",
261
+ target: f.path,
262
+ reason: `retired ${f.kind} (renamed or folded elsewhere; kept on disk by update's additive manifest merge)`,
263
+ group: "retired",
264
+ bytes: f.kind === "skill" ? dirBytes(f.path) : undefined,
265
+ });
266
+ }
267
+ plan.notes.push(`kept: ${path.join(os.homedir(), ".local", "share", "openspec", "schemas", "spec-driven")} (the FreeHarness schema override)`);
268
+ return plan;
269
+ }
270
+ /** Computes the project-scope cleanup list; nothing is touched. */
271
+ function planProject(projectRoot, deps) {
272
+ const plan = { scope: "project", items: [], notes: [] };
273
+ const claude = path.join(projectRoot, ".claude");
274
+ const legacy = path.join(claude, "commands", "openspec");
275
+ if (fs.existsSync(legacy)) {
276
+ plan.items.push({ kind: "dir", target: legacy, reason: "legacy OpenSpec /openspec:* commands (replaced by /opsx)", group: "legacy", bytes: dirBytes(legacy) });
277
+ }
278
+ if (deps.canonicalDir) {
279
+ const names = canonicalNames(deps.canonicalDir);
280
+ for (const a of names.agents) {
281
+ const f = path.join(claude, "agents", a);
282
+ if (fs.existsSync(f) && fs.statSync(f).isFile()) {
283
+ plan.items.push({ kind: "file", target: f, reason: "project copy of a global FreeHarness agent (shown twice in Claude Code)", group: "duplicates" });
284
+ }
285
+ }
286
+ for (const s of names.skills) {
287
+ const d = path.join(claude, "skills", s);
288
+ if (fs.existsSync(d) && fs.statSync(d).isDirectory()) {
289
+ plan.items.push({ kind: "dir", target: d, reason: "project copy of a global FreeHarness skill (shown twice in Claude Code)", group: "duplicates", bytes: dirBytes(d) });
290
+ }
291
+ }
292
+ }
293
+ const settings = path.join(claude, "settings.json");
294
+ if (fs.existsSync(settings) && pruneHooksMatching(settings, isOurHook, true) > 0) {
295
+ plan.items.push({ kind: "hooks", target: settings, reason: "FreeHarness hook entries (duplicates of the global ~/.claude/settings.json hooks)", group: "duplicates", hookOwner: "freeharness" });
296
+ }
297
+ // Old project-scope emission (pre-v0.3.0): hook scripts + safety.md under
298
+ // <project>/.freeharness/. Only FreeHarness-named files are listed; the manifest and
299
+ // anything foreign in that dir stay.
300
+ const fhDir = path.join(projectRoot, ".freeharness");
301
+ const oldHooks = path.join(fhDir, "hooks");
302
+ if (fs.existsSync(oldHooks)) {
303
+ const ours = fs.readdirSync(oldHooks).filter((n) => n.startsWith("freeharness-"));
304
+ if (ours.length > 0 && ours.length === fs.readdirSync(oldHooks).length) {
305
+ plan.items.push({ kind: "dir", target: oldHooks, reason: "old project-scope hook scripts (hooks are global since v0.3.0)", group: "duplicates", bytes: dirBytes(oldHooks) });
306
+ }
307
+ else {
308
+ for (const n of ours)
309
+ plan.items.push({ kind: "file", target: path.join(oldHooks, n), reason: "old project-scope hook script (hooks are global since v0.3.0)", group: "duplicates" });
310
+ }
311
+ }
312
+ const oldSafety = path.join(fhDir, "safety.md");
313
+ if (fs.existsSync(oldSafety))
314
+ plan.items.push({ kind: "file", target: oldSafety, reason: "old project-scope safety.md (global since v0.3.0)", group: "duplicates" });
315
+ plan.notes.push(`kept: ${path.join(claude, "commands", "opsx")} and ${path.join(claude, "skills", "openspec-*")} (ours)`);
316
+ return plan;
317
+ }
318
+ /** Builds the cleanup plan for a scope without touching anything; `items` is empty when there is nothing to do. */
319
+ export async function planCleanup(scope, projectRoot, deps = {}) {
320
+ return scope === "user" ? planUser(deps) : planProject(projectRoot, deps);
321
+ }
322
+ /** Removes the confirmed items of a plan (`confirmed` defaults to all); refused items are always skipped. */
323
+ export function applyCleanup(plan, confirmed = () => true) {
324
+ const out = { removed: [], skipped: [], warnings: [] };
325
+ for (const item of plan.items) {
326
+ if (item.refused || !confirmed(item)) {
327
+ out.skipped.push(item.target);
328
+ continue;
329
+ }
330
+ switch (item.kind) {
331
+ case "dir":
332
+ case "file":
333
+ fs.rmSync(item.target, { recursive: true, force: true });
334
+ out.removed.push(item.target);
335
+ break;
336
+ case "hooks": {
337
+ const claudeMem = item.hookOwner === UPSTREAM_PLUGIN_NAME;
338
+ pruneHooksMatching(item.target, claudeMem ? isClaudeMemHook : isOurHook, false);
339
+ out.removed.push(`${item.target} (${claudeMem ? UPSTREAM_PLUGIN_NAME : "FreeHarness"} hook entries)`);
340
+ break;
341
+ }
342
+ case "npm-global": {
343
+ const env = { ...process.env, NPM_CONFIG_UPDATE_NOTIFIER: "false" };
344
+ const run = (verb) => spawnSync("npm", [verb, "-g", item.target], { encoding: "utf8", timeout: 120000, env });
345
+ // A linked checkout goes through `unlink`; a real global install answers to `uninstall`.
346
+ const verbs = item.unlink ? ["unlink", "uninstall"] : ["uninstall"];
347
+ let verb = verbs[0];
348
+ let r = run(verb);
349
+ for (const next of verbs.slice(1)) {
350
+ if (r.status === 0)
351
+ break;
352
+ verb = next;
353
+ r = run(verb);
354
+ }
355
+ if (r.status === 0)
356
+ out.removed.push(`npm -g ${item.target}`);
357
+ else
358
+ out.warnings.push(`npm ${verb} -g ${item.target} failed (exit ${r.status ?? "?"}): ${(r.stderr || r.stdout || "").trim().split("\n")[0]}`);
359
+ break;
360
+ }
361
+ case "settings-key": {
362
+ const settings = JSON.parse(fs.readFileSync(item.target, "utf8"));
363
+ if (item.settingsKey && settings.enabledPlugins)
364
+ delete settings.enabledPlugins[item.settingsKey];
365
+ fs.writeFileSync(item.target, JSON.stringify(settings, null, 2) + "\n", "utf8");
366
+ out.removed.push(`${item.target} (removed ${item.settingsKey} from enabledPlugins)`);
367
+ break;
368
+ }
369
+ }
370
+ }
371
+ return out;
372
+ }
373
+ /** Renders a plan item for the preview / doctor output, one line, without the OK/WARN prefix. */
374
+ export function describeItem(item) {
375
+ const size = item.bytes !== undefined ? ` (${humanBytes(item.bytes)})` : "";
376
+ const what = item.kind === "npm-global"
377
+ ? `npm ${item.unlink ? "unlink" : "uninstall"} -g ${item.target}`
378
+ : item.kind === "hooks" || item.kind === "settings-key"
379
+ ? `edit: ${item.target}`
380
+ : `rm: ${item.target}`;
381
+ return `${what}${size} — ${item.reason}${item.refused ? ` — REFUSED: ${item.refused}` : ""}`;
382
+ }
@@ -0,0 +1,304 @@
1
+ import type { PrefixItem } from "./prefix.js";
2
+ export interface UsageBucket {
3
+ input: number;
4
+ output: number;
5
+ cache_read: number;
6
+ cache_write: number;
7
+ messages?: number;
8
+ est_usd?: number;
9
+ }
10
+ export interface AgentUsage {
11
+ model: string;
12
+ usage: {
13
+ input: number;
14
+ output: number;
15
+ cache_read: number;
16
+ cache_write: number;
17
+ total: number;
18
+ };
19
+ messages: number;
20
+ /** Transcript count merged into this entry; absent on lines predating per-transcript merging. */
21
+ dispatches?: number;
22
+ }
23
+ export interface UsageRecord {
24
+ ts: string;
25
+ event?: string;
26
+ session_id: string;
27
+ project: string;
28
+ cwd?: string;
29
+ transcript?: string;
30
+ models?: string[];
31
+ usage: UsageBucket & {
32
+ total?: number;
33
+ };
34
+ by_model?: Record<string, UsageBucket>;
35
+ turns?: number;
36
+ assistant_messages?: number;
37
+ est_usd: number;
38
+ warnings?: string[];
39
+ /** Optional: absent on lines written by an older cost-tracker hook. */
40
+ task_class?: string;
41
+ pruned_bytes?: number;
42
+ context_tokens?: number;
43
+ tool_calls?: number;
44
+ tool_output_chars?: number;
45
+ by_tool?: Record<string, {
46
+ calls: number;
47
+ output_chars: number;
48
+ }>;
49
+ /** Additive; absent on older lines. */
50
+ by_agent?: Record<string, AgentUsage>;
51
+ subagents?: {
52
+ input: number;
53
+ output: number;
54
+ cache_read: number;
55
+ cache_write: number;
56
+ total: number;
57
+ };
58
+ context_window?: number;
59
+ model_suffix?: string | null;
60
+ context_window_source?: "model" | "settings" | "inferred";
61
+ }
62
+ export interface PrunedRecord {
63
+ kind: "pruned";
64
+ ts: string;
65
+ session_id: string;
66
+ project: string;
67
+ bytes: number;
68
+ lines?: number;
69
+ path?: string;
70
+ command?: string;
71
+ }
72
+ export interface CostRow {
73
+ key: string;
74
+ sessions: number;
75
+ turns: number;
76
+ input: number;
77
+ output: number;
78
+ cache_read: number;
79
+ cache_write: number;
80
+ est_usd: number;
81
+ }
82
+ export interface CostReport {
83
+ log: string;
84
+ since?: string;
85
+ project?: string;
86
+ lines: number;
87
+ sessions: number;
88
+ byProject: CostRow[];
89
+ byModel: CostRow[];
90
+ total: CostRow;
91
+ last?: string;
92
+ }
93
+ /** Filters accepted by `cost report`. */
94
+ export interface CostFilter {
95
+ /** Only sessions whose last snapshot is within the last N days. */
96
+ days?: number;
97
+ /** Only this project (basename of the session cwd). */
98
+ project?: string;
99
+ /** Reference "now" (tests); defaults to the wall clock. */
100
+ now?: Date;
101
+ }
102
+ /** Usage log path: FREEHARNESS_COST_LOG when set, else ~/.claude/freeharness/cost/usage.jsonl (may not exist yet). */
103
+ export declare function costLogPath(): string;
104
+ /** Installed price table path (user scope): ~/.claude/freeharness/hooks/cost-prices.json (may not exist yet). */
105
+ export declare function costPricesPath(): string;
106
+ /** Reads and parses usage.jsonl (malformed lines skipped); records in file order, empty when the file is absent. */
107
+ export declare function readUsage(file: string): UsageRecord[];
108
+ /** Reads usage.jsonl into usage snapshots and pruner records (malformed lines skipped); file order, empty when the file is absent. */
109
+ export declare function readLog(file: string): {
110
+ usage: UsageRecord[];
111
+ pruned: PrunedRecord[];
112
+ };
113
+ /**
114
+ * Keeps the last snapshot per session (transcript path, else session id, else the
115
+ * line itself) so cumulative lines are not summed twice; one record per session,
116
+ * in first-seen order.
117
+ */
118
+ export declare function latestPerSession(records: UsageRecord[]): UsageRecord[];
119
+ /** Aggregates usage lines into per-project and per-model rows plus a total; rows sorted by estimated USD descending. */
120
+ export declare function aggregate(records: UsageRecord[], filter?: CostFilter, log?: string): CostReport;
121
+ /** Formats a token count compactly (1.2k, 3.4M). */
122
+ export declare function fmtTokens(n: number): string;
123
+ /** Renders the report as aligned text tables. */
124
+ export declare function formatReport(report: CostReport): string[];
125
+ /**
126
+ * Renders the `cost prefix` session-start injection as one line, apart from the main-session
127
+ * total: Claude Code injects the routing table into the session-start turn only, so its cost
128
+ * never rides every request the way the counted items do.
129
+ * @param injection The estimate's `injection` item.
130
+ * @returns The formatted line.
131
+ */
132
+ export declare function formatInjectionLine(injection: PrefixItem): string;
133
+ /** Doctor summary for the cost log: size, entries, last entry. */
134
+ export declare function costLogLine(file: string): string;
135
+ /** Filters accepted by `cost report --sinks`. */
136
+ export interface SinksFilter extends CostFilter {
137
+ /** Group sessions by task class instead of listing them. */
138
+ byTaskClass?: boolean;
139
+ top?: number;
140
+ }
141
+ /** One ranked sink row (a session or a task class). */
142
+ export interface SinkRow {
143
+ key: string;
144
+ task_class: string;
145
+ sessions: number;
146
+ tokens: number;
147
+ output: number;
148
+ tool_output_tokens: number;
149
+ tool_output_share: number;
150
+ pruned_bytes: number;
151
+ est_usd: number;
152
+ }
153
+ export interface ToolRow {
154
+ tool: string;
155
+ calls: number;
156
+ output_tokens: number;
157
+ }
158
+ export interface SinksReport {
159
+ log: string;
160
+ since?: string;
161
+ project?: string;
162
+ by: "session" | "task-class";
163
+ sessions: number;
164
+ untagged: number;
165
+ pruned_bytes: number;
166
+ byProject: {
167
+ project: string;
168
+ rows: SinkRow[];
169
+ tools: ToolRow[];
170
+ notes: string[];
171
+ }[];
172
+ }
173
+ /**
174
+ * Ranks token sinks per project: sessions (or task classes) by total tokens with the
175
+ * tool-output share and the pruned bytes recovered, and tools by output. Sessions without a
176
+ * task class are `untagged` and counted.
177
+ */
178
+ export declare function sinks(records: UsageRecord[], filter?: SinksFilter, log?: string): SinksReport;
179
+ /** Renders the sinks report. */
180
+ export declare function formatSinks(report: SinksReport): string[];
181
+ /** One `.freeharness/telemetry.jsonl` record written by the cost-tracker hook on SessionEnd (data-contract.md Sink Schema). */
182
+ export interface TelemetryRecord {
183
+ ts: string;
184
+ session_id: string;
185
+ project: string;
186
+ started: string | null;
187
+ ended: string;
188
+ turns: number;
189
+ change: string | null;
190
+ task: string | null;
191
+ tier: string | null;
192
+ task_class: string;
193
+ sinks: {
194
+ main: {
195
+ input: number;
196
+ output: number;
197
+ cache_read: number;
198
+ cache_write: number;
199
+ total: number;
200
+ };
201
+ subagents: {
202
+ input: number;
203
+ output: number;
204
+ cache_read: number;
205
+ cache_write: number;
206
+ total: number;
207
+ };
208
+ by_agent: Record<string, AgentUsage>;
209
+ observer: {
210
+ tokens: number;
211
+ model: string | null;
212
+ } | null;
213
+ observer_local: {
214
+ tokens: number;
215
+ model: string | null;
216
+ } | null;
217
+ tool_output_chars: number;
218
+ pruned_bytes: number;
219
+ agent_calls: number;
220
+ };
221
+ est_usd: number;
222
+ context_max: number;
223
+ context_window: number;
224
+ warnings: string[];
225
+ }
226
+ /**
227
+ * Reads `.freeharness/telemetry.jsonl`: a line that is not valid JSON or lacks
228
+ * `session_id` is skipped and counted on the returned array's `skipped` property; the
229
+ * last record per `session_id` wins (ties broken by later `ts`), matching the
230
+ * `latestPerSession` rule for usage.jsonl. Deduplicated records in first-seen order;
231
+ * empty (with `skipped: 0`) when the file is absent.
232
+ */
233
+ export declare function readTelemetry(file: string): TelemetryRecord[] & {
234
+ skipped: number;
235
+ };
236
+ /** One `cost report --changes` row: a tagged change, or the `untagged` catch-all. */
237
+ export interface ChangeRow {
238
+ change: string;
239
+ sessions: number;
240
+ medianMain: number;
241
+ totalMain: number;
242
+ medianSubagents: number;
243
+ totalSubagents: number;
244
+ totalObserver: number;
245
+ est_usd: number;
246
+ tierMix: string;
247
+ }
248
+ /**
249
+ * Groups telemetry records by their tagged `change` (records with no `change` fall
250
+ * into `untagged`): session count, median and total main/subagent tokens, summed
251
+ * observer tokens, summed est USD, and the tier mix (`standard 2, fast 1`, tiers with
252
+ * no session omitted). One row per change, ranked by total main tokens descending.
253
+ */
254
+ export declare function changes(records: TelemetryRecord[]): ChangeRow[];
255
+ /** Renders the `--changes` report; `skipped` counts lines `readTelemetry` dropped (malformed or without `session_id`). */
256
+ export declare function formatChanges(rows: ChangeRow[], file: string, skipped: number): string[];
257
+ /** Price row per MTok as read from `cost-prices.json` (see `canonical/hooks/cost-prices.json`). */
258
+ interface PriceRow {
259
+ input: number;
260
+ output: number;
261
+ cache_read: number;
262
+ cache_write: number;
263
+ window?: number;
264
+ }
265
+ /**
266
+ * Loads the price table for `agents()`'s USD estimate; a missing or malformed file
267
+ * yields an empty table so the estimate degrades to 0 instead of throwing.
268
+ * Mirrors `loadPrices` in canonical/hooks/freeharness-cost-tracker.mjs; kept as a
269
+ * separate copy since the hook is a standalone, dependency-free .mjs the CLI does not
270
+ * import from. Returns model name -> price row (per MTok).
271
+ */
272
+ export declare function loadPriceTable(file: string): Record<string, PriceRow>;
273
+ /** One `cost report --agents` row (ranked by total tokens). */
274
+ export interface AgentRow {
275
+ name: string;
276
+ model: string;
277
+ /** Transcript count (`AgentUsage.dispatches`, summed across sessions; 1 per entry on older lines that predate the field). */
278
+ dispatches: number;
279
+ /** Assistant-message count (`AgentUsage.messages`, summed across sessions); a separate figure from `dispatches`. */
280
+ messages: number;
281
+ input: number;
282
+ output: number;
283
+ cache_read: number;
284
+ cache_write: number;
285
+ tokens: number;
286
+ est_usd: number;
287
+ }
288
+ /**
289
+ * Ranks subagent dispatches by total tokens from each session's last `by_agent`
290
+ * snapshot: per agent name, the model with the most tokens (ties broken by name,
291
+ * mirroring the hook's `mergeAgentUsage`), `dispatches` and `messages` each summed
292
+ * across sessions from their own fields (an entry from before per-transcript merging
293
+ * carries no `dispatches` and counts as 1), tokens per bucket summed across sessions,
294
+ * and an est USD from the price table. Rows ranked by total tokens descending.
295
+ */
296
+ export declare function agents(records: UsageRecord[], prices?: Record<string, PriceRow>): AgentRow[];
297
+ /** Renders the `--agents` report; `priceTable.loaded: false` prints a notice, `true` (or omitted) prints none. */
298
+ export declare function formatAgents(rows: AgentRow[], priceTable?: {
299
+ file: string;
300
+ loaded: boolean;
301
+ }): string[];
302
+ /** Per-project telemetry file path: `<dir>/.freeharness/telemetry.jsonl` (may not exist yet); `dir` defaults to the current working directory. */
303
+ export declare function telemetryPath(dir?: string): string;
304
+ export {};