coding-agent-harness 1.0.2 → 1.0.5

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 (219) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +244 -87
  6. package/README.zh-CN.md +77 -35
  7. package/SKILL.md +32 -24
  8. package/docs-release/README.md +9 -5
  9. package/docs-release/architecture/overview.md +17 -5
  10. package/docs-release/architecture/overview.zh-CN.md +9 -5
  11. package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
  12. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  13. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  14. package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
  15. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  16. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
  17. package/docs-release/architecture/system-explainer/README.md +67 -0
  18. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
  19. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  20. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  21. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
  22. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  23. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
  24. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  25. package/docs-release/assets/dashboard-overview.png +0 -0
  26. package/docs-release/guides/agent-installation.en-US.md +39 -15
  27. package/docs-release/guides/agent-installation.md +43 -16
  28. package/docs-release/guides/contributing.md +100 -0
  29. package/docs-release/guides/contributing.zh-CN.md +99 -0
  30. package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
  31. package/docs-release/guides/document-audience-and-surfaces.md +3 -2
  32. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
  33. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
  34. package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
  35. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
  36. package/docs-release/guides/migration-playbook.en-US.md +14 -15
  37. package/docs-release/guides/migration-playbook.md +14 -15
  38. package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
  39. package/docs-release/guides/parent-control-repository-pattern.md +7 -5
  40. package/docs-release/guides/preset-development.md +238 -0
  41. package/docs-release/guides/repository-operating-models.en-US.md +5 -4
  42. package/docs-release/guides/repository-operating-models.md +5 -4
  43. package/docs-release/guides/task-state-machine.en-US.md +224 -0
  44. package/docs-release/guides/task-state-machine.md +231 -0
  45. package/docs-release/intl/en-US.md +1 -1
  46. package/docs-release/intl/zh-CN.md +1 -1
  47. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
  48. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  49. package/package.json +10 -4
  50. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  51. package/presets/legacy-migration/preset.yaml +134 -0
  52. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  53. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  54. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  55. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  56. package/presets/legacy-migration/templates/review.seed.md +12 -0
  57. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  58. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  59. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  60. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  61. package/presets/lesson-sedimentation/preset.yaml +23 -0
  62. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  63. package/presets/module/preset.yaml +25 -0
  64. package/presets/module/templates/execution_strategy.append.md +8 -0
  65. package/presets/module/templates/task_plan.append.md +17 -0
  66. package/presets/standard-task/preset.yaml +31 -0
  67. package/presets/standard-task/templates/task_plan.append.md +7 -0
  68. package/references/adversarial-review-standard.md +2 -2
  69. package/references/agents-md-pattern.md +2 -2
  70. package/references/delivery-operating-model-standard.md +3 -3
  71. package/references/docs-directory-standard.md +6 -7
  72. package/references/harness-ledger.md +53 -96
  73. package/references/lessons-governance.md +88 -93
  74. package/references/module-parallel-standard.md +14 -14
  75. package/references/planning-loop.md +12 -6
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +11 -2
  78. package/references/review-routing-standard.md +7 -1
  79. package/references/ssot-governance.md +67 -59
  80. package/references/taskr-gap-analysis.md +600 -0
  81. package/references/walkthrough-closeout.md +7 -7
  82. package/scripts/check-harness.mjs +40 -301
  83. package/scripts/commands/dashboard-command.mjs +67 -0
  84. package/scripts/commands/migration-command.mjs +126 -0
  85. package/scripts/commands/preset-command.mjs +73 -0
  86. package/scripts/commands/task-command.mjs +328 -0
  87. package/scripts/harness.mjs +59 -260
  88. package/scripts/lib/capability-registry.mjs +82 -28
  89. package/scripts/lib/check-module-parallel.mjs +230 -0
  90. package/scripts/lib/check-profiles.mjs +90 -228
  91. package/scripts/lib/check-task-contracts.mjs +55 -0
  92. package/scripts/lib/core-shared.mjs +65 -2
  93. package/scripts/lib/dashboard-data.mjs +155 -24
  94. package/scripts/lib/dashboard-workbench.mjs +131 -12
  95. package/scripts/lib/dashboard-writer.mjs +20 -4
  96. package/scripts/lib/git-status-summary.mjs +46 -0
  97. package/scripts/lib/governance-index-generator.mjs +174 -0
  98. package/scripts/lib/governance-sync.mjs +611 -0
  99. package/scripts/lib/governance-table-boundary.mjs +175 -0
  100. package/scripts/lib/harness-core.mjs +6 -0
  101. package/scripts/lib/lesson-maintenance.mjs +36 -29
  102. package/scripts/lib/markdown-utils.mjs +33 -0
  103. package/scripts/lib/migration-planner.mjs +4 -6
  104. package/scripts/lib/migration-support.mjs +1 -1
  105. package/scripts/lib/phase-kind.mjs +50 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +494 -0
  108. package/scripts/lib/preset-registry.mjs +776 -0
  109. package/scripts/lib/preset-resource-contracts.mjs +83 -0
  110. package/scripts/lib/review-confirm-git-gate.mjs +248 -0
  111. package/scripts/lib/status-builder.mjs +88 -0
  112. package/scripts/lib/status-dashboard-renderer.mjs +105 -0
  113. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  114. package/scripts/lib/task-audit-metadata.mjs +385 -0
  115. package/scripts/lib/task-audit-migration.mjs +350 -0
  116. package/scripts/lib/task-completion-consistency.mjs +26 -0
  117. package/scripts/lib/task-index.mjs +93 -0
  118. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  119. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  120. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
  121. package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
  122. package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
  123. package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
  124. package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
  125. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
  126. package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
  127. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  128. package/scripts/lib/task-lifecycle.mjs +338 -477
  129. package/scripts/lib/task-metadata.mjs +118 -0
  130. package/scripts/lib/task-review-model.mjs +455 -0
  131. package/scripts/lib/task-scanner.mjs +193 -372
  132. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  133. package/scripts/postinstall.mjs +14 -0
  134. package/skills/preset-creator/SKILL.md +179 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  139. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  140. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  141. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  142. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  143. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  144. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  145. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  146. package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
  147. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  148. package/templates/AGENTS.md.template +24 -18
  149. package/templates/dashboard/assets/app-src/00-state.js +13 -0
  150. package/templates/dashboard/assets/app-src/10-router.js +5 -1
  151. package/templates/dashboard/assets/app-src/20-overview.js +18 -8
  152. package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
  153. package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
  154. package/templates/dashboard/assets/app-src/45-review.js +241 -22
  155. package/templates/dashboard/assets/app-src/50-migration.js +24 -10
  156. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  157. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  158. package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
  159. package/templates/dashboard/assets/app.css +1501 -376
  160. package/templates/dashboard/assets/app.css.manifest.json +10 -0
  161. package/templates/dashboard/assets/app.js +1240 -101
  162. package/templates/dashboard/assets/app.manifest.json +2 -0
  163. package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
  164. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  165. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  166. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  167. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  168. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
  169. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  170. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  171. package/templates/dashboard/assets/i18n.js +263 -23
  172. package/templates/ledger/Harness-Ledger.md +13 -25
  173. package/templates/lessons/lesson-arch-process-change.md +1 -1
  174. package/templates/lessons/lesson-new-doc.md +1 -1
  175. package/templates/lessons/lesson-ref-change.md +1 -1
  176. package/templates/planning/INDEX.md +87 -0
  177. package/templates/planning/brief.md +1 -1
  178. package/templates/planning/execution_strategy.md +31 -0
  179. package/templates/planning/lesson_candidates.md +18 -6
  180. package/templates/planning/module_session_prompt.md +1 -0
  181. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  182. package/templates/planning/optional/references/INDEX.md +3 -3
  183. package/templates/planning/review.md +41 -0
  184. package/templates/planning/task_plan.md +5 -21
  185. package/templates/planning/visual_map.md +13 -9
  186. package/templates/planning/visual_map.simple.md +52 -0
  187. package/templates/reference/execution-workflow-standard.md +31 -3
  188. package/templates/reference/pull-request-standard.md +80 -0
  189. package/templates/reference/repo-governance-standard.md +7 -6
  190. package/templates/reference/review-routing-standard.md +6 -0
  191. package/templates/reference/walkthrough-standard.md +2 -1
  192. package/templates/verifier/verifier-output.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +25 -19
  194. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  195. package/templates-zh-CN/planning/INDEX.md +87 -0
  196. package/templates-zh-CN/planning/brief.md +1 -1
  197. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  198. package/templates-zh-CN/planning/lesson_candidates.md +18 -6
  199. package/templates-zh-CN/planning/module_session_prompt.md +1 -0
  200. package/templates-zh-CN/planning/review.md +41 -1
  201. package/templates-zh-CN/planning/task_plan.md +4 -44
  202. package/templates-zh-CN/planning/visual_map.md +14 -7
  203. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  204. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  205. package/templates-zh-CN/reference/docs-library-standard.md +1 -1
  206. package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
  207. package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
  208. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  209. package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
  210. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  211. package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
  212. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
  213. package/docs-release/assets/dashboard-overview-en.png +0 -0
  214. package/scripts/smoke-dashboard.mjs +0 -92
  215. package/scripts/test-harness.mjs +0 -1395
  216. package/templates/ssot/Feature-SSoT.md +0 -43
  217. package/templates/ssot/Lessons-SSoT.md +0 -44
  218. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  219. package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
@@ -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 = []) {