@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.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 (284) hide show
  1. package/AGENTS.md +155 -153
  2. package/CHANGELOG.md +338 -265
  3. package/README.md +345 -298
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/generate-task-dag.js +28 -28
  7. package/dist/application/evaluation/candidate-hash.js +75 -0
  8. package/dist/application/evaluation/candidate.js +52 -0
  9. package/dist/application/evaluation/replay.js +289 -0
  10. package/dist/application/evaluation/types.js +130 -0
  11. package/dist/cli/command-definitions.js +27 -7
  12. package/dist/cli/program.js +8 -4
  13. package/dist/commands/cursor-prompt.js +6 -6
  14. package/dist/commands/eval.js +235 -0
  15. package/dist/commands/init.js +544 -506
  16. package/dist/commands/knowledge.js +129 -31
  17. package/dist/commands/loop-benchmark.js +11 -11
  18. package/dist/commands/pi-reuse-benchmark.js +16 -16
  19. package/dist/executors/pi-sdk-executor.js +38 -24
  20. package/dist/executors/shell-executor.js +34 -2
  21. package/dist/executors/shell-presets.js +20 -0
  22. package/dist/executors/shell-verification.js +7 -0
  23. package/dist/governance/manifest-types.js +4 -0
  24. package/dist/infrastructure/evaluation/candidate-store.js +435 -0
  25. package/dist/infrastructure/evaluation/store.js +40 -0
  26. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  27. package/dist/task/config-types.js +28 -1
  28. package/dist/task/runtime.js +27 -27
  29. package/dist/worker/cli.js +96 -1
  30. package/dist/worker/delivery/package.js +3 -3
  31. package/dist/worker/feature/decision-loader.js +37 -6
  32. package/dist/worker/feature/next-action.js +10 -2
  33. package/dist/worker/feature/ready-plan-projection.js +81 -0
  34. package/dist/worker/feature/reducer.js +2 -1
  35. package/dist/worker/feature/review.js +19 -2
  36. package/dist/worker/feature/run.js +27 -2
  37. package/dist/worker/follow-up/approve.js +5 -2
  38. package/dist/worker/follow-up/factory.js +1 -1
  39. package/dist/worker/observability/read-model.js +246 -41
  40. package/dist/worker/observe/routes.js +173 -15
  41. package/dist/worker/observe/spec-evidence.js +281 -0
  42. package/dist/worker/observe/static/api.js +46 -27
  43. package/dist/worker/observe/static/app.js +150 -150
  44. package/dist/worker/observe/static/constants.js +148 -148
  45. package/dist/worker/observe/static/copy.js +67 -67
  46. package/dist/worker/observe/static/dag-helpers.js +172 -172
  47. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  48. package/dist/worker/observe/static/dag-layout.js +83 -83
  49. package/dist/worker/observe/static/dag-model.js +72 -72
  50. package/dist/worker/observe/static/dom.js +61 -61
  51. package/dist/worker/observe/static/format-pool.js +67 -67
  52. package/dist/worker/observe/static/format.js +292 -292
  53. package/dist/worker/observe/static/index.html +308 -308
  54. package/dist/worker/observe/static/kpi.js +94 -94
  55. package/dist/worker/observe/static/relations.js +133 -128
  56. package/dist/worker/observe/static/router.js +93 -85
  57. package/dist/worker/observe/static/run-processing.js +148 -148
  58. package/dist/worker/observe/static/shell-chrome.js +68 -68
  59. package/dist/worker/observe/static/state.js +253 -253
  60. package/dist/worker/observe/static/styles.css +1902 -1890
  61. package/dist/worker/observe/static/views/batch.js +227 -226
  62. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  63. package/dist/worker/observe/static/views/dag-inspector.js +607 -477
  64. package/dist/worker/observe/static/views/dag.js +362 -362
  65. package/dist/worker/observe/static/views/dashboard.js +445 -442
  66. package/dist/worker/observe/static/views/failures.js +143 -143
  67. package/dist/worker/observe/static/views/feature.js +492 -453
  68. package/dist/worker/observe/static/views/pool.js +350 -347
  69. package/dist/worker/observe/static/views/run.js +453 -453
  70. package/dist/worker/observe/static/views/session-timeline.js +205 -205
  71. package/dist/worker/observe/static/views/shell.js +7 -7
  72. package/dist/worker/observe/static/views/task.js +314 -260
  73. package/dist/worker/observe/static/views/timeline.js +163 -163
  74. package/dist/worker/pool/doctor.js +165 -0
  75. package/dist/worker/pool/migrate-state.js +303 -0
  76. package/dist/worker/pool/run-store.js +205 -17
  77. package/dist/worker/pool/types.js +17 -1
  78. package/dist/worker/pool/validation.js +100 -15
  79. package/dist/worker/report/morning-report.js +12 -2
  80. package/dist/worker/runner/run-ready.js +41 -26
  81. package/dist/worker/task-graph/ready-planner.js +136 -0
  82. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  83. package/dist/workflows/dag/canvas-observer.js +275 -275
  84. package/dist/workflows/dag/convergence/controller.js +16 -8
  85. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  86. package/dist/workflows/dag/failure-routing.js +12 -1
  87. package/dist/workflows/dag/init-hybrid.js +2404 -360
  88. package/dist/workflows/dag/node-execution.js +9 -0
  89. package/dist/workflows/dag/prompt.js +9 -0
  90. package/dist/workflows/dag/report.js +35 -1
  91. package/dist/workflows/dag/runner.js +28 -2
  92. package/dist/workflows/dag/task-demand-routing.js +383 -0
  93. package/dist/workflows/dag/types.js +51 -13
  94. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  95. package/dist/workflows/dag/validate.js +59 -1
  96. package/docs/README.md +106 -104
  97. package/docs/agent-dag-recovery-playbook.md +195 -184
  98. package/docs/agent-dag-runner.md +67 -67
  99. package/docs/architecture/README.md +26 -26
  100. package/docs/architecture/dag-execution.md +140 -140
  101. package/docs/architecture/evolution.md +54 -53
  102. package/docs/architecture/facts-and-state.md +71 -58
  103. package/docs/architecture/runtime-boundaries.md +191 -191
  104. package/docs/architecture/system-overview.md +93 -93
  105. package/docs/architecture/worker-and-feature.md +85 -81
  106. package/docs/cursor-prompt-sidecar.md +36 -36
  107. package/docs/decisions/README.md +18 -15
  108. package/docs/design/README.md +167 -77
  109. package/docs/development-principles.md +73 -73
  110. package/docs/exec-plans/README.md +6 -6
  111. package/docs/exec-plans/active/README.md +15 -9
  112. package/docs/exec-plans/completed/README.md +85 -73
  113. package/docs/feature-workflow.md +389 -261
  114. package/docs/harness-methodology-debugging.md +153 -153
  115. package/docs/harness-methodology-tdd.md +130 -130
  116. package/docs/harness-methodology-verification.md +27 -27
  117. package/docs/init-surface.manifest.json +289 -280
  118. package/docs/loop-agent-harness.md +142 -130
  119. package/docs/production-readiness.md +96 -96
  120. package/docs/progress/README.md +64 -54
  121. package/docs/reports/README.md +117 -94
  122. package/docs/skills/README.md +7 -7
  123. package/docs/skills/vetted-skill-registry.md +29 -27
  124. package/docs/templates/adr.md +60 -60
  125. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  126. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  127. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  128. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  129. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  130. package/docs/templates/agent-dag-report.schema.json +473 -473
  131. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  132. package/docs/templates/agent-dag.base.json +190 -190
  133. package/docs/templates/agent-dag.final-verification.json +185 -185
  134. package/docs/templates/agent-dag.schema.json +411 -383
  135. package/docs/templates/agent-dag.supervised-implementation.json +501 -501
  136. package/docs/templates/backend-test-analysis.schema.json +44 -0
  137. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
  138. package/docs/templates/backend-test-dag.json +311 -276
  139. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
  140. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
  141. package/docs/templates/exec-plan.md +64 -64
  142. package/docs/templates/feature-spec.md +53 -53
  143. package/docs/templates/frontend-design-contract.md +42 -33
  144. package/docs/templates/frontend-task-constraints.md +35 -25
  145. package/docs/templates/frontend-task-requirement.md +70 -61
  146. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  147. package/docs/templates/frontend-test-dag.json +23 -0
  148. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  149. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  150. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  151. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  152. package/docs/templates/harness.schema.json +221 -221
  153. package/docs/templates/hybrid-dag.json +188 -188
  154. package/docs/templates/init-evolution-review.md +35 -35
  155. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  156. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  157. package/docs/templates/knowledge-sync-dag.json +178 -0
  158. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  159. package/docs/templates/product-line/AGENTS.md +8 -8
  160. package/docs/templates/product-line/README.md +9 -9
  161. package/docs/templates/product-line/acceptance.yaml +14 -14
  162. package/docs/templates/product-line/closeout.yaml +9 -9
  163. package/docs/templates/product-line/design.md +13 -13
  164. package/docs/templates/product-line/links.md +10 -10
  165. package/docs/templates/product-line/requirement.md +17 -17
  166. package/docs/templates/product-line/task-graph.yaml +15 -15
  167. package/docs/templates/product-line/task.yaml +64 -64
  168. package/docs/templates/product-line/test-plan.md +7 -7
  169. package/docs/templates/production-readiness-checklist.md +57 -57
  170. package/docs/templates/progress-log.md +17 -17
  171. package/docs/templates/project-start-checklist.md +9 -9
  172. package/docs/templates/qa-report.md +48 -48
  173. package/docs/templates/sprint-contract.md +29 -29
  174. package/docs/templates/worker-dogfood-evidence.md +80 -80
  175. package/docs/templates/worker-dogfood-setup.md +68 -68
  176. package/docs/verification-matrix.md +70 -66
  177. package/examples/decision-gate-agent-dag.json +177 -177
  178. package/examples/example-dag.json +46 -46
  179. package/examples/hybrid-loop-agent-dag.json +189 -189
  180. package/harness.json +66 -66
  181. package/package.json +88 -46
  182. package/scripts/check-product-line-docs.sh +29 -29
  183. package/scripts/check-task-pool-root.sh +32 -32
  184. package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
  185. package/scripts/kb-graph-incremental-prepare.mjs +386 -0
  186. package/scripts/kb-graph-incremental-prepare.sh +5 -0
  187. package/scripts/kb-graph-materialize.mjs +105 -0
  188. package/scripts/kb-graph-materialize.sh +4 -0
  189. package/scripts/kb-graph-promote.mjs +164 -0
  190. package/scripts/kb-graph-promote.sh +4 -0
  191. package/scripts/kb-query.mjs +554 -0
  192. package/scripts/kb-query.sh +5 -0
  193. package/skills/agent-worker/SKILL.md +39 -37
  194. package/skills/agent-worker/references/agent-worker-operator.md +60 -43
  195. package/skills/ai-engineering-context/SKILL.md +48 -48
  196. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  197. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  198. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  199. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  200. package/skills/analyze-product-dependencies/references/example.md +76 -0
  201. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  202. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  203. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  204. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  205. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  206. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  207. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  208. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  209. package/skills/analyze-product-requirements/SKILL.md +90 -0
  210. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  211. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  212. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  213. package/skills/analyze-product-requirements/references/example.md +86 -0
  214. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  215. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  216. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  217. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  218. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  219. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  220. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  221. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  222. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  223. package/skills/code-review-core/SKILL.md +20 -20
  224. package/skills/codebase-scout/SKILL.md +19 -19
  225. package/skills/frontend-design-review/SKILL.md +66 -59
  226. package/skills/frontend-design-review/references/review-checklist.md +58 -37
  227. package/skills/frontend-implementation/SKILL.md +47 -51
  228. package/skills/frontend-implementation/references/code-standards.md +32 -34
  229. package/skills/frontend-implementation/references/design-spec.md +46 -46
  230. package/skills/frontend-implementation/references/node-contracts.md +76 -32
  231. package/skills/frontend-review/SKILL.md +59 -53
  232. package/skills/frontend-review/references/review-findings.md +47 -42
  233. package/skills/frontend-verification/SKILL.md +53 -40
  234. package/skills/frontend-verification/references/verification-checklist.md +68 -56
  235. package/skills/grill-me/SKILL.md +10 -10
  236. package/skills/grill-with-docs/SKILL.md +88 -88
  237. package/skills/grill-with-docs/adr-format.md +47 -47
  238. package/skills/grill-with-docs/context-format.md +60 -60
  239. package/skills/init-capability-evolution/SKILL.md +70 -70
  240. package/skills/loop-agent/SKILL.md +151 -151
  241. package/skills/loop-agent/references/README.md +67 -67
  242. package/skills/loop-agent/references/command-reference.md +505 -452
  243. package/skills/loop-agent/references/docs-converge.md +126 -126
  244. package/skills/loop-agent/references/harness-policy.md +263 -263
  245. package/skills/loop-agent/references/hybrid-dag.md +238 -233
  246. package/skills/loop-agent/references/learned/README.md +21 -21
  247. package/skills/loop-agent/references/long-running-loop.md +57 -57
  248. package/skills/loop-agent/references/model-routing.md +36 -36
  249. package/skills/loop-agent/references/multi-worktree.md +54 -54
  250. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  251. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  252. package/skills/loop-agent/references/pi-prompt.md +23 -23
  253. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  254. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  255. package/skills/loop-agent/references/task-workflow.md +89 -89
  256. package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
  257. package/skills/playwright-cli/SKILL.md +420 -0
  258. package/skills/playwright-cli/references/element-attributes.md +23 -0
  259. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  260. package/skills/playwright-cli/references/request-mocking.md +87 -0
  261. package/skills/playwright-cli/references/running-code.md +241 -0
  262. package/skills/playwright-cli/references/session-management.md +225 -0
  263. package/skills/playwright-cli/references/storage-state.md +275 -0
  264. package/skills/playwright-cli/references/test-generation.md +433 -0
  265. package/skills/playwright-cli/references/tracing.md +139 -0
  266. package/skills/playwright-cli/references/video-recording.md +143 -0
  267. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  268. package/skills/requesting-code-review/SKILL.md +101 -101
  269. package/skills/requesting-code-review/code-reviewer.md +168 -168
  270. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  271. package/skills/systematic-debugging/SKILL.md +296 -296
  272. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  273. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  274. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  275. package/skills/systematic-debugging/find-polluter.sh +63 -63
  276. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  277. package/skills/systematic-debugging/test-academic.md +14 -14
  278. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  279. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  280. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  281. package/skills/test-driven-development/SKILL.md +20 -20
  282. package/skills/using-git-worktrees/SKILL.md +215 -215
  283. package/skills/verification-before-completion/SKILL.md +154 -154
  284. package/skills/webapp-testing/SKILL.md +19 -19
@@ -14,10 +14,15 @@ import { createCompositeProgressReporter } from "./observability/progress-compos
14
14
  import { createRoutedWorkerEventStore } from "./observability/event-store.js";
15
15
  import { buildGlobalSnapshot } from "./observability/read-model.js";
16
16
  import { createObserveServer } from "./observe/server.js";
17
- import { getTaskPoolRoot, prepareTaskPoolRetry } from "./pool/run-store.js";
17
+ import { diagnoseTaskPoolStates, formatDoctorHuman, loadOperatorMapping } from "./pool/doctor.js";
18
+ import { migrateTaskPoolStates } from "./pool/migrate-state.js";
19
+ import { getTaskPoolRoot, prepareTaskPoolRetry, readFeatureTaskPoolStates } from "./pool/run-store.js";
20
+ import { TaskPoolStateError } from "./pool/types.js";
18
21
  import { taskSpecSchema } from "./task-spec/schema.js";
19
22
  import { validateTaskSpec } from "./task-spec/validate.js";
20
23
  import { validateFeatureTaskGraph } from "./task-graph/validate.js";
24
+ import { taskGraphSpecSchema } from "./task-graph/task-graph-schema.js";
25
+ import { planReadyTasks } from "./task-graph/ready-planner.js";
21
26
  import { renderFeatureReview, reviewFeature } from "./feature/review.js";
22
27
  import { renderFeatureRun, runFeature } from "./feature/run.js";
23
28
  import { draftFollowUpDecision } from "./follow-up/factory.js";
@@ -40,6 +45,58 @@ export function buildAgentWorkerProgram() {
40
45
  const report = program.command("report").description("Task Pool reporting utilities");
41
46
  const observe = program.command("observe").description("Observe UI server and snapshot utilities");
42
47
  const feature = program.command("feature").description("Feature-level review and delivery workflow");
48
+ const pool = program.command("pool").description("Task Pool state doctor and migration utilities");
49
+ pool
50
+ .command("doctor")
51
+ .requiredOption("--repo <repo-root>", "Target repo root")
52
+ .option("--json", "Emit stable JSON instead of the human summary")
53
+ .option("--mapping <file>", "Optional operator mapping JSON (taskId -> featureId)")
54
+ .description("Inventory legacy and v2 Task Pool states (read-only)")
55
+ .action(async (options) => {
56
+ const repoRoot = path.resolve(options.repo);
57
+ const mapping = await loadOperatorMapping(options.mapping);
58
+ const report = await diagnoseTaskPoolStates(repoRoot, { mapping });
59
+ process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : formatDoctorHuman(report));
60
+ });
61
+ pool
62
+ .command("migrate-state")
63
+ .requiredOption("--repo <repo-root>", "Target repo root")
64
+ .option("--dry-run", "Plan migration without writes (default when --apply is omitted)")
65
+ .option("--apply", "Apply migration (requires --owner and --reason)")
66
+ .option("--owner <owner>", "Migration owner for apply")
67
+ .option("--reason <reason>", "Why migration is being applied")
68
+ .option("--mapping <file>", "Optional operator mapping JSON (taskId -> featureId)")
69
+ .option("--json", "Emit stable JSON")
70
+ .description("Migrate legacy flat Task Pool states to feature-scoped v2 (default dry-run)")
71
+ .action(async (options) => {
72
+ try {
73
+ const result = await migrateTaskPoolStates({
74
+ repoRoot: path.resolve(options.repo),
75
+ apply: options.apply === true,
76
+ ...(options.owner ? { owner: options.owner } : {}),
77
+ ...(options.reason ? { reason: options.reason } : {}),
78
+ ...(options.mapping ? { mappingPath: options.mapping } : {}),
79
+ });
80
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
81
+ if (result.status === "failed")
82
+ process.exitCode = 1;
83
+ }
84
+ catch (error) {
85
+ const detail = error instanceof Error ? error.message : String(error);
86
+ const code = error instanceof TaskPoolStateError ? error.code : "task-pool-migrate-failed";
87
+ if (options.json) {
88
+ process.stdout.write(`${JSON.stringify({
89
+ schemaVersion: 1,
90
+ status: "failed",
91
+ error: { code, message: detail },
92
+ }, null, 2)}\n`);
93
+ }
94
+ else {
95
+ process.stderr.write(`${detail}\n`);
96
+ }
97
+ process.exitCode = 1;
98
+ }
99
+ });
43
100
  feature
44
101
  .command("review")
45
102
  .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
@@ -206,12 +263,14 @@ export function buildAgentWorkerProgram() {
206
263
  .command("retry")
207
264
  .argument("<task-id>", "Failed Task Pool task to requeue")
208
265
  .requiredOption("--repo <repo-root>", "Target repo root")
266
+ .option("--feature-id <feature-id>", "Canonical Feature id for the Task Pool state (required when identity is ambiguous)")
209
267
  .option("--reason <reason>", "Why the failure is safe to retry")
210
268
  .description("Explicitly requeue a failed task; the next run-ready uses a new workerRunId")
211
269
  .action(async (taskId, options) => {
212
270
  const result = await prepareTaskPoolRetry({
213
271
  repoRoot: path.resolve(options.repo),
214
272
  taskId,
273
+ ...(options.featureId ? { featureId: options.featureId } : {}),
215
274
  ...(options.reason ? { reason: options.reason } : {}),
216
275
  });
217
276
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
@@ -268,6 +327,42 @@ export function buildAgentWorkerProgram() {
268
327
  handleFollowUpCliError(error, options.json ?? false, "approve-followup");
269
328
  }
270
329
  });
330
+ batch
331
+ .command("plan-ready")
332
+ .requiredOption("--feature-dir <dir>", "Feature directory containing tasks/task-graph.yaml")
333
+ .requiredOption("--repo <repo-root>", "Target repo root")
334
+ .option("--limit <count>", "Maximum ready tasks to select")
335
+ .option("--json", "Emit stable JSON (default human summary)")
336
+ .description("Preview the read-only priority-aware ready plan without writing state or artifacts")
337
+ .action(async (options) => {
338
+ // Strict zero-write preview: load graph + specs + feature-scoped states only.
339
+ const featureDir = path.resolve(options.featureDir);
340
+ const repoRoot = path.resolve(options.repo);
341
+ const graph = taskGraphSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "tasks", "task-graph.yaml"), "utf-8")));
342
+ const taskSpecs = new Map();
343
+ for (const node of graph.nodes) {
344
+ const taskFile = node.task ?? `${node.id}.yaml`;
345
+ taskSpecs.set(node.id, taskSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "tasks", taskFile), "utf-8"))));
346
+ }
347
+ const selectionLimit = options.limit
348
+ ? Number.parseInt(options.limit, 10)
349
+ : graph.nodes.length;
350
+ if (!Number.isInteger(selectionLimit) || selectionLimit < 1) {
351
+ process.stderr.write("selectionLimit must be a positive integer\n");
352
+ process.exitCode = 1;
353
+ return;
354
+ }
355
+ const plan = planReadyTasks({
356
+ featureId: graph.feature_id,
357
+ graph,
358
+ taskSpecs,
359
+ states: await readFeatureTaskPoolStates(repoRoot, graph.feature_id),
360
+ selectionLimit,
361
+ });
362
+ process.stdout.write(options.json
363
+ ? `${JSON.stringify(plan, null, 2)}\n`
364
+ : `Selected: ${plan.selected.map((task) => task.taskId).join(", ") || "none"}\nDeferred: ${plan.deferred.map((task) => task.taskId).join(", ") || "none"}\nBlocked: ${plan.blocked.length}\n`);
365
+ });
271
366
  batch
272
367
  .command("run-ready")
273
368
  .requiredOption("--feature-dir <dir>", "Feature directory containing tasks/task-graph.yaml")
@@ -6,7 +6,7 @@ import { promisify } from "node:util";
6
6
  import YAML from "yaml";
7
7
  import { z } from "zod";
8
8
  import { controllerIdentitiesMatch, LOOP_AGENT_PACKAGE_NAME, } from "../loop-agent/loop-agent-client.js";
9
- import { getRunsJsonlPath, getTaskPoolRoot, readAllTaskPoolStates, readJsonlFile } from "../pool/run-store.js";
9
+ import { getRunsJsonlPath, getTaskPoolRoot, readFeatureTaskPoolStates, readJsonlFile } from "../pool/run-store.js";
10
10
  import { acceptanceSpecSchema } from "../task-graph/acceptance-schema.js";
11
11
  import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
12
12
  import { validateFeatureTaskGraph } from "../task-graph/validate.js";
@@ -104,7 +104,7 @@ export async function prepareFeatureDelivery(input) {
104
104
  }
105
105
  const acceptance = acceptanceSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "acceptance.yaml"), "utf-8")));
106
106
  const graph = taskGraphSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "tasks", "task-graph.yaml"), "utf-8")));
107
- const states = await readAllTaskPoolStates(repoRoot);
107
+ const states = await readFeatureTaskPoolStates(repoRoot, featureId);
108
108
  const runs = await readJsonlFile(getRunsJsonlPath(repoRoot));
109
109
  const taskSpecs = new Map();
110
110
  for (const node of graph.nodes)
@@ -208,7 +208,7 @@ export async function validateDeliveryPackage(input) {
208
208
  blockers.push(`Git worktree is not clean for Closeout: ${closeoutStatus}`);
209
209
  const graph = taskGraphSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "tasks", "task-graph.yaml"), "utf-8")));
210
210
  const acceptance = acceptanceSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "acceptance.yaml"), "utf-8")));
211
- const states = await readAllTaskPoolStates(repoRoot);
211
+ const states = await readFeatureTaskPoolStates(repoRoot, featureId);
212
212
  const specs = new Map();
213
213
  for (const node of graph.nodes)
214
214
  specs.set(node.id, taskSpecSchema.parse(YAML.parse(await readFile(path.join(featureDir, "tasks", node.task), "utf-8"))));
@@ -46,16 +46,47 @@ async function auditTaskPoolFacts(repoRoot) {
46
46
  }
47
47
  const stateDir = path.join(getTaskPoolRoot(repoRoot), "states");
48
48
  try {
49
- for (const entry of await readdir(stateDir))
50
- if (entry.endsWith(".json"))
49
+ for (const entry of await readdir(stateDir, { withFileTypes: true })) {
50
+ if (entry.isFile() && entry.name.endsWith(".json")) {
51
51
  try {
52
- const parsed = taskPoolStateFactSchema.safeParse(JSON.parse(await readFile(path.join(stateDir, entry), "utf-8")));
53
- if (!parsed.success || parsed.data.taskId !== entry.slice(0, -5))
54
- warnings.push(`Task Pool state is semantically invalid: ${entry}`);
52
+ const parsed = taskPoolStateFactSchema.safeParse(JSON.parse(await readFile(path.join(stateDir, entry.name), "utf-8")));
53
+ if (!parsed.success || parsed.data.taskId !== entry.name.slice(0, -5)) {
54
+ warnings.push(`Task Pool state is semantically invalid: ${entry.name}`);
55
+ }
56
+ else {
57
+ warnings.push(`legacy Task Pool state requires migration: ${entry.name}`);
58
+ }
55
59
  }
56
60
  catch {
57
- warnings.push(`Task Pool state is corrupt: ${entry}`);
61
+ warnings.push(`Task Pool state is corrupt: ${entry.name}`);
58
62
  }
63
+ continue;
64
+ }
65
+ if (!entry.isDirectory())
66
+ continue;
67
+ const featureId = entry.name;
68
+ try {
69
+ for (const stateName of await readdir(path.join(stateDir, featureId))) {
70
+ if (!stateName.endsWith(".json"))
71
+ continue;
72
+ const relative = `${featureId}/${stateName}`;
73
+ try {
74
+ const parsed = taskPoolStateFactSchema.safeParse(JSON.parse(await readFile(path.join(stateDir, featureId, stateName), "utf-8")));
75
+ const expectedTaskId = stateName.slice(0, -5);
76
+ if (!parsed.success || parsed.data.taskId !== expectedTaskId || parsed.data.featureId !== featureId) {
77
+ warnings.push(`Task Pool state is semantically invalid: ${relative}`);
78
+ }
79
+ }
80
+ catch {
81
+ warnings.push(`Task Pool state is corrupt: ${relative}`);
82
+ }
83
+ }
84
+ }
85
+ catch (error) {
86
+ if (!isNotFound(error))
87
+ warnings.push(`Task Pool states for ${featureId} are unreadable: ${message(error)}`);
88
+ }
89
+ }
59
90
  }
60
91
  catch (error) {
61
92
  if (!isNotFound(error))
@@ -35,7 +35,7 @@ export function projectNextAction(input, status) {
35
35
  return {
36
36
  kind: "retry_task",
37
37
  label: `重试环境失败任务 ${failed.taskId}`,
38
- command: `agent-worker task retry ${quote(failed.taskId)} --repo ${quote(input.repoRoot)}`,
38
+ command: `agent-worker task retry ${quote(failed.taskId)} --feature-id ${quote(input.featureId)} --repo ${quote(input.repoRoot)}`,
39
39
  };
40
40
  }
41
41
  return {
@@ -51,9 +51,17 @@ export function projectNextAction(input, status) {
51
51
  };
52
52
  }
53
53
  if (status === "ready") {
54
+ const selected = input.planning?.selected[0];
55
+ const deferredCount = input.planning?.deferred.length ?? 0;
56
+ const blockedCount = input.planning?.blocked.length ?? 0;
57
+ const reasonSuffix = selected && (deferredCount > 0 || blockedCount > 0)
58
+ ? `;deferred ${deferredCount} / blocked ${blockedCount}`
59
+ : "";
54
60
  return {
55
61
  kind: "run_feature",
56
- label: "执行当前 Ready tasks",
62
+ label: selected
63
+ ? `执行最高优先级 Ready task ${selected.taskId} (${selected.priority})${reasonSuffix}`
64
+ : "执行当前 Ready tasks",
57
65
  command: `agent-worker batch run-ready ${featureArgs}`,
58
66
  };
59
67
  }
@@ -0,0 +1,81 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { planReadyTasks, } from "../task-graph/ready-planner.js";
5
+ import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
6
+ import { taskSpecSchema } from "../task-spec/schema.js";
7
+ /** Single Feature-side entry to M1 planReadyTasks; never reimplements ordering. */
8
+ export function projectReadyPlan(input) {
9
+ return planReadyTasks(input);
10
+ }
11
+ /**
12
+ * Apply a tighter selectionLimit to an existing planner projection.
13
+ * Eligible order is preserved from planReadyTasks; this does not re-sort.
14
+ */
15
+ export function applySelectionLimit(plan, selectionLimit) {
16
+ if (!Number.isInteger(selectionLimit) || selectionLimit < 1) {
17
+ throw new Error("selectionLimit must be a positive integer");
18
+ }
19
+ const selected = plan.eligible.slice(0, selectionLimit);
20
+ const deferred = plan.eligible.slice(selectionLimit).map((candidate) => ({
21
+ ...candidate,
22
+ reasonCode: "selection-limit",
23
+ selectedAhead: selected.map((item) => item.taskId),
24
+ }));
25
+ return {
26
+ ...plan,
27
+ selectionLimit,
28
+ selected,
29
+ deferred,
30
+ summary: {
31
+ ...plan.summary,
32
+ selected: selected.length,
33
+ deferred: deferred.length,
34
+ },
35
+ };
36
+ }
37
+ export function selectedTaskIds(plan) {
38
+ return plan.selected.map((item) => item.taskId);
39
+ }
40
+ export function summarizePlanReasons(plan) {
41
+ return {
42
+ selected: plan.selected.map((item) => ({ taskId: item.taskId, priority: item.priority })),
43
+ deferred: plan.deferred.map((item) => ({
44
+ taskId: item.taskId,
45
+ reasonCode: item.reasonCode,
46
+ selectedAhead: item.selectedAhead,
47
+ })),
48
+ blocked: plan.blocked.map((item) => ({
49
+ taskId: item.taskId,
50
+ reasonCode: item.reasonCode,
51
+ reason: item.reason,
52
+ blockedBy: item.blockedBy,
53
+ })),
54
+ };
55
+ }
56
+ export async function loadReadyPlanInputs(featureDir) {
57
+ const resolved = path.resolve(featureDir);
58
+ const graphRaw = YAML.parse(await readFile(path.join(resolved, "tasks", "task-graph.yaml"), "utf-8"));
59
+ const graph = taskGraphSpecSchema.parse(graphRaw);
60
+ const taskSpecs = new Map();
61
+ for (const node of graph.nodes) {
62
+ const value = YAML.parse(await readFile(path.join(resolved, "tasks", node.task), "utf-8"));
63
+ taskSpecs.set(node.id, taskSpecSchema.parse(value));
64
+ }
65
+ return { featureId: graph.feature_id, graph, taskSpecs };
66
+ }
67
+ export async function projectReadyPlanForFeature(input) {
68
+ try {
69
+ const loaded = await loadReadyPlanInputs(input.featureDir);
70
+ return projectReadyPlan({
71
+ featureId: input.featureId || loaded.featureId,
72
+ graph: loaded.graph,
73
+ taskSpecs: loaded.taskSpecs,
74
+ states: input.states,
75
+ selectionLimit: input.selectionLimit,
76
+ });
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
@@ -10,7 +10,7 @@ const STATUS_LABELS = {
10
10
  };
11
11
  export function reduceFeature(input) {
12
12
  const status = deriveStatus(input);
13
- const readyTasks = input.tasks.filter((task) => isReady(task.taskId, input));
13
+ const readyTasks = input.planning?.eligible ?? input.tasks.filter((task) => isReady(task.taskId, input));
14
14
  const blockingItems = [];
15
15
  const resolvedFailures = new Set(input.resolvedFailureTaskIds ?? []);
16
16
  for (const error of input.validationErrors) {
@@ -88,6 +88,7 @@ export function reduceFeature(input) {
88
88
  closeout: input.closeout?.path ?? null,
89
89
  },
90
90
  projectionWarnings: [...input.projectionWarnings],
91
+ ...(input.planning ? { planning: input.planning } : {}),
91
92
  };
92
93
  }
93
94
  function deriveStatus(input) {
@@ -3,12 +3,14 @@ import { access, readFile, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
5
5
  import { z } from "zod";
6
- import { getRunsJsonlPath, getTaskPoolRoot, readAllTaskPoolStates, readJsonlFile } from "../pool/run-store.js";
6
+ import { getRunsJsonlPath, getTaskPoolRoot, readFeatureTaskPoolStates, readJsonlFile } from "../pool/run-store.js";
7
7
  import { acceptanceSpecSchema } from "../task-graph/acceptance-schema.js";
8
8
  import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
9
+ import { taskSpecSchema } from "../task-spec/schema.js";
9
10
  import { validateFeatureTaskGraph } from "../task-graph/validate.js";
10
11
  import { followUpActionCardSchema, followUpApprovalSchema, followUpDraftSchema, followUpEvidenceSchema } from "../follow-up/schema.js";
11
12
  import { readFollowUpIndex, resolveRepoFile, sha256File as sha256FollowUpFile } from "../follow-up/store.js";
13
+ import { projectReadyPlan } from "./ready-plan-projection.js";
12
14
  import { reduceFeature } from "./reducer.js";
13
15
  export async function reviewFeature(input) {
14
16
  const projection = await loadFeatureProjection(input);
@@ -67,7 +69,7 @@ async function loadFeatureProjection(input) {
67
69
  let states = {};
68
70
  let runs = [];
69
71
  try {
70
- states = await readAllTaskPoolStates(repoRoot);
72
+ states = await readFeatureTaskPoolStates(repoRoot, validation.featureId);
71
73
  runs = await readJsonlFile(getRunsJsonlPath(repoRoot));
72
74
  }
73
75
  catch (error) {
@@ -86,6 +88,20 @@ async function loadFeatureProjection(input) {
86
88
  return valid;
87
89
  });
88
90
  const graphNodes = graph.success ? graph.data.nodes : [];
91
+ let planning;
92
+ if (graph.success) {
93
+ try {
94
+ const taskSpecs = new Map();
95
+ for (const node of graph.data.nodes) {
96
+ const value = YAML.parse(await readFile(path.join(featureDir, "tasks", node.task), "utf-8"));
97
+ taskSpecs.set(node.id, taskSpecSchema.parse(value));
98
+ }
99
+ planning = projectReadyPlan({ featureId: validation.featureId, graph: graph.data, taskSpecs, states, selectionLimit: Math.max(graph.data.nodes.length, 1) });
100
+ }
101
+ catch (error) {
102
+ projectionWarnings.push(`Ready Planner projection is unavailable: ${message(error)}`);
103
+ }
104
+ }
89
105
  let pendingFollowUps = [];
90
106
  let actionCards = [];
91
107
  let resolvedFailureTaskIds = [];
@@ -292,6 +308,7 @@ async function loadFeatureProjection(input) {
292
308
  ...((await exists(morningReportPath)) ? { morningReport: morningReportPath } : {}),
293
309
  ...((await exists(observeSnapshotPath)) ? { observeSnapshot: observeSnapshotPath } : {}),
294
310
  projectionWarnings,
311
+ ...(planning ? { planning } : {}),
295
312
  };
296
313
  }
297
314
  async function exists(filePath) {
@@ -5,11 +5,12 @@ import { buildGlobalSnapshot } from "../observability/read-model.js";
5
5
  import { createSnapshotStore } from "../observability/snapshot-store.js";
6
6
  import { preflightTargetRepo } from "../preflight.js";
7
7
  import { noopProgressReporter } from "../progress-reporter.js";
8
- import { getTaskPoolRoot } from "../pool/run-store.js";
8
+ import { getTaskPoolRoot, listLegacyStateFiles } from "../pool/run-store.js";
9
9
  import { writeMorningReport } from "../report/morning-report.js";
10
10
  import { buildBatchRunId, runReadyTasks } from "../runner/run-ready.js";
11
11
  import { validateFeatureTaskGraph } from "../task-graph/validate.js";
12
12
  import { reviewFeature } from "./review.js";
13
+ import { applySelectionLimit, selectedTaskIds } from "./ready-plan-projection.js";
13
14
  import { finalizeGitTask, startGitTransaction } from "../delivery/git-transaction.js";
14
15
  export async function runFeature(options) {
15
16
  const featureDir = path.resolve(options.featureDir);
@@ -125,7 +126,13 @@ export async function runFeature(options) {
125
126
  controllerIdentity });
126
127
  }
127
128
  before = beforeStep.value;
128
- const readyTasks = before.tasks.filter((task) => task.status === "Ready").map((task) => task.taskId);
129
+ const fullReadyPlan = before.planning;
130
+ const readyPlan = fullReadyPlan
131
+ ? applySelectionLimit(fullReadyPlan, limitApplied)
132
+ : undefined;
133
+ const readyTasks = readyPlan
134
+ ? selectedTaskIds(readyPlan)
135
+ : before.tasks.filter((task) => task.status === "Ready").map((task) => task.taskId);
129
136
  if (options.dryRun) {
130
137
  steps.push({ ...planned("run-ready", readyTasks.length > 0 ? `execute at most ${limitApplied} Ready task` : "no Ready task"), artifacts: [expectedArtifacts.batchRun] }, { ...planned("morning-report", "refresh morning report"), artifacts: [expectedArtifacts.morningReport] }, { ...planned("observe-snapshot", "refresh Observe snapshot"), artifacts: [expectedArtifacts.observeSnapshot] }, planned("review-after", "derive final Feature review"));
131
138
  return {
@@ -138,6 +145,7 @@ export async function runFeature(options) {
138
145
  statusAfter: before.status,
139
146
  steps,
140
147
  readyTasks,
148
+ ...(readyPlan ? { readyPlan } : {}),
141
149
  executedTasks: [],
142
150
  artifacts: { batchRun: null, morningReport: null, observeSnapshot: null, ...(checkpointMode ? { gitTransaction: expectedArtifacts.gitTransaction ?? null } : {}) },
143
151
  expectedArtifacts,
@@ -151,6 +159,22 @@ export async function runFeature(options) {
151
159
  };
152
160
  }
153
161
  if (readyTasks.length > 0 && limitApplied > 0) {
162
+ const legacyStates = await listLegacyStateFiles(repoRoot);
163
+ if (legacyStates.length > 0) {
164
+ return failedResult({
165
+ featureId,
166
+ mode: "executed",
167
+ steps,
168
+ limitApplied,
169
+ code: "task-pool-state-migration-required",
170
+ message: `cannot execute Ready tasks while legacy Task Pool states require migration: ${legacyStates.map((item) => item.taskId).join(", ")}`,
171
+ evidence: legacyStates.map((item) => item.path),
172
+ expectedArtifacts,
173
+ controllerIdentity,
174
+ before,
175
+ readyTasks,
176
+ });
177
+ }
154
178
  progress.batch(`feature ${featureId}: executing ${Math.min(readyTasks.length, limitApplied)} of ${readyTasks.length} Ready task(s)`);
155
179
  const runStep = await timedStep(steps, "run-ready", async () => dependencies.runReady({
156
180
  repoRoot,
@@ -226,6 +250,7 @@ export async function runFeature(options) {
226
250
  statusAfter: after.value.status,
227
251
  steps,
228
252
  readyTasks,
253
+ ...(readyPlan ? { readyPlan } : {}),
229
254
  executedTasks: batch?.tasks ?? [],
230
255
  artifacts: { batchRun: batch?.batchRunPath ?? null, morningReport: report.value, observeSnapshot: snapshot.value, ...(gitTransaction ? { gitTransaction: gitTransaction.recordPath } : {}) },
231
256
  expectedArtifacts,
@@ -99,6 +99,8 @@ async function executeApproval(input) {
99
99
  }
100
100
  await input.faultInjector?.("before-state-write");
101
101
  await writeTaskPoolState(input.repoRoot, {
102
+ schemaVersion: 2,
103
+ featureId: input.featureId,
102
104
  taskId: loaded.draft.proposed_task_id,
103
105
  status: "Ready",
104
106
  updatedAt: (input.now ?? new Date()).toISOString(),
@@ -201,7 +203,7 @@ async function loadApprovalInputs(input) {
201
203
  entry, draft, draftPath, evidencePath, approvalPath, existingApproval,
202
204
  taskSpecPath: path.join(input.featureDir, "tasks", `${draft.proposed_task_id}.yaml`),
203
205
  graphPath: path.join(input.featureDir, "tasks", "task-graph.yaml"),
204
- statePath: getTaskStatePath(input.repoRoot, draft.proposed_task_id),
206
+ statePath: getTaskStatePath(input.repoRoot, { featureId: input.featureId, taskId: draft.proposed_task_id }),
205
207
  featureId: input.featureId,
206
208
  featureDir: input.featureDir,
207
209
  repoRoot: input.repoRoot,
@@ -237,8 +239,9 @@ async function assertApprovalPreconditions(loaded) {
237
239
  const graph = taskGraphSpecSchema.parse(YAML.parse(await readFile(loaded.graphPath, "utf-8")));
238
240
  if (graph.nodes.some((node) => node.id === loaded.draft.proposed_task_id))
239
241
  throw new Error(`proposed graph node already exists: ${loaded.draft.proposed_task_id}`);
240
- if (await readTaskPoolState(loaded.repoRoot, loaded.draft.proposed_task_id))
242
+ if (await readTaskPoolState(loaded.repoRoot, { featureId: loaded.featureId, taskId: loaded.draft.proposed_task_id })) {
241
243
  throw new Error(`Task Pool state already exists: ${loaded.draft.proposed_task_id}`);
244
+ }
242
245
  }
243
246
  function resultFromApproval(approval, repoRoot, approvalPath, idempotent) {
244
247
  return {
@@ -99,7 +99,7 @@ export async function draftFollowUpDecision(input, productBugOnly = false) {
99
99
  generated_from: { feature_id: run.featureId, task_id: input.taskId, worker_run_id: input.workerRunId, failure_category: category },
100
100
  action: policy.action,
101
101
  label: policy.label,
102
- ...(policy.recommendedCommand === "task-retry" ? { command: `agent-worker task retry ${JSON.stringify(input.taskId)} --repo ${JSON.stringify(repoRoot)}` } : {}),
102
+ ...(policy.recommendedCommand === "task-retry" ? { command: `agent-worker task retry ${JSON.stringify(input.taskId)} --feature-id ${JSON.stringify(run.featureId)} --repo ${JSON.stringify(repoRoot)}` } : {}),
103
103
  evidence_refs: evidenceRefs,
104
104
  dedupe_key: dedupeKey,
105
105
  created_at: (input.now ?? new Date()).toISOString(),