@tea-agent/loop-agent 0.16.2 → 0.16.3
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
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
- Pi SDK 执行长推理或大段结构化输出时不再把高频流式增量事件无界累积到内存;同一响应在多个生命周期事件中重复出现的 Token 用量只统计一次,避免 `Invalid string length` 和成本数据虚高。
|
|
16
16
|
- 后端测试复合执行节点继续保持 clean environment、失败分类和 fail-closed outcome,并为 initial/final Result、repair eligibility、traceability 与 Observe 投影保留结构化运行证据。
|
|
17
17
|
|
|
18
|
+
## [0.16.3] - 2026-07-19
|
|
19
|
+
|
|
20
|
+
### 修复
|
|
21
|
+
|
|
22
|
+
- 前端实现 DAG 在 review 前新增确定性 `frontend-worktree-diff-shell`,写出 run-owned `diff_patch` 与清单,避免 review 因找不到 actual diff 而误拦截。
|
|
23
|
+
|
|
18
24
|
## [0.16.2] - 2026-07-19
|
|
19
25
|
|
|
20
26
|
### 修复
|
|
@@ -8,6 +8,7 @@ import { buildRequirementCoverageGateShellCommand, expandShellPreset, buildVerdi
|
|
|
8
8
|
import { materializeBackendTestAnalysisContract } from "../workflows/dag/backend-test-analysis-contract.js";
|
|
9
9
|
import { materializeFrontendImplementationContract } from "../workflows/dag/frontend-implementation-contract.js";
|
|
10
10
|
import { formatFrontendVerificationTraceStdout, runFrontendVerificationTraceGate, } from "../workflows/dag/frontend-verification-trace.js";
|
|
11
|
+
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "../workflows/dag/frontend-worktree-diff.js";
|
|
11
12
|
import { formatFrontendFailureAssessStdout, formatFrontendRepairContractStdout, runFrontendFailureAssessGate, runFrontendRepairContractGate, } from "../workflows/dag/frontend-repair.js";
|
|
12
13
|
import { formatTraceabilityGateStdout, materializeBackendTestCaseManifest, runBackendTestTraceabilityGate, } from "../workflows/dag/backend-test-case-manifest.js";
|
|
13
14
|
import { materializeBackendTestExecutionContract } from "../workflows/dag/backend-test-execution-contract.js";
|
|
@@ -606,6 +607,32 @@ export async function executeDagShellNode(input, meta) {
|
|
|
606
607
|
};
|
|
607
608
|
}
|
|
608
609
|
}
|
|
610
|
+
if (shell?.commands?.length === 1 &&
|
|
611
|
+
shell.commands[0] === "frontend-worktree-diff-gate") {
|
|
612
|
+
const started = Date.now();
|
|
613
|
+
try {
|
|
614
|
+
const result = await runFrontendWorktreeDiffGate({
|
|
615
|
+
runDir: meta.runDir,
|
|
616
|
+
workspaceRoot: input.cwd,
|
|
617
|
+
});
|
|
618
|
+
return {
|
|
619
|
+
ok: true,
|
|
620
|
+
stdout: formatFrontendWorktreeDiffStdout(result),
|
|
621
|
+
stderr: "",
|
|
622
|
+
failureCategory: "success",
|
|
623
|
+
durationMs: Date.now() - started,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
return {
|
|
628
|
+
ok: false,
|
|
629
|
+
stdout: "",
|
|
630
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
631
|
+
failureCategory: "invalid-output",
|
|
632
|
+
durationMs: Date.now() - started,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
609
636
|
if (shell?.commands?.length === 1 &&
|
|
610
637
|
shell.commands[0] === "frontend-failure-assess-gate") {
|
|
611
638
|
const started = Date.now();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
6
|
+
export const FRONTEND_WORKTREE_DIFF_SCHEMA_ID = "frontend-worktree-diff-v1";
|
|
7
|
+
function runGit(cwd, args) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const child = spawn("git", args, {
|
|
10
|
+
cwd,
|
|
11
|
+
env: process.env,
|
|
12
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13
|
+
});
|
|
14
|
+
let stdout = "";
|
|
15
|
+
let stderr = "";
|
|
16
|
+
child.stdout.on("data", (chunk) => {
|
|
17
|
+
stdout += String(chunk);
|
|
18
|
+
});
|
|
19
|
+
child.stderr.on("data", (chunk) => {
|
|
20
|
+
stderr += String(chunk);
|
|
21
|
+
});
|
|
22
|
+
child.on("error", reject);
|
|
23
|
+
child.on("close", (code) => {
|
|
24
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function splitLines(text) {
|
|
29
|
+
return text
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map((line) => line.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Capture the current workspace worktree as a run-owned review artifact.
|
|
36
|
+
*
|
|
37
|
+
* Frontend review is contractually required to inspect an "actual diff". The
|
|
38
|
+
* model cannot invent one, and failure-path `changes.patch` under Task Pool is
|
|
39
|
+
* not visible while the DAG is still active. This gate freezes a deterministic
|
|
40
|
+
* patch + inventory under the current run directory before review runs.
|
|
41
|
+
*/
|
|
42
|
+
export async function runFrontendWorktreeDiffGate(input) {
|
|
43
|
+
const root = path.resolve(input.workspaceRoot);
|
|
44
|
+
const revParse = await runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
45
|
+
if (revParse.code !== 0 || revParse.stdout.trim() !== "true") {
|
|
46
|
+
throw new Error(`frontend worktree diff gate requires a git worktree: ${revParse.stderr.trim() || revParse.stdout.trim() || "not a git repository"}`);
|
|
47
|
+
}
|
|
48
|
+
// Match failure capture semantics: binary-capable tracked diff vs HEAD.
|
|
49
|
+
const diff = await runGit(root, ["diff", "--binary", "HEAD"]);
|
|
50
|
+
if (diff.code !== 0) {
|
|
51
|
+
throw new Error(`frontend worktree diff gate git diff failed: ${diff.stderr.trim() || diff.stdout.trim()}`);
|
|
52
|
+
}
|
|
53
|
+
const nameOnly = await runGit(root, [
|
|
54
|
+
"diff",
|
|
55
|
+
"--name-only",
|
|
56
|
+
"--diff-filter=ACDMRTUXB",
|
|
57
|
+
"HEAD",
|
|
58
|
+
]);
|
|
59
|
+
if (nameOnly.code !== 0) {
|
|
60
|
+
throw new Error(`frontend worktree diff gate git name-only failed: ${nameOnly.stderr.trim() || nameOnly.stdout.trim()}`);
|
|
61
|
+
}
|
|
62
|
+
const untracked = await runGit(root, [
|
|
63
|
+
"ls-files",
|
|
64
|
+
"--others",
|
|
65
|
+
"--exclude-standard",
|
|
66
|
+
]);
|
|
67
|
+
if (untracked.code !== 0) {
|
|
68
|
+
throw new Error(`frontend worktree diff gate ls-files failed: ${untracked.stderr.trim() || untracked.stdout.trim()}`);
|
|
69
|
+
}
|
|
70
|
+
const changedFiles = splitLines(nameOnly.stdout).sort();
|
|
71
|
+
const untrackedFiles = splitLines(untracked.stdout).sort();
|
|
72
|
+
const patchBody = diff.stdout.endsWith("\n") || diff.stdout.length === 0
|
|
73
|
+
? diff.stdout
|
|
74
|
+
: `${diff.stdout}\n`;
|
|
75
|
+
const untrackedSection = untrackedFiles.length === 0
|
|
76
|
+
? ""
|
|
77
|
+
: [
|
|
78
|
+
"",
|
|
79
|
+
"# Untracked files (inventory only; contents not inlined)",
|
|
80
|
+
...untrackedFiles.map((file) => `# untracked: ${file}`),
|
|
81
|
+
"",
|
|
82
|
+
].join("\n");
|
|
83
|
+
const patchText = `${patchBody}${untrackedSection}`;
|
|
84
|
+
const patchSha256 = createHash("sha256").update(patchText).digest("hex");
|
|
85
|
+
const relativePatch = "artifacts/diff_patch.patch";
|
|
86
|
+
const absolutePatch = path.join(input.runDir, ...relativePatch.split("/"));
|
|
87
|
+
await mkdir(path.dirname(absolutePatch), { recursive: true });
|
|
88
|
+
await writeFile(absolutePatch, patchText, "utf8");
|
|
89
|
+
const payload = {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
schemaId: FRONTEND_WORKTREE_DIFF_SCHEMA_ID,
|
|
92
|
+
patchPath: relativePatch,
|
|
93
|
+
patchSha256,
|
|
94
|
+
changedFiles,
|
|
95
|
+
untrackedFiles,
|
|
96
|
+
empty: changedFiles.length === 0 && untrackedFiles.length === 0,
|
|
97
|
+
capturedAt: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-worktree-diff.json", payload);
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
schemaId: FRONTEND_WORKTREE_DIFF_SCHEMA_ID,
|
|
103
|
+
patchPath: relativePatch,
|
|
104
|
+
patchAbsolutePath: absolutePatch,
|
|
105
|
+
patchSha256,
|
|
106
|
+
changedFiles,
|
|
107
|
+
untrackedFiles,
|
|
108
|
+
empty: payload.empty,
|
|
109
|
+
capturedAt: payload.capturedAt,
|
|
110
|
+
artifactPath,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function formatFrontendWorktreeDiffStdout(result) {
|
|
114
|
+
return [
|
|
115
|
+
`schemaId=${result.schemaId}`,
|
|
116
|
+
`patchPath=${result.patchPath}`,
|
|
117
|
+
`patchSha256=${result.patchSha256}`,
|
|
118
|
+
`changedFiles=${result.changedFiles.length}`,
|
|
119
|
+
`untrackedFiles=${result.untrackedFiles.length}`,
|
|
120
|
+
`empty=${result.empty ? "true" : "false"}`,
|
|
121
|
+
result.artifactPath ? `artifactPath=${result.artifactPath}` : "",
|
|
122
|
+
...result.changedFiles.map((file) => `changed:${file}`),
|
|
123
|
+
...result.untrackedFiles.map((file) => `untracked:${file}`),
|
|
124
|
+
]
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join("\n");
|
|
127
|
+
}
|
|
@@ -2328,9 +2328,33 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2328
2328
|
timeoutMs: 120000,
|
|
2329
2329
|
},
|
|
2330
2330
|
},
|
|
2331
|
+
{
|
|
2332
|
+
id: "frontend-worktree-diff-shell",
|
|
2333
|
+
depends_on: [
|
|
2334
|
+
"frontend-verification-retrace-shell",
|
|
2335
|
+
"frontend-behavior-reverify-shell",
|
|
2336
|
+
"frontend-static-reverify-shell",
|
|
2337
|
+
"frontend-repair-pi",
|
|
2338
|
+
implementId,
|
|
2339
|
+
],
|
|
2340
|
+
role: "verifier",
|
|
2341
|
+
executor: "shell",
|
|
2342
|
+
complexity: "LOW",
|
|
2343
|
+
writePolicy: "read-only",
|
|
2344
|
+
allowedPaths: readOnlyPaths,
|
|
2345
|
+
forbiddenPaths,
|
|
2346
|
+
outputContract: "Run-owned actual worktree diff_patch (artifacts/diff_patch.patch) plus contracts/frontend-worktree-diff.json inventory/hash for review. No product worktree writes.",
|
|
2347
|
+
subtask_prompt: "Capture the authoritative actual diff after implement/repair/reverify so frontend-review-pi can audit changed files without relying on failure-path patches or model summaries.",
|
|
2348
|
+
shell: {
|
|
2349
|
+
commands: ["frontend-worktree-diff-gate"],
|
|
2350
|
+
cwd: ".",
|
|
2351
|
+
timeoutMs: 120000,
|
|
2352
|
+
},
|
|
2353
|
+
},
|
|
2331
2354
|
{
|
|
2332
2355
|
id: "frontend-review-pi",
|
|
2333
2356
|
depends_on: [
|
|
2357
|
+
"frontend-worktree-diff-shell",
|
|
2334
2358
|
"frontend-verification-retrace-shell",
|
|
2335
2359
|
"frontend-static-reverify-shell",
|
|
2336
2360
|
"frontend-behavior-reverify-shell",
|
|
@@ -2359,7 +2383,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2359
2383
|
"Review the frontend implementation and verification evidence.",
|
|
2360
2384
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
2361
2385
|
"Any Critical or Important finding must force VERDICT: request-revision.",
|
|
2362
|
-
"Read the validated frontend-implementation-contract, frontend-verification-trace evidence, static/behavior shell facts, and actual diff. Trace proves command/file/symbol binding only—not semantic correctness.",
|
|
2386
|
+
"Read the validated frontend-implementation-contract, frontend-verification-trace evidence, static/behavior shell facts, and the run-owned actual diff from frontend-worktree-diff-shell (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
|
|
2363
2387
|
"Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
|
|
2364
2388
|
"Use the direct contract, original plan, revision/no-op result, and final design review to reconstruct the approved plan and design verdict; do not infer them from the implementation summary.",
|
|
2365
2389
|
"Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
|
package/package.json
CHANGED
|
@@ -12,8 +12,12 @@ references:
|
|
|
12
12
|
Use for `frontend-review-pi`; read the findings guide first. Required inputs are
|
|
13
13
|
original task/reference material, contract/constraints, Mock assessment, original and
|
|
14
14
|
revised/confirmed plan, final design verdict, implementation summary, actual diff,
|
|
15
|
-
and static/behavior/optional Mock shell evidence.
|
|
16
|
-
|
|
15
|
+
and static/behavior/optional Mock shell evidence. The actual diff is the run-owned
|
|
16
|
+
`frontend-worktree-diff-shell` artifacts: `contracts/frontend-worktree-diff.json` and
|
|
17
|
+
`artifacts/diff_patch.patch` under the current DAG run directory. When those artifacts
|
|
18
|
+
exist, treat them as the authoritative actual diff—do not request revision solely for
|
|
19
|
+
"missing diff". Only force revision for a missing actual diff when both artifacts are
|
|
20
|
+
absent; never invent a diff from an implementation summary alone.
|
|
17
21
|
|
|
18
22
|
## Verdict Contract
|
|
19
23
|
|