freeharness 0.4.1 → 0.4.3

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 (203) hide show
  1. package/NOTICE +2 -1
  2. package/README.md +24 -19
  3. package/canonical/HARVEST.yaml +27 -16
  4. package/canonical/PROVENANCE.yaml +3 -3
  5. package/canonical/agents/README.md +77 -36
  6. package/canonical/agents/agentic-architect.md +2 -1
  7. package/canonical/agents/database-reviewer.md +6 -8
  8. package/canonical/agents/explorer-ai-engineer-analyst.md +2 -6
  9. package/canonical/agents/implementer.md +30 -0
  10. package/canonical/agents/pr-reviewer.md +9 -11
  11. package/canonical/agents/python-reviewer.md +7 -9
  12. package/canonical/agents/report-formatter.md +7 -3
  13. package/canonical/agents/security-reviewer.md +14 -9
  14. package/canonical/agents/test-writer.md +5 -2
  15. package/canonical/hooks/cost-prices.json +11 -9
  16. package/canonical/hooks/freeharness-config-guard.mjs +4155 -93
  17. package/canonical/hooks/freeharness-context-monitor.mjs +88 -54
  18. package/canonical/hooks/freeharness-converge.mjs +78 -23
  19. package/canonical/hooks/freeharness-cost-tracker.mjs +1116 -218
  20. package/canonical/hooks/freeharness-memory-guard.mjs +20 -23
  21. package/canonical/hooks/freeharness-precompact.mjs +206 -87
  22. package/canonical/hooks/freeharness-quota-tap.mjs +173 -0
  23. package/canonical/hooks/freeharness-reload-context.mjs +197 -0
  24. package/canonical/hooks/freeharness-repeat-tool.mjs +1 -1
  25. package/canonical/hooks/freeharness-sdd-context.mjs +959 -0
  26. package/canonical/hooks/freeharness-session-start +297 -59
  27. package/canonical/hooks/hooks.claude-code.json +1 -1
  28. package/canonical/hooks/hooks.config-guard.claude-code.json +2 -2
  29. package/canonical/hooks/hooks.context-monitor.claude-code.json +1 -1
  30. package/canonical/hooks/hooks.converge.claude-code.json +1 -1
  31. package/canonical/hooks/hooks.memory-guard.claude-code.json +1 -1
  32. package/canonical/hooks/hooks.reload-context.claude-code.json +15 -0
  33. package/canonical/openspec/schema.spec-driven.yaml +29 -8
  34. package/canonical/openspec/templates/follow-ups.md +3 -2
  35. package/canonical/openspec/templates/proposal.md +1 -1
  36. package/canonical/openspec/templates/tasks.md +8 -0
  37. package/canonical/rules/CLAUDE.md +5 -4
  38. package/canonical/rules/routing.md +10 -9
  39. package/canonical/skills/agent-evaluation/SKILL.md +17 -6
  40. package/canonical/skills/agent-evaluation/evals/cases.yaml +38 -23
  41. package/canonical/skills/agentic-architecture/SKILL.md +4 -0
  42. package/canonical/skills/agentic-architecture/evals/cases.yaml +27 -0
  43. package/canonical/skills/code-review/SKILL.md +10 -3
  44. package/canonical/skills/code-review/evals/cases.yaml +1118 -27
  45. package/canonical/skills/git-workflow/SKILL.md +2 -0
  46. package/canonical/skills/local-code/SKILL.md +2 -2
  47. package/canonical/skills/local-code/evals/cases.yaml +1 -1
  48. package/canonical/skills/report-formatting/SKILL.md +10 -2
  49. package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +3 -3
  50. package/canonical/skills/report-formatting/reference/HELPERS.md +10 -5
  51. package/canonical/skills/report-formatting/reference/RELEASE.md +1 -2
  52. package/canonical/skills/report-formatting/reference/TEMPLATES.md +3 -2
  53. package/canonical/skills/report-formatting/reference/authoring.md +25 -37
  54. package/canonical/skills/report-formatting/scripts/build-architecture.js +1 -1
  55. package/canonical/skills/report-formatting/scripts/build-md-claro.js +5 -8
  56. package/canonical/skills/report-formatting/scripts/check-env.js +0 -4
  57. package/canonical/skills/report-formatting/scripts/docmodel.js +3 -11
  58. package/canonical/skills/report-formatting/scripts/example-build.js +8 -10
  59. package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +8 -10
  60. package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +8 -10
  61. package/canonical/skills/report-formatting/scripts/theme-claro.js +39 -10
  62. package/canonical/skills/subagent-driven-development/SKILL.md +35 -28
  63. package/canonical/skills/subagent-driven-development/evals/cases.yaml +3625 -19
  64. package/canonical/skills/subagent-driven-development/reference/brief.md +187 -35
  65. package/canonical/skills/subagent-driven-development/reference/handoff.md +96 -2
  66. package/canonical/skills/subagent-driven-development/reference/loop.md +56 -0
  67. package/canonical/skills/subagent-driven-development/reference/trees.md +40 -0
  68. package/canonical/skills/subagent-driven-development/reference/workspace.md +31 -0
  69. package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +4035 -327
  70. package/dist/adapters/harness/claude-code.d.ts +26 -3
  71. package/dist/adapters/harness/claude-code.js +214 -43
  72. package/dist/adapters/harness/opencode.js +17 -3
  73. package/dist/adapters/harness/plugin.js +28 -17
  74. package/dist/adapters/memory/doctor.d.ts +172 -33
  75. package/dist/adapters/memory/doctor.js +750 -139
  76. package/dist/adapters/memory/migrate.d.ts +95 -0
  77. package/dist/adapters/memory/migrate.js +497 -0
  78. package/dist/adapters/memory/paths.d.ts +15 -0
  79. package/dist/adapters/memory/paths.js +47 -0
  80. package/dist/adapters/memory/vendored.d.ts +52 -30
  81. package/dist/adapters/memory/vendored.js +161 -169
  82. package/dist/adapters/memory/worker-control.d.ts +9 -0
  83. package/dist/adapters/memory/worker-control.js +30 -0
  84. package/dist/adapters/sync/diff.js +4 -1
  85. package/dist/cli/cleanup-prompt.d.ts +35 -0
  86. package/dist/cli/cleanup-prompt.js +68 -0
  87. package/dist/cli/cleanup.d.ts +22 -24
  88. package/dist/cli/cleanup.js +204 -75
  89. package/dist/cli/cost-quota.d.ts +158 -0
  90. package/dist/cli/cost-quota.js +645 -0
  91. package/dist/cli/cost.d.ts +408 -46
  92. package/dist/cli/cost.js +1731 -57
  93. package/dist/cli/doctor.d.ts +52 -13
  94. package/dist/cli/doctor.js +199 -45
  95. package/dist/cli/hard-cleanup.d.ts +93 -0
  96. package/dist/cli/hard-cleanup.js +552 -0
  97. package/dist/cli/install-facts.d.ts +29 -0
  98. package/dist/cli/install-facts.js +68 -0
  99. package/dist/cli/json-key-edit.d.ts +18 -0
  100. package/dist/cli/json-key-edit.js +118 -0
  101. package/dist/cli/memory-import.d.ts +74 -0
  102. package/dist/cli/memory-import.js +469 -0
  103. package/dist/cli/plugin-validate.d.ts +12 -0
  104. package/dist/cli/plugin-validate.js +43 -0
  105. package/dist/cli/profile.d.ts +1 -1
  106. package/dist/cli/profile.js +10 -10
  107. package/dist/cli/uninstall.d.ts +1 -1
  108. package/dist/cli/uninstall.js +2 -2
  109. package/dist/cli/upstream-launchers.d.ts +49 -0
  110. package/dist/cli/upstream-launchers.js +287 -0
  111. package/dist/index.js +252 -73
  112. package/dist/infra/fsutil.d.ts +30 -3
  113. package/dist/infra/fsutil.js +68 -6
  114. package/dist/infra/manifest.d.ts +2 -1
  115. package/dist/infra/manifest.js +3 -2
  116. package/dist/infra/prices.d.ts +18 -0
  117. package/dist/infra/prices.js +64 -0
  118. package/node_modules/@freeharness/core/dist/registry.d.ts +1 -1
  119. package/node_modules/@freeharness/core/dist/registry.js +3 -3
  120. package/node_modules/@freeharness/core/dist/tiers.d.ts +10 -1
  121. package/node_modules/@freeharness/core/dist/tiers.js +24 -2
  122. package/node_modules/@freeharness/core/package.json +1 -1
  123. package/node_modules/@freeharness/sdd/PROVENANCE.yaml +279 -2
  124. package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +34 -8
  125. package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +7 -6
  126. package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +72 -64
  127. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +48 -16
  128. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +168 -70
  129. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +36 -6
  130. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +110 -49
  131. package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +1 -1
  132. package/node_modules/@freeharness/sdd/dist/src/shim/list.js +57 -14
  133. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.d.ts +7 -0
  134. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.js +118 -0
  135. package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +3 -1
  136. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.d.ts +12 -0
  137. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.js +94 -0
  138. package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +17 -0
  139. package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +182 -1
  140. package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +2 -2
  141. package/node_modules/@freeharness/sdd/dist/src/shim/status.js +34 -5
  142. package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +9 -0
  143. package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +49 -41
  144. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.d.ts +21 -0
  145. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.js +66 -0
  146. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.d.ts +4 -0
  147. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.js +17 -0
  148. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +95 -22
  149. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +6 -0
  150. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +14 -6
  151. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +1 -1
  152. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +1 -1
  153. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +3 -3
  154. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +24 -4
  155. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +4 -7
  156. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +13 -1
  157. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +21 -6
  158. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +80 -19
  159. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +42 -22
  160. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.d.ts +32 -0
  161. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.js +39 -0
  162. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +16 -9
  163. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +36 -1
  164. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +65 -30
  165. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +1 -1
  166. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +1 -1
  167. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +8 -13
  168. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +79 -102
  169. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +4 -9
  170. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +50 -10
  171. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +165 -37
  172. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +24 -3
  173. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +55 -8
  174. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +4 -1
  175. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +19 -9
  176. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +10 -0
  177. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +115 -0
  178. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +3 -3
  179. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +20 -20
  180. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +6 -0
  181. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +75 -14
  182. package/node_modules/@freeharness/sdd/package.json +1 -1
  183. package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +47 -7
  184. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +25 -7
  185. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +50 -11
  186. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +48 -10
  187. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +27 -7
  188. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +25 -12
  189. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +17 -5
  190. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +12 -1
  191. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +47 -33
  192. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +17 -5
  193. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +29 -1
  194. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +31 -13
  195. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +75 -27
  196. package/package.json +6 -4
  197. package/provenance/canonical.PROVENANCE.yaml +3 -3
  198. package/provenance/memory.PROVENANCE.yaml +472 -6
  199. package/provenance/sdd.PROVENANCE.yaml +279 -2
  200. package/canonical/hooks/freeharness-auto-memory.mjs +0 -158
  201. package/canonical/hooks/hooks.auto-memory.claude-code.json +0 -16
  202. package/canonical/openspec/config.base.yaml +0 -89
  203. package/canonical/rules/safety.md +0 -41
@@ -0,0 +1,9 @@
1
+ import fs from "node:fs";
2
+ export interface StopResult {
3
+ /** True when no worker answers and no live pid file remains. */
4
+ stopped: boolean;
5
+ wasRunning: boolean;
6
+ reason?: string;
7
+ }
8
+ /** True when `pidFile` exists and names a live process, or cannot be read as a pid record. */
9
+ export declare function pidFileLive(pidFile: string, f?: typeof fs): boolean;
@@ -0,0 +1,30 @@
1
+ // The worker's pid-file check and the stop outcome shared by the doctor's stop and restart paths.
2
+ import fs from "node:fs";
3
+ function readJsonText(raw) {
4
+ try {
5
+ return JSON.parse(raw) ?? {};
6
+ }
7
+ catch {
8
+ return {};
9
+ }
10
+ }
11
+ /** True when `pidFile` exists and names a live process, or cannot be read as a pid record. */
12
+ export function pidFileLive(pidFile, f = fs) {
13
+ let raw;
14
+ try {
15
+ raw = f.readFileSync(pidFile, "utf8");
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ const pid = Number(readJsonText(raw).pid);
21
+ if (!Number.isInteger(pid) || pid <= 0)
22
+ return true;
23
+ try {
24
+ process.kill(pid, 0);
25
+ return true;
26
+ }
27
+ catch (e) {
28
+ return e.code === "EPERM";
29
+ }
30
+ }
@@ -87,7 +87,10 @@ function manifest(target, repo, fork) {
87
87
  label: "plugin/.mcp.json", vendored: path.join(v, "plugin/.mcp.json"), upstream: path.join(fork, "plugin/.mcp.json"), contract: true,
88
88
  divergent: "rewritten to spawn ${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.cjs directly (memory-slim)",
89
89
  },
90
- { label: "mem-search/SKILL.md", vendored: path.join(v, "plugin/skills/mem-search/SKILL.md"), upstream: path.join(fork, "plugin/skills/mem-search/SKILL.md"), contract: true },
90
+ {
91
+ label: "mem-search/SKILL.md", vendored: path.join(v, "plugin/skills/mem-search/SKILL.md"), upstream: path.join(fork, "plugin/skills/mem-search/SKILL.md"), contract: true,
92
+ divergent: "rewritten to name only the tools the slim MCP server registers (radar-w43-fixes)",
93
+ },
91
94
  ];
92
95
  }
93
96
  throw new Error(`Unknown sync target: ${target}. Valid: sdd, memory.`);
@@ -0,0 +1,35 @@
1
+ import type { PromptPort } from "@freeharness/core";
2
+ import { type CleanupItem, type CleanupPlan, type CleanupResult } from "./cleanup.js";
3
+ import { type HardPlan, type HardResult } from "./hard-cleanup.js";
4
+ export interface CleanupAnswers {
5
+ main: boolean;
6
+ backups: boolean;
7
+ hard: boolean;
8
+ }
9
+ export interface CleanupDecision {
10
+ abort?: string;
11
+ remove: (item: CleanupItem) => boolean;
12
+ moveHard: boolean;
13
+ }
14
+ /**
15
+ * Maps the answers to pass-2 actions; the hard answer counts only with --hard.
16
+ * @param offersMoves The cleanup plan holds an archive item, so the abort line names moves too.
17
+ */
18
+ export declare function cleanupDecision(answers: CleanupAnswers, withHard: boolean, offersMoves?: boolean): CleanupDecision;
19
+ /** The third cleanup confirmation, for the hard items. */
20
+ export declare function hardPrompt(count: number, archiveRoot: string): string;
21
+ export interface CleanupPass2 {
22
+ plan: CleanupPlan;
23
+ hard?: HardPlan;
24
+ /** `--yes`: every prompt is answered yes and none is shown. */
25
+ yes: boolean;
26
+ prompt: Pick<PromptPort, "confirm">;
27
+ log: (line: string) => void;
28
+ }
29
+ /** `archived` is absent when the hard plan was not confirmed. */
30
+ export interface CleanupPass2Result {
31
+ done: CleanupResult;
32
+ archived?: HardResult;
33
+ }
34
+ /** Asks the three confirmations, then applies what was confirmed; undefined when all were no. */
35
+ export declare function runCleanupPass2(input: CleanupPass2): Promise<CleanupPass2Result | undefined>;
@@ -0,0 +1,68 @@
1
+ import { applyCleanup } from "./cleanup.js";
2
+ import { applyHard, printable } from "./hard-cleanup.js";
3
+ /**
4
+ * Maps the answers to pass-2 actions; the hard answer counts only with --hard.
5
+ * @param offersMoves The cleanup plan holds an archive item, so the abort line names moves too.
6
+ */
7
+ export function cleanupDecision(answers, withHard, offersMoves = false) {
8
+ const moveHard = withHard && answers.hard;
9
+ const nothing = !answers.main && !answers.backups && !moveHard;
10
+ let abort;
11
+ if (nothing)
12
+ abort = withHard || offersMoves ? "Aborted; nothing was removed or moved." : "Aborted; nothing was removed.";
13
+ return { abort, remove: (i) => (i.group === "backups" ? answers.backups : answers.main), moveHard };
14
+ }
15
+ /** The first cleanup confirmation; a zero remove or archive count is left out. */
16
+ function mainPrompt(removeCount, archiveCount) {
17
+ if (archiveCount === 0)
18
+ return `\nRemove the ${removeCount} item(s) above (backups excluded)?`;
19
+ if (removeCount === 0)
20
+ return `\nArchive the ${archiveCount} item(s) above (backups excluded)?`;
21
+ return `\nRemove ${removeCount} and archive ${archiveCount} item(s) above (backups excluded)?`;
22
+ }
23
+ /** The third cleanup confirmation, for the hard items. */
24
+ export function hardPrompt(count, archiveRoot) {
25
+ return `Move the ${count} item(s) to ${printable(archiveRoot)}?`;
26
+ }
27
+ /** Asks the three confirmations, then applies what was confirmed; undefined when all were no. */
28
+ export async function runCleanupPass2(input) {
29
+ const { plan, hard, yes, prompt, log } = input;
30
+ const removable = plan.items.filter((i) => !i.refused);
31
+ const main = removable.filter((i) => i.group !== "backups");
32
+ const backups = removable.filter((i) => i.group === "backups");
33
+ const movable = hard?.items.filter((i) => !i.refused) ?? [];
34
+ const archive = main.filter((i) => i.archiveDest !== undefined).length;
35
+ let confirmMain = yes;
36
+ let confirmBackups = yes;
37
+ let confirmHard = yes;
38
+ if (!yes) {
39
+ if (main.length > 0)
40
+ confirmMain = await prompt.confirm(mainPrompt(main.length - archive, archive));
41
+ if (backups.length > 0) {
42
+ const total = backups.reduce((n, b) => n + (b.bytes ?? 0), 0);
43
+ confirmBackups = await prompt.confirm(`Also delete the ${backups.length} memory backup dir(s) (${total >= 1048576 ? `${(total / 1048576).toFixed(1)} MB` : `${total} B`})? They cannot be recovered.`);
44
+ }
45
+ if (hard && movable.length > 0)
46
+ confirmHard = await prompt.confirm(hardPrompt(movable.length, hard.archiveRoot));
47
+ }
48
+ const decision = cleanupDecision({ main: confirmMain, backups: confirmBackups, hard: confirmHard }, hard !== undefined, archive > 0);
49
+ if (decision.abort) {
50
+ log(decision.abort);
51
+ return undefined;
52
+ }
53
+ const done = applyCleanup(plan, decision.remove);
54
+ for (const r of done.removed)
55
+ log(` removed: ${printable(r)}`);
56
+ for (const m of done.moved)
57
+ log(` moved: ${printable(m)}`);
58
+ for (const k of done.skipped)
59
+ log(` kept: ${printable(k)}`);
60
+ for (const w of done.warnings)
61
+ log(` WARN ${printable(w)}`);
62
+ const archived = hard && decision.moveHard ? applyHard(hard) : undefined;
63
+ for (const m of archived?.moved ?? [])
64
+ log(` moved: ${printable(m)}`);
65
+ for (const w of archived?.warnings ?? [])
66
+ log(` WARN ${printable(w)}`);
67
+ return { done, archived };
68
+ }
@@ -1,6 +1,5 @@
1
1
  import type { Scope } from "@freeharness/core";
2
2
  import { UPSTREAM_PLUGIN_NAME } from "../adapters/memory/vendored.js";
3
- import { type InstallManifest } from "../infra/manifest.js";
4
3
  import { type MemoryDiagnosis } from "../adapters/memory/doctor.js";
5
4
  /** The vendored `openspec` bin replaces it. */
6
5
  export declare const LEGACY_OPENSPEC_PACKAGE = "@fission-ai/openspec";
@@ -8,25 +7,11 @@ export declare const LEGACY_OPENSPEC_PACKAGE = "@fission-ai/openspec";
8
7
  export declare const CLI_PACKAGE = "freeharness";
9
8
  /** Backups confirm separately. */
10
9
  export type CleanupGroup = "memory" | "backups" | "legacy" | "duplicates" | "npm" | "retired";
11
- /** Retired by a rename or a fold, bare names under `~/.claude`. */
12
- export declare const RETIRED: {
13
- readonly skills: readonly ["local-code-intel"];
14
- readonly agents: readonly ["mle-reviewer", "mle-evaluator"];
15
- };
16
- /** One retired name found on disk, with its manifest-recorded flag. */
17
- export interface RetiredFinding {
18
- kind: "skill" | "agent";
19
- name: string;
20
- /** Skill dir or agent file. */
21
- path: string;
22
- /** Manifest lists `path` (agent) or a file under it (skill dir). */
23
- recorded: boolean;
24
- }
25
- /** Whose entries a `hooks` item prunes. */
26
- export type HookOwner = "freeharness" | typeof UPSTREAM_PLUGIN_NAME;
10
+ /** Whose entries a `hooks` item prunes; `retired` is only the entries running a retired hook script. */
11
+ export type HookOwner = "freeharness" | typeof UPSTREAM_PLUGIN_NAME | "retired";
27
12
  /** One leftover the cleanup knows how to remove. */
28
13
  export interface CleanupItem {
29
- kind: "dir" | "file" | "hooks" | "npm-global" | "settings-key";
14
+ kind: "dir" | "file" | "hooks" | "npm-global" | "json-key";
30
15
  /** Filesystem path, or the package name for `npm-global`. */
31
16
  target: string;
32
17
  reason: string;
@@ -39,8 +24,14 @@ export interface CleanupItem {
39
24
  hookOwner?: HookOwner;
40
25
  /** `npm-global` items: try `npm unlink -g` first, then `npm uninstall -g`. */
41
26
  unlink?: boolean;
42
- /** `settings-key` items: the `enabledPlugins` key removed from `target`. */
43
- settingsKey?: string;
27
+ /** `json-key` items: the key removed from the JSON object in `target`, outermost first. */
28
+ keyPath?: string[];
29
+ /** Plugin-cache and IDE-rule items: the archive path the item moves to instead of being removed; `json-key` items: where the pre-edit copy goes. */
30
+ archiveDest?: string;
31
+ /** Marks an IDE rule file, whose archive check differs from a plugin-cache item's. */
32
+ ideRule?: boolean;
33
+ /** Upstream launcher items, each kind with its own pass-2 check. */
34
+ launcher?: "mcp" | "marketplace";
44
35
  }
45
36
  /** What a cleanup pass would (or did) do. */
46
37
  export interface CleanupPlan {
@@ -48,10 +39,14 @@ export interface CleanupPlan {
48
39
  items: CleanupItem[];
49
40
  /** Informational lines: seen, but out of the fixed list. */
50
41
  notes: string[];
42
+ /** User scope: the dated archive every `archiveDest` lies in. */
43
+ archiveRoot?: string;
51
44
  }
52
45
  /** Outcome of `applyCleanup`. */
53
46
  export interface CleanupResult {
54
47
  removed: string[];
48
+ /** One `source -> destination` line per plugin-cache item moved into the archive. */
49
+ moved: string[];
55
50
  /** Refused, or not confirmed. */
56
51
  skipped: string[];
57
52
  /** Non-fatal failures (an npm uninstall that exited non-zero). */
@@ -63,6 +58,8 @@ export interface CleanupDeps {
63
58
  memory?: MemoryDiagnosis;
64
59
  /** Names the agents/skills a project may duplicate. */
65
60
  canonicalDir?: string;
61
+ /** The run's dated archive, shared with the hard plan (default: today's under the home dir). */
62
+ archiveRoot?: string;
66
63
  }
67
64
  /** Sums the size (bytes) of every file under a directory. */
68
65
  export declare function dirBytes(dir: string): number;
@@ -72,12 +69,13 @@ export declare function humanBytes(n: number): string;
72
69
  export declare function globalNpmPackages(): Record<string, string> | null;
73
70
  /** 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. */
74
71
  export declare function globalNpmFreeharnessBin(): string | undefined;
75
- /** Claude-mem data backups written by `freeharness memory backup` (`<dataDir>-backup-<stamp>`), oldest first. */
76
- export declare function memoryBackupDirs(): string[];
72
+ /**
73
+ * Memory data backups written by `freeharness memory backup` (`<dataDir>-backup-<stamp>`), sorted.
74
+ * Backups of the default directory count even while a redirected directory is in use.
75
+ */
76
+ export declare function memoryBackupDirs(env?: Record<string, string | undefined>, home?: string): string[];
77
77
  /** Drops install-manifest entries whose file no longer exists (else every removal is "drift"); 0 without a manifest. */
78
78
  export declare function pruneManifest(scope: Scope, projectRoot: string): number;
79
- /** Retired skill/agent names still present under a `.claude` dir: `update`'s additive merge never deletes them; an absent path is skipped. */
80
- export declare function retiredFindings(claudeDir: string, manifest: InstallManifest | undefined): RetiredFinding[];
81
79
  /** Builds the cleanup plan for a scope without touching anything; `items` is empty when there is nothing to do. */
82
80
  export declare function planCleanup(scope: Scope, projectRoot: string, deps?: CleanupDeps): Promise<CleanupPlan>;
83
81
  /** Removes the confirmed items of a plan (`confirmed` defaults to all); refused items are always skipped. */