coding-agent-harness 1.0.1 → 1.0.4

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 (262) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/README.en-US.md +14 -0
  4. package/README.md +230 -80
  5. package/README.zh-CN.md +290 -0
  6. package/SKILL.md +132 -198
  7. package/docs-release/README.md +80 -9
  8. package/docs-release/architecture/overview.md +298 -28
  9. package/docs-release/architecture/overview.zh-CN.md +292 -0
  10. package/docs-release/assets/dashboard-overview.png +0 -0
  11. package/docs-release/assets/harness-architecture.svg +163 -0
  12. package/docs-release/assets/harness-workflow.svg +64 -0
  13. package/docs-release/guides/agent-installation.en-US.md +237 -0
  14. package/docs-release/guides/agent-installation.md +149 -27
  15. package/docs-release/guides/contributing.md +100 -0
  16. package/docs-release/guides/contributing.zh-CN.md +99 -0
  17. package/docs-release/guides/document-audience-and-surfaces.en-US.md +113 -0
  18. package/docs-release/guides/document-audience-and-surfaces.md +113 -0
  19. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +334 -0
  20. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +334 -0
  21. package/docs-release/guides/legacy-migration-agent-prompt.md +373 -0
  22. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +350 -0
  23. package/docs-release/guides/migration-playbook.en-US.md +324 -0
  24. package/docs-release/guides/migration-playbook.md +328 -0
  25. package/docs-release/guides/parent-control-repository-pattern.en-US.md +254 -0
  26. package/docs-release/guides/parent-control-repository-pattern.md +254 -0
  27. package/docs-release/guides/preset-development.md +214 -0
  28. package/docs-release/guides/repository-operating-models.en-US.md +197 -0
  29. package/docs-release/guides/repository-operating-models.md +197 -0
  30. package/docs-release/guides/task-state-machine.en-US.md +207 -0
  31. package/docs-release/guides/task-state-machine.md +214 -0
  32. package/docs-release/intl/README.md +15 -0
  33. package/docs-release/intl/de-DE.md +18 -0
  34. package/docs-release/intl/en-US.md +18 -0
  35. package/docs-release/intl/es-ES.md +18 -0
  36. package/docs-release/intl/fr-FR.md +18 -0
  37. package/docs-release/intl/ja-JP.md +18 -0
  38. package/docs-release/intl/ko-KR.md +18 -0
  39. package/docs-release/intl/zh-CN.md +18 -0
  40. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/brief.md +13 -0
  41. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  42. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/lesson_candidates.md +24 -0
  43. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +1 -1
  44. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +4 -2
  45. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/{visual_roadmap.md → visual_map.md} +9 -1
  46. package/package.json +10 -3
  47. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  48. package/presets/legacy-migration/preset.yaml +134 -0
  49. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  50. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  51. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  52. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  53. package/presets/legacy-migration/templates/review.seed.md +12 -0
  54. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  55. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  56. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  57. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  58. package/presets/lesson-sedimentation/preset.yaml +23 -0
  59. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  60. package/presets/module/preset.yaml +25 -0
  61. package/presets/module/templates/execution_strategy.append.md +8 -0
  62. package/presets/module/templates/task_plan.append.md +17 -0
  63. package/presets/standard-task/preset.yaml +31 -0
  64. package/presets/standard-task/templates/task_plan.append.md +7 -0
  65. package/references/adversarial-review-standard.md +2 -2
  66. package/references/agents-md-pattern.md +5 -5
  67. package/references/delivery-operating-model-standard.md +3 -3
  68. package/references/docs-directory-standard.md +53 -10
  69. package/references/external-source-intake-standard.md +75 -0
  70. package/references/harness-ledger.md +53 -94
  71. package/references/legacy-12-phase-bootstrap.md +41 -0
  72. package/references/lessons-governance.md +100 -88
  73. package/references/module-parallel-standard.md +14 -14
  74. package/references/planning-loop.md +51 -7
  75. package/references/project-onboarding-audit.md +10 -0
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +12 -1
  78. package/references/review-routing-standard.md +7 -1
  79. package/references/ssot-governance.md +67 -59
  80. package/references/taskr-gap-analysis.md +600 -0
  81. package/references/testing-standard.md +50 -0
  82. package/references/walkthrough-closeout.md +10 -9
  83. package/scripts/check-harness.mjs +111 -331
  84. package/scripts/commands/dashboard-command.mjs +67 -0
  85. package/scripts/commands/migration-command.mjs +96 -0
  86. package/scripts/commands/preset-command.mjs +73 -0
  87. package/scripts/commands/task-command.mjs +327 -0
  88. package/scripts/harness.mjs +106 -20
  89. package/scripts/lib/capability-registry.mjs +591 -0
  90. package/scripts/lib/check-module-parallel.mjs +237 -0
  91. package/scripts/lib/check-profiles.mjs +418 -0
  92. package/scripts/lib/check-task-contracts.mjs +47 -0
  93. package/scripts/lib/core-shared.mjs +196 -0
  94. package/scripts/lib/dashboard-data.mjs +412 -0
  95. package/scripts/lib/dashboard-workbench.mjs +257 -0
  96. package/scripts/lib/dashboard-writer.mjs +107 -4
  97. package/scripts/lib/git-status-summary.mjs +46 -0
  98. package/scripts/lib/governance-index-generator.mjs +174 -0
  99. package/scripts/lib/governance-sync.mjs +514 -0
  100. package/scripts/lib/governance-table-boundary.mjs +175 -0
  101. package/scripts/lib/harness-core.mjs +15 -1318
  102. package/scripts/lib/lesson-maintenance.mjs +152 -0
  103. package/scripts/lib/markdown-utils.mjs +158 -0
  104. package/scripts/lib/migration-planner.mjs +478 -0
  105. package/scripts/lib/migration-support.mjs +312 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +497 -0
  108. package/scripts/lib/preset-registry.mjs +627 -0
  109. package/scripts/lib/preset-resource-contracts.mjs +83 -0
  110. package/scripts/lib/review-confirm-git-gate.mjs +248 -0
  111. package/scripts/lib/status-dashboard-renderer.mjs +102 -0
  112. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  113. package/scripts/lib/task-completion-consistency.mjs +16 -0
  114. package/scripts/lib/task-index.mjs +93 -0
  115. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  116. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  117. package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
  118. package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
  119. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  120. package/scripts/lib/task-lifecycle.mjs +649 -0
  121. package/scripts/lib/task-review-model.mjs +469 -0
  122. package/scripts/lib/task-scanner.mjs +576 -0
  123. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  124. package/scripts/postinstall.mjs +14 -0
  125. package/skills/preset-creator/SKILL.md +179 -0
  126. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  127. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  128. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
  129. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  130. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  131. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  132. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  133. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  134. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  137. package/{templates/planning/visual_roadmap.md → skills/preset-creator/references/complex-task-skeleton/visual_map.md} +24 -2
  138. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  139. package/templates/AGENTS.md.template +51 -36
  140. package/templates/architecture/Architecture-SSoT.md +21 -0
  141. package/templates/architecture/README.md +49 -0
  142. package/templates/architecture/critical-flows.md +22 -0
  143. package/templates/architecture/local-repo-context.md +20 -0
  144. package/templates/architecture/service-catalog.md +17 -0
  145. package/templates/architecture/services/service-template.md +31 -0
  146. package/templates/architecture/system-map.md +22 -0
  147. package/templates/dashboard/assets/app-src/00-state.js +42 -0
  148. package/templates/dashboard/assets/app-src/10-router.js +77 -0
  149. package/templates/dashboard/assets/app-src/20-overview.js +241 -0
  150. package/templates/dashboard/assets/app-src/30-tasks.js +409 -0
  151. package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
  152. package/templates/dashboard/assets/app-src/40-modules.js +58 -0
  153. package/templates/dashboard/assets/app-src/45-review.js +347 -0
  154. package/templates/dashboard/assets/app-src/50-migration.js +183 -0
  155. package/templates/dashboard/assets/app-src/60-shared.js +61 -0
  156. package/templates/dashboard/assets/app-src/90-bindings.js +524 -0
  157. package/templates/dashboard/assets/app.css +3107 -300
  158. package/templates/dashboard/assets/app.css.manifest.json +9 -0
  159. package/templates/dashboard/assets/app.js +2068 -306
  160. package/templates/dashboard/assets/app.manifest.json +12 -0
  161. package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
  162. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  163. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  164. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  165. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  166. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
  167. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  168. package/templates/dashboard/assets/i18n.js +531 -44
  169. package/templates/dashboard/assets/mermaid-renderer.js +58 -8
  170. package/templates/development/README.md +52 -0
  171. package/templates/development/codebase-map.md +11 -0
  172. package/templates/development/cross-repo-debugging.md +18 -0
  173. package/templates/development/external-context/service-template.md +33 -0
  174. package/templates/development/external-source-packs/README.md +24 -0
  175. package/templates/development/external-source-packs/digest-template.md +28 -0
  176. package/templates/development/local-setup.md +16 -0
  177. package/templates/development/stubs-and-mocks.md +11 -0
  178. package/templates/integrations/README.md +40 -0
  179. package/templates/integrations/api-contract.md +42 -0
  180. package/templates/integrations/event-contract.md +46 -0
  181. package/templates/integrations/third-party/vendor-template.md +42 -0
  182. package/templates/integrations/webhook-contract.md +41 -0
  183. package/templates/ledger/Harness-Ledger.md +13 -25
  184. package/templates/lessons/lesson-arch-process-change.md +1 -1
  185. package/templates/lessons/lesson-new-doc.md +1 -1
  186. package/templates/lessons/lesson-ref-change.md +1 -1
  187. package/templates/planning/brief.md +32 -0
  188. package/templates/planning/execution_strategy.md +31 -0
  189. package/templates/planning/lesson_candidates.md +70 -0
  190. package/templates/planning/long-running-task-contract.md +7 -0
  191. package/templates/planning/module_brief.md +25 -0
  192. package/templates/planning/module_session_prompt.md +6 -0
  193. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  194. package/templates/planning/optional/references/INDEX.md +3 -3
  195. package/templates/planning/review.md +59 -0
  196. package/templates/planning/task_plan.md +40 -15
  197. package/templates/planning/visual_map.md +50 -0
  198. package/templates/reference/docs-library-standard.md +31 -0
  199. package/templates/reference/execution-workflow-standard.md +5 -2
  200. package/templates/reference/external-source-intake-standard.md +82 -0
  201. package/templates/reference/harness-ledger-standard.md +1 -0
  202. package/templates/reference/pull-request-standard.md +80 -0
  203. package/templates/reference/repo-governance-standard.md +8 -5
  204. package/templates/reference/review-routing-standard.md +6 -0
  205. package/templates/reference/walkthrough-standard.md +3 -1
  206. package/templates/verifier/verifier-output.md +1 -1
  207. package/templates/walkthrough/walkthrough-template.md +2 -2
  208. package/templates-zh-CN/AGENTS.md.template +73 -70
  209. package/templates-zh-CN/architecture/Architecture-SSoT.md +21 -0
  210. package/templates-zh-CN/architecture/README.md +51 -0
  211. package/templates-zh-CN/architecture/critical-flows.md +24 -0
  212. package/templates-zh-CN/architecture/local-repo-context.md +20 -0
  213. package/templates-zh-CN/architecture/service-catalog.md +17 -0
  214. package/templates-zh-CN/architecture/services/service-template.md +31 -0
  215. package/templates-zh-CN/architecture/system-map.md +22 -0
  216. package/templates-zh-CN/development/README.md +54 -0
  217. package/templates-zh-CN/development/codebase-map.md +11 -0
  218. package/templates-zh-CN/development/cross-repo-debugging.md +18 -0
  219. package/templates-zh-CN/development/external-context/service-template.md +33 -0
  220. package/templates-zh-CN/development/external-source-packs/README.md +24 -0
  221. package/templates-zh-CN/development/external-source-packs/digest-template.md +28 -0
  222. package/templates-zh-CN/development/local-setup.md +16 -0
  223. package/templates-zh-CN/development/stubs-and-mocks.md +11 -0
  224. package/templates-zh-CN/integrations/README.md +42 -0
  225. package/templates-zh-CN/integrations/api-contract.md +42 -0
  226. package/templates-zh-CN/integrations/event-contract.md +46 -0
  227. package/templates-zh-CN/integrations/third-party/vendor-template.md +42 -0
  228. package/templates-zh-CN/integrations/webhook-contract.md +41 -0
  229. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  230. package/templates-zh-CN/planning/brief.md +32 -0
  231. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  232. package/templates-zh-CN/planning/lesson_candidates.md +70 -0
  233. package/templates-zh-CN/planning/long-running-task-contract.md +1 -1
  234. package/templates-zh-CN/planning/module_brief.md +25 -0
  235. package/templates-zh-CN/planning/module_plan.md +2 -2
  236. package/templates-zh-CN/planning/module_session_prompt.md +4 -3
  237. package/templates-zh-CN/planning/review.md +59 -1
  238. package/templates-zh-CN/planning/task_plan.md +37 -11
  239. package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
  240. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  241. package/templates-zh-CN/reference/docs-library-standard.md +36 -1
  242. package/templates-zh-CN/reference/execution-workflow-standard.md +10 -2
  243. package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
  244. package/templates-zh-CN/reference/harness-ledger-standard.md +7 -4
  245. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  246. package/templates-zh-CN/reference/repo-governance-standard.md +4 -1
  247. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  248. package/templates-zh-CN/reference/walkthrough-standard.md +6 -5
  249. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
  250. package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
  251. package/scripts/smoke-dashboard.mjs +0 -70
  252. package/scripts/test-harness.mjs +0 -483
  253. package/templates/ssot/Feature-SSoT.md +0 -43
  254. package/templates/ssot/Lessons-SSoT.md +0 -44
  255. package/templates-zh-CN/dashboard/assets/app.css +0 -399
  256. package/templates-zh-CN/dashboard/assets/app.js +0 -435
  257. package/templates-zh-CN/dashboard/assets/i18n.js +0 -47
  258. package/templates-zh-CN/dashboard/assets/markdown-reader.js +0 -116
  259. package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +0 -59
  260. package/templates-zh-CN/dashboard/index.html +0 -18
  261. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  262. package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
@@ -0,0 +1,524 @@
1
+ window.setModulePage = function(moduleKey, page) {
2
+ state.modulePages = state.modulePages || {};
3
+ state.modulePages[moduleKey] = page;
4
+ app();
5
+ };
6
+
7
+ function bind() {
8
+ document.querySelectorAll("[data-search]").forEach((input) => input.addEventListener("input", () => {
9
+ state.query = input.value;
10
+ state.taskPageByGroup = {};
11
+ state.taskGroupPage = 1;
12
+ app();
13
+ }));
14
+ document.querySelectorAll("[data-state-filter]").forEach((select) => select.addEventListener("change", () => {
15
+ state.taskState = select.value;
16
+ state.taskPageByGroup = {};
17
+ state.taskGroupPage = 1;
18
+ app();
19
+ }));
20
+ document.querySelectorAll("[data-group-mode]").forEach((select) => select.addEventListener("change", () => {
21
+ state.taskGroupMode = select.value;
22
+ state.taskPageByGroup = {};
23
+ state.taskGroupPage = 1;
24
+ app();
25
+ }));
26
+ document.querySelectorAll("[data-layout]").forEach((btn) => btn.addEventListener("click", () => {
27
+ state.taskLayout = btn.dataset.layout;
28
+ localStorage.setItem("harness.taskLayout", state.taskLayout);
29
+ app();
30
+ }));
31
+ document.querySelectorAll("[data-task-sort-order]").forEach((btn) => btn.addEventListener("click", () => {
32
+ state.taskSortOrder = btn.dataset.taskSortOrder === "asc" ? "asc" : "desc";
33
+ localStorage.setItem("harness.taskSortOrder", state.taskSortOrder);
34
+ state.taskPageByGroup = {};
35
+ state.taskGroupPage = 1;
36
+ app();
37
+ }));
38
+ document.querySelectorAll("[data-render-toggle]").forEach((button) => button.addEventListener("click", () => {
39
+ state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
40
+ app();
41
+ }));
42
+ document.querySelectorAll("[data-warning-filter]").forEach((button) => button.addEventListener("click", () => {
43
+ state.warningFilter = button.dataset.warningFilter || "all";
44
+ state.warningPage = 1;
45
+ app();
46
+ }));
47
+ document.querySelectorAll("[data-warning-filter-select]").forEach((select) => select.addEventListener("change", () => {
48
+ state.warningFilter = select.value;
49
+ state.warningPage = 1;
50
+ app();
51
+ }));
52
+ document.querySelectorAll("[data-review-queue-tab]").forEach((button) => button.addEventListener("click", () => {
53
+ state.reviewQueueTab = button.dataset.reviewQueueTab || "review";
54
+ state.reviewQueuePage = 1;
55
+ app();
56
+ }));
57
+ document.querySelectorAll("[data-review-reason-filter]").forEach((select) => select.addEventListener("change", () => {
58
+ state.reviewReasonFilter = select.value || "all";
59
+ state.reviewQueuePage = 1;
60
+ app();
61
+ }));
62
+ document.querySelectorAll("[data-review-sort]").forEach((select) => select.addEventListener("change", () => {
63
+ state.reviewSort = select.value || "queue";
64
+ state.reviewQueuePage = 1;
65
+ app();
66
+ }));
67
+ document.querySelectorAll("[data-page-kind]").forEach((button) => button.addEventListener("click", () => {
68
+ const page = Math.max(1, Number(button.dataset.page) || 1);
69
+ if (button.dataset.pageKind === "warning") state.warningPage = page;
70
+ if (button.dataset.pageKind === "task-groups") state.taskGroupPage = page;
71
+ if (button.dataset.pageKind === "task") state.taskPageByGroup[button.dataset.pageGroup || ""] = page;
72
+ if (button.dataset.pageKind === "review") state.reviewQueuePage = page;
73
+ app();
74
+ }));
75
+ document.querySelectorAll("[data-runway-phase]").forEach((link) => link.addEventListener("click", () => {
76
+ const phase = link.dataset.runwayPhase || "all";
77
+ if (phase === "module-classification") state.taskGroupMode = "module";
78
+ if (["triage", "active-task-contracts", "strict-cutover"].includes(phase)) state.warningFilter = phase === "triage" ? "all" : phase;
79
+ state.warningPage = 1;
80
+ state.taskGroupPage = 1;
81
+ if (link.getAttribute("href") === "#/") app();
82
+ }));
83
+ document.querySelectorAll("[data-theme-toggle]").forEach((button) => button.addEventListener("click", () => {
84
+ state.theme = state.theme === "dark" ? "light" : state.theme === "light" ? "system" : "dark";
85
+ localStorage.setItem("harness.theme", state.theme);
86
+ app();
87
+ }));
88
+ document.querySelectorAll("[data-language-toggle]").forEach((button) => button.addEventListener("click", () => {
89
+ setLocale(locale === "zh" ? "en" : "zh");
90
+ app();
91
+ }));
92
+ document.querySelectorAll("[data-open-drawer]").forEach((el) => el.addEventListener("click", (e) => {
93
+ e.preventDefault();
94
+ const taskId = el.dataset.openDrawer;
95
+ openDrawer(taskId);
96
+ }));
97
+ bindCopyTaskNameButtons(document);
98
+ bindRepairPromptButtons(document);
99
+ bindLessonSedimentationButtons(document);
100
+ document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
101
+ e.preventDefault();
102
+ const lessonId = el.dataset.openLessonDrawer;
103
+ openLessonDrawer(lessonId);
104
+ }));
105
+ document.querySelectorAll("[data-review-complete]").forEach((button) => button.addEventListener("click", () => completeReviewFromDashboard(button.dataset.reviewComplete)));
106
+ const overlay = document.getElementById("drawer-overlay");
107
+ if (overlay) overlay.addEventListener("click", closeDrawer);
108
+ }
109
+
110
+ async function loadRuntime() {
111
+ if (state.runtimeLoaded || window.__HARNESS_WORKBENCH__ !== true || !/^https?:$/.test(window.location.protocol)) return;
112
+ state.runtimeLoaded = true;
113
+ try {
114
+ const response = await fetch("/api/runtime", { cache: "no-store" });
115
+ if (!response.ok) return;
116
+ state.runtime = await response.json();
117
+ startRuntimePolling();
118
+ app();
119
+ } catch {
120
+ state.runtime = { mode: "static", csrfToken: "", writableActions: [] };
121
+ }
122
+ }
123
+
124
+ function startRuntimePolling() {
125
+ if (!state.runtime?.autoRefresh || state.runtimePoller) return;
126
+ state.runtimePoller = setInterval(async () => {
127
+ try {
128
+ const response = await fetch("/api/runtime", { cache: "no-store" });
129
+ if (!response.ok) return;
130
+ const nextRuntime = await response.json();
131
+ if (state.runtime?.snapshotVersion && nextRuntime.snapshotVersion !== state.runtime.snapshotVersion) {
132
+ window.location.reload();
133
+ return;
134
+ }
135
+ state.runtime = nextRuntime;
136
+ } catch {
137
+ clearInterval(state.runtimePoller);
138
+ state.runtimePoller = null;
139
+ }
140
+ }, 1500);
141
+ }
142
+
143
+ async function completeReviewFromDashboard(taskId) {
144
+ const result = document.querySelector(`[data-review-result="${CSS.escape(taskId)}"]`);
145
+ const checkbox = document.querySelector(`[data-review-confirm-check="${CSS.escape(taskId)}"]`);
146
+ const confirmInput = document.querySelector(`[data-review-confirm-text="${CSS.escape(taskId)}"]`);
147
+ const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
148
+ if (!checkbox?.checked) {
149
+ if (result) result.textContent = t("reviewChecklistRequired");
150
+ return;
151
+ }
152
+ if (!confirmInput?.value || ![task?.shortId, task?.id].includes(confirmInput.value.trim())) {
153
+ if (result) result.textContent = t("reviewConfirmTextMismatch");
154
+ return;
155
+ }
156
+ if (result) result.textContent = t("reviewSubmitting");
157
+ try {
158
+ const response = await fetch("/api/tasks/review-complete", {
159
+ method: "POST",
160
+ headers: {
161
+ "content-type": "application/json",
162
+ "x-harness-csrf": state.runtime?.csrfToken || "",
163
+ },
164
+ body: JSON.stringify({
165
+ taskId,
166
+ confirmText: confirmInput.value.trim(),
167
+ reviewer: "Human Reviewer",
168
+ message: "confirmed from dashboard workbench",
169
+ }),
170
+ });
171
+ const payload = await response.json();
172
+ if (!response.ok) throw new Error(payload.error || t("reviewCompleteFailed"));
173
+ if (result) result.textContent = t("reviewCompleteSuccess");
174
+ setTimeout(() => window.location.reload(), 500);
175
+ } catch (error) {
176
+ if (result) result.textContent = `${t("reviewCompleteFailed")}: ${error.message}`;
177
+ }
178
+ }
179
+
180
+ function renderDrawerContent(taskId) {
181
+ const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
182
+ if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
183
+
184
+ const header = `
185
+ <div class="task-drawer-header">
186
+ <div>
187
+ <h2>${escapeHtml(task.title)}</h2>
188
+ <p style="font-family: var(--font-mono); font-size: 11px; margin: 4px 0 0; color: var(--muted);">${escapeHtml(task.id)}</p>
189
+ ${taskCopyButton(task, "detail-copy")}
190
+ </div>
191
+ <button class="btn-close" data-close-drawer>×</button>
192
+ </div>
193
+ `;
194
+
195
+ const timeline = phaseTimeline(task);
196
+ const documents = taskDocumentLibrary(task, "");
197
+ const findings = openFindings(task);
198
+ const evidence = evidenceList(task);
199
+
200
+ const body = `
201
+ <div class="task-drawer-body stack">
202
+ <div class="drawer-task-summary">
203
+ <div>
204
+ <span>${t("statOverall")}</span>
205
+ <strong>${task.completion}%</strong>
206
+ </div>
207
+ <a href="#/tasks/${encodeURIComponent(task.id)}" class="btn-drawer-trigger">${t("fullView")}</a>
208
+ </div>
209
+ ${taskStateSummary(task)}
210
+ ${reviewActionPanel(task, { mode: "summary" })}
211
+ ${lessonCandidatePanel(task, { context: "drawer" })}
212
+ ${timeline}
213
+ ${documents}
214
+ ${findings}
215
+ ${evidence}
216
+ </div>
217
+ `;
218
+
219
+ return header + body;
220
+ }
221
+
222
+ function openDrawer(taskId) {
223
+ const drawer = document.getElementById("task-drawer");
224
+ const overlay = document.getElementById("drawer-overlay");
225
+ if (!drawer || !overlay) return;
226
+ drawer.innerHTML = renderDrawerContent(taskId);
227
+ drawer.classList.add("active");
228
+ overlay.classList.add("active");
229
+
230
+ drawer.querySelector("[data-close-drawer]").addEventListener("click", closeDrawer);
231
+ drawer.querySelectorAll("[data-render-toggle]").forEach((button) => button.addEventListener("click", () => {
232
+ state.renderMode = state.renderMode === "rendered" ? "source" : "rendered";
233
+ openDrawer(taskId);
234
+ }));
235
+ bindCopyTaskNameButtons(drawer);
236
+ bindRepairPromptButtons(drawer);
237
+ bindLessonSedimentationButtons(drawer);
238
+ drawer.querySelectorAll("[data-review-complete]").forEach((button) => button.addEventListener("click", () => completeReviewFromDashboard(button.dataset.reviewComplete)));
239
+ }
240
+
241
+ function bindCopyTaskNameButtons(root) {
242
+ root.querySelectorAll("[data-copy-task-name]").forEach((button) => button.addEventListener("click", async (event) => {
243
+ event.preventDefault();
244
+ event.stopPropagation();
245
+ const taskName = button.dataset.copyTaskName || "";
246
+ const defaultText = t("copyTaskNameShort");
247
+ try {
248
+ await copyText(taskName);
249
+ button.textContent = t("copyTaskNameSuccess");
250
+ } catch {
251
+ button.textContent = t("copyTaskNameFailed");
252
+ }
253
+ window.setTimeout(() => {
254
+ button.textContent = defaultText;
255
+ }, 1400);
256
+ }));
257
+ }
258
+
259
+ function bindRepairPromptButtons(root) {
260
+ root.querySelectorAll("[data-copy-repair-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
261
+ event.preventDefault();
262
+ event.stopPropagation();
263
+ const prompt = button.dataset.repairPrompt || "";
264
+ const defaultText = t("copyRepairPrompt");
265
+ try {
266
+ await copyText(prompt);
267
+ button.textContent = t("copyRepairPromptSuccess");
268
+ } catch {
269
+ button.textContent = t("copyTaskNameFailed");
270
+ }
271
+ window.setTimeout(() => {
272
+ button.textContent = defaultText;
273
+ }, 1400);
274
+ }));
275
+ }
276
+
277
+ function bindLessonSedimentationButtons(root) {
278
+ root.querySelectorAll("[data-copy-lesson-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
279
+ event.preventDefault();
280
+ event.stopPropagation();
281
+ const prompt = button.dataset.lessonPrompt || "";
282
+ const defaultText = t("copyLessonPrompt");
283
+ try {
284
+ await copyText(prompt);
285
+ button.textContent = t("copyRepairPromptSuccess");
286
+ } catch {
287
+ button.textContent = t("copyTaskNameFailed");
288
+ }
289
+ window.setTimeout(() => {
290
+ button.textContent = defaultText;
291
+ }, 1400);
292
+ }));
293
+ root.querySelectorAll("[data-create-lesson-sedimentation]").forEach((button) => button.addEventListener("click", async (event) => {
294
+ event.preventDefault();
295
+ event.stopPropagation();
296
+ await createLessonSedimentationFromDashboard(button);
297
+ }));
298
+ }
299
+
300
+ async function createLessonSedimentationFromDashboard(button) {
301
+ const taskId = button.dataset.createLessonSedimentation || "";
302
+ const candidateId = button.dataset.candidateId || "";
303
+ const result = document.querySelector(`[data-lesson-result="${CSS.escape(`${taskId}:${candidateId}`)}"]`);
304
+ if (result) result.textContent = t("lessonTaskCreating");
305
+ button.disabled = true;
306
+ try {
307
+ const response = await fetch("/api/tasks/lesson-sedimentation", {
308
+ method: "POST",
309
+ headers: {
310
+ "content-type": "application/json",
311
+ "x-harness-csrf": state.runtime?.csrfToken || "",
312
+ },
313
+ body: JSON.stringify({ taskId, candidateId }),
314
+ });
315
+ const payload = await response.json();
316
+ if (!response.ok) throw payload;
317
+ if (result) {
318
+ result.innerHTML = lessonSedimentationSuccess(payload);
319
+ bindLessonSedimentationButtons(result);
320
+ result.scrollIntoView({ block: "center", inline: "nearest" });
321
+ }
322
+ } catch (error) {
323
+ button.disabled = false;
324
+ if (result) result.innerHTML = lessonSedimentationFailure(error);
325
+ }
326
+ }
327
+
328
+ function lessonSedimentationSuccess(payload) {
329
+ const followUp = payload?.followUpTask || {};
330
+ const prompt = payload?.prompt || "";
331
+ const taskId = followUp.id || "";
332
+ const openHref = taskId ? `#/tasks/${encodeURIComponent(taskId)}` : "#/review";
333
+ return `<div class="workbench-action-result success">
334
+ <strong>${escapeHtml(t("lessonTaskCreated"))}</strong>
335
+ ${taskId ? `<a href="${openHref}">${escapeHtml(t("openFollowUpTask"))}</a>` : ""}
336
+ ${prompt ? `<button data-copy-lesson-prompt="${escapeAttr(taskId || "follow-up")}" data-lesson-prompt="${escapeAttr(prompt)}">${escapeHtml(t("copyLessonPrompt"))}</button>` : ""}
337
+ </div>`;
338
+ }
339
+
340
+ function lessonSedimentationFailure(error) {
341
+ const message = error?.error || error?.message || t("lessonTaskCreateFailed");
342
+ const recovery = Array.isArray(error?.recovery) ? error.recovery : [];
343
+ const details = error?.details || {};
344
+ const existingTask = details.followUpTask || details.existingTask || "";
345
+ return `<div class="workbench-action-result failed">
346
+ <strong>${escapeHtml(t("lessonTaskCreateFailed"))}</strong>
347
+ <span>${escapeHtml(message)}</span>
348
+ ${existingTask ? `<a href="#/tasks/${encodeURIComponent(existingTask)}">${escapeHtml(t("openFollowUpTask"))}</a>` : ""}
349
+ ${recovery.length ? `<ul>${recovery.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>` : ""}
350
+ </div>`;
351
+ }
352
+
353
+ async function copyText(text) {
354
+ if (navigator.clipboard?.writeText) {
355
+ await navigator.clipboard.writeText(text);
356
+ return;
357
+ }
358
+ const textarea = document.createElement("textarea");
359
+ textarea.value = text;
360
+ textarea.setAttribute("readonly", "");
361
+ textarea.style.position = "fixed";
362
+ textarea.style.left = "-9999px";
363
+ document.body.appendChild(textarea);
364
+ textarea.select();
365
+ const copied = document.execCommand("copy");
366
+ textarea.remove();
367
+ if (!copied) throw new Error("copy failed");
368
+ }
369
+
370
+ function renderLessonDrawerContent(lessonId) {
371
+ const lesson = lessonDocuments().find((item) => item.id === lessonId);
372
+
373
+ if (!lesson) {
374
+ return `<div class="task-drawer-header">
375
+ <h2>${escapeHtml(lessonId)}</h2>
376
+ <button class="btn-close" data-close-drawer>×</button>
377
+ </div>
378
+ <div class="task-drawer-body">
379
+ <div class="empty">${t("lessonNotFound")}</div>
380
+ </div>`;
381
+ }
382
+
383
+ const doc = lesson.doc || findDocument(lesson.path);
384
+
385
+ const header = `
386
+ <div class="task-drawer-header">
387
+ <div>
388
+ <h2>${escapeHtml(lessonId)}</h2>
389
+ <p style="font-size: 12px; margin: 4px 0 0; color: var(--muted); font-weight: 600;">${escapeHtml(lesson.title || lesson.path)}</p>
390
+ </div>
391
+ <button class="btn-close" data-close-drawer>×</button>
392
+ </div>
393
+ `;
394
+
395
+ let markdownBody = "";
396
+ if (doc && doc.content) {
397
+ markdownBody = `<div class="markdown">${window.HarnessMarkdown.render(doc.content, "rendered")}</div>`;
398
+ } else {
399
+ markdownBody = `
400
+ <div style="margin-bottom: 20px; background: var(--paper-2); padding: 16px; border-radius: 8px; border: 1px dashed var(--line);">
401
+ <p style="margin: 0; font-size: 13px; color: var(--muted);">${t("lessonDocMissing")}</p>
402
+ </div>
403
+ `;
404
+ }
405
+
406
+ const body = `
407
+ <div class="task-drawer-body stack">
408
+ ${markdownBody}
409
+ </div>
410
+ `;
411
+
412
+ return header + body;
413
+ }
414
+
415
+ function openLessonDrawer(lessonId) {
416
+ const drawer = document.getElementById("task-drawer");
417
+ const overlay = document.getElementById("drawer-overlay");
418
+ if (!drawer || !overlay) return;
419
+ drawer.innerHTML = renderLessonDrawerContent(lessonId);
420
+ drawer.classList.add("active");
421
+ overlay.classList.add("active");
422
+
423
+ drawer.querySelector("[data-close-drawer]").addEventListener("click", closeDrawer);
424
+ }
425
+
426
+ function closeDrawer() {
427
+ const drawer = document.getElementById("task-drawer");
428
+ const overlay = document.getElementById("drawer-overlay");
429
+ if (drawer) drawer.classList.remove("active");
430
+ if (overlay) overlay.classList.remove("active");
431
+ }
432
+
433
+ function ledgerPanel() {
434
+ const ledgerTable = (bundle.tables?.tables || []).find((table) => table.kind === "harness-ledger");
435
+ const rows = ledgerTable?.rows || [];
436
+
437
+ let closedCount = 0;
438
+ let openCount = 0;
439
+ let blockedCount = 0;
440
+
441
+ let lessonsReviewed = 0;
442
+ let lessonsTotal = 0;
443
+
444
+ let evidenceAudited = 0;
445
+ let evidenceTotal = 0;
446
+
447
+ for (const row of rows) {
448
+ const cells = row.cells || {};
449
+ const status = String(cells.Status || cells["\u72b6\u6001"] || "").toLowerCase();
450
+ if (status.includes("close") || status.includes("done") || status.includes("\u7ed3") || status.includes("\u5b8c")) {
451
+ closedCount++;
452
+ } else if (status.includes("block") || status.includes("\u963b")) {
453
+ blockedCount++;
454
+ } else {
455
+ openCount++;
456
+ }
457
+
458
+ const lesson = String(cells.Lessons || cells["\u7ecf\u9a8c"] || cells["\u7ecf\u9a8c\u5ba1\u67e5"] || cells["Lesson"] || "");
459
+ if (lesson) {
460
+ lessonsTotal++;
461
+ if (lesson.toLowerCase().includes("pass") || lesson.includes("\u901a\u8fc7") || lesson.includes("\u5c31\u7eea") || lesson.toLowerCase().includes("checked") || lesson.toLowerCase().includes("done")) {
462
+ lessonsReviewed++;
463
+ }
464
+ }
465
+
466
+ const evidence = String(cells.Evidence || cells["\u8bc1\u636e"] || cells["\u9a8c\u8bc1\u8bc1\u636e"] || cells["Evidence Checked"] || "");
467
+ if (evidence) {
468
+ evidenceTotal++;
469
+ if (evidence.toLowerCase().includes("pass") || evidence.includes("\u901a\u8fc7") || evidence.toLowerCase().includes("present") || evidence.toLowerCase().includes("verified") || evidence.toLowerCase().includes("done")) {
470
+ evidenceAudited++;
471
+ }
472
+ }
473
+ }
474
+
475
+ const total = closedCount + openCount + blockedCount || 1;
476
+ const closedPct = Math.round((closedCount / total) * 100);
477
+ const openPct = Math.round((openCount / total) * 100);
478
+ const blockedPct = total - closedPct - openPct;
479
+
480
+ const lessonsPct = lessonsTotal ? Math.round((lessonsReviewed / lessonsTotal) * 100) : 0;
481
+ const evidencePct = evidenceTotal ? Math.round((evidenceAudited / evidenceTotal) * 100) : 0;
482
+
483
+ if (rows.length === 0) return "";
484
+
485
+ return `<section class="ledger-panel">
486
+ <h2>${t("ssotLedger")}</h2>
487
+ <div class="ledger-split-bar" title="${t("tagClosed")}: ${closedCount}, ${t("tagOpen")}: ${openCount}, ${t("tagBlocked")}: ${blockedCount}">
488
+ <div class="ledger-split-segment closed" style="width: ${closedPct}%"></div>
489
+ <div class="ledger-split-segment open" style="width: ${openPct}%"></div>
490
+ <div class="ledger-split-segment blocked" style="width: ${Math.max(0, blockedPct)}%"></div>
491
+ </div>
492
+ <div class="ledger-split-legend">
493
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot closed"></i>${t("tagClosed")} (${closedCount})</span>
494
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot open"></i>${t("tagOpen")} (${openCount})</span>
495
+ <span class="ledger-split-legend-item"><i class="ledger-split-legend-dot blocked"></i>${t("tagBlocked")} (${blockedCount})</span>
496
+ </div>
497
+ <div class="ledger-gauge-row">
498
+ <div class="ledger-gauge-card">
499
+ <span>${t("lessonsCheckRate")}</span>
500
+ <strong>${lessonsPct}%</strong>
501
+ </div>
502
+ <div class="ledger-gauge-card">
503
+ <span>${t("evidenceAuditRate")}</span>
504
+ <strong>${evidencePct}%</strong>
505
+ </div>
506
+ </div>
507
+ </section>`;
508
+ }
509
+
510
+ function escapeHtml(value) {
511
+ return String(value ?? "")
512
+ .replaceAll("&", "&amp;")
513
+ .replaceAll("<", "&lt;")
514
+ .replaceAll(">", "&gt;")
515
+ .replaceAll('"', "&quot;");
516
+ }
517
+
518
+ function escapeAttr(value) {
519
+ return escapeHtml(value).replaceAll("'", "&#39;");
520
+ }
521
+
522
+ window.addEventListener("hashchange", app);
523
+ app();
524
+ loadRuntime();