coding-agent-harness 1.0.8 → 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 +10 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +19 -6
- package/dist/check-dist-observation.mjs +57 -29
- 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 +7 -7
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +51 -9
- 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 +51 -12
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +109 -52
- package/dist/harness.mjs +6 -304
- 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 +4 -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 +67 -29
- package/dist/lib/preset-registry.mjs +361 -71
- package/dist/lib/preset-runner.mjs +292 -26
- 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 +3 -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 +116 -160
- 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 +36 -17
- package/dist/lib/task-scanner.mjs +74 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +186 -29
- 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 +1 -0
- 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 +16 -12
- 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 +6 -1
- package/presets/release-closeout/preset.yaml +9 -9
- package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
- package/presets/release-closeout/templates/task_plan.append.md +5 -5
- 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/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/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/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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { datePrefix, lessonCandidatesFile, longRunningTaskContractFile, normalizeTarget, normalizeTaskId, readFileSafe, visualMapFile, } from "./core-shared.mjs";
|
|
4
|
+
import { resolveHarnessPaths, taskIdFromDirectory, taskRefPath, } from "./harness-paths.mjs";
|
|
5
|
+
import { collectTasks, listTaskPlanPaths, readTaskContractFile, readVisualMapContractFile, } from "./task-scanner.mjs";
|
|
6
|
+
export { isActiveTaskState, parsePhases, taskCutoverCounters, } from "./task-scanner.mjs";
|
|
7
|
+
export { parseTaskBudget, parseTaskContractInfo, parseTaskState, } from "./task-metadata.mjs";
|
|
8
|
+
export { readVisualMapContractFile } from "./task-scanner.mjs";
|
|
9
|
+
export function createScannerTaskRepository(targetInput = ".", defaults = {}) {
|
|
10
|
+
const target = normalizeRepositoryTarget(targetInput);
|
|
11
|
+
return {
|
|
12
|
+
list(query = {}) {
|
|
13
|
+
const tasks = collectTasks(target, {
|
|
14
|
+
requireGeneratedScaffoldProvenance: query.requireGeneratedScaffoldProvenance ?? defaults.requireGeneratedScaffoldProvenance,
|
|
15
|
+
closeoutContent: query.closeoutContent ?? defaults.closeoutContent,
|
|
16
|
+
});
|
|
17
|
+
return applyTaskQuery(tasks, query);
|
|
18
|
+
},
|
|
19
|
+
get(ref) {
|
|
20
|
+
const location = resolveRepositoryTaskLocation(target, ref);
|
|
21
|
+
const task = collectTasks(target, {
|
|
22
|
+
requireGeneratedScaffoldProvenance: defaults.requireGeneratedScaffoldProvenance,
|
|
23
|
+
closeoutContent: defaults.closeoutContent,
|
|
24
|
+
}).find((candidate) => candidate.id === location.id);
|
|
25
|
+
if (!task)
|
|
26
|
+
throw new Error(`Task not found: ${ref.id || ref.path || ""}`);
|
|
27
|
+
return task;
|
|
28
|
+
},
|
|
29
|
+
resolve(ref) {
|
|
30
|
+
return resolveRepositoryTaskLocation(target, ref);
|
|
31
|
+
},
|
|
32
|
+
readMaterials(ref) {
|
|
33
|
+
const location = resolveRepositoryTaskLocation(target, ref);
|
|
34
|
+
const taskDir = location.directory;
|
|
35
|
+
const taskPlanContent = readFileSafe(location.taskPlanPath);
|
|
36
|
+
return {
|
|
37
|
+
location,
|
|
38
|
+
index: readMaterialFile(path.join(taskDir, "INDEX.md")),
|
|
39
|
+
brief: readTaskContractFile(taskDir, "brief.md", ""),
|
|
40
|
+
taskPlan: materialFromContent(location.taskPlanPath, taskPlanContent),
|
|
41
|
+
executionStrategy: readMaterialFile(path.join(taskDir, "execution_strategy.md")),
|
|
42
|
+
visualMap: readVisualMapContractFile(taskDir, taskPlanContent),
|
|
43
|
+
progress: readMaterialFile(path.join(taskDir, "progress.md")),
|
|
44
|
+
findings: readMaterialFile(path.join(taskDir, "findings.md")),
|
|
45
|
+
review: readMaterialFile(path.join(taskDir, "review.md")),
|
|
46
|
+
lessonCandidates: readMaterialFile(path.join(taskDir, lessonCandidatesFile)),
|
|
47
|
+
longRunningContract: readMaterialFile(path.join(taskDir, longRunningTaskContractFile)),
|
|
48
|
+
walkthrough: readMaterialFile(path.join(taskDir, "walkthrough.md")),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function taskPlanPathFromRecord(target, task) {
|
|
54
|
+
const raw = String(task.taskPlanPath || "");
|
|
55
|
+
if (raw)
|
|
56
|
+
return absoluteTargetPath(target, raw);
|
|
57
|
+
const taskDir = absoluteTargetPath(target, String(task.path || ""));
|
|
58
|
+
return path.join(taskDir, "task_plan.md");
|
|
59
|
+
}
|
|
60
|
+
function normalizeRepositoryTarget(targetInput) {
|
|
61
|
+
if (targetInput && typeof targetInput === "object" && "projectRoot" in targetInput)
|
|
62
|
+
return targetInput;
|
|
63
|
+
return normalizeTarget(typeof targetInput === "string" ? targetInput : ".");
|
|
64
|
+
}
|
|
65
|
+
function applyTaskQuery(tasks, query) {
|
|
66
|
+
let result = [...tasks];
|
|
67
|
+
if (query.includeArchived === false) {
|
|
68
|
+
result = result.filter((task) => task.deletionState === "active" && task.hiddenByDefault !== true);
|
|
69
|
+
}
|
|
70
|
+
if (query.state)
|
|
71
|
+
result = result.filter((task) => task.state === String(query.state).toLowerCase().replaceAll("-", "_"));
|
|
72
|
+
if (query.module)
|
|
73
|
+
result = result.filter((task) => task.module === query.module);
|
|
74
|
+
if (query.queue) {
|
|
75
|
+
const normalizedQueue = queryToken(query.queue);
|
|
76
|
+
result = result.filter((task) => (task.taskQueues || []).map(queryToken).includes(normalizedQueue));
|
|
77
|
+
}
|
|
78
|
+
if (query.preset)
|
|
79
|
+
result = result.filter((task) => queryToken(task.taskPreset || "none") === queryToken(query.preset));
|
|
80
|
+
if (query.review)
|
|
81
|
+
result = result.filter((task) => queryToken(task.reviewStatus || "") === queryToken(query.review));
|
|
82
|
+
if (query.lesson) {
|
|
83
|
+
const needle = queryToken(query.lesson);
|
|
84
|
+
result = result.filter((task) => [task.lessonCandidateStatus, task.lessonCandidateReviewDecision, task.lessonCandidatePromotionState].some((value) => queryToken(value) === needle));
|
|
85
|
+
}
|
|
86
|
+
if (query.missingMaterials)
|
|
87
|
+
result = result.filter((task) => !task.materialsReady);
|
|
88
|
+
if (query.search) {
|
|
89
|
+
const needle = String(query.search).toLowerCase();
|
|
90
|
+
result = result.filter((task) => [
|
|
91
|
+
task.id,
|
|
92
|
+
task.taskKey,
|
|
93
|
+
task.shortId,
|
|
94
|
+
task.title,
|
|
95
|
+
task.currentPath,
|
|
96
|
+
task.taskPlanPath,
|
|
97
|
+
task.module,
|
|
98
|
+
task.inferredModule,
|
|
99
|
+
].some((value) => String(value || "").toLowerCase().includes(needle)));
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function resolveRepositoryTaskLocation(target, ref) {
|
|
104
|
+
const harnessPaths = (target.harness || resolveHarnessPaths(target));
|
|
105
|
+
const pathLocation = ref.path ? resolvePathRef(target, harnessPaths, ref.path) : null;
|
|
106
|
+
if (pathLocation)
|
|
107
|
+
return pathLocation;
|
|
108
|
+
const raw = normalizeRawTaskRef(ref.id || ref.path || "");
|
|
109
|
+
if (!raw)
|
|
110
|
+
throw new Error("Missing task id");
|
|
111
|
+
const direct = taskRefPath(harnessPaths, raw);
|
|
112
|
+
if (direct && fs.existsSync(path.join(direct, "task_plan.md")))
|
|
113
|
+
return taskLocationFromDirectory(harnessPaths, direct);
|
|
114
|
+
const normalized = normalizeTaskId(raw);
|
|
115
|
+
const candidates = taskDirectories(target).filter((taskDir) => {
|
|
116
|
+
const id = taskIdFromDirectory(harnessPaths, taskDir);
|
|
117
|
+
const dirName = path.basename(taskDir);
|
|
118
|
+
return id === raw || id.endsWith(`/${raw}`) || dirName === normalized;
|
|
119
|
+
});
|
|
120
|
+
if (candidates.length === 1)
|
|
121
|
+
return taskLocationFromDirectory(harnessPaths, candidates[0]);
|
|
122
|
+
if (candidates.length > 1) {
|
|
123
|
+
const options = candidates.map((taskDir) => `- ${taskIdFromDirectory(harnessPaths, taskDir)}`).join("\n");
|
|
124
|
+
throw new Error(`Ambiguous task reference: ${ref.id || ref.path}\n${options}`);
|
|
125
|
+
}
|
|
126
|
+
if (!datePrefix.test(normalized)) {
|
|
127
|
+
const datedCandidates = taskDirectories(target).filter((taskDir) => {
|
|
128
|
+
const dirName = path.basename(taskDir);
|
|
129
|
+
return datePrefix.test(dirName) && dirName.replace(datePrefix, "") === normalized;
|
|
130
|
+
});
|
|
131
|
+
if (datedCandidates.length === 1)
|
|
132
|
+
return taskLocationFromDirectory(harnessPaths, datedCandidates[0]);
|
|
133
|
+
if (datedCandidates.length > 1) {
|
|
134
|
+
const options = datedCandidates.map((taskDir) => `- ${taskIdFromDirectory(harnessPaths, taskDir)}`).join("\n");
|
|
135
|
+
throw new Error(`Ambiguous task reference: ${ref.id || ref.path}\n${options}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const legacy = path.join(harnessPaths.tasksRoot, normalized);
|
|
139
|
+
if (fs.existsSync(path.join(legacy, "task_plan.md")))
|
|
140
|
+
return taskLocationFromDirectory(harnessPaths, legacy);
|
|
141
|
+
throw new Error(`Task not found: ${ref.id || ref.path}`);
|
|
142
|
+
}
|
|
143
|
+
function resolvePathRef(target, harnessPaths, rawPath) {
|
|
144
|
+
const absolute = absoluteTargetPath(target, rawPath);
|
|
145
|
+
const taskPlanPath = path.basename(absolute) === "task_plan.md" ? absolute : path.join(absolute, "task_plan.md");
|
|
146
|
+
if (!fs.existsSync(taskPlanPath))
|
|
147
|
+
return null;
|
|
148
|
+
return taskLocationFromDirectory(harnessPaths, path.dirname(taskPlanPath));
|
|
149
|
+
}
|
|
150
|
+
function absoluteTargetPath(target, rawPath) {
|
|
151
|
+
const withoutPrefix = String(rawPath || "").replace(/^TARGET:/, "");
|
|
152
|
+
if (path.isAbsolute(withoutPrefix))
|
|
153
|
+
return withoutPrefix;
|
|
154
|
+
const normalized = withoutPrefix.replace(/^\/+/, "");
|
|
155
|
+
if (!normalized)
|
|
156
|
+
return "";
|
|
157
|
+
return path.join(target.projectRoot, normalized);
|
|
158
|
+
}
|
|
159
|
+
function normalizeRawTaskRef(rawRef) {
|
|
160
|
+
return String(rawRef || "")
|
|
161
|
+
.replace(/^TARGET:/, "")
|
|
162
|
+
.replace(/^coding-agent-harness\/planning\//, "")
|
|
163
|
+
.replace(/^planning\//, "")
|
|
164
|
+
.replace(new RegExp(`^${legacyPlanningPrefix()}\\/`), "")
|
|
165
|
+
.replace(/^\/+/, "");
|
|
166
|
+
}
|
|
167
|
+
function legacyPlanningPrefix() {
|
|
168
|
+
return "docs\\/09-PLANNING";
|
|
169
|
+
}
|
|
170
|
+
function taskDirectories(target) {
|
|
171
|
+
return listTaskPlanPaths(target).map((taskPlanPath) => path.dirname(taskPlanPath));
|
|
172
|
+
}
|
|
173
|
+
function taskLocationFromDirectory(harnessPaths, directory) {
|
|
174
|
+
return {
|
|
175
|
+
id: taskIdFromDirectory(harnessPaths, directory),
|
|
176
|
+
directory,
|
|
177
|
+
taskPlanPath: path.join(directory, "task_plan.md"),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function readMaterialFile(filePath) {
|
|
181
|
+
return materialFromContent(filePath, readFileSafe(filePath));
|
|
182
|
+
}
|
|
183
|
+
function materialFromContent(filePath, content) {
|
|
184
|
+
return {
|
|
185
|
+
path: filePath,
|
|
186
|
+
content,
|
|
187
|
+
source: content.trim() ? "standalone" : "missing",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function queryToken(value) {
|
|
191
|
+
return String(value || "").trim().toLowerCase().replaceAll("_", "-");
|
|
192
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Dynamic review queue modeling stays behavior-first until the metadata domain model PR.
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { lessonCandidatesFile, longRunningTaskContractFile, toPosix, visualMapFile, } from "./core-shared.mjs";
|
|
6
|
-
import { firstColumn, splitList, splitMarkdownRow, tableAfterHeading, } from "./markdown-utils.mjs";
|
|
5
|
+
import { firstColumn, splitList, splitMarkdownRow, stripFencedCodeBlocks, tableAfterHeading, } from "./markdown-utils.mjs";
|
|
7
6
|
import { implementationPhases, phaseHasRecordedProgress, } from "./phase-kind.mjs";
|
|
8
7
|
import { validateReviewConfirmationGitAudit } from "./review-confirm-git-gate.mjs";
|
|
9
8
|
import { isLessonCandidateDecisionComplete } from "./task-lesson-candidates.mjs";
|
|
@@ -18,6 +17,10 @@ export const reviewFindingColumns = {
|
|
|
18
17
|
waiverBy: ["Waiver By", "豁免人"],
|
|
19
18
|
};
|
|
20
19
|
export function normalizeReviewBoolean(value) {
|
|
20
|
+
if (value === true)
|
|
21
|
+
return "yes";
|
|
22
|
+
if (value === false)
|
|
23
|
+
return "no";
|
|
21
24
|
const raw = String(value || "").trim().toLowerCase();
|
|
22
25
|
if (/^(yes|true|open|是|开放)$/.test(raw))
|
|
23
26
|
return "yes";
|
|
@@ -49,8 +52,9 @@ export function parseTaskIdentity(taskPlanContent, fallbackTaskId) {
|
|
|
49
52
|
};
|
|
50
53
|
}
|
|
51
54
|
export function parseTaskTombstone(taskPlanContent) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
55
|
+
const scanContent = stripFencedCodeBlocks(String(taskPlanContent || ""));
|
|
56
|
+
const topLevelSupersedes = splitList(parseMetadataLine(scanContent, ["Supersedes", "合并自"]));
|
|
57
|
+
const match = String(scanContent || "").match(/^##\s*(?:Task Tombstone|任务墓碑)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
|
|
54
58
|
const fields = match ? fieldsFromMarkdownBlock(match[1] || "") : new Map();
|
|
55
59
|
if (fields.size === 0) {
|
|
56
60
|
return {
|
|
@@ -66,7 +70,10 @@ export function parseTaskTombstone(taskPlanContent) {
|
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
const state = normalizeMetadataValue(fields.get("state") || fields.get("状态") || "soft-deleted", "soft-deleted");
|
|
69
|
-
|
|
73
|
+
if (!["soft-deleted", "superseded", "archived"].includes(state)) {
|
|
74
|
+
throw new Error(`Invalid tombstone state: ${state}`);
|
|
75
|
+
}
|
|
76
|
+
const deletionState = state;
|
|
70
77
|
return {
|
|
71
78
|
deletionState,
|
|
72
79
|
supersededBy: fields.get("superseded by") || fields.get("替代任务") || "",
|
|
@@ -110,7 +117,7 @@ export function parseAgentReviewSubmission(reviewContent, { taskKey = "" } = {})
|
|
|
110
117
|
scannerVersion: fields.get("scanner version") || "",
|
|
111
118
|
};
|
|
112
119
|
}
|
|
113
|
-
export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true }) {
|
|
120
|
+
export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true, }) {
|
|
114
121
|
const issues = [];
|
|
115
122
|
const addIssue = (code, message, sourcePath, extra = {}) => {
|
|
116
123
|
issues.push({
|
|
@@ -167,10 +174,13 @@ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, re
|
|
|
167
174
|
export function requiresReviewMaterials({ state = "unknown", lifecycleState = "unknown", closeoutStatus = "missing" } = {}) {
|
|
168
175
|
return (state === "review" ||
|
|
169
176
|
state === "done" ||
|
|
170
|
-
["in_review", "review-blocked", "closing", "closed-review-pending"].includes(lifecycleState) ||
|
|
177
|
+
["in_review", "review-blocked", "closing", "closed-review-pending", "confirmed-finalization-pending", "lesson-finalization-pending"].includes(lifecycleState) ||
|
|
171
178
|
closeoutStatus === "closed");
|
|
172
179
|
}
|
|
173
|
-
export function
|
|
180
|
+
export function hasPendingLessonWork(lessonCandidates) {
|
|
181
|
+
return lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || (lessonCandidates?.openCount ?? 0) > 0;
|
|
182
|
+
}
|
|
183
|
+
export function deriveTaskQueues({ id, title, state, budget, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target, }) {
|
|
174
184
|
const queueReasons = [];
|
|
175
185
|
const pushReason = (reason) => {
|
|
176
186
|
queueReasons.push({
|
|
@@ -226,7 +236,7 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
|
|
|
226
236
|
message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
|
|
227
237
|
});
|
|
228
238
|
}
|
|
229
|
-
const hasLessonWork = lessonCandidates
|
|
239
|
+
const hasLessonWork = hasPendingLessonWork(lessonCandidates);
|
|
230
240
|
const taskQueues = [];
|
|
231
241
|
if (tombstone.deletionState !== "active") {
|
|
232
242
|
taskQueues.push("soft-deleted-superseded");
|
|
@@ -243,13 +253,21 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
|
|
|
243
253
|
taskQueues.push("lessons");
|
|
244
254
|
if (budget === "simple" && state === "done" && closeoutStatus === "closed")
|
|
245
255
|
taskQueues.push("finalized");
|
|
246
|
-
if (reviewStatus === "confirmed")
|
|
247
|
-
|
|
256
|
+
if (reviewStatus === "confirmed") {
|
|
257
|
+
if (closeoutStatus === "closed")
|
|
258
|
+
taskQueues.push("finalized");
|
|
259
|
+
else {
|
|
260
|
+
taskQueues.push("confirmed");
|
|
261
|
+
if (!hasLessonWork)
|
|
262
|
+
taskQueues.push("confirmed-finalization-pending");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
248
265
|
}
|
|
249
|
-
|
|
250
|
-
|
|
266
|
+
const normalizedTaskQueues = [...new Set(taskQueues)];
|
|
267
|
+
if (normalizedTaskQueues.length === 0)
|
|
268
|
+
normalizedTaskQueues.push("active");
|
|
251
269
|
return {
|
|
252
|
-
taskQueues,
|
|
270
|
+
taskQueues: normalizedTaskQueues,
|
|
253
271
|
queueReasons,
|
|
254
272
|
repairPrompt: renderRepairPrompt({ id, title, taskDir, target, reasons: queueReasons }),
|
|
255
273
|
};
|
|
@@ -267,7 +285,6 @@ export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit
|
|
|
267
285
|
if (taskAudit) {
|
|
268
286
|
const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
|
|
269
287
|
if (confirmation?.confirmed &&
|
|
270
|
-
confirmation.auditSource !== "migrated-legacy-review" &&
|
|
271
288
|
projectRoot &&
|
|
272
289
|
(indexPath || taskDir) &&
|
|
273
290
|
confirmation.commitSha) {
|
|
@@ -314,9 +331,9 @@ function hasAgentReviewSignal(reviewContent) {
|
|
|
314
331
|
return /本轮已检查|未发现阻塞目标的重要发现/.test(content);
|
|
315
332
|
}
|
|
316
333
|
export function isBlockingReviewRisk(risk) {
|
|
317
|
-
return /^P[0-2]$/i.test(risk?.severity || "") && (risk
|
|
334
|
+
return /^P[0-2]$/i.test(risk?.severity || "") && Boolean(risk?.open || risk?.blocksRelease);
|
|
318
335
|
}
|
|
319
|
-
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard" } = {}) {
|
|
336
|
+
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard", lessonCandidates = null } = {}) {
|
|
320
337
|
if (reviewStatus === "blocked-open-findings")
|
|
321
338
|
return "review-blocked";
|
|
322
339
|
if (budget === "simple" && closeoutStatus === "closed")
|
|
@@ -325,6 +342,8 @@ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missin
|
|
|
325
342
|
return "closed-review-pending";
|
|
326
343
|
if (closeoutStatus === "closed")
|
|
327
344
|
return "closed";
|
|
345
|
+
if (reviewStatus === "confirmed")
|
|
346
|
+
return hasPendingLessonWork(lessonCandidates) ? "lesson-finalization-pending" : "confirmed-finalization-pending";
|
|
328
347
|
if (state === "blocked")
|
|
329
348
|
return "blocked";
|
|
330
349
|
if (state === "done")
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, longRunningTaskContractFile, toPosix, readFileSafe, readJsonSafe, walkFiles, isArchivedHarnessPath, titleFromMarkdown, } from "./core-shared.mjs";
|
|
@@ -7,8 +6,35 @@ import { normalizePhaseActor, normalizePhaseKind, phaseCompletionAverage, } from
|
|
|
7
6
|
import { legacyAuditIssues, parseTaskAuditMetadata, scaffoldProvenanceSummaryFromTaskAudit, taskAuditMaterialIssues, } from "./task-audit-metadata.mjs";
|
|
8
7
|
import { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskStateInfo, } from "./task-metadata.mjs";
|
|
9
8
|
import { isLessonCandidateDecisionComplete, parseLessonCandidateStatus, validateLessonCandidateDetailArtifacts, } from "./task-lesson-candidates.mjs";
|
|
9
|
+
import { collectUneditedTemplateMaterialIssues } from "./task-template-materials.mjs";
|
|
10
10
|
import { assessMaterialsReadiness, collectReviewRisks, collectStateConflicts, deriveLifecycleState, deriveReviewQueueState, deriveTaskQueues, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
|
|
11
11
|
import { resolveHarnessPaths, safeAdoptionCapability, taskIdFromDirectory, taskLocalWalkthrough, } from "./harness-paths.mjs";
|
|
12
|
+
import { isExcludedTaskPlanPath } from "./task-discovery-contract.mjs";
|
|
13
|
+
function asLessonCandidateStatus(value) {
|
|
14
|
+
const candidate = isRecord(value) ? value : {};
|
|
15
|
+
const rows = Array.isArray(candidate.rows) ? candidate.rows.filter(isRecord).map((row) => Object.fromEntries(Object.entries(row).map(([key, item]) => [key, String(item || "")]))) : [];
|
|
16
|
+
const issues = Array.isArray(candidate.issues) ? candidate.issues.map((item) => String(item)) : [];
|
|
17
|
+
return {
|
|
18
|
+
status: String(candidate.status || ""),
|
|
19
|
+
declaredStatus: candidate.declaredStatus === undefined ? undefined : String(candidate.declaredStatus || ""),
|
|
20
|
+
schemaVersion: candidate.schemaVersion === undefined ? undefined : String(candidate.schemaVersion || ""),
|
|
21
|
+
reviewDecision: String(candidate.reviewDecision || ""),
|
|
22
|
+
promotionState: String(candidate.promotionState || ""),
|
|
23
|
+
closeoutToken: String(candidate.closeoutToken || ""),
|
|
24
|
+
rows,
|
|
25
|
+
openCount: Number(candidate.openCount || 0),
|
|
26
|
+
issues,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null;
|
|
31
|
+
}
|
|
32
|
+
function nestedRecord(value, key) {
|
|
33
|
+
if (!isRecord(value))
|
|
34
|
+
return {};
|
|
35
|
+
const nested = value[key];
|
|
36
|
+
return isRecord(nested) ? nested : {};
|
|
37
|
+
}
|
|
12
38
|
export { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskState, parseTaskStateInfo, } from "./task-metadata.mjs";
|
|
13
39
|
export { collectReviewRisks, deriveLifecycleState, deriveReviewQueueState, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
|
|
14
40
|
export { parseTaskAuditMetadata, } from "./task-audit-metadata.mjs";
|
|
@@ -73,17 +99,16 @@ export function isActiveTaskState(state) {
|
|
|
73
99
|
return ["active", "planned", "not_started", "in_progress", "review", "blocked", "reopened", "current-evidence"].includes(state);
|
|
74
100
|
}
|
|
75
101
|
export function listTaskPlanPaths(target) {
|
|
76
|
-
const harnessPaths = target.harness || resolveHarnessPaths(target);
|
|
102
|
+
const harnessPaths = (target.harness || resolveHarnessPaths(target));
|
|
77
103
|
const taskRoots = harnessPaths.taskRoots;
|
|
78
104
|
return taskRoots
|
|
79
|
-
.flatMap(walkFiles)
|
|
105
|
+
.flatMap((root) => walkFiles(root))
|
|
80
106
|
.filter((file) => file.endsWith("task_plan.md"))
|
|
81
|
-
.filter((file) => !file
|
|
82
|
-
.filter((file) => !file.includes(`${path.sep}_optional-structures${path.sep}`))
|
|
107
|
+
.filter((file) => !isExcludedTaskPlanPath(file, harnessPaths))
|
|
83
108
|
.filter((file) => !isArchivedHarnessPath(file));
|
|
84
109
|
}
|
|
85
110
|
export function taskIdForDirectory(target, taskDir) {
|
|
86
|
-
return taskIdFromDirectory(target.harness || resolveHarnessPaths(target), taskDir);
|
|
111
|
+
return taskIdFromDirectory((target.harness || resolveHarnessPaths(target)), taskDir);
|
|
87
112
|
}
|
|
88
113
|
export function inferTaskClassification({ id, title, relative, explicitModule, legacyCandidate = false }) {
|
|
89
114
|
if (explicitModule) {
|
|
@@ -93,6 +118,13 @@ export function inferTaskClassification({ id, title, relative, explicitModule, l
|
|
|
93
118
|
bucket: "module",
|
|
94
119
|
};
|
|
95
120
|
}
|
|
121
|
+
if (id.startsWith("TASKS/")) {
|
|
122
|
+
return {
|
|
123
|
+
module: "base",
|
|
124
|
+
source: "structure",
|
|
125
|
+
bucket: legacyCandidate ? "legacy" : "current",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
96
128
|
const text = `${id} ${title} ${relative}`.toLowerCase();
|
|
97
129
|
const rules = [
|
|
98
130
|
["dashboard", /dashboard|visibility|cockpit|console|ui|frontend|view|页面|看板|驾驶舱/],
|
|
@@ -157,7 +189,7 @@ export function taskCutoverCounters(tasks) {
|
|
|
157
189
|
};
|
|
158
190
|
}
|
|
159
191
|
export function collectTasks(target, { requireGeneratedScaffoldProvenance = false, taskPlanPaths, closeoutContent } = {}) {
|
|
160
|
-
const harnessPaths = target.harness || resolveHarnessPaths(target);
|
|
192
|
+
const harnessPaths = (target.harness || resolveHarnessPaths(target));
|
|
161
193
|
const paths = taskPlanPaths || listTaskPlanPaths(target);
|
|
162
194
|
const closeout = closeoutContent ?? (harnessPaths.version === 2 ? "" : readFileSafe(harnessPaths.legacy.closeoutPath));
|
|
163
195
|
return paths.map((taskPlanPath) => {
|
|
@@ -175,7 +207,7 @@ export function collectTasks(target, { requireGeneratedScaffoldProvenance = fals
|
|
|
175
207
|
const progress = readFileSafe(progressPath);
|
|
176
208
|
const review = readFileSafe(reviewPath);
|
|
177
209
|
const indexContent = readFileSafe(indexPath);
|
|
178
|
-
const parsedLessonCandidates = parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath));
|
|
210
|
+
const parsedLessonCandidates = asLessonCandidateStatus(parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath)));
|
|
179
211
|
const lessonDetailIssues = validateLessonCandidateDetailArtifacts(target, taskDir, parsedLessonCandidates);
|
|
180
212
|
const lessonCandidates = lessonDetailIssues.length
|
|
181
213
|
? { ...parsedLessonCandidates, issues: [...parsedLessonCandidates.issues, ...lessonDetailIssues] }
|
|
@@ -195,7 +227,7 @@ export function collectTasks(target, { requireGeneratedScaffoldProvenance = fals
|
|
|
195
227
|
required: requireGeneratedScaffoldProvenance && taskContract.generated,
|
|
196
228
|
});
|
|
197
229
|
const scaffoldProvenance = { summary: scaffoldProvenanceSummaryFromTaskAudit(taskAudit) };
|
|
198
|
-
const explicitModule = id.startsWith("MODULES/") ? id.split("/")[1] : null;
|
|
230
|
+
const explicitModule = id.startsWith("MODULES/") ? id.split("/")[1] ?? null : null;
|
|
199
231
|
const legacyCandidate = brief.source !== "standalone" || visualMap.status === "legacy-only" || !fs.existsSync(executionStrategyPath);
|
|
200
232
|
const classification = inferTaskClassification({ id, title, relative, explicitModule, legacyCandidate });
|
|
201
233
|
const briefVisualStatus = explicitVisualMapStatus(brief.content);
|
|
@@ -216,25 +248,40 @@ export function collectTasks(target, { requireGeneratedScaffoldProvenance = fals
|
|
|
216
248
|
const effectiveCloseoutStatus = budget === "simple" && stateInfo.state === "done" && completion === 100
|
|
217
249
|
? "closed"
|
|
218
250
|
: closeoutInfo.status;
|
|
219
|
-
const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, budget });
|
|
251
|
+
const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, budget, lessonCandidates });
|
|
252
|
+
const reviewSurfaceRequired = requiresReviewMaterials({
|
|
253
|
+
state: stateInfo.state,
|
|
254
|
+
lifecycleState,
|
|
255
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
256
|
+
});
|
|
220
257
|
const materialReadiness = assessMaterialsReadiness({
|
|
221
258
|
budget,
|
|
222
259
|
taskDir,
|
|
223
|
-
taskPlan,
|
|
224
260
|
brief,
|
|
225
261
|
visualMap,
|
|
226
262
|
reviewSubmission,
|
|
227
263
|
lessonCandidates,
|
|
228
264
|
phases,
|
|
229
265
|
longRunningContractPath,
|
|
230
|
-
reviewSurfaceRequired
|
|
231
|
-
state: stateInfo.state,
|
|
232
|
-
lifecycleState,
|
|
233
|
-
closeoutStatus: effectiveCloseoutStatus,
|
|
234
|
-
}),
|
|
266
|
+
reviewSurfaceRequired,
|
|
235
267
|
});
|
|
268
|
+
const templateMaterialIssues = reviewSurfaceRequired
|
|
269
|
+
? collectUneditedTemplateMaterialIssues(target, taskDir, {
|
|
270
|
+
briefContent: brief.content,
|
|
271
|
+
taskPlanContent: taskPlan,
|
|
272
|
+
executionStrategyContent: readFileSafe(executionStrategyPath),
|
|
273
|
+
visualMapContent: visualMap.content,
|
|
274
|
+
progressContent: progress,
|
|
275
|
+
findingsContent: readFileSafe(findingsPath),
|
|
276
|
+
reviewContent: review,
|
|
277
|
+
lessonCandidatesContent: readFileSafe(lessonCandidatesPath),
|
|
278
|
+
walkthroughPath: closeoutInfo.walkthroughPath,
|
|
279
|
+
humanReviewConfirmed: taskAudit.summary.humanReviewStatus === "confirmed",
|
|
280
|
+
})
|
|
281
|
+
: [];
|
|
236
282
|
const materialIssues = [
|
|
237
283
|
...materialReadiness.issues,
|
|
284
|
+
...templateMaterialIssues,
|
|
238
285
|
...taskAuditMaterialIssues(target, taskDir, taskAudit),
|
|
239
286
|
...legacyAuditIssues(target, taskDir, { briefContent: brief.content, reviewContent: review }),
|
|
240
287
|
];
|
|
@@ -368,17 +415,21 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
368
415
|
const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
|
|
369
416
|
const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
|
|
370
417
|
const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
|
|
371
|
-
const
|
|
418
|
+
const plan = nestedRecord(session, "plan");
|
|
419
|
+
const checks = nestedRecord(session, "checks");
|
|
420
|
+
const normal = nestedRecord(checks, "normal");
|
|
421
|
+
const strict = nestedRecord(checks, "strict");
|
|
422
|
+
const summary = nestedRecord(plan, "summary");
|
|
372
423
|
return {
|
|
373
424
|
targetLevel: metadata.migrationTargetLevel || "",
|
|
374
425
|
achievedLevel: metadata.migrationAchievedLevel || "",
|
|
375
426
|
evidenceBundle: evidenceBundle ? `TARGET:${evidenceBundle}` : "",
|
|
376
427
|
evidencePresent: Boolean(bundlePath && fs.existsSync(bundlePath)),
|
|
377
428
|
sessionPresent: Boolean(session),
|
|
378
|
-
sessionResult: session
|
|
379
|
-
normalStatus:
|
|
380
|
-
strictStatus:
|
|
381
|
-
strictDeferred: Boolean(session
|
|
429
|
+
sessionResult: isRecord(session) ? String(session.result || "") : "",
|
|
430
|
+
normalStatus: String(normal.status || ""),
|
|
431
|
+
strictStatus: String(strict.status || ""),
|
|
432
|
+
strictDeferred: isRecord(session) ? Boolean(session.strictDeferred) : false,
|
|
382
433
|
warnings: Number(summary.warnings || 0),
|
|
383
434
|
taskActions: Number(summary.taskActions || 0),
|
|
384
435
|
reviewSchemaGaps: Number(summary.reviewSchemaGaps || 0),
|
|
@@ -392,7 +443,7 @@ function formatEvidenceBundle(value) {
|
|
|
392
443
|
return normalized ? `TARGET:${normalized}` : "";
|
|
393
444
|
}
|
|
394
445
|
function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
395
|
-
const localWalkthrough = taskLocalWalkthrough(target.harness || resolveHarnessPaths(target), path.dirname(taskPlanPath));
|
|
446
|
+
const localWalkthrough = taskLocalWalkthrough((target.harness || resolveHarnessPaths(target)), path.dirname(taskPlanPath));
|
|
396
447
|
if (localWalkthrough) {
|
|
397
448
|
const content = readFileSafe(path.join(target.projectRoot, localWalkthrough));
|
|
398
449
|
const status = /^Closeout Status\s*:\s*(closed|complete|completed|done|已关闭|已完成)\s*$/im.test(content)
|
|
@@ -402,7 +453,7 @@ function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
|
402
453
|
}
|
|
403
454
|
if (!closeout.trim())
|
|
404
455
|
return { status: "missing", walkthroughPath: "" };
|
|
405
|
-
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
|
|
456
|
+
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot || path.join(target.projectRoot, "docs"), taskPlanPath))}`;
|
|
406
457
|
const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
|
|
407
458
|
const line = closeout
|
|
408
459
|
.split(/\r?\n/)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { lessonCandidatesFile, readFileSafe, toPosix, visualMapFile, } from "./core-shared.mjs";
|
|
3
|
+
export function collectUneditedTemplateMaterialIssues(target, taskDir, materials) {
|
|
4
|
+
const issues = [];
|
|
5
|
+
const files = [
|
|
6
|
+
{ label: "brief.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "brief.md"))), content: materials.briefContent },
|
|
7
|
+
{ label: "task_plan.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "task_plan.md"))), content: materials.taskPlanContent },
|
|
8
|
+
{ label: "execution_strategy.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "execution_strategy.md"))), content: materials.executionStrategyContent },
|
|
9
|
+
{ label: visualMapFile, sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, visualMapFile))), content: materials.visualMapContent },
|
|
10
|
+
{ label: "progress.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "progress.md"))), content: materials.progressContent },
|
|
11
|
+
{ label: "findings.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "findings.md"))), content: materials.findingsContent },
|
|
12
|
+
{ label: "review.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "review.md"))), content: materials.reviewContent },
|
|
13
|
+
{ label: lessonCandidatesFile, sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, lessonCandidatesFile))), content: materials.lessonCandidatesContent },
|
|
14
|
+
];
|
|
15
|
+
if (materials.walkthroughPath) {
|
|
16
|
+
files.push({
|
|
17
|
+
label: path.basename(materials.walkthroughPath),
|
|
18
|
+
sourcePath: materials.walkthroughPath,
|
|
19
|
+
content: readFileSafe(path.join(target.projectRoot, materials.walkthroughPath)),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const markers = uneditedCoreTemplateSlots(file.label, file.content);
|
|
24
|
+
if (markers.length === 0)
|
|
25
|
+
continue;
|
|
26
|
+
issues.push({
|
|
27
|
+
code: "unedited-template-material",
|
|
28
|
+
severity: "P2",
|
|
29
|
+
queue: "missing-materials",
|
|
30
|
+
sourcePath: `TARGET:${file.sourcePath}`,
|
|
31
|
+
sourceLine: 0,
|
|
32
|
+
owner: "agent",
|
|
33
|
+
message: `[unedited-template-material] Reviewable task material still contains default core content in ${file.label}: ${markers.slice(0, 3).join(", ")}.`,
|
|
34
|
+
allowedWritePaths: [`${toPosix(path.relative(target.projectRoot, taskDir))}/**`],
|
|
35
|
+
forbiddenActions: ["human-confirm", "edit-unrelated-task", "fabricate-evidence"],
|
|
36
|
+
validationCommands: ["node dist/harness.mjs check --profile target-project <target>"],
|
|
37
|
+
confidence: "high",
|
|
38
|
+
repairable: true,
|
|
39
|
+
enforceFailure: materials.humanReviewConfirmed,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return issues;
|
|
43
|
+
}
|
|
44
|
+
function uneditedCoreTemplateSlots(label, content) {
|
|
45
|
+
const text = String(content || "");
|
|
46
|
+
const markers = [];
|
|
47
|
+
if (label === "brief.md") {
|
|
48
|
+
if (sectionHasDefault(text, ["Outcome Statement", "一句话结果"], [
|
|
49
|
+
"One sentence stating the concrete result this task must produce.",
|
|
50
|
+
"用一句话说明这个任务完成后会产生什么具体结果。",
|
|
51
|
+
"说明这个任务完成后,用户或项目能直接看到的结果。",
|
|
52
|
+
]))
|
|
53
|
+
markers.push("brief-outcome");
|
|
54
|
+
}
|
|
55
|
+
else if (label === "task_plan.md") {
|
|
56
|
+
if (sectionHasDefault(text, ["Goal", "目标"], [
|
|
57
|
+
"[State the outcome this task must deliver in one sentence.]",
|
|
58
|
+
"[用一句话说明本任务完成后应达到的状态。]",
|
|
59
|
+
]))
|
|
60
|
+
markers.push("task-plan-goal");
|
|
61
|
+
}
|
|
62
|
+
else if (label === "execution_strategy.md") {
|
|
63
|
+
if (sectionHasDefault(text, ["Strategy Summary"], ["[Describe the execution approach, including why this operating model fits the risk and scope.]"]) ||
|
|
64
|
+
/\|\s*L0\s*\|\s*\[静态检查\s*\/\s*小范围自检\]\s*\|\s*`?progress\.md`?\s*\|\s*\[通过标准\]\s*\|/i.test(text)) {
|
|
65
|
+
markers.push("execution-strategy-core-plan");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (label === visualMapFile) {
|
|
69
|
+
if (hasDefaultExecutionPhase(text))
|
|
70
|
+
markers.push("visual-map-execution-phase");
|
|
71
|
+
}
|
|
72
|
+
else if (label === "progress.md") {
|
|
73
|
+
if (/^\|\s*YYYY-MM-DD HH:MM\s*\|\s*coordinator\s*\|\s*\[action taken\]\s*\|/im.test(text) ||
|
|
74
|
+
/^###\s*\[YYYY-MM-DD HH:MM\]\s*-\s*\[阶段名称\]\s*$/im.test(text) ||
|
|
75
|
+
/^-\s*做了什么:\[具体操作\]\s*$/im.test(text)) {
|
|
76
|
+
markers.push("progress-log-entry");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (label === "review.md") {
|
|
80
|
+
if (/\|\s*Evidence Summary\s*\|\s*\[(?:tests, diff, runtime, and review packet evidence|测试、diff、运行和审查材料证据)\]\s*\|/i.test(text) ||
|
|
81
|
+
/\|\s*E-001\s*\|[^|\n]*\|[^|\n]*\|\s*\[(?:what was checked and what it showed|检查了什么,结论是什么)\]\s*\|/i.test(text)) {
|
|
82
|
+
markers.push("review-evidence");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (label === lessonCandidatesFile) {
|
|
86
|
+
if (/\|\s*Task-level status\s*\|\s*no-candidate-accepted\s*\|/i.test(text) &&
|
|
87
|
+
/^(?:Not decided yet\. Fill this only when review accepts that the task produced no reusable lesson candidate\.|尚未判定。只有人工审查接受本任务没有可复用候选时,才填写这里。)\s*$/im.test(text)) {
|
|
88
|
+
markers.push("lesson-no-candidate-reason");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (label === "walkthrough.md") {
|
|
92
|
+
if (sectionHasDefault(text, ["Summary", "摘要"], ["Pending closeout.", "待收口。"]))
|
|
93
|
+
markers.push("walkthrough-summary");
|
|
94
|
+
}
|
|
95
|
+
return markers;
|
|
96
|
+
}
|
|
97
|
+
function sectionHasDefault(text, headings, defaults) {
|
|
98
|
+
const body = sectionBody(text, headings);
|
|
99
|
+
if (!body)
|
|
100
|
+
return false;
|
|
101
|
+
return defaults.some((value) => body === value || body.split(/\r?\n/).map((line) => line.trim()).includes(value));
|
|
102
|
+
}
|
|
103
|
+
function sectionBody(text, headings) {
|
|
104
|
+
const lines = text.split(/\r?\n/);
|
|
105
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
106
|
+
const match = lines[index].match(/^(#{2,6})\s+(.+?)\s*$/);
|
|
107
|
+
if (!match)
|
|
108
|
+
continue;
|
|
109
|
+
if (!headings.includes(match[2].trim()))
|
|
110
|
+
continue;
|
|
111
|
+
const level = match[1].length;
|
|
112
|
+
const body = [];
|
|
113
|
+
for (let bodyIndex = index + 1; bodyIndex < lines.length; bodyIndex += 1) {
|
|
114
|
+
const nextHeading = lines[bodyIndex].match(/^(#{2,6})\s+/);
|
|
115
|
+
if (nextHeading && nextHeading[1].length <= level)
|
|
116
|
+
break;
|
|
117
|
+
if (lines[bodyIndex].trim())
|
|
118
|
+
body.push(lines[bodyIndex].trim());
|
|
119
|
+
}
|
|
120
|
+
return body.join("\n").trim();
|
|
121
|
+
}
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
function hasDefaultExecutionPhase(text) {
|
|
125
|
+
const rows = text.split(/\r?\n/).filter((line) => /^\|\s*EXEC-01\s*\|/.test(line));
|
|
126
|
+
return rows.some((row) => {
|
|
127
|
+
const cells = row.split("|").map((cell) => cell.trim()).filter(Boolean);
|
|
128
|
+
return cells.some((cell) => cell === "Scoped implementation, document update, and verification evidence" || cell === "有边界的实现、文档切片和验证证据") &&
|
|
129
|
+
cells.some((cell) => cell === "diff, commands, worker handoff, or artifact path" || cell === "diff、commands、worker handoff 或 artifact path");
|
|
130
|
+
});
|
|
131
|
+
}
|