coding-agent-harness 1.0.7 → 1.1.0
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 +33 -0
- package/CONTRIBUTING.md +9 -5
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +32 -6
- package/dist/check-dist-observation.mjs +73 -28
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +88 -0
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +67 -8
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +65 -4
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +111 -53
- package/dist/harness.mjs +6 -303
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +5 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +68 -29
- package/dist/lib/preset-registry.mjs +374 -72
- package/dist/lib/preset-runner.mjs +560 -0
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +4 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +117 -159
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +38 -17
- package/dist/lib/task-scanner.mjs +75 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +187 -18
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +2 -1
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +19 -11
- package/postinstall.mjs +37 -0
- 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/release-closeout/checks/check-release-package.mjs +29 -0
- package/presets/release-closeout/preset.yaml +100 -0
- package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
- package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
- package/presets/release-closeout/templates/findings.seed.md +5 -0
- package/presets/release-closeout/templates/review.seed.md +3 -0
- package/presets/release-closeout/templates/task_plan.append.md +24 -0
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/pull-request-standard.md +2 -2
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- 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 +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/pull-request-standard.md +2 -2
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- 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 +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- 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/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/pull-request-standard.md +1 -1
- 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 +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- 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 +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Dashboard workbench HTTP handlers stay behavior-first until workbench request/response types are modeled.
|
|
3
2
|
import crypto from "node:crypto";
|
|
4
3
|
import { spawn } from "node:child_process";
|
|
@@ -6,24 +5,26 @@ import fs from "node:fs";
|
|
|
6
5
|
import http from "node:http";
|
|
7
6
|
import path from "node:path";
|
|
8
7
|
import { URL } from "node:url";
|
|
9
|
-
import { confirmTaskReview } from "./task-lifecycle.mjs";
|
|
10
|
-
import { createLessonSedimentationTask } from "./task-lesson-sedimentation.mjs";
|
|
8
|
+
import { confirmTaskReview, finalizeDeferredTaskReviewConfirmation, updateTaskLifecycle } from "./task-lifecycle.mjs";
|
|
9
|
+
import { createAggregateLessonSedimentationTask, createLessonSedimentationTask } from "./task-lesson-sedimentation.mjs";
|
|
11
10
|
import { normalizeTarget } from "./core-shared.mjs";
|
|
11
|
+
import { beginGovernanceSync, commitGovernanceSync, releaseGovernanceSync } from "./governance-sync.mjs";
|
|
12
12
|
import { dashboardWatchRoots } from "./harness-paths.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { createScannerTaskRepository } from "./task-repository.mjs";
|
|
14
14
|
import { writeDashboardFolder } from "./dashboard-data.mjs";
|
|
15
15
|
import { checkPresetPackage, installPresetPackage, listPresetPackages, seedBundledPresets, uninstallPresetPackage, } from "./preset-registry.mjs";
|
|
16
|
-
const jsonHeaders = { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" };
|
|
17
|
-
export async function serveDashboardWorkbench(outDir, targetInput, { host = "127.0.0.1", port = 0, localeOverride = "", autoRefresh = false, open = false, label = "dashboard workbench", recoverGeneratedDashboard = false } = {}) {
|
|
16
|
+
const jsonHeaders = { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "connection": "close" };
|
|
17
|
+
export async function serveDashboardWorkbench(outDir, targetInput, { host = "127.0.0.1", port = 0, localeOverride = "", autoRefresh = false, open = false, label = "dashboard workbench", recoverGeneratedDashboard = false, replaceExistingDashboardOutput = false } = {}) {
|
|
18
18
|
if (host !== "127.0.0.1")
|
|
19
19
|
throw new Error("dashboard workbench only supports --host 127.0.0.1");
|
|
20
20
|
const target = normalizeTarget(targetInput);
|
|
21
|
+
const taskRepository = createScannerTaskRepository(target);
|
|
21
22
|
const outputDir = path.resolve(outDir);
|
|
22
23
|
const csrfToken = crypto.randomBytes(24).toString("hex");
|
|
23
24
|
const options = localeOverride ? { localeOverride } : {};
|
|
24
25
|
let snapshotVersion = Date.now();
|
|
25
26
|
const regenerate = () => {
|
|
26
|
-
writeDashboardFolder(outputDir, targetInput, { ...options, workbenchRuntime: true, recoverGeneratedDashboard });
|
|
27
|
+
writeDashboardFolder(outputDir, targetInput, { ...options, workbenchRuntime: true, recoverGeneratedDashboard, replaceExistingDashboardOutput });
|
|
27
28
|
snapshotVersion = Date.now();
|
|
28
29
|
};
|
|
29
30
|
regenerate();
|
|
@@ -37,7 +38,7 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
37
38
|
writeJson(response, 200, {
|
|
38
39
|
mode: "workbench",
|
|
39
40
|
csrfToken,
|
|
40
|
-
writableActions: ["review-complete", "lesson-sedimentation-task", "preset-check", "preset-install", "preset-seed", "preset-uninstall"],
|
|
41
|
+
writableActions: ["review-complete", "review-complete-bulk", "task-complete", "lesson-sedimentation-task", "lesson-sedimentation-bulk", "preset-check", "preset-install", "preset-seed", "preset-uninstall"],
|
|
41
42
|
target: target.projectRoot,
|
|
42
43
|
autoRefresh: autoRefresh === true,
|
|
43
44
|
snapshotVersion,
|
|
@@ -48,7 +49,7 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
48
49
|
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
49
50
|
const body = await readJsonBody(request);
|
|
50
51
|
const taskId = String(body.taskId || "");
|
|
51
|
-
const task =
|
|
52
|
+
const task = findWorkbenchTask(taskRepository, taskId);
|
|
52
53
|
if (!task) {
|
|
53
54
|
writeJson(response, 404, { error: "Task not found" });
|
|
54
55
|
return;
|
|
@@ -71,12 +72,100 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
71
72
|
writeJson(response, 200, result);
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
75
|
+
if (requestUrl.pathname === "/api/tasks/task-complete" && request.method === "POST") {
|
|
76
|
+
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
77
|
+
const body = await readJsonBody(request);
|
|
78
|
+
const taskId = String(body.taskId || "");
|
|
79
|
+
const task = findWorkbenchTask(taskRepository, taskId);
|
|
80
|
+
if (!task) {
|
|
81
|
+
writeJson(response, 404, { error: "Task not found" });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (taskHasPendingLessonWork(task)) {
|
|
85
|
+
writeJson(response, 409, closeoutRejectionPayload(task, "Lesson candidate promotion or sedimentation work must be resolved before closeout."));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!taskReadyForCloseout(task)) {
|
|
89
|
+
writeJson(response, 409, closeoutRejectionPayload(task));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const result = updateTaskLifecycle(target.projectRoot, taskId, {
|
|
93
|
+
event: "task-complete",
|
|
94
|
+
state: "done",
|
|
95
|
+
message: body.message || "closed from dashboard workbench",
|
|
96
|
+
evidence: body.evidence || "",
|
|
97
|
+
});
|
|
98
|
+
regenerate();
|
|
99
|
+
writeJson(response, 200, result);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (requestUrl.pathname === "/api/tasks/review-complete-bulk" && request.method === "POST") {
|
|
103
|
+
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
104
|
+
const body = await readJsonBody(request);
|
|
105
|
+
const requestedTaskIds = Array.isArray(body.taskIds) ? body.taskIds.map((id) => String(id || "").trim()).filter(Boolean) : [];
|
|
106
|
+
const taskIds = uniqueValues(requestedTaskIds);
|
|
107
|
+
if (taskIds.length === 0) {
|
|
108
|
+
writeJson(response, 400, { error: "No review tasks selected" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const taskId of taskIds) {
|
|
113
|
+
const task = findWorkbenchTask(taskRepository, taskId);
|
|
114
|
+
if (!task) {
|
|
115
|
+
results.push({ taskId, ok: false, status: 404, error: "Task not found" });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!isTaskInReviewQueue(task)) {
|
|
119
|
+
const payload = reviewQueueRejectionPayload(task);
|
|
120
|
+
results.push({ taskId, ok: false, status: 409, ...payload });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (task.reviewStatus === "confirmed") {
|
|
124
|
+
results.push({ taskId, ok: false, status: 409, error: "Review is already confirmed." });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const result = confirmTaskReview(target.projectRoot, taskId, {
|
|
129
|
+
reviewer: body.reviewer || "Human Reviewer",
|
|
130
|
+
message: body.message || "bulk confirmed from dashboard workbench",
|
|
131
|
+
evidence: body.evidence || "",
|
|
132
|
+
confirmText: task.shortId || task.id,
|
|
133
|
+
deferCommit: true,
|
|
134
|
+
});
|
|
135
|
+
results.push({ taskId, ok: true, status: 200, audit: result.audit, task: { id: result.task?.id || taskId } });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
results.push({ taskId, ok: false, status: errorStatus(error), ...errorPayload(error) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const confirmed = results.filter((result) => result.ok).length;
|
|
142
|
+
const failed = results.length - confirmed;
|
|
143
|
+
if (confirmed > 0) {
|
|
144
|
+
const allowedPaths = uniqueValues(results.filter((result) => result.ok).flatMap((result) => result.audit?.allowedPaths || []));
|
|
145
|
+
const confirmCommit = commitWorkbenchBatch(target, allowedPaths, { operation: "review-complete-bulk", message: "chore: confirm selected reviews" });
|
|
146
|
+
for (const result of results.filter((item) => item.ok)) {
|
|
147
|
+
finalizeDeferredTaskReviewConfirmation(target.projectRoot, result.taskId, { commitSha: confirmCommit.commitSha });
|
|
148
|
+
}
|
|
149
|
+
const auditCommit = commitWorkbenchBatch(target, allowedPaths, { operation: "review-complete-bulk-audit", message: "chore: record selected review confirmation audit" });
|
|
150
|
+
for (const result of results.filter((item) => item.ok)) {
|
|
151
|
+
result.audit = {
|
|
152
|
+
...result.audit,
|
|
153
|
+
commitSha: confirmCommit.commitSha,
|
|
154
|
+
auditCommitSha: auditCommit.commitSha,
|
|
155
|
+
auditStatus: "committed",
|
|
156
|
+
allowedPaths,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
writeJson(response, confirmed > 0 ? 200 : 409, { ok: failed === 0, confirmed, failed, results });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
74
163
|
if (requestUrl.pathname === "/api/tasks/lesson-sedimentation" && request.method === "POST") {
|
|
75
164
|
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
76
165
|
const body = await readJsonBody(request);
|
|
77
166
|
const taskId = String(body.taskId || "");
|
|
78
167
|
const candidateId = String(body.candidateId || "");
|
|
79
|
-
const task =
|
|
168
|
+
const task = findWorkbenchTask(taskRepository, taskId);
|
|
80
169
|
if (!task) {
|
|
81
170
|
writeJson(response, 404, { error: "Task not found" });
|
|
82
171
|
return;
|
|
@@ -92,6 +181,40 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
92
181
|
writeJson(response, 200, result);
|
|
93
182
|
return;
|
|
94
183
|
}
|
|
184
|
+
if (requestUrl.pathname === "/api/tasks/lesson-sedimentation-bulk" && request.method === "POST") {
|
|
185
|
+
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
186
|
+
const body = await readJsonBody(request);
|
|
187
|
+
const selections = normalizeBulkLessonSelections(body.selections);
|
|
188
|
+
if (selections.length === 0) {
|
|
189
|
+
writeJson(response, 400, { error: "No lesson candidates selected" });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const result = createAggregateLessonSedimentationTask(target.projectRoot, selections, {
|
|
194
|
+
title: body.title || "",
|
|
195
|
+
});
|
|
196
|
+
const results = selections.map((selection) => ({
|
|
197
|
+
...selection,
|
|
198
|
+
ok: true,
|
|
199
|
+
status: 200,
|
|
200
|
+
followUpTask: result.followUpTask,
|
|
201
|
+
}));
|
|
202
|
+
writeJson(response, 200, {
|
|
203
|
+
ok: true,
|
|
204
|
+
created: 1,
|
|
205
|
+
candidates: result.candidates.length,
|
|
206
|
+
failed: 0,
|
|
207
|
+
followUpTask: result.followUpTask,
|
|
208
|
+
prompt: result.prompt,
|
|
209
|
+
governance: result.governance,
|
|
210
|
+
results,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
writeJson(response, errorStatus(error), { ok: false, created: 0, candidates: selections.length, failed: selections.length, ...errorPayload(error) });
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
95
218
|
if (requestUrl.pathname === "/api/presets/check" && request.method === "POST") {
|
|
96
219
|
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
97
220
|
const body = await readJsonBody(request);
|
|
@@ -159,16 +282,19 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
159
282
|
writeJson(response, 405, { error: "Method not allowed" });
|
|
160
283
|
return;
|
|
161
284
|
}
|
|
285
|
+
if (request.method === "GET" && isDashboardDataRequest(requestUrl.pathname))
|
|
286
|
+
regenerate();
|
|
162
287
|
serveStaticFile(response, outputDir, requestUrl.pathname, request.method === "HEAD");
|
|
163
288
|
}
|
|
164
289
|
catch (error) {
|
|
165
|
-
const
|
|
290
|
+
const message = errorMessage(error);
|
|
291
|
+
const status = errorStatus(error, /CSRF|Origin|Host/.test(message) ? 403 : 400);
|
|
166
292
|
writeJson(response, status, errorPayload(error));
|
|
167
293
|
}
|
|
168
294
|
});
|
|
169
295
|
await new Promise((resolve, reject) => {
|
|
170
296
|
server.once("error", reject);
|
|
171
|
-
server.listen(Number(port), host, resolve);
|
|
297
|
+
server.listen(Number(port), host, () => resolve());
|
|
172
298
|
});
|
|
173
299
|
const address = server.address();
|
|
174
300
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -194,9 +320,83 @@ function normalizePresetScope(value) {
|
|
|
194
320
|
throw new Error(`Invalid preset scope: ${scope}`);
|
|
195
321
|
return scope;
|
|
196
322
|
}
|
|
323
|
+
function isDashboardDataRequest(urlPath) {
|
|
324
|
+
return urlPath === "/assets/dashboard-data.js" || urlPath.startsWith("/data/");
|
|
325
|
+
}
|
|
326
|
+
function commitWorkbenchBatch(target, allowedPaths, { operation, message }) {
|
|
327
|
+
const paths = uniqueValues(allowedPaths || []);
|
|
328
|
+
const context = beginGovernanceSync(target, {
|
|
329
|
+
operation,
|
|
330
|
+
allowDirtyWorktree: true,
|
|
331
|
+
allowedRelativePaths: paths,
|
|
332
|
+
allowDirtyWriteScope: true,
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
return commitGovernanceSync(context, paths, { message });
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
releaseGovernanceSync(context);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function uniqueValues(values) {
|
|
342
|
+
const seen = new Set();
|
|
343
|
+
const result = [];
|
|
344
|
+
for (const value of values) {
|
|
345
|
+
if (seen.has(value))
|
|
346
|
+
continue;
|
|
347
|
+
seen.add(value);
|
|
348
|
+
result.push(value);
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function normalizeBulkLessonSelections(rawSelections) {
|
|
353
|
+
if (!Array.isArray(rawSelections))
|
|
354
|
+
return [];
|
|
355
|
+
const seen = new Set();
|
|
356
|
+
const result = [];
|
|
357
|
+
for (const selection of rawSelections) {
|
|
358
|
+
const taskId = String(selection?.taskId || "").trim();
|
|
359
|
+
const candidateId = String(selection?.candidateId || "").trim();
|
|
360
|
+
if (!taskId || !candidateId)
|
|
361
|
+
continue;
|
|
362
|
+
const key = `${taskId}\n${candidateId}`;
|
|
363
|
+
if (seen.has(key))
|
|
364
|
+
continue;
|
|
365
|
+
seen.add(key);
|
|
366
|
+
result.push({ taskId, candidateId });
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
function findWorkbenchTask(repository, taskId) {
|
|
371
|
+
try {
|
|
372
|
+
return repository.get({ id: taskId });
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
197
378
|
function isTaskInReviewQueue(task) {
|
|
198
379
|
return task?.reviewQueueState === "ready-to-confirm" && Array.isArray(task?.taskQueues) && task.taskQueues.includes("review");
|
|
199
380
|
}
|
|
381
|
+
function taskHasPendingLessonWork(task) {
|
|
382
|
+
const queues = Array.isArray(task?.taskQueues) ? task.taskQueues : [];
|
|
383
|
+
const candidateRows = Array.isArray(task?.lessonCandidateRows) ? task.lessonCandidateRows : [];
|
|
384
|
+
return queues.includes("lessons") ||
|
|
385
|
+
task?.lessonCandidateStatus === "needs-promotion" ||
|
|
386
|
+
task?.lessonCandidatePromotionState === "queued" ||
|
|
387
|
+
candidateRows.some((candidate) => ["ready-for-review", "needs-promotion"].includes(String(candidate?.status || "")));
|
|
388
|
+
}
|
|
389
|
+
function taskReadyForCloseout(task) {
|
|
390
|
+
if (!task)
|
|
391
|
+
return false;
|
|
392
|
+
if (task.reviewStatus !== "confirmed")
|
|
393
|
+
return false;
|
|
394
|
+
if (task.closeoutStatus === "closed")
|
|
395
|
+
return false;
|
|
396
|
+
if (taskHasPendingLessonWork(task))
|
|
397
|
+
return false;
|
|
398
|
+
return ["no-candidate-accepted", "promoted", "rejected"].includes(String(task.lessonCandidateStatus || ""));
|
|
399
|
+
}
|
|
200
400
|
function reviewQueueRejectionPayload(task) {
|
|
201
401
|
return {
|
|
202
402
|
error: "Review completion is only available for tasks in the review queue.",
|
|
@@ -208,6 +408,18 @@ function reviewQueueRejectionPayload(task) {
|
|
|
208
408
|
taskId: task?.id || "",
|
|
209
409
|
};
|
|
210
410
|
}
|
|
411
|
+
function closeoutRejectionPayload(task, error = "Task closeout is only available after human review is confirmed and Lesson routing is complete.") {
|
|
412
|
+
return {
|
|
413
|
+
error,
|
|
414
|
+
closeoutStatus: task?.closeoutStatus || "unknown",
|
|
415
|
+
lifecycleState: task?.lifecycleState || "unknown",
|
|
416
|
+
reviewStatus: task?.reviewStatus || "unknown",
|
|
417
|
+
taskQueues: Array.isArray(task?.taskQueues) ? task.taskQueues : [],
|
|
418
|
+
lessonCandidateStatus: task?.lessonCandidateStatus || "unknown",
|
|
419
|
+
lessonCandidatePromotionState: task?.lessonCandidatePromotionState || "unknown",
|
|
420
|
+
taskId: task?.id || "",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
211
423
|
function startPollingWatch(roots, regenerate) {
|
|
212
424
|
let lastMtime = latestTreeMtime(roots);
|
|
213
425
|
let timer = null;
|
|
@@ -216,13 +428,14 @@ function startPollingWatch(roots, regenerate) {
|
|
|
216
428
|
if (nextMtime <= lastMtime)
|
|
217
429
|
return;
|
|
218
430
|
lastMtime = nextMtime;
|
|
219
|
-
|
|
431
|
+
if (timer)
|
|
432
|
+
clearTimeout(timer);
|
|
220
433
|
timer = setTimeout(() => {
|
|
221
434
|
try {
|
|
222
435
|
regenerate();
|
|
223
436
|
}
|
|
224
437
|
catch (error) {
|
|
225
|
-
console.error(`dashboard regeneration failed: ${error
|
|
438
|
+
console.error(`dashboard regeneration failed: ${errorMessage(error)}`);
|
|
226
439
|
}
|
|
227
440
|
}, 250);
|
|
228
441
|
}, 1000);
|
|
@@ -306,15 +519,26 @@ function writeJson(response, status, payload) {
|
|
|
306
519
|
response.end(`${JSON.stringify(payload)}\n`);
|
|
307
520
|
}
|
|
308
521
|
function errorPayload(error) {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
522
|
+
const source = isRecord(error) ? error : {};
|
|
523
|
+
const payload = { error: errorMessage(error) };
|
|
524
|
+
if (source.code)
|
|
525
|
+
payload.code = source.code;
|
|
526
|
+
if (Array.isArray(source.recovery) && source.recovery.length > 0)
|
|
527
|
+
payload.recovery = source.recovery;
|
|
528
|
+
if (source.details)
|
|
529
|
+
payload.details = source.details;
|
|
316
530
|
return payload;
|
|
317
531
|
}
|
|
532
|
+
function errorStatus(error, fallback = 400) {
|
|
533
|
+
const source = isRecord(error) ? error : {};
|
|
534
|
+
return typeof source.status === "number" ? source.status : fallback;
|
|
535
|
+
}
|
|
536
|
+
function errorMessage(error) {
|
|
537
|
+
return error instanceof Error ? error.message : String(error || "unknown error");
|
|
538
|
+
}
|
|
539
|
+
function isRecord(value) {
|
|
540
|
+
return typeof value === "object" && value !== null;
|
|
541
|
+
}
|
|
318
542
|
function mimeType(filePath) {
|
|
319
543
|
if (filePath.endsWith(".html"))
|
|
320
544
|
return "text/html; charset=utf-8";
|
|
@@ -28,6 +28,8 @@ export function writeDashboardDirectory(outDir, bundle, options = {}) {
|
|
|
28
28
|
writeJsonFile(path.join(target, "data/tables.json"), bundle.tables);
|
|
29
29
|
writeJsonFile(path.join(target, "data/documents.json"), bundle.documents);
|
|
30
30
|
writeJsonFile(path.join(target, "data/graph.json"), bundle.graph);
|
|
31
|
+
writeJsonFile(path.join(target, "data/modules.json"), bundle.modules || []);
|
|
32
|
+
writeJsonFile(path.join(target, "data/moduleSummary.json"), bundle.moduleSummary || {});
|
|
31
33
|
writeJsonFile(path.join(target, "data/adoption.json"), bundle.adoption);
|
|
32
34
|
writeJsonFile(path.join(target, "data/presetCatalog.json"), bundle.presetCatalog);
|
|
33
35
|
fs.writeFileSync(path.join(target, "assets/dashboard-data.js"), `window.__HARNESS_DASHBOARD__ = ${JSON.stringify(bundle, null, 2)};\n`);
|
|
@@ -150,7 +152,8 @@ function assertSafeDashboardTarget(target, options) {
|
|
|
150
152
|
const entries = fs.readdirSync(target);
|
|
151
153
|
const hasMarker = fs.existsSync(path.join(target, dashboardMarker));
|
|
152
154
|
const canRecoverGeneratedDashboard = options.recoverGeneratedDashboard === true && looksLikeGeneratedDashboardDirectory(target);
|
|
153
|
-
|
|
155
|
+
const canReplaceExistingOutput = options.replaceExistingDashboardOutput === true;
|
|
156
|
+
if (entries.length > 0 && !hasMarker && !canRecoverGeneratedDashboard && !canReplaceExistingOutput) {
|
|
154
157
|
throw new Error(`Refusing to overwrite non-dashboard directory: ${target}`);
|
|
155
158
|
}
|
|
156
159
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Governance index rendering stays behavior-first until task/governance surface types are modeled.
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
@@ -21,9 +20,9 @@ export function rebuildGovernanceIndexes(targetInput, { dryRun = false, archive
|
|
|
21
20
|
const changes = surfaces.map((surface) => ({
|
|
22
21
|
surface: surface.surface,
|
|
23
22
|
destination: surface.relative,
|
|
24
|
-
action: surface
|
|
25
|
-
?
|
|
26
|
-
:
|
|
23
|
+
action: isArchiveOnlySurface(surface)
|
|
24
|
+
? effectiveApply ? "archive-legacy-governance-table" : "would-archive-legacy-governance-table"
|
|
25
|
+
: effectiveApply ? "rebuild-governance-index" : "would-rebuild-governance-index",
|
|
27
26
|
generatedRows: surface.rows.length,
|
|
28
27
|
archive: archive ? `${archiveDir}/${surface.relative}` : "",
|
|
29
28
|
}));
|
|
@@ -38,7 +37,7 @@ export function rebuildGovernanceIndexes(targetInput, { dryRun = false, archive
|
|
|
38
37
|
fs.copyFileSync(surface.absolute, archivePath);
|
|
39
38
|
allowed.push(toPosix(path.relative(target.projectRoot, archivePath)));
|
|
40
39
|
}
|
|
41
|
-
if (surface
|
|
40
|
+
if (isArchiveOnlySurface(surface)) {
|
|
42
41
|
if (archive && fs.existsSync(surface.absolute)) {
|
|
43
42
|
fs.rmSync(surface.absolute);
|
|
44
43
|
allowed.push(surface.relative);
|
|
@@ -56,6 +55,9 @@ export function rebuildGovernanceIndexes(targetInput, { dryRun = false, archive
|
|
|
56
55
|
releaseGovernanceSync(context);
|
|
57
56
|
}
|
|
58
57
|
}
|
|
58
|
+
function isArchiveOnlySurface(surface) {
|
|
59
|
+
return "archiveOnly" in surface && surface.archiveOnly === true;
|
|
60
|
+
}
|
|
59
61
|
function governanceSurfaces(target, tasks) {
|
|
60
62
|
return [
|
|
61
63
|
{
|