@tea-agent/loop-agent 0.17.1 → 0.18.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 +96 -22
- package/dist/application/dag/args.js +9 -2
- package/dist/executors/dag-pi-executor.js +11 -0
- package/dist/executors/shell-executor.js +62 -2
- package/dist/governance/spine-audit.js +5 -2
- package/dist/worker/console/draft-store.js +81 -3
- package/dist/worker/console/interview/grill-me.js +253 -0
- package/dist/worker/console/operation-runner.js +2 -1
- package/dist/worker/console/operator-actions.js +609 -20
- package/dist/worker/console/pi-readiness.js +2 -0
- package/dist/worker/console/prd-identity.js +102 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +76 -0
- package/dist/worker/console/server.js +11 -2
- package/dist/worker/console/static/assets/index-KUSib7aM.js +16 -0
- package/dist/worker/console/static/assets/index-ucIzpaGJ.css +1 -0
- package/dist/worker/console/static/index.html +3 -3
- package/dist/worker/console/workflow-kinds.js +46 -0
- package/dist/worker/observe/routes.js +8 -1
- package/dist/worker/observe/static/index.html +8 -8
- package/dist/worker/observe/static/styles.css +296 -181
- package/dist/workflows/dag/backend-test-intake-context.js +148 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +528 -0
- package/dist/workflows/dag/init-hybrid.js +93 -109
- package/dist/workflows/dag/types.js +4 -0
- package/dist/workflows/loop/benchmark.js +1 -1
- package/docs/README.md +84 -39
- package/docs/architecture/README.md +8 -4
- package/docs/architecture/dag-execution.md +8 -1
- package/docs/architecture/evolution.md +38 -25
- package/docs/architecture/facts-and-state.md +2 -0
- package/docs/architecture/system-overview.md +6 -5
- package/docs/architecture/worker-and-feature.md +15 -8
- package/docs/init-surface.manifest.json +10 -1
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
- package/docs/templates/backend-test-dag.json +74 -219
- package/docs/templates/branch-merge-report.md +1 -1
- package/docs/templates/exec-plan.md +2 -0
- package/docs/templates/progress-log.md +3 -0
- package/package.json +1 -1
- package/skills/init-capability-evolution/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/index-BEIdBogJ.js +0 -9
- package/dist/worker/console/static/assets/index-Rt0TqimP.css +0 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic grill-me Requirement Interview engine (design §7.8).
|
|
3
|
+
* One open question at a time; Draft updates from answers; integrity
|
|
4
|
+
* assessment when required fields are present.
|
|
5
|
+
*/
|
|
6
|
+
import { consoleWorkflowKindPromptList, normalizeConsoleWorkflowKind, } from "../workflow-kinds.js";
|
|
7
|
+
const GAP_ORDER = [
|
|
8
|
+
"objective",
|
|
9
|
+
"scope",
|
|
10
|
+
"nonGoals",
|
|
11
|
+
"acceptanceCriteria",
|
|
12
|
+
"taskKind",
|
|
13
|
+
"allowedPaths",
|
|
14
|
+
"forbiddenPaths",
|
|
15
|
+
"verifyCommands",
|
|
16
|
+
];
|
|
17
|
+
function hasText(value) {
|
|
18
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
19
|
+
}
|
|
20
|
+
function hasItems(value) {
|
|
21
|
+
return Array.isArray(value) && value.length > 0;
|
|
22
|
+
}
|
|
23
|
+
export function listDraftGaps(draft) {
|
|
24
|
+
const gaps = [];
|
|
25
|
+
const req = draft.requirement ?? {};
|
|
26
|
+
if (!hasText(req.objective))
|
|
27
|
+
gaps.push("objective");
|
|
28
|
+
if (!hasItems(req.scope))
|
|
29
|
+
gaps.push("scope");
|
|
30
|
+
if (!hasItems(req.nonGoals))
|
|
31
|
+
gaps.push("nonGoals");
|
|
32
|
+
if (!hasItems(req.acceptanceCriteria))
|
|
33
|
+
gaps.push("acceptanceCriteria");
|
|
34
|
+
if (!hasText(draft.taskKind))
|
|
35
|
+
gaps.push("taskKind");
|
|
36
|
+
if (!hasItems(draft.constraints?.allowedPaths))
|
|
37
|
+
gaps.push("allowedPaths");
|
|
38
|
+
if (!hasItems(draft.constraints?.forbiddenPaths))
|
|
39
|
+
gaps.push("forbiddenPaths");
|
|
40
|
+
if (!hasItems(draft.verification?.commands))
|
|
41
|
+
gaps.push("verifyCommands");
|
|
42
|
+
return gaps;
|
|
43
|
+
}
|
|
44
|
+
export function isDraftStructurallyComplete(draft) {
|
|
45
|
+
return listDraftGaps(draft).length === 0;
|
|
46
|
+
}
|
|
47
|
+
export function buildQuestionForGap(gapId, draft) {
|
|
48
|
+
const taskId = draft.taskId;
|
|
49
|
+
const questions = {
|
|
50
|
+
objective: {
|
|
51
|
+
id: `q-${gapId}`,
|
|
52
|
+
gapId,
|
|
53
|
+
text: `任务 ${taskId} 的核心目标(objective)是什么?请用一句话说明要交付什么。`,
|
|
54
|
+
recommendation: `实现并验证 ${taskId} 的明确、可验收交付物`,
|
|
55
|
+
affectsFields: ["requirement.objective"],
|
|
56
|
+
risk: "high",
|
|
57
|
+
},
|
|
58
|
+
scope: {
|
|
59
|
+
id: `q-${gapId}`,
|
|
60
|
+
gapId,
|
|
61
|
+
text: "本轮允许改动的范围(scope)是哪些路径/模块?用逗号或换行列出。",
|
|
62
|
+
recommendation: "src/worker/console/**",
|
|
63
|
+
affectsFields: ["requirement.scope"],
|
|
64
|
+
risk: "high",
|
|
65
|
+
},
|
|
66
|
+
nonGoals: {
|
|
67
|
+
id: `q-${gapId}`,
|
|
68
|
+
gapId,
|
|
69
|
+
text: "明确非目标(non-goals)是什么?本轮刻意不做哪些事?",
|
|
70
|
+
recommendation: "Phase 4 General Operator Chat;合并 Console/Observe",
|
|
71
|
+
affectsFields: ["requirement.nonGoals"],
|
|
72
|
+
risk: "medium",
|
|
73
|
+
},
|
|
74
|
+
acceptanceCriteria: {
|
|
75
|
+
id: `q-${gapId}`,
|
|
76
|
+
gapId,
|
|
77
|
+
text: "验收标准有哪些?可用分号或换行列出(将编号为 AC-1…)。",
|
|
78
|
+
recommendation: "Happy Path 可走通 Interview→validate→apply;无 Cancel;无直接改代码 CTA",
|
|
79
|
+
affectsFields: ["requirement.acceptanceCriteria"],
|
|
80
|
+
risk: "high",
|
|
81
|
+
},
|
|
82
|
+
taskKind: {
|
|
83
|
+
id: `q-${gapId}`,
|
|
84
|
+
gapId,
|
|
85
|
+
text: `流程类型(taskKind)应设为?(${consoleWorkflowKindPromptList()})`,
|
|
86
|
+
recommendation: "standard",
|
|
87
|
+
affectsFields: ["taskKind"],
|
|
88
|
+
risk: "medium",
|
|
89
|
+
},
|
|
90
|
+
allowedPaths: {
|
|
91
|
+
id: `q-${gapId}`,
|
|
92
|
+
gapId,
|
|
93
|
+
text: "结构化 allowedPaths(写入白名单)?逗号/换行分隔 glob。",
|
|
94
|
+
recommendation: "src/worker/console/**, test/worker/console/**",
|
|
95
|
+
affectsFields: ["constraints.allowedPaths"],
|
|
96
|
+
risk: "high",
|
|
97
|
+
},
|
|
98
|
+
forbiddenPaths: {
|
|
99
|
+
id: `q-${gapId}`,
|
|
100
|
+
gapId,
|
|
101
|
+
text: "forbiddenPaths(禁止写入)?逗号/换行分隔。",
|
|
102
|
+
recommendation: "src/cli/**, bin/**, package.json",
|
|
103
|
+
affectsFields: ["constraints.forbiddenPaths"],
|
|
104
|
+
risk: "high",
|
|
105
|
+
},
|
|
106
|
+
verifyCommands: {
|
|
107
|
+
id: `q-${gapId}`,
|
|
108
|
+
gapId,
|
|
109
|
+
text: "验证命令(verifyCommands)?可给一条或多条 shell(换行分隔)。",
|
|
110
|
+
recommendation: "npx vitest run test/worker/console",
|
|
111
|
+
affectsFields: ["verification.commands"],
|
|
112
|
+
risk: "medium",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
return questions[gapId];
|
|
116
|
+
}
|
|
117
|
+
export function nextGrillMeQuestion(draft) {
|
|
118
|
+
const gaps = listDraftGaps(draft);
|
|
119
|
+
for (const gapId of GAP_ORDER) {
|
|
120
|
+
if (gaps.includes(gapId))
|
|
121
|
+
return buildQuestionForGap(gapId, draft);
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
function splitList(text) {
|
|
126
|
+
return text
|
|
127
|
+
.split(/[\n,;,;]+/)
|
|
128
|
+
.map((s) => s.trim())
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
}
|
|
131
|
+
function resolveAnswerText(response, recommendation, text) {
|
|
132
|
+
if (response === "accept-recommendation")
|
|
133
|
+
return recommendation;
|
|
134
|
+
if (response === "defer")
|
|
135
|
+
return text?.trim() || recommendation;
|
|
136
|
+
return (text ?? "").trim() || recommendation;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Apply one grill-me answer onto Draft. Returns updated draft (immutable merge).
|
|
140
|
+
*/
|
|
141
|
+
export function applyGrillMeAnswer(draft, question, answer) {
|
|
142
|
+
const value = resolveAnswerText(answer.response, question.recommendation, answer.text);
|
|
143
|
+
const next = {
|
|
144
|
+
...draft,
|
|
145
|
+
requirement: { ...draft.requirement },
|
|
146
|
+
constraints: {
|
|
147
|
+
invariants: draft.constraints?.invariants ?? [],
|
|
148
|
+
allowedPaths: draft.constraints?.allowedPaths ?? [],
|
|
149
|
+
forbiddenPaths: draft.constraints?.forbiddenPaths ?? [],
|
|
150
|
+
...draft.constraints,
|
|
151
|
+
},
|
|
152
|
+
verification: {
|
|
153
|
+
commands: draft.verification?.commands ?? [],
|
|
154
|
+
...draft.verification,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
switch (question.gapId) {
|
|
158
|
+
case "objective":
|
|
159
|
+
next.requirement = { ...next.requirement, objective: value };
|
|
160
|
+
break;
|
|
161
|
+
case "scope":
|
|
162
|
+
next.requirement = { ...next.requirement, scope: splitList(value) };
|
|
163
|
+
break;
|
|
164
|
+
case "nonGoals":
|
|
165
|
+
next.requirement = { ...next.requirement, nonGoals: splitList(value) };
|
|
166
|
+
break;
|
|
167
|
+
case "acceptanceCriteria":
|
|
168
|
+
next.requirement = {
|
|
169
|
+
...next.requirement,
|
|
170
|
+
acceptanceCriteria: splitList(value).map((text, i) => ({
|
|
171
|
+
id: `AC-${i + 1}`,
|
|
172
|
+
text,
|
|
173
|
+
})),
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
case "taskKind":
|
|
177
|
+
next.taskKind = normalizeConsoleWorkflowKind(value, "standard");
|
|
178
|
+
break;
|
|
179
|
+
case "allowedPaths":
|
|
180
|
+
next.constraints = {
|
|
181
|
+
...next.constraints,
|
|
182
|
+
allowedPaths: splitList(value),
|
|
183
|
+
};
|
|
184
|
+
break;
|
|
185
|
+
case "forbiddenPaths":
|
|
186
|
+
next.constraints = {
|
|
187
|
+
...next.constraints,
|
|
188
|
+
forbiddenPaths: splitList(value),
|
|
189
|
+
};
|
|
190
|
+
break;
|
|
191
|
+
case "verifyCommands":
|
|
192
|
+
next.verification = {
|
|
193
|
+
...next.verification,
|
|
194
|
+
commands: splitList(value).map((command, i) => ({
|
|
195
|
+
label: `verify-${i + 1}`,
|
|
196
|
+
command,
|
|
197
|
+
timeoutMs: 120_000,
|
|
198
|
+
})),
|
|
199
|
+
};
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
if (answer.response === "defer" && question.risk !== "high") {
|
|
203
|
+
const assumptions = Array.isArray(draft.assumptions)
|
|
204
|
+
? [...draft.assumptions]
|
|
205
|
+
: [];
|
|
206
|
+
assumptions.push({
|
|
207
|
+
questionId: question.id,
|
|
208
|
+
gapId: question.gapId,
|
|
209
|
+
assumed: value,
|
|
210
|
+
at: new Date().toISOString(),
|
|
211
|
+
});
|
|
212
|
+
next.assumptions = assumptions;
|
|
213
|
+
}
|
|
214
|
+
return next;
|
|
215
|
+
}
|
|
216
|
+
export function emptyDraft(taskId, title, options) {
|
|
217
|
+
const taskKind = options?.taskKind?.trim()
|
|
218
|
+
? normalizeConsoleWorkflowKind(options.taskKind, "standard")
|
|
219
|
+
: undefined;
|
|
220
|
+
return {
|
|
221
|
+
schemaVersion: 1,
|
|
222
|
+
taskId,
|
|
223
|
+
title: title?.trim() || taskId,
|
|
224
|
+
taskKind,
|
|
225
|
+
requirement: {},
|
|
226
|
+
constraints: {
|
|
227
|
+
invariants: [],
|
|
228
|
+
allowedPaths: [],
|
|
229
|
+
forbiddenPaths: [],
|
|
230
|
+
},
|
|
231
|
+
verification: {
|
|
232
|
+
commands: [],
|
|
233
|
+
},
|
|
234
|
+
openQuestions: [],
|
|
235
|
+
assumptions: [],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
export function draftSummary(draft) {
|
|
239
|
+
return {
|
|
240
|
+
taskId: draft.taskId,
|
|
241
|
+
title: draft.title,
|
|
242
|
+
taskKind: draft.taskKind ?? null,
|
|
243
|
+
objective: draft.requirement?.objective ?? null,
|
|
244
|
+
scope: draft.requirement?.scope ?? [],
|
|
245
|
+
nonGoals: draft.requirement?.nonGoals ?? [],
|
|
246
|
+
acceptanceCriteria: draft.requirement?.acceptanceCriteria ?? [],
|
|
247
|
+
allowedPaths: draft.constraints?.allowedPaths ?? [],
|
|
248
|
+
forbiddenPaths: draft.constraints?.forbiddenPaths ?? [],
|
|
249
|
+
verifyCommands: draft.verification?.commands ?? [],
|
|
250
|
+
gaps: listDraftGaps(draft),
|
|
251
|
+
structurallyComplete: isDraftStructurallyComplete(draft),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -128,7 +128,8 @@ async function finalizeFromWorkerResult(operationId, result, deps) {
|
|
|
128
128
|
errorMessage: ok
|
|
129
129
|
? undefined
|
|
130
130
|
: (envelope?.error?.message ??
|
|
131
|
-
|
|
131
|
+
(previewStderr.trim() ||
|
|
132
|
+
`command failed with exit ${result.exitCode}`)),
|
|
132
133
|
});
|
|
133
134
|
stateEvent(deps.events, updated, state);
|
|
134
135
|
deps.events.append(operationId, {
|