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.
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +2 -2
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +96 -4
- package/README.zh-CN.md +75 -4
- package/SKILL.md +52 -51
- package/dist/build-dist.mjs +189 -0
- package/dist/check-dist-observation.mjs +428 -0
- package/dist/check-harness.mjs +489 -0
- package/dist/check-import-graph.mjs +511 -0
- package/dist/check-runtime-emit.mjs +304 -0
- package/dist/check-type-boundaries.mjs +139 -0
- package/dist/commands/dashboard-command.mjs +80 -0
- package/dist/commands/migration-command.mjs +152 -0
- package/dist/commands/preset-command.mjs +91 -0
- package/dist/commands/task-command.mjs +324 -0
- package/dist/harness.mjs +304 -0
- package/dist/lib/capability-registry.mjs +643 -0
- package/dist/lib/check-module-parallel.mjs +227 -0
- package/dist/lib/check-profiles.mjs +414 -0
- package/dist/lib/check-task-contracts.mjs +54 -0
- package/dist/lib/core-shared.mjs +254 -0
- package/dist/lib/dashboard-data.mjs +608 -0
- package/dist/lib/dashboard-workbench.mjs +334 -0
- package/dist/lib/dashboard-writer.mjs +200 -0
- package/dist/lib/git-status-summary.mjs +45 -0
- package/dist/lib/governance-index-generator.mjs +236 -0
- package/dist/lib/governance-sync.mjs +617 -0
- package/dist/lib/governance-table-boundary.mjs +161 -0
- package/{scripts → dist}/lib/harness-core.mjs +3 -0
- package/dist/lib/harness-paths.mjs +338 -0
- package/dist/lib/lesson-maintenance.mjs +139 -0
- package/dist/lib/markdown-utils.mjs +193 -0
- package/dist/lib/migration-planner.mjs +439 -0
- package/dist/lib/migration-support.mjs +317 -0
- package/dist/lib/phase-kind.mjs +46 -0
- package/dist/lib/preset-audit-contracts.mjs +40 -0
- package/dist/lib/preset-engine.mjs +516 -0
- package/dist/lib/preset-registry.mjs +831 -0
- package/dist/lib/preset-resource-contracts.mjs +83 -0
- package/dist/lib/review-confirm-git-gate.mjs +244 -0
- package/dist/lib/status-builder.mjs +87 -0
- package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
- package/dist/lib/structure-migration.mjs +404 -0
- package/dist/lib/subagent-authorization-audit.mjs +198 -0
- package/dist/lib/task-audit-metadata.mjs +376 -0
- package/dist/lib/task-audit-migration.mjs +355 -0
- package/dist/lib/task-completion-consistency.mjs +29 -0
- package/dist/lib/task-index.mjs +133 -0
- package/dist/lib/task-lesson-candidates.mjs +239 -0
- package/dist/lib/task-lesson-sedimentation.mjs +300 -0
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
- package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
- package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
- package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
- package/dist/lib/task-lifecycle/template-files.mjs +52 -0
- package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
- package/dist/lib/task-lifecycle.mjs +611 -0
- package/dist/lib/task-metadata.mjs +116 -0
- package/dist/lib/task-review-model.mjs +474 -0
- package/dist/lib/task-scanner.mjs +439 -0
- package/dist/lib/task-tombstone-commands.mjs +125 -0
- package/dist/postinstall.mjs +14 -0
- package/dist/run-built-tests.mjs +84 -0
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/overview.md +13 -13
- package/docs-release/architecture/overview.zh-CN.md +13 -13
- package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/guides/agent-installation.en-US.md +22 -15
- package/docs-release/guides/agent-installation.md +23 -15
- package/docs-release/guides/contributing.md +3 -3
- package/docs-release/guides/contributing.zh-CN.md +3 -3
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
- package/docs-release/guides/document-audience-and-surfaces.md +10 -10
- package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
- package/docs-release/guides/migration-playbook.en-US.md +63 -1
- package/docs-release/guides/migration-playbook.md +59 -1
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
- package/docs-release/guides/parent-control-repository-pattern.md +25 -25
- package/docs-release/guides/preset-development.md +28 -4
- package/docs-release/guides/repository-operating-models.en-US.md +21 -21
- package/docs-release/guides/repository-operating-models.md +21 -21
- package/docs-release/guides/task-state-machine.en-US.md +35 -18
- package/docs-release/guides/task-state-machine.md +35 -18
- package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
- package/examples/minimal-project/AGENTS.md +2 -2
- package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
- package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
- package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
- package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
- package/package.json +22 -13
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/standard-task/preset.yaml +2 -2
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +14 -14
- package/references/cadence-ledger.md +1 -1
- package/references/ci-cd-standard.md +1 -1
- package/references/delivery-operating-model-standard.md +4 -4
- package/references/docs-directory-standard.md +65 -159
- package/references/external-source-intake-standard.md +10 -10
- package/references/harness-ledger.md +6 -6
- package/references/legacy-12-phase-bootstrap.md +2 -2
- package/references/lessons-governance.md +15 -15
- package/references/long-running-task-standard.md +6 -6
- package/references/module-parallel-standard.md +34 -34
- package/references/planning-loop.md +6 -6
- package/references/project-onboarding-audit.md +4 -4
- package/references/regression-system.md +2 -2
- package/references/repo-governance-standard.md +4 -4
- package/references/review-routing-standard.md +1 -1
- package/references/ssot-governance.md +19 -19
- package/references/taskr-gap-analysis.md +5 -5
- package/references/walkthrough-closeout.md +14 -14
- package/references/worktree-parallel.md +3 -3
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
- package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
- package/templates/AGENTS.md.template +31 -29
- package/templates/architecture/README.md +4 -4
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +12 -0
- package/templates/dashboard/assets/app-src/10-router.js +3 -0
- package/templates/dashboard/assets/app-src/20-overview.js +13 -3
- package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
- package/templates/dashboard/assets/app-src/40-modules.js +1 -1
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
- package/templates/dashboard/assets/app.css +583 -0
- package/templates/dashboard/assets/app.css.manifest.json +1 -0
- package/templates/dashboard/assets/app.js +585 -11
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/i18n.js +144 -4
- package/templates/development/README.md +10 -10
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +2 -2
- package/templates/development/external-source-packs/README.md +4 -4
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +2 -2
- package/templates/integrations/event-contract.md +2 -2
- package/templates/integrations/third-party/vendor-template.md +2 -2
- package/templates/integrations/webhook-contract.md +2 -2
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/planning/INDEX.md +88 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/module_session_prompt.md +2 -1
- package/templates/planning/review.md +0 -18
- package/templates/planning/task_plan.md +5 -44
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/planning/walkthrough.md +47 -0
- package/templates/reference/docs-library-standard.md +8 -8
- package/templates/reference/execution-workflow-standard.md +29 -2
- package/templates/reference/external-source-intake-standard.md +15 -15
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/ssot/Module-Registry.md +1 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +31 -29
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +4 -4
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +10 -10
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +2 -2
- package/templates-zh-CN/development/external-source-packs/README.md +4 -4
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +2 -2
- package/templates-zh-CN/integrations/event-contract.md +2 -2
- package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
- package/templates-zh-CN/integrations/webhook-contract.md +2 -2
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/module_session_prompt.md +12 -11
- package/templates-zh-CN/planning/review.md +0 -18
- package/templates-zh-CN/planning/task_plan.md +3 -63
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- package/templates-zh-CN/planning/walkthrough.md +47 -0
- package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +28 -28
- package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
- package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
- package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +1 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
- package/templates-zh-CN/ssot/Module-Registry.md +3 -3
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
- package/tsconfig.dist.json +16 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtime.json +24 -0
- package/examples/minimal-project/.harness-capabilities.json +0 -8
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
- package/scripts/check-harness.mjs +0 -508
- package/scripts/commands/dashboard-command.mjs +0 -67
- package/scripts/commands/migration-command.mjs +0 -96
- package/scripts/commands/preset-command.mjs +0 -73
- package/scripts/commands/task-command.mjs +0 -327
- package/scripts/harness.mjs +0 -287
- package/scripts/lib/capability-registry.mjs +0 -591
- package/scripts/lib/check-module-parallel.mjs +0 -237
- package/scripts/lib/check-profiles.mjs +0 -418
- package/scripts/lib/check-task-contracts.mjs +0 -47
- package/scripts/lib/core-shared.mjs +0 -196
- package/scripts/lib/dashboard-data.mjs +0 -412
- package/scripts/lib/dashboard-workbench.mjs +0 -257
- package/scripts/lib/dashboard-writer.mjs +0 -198
- package/scripts/lib/git-status-summary.mjs +0 -46
- package/scripts/lib/governance-index-generator.mjs +0 -174
- package/scripts/lib/governance-sync.mjs +0 -514
- package/scripts/lib/governance-table-boundary.mjs +0 -175
- package/scripts/lib/lesson-maintenance.mjs +0 -152
- package/scripts/lib/markdown-utils.mjs +0 -158
- package/scripts/lib/migration-planner.mjs +0 -478
- package/scripts/lib/migration-support.mjs +0 -312
- package/scripts/lib/preset-audit-contracts.mjs +0 -37
- package/scripts/lib/preset-engine.mjs +0 -497
- package/scripts/lib/preset-registry.mjs +0 -627
- package/scripts/lib/preset-resource-contracts.mjs +0 -83
- package/scripts/lib/review-confirm-git-gate.mjs +0 -248
- package/scripts/lib/subagent-authorization-audit.mjs +0 -196
- package/scripts/lib/task-completion-consistency.mjs +0 -16
- package/scripts/lib/task-index.mjs +0 -93
- package/scripts/lib/task-lesson-candidates.mjs +0 -242
- package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
- package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
- package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
- package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
- package/scripts/lib/task-lifecycle.mjs +0 -649
- package/scripts/lib/task-review-model.mjs +0 -469
- package/scripts/lib/task-scanner.mjs +0 -576
- package/scripts/lib/task-tombstone-commands.mjs +0 -140
- package/scripts/postinstall.mjs +0 -14
- package/templates/walkthrough/Closeout-SSoT.md +0 -43
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
- /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
- /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 ${
|
|
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
|
-
${
|
|
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:
|
|
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
|
-
|
|
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();
|