@kal-elsam/kairo-runtime 0.1.0

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 (315) hide show
  1. package/README.md +937 -0
  2. package/bin/harness.js +11 -0
  3. package/bin/kairo-runtime.js +11 -0
  4. package/bin/kairo.js +11 -0
  5. package/global-template/components/catalog.json +25 -0
  6. package/global-template/components/orchestrator/orchestrator.md +55 -0
  7. package/global-template/components/sdd-core/handoff.md +38 -0
  8. package/global-template/components/sdd-core/spec-sizing.md +38 -0
  9. package/global-template/components/sdd-core/workflow.md +31 -0
  10. package/global-template/core/orchestrator.md +55 -0
  11. package/package.json +64 -0
  12. package/prompts/HARNESS_ENFORCEMENT_UPGRADE.md +11 -0
  13. package/prompts/HARNESS_ENTERPRISE.md +33 -0
  14. package/prompts/HARNESS_INSTALLER_MASTER.md +853 -0
  15. package/prompts/HARNESS_MINIMAL.md +30 -0
  16. package/prompts/HARNESS_SPEC_SIZING_UPGRADE.md +41 -0
  17. package/prompts/HARNESS_STANDARD.md +35 -0
  18. package/prompts/HARNESS_UNIVERSAL_PARITY.md +56 -0
  19. package/repo-template/.claude/agents/adapter-parity-reviewer.md +17 -0
  20. package/repo-template/.claude/agents/architect.md +17 -0
  21. package/repo-template/.claude/agents/eval-engineer.md +17 -0
  22. package/repo-template/.claude/agents/reviewer.md +17 -0
  23. package/repo-template/.claude/agents/sdd-reviewer.md +17 -0
  24. package/repo-template/.claude/agents/sdd-spec-writer.md +17 -0
  25. package/repo-template/.claude/skills/checkpoint/SKILL.md +13 -0
  26. package/repo-template/.claude/skills/eval-design/SKILL.md +3 -0
  27. package/repo-template/.claude/skills/evals/SKILL.md +13 -0
  28. package/repo-template/.claude/skills/loop-engineering/SKILL.md +16 -0
  29. package/repo-template/.claude/skills/maintainability-review/SKILL.md +3 -0
  30. package/repo-template/.claude/skills/quality-gate-review/SKILL.md +3 -0
  31. package/repo-template/.claude/skills/sdd/SKILL.md +13 -0
  32. package/repo-template/.claude/skills/spec-complexity-classifier/SKILL.md +10 -0
  33. package/repo-template/.claude/skills/spec-escalation-review/SKILL.md +10 -0
  34. package/repo-template/.claude/skills/spec-intake/SKILL.md +10 -0
  35. package/repo-template/.claude/skills/tdd/SKILL.md +13 -0
  36. package/repo-template/.claude/skills/trust-review/SKILL.md +3 -0
  37. package/repo-template/.codex/skills/adapter-parity/SKILL.md +29 -0
  38. package/repo-template/.codex/skills/checkpoint/SKILL.md +36 -0
  39. package/repo-template/.codex/skills/eval-design/SKILL.md +3 -0
  40. package/repo-template/.codex/skills/evals/SKILL.md +28 -0
  41. package/repo-template/.codex/skills/loop-engineering/SKILL.md +24 -0
  42. package/repo-template/.codex/skills/maintainability-review/SKILL.md +3 -0
  43. package/repo-template/.codex/skills/quality-gate-review/SKILL.md +3 -0
  44. package/repo-template/.codex/skills/sdd/SKILL.md +30 -0
  45. package/repo-template/.codex/skills/spec-complexity-classifier/SKILL.md +10 -0
  46. package/repo-template/.codex/skills/spec-escalation-review/SKILL.md +10 -0
  47. package/repo-template/.codex/skills/spec-intake/SKILL.md +10 -0
  48. package/repo-template/.codex/skills/tdd/SKILL.md +27 -0
  49. package/repo-template/.codex/skills/trust-review/SKILL.md +3 -0
  50. package/repo-template/.cursor/agents/adapter-parity-reviewer.md +29 -0
  51. package/repo-template/.cursor/agents/architect.md +27 -0
  52. package/repo-template/.cursor/agents/context-engineer.md +27 -0
  53. package/repo-template/.cursor/agents/debugger.md +27 -0
  54. package/repo-template/.cursor/agents/eval-engineer.md +27 -0
  55. package/repo-template/.cursor/agents/harness-orchestrator.md +27 -0
  56. package/repo-template/.cursor/agents/loop-controller.md +17 -0
  57. package/repo-template/.cursor/agents/loop-debugger.md +16 -0
  58. package/repo-template/.cursor/agents/reviewer.md +27 -0
  59. package/repo-template/.cursor/agents/sdd/sdd-acceptance-reviewer.md +31 -0
  60. package/repo-template/.cursor/agents/sdd/sdd-implementation-guardian.md +31 -0
  61. package/repo-template/.cursor/agents/sdd/sdd-intake-analyst.md +31 -0
  62. package/repo-template/.cursor/agents/sdd/sdd-orchestrator.md +31 -0
  63. package/repo-template/.cursor/agents/sdd/sdd-spec-reviewer.md +31 -0
  64. package/repo-template/.cursor/agents/sdd/sdd-spec-writer.md +31 -0
  65. package/repo-template/.cursor/agents/sdd/sdd-task-breakdown.md +31 -0
  66. package/repo-template/.cursor/agents/sdd/sdd-technical-planner.md +31 -0
  67. package/repo-template/.cursor/agents/sdd/sdd-test-planner.md +31 -0
  68. package/repo-template/.cursor/agents/spec-intake-analyst.md +14 -0
  69. package/repo-template/.cursor/agents/test-engineer.md +27 -0
  70. package/repo-template/.cursor/commands/adapter-parity.md +34 -0
  71. package/repo-template/.cursor/commands/checkpoint.md +30 -0
  72. package/repo-template/.cursor/commands/debug.md +13 -0
  73. package/repo-template/.cursor/commands/init-eval.md +12 -0
  74. package/repo-template/.cursor/commands/init-feature.md +12 -0
  75. package/repo-template/.cursor/commands/init-harness.md +13 -0
  76. package/repo-template/.cursor/commands/loop-debug.md +3 -0
  77. package/repo-template/.cursor/commands/loop-review.md +3 -0
  78. package/repo-template/.cursor/commands/loop-run.md +5 -0
  79. package/repo-template/.cursor/commands/review.md +21 -0
  80. package/repo-template/.cursor/commands/sdd-intake.md +18 -0
  81. package/repo-template/.cursor/commands/sdd-plan.md +18 -0
  82. package/repo-template/.cursor/commands/sdd-spec.md +18 -0
  83. package/repo-template/.cursor/commands/sdd-tasks.md +18 -0
  84. package/repo-template/.cursor/commands/sdd-validate.md +18 -0
  85. package/repo-template/.cursor/commands/spec-escalate.md +9 -0
  86. package/repo-template/.cursor/commands/spec-intake.md +10 -0
  87. package/repo-template/.cursor/commands/spec-size.md +21 -0
  88. package/repo-template/.cursor/rules/adapter-parity.mdc +20 -0
  89. package/repo-template/.cursor/rules/api.mdc +12 -0
  90. package/repo-template/.cursor/rules/core.mdc +21 -0
  91. package/repo-template/.cursor/rules/enforcement.mdc +16 -0
  92. package/repo-template/.cursor/rules/evals.mdc +10 -0
  93. package/repo-template/.cursor/rules/git.mdc +19 -0
  94. package/repo-template/.cursor/rules/harness.mdc +14 -0
  95. package/repo-template/.cursor/rules/loops.mdc +17 -0
  96. package/repo-template/.cursor/rules/testing.mdc +13 -0
  97. package/repo-template/.cursor/rules/ui.mdc +13 -0
  98. package/repo-template/.cursor/skills/api-design/SKILL.md +23 -0
  99. package/repo-template/.cursor/skills/architecture/SKILL.md +23 -0
  100. package/repo-template/.cursor/skills/code-review/SKILL.md +23 -0
  101. package/repo-template/.cursor/skills/context-graph/SKILL.md +23 -0
  102. package/repo-template/.cursor/skills/debugging/SKILL.md +23 -0
  103. package/repo-template/.cursor/skills/evals/SKILL.md +23 -0
  104. package/repo-template/.cursor/skills/git-workflow/SKILL.md +23 -0
  105. package/repo-template/.cursor/skills/testing/SKILL.md +23 -0
  106. package/repo-template/.cursor/skills/ui-design/SKILL.md +23 -0
  107. package/repo-template/.gentle-ai/README.md +37 -0
  108. package/repo-template/.gentle-ai/loops/README.md +27 -0
  109. package/repo-template/.gentle-ai/loops/sdd-tdd-repair.loop.md +27 -0
  110. package/repo-template/.gentle-ai/model-routing.md +25 -0
  111. package/repo-template/.github/copilot-instructions.md +37 -0
  112. package/repo-template/.github/dependabot.yml +11 -0
  113. package/repo-template/.github/instructions/harness.instructions.md +11 -0
  114. package/repo-template/.github/workflows/harness-quality-gate.yml +67 -0
  115. package/repo-template/.github/workflows/harness-security-gate.yml +32 -0
  116. package/repo-template/.harness/spec-sizing.json +33 -0
  117. package/repo-template/.opencode/README.md +71 -0
  118. package/repo-template/.opencode/agents/adapter-parity-reviewer.md +23 -0
  119. package/repo-template/.opencode/agents/architect.md +38 -0
  120. package/repo-template/.opencode/agents/checkpoint-reviewer.md +38 -0
  121. package/repo-template/.opencode/agents/eval-engineer.md +38 -0
  122. package/repo-template/.opencode/agents/loop-controller.md +38 -0
  123. package/repo-template/.opencode/agents/loop-debugger.md +38 -0
  124. package/repo-template/.opencode/agents/model-router.md +38 -0
  125. package/repo-template/.opencode/agents/sdd-orchestrator.md +38 -0
  126. package/repo-template/.opencode/agents/sdd-spec-reviewer.md +38 -0
  127. package/repo-template/.opencode/agents/spec-intake-analyst.md +19 -0
  128. package/repo-template/.opencode/agents/tdd-engineer.md +38 -0
  129. package/repo-template/.opencode/commands/checkpoint.md +3 -0
  130. package/repo-template/.opencode/commands/eval-design.md +3 -0
  131. package/repo-template/.opencode/commands/maintainability-review.md +3 -0
  132. package/repo-template/.opencode/commands/quality-gate.md +3 -0
  133. package/repo-template/.opencode/commands/spec-escalate.md +9 -0
  134. package/repo-template/.opencode/commands/spec-intake.md +10 -0
  135. package/repo-template/.opencode/commands/spec-size.md +21 -0
  136. package/repo-template/.opencode/commands/trust-review.md +3 -0
  137. package/repo-template/.opencode/loops/ai-behavior-improvement.loop.md +15 -0
  138. package/repo-template/.opencode/loops/bug-repair.loop.md +16 -0
  139. package/repo-template/.opencode/loops/documentation-sync.loop.md +13 -0
  140. package/repo-template/.opencode/loops/feature-development.loop.md +25 -0
  141. package/repo-template/.opencode/skills/spec-complexity-classifier/SKILL.md +10 -0
  142. package/repo-template/.opencode/skills/spec-escalation-review/SKILL.md +10 -0
  143. package/repo-template/.opencode/skills/spec-intake/SKILL.md +10 -0
  144. package/repo-template/.pi/README.md +21 -0
  145. package/repo-template/.pi/prompts/checkpoint.md +11 -0
  146. package/repo-template/.pi/prompts/loop-run.md +10 -0
  147. package/repo-template/.pi/prompts/sdd-plan.md +11 -0
  148. package/repo-template/.pi/prompts/sdd-spec.md +13 -0
  149. package/repo-template/.pi/prompts/spec-escalate.md +9 -0
  150. package/repo-template/.pi/prompts/spec-intake.md +10 -0
  151. package/repo-template/.pi/prompts/spec-size.md +21 -0
  152. package/repo-template/.pi/skills/adapter-parity/SKILL.md +7 -0
  153. package/repo-template/.pi/skills/checkpoint/SKILL.md +7 -0
  154. package/repo-template/.pi/skills/eval-design/SKILL.md +3 -0
  155. package/repo-template/.pi/skills/evals/SKILL.md +7 -0
  156. package/repo-template/.pi/skills/loop-engineering/SKILL.md +7 -0
  157. package/repo-template/.pi/skills/maintainability-review/SKILL.md +3 -0
  158. package/repo-template/.pi/skills/quality-gate-review/SKILL.md +3 -0
  159. package/repo-template/.pi/skills/sdd/SKILL.md +7 -0
  160. package/repo-template/.pi/skills/spec-complexity-classifier/SKILL.md +10 -0
  161. package/repo-template/.pi/skills/spec-escalation-review/SKILL.md +10 -0
  162. package/repo-template/.pi/skills/spec-intake/SKILL.md +10 -0
  163. package/repo-template/.pi/skills/tdd/SKILL.md +7 -0
  164. package/repo-template/.pi/skills/trust-review/SKILL.md +3 -0
  165. package/repo-template/AGENT.md +8 -0
  166. package/repo-template/AGENTS.md +165 -0
  167. package/repo-template/CLAUDE.md +9 -0
  168. package/repo-template/GEMINI.md +9 -0
  169. package/repo-template/docs/ai/adapter-parity.md +135 -0
  170. package/repo-template/docs/ai/agent-workflow.md +45 -0
  171. package/repo-template/docs/ai/api.md +28 -0
  172. package/repo-template/docs/ai/architecture.md +53 -0
  173. package/repo-template/docs/ai/context-budget.md +54 -0
  174. package/repo-template/docs/ai/context-graph.md +39 -0
  175. package/repo-template/docs/ai/conventions.md +34 -0
  176. package/repo-template/docs/ai/decision-log.md +5 -0
  177. package/repo-template/docs/ai/enforcement.md +63 -0
  178. package/repo-template/docs/ai/eval-strategy.md +42 -0
  179. package/repo-template/docs/ai/evals.md +44 -0
  180. package/repo-template/docs/ai/git-workflow.md +35 -0
  181. package/repo-template/docs/ai/governance.md +61 -0
  182. package/repo-template/docs/ai/harness.md +49 -0
  183. package/repo-template/docs/ai/installer-cli.md +34 -0
  184. package/repo-template/docs/ai/loop-log.md +5 -0
  185. package/repo-template/docs/ai/loop-observability.md +57 -0
  186. package/repo-template/docs/ai/loop-policy.md +86 -0
  187. package/repo-template/docs/ai/loops.md +95 -0
  188. package/repo-template/docs/ai/maintainability-gates.md +26 -0
  189. package/repo-template/docs/ai/memory.md +71 -0
  190. package/repo-template/docs/ai/model-policy.md +105 -0
  191. package/repo-template/docs/ai/observability-runtime.md +48 -0
  192. package/repo-template/docs/ai/provider-routing.md +138 -0
  193. package/repo-template/docs/ai/quality-gates.md +48 -0
  194. package/repo-template/docs/ai/rollback-runtime.md +25 -0
  195. package/repo-template/docs/ai/security.md +12 -0
  196. package/repo-template/docs/ai/spec-driven-development.md +124 -0
  197. package/repo-template/docs/ai/spec-escalation.md +44 -0
  198. package/repo-template/docs/ai/spec-intake.md +46 -0
  199. package/repo-template/docs/ai/spec-sizing.md +207 -0
  200. package/repo-template/docs/ai/test-driven-development.md +35 -0
  201. package/repo-template/docs/ai/testing.md +28 -0
  202. package/repo-template/docs/ai/tool-adapters.md +186 -0
  203. package/repo-template/docs/ai/trust-allowlist.md +5 -0
  204. package/repo-template/docs/ai/trust-policy.md +41 -0
  205. package/repo-template/docs/ai/ui.md +17 -0
  206. package/repo-template/docs/skills/adapter-parity-review.md +46 -0
  207. package/repo-template/docs/skills/api-design.md +37 -0
  208. package/repo-template/docs/skills/architecture.md +37 -0
  209. package/repo-template/docs/skills/code-review.md +37 -0
  210. package/repo-template/docs/skills/context-graph.md +38 -0
  211. package/repo-template/docs/skills/debugging.md +37 -0
  212. package/repo-template/docs/skills/eval-design.md +12 -0
  213. package/repo-template/docs/skills/evals.md +37 -0
  214. package/repo-template/docs/skills/git-workflow.md +37 -0
  215. package/repo-template/docs/skills/look-trust-review.md +12 -0
  216. package/repo-template/docs/skills/loop-debugging.md +16 -0
  217. package/repo-template/docs/skills/loop-design.md +39 -0
  218. package/repo-template/docs/skills/loop-retrospective.md +15 -0
  219. package/repo-template/docs/skills/loop-review.md +16 -0
  220. package/repo-template/docs/skills/maintainability-review.md +12 -0
  221. package/repo-template/docs/skills/model-selection.md +36 -0
  222. package/repo-template/docs/skills/quality-gate-review.md +12 -0
  223. package/repo-template/docs/skills/spec-complexity-classifier.md +41 -0
  224. package/repo-template/docs/skills/spec-escalation-review.md +34 -0
  225. package/repo-template/docs/skills/spec-intake.md +25 -0
  226. package/repo-template/docs/skills/testing.md +37 -0
  227. package/repo-template/docs/skills/tool-adapter-sync.md +36 -0
  228. package/repo-template/docs/skills/ui-design.md +37 -0
  229. package/repo-template/docs/specs/README.md +9 -0
  230. package/repo-template/docs/specs/templates/basic-spec.md +41 -0
  231. package/repo-template/docs/specs/templates/complex-spec.md +145 -0
  232. package/repo-template/docs/specs/templates/standard-spec.md +58 -0
  233. package/repo-template/evals/README.md +11 -0
  234. package/repo-template/evals/golden/sample-golden.json +12 -0
  235. package/repo-template/evals/loop-regression/README.md +11 -0
  236. package/repo-template/evals/regression/sample-regression.json +12 -0
  237. package/repo-template/evals/schema/sample-schema.json +12 -0
  238. package/repo-template/evals/tool-calls/sample-tool-calls.json +12 -0
  239. package/repo-template/opencode.json.sample +29 -0
  240. package/repo-template/scripts/harness/backup.mjs +7 -0
  241. package/repo-template/scripts/harness/doctor.mjs +20 -0
  242. package/repo-template/scripts/harness/run-evals.mjs +27 -0
  243. package/repo-template/setup-agent-links.sh +54 -0
  244. package/scripts/check-published-release.mjs +127 -0
  245. package/scripts/check-release-commit.mjs +32 -0
  246. package/scripts/install.sh +225 -0
  247. package/scripts/installer-smoke-test.sh +155 -0
  248. package/scripts/lib/attribution-guard.mjs +59 -0
  249. package/scripts/registry-smoke-test.sh +149 -0
  250. package/scripts/smoke-test.sh +197 -0
  251. package/scripts/ux-smoke-test.sh +140 -0
  252. package/src/cli.js +625 -0
  253. package/src/doctor.js +68 -0
  254. package/src/global/adapter-context.js +27 -0
  255. package/src/global/adapter-matrix.js +38 -0
  256. package/src/global/adapters/claude.js +8 -0
  257. package/src/global/adapters/codex.js +8 -0
  258. package/src/global/adapters/cursor.js +8 -0
  259. package/src/global/adapters/opencode.js +8 -0
  260. package/src/global/agents.js +7 -0
  261. package/src/global/apply-confirmation.js +62 -0
  262. package/src/global/backups.js +54 -0
  263. package/src/global/brand/cli.js +52 -0
  264. package/src/global/brand/index.js +85 -0
  265. package/src/global/clack/setup-preview.js +48 -0
  266. package/src/global/clack/setup-wizard-constants.js +6 -0
  267. package/src/global/clack/setup-wizard.js +207 -0
  268. package/src/global/clack/theme.js +145 -0
  269. package/src/global/component-authoring.js +121 -0
  270. package/src/global/component-builders.js +43 -0
  271. package/src/global/component-distribution.js +270 -0
  272. package/src/global/component-installer.js +106 -0
  273. package/src/global/component-paths.js +22 -0
  274. package/src/global/component-registry.js +89 -0
  275. package/src/global/components/orchestrator.js +14 -0
  276. package/src/global/components/sdd-core.js +17 -0
  277. package/src/global/diff.js +233 -0
  278. package/src/global/drift.js +264 -0
  279. package/src/global/explain.js +144 -0
  280. package/src/global/global-cli.js +1145 -0
  281. package/src/global/global-doctor.js +67 -0
  282. package/src/global/global-installer.js +284 -0
  283. package/src/global/history.js +313 -0
  284. package/src/global/ink/run-setup-ink.js +77 -0
  285. package/src/global/ink/setup-app.js +278 -0
  286. package/src/global/ink/setup-routing.js +51 -0
  287. package/src/global/ink/setup-state.js +180 -0
  288. package/src/global/ink/terminal.js +13 -0
  289. package/src/global/json-output.js +22 -0
  290. package/src/global/load-component-catalog.js +41 -0
  291. package/src/global/load-workspace-component-catalog.js +123 -0
  292. package/src/global/managed-body.js +28 -0
  293. package/src/global/managed-config-adapter.js +134 -0
  294. package/src/global/managed-section.js +73 -0
  295. package/src/global/npm-registry.js +25 -0
  296. package/src/global/paths.js +22 -0
  297. package/src/global/policy.js +344 -0
  298. package/src/global/preflight.js +65 -0
  299. package/src/global/registry.js +79 -0
  300. package/src/global/report.js +110 -0
  301. package/src/global/rollback.js +126 -0
  302. package/src/global/setup.js +284 -0
  303. package/src/global/state-migration.js +65 -0
  304. package/src/global/state.js +50 -0
  305. package/src/global/status.js +94 -0
  306. package/src/global/sync.js +129 -0
  307. package/src/global/upgrade.js +145 -0
  308. package/src/harness-files.js +145 -0
  309. package/src/harness-updater.js +120 -0
  310. package/src/hash.js +5 -0
  311. package/src/manifest.js +36 -0
  312. package/src/project-detection.js +105 -0
  313. package/src/template-installer.js +72 -0
  314. package/src/text-template.js +24 -0
  315. package/src/workspace-cli.js +115 -0
@@ -0,0 +1,67 @@
1
+ import { listBackupSnapshots } from "./backups.js";
2
+ import { buildAdapterContext } from "./adapter-context.js";
3
+ import { detectGlobalDrift, hasRepairableDrift } from "./drift.js";
4
+ import { harnessHomePaths } from "./paths.js";
5
+ import { resolveComponent } from "./component-registry.js";
6
+ import { readGlobalState } from "./state.js";
7
+ import { formatCliCommand } from "./brand/cli.js";
8
+
9
+ export async function runGlobalDoctorChecks(homeDir, { packageRoot, workspaceRoot = null } = {}) {
10
+ const paths = harnessHomePaths(homeDir);
11
+ const state = await readGlobalState(paths.statePath);
12
+ const installedComponents = (state?.components ?? []).map((entry) => resolveComponent(entry.id, { workspaceRoot }));
13
+ const context = buildAdapterContext({
14
+ homeDir,
15
+ packageName: state?.packageName ?? "",
16
+ packageRoot,
17
+ workspaceRoot,
18
+ components: installedComponents
19
+ });
20
+ const checks = packageRoot
21
+ ? await detectGlobalDrift({ homeDir, paths, state, packageRoot, workspaceRoot, context })
22
+ : [stateOnlyCheck(state)];
23
+
24
+ if (packageRoot) {
25
+ checks.push(await backupsCheck(paths));
26
+ }
27
+
28
+ const hasMissing = checks.some((check) => check.status === "missing");
29
+ const hasStale = checks.some((check) => check.status === "stale");
30
+
31
+ return {
32
+ checks,
33
+ ok: !hasMissing && !hasStale,
34
+ hasDrift: hasRepairableDrift(checks),
35
+ state,
36
+ paths
37
+ };
38
+ }
39
+
40
+ function stateOnlyCheck(state) {
41
+ if (!state) {
42
+ return {
43
+ name: "~/.harness/state.json",
44
+ status: "missing",
45
+ category: "state",
46
+ detail: `Not found. Run "${formatCliCommand("install")}" to configure the local ecosystem.`
47
+ };
48
+ }
49
+
50
+ return {
51
+ name: "~/.harness/state.json",
52
+ status: "ok",
53
+ category: "state",
54
+ detail: `cliVersion=${state.cliVersion ?? "unknown"}`
55
+ };
56
+ }
57
+
58
+ async function backupsCheck(paths) {
59
+ const snapshots = await listBackupSnapshots(paths.backupsDir);
60
+
61
+ return {
62
+ name: "~/.harness/backups",
63
+ status: "ok",
64
+ category: "backups",
65
+ detail: snapshots.length > 0 ? `${snapshots.length} snapshot(s)` : "No snapshots yet."
66
+ };
67
+ }
@@ -0,0 +1,284 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { buildAdapterContext } from "./adapter-context.js";
4
+ import { backupTimestamp } from "./backups.js";
5
+ import { buildComponentStateEntries, resolveTargetComponents } from "./component-registry.js";
6
+ import { installComponentAssets, repairComponentAssets } from "./component-installer.js";
7
+ import { detectGlobalDrift, hasRepairableDrift } from "./drift.js";
8
+ import { harnessHomePaths } from "./paths.js";
9
+ import {
10
+ buildAdapterStateEntry,
11
+ detectInstalledAdapters,
12
+ resolveAdapter,
13
+ resolveTargetAdapters
14
+ } from "./registry.js";
15
+ import { createGlobalState, readGlobalState, writeGlobalState } from "./state.js";
16
+ import {
17
+ getInstalledAdapterIds,
18
+ getInstalledComponentIds,
19
+ normalizeGlobalState
20
+ } from "./state-migration.js";
21
+ import { formatCliCommand } from "./brand/cli.js";
22
+
23
+ export async function installGlobalHarness({
24
+ packageRoot,
25
+ packageName,
26
+ cliVersion,
27
+ homeDir,
28
+ workspaceRoot = null,
29
+ agents = null,
30
+ components = null,
31
+ noDefaultComponents = false,
32
+ dryRun = false
33
+ }) {
34
+ const paths = harnessHomePaths(homeDir);
35
+ const timestamp = backupTimestamp();
36
+ const targetComponents = resolveTargetComponents({ components, noDefaultComponents, workspaceRoot });
37
+ const context = buildAdapterContext({
38
+ homeDir,
39
+ packageName,
40
+ packageRoot,
41
+ workspaceRoot,
42
+ components: targetComponents,
43
+ dryRun,
44
+ timestamp
45
+ });
46
+ const targetAdapters = resolveTargetAdapters(context, agents);
47
+ const result = {
48
+ scope: "agent-global",
49
+ homeDir,
50
+ stateRoot: paths.root,
51
+ agents: targetAdapters.map((adapter) => adapter.id),
52
+ components: targetComponents.map((component) => component.id),
53
+ coreFiles: [],
54
+ assetsRepaired: [],
55
+ assetsUnchanged: [],
56
+ configsCreated: [],
57
+ configsUpdated: [],
58
+ configsRepaired: [],
59
+ configsUnchanged: [],
60
+ backups: [],
61
+ driftDetected: false
62
+ };
63
+
64
+ const componentFiles = await installComponentAssets({
65
+ packageRoot,
66
+ workspaceRoot,
67
+ paths,
68
+ components: targetComponents,
69
+ dryRun
70
+ });
71
+ result.coreFiles.push(...Object.keys(componentFiles));
72
+ result.assetsRepaired.push(...Object.keys(componentFiles));
73
+
74
+ await applyAdapterPlans({ context, targetAdapters, result });
75
+
76
+ if (!dryRun) {
77
+ await persistGlobalState({
78
+ paths,
79
+ packageName,
80
+ cliVersion,
81
+ targetAdapters,
82
+ targetComponents,
83
+ homeDir,
84
+ componentFiles,
85
+ backups: result.backups
86
+ });
87
+ }
88
+
89
+ return result;
90
+ }
91
+
92
+ export async function updateGlobalHarness(options) {
93
+ const paths = harnessHomePaths(options.homeDir);
94
+ const state = await readGlobalState(paths.statePath);
95
+
96
+ if (!state) {
97
+ throw new Error(`No global state found at ~/.harness/state.json. Run "${formatCliCommand("install")}" first.`);
98
+ }
99
+
100
+ return syncGlobalHarness({
101
+ ...options,
102
+ agents: options.agents ?? getInstalledAdapterIds(state),
103
+ components: options.components ?? getInstalledComponentIds(state)
104
+ });
105
+ }
106
+
107
+ export async function syncGlobalHarness({
108
+ packageRoot,
109
+ packageName,
110
+ cliVersion,
111
+ homeDir,
112
+ workspaceRoot = null,
113
+ agents,
114
+ components,
115
+ dryRun = false
116
+ }) {
117
+ const paths = harnessHomePaths(homeDir);
118
+ const state = await readGlobalState(paths.statePath);
119
+ const timestamp = backupTimestamp();
120
+ const targetComponents = resolveTargetComponents({ components, workspaceRoot });
121
+ const context = buildAdapterContext({
122
+ homeDir,
123
+ packageName: packageName ?? state.packageName,
124
+ packageRoot,
125
+ workspaceRoot,
126
+ components: targetComponents,
127
+ dryRun,
128
+ timestamp
129
+ });
130
+ const targetAdapters = resolveTargetAdapters(context, agents);
131
+ const driftChecks = await detectGlobalDrift({ homeDir, paths, state, packageRoot, workspaceRoot, context });
132
+ const result = {
133
+ scope: "agent-global",
134
+ homeDir,
135
+ stateRoot: paths.root,
136
+ agents: targetAdapters.map((adapter) => adapter.id),
137
+ components: targetComponents.map((component) => component.id),
138
+ coreFiles: [],
139
+ assetsRepaired: [],
140
+ assetsUnchanged: [],
141
+ configsCreated: [],
142
+ configsUpdated: [],
143
+ configsRepaired: [],
144
+ configsUnchanged: [],
145
+ backups: [],
146
+ driftDetected: hasRepairableDrift(driftChecks),
147
+ repairs: driftChecks.filter((check) => check.status === "missing" || check.status === "stale")
148
+ };
149
+
150
+ const { coreFiles, repaired, unchanged } = await repairComponentAssets({
151
+ packageRoot,
152
+ workspaceRoot,
153
+ paths,
154
+ components: targetComponents,
155
+ state,
156
+ dryRun
157
+ });
158
+ result.coreFiles = Object.keys(coreFiles);
159
+ result.assetsRepaired = repaired;
160
+ result.assetsUnchanged = unchanged;
161
+
162
+ const driftedConfigs = new Set(
163
+ driftChecks
164
+ .filter((check) =>
165
+ (check.category === "managed_section" || check.category === "component_section")
166
+ && (check.status === "missing" || check.status === "stale")
167
+ && check.configFile)
168
+ .map((check) => check.configFile)
169
+ );
170
+
171
+ for (const adapter of targetAdapters) {
172
+ const needsRepair = driftedConfigs.has(adapter.assets.configFile);
173
+ if (!needsRepair) {
174
+ result.configsUnchanged.push(adapter.assets.configFile);
175
+ continue;
176
+ }
177
+
178
+ const plan = adapter.plan(context);
179
+ const applied = await adapter.apply(context, plan);
180
+
181
+ if (applied.backupPath) result.backups.push(applied.backupPath);
182
+ if (applied.action === "create") result.configsCreated.push(applied.configFile);
183
+ else if (applied.action === "update") result.configsUpdated.push(applied.configFile);
184
+ if (applied.action !== "unchanged") result.configsRepaired.push(applied.configFile);
185
+ }
186
+
187
+ if (!dryRun) {
188
+ await persistGlobalState({
189
+ paths,
190
+ packageName: packageName ?? state.packageName,
191
+ cliVersion,
192
+ targetAdapters,
193
+ targetComponents,
194
+ homeDir,
195
+ componentFiles: coreFiles,
196
+ backups: [...(state.backups ?? []), ...result.backups],
197
+ installedAt: state.installedAt
198
+ });
199
+ }
200
+
201
+ return result;
202
+ }
203
+
204
+ export async function uninstallGlobalHarness({ homeDir, dryRun = false }) {
205
+ const paths = harnessHomePaths(homeDir);
206
+ const rawState = await readGlobalState(paths.statePath);
207
+ const state = normalizeGlobalState(rawState);
208
+ const timestamp = backupTimestamp();
209
+ const installedComponents = state?.components?.map((entry) => entry.id) ?? [];
210
+ const context = buildAdapterContext({
211
+ homeDir,
212
+ packageName: state?.packageName ?? "",
213
+ components: installedComponents.map((id) => ({ id })),
214
+ dryRun,
215
+ timestamp
216
+ });
217
+ const adapterIds = state?.adapters?.map((entry) => entry.id)
218
+ ?? detectInstalledAdapters(context);
219
+ const result = {
220
+ scope: "agent-global",
221
+ agents: adapterIds,
222
+ components: installedComponents,
223
+ configsCleaned: [],
224
+ backups: [],
225
+ stateRemoved: false
226
+ };
227
+
228
+ for (const adapterId of adapterIds) {
229
+ const adapter = resolveAdapter(adapterId);
230
+ const stateEntry = state?.adapters?.find((entry) => entry.id === adapterId);
231
+ const removed = await adapter.uninstall(context, stateEntry);
232
+
233
+ if (removed.backupPath) result.backups.push(removed.backupPath);
234
+ if (removed.cleaned) result.configsCleaned.push(removed.configFile);
235
+ }
236
+
237
+ if (state && !dryRun) {
238
+ await rm(paths.statePath, { force: true });
239
+ await rm(join(paths.root, "components"), { recursive: true, force: true });
240
+ await rm(paths.coreDir, { recursive: true, force: true });
241
+ }
242
+
243
+ result.stateRemoved = Boolean(state);
244
+ return result;
245
+ }
246
+
247
+ async function applyAdapterPlans({ context, targetAdapters, result }) {
248
+ for (const adapter of targetAdapters) {
249
+ const plan = adapter.plan(context);
250
+ const applied = await adapter.apply(context, plan);
251
+
252
+ if (applied.backupPath) result.backups.push(applied.backupPath);
253
+
254
+ if (applied.action === "create") result.configsCreated.push(applied.configFile);
255
+ else if (applied.action === "update") result.configsUpdated.push(applied.configFile);
256
+ else if (applied.action === "unchanged") result.configsUnchanged.push(applied.configFile);
257
+ }
258
+ }
259
+
260
+ async function persistGlobalState({
261
+ paths,
262
+ packageName,
263
+ cliVersion,
264
+ targetAdapters,
265
+ targetComponents,
266
+ homeDir,
267
+ componentFiles,
268
+ backups,
269
+ installedAt
270
+ }) {
271
+ const existingState = installedAt ? { installedAt } : await readGlobalState(paths.statePath);
272
+ const adapterEntries = targetAdapters.map((adapter) => buildAdapterStateEntry(adapter, homeDir));
273
+ const componentEntries = buildComponentStateEntries(targetComponents, targetAdapters);
274
+
275
+ await writeGlobalState(paths.statePath, createGlobalState({
276
+ packageName,
277
+ cliVersion,
278
+ adapters: adapterEntries,
279
+ components: componentEntries,
280
+ coreFiles: componentFiles,
281
+ backups,
282
+ installedAt: existingState?.installedAt
283
+ }));
284
+ }
@@ -0,0 +1,313 @@
1
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { harnessHomePaths } from "./paths.js";
4
+ import { buildEffectivePolicy, loadConsentAudit } from "./policy.js";
5
+ import { buildStatusReport } from "./status.js";
6
+
7
+ export function getHistoryPath(homeDir) {
8
+ return harnessHomePaths(homeDir).historyPath;
9
+ }
10
+
11
+ export function buildCheckSnapshot(counts) {
12
+ if (!counts) return null;
13
+
14
+ return {
15
+ ok: counts.ok ?? 0,
16
+ missing: counts.missing ?? 0,
17
+ stale: counts.stale ?? 0,
18
+ warning: counts.warning ?? 0
19
+ };
20
+ }
21
+
22
+ export function createHistoryEvent({ cliVersion, ...fields }) {
23
+ return {
24
+ timestamp: new Date().toISOString(),
25
+ cliVersion,
26
+ ...fields
27
+ };
28
+ }
29
+
30
+ export async function appendHistoryEvent(homeDir, event) {
31
+ const { root, historyPath } = harnessHomePaths(homeDir);
32
+ await mkdir(root, { recursive: true });
33
+ await appendFile(historyPath, `${JSON.stringify(event)}\n`, "utf8");
34
+ }
35
+
36
+ export async function readHistoryEvents(homeDir, { limit = null, command = null, action = null } = {}) {
37
+ const { historyPath } = harnessHomePaths(homeDir);
38
+
39
+ if (!existsSync(historyPath)) {
40
+ return { events: [], warnings: [] };
41
+ }
42
+
43
+ const content = await readFile(historyPath, "utf8");
44
+ const lines = content.split("\n").filter((line) => line.trim().length > 0);
45
+ const events = [];
46
+ const warnings = [];
47
+
48
+ for (let index = 0; index < lines.length; index += 1) {
49
+ try {
50
+ events.push(JSON.parse(lines[index]));
51
+ } catch (error) {
52
+ warnings.push({ line: index + 1, message: error.message });
53
+ }
54
+ }
55
+
56
+ let filtered = events;
57
+
58
+ if (command) {
59
+ filtered = filtered.filter((event) => event.command === command);
60
+ }
61
+
62
+ if (action) {
63
+ filtered = filtered.filter((event) => event.action === action);
64
+ }
65
+
66
+ if (limit != null && limit > 0) {
67
+ return { events: filtered.slice(-limit), warnings };
68
+ }
69
+
70
+ return { events: filtered, warnings };
71
+ }
72
+
73
+ export async function readLastHistoryEvent(homeDir, { command = null, action = null } = {}) {
74
+ const { events, warnings } = await readHistoryEvents(homeDir, { command, action });
75
+ return {
76
+ event: events.length > 0 ? events[events.length - 1] : null,
77
+ warnings
78
+ };
79
+ }
80
+
81
+ async function resolveHistoryPolicy(homeDir) {
82
+ const policy = await buildEffectivePolicy(homeDir);
83
+
84
+ return {
85
+ profile: policy.profile,
86
+ applyMode: policy.applyMode,
87
+ preflight: policy.preflight,
88
+ source: policy.source
89
+ };
90
+ }
91
+
92
+ async function resolveConsentSource(homeDir, consentOptions) {
93
+ const consent = await loadConsentAudit(homeDir, consentOptions);
94
+ return consent.consentSource;
95
+ }
96
+
97
+ function buildConsentOptions(options, { applying, dryRun }) {
98
+ return {
99
+ yes: options.yes,
100
+ confirm: options.confirm,
101
+ yesExplicit: options.yesExplicit,
102
+ confirmExplicit: options.confirmExplicit,
103
+ preflight: options.preflight,
104
+ preflightExplicit: options.preflightExplicit,
105
+ interactive: options.interactive,
106
+ applying,
107
+ dryRun,
108
+ json: options.json
109
+ };
110
+ }
111
+
112
+ async function recordEvent(homeDir, cliVersion, fields) {
113
+ const policy = fields.policy ?? await resolveHistoryPolicy(homeDir);
114
+
115
+ await appendHistoryEvent(homeDir, createHistoryEvent({
116
+ cliVersion,
117
+ policy,
118
+ consentSource: fields.consentSource ?? "none",
119
+ agents: fields.agents ?? null,
120
+ components: fields.components ?? null,
121
+ checksBefore: fields.checksBefore ?? null,
122
+ checksAfter: fields.checksAfter ?? null,
123
+ backupsCreated: fields.backupsCreated ?? null,
124
+ snapshotsUsed: fields.snapshotsUsed ?? null,
125
+ command: fields.command,
126
+ action: fields.action,
127
+ wrote: fields.wrote,
128
+ dryRun: fields.dryRun ?? false
129
+ }));
130
+ }
131
+
132
+ export async function recordSetupHistory(homeDir, {
133
+ cliVersion,
134
+ options,
135
+ outcome,
136
+ checksBefore = null,
137
+ packageRoot,
138
+ workspaceRoot
139
+ }) {
140
+ if (options.dryRun) {
141
+ return;
142
+ }
143
+
144
+ if (outcome.cancelled) {
145
+ await recordEvent(homeDir, cliVersion, {
146
+ command: "setup",
147
+ action: "cancelled",
148
+ wrote: false,
149
+ dryRun: false,
150
+ checksBefore
151
+ });
152
+ return;
153
+ }
154
+
155
+ const consentSource = await resolveConsentSource(
156
+ homeDir,
157
+ buildConsentOptions(options, { applying: true, dryRun: false })
158
+ );
159
+ const postReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
160
+
161
+ await recordEvent(homeDir, cliVersion, {
162
+ command: "setup",
163
+ action: "applied",
164
+ wrote: true,
165
+ dryRun: false,
166
+ consentSource,
167
+ agents: outcome.result.agents,
168
+ components: outcome.result.components,
169
+ checksBefore,
170
+ checksAfter: buildCheckSnapshot(postReport.counts),
171
+ backupsCreated: outcome.result.backups
172
+ });
173
+ }
174
+
175
+ export async function recordSyncHistory(homeDir, {
176
+ cliVersion,
177
+ options,
178
+ outcome,
179
+ checksBefore,
180
+ packageRoot,
181
+ workspaceRoot
182
+ }) {
183
+ if (options.dryRun || outcome.action === "setup-required" || outcome.action === "noop" || outcome.action === "plan") {
184
+ return;
185
+ }
186
+
187
+ if (outcome.action === "cancelled") {
188
+ await recordEvent(homeDir, cliVersion, {
189
+ command: "sync",
190
+ action: "cancelled",
191
+ wrote: false,
192
+ dryRun: false,
193
+ checksBefore
194
+ });
195
+ return;
196
+ }
197
+
198
+ const consentSource = await resolveConsentSource(
199
+ homeDir,
200
+ buildConsentOptions(options, { applying: true, dryRun: false })
201
+ );
202
+ const postReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
203
+
204
+ await recordEvent(homeDir, cliVersion, {
205
+ command: "sync",
206
+ action: "repaired",
207
+ wrote: outcome.wrote,
208
+ dryRun: false,
209
+ consentSource,
210
+ agents: outcome.result?.agents ?? null,
211
+ components: outcome.result?.components ?? null,
212
+ checksBefore,
213
+ checksAfter: buildCheckSnapshot(postReport.counts),
214
+ backupsCreated: outcome.result?.backups ?? null
215
+ });
216
+ }
217
+
218
+ export async function recordUpgradeHistory(homeDir, {
219
+ cliVersion,
220
+ options,
221
+ outcome,
222
+ checksBefore = null,
223
+ packageRoot,
224
+ workspaceRoot
225
+ }) {
226
+ if (outcome.cancelled) {
227
+ await recordEvent(homeDir, cliVersion, {
228
+ command: "upgrade",
229
+ action: "cancelled",
230
+ wrote: false,
231
+ dryRun: false,
232
+ checksBefore
233
+ });
234
+ return;
235
+ }
236
+
237
+ if (outcome.dryRun) {
238
+ return;
239
+ }
240
+
241
+ const consentSource = await resolveConsentSource(
242
+ homeDir,
243
+ buildConsentOptions(options, { applying: true, dryRun: false })
244
+ );
245
+ const postReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
246
+
247
+ await recordEvent(homeDir, cliVersion, {
248
+ command: "upgrade",
249
+ action: "applied",
250
+ wrote: true,
251
+ dryRun: false,
252
+ consentSource,
253
+ agents: outcome.result?.agents ?? null,
254
+ components: outcome.result?.components ?? null,
255
+ checksBefore,
256
+ checksAfter: buildCheckSnapshot(postReport.counts),
257
+ backupsCreated: outcome.result?.backups ?? null
258
+ });
259
+ }
260
+
261
+ export async function recordRollbackHistory(homeDir, {
262
+ cliVersion,
263
+ snapshot,
264
+ result
265
+ }) {
266
+ await recordEvent(homeDir, cliVersion, {
267
+ command: "rollback",
268
+ action: result.noop ? "noop" : "applied",
269
+ wrote: !result.noop,
270
+ dryRun: false,
271
+ snapshotsUsed: [snapshot],
272
+ backupsCreated: result.safetyBackup ? [result.safetyBackup] : null
273
+ });
274
+ }
275
+
276
+ export async function recordUninstallHistory(homeDir, {
277
+ cliVersion,
278
+ options,
279
+ result
280
+ }) {
281
+ if (options.dryRun) {
282
+ return;
283
+ }
284
+
285
+ await recordEvent(homeDir, cliVersion, {
286
+ command: "uninstall",
287
+ action: "applied",
288
+ wrote: true,
289
+ dryRun: false,
290
+ backupsCreated: result.backups
291
+ });
292
+ }
293
+
294
+ export async function recordPolicyHistory(homeDir, {
295
+ cliVersion,
296
+ action
297
+ }) {
298
+ await recordEvent(homeDir, cliVersion, {
299
+ command: "policy",
300
+ action,
301
+ wrote: true,
302
+ dryRun: false
303
+ });
304
+ }
305
+
306
+ export function formatHistoryEvent(event) {
307
+ const wrote = event.wrote ? "yes" : "no";
308
+ const agents = Array.isArray(event.agents) ? event.agents.join(",") : "-";
309
+ const components = Array.isArray(event.components) ? event.components.join(",") : "-";
310
+ const consent = event.consentSource ?? "none";
311
+
312
+ return `${event.timestamp} ${event.command.padEnd(8)} ${String(event.action).padEnd(10)} wrote=${wrote} consent=${consent} agents=${agents} components=${components}`;
313
+ }