coding-agent-harness 1.0.5 → 1.0.6

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 (260) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/README.md +63 -3
  3. package/README.zh-CN.md +52 -3
  4. package/SKILL.md +43 -43
  5. package/dist/build-dist.mjs +189 -0
  6. package/dist/check-dist-observation.mjs +428 -0
  7. package/dist/check-harness.mjs +489 -0
  8. package/dist/check-import-graph.mjs +511 -0
  9. package/dist/check-runtime-emit.mjs +304 -0
  10. package/dist/check-type-boundaries.mjs +139 -0
  11. package/dist/commands/dashboard-command.mjs +80 -0
  12. package/dist/commands/migration-command.mjs +152 -0
  13. package/dist/commands/preset-command.mjs +91 -0
  14. package/dist/commands/task-command.mjs +324 -0
  15. package/dist/harness.mjs +304 -0
  16. package/dist/lib/capability-registry.mjs +643 -0
  17. package/dist/lib/check-module-parallel.mjs +227 -0
  18. package/dist/lib/check-profiles.mjs +414 -0
  19. package/dist/lib/check-task-contracts.mjs +54 -0
  20. package/dist/lib/core-shared.mjs +254 -0
  21. package/dist/lib/dashboard-data.mjs +608 -0
  22. package/dist/lib/dashboard-workbench.mjs +334 -0
  23. package/dist/lib/dashboard-writer.mjs +200 -0
  24. package/dist/lib/git-status-summary.mjs +45 -0
  25. package/dist/lib/governance-index-generator.mjs +236 -0
  26. package/dist/lib/governance-sync.mjs +617 -0
  27. package/dist/lib/governance-table-boundary.mjs +161 -0
  28. package/{scripts → dist}/lib/harness-core.mjs +2 -0
  29. package/dist/lib/harness-paths.mjs +338 -0
  30. package/dist/lib/lesson-maintenance.mjs +139 -0
  31. package/dist/lib/markdown-utils.mjs +193 -0
  32. package/dist/lib/migration-planner.mjs +439 -0
  33. package/dist/lib/migration-support.mjs +317 -0
  34. package/dist/lib/phase-kind.mjs +46 -0
  35. package/dist/lib/preset-audit-contracts.mjs +40 -0
  36. package/dist/lib/preset-engine.mjs +516 -0
  37. package/dist/lib/preset-registry.mjs +831 -0
  38. package/dist/lib/preset-resource-contracts.mjs +83 -0
  39. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  40. package/dist/lib/status-builder.mjs +87 -0
  41. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
  42. package/dist/lib/structure-migration.mjs +404 -0
  43. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  44. package/dist/lib/task-audit-metadata.mjs +376 -0
  45. package/dist/lib/task-audit-migration.mjs +355 -0
  46. package/dist/lib/task-completion-consistency.mjs +29 -0
  47. package/dist/lib/task-index.mjs +133 -0
  48. package/dist/lib/task-lesson-candidates.mjs +239 -0
  49. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  50. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  51. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  52. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  53. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  54. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  55. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  56. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  57. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  58. package/dist/lib/task-lifecycle.mjs +611 -0
  59. package/dist/lib/task-metadata.mjs +116 -0
  60. package/dist/lib/task-review-model.mjs +474 -0
  61. package/dist/lib/task-scanner.mjs +439 -0
  62. package/dist/lib/task-tombstone-commands.mjs +125 -0
  63. package/dist/postinstall.mjs +14 -0
  64. package/dist/run-built-tests.mjs +84 -0
  65. package/docs-release/README.md +1 -0
  66. package/docs-release/architecture/overview.md +12 -12
  67. package/docs-release/architecture/overview.zh-CN.md +12 -12
  68. package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
  69. package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
  70. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
  71. package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
  72. package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
  73. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
  74. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
  75. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
  76. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
  77. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
  78. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  79. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +1 -4
  80. package/docs-release/guides/agent-installation.en-US.md +14 -8
  81. package/docs-release/guides/agent-installation.md +14 -8
  82. package/docs-release/guides/contributing.md +3 -3
  83. package/docs-release/guides/contributing.zh-CN.md +3 -3
  84. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  85. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  86. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  87. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  88. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  89. package/docs-release/guides/migration-playbook.md +59 -1
  90. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  91. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  92. package/docs-release/guides/preset-development.md +2 -2
  93. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  94. package/docs-release/guides/repository-operating-models.md +21 -21
  95. package/docs-release/guides/task-state-machine.en-US.md +5 -5
  96. package/docs-release/guides/task-state-machine.md +5 -5
  97. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  98. package/examples/minimal-project/AGENTS.md +2 -2
  99. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  100. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  101. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  102. package/package.json +20 -12
  103. package/presets/legacy-migration/preset.yaml +5 -5
  104. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  105. package/presets/lesson-sedimentation/preset.yaml +3 -3
  106. package/presets/module/preset.yaml +2 -2
  107. package/presets/module/templates/execution_strategy.append.md +1 -1
  108. package/presets/module/templates/task_plan.append.md +3 -3
  109. package/presets/standard-task/preset.yaml +2 -2
  110. package/references/adversarial-review-standard.md +2 -2
  111. package/references/agents-md-pattern.md +14 -14
  112. package/references/cadence-ledger.md +1 -1
  113. package/references/ci-cd-standard.md +1 -1
  114. package/references/delivery-operating-model-standard.md +4 -4
  115. package/references/docs-directory-standard.md +65 -159
  116. package/references/external-source-intake-standard.md +10 -10
  117. package/references/harness-ledger.md +5 -5
  118. package/references/legacy-12-phase-bootstrap.md +2 -2
  119. package/references/lessons-governance.md +15 -15
  120. package/references/long-running-task-standard.md +6 -6
  121. package/references/module-parallel-standard.md +34 -34
  122. package/references/planning-loop.md +6 -6
  123. package/references/project-onboarding-audit.md +4 -4
  124. package/references/regression-system.md +2 -2
  125. package/references/repo-governance-standard.md +4 -4
  126. package/references/review-routing-standard.md +1 -1
  127. package/references/ssot-governance.md +19 -19
  128. package/references/taskr-gap-analysis.md +5 -5
  129. package/references/walkthrough-closeout.md +14 -14
  130. package/references/worktree-parallel.md +3 -3
  131. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  132. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  133. package/templates/AGENTS.md.template +26 -26
  134. package/templates/architecture/README.md +4 -4
  135. package/templates/architecture/service-catalog.md +2 -2
  136. package/templates/architecture/services/service-template.md +1 -1
  137. package/templates/dashboard/assets/app-src/20-overview.js +11 -5
  138. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  139. package/templates/dashboard/assets/app.js +12 -6
  140. package/templates/dashboard/assets/i18n.js +4 -2
  141. package/templates/development/README.md +10 -10
  142. package/templates/development/cross-repo-debugging.md +3 -3
  143. package/templates/development/external-context/service-template.md +2 -2
  144. package/templates/development/external-source-packs/README.md +4 -4
  145. package/templates/integrations/README.md +4 -4
  146. package/templates/integrations/api-contract.md +2 -2
  147. package/templates/integrations/event-contract.md +2 -2
  148. package/templates/integrations/third-party/vendor-template.md +2 -2
  149. package/templates/integrations/webhook-contract.md +2 -2
  150. package/templates/ledger/Harness-Ledger.md +1 -1
  151. package/templates/planning/INDEX.md +1 -0
  152. package/templates/planning/module_session_prompt.md +1 -1
  153. package/templates/planning/task_plan.md +1 -1
  154. package/templates/planning/walkthrough.md +47 -0
  155. package/templates/reference/docs-library-standard.md +8 -8
  156. package/templates/reference/external-source-intake-standard.md +15 -15
  157. package/templates/reference/repo-governance-standard.md +1 -1
  158. package/templates/ssot/Module-Registry.md +1 -1
  159. package/templates/walkthrough/walkthrough-template.md +2 -2
  160. package/templates-zh-CN/AGENTS.md.template +26 -26
  161. package/templates-zh-CN/CLAUDE.md.template +1 -1
  162. package/templates-zh-CN/architecture/README.md +4 -4
  163. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  164. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  165. package/templates-zh-CN/development/README.md +10 -10
  166. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  167. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  168. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  169. package/templates-zh-CN/integrations/README.md +4 -4
  170. package/templates-zh-CN/integrations/api-contract.md +2 -2
  171. package/templates-zh-CN/integrations/event-contract.md +2 -2
  172. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  173. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  174. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  175. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  176. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  177. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  178. package/templates-zh-CN/planning/module_session_prompt.md +11 -11
  179. package/templates-zh-CN/planning/walkthrough.md +47 -0
  180. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  181. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  182. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  183. package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
  184. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  185. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  186. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  187. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  188. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  189. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  190. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  191. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  192. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  193. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  194. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  195. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  196. package/tsconfig.dist.json +16 -0
  197. package/tsconfig.json +25 -0
  198. package/tsconfig.runtime.json +24 -0
  199. package/examples/minimal-project/.harness-capabilities.json +0 -8
  200. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  201. package/scripts/check-harness.mjs +0 -508
  202. package/scripts/commands/dashboard-command.mjs +0 -67
  203. package/scripts/commands/migration-command.mjs +0 -126
  204. package/scripts/commands/preset-command.mjs +0 -73
  205. package/scripts/commands/task-command.mjs +0 -328
  206. package/scripts/harness.mjs +0 -291
  207. package/scripts/lib/capability-registry.mjs +0 -587
  208. package/scripts/lib/check-module-parallel.mjs +0 -230
  209. package/scripts/lib/check-profiles.mjs +0 -372
  210. package/scripts/lib/check-task-contracts.mjs +0 -55
  211. package/scripts/lib/core-shared.mjs +0 -249
  212. package/scripts/lib/dashboard-data.mjs +0 -520
  213. package/scripts/lib/dashboard-workbench.mjs +0 -336
  214. package/scripts/lib/dashboard-writer.mjs +0 -202
  215. package/scripts/lib/git-status-summary.mjs +0 -46
  216. package/scripts/lib/governance-index-generator.mjs +0 -174
  217. package/scripts/lib/governance-sync.mjs +0 -611
  218. package/scripts/lib/governance-table-boundary.mjs +0 -175
  219. package/scripts/lib/lesson-maintenance.mjs +0 -152
  220. package/scripts/lib/markdown-utils.mjs +0 -191
  221. package/scripts/lib/migration-planner.mjs +0 -476
  222. package/scripts/lib/migration-support.mjs +0 -312
  223. package/scripts/lib/phase-kind.mjs +0 -50
  224. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  225. package/scripts/lib/preset-engine.mjs +0 -494
  226. package/scripts/lib/preset-registry.mjs +0 -776
  227. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  228. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  229. package/scripts/lib/status-builder.mjs +0 -88
  230. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  231. package/scripts/lib/task-audit-metadata.mjs +0 -385
  232. package/scripts/lib/task-audit-migration.mjs +0 -350
  233. package/scripts/lib/task-completion-consistency.mjs +0 -26
  234. package/scripts/lib/task-index.mjs +0 -93
  235. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  236. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  237. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
  238. package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
  239. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
  240. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
  241. package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
  242. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
  243. package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
  244. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  245. package/scripts/lib/task-lifecycle.mjs +0 -616
  246. package/scripts/lib/task-metadata.mjs +0 -118
  247. package/scripts/lib/task-review-model.mjs +0 -455
  248. package/scripts/lib/task-scanner.mjs +0 -503
  249. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  250. package/scripts/postinstall.mjs +0 -14
  251. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  252. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  253. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  254. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
  255. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  256. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  257. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  258. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  259. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  260. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -1,587 +0,0 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { spawnSync } from "node:child_process";
5
- import {
6
- repoRoot,
7
- visualMapFile,
8
- normalizeTarget,
9
- toPosix,
10
- exists,
11
- existsInDocs,
12
- readFileSafe,
13
- readJsonSafe,
14
- readBundledTemplate,
15
- walkFiles,
16
- normalizeLocale,
17
- localizedTemplateSource,
18
- userPresetRootForHome,
19
- } from "./core-shared.mjs";
20
- import { listBundledPresetIds, seedBundledPresets } from "./preset-registry.mjs";
21
-
22
- export const capabilityDefinitions = {
23
- core: {
24
- description: "Planning loop and task execution records.",
25
- selectWhen: "Always install. This is the required document kernel.",
26
- default: true,
27
- dependencies: [],
28
- artifacts: ["docs/09-PLANNING"],
29
- },
30
- "module-parallel": {
31
- description: "Module registry, module plans, session prompts, and worker handoff.",
32
- selectWhen: "Use only when the project has two or more independent modules that need parallel ownership.",
33
- default: false,
34
- dependencies: ["core"],
35
- artifacts: ["docs/09-PLANNING/Module-Registry.md", "docs/09-PLANNING/MODULES"],
36
- },
37
- "subagent-worker": {
38
- description: "Commit-backed worker handoff protocol for code-changing subagents.",
39
- selectWhen: "Use only when code-changing subagents will work in dedicated worktrees with commit-backed handoff.",
40
- default: false,
41
- dependencies: ["module-parallel"],
42
- artifacts: ["docs/09-PLANNING/MODULES"],
43
- },
44
- "adversarial-review": {
45
- description: "Machine-gateable adversarial review reports and verifier output contract.",
46
- selectWhen: "Use when release, architecture, security, data, or strategy risk requires an independent review artifact.",
47
- default: false,
48
- dependencies: ["core"],
49
- artifacts: ["docs/09-PLANNING/TASKS"],
50
- },
51
- "long-running-task": {
52
- description: "Long-running task contract with review cadence and stop conditions.",
53
- selectWhen: "Use when agents may run across many loops without user confirmation after every step.",
54
- default: false,
55
- dependencies: ["core"],
56
- artifacts: ["docs/09-PLANNING/TASKS/_task-template/long-running-task-contract.md"],
57
- },
58
- "dashboard": {
59
- description: "Read-only HTML dashboard generated from harness status JSON.",
60
- selectWhen: "Use when users or agents need a local read-only status surface.",
61
- default: false,
62
- dependencies: ["core"],
63
- artifacts: [],
64
- },
65
- "safe-adoption": {
66
- description: "Legacy compatibility and assisted capability adoption.",
67
- selectWhen: "Use when adopting v1.0 into an existing harness project without rewriting history.",
68
- default: false,
69
- dependencies: ["core"],
70
- artifacts: [],
71
- },
72
- };
73
-
74
- export const capabilityAliases = {
75
- "review-contract": "adversarial-review",
76
- };
77
-
78
- export const allowedCapabilityStates = new Set(["scaffolded", "configured", "verified"]);
79
- export const userInstallTargets = {
80
- codex: [".codex", "skills", "coding-agent-harness"],
81
- claude: [".claude", "skills", "coding-agent-harness"],
82
- gemini: [".gemini", "skills", "coding-agent-harness"],
83
- openclaw: [".openclaw", "skills", "coding-agent-harness"],
84
- agents: [".agents", "skills", "coding-agent-harness"],
85
- };
86
-
87
- export function readCapabilityRegistry(target) {
88
- const registryPath = path.join(target.projectRoot, ".harness-capabilities.json");
89
- if (!fs.existsSync(registryPath)) {
90
- return {
91
- mode: "legacy-compat",
92
- path: registryPath,
93
- capabilities: [{ name: "core", state: "configured" }],
94
- locale: "en-US",
95
- raw: null,
96
- errors: [],
97
- };
98
- }
99
-
100
- let readError = null;
101
- const raw = readJsonSafe(registryPath, null, { onError: (error) => { readError = error; } });
102
- if (raw) {
103
- const locale = normalizeLocale(raw.locale);
104
- const capabilities = Array.isArray(raw.capabilities)
105
- ? raw.capabilities.map((entry) =>
106
- typeof entry === "string"
107
- ? { name: normalizeCapabilityName(entry), state: "scaffolded" }
108
- : { name: normalizeCapabilityName(entry.name), state: entry.state || "scaffolded" },
109
- )
110
- : [];
111
- return { mode: "declared-capability", path: registryPath, capabilities, raw, locale, errors: [] };
112
- }
113
- return { mode: "declared-capability", path: registryPath, capabilities: [], raw: null, errors: [readError?.message || "invalid .harness-capabilities.json"] };
114
- }
115
-
116
- export function normalizeCapabilityName(name) {
117
- return capabilityAliases[name] || name;
118
- }
119
-
120
- export function validateSourcePackageBoundary(targetInput = ".") {
121
- const root = path.resolve(targetInput || ".");
122
- const gitProbe = spawnSync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { encoding: "utf8" });
123
- if (gitProbe.status !== 0) return { failures: [], warnings: [] };
124
- const staged = spawnSync("git", ["-C", root, "diff", "--cached", "--name-only", "-z"], { encoding: "utf8" });
125
- if (staged.status !== 0) return { failures: [], warnings: [`could not inspect staged files: ${staged.stderr.trim() || staged.status}`] };
126
- const localOnly = staged.stdout
127
- .split("\0")
128
- .filter(Boolean)
129
- .filter((file) => file === "AGENTS.md" || file === "CLAUDE.md" || file === "docs" || file.startsWith("docs/") || file === ".harness-private" || file.startsWith(".harness-private/"));
130
- const tracked = spawnSync("git", ["-C", root, "ls-files", "-z", "--", "harness-dashboard.html"], { encoding: "utf8" });
131
- const generatedRootDashboard = tracked.status === 0
132
- ? tracked.stdout.split("\0").filter(Boolean)
133
- .filter((file) => fs.existsSync(path.join(root, file)))
134
- : [];
135
- const internalScripts = ["scripts/test-harness.mjs", "scripts/smoke-dashboard.mjs"]
136
- .filter((file) => fs.existsSync(path.join(root, file)));
137
- const dashboardAppDrift = validateDashboardAppAssembly(root);
138
- const dashboardCssDrift = validateDashboardAssetAssembly(root, "app.css.manifest.json", "app.css", "dashboard assets/app.css does not match css-src manifest assembly");
139
- return {
140
- failures: [
141
- ...localOnly.map((file) => `private local-only file staged: ${file}`),
142
- ...generatedRootDashboard.map((file) => `generated dashboard file tracked in source root: ${file}`),
143
- ...internalScripts.map((file) => `internal test/smoke file in publishable scripts directory: ${file}`),
144
- ...dashboardAppDrift,
145
- ...dashboardCssDrift,
146
- ],
147
- warnings: tracked.status === 0 ? [] : [`could not inspect tracked generated dashboard files: ${tracked.stderr.trim() || tracked.status}`],
148
- };
149
- }
150
-
151
- function validateDashboardAppAssembly(root) {
152
- return validateDashboardAssetAssembly(root, "app.manifest.json", "app.js", "dashboard assets/app.js does not match app-src manifest assembly");
153
- }
154
-
155
- function validateDashboardAssetAssembly(root, manifestName, assetName, driftMessage) {
156
- const assetsDir = path.join(root, "templates/dashboard/assets");
157
- const manifestPath = path.join(assetsDir, manifestName);
158
- const assetPath = path.join(assetsDir, assetName);
159
- if (!fs.existsSync(manifestPath) || !fs.existsSync(assetPath)) return [];
160
- try {
161
- const manifest = readJsonSafe(manifestPath, null);
162
- if (!Array.isArray(manifest) || manifest.length === 0) {
163
- return [`dashboard asset manifest must list source files: ${manifestName}`];
164
- }
165
- const assembled = `${manifest.map((relativePath) => {
166
- const source = path.join(assetsDir, relativePath);
167
- if (!fs.existsSync(source)) throw new Error(`missing ${relativePath}`);
168
- return fs.readFileSync(source, "utf8").trimEnd();
169
- }).join("\n\n")}\n`;
170
- const trackedAsset = fs.readFileSync(assetPath, "utf8");
171
- return trackedAsset === assembled ? [] : [driftMessage];
172
- } catch (error) {
173
- return [`could not validate dashboard asset assembly (${assetName}): ${error.message}`];
174
- }
175
- }
176
-
177
- export function detectCapabilities(target) {
178
- const detected = new Set(["core"]);
179
- if (existsInDocs(target, "09-PLANNING/Module-Registry.md")) detected.add("module-parallel");
180
- if (existsInDocs(target, "11-REFERENCE/adversarial-review-standard.md")) detected.add("adversarial-review");
181
- if (
182
- existsInDocs(target, "11-REFERENCE/long-running-task-standard.md") ||
183
- existsInDocs(target, "09-PLANNING/TASKS/_task-template/long-running-task-contract.md")
184
- ) {
185
- detected.add("long-running-task");
186
- }
187
- return [...detected];
188
- }
189
-
190
- export function buildInstallReport({ target, locale, capabilities, changes, dryRun = false, operation = "init" }) {
191
- const selected = new Set(capabilities.map(normalizeCapabilityName));
192
- return {
193
- operation,
194
- dryRun,
195
- target: target.projectRoot,
196
- locale,
197
- capabilities: Object.entries(capabilityDefinitions).map(([name, definition]) => ({
198
- name,
199
- selected: selected.has(name),
200
- default: definition.default === true,
201
- dependencies: definition.dependencies,
202
- description: definition.description,
203
- selectWhen: definition.selectWhen,
204
- })),
205
- selectedCapabilities: capabilities,
206
- created: changes.filter((change) => ["create", "would-create"].includes(change.action)).map((change) => change.destination),
207
- skipped: changes.filter((change) => change.action === "skip-existing").map((change) => change.destination),
208
- agentInstructions: [
209
- "Agents must choose locale during Decide and pass --locale zh-CN|en-US explicitly in non-interactive installs.",
210
- "Use core for every install; add optional capabilities only when their selectWhen rule is true.",
211
- "Bundled presets are seeded during init; use harness preset list --json before choosing task presets.",
212
- "After scaffold, run Configure before marking capabilities configured or verified.",
213
- "Run harness check/status/dashboard and record residuals before delivery.",
214
- ],
215
- verificationCommands: [
216
- `harness check --profile target-project ${target.projectRoot}`,
217
- `harness status --json ${target.projectRoot}`,
218
- `harness dashboard --out /tmp/harness-dashboard.html ${target.projectRoot}`,
219
- ],
220
- };
221
- }
222
-
223
- function packageVersion() {
224
- try {
225
- const pkg = readJsonSafe(path.join(repoRoot, "package.json"), {});
226
- return pkg.version || "";
227
- } catch {
228
- return "";
229
- }
230
- }
231
-
232
- function userHome(home = "") {
233
- return path.resolve(home || os.homedir());
234
- }
235
-
236
- function normalizeUserAgent(agent = "codex") {
237
- const normalized = String(agent || "codex").toLowerCase();
238
- if (normalized === "all") return Object.keys(userInstallTargets);
239
- if (!userInstallTargets[normalized]) throw new Error(`Unknown user agent target: ${agent}`);
240
- return [normalized];
241
- }
242
-
243
- function targetForUserAgent(agent, home = "") {
244
- return path.join(userHome(home), ...userInstallTargets[agent]);
245
- }
246
-
247
- function skillPackageEntries() {
248
- return [
249
- "README.md",
250
- "CHANGELOG.md",
251
- "SKILL.md",
252
- "LICENSE",
253
- "package.json",
254
- "references",
255
- "templates",
256
- "templates-zh-CN",
257
- "presets",
258
- "scripts",
259
- "docs-release",
260
- "examples",
261
- ];
262
- }
263
-
264
- function listPackageFiles() {
265
- return skillPackageEntries()
266
- .flatMap((entry) => {
267
- const fullPath = path.join(repoRoot, entry);
268
- if (!fs.existsSync(fullPath)) return [];
269
- if (fs.statSync(fullPath).isFile()) return [toPosix(path.relative(repoRoot, fullPath))];
270
- return walkFiles(fullPath).map((file) => toPosix(path.relative(repoRoot, file)));
271
- })
272
- .sort();
273
- }
274
-
275
- function copySkillPackage(targetRoot, { dryRun = false, force = false } = {}) {
276
- const changes = [];
277
- for (const relativeFile of listPackageFiles()) {
278
- const source = path.join(repoRoot, relativeFile);
279
- const destination = path.join(targetRoot, relativeFile);
280
- const existsAlready = fs.existsSync(destination);
281
- const action = existsAlready ? (force ? "overwrite" : "skip-existing") : dryRun ? "would-create" : "create";
282
- changes.push({ source: relativeFile, destination, action });
283
- if (dryRun || (existsAlready && !force)) continue;
284
- fs.mkdirSync(path.dirname(destination), { recursive: true });
285
- fs.copyFileSync(source, destination);
286
- }
287
- return changes;
288
- }
289
-
290
- export function installUserSkill({ agent = "codex", home = "", dryRun = false, force = false, seedPresets = true } = {}) {
291
- const agents = normalizeUserAgent(agent);
292
- const targets = agents.map((targetAgent) => {
293
- const target = targetForUserAgent(targetAgent, home);
294
- const changes = copySkillPackage(target, { dryRun, force });
295
- return {
296
- agent: targetAgent,
297
- target,
298
- changes,
299
- created: changes.filter((change) => ["create", "would-create"].includes(change.action)).length,
300
- overwritten: changes.filter((change) => change.action === "overwrite").length,
301
- skipped: changes.filter((change) => change.action === "skip-existing").length,
302
- };
303
- });
304
- const presetSeed = seedPresets ? seedBundledPresets({ scope: "user", home, dryRun, force }) : null;
305
- const changed = targets.some((target) => target.created > 0 || target.overwritten > 0) || (presetSeed && (presetSeed.created > 0 || presetSeed.overwritten > 0));
306
- const onlySkipped =
307
- targets.every((target) => target.created === 0 && target.overwritten === 0 && target.skipped > 0) &&
308
- (!presetSeed || presetSeed.presets.every((preset) => preset.action === "skip-existing"));
309
- return {
310
- operation: "install-user",
311
- status: dryRun ? "dry-run" : changed ? "installed" : onlySkipped ? "already-present" : "no-op",
312
- dryRun,
313
- force,
314
- version: packageVersion(),
315
- source: repoRoot,
316
- presets: presetSeed,
317
- targets,
318
- };
319
- }
320
-
321
- function readInstalledVersion(targetRoot) {
322
- try {
323
- const pkg = readJsonSafe(path.join(targetRoot, "package.json"), {});
324
- return pkg.version || "";
325
- } catch {
326
- return "";
327
- }
328
- }
329
-
330
- function commandOnPath(command) {
331
- const paths = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
332
- const extensions = process.platform === "win32" ? (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";") : [""];
333
- for (const base of paths) {
334
- for (const extension of extensions) {
335
- const candidate = path.join(base, `${command}${extension}`);
336
- if (fs.existsSync(candidate)) return candidate;
337
- }
338
- }
339
- return "";
340
- }
341
-
342
- export function doctorUserSkill({ agent = "codex", home = "" } = {}) {
343
- const required = [
344
- "SKILL.md",
345
- "package.json",
346
- "references",
347
- "templates",
348
- "templates-zh-CN",
349
- "presets",
350
- "scripts/harness.mjs",
351
- "docs-release/guides/agent-installation.md",
352
- ];
353
- const targets = normalizeUserAgent(agent).map((targetAgent) => {
354
- const target = targetForUserAgent(targetAgent, home);
355
- const missing = required.filter((relativePath) => !fs.existsSync(path.join(target, relativePath)));
356
- return {
357
- agent: targetAgent,
358
- target,
359
- status: missing.length === 0 ? "pass" : "fail",
360
- version: readInstalledVersion(target),
361
- missing,
362
- };
363
- });
364
- const presetRoot = userPresetRootForHome(home);
365
- const missingPresets = listBundledPresetIds().filter((id) => !fs.existsSync(path.join(presetRoot, id, "preset.yaml")));
366
- const presets = {
367
- root: presetRoot,
368
- status: missingPresets.length === 0 ? "pass" : "fail",
369
- missing: missingPresets,
370
- };
371
- const harnessCommand = commandOnPath("harness");
372
- return {
373
- operation: "doctor-user",
374
- status: targets.every((target) => target.status === "pass") && presets.status === "pass" ? "pass" : "fail",
375
- version: packageVersion(),
376
- harnessCommand: harnessCommand || null,
377
- presets,
378
- targets,
379
- };
380
- }
381
-
382
- export function validateCapabilities(target) {
383
- const registry = readCapabilityRegistry(target);
384
- const detected = detectCapabilities(target);
385
- const failures = [];
386
- const warnings = [];
387
- const byName = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
388
-
389
- for (const error of registry.errors) failures.push(`invalid .harness-capabilities.json: ${error}`);
390
- for (const capability of registry.capabilities) {
391
- if (!capabilityDefinitions[capability.name]) {
392
- failures.push(`unknown capability: ${capability.name}`);
393
- continue;
394
- }
395
- if (!allowedCapabilityStates.has(capability.state)) {
396
- failures.push(`capability ${capability.name} has invalid state: ${capability.state}`);
397
- }
398
- for (const dependency of capabilityDefinitions[capability.name].dependencies) {
399
- if (!byName.has(dependency)) failures.push(`capability ${capability.name} missing dependency: ${dependency}`);
400
- }
401
- if (registry.mode === "declared-capability") {
402
- for (const artifact of capabilityDefinitions[capability.name].artifacts) {
403
- if (!exists(target, artifact)) {
404
- failures.push(`capability ${capability.name} missing required artifact: ${artifact}`);
405
- }
406
- }
407
- }
408
- }
409
-
410
- if (registry.mode === "declared-capability") {
411
- for (const capability of detected) {
412
- if (!byName.has(capability)) warnings.push(`orphan capability artifact detected without declaration: ${capability}`);
413
- }
414
- } else {
415
- warnings.push("legacy-compat mode: no .harness-capabilities.json; adoption suggestion is available");
416
- }
417
-
418
- return { registry, detected, failures, warnings };
419
- }
420
-
421
-
422
- export function plannedInitFiles(capabilities = ["core"], { locale = "en-US" } = {}) {
423
- const files = [
424
- ["AGENTS.md", "templates/AGENTS.md.template"],
425
- ["CLAUDE.md", "templates/CLAUDE.md.template"],
426
- ["docs/Harness-Ledger.md", "templates/ledger/Harness-Ledger.md"],
427
- ["docs/03-ARCHITECTURE/README.md", "templates/architecture/README.md"],
428
- ["docs/03-ARCHITECTURE/Architecture-SSoT.md", "templates/architecture/Architecture-SSoT.md"],
429
- ["docs/03-ARCHITECTURE/local-repo-context.md", "templates/architecture/local-repo-context.md"],
430
- ["docs/03-ARCHITECTURE/system-map.md", "templates/architecture/system-map.md"],
431
- ["docs/03-ARCHITECTURE/service-catalog.md", "templates/architecture/service-catalog.md"],
432
- ["docs/03-ARCHITECTURE/critical-flows.md", "templates/architecture/critical-flows.md"],
433
- ["docs/03-ARCHITECTURE/services/_service-template.md", "templates/architecture/services/service-template.md"],
434
- ["docs/04-DEVELOPMENT/README.md", "templates/development/README.md"],
435
- ["docs/04-DEVELOPMENT/local-setup.md", "templates/development/local-setup.md"],
436
- ["docs/04-DEVELOPMENT/codebase-map.md", "templates/development/codebase-map.md"],
437
- ["docs/04-DEVELOPMENT/external-context/_service-template.md", "templates/development/external-context/service-template.md"],
438
- ["docs/04-DEVELOPMENT/external-source-packs/README.md", "templates/development/external-source-packs/README.md"],
439
- ["docs/04-DEVELOPMENT/external-source-packs/_digest-template.md", "templates/development/external-source-packs/digest-template.md"],
440
- ["docs/04-DEVELOPMENT/stubs-and-mocks.md", "templates/development/stubs-and-mocks.md"],
441
- ["docs/04-DEVELOPMENT/cross-repo-debugging.md", "templates/development/cross-repo-debugging.md"],
442
- ["docs/06-INTEGRATIONS/README.md", "templates/integrations/README.md"],
443
- ["docs/06-INTEGRATIONS/_api-contract-template.md", "templates/integrations/api-contract.md"],
444
- ["docs/06-INTEGRATIONS/_event-contract-template.md", "templates/integrations/event-contract.md"],
445
- ["docs/06-INTEGRATIONS/_webhook-contract-template.md", "templates/integrations/webhook-contract.md"],
446
- ["docs/06-INTEGRATIONS/third-party/_vendor-template.md", "templates/integrations/third-party/vendor-template.md"],
447
- ["docs/09-PLANNING/TASKS/_task-template/brief.md", "templates/planning/brief.md"],
448
- ["docs/09-PLANNING/TASKS/_task-template/task_plan.md", "templates/planning/task_plan.md"],
449
- ["docs/09-PLANNING/TASKS/_task-template/execution_strategy.md", "templates/planning/execution_strategy.md"],
450
- [`docs/09-PLANNING/TASKS/_task-template/${visualMapFile}`, "templates/planning/visual_map.md"],
451
- ["docs/09-PLANNING/TASKS/_task-template/findings.md", "templates/planning/findings.md"],
452
- ["docs/09-PLANNING/TASKS/_task-template/progress.md", "templates/planning/progress.md"],
453
- ["docs/09-PLANNING/TASKS/_task-template/review.md", "templates/planning/review.md"],
454
- ["docs/05-TEST-QA/Regression-SSoT.md", "templates/ssot/Regression-SSoT.md"],
455
- ["docs/05-TEST-QA/Cadence-Ledger.md", "templates/regression/Cadence-Ledger.md"],
456
- ["docs/10-WALKTHROUGH/_walkthrough-template.md", "templates/walkthrough/walkthrough-template.md"],
457
- ["docs/10-WALKTHROUGH/Closeout-SSoT.md", "templates/walkthrough/Closeout-SSoT.md"],
458
- ["docs/11-REFERENCE/external-source-intake-standard.md", "templates/reference/external-source-intake-standard.md"],
459
- ];
460
- if (capabilities.includes("module-parallel")) {
461
- files.push(["docs/09-PLANNING/Module-Registry.md", "templates/ssot/Module-Registry.md"]);
462
- files.push(["docs/09-PLANNING/MODULES/Session-Prompt-Pack.md", "templates/planning/module_session_prompt.md"]);
463
- files.push(["docs/09-PLANNING/MODULES/_module-template/brief.md", "templates/planning/module_brief.md"]);
464
- files.push(["docs/09-PLANNING/MODULES/_module-template/module_plan.md", "templates/planning/module_plan.md"]);
465
- files.push(["docs/09-PLANNING/MODULES/_module-template/execution_strategy.md", "templates/planning/execution_strategy.md"]);
466
- files.push([`docs/09-PLANNING/MODULES/_module-template/${visualMapFile}`, "templates/planning/visual_map.md"]);
467
- files.push(["docs/09-PLANNING/MODULES/_module-template/session_prompt.md", "templates/planning/module_session_prompt.md"]);
468
- files.push(["docs/09-PLANNING/MODULES/_task-template/task_plan.md", "templates/planning/task_plan.md"]);
469
- files.push(["docs/09-PLANNING/MODULES/_task-template/execution_strategy.md", "templates/planning/execution_strategy.md"]);
470
- files.push([`docs/09-PLANNING/MODULES/_task-template/${visualMapFile}`, "templates/planning/visual_map.md"]);
471
- files.push(["docs/09-PLANNING/MODULES/_task-template/findings.md", "templates/planning/findings.md"]);
472
- files.push(["docs/09-PLANNING/MODULES/_task-template/progress.md", "templates/planning/progress.md"]);
473
- files.push(["docs/09-PLANNING/MODULES/_task-template/review.md", "templates/planning/review.md"]);
474
- }
475
- if (capabilities.includes("long-running-task")) {
476
- files.push(["docs/09-PLANNING/TASKS/_task-template/long-running-task-contract.md", "templates/planning/long-running-task-contract.md"]);
477
- }
478
- return files.map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
479
- }
480
-
481
- export function writeInitFiles(targetInput, capabilities, { dryRun = true, locale = "en-US", addNpmScripts = false } = {}) {
482
- const target = normalizeTarget(targetInput);
483
- const normalizedCapabilities = [...new Set(capabilities.map(normalizeCapabilityName))];
484
- const normalizedLocale = normalizeLocale(locale);
485
- const existingRegistry = readCapabilityRegistry(target);
486
- if (existingRegistry.raw) {
487
- const installed = new Set(existingRegistry.capabilities.map((capability) => capability.name));
488
- const requested = new Set(normalizedCapabilities);
489
- const same =
490
- installed.size === requested.size &&
491
- [...installed].every((capability) => requested.has(capability));
492
- if (!same) {
493
- throw new Error("Existing capability registry differs from requested init capabilities; use add-capability instead.");
494
- }
495
- }
496
- const planned = plannedInitFiles(normalizedCapabilities, { locale: normalizedLocale });
497
- const changes = [];
498
- for (const [destination, source] of planned) {
499
- const destinationPath = path.join(target.projectRoot, destination);
500
- const sourcePath = path.join(repoRoot, source);
501
- const existsAlready = fs.existsSync(destinationPath);
502
- changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
503
- if (!dryRun && !existsAlready) {
504
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
505
- fs.copyFileSync(sourcePath, destinationPath);
506
- }
507
- }
508
- if (addNpmScripts) {
509
- changes.push(...writeNpmScripts(target, { dryRun }));
510
- }
511
- const registry = {
512
- version: 1,
513
- locale: normalizedLocale,
514
- capabilities: normalizedCapabilities.map((name) => ({ name, state: "scaffolded" })),
515
- };
516
- if (!dryRun) {
517
- const registryPath = path.join(target.projectRoot, ".harness-capabilities.json");
518
- if (!fs.existsSync(registryPath)) fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
519
- }
520
- const presetSeed = seedBundledPresets({ scope: "project", targetInput: target.projectRoot, dryRun });
521
- const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: normalizedCapabilities, changes, dryRun, operation: "init" });
522
- return { target, capabilities: normalizedCapabilities, locale: normalizedLocale, changes, presetSeed, nextCommands: initNextCommands(), report };
523
- }
524
-
525
- function initNextCommands() {
526
- return [
527
- "npx --yes coding-agent-harness dev .",
528
- "npx --yes coding-agent-harness dashboard --out-dir tmp/harness-dashboard .",
529
- ];
530
- }
531
-
532
- function writeNpmScripts(target, { dryRun = true } = {}) {
533
- const packagePath = path.join(target.projectRoot, "package.json");
534
- if (!fs.existsSync(packagePath)) throw new Error("init --add-npm-scripts requires an existing package.json");
535
- const pkg = readJsonSafe(packagePath, {});
536
- const scripts = { ...(pkg.scripts || {}) };
537
- const additions = {
538
- "harness:dev": "coding-agent-harness dev .",
539
- "harness:dashboard": "coding-agent-harness dashboard --out-dir tmp/harness-dashboard .",
540
- };
541
- let changed = false;
542
- const scriptChanges = [];
543
- for (const [name, command] of Object.entries(additions)) {
544
- if (scripts[name]) {
545
- scriptChanges.push({ destination: "package.json", source: `scripts.${name}`, action: "skip-existing-script" });
546
- continue;
547
- }
548
- scripts[name] = command;
549
- changed = true;
550
- }
551
- if (!changed) return scriptChanges;
552
- if (!dryRun) {
553
- fs.writeFileSync(packagePath, `${JSON.stringify({ ...pkg, scripts }, null, 2)}\n`);
554
- }
555
- return [{ destination: "package.json", source: "npm-scripts", action: dryRun ? "would-update-scripts" : "update-scripts" }, ...scriptChanges];
556
- }
557
-
558
- export function addCapability(targetInput, capabilityName, { dryRun = true, locale = "" } = {}) {
559
- const target = normalizeTarget(targetInput);
560
- const normalizedCapability = normalizeCapabilityName(capabilityName);
561
- if (!capabilityDefinitions[normalizedCapability]) throw new Error(`Unknown capability: ${capabilityName}`);
562
- const registry = readCapabilityRegistry(target);
563
- const normalizedLocale = normalizeLocale(registry.raw ? registry.locale : locale || "en-US");
564
- const capabilityMap = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
565
- for (const dependency of capabilityDefinitions[normalizedCapability].dependencies) {
566
- if (!capabilityMap.has(dependency)) capabilityMap.set(dependency, { name: dependency, state: "scaffolded" });
567
- }
568
- if (!capabilityMap.has(normalizedCapability)) capabilityMap.set(normalizedCapability, { name: normalizedCapability, state: "scaffolded" });
569
- const next = { version: 1, locale: normalizedLocale, capabilities: [...capabilityMap.values()] };
570
- const scaffold = plannedInitFiles([...capabilityMap.keys()], { locale: normalizedLocale });
571
- const changes = [];
572
- for (const [destination, source] of scaffold) {
573
- const destinationPath = path.join(target.projectRoot, destination);
574
- const sourcePath = path.join(repoRoot, source);
575
- const existsAlready = fs.existsSync(destinationPath);
576
- changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
577
- if (!dryRun && !existsAlready) {
578
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
579
- fs.copyFileSync(sourcePath, destinationPath);
580
- }
581
- }
582
- if (!dryRun) {
583
- fs.writeFileSync(path.join(target.projectRoot, ".harness-capabilities.json"), `${JSON.stringify(next, null, 2)}\n`);
584
- }
585
- const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: [...capabilityMap.keys()], changes, dryRun, operation: "add-capability" });
586
- return { target, dryRun, registry: next, changes, report };
587
- }