coding-agent-harness 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +244 -87
- package/README.zh-CN.md +77 -35
- package/SKILL.md +32 -24
- 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/architecture/system-explainer/01-system-overview.md +217 -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 +239 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -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 +250 -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 +323 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +39 -15
- package/docs-release/guides/agent-installation.md +43 -16
- 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 +238 -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 +224 -0
- package/docs-release/guides/task-state-machine.md +231 -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/INDEX.md +60 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +10 -4
- 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 +126 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +328 -0
- package/scripts/harness.mjs +59 -260
- package/scripts/lib/capability-registry.mjs +82 -28
- package/scripts/lib/check-module-parallel.mjs +230 -0
- package/scripts/lib/check-profiles.mjs +90 -228
- package/scripts/lib/check-task-contracts.mjs +55 -0
- package/scripts/lib/core-shared.mjs +65 -2
- package/scripts/lib/dashboard-data.mjs +155 -24
- package/scripts/lib/dashboard-workbench.mjs +131 -12
- package/scripts/lib/dashboard-writer.mjs +20 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +611 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +6 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +494 -0
- package/scripts/lib/preset-registry.mjs +776 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +105 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-audit-metadata.mjs +385 -0
- package/scripts/lib/task-audit-migration.mjs +350 -0
- package/scripts/lib/task-completion-consistency.mjs +26 -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/create-task-helpers.mjs +67 -0
- package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
- package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
- package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +338 -477
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +455 -0
- package/scripts/lib/task-scanner.mjs +193 -372
- 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 +43 -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 +24 -18
- package/templates/dashboard/assets/app-src/00-state.js +13 -0
- package/templates/dashboard/assets/app-src/10-router.js +5 -1
- package/templates/dashboard/assets/app-src/20-overview.js +18 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +286 -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/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 +302 -29
- package/templates/dashboard/assets/app.css +1501 -376
- package/templates/dashboard/assets/app.css.manifest.json +10 -0
- package/templates/dashboard/assets/app.js +1240 -101
- package/templates/dashboard/assets/app.manifest.json +2 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +346 -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 +489 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +263 -23
- 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/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +41 -0
- package/templates/planning/task_plan.md +5 -21
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +31 -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 +25 -19
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- 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/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +41 -1
- package/templates-zh-CN/planning/task_plan.md +4 -44
- 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/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 +33 -7
- 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
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { walkFiles } from "./core-shared.mjs";
|
|
4
|
+
|
|
5
|
+
function stripMarkdownCode(value) {
|
|
6
|
+
return String(value || "").replace(/`/g, "").trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function modulePromptBlock(content, key) {
|
|
10
|
+
const heading = `## Module: ${key}`;
|
|
11
|
+
const start = content.indexOf(heading);
|
|
12
|
+
if (start < 0) return "";
|
|
13
|
+
const rest = content.slice(start + heading.length);
|
|
14
|
+
const next = rest.search(/\n## Module: /);
|
|
15
|
+
return next >= 0 ? rest.slice(0, next) : rest;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function listModuleTaskPlans({ targetRoot, rel, filePath }) {
|
|
19
|
+
const modulesRoot = filePath("docs/09-PLANNING/MODULES");
|
|
20
|
+
if (!fs.existsSync(modulesRoot)) return [];
|
|
21
|
+
return walkFiles(modulesRoot, {
|
|
22
|
+
dirFilter: (_dirName, fullPath) => {
|
|
23
|
+
const relativePath = rel(path.relative(targetRoot, fullPath));
|
|
24
|
+
return !relativePath.includes("/_archive/") && !relativePath.endsWith("/_task-template");
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
.map((file) => rel(path.relative(targetRoot, file)))
|
|
28
|
+
.filter((relativePath) => /\/TASKS\/[^/]+\/task_plan\.md$/.test(relativePath));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseModuleTaskPath(taskPlanPath) {
|
|
32
|
+
const match = taskPlanPath.match(/^docs\/09-PLANNING\/MODULES\/([^/]+)\/TASKS\/([^/]+)\/task_plan\.md$/);
|
|
33
|
+
if (!match) return null;
|
|
34
|
+
return { moduleKey: match[1], taskDir: match[2] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extractStepId(taskPlanContent, taskDir) {
|
|
38
|
+
const fromPlan = taskPlanContent.match(/^- Step ID:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
39
|
+
if (fromPlan) return fromPlan[1];
|
|
40
|
+
const fromModuleSection = taskPlanContent.match(/^- Step:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
41
|
+
if (fromModuleSection) return fromModuleSection[1];
|
|
42
|
+
const fromDir = taskDir.match(/^([A-Z]{2,5}-\d{2})-/);
|
|
43
|
+
return fromDir ? fromDir[1] : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readTaskProgress(taskPlanPath, { exists, read }) {
|
|
47
|
+
const progressPath = taskPlanPath.replace(/task_plan\.md$/, "progress.md");
|
|
48
|
+
return exists(progressPath) ? read(progressPath) : "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeModuleTaskStatus(status) {
|
|
52
|
+
const value = String(status || "").trim().toLowerCase();
|
|
53
|
+
const aliases = new Map([
|
|
54
|
+
["未开始", "not-started"],
|
|
55
|
+
["未启动", "not-started"],
|
|
56
|
+
["进行中", "in-progress"],
|
|
57
|
+
["开发中", "in-progress"],
|
|
58
|
+
["规划审查", "planning-review"],
|
|
59
|
+
["已完成", "completed"],
|
|
60
|
+
["完成", "completed"],
|
|
61
|
+
["已关闭", "closed"],
|
|
62
|
+
["关闭", "closed"],
|
|
63
|
+
["已阻塞", "blocked"],
|
|
64
|
+
["阻塞", "blocked"],
|
|
65
|
+
]);
|
|
66
|
+
return aliases.get(value) || value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readTaskProgressStatus(taskPlanPath, context) {
|
|
70
|
+
const progress = readTaskProgress(taskPlanPath, context);
|
|
71
|
+
if (!progress) return "";
|
|
72
|
+
const match = progress.match(/^##\s*(?:Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
73
|
+
return match ? normalizeModuleTaskStatus(stripMarkdownCode(match[1])) : "";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isActiveModuleTaskStatus(status) {
|
|
77
|
+
if (!status) return false;
|
|
78
|
+
return !new Set([
|
|
79
|
+
"not-started",
|
|
80
|
+
"blocked-not-started",
|
|
81
|
+
"complete",
|
|
82
|
+
"completed",
|
|
83
|
+
"closed",
|
|
84
|
+
"closed-with-residual",
|
|
85
|
+
"closed-local-only",
|
|
86
|
+
"superseded",
|
|
87
|
+
]).has(status);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasPendingCoordinatorHandoff(taskPlanContent, progressContent) {
|
|
91
|
+
const combined = `${taskPlanContent}\n${progressContent}`;
|
|
92
|
+
return /Coordinator Handoff/i.test(combined) && /pending-coordinator-pass/i.test(combined);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkModuleTaskSsotIndex(registryRows, context) {
|
|
96
|
+
const { exists, read, fail, warn, requireGlobalModuleSync } = context;
|
|
97
|
+
const registryByModule = new Map(registryRows.map((cells) => [cells[0], cells]));
|
|
98
|
+
const ledgerContent = exists("docs/Harness-Ledger.md") ? read("docs/Harness-Ledger.md") : "";
|
|
99
|
+
const taskPlans = listModuleTaskPlans(context);
|
|
100
|
+
|
|
101
|
+
for (const taskPlanPath of taskPlans) {
|
|
102
|
+
const parsed = parseModuleTaskPath(taskPlanPath);
|
|
103
|
+
if (!parsed) continue;
|
|
104
|
+
const { moduleKey, taskDir } = parsed;
|
|
105
|
+
const modulePlanPath = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
|
|
106
|
+
if (!exists(modulePlanPath)) continue;
|
|
107
|
+
|
|
108
|
+
const taskPlan = read(taskPlanPath);
|
|
109
|
+
const taskProgress = readTaskProgress(taskPlanPath, context);
|
|
110
|
+
const taskProgressStatus = readTaskProgressStatus(taskPlanPath, context);
|
|
111
|
+
const taskIsActive = isActiveModuleTaskStatus(taskProgressStatus);
|
|
112
|
+
const stepId = extractStepId(taskPlan, taskDir);
|
|
113
|
+
if (!stepId) {
|
|
114
|
+
if (taskIsActive) {
|
|
115
|
+
fail(`${taskPlanPath} does not expose a Step ID and task directory does not start with <PREFIX-NN>`);
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const modulePlan = read(modulePlanPath);
|
|
121
|
+
const moduleRelativeTaskPlan = `TASKS/${taskDir}/task_plan.md`;
|
|
122
|
+
if (!modulePlan.includes(stepId) || !modulePlan.includes(moduleRelativeTaskPlan)) {
|
|
123
|
+
fail(`${modulePlanPath} does not index ${stepId} task plan ${moduleRelativeTaskPlan}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!taskIsActive) continue;
|
|
127
|
+
|
|
128
|
+
const registryRow = registryByModule.get(moduleKey);
|
|
129
|
+
const reviewPath = taskPlanPath.replace(/task_plan\.md$/, "review.md");
|
|
130
|
+
const registrySynced = Boolean(registryRow && registryRow[4] === stepId);
|
|
131
|
+
const ledgerSynced = ledgerContent.includes(taskPlanPath) && (!exists(reviewPath) || ledgerContent.includes(reviewPath));
|
|
132
|
+
if (registrySynced && ledgerSynced) continue;
|
|
133
|
+
|
|
134
|
+
if (requireGlobalModuleSync) {
|
|
135
|
+
if (!registryRow) {
|
|
136
|
+
fail(`docs/09-PLANNING/Module-Registry.md does not include active module ${moduleKey} for ${taskPlanPath}`);
|
|
137
|
+
} else if (registryRow[4] !== stepId) {
|
|
138
|
+
fail(`docs/09-PLANNING/Module-Registry.md row ${moduleKey} current step is ${registryRow[4]}, but active task is ${stepId}`);
|
|
139
|
+
}
|
|
140
|
+
if (!ledgerContent.includes(taskPlanPath)) {
|
|
141
|
+
fail(`docs/Harness-Ledger.md does not index active module task plan ${taskPlanPath}`);
|
|
142
|
+
}
|
|
143
|
+
if (exists(reviewPath) && !ledgerContent.includes(reviewPath)) {
|
|
144
|
+
fail(`docs/Harness-Ledger.md does not index active module review ${reviewPath}`);
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (hasPendingCoordinatorHandoff(taskPlan, taskProgress)) {
|
|
150
|
+
warn(`${taskPlanPath} has pending coordinator handoff; run coordinator pass before final integration or set HARNESS_REQUIRE_GLOBAL_MODULE_SYNC=1 for strict gate`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
fail(`${taskPlanPath} is active but is neither globally synced nor marked with Coordinator Handoff: pending-coordinator-pass`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function checkModuleParallelStructure(context) {
|
|
158
|
+
const { exists, read, fail, requireFile, markdownTable } = context;
|
|
159
|
+
if (!exists("docs/09-PLANNING/Module-Registry.md")) return;
|
|
160
|
+
|
|
161
|
+
requireFile("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
162
|
+
const hasPromptPack = exists("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
163
|
+
for (const templateFile of [
|
|
164
|
+
"docs/09-PLANNING/MODULES/_task-template/task_plan.md",
|
|
165
|
+
"docs/09-PLANNING/MODULES/_task-template/progress.md",
|
|
166
|
+
"docs/09-PLANNING/MODULES/_task-template/findings.md",
|
|
167
|
+
"docs/09-PLANNING/MODULES/_task-template/review.md",
|
|
168
|
+
]) {
|
|
169
|
+
requireFile(templateFile);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const registryContent = read("docs/09-PLANNING/Module-Registry.md");
|
|
173
|
+
for (const term of ["PREFIX", "Current Step", "Status", "Write Scope"]) {
|
|
174
|
+
if (!registryContent.includes(term)) {
|
|
175
|
+
fail(`docs/09-PLANNING/Module-Registry.md missing registry column or section: ${term}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const registryRows = markdownTable(registryContent)
|
|
180
|
+
.filter((cells) => cells.length >= 6)
|
|
181
|
+
.filter((cells) => /^(_shared|[a-z][a-z0-9-]*)$/.test(cells[0] || "") && /^[A-Z]{2,5}$/.test(cells[2] || ""));
|
|
182
|
+
|
|
183
|
+
if (registryRows.length === 0) {
|
|
184
|
+
fail("docs/09-PLANNING/Module-Registry.md has no active module rows");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const promptPack = hasPromptPack ? read("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md") : "";
|
|
188
|
+
if (hasPromptPack && !/Subagent Worker Invariant|worker[\s\S]{0,120}worktree[\s\S]{0,120}commit SHA/i.test(promptPack)) {
|
|
189
|
+
fail("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md missing subagent worker worktree/commit handoff rule");
|
|
190
|
+
}
|
|
191
|
+
for (const cells of registryRows) {
|
|
192
|
+
const [key, , prefix, branch, currentStep, status] = cells;
|
|
193
|
+
requireFile(`docs/09-PLANNING/MODULES/${key}/module_plan.md`);
|
|
194
|
+
if (!/^(planned|in-progress|paused|completed)$/.test(status)) {
|
|
195
|
+
fail(`docs/09-PLANNING/Module-Registry.md row ${key} has invalid status: ${status}`);
|
|
196
|
+
}
|
|
197
|
+
if (currentStep !== `${prefix}-00` && !currentStep.startsWith(`${prefix}-`)) {
|
|
198
|
+
fail(`docs/09-PLANNING/Module-Registry.md row ${key} current step does not match prefix ${prefix}: ${currentStep}`);
|
|
199
|
+
}
|
|
200
|
+
const branchName = stripMarkdownCode(branch);
|
|
201
|
+
if (!branchName.startsWith("codex/")) {
|
|
202
|
+
fail(`docs/09-PLANNING/Module-Registry.md row ${key} branch must use codex/ prefix: ${branch}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const block = modulePromptBlock(promptPack, key);
|
|
206
|
+
if (!block) {
|
|
207
|
+
if (!exists(`docs/09-PLANNING/MODULES/${key}/session_prompt.md`)) {
|
|
208
|
+
fail(`missing module session prompt for ${key}`);
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
for (const term of [
|
|
213
|
+
"Current Step",
|
|
214
|
+
branchName,
|
|
215
|
+
"Preflight:",
|
|
216
|
+
"Before code edits:",
|
|
217
|
+
"Write scope:",
|
|
218
|
+
"Forbidden without coordination:",
|
|
219
|
+
"Shared Coordination:",
|
|
220
|
+
"Verification:",
|
|
221
|
+
"Closeout:",
|
|
222
|
+
"Stop conditions:",
|
|
223
|
+
]) {
|
|
224
|
+
if (!block.includes(term)) {
|
|
225
|
+
fail(`module session prompt for ${key} missing required term: ${term}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
checkModuleTaskSsotIndex(registryRows, context);
|
|
230
|
+
}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
legacyChecker,
|
|
7
7
|
visualMapFile,
|
|
8
8
|
legacyVisualRoadmapFile,
|
|
9
|
-
lessonCandidatesFile,
|
|
10
9
|
allowedReviewDispositions,
|
|
11
10
|
allowedPhaseStates,
|
|
12
11
|
allowedEvidenceStatus,
|
|
@@ -23,19 +22,20 @@ import {
|
|
|
23
22
|
firstColumn,
|
|
24
23
|
contentHasAny,
|
|
25
24
|
} from "./markdown-utils.mjs";
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from "./
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} from "./
|
|
25
|
+
import { validateCapabilities } from "./capability-registry.mjs";
|
|
26
|
+
import { readPresetPackage } from "./preset-registry.mjs";
|
|
27
|
+
import { validateTaskPresetAuditSnapshot } from "./preset-audit-contracts.mjs";
|
|
28
|
+
import { validatePresetResourcesForTask } from "./preset-resource-contracts.mjs";
|
|
29
|
+
import { collectTasks, listTaskPlanPaths, parseTaskBudget, readVisualMapContractFile, parsePhases } from "./task-scanner.mjs";
|
|
30
|
+
import { normalizeReviewBoolean, reviewFindingColumns } from "./task-review-model.mjs";
|
|
31
|
+
import { allowedPhaseActors, allowedPhaseKinds } from "./phase-kind.mjs";
|
|
32
|
+
import { validateTaskCompletionConsistency } from "./task-completion-consistency.mjs";
|
|
33
|
+
import { validatePlanContracts } from "./check-task-contracts.mjs";
|
|
34
|
+
import { validateGovernanceTableBoundaries } from "./governance-table-boundary.mjs";
|
|
35
|
+
import { validateSubagentAuthorization } from "./subagent-authorization-audit.mjs";
|
|
36
|
+
import { summarizeGitState } from "./git-status-summary.mjs";
|
|
37
|
+
import { buildStatusData } from "./status-builder.mjs";
|
|
38
|
+
export { renderDashboard } from "./status-dashboard-renderer.mjs";
|
|
39
39
|
|
|
40
40
|
export function runLegacyCheck(target) {
|
|
41
41
|
const checkTarget = target.docsOnly ? target.projectRoot : target.input;
|
|
@@ -94,10 +94,10 @@ export function validateReviewSchema(target, { strict = true } = {}) {
|
|
|
94
94
|
}
|
|
95
95
|
const { header, rows } = tableAfterHeading(content, /^ID$/i);
|
|
96
96
|
if (rows.length === 0) continue;
|
|
97
|
-
const severityIndex = getColumnAny(header,
|
|
98
|
-
const openIndex = getColumnAny(header,
|
|
99
|
-
const dispositionIndex = getColumnAny(header,
|
|
100
|
-
const blocksIndex = getColumnAny(header,
|
|
97
|
+
const severityIndex = getColumnAny(header, reviewFindingColumns.severity);
|
|
98
|
+
const openIndex = getColumnAny(header, reviewFindingColumns.open);
|
|
99
|
+
const dispositionIndex = getColumnAny(header, reviewFindingColumns.disposition);
|
|
100
|
+
const blocksIndex = getColumnAny(header, reviewFindingColumns.blocksRelease);
|
|
101
101
|
const followUpIndex = getColumnAny(header, ["Follow-up", "跟进"]);
|
|
102
102
|
const evidenceCheckedIndex = getColumnAny(header, ["Evidence Checked", "已检查证据"]);
|
|
103
103
|
if ([severityIndex, openIndex, dispositionIndex, blocksIndex].some((index) => index < 0)) {
|
|
@@ -108,9 +108,9 @@ export function validateReviewSchema(target, { strict = true } = {}) {
|
|
|
108
108
|
const id = row[0] || "";
|
|
109
109
|
const severity = row[severityIndex] || "";
|
|
110
110
|
if (!/^P[0-3]$/.test(severity) && !/^(R|SR)-\d+/i.test(id)) continue;
|
|
111
|
-
const open = (row[openIndex] || "")
|
|
111
|
+
const open = normalizeReviewBoolean(row[openIndex] || "");
|
|
112
112
|
const disposition = (row[dispositionIndex] || "").toLowerCase();
|
|
113
|
-
const blocks = (row[blocksIndex] || "")
|
|
113
|
+
const blocks = normalizeReviewBoolean(row[blocksIndex] || "");
|
|
114
114
|
const followUp = row[followUpIndex] || "";
|
|
115
115
|
if (!/^P[0-3]$/.test(severity)) report(`${relative} ${id} invalid severity: ${severity}`);
|
|
116
116
|
if (!["yes", "no"].includes(open)) report(`${relative} ${id} invalid Open value: ${open}`);
|
|
@@ -128,18 +128,19 @@ export function validateReviewSchema(target, { strict = true } = {}) {
|
|
|
128
128
|
for (const ref of refs) {
|
|
129
129
|
if (ref !== "none" && /^E-\d+/i.test(ref) && !evidenceIds.has(ref)) {
|
|
130
130
|
failures.push(`${relative} ${id} references missing evidence id: ${ref}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
133
|
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
}
|
|
136
137
|
return { failures, warnings };
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
export function validateVisualMaps(target) {
|
|
140
|
+
export function validateVisualMaps(target, { taskPlanPaths } = {}) {
|
|
140
141
|
const failures = [];
|
|
141
142
|
const warnings = [];
|
|
142
|
-
for (const taskPlanPath of listTaskPlanPaths(target)) {
|
|
143
|
+
for (const taskPlanPath of taskPlanPaths || listTaskPlanPaths(target)) {
|
|
143
144
|
const taskDir = path.dirname(taskPlanPath);
|
|
144
145
|
const visualMapPath = path.join(taskDir, visualMapFile);
|
|
145
146
|
const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
|
|
@@ -153,7 +154,10 @@ export function validateVisualMaps(target) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
const phases = parsePhases(visualMap.content);
|
|
157
|
+
const budget = parseTaskBudget(taskPlan);
|
|
156
158
|
for (const phase of phases) {
|
|
159
|
+
if (!allowedPhaseKinds.has(phase.kind)) failures.push(`${relative} phase ${phase.id} invalid kind: ${phase.kind}`);
|
|
160
|
+
if (!allowedPhaseActors.has(phase.actor)) failures.push(`${relative} phase ${phase.id} invalid actor: ${phase.actor}`);
|
|
157
161
|
if (!allowedPhaseStates.has(phase.state)) failures.push(`${relative} phase ${phase.id} invalid state: ${phase.state}`);
|
|
158
162
|
if (!allowedEvidenceStatus.has(phase.evidenceStatus)) {
|
|
159
163
|
failures.push(`${relative} phase ${phase.id} invalid evidence status: ${phase.evidenceStatus}`);
|
|
@@ -168,6 +172,9 @@ export function validateVisualMaps(target) {
|
|
|
168
172
|
failures.push(`${relative} missing Visual Map Contract: v1.0`);
|
|
169
173
|
}
|
|
170
174
|
if (visualMap.source === "canonical" && phases.length === 0) warnings.push(`${relative} has no Visual Map phase table`);
|
|
175
|
+
if (visualMap.source === "canonical" && budget !== "simple" && phases.length > 0 && !phases.some((phase) => phase.kind === "execution" && phase.state !== "skipped")) {
|
|
176
|
+
failures.push(`${relative} requires at least one non-skipped execution phase`);
|
|
177
|
+
}
|
|
171
178
|
if (visualMap.source === "legacy" && fs.existsSync(legacyPath)) {
|
|
172
179
|
warnings.push(`${relative} missing; legacy visual_roadmap.md is rewrite input only`);
|
|
173
180
|
} else if (visualMap.source === "legacy" && phases.length > 0) {
|
|
@@ -177,34 +184,7 @@ export function validateVisualMaps(target) {
|
|
|
177
184
|
return { failures, warnings };
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
export function
|
|
181
|
-
const failures = [];
|
|
182
|
-
const warnings = [];
|
|
183
|
-
const report = (message) => {
|
|
184
|
-
if (strict) failures.push(message);
|
|
185
|
-
else warnings.push(`adoption-needed: ${message}`);
|
|
186
|
-
};
|
|
187
|
-
for (const taskPlanPath of listTaskPlanPaths(target)) {
|
|
188
|
-
const taskDir = path.dirname(taskPlanPath);
|
|
189
|
-
const relativeDir = toPosix(path.relative(target.projectRoot, taskDir));
|
|
190
|
-
const taskPlanContent = readFileSafe(taskPlanPath);
|
|
191
|
-
const budget = parseTaskBudget(taskPlanContent);
|
|
192
|
-
const taskContract = parseTaskContractInfo(taskPlanContent);
|
|
193
|
-
if (!taskContract.generated) {
|
|
194
|
-
warnings.push(`adoption-needed: ${relativeDir} missing Task Contract: harness-task/v1 marker`);
|
|
195
|
-
}
|
|
196
|
-
const requiredFiles = budget === "simple" ? [visualMapFile] : ["execution_strategy.md", visualMapFile, lessonCandidatesFile];
|
|
197
|
-
for (const fileName of requiredFiles) {
|
|
198
|
-
if (!fs.existsSync(path.join(taskDir, fileName))) {
|
|
199
|
-
if (taskContract.generated) failures.push(`${relativeDir} missing ${fileName}`);
|
|
200
|
-
else report(`${relativeDir} missing ${fileName}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return { failures, warnings };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function validateTaskPresetContracts(target) {
|
|
187
|
+
export function validateTaskPresetContracts(target, { tasks } = {}) {
|
|
208
188
|
const failures = [];
|
|
209
189
|
const allowedMigrationLevels = new Set([
|
|
210
190
|
"migration-baseline",
|
|
@@ -212,15 +192,40 @@ export function validateTaskPresetContracts(target) {
|
|
|
212
192
|
"migration-full-cutover",
|
|
213
193
|
"migration-deferred",
|
|
214
194
|
]);
|
|
215
|
-
for (const task of collectTasks(target)) {
|
|
195
|
+
for (const task of tasks || collectTasks(target)) {
|
|
216
196
|
if (!task.taskPreset || task.taskPreset === "none") continue;
|
|
197
|
+
let presetPackage = null;
|
|
198
|
+
try {
|
|
199
|
+
presetPackage = readPresetPackage(task.taskPreset, { targetInput: target.projectRoot });
|
|
200
|
+
} catch (error) {
|
|
201
|
+
failures.push(`${task.path} unsupported Task Preset: ${task.taskPreset} (${error.message})`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (presetPackage?.task?.kind && task.taskKind !== presetPackage.task.kind) {
|
|
205
|
+
failures.push(`${task.path} ${task.taskPreset} preset Task Kind mismatch: expected ${presetPackage.task.kind}, got ${task.taskKind || "(missing)"}`);
|
|
206
|
+
}
|
|
207
|
+
if (String(task.presetVersion || "") !== String(presetPackage.version)) {
|
|
208
|
+
failures.push(`${task.path} ${task.taskPreset} preset missing Preset Version ${presetPackage.version}`);
|
|
209
|
+
}
|
|
210
|
+
if (task.taskPreset !== "lesson-sedimentation" && (presetPackage.evidence?.bundleDir || presetPackage.audit?.evidenceFiles?.length || Object.keys(presetPackage.evidence?.files || {}).length)) {
|
|
211
|
+
if (!task.evidenceBundle) failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle`);
|
|
212
|
+
else if (!fs.existsSync(path.join(target.projectRoot, String(task.evidenceBundle).replace(/^TARGET:/, "").replace(/^\/+/, "")))) {
|
|
213
|
+
failures.push(`${task.path} ${task.taskPreset} preset Evidence Bundle missing: ${task.evidenceBundle}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (task.taskPreset !== "lesson-sedimentation") {
|
|
217
|
+
failures.push(...validateTaskPresetAuditSnapshot(target, task, presetPackage));
|
|
218
|
+
}
|
|
219
|
+
failures.push(...validatePresetResourcesForTask(target, task, presetPackage));
|
|
220
|
+
if (task.taskPreset === "lesson-sedimentation") {
|
|
221
|
+
if (!["standard", "complex"].includes(task.budget)) failures.push(`${task.path} lesson-sedimentation preset requires Selected budget: standard or complex`);
|
|
222
|
+
if (!task.taskPlanPath) failures.push(`${task.path} lesson-sedimentation preset missing task plan`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
217
225
|
if (task.taskPreset !== "legacy-migration") {
|
|
218
|
-
failures.push(`${task.path} unsupported Task Preset: ${task.taskPreset}`);
|
|
219
226
|
continue;
|
|
220
227
|
}
|
|
221
228
|
if (task.budget !== "complex") failures.push(`${task.path} legacy-migration preset requires Selected budget: complex`);
|
|
222
|
-
if (!task.presetVersion) failures.push(`${task.path} legacy-migration preset missing Preset Version`);
|
|
223
|
-
if (!task.taskKind || task.taskKind === "general") failures.push(`${task.path} legacy-migration preset missing Task Kind`);
|
|
224
229
|
if (!allowedMigrationLevels.has(task.migrationTargetLevel)) {
|
|
225
230
|
failures.push(`${task.path} legacy-migration preset invalid Migration Target Level: ${task.migrationTargetLevel || "(missing)"}`);
|
|
226
231
|
}
|
|
@@ -228,9 +233,7 @@ export function validateTaskPresetContracts(target) {
|
|
|
228
233
|
if (achievedLevel !== "pending" && !allowedMigrationLevels.has(achievedLevel)) {
|
|
229
234
|
failures.push(`${task.path} legacy-migration preset invalid Migration Achieved Level: ${achievedLevel || "(missing)"}`);
|
|
230
235
|
}
|
|
231
|
-
if (!task.
|
|
232
|
-
failures.push(`${task.path} legacy-migration preset missing Evidence Bundle`);
|
|
233
|
-
} else if (!task.migrationSnapshot?.evidencePresent) {
|
|
236
|
+
if (task.evidenceBundle && !task.migrationSnapshot?.evidencePresent) {
|
|
234
237
|
failures.push(`${task.path} legacy-migration preset Evidence Bundle missing: ${task.evidenceBundle}`);
|
|
235
238
|
} else if (!task.migrationSnapshot?.sessionPresent) {
|
|
236
239
|
failures.push(`${task.path} legacy-migration preset Evidence Bundle missing session.json`);
|
|
@@ -315,196 +318,55 @@ export function validateContextDocs(target, { strict = true } = {}) {
|
|
|
315
318
|
|
|
316
319
|
export function buildStatus(targetInput, options = {}) {
|
|
317
320
|
const target = normalizeTarget(targetInput);
|
|
321
|
+
const gitState = summarizeGitState(target);
|
|
318
322
|
const capabilityState = validateCapabilities(target);
|
|
319
323
|
const declaredCapabilities = new Set(capabilityState.registry.capabilities.map((capability) => capability.name));
|
|
320
324
|
const safeAdoptionMode = declaredCapabilities.has("safe-adoption");
|
|
321
325
|
const shouldRunLegacy = !options.skipLegacyCheck && (capabilityState.registry.mode === "legacy-compat" || safeAdoptionMode);
|
|
322
326
|
const legacy = shouldRunLegacy ? runLegacyCheck(target) : { status: "skipped", code: 0, stdout: "", stderr: "" };
|
|
323
327
|
const contractStrict = Boolean(options.strict) || (capabilityState.registry.mode !== "legacy-compat" && !safeAdoptionMode);
|
|
328
|
+
const taskPlanPaths = listTaskPlanPaths(target);
|
|
329
|
+
const closeoutContent = readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
330
|
+
const tasks = collectTasks(target, { requireGeneratedScaffoldProvenance: contractStrict, taskPlanPaths, closeoutContent });
|
|
324
331
|
const reviews = validateReviewSchema(target, { strict: contractStrict });
|
|
325
|
-
const visualMaps = validateVisualMaps(target);
|
|
326
|
-
const planContracts = validatePlanContracts(target, { strict: contractStrict });
|
|
327
|
-
const presetContracts = validateTaskPresetContracts(target);
|
|
332
|
+
const visualMaps = validateVisualMaps(target, { taskPlanPaths });
|
|
333
|
+
const planContracts = validatePlanContracts(target, { strict: contractStrict, taskPlanPaths });
|
|
334
|
+
const presetContracts = validateTaskPresetContracts(target, { tasks });
|
|
328
335
|
const contextDocs = validateContextDocs(target, { strict: contractStrict });
|
|
329
|
-
const
|
|
330
|
-
const
|
|
336
|
+
const governanceBoundaries = validateGovernanceTableBoundaries(target);
|
|
337
|
+
const subagentAuthorization = validateSubagentAuthorization(target, { strict: contractStrict });
|
|
338
|
+
const failures = [...capabilityState.failures, ...reviews.failures, ...visualMaps.failures, ...planContracts.failures, ...presetContracts.failures, ...contextDocs.failures, ...governanceBoundaries.failures, ...subagentAuthorization.failures];
|
|
339
|
+
const warnings = [...capabilityState.warnings, ...reviews.warnings, ...visualMaps.warnings, ...planContracts.warnings, ...presetContracts.warnings, ...contextDocs.warnings, ...governanceBoundaries.warnings, ...subagentAuthorization.warnings, ...gitState.warnings];
|
|
331
340
|
if (legacy.status === "fail") {
|
|
332
341
|
if (options.strictLegacy) failures.push("legacy check failed");
|
|
333
342
|
else warnings.push(`adoption-needed: legacy check failed: ${(legacy.stderr || legacy.stdout).trim()}`);
|
|
334
343
|
}
|
|
335
344
|
|
|
336
|
-
const
|
|
345
|
+
const taskCompletionConsistency = validateTaskCompletionConsistency(tasks);
|
|
346
|
+
failures.push(...taskCompletionConsistency.failures);
|
|
347
|
+
warnings.push(...taskCompletionConsistency.warnings);
|
|
337
348
|
const briefReady = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
338
349
|
const briefMissing = tasks.length - briefReady;
|
|
339
350
|
for (const task of tasks) {
|
|
351
|
+
for (const issue of task.materialIssues || []) {
|
|
352
|
+
if (!String(issue.code || "").startsWith("missing-task-audit") && !String(issue.code || "").startsWith("legacy-")) continue;
|
|
353
|
+
const message = `${String(issue.sourcePath || task.path).replace(/^TARGET:/, "")} ${issue.message}`;
|
|
354
|
+
if (contractStrict || options.strictLegacy) failures.push(message);
|
|
355
|
+
else warnings.push(`adoption-needed: ${message}`);
|
|
356
|
+
}
|
|
340
357
|
if (task.stateSource === "invalid") {
|
|
341
358
|
const message = `${task.path}/progress.md invalid task state: ${task.stateRaw}`;
|
|
342
359
|
if (contractStrict || options.strictLegacy) failures.push(message);
|
|
343
360
|
else warnings.push(`adoption-needed: ${message}`);
|
|
344
361
|
}
|
|
345
362
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
failures.length === 0 &&
|
|
353
|
-
warnings.length === 0 &&
|
|
354
|
-
cutoverCounters.legacyVisualOnlyCount === 0 &&
|
|
355
|
-
cutoverCounters.unknownClassificationCount === 0 &&
|
|
356
|
-
cutoverCounters.weakBriefCount === 0 &&
|
|
357
|
-
cutoverCounters.missingCanonicalVisualMapCount === 0;
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
project: {
|
|
361
|
-
name: path.basename(target.projectRoot),
|
|
362
|
-
root: `TARGET:${target.docsOnly ? toPosix(path.relative(target.projectRoot, target.docsRoot)) : "."}`,
|
|
363
|
-
docsOnly: target.docsOnly,
|
|
364
|
-
},
|
|
365
|
-
schemaVersion: 2,
|
|
366
|
-
generatedAt: new Date().toISOString(),
|
|
367
|
-
mode: capabilityState.registry.mode,
|
|
368
|
-
checkState: {
|
|
369
|
-
status: failures.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass",
|
|
370
|
-
failures: failures.length,
|
|
371
|
-
warnings: warnings.length,
|
|
372
|
-
details: { failures, warnings },
|
|
373
|
-
legacy,
|
|
374
|
-
},
|
|
375
|
-
summary: {
|
|
376
|
-
tasks: tasks.length,
|
|
377
|
-
briefCoverage: {
|
|
378
|
-
ready: briefReady,
|
|
379
|
-
missing: briefMissing,
|
|
380
|
-
total: tasks.length,
|
|
381
|
-
},
|
|
382
|
-
visualMapCoverage: {
|
|
383
|
-
canonical: tasks.filter((task) => task.visualMapSource === "canonical").length,
|
|
384
|
-
legacyOnly: cutoverCounters.legacyVisualOnlyCount,
|
|
385
|
-
missing: tasks.filter((task) => task.visualMapStatus === "missing").length,
|
|
386
|
-
total: tasks.length,
|
|
387
|
-
},
|
|
388
|
-
fullCutoverEligible,
|
|
389
|
-
legacyVisualOnlyCount: cutoverCounters.legacyVisualOnlyCount,
|
|
390
|
-
unknownClassificationCount: cutoverCounters.unknownClassificationCount,
|
|
391
|
-
weakBriefCount: cutoverCounters.weakBriefCount,
|
|
392
|
-
visualMapRequiredCount: cutoverCounters.visualMapRequiredCount,
|
|
393
|
-
missingCanonicalVisualMapCount: cutoverCounters.missingCanonicalVisualMapCount,
|
|
394
|
-
},
|
|
395
|
-
capabilities: [...capabilityNames.values()].map((capability) => ({
|
|
396
|
-
name: capability.name,
|
|
397
|
-
state: capability.state || "configured",
|
|
398
|
-
dependencyStatus: capabilityDefinitions[capability.name]?.dependencies.every((dependency) => capabilityNames.has(dependency))
|
|
399
|
-
? "valid"
|
|
400
|
-
: "invalid",
|
|
401
|
-
warnings: capabilityState.warnings.filter((warning) => warning.includes(capability.name)),
|
|
402
|
-
})),
|
|
363
|
+
return buildStatusData(target, {
|
|
364
|
+
capabilityState,
|
|
365
|
+
gitState,
|
|
366
|
+
legacy,
|
|
367
|
+
failures,
|
|
368
|
+
warnings,
|
|
403
369
|
tasks,
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export function renderDashboard(status) {
|
|
410
|
-
const taskCards = status.tasks
|
|
411
|
-
.map((task) => {
|
|
412
|
-
const phases = task.phases
|
|
413
|
-
.map(
|
|
414
|
-
(phase) => `<div class="phase ${escapeHtml(phase.state)}">
|
|
415
|
-
<div class="phase-top"><strong>${escapeHtml(phase.id)}</strong><span>${phase.completion}%</span></div>
|
|
416
|
-
<div class="phase-output">${escapeHtml(phase.output)}</div>
|
|
417
|
-
<div class="meter"><i style="width:${phase.completion}%"></i></div>
|
|
418
|
-
<div class="muted">${escapeHtml(phase.state)} · evidence ${escapeHtml(phase.evidenceStatus)}</div>
|
|
419
|
-
</div>`,
|
|
420
|
-
)
|
|
421
|
-
.join("");
|
|
422
|
-
const risks = task.risks
|
|
423
|
-
.map((risk) => `<span class="risk ${risk.open || risk.blocksRelease ? "open" : ""}">${escapeHtml(risk.severity)} ${escapeHtml(risk.summary)}</span>`)
|
|
424
|
-
.join("");
|
|
425
|
-
const evidence = task.evidence
|
|
426
|
-
.map((item) => `<span class="evidence">${escapeHtml(item.type)} · ${escapeHtml(item.summary)}</span>`)
|
|
427
|
-
.join("");
|
|
428
|
-
const evidenceMeter = evidenceCompletion(task.phases);
|
|
429
|
-
return `<section class="task">
|
|
430
|
-
<div class="task-head">
|
|
431
|
-
<div><h2>${escapeHtml(task.title)}</h2><p>${escapeHtml(task.path)}</p></div>
|
|
432
|
-
<div class="score">${task.completion}%</div>
|
|
433
|
-
</div>
|
|
434
|
-
<div class="meter"><i style="width:${task.completion}%"></i></div>
|
|
435
|
-
<div class="phases">${phases || '<div class="empty">No phase table</div>'}</div>
|
|
436
|
-
<div class="evidence-row"><strong>Evidence</strong><div class="meter small"><i style="width:${evidenceMeter}%"></i></div>${evidence || '<span class="empty">No evidence</span>'}</div>
|
|
437
|
-
<div class="risks">${risks || '<span class="ok">No open visual risk</span>'}</div>
|
|
438
|
-
</section>`;
|
|
439
|
-
})
|
|
440
|
-
.join("");
|
|
441
|
-
const chips = status.capabilities
|
|
442
|
-
.map((capability) => `<span class="chip ${escapeHtml(capability.state)}">${escapeHtml(capability.name)} · ${escapeHtml(capability.state)}</span>`)
|
|
443
|
-
.join("");
|
|
444
|
-
const failures = status.checkState.details.failures.map((failure) => `<li>${escapeHtml(failure)}</li>`).join("");
|
|
445
|
-
const warnings = status.checkState.details.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
|
|
446
|
-
const handoffs = status.handoffs
|
|
447
|
-
.map((handoff) => `<span class="handoff">${escapeHtml(handoff.state)} · ${escapeHtml(handoff.summary)}</span>`)
|
|
448
|
-
.join("");
|
|
449
|
-
const activity = status.recentActivity
|
|
450
|
-
.map((item) => `<li><strong>${escapeHtml(item.type)}</strong> ${escapeHtml(item.summary)}</li>`)
|
|
451
|
-
.join("");
|
|
452
|
-
return `<!doctype html>
|
|
453
|
-
<html lang="zh-CN">
|
|
454
|
-
<head>
|
|
455
|
-
<meta charset="utf-8">
|
|
456
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
457
|
-
<title>${escapeHtml(status.project.name)} Harness Dashboard</title>
|
|
458
|
-
<style>
|
|
459
|
-
:root{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#17202a;background:#f6f7f9}
|
|
460
|
-
body{margin:0}.shell{max-width:1180px;margin:0 auto;padding:28px}
|
|
461
|
-
header{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;margin-bottom:24px}
|
|
462
|
-
h1,h2{margin:0;letter-spacing:0}h1{font-size:30px}h2{font-size:18px}p{margin:6px 0;color:#687382}
|
|
463
|
-
.pill,.chip,.risk,.ok{display:inline-flex;align-items:center;border-radius:999px;padding:6px 10px;font-size:12px;margin:4px;background:#e8edf3;color:#273444}
|
|
464
|
-
.pass,.verified{background:#dff5e8;color:#125c32}.warn,.configured{background:#fff0cc;color:#765100}.fail,.open{background:#ffe1df;color:#8a1c12}.scaffolded{background:#e8edf3;color:#273444}
|
|
465
|
-
.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:20px}.stat,.task{background:#fff;border:1px solid #e4e8ee;border-radius:8px;padding:16px}
|
|
466
|
-
.stat strong{font-size:24px;display:block}.capabilities{margin-bottom:20px}.task{margin-bottom:16px}.task-head{display:flex;justify-content:space-between;gap:16px}
|
|
467
|
-
.score{font-size:28px;font-weight:700;color:#223047}.meter{height:8px;background:#edf1f5;border-radius:99px;overflow:hidden;margin:10px 0}.meter i{display:block;height:100%;background:#2f6fed}.meter.small{height:6px;max-width:180px}
|
|
468
|
-
.evidence,.handoff{display:inline-flex;padding:5px 8px;margin:4px;border-radius:6px;background:#edf7ff;color:#214d72;font-size:12px}.handoff{background:#fff3d8;color:#745000}
|
|
469
|
-
.phases{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin-top:12px}.phase{border:1px solid #e5eaf0;border-radius:8px;padding:12px;background:#fbfcfe}.phase-top{display:flex;justify-content:space-between}.phase-output{min-height:38px;margin-top:8px}
|
|
470
|
-
.risks{margin-top:12px}.empty{color:#8a95a3}.panel{background:#fff;border:1px solid #e4e8ee;border-radius:8px;padding:16px;margin-top:16px}
|
|
471
|
-
@media(max-width:760px){.shell{padding:16px}header{display:block}.grid{grid-template-columns:1fr 1fr}.task-head{display:block}}
|
|
472
|
-
</style>
|
|
473
|
-
</head>
|
|
474
|
-
<body><main class="shell">
|
|
475
|
-
<header>
|
|
476
|
-
<div><h1>${escapeHtml(status.project.name)} Harness Dashboard</h1><p>${escapeHtml(status.project.root)} · ${escapeHtml(status.generatedAt)}</p></div>
|
|
477
|
-
<span class="pill ${escapeHtml(status.checkState.status)}">${escapeHtml(status.checkState.status)} · ${escapeHtml(status.mode)}</span>
|
|
478
|
-
</header>
|
|
479
|
-
<section class="grid">
|
|
480
|
-
<div class="stat"><strong>${status.tasks.length}</strong><span>Tasks</span></div>
|
|
481
|
-
<div class="stat"><strong>${status.capabilities.length}</strong><span>Capabilities</span></div>
|
|
482
|
-
<div class="stat"><strong>${status.checkState.failures}</strong><span>Failures</span></div>
|
|
483
|
-
<div class="stat"><strong>${status.checkState.warnings}</strong><span>Warnings</span></div>
|
|
484
|
-
</section>
|
|
485
|
-
<section class="capabilities">${chips}</section>
|
|
486
|
-
<section class="panel"><h2>Handoffs</h2>${handoffs || '<span class="ok">No pending handoff</span>'}</section>
|
|
487
|
-
${taskCards || '<section class="task">No tasks found.</section>'}
|
|
488
|
-
<section class="panel"><h2>Recent Activity</h2><ul>${activity || "<li>None</li>"}</ul></section>
|
|
489
|
-
<section class="panel"><h2>Failures</h2><ul>${failures || "<li>None</li>"}</ul><h2>Warnings</h2><ul>${warnings || "<li>None</li>"}</ul></section>
|
|
490
|
-
</main></body></html>`;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function escapeHtml(value) {
|
|
494
|
-
return String(value ?? "")
|
|
495
|
-
.replaceAll("&", "&")
|
|
496
|
-
.replaceAll("<", "<")
|
|
497
|
-
.replaceAll(">", ">")
|
|
498
|
-
.replaceAll('"', """);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function evidenceCompletion(phases) {
|
|
502
|
-
const scored = phases.filter((phase) => phase.state !== "skipped");
|
|
503
|
-
if (scored.length === 0) return 0;
|
|
504
|
-
const score = scored.reduce((sum, phase) => {
|
|
505
|
-
if (["present", "waived"].includes(phase.evidenceStatus)) return sum + 100;
|
|
506
|
-
if (phase.evidenceStatus === "partial") return sum + 50;
|
|
507
|
-
return sum;
|
|
508
|
-
}, 0);
|
|
509
|
-
return Math.round(score / scored.length);
|
|
370
|
+
validationMode: "validated",
|
|
371
|
+
});
|
|
510
372
|
}
|