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.
Files changed (219) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +244 -87
  6. package/README.zh-CN.md +77 -35
  7. package/SKILL.md +32 -24
  8. package/docs-release/README.md +9 -5
  9. package/docs-release/architecture/overview.md +17 -5
  10. package/docs-release/architecture/overview.zh-CN.md +9 -5
  11. package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
  12. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  13. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  14. package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
  15. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  16. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
  17. package/docs-release/architecture/system-explainer/README.md +67 -0
  18. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
  19. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  20. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  21. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
  22. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  23. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
  24. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  25. package/docs-release/assets/dashboard-overview.png +0 -0
  26. package/docs-release/guides/agent-installation.en-US.md +39 -15
  27. package/docs-release/guides/agent-installation.md +43 -16
  28. package/docs-release/guides/contributing.md +100 -0
  29. package/docs-release/guides/contributing.zh-CN.md +99 -0
  30. package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
  31. package/docs-release/guides/document-audience-and-surfaces.md +3 -2
  32. package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
  33. package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
  34. package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
  35. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
  36. package/docs-release/guides/migration-playbook.en-US.md +14 -15
  37. package/docs-release/guides/migration-playbook.md +14 -15
  38. package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
  39. package/docs-release/guides/parent-control-repository-pattern.md +7 -5
  40. package/docs-release/guides/preset-development.md +238 -0
  41. package/docs-release/guides/repository-operating-models.en-US.md +5 -4
  42. package/docs-release/guides/repository-operating-models.md +5 -4
  43. package/docs-release/guides/task-state-machine.en-US.md +224 -0
  44. package/docs-release/guides/task-state-machine.md +231 -0
  45. package/docs-release/intl/en-US.md +1 -1
  46. package/docs-release/intl/zh-CN.md +1 -1
  47. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
  48. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
  49. package/package.json +10 -4
  50. package/presets/legacy-migration/checks/preset-check.mjs +3 -0
  51. package/presets/legacy-migration/preset.yaml +134 -0
  52. package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
  53. package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
  54. package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
  55. package/presets/legacy-migration/templates/findings.seed.md +17 -0
  56. package/presets/legacy-migration/templates/review.seed.md +12 -0
  57. package/presets/legacy-migration/templates/task_plan.append.md +9 -0
  58. package/presets/legacy-migration/templates/visual_map.append.md +12 -0
  59. package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
  60. package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
  61. package/presets/lesson-sedimentation/preset.yaml +23 -0
  62. package/presets/lesson-sedimentation/templates/prompt.md +23 -0
  63. package/presets/module/preset.yaml +25 -0
  64. package/presets/module/templates/execution_strategy.append.md +8 -0
  65. package/presets/module/templates/task_plan.append.md +17 -0
  66. package/presets/standard-task/preset.yaml +31 -0
  67. package/presets/standard-task/templates/task_plan.append.md +7 -0
  68. package/references/adversarial-review-standard.md +2 -2
  69. package/references/agents-md-pattern.md +2 -2
  70. package/references/delivery-operating-model-standard.md +3 -3
  71. package/references/docs-directory-standard.md +6 -7
  72. package/references/harness-ledger.md +53 -96
  73. package/references/lessons-governance.md +88 -93
  74. package/references/module-parallel-standard.md +14 -14
  75. package/references/planning-loop.md +12 -6
  76. package/references/pull-request-standard.md +118 -0
  77. package/references/repo-governance-standard.md +11 -2
  78. package/references/review-routing-standard.md +7 -1
  79. package/references/ssot-governance.md +67 -59
  80. package/references/taskr-gap-analysis.md +600 -0
  81. package/references/walkthrough-closeout.md +7 -7
  82. package/scripts/check-harness.mjs +40 -301
  83. package/scripts/commands/dashboard-command.mjs +67 -0
  84. package/scripts/commands/migration-command.mjs +126 -0
  85. package/scripts/commands/preset-command.mjs +73 -0
  86. package/scripts/commands/task-command.mjs +328 -0
  87. package/scripts/harness.mjs +59 -260
  88. package/scripts/lib/capability-registry.mjs +82 -28
  89. package/scripts/lib/check-module-parallel.mjs +230 -0
  90. package/scripts/lib/check-profiles.mjs +90 -228
  91. package/scripts/lib/check-task-contracts.mjs +55 -0
  92. package/scripts/lib/core-shared.mjs +65 -2
  93. package/scripts/lib/dashboard-data.mjs +155 -24
  94. package/scripts/lib/dashboard-workbench.mjs +131 -12
  95. package/scripts/lib/dashboard-writer.mjs +20 -4
  96. package/scripts/lib/git-status-summary.mjs +46 -0
  97. package/scripts/lib/governance-index-generator.mjs +174 -0
  98. package/scripts/lib/governance-sync.mjs +611 -0
  99. package/scripts/lib/governance-table-boundary.mjs +175 -0
  100. package/scripts/lib/harness-core.mjs +6 -0
  101. package/scripts/lib/lesson-maintenance.mjs +36 -29
  102. package/scripts/lib/markdown-utils.mjs +33 -0
  103. package/scripts/lib/migration-planner.mjs +4 -6
  104. package/scripts/lib/migration-support.mjs +1 -1
  105. package/scripts/lib/phase-kind.mjs +50 -0
  106. package/scripts/lib/preset-audit-contracts.mjs +37 -0
  107. package/scripts/lib/preset-engine.mjs +494 -0
  108. package/scripts/lib/preset-registry.mjs +776 -0
  109. package/scripts/lib/preset-resource-contracts.mjs +83 -0
  110. package/scripts/lib/review-confirm-git-gate.mjs +248 -0
  111. package/scripts/lib/status-builder.mjs +88 -0
  112. package/scripts/lib/status-dashboard-renderer.mjs +105 -0
  113. package/scripts/lib/subagent-authorization-audit.mjs +196 -0
  114. package/scripts/lib/task-audit-metadata.mjs +385 -0
  115. package/scripts/lib/task-audit-migration.mjs +350 -0
  116. package/scripts/lib/task-completion-consistency.mjs +26 -0
  117. package/scripts/lib/task-index.mjs +93 -0
  118. package/scripts/lib/task-lesson-candidates.mjs +242 -0
  119. package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
  120. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
  121. package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
  122. package/scripts/lib/task-lifecycle/review-confirm.mjs +112 -0
  123. package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
  124. package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
  125. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
  126. package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
  127. package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
  128. package/scripts/lib/task-lifecycle.mjs +338 -477
  129. package/scripts/lib/task-metadata.mjs +118 -0
  130. package/scripts/lib/task-review-model.mjs +455 -0
  131. package/scripts/lib/task-scanner.mjs +193 -372
  132. package/scripts/lib/task-tombstone-commands.mjs +140 -0
  133. package/scripts/postinstall.mjs +14 -0
  134. package/skills/preset-creator/SKILL.md +179 -0
  135. package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
  136. package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
  139. package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
  140. package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
  141. package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
  142. package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
  143. package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
  144. package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
  145. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
  146. package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
  147. package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
  148. package/templates/AGENTS.md.template +24 -18
  149. package/templates/dashboard/assets/app-src/00-state.js +13 -0
  150. package/templates/dashboard/assets/app-src/10-router.js +5 -1
  151. package/templates/dashboard/assets/app-src/20-overview.js +18 -8
  152. package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
  153. package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
  154. package/templates/dashboard/assets/app-src/45-review.js +241 -22
  155. package/templates/dashboard/assets/app-src/50-migration.js +24 -10
  156. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  157. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  158. package/templates/dashboard/assets/app-src/90-bindings.js +302 -29
  159. package/templates/dashboard/assets/app.css +1501 -376
  160. package/templates/dashboard/assets/app.css.manifest.json +10 -0
  161. package/templates/dashboard/assets/app.js +1240 -101
  162. package/templates/dashboard/assets/app.manifest.json +2 -0
  163. package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
  164. package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
  165. package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
  166. package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
  167. package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
  168. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
  169. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  170. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
  171. package/templates/dashboard/assets/i18n.js +263 -23
  172. package/templates/ledger/Harness-Ledger.md +13 -25
  173. package/templates/lessons/lesson-arch-process-change.md +1 -1
  174. package/templates/lessons/lesson-new-doc.md +1 -1
  175. package/templates/lessons/lesson-ref-change.md +1 -1
  176. package/templates/planning/INDEX.md +87 -0
  177. package/templates/planning/brief.md +1 -1
  178. package/templates/planning/execution_strategy.md +31 -0
  179. package/templates/planning/lesson_candidates.md +18 -6
  180. package/templates/planning/module_session_prompt.md +1 -0
  181. package/templates/planning/optional/artifacts/INDEX.md +3 -3
  182. package/templates/planning/optional/references/INDEX.md +3 -3
  183. package/templates/planning/review.md +41 -0
  184. package/templates/planning/task_plan.md +5 -21
  185. package/templates/planning/visual_map.md +13 -9
  186. package/templates/planning/visual_map.simple.md +52 -0
  187. package/templates/reference/execution-workflow-standard.md +31 -3
  188. package/templates/reference/pull-request-standard.md +80 -0
  189. package/templates/reference/repo-governance-standard.md +7 -6
  190. package/templates/reference/review-routing-standard.md +6 -0
  191. package/templates/reference/walkthrough-standard.md +2 -1
  192. package/templates/verifier/verifier-output.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +25 -19
  194. package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
  195. package/templates-zh-CN/planning/INDEX.md +87 -0
  196. package/templates-zh-CN/planning/brief.md +1 -1
  197. package/templates-zh-CN/planning/execution_strategy.md +30 -0
  198. package/templates-zh-CN/planning/lesson_candidates.md +18 -6
  199. package/templates-zh-CN/planning/module_session_prompt.md +1 -0
  200. package/templates-zh-CN/planning/review.md +41 -1
  201. package/templates-zh-CN/planning/task_plan.md +4 -44
  202. package/templates-zh-CN/planning/visual_map.md +14 -7
  203. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  204. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  205. package/templates-zh-CN/reference/docs-library-standard.md +1 -1
  206. package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
  207. package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
  208. package/templates-zh-CN/reference/pull-request-standard.md +106 -0
  209. package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
  210. package/templates-zh-CN/reference/review-routing-standard.md +8 -1
  211. package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
  212. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
  213. package/docs-release/assets/dashboard-overview-en.png +0 -0
  214. package/scripts/smoke-dashboard.mjs +0 -92
  215. package/scripts/test-harness.mjs +0 -1395
  216. package/templates/ssot/Feature-SSoT.md +0 -43
  217. package/templates/ssot/Lessons-SSoT.md +0 -44
  218. package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
  219. 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
- export const allowedLessonCandidateTaskStatuses = new Set([
25
- "missing",
26
- "pending-review",
27
- "no-candidate-accepted",
28
- "needs-promotion",
29
- "promoted",
30
- "rejected",
31
- ]);
32
-
33
- export const allowedLessonCandidateRowStatuses = new Set([
34
- "ready-for-review",
35
- "needs-promotion",
36
- "promoted",
37
- "rejected",
38
- ]);
39
-
40
- export const reviewCompleteLessonCandidateStatuses = new Set([
41
- "no-candidate-accepted",
42
- "needs-promotion",
43
- "promoted",
44
- "rejected",
45
- ]);
46
-
47
- export function parseTaskState(progressContent) {
48
- return parseTaskStateInfo(progressContent).state;
49
- }
50
-
51
- export function parseTaskBudget(taskPlanContent) {
52
- const match =
53
- String(taskPlanContent || "").match(/^Selected budget\s*[::]\s*([^\n]+)/im) ||
54
- String(taskPlanContent || "").match(/^选择预算\s*[::]\s*([^\n]+)/im);
55
- if (!match) return "standard";
56
- const raw = match[1].replace(/`/g, "").trim().toLowerCase();
57
- const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
58
- if (allowedTaskBudgets.has(normalized)) return normalized;
59
- if (["long-running", "longrunning", "module-parallel"].includes(normalized)) return "complex";
60
- return "standard";
61
- }
62
-
63
- function parseMetadataLine(content, labels) {
64
- const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
65
- const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
66
- return match ? match[1].replace(/`/g, "").trim() : "";
67
- }
68
-
69
- function normalizeMetadataValue(value, fallback = "") {
70
- const normalized = String(value || "")
71
- .replace(/`/g, "")
72
- .trim()
73
- .toLowerCase()
74
- .replaceAll("_", "-")
75
- .replace(/\s+/g, "-");
76
- return normalized || fallback;
77
- }
78
-
79
- export function parseTaskMetadata(taskPlanContent) {
80
- const content = String(taskPlanContent || "");
81
- const kind = normalizeMetadataValue(parseMetadataLine(content, ["Task Kind", "任务类型"]), "general");
82
- const preset = normalizeMetadataValue(parseMetadataLine(content, ["Task Preset", "Preset", "任务预设"]), "none");
83
- const presetVersion = parseMetadataLine(content, ["Preset Version", "预设版本"]);
84
- const migrationTargetLevel = normalizeMetadataValue(
85
- parseMetadataLine(content, ["Migration Target Level", "Target Level", "迁移目标等级", "目标等级"]),
86
- "",
87
- );
88
- const migrationAchievedLevel = normalizeMetadataValue(
89
- parseMetadataLine(content, ["Migration Achieved Level", "Achieved Level", "迁移实际完成等级", "实际完成等级"]),
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
- return listTaskPlanPaths(target).map((taskPlanPath) => {
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 lessonCandidates = parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath));
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 reviewConfirmation = parseReviewConfirmation(review);
333
- const reviewStatus = taskReviewStatus({ reviewContent: review, risks, confirmation: reviewConfirmation });
334
- const closeoutInfo = taskCloseoutInfo(target, taskPlanPath);
335
- const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: closeoutInfo.status });
336
- const stateConflicts = collectStateConflicts({ state: stateInfo.state, reviewStatus, closeoutStatus: closeoutInfo.status, lifecycleState });
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
- closeoutStatus: closeoutInfo.status,
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
- let session = null;
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
- export function parseLessonCandidateStatus(content) {
436
- const text = String(content || "");
437
- if (!text.trim()) {
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 aggregateLessonCandidateStatus(rows, declaredStatus) {
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) => ({