@tea-agent/loop-agent 0.24.10 → 0.25.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 (72) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/executors/dag-pi-executor.js +74 -13
  3. package/dist/executors/pi-executor.js +25 -7
  4. package/dist/executors/pi-prompt-transport.js +198 -0
  5. package/dist/executors/pi-sdk-executor.js +1 -1
  6. package/dist/executors/process-tree.js +33 -0
  7. package/dist/executors/shell-executor.js +239 -47
  8. package/dist/executors/shell-verification.js +4 -2
  9. package/dist/infrastructure/harness/task-store.js +31 -0
  10. package/dist/shared/operator/capabilities.js +6 -0
  11. package/dist/verification/maven/cache.js +142 -0
  12. package/dist/verification/maven/index.js +120 -0
  13. package/dist/verification/maven/plan-commands.js +421 -0
  14. package/dist/verification/maven/pom-static.js +136 -0
  15. package/dist/verification/maven/scope-resolve.js +153 -0
  16. package/dist/verification/maven/stale.js +130 -0
  17. package/dist/verification/maven/types.js +23 -0
  18. package/dist/verification/maven/workspace-graph.js +322 -0
  19. package/dist/worker/cli.js +177 -7
  20. package/dist/worker/delivery/final-verification.js +12 -0
  21. package/dist/worker/feature/advance.js +301 -0
  22. package/dist/worker/feature/doctor.js +223 -0
  23. package/dist/worker/feature/next-action.js +11 -3
  24. package/dist/worker/feature/scaffold.js +798 -0
  25. package/dist/workflows/dag/backend-test-markdown-workflow.js +127 -0
  26. package/dist/workflows/dag/failure-category.js +5 -1
  27. package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
  28. package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
  29. package/dist/workflows/dag/init-hybrid.js +208 -19
  30. package/dist/workflows/dag/reconcile-run.js +24 -0
  31. package/dist/workflows/dag/types.js +44 -0
  32. package/dist/workflows/dag/validate.js +16 -0
  33. package/docs/architecture/dag-execution.md +5 -1
  34. package/docs/architecture/runtime-boundaries.md +11 -1
  35. package/docs/architecture/worker-and-feature.md +13 -0
  36. package/docs/init-surface.manifest.json +3 -0
  37. package/docs/templates/agent-dag.schema.json +9 -0
  38. package/docs/templates/backend-test-dag.json +3 -3
  39. package/docs/templates/frontend-implementation-contract.schema.json +2 -2
  40. package/docs/templates/product-line/README.md +17 -1
  41. package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
  42. package/docs/templates/product-line/scaffold-samples/README.md +36 -0
  43. package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
  44. package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
  45. package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
  46. package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
  47. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
  48. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
  49. package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
  50. package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
  51. package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
  52. package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
  53. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
  54. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
  55. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
  56. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
  57. package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
  58. package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
  59. package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
  60. package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
  61. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
  62. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
  63. package/examples/l5-report-coms-process-definition.html +322 -0
  64. package/package.json +1 -1
  65. package/skills/agent-worker/references/agent-worker-operator.md +84 -0
  66. package/skills/frontend-bounded-implement/SKILL.md +53 -0
  67. package/skills/frontend-implementation/SKILL.md +3 -7
  68. package/skills/frontend-implementation/references/node-contracts.md +3 -3
  69. package/skills/loop-agent/SKILL.md +8 -8
  70. package/skills/loop-agent/references/command-reference.md +14 -2
  71. package/skills/loop-agent/references/harness-policy.md +22 -0
  72. package/skills/loop-agent/references/task-workflow.md +5 -0
@@ -0,0 +1,223 @@
1
+ import { access } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { defaultFeatureEvidencePaths } from "../delivery/final-verification.js";
6
+ import { transactionRecordPath } from "../delivery/git-transaction.js";
7
+ import { readFeatureTaskPoolStates } from "../pool/run-store.js";
8
+ import { validateFeatureTaskGraph } from "../task-graph/validate.js";
9
+ import { reviewFeature } from "./review.js";
10
+ import { resolveFinalVerifyTaskId } from "./advance.js";
11
+ const execFileAsync = promisify(execFile);
12
+ /**
13
+ * Read-only Feature delivery readiness doctor (not pool migrate / not auto-repair).
14
+ */
15
+ export async function diagnoseFeature(input) {
16
+ const featureDir = path.resolve(input.featureDir);
17
+ const repoRoot = path.resolve(input.repoRoot);
18
+ const findings = [];
19
+ const nextCommands = [];
20
+ const validation = await validateFeatureTaskGraph(featureDir);
21
+ const featureId = validation.featureId || "unknown";
22
+ if (!validation.ok) {
23
+ findings.push({
24
+ id: "feature-validation",
25
+ severity: "error",
26
+ message: `Feature Packet invalid: ${validation.errors.map((e) => e.code).join(", ")}`,
27
+ evidence: validation.errors.flatMap((e) => (e.path ? [e.path] : [])),
28
+ suggestedCommand: `agent-worker task validate-feature ${JSON.stringify(featureDir)}`,
29
+ });
30
+ }
31
+ let review;
32
+ try {
33
+ review = await reviewFeature({ featureDir, repoRoot });
34
+ }
35
+ catch (error) {
36
+ findings.push({
37
+ id: "feature-review",
38
+ severity: "error",
39
+ message: error instanceof Error ? error.message : String(error),
40
+ });
41
+ }
42
+ const finalTaskId = review ? resolveFinalVerifyTaskId(review) : null;
43
+ const defaults = defaultFeatureEvidencePaths(featureId);
44
+ const deliveryManifestRel = `.harness/task-pool/artifacts/features/${featureId}/delivery/delivery-manifest.json`;
45
+ const gitTxAbs = transactionRecordPath(repoRoot, featureId);
46
+ const gitTxRel = path.relative(repoRoot, gitTxAbs).replace(/\\/g, "/");
47
+ const evidence = {
48
+ qaPass: {
49
+ path: defaults.qaEvidencePath,
50
+ present: await exists(path.join(repoRoot, defaults.qaEvidencePath)),
51
+ },
52
+ finalVerification: {
53
+ path: defaults.finalVerificationPath,
54
+ present: await exists(path.join(repoRoot, defaults.finalVerificationPath)),
55
+ },
56
+ bundle: {
57
+ path: defaults.bundleEvidencePath,
58
+ present: await exists(path.join(repoRoot, defaults.bundleEvidencePath)),
59
+ },
60
+ deliveryManifest: {
61
+ path: deliveryManifestRel,
62
+ present: await exists(path.join(repoRoot, deliveryManifestRel)),
63
+ },
64
+ gitTransaction: {
65
+ path: gitTxRel,
66
+ present: await exists(gitTxAbs),
67
+ },
68
+ };
69
+ const statesById = validation.ok
70
+ ? await readFeatureTaskPoolStates(repoRoot, featureId)
71
+ : {};
72
+ const states = Object.values(statesById);
73
+ const taskSummary = {
74
+ total: states.length,
75
+ done: states.filter((s) => s.status === "Done").length,
76
+ failed: states.filter((s) => s.status === "Failed").length,
77
+ running: states.filter((s) => s.status === "Running").length,
78
+ ready: states.filter((s) => s.status === "Ready").length,
79
+ other: states.filter((s) => !["Done", "Failed", "Running", "Ready"].includes(s.status)).length,
80
+ };
81
+ if (taskSummary.running > 0) {
82
+ const running = states.filter((s) => s.status === "Running");
83
+ findings.push({
84
+ id: "running-tasks",
85
+ severity: "warn",
86
+ message: `${taskSummary.running} task(s) still Running`,
87
+ evidence: running.map((s) => s.taskId),
88
+ suggestedCommand: `agent-worker task reconcile <task-id> --feature-id ${JSON.stringify(featureId)} --repo ${JSON.stringify(repoRoot)} --action abandon`,
89
+ });
90
+ }
91
+ if (taskSummary.failed > 0) {
92
+ const failed = states.filter((s) => s.status === "Failed");
93
+ findings.push({
94
+ id: "failed-tasks",
95
+ severity: "warn",
96
+ message: `${taskSummary.failed} task(s) Failed`,
97
+ evidence: failed.map((s) => s.taskId),
98
+ suggestedCommand: `agent-worker task retry <task-id> --feature-id ${JSON.stringify(featureId)} --repo ${JSON.stringify(repoRoot)} --reason <reason>`,
99
+ });
100
+ }
101
+ const dirty = await gitDirty(repoRoot);
102
+ if (dirty.dirty) {
103
+ findings.push({
104
+ id: "dirty-worktree",
105
+ severity: "warn",
106
+ message: "Target repo worktree is dirty (may block Delivery/closeout)",
107
+ evidence: dirty.lines.slice(0, 20),
108
+ });
109
+ }
110
+ if (!evidence.gitTransaction.present && taskSummary.done > 0) {
111
+ findings.push({
112
+ id: "missing-git-transaction",
113
+ severity: "info",
114
+ message: "No Feature git transaction record yet (expected before checkpointed Delivery)",
115
+ evidence: [evidence.gitTransaction.path],
116
+ });
117
+ }
118
+ const allTasksDone = review &&
119
+ review.tasks.length > 0 &&
120
+ review.tasks.every((t) => t.status === "Done");
121
+ if (allTasksDone) {
122
+ if (!evidence.qaPass.present || !evidence.finalVerification.present) {
123
+ findings.push({
124
+ id: "missing-final-evidence",
125
+ severity: "info",
126
+ message: "Implementation/QA tasks Done but verify-final evidence missing (ADR 0007)",
127
+ evidence: [
128
+ evidence.qaPass.path,
129
+ evidence.finalVerification.path,
130
+ ].filter((p, i) => i === 0 ? !evidence.qaPass.present : !evidence.finalVerification.present),
131
+ suggestedCommand: finalTaskId
132
+ ? `agent-worker feature advance --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)} --task-id ${JSON.stringify(finalTaskId)} --dry-run --json`
133
+ : `agent-worker feature verify-final --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)} --task-id <FINAL-VERIFY-id> --json`,
134
+ });
135
+ if (finalTaskId) {
136
+ nextCommands.push(`agent-worker feature advance --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)} --task-id ${JSON.stringify(finalTaskId)} --json`);
137
+ }
138
+ }
139
+ else if (!evidence.deliveryManifest.present) {
140
+ findings.push({
141
+ id: "missing-delivery",
142
+ severity: "info",
143
+ message: "Final evidence present but Delivery Package not written",
144
+ suggestedCommand: `agent-worker feature advance --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)}${finalTaskId ? ` --task-id ${JSON.stringify(finalTaskId)}` : ""} --json`,
145
+ });
146
+ nextCommands.push(`agent-worker feature advance --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)}${finalTaskId ? ` --task-id ${JSON.stringify(finalTaskId)}` : ""} --json`);
147
+ }
148
+ else if (review?.status === "deliverable") {
149
+ findings.push({
150
+ id: "ready-for-closeout",
151
+ severity: "info",
152
+ message: "Feature is deliverable; preview or apply closeout",
153
+ suggestedCommand: `agent-worker feature closeout --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)} --json`,
154
+ });
155
+ nextCommands.push(`agent-worker feature advance --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)}${finalTaskId ? ` --task-id ${JSON.stringify(finalTaskId)}` : ""} --apply --owner <owner> --json`);
156
+ }
157
+ }
158
+ else if (review?.status === "ready") {
159
+ nextCommands.push(`agent-worker feature run --feature-dir ${JSON.stringify(featureDir)} --repo ${JSON.stringify(repoRoot)} --limit 1 --json`);
160
+ }
161
+ const hasError = findings.some((f) => f.severity === "error");
162
+ const hasWarn = findings.some((f) => f.severity === "warn");
163
+ const status = hasError ? "blocked" : hasWarn ? "attention" : "healthy";
164
+ if (nextCommands.length === 0 && review?.nextAction?.command) {
165
+ nextCommands.push(review.nextAction.command);
166
+ }
167
+ return {
168
+ schemaVersion: 1,
169
+ featureId,
170
+ status,
171
+ ...(review ? { reviewStatus: review.status } : {}),
172
+ finalTaskId,
173
+ evidence,
174
+ taskSummary,
175
+ findings,
176
+ nextCommands,
177
+ };
178
+ }
179
+ export function renderFeatureDoctor(result) {
180
+ const lines = [
181
+ `Feature: ${result.featureId}`,
182
+ `Doctor: ${result.status}${result.reviewStatus ? ` (review=${result.reviewStatus})` : ""}`,
183
+ `Tasks: total=${result.taskSummary.total} done=${result.taskSummary.done} failed=${result.taskSummary.failed} running=${result.taskSummary.running} ready=${result.taskSummary.ready}`,
184
+ `Final task: ${result.finalTaskId ?? "none"}`,
185
+ `Evidence: qa=${result.evidence.qaPass.present ? "yes" : "no"} final=${result.evidence.finalVerification.present ? "yes" : "no"} bundle=${result.evidence.bundle.present ? "yes" : "no"} delivery=${result.evidence.deliveryManifest.present ? "yes" : "no"}`,
186
+ ...result.findings.map((f) => `${severityMark(f.severity)} ${f.id}: ${f.message}${f.suggestedCommand ? `\n → ${f.suggestedCommand}` : ""}`),
187
+ ];
188
+ if (result.nextCommands.length) {
189
+ lines.push("Next commands:");
190
+ for (const cmd of result.nextCommands)
191
+ lines.push(` - ${cmd}`);
192
+ }
193
+ return `${lines.join("\n")}\n`;
194
+ }
195
+ function severityMark(severity) {
196
+ if (severity === "error")
197
+ return "✗";
198
+ if (severity === "warn")
199
+ return "!";
200
+ return "·";
201
+ }
202
+ async function exists(filePath) {
203
+ try {
204
+ await access(filePath);
205
+ return true;
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ }
211
+ async function gitDirty(repoRoot) {
212
+ try {
213
+ const { stdout } = await execFileAsync("git", ["status", "--short", "--branch"], { cwd: repoRoot, encoding: "utf8" });
214
+ const lines = stdout
215
+ .split(/\r?\n/)
216
+ .map((l) => l.trimEnd())
217
+ .filter((l) => l && !l.startsWith("##"));
218
+ return { dirty: lines.length > 0, lines };
219
+ }
220
+ catch {
221
+ return { dirty: false, lines: [] };
222
+ }
223
+ }
@@ -66,13 +66,21 @@ export function projectNextAction(input, status) {
66
66
  };
67
67
  }
68
68
  if (status === "awaiting_qa") {
69
+ const finalish = input.tasks.find((task) => /(final[-_]?(verify|verification))|closeout/i.test(task.taskId));
70
+ if (finalish && (finalish.status === "Done" || finalish.status === "Ready")) {
71
+ return {
72
+ kind: "advance_delivery",
73
+ label: "运行 feature advance(verify-final 权威路径)",
74
+ command: `agent-worker feature advance ${featureArgs} --task-id ${quote(finalish.taskId)}`,
75
+ };
76
+ }
69
77
  return { kind: "complete_qa", label: "补齐 QA verdict 与 required AC 证据" };
70
78
  }
71
79
  if (status === "deliverable") {
72
80
  return {
73
- kind: "prepare_closeout",
74
- label: "预览 Feature Closeout gates",
75
- command: `agent-worker feature closeout ${featureArgs}`,
81
+ kind: "advance_delivery",
82
+ label: "编排 verify-final delivery → closeout(ADR 0007)",
83
+ command: `agent-worker feature advance ${featureArgs}`,
76
84
  };
77
85
  }
78
86
  return null;