@tea-agent/loop-agent 0.24.11-beta.0 → 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.
- package/CHANGELOG.md +28 -5
- package/dist/commands/init.js +16 -2
- package/dist/executors/dag-pi-executor.js +74 -13
- package/dist/executors/pi-executor.js +25 -7
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-sdk-executor.js +1 -1
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +332 -24
- package/dist/executors/shell-verification.js +4 -2
- package/dist/infrastructure/harness/task-store.js +31 -0
- package/dist/shared/operator/capabilities.js +6 -0
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +236 -21
- package/dist/worker/delivery/final-verification.js +12 -0
- package/dist/worker/delivery/git-transaction.js +32 -7
- package/dist/worker/delivery/package.js +9 -5
- package/dist/worker/delivery/verification-bundle.js +26 -5
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/next-action.js +11 -3
- package/dist/worker/feature/scaffold.js +798 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/node-input.js +444 -0
- package/dist/worker/observe/routes.js +17 -0
- package/dist/worker/observe/static/api.js +9 -0
- package/dist/worker/observe/static/constants.js +9 -0
- package/dist/worker/observe/static/state.js +14 -0
- package/dist/worker/observe/static/styles.css +74 -0
- package/dist/worker/observe/static/views/dag-inspector.js +371 -15
- package/dist/worker/outcomes/projector.js +5 -1
- package/dist/worker/runner/run-ready.js +41 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +202 -9
- package/dist/workflows/dag/backend-test-result-contract.js +103 -0
- package/dist/workflows/dag/failure-category.js +5 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
- package/dist/workflows/dag/frontend-test-result-contract.js +106 -41
- package/dist/workflows/dag/init-hybrid.js +220 -32
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/reconcile-run.js +24 -0
- package/dist/workflows/dag/types.js +46 -0
- package/dist/workflows/dag/validate.js +19 -2
- package/docs/architecture/dag-execution.md +5 -1
- package/docs/architecture/runtime-boundaries.md +11 -1
- package/docs/architecture/worker-and-feature.md +2 -0
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/product-line/README.md +17 -1
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/package.json +1 -1
- package/skills/agent-worker/references/agent-worker-operator.md +84 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/loop-agent/SKILL.md +8 -8
- package/skills/loop-agent/references/command-reference.md +14 -2
- package/skills/loop-agent/references/harness-policy.md +22 -0
- 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: "
|
|
74
|
-
label: "
|
|
75
|
-
command: `agent-worker feature
|
|
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;
|