coding-agent-harness 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +9 -5
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +32 -6
- package/dist/check-dist-observation.mjs +73 -28
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +88 -0
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +67 -8
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +65 -4
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +111 -53
- package/dist/harness.mjs +6 -303
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +5 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +68 -29
- package/dist/lib/preset-registry.mjs +374 -72
- package/dist/lib/preset-runner.mjs +560 -0
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +4 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +117 -159
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +38 -17
- package/dist/lib/task-scanner.mjs +75 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +187 -18
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +2 -1
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +19 -11
- package/postinstall.mjs +37 -0
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/release-closeout/checks/check-release-package.mjs +29 -0
- package/presets/release-closeout/preset.yaml +100 -0
- package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
- package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
- package/presets/release-closeout/templates/findings.seed.md +5 -0
- package/presets/release-closeout/templates/review.seed.md +3 -0
- package/presets/release-closeout/templates/task_plan.append.md +24 -0
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/pull-request-standard.md +2 -2
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/pull-request-standard.md +2 -2
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/pull-request-standard.md +1 -1
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -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 {
|
|
@@ -58,6 +62,7 @@ export function parseTaskTombstone(taskPlanContent) {
|
|
|
58
62
|
supersededBy: "",
|
|
59
63
|
supersedes: topLevelSupersedes,
|
|
60
64
|
deleteReason: "",
|
|
65
|
+
archiveMetadata: {},
|
|
61
66
|
hiddenByDefault: false,
|
|
62
67
|
reopenEligible: false,
|
|
63
68
|
archiveEligible: false,
|
|
@@ -65,12 +70,16 @@ export function parseTaskTombstone(taskPlanContent) {
|
|
|
65
70
|
};
|
|
66
71
|
}
|
|
67
72
|
const state = normalizeMetadataValue(fields.get("state") || fields.get("状态") || "soft-deleted", "soft-deleted");
|
|
68
|
-
|
|
73
|
+
if (!["soft-deleted", "superseded", "archived"].includes(state)) {
|
|
74
|
+
throw new Error(`Invalid tombstone state: ${state}`);
|
|
75
|
+
}
|
|
76
|
+
const deletionState = state;
|
|
69
77
|
return {
|
|
70
78
|
deletionState,
|
|
71
79
|
supersededBy: fields.get("superseded by") || fields.get("替代任务") || "",
|
|
72
80
|
supersedes: [...new Set([...topLevelSupersedes, ...splitList(fields.get("supersedes") || fields.get("合并自") || "")])],
|
|
73
81
|
deleteReason: fields.get("reason") || fields.get("原因") || "",
|
|
82
|
+
archiveMetadata: Object.fromEntries([...fields.entries()].filter(([key]) => !["state", "状态", "superseded by", "替代任务", "supersedes", "合并自", "reason", "原因", "reopen eligible", "可重新打开", "archive eligible", "可归档"].includes(key))),
|
|
74
83
|
hiddenByDefault: true,
|
|
75
84
|
reopenEligible: parseTombstoneBooleanLike(fields.get("reopen eligible") || fields.get("可重新打开")),
|
|
76
85
|
archiveEligible: parseTombstoneBooleanLike(fields.get("archive eligible") || fields.get("可归档")),
|
|
@@ -108,7 +117,7 @@ export function parseAgentReviewSubmission(reviewContent, { taskKey = "" } = {})
|
|
|
108
117
|
scannerVersion: fields.get("scanner version") || "",
|
|
109
118
|
};
|
|
110
119
|
}
|
|
111
|
-
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, }) {
|
|
112
121
|
const issues = [];
|
|
113
122
|
const addIssue = (code, message, sourcePath, extra = {}) => {
|
|
114
123
|
issues.push({
|
|
@@ -165,10 +174,13 @@ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, re
|
|
|
165
174
|
export function requiresReviewMaterials({ state = "unknown", lifecycleState = "unknown", closeoutStatus = "missing" } = {}) {
|
|
166
175
|
return (state === "review" ||
|
|
167
176
|
state === "done" ||
|
|
168
|
-
["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) ||
|
|
169
178
|
closeoutStatus === "closed");
|
|
170
179
|
}
|
|
171
|
-
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, }) {
|
|
172
184
|
const queueReasons = [];
|
|
173
185
|
const pushReason = (reason) => {
|
|
174
186
|
queueReasons.push({
|
|
@@ -224,7 +236,7 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
|
|
|
224
236
|
message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
|
|
225
237
|
});
|
|
226
238
|
}
|
|
227
|
-
const hasLessonWork = lessonCandidates
|
|
239
|
+
const hasLessonWork = hasPendingLessonWork(lessonCandidates);
|
|
228
240
|
const taskQueues = [];
|
|
229
241
|
if (tombstone.deletionState !== "active") {
|
|
230
242
|
taskQueues.push("soft-deleted-superseded");
|
|
@@ -241,13 +253,21 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
|
|
|
241
253
|
taskQueues.push("lessons");
|
|
242
254
|
if (budget === "simple" && state === "done" && closeoutStatus === "closed")
|
|
243
255
|
taskQueues.push("finalized");
|
|
244
|
-
if (reviewStatus === "confirmed")
|
|
245
|
-
|
|
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
|
+
}
|
|
246
265
|
}
|
|
247
|
-
|
|
248
|
-
|
|
266
|
+
const normalizedTaskQueues = [...new Set(taskQueues)];
|
|
267
|
+
if (normalizedTaskQueues.length === 0)
|
|
268
|
+
normalizedTaskQueues.push("active");
|
|
249
269
|
return {
|
|
250
|
-
taskQueues,
|
|
270
|
+
taskQueues: normalizedTaskQueues,
|
|
251
271
|
queueReasons,
|
|
252
272
|
repairPrompt: renderRepairPrompt({ id, title, taskDir, target, reasons: queueReasons }),
|
|
253
273
|
};
|
|
@@ -265,7 +285,6 @@ export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit
|
|
|
265
285
|
if (taskAudit) {
|
|
266
286
|
const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
|
|
267
287
|
if (confirmation?.confirmed &&
|
|
268
|
-
confirmation.auditSource !== "migrated-legacy-review" &&
|
|
269
288
|
projectRoot &&
|
|
270
289
|
(indexPath || taskDir) &&
|
|
271
290
|
confirmation.commitSha) {
|
|
@@ -312,9 +331,9 @@ function hasAgentReviewSignal(reviewContent) {
|
|
|
312
331
|
return /本轮已检查|未发现阻塞目标的重要发现/.test(content);
|
|
313
332
|
}
|
|
314
333
|
export function isBlockingReviewRisk(risk) {
|
|
315
|
-
return /^P[0-2]$/i.test(risk?.severity || "") && (risk
|
|
334
|
+
return /^P[0-2]$/i.test(risk?.severity || "") && Boolean(risk?.open || risk?.blocksRelease);
|
|
316
335
|
}
|
|
317
|
-
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 } = {}) {
|
|
318
337
|
if (reviewStatus === "blocked-open-findings")
|
|
319
338
|
return "review-blocked";
|
|
320
339
|
if (budget === "simple" && closeoutStatus === "closed")
|
|
@@ -323,6 +342,8 @@ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missin
|
|
|
323
342
|
return "closed-review-pending";
|
|
324
343
|
if (closeoutStatus === "closed")
|
|
325
344
|
return "closed";
|
|
345
|
+
if (reviewStatus === "confirmed")
|
|
346
|
+
return hasPendingLessonWork(lessonCandidates) ? "lesson-finalization-pending" : "confirmed-finalization-pending";
|
|
326
347
|
if (state === "blocked")
|
|
327
348
|
return "blocked";
|
|
328
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
|
];
|
|
@@ -344,6 +391,7 @@ export function collectTasks(target, { requireGeneratedScaffoldProvenance = fals
|
|
|
344
391
|
supersededBy: tombstone.supersededBy,
|
|
345
392
|
supersedes: tombstone.supersedes,
|
|
346
393
|
deleteReason: tombstone.deleteReason,
|
|
394
|
+
archiveMetadata: tombstone.archiveMetadata || {},
|
|
347
395
|
hiddenByDefault: tombstone.hiddenByDefault,
|
|
348
396
|
reopenEligible: tombstone.reopenEligible,
|
|
349
397
|
archiveEligible: tombstone.archiveEligible,
|
|
@@ -367,17 +415,21 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
367
415
|
const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
|
|
368
416
|
const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
|
|
369
417
|
const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
|
|
370
|
-
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");
|
|
371
423
|
return {
|
|
372
424
|
targetLevel: metadata.migrationTargetLevel || "",
|
|
373
425
|
achievedLevel: metadata.migrationAchievedLevel || "",
|
|
374
426
|
evidenceBundle: evidenceBundle ? `TARGET:${evidenceBundle}` : "",
|
|
375
427
|
evidencePresent: Boolean(bundlePath && fs.existsSync(bundlePath)),
|
|
376
428
|
sessionPresent: Boolean(session),
|
|
377
|
-
sessionResult: session
|
|
378
|
-
normalStatus:
|
|
379
|
-
strictStatus:
|
|
380
|
-
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,
|
|
381
433
|
warnings: Number(summary.warnings || 0),
|
|
382
434
|
taskActions: Number(summary.taskActions || 0),
|
|
383
435
|
reviewSchemaGaps: Number(summary.reviewSchemaGaps || 0),
|
|
@@ -391,7 +443,7 @@ function formatEvidenceBundle(value) {
|
|
|
391
443
|
return normalized ? `TARGET:${normalized}` : "";
|
|
392
444
|
}
|
|
393
445
|
function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
394
|
-
const localWalkthrough = taskLocalWalkthrough(target.harness || resolveHarnessPaths(target), path.dirname(taskPlanPath));
|
|
446
|
+
const localWalkthrough = taskLocalWalkthrough((target.harness || resolveHarnessPaths(target)), path.dirname(taskPlanPath));
|
|
395
447
|
if (localWalkthrough) {
|
|
396
448
|
const content = readFileSafe(path.join(target.projectRoot, localWalkthrough));
|
|
397
449
|
const status = /^Closeout Status\s*:\s*(closed|complete|completed|done|已关闭|已完成)\s*$/im.test(content)
|
|
@@ -401,7 +453,7 @@ function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
|
401
453
|
}
|
|
402
454
|
if (!closeout.trim())
|
|
403
455
|
return { status: "missing", walkthroughPath: "" };
|
|
404
|
-
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
|
|
456
|
+
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot || path.join(target.projectRoot, "docs"), taskPlanPath))}`;
|
|
405
457
|
const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
|
|
406
458
|
const line = closeout
|
|
407
459
|
.split(/\r?\n/)
|