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
@@ -1,24 +1,38 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import crypto from "node:crypto";
5
- import { visualMapFile, legacyVisualRoadmapFile, lessonCandidatesFile, allowedTaskStates, allowedTaskBudgets, allowedPhaseStates, allowedEvidenceStatus, normalizeTarget, normalizeLocale, toPosix, readFileSafe, readBundledTemplate, todayDate, localDate, datePrefix, normalizeTaskId, renderTaskTemplate, } from "./core-shared.mjs";
4
+ import { visualMapFile, legacyVisualRoadmapFile, allowedTaskStates, allowedTaskBudgets, allowedPhaseStates, allowedEvidenceStatus, normalizeTarget, normalizeLocale, toPosix, readFileSafe, readBundledTemplate, todayDate, localDate, datePrefix, normalizeTaskId, renderTaskTemplate } from "./core-shared.mjs";
6
5
  import { readCapabilityRegistry } from "./capability-registry.mjs";
7
6
  import { readPresetPackage } from "./preset-registry.mjs";
8
- import { assertPresetWriteScope, buildPresetContext, evaluateTemplateValues, resolvePresetInputs, renderPresetResourceIndex, renderPresetTaskTemplate, } from "./preset-engine.mjs";
9
- import { collectTasks, listTaskPlanPaths, parseTaskBudget, taskIdForDirectory, } from "./task-scanner.mjs";
7
+ import { renderPresetResourceIndex } from "./preset-engine.mjs";
8
+ import { parseTaskBudget } from "./task-metadata.mjs";
9
+ import { createScannerTaskRepository } from "./task-repository.mjs";
10
10
  import { getColumn, firstColumn, updateMarkdownTableRow } from "./markdown-utils.mjs";
11
11
  import { validateLifecycleTransition, validateReviewEntryGate } from "./task-lifecycle/review-gates.mjs";
12
12
  import { advanceLifecyclePhase, autoRecordNoLessonCandidateDecision } from "./task-lifecycle/phase-sync.mjs";
13
- import { confirmTaskReview as confirmTaskReviewWithContext } from "./task-lifecycle/review-confirm.mjs";
13
+ import { confirmTaskReview as confirmTaskReviewWithContext, finalizeDeferredTaskReviewConfirmation as finalizeDeferredTaskReviewConfirmationWithContext } from "./task-lifecycle/review-confirm.mjs";
14
14
  import { appendProgressLog, markWalkthroughClosed } from "./task-lifecycle/text-utils.mjs";
15
15
  import { buildScaffoldProvenance } from "./task-lifecycle/scaffold-provenance.mjs";
16
16
  import { buildCreationTaskAudit } from "./task-audit-metadata.mjs";
17
- import { renderAgentReviewSubmission, replaceAgentReviewSubmission, } from "./task-lifecycle/review-submission.mjs";
18
- import { appendLongRunningContractFile, moduleTemplateFiles, taskFilesForBudget, } from "./task-lifecycle/template-files.mjs";
19
- import { planCreateTaskChanges, refreshPresetCommandAudit, resolveImplicitCreateTarget, } from "./task-lifecycle/create-task-helpers.mjs";
20
- import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync, syncModuleStepGovernance, syncTaskGovernance, } from "./governance-sync.mjs";
21
- import { taskRefPath } from "./harness-paths.mjs";
17
+ import { renderAgentReviewSubmission, replaceAgentReviewSubmission } from "./task-lifecycle/review-submission.mjs";
18
+ import { appendLongRunningContractFile, moduleTemplateFiles, taskFilesForBudget } from "./task-lifecycle/template-files.mjs";
19
+ import { planCreateTaskChanges, refreshPresetCommandAudit, resolveImplicitCreateTarget } from "./task-lifecycle/create-task-helpers.mjs";
20
+ import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync, syncModuleStepGovernance, syncTaskGovernance } from "./governance-sync.mjs";
21
+ import { normalizeHarnessModuleKey, prepareModuleRegistration, prepareModuleStepRegistrationUpdate, readHarnessModules, registeredHarnessModule } from "./module-registry.mjs";
22
+ import { assertLifecyclePresetWriteScope, buildLifecyclePresetContext, evaluatePresetValues, renderLifecyclePresetTaskTemplate, resolveLifecyclePresetInputs } from "./task-lifecycle/preset-interop.mjs";
23
+ import { taskIdFromDirectory } from "./harness-paths.mjs";
24
+ function asLifecycleTarget(target) {
25
+ return target;
26
+ }
27
+ function changeDestinations(changes) {
28
+ return changes.map((change) => change.destination).filter(Boolean);
29
+ }
30
+ function lifecycleGateEvent(event) {
31
+ return String(event || "task-log");
32
+ }
33
+ function findReviewTaskByDirectory(target, taskDir) {
34
+ return findTaskByDirectory(asLifecycleTarget(normalizeTarget(target.projectRoot)), taskDir);
35
+ }
22
36
  function taskRoot(target, taskId, { moduleKey = "" } = {}) {
23
37
  const normalizedTaskId = normalizeTaskId(taskId);
24
38
  if (moduleKey) {
@@ -30,59 +44,18 @@ function taskRoot(target, taskId, { moduleKey = "" } = {}) {
30
44
  return path.join(target.harness.tasksRoot, normalizedTaskId);
31
45
  }
32
46
  export function resolveTaskDirectory(target, taskRef) {
33
- const raw = String(taskRef || "")
34
- .replace(/^coding-agent-harness\/planning\//, "")
35
- .replace(/^planning\//, "")
36
- .replace(new RegExp(`^${legacyPlanningPrefix()}\\/`), "")
37
- .replace(/^\/+/, "");
38
- if (!raw)
39
- throw new Error("Missing task id");
40
- const direct = directTaskRefPath(target, raw);
41
- if (direct && fs.existsSync(path.join(direct, "task_plan.md")))
42
- return direct;
43
- const normalized = normalizeTaskId(raw);
44
- const candidates = listTaskPlanPaths(target)
45
- .map((taskPlanPath) => path.dirname(taskPlanPath))
46
- .filter((taskDir) => {
47
- const id = taskIdForDirectory(target, taskDir);
48
- const dirName = path.basename(taskDir);
49
- return id === raw || id.endsWith(`/${raw}`) || dirName === normalized;
50
- });
51
- if (candidates.length === 1)
52
- return candidates[0];
53
- if (candidates.length > 1) {
54
- const options = candidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
55
- throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
56
- }
57
- // Try bare slug resolution: match normalized slug against dated directories
58
- if (!datePrefix.test(normalized)) {
59
- const datedCandidates = listTaskPlanPaths(target)
60
- .map((taskPlanPath) => path.dirname(taskPlanPath))
61
- .filter((taskDir) => {
62
- const dirName = path.basename(taskDir);
63
- return datePrefix.test(dirName) && dirName.replace(datePrefix, "") === normalized;
64
- });
65
- if (datedCandidates.length === 1)
66
- return datedCandidates[0];
67
- if (datedCandidates.length > 1) {
68
- const options = datedCandidates.map((taskDir) => `- ${taskIdForDirectory(target, taskDir)}`).join("\n");
69
- throw new Error(`Ambiguous task reference: ${taskRef}\n${options}`);
70
- }
71
- }
72
- const legacy = taskRoot(target, normalized);
73
- if (fs.existsSync(path.join(legacy, "task_plan.md")))
74
- return legacy;
75
- throw new Error(`Task not found: ${taskRef}`);
76
- }
77
- function directTaskRefPath(target, raw) {
78
- return taskRefPath(target.harness, raw);
47
+ return createScannerTaskRepository(target).resolve({ id: taskRef }).directory;
79
48
  }
80
- function legacyPlanningPrefix() {
81
- return "docs\\/09-PLANNING";
49
+ function taskIdForDirectory(target, taskDir) {
50
+ return taskIdFromDirectory(target.harness, taskDir);
82
51
  }
83
52
  function findTaskByDirectory(target, taskDir) {
84
- const id = taskIdForDirectory(target, taskDir);
85
- return collectTasks(target).find((task) => task.id === id) || null;
53
+ try {
54
+ return createScannerTaskRepository(target).get({ path: taskDir });
55
+ }
56
+ catch {
57
+ return undefined;
58
+ }
86
59
  }
87
60
  function stateLabel(state, locale) {
88
61
  if (normalizeLocale(locale) !== "zh-CN")
@@ -150,13 +123,13 @@ function resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey,
150
123
  }
151
124
  throw new Error(`Unable to allocate automatic task id for: ${semanticSlug}`);
152
125
  }
153
- export function createTask(targetInput, taskId, { title = "", locale = "en-US", dryRun = false, moduleKey = "", budget = "standard", longRunning = false, preset = "", fromSession = "", presetArgs = [], automaticTaskId = false } = {}) {
126
+ export function createTask(targetInput, taskId, { title = "", locale = "en-US", dryRun = false, moduleKey = "", budget = "standard", longRunning = false, preset = "", fromSession = "", presetArgs = [], automaticTaskId = false, deferCommit = false, allowDirtyRelativePaths = [], registerModule = false, moduleRegistration = {} } = {}) {
154
127
  const requestedPreset = preset || (moduleKey ? "module" : "");
155
128
  const presetTargetInput = resolveImplicitCreateTarget(targetInput, fromSession);
156
129
  const normalizedPreset = normalizeTaskPresetInput(requestedPreset, { targetInput: presetTargetInput });
157
130
  const presetPackage = normalizedPreset === "none" ? null : readPresetPackage(normalizedPreset, { targetInput: presetTargetInput });
158
- const presetInputs = presetPackage ? resolvePresetInputs(presetPackage, { cliArgs: presetArgs, fromSession, targetInput: presetTargetInput }) : null;
159
- const target = normalizeTarget(presetInputs?.targetInput || presetTargetInput || targetInput);
131
+ const presetInputs = presetPackage ? resolveLifecyclePresetInputs(presetPackage, { cliArgs: presetArgs, fromSession, targetInput: presetTargetInput }) : null;
132
+ const target = asLifecycleTarget(normalizeTarget(presetInputs?.targetInput || presetTargetInput || targetInput));
160
133
  if (presetInputs?.targetInput && targetInput && targetInput !== "." && path.resolve(targetInput) !== path.resolve(presetInputs.targetInput)) {
161
134
  throw new Error(`--from-session target mismatch: session target is ${presetInputs.targetInput}`);
162
135
  }
@@ -167,7 +140,15 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
167
140
  throw new Error(`${normalizedPreset} preset is project-level and cannot be combined with --module`);
168
141
  if (presetPackage?.task?.requiresFromSession === true && !fromSession)
169
142
  throw new Error(`${normalizedPreset} preset requires --from-session`);
170
- const normalizedModuleKey = moduleKey ? normalizeTaskId(moduleKey) : "";
143
+ const normalizedModuleKey = moduleKey ? normalizeHarnessModuleKey(moduleKey) : "";
144
+ const plannedModuleRegistration = normalizedModuleKey && !registeredHarnessModule(target, normalizedModuleKey)
145
+ ? registerModule
146
+ ? prepareModuleRegistration(target, normalizedModuleKey, moduleRegistration, { dryRun: true })
147
+ : null
148
+ : null;
149
+ if (normalizedModuleKey && !registeredHarnessModule(target, normalizedModuleKey) && !plannedModuleRegistration) {
150
+ throw new Error(`Unknown module: ${normalizedModuleKey}. Register it first with: harness module register ${normalizedModuleKey} --title <title> --prefix <PREFIX> --scope <path> ${target.projectRoot}`);
151
+ }
171
152
  const identity = resolveTaskIdentity({ target, taskId, title, presetPackage, moduleKey: normalizedModuleKey, automaticTaskId });
172
153
  const normalizedTaskId = identity.normalizedTaskId;
173
154
  const semanticSlug = identity.semanticSlug;
@@ -190,9 +171,9 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
190
171
  automaticTaskId,
191
172
  });
192
173
  const baseTaskAudit = buildCreationTaskAudit(scaffoldProvenance, { projectRoot: target.projectRoot });
193
- const evaluatedPresetValues = presetPackage ? evaluateTemplateValues(presetPackage, presetInputs.inputs, { taskId: normalizedTaskId, taskTitle, moduleKey: normalizedModuleKey }) : null;
194
- const presetContext = presetPackage
195
- ? buildPresetContext({ ...presetPackage, task: { ...(presetPackage.task || {}), kind: presetPackage.task?.kind || "general" } }, {
174
+ const evaluatedPresetValues = presetPackage && presetInputs ? evaluatePresetValues(presetPackage, presetInputs.inputs, { taskId: normalizedTaskId, taskTitle, moduleKey: normalizedModuleKey, target }) : null;
175
+ const presetContext = presetPackage && presetInputs && evaluatedPresetValues
176
+ ? buildLifecyclePresetContext({ ...presetPackage, task: { ...(presetPackage.task || {}), kind: presetPackage.task?.kind || "general" } }, {
196
177
  target,
197
178
  taskDir: directory,
198
179
  taskId: normalizedTaskId,
@@ -225,14 +206,17 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
225
206
  normalizedLocale,
226
207
  normalizedBudget,
227
208
  longRunning,
228
- presetContext,
229
- task,
209
+ presetContext: presetContext || undefined,
230
210
  });
231
211
  const plannedGovernance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun: true });
232
- const plannedWriteScopes = governanceRelativePaths([...plannedChanges, ...plannedGovernance.changes]);
212
+ const plannedWriteScopes = [...governanceRelativePaths(plannedModuleRegistration?.changes || []), ...changeDestinations(plannedChanges), ...governanceRelativePaths(plannedGovernance.changes)];
233
213
  const changes = [];
234
- const governanceContext = beginGovernanceSync(target, { operation: `new-task ${normalizedTaskId}`, dryRun, allowDirtyWorktree: true, allowedRelativePaths: plannedWriteScopes });
214
+ const governanceContext = beginGovernanceSync(target, { operation: `new-task ${normalizedTaskId}`, dryRun, allowDirtyWorktree: true, allowedRelativePaths: [...plannedWriteScopes, ...(allowDirtyRelativePaths || [])], allowDirtyWriteScope: deferCommit });
235
215
  try {
216
+ if (plannedModuleRegistration) {
217
+ const moduleRegistrationResult = prepareModuleRegistration(target, normalizedModuleKey, moduleRegistration, { dryRun });
218
+ changes.push(...moduleRegistrationResult.changes);
219
+ }
236
220
  if (normalizedModuleKey) {
237
221
  const moduleDirectory = target.harness.version === 2
238
222
  ? path.join(target.harness.modulesRoot, normalizedModuleKey)
@@ -247,7 +231,7 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
247
231
  action: dryRun ? "would-create" : "create",
248
232
  });
249
233
  if (presetPackage)
250
- assertPresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)));
234
+ assertLifecyclePresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)), target);
251
235
  if (dryRun)
252
236
  continue;
253
237
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
@@ -263,6 +247,7 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
263
247
  longRunning,
264
248
  scaffoldProvenance,
265
249
  taskAudit: buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot }),
250
+ target,
266
251
  }));
267
252
  }
268
253
  }
@@ -278,11 +263,11 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
278
263
  action: dryRun ? "would-create" : "create",
279
264
  });
280
265
  if (presetPackage)
281
- assertPresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)));
266
+ assertLifecyclePresetWriteScope(presetPackage, toPosix(path.relative(target.projectRoot, destinationPath)), target);
282
267
  if (dryRun)
283
268
  continue;
284
269
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
285
- fs.writeFileSync(destinationPath, renderPresetTaskTemplate(destination, renderTaskTemplate(readBundledTemplate(source), {
270
+ fs.writeFileSync(destinationPath, renderLifecyclePresetTaskTemplate(destination, renderTaskTemplate(readBundledTemplate(source), {
286
271
  taskId: normalizedTaskId,
287
272
  title: taskTitle,
288
273
  locale: normalizedLocale,
@@ -299,17 +284,19 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
299
284
  taskAudit: destination === "INDEX.md"
300
285
  ? buildCreationTaskAudit({ ...scaffoldProvenance, templateSource: source }, { projectRoot: target.projectRoot })
301
286
  : baseTaskAudit,
287
+ target,
302
288
  }), presetContext));
303
289
  }
304
290
  if (presetContext) {
305
- for (const evidence of presetContext.evidenceFiles) {
291
+ for (const evidence of presetContext.evidenceFiles || []) {
306
292
  const destinationPath = path.join(target.projectRoot, evidence.relativePath);
307
293
  changes.push({
308
294
  destination: toPosix(evidence.relativePath),
309
295
  source: evidence.source,
310
296
  action: dryRun ? "would-create" : "create",
311
297
  });
312
- assertPresetWriteScope(presetPackage, toPosix(evidence.relativePath));
298
+ if (presetPackage)
299
+ assertLifecyclePresetWriteScope(presetPackage, toPosix(evidence.relativePath), target);
313
300
  if (dryRun)
314
301
  continue;
315
302
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
@@ -322,7 +309,8 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
322
309
  source: resource.source,
323
310
  action: dryRun ? "would-create" : "create",
324
311
  });
325
- assertPresetWriteScope(presetPackage, toPosix(resource.relativePath));
312
+ if (presetPackage)
313
+ assertLifecyclePresetWriteScope(presetPackage, toPosix(resource.relativePath), target);
326
314
  if (dryRun)
327
315
  continue;
328
316
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
@@ -339,7 +327,8 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
339
327
  source: `preset-${kind}-index`,
340
328
  action: dryRun ? "would-update" : "update",
341
329
  });
342
- assertPresetWriteScope(presetPackage, relativePath);
330
+ if (presetPackage)
331
+ assertLifecyclePresetWriteScope(presetPackage, relativePath, target);
343
332
  if (dryRun)
344
333
  continue;
345
334
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
@@ -349,14 +338,12 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
349
338
  }
350
339
  const governance = syncTaskGovernance(target, task, { event: "new-task", state: "planned", message: "task registered by CLI", dryRun });
351
340
  changes.push(...governance.changes);
352
- const commandWriteScopes = governanceRelativePaths(changes);
341
+ const commandWriteScopes = [...changeDestinations(changes), ...governanceRelativePaths(governance.changes)];
353
342
  if (presetContext) {
354
343
  refreshPresetCommandAudit(target, presetContext, { commandWriteScopes, dryRun });
355
344
  task.presetAudit = presetContext.audit;
356
345
  }
357
- const commit = commitGovernanceSync(governanceContext, commandWriteScopes, {
358
- message: `chore(harness): register task ${task.id}`,
359
- });
346
+ const commit = deferCommit ? { committed: false, reason: "deferred", allowedPaths: commandWriteScopes } : commitGovernanceSync(governanceContext, commandWriteScopes, { message: `chore(harness): register task ${task.id}` });
360
347
  return {
361
348
  dryRun,
362
349
  task,
@@ -369,18 +356,20 @@ export function createTask(targetInput, taskId, { title = "", locale = "en-US",
369
356
  }
370
357
  }
371
358
  export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", state = "", message = "", evidence = "" } = {}) {
372
- const target = normalizeTarget(targetInput);
359
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
360
+ const normalizedEvent = lifecycleGateEvent(event);
373
361
  const taskDir = resolveTaskDirectory(target, taskId);
374
362
  const progressPath = path.join(taskDir, "progress.md");
375
363
  const registry = readCapabilityRegistry(target);
376
364
  const normalizedState = state ? String(state).toLowerCase().replaceAll("-", "_") : "";
377
365
  if (normalizedState && !allowedTaskStates.has(normalizedState))
378
366
  throw new Error(`Invalid task state: ${state}`);
367
+ assertLifecycleEventStateConsistency(normalizedEvent, normalizedState);
379
368
  const currentTask = findTaskByDirectory(target, taskDir);
380
369
  const canonicalTaskId = taskIdForDirectory(target, taskDir);
381
370
  const budget = parseTaskBudget(readFileSafe(path.join(taskDir, "task_plan.md")));
382
371
  validateLifecycleTransition({
383
- event,
372
+ event: normalizedEvent,
384
373
  currentState: currentTask?.state || "unknown",
385
374
  budget,
386
375
  reviewContent: readFileSafe(path.join(taskDir, "review.md")),
@@ -395,11 +384,11 @@ export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", s
395
384
  try {
396
385
  let content = readFileSafe(progressPath);
397
386
  if (normalizedState)
398
- content = updateProgressState(content, normalizedState, registry.locale);
387
+ content = updateProgressState(content, normalizedState, registry.locale || "en-US");
399
388
  content = appendProgressLog(content, { event, message, evidence });
400
389
  fs.writeFileSync(progressPath, content.endsWith("\n") ? content : `${content}\n`);
401
390
  const allowedPaths = [toPosix(path.relative(target.projectRoot, progressPath))];
402
- const advancedPhasePath = advanceLifecyclePhase(target, taskDir, event);
391
+ const advancedPhasePath = advanceLifecyclePhase(target, taskDir, normalizedEvent);
403
392
  if (advancedPhasePath)
404
393
  allowedPaths.push(advancedPhasePath);
405
394
  if (event === "task-review") {
@@ -446,13 +435,26 @@ export function updateTaskLifecycle(targetInput, taskId, { event = "task-log", s
446
435
  releaseGovernanceSync(governanceContext);
447
436
  }
448
437
  }
449
- export function confirmTaskReview(targetInput, taskId, { reviewer = "Human Reviewer", message = "", confirmText = "", evidence = "" } = {}) {
450
- const target = normalizeTarget(targetInput);
451
- const taskDir = resolveTaskDirectory(target, taskId);
452
- return confirmTaskReviewWithContext({ target, taskDir, findTaskByDirectory }, { reviewer, message, confirmText, evidence });
438
+ function assertLifecycleEventStateConsistency(event, state) {
439
+ if (!state)
440
+ return;
441
+ if (state === "done" && event !== "task-complete") {
442
+ throw new Error(`State done must be written through task-complete, not ${event}.`);
443
+ }
444
+ if (state === "review" && event !== "task-review") {
445
+ throw new Error(`State review must be written through task-review, not ${event}.`);
446
+ }
447
+ }
448
+ export function confirmTaskReview(targetInput, taskId, { reviewer = "Human Reviewer", message = "", confirmText = "", evidence = "", deferCommit = false } = {}) {
449
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
450
+ return confirmTaskReviewWithContext({ target, taskDir: resolveTaskDirectory(target, taskId), findTaskByDirectory: findReviewTaskByDirectory }, { reviewer, message, confirmText, evidence, deferCommit });
451
+ }
452
+ export function finalizeDeferredTaskReviewConfirmation(targetInput, taskId, { commitSha = "" } = {}) {
453
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
454
+ return finalizeDeferredTaskReviewConfirmationWithContext({ target, taskDir: resolveTaskDirectory(target, taskId), findTaskByDirectory: findReviewTaskByDirectory }, { commitSha });
453
455
  }
454
456
  export function updateTaskPhase(targetInput, taskId, phaseId, { state = "", completion = "", evidenceStatus = "" } = {}) {
455
- const target = normalizeTarget(targetInput);
457
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
456
458
  const taskDir = resolveTaskDirectory(target, taskId);
457
459
  const visualMapPath = path.join(taskDir, visualMapFile);
458
460
  const legacyPath = path.join(taskDir, legacyVisualRoadmapFile);
@@ -504,8 +506,8 @@ export function updateTaskPhase(targetInput, taskId, phaseId, { state = "", comp
504
506
  }
505
507
  }
506
508
  export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" } = {}) {
507
- const target = normalizeTarget(targetInput);
508
- const normalizedModuleKey = normalizeTaskId(moduleKey);
509
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
510
+ const normalizedModuleKey = normalizeHarnessModuleKey(moduleKey);
509
511
  const normalizedState = String(state || "done").toLowerCase().replaceAll("_", "-");
510
512
  if (!["planned", "in-progress", "done", "blocked", "superseded"].includes(normalizedState))
511
513
  throw new Error(`Invalid module step state: ${state}`);
@@ -529,39 +531,11 @@ export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" }
529
531
  try {
530
532
  content = stepUpdate.content;
531
533
  fs.writeFileSync(modulePlanPath, content);
532
- const registryPath = path.join(target.harness.modulesRoot, "Module-Registry.md");
533
- if (fs.existsSync(registryPath)) {
534
- let registry = readFileSafe(registryPath);
535
- const registryUpdate = updateMarkdownTableRow(registry, /^(ID|模块 Key)$/i, (header, row) => {
536
- const moduleIndex = firstColumn(header, ["Module", "模块", "模块 Key"]);
537
- const taskPlanIndex = getColumn(header, "Task Plan");
538
- const matchesModule = normalizeTaskId(row[moduleIndex] || "") === normalizedModuleKey;
539
- const matchesPlan = taskPlanIndex >= 0 && String(row[taskPlanIndex] || "").includes(`/MODULES/${normalizedModuleKey}/`);
540
- if (!matchesModule && !matchesPlan)
541
- return null;
542
- const next = [...row];
543
- const statusIndex = firstColumn(header, ["Status", "状态"]);
544
- const updatedIndex = firstColumn(header, ["Updated", "更新时间"]);
545
- const currentStepIndex = firstColumn(header, ["Current Step", "当前步骤"]);
546
- const chineseRegistry = header.some((cell) => /模块 Key|模块名称|状态|更新时间/.test(cell));
547
- if (statusIndex >= 0) {
548
- next[statusIndex] = normalizedState === "done"
549
- ? chineseRegistry ? "completed" : "merged"
550
- : normalizedState === "in-progress" ? chineseRegistry ? "in-progress" : "active" : normalizedState;
551
- }
552
- if (currentStepIndex >= 0)
553
- next[currentStepIndex] = stepId;
554
- if (updatedIndex >= 0)
555
- next[updatedIndex] = todayDate();
556
- return next;
557
- });
558
- registry = registryUpdate.content;
559
- fs.writeFileSync(registryPath, registry);
560
- }
534
+ const moduleRegistration = prepareModuleStepRegistrationUpdate(target, normalizedModuleKey, { stepId, state: normalizedState });
561
535
  const governance = syncModuleStepGovernance(target, { moduleKey: normalizedModuleKey, stepId, state: normalizedState });
562
536
  const commit = commitGovernanceSync(governanceContext, [
563
537
  toPosix(path.relative(target.projectRoot, modulePlanPath)),
564
- toPosix(path.relative(target.projectRoot, registryPath)),
538
+ ...governanceRelativePaths(moduleRegistration.changes),
565
539
  ...governanceRelativePaths(governance.changes),
566
540
  ], { message: `chore(harness): update module ${normalizedModuleKey} step ${stepId}` });
567
541
  return { event: "module-step", moduleKey: normalizedModuleKey, stepId, state: normalizedState, governance: { ...governance, commit } };
@@ -570,42 +544,26 @@ export function updateModuleStep(targetInput, moduleKey, stepId, { state = "" }
570
544
  releaseGovernanceSync(governanceContext);
571
545
  }
572
546
  }
573
- export function listLifecycleTasks(targetInput, { state = "", moduleKey = "", queue = "", preset = "", review = "", lesson = "", search = "", missingMaterials = false } = {}) {
574
- const target = normalizeTarget(targetInput);
575
- let tasks = collectTasks(target);
576
- if (state)
577
- tasks = tasks.filter((task) => task.state === String(state).toLowerCase().replaceAll("-", "_"));
578
- if (moduleKey)
579
- tasks = tasks.filter((task) => task.module === normalizeTaskId(moduleKey));
580
- if (queue) {
581
- const normalizedQueue = queryToken(queue);
582
- tasks = tasks.filter((task) => (task.taskQueues || []).map(queryToken).includes(normalizedQueue));
583
- }
584
- if (preset)
585
- tasks = tasks.filter((task) => queryToken(task.taskPreset || "none") === queryToken(preset));
586
- if (review)
587
- tasks = tasks.filter((task) => queryToken(task.reviewStatus || "") === queryToken(review));
588
- if (lesson) {
589
- const needle = queryToken(lesson);
590
- tasks = tasks.filter((task) => [task.lessonCandidateStatus, task.lessonCandidateReviewDecision, task.lessonCandidatePromotionState].some((value) => queryToken(value) === needle));
547
+ export function listLifecycleTasks(targetInput, { state = "", moduleKey = "", queue = "", preset = "", review = "", lesson = "", search = "", missingMaterials = false, includeArchived = false } = {}) {
548
+ const target = asLifecycleTarget(normalizeTarget(targetInput));
549
+ const tasks = createScannerTaskRepository(target).list({
550
+ includeArchived,
551
+ state,
552
+ module: moduleKey ? normalizeHarnessModuleKey(moduleKey) : "",
553
+ queue,
554
+ preset,
555
+ review,
556
+ lesson,
557
+ missingMaterials,
558
+ search,
559
+ });
560
+ let modules = [];
561
+ try {
562
+ const registry = readHarnessModules(target);
563
+ modules = Object.entries(registry.items || {}).map(([key, module]) => ({ key, ...module }));
591
564
  }
592
- if (missingMaterials)
593
- tasks = tasks.filter((task) => !task.materialsReady);
594
- if (search) {
595
- const needle = String(search).toLowerCase();
596
- tasks = tasks.filter((task) => [
597
- task.id,
598
- task.taskKey,
599
- task.shortId,
600
- task.title,
601
- task.currentPath,
602
- task.taskPlanPath,
603
- task.module,
604
- task.inferredModule,
605
- ].some((value) => String(value || "").toLowerCase().includes(needle)));
565
+ catch {
566
+ modules = [];
606
567
  }
607
- return { tasks };
608
- }
609
- function queryToken(value) {
610
- return String(value || "").trim().toLowerCase().replaceAll("_", "-");
568
+ return { tasks, modules };
611
569
  }
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  // Dynamic metadata parsing stays behavior-first until the metadata domain model PR.
3
- import { allowedTaskStates, allowedTaskBudgets, taskContractMarker, } from "./core-shared.mjs";
2
+ import { allowedTaskStates, taskContractMarker, } from "./core-shared.mjs";
4
3
  import { tableAfterHeading, firstColumn } from "./markdown-utils.mjs";
5
4
  export function parseTaskState(progressContent) {
6
5
  return parseTaskStateInfo(progressContent).state;
@@ -12,7 +11,7 @@ export function parseTaskBudget(taskPlanContent) {
12
11
  return "standard";
13
12
  const raw = match[1].replace(/`/g, "").trim().toLowerCase();
14
13
  const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
15
- if (allowedTaskBudgets.has(normalized))
14
+ if (isTaskBudget(normalized))
16
15
  return normalized;
17
16
  if (["long-running", "longrunning", "module-parallel"].includes(normalized))
18
17
  return "complex";
@@ -76,7 +75,7 @@ export function parseTaskStateInfo(progressContent) {
76
75
  ["pending", "planned"],
77
76
  ]);
78
77
  const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
79
- return allowedTaskStates.has(normalized)
78
+ return isKnownTaskState(normalized)
80
79
  ? { state: normalized, source: "explicit", raw }
81
80
  : { state: "unknown", source: "invalid", raw };
82
81
  }
@@ -85,7 +84,7 @@ function inferLegacyTaskState(progressContent) {
85
84
  const statusIndex = firstColumn(header, ["Status", "状态"]);
86
85
  if (statusIndex < 0 || rows.length === 0)
87
86
  return { state: "unknown", source: "missing", raw: "" };
88
- const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(Boolean);
87
+ const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(isKnownTaskState);
89
88
  if (states.includes("blocked"))
90
89
  return { state: "blocked", source: "legacy-table", raw: "blocked" };
91
90
  if (states.includes("in_progress"))
@@ -114,3 +113,9 @@ function normalizeLegacyState(value) {
114
113
  return "planned";
115
114
  return "";
116
115
  }
116
+ function isKnownTaskState(value) {
117
+ return typeof value === "string" && allowedTaskStates.has(value);
118
+ }
119
+ function isTaskBudget(value) {
120
+ return value === "simple" || value === "standard" || value === "complex";
121
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { validateTaskPresetAuditSnapshot } from "./preset-audit-contracts.mjs";
4
+ import { validatePresetResourcesForTask } from "./preset-resource-contracts.mjs";
5
+ export function validateRegularTaskPresetContract(target, task, presetPackage) {
6
+ const failures = [];
7
+ const warnings = [];
8
+ const driftWarnings = [];
9
+ if (presetPackage.task?.kind && task.taskKind !== presetPackage.task.kind) {
10
+ driftWarnings.push(`${task.path} ${task.taskPreset} preset Task Kind mismatch: expected ${presetPackage.task.kind}, got ${task.taskKind || "(missing)"}`);
11
+ }
12
+ if (String(task.presetVersion || "") !== String(presetPackage.version)) {
13
+ driftWarnings.push(`${task.path} ${task.taskPreset} preset version drift: task ${task.presetVersion || "(missing)"}, current ${presetPackage.version}`);
14
+ }
15
+ if (presetNeedsEvidenceBundle(presetPackage)) {
16
+ if (!task.evidenceBundle)
17
+ failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle`);
18
+ else if (!fs.existsSync(path.join(target.projectRoot, String(task.evidenceBundle).replace(/^TARGET:/, "").replace(/^\/+/, "")))) {
19
+ failures.push(`${task.path} ${task.taskPreset} preset Evidence Bundle missing: ${task.evidenceBundle}`);
20
+ }
21
+ }
22
+ for (const issue of validateTaskPresetAuditSnapshot(target, task, presetPackage)) {
23
+ if (issue.includes("preset manifest hash mismatch"))
24
+ driftWarnings.push(issue);
25
+ else
26
+ failures.push(issue);
27
+ }
28
+ const resourceIssues = validatePresetResourcesForTask(target, task, presetPackage);
29
+ if (driftWarnings.length) {
30
+ warnings.push(...driftWarnings.map(toPresetDriftWarning));
31
+ warnings.push(...resourceIssues.map(toPresetDriftWarning));
32
+ }
33
+ else {
34
+ failures.push(...resourceIssues);
35
+ }
36
+ return { failures, warnings };
37
+ }
38
+ function presetNeedsEvidenceBundle(presetPackage) {
39
+ return Boolean(presetPackage.evidence?.bundleDir ||
40
+ presetPackage.audit?.evidenceFiles?.length ||
41
+ Object.keys(presetPackage.evidence?.files || {}).length);
42
+ }
43
+ function toPresetDriftWarning(issue) {
44
+ return `preset-drift-warning: creation-time preset provenance drift; ${issue}`;
45
+ }