@tea-agent/loop-agent 0.8.0 → 0.10.0-alpha.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 (188) hide show
  1. package/AGENTS.md +10 -0
  2. package/CHANGELOG.md +101 -1
  3. package/README.md +69 -5
  4. package/dist/application/dag/args.js +13 -16
  5. package/dist/application/dag/generate-task-dag.js +32 -2
  6. package/dist/application/dag/run-dag.js +1 -27
  7. package/dist/application/dag/validate-dag.js +2 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +7 -11
  10. package/dist/cli/program.js +9 -21
  11. package/dist/commands/cursor-prompt.js +42 -82
  12. package/dist/commands/dag-approve.js +36 -0
  13. package/dist/commands/dag-reconcile-run.js +118 -0
  14. package/dist/commands/delegate.js +75 -77
  15. package/dist/commands/doctor.js +0 -18
  16. package/dist/commands/init.js +60 -40
  17. package/dist/commands/instructions.js +7 -10
  18. package/dist/commands/loop.js +4 -20
  19. package/dist/executors/config-core.js +0 -51
  20. package/dist/executors/dag-pi-executor.js +1 -1
  21. package/dist/executors/dag.js +0 -1
  22. package/dist/executors/index.js +0 -2
  23. package/dist/executors/model-routing.js +9 -9
  24. package/dist/executors/shell-executor.js +75 -9
  25. package/dist/governance/checks.js +6 -3
  26. package/dist/governance/manifest-types.js +33 -2
  27. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  28. package/dist/records/harvest.js +2 -23
  29. package/dist/records/one-shot-runs.js +1 -1
  30. package/dist/shared/artifacts-core.js +24 -5
  31. package/dist/shared/output-truncation.js +37 -0
  32. package/dist/shared/package-metadata.js +353 -0
  33. package/dist/shared/reference-context.js +48 -22
  34. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  35. package/dist/sidecars/cursor-prompt/index.js +3 -0
  36. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  37. package/dist/task/config-types.js +30 -13
  38. package/dist/task/delegate.js +9 -21
  39. package/dist/task/runtime.js +2 -3
  40. package/dist/worker/cli.js +243 -0
  41. package/dist/worker/closeout/apply.js +73 -0
  42. package/dist/worker/closeout/preview.js +30 -0
  43. package/dist/worker/delivery/final-verification.js +194 -0
  44. package/dist/worker/delivery/git-transaction.js +354 -0
  45. package/dist/worker/delivery/package.js +502 -0
  46. package/dist/worker/feature/decision-loader.js +68 -0
  47. package/dist/worker/feature/discover.js +14 -0
  48. package/dist/worker/feature/next-action.js +74 -0
  49. package/dist/worker/feature/reducer.js +133 -0
  50. package/dist/worker/feature/review.js +502 -0
  51. package/dist/worker/feature/run.js +365 -0
  52. package/dist/worker/feature/types.js +1 -0
  53. package/dist/worker/follow-up/approve.js +270 -0
  54. package/dist/worker/follow-up/factory.js +234 -0
  55. package/dist/worker/follow-up/paths.js +25 -0
  56. package/dist/worker/follow-up/policy.js +26 -0
  57. package/dist/worker/follow-up/schema.js +93 -0
  58. package/dist/worker/follow-up/store.js +96 -0
  59. package/dist/worker/loop-agent/loop-agent-client.js +345 -101
  60. package/dist/worker/metrics/projector.js +139 -0
  61. package/dist/worker/observability/read-model.js +282 -15
  62. package/dist/worker/observe/paths.js +17 -5
  63. package/dist/worker/observe/routes.js +78 -20
  64. package/dist/worker/observe/server.js +8 -6
  65. package/dist/worker/observe/static/app.js +1045 -177
  66. package/dist/worker/observe/static/index.html +70 -43
  67. package/dist/worker/observe/static/styles.css +553 -610
  68. package/dist/worker/pool/run-store.js +14 -2
  69. package/dist/worker/pool/validation.js +59 -0
  70. package/dist/worker/preflight.js +49 -1
  71. package/dist/worker/report/morning-report.js +41 -6
  72. package/dist/worker/run-task/run-task.js +23 -13
  73. package/dist/worker/runner/run-ready.js +89 -11
  74. package/dist/worker/task-spec/schema.js +0 -1
  75. package/dist/workflows/dag/convergence/controller.js +1 -1
  76. package/dist/workflows/dag/executor-registry.js +0 -2
  77. package/dist/workflows/dag/governance-profile.js +10 -0
  78. package/dist/workflows/dag/init-hybrid.js +601 -26
  79. package/dist/workflows/dag/lifecycle.js +146 -0
  80. package/dist/workflows/dag/node-execution.js +64 -7
  81. package/dist/workflows/dag/prompt.js +16 -0
  82. package/dist/workflows/dag/report.js +2 -0
  83. package/dist/workflows/dag/runner.js +176 -119
  84. package/dist/workflows/dag/scheduler.js +7 -2
  85. package/dist/workflows/dag/skill-snapshot.js +527 -0
  86. package/dist/workflows/dag/types.js +45 -9
  87. package/dist/workflows/dag/validate.js +5 -8
  88. package/dist/workflows/loop/actions/dag-action.js +0 -2
  89. package/dist/workflows/loop/actions/shared.js +1 -1
  90. package/dist/workflows/loop/actions.js +14 -31
  91. package/dist/workflows/loop/benchmark.js +1 -1
  92. package/dist/workflows/loop/index.js +1 -1
  93. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  94. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  95. package/docs/README.md +35 -12
  96. package/docs/agent-dag-recovery-playbook.md +1 -1
  97. package/docs/architecture/README.md +26 -0
  98. package/docs/architecture/dag-execution.md +134 -0
  99. package/docs/architecture/evolution.md +52 -0
  100. package/docs/architecture/facts-and-state.md +58 -0
  101. package/docs/architecture/runtime-boundaries.md +41 -15
  102. package/docs/architecture/system-overview.md +93 -0
  103. package/docs/architecture/worker-and-feature.md +81 -0
  104. package/docs/cursor-prompt-sidecar.md +36 -0
  105. package/docs/decisions/README.md +13 -1
  106. package/docs/design/README.md +39 -13
  107. package/docs/development-principles.md +1 -1
  108. package/docs/exec-plans/active/README.md +2 -2
  109. package/docs/exec-plans/completed/README.md +21 -0
  110. package/docs/feature-workflow.md +44 -4
  111. package/docs/init-surface.manifest.json +63 -1
  112. package/docs/loop-agent-harness.md +65 -3
  113. package/docs/progress/README.md +27 -0
  114. package/docs/reports/README.md +74 -5
  115. package/docs/skills/README.md +2 -1
  116. package/docs/skills/vetted-skill-registry.md +2 -1
  117. package/docs/templates/agent-dag-report.schema.json +4 -2
  118. package/docs/templates/agent-dag.base.json +0 -5
  119. package/docs/templates/agent-dag.final-verification.json +0 -5
  120. package/docs/templates/agent-dag.schema.json +1 -2
  121. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  122. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
  123. package/docs/templates/backend-test-dag.json +213 -0
  124. package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
  125. package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
  126. package/docs/templates/frontend-design-contract.md +33 -0
  127. package/docs/templates/frontend-task-constraints.md +25 -0
  128. package/docs/templates/frontend-task-requirement.md +61 -0
  129. package/docs/templates/harness.schema.json +8 -5
  130. package/docs/templates/hybrid-dag.json +1 -6
  131. package/docs/templates/init-evolution-review.md +4 -2
  132. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  133. package/docs/templates/product-line/task.yaml +0 -1
  134. package/docs/templates/worker-dogfood-evidence.md +28 -0
  135. package/docs/templates/worker-dogfood-setup.md +20 -0
  136. package/docs/verification-matrix.md +17 -0
  137. package/examples/decision-gate-agent-dag.json +87 -33
  138. package/examples/example-dag.json +0 -5
  139. package/examples/hybrid-loop-agent-dag.json +0 -5
  140. package/harness.json +6 -11
  141. package/package.json +22 -44
  142. package/scripts/check-product-line-docs.sh +10 -3
  143. package/scripts/check-task-pool-root.sh +1 -1
  144. package/skills/agent-worker/SKILL.md +37 -0
  145. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  146. package/skills/frontend-design-review/SKILL.md +59 -0
  147. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  148. package/skills/frontend-implementation/SKILL.md +48 -0
  149. package/skills/frontend-implementation/references/code-standards.md +34 -0
  150. package/skills/frontend-implementation/references/design-spec.md +46 -0
  151. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  152. package/skills/frontend-review/SKILL.md +53 -0
  153. package/skills/frontend-review/references/review-findings.md +42 -0
  154. package/skills/frontend-verification/SKILL.md +40 -0
  155. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  156. package/skills/grill-me/SKILL.md +10 -0
  157. package/skills/grill-with-docs/SKILL.md +88 -0
  158. package/skills/grill-with-docs/adr-format.md +47 -0
  159. package/skills/grill-with-docs/context-format.md +60 -0
  160. package/skills/init-capability-evolution/SKILL.md +1 -0
  161. package/skills/loop-agent/SKILL.md +11 -9
  162. package/skills/loop-agent/references/command-reference.md +28 -15
  163. package/skills/loop-agent/references/docs-converge.md +126 -0
  164. package/skills/loop-agent/references/harness-policy.md +7 -7
  165. package/skills/loop-agent/references/hybrid-dag.md +13 -15
  166. package/skills/loop-agent/references/long-running-loop.md +4 -6
  167. package/skills/loop-agent/references/multi-worktree.md +6 -6
  168. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  169. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  170. package/skills/loop-agent/references/verification-and-failure-handling.md +8 -3
  171. package/skills/using-git-worktrees/SKILL.md +215 -0
  172. package/dist/commands/cursor-worker.js +0 -43
  173. package/dist/cursor-worker-entry.js +0 -8
  174. package/dist/executors/cursor-artifacts.js +0 -33
  175. package/dist/executors/cursor-execution-log.js +0 -81
  176. package/dist/executors/cursor-executor-artifacts.js +0 -134
  177. package/dist/executors/cursor-run.js +0 -115
  178. package/dist/executors/cursor-tool.js +0 -94
  179. package/dist/executors/cursor-worker-client.js +0 -223
  180. package/dist/executors/cursor-worker-protocol.js +0 -18
  181. package/dist/executors/cursor-worker-server.js +0 -54
  182. package/dist/executors/cursor-worker.js +0 -3
  183. package/dist/executors/cursor.js +0 -6
  184. package/dist/executors/dag-cursor-executor.js +0 -87
  185. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  186. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  187. package/docs/cursor-executor-usage.md +0 -25
  188. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -0,0 +1,3 @@
1
+ export { DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_IDLE_TIMEOUT_MS, WAIT_AFTER_STREAM_GRACE_MS, MAX_CURSOR_STDOUT_BYTES, MAX_CURSOR_STDERR_BYTES, buildCursorPrompt, buildArtifactPathPrompt, resolveArtifactWriteDir, buildRedactedExecutorRecord, buildCursorExecutorEnv, classifyCursorFailure, mapRunStatusToExecutionStatus, CursorTimeoutError, isCursorTimeoutError, withCursorTimeout, buildStreamIdleTimeoutMessage, bestEffortCancelRun, extractCursorTokenUsage, executeCursorTask, redactApiKey, } from "./executor.js";
2
+ export { truncateOutput, redactPromptForLog, } from "../../shared/output-truncation.js";
3
+ export { executeCursorPromptStream, } from "./stream.js";
@@ -0,0 +1,121 @@
1
+ import { bestEffortCancelRun, buildCursorPrompt, classifyCursorFailure, mapRunStatusToExecutionStatus, } from "./executor.js";
2
+ async function createCursorStreamAgent(input) {
3
+ const { Agent } = await import("@cursor/sdk");
4
+ return Agent.create({
5
+ apiKey: input.apiKey,
6
+ name: "pi-cursor-prompt",
7
+ model: { id: input.model },
8
+ local: { cwd: input.cwd },
9
+ });
10
+ }
11
+ /**
12
+ * One-shot streaming path for cursor-prompt CLI. Loads @cursor/sdk only here.
13
+ */
14
+ export async function executeCursorPromptStream(input) {
15
+ const apiKey = input.apiKey ?? process.env.CURSOR_API_KEY;
16
+ if (!apiKey?.trim()) {
17
+ return {
18
+ ok: false,
19
+ status: "failed",
20
+ failureCategory: "missing-api-key",
21
+ model: input.model,
22
+ durationMs: 0,
23
+ stdout: "",
24
+ stderr: "CURSOR_API_KEY environment variable is not set.",
25
+ details: { startupError: true },
26
+ };
27
+ }
28
+ const prompt = buildCursorPrompt(input.task, { runDir: input.runDir });
29
+ const startedAt = Date.now();
30
+ let timeoutHandle;
31
+ let agent;
32
+ let run;
33
+ let timedOut = false;
34
+ try {
35
+ agent = await (input.agentFactory ?? createCursorStreamAgent)({
36
+ apiKey,
37
+ cwd: input.cwd,
38
+ model: input.model,
39
+ });
40
+ const runPromise = (async () => {
41
+ run = await agent.send(prompt);
42
+ for await (const event of run.stream()) {
43
+ if (event.type === "assistant") {
44
+ for (const block of event.message.content) {
45
+ if (block.type === "text" && block.text) {
46
+ input.callbacks?.onAssistantText?.(block.text);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return run.wait();
52
+ })();
53
+ const timeoutRace = new Promise((resolve) => {
54
+ timeoutHandle = setTimeout(() => resolve("timeout"), input.timeoutMs);
55
+ });
56
+ const raced = await Promise.race([
57
+ runPromise.then((value) => ({ kind: "done", value })),
58
+ timeoutRace.then(() => ({ kind: "timeout" })),
59
+ ]);
60
+ if (raced.kind === "timeout") {
61
+ timedOut = true;
62
+ await bestEffortCancelRun(run);
63
+ return {
64
+ ok: false,
65
+ status: "timeout",
66
+ failureCategory: "timeout",
67
+ model: input.model,
68
+ durationMs: Date.now() - startedAt,
69
+ stdout: "",
70
+ stderr: `Cursor execution timed out after ${input.timeoutMs}ms`,
71
+ details: { timedOut: true, timeoutKind: "total" },
72
+ };
73
+ }
74
+ const sdkResult = raced.value;
75
+ const runStatus = String(sdkResult.status ?? "");
76
+ const status = mapRunStatusToExecutionStatus(runStatus, false);
77
+ const ok = status === "completed";
78
+ return {
79
+ ok,
80
+ status,
81
+ failureCategory: ok
82
+ ? "success"
83
+ : classifyCursorFailure({ runStatus, stderr: "", stdout: "" }),
84
+ model: input.model,
85
+ durationMs: sdkResult.durationMs ?? Date.now() - startedAt,
86
+ stdout: "",
87
+ stderr: "",
88
+ };
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ return {
93
+ ok: false,
94
+ status: "failed",
95
+ failureCategory: classifyCursorFailure({
96
+ startupError: message,
97
+ stderr: message,
98
+ }),
99
+ model: input.model,
100
+ durationMs: Date.now() - startedAt,
101
+ stdout: "",
102
+ stderr: message,
103
+ details: { startupError: true },
104
+ };
105
+ }
106
+ finally {
107
+ if (timeoutHandle)
108
+ clearTimeout(timeoutHandle);
109
+ if (!timedOut && run?.status === "running") {
110
+ await bestEffortCancelRun(run);
111
+ }
112
+ if (agent) {
113
+ try {
114
+ await agent[Symbol.asyncDispose]();
115
+ }
116
+ catch {
117
+ // The one-shot result remains authoritative; disposal is best effort.
118
+ }
119
+ }
120
+ }
121
+ }
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { taskExecutorSchema } from "../governance/manifest-types.js";
3
2
  export const taskFlowSchema = z.enum([
4
3
  "auto",
5
4
  "micro",
@@ -8,7 +7,12 @@ export const taskFlowSchema = z.enum([
8
7
  "loop",
9
8
  "feature-study",
10
9
  ]);
11
- export const taskKindSchema = z.enum(["standard", "feature-study"]);
10
+ export const taskKindSchema = z.enum([
11
+ "standard",
12
+ "feature-study",
13
+ "frontend-implementation",
14
+ "backend-test",
15
+ ]);
12
16
  export const referenceRepoConfigSchema = z.object({
13
17
  name: z.string().min(1),
14
18
  path: z.string().min(1),
@@ -40,11 +44,13 @@ export const dagVerifyStrategySchema = z
40
44
  .default("adapter"),
41
45
  })
42
46
  .optional();
43
- export const loopAutoWritePolicySchema = z.enum([
47
+ export const loopAutoExecutionPolicySchema = z.enum([
44
48
  "off",
45
49
  "approval-required",
46
50
  "enabled",
47
51
  ]);
52
+ export const LOOP_AUTO_WRITE_POLICY_REMOVED_ERROR = "task field loopAutoWritePolicy is no longer supported; use loopAutoExecutionPolicy (off | approval-required | enabled)";
53
+ export const CURSOR_TASK_FIELD_REMOVED_ERROR = 'task fields "executor" and "cursorModel" are no longer supported; governed runtime is Pi-only';
48
54
  export const convergenceConfigSchema = z.object({
49
55
  enabled: z.boolean().optional().default(false),
50
56
  maxPasses: z.number().int().positive().optional().default(3),
@@ -52,11 +58,11 @@ export const convergenceConfigSchema = z.object({
52
58
  stopOnHardVerifyPass: z.boolean().optional().default(true),
53
59
  pauseOnRegression: z.boolean().optional().default(true),
54
60
  });
55
- export const taskConfigSchema = z.object({
61
+ const taskConfigObjectSchema = z.object({
56
62
  taskId: z.string(),
57
63
  title: z.string(),
58
64
  sourceFiles: z.array(z.string()),
59
- /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地 */
65
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 使用前端实现 DAG 模板 */
60
66
  taskKind: taskKindSchema.optional().default("standard"),
61
67
  referenceRepos: z.array(referenceRepoConfigSchema).optional().default([]),
62
68
  referenceDocs: z.array(referenceDocConfigSchema).optional().default([]),
@@ -67,7 +73,7 @@ export const taskConfigSchema = z.object({
67
73
  hardConstraints: z.array(z.string()).optional().default([]),
68
74
  autoCommitAfterVerify: z.boolean().optional().default(true),
69
75
  autoCommitMessage: z.string().optional().default(""),
70
- /** Explicit reason that a medium/large task could not use DAG before loop cursor-fix. */
76
+ /** Explicit reason that a medium/large task could not use DAG before loop auto execution. */
71
77
  dagFallbackReason: z.string().optional(),
72
78
  /** full: attach governance bundle; slim: analyze/plan 只带最少仓库上下文(source + harness 等) */
73
79
  contextProfile: contextProfileSchema.optional().default("full"),
@@ -76,7 +82,7 @@ export const taskConfigSchema = z.object({
76
82
  complexity: taskComplexitySchema.optional().default("medium"),
77
83
  /** Explicit delivery capabilities that affect writer contracts/model tier without changing risk. */
78
84
  capabilities: z.array(taskCapabilitySchema).optional(),
79
- verifyMode: verifyModeSchema.optional().default("parallel"),
85
+ verifyMode: verifyModeSchema.optional().default("serial"),
80
86
  verifyPreset: verifyPresetSchema.optional().default("auto"),
81
87
  /** Task-contract commands that must be included in final DAG shell verification. */
82
88
  verifyCommands: z.array(taskVerifyCommandSchema).optional().default([]),
@@ -90,8 +96,10 @@ export const taskConfigSchema = z.object({
90
96
  maxFixLoops: z.number().int().min(0).optional().default(2),
91
97
  /** Supervised DAG convergence is opt-in until runtime smoke evidence is stronger. */
92
98
  convergence: convergenceConfigSchema.optional().default({ enabled: false }),
93
- /** Outer loop auto mode never writes by default; cursor-fix requires this policy plus bounded path guards. */
94
- loopAutoWritePolicy: loopAutoWritePolicySchema.optional().default("off"),
99
+ /** Outer loop auto mode never writes by default; DAG execute requires this policy plus path/writeSet gates. */
100
+ loopAutoExecutionPolicy: loopAutoExecutionPolicySchema
101
+ .optional()
102
+ .default("off"),
95
103
  requireRetrospective: z.boolean().optional().default(false),
96
104
  /** inherit: use host shell env; clean: sanitized env for deterministic verify */
97
105
  verifyEnv: z.enum(["inherit", "clean"]).optional().default("clean"),
@@ -99,9 +107,18 @@ export const taskConfigSchema = z.object({
99
107
  maxGoalContinuationsPerRun: z.number().int().positive().optional().default(5),
100
108
  /** Pi subagent assisted mode: 'off' (default), 'analyze-plan', or 'full' */
101
109
  piSubagentMode: piSubagentModeSchema.optional().default("off"),
102
- /** Leaf executor: default pi; cursor requires delegate + worktree isolation */
103
- executor: taskExecutorSchema.optional().default("pi"),
104
- /** Cursor model override (e.g. gpt-5.5); falls back to harness executors.cursor.defaultModel */
105
- cursorModel: z.string().optional(),
106
110
  notes: z.string().optional().default(""),
107
111
  });
112
+ export const taskConfigSchema = z.preprocess((raw) => {
113
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
114
+ return raw;
115
+ }
116
+ const obj = raw;
117
+ if ("loopAutoWritePolicy" in obj) {
118
+ throw new Error(LOOP_AUTO_WRITE_POLICY_REMOVED_ERROR);
119
+ }
120
+ if ("executor" in obj || "cursorModel" in obj) {
121
+ throw new Error(CURSOR_TASK_FIELD_REMOVED_ERROR);
122
+ }
123
+ return raw;
124
+ }, taskConfigObjectSchema);
@@ -9,31 +9,24 @@ import { getTaskPaths, loadTaskConfig } from './runtime.js';
9
9
  import { copyDir } from '../records/harvest.js';
10
10
  import { saveWorkflowState } from './state.js';
11
11
  import { workflowStateSchema } from '../shared/types.js';
12
- import { assertCursorExecutorAvailable, resolveTaskExecutor, validateCursorTaskPreflight } from '../executors/config-core.js';
13
- import { resolveDelegateExecutionMode } from '../executors/cursor-execution-log.js';
14
12
  /**
15
13
  * Atomic delegation: validate source → check conflicts → create worktree →
16
14
  * sync source → optionally symlink node_modules.
17
15
  *
18
- * Execution runs in-process for Cursor SDK; Pi worktrees are prepared for manual DAG follow-up.
16
+ * Default is isolation only; callers may run Pi DAG via application use cases.
19
17
  */
20
18
  export async function delegateTask(repoRoot, manifest, taskId, opts = {}) {
21
19
  const symlinkEnabled = opts.symlinkNodeModules !== false;
22
20
  await validateSourceMaterials(repoRoot, taskId);
23
- const taskConfig = await loadTaskConfig(repoRoot, taskId);
21
+ await loadTaskConfig(repoRoot, taskId);
24
22
  const effectiveManifest = manifest ?? (await loadHarnessManifest(repoRoot));
25
- const executor = resolveTaskExecutor(taskConfig, opts.executor);
26
- if (executor === 'cursor') {
27
- assertCursorExecutorAvailable(effectiveManifest);
28
- validateCursorTaskPreflight(taskConfig);
29
- }
30
23
  await validateNoConflicts(repoRoot, taskId, opts.branch);
31
24
  const worktree = await createWorktree(repoRoot, effectiveManifest, {
32
25
  taskId,
33
26
  branch: opts.branch,
34
27
  base: opts.base,
35
28
  });
36
- await syncSourceToWorktree(repoRoot, taskId, worktree.path, executor);
29
+ await syncSourceToWorktree(repoRoot, taskId, worktree.path);
37
30
  let symlinkedNodeModules = false;
38
31
  if (symlinkEnabled) {
39
32
  symlinkedNodeModules = await setupNodeModulesSymlink(repoRoot, worktree.path);
@@ -46,13 +39,9 @@ export async function delegateTask(repoRoot, manifest, taskId, opts = {}) {
46
39
  branch: worktree.branch,
47
40
  baseBranch: worktree.base,
48
41
  symlinkedNodeModules,
49
- executor,
50
- executionMode: resolveDelegateExecutionMode(executor),
42
+ executionMode: 'pi-worktree',
51
43
  };
52
44
  }
53
- // ---------------------------------------------------------------------------
54
- // Internal helpers
55
- // ---------------------------------------------------------------------------
56
45
  async function validateSourceMaterials(repoRoot, taskId) {
57
46
  const { taskConfigPath, sourceDir } = getTaskPaths(repoRoot, taskId);
58
47
  try {
@@ -96,7 +85,7 @@ async function validateNoConflicts(repoRoot, taskId, branch) {
96
85
  console.error(`[delegate] warning: branch "${targetBranch}" already exists; will reuse it for the worktree.`);
97
86
  }
98
87
  }
99
- async function syncSourceToWorktree(repoRoot, taskId, worktreePath, executor) {
88
+ async function syncSourceToWorktree(repoRoot, taskId, worktreePath) {
100
89
  const srcPaths = getTaskPaths(repoRoot, taskId);
101
90
  const destPaths = getTaskPaths(worktreePath, taskId);
102
91
  const destTaskDir = destPaths.taskDir;
@@ -106,16 +95,15 @@ async function syncSourceToWorktree(repoRoot, taskId, worktreePath, executor) {
106
95
  await copyDir(srcPaths.sourceDir, destSourceDir);
107
96
  const rawJson = await readFile(srcPaths.taskConfigPath, 'utf-8');
108
97
  const parsedTaskConfig = JSON.parse(rawJson);
109
- parsedTaskConfig.executor = executor;
110
98
  await mkdir(path.dirname(destTaskConfigPath), { recursive: true });
111
99
  await writeTaskConfig(worktreePath, taskId, parsedTaskConfig);
112
100
  await mkdir(destPaths.logsDir, { recursive: true });
113
101
  await initializeArtifacts(destTaskDir);
114
102
  await initializeTaskLogStubs(worktreePath, taskId);
115
- const state = await loadOrCreateWorkflowState(srcPaths.statePath, taskId, executor);
103
+ const state = await loadOrCreateWorkflowState(srcPaths.statePath, taskId);
116
104
  await saveWorkflowState(destPaths.statePath, state);
117
105
  }
118
- async function loadOrCreateWorkflowState(statePath, taskId, executor) {
106
+ async function loadOrCreateWorkflowState(statePath, taskId) {
119
107
  try {
120
108
  const raw = await readFile(statePath, 'utf-8');
121
109
  return workflowStateSchema.parse(JSON.parse(raw));
@@ -129,8 +117,8 @@ async function loadOrCreateWorkflowState(statePath, taskId, executor) {
129
117
  completedSteps: [],
130
118
  lastUpdated: now,
131
119
  executor: {
132
- name: executor,
133
- mode: executor === 'cursor' ? 'sdk' : 'json',
120
+ name: 'pi',
121
+ mode: 'json',
134
122
  },
135
123
  artifacts: {
136
124
  analysis: false,
@@ -102,7 +102,7 @@ export async function createTask(repoRoot, taskId, title) {
102
102
  complexity: 'medium',
103
103
  dagFallbackReason: '',
104
104
  contextProfile: 'full',
105
- verifyMode: 'parallel',
105
+ verifyMode: 'serial',
106
106
  verifyPreset: 'auto',
107
107
  verifyCommands: [],
108
108
  verifyQuota: 'full',
@@ -117,11 +117,10 @@ export async function createTask(repoRoot, taskId, title) {
117
117
  stopOnHardVerifyPass: true,
118
118
  pauseOnRegression: true,
119
119
  },
120
- loopAutoWritePolicy: 'off',
120
+ loopAutoExecutionPolicy: 'off',
121
121
  verifyEnv: 'clean',
122
122
  maxGoalContinuationsPerRun: 5,
123
123
  piSubagentMode: 'off',
124
- executor: 'pi',
125
124
  notes: '',
126
125
  };
127
126
  const state = {
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Command } from "commander";
6
+ import { readPackageVersion } from "../shared/package-metadata.js";
6
7
  import YAML from "yaml";
7
8
  import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
8
9
  import { resolveLoopAgentProfile } from "./profile-mapping.js";
@@ -17,17 +18,158 @@ import { getTaskPoolRoot, prepareTaskPoolRetry } from "./pool/run-store.js";
17
18
  import { taskSpecSchema } from "./task-spec/schema.js";
18
19
  import { validateTaskSpec } from "./task-spec/validate.js";
19
20
  import { validateFeatureTaskGraph } from "./task-graph/validate.js";
21
+ import { renderFeatureReview, reviewFeature } from "./feature/review.js";
22
+ import { renderFeatureRun, runFeature } from "./feature/run.js";
23
+ import { draftFollowUpDecision } from "./follow-up/factory.js";
24
+ import { approveFollowUp } from "./follow-up/approve.js";
25
+ import { prepareFeatureDelivery } from "./delivery/package.js";
26
+ import { previewFeatureCloseout, renderCloseoutPreview } from "./closeout/preview.js";
27
+ import { applyFeatureCloseout } from "./closeout/apply.js";
28
+ import { projectMonthlyMetrics } from "./metrics/projector.js";
29
+ import { runFeatureFinalVerification } from "./delivery/final-verification.js";
20
30
  export function buildAgentWorkerProgram() {
21
31
  const program = new Command();
22
32
  program
23
33
  .name("agent-worker")
24
34
  .description("Local product-line worker utilities for TaskSpec validation")
35
+ .version(readPackageVersion(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
25
36
  .showHelpAfterError()
26
37
  .showSuggestionAfterError();
27
38
  const task = program.command("task").description("TaskSpec utilities");
28
39
  const batch = program.command("batch").description("Task Pool batch utilities");
29
40
  const report = program.command("report").description("Task Pool reporting utilities");
30
41
  const observe = program.command("observe").description("Observe UI server and snapshot utilities");
42
+ const feature = program.command("feature").description("Feature-level review and delivery workflow");
43
+ feature
44
+ .command("review")
45
+ .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
46
+ .requiredOption("--repo <repo-root>", "Target repo root")
47
+ .option("--json", "Emit stable JSON instead of the human summary")
48
+ .description("Derive Feature status, blockers, acceptance coverage, and next action")
49
+ .action(async (options) => {
50
+ const result = await reviewFeature({
51
+ featureDir: path.resolve(options.featureDir),
52
+ repoRoot: path.resolve(options.repo),
53
+ });
54
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : renderFeatureReview(result));
55
+ });
56
+ feature
57
+ .command("run")
58
+ .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
59
+ .requiredOption("--repo <repo-root>", "Target repo root")
60
+ .option("--dry-run", "Show validation, Ready tasks, steps, and expected artifacts without writes")
61
+ .option("--json", "Emit stable JSON instead of the human summary")
62
+ .option("--limit <count>", "Maximum Ready tasks (without checkpoint mode capped at 1)", "1")
63
+ .option("--git-mode <mode>", "Git mode: none or checkpoint", "none")
64
+ .option("--keep-failed-diff", "Keep a failed task diff and stop instead of restoring")
65
+ .option("--loop-agent-bin <bin>", "loop-agent binary", "loop-agent")
66
+ .option("--batch-run-id <id>", "Batch run id")
67
+ .option("--check-repo", "Run bash scripts/check-repo.sh during target repo preflight")
68
+ .option("--check-repo-command <command...>", "Override the check-repo preflight command")
69
+ .option("--quiet", "Suppress human-readable progress on stderr")
70
+ .option("--pi-model <model>", "Smoke override for pi executor nodes")
71
+ .option("--expected-controller-version <version>", "Exact expected controller version (fail before writes if mismatched)")
72
+ .option("--expected-controller-fingerprint <value>", "Exact expected controller fingerprint sha256:<hex> (fail before writes if mismatched)")
73
+ .description("Validate and advance one Ready Feature task through the existing Worker pipeline")
74
+ .action(async (options) => {
75
+ if (options.gitMode !== "none" && options.gitMode !== "checkpoint")
76
+ throw new Error("feature run --git-mode must be none or checkpoint");
77
+ if (options.keepFailedDiff && options.gitMode !== "checkpoint")
78
+ throw new Error("--keep-failed-diff requires --git-mode checkpoint");
79
+ const repoRoot = path.resolve(options.repo);
80
+ const batchRunId = options.batchRunId ?? buildBatchRunId(new Date());
81
+ const expectedIdentity = buildIdentityExpectation(options);
82
+ const client = new LoopAgentClient({
83
+ loopAgentBin: options.loopAgentBin,
84
+ artifactRoot: path.join(getTaskPoolRoot(repoRoot), "artifacts", batchRunId),
85
+ resolveIdentity: true,
86
+ });
87
+ let progress;
88
+ try {
89
+ progress = createCompositeProgressReporter({
90
+ quiet: options.quiet ?? options.dryRun ?? false,
91
+ eventStore: createRoutedWorkerEventStore(repoRoot),
92
+ context: { batchRunId },
93
+ });
94
+ }
95
+ catch {
96
+ progress = createProgressReporter({ quiet: options.quiet ?? options.dryRun ?? false });
97
+ }
98
+ const result = await runFeature({
99
+ featureDir: path.resolve(options.featureDir),
100
+ repoRoot,
101
+ client,
102
+ dryRun: options.dryRun ?? false,
103
+ limit: Number.parseInt(options.limit, 10),
104
+ batchRunId,
105
+ runCheckRepo: options.checkRepo ?? false,
106
+ ...(options.checkRepoCommand ? { checkRepoCommand: options.checkRepoCommand } : {}),
107
+ progress,
108
+ ...(options.piModel ? { piModel: options.piModel } : {}),
109
+ gitMode: options.gitMode === "checkpoint" ? "checkpoint" : "none",
110
+ keepFailedDiff: options.keepFailedDiff ?? false,
111
+ controllerIdentity: client.getIdentity(),
112
+ controllerExpectation: expectedIdentity,
113
+ });
114
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : renderFeatureRun(result));
115
+ if (result.status === "failed" || result.status === "needs-action")
116
+ process.exitCode = 1;
117
+ });
118
+ feature
119
+ .command("verify-final")
120
+ .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
121
+ .requiredOption("--repo <repo-root>", "Target repo root")
122
+ .requiredOption("--task-id <id>", "Completed qa-execute TaskSpec to rerun as dedicated final verification")
123
+ .option("--loop-agent-bin <bin>", "loop-agent binary", "loop-agent")
124
+ .option("--json", "Emit stable JSON")
125
+ .option("--expected-controller-version <version>", "Exact expected controller version (fail before writes if mismatched)")
126
+ .option("--expected-controller-fingerprint <value>", "Exact expected controller fingerprint sha256:<hex> (fail before writes if mismatched)")
127
+ .description("Run an independent HEAD-bound final QA verification and project Delivery evidence")
128
+ .action(async (options) => {
129
+ const repoRoot = path.resolve(options.repo);
130
+ const client = new LoopAgentClient({ loopAgentBin: options.loopAgentBin, artifactRoot: path.join(getTaskPoolRoot(repoRoot), "artifacts", `final-verification-${Date.now()}`), resolveIdentity: true });
131
+ const result = await runFeatureFinalVerification({ featureDir: path.resolve(options.featureDir), repoRoot, taskId: options.taskId, client, controllerIdentity: client.getIdentity(), controllerExpectation: buildIdentityExpectation(options) });
132
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : `Feature: ${result.featureId}\nFinal verification: ${result.workerRunId}\nQA evidence: ${result.qaEvidencePath}\nFinal evidence: ${result.finalVerificationPath}\n`);
133
+ });
134
+ feature
135
+ .command("delivery")
136
+ .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
137
+ .requiredOption("--repo <repo-root>", "Target repo root")
138
+ .requiredOption("--qa-evidence <path>", "Repo-local QA pass evidence")
139
+ .requiredOption("--final-verification <path>", "Repo-local fresh final verification evidence")
140
+ .option("--waivers <path>", "Repo-local acceptance waiver decisions JSON")
141
+ .option("--dry-run", "Validate and show the Delivery Package plan without writes")
142
+ .option("--json", "Emit stable JSON")
143
+ .description("Validate checkpointed Feature evidence and prepare a local Delivery Package")
144
+ .action(async (options) => {
145
+ const result = await prepareFeatureDelivery({
146
+ featureDir: path.resolve(options.featureDir), repoRoot: path.resolve(options.repo),
147
+ qaEvidencePath: options.qaEvidence, finalVerificationPath: options.finalVerification,
148
+ ...(options.waivers ? { waiverPath: options.waivers } : {}), dryRun: options.dryRun ?? false,
149
+ });
150
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : `Feature: ${result.featureId}\nDelivery: ${result.status}\nArtifacts: ${Object.values(result.artifacts).join(", ")}\nBlockers: ${result.blockers.join("; ") || "none"}\n`);
151
+ if (result.status === "blocked")
152
+ process.exitCode = 1;
153
+ });
154
+ feature
155
+ .command("closeout")
156
+ .requiredOption("--feature-dir <dir>", "Feature directory containing acceptance.yaml and tasks/")
157
+ .requiredOption("--repo <repo-root>", "Target repo root")
158
+ .option("--apply", "Apply Feature Closeout (reserved for M2-08)")
159
+ .option("--owner <owner>", "Closeout owner for apply")
160
+ .option("--json", "Emit stable JSON")
161
+ .description("Preview Feature Closeout gates without modifying the Feature Packet")
162
+ .action(async (options) => {
163
+ if (options.apply) {
164
+ const result = await applyFeatureCloseout({ featureDir: path.resolve(options.featureDir), repoRoot: path.resolve(options.repo), owner: options.owner ?? "" });
165
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
166
+ return;
167
+ }
168
+ const result = await previewFeatureCloseout({ featureDir: path.resolve(options.featureDir), repoRoot: path.resolve(options.repo) });
169
+ process.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : renderCloseoutPreview(result));
170
+ if (result.status === "blocked")
171
+ process.exitCode = 1;
172
+ });
31
173
  task
32
174
  .command("validate")
33
175
  .argument("<task-yaml>", "TaskSpec YAML file")
@@ -74,6 +216,58 @@ export function buildAgentWorkerProgram() {
74
216
  });
75
217
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
76
218
  });
219
+ task
220
+ .command("draft-followup")
221
+ .argument("<task-id>", "Failed parent Task id")
222
+ .requiredOption("--worker-run-id <id>", "Failed Worker run id")
223
+ .requiredOption("--repo <repo-root>", "Target repo root")
224
+ .requiredOption("--feature-dir <dir>", "Feature Packet directory")
225
+ .option("--json", "Emit JSON instead of the human summary")
226
+ .description("Create or reuse a classified Follow-up Task draft or human action card")
227
+ .action(async (taskId, options) => {
228
+ try {
229
+ const result = await draftFollowUpDecision({
230
+ repoRoot: path.resolve(options.repo),
231
+ featureDir: path.resolve(options.featureDir),
232
+ taskId,
233
+ workerRunId: options.workerRunId,
234
+ });
235
+ process.stdout.write(options.json
236
+ ? `${JSON.stringify(result, null, 2)}\n`
237
+ : result.kind === "TaskDraft"
238
+ ? `Follow-up: ${result.followUpId}\n结果: ${result.created ? "已生成任务草稿" : "复用现有任务草稿"}\n下一步: agent-worker feature approve-followup --feature-dir ${JSON.stringify(path.resolve(options.featureDir))} --followup-id ${result.followUpId} --repo ${JSON.stringify(path.resolve(options.repo))} --owner <owner>\n证据: ${result.draftPath}\n`
239
+ : `Follow-up: ${result.followUpId}\n结果: ${result.created ? "已生成人工行动卡" : "复用现有行动卡"}\n下一步: ${result.actionCard.command ?? result.actionCard.label}\n证据: ${result.draftPath}\n`);
240
+ }
241
+ catch (error) {
242
+ handleFollowUpCliError(error, options.json ?? false, "draft-followup");
243
+ }
244
+ });
245
+ feature
246
+ .command("approve-followup")
247
+ .requiredOption("--feature-dir <dir>", "Feature Packet directory")
248
+ .requiredOption("--followup-id <id>", "Follow-up approval id")
249
+ .requiredOption("--repo <repo-root>", "Target repo root")
250
+ .requiredOption("--owner <name>", "Human owner approving the Follow-up")
251
+ .option("--dry-run", "Validate baseline and show the transaction plan without writes")
252
+ .option("--json", "Emit JSON instead of the human summary")
253
+ .description("Approve a Follow-up and atomically add its TaskSpec to the Feature")
254
+ .action(async (options) => {
255
+ try {
256
+ const result = await approveFollowUp({
257
+ repoRoot: path.resolve(options.repo),
258
+ featureDir: path.resolve(options.featureDir),
259
+ followUpId: options.followupId,
260
+ owner: options.owner,
261
+ dryRun: options.dryRun ?? false,
262
+ });
263
+ process.stdout.write(options.json
264
+ ? `${JSON.stringify(result, null, 2)}\n`
265
+ : `Follow-up: ${result.followUpId}\n结果: ${result.status === "planned" ? "批准事务预览通过" : result.idempotent ? "已批准(幂等复用)" : "已批准并进入 Ready"}\n下一步: agent-worker feature review --feature-dir ${JSON.stringify(path.resolve(options.featureDir))} --repo ${JSON.stringify(path.resolve(options.repo))}\n证据: ${result.approvalPath}\n`);
266
+ }
267
+ catch (error) {
268
+ handleFollowUpCliError(error, options.json ?? false, "approve-followup");
269
+ }
270
+ });
77
271
  batch
78
272
  .command("run-ready")
79
273
  .requiredOption("--feature-dir <dir>", "Feature directory containing tasks/task-graph.yaml")
@@ -85,13 +279,17 @@ export function buildAgentWorkerProgram() {
85
279
  .option("--check-repo-command <command...>", "Override the check-repo preflight command")
86
280
  .option("--quiet", "Suppress human-readable progress on stderr (JSON still goes to stdout)")
87
281
  .option("--pi-model <model>", "Smoke override: force every pi executor node to this model (drops --strict-models)")
282
+ .option("--expected-controller-version <version>", "Exact expected controller version (fail before writes if mismatched)")
283
+ .option("--expected-controller-fingerprint <value>", "Exact expected controller fingerprint sha256:<hex> (fail before writes if mismatched)")
88
284
  .description("Run ready TaskSpec tasks serially")
89
285
  .action(async (options) => {
90
286
  const repoRoot = path.resolve(options.repo);
91
287
  const batchRunId = options.batchRunId ?? buildBatchRunId(new Date());
288
+ const expectedIdentity = buildIdentityExpectation(options);
92
289
  const client = new LoopAgentClient({
93
290
  loopAgentBin: options.loopAgentBin,
94
291
  artifactRoot: path.join(getTaskPoolRoot(repoRoot), "artifacts", batchRunId),
292
+ resolveIdentity: true,
95
293
  });
96
294
  let progress;
97
295
  try {
@@ -117,6 +315,8 @@ export function buildAgentWorkerProgram() {
117
315
  : {}),
118
316
  progress,
119
317
  ...(options.piModel ? { piModel: options.piModel } : {}),
318
+ controllerIdentity: client.getIdentity(),
319
+ controllerExpectation: expectedIdentity,
120
320
  });
121
321
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
122
322
  if (result.status !== "completed")
@@ -169,14 +369,57 @@ export function buildAgentWorkerProgram() {
169
369
  });
170
370
  process.stdout.write(`${JSON.stringify({ ok: true, outputPath }, null, 2)}\n`);
171
371
  });
372
+ report
373
+ .command("metrics")
374
+ .requiredOption("--repo <repo-root>", "Target repo root")
375
+ .requiredOption("--month <yyyy-mm>", "UTC reporting month")
376
+ .option("--json", "Emit the metrics JSON on stdout")
377
+ .description("Project monthly Feature delivery metrics to JSON and Markdown")
378
+ .action(async (options) => {
379
+ const result = await projectMonthlyMetrics({ repoRoot: path.resolve(options.repo), month: options.month });
380
+ process.stdout.write(options.json ? `${JSON.stringify(result.metrics, null, 2)}\n` : `${JSON.stringify({ ok: true, jsonPath: result.jsonPath, markdownPath: result.markdownPath }, null, 2)}\n`);
381
+ });
172
382
  return program;
173
383
  }
384
+ export function buildIdentityExpectation(options) {
385
+ if (options.expectedControllerVersion || options.expectedControllerFingerprint) {
386
+ const expectation = {};
387
+ if (options.expectedControllerVersion)
388
+ expectation.expectedVersion = options.expectedControllerVersion;
389
+ if (options.expectedControllerFingerprint)
390
+ expectation.expectedFingerprint = options.expectedControllerFingerprint;
391
+ return expectation;
392
+ }
393
+ return undefined;
394
+ }
174
395
  export async function main(argv = process.argv) {
175
396
  await buildAgentWorkerProgram().parseAsync(argv);
176
397
  }
177
398
  async function readTaskYaml(taskSpecPath) {
178
399
  return YAML.parse(await readFile(taskSpecPath, "utf-8"));
179
400
  }
401
+ function handleFollowUpCliError(error, json, action) {
402
+ const detail = error instanceof Error ? error.message : String(error);
403
+ if (!json)
404
+ throw error;
405
+ const code = detail.includes("only supports ProductBug")
406
+ ? "unsupported-failure-category"
407
+ : detail.includes("baseline changed")
408
+ ? "stale-feature-baseline"
409
+ : detail.includes("already exists") || detail.includes("collision")
410
+ ? "follow-up-conflict"
411
+ : detail.includes("evidence") || detail.includes("draft hash")
412
+ ? "follow-up-evidence-invalid"
413
+ : "follow-up-failed";
414
+ process.stdout.write(`${JSON.stringify({
415
+ schemaVersion: 1,
416
+ status: "failed",
417
+ action,
418
+ error: { code, message: detail },
419
+ nextAction: action === "draft-followup" ? "Inspect the failed Worker run and failure category." : "Regenerate and review the Follow-up draft before approving.",
420
+ }, null, 2)}\n`);
421
+ process.exitCode = 1;
422
+ }
180
423
  function isDirectRun() {
181
424
  const entry = process.argv[1];
182
425
  if (!entry)