coding-agent-harness 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +9 -5
  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 +32 -6
  7. package/dist/check-dist-observation.mjs +73 -28
  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 +88 -0
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +67 -8
  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 +65 -4
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +111 -53
  20. package/dist/harness.mjs +6 -303
  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 +5 -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 +68 -29
  44. package/dist/lib/preset-registry.mjs +374 -72
  45. package/dist/lib/preset-runner.mjs +560 -0
  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 +4 -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 +117 -159
  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 +38 -17
  70. package/dist/lib/task-scanner.mjs +75 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +187 -18
  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 +2 -1
  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 +19 -11
  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 +29 -0
  120. package/presets/release-closeout/preset.yaml +100 -0
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
  122. package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
  123. package/presets/release-closeout/templates/findings.seed.md +5 -0
  124. package/presets/release-closeout/templates/review.seed.md +3 -0
  125. package/presets/release-closeout/templates/task_plan.append.md +24 -0
  126. package/presets/standard-task/preset.yaml +2 -2
  127. package/references/agents-md-pattern.md +23 -17
  128. package/references/lessons-governance.md +2 -2
  129. package/references/module-parallel-standard.md +3 -6
  130. package/references/pull-request-standard.md +2 -2
  131. package/references/ssot-governance.md +2 -2
  132. package/references/taskr-gap-analysis.md +3 -3
  133. package/run-dist.mjs +34 -0
  134. package/skills/preset-creator/SKILL.md +40 -8
  135. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  136. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  137. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  138. package/templates/AGENTS.md.template +28 -26
  139. package/templates/architecture/README.md +2 -2
  140. package/templates/architecture/service-catalog.md +2 -2
  141. package/templates/architecture/services/service-template.md +1 -1
  142. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  143. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  144. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  145. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  146. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  148. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  149. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  150. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  151. package/templates/dashboard/assets/app.css +928 -53
  152. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  153. package/templates/dashboard/assets/app.js +1071 -98
  154. package/templates/dashboard/assets/app.manifest.json +1 -0
  155. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  156. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  157. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  158. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  159. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  160. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  161. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  162. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  163. package/templates/dashboard/assets/i18n.js +166 -2
  164. package/templates/development/README.md +9 -9
  165. package/templates/development/cross-repo-debugging.md +3 -3
  166. package/templates/development/external-context/service-template.md +1 -1
  167. package/templates/development/external-source-packs/README.md +2 -2
  168. package/templates/integrations/README.md +4 -4
  169. package/templates/integrations/api-contract.md +1 -1
  170. package/templates/integrations/event-contract.md +1 -1
  171. package/templates/integrations/third-party/vendor-template.md +1 -1
  172. package/templates/integrations/webhook-contract.md +1 -1
  173. package/templates/ledger/Harness-Ledger.md +1 -1
  174. package/templates/modules/module_brief.md +50 -0
  175. package/templates/modules/module_plan.md +49 -0
  176. package/templates/modules/registry_view.md +9 -0
  177. package/templates/modules/session_prompt_pack.md +55 -0
  178. package/templates/planning/brief.md +32 -8
  179. package/templates/planning/module_brief.md +28 -3
  180. package/templates/planning/module_plan.md +26 -11
  181. package/templates/planning/module_session_prompt.md +11 -2
  182. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  183. package/templates/planning/review.md +1 -1
  184. package/templates/planning/visual_map.md +1 -1
  185. package/templates/reference/docs-library-standard.md +7 -7
  186. package/templates/reference/execution-workflow-standard.md +13 -0
  187. package/templates/reference/external-source-intake-standard.md +10 -10
  188. package/templates/reference/pull-request-standard.md +2 -2
  189. package/templates/reference/repo-governance-standard.md +1 -1
  190. package/templates/reference/review-routing-standard.md +4 -0
  191. package/templates/ssot/Module-Registry.md +4 -38
  192. package/templates/walkthrough/walkthrough-template.md +1 -1
  193. package/templates-zh-CN/AGENTS.md.template +27 -25
  194. package/templates-zh-CN/CLAUDE.md.template +1 -1
  195. package/templates-zh-CN/architecture/README.md +2 -2
  196. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  197. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  198. package/templates-zh-CN/development/README.md +9 -9
  199. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  200. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  201. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  202. package/templates-zh-CN/integrations/README.md +4 -4
  203. package/templates-zh-CN/integrations/api-contract.md +1 -1
  204. package/templates-zh-CN/integrations/event-contract.md +1 -1
  205. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  206. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  207. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  208. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  209. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  210. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  211. package/templates-zh-CN/modules/module_brief.md +47 -0
  212. package/templates-zh-CN/modules/module_plan.md +48 -0
  213. package/templates-zh-CN/modules/registry_view.md +9 -0
  214. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  215. package/templates-zh-CN/planning/INDEX.md +1 -0
  216. package/templates-zh-CN/planning/brief.md +26 -7
  217. package/templates-zh-CN/planning/module_brief.md +24 -2
  218. package/templates-zh-CN/planning/module_plan.md +35 -29
  219. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  220. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  221. package/templates-zh-CN/planning/review.md +1 -1
  222. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  223. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  224. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  225. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  226. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  227. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  228. package/templates-zh-CN/reference/pull-request-standard.md +1 -1
  229. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  230. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  231. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  232. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  233. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  234. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  235. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  236. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  237. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  238. 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 {
@@ -58,6 +62,7 @@ export function parseTaskTombstone(taskPlanContent) {
58
62
  supersededBy: "",
59
63
  supersedes: topLevelSupersedes,
60
64
  deleteReason: "",
65
+ archiveMetadata: {},
61
66
  hiddenByDefault: false,
62
67
  reopenEligible: false,
63
68
  archiveEligible: false,
@@ -65,12 +70,16 @@ export function parseTaskTombstone(taskPlanContent) {
65
70
  };
66
71
  }
67
72
  const state = normalizeMetadataValue(fields.get("state") || fields.get("状态") || "soft-deleted", "soft-deleted");
68
- 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;
69
77
  return {
70
78
  deletionState,
71
79
  supersededBy: fields.get("superseded by") || fields.get("替代任务") || "",
72
80
  supersedes: [...new Set([...topLevelSupersedes, ...splitList(fields.get("supersedes") || fields.get("合并自") || "")])],
73
81
  deleteReason: fields.get("reason") || fields.get("原因") || "",
82
+ archiveMetadata: Object.fromEntries([...fields.entries()].filter(([key]) => !["state", "状态", "superseded by", "替代任务", "supersedes", "合并自", "reason", "原因", "reopen eligible", "可重新打开", "archive eligible", "可归档"].includes(key))),
74
83
  hiddenByDefault: true,
75
84
  reopenEligible: parseTombstoneBooleanLike(fields.get("reopen eligible") || fields.get("可重新打开")),
76
85
  archiveEligible: parseTombstoneBooleanLike(fields.get("archive eligible") || fields.get("可归档")),
@@ -108,7 +117,7 @@ export function parseAgentReviewSubmission(reviewContent, { taskKey = "" } = {})
108
117
  scannerVersion: fields.get("scanner version") || "",
109
118
  };
110
119
  }
111
- export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true }) {
120
+ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, reviewSubmission, lessonCandidates, phases, longRunningContractPath, reviewSurfaceRequired = true, }) {
112
121
  const issues = [];
113
122
  const addIssue = (code, message, sourcePath, extra = {}) => {
114
123
  issues.push({
@@ -165,10 +174,13 @@ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, re
165
174
  export function requiresReviewMaterials({ state = "unknown", lifecycleState = "unknown", closeoutStatus = "missing" } = {}) {
166
175
  return (state === "review" ||
167
176
  state === "done" ||
168
- ["in_review", "review-blocked", "closing", "closed-review-pending"].includes(lifecycleState) ||
177
+ ["in_review", "review-blocked", "closing", "closed-review-pending", "confirmed-finalization-pending", "lesson-finalization-pending"].includes(lifecycleState) ||
169
178
  closeoutStatus === "closed");
170
179
  }
171
- export function 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, }) {
172
184
  const queueReasons = [];
173
185
  const pushReason = (reason) => {
174
186
  queueReasons.push({
@@ -224,7 +236,7 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
224
236
  message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
225
237
  });
226
238
  }
227
- const hasLessonWork = lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || lessonCandidates?.openCount > 0;
239
+ const hasLessonWork = hasPendingLessonWork(lessonCandidates);
228
240
  const taskQueues = [];
229
241
  if (tombstone.deletionState !== "active") {
230
242
  taskQueues.push("soft-deleted-superseded");
@@ -241,13 +253,21 @@ export function deriveTaskQueues({ id, title, state, budget, reviewStatus, revie
241
253
  taskQueues.push("lessons");
242
254
  if (budget === "simple" && state === "done" && closeoutStatus === "closed")
243
255
  taskQueues.push("finalized");
244
- if (reviewStatus === "confirmed")
245
- 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
+ }
246
265
  }
247
- if (taskQueues.length === 0)
248
- taskQueues.push("active");
266
+ const normalizedTaskQueues = [...new Set(taskQueues)];
267
+ if (normalizedTaskQueues.length === 0)
268
+ normalizedTaskQueues.push("active");
249
269
  return {
250
- taskQueues,
270
+ taskQueues: normalizedTaskQueues,
251
271
  queueReasons,
252
272
  repairPrompt: renderRepairPrompt({ id, title, taskDir, target, reasons: queueReasons }),
253
273
  };
@@ -265,7 +285,6 @@ export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit
265
285
  if (taskAudit) {
266
286
  const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
267
287
  if (confirmation?.confirmed &&
268
- confirmation.auditSource !== "migrated-legacy-review" &&
269
288
  projectRoot &&
270
289
  (indexPath || taskDir) &&
271
290
  confirmation.commitSha) {
@@ -312,9 +331,9 @@ function hasAgentReviewSignal(reviewContent) {
312
331
  return /本轮已检查|未发现阻塞目标的重要发现/.test(content);
313
332
  }
314
333
  export function isBlockingReviewRisk(risk) {
315
- return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
334
+ return /^P[0-2]$/i.test(risk?.severity || "") && Boolean(risk?.open || risk?.blocksRelease);
316
335
  }
317
- export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard" } = {}) {
336
+ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard", lessonCandidates = null } = {}) {
318
337
  if (reviewStatus === "blocked-open-findings")
319
338
  return "review-blocked";
320
339
  if (budget === "simple" && closeoutStatus === "closed")
@@ -323,6 +342,8 @@ export function deriveLifecycleState({ state = "unknown", reviewStatus = "missin
323
342
  return "closed-review-pending";
324
343
  if (closeoutStatus === "closed")
325
344
  return "closed";
345
+ if (reviewStatus === "confirmed")
346
+ return hasPendingLessonWork(lessonCandidates) ? "lesson-finalization-pending" : "confirmed-finalization-pending";
326
347
  if (state === "blocked")
327
348
  return "blocked";
328
349
  if (state === "done")
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, longRunningTaskContractFile, toPosix, readFileSafe, readJsonSafe, walkFiles, isArchivedHarnessPath, titleFromMarkdown, } from "./core-shared.mjs";
@@ -7,8 +6,35 @@ import { normalizePhaseActor, normalizePhaseKind, phaseCompletionAverage, } from
7
6
  import { legacyAuditIssues, parseTaskAuditMetadata, scaffoldProvenanceSummaryFromTaskAudit, taskAuditMaterialIssues, } from "./task-audit-metadata.mjs";
8
7
  import { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskStateInfo, } from "./task-metadata.mjs";
9
8
  import { isLessonCandidateDecisionComplete, parseLessonCandidateStatus, validateLessonCandidateDetailArtifacts, } from "./task-lesson-candidates.mjs";
9
+ import { collectUneditedTemplateMaterialIssues } from "./task-template-materials.mjs";
10
10
  import { assessMaterialsReadiness, collectReviewRisks, collectStateConflicts, deriveLifecycleState, deriveReviewQueueState, deriveTaskQueues, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
11
11
  import { resolveHarnessPaths, safeAdoptionCapability, taskIdFromDirectory, taskLocalWalkthrough, } from "./harness-paths.mjs";
12
+ import { isExcludedTaskPlanPath } from "./task-discovery-contract.mjs";
13
+ function asLessonCandidateStatus(value) {
14
+ const candidate = isRecord(value) ? value : {};
15
+ const rows = Array.isArray(candidate.rows) ? candidate.rows.filter(isRecord).map((row) => Object.fromEntries(Object.entries(row).map(([key, item]) => [key, String(item || "")]))) : [];
16
+ const issues = Array.isArray(candidate.issues) ? candidate.issues.map((item) => String(item)) : [];
17
+ return {
18
+ status: String(candidate.status || ""),
19
+ declaredStatus: candidate.declaredStatus === undefined ? undefined : String(candidate.declaredStatus || ""),
20
+ schemaVersion: candidate.schemaVersion === undefined ? undefined : String(candidate.schemaVersion || ""),
21
+ reviewDecision: String(candidate.reviewDecision || ""),
22
+ promotionState: String(candidate.promotionState || ""),
23
+ closeoutToken: String(candidate.closeoutToken || ""),
24
+ rows,
25
+ openCount: Number(candidate.openCount || 0),
26
+ issues,
27
+ };
28
+ }
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null;
31
+ }
32
+ function nestedRecord(value, key) {
33
+ if (!isRecord(value))
34
+ return {};
35
+ const nested = value[key];
36
+ return isRecord(nested) ? nested : {};
37
+ }
12
38
  export { parseTaskBudget, parseTaskContractInfo, parseTaskMetadata, parseTaskState, parseTaskStateInfo, } from "./task-metadata.mjs";
13
39
  export { collectReviewRisks, deriveLifecycleState, deriveReviewQueueState, isBlockingReviewRisk, parseAgentReviewSubmission, parseReviewConfirmation, parseTaskIdentity, parseTaskTombstone, requiresReviewMaterials, taskReviewStatus, taskScannerVersion, } from "./task-review-model.mjs";
14
40
  export { parseTaskAuditMetadata, } from "./task-audit-metadata.mjs";
@@ -73,17 +99,16 @@ export function isActiveTaskState(state) {
73
99
  return ["active", "planned", "not_started", "in_progress", "review", "blocked", "reopened", "current-evidence"].includes(state);
74
100
  }
75
101
  export function listTaskPlanPaths(target) {
76
- const harnessPaths = target.harness || resolveHarnessPaths(target);
102
+ const harnessPaths = (target.harness || resolveHarnessPaths(target));
77
103
  const taskRoots = harnessPaths.taskRoots;
78
104
  return taskRoots
79
- .flatMap(walkFiles)
105
+ .flatMap((root) => walkFiles(root))
80
106
  .filter((file) => file.endsWith("task_plan.md"))
81
- .filter((file) => !file.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
  ];
@@ -344,6 +391,7 @@ export function collectTasks(target, { requireGeneratedScaffoldProvenance = fals
344
391
  supersededBy: tombstone.supersededBy,
345
392
  supersedes: tombstone.supersedes,
346
393
  deleteReason: tombstone.deleteReason,
394
+ archiveMetadata: tombstone.archiveMetadata || {},
347
395
  hiddenByDefault: tombstone.hiddenByDefault,
348
396
  reopenEligible: tombstone.reopenEligible,
349
397
  archiveEligible: tombstone.archiveEligible,
@@ -367,17 +415,21 @@ function collectMigrationSnapshot(target, metadata) {
367
415
  const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
368
416
  const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
369
417
  const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
370
- const 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");
371
423
  return {
372
424
  targetLevel: metadata.migrationTargetLevel || "",
373
425
  achievedLevel: metadata.migrationAchievedLevel || "",
374
426
  evidenceBundle: evidenceBundle ? `TARGET:${evidenceBundle}` : "",
375
427
  evidencePresent: Boolean(bundlePath && fs.existsSync(bundlePath)),
376
428
  sessionPresent: Boolean(session),
377
- sessionResult: session?.result || "",
378
- normalStatus: session?.checks?.normal?.status || "",
379
- strictStatus: session?.checks?.strict?.status || "",
380
- 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,
381
433
  warnings: Number(summary.warnings || 0),
382
434
  taskActions: Number(summary.taskActions || 0),
383
435
  reviewSchemaGaps: Number(summary.reviewSchemaGaps || 0),
@@ -391,7 +443,7 @@ function formatEvidenceBundle(value) {
391
443
  return normalized ? `TARGET:${normalized}` : "";
392
444
  }
393
445
  function taskCloseoutInfo(target, taskPlanPath, closeout) {
394
- const localWalkthrough = taskLocalWalkthrough(target.harness || resolveHarnessPaths(target), path.dirname(taskPlanPath));
446
+ const localWalkthrough = taskLocalWalkthrough((target.harness || resolveHarnessPaths(target)), path.dirname(taskPlanPath));
395
447
  if (localWalkthrough) {
396
448
  const content = readFileSafe(path.join(target.projectRoot, localWalkthrough));
397
449
  const status = /^Closeout Status\s*:\s*(closed|complete|completed|done|已关闭|已完成)\s*$/im.test(content)
@@ -401,7 +453,7 @@ function taskCloseoutInfo(target, taskPlanPath, closeout) {
401
453
  }
402
454
  if (!closeout.trim())
403
455
  return { status: "missing", walkthroughPath: "" };
404
- const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
456
+ const docsRelative = `docs/${toPosix(path.relative(target.docsRoot || path.join(target.projectRoot, "docs"), taskPlanPath))}`;
405
457
  const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
406
458
  const line = closeout
407
459
  .split(/\r?\n/)