coding-agent-harness 1.0.4 → 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 (279) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +96 -4
  6. package/README.zh-CN.md +75 -4
  7. package/SKILL.md +52 -51
  8. package/dist/build-dist.mjs +189 -0
  9. package/dist/check-dist-observation.mjs +428 -0
  10. package/dist/check-harness.mjs +489 -0
  11. package/dist/check-import-graph.mjs +511 -0
  12. package/dist/check-runtime-emit.mjs +304 -0
  13. package/dist/check-type-boundaries.mjs +139 -0
  14. package/dist/commands/dashboard-command.mjs +80 -0
  15. package/dist/commands/migration-command.mjs +152 -0
  16. package/dist/commands/preset-command.mjs +91 -0
  17. package/dist/commands/task-command.mjs +324 -0
  18. package/dist/harness.mjs +304 -0
  19. package/dist/lib/capability-registry.mjs +643 -0
  20. package/dist/lib/check-module-parallel.mjs +227 -0
  21. package/dist/lib/check-profiles.mjs +414 -0
  22. package/dist/lib/check-task-contracts.mjs +54 -0
  23. package/dist/lib/core-shared.mjs +254 -0
  24. package/dist/lib/dashboard-data.mjs +608 -0
  25. package/dist/lib/dashboard-workbench.mjs +334 -0
  26. package/dist/lib/dashboard-writer.mjs +200 -0
  27. package/dist/lib/git-status-summary.mjs +45 -0
  28. package/dist/lib/governance-index-generator.mjs +236 -0
  29. package/dist/lib/governance-sync.mjs +617 -0
  30. package/dist/lib/governance-table-boundary.mjs +161 -0
  31. package/{scripts → dist}/lib/harness-core.mjs +3 -0
  32. package/dist/lib/harness-paths.mjs +338 -0
  33. package/dist/lib/lesson-maintenance.mjs +139 -0
  34. package/dist/lib/markdown-utils.mjs +193 -0
  35. package/dist/lib/migration-planner.mjs +439 -0
  36. package/dist/lib/migration-support.mjs +317 -0
  37. package/dist/lib/phase-kind.mjs +46 -0
  38. package/dist/lib/preset-audit-contracts.mjs +40 -0
  39. package/dist/lib/preset-engine.mjs +516 -0
  40. package/dist/lib/preset-registry.mjs +831 -0
  41. package/dist/lib/preset-resource-contracts.mjs +83 -0
  42. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  43. package/dist/lib/status-builder.mjs +87 -0
  44. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
  45. package/dist/lib/structure-migration.mjs +404 -0
  46. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  47. package/dist/lib/task-audit-metadata.mjs +376 -0
  48. package/dist/lib/task-audit-migration.mjs +355 -0
  49. package/dist/lib/task-completion-consistency.mjs +29 -0
  50. package/dist/lib/task-index.mjs +133 -0
  51. package/dist/lib/task-lesson-candidates.mjs +239 -0
  52. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  53. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  54. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  55. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  56. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  57. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  58. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  59. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  60. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  61. package/dist/lib/task-lifecycle.mjs +611 -0
  62. package/dist/lib/task-metadata.mjs +116 -0
  63. package/dist/lib/task-review-model.mjs +474 -0
  64. package/dist/lib/task-scanner.mjs +439 -0
  65. package/dist/lib/task-tombstone-commands.mjs +125 -0
  66. package/dist/postinstall.mjs +14 -0
  67. package/dist/run-built-tests.mjs +84 -0
  68. package/docs-release/README.md +1 -0
  69. package/docs-release/architecture/overview.md +13 -13
  70. package/docs-release/architecture/overview.zh-CN.md +13 -13
  71. package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
  72. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  73. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  74. package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
  75. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  76. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
  77. package/docs-release/architecture/system-explainer/README.md +67 -0
  78. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
  79. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  80. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  81. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
  82. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  83. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
  84. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  85. package/docs-release/guides/agent-installation.en-US.md +22 -15
  86. package/docs-release/guides/agent-installation.md +23 -15
  87. package/docs-release/guides/contributing.md +3 -3
  88. package/docs-release/guides/contributing.zh-CN.md +3 -3
  89. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  90. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  91. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  92. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  93. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  94. package/docs-release/guides/migration-playbook.md +59 -1
  95. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  96. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  97. package/docs-release/guides/preset-development.md +28 -4
  98. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  99. package/docs-release/guides/repository-operating-models.md +21 -21
  100. package/docs-release/guides/task-state-machine.en-US.md +35 -18
  101. package/docs-release/guides/task-state-machine.md +35 -18
  102. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  103. package/examples/minimal-project/AGENTS.md +2 -2
  104. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  105. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
  106. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  107. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  108. package/package.json +22 -13
  109. package/presets/legacy-migration/preset.yaml +5 -5
  110. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  111. package/presets/lesson-sedimentation/preset.yaml +3 -3
  112. package/presets/module/preset.yaml +2 -2
  113. package/presets/module/templates/execution_strategy.append.md +1 -1
  114. package/presets/module/templates/task_plan.append.md +3 -3
  115. package/presets/standard-task/preset.yaml +2 -2
  116. package/references/adversarial-review-standard.md +2 -2
  117. package/references/agents-md-pattern.md +14 -14
  118. package/references/cadence-ledger.md +1 -1
  119. package/references/ci-cd-standard.md +1 -1
  120. package/references/delivery-operating-model-standard.md +4 -4
  121. package/references/docs-directory-standard.md +65 -159
  122. package/references/external-source-intake-standard.md +10 -10
  123. package/references/harness-ledger.md +6 -6
  124. package/references/legacy-12-phase-bootstrap.md +2 -2
  125. package/references/lessons-governance.md +15 -15
  126. package/references/long-running-task-standard.md +6 -6
  127. package/references/module-parallel-standard.md +34 -34
  128. package/references/planning-loop.md +6 -6
  129. package/references/project-onboarding-audit.md +4 -4
  130. package/references/regression-system.md +2 -2
  131. package/references/repo-governance-standard.md +4 -4
  132. package/references/review-routing-standard.md +1 -1
  133. package/references/ssot-governance.md +19 -19
  134. package/references/taskr-gap-analysis.md +5 -5
  135. package/references/walkthrough-closeout.md +14 -14
  136. package/references/worktree-parallel.md +3 -3
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  139. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  140. package/templates/AGENTS.md.template +31 -29
  141. package/templates/architecture/README.md +4 -4
  142. package/templates/architecture/service-catalog.md +2 -2
  143. package/templates/architecture/services/service-template.md +1 -1
  144. package/templates/dashboard/assets/app-src/00-state.js +12 -0
  145. package/templates/dashboard/assets/app-src/10-router.js +3 -0
  146. package/templates/dashboard/assets/app-src/20-overview.js +13 -3
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
  148. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  149. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  150. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  151. package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
  152. package/templates/dashboard/assets/app.css +583 -0
  153. package/templates/dashboard/assets/app.css.manifest.json +1 -0
  154. package/templates/dashboard/assets/app.js +585 -11
  155. package/templates/dashboard/assets/app.manifest.json +1 -0
  156. package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
  158. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  159. package/templates/dashboard/assets/i18n.js +144 -4
  160. package/templates/development/README.md +10 -10
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +2 -2
  163. package/templates/development/external-source-packs/README.md +4 -4
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +2 -2
  166. package/templates/integrations/event-contract.md +2 -2
  167. package/templates/integrations/third-party/vendor-template.md +2 -2
  168. package/templates/integrations/webhook-contract.md +2 -2
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/planning/INDEX.md +88 -0
  171. package/templates/planning/brief.md +1 -1
  172. package/templates/planning/module_session_prompt.md +2 -1
  173. package/templates/planning/review.md +0 -18
  174. package/templates/planning/task_plan.md +5 -44
  175. package/templates/planning/visual_map.md +13 -9
  176. package/templates/planning/visual_map.simple.md +52 -0
  177. package/templates/planning/walkthrough.md +47 -0
  178. package/templates/reference/docs-library-standard.md +8 -8
  179. package/templates/reference/execution-workflow-standard.md +29 -2
  180. package/templates/reference/external-source-intake-standard.md +15 -15
  181. package/templates/reference/repo-governance-standard.md +1 -1
  182. package/templates/ssot/Module-Registry.md +1 -1
  183. package/templates/walkthrough/walkthrough-template.md +2 -2
  184. package/templates-zh-CN/AGENTS.md.template +31 -29
  185. package/templates-zh-CN/CLAUDE.md.template +1 -1
  186. package/templates-zh-CN/architecture/README.md +4 -4
  187. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  188. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  189. package/templates-zh-CN/development/README.md +10 -10
  190. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  191. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  192. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  193. package/templates-zh-CN/integrations/README.md +4 -4
  194. package/templates-zh-CN/integrations/api-contract.md +2 -2
  195. package/templates-zh-CN/integrations/event-contract.md +2 -2
  196. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  197. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  198. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  199. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  200. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  201. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  202. package/templates-zh-CN/planning/INDEX.md +87 -0
  203. package/templates-zh-CN/planning/brief.md +1 -1
  204. package/templates-zh-CN/planning/module_session_prompt.md +12 -11
  205. package/templates-zh-CN/planning/review.md +0 -18
  206. package/templates-zh-CN/planning/task_plan.md +3 -63
  207. package/templates-zh-CN/planning/visual_map.md +14 -7
  208. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  209. package/templates-zh-CN/planning/walkthrough.md +47 -0
  210. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  211. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  212. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  213. package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
  214. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  215. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  216. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  217. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  218. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  219. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  220. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  221. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  222. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  223. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  224. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  225. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  226. package/tsconfig.dist.json +16 -0
  227. package/tsconfig.json +25 -0
  228. package/tsconfig.runtime.json +24 -0
  229. package/examples/minimal-project/.harness-capabilities.json +0 -8
  230. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  231. package/scripts/check-harness.mjs +0 -508
  232. package/scripts/commands/dashboard-command.mjs +0 -67
  233. package/scripts/commands/migration-command.mjs +0 -96
  234. package/scripts/commands/preset-command.mjs +0 -73
  235. package/scripts/commands/task-command.mjs +0 -327
  236. package/scripts/harness.mjs +0 -287
  237. package/scripts/lib/capability-registry.mjs +0 -591
  238. package/scripts/lib/check-module-parallel.mjs +0 -237
  239. package/scripts/lib/check-profiles.mjs +0 -418
  240. package/scripts/lib/check-task-contracts.mjs +0 -47
  241. package/scripts/lib/core-shared.mjs +0 -196
  242. package/scripts/lib/dashboard-data.mjs +0 -412
  243. package/scripts/lib/dashboard-workbench.mjs +0 -257
  244. package/scripts/lib/dashboard-writer.mjs +0 -198
  245. package/scripts/lib/git-status-summary.mjs +0 -46
  246. package/scripts/lib/governance-index-generator.mjs +0 -174
  247. package/scripts/lib/governance-sync.mjs +0 -514
  248. package/scripts/lib/governance-table-boundary.mjs +0 -175
  249. package/scripts/lib/lesson-maintenance.mjs +0 -152
  250. package/scripts/lib/markdown-utils.mjs +0 -158
  251. package/scripts/lib/migration-planner.mjs +0 -478
  252. package/scripts/lib/migration-support.mjs +0 -312
  253. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  254. package/scripts/lib/preset-engine.mjs +0 -497
  255. package/scripts/lib/preset-registry.mjs +0 -627
  256. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  257. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  258. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  259. package/scripts/lib/task-completion-consistency.mjs +0 -16
  260. package/scripts/lib/task-index.mjs +0 -93
  261. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  262. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  263. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
  264. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
  265. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  266. package/scripts/lib/task-lifecycle.mjs +0 -649
  267. package/scripts/lib/task-review-model.mjs +0 -469
  268. package/scripts/lib/task-scanner.mjs +0 -576
  269. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  270. package/scripts/postinstall.mjs +0 -14
  271. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  272. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  273. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  274. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  275. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  276. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  277. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  278. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  279. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,334 @@
1
+ // @ts-nocheck
2
+ // Dashboard workbench HTTP handlers stay behavior-first until workbench request/response types are modeled.
3
+ import crypto from "node:crypto";
4
+ import { spawn } from "node:child_process";
5
+ import fs from "node:fs";
6
+ import http from "node:http";
7
+ import path from "node:path";
8
+ import { URL } from "node:url";
9
+ import { confirmTaskReview } from "./task-lifecycle.mjs";
10
+ import { createLessonSedimentationTask } from "./task-lesson-sedimentation.mjs";
11
+ import { normalizeTarget } from "./core-shared.mjs";
12
+ import { dashboardWatchRoots } from "./harness-paths.mjs";
13
+ import { collectTasks } from "./task-scanner.mjs";
14
+ import { writeDashboardFolder } from "./dashboard-data.mjs";
15
+ import { checkPresetPackage, installPresetPackage, listPresetPackages, seedBundledPresets, uninstallPresetPackage, } from "./preset-registry.mjs";
16
+ const jsonHeaders = { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" };
17
+ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127.0.0.1", port = 0, localeOverride = "", autoRefresh = false, open = false, label = "dashboard workbench", recoverGeneratedDashboard = false } = {}) {
18
+ if (host !== "127.0.0.1")
19
+ throw new Error("dashboard workbench only supports --host 127.0.0.1");
20
+ const target = normalizeTarget(targetInput);
21
+ const outputDir = path.resolve(outDir);
22
+ const csrfToken = crypto.randomBytes(24).toString("hex");
23
+ const options = localeOverride ? { localeOverride } : {};
24
+ let snapshotVersion = Date.now();
25
+ const regenerate = () => {
26
+ writeDashboardFolder(outputDir, targetInput, { ...options, workbenchRuntime: true, recoverGeneratedDashboard });
27
+ snapshotVersion = Date.now();
28
+ };
29
+ regenerate();
30
+ const server = http.createServer(async (request, response) => {
31
+ try {
32
+ const address = server.address();
33
+ const actualPort = typeof address === "object" && address ? address.port : port;
34
+ const origin = `http://${host}:${actualPort}`;
35
+ const requestUrl = new URL(request.url || "/", origin);
36
+ if (requestUrl.pathname === "/api/runtime" && request.method === "GET") {
37
+ writeJson(response, 200, {
38
+ mode: "workbench",
39
+ csrfToken,
40
+ writableActions: ["review-complete", "lesson-sedimentation-task", "preset-check", "preset-install", "preset-seed", "preset-uninstall"],
41
+ target: target.projectRoot,
42
+ autoRefresh: autoRefresh === true,
43
+ snapshotVersion,
44
+ });
45
+ return;
46
+ }
47
+ if (requestUrl.pathname === "/api/tasks/review-complete" && request.method === "POST") {
48
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
49
+ const body = await readJsonBody(request);
50
+ const taskId = String(body.taskId || "");
51
+ const task = collectTasks(target).find((item) => item.id === taskId);
52
+ if (!task) {
53
+ writeJson(response, 404, { error: "Task not found" });
54
+ return;
55
+ }
56
+ if (!isTaskInReviewQueue(task)) {
57
+ writeJson(response, 409, reviewQueueRejectionPayload(task));
58
+ return;
59
+ }
60
+ if (task.reviewStatus === "confirmed") {
61
+ writeJson(response, 409, { error: "Review is already confirmed." });
62
+ return;
63
+ }
64
+ const result = confirmTaskReview(target.projectRoot, taskId, {
65
+ reviewer: body.reviewer || "Human Reviewer",
66
+ message: body.message || "confirmed from dashboard workbench",
67
+ evidence: body.evidence || "",
68
+ confirmText: body.confirmText || "",
69
+ });
70
+ regenerate();
71
+ writeJson(response, 200, result);
72
+ return;
73
+ }
74
+ if (requestUrl.pathname === "/api/tasks/lesson-sedimentation" && request.method === "POST") {
75
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
76
+ const body = await readJsonBody(request);
77
+ const taskId = String(body.taskId || "");
78
+ const candidateId = String(body.candidateId || "");
79
+ const task = collectTasks(target).find((item) => item.id === taskId);
80
+ if (!task) {
81
+ writeJson(response, 404, { error: "Task not found" });
82
+ return;
83
+ }
84
+ if (!candidateId) {
85
+ writeJson(response, 400, { error: "Missing lesson candidate id" });
86
+ return;
87
+ }
88
+ const result = createLessonSedimentationTask(target.projectRoot, taskId, candidateId, {
89
+ title: body.title || "",
90
+ });
91
+ regenerate();
92
+ writeJson(response, 200, result);
93
+ return;
94
+ }
95
+ if (requestUrl.pathname === "/api/presets/check" && request.method === "POST") {
96
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
97
+ const body = await readJsonBody(request);
98
+ const id = String(body.id || "");
99
+ if (!id) {
100
+ writeJson(response, 400, { error: "Missing preset id" });
101
+ return;
102
+ }
103
+ const result = checkPresetPackage(id, { targetInput: target.projectRoot });
104
+ writeJson(response, 200, result);
105
+ return;
106
+ }
107
+ if (requestUrl.pathname === "/api/presets/install" && request.method === "POST") {
108
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
109
+ const body = await readJsonBody(request);
110
+ const source = String(body.source || "");
111
+ if (!source) {
112
+ writeJson(response, 400, { error: "Missing preset source" });
113
+ return;
114
+ }
115
+ if (/^https?:\/\//i.test(source)) {
116
+ writeJson(response, 400, { error: "Network preset sources are not supported by the dashboard workbench." });
117
+ return;
118
+ }
119
+ const scope = normalizePresetScope(body.scope);
120
+ const result = installPresetPackage(source, { force: body.force === true, scope, targetInput: target.projectRoot });
121
+ regenerate();
122
+ writeJson(response, 200, { ...result, scope });
123
+ return;
124
+ }
125
+ if (requestUrl.pathname === "/api/presets/seed" && request.method === "POST") {
126
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
127
+ const body = await readJsonBody(request);
128
+ const scope = normalizePresetScope(body.scope);
129
+ const result = seedBundledPresets({ force: body.force === true, dryRun: body.dryRun === true, scope, targetInput: target.projectRoot });
130
+ if (body.dryRun !== true)
131
+ regenerate();
132
+ writeJson(response, 200, result);
133
+ return;
134
+ }
135
+ if (requestUrl.pathname === "/api/presets/uninstall" && request.method === "POST") {
136
+ assertTrustedWorkbenchRequest(request, { origin, csrfToken });
137
+ const body = await readJsonBody(request);
138
+ const id = String(body.id || "");
139
+ if (!id) {
140
+ writeJson(response, 400, { error: "Missing preset id" });
141
+ return;
142
+ }
143
+ if (String(body.confirmText || "").trim() !== id) {
144
+ writeJson(response, 400, { error: "Preset uninstall requires typing the preset id." });
145
+ return;
146
+ }
147
+ const scope = normalizePresetScope(body.scope);
148
+ const discovered = listPresetPackages({ targetInput: target.projectRoot }).find((preset) => preset.id === id);
149
+ if (discovered?.source === "builtin") {
150
+ writeJson(response, 409, { error: "Builtin preset cannot be uninstalled from the dashboard workbench.", id, source: "builtin" });
151
+ return;
152
+ }
153
+ const result = uninstallPresetPackage(id, { scope, targetInput: target.projectRoot });
154
+ regenerate();
155
+ writeJson(response, 200, { ...result, scope });
156
+ return;
157
+ }
158
+ if (request.method !== "GET" && request.method !== "HEAD") {
159
+ writeJson(response, 405, { error: "Method not allowed" });
160
+ return;
161
+ }
162
+ serveStaticFile(response, outputDir, requestUrl.pathname, request.method === "HEAD");
163
+ }
164
+ catch (error) {
165
+ const status = error.status || (/CSRF|Origin|Host/.test(error.message) ? 403 : 400);
166
+ writeJson(response, status, errorPayload(error));
167
+ }
168
+ });
169
+ await new Promise((resolve, reject) => {
170
+ server.once("error", reject);
171
+ server.listen(Number(port), host, resolve);
172
+ });
173
+ const address = server.address();
174
+ const actualPort = typeof address === "object" && address ? address.port : port;
175
+ const url = `http://${host}:${actualPort}/`;
176
+ let watcher = null;
177
+ if (autoRefresh)
178
+ watcher = startPollingWatch(dashboardWatchRoots(target.harness), regenerate);
179
+ console.log(`${label}: ${url} csrf=${csrfToken} outDir=${outputDir}`);
180
+ if (open)
181
+ openBrowser(url);
182
+ const close = () => {
183
+ if (watcher)
184
+ clearInterval(watcher);
185
+ server.close(() => process.exit(0));
186
+ };
187
+ process.once("SIGINT", close);
188
+ process.once("SIGTERM", close);
189
+ await new Promise(() => { });
190
+ }
191
+ function normalizePresetScope(value) {
192
+ const scope = String(value || "project");
193
+ if (scope !== "project" && scope !== "user")
194
+ throw new Error(`Invalid preset scope: ${scope}`);
195
+ return scope;
196
+ }
197
+ function isTaskInReviewQueue(task) {
198
+ return task?.reviewQueueState === "ready-to-confirm" && Array.isArray(task?.taskQueues) && task.taskQueues.includes("review");
199
+ }
200
+ function reviewQueueRejectionPayload(task) {
201
+ return {
202
+ error: "Review completion is only available for tasks in the review queue.",
203
+ reviewQueueState: task?.reviewQueueState || "unknown",
204
+ taskQueues: Array.isArray(task?.taskQueues) ? task.taskQueues : [],
205
+ queueReasons: Array.isArray(task?.queueReasons) ? task.queueReasons : [],
206
+ repairPrompt: task?.repairPrompt || "",
207
+ reviewStatus: task?.reviewStatus || "unknown",
208
+ taskId: task?.id || "",
209
+ };
210
+ }
211
+ function startPollingWatch(roots, regenerate) {
212
+ let lastMtime = latestTreeMtime(roots);
213
+ let timer = null;
214
+ return setInterval(() => {
215
+ const nextMtime = latestTreeMtime(roots);
216
+ if (nextMtime <= lastMtime)
217
+ return;
218
+ lastMtime = nextMtime;
219
+ clearTimeout(timer);
220
+ timer = setTimeout(() => {
221
+ try {
222
+ regenerate();
223
+ }
224
+ catch (error) {
225
+ console.error(`dashboard regeneration failed: ${error.message}`);
226
+ }
227
+ }, 250);
228
+ }, 1000);
229
+ }
230
+ function latestTreeMtime(roots) {
231
+ let latest = 0;
232
+ const watchRoots = Array.isArray(roots) ? roots : [roots];
233
+ const visit = (dir) => {
234
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
235
+ if ([".git", "node_modules", "tmp"].includes(entry.name))
236
+ continue;
237
+ const fullPath = path.join(dir, entry.name);
238
+ const stat = fs.statSync(fullPath);
239
+ latest = Math.max(latest, stat.mtimeMs);
240
+ if (entry.isDirectory())
241
+ visit(fullPath);
242
+ }
243
+ };
244
+ for (const root of watchRoots) {
245
+ if (fs.existsSync(root))
246
+ visit(root);
247
+ }
248
+ return latest;
249
+ }
250
+ function openBrowser(url) {
251
+ const command = process.platform === "darwin" ? "open" :
252
+ process.platform === "win32" ? "cmd" :
253
+ "xdg-open";
254
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
255
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
256
+ child.on("error", () => { });
257
+ child.unref();
258
+ }
259
+ function assertTrustedWorkbenchRequest(request, { origin, csrfToken }) {
260
+ const host = request.headers.host || "";
261
+ if (host !== origin.replace(/^http:\/\//, ""))
262
+ throw new Error("Host mismatch");
263
+ if (request.headers.origin !== origin)
264
+ throw new Error("Origin mismatch");
265
+ if (request.headers["x-harness-csrf"] !== csrfToken)
266
+ throw new Error("CSRF token mismatch");
267
+ }
268
+ function readJsonBody(request) {
269
+ return new Promise((resolve, reject) => {
270
+ let raw = "";
271
+ request.setEncoding("utf8");
272
+ request.on("data", (chunk) => {
273
+ raw += chunk;
274
+ if (raw.length > 32_768) {
275
+ reject(new Error("Request body too large"));
276
+ request.destroy();
277
+ }
278
+ });
279
+ request.on("end", () => {
280
+ try {
281
+ resolve(raw ? JSON.parse(raw) : {});
282
+ }
283
+ catch {
284
+ reject(new Error("Invalid JSON body"));
285
+ }
286
+ });
287
+ request.on("error", reject);
288
+ });
289
+ }
290
+ function serveStaticFile(response, outputDir, urlPath, headOnly) {
291
+ const decoded = decodeURIComponent(urlPath);
292
+ const relative = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
293
+ const filePath = path.resolve(outputDir, relative);
294
+ if (!isPathInside(filePath, outputDir) || !fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
295
+ writeJson(response, 404, { error: "Not found" });
296
+ return;
297
+ }
298
+ response.writeHead(200, { "content-type": mimeType(filePath), "cache-control": "no-store" });
299
+ if (!headOnly)
300
+ response.end(fs.readFileSync(filePath));
301
+ else
302
+ response.end();
303
+ }
304
+ function writeJson(response, status, payload) {
305
+ response.writeHead(status, jsonHeaders);
306
+ response.end(`${JSON.stringify(payload)}\n`);
307
+ }
308
+ function errorPayload(error) {
309
+ const payload = { error: error.message };
310
+ if (error.code)
311
+ payload.code = error.code;
312
+ if (Array.isArray(error.recovery) && error.recovery.length > 0)
313
+ payload.recovery = error.recovery;
314
+ if (error.details)
315
+ payload.details = error.details;
316
+ return payload;
317
+ }
318
+ function mimeType(filePath) {
319
+ if (filePath.endsWith(".html"))
320
+ return "text/html; charset=utf-8";
321
+ if (filePath.endsWith(".js"))
322
+ return "text/javascript; charset=utf-8";
323
+ if (filePath.endsWith(".css"))
324
+ return "text/css; charset=utf-8";
325
+ if (filePath.endsWith(".json"))
326
+ return "application/json; charset=utf-8";
327
+ if (filePath.endsWith(".md"))
328
+ return "text/markdown; charset=utf-8";
329
+ return "application/octet-stream";
330
+ }
331
+ function isPathInside(candidate, parent) {
332
+ const relative = path.relative(path.resolve(parent), path.resolve(candidate));
333
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
334
+ }
@@ -0,0 +1,200 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ // @ts-ignore core-shared remains a JS runtime dependency until its migration PR.
5
+ import { readJsonSafe } from "./core-shared.mjs";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const repoRoot = path.resolve(__dirname, "../..");
8
+ const dashboardTemplateRoot = path.join(repoRoot, "templates/dashboard");
9
+ const dashboardMarker = ".harness-dashboard";
10
+ export function writeJsonFile(filePath, value) {
11
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
12
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
13
+ }
14
+ export function copyDashboardAssets(outDir, options = {}) {
15
+ copyDirectory(dashboardTemplateRootForLocale(options.locale), outDir);
16
+ }
17
+ export function writeDashboardDirectory(outDir, bundle, options = {}) {
18
+ const target = path.resolve(outDir);
19
+ assertSafeDashboardTarget(target, options);
20
+ if (fs.existsSync(target))
21
+ fs.rmSync(target, { recursive: true, force: true });
22
+ fs.mkdirSync(target, { recursive: true });
23
+ fs.writeFileSync(path.join(target, dashboardMarker), "generated dashboard directory\n");
24
+ copyDashboardAssets(target, options);
25
+ fs.writeFileSync(path.join(target, "assets/app.js"), readDashboardApp(dashboardTemplateRootForLocale(options.locale)));
26
+ fs.writeFileSync(path.join(target, "index.html"), renderDashboardIndex(options.locale, options));
27
+ writeJsonFile(path.join(target, "data/status.json"), bundle.status);
28
+ writeJsonFile(path.join(target, "data/tables.json"), bundle.tables);
29
+ writeJsonFile(path.join(target, "data/documents.json"), bundle.documents);
30
+ writeJsonFile(path.join(target, "data/graph.json"), bundle.graph);
31
+ writeJsonFile(path.join(target, "data/adoption.json"), bundle.adoption);
32
+ writeJsonFile(path.join(target, "data/presetCatalog.json"), bundle.presetCatalog);
33
+ fs.writeFileSync(path.join(target, "assets/dashboard-data.js"), `window.__HARNESS_DASHBOARD__ = ${JSON.stringify(bundle, null, 2)};\n`);
34
+ fs.writeFileSync(path.join(target, "README.md"), [
35
+ "# Harness Dashboard",
36
+ "",
37
+ "This is a read-only static snapshot generated by `harness dashboard --out-dir`.",
38
+ "Open `index.html` directly in a browser. No target project files are read at browser runtime.",
39
+ "",
40
+ ].join("\n"));
41
+ return target;
42
+ }
43
+ export function writeDashboardFile(outFile, bundle, options = {}) {
44
+ const target = path.resolve(outFile);
45
+ fs.mkdirSync(path.dirname(target), { recursive: true });
46
+ fs.writeFileSync(target, renderDashboardFile(bundle, options));
47
+ return target;
48
+ }
49
+ export function renderDashboardFile(bundle, options = {}) {
50
+ const templateRoot = dashboardTemplateRootForLocale(options.locale);
51
+ const readAsset = (relativePath) => fs.readFileSync(path.join(templateRoot, relativePath), "utf8");
52
+ const css = readAsset("assets/app.css");
53
+ const i18n = readAsset("assets/i18n.js");
54
+ const markdown = readAsset("assets/markdown-reader.js");
55
+ const mermaid = readAsset("assets/mermaid-renderer.js");
56
+ const app = readDashboardApp(templateRoot);
57
+ const title = options.locale === "zh-CN" ? "Harness 控制台" : "Harness Dashboard";
58
+ const payload = JSON.stringify(bundle).replace(/</g, "\\u003c");
59
+ const runtimeLocale = options.locale === "zh-CN" ? "zh" : "en";
60
+ return `<!doctype html>
61
+ <html lang="${options.locale === "zh-CN" ? "zh-CN" : "en"}">
62
+ <head>
63
+ <meta charset="utf-8">
64
+ <meta name="viewport" content="width=device-width, initial-scale=1">
65
+ <title>${title}</title>
66
+ <link rel="icon" href="data:,">
67
+ <style>${css}</style>
68
+ </head>
69
+ <body>
70
+ <div id="app" class="app-shell" aria-live="polite"></div>
71
+ <script>window.__HARNESS_DASHBOARD__ = ${payload};</script>
72
+ <script>window.__HARNESS_LOCALE__ = ${JSON.stringify(runtimeLocale)};</script>
73
+ <script>${i18n}</script>
74
+ <script>${markdown}</script>
75
+ <script>${mermaid}</script>
76
+ <script>${app}</script>
77
+ </body>
78
+ </html>`;
79
+ }
80
+ function renderDashboardIndex(locale = "en-US", options = {}) {
81
+ const normalizedLocale = locale === "zh-CN" ? "zh-CN" : "en";
82
+ const runtimeLocale = locale === "zh-CN" ? "zh" : "en";
83
+ const title = locale === "zh-CN" ? "Harness 控制台" : "Harness Dashboard";
84
+ const assetVersion = Date.now();
85
+ return `<!doctype html>
86
+ <html lang="${normalizedLocale}">
87
+ <head>
88
+ <meta charset="utf-8">
89
+ <meta name="viewport" content="width=device-width, initial-scale=1">
90
+ <title>${title}</title>
91
+ <link rel="icon" href="data:,">
92
+ <link rel="stylesheet" href="./assets/app.css?v=${assetVersion}">
93
+ </head>
94
+ <body>
95
+ <div id="app" class="app-shell" aria-live="polite"></div>
96
+ <script src="./assets/dashboard-data.js?v=${assetVersion}"></script>
97
+ <script>window.__HARNESS_LOCALE__ = ${JSON.stringify(runtimeLocale)};</script>
98
+ <script>window.__HARNESS_WORKBENCH__ = ${options.workbenchRuntime === true ? "true" : "false"};</script>
99
+ <script src="./assets/i18n.js?v=${assetVersion}"></script>
100
+ <script src="./assets/markdown-reader.js?v=${assetVersion}"></script>
101
+ <script src="./assets/mermaid-renderer.js?v=${assetVersion}"></script>
102
+ <script src="./assets/app.js?v=${assetVersion}"></script>
103
+ </body>
104
+ </html>`;
105
+ }
106
+ function readDashboardApp(templateRoot) {
107
+ const manifestPath = path.join(templateRoot, "assets/app.manifest.json");
108
+ if (!fs.existsSync(manifestPath))
109
+ return fs.readFileSync(path.join(templateRoot, "assets/app.js"), "utf8");
110
+ const manifest = readJsonSafe(manifestPath, null);
111
+ if (!Array.isArray(manifest) || manifest.length === 0)
112
+ throw new Error(`Invalid dashboard app manifest: ${manifestPath}`);
113
+ return `${manifest.map((relativePath) => {
114
+ const source = path.join(templateRoot, "assets", relativePath);
115
+ if (!fs.existsSync(source))
116
+ throw new Error(`Dashboard app source missing: ${relativePath}`);
117
+ return fs.readFileSync(source, "utf8").trimEnd();
118
+ }).join("\n\n")}\n`;
119
+ }
120
+ function assertSafeDashboardTarget(target, options) {
121
+ const localizedDashboardTemplateRoot = dashboardTemplateRootForLocale(options.locale);
122
+ const sourceRepoRoot = options.repoRoot ? path.resolve(options.repoRoot) : "";
123
+ const projectRoot = options.projectRoot ? path.resolve(options.projectRoot) : "";
124
+ if (sourceRepoRoot &&
125
+ projectRoot &&
126
+ isPathInside(projectRoot, sourceRepoRoot) &&
127
+ path.basename(projectRoot) === ".harness-private" &&
128
+ isPathInside(target, sourceRepoRoot) &&
129
+ !isPathInside(target, projectRoot)) {
130
+ throw new Error(`Refusing private dashboard output inside publishable source package: ${target}`);
131
+ }
132
+ const protectedRoots = [
133
+ path.parse(target).root,
134
+ process.env.HOME,
135
+ options.repoRoot,
136
+ options.projectRoot,
137
+ options.docsRoot,
138
+ dashboardTemplateRoot,
139
+ localizedDashboardTemplateRoot,
140
+ ].filter((item) => Boolean(item)).map((item) => path.resolve(item));
141
+ if (protectedRoots.includes(target)) {
142
+ throw new Error(`Refusing unsafe dashboard output directory: ${target}`);
143
+ }
144
+ for (const root of [options.docsRoot, dashboardTemplateRoot, localizedDashboardTemplateRoot].filter((item) => Boolean(item)).map((item) => path.resolve(item))) {
145
+ if (isPathInside(target, root)) {
146
+ throw new Error(`Refusing dashboard output inside protected directory: ${target}`);
147
+ }
148
+ }
149
+ if (fs.existsSync(target)) {
150
+ const entries = fs.readdirSync(target);
151
+ const hasMarker = fs.existsSync(path.join(target, dashboardMarker));
152
+ const canRecoverGeneratedDashboard = options.recoverGeneratedDashboard === true && looksLikeGeneratedDashboardDirectory(target);
153
+ if (entries.length > 0 && !hasMarker && !canRecoverGeneratedDashboard) {
154
+ throw new Error(`Refusing to overwrite non-dashboard directory: ${target}`);
155
+ }
156
+ }
157
+ }
158
+ function looksLikeGeneratedDashboardDirectory(target) {
159
+ const full = [
160
+ "index.html",
161
+ "README.md",
162
+ "assets/app.js",
163
+ "assets/dashboard-data.js",
164
+ "data/status.json",
165
+ ].every((relativePath) => fs.existsSync(path.join(target, relativePath)));
166
+ if (full)
167
+ return true;
168
+ const readmePath = path.join(target, "README.md");
169
+ if (!fs.existsSync(readmePath))
170
+ return false;
171
+ try {
172
+ const content = fs.readFileSync(readmePath, "utf8");
173
+ return content.includes("Harness Dashboard") && content.includes("harness dashboard");
174
+ }
175
+ catch {
176
+ return false;
177
+ }
178
+ }
179
+ function dashboardTemplateRootForLocale(locale = "en-US") {
180
+ return dashboardTemplateRoot;
181
+ }
182
+ function isPathInside(candidate, parent) {
183
+ const relative = path.relative(parent, candidate);
184
+ return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
185
+ }
186
+ function copyDirectory(from, to) {
187
+ if (!fs.existsSync(from))
188
+ throw new Error(`Missing dashboard template directory: ${from}`);
189
+ fs.mkdirSync(to, { recursive: true });
190
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
191
+ const source = path.join(from, entry.name);
192
+ const destination = path.join(to, entry.name);
193
+ if (entry.isDirectory()) {
194
+ copyDirectory(source, destination);
195
+ }
196
+ else {
197
+ fs.copyFileSync(source, destination);
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,45 @@
1
+ // @ts-nocheck
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { inspectGit } from "./governance-sync.mjs";
5
+ export function summarizeGitState(target) {
6
+ const state = inspectGit(target.projectRoot);
7
+ if (!state.inGit) {
8
+ return {
9
+ summary: {
10
+ inGit: false,
11
+ root: "",
12
+ dirty: false,
13
+ entries: [],
14
+ blocksCliAutoCommit: false,
15
+ },
16
+ warnings: [],
17
+ };
18
+ }
19
+ const targetRoot = real(target.projectRoot);
20
+ const gitRoot = real(state.gitRoot);
21
+ const rootMatches = gitRoot === targetRoot;
22
+ const entries = rootMatches ? state.entries.map((entry) => ({
23
+ path: entry.path,
24
+ index: entry.index,
25
+ worktree: entry.worktree,
26
+ })) : [];
27
+ const dirty = entries.length > 0;
28
+ const warnings = [];
29
+ if (dirty) {
30
+ warnings.push(`dirty-state: ${entries.length} uncommitted Git path(s) may block CLI-owned auto-commit when they overlap a command write scope or are staged; commit them or record owner/no-commit reason before lifecycle commands.`);
31
+ }
32
+ return {
33
+ summary: {
34
+ inGit: true,
35
+ root: rootMatches ? "TARGET:." : "outside-target",
36
+ dirty,
37
+ entries,
38
+ blocksCliAutoCommit: !rootMatches || dirty,
39
+ },
40
+ warnings,
41
+ };
42
+ }
43
+ function real(filePath) {
44
+ return fs.realpathSync(filePath);
45
+ }