coding-agent-harness 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/CONTRIBUTING.md +8 -4
  3. package/README.md +12 -2
  4. package/README.zh-CN.md +10 -2
  5. package/SKILL.md +14 -3
  6. package/dist/build-dist.mjs +19 -6
  7. package/dist/check-dist-observation.mjs +57 -29
  8. package/dist/check-harness.mjs +0 -1
  9. package/dist/check-import-graph.mjs +44 -27
  10. package/dist/check-lite-forbidden-surfaces.mjs +121 -0
  11. package/dist/check-no-ts-nocheck.mjs +7 -7
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +51 -9
  14. package/dist/commands/dashboard-command.mjs +52 -14
  15. package/dist/commands/migration-command.mjs +18 -8
  16. package/dist/commands/module-command.mjs +142 -0
  17. package/dist/commands/preset-command.mjs +51 -12
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +109 -52
  20. package/dist/harness.mjs +6 -304
  21. package/dist/lib/capability-registry.mjs +229 -53
  22. package/dist/lib/check-module-parallel.mjs +1 -6
  23. package/dist/lib/check-profiles.mjs +39 -46
  24. package/dist/lib/check-task-contracts.mjs +6 -4
  25. package/dist/lib/command-registry.mjs +248 -0
  26. package/dist/lib/core-shared.mjs +78 -3
  27. package/dist/lib/dashboard-data.mjs +203 -22
  28. package/dist/lib/dashboard-workbench.mjs +245 -21
  29. package/dist/lib/dashboard-writer.mjs +4 -1
  30. package/dist/lib/git-status-summary.mjs +0 -1
  31. package/dist/lib/governance-index-generator.mjs +7 -5
  32. package/dist/lib/governance-sync.mjs +46 -121
  33. package/dist/lib/governance-table-boundary.mjs +1 -14
  34. package/dist/lib/harness-core.mjs +4 -1
  35. package/dist/lib/harness-paths.mjs +115 -1
  36. package/dist/lib/impact-classifier.mjs +420 -0
  37. package/dist/lib/lesson-maintenance.mjs +1 -2
  38. package/dist/lib/markdown-utils.mjs +50 -1
  39. package/dist/lib/migration-planner.mjs +31 -16
  40. package/dist/lib/migration-support.mjs +5 -4
  41. package/dist/lib/module-registry.mjs +296 -0
  42. package/dist/lib/preset-audit-contracts.mjs +24 -1
  43. package/dist/lib/preset-engine.mjs +67 -29
  44. package/dist/lib/preset-registry.mjs +361 -71
  45. package/dist/lib/preset-runner.mjs +292 -26
  46. package/dist/lib/review-confirm-git-gate.mjs +73 -19
  47. package/dist/lib/status-builder.mjs +23 -8
  48. package/dist/lib/structure-migration.mjs +6 -4
  49. package/dist/lib/subagent-authorization-audit.mjs +8 -2
  50. package/dist/lib/task-archive-eligibility.mjs +65 -0
  51. package/dist/lib/task-audit-metadata.mjs +25 -11
  52. package/dist/lib/task-audit-migration.mjs +21 -14
  53. package/dist/lib/task-discovery-contract.mjs +32 -0
  54. package/dist/lib/task-index.mjs +3 -2
  55. package/dist/lib/task-lesson-candidates.mjs +1 -2
  56. package/dist/lib/task-lesson-sedimentation.mjs +310 -9
  57. package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
  58. package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
  59. package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
  60. package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
  61. package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
  62. package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
  63. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
  64. package/dist/lib/task-lifecycle/template-files.mjs +2 -5
  65. package/dist/lib/task-lifecycle.mjs +116 -160
  66. package/dist/lib/task-metadata.mjs +10 -5
  67. package/dist/lib/task-preset-contract-drift.mjs +45 -0
  68. package/dist/lib/task-repository.mjs +192 -0
  69. package/dist/lib/task-review-model.mjs +36 -17
  70. package/dist/lib/task-scanner.mjs +74 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +186 -29
  73. package/dist/lib/types/check-profiles.js +1 -0
  74. package/dist/lib/types/impact.js +1 -0
  75. package/dist/lib/types/preset.js +1 -0
  76. package/dist/lib/types/task-lifecycle.js +1 -0
  77. package/dist/lib/types/task-scanner.js +1 -0
  78. package/dist/postinstall.mjs +2 -2
  79. package/dist/run-built-tests.mjs +10 -3
  80. package/docs-release/README.md +1 -0
  81. package/docs-release/architecture/document-contract-kernel/README.md +150 -0
  82. package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
  83. package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
  84. package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
  85. package/docs-release/architecture/overview.md +2 -2
  86. package/docs-release/architecture/overview.zh-CN.md +2 -2
  87. package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
  88. package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
  89. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
  90. package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
  91. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
  92. package/docs-release/architecture/system-explainer/README.md +1 -1
  93. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
  94. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
  95. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
  96. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  97. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
  98. package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
  99. package/docs-release/guides/agent-installation.en-US.md +4 -6
  100. package/docs-release/guides/agent-installation.md +11 -8
  101. package/docs-release/guides/contributing.md +10 -3
  102. package/docs-release/guides/contributing.zh-CN.md +10 -3
  103. package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
  104. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
  105. package/docs-release/guides/migration-playbook.en-US.md +9 -6
  106. package/docs-release/guides/migration-playbook.md +9 -6
  107. package/docs-release/guides/preset-development.md +68 -2
  108. package/docs-release/guides/task-state-machine.en-US.md +8 -8
  109. package/docs-release/guides/task-state-machine.md +7 -7
  110. package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
  111. package/package.json +16 -12
  112. package/postinstall.mjs +37 -0
  113. package/presets/legacy-migration/preset.yaml +5 -5
  114. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  115. package/presets/lesson-sedimentation/preset.yaml +3 -3
  116. package/presets/module/preset.yaml +2 -2
  117. package/presets/module/templates/execution_strategy.append.md +1 -1
  118. package/presets/module/templates/task_plan.append.md +3 -3
  119. package/presets/release-closeout/checks/check-release-package.mjs +6 -1
  120. package/presets/release-closeout/preset.yaml +9 -9
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
  122. package/presets/release-closeout/templates/task_plan.append.md +5 -5
  123. package/presets/standard-task/preset.yaml +2 -2
  124. package/references/agents-md-pattern.md +23 -17
  125. package/references/lessons-governance.md +2 -2
  126. package/references/module-parallel-standard.md +3 -6
  127. package/references/ssot-governance.md +2 -2
  128. package/references/taskr-gap-analysis.md +3 -3
  129. package/run-dist.mjs +34 -0
  130. package/skills/preset-creator/SKILL.md +40 -8
  131. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  132. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  133. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  134. package/templates/AGENTS.md.template +28 -26
  135. package/templates/architecture/README.md +2 -2
  136. package/templates/architecture/service-catalog.md +2 -2
  137. package/templates/architecture/services/service-template.md +1 -1
  138. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  139. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  140. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  141. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  142. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  143. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  144. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  145. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  146. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  147. package/templates/dashboard/assets/app.css +928 -53
  148. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  149. package/templates/dashboard/assets/app.js +1071 -98
  150. package/templates/dashboard/assets/app.manifest.json +1 -0
  151. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  152. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  153. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  154. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  155. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  156. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  158. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  159. package/templates/dashboard/assets/i18n.js +166 -2
  160. package/templates/development/README.md +9 -9
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +1 -1
  163. package/templates/development/external-source-packs/README.md +2 -2
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +1 -1
  166. package/templates/integrations/event-contract.md +1 -1
  167. package/templates/integrations/third-party/vendor-template.md +1 -1
  168. package/templates/integrations/webhook-contract.md +1 -1
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/modules/module_brief.md +50 -0
  171. package/templates/modules/module_plan.md +49 -0
  172. package/templates/modules/registry_view.md +9 -0
  173. package/templates/modules/session_prompt_pack.md +55 -0
  174. package/templates/planning/brief.md +32 -8
  175. package/templates/planning/module_brief.md +28 -3
  176. package/templates/planning/module_plan.md +26 -11
  177. package/templates/planning/module_session_prompt.md +11 -2
  178. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  179. package/templates/planning/review.md +1 -1
  180. package/templates/planning/visual_map.md +1 -1
  181. package/templates/reference/docs-library-standard.md +7 -7
  182. package/templates/reference/execution-workflow-standard.md +13 -0
  183. package/templates/reference/external-source-intake-standard.md +10 -10
  184. package/templates/reference/repo-governance-standard.md +1 -1
  185. package/templates/reference/review-routing-standard.md +4 -0
  186. package/templates/ssot/Module-Registry.md +4 -38
  187. package/templates/walkthrough/walkthrough-template.md +1 -1
  188. package/templates-zh-CN/AGENTS.md.template +27 -25
  189. package/templates-zh-CN/CLAUDE.md.template +1 -1
  190. package/templates-zh-CN/architecture/README.md +2 -2
  191. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  192. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  193. package/templates-zh-CN/development/README.md +9 -9
  194. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  195. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  196. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  197. package/templates-zh-CN/integrations/README.md +4 -4
  198. package/templates-zh-CN/integrations/api-contract.md +1 -1
  199. package/templates-zh-CN/integrations/event-contract.md +1 -1
  200. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  201. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  202. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  203. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  204. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  205. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  206. package/templates-zh-CN/modules/module_brief.md +47 -0
  207. package/templates-zh-CN/modules/module_plan.md +48 -0
  208. package/templates-zh-CN/modules/registry_view.md +9 -0
  209. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  210. package/templates-zh-CN/planning/INDEX.md +1 -0
  211. package/templates-zh-CN/planning/brief.md +26 -7
  212. package/templates-zh-CN/planning/module_brief.md +24 -2
  213. package/templates-zh-CN/planning/module_plan.md +35 -29
  214. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  215. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  216. package/templates-zh-CN/planning/review.md +1 -1
  217. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  218. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  219. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  220. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  221. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  222. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  223. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  224. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  225. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  226. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  227. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  228. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  229. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  230. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  231. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  232. 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 topLevelSupersedes = splitList(parseMetadataLine(taskPlanContent, ["Supersedes", "合并自"]));
53
- const match = String(taskPlanContent || "").match(/^##\s*(?:Task Tombstone|任务墓碑)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
55
+ const scanContent = stripFencedCodeBlocks(String(taskPlanContent || ""));
56
+ const topLevelSupersedes = splitList(parseMetadataLine(scanContent, ["Supersedes", "合并自"]));
57
+ const match = String(scanContent || "").match(/^##\s*(?:Task Tombstone|任务墓碑)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
54
58
  const fields = match ? fieldsFromMarkdownBlock(match[1] || "") : new Map();
55
59
  if (fields.size === 0) {
56
60
  return {
@@ -66,7 +70,10 @@ export function parseTaskTombstone(taskPlanContent) {
66
70
  };
67
71
  }
68
72
  const state = normalizeMetadataValue(fields.get("state") || fields.get("状态") || "soft-deleted", "soft-deleted");
69
- const deletionState = ["soft-deleted", "superseded", "archived"].includes(state) ? state : "soft-deleted";
73
+ if (!["soft-deleted", "superseded", "archived"].includes(state)) {
74
+ throw new Error(`Invalid tombstone state: ${state}`);
75
+ }
76
+ const deletionState = state;
70
77
  return {
71
78
  deletionState,
72
79
  supersededBy: fields.get("superseded by") || fields.get("替代任务") || "",
@@ -110,7 +117,7 @@ export function parseAgentReviewSubmission(reviewContent, { taskKey = "" } = {})
110
117
  scannerVersion: fields.get("scanner version") || "",
111
118
  };
112
119
  }
113
- export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true }) {
120
+ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true, }) {
114
121
  const issues = [];
115
122
  const addIssue = (code, message, sourcePath, extra = {}) => {
116
123
  issues.push({
@@ -167,10 +174,13 @@ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, re
167
174
  export function requiresReviewMaterials({ state = "unknown", lifecycleState = "unknown", closeoutStatus = "missing" } = {}) {
168
175
  return (state === "review" ||
169
176
  state === "done" ||
170
- ["in_review", "review-blocked", "closing", "closed-review-pending"].includes(lifecycleState) ||
177
+ ["in_review", "review-blocked", "closing", "closed-review-pending", "confirmed-finalization-pending", "lesson-finalization-pending"].includes(lifecycleState) ||
171
178
  closeoutStatus === "closed");
172
179
  }
173
- export function deriveTaskQueues({ id, title, state, budget, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target }) {
180
+ export function hasPendingLessonWork(lessonCandidates) {
181
+ return lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || (lessonCandidates?.openCount ?? 0) > 0;
182
+ }
183
+ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target, }) {
174
184
  const queueReasons = [];
175
185
  const pushReason = (reason) => {
176
186
  queueReasons.push({
@@ -226,7 +236,7 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
226
236
  message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
227
237
  });
228
238
  }
229
- const hasLessonWork = lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || lessonCandidates?.openCount > 0;
239
+ const hasLessonWork = hasPendingLessonWork(lessonCandidates);
230
240
  const taskQueues = [];
231
241
  if (tombstone.deletionState !== "active") {
232
242
  taskQueues.push("soft-deleted-superseded");
@@ -243,13 +253,21 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
243
253
  taskQueues.push("lessons");
244
254
  if (budget === "simple" && state === "done" && closeoutStatus === "closed")
245
255
  taskQueues.push("finalized");
246
- if (reviewStatus === "confirmed")
247
- taskQueues.push(closeoutStatus === "closed" ? "finalized" : "confirmed");
256
+ if (reviewStatus === "confirmed") {
257
+ if (closeoutStatus === "closed")
258
+ taskQueues.push("finalized");
259
+ else {
260
+ taskQueues.push("confirmed");
261
+ if (!hasLessonWork)
262
+ taskQueues.push("confirmed-finalization-pending");
263
+ }
264
+ }
248
265
  }
249
- if (taskQueues.length === 0)
250
- taskQueues.push("active");
266
+ const normalizedTaskQueues = [...new Set(taskQueues)];
267
+ if (normalizedTaskQueues.length === 0)
268
+ normalizedTaskQueues.push("active");
251
269
  return {
252
- taskQueues,
270
+ taskQueues: normalizedTaskQueues,
253
271
  queueReasons,
254
272
  repairPrompt: renderRepairPrompt({ id, title, taskDir, target, reasons: queueReasons }),
255
273
  };
@@ -267,7 +285,6 @@ export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit
267
285
  if (taskAudit) {
268
286
  const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
269
287
  if (confirmation?.confirmed &&
270
- confirmation.auditSource !== "migrated-legacy-review" &&
271
288
  projectRoot &&
272
289
  (indexPath || taskDir) &&
273
290
  confirmation.commitSha) {
@@ -314,9 +331,9 @@ function hasAgentReviewSignal(reviewContent) {
314
331
  return /本轮已检查|未发现阻塞目标的重要发现/.test(content);
315
332
  }
316
333
  export function isBlockingReviewRisk(risk) {
317
- return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
334
+ return /^P[0-2]$/i.test(risk?.severity || "") && Boolean(risk?.open || risk?.blocksRelease);
318
335
  }
319
- export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard" } = {}) {
336
+ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard", lessonCandidates = null } = {}) {
320
337
  if (reviewStatus === "blocked-open-findings")
321
338
  return "review-blocked";
322
339
  if (budget === "simple" && closeoutStatus === "closed")
@@ -325,6 +342,8 @@ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missin
325
342
  return "closed-review-pending";
326
343
  if (closeoutStatus === "closed")
327
344
  return "closed";
345
+ if (reviewStatus === "confirmed")
346
+ return hasPendingLessonWork(lessonCandidates) ? "lesson-finalization-pending" : "confirmed-finalization-pending";
328
347
  if (state === "blocked")
329
348
  return "blocked";
330
349
  if (state === "done")
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, longRunningTaskContractFile, toPosix, readFileSafe, readJsonSafe, walkFiles, isArchivedHarnessPath, titleFromMarkdown, } from "./core-shared.mjs";
@@ -7,8 +6,35 @@ import { normalizePhaseActor, normalizePhaseKind, phaseCompletionAverage, } from
7
6
  import { legacyAuditIssues, parseTaskAuditMetadata, scaffoldProvenanceSummaryFromTaskAudit, taskAuditMaterialIssues, } from "./task-audit-metadata.mjs";
8
7
  import { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskStateInfo, } from "./task-metadata.mjs";
9
8
  import { isLessonCandidateDecisionComplete, parseLessonCandidateStatus, validateLessonCandidateDetailArtifacts, } from "./task-lesson-candidates.mjs";
9
+ import { collectUneditedTemplateMaterialIssues } from "./task-template-materials.mjs";
10
10
  import { assessMaterialsReadiness, collectReviewRisks, collectStateConflicts, deriveLifecycleState, deriveReviewQueueState, deriveTaskQueues, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
11
11
  import { resolveHarnessPaths, safeAdoptionCapability, taskIdFromDirectory, taskLocalWalkthrough, } from "./harness-paths.mjs";
12
+ import { isExcludedTaskPlanPath } from "./task-discovery-contract.mjs";
13
+ function asLessonCandidateStatus(value) {
14
+ const candidate = isRecord(value) ? value : {};
15
+ const rows = Array.isArray(candidate.rows) ? candidate.rows.filter(isRecord).map((row) => Object.fromEntries(Object.entries(row).map(([key, item]) => [key, String(item || "")]))) : [];
16
+ const issues = Array.isArray(candidate.issues) ? candidate.issues.map((item) => String(item)) : [];
17
+ return {
18
+ status: String(candidate.status || ""),
19
+ declaredStatus: candidate.declaredStatus === undefined ? undefined : String(candidate.declaredStatus || ""),
20
+ schemaVersion: candidate.schemaVersion === undefined ? undefined : String(candidate.schemaVersion || ""),
21
+ reviewDecision: String(candidate.reviewDecision || ""),
22
+ promotionState: String(candidate.promotionState || ""),
23
+ closeoutToken: String(candidate.closeoutToken || ""),
24
+ rows,
25
+ openCount: Number(candidate.openCount || 0),
26
+ issues,
27
+ };
28
+ }
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null;
31
+ }
32
+ function nestedRecord(value, key) {
33
+ if (!isRecord(value))
34
+ return {};
35
+ const nested = value[key];
36
+ return isRecord(nested) ? nested : {};
37
+ }
12
38
  export { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskState, parseTaskStateInfo, } from "./task-metadata.mjs";
13
39
  export { collectReviewRisks, deriveLifecycleState, deriveReviewQueueState, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
14
40
  export { parseTaskAuditMetadata, } from "./task-audit-metadata.mjs";
@@ -73,17 +99,16 @@ export function isActiveTaskState(state) {
73
99
  return ["active", "planned", "not_started", "in_progress", "review", "blocked", "reopened", "current-evidence"].includes(state);
74
100
  }
75
101
  export function listTaskPlanPaths(target) {
76
- const harnessPaths = target.harness || resolveHarnessPaths(target);
102
+ const harnessPaths = (target.harness || resolveHarnessPaths(target));
77
103
  const taskRoots = harnessPaths.taskRoots;
78
104
  return taskRoots
79
- .flatMap(walkFiles)
105
+ .flatMap((root) => walkFiles(root))
80
106
  .filter((file) => file.endsWith("task_plan.md"))
81
- .filter((file) => !file.includes(`${path.sep}_task-template${path.sep}`))
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: requiresReviewMaterials({
231
- state: stateInfo.state,
232
- lifecycleState,
233
- closeoutStatus: effectiveCloseoutStatus,
234
- }),
266
+ reviewSurfaceRequired,
235
267
  });
268
+ const templateMaterialIssues = reviewSurfaceRequired
269
+ ? collectUneditedTemplateMaterialIssues(target, taskDir, {
270
+ briefContent: brief.content,
271
+ taskPlanContent: taskPlan,
272
+ executionStrategyContent: readFileSafe(executionStrategyPath),
273
+ visualMapContent: visualMap.content,
274
+ progressContent: progress,
275
+ findingsContent: readFileSafe(findingsPath),
276
+ reviewContent: review,
277
+ lessonCandidatesContent: readFileSafe(lessonCandidatesPath),
278
+ walkthroughPath: closeoutInfo.walkthroughPath,
279
+ humanReviewConfirmed: taskAudit.summary.humanReviewStatus === "confirmed",
280
+ })
281
+ : [];
236
282
  const materialIssues = [
237
283
  ...materialReadiness.issues,
284
+ ...templateMaterialIssues,
238
285
  ...taskAuditMaterialIssues(target, taskDir, taskAudit),
239
286
  ...legacyAuditIssues(target, taskDir, { briefContent: brief.content, reviewContent: review }),
240
287
  ];
@@ -368,17 +415,21 @@ function collectMigrationSnapshot(target, metadata) {
368
415
  const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
369
416
  const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
370
417
  const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
371
- const summary = session?.plan?.summary || {};
418
+ const plan = nestedRecord(session, "plan");
419
+ const checks = nestedRecord(session, "checks");
420
+ const normal = nestedRecord(checks, "normal");
421
+ const strict = nestedRecord(checks, "strict");
422
+ const summary = nestedRecord(plan, "summary");
372
423
  return {
373
424
  targetLevel: metadata.migrationTargetLevel || "",
374
425
  achievedLevel: metadata.migrationAchievedLevel || "",
375
426
  evidenceBundle: evidenceBundle ? `TARGET:${evidenceBundle}` : "",
376
427
  evidencePresent: Boolean(bundlePath && fs.existsSync(bundlePath)),
377
428
  sessionPresent: Boolean(session),
378
- sessionResult: session?.result || "",
379
- normalStatus: session?.checks?.normal?.status || "",
380
- strictStatus: session?.checks?.strict?.status || "",
381
- strictDeferred: Boolean(session?.strictDeferred),
429
+ sessionResult: isRecord(session) ? String(session.result || "") : "",
430
+ normalStatus: String(normal.status || ""),
431
+ strictStatus: String(strict.status || ""),
432
+ strictDeferred: isRecord(session) ? Boolean(session.strictDeferred) : false,
382
433
  warnings: Number(summary.warnings || 0),
383
434
  taskActions: Number(summary.taskActions || 0),
384
435
  reviewSchemaGaps: Number(summary.reviewSchemaGaps || 0),
@@ -392,7 +443,7 @@ function formatEvidenceBundle(value) {
392
443
  return normalized ? `TARGET:${normalized}` : "";
393
444
  }
394
445
  function taskCloseoutInfo(target, taskPlanPath, closeout) {
395
- const localWalkthrough = taskLocalWalkthrough(target.harness || resolveHarnessPaths(target), path.dirname(taskPlanPath));
446
+ const localWalkthrough = taskLocalWalkthrough((target.harness || resolveHarnessPaths(target)), path.dirname(taskPlanPath));
396
447
  if (localWalkthrough) {
397
448
  const content = readFileSafe(path.join(target.projectRoot, localWalkthrough));
398
449
  const status = /^Closeout Status\s*:\s*(closed|complete|completed|done|已关闭|已完成)\s*$/im.test(content)
@@ -402,7 +453,7 @@ function taskCloseoutInfo(target, taskPlanPath, closeout) {
402
453
  }
403
454
  if (!closeout.trim())
404
455
  return { status: "missing", walkthroughPath: "" };
405
- const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
456
+ const docsRelative = `docs/${toPosix(path.relative(target.docsRoot || path.join(target.projectRoot, "docs"), taskPlanPath))}`;
406
457
  const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
407
458
  const line = closeout
408
459
  .split(/\r?\n/)
@@ -0,0 +1,131 @@
1
+ import path from "node:path";
2
+ import { lessonCandidatesFile, readFileSafe, toPosix, visualMapFile, } from "./core-shared.mjs";
3
+ export function collectUneditedTemplateMaterialIssues(target, taskDir, materials) {
4
+ const issues = [];
5
+ const files = [
6
+ { label: "brief.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "brief.md"))), content: materials.briefContent },
7
+ { label: "task_plan.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "task_plan.md"))), content: materials.taskPlanContent },
8
+ { label: "execution_strategy.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "execution_strategy.md"))), content: materials.executionStrategyContent },
9
+ { label: visualMapFile, sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, visualMapFile))), content: materials.visualMapContent },
10
+ { label: "progress.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "progress.md"))), content: materials.progressContent },
11
+ { label: "findings.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "findings.md"))), content: materials.findingsContent },
12
+ { label: "review.md", sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, "review.md"))), content: materials.reviewContent },
13
+ { label: lessonCandidatesFile, sourcePath: toPosix(path.relative(target.projectRoot, path.join(taskDir, lessonCandidatesFile))), content: materials.lessonCandidatesContent },
14
+ ];
15
+ if (materials.walkthroughPath) {
16
+ files.push({
17
+ label: path.basename(materials.walkthroughPath),
18
+ sourcePath: materials.walkthroughPath,
19
+ content: readFileSafe(path.join(target.projectRoot, materials.walkthroughPath)),
20
+ });
21
+ }
22
+ for (const file of files) {
23
+ const markers = uneditedCoreTemplateSlots(file.label, file.content);
24
+ if (markers.length === 0)
25
+ continue;
26
+ issues.push({
27
+ code: "unedited-template-material",
28
+ severity: "P2",
29
+ queue: "missing-materials",
30
+ sourcePath: `TARGET:${file.sourcePath}`,
31
+ sourceLine: 0,
32
+ owner: "agent",
33
+ message: `[unedited-template-material] Reviewable task material still contains default core content in ${file.label}: ${markers.slice(0, 3).join(", ")}.`,
34
+ allowedWritePaths: [`${toPosix(path.relative(target.projectRoot, taskDir))}/**`],
35
+ forbiddenActions: ["human-confirm", "edit-unrelated-task", "fabricate-evidence"],
36
+ validationCommands: ["node dist/harness.mjs check --profile target-project <target>"],
37
+ confidence: "high",
38
+ repairable: true,
39
+ enforceFailure: materials.humanReviewConfirmed,
40
+ });
41
+ }
42
+ return issues;
43
+ }
44
+ function uneditedCoreTemplateSlots(label, content) {
45
+ const text = String(content || "");
46
+ const markers = [];
47
+ if (label === "brief.md") {
48
+ if (sectionHasDefault(text, ["Outcome Statement", "一句话结果"], [
49
+ "One sentence stating the concrete result this task must produce.",
50
+ "用一句话说明这个任务完成后会产生什么具体结果。",
51
+ "说明这个任务完成后,用户或项目能直接看到的结果。",
52
+ ]))
53
+ markers.push("brief-outcome");
54
+ }
55
+ else if (label === "task_plan.md") {
56
+ if (sectionHasDefault(text, ["Goal", "目标"], [
57
+ "[State the outcome this task must deliver in one sentence.]",
58
+ "[用一句话说明本任务完成后应达到的状态。]",
59
+ ]))
60
+ markers.push("task-plan-goal");
61
+ }
62
+ else if (label === "execution_strategy.md") {
63
+ if (sectionHasDefault(text, ["Strategy Summary"], ["[Describe the execution approach, including why this operating model fits the risk and scope.]"]) ||
64
+ /\|\s*L0\s*\|\s*\[静态检查\s*\/\s*小范围自检\]\s*\|\s*`?progress\.md`?\s*\|\s*\[通过标准\]\s*\|/i.test(text)) {
65
+ markers.push("execution-strategy-core-plan");
66
+ }
67
+ }
68
+ else if (label === visualMapFile) {
69
+ if (hasDefaultExecutionPhase(text))
70
+ markers.push("visual-map-execution-phase");
71
+ }
72
+ else if (label === "progress.md") {
73
+ if (/^\|\s*YYYY-MM-DD HH:MM\s*\|\s*coordinator\s*\|\s*\[action taken\]\s*\|/im.test(text) ||
74
+ /^###\s*\[YYYY-MM-DD HH:MM\]\s*-\s*\[阶段名称\]\s*$/im.test(text) ||
75
+ /^-\s*做了什么:\[具体操作\]\s*$/im.test(text)) {
76
+ markers.push("progress-log-entry");
77
+ }
78
+ }
79
+ else if (label === "review.md") {
80
+ if (/\|\s*Evidence Summary\s*\|\s*\[(?:tests, diff, runtime, and review packet evidence|测试、diff、运行和审查材料证据)\]\s*\|/i.test(text) ||
81
+ /\|\s*E-001\s*\|[^|\n]*\|[^|\n]*\|\s*\[(?:what was checked and what it showed|检查了什么,结论是什么)\]\s*\|/i.test(text)) {
82
+ markers.push("review-evidence");
83
+ }
84
+ }
85
+ else if (label === lessonCandidatesFile) {
86
+ if (/\|\s*Task-level status\s*\|\s*no-candidate-accepted\s*\|/i.test(text) &&
87
+ /^(?:Not decided yet\. Fill this only when review accepts that the task produced no reusable lesson candidate\.|尚未判定。只有人工审查接受本任务没有可复用候选时,才填写这里。)\s*$/im.test(text)) {
88
+ markers.push("lesson-no-candidate-reason");
89
+ }
90
+ }
91
+ else if (label === "walkthrough.md") {
92
+ if (sectionHasDefault(text, ["Summary", "摘要"], ["Pending closeout.", "待收口。"]))
93
+ markers.push("walkthrough-summary");
94
+ }
95
+ return markers;
96
+ }
97
+ function sectionHasDefault(text, headings, defaults) {
98
+ const body = sectionBody(text, headings);
99
+ if (!body)
100
+ return false;
101
+ return defaults.some((value) => body === value || body.split(/\r?\n/).map((line) => line.trim()).includes(value));
102
+ }
103
+ function sectionBody(text, headings) {
104
+ const lines = text.split(/\r?\n/);
105
+ for (let index = 0; index < lines.length; index += 1) {
106
+ const match = lines[index].match(/^(#{2,6})\s+(.+?)\s*$/);
107
+ if (!match)
108
+ continue;
109
+ if (!headings.includes(match[2].trim()))
110
+ continue;
111
+ const level = match[1].length;
112
+ const body = [];
113
+ for (let bodyIndex = index + 1; bodyIndex < lines.length; bodyIndex += 1) {
114
+ const nextHeading = lines[bodyIndex].match(/^(#{2,6})\s+/);
115
+ if (nextHeading && nextHeading[1].length <= level)
116
+ break;
117
+ if (lines[bodyIndex].trim())
118
+ body.push(lines[bodyIndex].trim());
119
+ }
120
+ return body.join("\n").trim();
121
+ }
122
+ return "";
123
+ }
124
+ function hasDefaultExecutionPhase(text) {
125
+ const rows = text.split(/\r?\n/).filter((line) => /^\|\s*EXEC-01\s*\|/.test(line));
126
+ return rows.some((row) => {
127
+ const cells = row.split("|").map((cell) => cell.trim()).filter(Boolean);
128
+ return cells.some((cell) => cell === "Scoped implementation, document update, and verification evidence" || cell === "有边界的实现、文档切片和验证证据") &&
129
+ cells.some((cell) => cell === "diff, commands, worker handoff, or artifact path" || cell === "diff、commands、worker handoff 或 artifact path");
130
+ });
131
+ }