@tea-agent/loop-agent 0.26.5-beta.1 → 0.27.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 (71) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +13 -23
  4. package/dist/application/dag/args.js +7 -7
  5. package/dist/application/dag/generate-task-dag.js +2 -2
  6. package/dist/application/dag/run-dag.js +3 -4
  7. package/dist/application/dag/validate-dag.js +1 -1
  8. package/dist/application/task-lifecycle/advance.js +845 -0
  9. package/dist/application/task-lifecycle/gates.js +80 -0
  10. package/dist/application/task-lifecycle/index.js +7 -0
  11. package/dist/application/task-lifecycle/observe.js +395 -0
  12. package/dist/application/task-lifecycle/plan-transitions.js +224 -0
  13. package/dist/application/task-lifecycle/recommendations.js +180 -0
  14. package/dist/application/task-lifecycle/record.js +73 -0
  15. package/dist/application/task-lifecycle/types.js +1 -0
  16. package/dist/cli/command-definitions.js +14 -111
  17. package/dist/cli/help.js +6 -7
  18. package/dist/cli/program.js +26 -90
  19. package/dist/cli/update/policy.js +0 -1
  20. package/dist/commands/dag-final-verification.js +1 -1
  21. package/dist/commands/dag-init-hybrid.js +1 -1
  22. package/dist/commands/delegate.js +2 -2
  23. package/dist/commands/init.js +21 -19
  24. package/dist/commands/run-dag-progress.js +1 -1
  25. package/dist/commands/status.js +18 -17
  26. package/dist/commands/study-init.js +2 -2
  27. package/dist/commands/task-advance.js +335 -0
  28. package/dist/commands/task-contract.js +3 -5
  29. package/dist/commands/task-source-prepare.js +9 -4
  30. package/dist/commands/task-status.js +133 -0
  31. package/dist/governance/manifest-types.js +2 -2
  32. package/dist/shared/operator/capabilities.js +1358 -243
  33. package/dist/task/contract/adopt.js +1 -1
  34. package/dist/task/contract/apply.js +1 -1
  35. package/dist/task/contract/import-revision.js +1 -1
  36. package/dist/task/contract/recover.js +2 -2
  37. package/dist/task/read-model.js +18 -23
  38. package/dist/task/runtime.js +1 -1
  39. package/dist/task/source-prepare/completeness.js +1 -1
  40. package/dist/task/source-prepare/parse-intent.js +6 -1
  41. package/dist/task/source-prepare/prepare.js +23 -20
  42. package/dist/worker/console/operator-actions.js +288 -95
  43. package/dist/worker/console/recovery-cta.js +4 -4
  44. package/dist/worker/console/static/assets/{index-CSRIhuzh.js → index-CNO7n6qB.js} +1 -1
  45. package/dist/worker/console/static/index.html +1 -1
  46. package/dist/worker/materialize/harness-task-materializer.js +6 -5
  47. package/dist/worker/run-task/run-task.js +204 -112
  48. package/dist/worker/runner/run-ready.js +1 -1
  49. package/dist/workflows/dag/frontend-implementation-contract.js +5 -57
  50. package/dist/workflows/dag/frontend-prewrite-gate.js +1 -6
  51. package/dist/workflows/dag/init-hybrid.js +11 -42
  52. package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
  53. package/docs/templates/evaluation/agents-map-verbose-v0.md +3 -3
  54. package/docs/templates/harness.schema.json +2 -2
  55. package/docs/templates/init-managed-agents.md +13 -16
  56. package/docs/templates/production-readiness-checklist.md +3 -3
  57. package/harness.json +4 -4
  58. package/package.json +1 -1
  59. package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
  60. package/scripts/kb-graph-incremental-prepare.mjs +2 -2
  61. package/skills/loop-agent/SKILL.md +18 -13
  62. package/skills/loop-agent/references/README.md +1 -1
  63. package/skills/loop-agent/references/command-reference.md +55 -84
  64. package/skills/loop-agent/references/harness-policy.md +19 -23
  65. package/skills/loop-agent/references/hybrid-dag.md +31 -33
  66. package/skills/loop-agent/references/long-running-loop.md +2 -2
  67. package/skills/loop-agent/references/one-shot-runs.md +4 -5
  68. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  69. package/skills/loop-agent/references/post-implementation-and-patterns.md +4 -4
  70. package/skills/loop-agent/references/source-and-plan-practice.md +45 -51
  71. package/skills/loop-agent/references/task-workflow.md +14 -15
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Pure reducer: given a lifecycle snapshot + intent flags, decide the next
3
+ * safe automatic transitions until a stop condition.
4
+ */
5
+ export function planTransitions(input) {
6
+ const { snapshot } = input;
7
+ const actions = [];
8
+ const expectedTransitions = [];
9
+ const blockers = [...snapshot.blockers];
10
+ if (input.rejectGate) {
11
+ return {
12
+ actions: [{ type: "stop", reason: "terminal" }],
13
+ gate: snapshot.gate ?? null,
14
+ blockers,
15
+ next: {
16
+ kind: "advance",
17
+ command: `loop-agent task advance ${snapshot.taskId} --json`,
18
+ },
19
+ expectedTransitions: ["write-set-rejected"],
20
+ };
21
+ }
22
+ if (!snapshot.exists) {
23
+ // Create is always allowed when advance is invoked for a missing task.
24
+ // Further intake (PRD/boundaries) is required before contract/DAG steps.
25
+ actions.push({ type: "create-task" });
26
+ expectedTransitions.push("task-created");
27
+ if (!input.hasPrdInput && !input.hasEngineeringBoundary) {
28
+ actions.push({ type: "stop", reason: "blocker" });
29
+ blockers.push({
30
+ code: "INTAKE_REQUIRED",
31
+ message: "task created or creatable; provide --prd and engineering boundary flags (or --from-text) to continue",
32
+ });
33
+ return {
34
+ actions,
35
+ gate: null,
36
+ blockers,
37
+ next: {
38
+ kind: "advance",
39
+ command: `loop-agent task advance ${snapshot.taskId} --prd <path> --allowed-path <glob> --json`,
40
+ },
41
+ expectedTransitions,
42
+ };
43
+ }
44
+ }
45
+ if (snapshot.contract?.transactionIncomplete) {
46
+ actions.push({ type: "recover-contract" });
47
+ expectedTransitions.push("contract-recovered");
48
+ }
49
+ const sourceReady = snapshot.source?.requirementReady === true;
50
+ const contractManaged = snapshot.contract?.effectiveStatus === "managed" ||
51
+ Boolean(snapshot.contract?.revision && snapshot.contract.revision > 0);
52
+ const alreadyImportedPrd = (snapshot.source?.importedPrdCount ?? 0) > 0;
53
+ // Prefer prepare once PRDs are on disk; re-import only when caller still
54
+ // signals hasPrdInput (advance.ts latches this after one import per call).
55
+ const shouldImportPrd = input.hasPrdInput;
56
+ if (input.forcePrepareContract) {
57
+ if (shouldImportPrd) {
58
+ actions.push({ type: "import-prd" });
59
+ expectedTransitions.push("prd-imported");
60
+ }
61
+ actions.push({ type: "prepare-contract" });
62
+ expectedTransitions.push("contract-managed");
63
+ }
64
+ else if (!sourceReady || !contractManaged) {
65
+ if ((shouldImportPrd || alreadyImportedPrd) &&
66
+ !input.hasEngineeringBoundary) {
67
+ // Console bootstrap / import-only: archive PRD(s), do not prepare contract yet.
68
+ if (shouldImportPrd) {
69
+ actions.push({ type: "import-prd" });
70
+ expectedTransitions.push("prd-imported");
71
+ }
72
+ actions.push({ type: "stop", reason: "blocker" });
73
+ blockers.push({
74
+ code: "BOUNDARY_REQUIRED",
75
+ message: "PRD imported; provide --allowed-path / --verify (or --from-text with boundaries) to prepare contract and open writeSet gate",
76
+ });
77
+ return {
78
+ actions,
79
+ gate: null,
80
+ blockers,
81
+ next: {
82
+ kind: "advance",
83
+ command: `loop-agent task advance ${snapshot.taskId} --allowed-path <glob> --verify <label:command> --json`,
84
+ },
85
+ expectedTransitions,
86
+ };
87
+ }
88
+ if (shouldImportPrd ||
89
+ input.hasEngineeringBoundary ||
90
+ alreadyImportedPrd ||
91
+ snapshot.exists) {
92
+ if (shouldImportPrd) {
93
+ actions.push({ type: "import-prd" });
94
+ expectedTransitions.push("prd-imported");
95
+ }
96
+ actions.push({ type: "prepare-contract" });
97
+ expectedTransitions.push("contract-managed");
98
+ }
99
+ else {
100
+ blockers.push({
101
+ code: "CONTRACT_INCOMPLETE",
102
+ message: "source/contract not ready; re-run advance with PRD and boundaries",
103
+ });
104
+ return stopWith(actions, expectedTransitions, blockers, snapshot, "blocker");
105
+ }
106
+ }
107
+ const dagExists = snapshot.dagDraft?.exists === true;
108
+ const strictValidated = snapshot.dagDraft?.strictValidated === true;
109
+ if (!dagExists) {
110
+ actions.push({ type: "generate-dag" });
111
+ expectedTransitions.push("dag-generated");
112
+ }
113
+ if (!strictValidated) {
114
+ actions.push({ type: "strict-validate-dag" });
115
+ expectedTransitions.push("dag-strict-validated");
116
+ }
117
+ // After validate, open write-set gate unless already approved/rejected for current digest.
118
+ const gate = snapshot.gate ?? null;
119
+ const postRunState = snapshot.lifecycleState === "running" ||
120
+ snapshot.lifecycleState === "run-succeeded" ||
121
+ snapshot.lifecycleState === "run-failed" ||
122
+ snapshot.lifecycleState === "evidence-closed" ||
123
+ snapshot.lifecycleState === "needs-attention" ||
124
+ snapshot.lifecycleState === "awaiting-decision";
125
+ const approvedForCurrent = postRunState || (input.approveGate && gate !== null);
126
+ if (!approvedForCurrent && (strictValidated || !dagExists)) {
127
+ // Gate opens after generate+validate in the advance loop; plan includes open.
128
+ if (!input.approveGate) {
129
+ actions.push({ type: "open-write-set-gate" });
130
+ expectedTransitions.push("write-set-gate-opened");
131
+ if (input.dryRun) {
132
+ actions.push({ type: "stop", reason: "dry-run" });
133
+ }
134
+ else {
135
+ actions.push({ type: "stop", reason: "gate" });
136
+ }
137
+ return {
138
+ actions,
139
+ gate,
140
+ blockers,
141
+ next: gate
142
+ ? { kind: "approve-gate", command: gate.approveCommand }
143
+ : {
144
+ kind: "advance",
145
+ command: `loop-agent task advance ${snapshot.taskId} --json`,
146
+ },
147
+ expectedTransitions,
148
+ };
149
+ }
150
+ }
151
+ // Only start/monitor when not already in a terminal post-run state.
152
+ const alreadyTerminalRun = snapshot.lifecycleState === "run-succeeded" ||
153
+ snapshot.lifecycleState === "run-failed" ||
154
+ snapshot.lifecycleState === "evidence-closed";
155
+ if (!alreadyTerminalRun) {
156
+ if (input.approveGate && !postRunState) {
157
+ expectedTransitions.push("write-set-approved");
158
+ actions.push({ type: "start-or-monitor-run" });
159
+ expectedTransitions.push("dag-run-started", "dag-run-monitored");
160
+ }
161
+ else if (snapshot.lifecycleState === "running" ||
162
+ snapshot.activeRun ||
163
+ (input.approveGate && Boolean(snapshot.activeRun))) {
164
+ actions.push({ type: "start-or-monitor-run" });
165
+ expectedTransitions.push("dag-run-monitored");
166
+ }
167
+ }
168
+ if (!input.skipFinalize &&
169
+ (snapshot.lifecycleState === "run-succeeded" ||
170
+ (snapshot.latestRun?.lifecycle === "completed" &&
171
+ snapshot.latestRun.status === "finished"))) {
172
+ if (!snapshot.promotion?.ready) {
173
+ actions.push({ type: "promote" });
174
+ expectedTransitions.push("promotion-completed");
175
+ }
176
+ if (!snapshot.closeout?.exists) {
177
+ actions.push({ type: "closeout" });
178
+ expectedTransitions.push("closeout-completed");
179
+ }
180
+ }
181
+ if (snapshot.lifecycleState === "evidence-closed") {
182
+ return {
183
+ actions: [{ type: "stop", reason: "terminal" }],
184
+ gate: null,
185
+ blockers,
186
+ next: { kind: "none" },
187
+ expectedTransitions: [],
188
+ };
189
+ }
190
+ if (input.dryRun) {
191
+ actions.push({ type: "stop", reason: "dry-run" });
192
+ }
193
+ return {
194
+ actions,
195
+ gate,
196
+ blockers,
197
+ next: deriveNext(snapshot, gate, input.approveGate),
198
+ expectedTransitions,
199
+ };
200
+ }
201
+ function stopWith(actions, expectedTransitions, blockers, snapshot, reason) {
202
+ return {
203
+ actions: [...actions, { type: "stop", reason }],
204
+ gate: snapshot.gate ?? null,
205
+ blockers,
206
+ next: {
207
+ kind: "advance",
208
+ command: `loop-agent task advance ${snapshot.taskId} --json`,
209
+ },
210
+ expectedTransitions,
211
+ };
212
+ }
213
+ function deriveNext(snapshot, gate, approveGate) {
214
+ if (gate && !approveGate) {
215
+ return { kind: "approve-gate", command: gate.approveCommand };
216
+ }
217
+ if (snapshot.lifecycleState === "evidence-closed") {
218
+ return { kind: "none" };
219
+ }
220
+ return {
221
+ kind: "advance",
222
+ command: `loop-agent task advance ${snapshot.taskId} --json`,
223
+ };
224
+ }
@@ -0,0 +1,180 @@
1
+ import { createHash } from "node:crypto";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { loadTaskConfig } from "../../task/runtime.js";
5
+ function sha256Canonical(value) {
6
+ const json = JSON.stringify(value);
7
+ return `sha256:${createHash("sha256").update(json, "utf8").digest("hex")}`;
8
+ }
9
+ async function pathExists(filePath) {
10
+ try {
11
+ await access(filePath);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function readPackageScripts(repoRoot) {
19
+ const packageJsonPath = path.join(repoRoot, "package.json");
20
+ if (!(await pathExists(packageJsonPath)))
21
+ return {};
22
+ try {
23
+ const raw = await readFile(packageJsonPath, "utf-8");
24
+ const parsed = JSON.parse(raw);
25
+ return parsed.scripts ?? {};
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ /**
32
+ * Deterministic engineering-boundary recommendations (P1).
33
+ *
34
+ * Priority:
35
+ * 1. managed task config (highest)
36
+ * 2. package.json scripts (typecheck/test/check-repo)
37
+ * 3. PRD path-like tokens as weak candidates only (never auto-elevated)
38
+ *
39
+ * Recommendations never enter contract draft unless acceptDigest matches.
40
+ */
41
+ export async function buildTaskLifecycleRecommendations(input) {
42
+ const allowedPaths = [];
43
+ const verifyCommands = [];
44
+ // 1) managed task config
45
+ try {
46
+ const config = await loadTaskConfig(input.repoRoot, input.taskId);
47
+ for (const glob of config.allowedPaths ?? []) {
48
+ if (!glob.trim())
49
+ continue;
50
+ allowedPaths.push({
51
+ glob: glob.trim(),
52
+ reason: "existing managed task.json.allowedPaths",
53
+ source: "managed-task-config",
54
+ });
55
+ }
56
+ for (const cmd of config.verifyCommands ?? []) {
57
+ const label = cmd.label?.trim() || "verify";
58
+ const command = cmd.command?.trim();
59
+ if (!command)
60
+ continue;
61
+ verifyCommands.push({
62
+ label,
63
+ command,
64
+ reason: "existing managed task.json.verifyCommands",
65
+ source: "managed-task-config",
66
+ });
67
+ }
68
+ }
69
+ catch {
70
+ // task may not exist yet
71
+ }
72
+ // 2) package.json scripts (only if not already covered by managed config)
73
+ const scripts = await readPackageScripts(input.repoRoot);
74
+ const hasVerifyLabel = (label) => verifyCommands.some((entry) => entry.label === label);
75
+ const scriptCandidates = [
76
+ { label: "typecheck", script: "typecheck" },
77
+ { label: "test", script: "test" },
78
+ { label: "check-repo", script: "check:repo" },
79
+ { label: "check-repo", script: "check-repo" },
80
+ ];
81
+ for (const candidate of scriptCandidates) {
82
+ const scriptBody = scripts[candidate.script];
83
+ if (!scriptBody)
84
+ continue;
85
+ if (hasVerifyLabel(candidate.label))
86
+ continue;
87
+ verifyCommands.push({
88
+ label: candidate.label,
89
+ command: `npm run ${candidate.script}`,
90
+ reason: `package.json scripts.${candidate.script}`,
91
+ source: "package-scripts",
92
+ });
93
+ }
94
+ // 3) weak PRD path candidates (never auto-accepted alone)
95
+ const prdTexts = input.prdTexts ?? [];
96
+ const seenGlobs = new Set(allowedPaths.map((entry) => entry.glob));
97
+ const pathLike = /(?:^|[\s`"'(])((?:src|test|tests|docs|website|scripts|skills)\/[A-Za-z0-9_./ conf-]+(?:\/\*\*)?)/gm;
98
+ for (const text of prdTexts) {
99
+ for (const match of text.matchAll(pathLike)) {
100
+ const raw = match[1]?.trim();
101
+ if (!raw)
102
+ continue;
103
+ const glob = raw.endsWith("/**")
104
+ ? raw
105
+ : raw.endsWith("/")
106
+ ? `${raw}**`
107
+ : raw.includes("*")
108
+ ? raw
109
+ : `${raw.replace(/\/$/, "")}/**`;
110
+ if (seenGlobs.has(glob))
111
+ continue;
112
+ // Skip overly broad roots
113
+ if (glob === "src/**" || glob === "docs/**" || glob === "test/**") {
114
+ // still allow as weak candidate with explicit reason
115
+ }
116
+ seenGlobs.add(glob);
117
+ allowedPaths.push({
118
+ glob,
119
+ reason: "path-like token in PRD (weak candidate; not auto-elevated)",
120
+ source: "prd-weak-candidate",
121
+ });
122
+ }
123
+ }
124
+ // Stable sort for digest
125
+ allowedPaths.sort((a, b) => a.glob.localeCompare(b.glob));
126
+ verifyCommands.sort((a, b) => a.label.localeCompare(b.label));
127
+ const digestPayload = {
128
+ schemaVersion: 1,
129
+ taskId: input.taskId,
130
+ allowedPaths: allowedPaths.map((entry) => ({
131
+ glob: entry.glob,
132
+ source: entry.source,
133
+ })),
134
+ verifyCommands: verifyCommands.map((entry) => ({
135
+ label: entry.label,
136
+ command: entry.command,
137
+ source: entry.source,
138
+ })),
139
+ };
140
+ const digest = sha256Canonical(digestPayload);
141
+ return {
142
+ allowedPaths,
143
+ verifyCommands,
144
+ digest,
145
+ requiresAccept: true,
146
+ };
147
+ }
148
+ export function applyAcceptedRecommendations(input) {
149
+ const explicitAllowed = input.explicitAllowedPaths ?? [];
150
+ const explicitVerify = input.explicitVerifyCommands ?? [];
151
+ if (explicitAllowed.length > 0 || explicitVerify.length > 0) {
152
+ return {
153
+ allowedPaths: explicitAllowed.length > 0 ? explicitAllowed : undefined,
154
+ verifyCommands: explicitVerify.length > 0 ? explicitVerify : undefined,
155
+ accepted: false,
156
+ staleAccept: false,
157
+ };
158
+ }
159
+ const accept = input.acceptDigest?.trim();
160
+ if (!accept) {
161
+ return { accepted: false, staleAccept: false };
162
+ }
163
+ if (accept !== input.recommendations.digest) {
164
+ return { accepted: false, staleAccept: true };
165
+ }
166
+ // Only elevate non-weak sources automatically when accepted.
167
+ const allowedPaths = input.recommendations.allowedPaths
168
+ .filter((entry) => entry.source !== "prd-weak-candidate")
169
+ .map((entry) => entry.glob);
170
+ const verifyCommands = input.recommendations.verifyCommands.map((entry) => ({
171
+ label: entry.label,
172
+ command: entry.command,
173
+ }));
174
+ return {
175
+ allowedPaths: allowedPaths.length > 0 ? allowedPaths : undefined,
176
+ verifyCommands: verifyCommands.length > 0 ? verifyCommands : undefined,
177
+ accepted: true,
178
+ staleAccept: false,
179
+ };
180
+ }
@@ -0,0 +1,73 @@
1
+ import { access, mkdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
4
+ import { getTaskDir } from "../../task/runtime.js";
5
+ export function getLifecycleRecordPath(repoRoot, taskId) {
6
+ return path.join(getTaskDir(repoRoot, taskId), "lifecycle.json");
7
+ }
8
+ async function exists(filePath) {
9
+ try {
10
+ await access(filePath);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ export async function loadLifecycleRecord(repoRoot, taskId) {
18
+ const filePath = getLifecycleRecordPath(repoRoot, taskId);
19
+ if (!(await exists(filePath)))
20
+ return null;
21
+ try {
22
+ const raw = JSON.parse(await readFile(filePath, "utf-8"));
23
+ if (raw.schemaVersion !== 1 || raw.taskId !== taskId)
24
+ return null;
25
+ return raw;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export function emptyLifecycleRecord(taskId) {
32
+ return {
33
+ schemaVersion: 1,
34
+ taskId,
35
+ updatedAt: new Date().toISOString(),
36
+ completedTransitions: [],
37
+ runAssociations: [],
38
+ };
39
+ }
40
+ export async function saveLifecycleRecord(repoRoot, record) {
41
+ const filePath = getLifecycleRecordPath(repoRoot, record.taskId);
42
+ await mkdir(path.dirname(filePath), { recursive: true });
43
+ const next = {
44
+ ...record,
45
+ updatedAt: new Date().toISOString(),
46
+ };
47
+ await writeJsonAtomic(filePath, next);
48
+ }
49
+ export function appendTransition(record, transition) {
50
+ if (record.completedTransitions.includes(transition)) {
51
+ return record;
52
+ }
53
+ return {
54
+ ...record,
55
+ completedTransitions: [...record.completedTransitions, transition],
56
+ };
57
+ }
58
+ export function recordRunAssociation(record, run) {
59
+ if (record.runAssociations.some((entry) => entry.runId === run.runId)) {
60
+ return record;
61
+ }
62
+ return {
63
+ ...record,
64
+ runAssociations: [
65
+ ...record.runAssociations,
66
+ {
67
+ runId: run.runId,
68
+ recordedAt: new Date().toISOString(),
69
+ source: run.source,
70
+ },
71
+ ],
72
+ };
73
+ }
@@ -0,0 +1 @@
1
+ export const TASK_LIFECYCLE_SCHEMA_VERSION = 1;
@@ -5,21 +5,16 @@ import { runEval } from "../commands/eval.js";
5
5
  import { runCoverageAudit } from "../commands/coverage-audit.js";
6
6
  import { runCoverageReport } from "../commands/coverage-report.js";
7
7
  import { runExamples } from "../commands/examples.js";
8
- import { runCloseout } from "../commands/closeout.js";
9
8
  import { runHandoffCheck } from "../commands/handoff-check.js";
10
9
  import { runInspect } from "../commands/inspect.js";
11
10
  import { runInstructions } from "../commands/instructions.js";
12
- import { runNewTask } from "../commands/new-task.js";
13
- import { runImportPrd } from "../commands/import-prd.js";
14
- import { runTaskContract } from "../commands/task-contract.js";
15
- import { runTaskSourcePrepare } from "../commands/task-source-prepare.js";
11
+ import { runTaskAdvance } from "../commands/task-advance.js";
12
+ import { runTaskStatus } from "../commands/task-status.js";
16
13
  import { runOperator } from "../commands/operator.js";
17
14
  import { runPlanList } from "../commands/plan-list.js";
18
15
  import { runPlanCheck, runPlanComplete, runPlanCreate, } from "../commands/plan.js";
19
- import { runPromoteRun } from "../commands/promote-run.js";
20
16
  import { runSpine } from "../commands/spine.js";
21
17
  import { runStats } from "../commands/stats.js";
22
- import { runStatus } from "../commands/status.js";
23
18
  import { runWorktreeCreate } from "../commands/worktree-create.js";
24
19
  import { runWorktreeList } from "../commands/worktree-list.js";
25
20
  import { runWorktreeRemove } from "../commands/worktree-remove.js";
@@ -42,7 +37,6 @@ import { runDagRerun } from "../commands/dag-rerun.js";
42
37
  import { runDagResume } from "../commands/dag-resume.js";
43
38
  import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
44
39
  import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
45
- import { runDagRunTask } from "../commands/dag-run-task.js";
46
40
  import { runDagCloseoutDraft, runDagReport } from "../commands/dag-report.js";
47
41
  import { runDagFinalVerification } from "../commands/dag-final-verification.js";
48
42
  import { runKnowledge } from "../commands/knowledge.js";
@@ -121,7 +115,7 @@ const KNOWLEDGE_SUBCOMMANDS = [
121
115
  ];
122
116
  const DAG_SUBCOMMANDS = [
123
117
  "init-hybrid",
124
- "run-task",
118
+ "execute",
125
119
  "validate",
126
120
  "workflow-plan",
127
121
  "workflow-validate",
@@ -140,17 +134,7 @@ const DAG_SUBCOMMANDS = [
140
134
  "final-verification",
141
135
  "decision",
142
136
  ];
143
- const TASK_SUBCOMMANDS = ["contract", "source"];
144
- const TASK_CONTRACT_SUBCOMMANDS = [
145
- "show",
146
- "validate",
147
- "diff",
148
- "apply",
149
- "adopt",
150
- "doctor",
151
- "recover",
152
- ];
153
- const TASK_SOURCE_SUBCOMMANDS = ["prepare"];
137
+ const TASK_SUBCOMMANDS = ["advance", "status"];
154
138
  const OPERATOR_SUBCOMMANDS = ["capabilities"];
155
139
  const WORKFLOW_SUBCOMMANDS = [
156
140
  "list",
@@ -246,57 +230,21 @@ export const COMMAND_DEFINITIONS = [
246
230
  await runEval(repoRoot, [subcommand, ...rest].filter((arg) => Boolean(arg)));
247
231
  },
248
232
  },
249
- {
250
- name: "new-task",
251
- adapter: "required",
252
- tier: "primary",
253
- intent: "Create a harness task before DAG autonomous work.",
254
- usage: "new-task <task-id> [title]",
255
- handler: async ({ repoRoot, subcommand, rest }) => {
256
- const [taskId, ...titleParts] = [subcommand, ...rest];
257
- if (!taskId)
258
- throw new Error("taskId required");
259
- await runNewTask(repoRoot, taskId, titleParts.join(" ") || undefined);
260
- },
261
- },
262
- {
263
- name: "import-prd",
264
- adapter: "required",
265
- tier: "primary",
266
- intent: "Copy a user PRD into source/references as an immutable fact source before deriving source/需求.md.",
267
- usage: "import-prd <task-id> --file <path> [--name requirement] [--role requirement] [--json]",
268
- handler: async ({ repoRoot, subcommand, rest }) => {
269
- await runImportPrd(repoRoot, [subcommand, ...rest].filter(Boolean));
270
- },
271
- },
272
233
  {
273
234
  name: "task",
274
235
  adapter: "required",
275
236
  tier: "primary",
276
- intent: "Task Contract and source intake: contract show/validate/diff/apply/adopt/doctor/recover; source prepare (PRD-first Draft projection).",
277
- usage: "task <contract|source> ... (contract <show|validate|diff|apply|adopt|doctor|recover>; source prepare ...)",
237
+ intent: "Task lifecycle: advance (mutation) and status (read).",
238
+ usage: "task <advance|status> ...",
278
239
  subcommands: [...TASK_SUBCOMMANDS],
279
240
  handler: async ({ repoRoot, subcommand, rest }) => {
280
- if (subcommand === "contract") {
281
- const [contractSub, ...contractRest] = rest;
282
- if (!contractSub ||
283
- !TASK_CONTRACT_SUBCOMMANDS.includes(contractSub)) {
284
- throw new Error(`usage: task contract <${TASK_CONTRACT_SUBCOMMANDS.join("|")}> ...`);
285
- }
286
- await runTaskContract(repoRoot, [contractSub, ...contractRest]);
241
+ if (subcommand === "advance") {
242
+ await runTaskAdvance(repoRoot, rest);
287
243
  return;
288
244
  }
289
- if (subcommand === "source") {
290
- const [sourceSub, ...sourceRest] = rest;
291
- if (!sourceSub ||
292
- !TASK_SOURCE_SUBCOMMANDS.includes(sourceSub)) {
293
- throw new Error(`usage: task source <${TASK_SOURCE_SUBCOMMANDS.join("|")}> ...`);
294
- }
295
- if (sourceSub === "prepare") {
296
- await runTaskSourcePrepare(repoRoot, sourceRest);
297
- return;
298
- }
299
- throw new Error(`usage: task source <${TASK_SOURCE_SUBCOMMANDS.join("|")}> ...`);
245
+ if (subcommand === "status") {
246
+ await runTaskStatus(repoRoot, rest);
247
+ return;
300
248
  }
301
249
  throw new Error(formatSubcommandUsageError("task", TASK_SUBCOMMANDS));
302
250
  },
@@ -312,19 +260,6 @@ export const COMMAND_DEFINITIONS = [
312
260
  await runOperator(repoRoot, [subcommand, ...rest].filter((arg) => Boolean(arg)));
313
261
  },
314
262
  },
315
- {
316
- name: "status",
317
- adapter: "required",
318
- tier: "operator",
319
- intent: "Inspect a task state and machine-readable action context.",
320
- usage: "status <task-id> [--json]",
321
- handler: async ({ repoRoot, subcommand }) => {
322
- const taskId = subcommand;
323
- if (!taskId)
324
- throw new Error("taskId required");
325
- await runStatus(repoRoot, taskId);
326
- },
327
- },
328
263
  {
329
264
  name: "instructions",
330
265
  adapter: "required",
@@ -335,27 +270,6 @@ export const COMMAND_DEFINITIONS = [
335
270
  await runInstructions(repoRoot, [subcommand, ...rest].filter(Boolean));
336
271
  },
337
272
  },
338
- {
339
- name: "promote-run",
340
- adapter: "required",
341
- tier: "operator",
342
- intent: "Promote completed DAG or one-shot run evidence into task artifacts without mutating run facts.",
343
- usage: "promote-run <task-id> --run-id <run-id>",
344
- handler: async ({ repoRoot, subcommand, rest }) => {
345
- await runPromoteRun(repoRoot, [subcommand, ...rest].filter(Boolean));
346
- },
347
- },
348
- {
349
- name: "closeout",
350
- adapter: "required",
351
- tier: "operator",
352
- intent: "Create deterministic task closeout/progress records from promoted task artifacts.",
353
- usage: "closeout task <task-id>",
354
- subcommands: [...CLOSEOUT_SUBCOMMANDS],
355
- handler: async ({ repoRoot, subcommand, rest }) => {
356
- await runCloseout(repoRoot, [subcommand, ...rest].filter(Boolean));
357
- },
358
- },
359
273
  {
360
274
  name: "stats",
361
275
  adapter: "required",
@@ -614,9 +528,9 @@ export const COMMAND_DEFINITIONS = [
614
528
  await runDagDoctor(repoRoot, doctorArgs);
615
529
  return;
616
530
  }
617
- if (subcommand === "run-task") {
618
- const runTaskArgs = rest.filter((arg) => Boolean(arg));
619
- await runDagRunTask(repoRoot, runTaskArgs);
531
+ if (subcommand === "execute") {
532
+ const executeArgs = rest.filter((arg) => Boolean(arg));
533
+ await runRunDag(repoRoot, executeArgs);
620
534
  return;
621
535
  }
622
536
  if (subcommand === "report") {
@@ -672,17 +586,6 @@ export const COMMAND_DEFINITIONS = [
672
586
  throw new Error(formatSubcommandUsageError("dag", DAG_SUBCOMMANDS));
673
587
  },
674
588
  },
675
- {
676
- name: "run-dag",
677
- adapter: "required",
678
- tier: "primary",
679
- intent: "Execute a reviewed Agent DAG spec.",
680
- usage: "run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--quiet] [--progress-interval-ms N] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]] [--events-jsonl <path>] [--worker-association <json>]",
681
- handler: async ({ repoRoot, subcommand, rest }) => {
682
- const runDagArgs = [subcommand, ...rest].filter((arg) => Boolean(arg));
683
- await runRunDag(repoRoot, runDagArgs);
684
- },
685
- },
686
589
  {
687
590
  name: "workflow",
688
591
  adapter: "required",