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
@@ -12,6 +12,18 @@ const state = {
12
12
  taskGroupPage: 1,
13
13
  warningFilter: "all",
14
14
  warningPage: 1,
15
+ presetQuery: "",
16
+ presetSourceFilter: "all",
17
+ selectedPresetKey: "",
18
+ selectedPresetId: "",
19
+ presetActionResult: null,
20
+ presetInstallSource: "",
21
+ presetInstallScope: "project",
22
+ presetInstallForce: false,
23
+ presetSeedScope: "project",
24
+ presetSeedForce: false,
25
+ presetUninstallScope: "project",
26
+ presetUninstallConfirm: "",
15
27
  renderMode: "rendered",
16
28
  theme: localStorage.getItem("harness.theme") || "system",
17
29
  taskLayout: localStorage.getItem("harness.taskLayout") || "list",
@@ -72,6 +84,7 @@ function shell() {
72
84
  ${routeLink("#/tasks", t("taskIndex"), "tasks")}
73
85
  ${routeLink("#/review", t("reviewQueue"), "review")}
74
86
  ${routeLink("#/modules", t("moduleView"), "modules")}
87
+ ${routeLink("#/presets", t("presetCatalog"), "presets")}
75
88
  <button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
76
89
  <button data-theme-toggle>${themeLabel()}</button>
77
90
  </div>
@@ -98,6 +111,7 @@ function renderRoute() {
98
111
  if (route.name === "reviewTask") return reviewWorkspace(route);
99
112
  if (route.name === "review") return reviewQueue();
100
113
  if (route.name === "modules") return modulesView(route.id);
114
+ if (route.name === "presets") return presetsView();
101
115
  if (route.name === "tasks") return taskIndex();
102
116
  return overview();
103
117
  }
@@ -109,6 +123,7 @@ function currentRoute() {
109
123
  if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
110
124
  if (parts[0] === "review") return { name: "review" };
111
125
  if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
126
+ if (parts[0] === "presets") return { name: "presets" };
112
127
  if (parts[0] === "tasks") return { name: "tasks" };
113
128
  return { name: "overview" };
114
129
  }
@@ -137,6 +152,9 @@ function overview() {
137
152
 
138
153
  function statusStrip() {
139
154
  const status = bundle.status?.checkState?.status || "unknown";
155
+ const validationMode = bundle.status?.checkState?.validationMode || "validated";
156
+ const snapshotOnly = validationMode === "data-only" && !isWorkbenchRuntime();
157
+ const displayState = snapshotOnly ? "snapshot" : status;
140
158
  const failures = bundle.status?.checkState?.failures || 0;
141
159
  const warnings = bundle.status?.checkState?.warnings || 0;
142
160
  const tasks = bundle.status?.tasks || [];
@@ -144,9 +162,9 @@ function statusStrip() {
144
162
  const visual = summary.visualMapCoverage || {};
145
163
  const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
146
164
  return `<section class="status-card-group">
147
- <div class="status-primary ${status}">
148
- <span>${t("readiness")}</span>
149
- <strong>${label(status)}</strong>
165
+ <div class="status-primary ${displayState}">
166
+ <span>${snapshotOnly ? t("snapshotStatus") : t("readiness")}</span>
167
+ <strong>${snapshotOnly ? t("snapshot") : label(status)}</strong>
150
168
  <p>${nextActionText()}</p>
151
169
  </div>
152
170
  <div class="metrics-grid">
@@ -167,15 +185,22 @@ function metric(labelText, value) {
167
185
  }
168
186
 
169
187
  function nextActionText() {
188
+ const dataOnly = (bundle.status?.checkState?.validationMode || "validated") === "data-only";
189
+ if (dataOnly && !isWorkbenchRuntime()) return t("snapshotNotValidated");
170
190
  const failures = bundle.status?.checkState?.failures || 0;
171
191
  if (failures > 0) return t("resolveBlockers");
172
192
  const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
173
193
  if (missingBriefs > 0) return `${missingBriefs} ${t("missingBriefs")}`;
174
194
  const warnings = bundle.status?.checkState?.warnings || 0;
175
195
  if (warnings > 0) return t("reviewAdvice");
196
+ if (dataOnly) return t("workbenchDataOnly");
176
197
  return t("noBlockers");
177
198
  }
178
199
 
200
+ function isWorkbenchRuntime() {
201
+ return window.__HARNESS_WORKBENCH__ === true || state.runtime?.mode === "workbench";
202
+ }
203
+
179
204
  function flowPanel() {
180
205
  const tasks = bundle.status?.tasks || [];
181
206
  const total = tasks.length;
@@ -845,17 +870,57 @@ function taskQueueReasonSummary(task) {
845
870
  }
846
871
 
847
872
  function phaseTimeline(task) {
873
+ const knownKinds = new Set(["init", "execution", "gate"]);
874
+ const groups = [
875
+ ["init", "Init"],
876
+ ["execution", "Execution"],
877
+ ["gate", "Gate"],
878
+ ["other", "Other / Invalid"],
879
+ ];
880
+ const phases = task.phases || [];
881
+ const grouped = groups
882
+ .map(([kind, label]) => {
883
+ const items = kind === "other"
884
+ ? phases.filter((phase) => !knownKinds.has(phase.kind || "execution"))
885
+ : phases.filter((phase) => (phase.kind || "execution") === kind);
886
+ if (!items.length) return "";
887
+ return `<div class="phase-kind-group ${escapeAttr(kind)}">
888
+ <h3>${escapeHtml(label)}</h3>
889
+ ${items.map(phaseStep).join("")}
890
+ </div>`;
891
+ })
892
+ .join("");
848
893
  return `<section class="phase-timeline">
849
894
  <h2>${t("phaseTimeline")}</h2>
850
- ${(task.phases || []).map((phase) => `<div class="phase-step ${phase.state}">
851
- <strong>${escapeHtml(phase.id)}</strong>
852
- <span>${phase.completion}%</span>
853
- <p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
854
- ${progressBar(phase.completion)}
855
- </div>`).join("") || emptyState(t("noPhaseData"))}
895
+ ${grouped || emptyState(t("noPhaseData"))}
856
896
  </section>`;
857
897
  }
858
898
 
899
+ function phaseStep(phase) {
900
+ const kind = phase.kind || "execution";
901
+ const actor = phase.actor || "agent";
902
+ const knownKind = ["init", "execution", "gate"].includes(kind);
903
+ const kindLabel = knownKind ? escapeHtml(kind) : `<span class="tag warn">${escapeHtml(kind)}</span>`;
904
+ const phaseKindClass = knownKind ? kind : "other";
905
+ return `<div class="phase-step ${escapeAttr(phase.state)} ${escapeAttr(phaseKindClass)}">
906
+ <div class="phase-step-head">
907
+ <strong>${escapeHtml(phase.id)}</strong>
908
+ <span>${kindLabel} · ${phase.completion}%</span>
909
+ </div>
910
+ <p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
911
+ ${progressBar(phase.completion)}
912
+ <div class="phase-meta">
913
+ ${phaseMetaTag(actor)}
914
+ ${tag(phase.evidenceStatus || "missing")}
915
+ </div>
916
+ ${phase.exitCommand ? `<code class="phase-exit-command">${escapeHtml(phase.exitCommand)}</code>` : ""}
917
+ </div>`;
918
+ }
919
+
920
+ function phaseMetaTag(value) {
921
+ return `<span class="tag">${escapeHtml(String(value || "unknown").replaceAll("_", " "))}</span>`;
922
+ }
923
+
859
924
  function taskDocSection(task, fileName, title, required) {
860
925
  const doc = taskDocument(task, fileName);
861
926
  if (!doc && !required) return "";
@@ -1055,7 +1120,7 @@ function moduleCard(module) {
1055
1120
  const pageCount = Math.ceil(tasks.length / 8) || 1;
1056
1121
  const visibleTasks = tasks.slice((currentPage - 1) * 8, currentPage * 8);
1057
1122
 
1058
- const brief = findDocument(`TARGET:docs/09-PLANNING/MODULES/${moduleKey}/brief.md`);
1123
+ const brief = findDocument(module.briefPath || `TARGET:coding-agent-harness/planning/modules/${moduleKey}/brief.md`);
1059
1124
 
1060
1125
  let pagerHtml = "";
1061
1126
  if (tasks.length > 8) {
@@ -1609,6 +1674,382 @@ function healthPanel() {
1609
1674
  </section>`;
1610
1675
  }
1611
1676
 
1677
+ function presetsView() {
1678
+ ensurePresetState();
1679
+ const catalog = bundle.presetCatalog || { summary: {}, roots: [], presets: [] };
1680
+ let presets = filteredPresets();
1681
+ syncVisiblePresetSelection(presets);
1682
+ presets = filteredPresets();
1683
+ const selected = selectedPreset(presets);
1684
+ syncPresetUninstallScope(selected);
1685
+ return `<div class="presets-page stack">
1686
+ <section class="flow-panel preset-command-center">
1687
+ <div class="section-head">
1688
+ <div>
1689
+ <p class="eyebrow">${t("presetCatalog")}</p>
1690
+ <h2>${t("presetCatalog")}</h2>
1691
+ <p class="subtle">${t("presetCatalogSubtitle")}</p>
1692
+ </div>
1693
+ <span class="preset-count-pill">${presets.length}/${catalog.summary?.total || 0}</span>
1694
+ </div>
1695
+ <div class="preset-priority-strip" aria-label="${escapeAttr(t("presetPriorityTitle"))}">
1696
+ ${presetPriorityStep("project", 1)}
1697
+ ${presetPriorityStep("user", 2)}
1698
+ ${presetPriorityStep("builtin", 3)}
1699
+ </div>
1700
+ <div class="preset-toolbar">
1701
+ <div class="input-group">
1702
+ <input data-preset-search value="${escapeAttr(state.presetQuery)}" placeholder="${escapeAttr(t("presetSearchPlaceholder"))}" aria-label="${escapeAttr(t("presetSearch"))}">
1703
+ </div>
1704
+ <div class="preset-source-tabs" role="tablist" aria-label="${escapeAttr(t("presetSourceFilter"))}">
1705
+ ${presetSourceOptions().map((source) => presetSourceButton(source)).join("")}
1706
+ </div>
1707
+ </div>
1708
+ </section>
1709
+ <section class="preset-workspace">
1710
+ <div class="flow-panel preset-collection-panel">
1711
+ <div class="preset-panel-heading">
1712
+ <div>
1713
+ <h3>${t("presetCollection")}</h3>
1714
+ <p>${t("presetCollectionHint")}</p>
1715
+ </div>
1716
+ </div>
1717
+ <div class="preset-catalog-list">
1718
+ ${presets.map((preset) => presetCard(preset, selected ? presetKey(selected) : "")).join("") || emptyState(t("noPresets"))}
1719
+ </div>
1720
+ </div>
1721
+ <div class="preset-detail-workspace stack">
1722
+ ${presetDetailPanel(selected)}
1723
+ ${presetLayerStackPanel(selected)}
1724
+ </div>
1725
+ <aside class="preset-context-actions stack">
1726
+ ${presetActionPanel(selected)}
1727
+ ${presetImportPanel()}
1728
+ ${presetRestorePanel()}
1729
+ ${presetSummaryPanel(catalog)}
1730
+ </aside>
1731
+ </section>
1732
+ </div>`;
1733
+ }
1734
+
1735
+ function ensurePresetState() {
1736
+ const presets = bundle.presetCatalog?.presets || [];
1737
+ if (!state.selectedPresetKey && state.selectedPresetId) {
1738
+ const legacySelection = presets.find((preset) => preset.id === state.selectedPresetId);
1739
+ if (legacySelection) state.selectedPresetKey = presetKey(legacySelection);
1740
+ }
1741
+ if (!state.selectedPresetKey && presets[0]) {
1742
+ state.selectedPresetKey = presetKey(presets[0]);
1743
+ state.presetUninstallConfirm = "";
1744
+ }
1745
+ if (state.selectedPresetKey && !presets.some((preset) => presetKey(preset) === state.selectedPresetKey) && presets[0]) {
1746
+ state.selectedPresetKey = presetKey(presets[0]);
1747
+ state.presetUninstallConfirm = "";
1748
+ }
1749
+ }
1750
+
1751
+ function presetSourceOptions() {
1752
+ return [
1753
+ ["all", t("allPresets")],
1754
+ ["project", t("presetSourceProject")],
1755
+ ["user", t("presetSourceUser")],
1756
+ ["builtin", t("presetSourceBuiltin")],
1757
+ ];
1758
+ }
1759
+
1760
+ function presetSourceButton([source, labelText]) {
1761
+ const active = state.presetSourceFilter === source;
1762
+ const count = source === "all" ? (bundle.presetCatalog?.summary?.total || 0) : (bundle.presetCatalog?.summary?.[source] || 0);
1763
+ return `<button type="button" class="${active ? "active" : ""}" data-preset-source-filter="${escapeAttr(source)}" role="tab" aria-selected="${active ? "true" : "false"}">
1764
+ <span>${escapeHtml(labelText)}</span>
1765
+ <strong>${count}</strong>
1766
+ </button>`;
1767
+ }
1768
+
1769
+ function filteredPresets() {
1770
+ const query = String(state.presetQuery || "").trim().toLowerCase();
1771
+ return (bundle.presetCatalog?.presets || []).filter((preset) => {
1772
+ if (state.presetSourceFilter !== "all" && preset.source !== state.presetSourceFilter) return false;
1773
+ return presetMatchesQuery(preset, query);
1774
+ });
1775
+ }
1776
+
1777
+ function presetMatchesQuery(preset, query = state.presetQuery) {
1778
+ const normalizedQuery = String(query || "").trim().toLowerCase();
1779
+ if (!normalizedQuery) return true;
1780
+ return [
1781
+ preset.id,
1782
+ preset.source,
1783
+ preset.purpose,
1784
+ preset.taskKind,
1785
+ preset.manifestPath,
1786
+ preset.version,
1787
+ ...(preset.compatibleBudgets || []),
1788
+ ].some((value) => String(value || "").toLowerCase().includes(normalizedQuery));
1789
+ }
1790
+
1791
+ function syncVisiblePresetSelection(visiblePresets) {
1792
+ if (!visiblePresets.length) {
1793
+ state.selectedPresetKey = "";
1794
+ state.presetUninstallConfirm = "";
1795
+ return;
1796
+ }
1797
+ if (!visiblePresets.some((preset) => presetKey(preset) === state.selectedPresetKey)) {
1798
+ state.selectedPresetKey = presetKey(visiblePresets[0]);
1799
+ state.presetUninstallConfirm = "";
1800
+ }
1801
+ }
1802
+
1803
+ function selectedPreset(visiblePresets = filteredPresets()) {
1804
+ return visiblePresets.find((preset) => presetKey(preset) === state.selectedPresetKey) || visiblePresets[0] || null;
1805
+ }
1806
+
1807
+ function presetCard(preset, selectedId) {
1808
+ const key = presetKey(preset);
1809
+ const selected = key === selectedId;
1810
+ return `<article class="preset-card ${selected ? "active" : ""} ${preset.effective ? "effective" : "shadowed"}">
1811
+ <div class="preset-card-topline">
1812
+ <button type="button" class="preset-card-select" data-preset-select="${escapeAttr(key)}" aria-pressed="${selected ? "true" : "false"}">
1813
+ <span class="card-id">${escapeHtml(preset.id)}</span>
1814
+ </button>
1815
+ <div class="preset-card-tools">
1816
+ ${presetSourceBadge(preset.source)}
1817
+ ${presetStatusBadge(preset)}
1818
+ <button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}" title="${escapeAttr(t("copyPresetId"))}">${t("copyIdShort")}</button>
1819
+ </div>
1820
+ </div>
1821
+ <button type="button" class="preset-card-body" data-preset-select="${escapeAttr(key)}">
1822
+ <span>${escapeHtml(preset.purpose || t("none"))}</span>
1823
+ </button>
1824
+ <div class="preset-card-meta">
1825
+ <span>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(preset))}</span>
1826
+ <span>${t("taskKind")}: ${escapeHtml(preset.taskKind || t("none"))}</span>
1827
+ <span>${t("budgets")}: ${escapeHtml((preset.compatibleBudgets || []).join(", ") || t("none"))}</span>
1828
+ </div>
1829
+ <code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
1830
+ </article>`;
1831
+ }
1832
+
1833
+ function presetKey(preset) {
1834
+ return preset?.key || `${preset?.source || "unknown"}:${preset?.id || ""}`;
1835
+ }
1836
+
1837
+ function presetSourceRank(source) {
1838
+ return { project: 1, user: 2, builtin: 3 }[source] || 9;
1839
+ }
1840
+
1841
+ function presetLayersForId(id) {
1842
+ return (bundle.presetCatalog?.presets || [])
1843
+ .filter((preset) => preset.id === id)
1844
+ .sort((a, b) => presetSourceRank(a.source) - presetSourceRank(b.source));
1845
+ }
1846
+
1847
+ function syncPresetUninstallScope(preset) {
1848
+ if (preset && ["project", "user"].includes(preset.source)) state.presetUninstallScope = preset.source;
1849
+ }
1850
+
1851
+ function presetPriorityStep(source, index) {
1852
+ return `<div class="preset-priority-step">
1853
+ <span>${index}</span>
1854
+ <strong>${escapeHtml(t(`presetSource_${source}`) || source)}</strong>
1855
+ </div>`;
1856
+ }
1857
+
1858
+ function presetSourceBadge(source) {
1859
+ const normalized = String(source || "unknown");
1860
+ return `<span class="tag preset-source-badge ${escapeAttr(normalized)}">${escapeHtml(t(`presetSource_${normalized}`) || normalized)}</span>`;
1861
+ }
1862
+
1863
+ function presetStatusBadge(preset) {
1864
+ return `<span class="tag ${preset.effective ? "pass" : "warn"}">${escapeHtml(preset.effective ? t("presetEffective") : t("presetShadowed"))}</span>`;
1865
+ }
1866
+
1867
+ function formatPresetVersion(preset) {
1868
+ return preset?.version ?? t("none");
1869
+ }
1870
+
1871
+ function presetSummaryPanel(catalog) {
1872
+ const roots = catalog.roots || [];
1873
+ return `<section class="side-panel preset-summary-panel">
1874
+ <h3>${t("presetSources")}</h3>
1875
+ <p class="preset-helper">${t("presetSourcesHint")}</p>
1876
+ <div class="metrics-grid compact">
1877
+ ${metric(t("presetSourceProject"), catalog.summary?.project || 0)}
1878
+ ${metric(t("presetSourceUser"), catalog.summary?.user || 0)}
1879
+ ${metric(t("presetSourceBuiltin"), catalog.summary?.builtin || 0)}
1880
+ </div>
1881
+ <div class="preset-roots">
1882
+ ${roots.map((root) => `<div><strong>${escapeHtml(t(`presetSource_${root.source}`) || root.source)}</strong><code>${escapeHtml(root.path || "")}</code></div>`).join("")}
1883
+ </div>
1884
+ </section>`;
1885
+ }
1886
+
1887
+ function presetDetailPanel(preset) {
1888
+ if (!preset) return `<section class="flow-panel preset-detail-panel">${emptyState(t("noPresets"))}</section>`;
1889
+ const inspectCommand = `harness preset inspect ${preset.id} --json .`;
1890
+ const checkCommand = `harness preset check ${preset.id} --json .`;
1891
+ const commandRows = preset.effective
1892
+ ? `${presetCommandRow(inspectCommand)}${presetCommandRow(checkCommand)}`
1893
+ : `<div class="preset-command-warning">${escapeHtml(t("presetCommandsEffectiveOnly"))}</div>`;
1894
+ return `<section class="flow-panel preset-detail-panel">
1895
+ <div class="preset-detail-hero">
1896
+ <div>
1897
+ <div class="preset-detail-title-row">
1898
+ <h3>${escapeHtml(preset.id)}</h3>
1899
+ <button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}">${t("copyPresetId")}</button>
1900
+ </div>
1901
+ <p>${escapeHtml(preset.purpose || "")}</p>
1902
+ </div>
1903
+ <div class="preset-detail-badges">
1904
+ ${presetSourceBadge(preset.source)}
1905
+ ${presetStatusBadge(preset)}
1906
+ </div>
1907
+ </div>
1908
+ <dl class="preset-detail-list">
1909
+ ${presetDetailRow(t("manifestVersion"), formatPresetVersion(preset))}
1910
+ ${presetDetailRow(t("source"), t(`presetSource_${preset.source}`) || preset.source)}
1911
+ ${presetDetailRow(t("status"), preset.effective ? t("presetEffective") : t("presetShadowed"))}
1912
+ ${presetDetailRow(t("taskKind"), preset.taskKind || t("none"))}
1913
+ ${presetDetailRow(t("budgets"), (preset.compatibleBudgets || []).join(", ") || t("none"))}
1914
+ ${presetDetailRow(t("inputs"), preset.inputCount || 0)}
1915
+ ${presetDetailRow(t("references"), preset.referenceCount || 0)}
1916
+ ${presetDetailRow(t("artifacts"), preset.artifactCount || 0)}
1917
+ ${presetDetailRow(t("writeScopes"), preset.writeScopeCount || 0)}
1918
+ ${presetDetailRow(t("requiredReads"), preset.requiredReadCount || 0)}
1919
+ </dl>
1920
+ <div class="preset-path-block">
1921
+ <span>${t("manifestPath")}</span>
1922
+ <code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
1923
+ </div>
1924
+ <div class="preset-command-list">
1925
+ ${commandRows}
1926
+ </div>
1927
+ </section>`;
1928
+ }
1929
+
1930
+ function presetDetailRow(labelText, value) {
1931
+ return `<div><dt>${escapeHtml(labelText)}</dt><dd>${escapeHtml(String(value ?? ""))}</dd></div>`;
1932
+ }
1933
+
1934
+ function presetCommandRow(command) {
1935
+ return `<div class="preset-command-row">
1936
+ <code>${escapeHtml(command)}</code>
1937
+ <button type="button" class="copy-inline" data-copy-preset-command="${escapeAttr(command)}">${t("copyCommand")}</button>
1938
+ </div>`;
1939
+ }
1940
+
1941
+ function presetLayerStackPanel(preset) {
1942
+ if (!preset) return "";
1943
+ const layers = presetLayersForId(preset.id);
1944
+ return `<section class="flow-panel preset-layer-panel">
1945
+ <div class="preset-panel-heading">
1946
+ <div>
1947
+ <h3>${t("presetLayerStack")}</h3>
1948
+ <p>${t("presetLayerStackHint")}</p>
1949
+ </div>
1950
+ </div>
1951
+ <div class="preset-layer-list">
1952
+ ${layers.map((layer) => `<button type="button" class="preset-layer-row ${presetKey(layer) === presetKey(preset) ? "active" : ""}" data-preset-select="${escapeAttr(presetKey(layer))}">
1953
+ <span class="preset-layer-rank">${presetSourceRank(layer.source)}</span>
1954
+ <span>
1955
+ <strong>${escapeHtml(t(`presetSource_${layer.source}`) || layer.source)}</strong>
1956
+ <small>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(layer))}</small>
1957
+ </span>
1958
+ ${presetStatusBadge(layer)}
1959
+ </button>`).join("")}
1960
+ </div>
1961
+ </section>`;
1962
+ }
1963
+
1964
+ function presetActionPanel(preset) {
1965
+ const staticNote = canUseWorkbenchAction("preset-install") ? "" : `<p class="lesson-action-note">${escapeHtml(t("presetWorkbenchRequired"))}</p>`;
1966
+ const lockedUninstallScope = preset && ["project", "user"].includes(preset.source) ? preset.source : "";
1967
+ const confirmMatches = Boolean(preset && state.presetUninstallConfirm.trim() === preset.id);
1968
+ const canCheck = canUseWorkbenchAction("preset-check") && preset && preset.effective;
1969
+ const canUninstall = canUseWorkbenchAction("preset-uninstall") && preset && preset.source !== "builtin" && confirmMatches;
1970
+ return `<section class="side-panel preset-action-panel">
1971
+ <div class="preset-panel-heading">
1972
+ <div>
1973
+ <h3>${t("presetContextActions")}</h3>
1974
+ <p>${preset ? escapeHtml(preset.id) : t("noPresets")}</p>
1975
+ </div>
1976
+ </div>
1977
+ ${staticNote}
1978
+ ${presetActionResult()}
1979
+ <div class="preset-action-group">
1980
+ <h4>${t("presetCheck")}</h4>
1981
+ <p>${preset?.effective ? t("presetCheckHint") : t("presetShadowedActionHint")}</p>
1982
+ <button data-preset-check="${escapeAttr(preset?.id || "")}" ${canCheck ? "" : "disabled"}>${t("presetCheckSelected")}</button>
1983
+ </div>
1984
+ <div class="preset-action-group danger">
1985
+ <h4>${t("presetUninstallSelected")}</h4>
1986
+ <p>${preset?.source === "builtin" ? t("presetBuiltinImmutable") : t("presetUninstallHint")}</p>
1987
+ <label>${t("scope")}<select data-preset-uninstall-scope ${lockedUninstallScope ? "disabled" : ""}>
1988
+ ${presetScopeOptions(lockedUninstallScope || state.presetUninstallScope)}
1989
+ </select></label>
1990
+ <div class="preset-confirm-row">
1991
+ <label>${t("confirmPresetId")}<input data-preset-uninstall-confirm value="${escapeAttr(state.presetUninstallConfirm)}" placeholder="${escapeAttr(preset?.id || "")}"></label>
1992
+ <button type="button" data-preset-fill-confirm="${escapeAttr(preset?.id || "")}" ${preset && preset.source !== "builtin" ? "" : "disabled"}>${t("useSelectedId")}</button>
1993
+ </div>
1994
+ ${preset && preset.source !== "builtin" && !confirmMatches ? `<p class="preset-confirm-warning">${escapeHtml(t("presetConfirmRequired"))}</p>` : ""}
1995
+ <button data-preset-uninstall="${escapeAttr(preset?.id || "")}" ${canUninstall ? "" : "disabled"}>${t("presetUninstallSelected")}</button>
1996
+ </div>
1997
+ </section>`;
1998
+ }
1999
+
2000
+ function presetImportPanel() {
2001
+ return `<section class="side-panel preset-action-panel">
2002
+ <div class="preset-panel-heading">
2003
+ <div>
2004
+ <h3>${t("presetImportTitle")}</h3>
2005
+ <p>${t("presetImportHint")}</p>
2006
+ </div>
2007
+ </div>
2008
+ <div class="preset-action-group">
2009
+ <label>${t("source")}<input data-preset-install-source value="${escapeAttr(state.presetInstallSource)}" placeholder="${escapeAttr(t("presetInstallSourcePlaceholder"))}"></label>
2010
+ <label>${t("scope")}<select data-preset-install-scope>
2011
+ ${presetScopeOptions(state.presetInstallScope)}
2012
+ </select></label>
2013
+ <label class="check-row"><input type="checkbox" data-preset-install-force ${state.presetInstallForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
2014
+ <button data-preset-install ${canUseWorkbenchAction("preset-install") ? "" : "disabled"}>${t("presetInstall")}</button>
2015
+ </div>
2016
+ </section>`;
2017
+ }
2018
+
2019
+ function presetRestorePanel() {
2020
+ return `<section class="side-panel preset-action-panel">
2021
+ <div class="preset-panel-heading">
2022
+ <div>
2023
+ <h3>${t("presetRestoreBundled")}</h3>
2024
+ <p>${t("presetRestoreBundledHint")}</p>
2025
+ </div>
2026
+ </div>
2027
+ <div class="preset-action-group">
2028
+ <label>${t("scope")}<select data-preset-seed-scope>
2029
+ ${presetScopeOptions(state.presetSeedScope)}
2030
+ </select></label>
2031
+ <label class="check-row"><input type="checkbox" data-preset-seed-force ${state.presetSeedForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
2032
+ <button data-preset-seed ${canUseWorkbenchAction("preset-seed") ? "" : "disabled"}>${t("presetRestoreBundled")}</button>
2033
+ </div>
2034
+ </section>`;
2035
+ }
2036
+
2037
+ function presetScopeOptions(current) {
2038
+ return [["project", t("presetSourceProject")], ["user", t("presetSourceUser")]]
2039
+ .map(([value, labelText]) => `<option value="${value}" ${current === value ? "selected" : ""}>${escapeHtml(labelText)}</option>`)
2040
+ .join("");
2041
+ }
2042
+
2043
+ function presetActionResult() {
2044
+ const result = state.presetActionResult;
2045
+ if (!result) return "";
2046
+ const klass = result.ok ? "success" : "failed";
2047
+ return `<div class="workbench-action-result ${klass}">
2048
+ <strong>${escapeHtml(result.title || "")}</strong>
2049
+ <span>${escapeHtml(result.message || "")}</span>
2050
+ </div>`;
2051
+ }
2052
+
1612
2053
  function taskDocument(task, fileName) {
1613
2054
  if (fileName === "__walkthrough__" && task.walkthroughPath) return findDocument(task.walkthroughPath);
1614
2055
  return findDocument(`${task.path}/${fileName}`);
@@ -1639,7 +2080,9 @@ function tag(value) {
1639
2080
  }
1640
2081
 
1641
2082
  function label(value) {
1642
- return t(`state_${value}`) || String(value || "unknown").replaceAll("_", " ");
2083
+ const key = `state_${value}`;
2084
+ const translated = t(key);
2085
+ return translated === key ? String(value || "unknown").replaceAll("_", " ") : translated;
1643
2086
  }
1644
2087
 
1645
2088
  function list(items = []) {
@@ -1722,6 +2165,68 @@ function bind() {
1722
2165
  state.warningPage = 1;
1723
2166
  app();
1724
2167
  }));
2168
+ document.querySelectorAll("[data-preset-search]").forEach((input) => input.addEventListener("input", () => {
2169
+ state.presetQuery = input.value;
2170
+ app();
2171
+ }));
2172
+ document.querySelectorAll("[data-preset-source-filter]").forEach((button) => button.addEventListener("click", () => {
2173
+ state.presetSourceFilter = button.dataset.presetSourceFilter || "all";
2174
+ state.selectedPresetKey = "";
2175
+ state.presetUninstallConfirm = "";
2176
+ app();
2177
+ }));
2178
+ document.querySelectorAll("[data-preset-select]").forEach((button) => button.addEventListener("click", () => {
2179
+ state.selectedPresetKey = button.dataset.presetSelect || "";
2180
+ state.selectedPresetId = "";
2181
+ const selectedPreset = (bundle.presetCatalog?.presets || []).find((preset) => presetKey(preset) === state.selectedPresetKey);
2182
+ if (selectedPreset && state.presetSourceFilter !== "all" && selectedPreset.source !== state.presetSourceFilter) {
2183
+ state.presetSourceFilter = selectedPreset.source;
2184
+ }
2185
+ if (selectedPreset && !presetMatchesQuery(selectedPreset)) state.presetQuery = "";
2186
+ if (selectedPreset && ["project", "user"].includes(selectedPreset.source)) state.presetUninstallScope = selectedPreset.source;
2187
+ state.presetUninstallConfirm = "";
2188
+ app();
2189
+ }));
2190
+ document.querySelectorAll("[data-preset-install-source]").forEach((input) => input.addEventListener("input", () => {
2191
+ state.presetInstallSource = input.value;
2192
+ }));
2193
+ document.querySelectorAll("[data-preset-install-scope]").forEach((select) => select.addEventListener("change", () => {
2194
+ state.presetInstallScope = select.value || "project";
2195
+ }));
2196
+ document.querySelectorAll("[data-preset-install-force]").forEach((input) => input.addEventListener("change", () => {
2197
+ state.presetInstallForce = input.checked;
2198
+ }));
2199
+ document.querySelectorAll("[data-preset-seed-scope]").forEach((select) => select.addEventListener("change", () => {
2200
+ state.presetSeedScope = select.value || "project";
2201
+ }));
2202
+ document.querySelectorAll("[data-preset-seed-force]").forEach((input) => input.addEventListener("change", () => {
2203
+ state.presetSeedForce = input.checked;
2204
+ }));
2205
+ document.querySelectorAll("[data-preset-uninstall-scope]").forEach((select) => select.addEventListener("change", () => {
2206
+ state.presetUninstallScope = select.value || "project";
2207
+ }));
2208
+ document.querySelectorAll("[data-preset-uninstall-confirm]").forEach((input) => input.addEventListener("input", () => {
2209
+ state.presetUninstallConfirm = input.value;
2210
+ }));
2211
+ document.querySelectorAll("[data-preset-fill-confirm]").forEach((button) => button.addEventListener("click", () => {
2212
+ state.presetUninstallConfirm = button.dataset.presetFillConfirm || "";
2213
+ app();
2214
+ }));
2215
+ document.querySelectorAll("[data-preset-check]").forEach((button) => button.addEventListener("click", () => runPresetAction("check", { id: button.dataset.presetCheck || "" })));
2216
+ document.querySelectorAll("[data-preset-install]").forEach((button) => button.addEventListener("click", () => runPresetAction("install", {
2217
+ source: state.presetInstallSource,
2218
+ scope: state.presetInstallScope,
2219
+ force: state.presetInstallForce,
2220
+ })));
2221
+ document.querySelectorAll("[data-preset-seed]").forEach((button) => button.addEventListener("click", () => runPresetAction("seed", {
2222
+ scope: state.presetSeedScope,
2223
+ force: state.presetSeedForce,
2224
+ })));
2225
+ document.querySelectorAll("[data-preset-uninstall]").forEach((button) => button.addEventListener("click", () => runPresetAction("uninstall", {
2226
+ id: button.dataset.presetUninstall || "",
2227
+ scope: state.presetUninstallScope,
2228
+ confirmText: state.presetUninstallConfirm,
2229
+ })));
1725
2230
  document.querySelectorAll("[data-review-queue-tab]").forEach((button) => button.addEventListener("click", () => {
1726
2231
  state.reviewQueueTab = button.dataset.reviewQueueTab || "review";
1727
2232
  state.reviewQueuePage = 1;
@@ -1768,6 +2273,7 @@ function bind() {
1768
2273
  openDrawer(taskId);
1769
2274
  }));
1770
2275
  bindCopyTaskNameButtons(document);
2276
+ bindPresetCopyButtons(document);
1771
2277
  bindRepairPromptButtons(document);
1772
2278
  bindLessonSedimentationButtons(document);
1773
2279
  document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
@@ -1850,6 +2356,45 @@ async function completeReviewFromDashboard(taskId) {
1850
2356
  }
1851
2357
  }
1852
2358
 
2359
+ async function runPresetAction(action, body) {
2360
+ state.presetActionResult = { ok: true, title: t("presetActionRunning"), message: action };
2361
+ app();
2362
+ try {
2363
+ const response = await fetch(`/api/presets/${action}`, {
2364
+ method: "POST",
2365
+ headers: {
2366
+ "content-type": "application/json",
2367
+ "x-harness-csrf": state.runtime?.csrfToken || "",
2368
+ },
2369
+ body: JSON.stringify(body),
2370
+ });
2371
+ const payload = await response.json();
2372
+ if (!response.ok) throw payload;
2373
+ state.presetActionResult = {
2374
+ ok: true,
2375
+ title: t("presetActionSuccess"),
2376
+ message: presetActionMessage(action, payload),
2377
+ };
2378
+ app();
2379
+ if (["install", "seed", "uninstall"].includes(action)) setTimeout(() => window.location.reload(), 650);
2380
+ } catch (error) {
2381
+ state.presetActionResult = {
2382
+ ok: false,
2383
+ title: t("presetActionFailed"),
2384
+ message: error?.error || error?.message || String(error || action),
2385
+ };
2386
+ app();
2387
+ }
2388
+ }
2389
+
2390
+ function presetActionMessage(action, payload) {
2391
+ if (action === "check") return `${payload.id || ""} ${payload.status || ""}`.trim();
2392
+ if (action === "install") return `${payload.id || ""} -> ${payload.scope || ""}`.trim();
2393
+ if (action === "seed") return `${payload.created || 0} ${t("created")} · ${payload.skipped || 0} ${t("skipped")}`;
2394
+ if (action === "uninstall") return `${payload.id || ""} ${payload.removed ? t("removed") : t("notInstalled")}`.trim();
2395
+ return action;
2396
+ }
2397
+
1853
2398
  function renderDrawerContent(taskId) {
1854
2399
  const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
1855
2400
  if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
@@ -1929,6 +2474,35 @@ function bindCopyTaskNameButtons(root) {
1929
2474
  }));
1930
2475
  }
1931
2476
 
2477
+ function bindPresetCopyButtons(root) {
2478
+ root.querySelectorAll("[data-copy-preset-id]").forEach((button) => button.addEventListener("click", async (event) => {
2479
+ event.preventDefault();
2480
+ event.stopPropagation();
2481
+ const presetId = button.dataset.copyPresetId || "";
2482
+ const defaultText = button.textContent;
2483
+ try {
2484
+ await copyText(presetId);
2485
+ button.textContent = t("copyTaskNameSuccess");
2486
+ } catch {
2487
+ button.textContent = t("copyTaskNameFailed");
2488
+ }
2489
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
2490
+ }));
2491
+ root.querySelectorAll("[data-copy-preset-command]").forEach((button) => button.addEventListener("click", async (event) => {
2492
+ event.preventDefault();
2493
+ event.stopPropagation();
2494
+ const command = button.dataset.copyPresetCommand || "";
2495
+ const defaultText = button.textContent;
2496
+ try {
2497
+ await copyText(command);
2498
+ button.textContent = t("copyTaskNameSuccess");
2499
+ } catch {
2500
+ button.textContent = t("copyTaskNameFailed");
2501
+ }
2502
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
2503
+ }));
2504
+ }
2505
+
1932
2506
  function bindRepairPromptButtons(root) {
1933
2507
  root.querySelectorAll("[data-copy-repair-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
1934
2508
  event.preventDefault();