coding-agent-harness 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +96 -4
  6. package/README.zh-CN.md +75 -4
  7. package/SKILL.md +52 -51
  8. package/dist/build-dist.mjs +189 -0
  9. package/dist/check-dist-observation.mjs +428 -0
  10. package/dist/check-harness.mjs +489 -0
  11. package/dist/check-import-graph.mjs +511 -0
  12. package/dist/check-runtime-emit.mjs +304 -0
  13. package/dist/check-type-boundaries.mjs +139 -0
  14. package/dist/commands/dashboard-command.mjs +80 -0
  15. package/dist/commands/migration-command.mjs +152 -0
  16. package/dist/commands/preset-command.mjs +91 -0
  17. package/dist/commands/task-command.mjs +324 -0
  18. package/dist/harness.mjs +304 -0
  19. package/dist/lib/capability-registry.mjs +643 -0
  20. package/dist/lib/check-module-parallel.mjs +227 -0
  21. package/dist/lib/check-profiles.mjs +414 -0
  22. package/dist/lib/check-task-contracts.mjs +54 -0
  23. package/dist/lib/core-shared.mjs +254 -0
  24. package/dist/lib/dashboard-data.mjs +608 -0
  25. package/dist/lib/dashboard-workbench.mjs +334 -0
  26. package/dist/lib/dashboard-writer.mjs +200 -0
  27. package/dist/lib/git-status-summary.mjs +45 -0
  28. package/dist/lib/governance-index-generator.mjs +236 -0
  29. package/dist/lib/governance-sync.mjs +617 -0
  30. package/dist/lib/governance-table-boundary.mjs +161 -0
  31. package/{scripts → dist}/lib/harness-core.mjs +3 -0
  32. package/dist/lib/harness-paths.mjs +338 -0
  33. package/dist/lib/lesson-maintenance.mjs +139 -0
  34. package/dist/lib/markdown-utils.mjs +193 -0
  35. package/dist/lib/migration-planner.mjs +439 -0
  36. package/dist/lib/migration-support.mjs +317 -0
  37. package/dist/lib/phase-kind.mjs +46 -0
  38. package/dist/lib/preset-audit-contracts.mjs +40 -0
  39. package/dist/lib/preset-engine.mjs +516 -0
  40. package/dist/lib/preset-registry.mjs +831 -0
  41. package/dist/lib/preset-resource-contracts.mjs +83 -0
  42. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  43. package/dist/lib/status-builder.mjs +87 -0
  44. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
  45. package/dist/lib/structure-migration.mjs +404 -0
  46. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  47. package/dist/lib/task-audit-metadata.mjs +376 -0
  48. package/dist/lib/task-audit-migration.mjs +355 -0
  49. package/dist/lib/task-completion-consistency.mjs +29 -0
  50. package/dist/lib/task-index.mjs +133 -0
  51. package/dist/lib/task-lesson-candidates.mjs +239 -0
  52. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  53. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  54. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  55. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  56. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  57. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  58. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  59. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  60. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  61. package/dist/lib/task-lifecycle.mjs +611 -0
  62. package/dist/lib/task-metadata.mjs +116 -0
  63. package/dist/lib/task-review-model.mjs +474 -0
  64. package/dist/lib/task-scanner.mjs +439 -0
  65. package/dist/lib/task-tombstone-commands.mjs +125 -0
  66. package/dist/postinstall.mjs +14 -0
  67. package/dist/run-built-tests.mjs +84 -0
  68. package/docs-release/README.md +1 -0
  69. package/docs-release/architecture/overview.md +13 -13
  70. package/docs-release/architecture/overview.zh-CN.md +13 -13
  71. package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
  72. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  73. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  74. package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
  75. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  76. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
  77. package/docs-release/architecture/system-explainer/README.md +67 -0
  78. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
  79. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  80. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  81. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
  82. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  83. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
  84. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  85. package/docs-release/guides/agent-installation.en-US.md +22 -15
  86. package/docs-release/guides/agent-installation.md +23 -15
  87. package/docs-release/guides/contributing.md +3 -3
  88. package/docs-release/guides/contributing.zh-CN.md +3 -3
  89. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  90. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  91. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  92. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  93. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  94. package/docs-release/guides/migration-playbook.md +59 -1
  95. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  96. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  97. package/docs-release/guides/preset-development.md +28 -4
  98. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  99. package/docs-release/guides/repository-operating-models.md +21 -21
  100. package/docs-release/guides/task-state-machine.en-US.md +35 -18
  101. package/docs-release/guides/task-state-machine.md +35 -18
  102. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  103. package/examples/minimal-project/AGENTS.md +2 -2
  104. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  105. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
  106. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  107. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  108. package/package.json +22 -13
  109. package/presets/legacy-migration/preset.yaml +5 -5
  110. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  111. package/presets/lesson-sedimentation/preset.yaml +3 -3
  112. package/presets/module/preset.yaml +2 -2
  113. package/presets/module/templates/execution_strategy.append.md +1 -1
  114. package/presets/module/templates/task_plan.append.md +3 -3
  115. package/presets/standard-task/preset.yaml +2 -2
  116. package/references/adversarial-review-standard.md +2 -2
  117. package/references/agents-md-pattern.md +14 -14
  118. package/references/cadence-ledger.md +1 -1
  119. package/references/ci-cd-standard.md +1 -1
  120. package/references/delivery-operating-model-standard.md +4 -4
  121. package/references/docs-directory-standard.md +65 -159
  122. package/references/external-source-intake-standard.md +10 -10
  123. package/references/harness-ledger.md +6 -6
  124. package/references/legacy-12-phase-bootstrap.md +2 -2
  125. package/references/lessons-governance.md +15 -15
  126. package/references/long-running-task-standard.md +6 -6
  127. package/references/module-parallel-standard.md +34 -34
  128. package/references/planning-loop.md +6 -6
  129. package/references/project-onboarding-audit.md +4 -4
  130. package/references/regression-system.md +2 -2
  131. package/references/repo-governance-standard.md +4 -4
  132. package/references/review-routing-standard.md +1 -1
  133. package/references/ssot-governance.md +19 -19
  134. package/references/taskr-gap-analysis.md +5 -5
  135. package/references/walkthrough-closeout.md +14 -14
  136. package/references/worktree-parallel.md +3 -3
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  139. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  140. package/templates/AGENTS.md.template +31 -29
  141. package/templates/architecture/README.md +4 -4
  142. package/templates/architecture/service-catalog.md +2 -2
  143. package/templates/architecture/services/service-template.md +1 -1
  144. package/templates/dashboard/assets/app-src/00-state.js +12 -0
  145. package/templates/dashboard/assets/app-src/10-router.js +3 -0
  146. package/templates/dashboard/assets/app-src/20-overview.js +13 -3
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
  148. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  149. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  150. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  151. package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
  152. package/templates/dashboard/assets/app.css +583 -0
  153. package/templates/dashboard/assets/app.css.manifest.json +1 -0
  154. package/templates/dashboard/assets/app.js +585 -11
  155. package/templates/dashboard/assets/app.manifest.json +1 -0
  156. package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
  158. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  159. package/templates/dashboard/assets/i18n.js +144 -4
  160. package/templates/development/README.md +10 -10
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +2 -2
  163. package/templates/development/external-source-packs/README.md +4 -4
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +2 -2
  166. package/templates/integrations/event-contract.md +2 -2
  167. package/templates/integrations/third-party/vendor-template.md +2 -2
  168. package/templates/integrations/webhook-contract.md +2 -2
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/planning/INDEX.md +88 -0
  171. package/templates/planning/brief.md +1 -1
  172. package/templates/planning/module_session_prompt.md +2 -1
  173. package/templates/planning/review.md +0 -18
  174. package/templates/planning/task_plan.md +5 -44
  175. package/templates/planning/visual_map.md +13 -9
  176. package/templates/planning/visual_map.simple.md +52 -0
  177. package/templates/planning/walkthrough.md +47 -0
  178. package/templates/reference/docs-library-standard.md +8 -8
  179. package/templates/reference/execution-workflow-standard.md +29 -2
  180. package/templates/reference/external-source-intake-standard.md +15 -15
  181. package/templates/reference/repo-governance-standard.md +1 -1
  182. package/templates/ssot/Module-Registry.md +1 -1
  183. package/templates/walkthrough/walkthrough-template.md +2 -2
  184. package/templates-zh-CN/AGENTS.md.template +31 -29
  185. package/templates-zh-CN/CLAUDE.md.template +1 -1
  186. package/templates-zh-CN/architecture/README.md +4 -4
  187. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  188. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  189. package/templates-zh-CN/development/README.md +10 -10
  190. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  191. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  192. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  193. package/templates-zh-CN/integrations/README.md +4 -4
  194. package/templates-zh-CN/integrations/api-contract.md +2 -2
  195. package/templates-zh-CN/integrations/event-contract.md +2 -2
  196. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  197. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  198. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  199. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  200. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  201. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  202. package/templates-zh-CN/planning/INDEX.md +87 -0
  203. package/templates-zh-CN/planning/brief.md +1 -1
  204. package/templates-zh-CN/planning/module_session_prompt.md +12 -11
  205. package/templates-zh-CN/planning/review.md +0 -18
  206. package/templates-zh-CN/planning/task_plan.md +3 -63
  207. package/templates-zh-CN/planning/visual_map.md +14 -7
  208. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  209. package/templates-zh-CN/planning/walkthrough.md +47 -0
  210. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  211. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  212. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  213. package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
  214. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  215. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  216. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  217. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  218. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  219. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  220. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  221. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  222. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  223. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  224. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  225. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  226. package/tsconfig.dist.json +16 -0
  227. package/tsconfig.json +25 -0
  228. package/tsconfig.runtime.json +24 -0
  229. package/examples/minimal-project/.harness-capabilities.json +0 -8
  230. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  231. package/scripts/check-harness.mjs +0 -508
  232. package/scripts/commands/dashboard-command.mjs +0 -67
  233. package/scripts/commands/migration-command.mjs +0 -96
  234. package/scripts/commands/preset-command.mjs +0 -73
  235. package/scripts/commands/task-command.mjs +0 -327
  236. package/scripts/harness.mjs +0 -287
  237. package/scripts/lib/capability-registry.mjs +0 -591
  238. package/scripts/lib/check-module-parallel.mjs +0 -237
  239. package/scripts/lib/check-profiles.mjs +0 -418
  240. package/scripts/lib/check-task-contracts.mjs +0 -47
  241. package/scripts/lib/core-shared.mjs +0 -196
  242. package/scripts/lib/dashboard-data.mjs +0 -412
  243. package/scripts/lib/dashboard-workbench.mjs +0 -257
  244. package/scripts/lib/dashboard-writer.mjs +0 -198
  245. package/scripts/lib/git-status-summary.mjs +0 -46
  246. package/scripts/lib/governance-index-generator.mjs +0 -174
  247. package/scripts/lib/governance-sync.mjs +0 -514
  248. package/scripts/lib/governance-table-boundary.mjs +0 -175
  249. package/scripts/lib/lesson-maintenance.mjs +0 -152
  250. package/scripts/lib/markdown-utils.mjs +0 -158
  251. package/scripts/lib/migration-planner.mjs +0 -478
  252. package/scripts/lib/migration-support.mjs +0 -312
  253. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  254. package/scripts/lib/preset-engine.mjs +0 -497
  255. package/scripts/lib/preset-registry.mjs +0 -627
  256. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  257. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  258. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  259. package/scripts/lib/task-completion-consistency.mjs +0 -16
  260. package/scripts/lib/task-index.mjs +0 -93
  261. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  262. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  263. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
  264. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
  265. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  266. package/scripts/lib/task-lifecycle.mjs +0 -649
  267. package/scripts/lib/task-review-model.mjs +0 -469
  268. package/scripts/lib/task-scanner.mjs +0 -576
  269. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  270. package/scripts/postinstall.mjs +0 -14
  271. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  272. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  273. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  274. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  275. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  276. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  277. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  278. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  279. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,375 @@
1
+ function presetsView() {
2
+ ensurePresetState();
3
+ const catalog = bundle.presetCatalog || { summary: {}, roots: [], presets: [] };
4
+ let presets = filteredPresets();
5
+ syncVisiblePresetSelection(presets);
6
+ presets = filteredPresets();
7
+ const selected = selectedPreset(presets);
8
+ syncPresetUninstallScope(selected);
9
+ return `<div class="presets-page stack">
10
+ <section class="flow-panel preset-command-center">
11
+ <div class="section-head">
12
+ <div>
13
+ <p class="eyebrow">${t("presetCatalog")}</p>
14
+ <h2>${t("presetCatalog")}</h2>
15
+ <p class="subtle">${t("presetCatalogSubtitle")}</p>
16
+ </div>
17
+ <span class="preset-count-pill">${presets.length}/${catalog.summary?.total || 0}</span>
18
+ </div>
19
+ <div class="preset-priority-strip" aria-label="${escapeAttr(t("presetPriorityTitle"))}">
20
+ ${presetPriorityStep("project", 1)}
21
+ ${presetPriorityStep("user", 2)}
22
+ ${presetPriorityStep("builtin", 3)}
23
+ </div>
24
+ <div class="preset-toolbar">
25
+ <div class="input-group">
26
+ <input data-preset-search value="${escapeAttr(state.presetQuery)}" placeholder="${escapeAttr(t("presetSearchPlaceholder"))}" aria-label="${escapeAttr(t("presetSearch"))}">
27
+ </div>
28
+ <div class="preset-source-tabs" role="tablist" aria-label="${escapeAttr(t("presetSourceFilter"))}">
29
+ ${presetSourceOptions().map((source) => presetSourceButton(source)).join("")}
30
+ </div>
31
+ </div>
32
+ </section>
33
+ <section class="preset-workspace">
34
+ <div class="flow-panel preset-collection-panel">
35
+ <div class="preset-panel-heading">
36
+ <div>
37
+ <h3>${t("presetCollection")}</h3>
38
+ <p>${t("presetCollectionHint")}</p>
39
+ </div>
40
+ </div>
41
+ <div class="preset-catalog-list">
42
+ ${presets.map((preset) => presetCard(preset, selected ? presetKey(selected) : "")).join("") || emptyState(t("noPresets"))}
43
+ </div>
44
+ </div>
45
+ <div class="preset-detail-workspace stack">
46
+ ${presetDetailPanel(selected)}
47
+ ${presetLayerStackPanel(selected)}
48
+ </div>
49
+ <aside class="preset-context-actions stack">
50
+ ${presetActionPanel(selected)}
51
+ ${presetImportPanel()}
52
+ ${presetRestorePanel()}
53
+ ${presetSummaryPanel(catalog)}
54
+ </aside>
55
+ </section>
56
+ </div>`;
57
+ }
58
+
59
+ function ensurePresetState() {
60
+ const presets = bundle.presetCatalog?.presets || [];
61
+ if (!state.selectedPresetKey && state.selectedPresetId) {
62
+ const legacySelection = presets.find((preset) => preset.id === state.selectedPresetId);
63
+ if (legacySelection) state.selectedPresetKey = presetKey(legacySelection);
64
+ }
65
+ if (!state.selectedPresetKey && presets[0]) {
66
+ state.selectedPresetKey = presetKey(presets[0]);
67
+ state.presetUninstallConfirm = "";
68
+ }
69
+ if (state.selectedPresetKey && !presets.some((preset) => presetKey(preset) === state.selectedPresetKey) && presets[0]) {
70
+ state.selectedPresetKey = presetKey(presets[0]);
71
+ state.presetUninstallConfirm = "";
72
+ }
73
+ }
74
+
75
+ function presetSourceOptions() {
76
+ return [
77
+ ["all", t("allPresets")],
78
+ ["project", t("presetSourceProject")],
79
+ ["user", t("presetSourceUser")],
80
+ ["builtin", t("presetSourceBuiltin")],
81
+ ];
82
+ }
83
+
84
+ function presetSourceButton([source, labelText]) {
85
+ const active = state.presetSourceFilter === source;
86
+ const count = source === "all" ? (bundle.presetCatalog?.summary?.total || 0) : (bundle.presetCatalog?.summary?.[source] || 0);
87
+ return `<button type="button" class="${active ? "active" : ""}" data-preset-source-filter="${escapeAttr(source)}" role="tab" aria-selected="${active ? "true" : "false"}">
88
+ <span>${escapeHtml(labelText)}</span>
89
+ <strong>${count}</strong>
90
+ </button>`;
91
+ }
92
+
93
+ function filteredPresets() {
94
+ const query = String(state.presetQuery || "").trim().toLowerCase();
95
+ return (bundle.presetCatalog?.presets || []).filter((preset) => {
96
+ if (state.presetSourceFilter !== "all" && preset.source !== state.presetSourceFilter) return false;
97
+ return presetMatchesQuery(preset, query);
98
+ });
99
+ }
100
+
101
+ function presetMatchesQuery(preset, query = state.presetQuery) {
102
+ const normalizedQuery = String(query || "").trim().toLowerCase();
103
+ if (!normalizedQuery) return true;
104
+ return [
105
+ preset.id,
106
+ preset.source,
107
+ preset.purpose,
108
+ preset.taskKind,
109
+ preset.manifestPath,
110
+ preset.version,
111
+ ...(preset.compatibleBudgets || []),
112
+ ].some((value) => String(value || "").toLowerCase().includes(normalizedQuery));
113
+ }
114
+
115
+ function syncVisiblePresetSelection(visiblePresets) {
116
+ if (!visiblePresets.length) {
117
+ state.selectedPresetKey = "";
118
+ state.presetUninstallConfirm = "";
119
+ return;
120
+ }
121
+ if (!visiblePresets.some((preset) => presetKey(preset) === state.selectedPresetKey)) {
122
+ state.selectedPresetKey = presetKey(visiblePresets[0]);
123
+ state.presetUninstallConfirm = "";
124
+ }
125
+ }
126
+
127
+ function selectedPreset(visiblePresets = filteredPresets()) {
128
+ return visiblePresets.find((preset) => presetKey(preset) === state.selectedPresetKey) || visiblePresets[0] || null;
129
+ }
130
+
131
+ function presetCard(preset, selectedId) {
132
+ const key = presetKey(preset);
133
+ const selected = key === selectedId;
134
+ return `<article class="preset-card ${selected ? "active" : ""} ${preset.effective ? "effective" : "shadowed"}">
135
+ <div class="preset-card-topline">
136
+ <button type="button" class="preset-card-select" data-preset-select="${escapeAttr(key)}" aria-pressed="${selected ? "true" : "false"}">
137
+ <span class="card-id">${escapeHtml(preset.id)}</span>
138
+ </button>
139
+ <div class="preset-card-tools">
140
+ ${presetSourceBadge(preset.source)}
141
+ ${presetStatusBadge(preset)}
142
+ <button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}" title="${escapeAttr(t("copyPresetId"))}">${t("copyIdShort")}</button>
143
+ </div>
144
+ </div>
145
+ <button type="button" class="preset-card-body" data-preset-select="${escapeAttr(key)}">
146
+ <span>${escapeHtml(preset.purpose || t("none"))}</span>
147
+ </button>
148
+ <div class="preset-card-meta">
149
+ <span>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(preset))}</span>
150
+ <span>${t("taskKind")}: ${escapeHtml(preset.taskKind || t("none"))}</span>
151
+ <span>${t("budgets")}: ${escapeHtml((preset.compatibleBudgets || []).join(", ") || t("none"))}</span>
152
+ </div>
153
+ <code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
154
+ </article>`;
155
+ }
156
+
157
+ function presetKey(preset) {
158
+ return preset?.key || `${preset?.source || "unknown"}:${preset?.id || ""}`;
159
+ }
160
+
161
+ function presetSourceRank(source) {
162
+ return { project: 1, user: 2, builtin: 3 }[source] || 9;
163
+ }
164
+
165
+ function presetLayersForId(id) {
166
+ return (bundle.presetCatalog?.presets || [])
167
+ .filter((preset) => preset.id === id)
168
+ .sort((a, b) => presetSourceRank(a.source) - presetSourceRank(b.source));
169
+ }
170
+
171
+ function syncPresetUninstallScope(preset) {
172
+ if (preset && ["project", "user"].includes(preset.source)) state.presetUninstallScope = preset.source;
173
+ }
174
+
175
+ function presetPriorityStep(source, index) {
176
+ return `<div class="preset-priority-step">
177
+ <span>${index}</span>
178
+ <strong>${escapeHtml(t(`presetSource_${source}`) || source)}</strong>
179
+ </div>`;
180
+ }
181
+
182
+ function presetSourceBadge(source) {
183
+ const normalized = String(source || "unknown");
184
+ return `<span class="tag preset-source-badge ${escapeAttr(normalized)}">${escapeHtml(t(`presetSource_${normalized}`) || normalized)}</span>`;
185
+ }
186
+
187
+ function presetStatusBadge(preset) {
188
+ return `<span class="tag ${preset.effective ? "pass" : "warn"}">${escapeHtml(preset.effective ? t("presetEffective") : t("presetShadowed"))}</span>`;
189
+ }
190
+
191
+ function formatPresetVersion(preset) {
192
+ return preset?.version ?? t("none");
193
+ }
194
+
195
+ function presetSummaryPanel(catalog) {
196
+ const roots = catalog.roots || [];
197
+ return `<section class="side-panel preset-summary-panel">
198
+ <h3>${t("presetSources")}</h3>
199
+ <p class="preset-helper">${t("presetSourcesHint")}</p>
200
+ <div class="metrics-grid compact">
201
+ ${metric(t("presetSourceProject"), catalog.summary?.project || 0)}
202
+ ${metric(t("presetSourceUser"), catalog.summary?.user || 0)}
203
+ ${metric(t("presetSourceBuiltin"), catalog.summary?.builtin || 0)}
204
+ </div>
205
+ <div class="preset-roots">
206
+ ${roots.map((root) => `<div><strong>${escapeHtml(t(`presetSource_${root.source}`) || root.source)}</strong><code>${escapeHtml(root.path || "")}</code></div>`).join("")}
207
+ </div>
208
+ </section>`;
209
+ }
210
+
211
+ function presetDetailPanel(preset) {
212
+ if (!preset) return `<section class="flow-panel preset-detail-panel">${emptyState(t("noPresets"))}</section>`;
213
+ const inspectCommand = `harness preset inspect ${preset.id} --json .`;
214
+ const checkCommand = `harness preset check ${preset.id} --json .`;
215
+ const commandRows = preset.effective
216
+ ? `${presetCommandRow(inspectCommand)}${presetCommandRow(checkCommand)}`
217
+ : `<div class="preset-command-warning">${escapeHtml(t("presetCommandsEffectiveOnly"))}</div>`;
218
+ return `<section class="flow-panel preset-detail-panel">
219
+ <div class="preset-detail-hero">
220
+ <div>
221
+ <div class="preset-detail-title-row">
222
+ <h3>${escapeHtml(preset.id)}</h3>
223
+ <button type="button" class="copy-inline" data-copy-preset-id="${escapeAttr(preset.id)}">${t("copyPresetId")}</button>
224
+ </div>
225
+ <p>${escapeHtml(preset.purpose || "")}</p>
226
+ </div>
227
+ <div class="preset-detail-badges">
228
+ ${presetSourceBadge(preset.source)}
229
+ ${presetStatusBadge(preset)}
230
+ </div>
231
+ </div>
232
+ <dl class="preset-detail-list">
233
+ ${presetDetailRow(t("manifestVersion"), formatPresetVersion(preset))}
234
+ ${presetDetailRow(t("source"), t(`presetSource_${preset.source}`) || preset.source)}
235
+ ${presetDetailRow(t("status"), preset.effective ? t("presetEffective") : t("presetShadowed"))}
236
+ ${presetDetailRow(t("taskKind"), preset.taskKind || t("none"))}
237
+ ${presetDetailRow(t("budgets"), (preset.compatibleBudgets || []).join(", ") || t("none"))}
238
+ ${presetDetailRow(t("inputs"), preset.inputCount || 0)}
239
+ ${presetDetailRow(t("references"), preset.referenceCount || 0)}
240
+ ${presetDetailRow(t("artifacts"), preset.artifactCount || 0)}
241
+ ${presetDetailRow(t("writeScopes"), preset.writeScopeCount || 0)}
242
+ ${presetDetailRow(t("requiredReads"), preset.requiredReadCount || 0)}
243
+ </dl>
244
+ <div class="preset-path-block">
245
+ <span>${t("manifestPath")}</span>
246
+ <code class="preset-manifest-path">${escapeHtml(preset.manifestPath || "")}</code>
247
+ </div>
248
+ <div class="preset-command-list">
249
+ ${commandRows}
250
+ </div>
251
+ </section>`;
252
+ }
253
+
254
+ function presetDetailRow(labelText, value) {
255
+ return `<div><dt>${escapeHtml(labelText)}</dt><dd>${escapeHtml(String(value ?? ""))}</dd></div>`;
256
+ }
257
+
258
+ function presetCommandRow(command) {
259
+ return `<div class="preset-command-row">
260
+ <code>${escapeHtml(command)}</code>
261
+ <button type="button" class="copy-inline" data-copy-preset-command="${escapeAttr(command)}">${t("copyCommand")}</button>
262
+ </div>`;
263
+ }
264
+
265
+ function presetLayerStackPanel(preset) {
266
+ if (!preset) return "";
267
+ const layers = presetLayersForId(preset.id);
268
+ return `<section class="flow-panel preset-layer-panel">
269
+ <div class="preset-panel-heading">
270
+ <div>
271
+ <h3>${t("presetLayerStack")}</h3>
272
+ <p>${t("presetLayerStackHint")}</p>
273
+ </div>
274
+ </div>
275
+ <div class="preset-layer-list">
276
+ ${layers.map((layer) => `<button type="button" class="preset-layer-row ${presetKey(layer) === presetKey(preset) ? "active" : ""}" data-preset-select="${escapeAttr(presetKey(layer))}">
277
+ <span class="preset-layer-rank">${presetSourceRank(layer.source)}</span>
278
+ <span>
279
+ <strong>${escapeHtml(t(`presetSource_${layer.source}`) || layer.source)}</strong>
280
+ <small>${t("manifestVersion")}: ${escapeHtml(formatPresetVersion(layer))}</small>
281
+ </span>
282
+ ${presetStatusBadge(layer)}
283
+ </button>`).join("")}
284
+ </div>
285
+ </section>`;
286
+ }
287
+
288
+ function presetActionPanel(preset) {
289
+ const staticNote = canUseWorkbenchAction("preset-install") ? "" : `<p class="lesson-action-note">${escapeHtml(t("presetWorkbenchRequired"))}</p>`;
290
+ const lockedUninstallScope = preset && ["project", "user"].includes(preset.source) ? preset.source : "";
291
+ const confirmMatches = Boolean(preset && state.presetUninstallConfirm.trim() === preset.id);
292
+ const canCheck = canUseWorkbenchAction("preset-check") && preset && preset.effective;
293
+ const canUninstall = canUseWorkbenchAction("preset-uninstall") && preset && preset.source !== "builtin" && confirmMatches;
294
+ return `<section class="side-panel preset-action-panel">
295
+ <div class="preset-panel-heading">
296
+ <div>
297
+ <h3>${t("presetContextActions")}</h3>
298
+ <p>${preset ? escapeHtml(preset.id) : t("noPresets")}</p>
299
+ </div>
300
+ </div>
301
+ ${staticNote}
302
+ ${presetActionResult()}
303
+ <div class="preset-action-group">
304
+ <h4>${t("presetCheck")}</h4>
305
+ <p>${preset?.effective ? t("presetCheckHint") : t("presetShadowedActionHint")}</p>
306
+ <button data-preset-check="${escapeAttr(preset?.id || "")}" ${canCheck ? "" : "disabled"}>${t("presetCheckSelected")}</button>
307
+ </div>
308
+ <div class="preset-action-group danger">
309
+ <h4>${t("presetUninstallSelected")}</h4>
310
+ <p>${preset?.source === "builtin" ? t("presetBuiltinImmutable") : t("presetUninstallHint")}</p>
311
+ <label>${t("scope")}<select data-preset-uninstall-scope ${lockedUninstallScope ? "disabled" : ""}>
312
+ ${presetScopeOptions(lockedUninstallScope || state.presetUninstallScope)}
313
+ </select></label>
314
+ <div class="preset-confirm-row">
315
+ <label>${t("confirmPresetId")}<input data-preset-uninstall-confirm value="${escapeAttr(state.presetUninstallConfirm)}" placeholder="${escapeAttr(preset?.id || "")}"></label>
316
+ <button type="button" data-preset-fill-confirm="${escapeAttr(preset?.id || "")}" ${preset && preset.source !== "builtin" ? "" : "disabled"}>${t("useSelectedId")}</button>
317
+ </div>
318
+ ${preset && preset.source !== "builtin" && !confirmMatches ? `<p class="preset-confirm-warning">${escapeHtml(t("presetConfirmRequired"))}</p>` : ""}
319
+ <button data-preset-uninstall="${escapeAttr(preset?.id || "")}" ${canUninstall ? "" : "disabled"}>${t("presetUninstallSelected")}</button>
320
+ </div>
321
+ </section>`;
322
+ }
323
+
324
+ function presetImportPanel() {
325
+ return `<section class="side-panel preset-action-panel">
326
+ <div class="preset-panel-heading">
327
+ <div>
328
+ <h3>${t("presetImportTitle")}</h3>
329
+ <p>${t("presetImportHint")}</p>
330
+ </div>
331
+ </div>
332
+ <div class="preset-action-group">
333
+ <label>${t("source")}<input data-preset-install-source value="${escapeAttr(state.presetInstallSource)}" placeholder="${escapeAttr(t("presetInstallSourcePlaceholder"))}"></label>
334
+ <label>${t("scope")}<select data-preset-install-scope>
335
+ ${presetScopeOptions(state.presetInstallScope)}
336
+ </select></label>
337
+ <label class="check-row"><input type="checkbox" data-preset-install-force ${state.presetInstallForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
338
+ <button data-preset-install ${canUseWorkbenchAction("preset-install") ? "" : "disabled"}>${t("presetInstall")}</button>
339
+ </div>
340
+ </section>`;
341
+ }
342
+
343
+ function presetRestorePanel() {
344
+ return `<section class="side-panel preset-action-panel">
345
+ <div class="preset-panel-heading">
346
+ <div>
347
+ <h3>${t("presetRestoreBundled")}</h3>
348
+ <p>${t("presetRestoreBundledHint")}</p>
349
+ </div>
350
+ </div>
351
+ <div class="preset-action-group">
352
+ <label>${t("scope")}<select data-preset-seed-scope>
353
+ ${presetScopeOptions(state.presetSeedScope)}
354
+ </select></label>
355
+ <label class="check-row"><input type="checkbox" data-preset-seed-force ${state.presetSeedForce ? "checked" : ""}> ${t("forceOverwrite")}</label>
356
+ <button data-preset-seed ${canUseWorkbenchAction("preset-seed") ? "" : "disabled"}>${t("presetRestoreBundled")}</button>
357
+ </div>
358
+ </section>`;
359
+ }
360
+
361
+ function presetScopeOptions(current) {
362
+ return [["project", t("presetSourceProject")], ["user", t("presetSourceUser")]]
363
+ .map(([value, labelText]) => `<option value="${value}" ${current === value ? "selected" : ""}>${escapeHtml(labelText)}</option>`)
364
+ .join("");
365
+ }
366
+
367
+ function presetActionResult() {
368
+ const result = state.presetActionResult;
369
+ if (!result) return "";
370
+ const klass = result.ok ? "success" : "failed";
371
+ return `<div class="workbench-action-result ${klass}">
372
+ <strong>${escapeHtml(result.title || "")}</strong>
373
+ <span>${escapeHtml(result.message || "")}</span>
374
+ </div>`;
375
+ }
@@ -28,7 +28,9 @@ function tag(value) {
28
28
  }
29
29
 
30
30
  function label(value) {
31
- return t(`state_${value}`) || String(value || "unknown").replaceAll("_", " ");
31
+ const key = `state_${value}`;
32
+ const translated = t(key);
33
+ return translated === key ? String(value || "unknown").replaceAll("_", " ") : translated;
32
34
  }
33
35
 
34
36
  function list(items = []) {
@@ -49,6 +49,68 @@ function bind() {
49
49
  state.warningPage = 1;
50
50
  app();
51
51
  }));
52
+ document.querySelectorAll("[data-preset-search]").forEach((input) => input.addEventListener("input", () => {
53
+ state.presetQuery = input.value;
54
+ app();
55
+ }));
56
+ document.querySelectorAll("[data-preset-source-filter]").forEach((button) => button.addEventListener("click", () => {
57
+ state.presetSourceFilter = button.dataset.presetSourceFilter || "all";
58
+ state.selectedPresetKey = "";
59
+ state.presetUninstallConfirm = "";
60
+ app();
61
+ }));
62
+ document.querySelectorAll("[data-preset-select]").forEach((button) => button.addEventListener("click", () => {
63
+ state.selectedPresetKey = button.dataset.presetSelect || "";
64
+ state.selectedPresetId = "";
65
+ const selectedPreset = (bundle.presetCatalog?.presets || []).find((preset) => presetKey(preset) === state.selectedPresetKey);
66
+ if (selectedPreset && state.presetSourceFilter !== "all" && selectedPreset.source !== state.presetSourceFilter) {
67
+ state.presetSourceFilter = selectedPreset.source;
68
+ }
69
+ if (selectedPreset && !presetMatchesQuery(selectedPreset)) state.presetQuery = "";
70
+ if (selectedPreset && ["project", "user"].includes(selectedPreset.source)) state.presetUninstallScope = selectedPreset.source;
71
+ state.presetUninstallConfirm = "";
72
+ app();
73
+ }));
74
+ document.querySelectorAll("[data-preset-install-source]").forEach((input) => input.addEventListener("input", () => {
75
+ state.presetInstallSource = input.value;
76
+ }));
77
+ document.querySelectorAll("[data-preset-install-scope]").forEach((select) => select.addEventListener("change", () => {
78
+ state.presetInstallScope = select.value || "project";
79
+ }));
80
+ document.querySelectorAll("[data-preset-install-force]").forEach((input) => input.addEventListener("change", () => {
81
+ state.presetInstallForce = input.checked;
82
+ }));
83
+ document.querySelectorAll("[data-preset-seed-scope]").forEach((select) => select.addEventListener("change", () => {
84
+ state.presetSeedScope = select.value || "project";
85
+ }));
86
+ document.querySelectorAll("[data-preset-seed-force]").forEach((input) => input.addEventListener("change", () => {
87
+ state.presetSeedForce = input.checked;
88
+ }));
89
+ document.querySelectorAll("[data-preset-uninstall-scope]").forEach((select) => select.addEventListener("change", () => {
90
+ state.presetUninstallScope = select.value || "project";
91
+ }));
92
+ document.querySelectorAll("[data-preset-uninstall-confirm]").forEach((input) => input.addEventListener("input", () => {
93
+ state.presetUninstallConfirm = input.value;
94
+ }));
95
+ document.querySelectorAll("[data-preset-fill-confirm]").forEach((button) => button.addEventListener("click", () => {
96
+ state.presetUninstallConfirm = button.dataset.presetFillConfirm || "";
97
+ app();
98
+ }));
99
+ document.querySelectorAll("[data-preset-check]").forEach((button) => button.addEventListener("click", () => runPresetAction("check", { id: button.dataset.presetCheck || "" })));
100
+ document.querySelectorAll("[data-preset-install]").forEach((button) => button.addEventListener("click", () => runPresetAction("install", {
101
+ source: state.presetInstallSource,
102
+ scope: state.presetInstallScope,
103
+ force: state.presetInstallForce,
104
+ })));
105
+ document.querySelectorAll("[data-preset-seed]").forEach((button) => button.addEventListener("click", () => runPresetAction("seed", {
106
+ scope: state.presetSeedScope,
107
+ force: state.presetSeedForce,
108
+ })));
109
+ document.querySelectorAll("[data-preset-uninstall]").forEach((button) => button.addEventListener("click", () => runPresetAction("uninstall", {
110
+ id: button.dataset.presetUninstall || "",
111
+ scope: state.presetUninstallScope,
112
+ confirmText: state.presetUninstallConfirm,
113
+ })));
52
114
  document.querySelectorAll("[data-review-queue-tab]").forEach((button) => button.addEventListener("click", () => {
53
115
  state.reviewQueueTab = button.dataset.reviewQueueTab || "review";
54
116
  state.reviewQueuePage = 1;
@@ -95,6 +157,7 @@ function bind() {
95
157
  openDrawer(taskId);
96
158
  }));
97
159
  bindCopyTaskNameButtons(document);
160
+ bindPresetCopyButtons(document);
98
161
  bindRepairPromptButtons(document);
99
162
  bindLessonSedimentationButtons(document);
100
163
  document.querySelectorAll("[data-open-lesson-drawer]").forEach((el) => el.addEventListener("click", (e) => {
@@ -177,6 +240,45 @@ async function completeReviewFromDashboard(taskId) {
177
240
  }
178
241
  }
179
242
 
243
+ async function runPresetAction(action, body) {
244
+ state.presetActionResult = { ok: true, title: t("presetActionRunning"), message: action };
245
+ app();
246
+ try {
247
+ const response = await fetch(`/api/presets/${action}`, {
248
+ method: "POST",
249
+ headers: {
250
+ "content-type": "application/json",
251
+ "x-harness-csrf": state.runtime?.csrfToken || "",
252
+ },
253
+ body: JSON.stringify(body),
254
+ });
255
+ const payload = await response.json();
256
+ if (!response.ok) throw payload;
257
+ state.presetActionResult = {
258
+ ok: true,
259
+ title: t("presetActionSuccess"),
260
+ message: presetActionMessage(action, payload),
261
+ };
262
+ app();
263
+ if (["install", "seed", "uninstall"].includes(action)) setTimeout(() => window.location.reload(), 650);
264
+ } catch (error) {
265
+ state.presetActionResult = {
266
+ ok: false,
267
+ title: t("presetActionFailed"),
268
+ message: error?.error || error?.message || String(error || action),
269
+ };
270
+ app();
271
+ }
272
+ }
273
+
274
+ function presetActionMessage(action, payload) {
275
+ if (action === "check") return `${payload.id || ""} ${payload.status || ""}`.trim();
276
+ if (action === "install") return `${payload.id || ""} -> ${payload.scope || ""}`.trim();
277
+ if (action === "seed") return `${payload.created || 0} ${t("created")} · ${payload.skipped || 0} ${t("skipped")}`;
278
+ if (action === "uninstall") return `${payload.id || ""} ${payload.removed ? t("removed") : t("notInstalled")}`.trim();
279
+ return action;
280
+ }
281
+
180
282
  function renderDrawerContent(taskId) {
181
283
  const task = (bundle.status?.tasks || []).find((item) => item.id === taskId);
182
284
  if (!task) return `<div class="empty">${t("taskNotFound")}</div>`;
@@ -256,6 +358,35 @@ function bindCopyTaskNameButtons(root) {
256
358
  }));
257
359
  }
258
360
 
361
+ function bindPresetCopyButtons(root) {
362
+ root.querySelectorAll("[data-copy-preset-id]").forEach((button) => button.addEventListener("click", async (event) => {
363
+ event.preventDefault();
364
+ event.stopPropagation();
365
+ const presetId = button.dataset.copyPresetId || "";
366
+ const defaultText = button.textContent;
367
+ try {
368
+ await copyText(presetId);
369
+ button.textContent = t("copyTaskNameSuccess");
370
+ } catch {
371
+ button.textContent = t("copyTaskNameFailed");
372
+ }
373
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
374
+ }));
375
+ root.querySelectorAll("[data-copy-preset-command]").forEach((button) => button.addEventListener("click", async (event) => {
376
+ event.preventDefault();
377
+ event.stopPropagation();
378
+ const command = button.dataset.copyPresetCommand || "";
379
+ const defaultText = button.textContent;
380
+ try {
381
+ await copyText(command);
382
+ button.textContent = t("copyTaskNameSuccess");
383
+ } catch {
384
+ button.textContent = t("copyTaskNameFailed");
385
+ }
386
+ setTimeout(() => { button.textContent = defaultText; }, 1200);
387
+ }));
388
+ }
389
+
259
390
  function bindRepairPromptButtons(root) {
260
391
  root.querySelectorAll("[data-copy-repair-prompt]").forEach((button) => button.addEventListener("click", async (event) => {
261
392
  event.preventDefault();