@tea-agent/loop-agent 0.19.0 → 0.20.1-beta.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 (31) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/application/dag/generate-task-dag.js +12 -1
  3. package/dist/executors/shell-executor.js +189 -2
  4. package/dist/worker/observe/spec-evidence.js +33 -0
  5. package/dist/worker/observe/static/views/dag-inspector.js +67 -4
  6. package/dist/workflows/dag/backend-test-markdown-workflow.js +163 -41
  7. package/dist/workflows/dag/backend-test-result-contract.js +30 -7
  8. package/dist/workflows/dag/frontend-prewrite-gate.js +172 -0
  9. package/dist/workflows/dag/frontend-project-capability.js +6 -2
  10. package/dist/workflows/dag/frontend-repair.js +7 -1
  11. package/dist/workflows/dag/frontend-review-context.js +43 -0
  12. package/dist/workflows/dag/frontend-verification-trace.js +34 -15
  13. package/dist/workflows/dag/governance-profile.js +14 -6
  14. package/dist/workflows/dag/init-hybrid.js +144 -399
  15. package/dist/workflows/dag/types.js +33 -0
  16. package/dist/workflows/dag/validate.js +22 -1
  17. package/docs/README.md +1 -0
  18. package/docs/templates/agent-dag.schema.json +40 -0
  19. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +23 -192
  20. package/docs/templates/backend-test-dag.json +8 -8
  21. package/docs/templates/backend-test-dag.review-cases.prompt.md +22 -75
  22. package/package.json +1 -1
  23. package/skills/frontend-design-review/SKILL.md +5 -3
  24. package/skills/frontend-design-review/references/review-checklist.md +3 -2
  25. package/skills/frontend-implementation/references/design-spec.md +16 -8
  26. package/skills/frontend-implementation/references/node-contracts.md +6 -8
  27. package/skills/frontend-review/SKILL.md +12 -9
  28. package/skills/frontend-review/references/review-findings.md +5 -1
  29. package/skills/frontend-verification/SKILL.md +5 -3
  30. package/skills/frontend-verification/references/verification-checklist.md +3 -2
  31. package/skills/loop-agent/references/hybrid-dag.md +2 -2
@@ -0,0 +1,43 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
4
+ import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
5
+ export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
6
+ async function readRequiredJson(runDir, relativePath) {
7
+ try {
8
+ return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
9
+ }
10
+ catch {
11
+ throw new Error(`frontend review context missing or invalid ${relativePath}`);
12
+ }
13
+ }
14
+ export async function runFrontendReviewContextGate(input) {
15
+ const diff = await runFrontendWorktreeDiffGate(input);
16
+ const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
17
+ const verificationTrace = await readRequiredJson(input.runDir, "contracts/frontend-verification-trace.json");
18
+ const repairAssessment = await readRequiredJson(input.runDir, "contracts/frontend-repair-assessment.json");
19
+ const payload = {
20
+ schemaVersion: 1,
21
+ schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
22
+ contract,
23
+ verificationTrace,
24
+ repairAssessment,
25
+ diff: {
26
+ schemaId: diff.schemaId,
27
+ patchPath: diff.patchPath,
28
+ patchSha256: diff.patchSha256,
29
+ changedFiles: diff.changedFiles,
30
+ untrackedFiles: diff.untrackedFiles,
31
+ empty: diff.empty,
32
+ },
33
+ };
34
+ const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
35
+ return { ok: true, artifactPath, diff };
36
+ }
37
+ export function formatFrontendReviewContextStdout(result) {
38
+ return [
39
+ "Frontend review context: pass",
40
+ `Artifact: ${result.artifactPath}`,
41
+ formatFrontendWorktreeDiffStdout(result.diff),
42
+ ].join("\n");
43
+ }
@@ -95,26 +95,45 @@ export async function runFrontendVerificationTraceGate(input) {
95
95
  }
96
96
  throw new Error(`trace: missing static/behavior verify node records (tried ${errors.join(", ")})`);
97
97
  }
98
- const staticNode = await loadFirstNode(staticCandidates);
99
- const behaviorNode = await loadFirstNode(behaviorCandidates);
98
+ const staticNode = input.evidence
99
+ ? {
100
+ nodeId: input.evidence.static.nodeId,
101
+ record: {
102
+ status: "FINISHED",
103
+ verifyEvidence: { commandLabels: input.evidence.static.commandLabels },
104
+ },
105
+ }
106
+ : await loadFirstNode(staticCandidates);
107
+ const behaviorNode = input.evidence
108
+ ? {
109
+ nodeId: input.evidence.behavior.nodeId,
110
+ record: {
111
+ status: "FINISHED",
112
+ verifyEvidence: { commandLabels: input.evidence.behavior.commandLabels },
113
+ },
114
+ }
115
+ : await loadFirstNode(behaviorCandidates);
100
116
  assertNodeFinished(staticNode.nodeId, staticNode.record);
101
117
  assertNodeFinished(behaviorNode.nodeId, behaviorNode.record);
102
118
  const labelOwners = collectCommandLabels({ nodeId: staticNode.nodeId, record: staticNode.record }, { nodeId: behaviorNode.nodeId, record: behaviorNode.record });
103
- const assessPath = path.join(input.runDir, "frontend-mock-assess-pi.json");
104
- try {
105
- const assess = (await readJsonFile(assessPath));
106
- const text = assess.assistantText?.trim() || assess.stdout?.trim() || "";
107
- const strategy = parseMockStrategyFromAssess(text);
108
- if (strategy && strategy !== contract.mockApi.strategy) {
109
- throw new Error(`trace: mock strategy mismatch: assess=${strategy} contract=${contract.mockApi.strategy}`);
119
+ // Legacy runs may still carry a standalone Mock assessment. New slim runs
120
+ // bind the strategy directly through the validated implementation contract.
121
+ if (!input.evidence) {
122
+ const assessPath = path.join(input.runDir, "frontend-mock-assess-pi.json");
123
+ try {
124
+ const assess = (await readJsonFile(assessPath));
125
+ const text = assess.assistantText?.trim() || assess.stdout?.trim() || "";
126
+ const strategy = parseMockStrategyFromAssess(text);
127
+ if (strategy && strategy !== contract.mockApi.strategy) {
128
+ throw new Error(`trace: mock strategy mismatch: assess=${strategy} contract=${contract.mockApi.strategy}`);
129
+ }
110
130
  }
111
- }
112
- catch (error) {
113
- if (error instanceof Error &&
114
- error.message.startsWith("trace: mock strategy mismatch")) {
115
- throw error;
131
+ catch (error) {
132
+ if (error instanceof Error &&
133
+ error.message.startsWith("trace: mock strategy mismatch")) {
134
+ throw error;
135
+ }
116
136
  }
117
- // assess optional for pure unit fixtures without mock node
118
137
  }
119
138
  const targets = [];
120
139
  const hardIssues = [];
@@ -104,16 +104,24 @@ function writeSetMatchesAnyPattern(writeSet, patterns) {
104
104
  return undefined;
105
105
  }
106
106
  function shellCommandLooksDeterministic(command) {
107
- return (/\bnpx vitest run\b/.test(command) ||
108
- /\bnpm run (lint|typecheck|test)\b/.test(command) ||
109
- /check-repo\.sh/.test(command) ||
110
- /\bshell\.preset\b/.test(command) ||
111
- /loop-agent-standard-verify/.test(command));
107
+ const normalized = command.replace(/["']/g, "");
108
+ return (/\bnpx vitest run\b/.test(normalized) ||
109
+ /\bnpm (?:run )?(?:lint|typecheck|test)\b/.test(normalized) ||
110
+ /check-repo\.sh/.test(normalized) ||
111
+ /\bshell\.preset\b/.test(normalized) ||
112
+ /loop-agent-standard-verify/.test(normalized));
112
113
  }
113
114
  function taskHasDeterministicShellVerification(task) {
114
115
  if (task.executor !== "shell")
115
116
  return false;
116
- const commands = resolveShellCommands(task.shell ?? { commands: [] });
117
+ const bundle = task.shell?.frontendVerificationBundle;
118
+ const commands = bundle
119
+ ? [
120
+ ...bundle.mockCommands,
121
+ ...bundle.staticCommands,
122
+ ...bundle.behaviorCommands,
123
+ ]
124
+ : resolveShellCommands(task.shell ?? { commands: [] });
117
125
  return commands.some(shellCommandLooksDeterministic);
118
126
  }
119
127
  function collectExclusiveWriters(spec) {