coding-agent-harness 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +244 -87
- package/README.zh-CN.md +77 -35
- package/SKILL.md +32 -24
- package/docs-release/README.md +9 -5
- package/docs-release/architecture/overview.md +17 -5
- package/docs-release/architecture/overview.zh-CN.md +9 -5
- package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +39 -15
- package/docs-release/guides/agent-installation.md +43 -16
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
- package/docs-release/guides/document-audience-and-surfaces.md +3 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
- package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
- package/docs-release/guides/migration-playbook.en-US.md +14 -15
- package/docs-release/guides/migration-playbook.md +14 -15
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
- package/docs-release/guides/parent-control-repository-pattern.md +7 -5
- package/docs-release/guides/preset-development.md +238 -0
- package/docs-release/guides/repository-operating-models.en-US.md +5 -4
- package/docs-release/guides/repository-operating-models.md +5 -4
- package/docs-release/guides/task-state-machine.en-US.md +224 -0
- package/docs-release/guides/task-state-machine.md +231 -0
- package/docs-release/intl/en-US.md +1 -1
- package/docs-release/intl/zh-CN.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +10 -4
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +2 -2
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +6 -7
- package/references/harness-ledger.md +53 -96
- package/references/lessons-governance.md +88 -93
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +12 -6
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +11 -2
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/walkthrough-closeout.md +7 -7
- package/scripts/check-harness.mjs +40 -301
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +126 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +328 -0
- package/scripts/harness.mjs +59 -260
- package/scripts/lib/capability-registry.mjs +82 -28
- package/scripts/lib/check-module-parallel.mjs +230 -0
- package/scripts/lib/check-profiles.mjs +90 -228
- package/scripts/lib/check-task-contracts.mjs +55 -0
- package/scripts/lib/core-shared.mjs +65 -2
- package/scripts/lib/dashboard-data.mjs +155 -24
- package/scripts/lib/dashboard-workbench.mjs +131 -12
- package/scripts/lib/dashboard-writer.mjs +20 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +611 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +6 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +494 -0
- package/scripts/lib/preset-registry.mjs +776 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +105 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-audit-metadata.mjs +385 -0
- package/scripts/lib/task-audit-migration.mjs +350 -0
- package/scripts/lib/task-completion-consistency.mjs +26 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
- package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
- package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
- package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +338 -477
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +455 -0
- package/scripts/lib/task-scanner.mjs +193 -372
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +24 -18
- package/templates/dashboard/assets/app-src/00-state.js +13 -0
- package/templates/dashboard/assets/app-src/10-router.js +5 -1
- package/templates/dashboard/assets/app-src/20-overview.js +18 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
- package/templates/dashboard/assets/app-src/45-review.js +241 -22
- package/templates/dashboard/assets/app-src/50-migration.js +24 -10
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
- package/templates/dashboard/assets/app.css +1501 -376
- package/templates/dashboard/assets/app.css.manifest.json +10 -0
- package/templates/dashboard/assets/app.js +1240 -101
- package/templates/dashboard/assets/app.manifest.json +2 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +263 -23
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +41 -0
- package/templates/planning/task_plan.md +5 -21
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +31 -3
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +7 -6
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +25 -19
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +18 -6
- package/templates-zh-CN/planning/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +41 -1
- package/templates-zh-CN/planning/task_plan.md +4 -44
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +1 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
- package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/scripts/smoke-dashboard.mjs +0 -92
- package/scripts/test-harness.mjs +0 -1395
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
allowedTaskStates,
|
|
5
|
-
allowedTaskBudgets,
|
|
6
4
|
visualMapFile,
|
|
7
5
|
legacyVisualRoadmapFile,
|
|
8
6
|
lessonCandidatesFile,
|
|
9
7
|
longRunningTaskContractFile,
|
|
10
|
-
taskContractMarker,
|
|
11
8
|
toPosix,
|
|
12
9
|
readFileSafe,
|
|
10
|
+
readJsonSafe,
|
|
13
11
|
walkFiles,
|
|
14
12
|
titleFromMarkdown,
|
|
15
13
|
} from "./core-shared.mjs";
|
|
@@ -18,165 +16,103 @@ import {
|
|
|
18
16
|
firstColumn,
|
|
19
17
|
splitList,
|
|
20
18
|
splitDependencies,
|
|
21
|
-
getColumn,
|
|
22
19
|
} from "./markdown-utils.mjs";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
92
|
-
const evidenceBundle = parseMetadataLine(content, ["Evidence Bundle", "证据包"]);
|
|
93
|
-
return {
|
|
94
|
-
kind,
|
|
95
|
-
preset,
|
|
96
|
-
presetVersion,
|
|
97
|
-
migrationTargetLevel,
|
|
98
|
-
migrationAchievedLevel,
|
|
99
|
-
evidenceBundle,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function parseTaskContractInfo(taskPlanContent) {
|
|
104
|
-
const content = String(taskPlanContent || "");
|
|
105
|
-
const explicit =
|
|
106
|
-
content.match(/^Task Contract\s*[::]\s*`?([^`\n]+)`?\s*$/im) ||
|
|
107
|
-
content.match(/^任务合同\s*[::]\s*`?([^`\n]+)`?\s*$/im);
|
|
108
|
-
const version = explicit ? explicit[1].trim() : "";
|
|
109
|
-
return {
|
|
110
|
-
version,
|
|
111
|
-
generated: version === "harness-task/v1" || content.includes(taskContractMarker),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function parseTaskStateInfo(progressContent) {
|
|
116
|
-
const match = progressContent.match(/^##\s*(?:Current Status|Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
117
|
-
if (!match) return inferLegacyTaskState(progressContent);
|
|
118
|
-
const raw = match[1].replace(/`/g, "").trim();
|
|
119
|
-
if (!raw || raw.includes("|") || /^[-*]\s+/.test(raw)) return inferLegacyTaskState(progressContent);
|
|
120
|
-
const aliases = new Map([
|
|
121
|
-
["进行中", "in_progress"],
|
|
122
|
-
["已完成", "done"],
|
|
123
|
-
["未开始", "not_started"],
|
|
124
|
-
["计划中", "planned"],
|
|
125
|
-
["审查中", "review"],
|
|
126
|
-
["已阻塞", "blocked"],
|
|
127
|
-
["pending", "planned"],
|
|
128
|
-
]);
|
|
129
|
-
const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
130
|
-
return allowedTaskStates.has(normalized)
|
|
131
|
-
? { state: normalized, source: "explicit", raw }
|
|
132
|
-
: { state: "unknown", source: "invalid", raw };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function inferLegacyTaskState(progressContent) {
|
|
136
|
-
const { header, rows } = tableAfterHeading(progressContent, /^(Status|状态)$/i);
|
|
137
|
-
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
138
|
-
if (statusIndex < 0 || rows.length === 0) return { state: "unknown", source: "missing", raw: "" };
|
|
139
|
-
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(Boolean);
|
|
140
|
-
if (states.includes("blocked")) return { state: "blocked", source: "legacy-table", raw: "blocked" };
|
|
141
|
-
if (states.includes("in_progress")) return { state: "in_progress", source: "legacy-table", raw: "in_progress" };
|
|
142
|
-
if (states.includes("review")) return { state: "review", source: "legacy-table", raw: "review" };
|
|
143
|
-
if (states.length > 0 && states.every((state) => state === "done")) return { state: "done", source: "legacy-table", raw: "done" };
|
|
144
|
-
if (states.some((state) => ["planned", "not_started"].includes(state))) return { state: "planned", source: "legacy-table", raw: "planned" };
|
|
145
|
-
return { state: "unknown", source: "missing", raw: "" };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function normalizeLegacyState(value) {
|
|
149
|
-
const raw = String(value || "").replace(/`/g, "").trim().toLowerCase();
|
|
150
|
-
if (!raw || /^(none|n\/a|na|-|—|–|无)$/.test(raw)) return "";
|
|
151
|
-
if (/block|阻塞|blocked/.test(raw)) return "blocked";
|
|
152
|
-
if (/in[-_\s]?progress|doing|active|进行中|当前|working/.test(raw)) return "in_progress";
|
|
153
|
-
if (/review|审查|审核|验证中/.test(raw)) return "review";
|
|
154
|
-
if (/done|complete|completed|merged|closed|完成|已完成/.test(raw)) return "done";
|
|
155
|
-
if (/pending|planned|todo|not[-_\s]?started|未开始|计划/.test(raw)) return "planned";
|
|
156
|
-
return "";
|
|
157
|
-
}
|
|
20
|
+
import {
|
|
21
|
+
normalizePhaseActor,
|
|
22
|
+
normalizePhaseKind,
|
|
23
|
+
phaseCompletionAverage,
|
|
24
|
+
} from "./phase-kind.mjs";
|
|
25
|
+
import {
|
|
26
|
+
legacyAuditIssues,
|
|
27
|
+
parseTaskAuditMetadata,
|
|
28
|
+
scaffoldProvenanceSummaryFromTaskAudit,
|
|
29
|
+
taskAuditMaterialIssues,
|
|
30
|
+
} from "./task-audit-metadata.mjs";
|
|
31
|
+
import {
|
|
32
|
+
parseTaskBudget,
|
|
33
|
+
parseTaskContractInfo,
|
|
34
|
+
parseTaskMetadata,
|
|
35
|
+
parseTaskStateInfo,
|
|
36
|
+
} from "./task-metadata.mjs";
|
|
37
|
+
import {
|
|
38
|
+
isLessonCandidateDecisionComplete,
|
|
39
|
+
parseLessonCandidateStatus,
|
|
40
|
+
validateLessonCandidateDetailArtifacts,
|
|
41
|
+
} from "./task-lesson-candidates.mjs";
|
|
42
|
+
import {
|
|
43
|
+
assessMaterialsReadiness,
|
|
44
|
+
collectReviewRisks,
|
|
45
|
+
collectStateConflicts,
|
|
46
|
+
deriveLifecycleState,
|
|
47
|
+
deriveReviewQueueState,
|
|
48
|
+
deriveTaskQueues,
|
|
49
|
+
isBlockingReviewRisk,
|
|
50
|
+
parseAgentReviewSubmission,
|
|
51
|
+
parseReviewConfirmation,
|
|
52
|
+
parseTaskIdentity,
|
|
53
|
+
parseTaskTombstone,
|
|
54
|
+
requiresReviewMaterials,
|
|
55
|
+
taskReviewStatus,
|
|
56
|
+
taskScannerVersion,
|
|
57
|
+
} from "./task-review-model.mjs";
|
|
58
|
+
export {
|
|
59
|
+
parseTaskBudget,
|
|
60
|
+
parseTaskContractInfo,
|
|
61
|
+
parseTaskMetadata,
|
|
62
|
+
parseTaskState,
|
|
63
|
+
parseTaskStateInfo,
|
|
64
|
+
} from "./task-metadata.mjs";
|
|
65
|
+
export {
|
|
66
|
+
collectReviewRisks,
|
|
67
|
+
deriveLifecycleState,
|
|
68
|
+
deriveReviewQueueState,
|
|
69
|
+
isBlockingReviewRisk,
|
|
70
|
+
parseAgentReviewSubmission,
|
|
71
|
+
parseReviewConfirmation,
|
|
72
|
+
parseTaskIdentity,
|
|
73
|
+
parseTaskTombstone,
|
|
74
|
+
requiresReviewMaterials,
|
|
75
|
+
taskReviewStatus,
|
|
76
|
+
taskScannerVersion,
|
|
77
|
+
} from "./task-review-model.mjs";
|
|
78
|
+
export {
|
|
79
|
+
parseTaskAuditMetadata,
|
|
80
|
+
} from "./task-audit-metadata.mjs";
|
|
81
|
+
export {
|
|
82
|
+
allowedLessonCandidateRowStatuses,
|
|
83
|
+
allowedLessonCandidateTaskStatuses,
|
|
84
|
+
isLessonCandidateDecisionComplete,
|
|
85
|
+
parseLessonCandidateStatus,
|
|
86
|
+
reviewCompleteLessonCandidateStatuses,
|
|
87
|
+
} from "./task-lesson-candidates.mjs";
|
|
158
88
|
|
|
159
89
|
export function parsePhases(taskPlanContent) {
|
|
160
90
|
const { header, rows } = tableAfterHeading(taskPlanContent, /^Phase ID$/i);
|
|
161
91
|
if (rows.length === 0) return [];
|
|
162
92
|
const indexes = {
|
|
163
93
|
id: firstColumn(header, ["Phase ID", "阶段 ID"]),
|
|
94
|
+
kind: firstColumn(header, ["Kind", "阶段类型", "类型"]),
|
|
164
95
|
dependsOn: firstColumn(header, ["Depends On", "依赖"]),
|
|
165
96
|
state: firstColumn(header, ["State", "状态"]),
|
|
166
97
|
completion: firstColumn(header, ["Completion", "完成度"]),
|
|
167
98
|
output: firstColumn(header, ["Output", "产出"]),
|
|
168
99
|
requiredEvidence: firstColumn(header, ["Required Evidence", "必要证据"]),
|
|
100
|
+
exitCommand: firstColumn(header, ["Exit Command", "出口命令", "退出命令"]),
|
|
101
|
+
actor: firstColumn(header, ["Actor", "执行者", "角色"]),
|
|
169
102
|
evidenceStatus: firstColumn(header, ["Evidence Status", "证据状态"]),
|
|
170
103
|
blockingRisk: firstColumn(header, ["Blocking Risk", "阻塞风险"]),
|
|
171
104
|
owner: firstColumn(header, ["Owner / Handoff", "负责人 / 交接"]),
|
|
172
105
|
};
|
|
173
106
|
return rows.map((row) => ({
|
|
174
107
|
id: row[indexes.id] || "",
|
|
108
|
+
kind: normalizePhaseKind(row[indexes.kind]),
|
|
175
109
|
dependsOn: splitDependencies(row[indexes.dependsOn] || ""),
|
|
176
110
|
state: row[indexes.state] || "planned",
|
|
177
111
|
completion: Number.parseInt(String(row[indexes.completion] || "0").replace("%", ""), 10) || 0,
|
|
178
112
|
output: row[indexes.output] || "",
|
|
179
113
|
requiredEvidence: splitList(row[indexes.requiredEvidence] || ""),
|
|
114
|
+
exitCommand: row[indexes.exitCommand] || "",
|
|
115
|
+
actor: normalizePhaseActor(row[indexes.actor]),
|
|
180
116
|
evidenceStatus: row[indexes.evidenceStatus] || "missing",
|
|
181
117
|
blockingRisk: row[indexes.blockingRisk] || "",
|
|
182
118
|
owner: row[indexes.owner] || "",
|
|
@@ -293,12 +229,15 @@ export function taskCutoverCounters(tasks) {
|
|
|
293
229
|
};
|
|
294
230
|
}
|
|
295
231
|
|
|
296
|
-
export function collectTasks(target) {
|
|
297
|
-
|
|
232
|
+
export function collectTasks(target, { requireGeneratedScaffoldProvenance = false, taskPlanPaths, closeoutContent } = {}) {
|
|
233
|
+
const paths = taskPlanPaths || listTaskPlanPaths(target);
|
|
234
|
+
const closeout = closeoutContent ?? readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
235
|
+
return paths.map((taskPlanPath) => {
|
|
298
236
|
const taskDir = path.dirname(taskPlanPath);
|
|
299
237
|
const taskPlan = readFileSafe(taskPlanPath);
|
|
300
238
|
const brief = readTaskContractFile(taskDir, "brief.md", "");
|
|
301
239
|
const executionStrategyPath = path.join(taskDir, "execution_strategy.md");
|
|
240
|
+
const indexPath = path.join(taskDir, "INDEX.md");
|
|
302
241
|
const progressPath = path.join(taskDir, "progress.md");
|
|
303
242
|
const reviewPath = path.join(taskDir, "review.md");
|
|
304
243
|
const findingsPath = path.join(taskDir, "findings.md");
|
|
@@ -307,35 +246,107 @@ export function collectTasks(target) {
|
|
|
307
246
|
const visualMap = readVisualMapContractFile(taskDir, taskPlan);
|
|
308
247
|
const progress = readFileSafe(progressPath);
|
|
309
248
|
const review = readFileSafe(reviewPath);
|
|
310
|
-
const
|
|
249
|
+
const indexContent = readFileSafe(indexPath);
|
|
250
|
+
const parsedLessonCandidates = parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath));
|
|
251
|
+
const lessonDetailIssues = validateLessonCandidateDetailArtifacts(target, taskDir, parsedLessonCandidates);
|
|
252
|
+
const lessonCandidates = lessonDetailIssues.length
|
|
253
|
+
? { ...parsedLessonCandidates, issues: [...parsedLessonCandidates.issues, ...lessonDetailIssues] }
|
|
254
|
+
: parsedLessonCandidates;
|
|
311
255
|
const phases = parsePhases(visualMap.content);
|
|
312
|
-
const completion =
|
|
313
|
-
phases.length > 0
|
|
314
|
-
? Math.round(
|
|
315
|
-
phases.filter((phase) => phase.state !== "skipped").reduce((sum, phase) => sum + phase.completion, 0) /
|
|
316
|
-
Math.max(1, phases.filter((phase) => phase.state !== "skipped").length),
|
|
317
|
-
)
|
|
318
|
-
: 0;
|
|
256
|
+
const completion = phaseCompletionAverage(phases);
|
|
319
257
|
const relative = toPosix(path.relative(target.projectRoot, taskDir));
|
|
320
258
|
const id = taskIdForDirectory(target, taskDir);
|
|
259
|
+
const identity = parseTaskIdentity(taskPlan, id);
|
|
260
|
+
const tombstone = parseTaskTombstone(taskPlan);
|
|
321
261
|
const title = titleFromMarkdown(brief.content || taskPlan, path.basename(taskDir));
|
|
322
262
|
const stateInfo = parseTaskStateInfo(progress);
|
|
323
263
|
const budget = parseTaskBudget(taskPlan);
|
|
324
264
|
const metadata = parseTaskMetadata(taskPlan);
|
|
325
265
|
const taskContract = parseTaskContractInfo(taskPlan);
|
|
266
|
+
const taskAudit = parseTaskAuditMetadata(indexContent, {
|
|
267
|
+
required: requireGeneratedScaffoldProvenance && taskContract.generated,
|
|
268
|
+
});
|
|
269
|
+
const scaffoldProvenance = { summary: scaffoldProvenanceSummaryFromTaskAudit(taskAudit) };
|
|
326
270
|
const explicitModule = id.startsWith("MODULES/") ? id.split("/")[1] : null;
|
|
327
271
|
const legacyCandidate = brief.source !== "standalone" || visualMap.status === "legacy-only" || !fs.existsSync(executionStrategyPath);
|
|
328
272
|
const classification = inferTaskClassification({ id, title, relative, explicitModule, legacyCandidate });
|
|
329
273
|
const briefVisualStatus = explicitVisualMapStatus(brief.content);
|
|
330
274
|
const visualMapStatus = briefVisualStatus === "not-needed" && visualMap.status === "missing" ? "not-needed" : visualMap.status;
|
|
331
275
|
const risks = collectReviewRisks(review);
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
276
|
+
const reviewSubmission = parseAgentReviewSubmission(review, { taskKey: identity.taskKey });
|
|
277
|
+
const reviewConfirmation = parseReviewConfirmation(review, {
|
|
278
|
+
taskKey: identity.taskKey,
|
|
279
|
+
taskAudit,
|
|
280
|
+
projectRoot: target.projectRoot,
|
|
281
|
+
taskDir,
|
|
282
|
+
indexPath,
|
|
283
|
+
reviewPath,
|
|
284
|
+
progressPath,
|
|
285
|
+
});
|
|
286
|
+
const reviewStatus = taskReviewStatus({ reviewContent: review, risks, confirmation: reviewConfirmation, submission: reviewSubmission });
|
|
287
|
+
const closeoutInfo = taskCloseoutInfo(target, taskPlanPath, closeout);
|
|
288
|
+
const effectiveCloseoutStatus = budget === "simple" && stateInfo.state === "done" && completion === 100
|
|
289
|
+
? "closed"
|
|
290
|
+
: closeoutInfo.status;
|
|
291
|
+
const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, budget });
|
|
292
|
+
const materialReadiness = assessMaterialsReadiness({
|
|
293
|
+
budget,
|
|
294
|
+
taskDir,
|
|
295
|
+
taskPlan,
|
|
296
|
+
brief,
|
|
297
|
+
visualMap,
|
|
298
|
+
reviewSubmission,
|
|
299
|
+
lessonCandidates,
|
|
300
|
+
phases,
|
|
301
|
+
longRunningContractPath,
|
|
302
|
+
reviewSurfaceRequired: requiresReviewMaterials({
|
|
303
|
+
state: stateInfo.state,
|
|
304
|
+
lifecycleState,
|
|
305
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
306
|
+
}),
|
|
307
|
+
});
|
|
308
|
+
const materialIssues = [
|
|
309
|
+
...materialReadiness.issues,
|
|
310
|
+
...taskAuditMaterialIssues(target, taskDir, taskAudit),
|
|
311
|
+
...legacyAuditIssues(target, taskDir, { briefContent: brief.content, reviewContent: review }),
|
|
312
|
+
];
|
|
313
|
+
const stateConflicts = collectStateConflicts({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, lifecycleState, budget });
|
|
314
|
+
const reviewQueueState = deriveReviewQueueState({
|
|
315
|
+
state: stateInfo.state,
|
|
316
|
+
lifecycleState,
|
|
317
|
+
reviewStatus,
|
|
318
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
319
|
+
budget,
|
|
320
|
+
walkthroughPath: closeoutInfo.walkthroughPath,
|
|
321
|
+
lessonCandidateDecisionComplete: isLessonCandidateDecisionComplete(lessonCandidates),
|
|
322
|
+
materialsReady: materialIssues.length === 0,
|
|
323
|
+
deletionState: tombstone.deletionState,
|
|
324
|
+
});
|
|
325
|
+
const queueModel = deriveTaskQueues({
|
|
326
|
+
id,
|
|
327
|
+
title,
|
|
328
|
+
state: stateInfo.state,
|
|
329
|
+
budget,
|
|
330
|
+
reviewStatus,
|
|
331
|
+
reviewSubmission,
|
|
332
|
+
reviewConfirmation,
|
|
333
|
+
reviewQueueState,
|
|
334
|
+
materialIssues,
|
|
335
|
+
risks,
|
|
336
|
+
stateConflicts,
|
|
337
|
+
lessonCandidates,
|
|
338
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
339
|
+
tombstone,
|
|
340
|
+
taskDir,
|
|
341
|
+
target,
|
|
342
|
+
});
|
|
337
343
|
return {
|
|
338
344
|
id,
|
|
345
|
+
taskKey: identity.taskKey,
|
|
346
|
+
currentPath: `TARGET:${relative}`,
|
|
347
|
+
originalPath: `TARGET:${relative}`,
|
|
348
|
+
aliases: [],
|
|
349
|
+
identitySource: identity.identitySource,
|
|
339
350
|
shortId: path.basename(taskDir),
|
|
340
351
|
title,
|
|
341
352
|
path: `TARGET:${relative}`,
|
|
@@ -368,12 +379,22 @@ export function collectTasks(target) {
|
|
|
368
379
|
presetVersion: metadata.presetVersion,
|
|
369
380
|
migrationTargetLevel: metadata.migrationTargetLevel,
|
|
370
381
|
migrationAchievedLevel: metadata.migrationAchievedLevel,
|
|
371
|
-
evidenceBundle: metadata.evidenceBundle,
|
|
382
|
+
evidenceBundle: formatEvidenceBundle(metadata.evidenceBundle),
|
|
372
383
|
migrationSnapshot: collectMigrationSnapshot(target, metadata),
|
|
384
|
+
scaffoldProvenance: scaffoldProvenance.summary,
|
|
385
|
+
taskAudit: taskAudit.summary,
|
|
373
386
|
lifecycleState,
|
|
374
387
|
reviewStatus,
|
|
388
|
+
reviewSubmitted: Boolean(reviewSubmission?.submitted),
|
|
389
|
+
reviewSubmission,
|
|
390
|
+
reviewQueueState,
|
|
375
391
|
reviewConfirmation,
|
|
376
|
-
|
|
392
|
+
materialsReady: materialIssues.length === 0,
|
|
393
|
+
materialIssues,
|
|
394
|
+
taskQueues: queueModel.taskQueues,
|
|
395
|
+
queueReasons: queueModel.queueReasons,
|
|
396
|
+
repairPrompt: queueModel.repairPrompt,
|
|
397
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
377
398
|
walkthroughPath: closeoutInfo.walkthroughPath ? `TARGET:${closeoutInfo.walkthroughPath}` : "",
|
|
378
399
|
lessonCandidatePath: fs.existsSync(lessonCandidatesPath)
|
|
379
400
|
? `TARGET:${toPosix(path.relative(target.projectRoot, lessonCandidatesPath))}`
|
|
@@ -383,6 +404,7 @@ export function collectTasks(target) {
|
|
|
383
404
|
lessonCandidatePromotionState: lessonCandidates.promotionState,
|
|
384
405
|
lessonCandidateCloseoutToken: lessonCandidates.closeoutToken,
|
|
385
406
|
lessonCandidateRowCount: lessonCandidates.rows.length,
|
|
407
|
+
lessonCandidateRows: lessonCandidates.rows,
|
|
386
408
|
lessonCandidateOpenCount: lessonCandidates.openCount,
|
|
387
409
|
lessonCandidateIssues: lessonCandidates.issues,
|
|
388
410
|
lessonCandidateDecisionComplete: isLessonCandidateDecisionComplete(lessonCandidates),
|
|
@@ -390,6 +412,16 @@ export function collectTasks(target) {
|
|
|
390
412
|
? `TARGET:${toPosix(path.relative(target.projectRoot, longRunningContractPath))}`
|
|
391
413
|
: "",
|
|
392
414
|
longRunningContractStatus: fs.existsSync(longRunningContractPath) ? "present" : "missing",
|
|
415
|
+
deletionState: tombstone.deletionState,
|
|
416
|
+
supersededBy: tombstone.supersededBy,
|
|
417
|
+
supersedes: tombstone.supersedes,
|
|
418
|
+
deleteReason: tombstone.deleteReason,
|
|
419
|
+
hiddenByDefault: tombstone.hiddenByDefault,
|
|
420
|
+
reopenEligible: tombstone.reopenEligible,
|
|
421
|
+
archiveEligible: tombstone.archiveEligible,
|
|
422
|
+
tombstoneSourcePath: tombstone.tombstoneSourcePath
|
|
423
|
+
? `TARGET:${toPosix(path.relative(target.projectRoot, path.join(taskDir, "task_plan.md")))}#Task Tombstone`
|
|
424
|
+
: "",
|
|
393
425
|
stateConflicts,
|
|
394
426
|
completion,
|
|
395
427
|
phases,
|
|
@@ -406,12 +438,7 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
406
438
|
const evidenceBundle = String(metadata.evidenceBundle || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
|
|
407
439
|
const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
|
|
408
440
|
const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
session = sessionPath && fs.existsSync(sessionPath) ? JSON.parse(fs.readFileSync(sessionPath, "utf8")) : null;
|
|
412
|
-
} catch {
|
|
413
|
-
session = null;
|
|
414
|
-
}
|
|
441
|
+
const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
|
|
415
442
|
const summary = session?.plan?.summary || {};
|
|
416
443
|
return {
|
|
417
444
|
targetLevel: metadata.migrationTargetLevel || "",
|
|
@@ -432,138 +459,12 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
432
459
|
};
|
|
433
460
|
}
|
|
434
461
|
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
return emptyLessonCandidateStatus("missing", ["missing-candidate-file"]);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const fields = lessonCandidateFields(text);
|
|
442
|
-
const declaredStatus = normalizeLessonCandidateStatus(fields.get("task-level status") || "pending-review");
|
|
443
|
-
const reviewDecision = normalizeCandidateField(fields.get("review decision") || "pending-human-review");
|
|
444
|
-
const promotionState = normalizeCandidateField(fields.get("promotion state") || "not-promoted");
|
|
445
|
-
const closeoutToken = String(fields.get("closeout token") || "pending").trim();
|
|
446
|
-
const rows = lessonCandidateRows(text);
|
|
447
|
-
const issues = [];
|
|
448
|
-
|
|
449
|
-
if (!allowedLessonCandidateTaskStatuses.has(declaredStatus)) {
|
|
450
|
-
issues.push(`invalid-task-status:${declaredStatus}`);
|
|
451
|
-
}
|
|
452
|
-
for (const row of rows) {
|
|
453
|
-
if (!allowedLessonCandidateRowStatuses.has(row.status)) issues.push(`invalid-row-status:${row.id || "missing-id"}:${row.status}`);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const aggregateStatus = aggregateLessonCandidateStatus(rows, declaredStatus);
|
|
457
|
-
if (declaredStatus !== aggregateStatus && declaredStatus !== "missing") {
|
|
458
|
-
issues.push(`status-aggregate-mismatch:${declaredStatus}->${aggregateStatus}`);
|
|
459
|
-
}
|
|
460
|
-
if (aggregateStatus === "no-candidate-accepted" && !noCandidateReason(text)) {
|
|
461
|
-
issues.push("missing-no-candidate-reason");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return {
|
|
465
|
-
status: aggregateStatus,
|
|
466
|
-
declaredStatus,
|
|
467
|
-
schemaVersion: fields.get("schema version") || "",
|
|
468
|
-
reviewDecision,
|
|
469
|
-
promotionState,
|
|
470
|
-
closeoutToken,
|
|
471
|
-
rows,
|
|
472
|
-
openCount: rows.filter((row) => ["ready-for-review", "needs-promotion"].includes(row.status)).length,
|
|
473
|
-
issues,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
export function isLessonCandidateDecisionComplete(candidateStatus) {
|
|
478
|
-
if (!candidateStatus || candidateStatus.issues?.length) return false;
|
|
479
|
-
return reviewCompleteLessonCandidateStatuses.has(candidateStatus.status);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function emptyLessonCandidateStatus(status, issues = []) {
|
|
483
|
-
return {
|
|
484
|
-
status,
|
|
485
|
-
declaredStatus: status,
|
|
486
|
-
schemaVersion: "",
|
|
487
|
-
reviewDecision: "",
|
|
488
|
-
promotionState: "",
|
|
489
|
-
closeoutToken: "",
|
|
490
|
-
rows: [],
|
|
491
|
-
openCount: 0,
|
|
492
|
-
issues,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function lessonCandidateFields(content) {
|
|
497
|
-
const { header, rows } = tableAfterHeading(content, /^Field$/i);
|
|
498
|
-
const fieldIndex = firstColumn(header, ["Field", "字段"]);
|
|
499
|
-
const valueIndex = firstColumn(header, ["Value", "值"]);
|
|
500
|
-
const fields = new Map();
|
|
501
|
-
if (fieldIndex < 0 || valueIndex < 0) return fields;
|
|
502
|
-
for (const row of rows) {
|
|
503
|
-
const key = String(row[fieldIndex] || "").trim().toLowerCase();
|
|
504
|
-
if (key) fields.set(key, String(row[valueIndex] || "").trim());
|
|
505
|
-
}
|
|
506
|
-
return fields;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function lessonCandidateRows(content) {
|
|
510
|
-
const { header, rows } = tableAfterHeading(content, /^ID$/i);
|
|
511
|
-
const idIndex = firstColumn(header, ["ID", "候选 ID"]);
|
|
512
|
-
const statusIndex = firstColumn(header, ["Row Status", "行状态", "Status", "状态"]);
|
|
513
|
-
const titleIndex = firstColumn(header, ["Title", "标题"]);
|
|
514
|
-
const decisionIndex = firstColumn(header, ["Review Decision", "审查决定"]);
|
|
515
|
-
const targetIndex = firstColumn(header, ["Promotion Target", "沉淀目标"]);
|
|
516
|
-
if (idIndex < 0 || statusIndex < 0) return [];
|
|
517
|
-
return rows
|
|
518
|
-
.filter((row) => /^LC-[A-Za-z0-9-]+$/i.test(row[idIndex] || ""))
|
|
519
|
-
.map((row) => ({
|
|
520
|
-
id: row[idIndex] || "",
|
|
521
|
-
status: normalizeLessonCandidateStatus(row[statusIndex] || ""),
|
|
522
|
-
title: row[titleIndex] || "",
|
|
523
|
-
reviewDecision: row[decisionIndex] || "",
|
|
524
|
-
promotionTarget: row[targetIndex] || "",
|
|
525
|
-
}));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function normalizeLessonCandidateStatus(value) {
|
|
529
|
-
return String(value || "")
|
|
530
|
-
.replace(/`/g, "")
|
|
531
|
-
.trim()
|
|
532
|
-
.toLowerCase()
|
|
533
|
-
.replaceAll("_", "-")
|
|
534
|
-
.replace(/\s+/g, "-");
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function normalizeCandidateField(value) {
|
|
538
|
-
return String(value || "").replace(/`/g, "").trim().toLowerCase().replaceAll("_", "-").replace(/\s+/g, "-");
|
|
462
|
+
function formatEvidenceBundle(value) {
|
|
463
|
+
const normalized = String(value || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
|
|
464
|
+
return normalized ? `TARGET:${normalized}` : "";
|
|
539
465
|
}
|
|
540
466
|
|
|
541
|
-
function
|
|
542
|
-
if (rows.length === 0) return declaredStatus === "no-candidate-accepted" ? "no-candidate-accepted" : declaredStatus;
|
|
543
|
-
const statuses = rows.map((row) => row.status);
|
|
544
|
-
if (statuses.includes("ready-for-review")) return "pending-review";
|
|
545
|
-
if (statuses.includes("needs-promotion")) return "needs-promotion";
|
|
546
|
-
if (statuses.every((status) => status === "promoted")) return "promoted";
|
|
547
|
-
if (statuses.every((status) => status === "rejected")) return "rejected";
|
|
548
|
-
if (statuses.every((status) => ["promoted", "rejected"].includes(status))) return "promoted";
|
|
549
|
-
return declaredStatus;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function noCandidateReason(content) {
|
|
553
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
554
|
-
const start = lines.findIndex((line) => /^##\s*No-Candidate Reason\s*$/i.test(line.trim()));
|
|
555
|
-
if (start < 0) return "";
|
|
556
|
-
const body = [];
|
|
557
|
-
for (const line of lines.slice(start + 1)) {
|
|
558
|
-
if (/^##\s+/.test(line)) break;
|
|
559
|
-
body.push(line);
|
|
560
|
-
}
|
|
561
|
-
return body.join("\n").replace(/`/g, "").trim();
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
function taskCloseoutInfo(target, taskPlanPath) {
|
|
566
|
-
const closeout = readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
467
|
+
function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
567
468
|
if (!closeout.trim()) return { status: "missing", walkthroughPath: "" };
|
|
568
469
|
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
|
|
569
470
|
const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
|
|
@@ -585,91 +486,11 @@ function extractWalkthroughPath(target, closeoutLine) {
|
|
|
585
486
|
return projectRelative;
|
|
586
487
|
}
|
|
587
488
|
|
|
588
|
-
export function parseReviewConfirmation(reviewContent) {
|
|
589
|
-
const match = String(reviewContent || "").match(/^##\s*(?:Human Review Confirmation|人工审查确认)\s*$([\s\S]*?)(?=^##\s+|\s*$)/im);
|
|
590
|
-
if (!match) return null;
|
|
591
|
-
const block = match[1] || "";
|
|
592
|
-
const timeMatch = block.match(/\|\s*(\d{4}-\d{2}-\d{2}[^|]*)\|/);
|
|
593
|
-
const reviewerMatch = block.match(/Reviewer\s*[::]\s*([^\n]+)/i) || block.match(/审查人\s*[::]\s*([^\n]+)/);
|
|
594
|
-
return {
|
|
595
|
-
confirmed: true,
|
|
596
|
-
confirmedAt: timeMatch ? timeMatch[1].trim() : "",
|
|
597
|
-
reviewer: reviewerMatch ? reviewerMatch[1].trim() : "",
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
export function taskReviewStatus({ reviewContent = "", risks = [], confirmation = null } = {}) {
|
|
602
|
-
if (risks.some(isBlockingReviewRisk)) return "blocked-open-findings";
|
|
603
|
-
if (confirmation?.confirmed) return "confirmed";
|
|
604
|
-
if (!String(reviewContent || "").trim()) return "missing";
|
|
605
|
-
if (/Verdict\s*[::]\s*yes/i.test(reviewContent) || /本轮已检查|未发现阻塞目标的重要发现/.test(reviewContent)) return "reviewed-unconfirmed";
|
|
606
|
-
return "required";
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
export function isBlockingReviewRisk(risk) {
|
|
610
|
-
return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing" } = {}) {
|
|
614
|
-
if (closeoutStatus === "closed") return "closed";
|
|
615
|
-
if (state === "blocked") return "blocked";
|
|
616
|
-
if (reviewStatus === "blocked-open-findings") return "review-blocked";
|
|
617
|
-
if (state === "done") return "closing";
|
|
618
|
-
if (state === "review") return "in_review";
|
|
619
|
-
if (state === "in_progress") return "active";
|
|
620
|
-
if (["planned", "not_started"].includes(state)) return "ready";
|
|
621
|
-
return "unknown";
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function collectStateConflicts({ state, reviewStatus, closeoutStatus, lifecycleState }) {
|
|
625
|
-
const conflicts = [];
|
|
626
|
-
if (state === "done" && closeoutStatus !== "closed") {
|
|
627
|
-
conflicts.push({
|
|
628
|
-
code: "done-without-closeout",
|
|
629
|
-
severity: "warn",
|
|
630
|
-
message: "Task state is done, but closeout is still missing or pending.",
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
if (reviewStatus === "blocked-open-findings") {
|
|
634
|
-
conflicts.push({
|
|
635
|
-
code: "review-blocked-open-findings",
|
|
636
|
-
severity: "block",
|
|
637
|
-
message: "Open P0-P2 review findings block human review confirmation.",
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
if (lifecycleState === "closed" && reviewStatus === "blocked-open-findings") {
|
|
641
|
-
conflicts.push({
|
|
642
|
-
code: "closed-with-blocking-review",
|
|
643
|
-
severity: "block",
|
|
644
|
-
message: "Closeout is closed while review findings still block release.",
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
return conflicts;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
489
|
function collectHandoffs(progressContent, taskId) {
|
|
651
490
|
if (!/Coordinator Handoff/i.test(progressContent) || !/pending-coordinator-pass/i.test(progressContent)) return [];
|
|
652
491
|
return [{ id: `H-${taskId}`, from: "worker", to: "coordinator", state: "pending", summary: "Coordinator handoff pending" }];
|
|
653
492
|
}
|
|
654
493
|
|
|
655
|
-
export function collectReviewRisks(reviewContent) {
|
|
656
|
-
const { header, rows } = tableAfterHeading(reviewContent, /^ID$/i);
|
|
657
|
-
const severityIndex = getColumn(header, "Severity");
|
|
658
|
-
const findingIndex = getColumn(header, "Finding");
|
|
659
|
-
const openIndex = getColumn(header, "Open");
|
|
660
|
-
const blocksIndex = getColumn(header, "Blocks Release");
|
|
661
|
-
if (severityIndex < 0 || findingIndex < 0) return [];
|
|
662
|
-
return rows
|
|
663
|
-
.filter((row) => /^P[0-3]$/i.test(row[severityIndex] || ""))
|
|
664
|
-
.map((row) => ({
|
|
665
|
-
id: row[0],
|
|
666
|
-
severity: row[severityIndex],
|
|
667
|
-
open: /^yes$/i.test(row[openIndex] || "no"),
|
|
668
|
-
blocksRelease: /^yes$/i.test(row[blocksIndex] || "no"),
|
|
669
|
-
summary: row[findingIndex],
|
|
670
|
-
}));
|
|
671
|
-
}
|
|
672
|
-
|
|
673
494
|
function collectEvidence(progressContent) {
|
|
674
495
|
const matches = [...progressContent.matchAll(/\b(command|diff|fixture|screenshot|review|report):((?:PUBLIC|PRIVATE|TARGET|EXTERNAL|URL):[^:\s|]+):([^\n|]+)/g)];
|
|
675
496
|
return matches.map((match, index) => ({
|