coding-agent-harness 1.0.2 → 1.0.4
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 +25 -0
- package/CONTRIBUTING.md +98 -0
- package/README.md +211 -86
- package/README.zh-CN.md +54 -34
- package/SKILL.md +25 -18
- package/docs-release/README.md +9 -5
- package/docs-release/architecture/overview.md +17 -5
- package/docs-release/architecture/overview.zh-CN.md +9 -5
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +31 -8
- package/docs-release/guides/agent-installation.md +34 -9
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
- package/docs-release/guides/document-audience-and-surfaces.md +3 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
- package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
- package/docs-release/guides/migration-playbook.en-US.md +14 -15
- package/docs-release/guides/migration-playbook.md +14 -15
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
- package/docs-release/guides/parent-control-repository-pattern.md +7 -5
- package/docs-release/guides/preset-development.md +214 -0
- package/docs-release/guides/repository-operating-models.en-US.md +5 -4
- package/docs-release/guides/repository-operating-models.md +5 -4
- package/docs-release/guides/task-state-machine.en-US.md +207 -0
- package/docs-release/guides/task-state-machine.md +214 -0
- package/docs-release/intl/en-US.md +1 -1
- package/docs-release/intl/zh-CN.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +8 -3
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +2 -2
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +6 -7
- package/references/harness-ledger.md +53 -96
- package/references/lessons-governance.md +88 -93
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +12 -6
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +11 -2
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/walkthrough-closeout.md +7 -7
- package/scripts/check-harness.mjs +40 -301
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +96 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +327 -0
- package/scripts/harness.mjs +55 -260
- package/scripts/lib/capability-registry.mjs +66 -8
- package/scripts/lib/check-module-parallel.mjs +237 -0
- package/scripts/lib/check-profiles.mjs +61 -153
- package/scripts/lib/check-task-contracts.mjs +47 -0
- package/scripts/lib/core-shared.mjs +10 -0
- package/scripts/lib/dashboard-data.mjs +29 -6
- package/scripts/lib/dashboard-workbench.mjs +52 -12
- package/scripts/lib/dashboard-writer.mjs +14 -2
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +514 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +5 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +497 -0
- package/scripts/lib/preset-registry.mjs +627 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-dashboard-renderer.mjs +102 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-completion-consistency.mjs +16 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +297 -403
- package/scripts/lib/task-review-model.mjs +469 -0
- package/scripts/lib/task-scanner.mjs +130 -236
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +19 -15
- package/templates/dashboard/assets/app-src/00-state.js +1 -0
- package/templates/dashboard/assets/app-src/10-router.js +2 -1
- package/templates/dashboard/assets/app-src/20-overview.js +11 -5
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
- package/templates/dashboard/assets/app-src/45-review.js +241 -22
- package/templates/dashboard/assets/app-src/50-migration.js +24 -10
- package/templates/dashboard/assets/app-src/90-bindings.js +171 -29
- package/templates/dashboard/assets/app.css +698 -156
- package/templates/dashboard/assets/app.css.manifest.json +9 -0
- package/templates/dashboard/assets/app.js +662 -91
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +123 -21
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +59 -0
- package/templates/planning/task_plan.md +36 -13
- package/templates/reference/execution-workflow-standard.md +4 -3
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +7 -6
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +20 -16
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +18 -6
- package/templates-zh-CN/planning/review.md +59 -1
- package/templates-zh-CN/planning/task_plan.md +30 -10
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +1 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +4 -3
- package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/scripts/smoke-dashboard.mjs +0 -92
- package/scripts/test-harness.mjs +0 -1395
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -56,7 +56,6 @@ function collectDashboardDocumentPaths(target) {
|
|
|
56
56
|
"09-PLANNING/Module-Registry.md",
|
|
57
57
|
"05-TEST-QA/Regression-SSoT.md",
|
|
58
58
|
"05-TEST-QA/Cadence-Ledger.md",
|
|
59
|
-
"01-GOVERNANCE/Lessons-SSoT.md",
|
|
60
59
|
"10-WALKTHROUGH/Closeout-SSoT.md",
|
|
61
60
|
]) {
|
|
62
61
|
addDocsPath(relativePath);
|
|
@@ -104,7 +103,7 @@ function documentKind(source) {
|
|
|
104
103
|
if (lower.includes("module-registry.md")) return "module-registry";
|
|
105
104
|
if (lower.includes("regression-ssot.md")) return "regression-ssot";
|
|
106
105
|
if (lower.includes("cadence-ledger.md")) return "cadence-ledger";
|
|
107
|
-
if (
|
|
106
|
+
if (/\/01-governance\/lessons\/[^/]+\.md$/i.test(lower)) return "lesson-detail";
|
|
108
107
|
if (lower.endsWith("/progress.md")) return "task-progress";
|
|
109
108
|
if (lower.endsWith("/brief.md")) return "task-brief";
|
|
110
109
|
if (lower.endsWith("/review.md")) return "task-review";
|
|
@@ -200,6 +199,7 @@ export function collectGraph(status, tables = { tables: [] }) {
|
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
export function categorizeWarning(message) {
|
|
202
|
+
if (/governance-table-entropy/i.test(message)) return "Governance Table Boundary";
|
|
203
203
|
if (/missing execution_strategy\.md|missing visual_(?:map|roadmap)\.md|Visual (?:Map|Roadmap)/i.test(message)) return "Plan Contract Missing";
|
|
204
204
|
if (/legacy-compat|adoption-needed|legacy check/i.test(message)) return "Adoption Advice";
|
|
205
205
|
if (/Evidence|evidence/i.test(message)) return "Missing Evidence";
|
|
@@ -213,6 +213,7 @@ function warningType(message) {
|
|
|
213
213
|
if (/missing visual_map\.md|Visual Map/i.test(message)) return "missing-visual-map";
|
|
214
214
|
if (/missing visual_roadmap\.md|Visual Roadmap/i.test(message)) return "missing-visual-roadmap";
|
|
215
215
|
if (/Reviewer Identity|Confidence Challenge|Final Confidence Basis|Evidence Checked/i.test(message)) return "review-schema-gap";
|
|
216
|
+
if (/governance-table-entropy/i.test(message)) return "governance-table-entropy";
|
|
216
217
|
if (/Evidence|evidence/i.test(message)) return "missing-evidence";
|
|
217
218
|
if (/missing required file/i.test(message)) return "legacy-reference-gap";
|
|
218
219
|
if (/legacy-compat|legacy check|adoption-needed/i.test(message)) return "capability-adoption";
|
|
@@ -231,6 +232,7 @@ function warningScope(message) {
|
|
|
231
232
|
|
|
232
233
|
function warningPhase(type, scope) {
|
|
233
234
|
if (type === "capability-adoption") return "baseline";
|
|
235
|
+
if (type === "governance-table-entropy") return "global-table-boundary";
|
|
234
236
|
if (type === "missing-brief" || type === "missing-execution-strategy" || type === "missing-visual-map" || type === "missing-visual-roadmap") return "active-task-contracts";
|
|
235
237
|
if (scope === "module") return "module-classification";
|
|
236
238
|
if (type === "review-schema-gap" || type === "missing-evidence") return "review-evidence";
|
|
@@ -240,6 +242,7 @@ function warningPhase(type, scope) {
|
|
|
240
242
|
|
|
241
243
|
function warningFixability(type, scope) {
|
|
242
244
|
if (["missing-brief", "missing-execution-strategy", "missing-visual-map", "missing-visual-roadmap"].includes(type)) return "guided";
|
|
245
|
+
if (type === "governance-table-entropy") return "manual";
|
|
243
246
|
if (type === "legacy-reference-gap" || scope === "reference") return "template";
|
|
244
247
|
if (type === "capability-adoption") return "decision";
|
|
245
248
|
if (type === "review-schema-gap" || type === "missing-evidence") return "human-evidence";
|
|
@@ -248,6 +251,7 @@ function warningFixability(type, scope) {
|
|
|
248
251
|
|
|
249
252
|
function warningPriority(type, scope, message) {
|
|
250
253
|
if (/fail|invalid|blocked/i.test(message) || type === "schema-drift") return "P1";
|
|
254
|
+
if (type === "governance-table-entropy") return /legacy-report-only/i.test(message) ? "P3" : "P2";
|
|
251
255
|
if (["missing-brief", "missing-execution-strategy", "missing-visual-map", "missing-visual-roadmap"].includes(type) && scope === "task") return "P2";
|
|
252
256
|
if (type === "review-schema-gap" || type === "missing-evidence") return "P2";
|
|
253
257
|
if (type === "capability-adoption") return "P3";
|
|
@@ -284,19 +288,24 @@ function summarizeWarnings(warnings) {
|
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
export function collectAdoption(status) {
|
|
287
|
-
const
|
|
291
|
+
const dashboardMessages = [
|
|
292
|
+
...(status.checkState.details.warnings || []),
|
|
293
|
+
...(status.checkState.details.failures || []).filter((message) => /governance-table-entropy/i.test(message)),
|
|
294
|
+
];
|
|
295
|
+
const warnings = dashboardMessages.flatMap((message) => splitWarningMessage(message)).map((message, index) => {
|
|
288
296
|
const type = warningType(message);
|
|
289
297
|
const scope = warningScope(message);
|
|
290
298
|
const affectedPaths = warningAffectedPaths(message);
|
|
299
|
+
const stableSuffix = type === "governance-table-entropy" ? `-${stableWarningIdPart(governanceWarningRowKey(message))}` : "";
|
|
291
300
|
return {
|
|
292
|
-
id: `AD-${String(index + 1).padStart(3, "0")}`,
|
|
301
|
+
id: `AD-${String(index + 1).padStart(3, "0")}${stableSuffix}`,
|
|
293
302
|
category: categorizeWarning(message),
|
|
294
303
|
type,
|
|
295
304
|
scope,
|
|
296
305
|
priority: warningPriority(type, scope, message),
|
|
297
306
|
phase: warningPhase(type, scope),
|
|
298
307
|
fixability: warningFixability(type, scope),
|
|
299
|
-
status: "open",
|
|
308
|
+
status: /legacy-report-only/i.test(message) ? "legacy-report-only" : "open",
|
|
300
309
|
confidence: warningConfidence(message),
|
|
301
310
|
severity: status.mode === "legacy-compat" ? "advice" : "warning",
|
|
302
311
|
title: warningTitle(message),
|
|
@@ -330,6 +339,18 @@ export function collectAdoption(status) {
|
|
|
330
339
|
};
|
|
331
340
|
}
|
|
332
341
|
|
|
342
|
+
function governanceWarningRowKey(message) {
|
|
343
|
+
const match = String(message || "").match(/\brow\s+([^:]+)/i);
|
|
344
|
+
return match ? match[1].trim() : "global-table";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function stableWarningIdPart(value) {
|
|
348
|
+
return String(value || "global-table")
|
|
349
|
+
.replace(/[^A-Za-z0-9_-]+/g, "-")
|
|
350
|
+
.replace(/^-+|-+$/g, "")
|
|
351
|
+
.slice(0, 80) || "global-table";
|
|
352
|
+
}
|
|
353
|
+
|
|
333
354
|
export function splitWarningMessage(message) {
|
|
334
355
|
return String(message || "")
|
|
335
356
|
.split(/\n-\s+/)
|
|
@@ -338,6 +359,7 @@ export function splitWarningMessage(message) {
|
|
|
338
359
|
}
|
|
339
360
|
|
|
340
361
|
function warningTitle(message) {
|
|
362
|
+
if (/governance-table-entropy/i.test(message)) return "Global table boundary";
|
|
341
363
|
if (/missing execution_strategy\.md/i.test(message)) return "Missing execution strategy";
|
|
342
364
|
if (/missing visual_map\.md|Visual Map/i.test(message)) return "Missing visual map";
|
|
343
365
|
if (/missing visual_roadmap\.md|Visual Roadmap/i.test(message)) return "Missing legacy visual roadmap";
|
|
@@ -354,6 +376,7 @@ function warningAffected(message) {
|
|
|
354
376
|
}
|
|
355
377
|
|
|
356
378
|
function warningAction(message) {
|
|
379
|
+
if (/governance-table-entropy/i.test(message)) return "Move local detail to module/task docs; keep the global row to summary, state, route, and audit result.";
|
|
357
380
|
if (/execution_strategy\.md/i.test(message)) return "Add standalone execution strategy file.";
|
|
358
381
|
if (/visual_map\.md|Visual Map/i.test(message)) return "Add standalone visual map file.";
|
|
359
382
|
if (/visual_roadmap\.md|Visual Roadmap/i.test(message)) return "Rewrite legacy visual_roadmap.md into canonical visual_map.md.";
|
|
@@ -377,7 +400,7 @@ export function writeDashboardFolder(outDir, targetInput, options = {}) {
|
|
|
377
400
|
const registry = readCapabilityRegistry(target);
|
|
378
401
|
const locale = options.localeOverride || registry.locale;
|
|
379
402
|
const bundle = buildDashboardBundle(targetInput, options);
|
|
380
|
-
return writeDashboardDirectory(outDir, bundle, { repoRoot, projectRoot: target.projectRoot, docsRoot: target.docsRoot, locale, workbenchRuntime: options.workbenchRuntime === true });
|
|
403
|
+
return writeDashboardDirectory(outDir, bundle, { repoRoot, projectRoot: target.projectRoot, docsRoot: target.docsRoot, locale, workbenchRuntime: options.workbenchRuntime === true, recoverGeneratedDashboard: options.recoverGeneratedDashboard === true });
|
|
381
404
|
}
|
|
382
405
|
|
|
383
406
|
export function writeDashboardSingleFile(outFile, targetInput, options = {}) {
|
|
@@ -5,13 +5,14 @@ import http from "node:http";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { URL } from "node:url";
|
|
7
7
|
import { confirmTaskReview } from "./task-lifecycle.mjs";
|
|
8
|
+
import { createLessonSedimentationTask } from "./task-lesson-sedimentation.mjs";
|
|
8
9
|
import { normalizeTarget } from "./core-shared.mjs";
|
|
9
10
|
import { collectTasks } from "./task-scanner.mjs";
|
|
10
11
|
import { writeDashboardFolder } from "./dashboard-data.mjs";
|
|
11
12
|
|
|
12
13
|
const jsonHeaders = { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" };
|
|
13
14
|
|
|
14
|
-
export async function serveDashboardWorkbench(outDir, targetInput, { host = "127.0.0.1", port = 0, localeOverride = "", autoRefresh = false, open = false, label = "dashboard workbench" } = {}) {
|
|
15
|
+
export async function serveDashboardWorkbench(outDir, targetInput, { host = "127.0.0.1", port = 0, localeOverride = "", autoRefresh = false, open = false, label = "dashboard workbench", recoverGeneratedDashboard = false } = {}) {
|
|
15
16
|
if (host !== "127.0.0.1") throw new Error("dashboard workbench only supports --host 127.0.0.1");
|
|
16
17
|
const target = normalizeTarget(targetInput);
|
|
17
18
|
const outputDir = path.resolve(outDir);
|
|
@@ -19,7 +20,7 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
19
20
|
const options = localeOverride ? { localeOverride } : {};
|
|
20
21
|
let snapshotVersion = Date.now();
|
|
21
22
|
const regenerate = () => {
|
|
22
|
-
writeDashboardFolder(outputDir, targetInput, { ...options, workbenchRuntime: true });
|
|
23
|
+
writeDashboardFolder(outputDir, targetInput, { ...options, workbenchRuntime: true, recoverGeneratedDashboard });
|
|
23
24
|
snapshotVersion = Date.now();
|
|
24
25
|
};
|
|
25
26
|
regenerate();
|
|
@@ -35,7 +36,7 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
35
36
|
writeJson(response, 200, {
|
|
36
37
|
mode: "workbench",
|
|
37
38
|
csrfToken,
|
|
38
|
-
writableActions: ["review-complete"],
|
|
39
|
+
writableActions: ["review-complete", "lesson-sedimentation-task"],
|
|
39
40
|
target: target.projectRoot,
|
|
40
41
|
autoRefresh: autoRefresh === true,
|
|
41
42
|
snapshotVersion,
|
|
@@ -52,8 +53,8 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
52
53
|
writeJson(response, 404, { error: "Task not found" });
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
|
-
if (!
|
|
56
|
-
writeJson(response, 409,
|
|
56
|
+
if (!isTaskInReviewQueue(task)) {
|
|
57
|
+
writeJson(response, 409, reviewQueueRejectionPayload(task));
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
60
|
if (task.reviewStatus === "confirmed") {
|
|
@@ -71,14 +72,36 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (requestUrl.pathname === "/api/tasks/lesson-sedimentation" && request.method === "POST") {
|
|
76
|
+
assertTrustedWorkbenchRequest(request, { origin, csrfToken });
|
|
77
|
+
const body = await readJsonBody(request);
|
|
78
|
+
const taskId = String(body.taskId || "");
|
|
79
|
+
const candidateId = String(body.candidateId || "");
|
|
80
|
+
const task = collectTasks(target).find((item) => item.id === taskId);
|
|
81
|
+
if (!task) {
|
|
82
|
+
writeJson(response, 404, { error: "Task not found" });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!candidateId) {
|
|
86
|
+
writeJson(response, 400, { error: "Missing lesson candidate id" });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const result = createLessonSedimentationTask(target.projectRoot, taskId, candidateId, {
|
|
90
|
+
title: body.title || "",
|
|
91
|
+
});
|
|
92
|
+
regenerate();
|
|
93
|
+
writeJson(response, 200, result);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
75
98
|
writeJson(response, 405, { error: "Method not allowed" });
|
|
76
99
|
return;
|
|
77
100
|
}
|
|
78
101
|
serveStaticFile(response, outputDir, requestUrl.pathname, request.method === "HEAD");
|
|
79
102
|
} catch (error) {
|
|
80
|
-
const status = /CSRF|Origin|Host/.test(error.message) ? 403 : 400;
|
|
81
|
-
writeJson(response, status,
|
|
103
|
+
const status = error.status || (/CSRF|Origin|Host/.test(error.message) ? 403 : 400);
|
|
104
|
+
writeJson(response, status, errorPayload(error));
|
|
82
105
|
}
|
|
83
106
|
});
|
|
84
107
|
|
|
@@ -103,11 +126,20 @@ export async function serveDashboardWorkbench(outDir, targetInput, { host = "127
|
|
|
103
126
|
await new Promise(() => {});
|
|
104
127
|
}
|
|
105
128
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
function isTaskInReviewQueue(task) {
|
|
130
|
+
return task?.reviewQueueState === "ready-to-confirm" && Array.isArray(task?.taskQueues) && task.taskQueues.includes("review");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function reviewQueueRejectionPayload(task) {
|
|
134
|
+
return {
|
|
135
|
+
error: "Review completion is only available for tasks in the review queue.",
|
|
136
|
+
reviewQueueState: task?.reviewQueueState || "unknown",
|
|
137
|
+
taskQueues: Array.isArray(task?.taskQueues) ? task.taskQueues : [],
|
|
138
|
+
queueReasons: Array.isArray(task?.queueReasons) ? task.queueReasons : [],
|
|
139
|
+
repairPrompt: task?.repairPrompt || "",
|
|
140
|
+
reviewStatus: task?.reviewStatus || "unknown",
|
|
141
|
+
taskId: task?.id || "",
|
|
142
|
+
};
|
|
111
143
|
}
|
|
112
144
|
|
|
113
145
|
function startPollingWatch(root, regenerate) {
|
|
@@ -202,6 +234,14 @@ function writeJson(response, status, payload) {
|
|
|
202
234
|
response.end(`${JSON.stringify(payload)}\n`);
|
|
203
235
|
}
|
|
204
236
|
|
|
237
|
+
function errorPayload(error) {
|
|
238
|
+
const payload = { error: error.message };
|
|
239
|
+
if (error.code) payload.code = error.code;
|
|
240
|
+
if (Array.isArray(error.recovery) && error.recovery.length > 0) payload.recovery = error.recovery;
|
|
241
|
+
if (error.details) payload.details = error.details;
|
|
242
|
+
return payload;
|
|
243
|
+
}
|
|
244
|
+
|
|
205
245
|
function mimeType(filePath) {
|
|
206
246
|
if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
|
|
207
247
|
if (filePath.endsWith(".js")) return "text/javascript; charset=utf-8";
|
|
@@ -18,10 +18,11 @@ export function writeDashboardDirectory(outDir, bundle, options = {}) {
|
|
|
18
18
|
const target = path.resolve(outDir);
|
|
19
19
|
assertSafeDashboardTarget(target, options);
|
|
20
20
|
if (fs.existsSync(target)) fs.rmSync(target, { recursive: true, force: true });
|
|
21
|
+
fs.mkdirSync(target, { recursive: true });
|
|
22
|
+
fs.writeFileSync(path.join(target, dashboardMarker), "generated dashboard directory\n");
|
|
21
23
|
copyDashboardAssets(target, options);
|
|
22
24
|
fs.writeFileSync(path.join(target, "assets/app.js"), readDashboardApp(dashboardTemplateRootForLocale(options.locale)));
|
|
23
25
|
fs.writeFileSync(path.join(target, "index.html"), renderDashboardIndex(options.locale, options));
|
|
24
|
-
fs.writeFileSync(path.join(target, dashboardMarker), "generated dashboard directory\n");
|
|
25
26
|
writeJsonFile(path.join(target, "data/status.json"), bundle.status);
|
|
26
27
|
writeJsonFile(path.join(target, "data/tables.json"), bundle.tables);
|
|
27
28
|
writeJsonFile(path.join(target, "data/documents.json"), bundle.documents);
|
|
@@ -156,12 +157,23 @@ function assertSafeDashboardTarget(target, options) {
|
|
|
156
157
|
if (fs.existsSync(target)) {
|
|
157
158
|
const entries = fs.readdirSync(target);
|
|
158
159
|
const hasMarker = fs.existsSync(path.join(target, dashboardMarker));
|
|
159
|
-
|
|
160
|
+
const canRecoverGeneratedDashboard = options.recoverGeneratedDashboard === true && looksLikeGeneratedDashboardDirectory(target);
|
|
161
|
+
if (entries.length > 0 && !hasMarker && !canRecoverGeneratedDashboard) {
|
|
160
162
|
throw new Error(`Refusing to overwrite non-dashboard directory: ${target}`);
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
function looksLikeGeneratedDashboardDirectory(target) {
|
|
168
|
+
return [
|
|
169
|
+
"index.html",
|
|
170
|
+
"README.md",
|
|
171
|
+
"assets/app.js",
|
|
172
|
+
"assets/dashboard-data.js",
|
|
173
|
+
"data/status.json",
|
|
174
|
+
].every((relativePath) => fs.existsSync(path.join(target, relativePath)));
|
|
175
|
+
}
|
|
176
|
+
|
|
165
177
|
function dashboardTemplateRootForLocale(locale = "en-US") {
|
|
166
178
|
return dashboardTemplateRoot;
|
|
167
179
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { inspectGit } from "./governance-sync.mjs";
|
|
4
|
+
|
|
5
|
+
export function summarizeGitState(target) {
|
|
6
|
+
const state = inspectGit(target.projectRoot);
|
|
7
|
+
if (!state.inGit) {
|
|
8
|
+
return {
|
|
9
|
+
summary: {
|
|
10
|
+
inGit: false,
|
|
11
|
+
root: "",
|
|
12
|
+
dirty: false,
|
|
13
|
+
entries: [],
|
|
14
|
+
blocksCliAutoCommit: false,
|
|
15
|
+
},
|
|
16
|
+
warnings: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const targetRoot = real(target.projectRoot);
|
|
20
|
+
const gitRoot = real(state.gitRoot);
|
|
21
|
+
const rootMatches = gitRoot === targetRoot;
|
|
22
|
+
const entries = rootMatches ? state.entries.map((entry) => ({
|
|
23
|
+
path: entry.path,
|
|
24
|
+
index: entry.index,
|
|
25
|
+
worktree: entry.worktree,
|
|
26
|
+
})) : [];
|
|
27
|
+
const dirty = entries.length > 0;
|
|
28
|
+
const warnings = [];
|
|
29
|
+
if (dirty) {
|
|
30
|
+
warnings.push(`dirty-state: ${entries.length} uncommitted Git path(s) will block CLI-owned auto-commit; commit them or record owner/no-commit reason before lifecycle commands.`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
summary: {
|
|
34
|
+
inGit: true,
|
|
35
|
+
root: rootMatches ? "TARGET:." : "outside-target",
|
|
36
|
+
dirty,
|
|
37
|
+
entries,
|
|
38
|
+
blocksCliAutoCommit: !rootMatches || dirty,
|
|
39
|
+
},
|
|
40
|
+
warnings,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function real(filePath) {
|
|
45
|
+
return fs.realpathSync(filePath);
|
|
46
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
normalizeTarget,
|
|
5
|
+
readBundledTemplate,
|
|
6
|
+
todayDate,
|
|
7
|
+
toPosix,
|
|
8
|
+
} from "./core-shared.mjs";
|
|
9
|
+
import { splitMarkdownRow } from "./markdown-utils.mjs";
|
|
10
|
+
import { collectTasks } from "./task-scanner.mjs";
|
|
11
|
+
import {
|
|
12
|
+
beginGovernanceSync,
|
|
13
|
+
commitGovernanceSync,
|
|
14
|
+
moduleGeneratedIndexSurfaces,
|
|
15
|
+
releaseGovernanceSync,
|
|
16
|
+
} from "./governance-sync.mjs";
|
|
17
|
+
import { markdownCell } from "./task-lifecycle/text-utils.mjs";
|
|
18
|
+
|
|
19
|
+
export function rebuildGovernanceIndexes(targetInput, { dryRun = false, archive = false, apply = false } = {}) {
|
|
20
|
+
const target = normalizeTarget(targetInput);
|
|
21
|
+
const effectiveApply = Boolean(apply && !dryRun);
|
|
22
|
+
const context = beginGovernanceSync(target, { operation: "governance rebuild", dryRun: !effectiveApply });
|
|
23
|
+
try {
|
|
24
|
+
const tasks = collectTasks(target)
|
|
25
|
+
.filter((task) => task.deletionState !== "deleted")
|
|
26
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
27
|
+
const surfaces = [...governanceSurfaces(target, tasks), ...moduleGeneratedIndexSurfaces(target, tasks)];
|
|
28
|
+
const archiveDir = archive ? uniqueArchiveDir(target) : "";
|
|
29
|
+
const changes = surfaces.map((surface) => ({
|
|
30
|
+
surface: surface.surface,
|
|
31
|
+
destination: surface.relative,
|
|
32
|
+
action: surface.archiveOnly
|
|
33
|
+
? apply ? "archive-legacy-governance-table" : "would-archive-legacy-governance-table"
|
|
34
|
+
: apply ? "rebuild-governance-index" : "would-rebuild-governance-index",
|
|
35
|
+
generatedRows: surface.rows.length,
|
|
36
|
+
archive: archive ? `${archiveDir}/${surface.relative}` : "",
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
if (!effectiveApply) {
|
|
40
|
+
return { dryRun: true, archive, applied: false, archiveDir, changes };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const allowed = [];
|
|
44
|
+
for (const surface of surfaces) {
|
|
45
|
+
if (archive && fs.existsSync(surface.absolute)) {
|
|
46
|
+
const archivePath = path.join(target.projectRoot, archiveDir, surface.relative);
|
|
47
|
+
fs.mkdirSync(path.dirname(archivePath), { recursive: true });
|
|
48
|
+
fs.copyFileSync(surface.absolute, archivePath);
|
|
49
|
+
allowed.push(toPosix(path.relative(target.projectRoot, archivePath)));
|
|
50
|
+
}
|
|
51
|
+
if (surface.archiveOnly) {
|
|
52
|
+
if (archive && fs.existsSync(surface.absolute)) {
|
|
53
|
+
fs.rmSync(surface.absolute);
|
|
54
|
+
allowed.push(surface.relative);
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
fs.mkdirSync(path.dirname(surface.absolute), { recursive: true });
|
|
59
|
+
fs.writeFileSync(surface.absolute, surface.content);
|
|
60
|
+
allowed.push(surface.relative);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const commit = commitGovernanceSync(context, allowed, { message: "chore(harness): rebuild generated governance indexes" });
|
|
64
|
+
return { dryRun: false, archive, applied: true, archiveDir, changes, commit };
|
|
65
|
+
} finally {
|
|
66
|
+
releaseGovernanceSync(context);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function governanceSurfaces(target, tasks) {
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
surface: "harness-ledger",
|
|
74
|
+
absolute: path.join(target.docsRoot, "Harness-Ledger.md"),
|
|
75
|
+
relative: toPosix(path.relative(target.projectRoot, path.join(target.docsRoot, "Harness-Ledger.md"))),
|
|
76
|
+
rows: tasks.map(ledgerRow),
|
|
77
|
+
content: replaceTableRows(readBundledTemplate("templates/ledger/Harness-Ledger.md"), /^ID$/i, tasks.map(ledgerRow)),
|
|
78
|
+
},
|
|
79
|
+
...legacyFeatureSurfaces(target),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function legacyFeatureSurfaces(target) {
|
|
84
|
+
return [
|
|
85
|
+
["legacy-feature-ssot", path.join(target.docsRoot, "09-PLANNING/Feature-SSoT.md")],
|
|
86
|
+
["legacy-private-feature-ssot", path.join(target.docsRoot, "09-PLANNING/Private-Feature-SSoT.md")],
|
|
87
|
+
].filter(([, absolute]) => fs.existsSync(absolute)).map(([surface, absolute]) => ({
|
|
88
|
+
surface,
|
|
89
|
+
absolute,
|
|
90
|
+
relative: toPosix(path.relative(target.projectRoot, absolute)),
|
|
91
|
+
rows: [],
|
|
92
|
+
content: "",
|
|
93
|
+
archiveOnly: true,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function replaceTableRows(content, headerPattern, rows) {
|
|
98
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
99
|
+
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
100
|
+
if (!lines[index].trim().startsWith("|")) continue;
|
|
101
|
+
const header = splitMarkdownRow(lines[index]);
|
|
102
|
+
if (!header.some((cell) => headerPattern.test(cell))) continue;
|
|
103
|
+
const separator = splitMarkdownRow(lines[index + 1]);
|
|
104
|
+
if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
105
|
+
let end = index + 2;
|
|
106
|
+
while (end < lines.length && lines[end].trim().startsWith("|")) end += 1;
|
|
107
|
+
const fitted = rows.map((row) => fitRow(row, header.length));
|
|
108
|
+
lines.splice(index + 2, end - index - 2, ...fitted.map((row) => `| ${row.join(" | ")} |`));
|
|
109
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
110
|
+
}
|
|
111
|
+
return `${String(content || "").trimEnd()}\n\n${rows.map((row) => `| ${row.join(" | ")} |`).join("\n")}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function fitRow(row, length) {
|
|
115
|
+
const next = row.map((cell) => markdownCell(cell));
|
|
116
|
+
while (next.length < length) next.push("");
|
|
117
|
+
return next.slice(0, length);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function ledgerRow(task) {
|
|
121
|
+
const plan = stripTarget(task.taskPlanPath || `${stripTarget(task.path)}/task_plan.md`);
|
|
122
|
+
const scope = task.module ? "module" : "task";
|
|
123
|
+
const moduleKey = task.module || "none";
|
|
124
|
+
return [
|
|
125
|
+
taskLedgerId(task),
|
|
126
|
+
scope,
|
|
127
|
+
moduleKey,
|
|
128
|
+
task.title || task.shortId || task.id,
|
|
129
|
+
mapLedgerState(task.state),
|
|
130
|
+
Array.isArray(task.taskQueues) && task.taskQueues.length ? task.taskQueues.join(",") : "none",
|
|
131
|
+
plan,
|
|
132
|
+
task.reviewStatus === "confirmed" ? stripTarget(task.reviewPath) : (task.reviewStatus || "pending"),
|
|
133
|
+
task.lessonCandidateDecisionComplete ? "checked" : (task.lessonCandidateStatus || "pending"),
|
|
134
|
+
task.walkthroughPath ? stripTarget(task.walkthroughPath) : (task.closeoutStatus || "pending"),
|
|
135
|
+
residual(task),
|
|
136
|
+
todayDate(),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function residual(task) {
|
|
141
|
+
if (Array.isArray(task.stateConflicts) && task.stateConflicts.length) return `state-conflicts:${task.stateConflicts.length}`;
|
|
142
|
+
if (Array.isArray(task.materialIssues) && task.materialIssues.length) return `material-issues:${task.materialIssues.length}`;
|
|
143
|
+
return "none";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function taskLedgerId(task) {
|
|
147
|
+
return `HL-${taskSlug(task)}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function taskSlug(task) {
|
|
151
|
+
return String(task.shortId || task.id || "task").replace(/^TASKS\//, "").replace(/^MODULES\//, "").replace(/[^A-Za-z0-9-]+/g, "-").slice(0, 72);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function stripTarget(value) {
|
|
155
|
+
return String(value || "").replace(/^TARGET:/, "");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function mapLedgerState(state) {
|
|
159
|
+
if (state === "in_progress") return "active";
|
|
160
|
+
if (state === "review") return "review";
|
|
161
|
+
if (state === "done") return "closed";
|
|
162
|
+
if (state === "blocked") return "blocked";
|
|
163
|
+
return "planned";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function uniqueArchiveDir(target) {
|
|
167
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(".", "-");
|
|
168
|
+
const base = `docs/01-GOVERNANCE/archive/generated-governance-tables/${stamp}`;
|
|
169
|
+
for (let index = 0; index < 100; index += 1) {
|
|
170
|
+
const candidate = index === 0 ? base : `${base}-${String(index).padStart(2, "0")}`;
|
|
171
|
+
if (!fs.existsSync(path.join(target.projectRoot, candidate))) return candidate;
|
|
172
|
+
}
|
|
173
|
+
throw new Error("Unable to allocate a unique governance table archive directory");
|
|
174
|
+
}
|