@tea-agent/loop-agent 0.43.0-next.12 → 0.43.0-next.13
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/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.43.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.43.0-next.13",
|
|
4
|
+
"gitSha": "4e92f5b2b1c43bc8971d9c9e6c21f76fa557189d",
|
|
5
|
+
"builtAt": "2026-09-11T00:55:37.486Z"
|
|
6
6
|
}
|
|
@@ -94,48 +94,43 @@ const DAG_RUNS_PREFIX = ".harness/dag-runs/";
|
|
|
94
94
|
* They are not product evidence and must not fail exclusive write guards.
|
|
95
95
|
* Intentional report/product writes remain governed by writePolicy/writeSet.
|
|
96
96
|
*/
|
|
97
|
-
|
|
98
|
-
const normalized = normalizePath(filePath);
|
|
99
|
-
if (!normalized)
|
|
100
|
-
return false;
|
|
97
|
+
const EPHEMERAL_CACHE_DIRS = [
|
|
101
98
|
// CodeGraph extension sync side effect (plan 2026-08-21 D7): a loaded
|
|
102
99
|
// pi-codegraph extension keeps an existing .codegraph/ index fresh at
|
|
103
100
|
// session start; those index files are extension-owned run state, not
|
|
104
101
|
// writer output, so they never count as write-guard violations.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
normalized.startsWith(".pytest_cache/")) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
if (normalized === ".mypy_cache" || normalized.startsWith(".mypy_cache/")) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
if (normalized === ".ruff_cache" || normalized.startsWith(".ruff_cache/")) {
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
102
|
+
".codegraph",
|
|
103
|
+
".pytest_cache",
|
|
104
|
+
".mypy_cache",
|
|
105
|
+
".ruff_cache",
|
|
118
106
|
// playwright-cli default session dumps (console/page/network) under repo cwd.
|
|
119
107
|
// Real browser case evidence must still be written under testcase/** explicitly.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (normalized === ".coverage" || normalized.startsWith(".coverage.")) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
108
|
+
".playwright-cli",
|
|
109
|
+
];
|
|
110
|
+
const EPHEMERAL_CACHE_DIR_PREFIXES = [
|
|
127
111
|
// vite/vitest runtime caches live under node_modules/.vite (e.g.
|
|
128
112
|
// node_modules/.vite/vitest/results.json). node_modules is the gitignored
|
|
129
113
|
// dependency tree — tool cache writes there are never a writer's product
|
|
130
114
|
// change and must not trip the write guard.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
115
|
+
"node_modules/.vite",
|
|
116
|
+
];
|
|
117
|
+
const EPHEMERAL_CACHE_FILES = new Set([".coverage"]);
|
|
118
|
+
const EPHEMERAL_CACHE_PATH_PATTERNS = [
|
|
119
|
+
/(?:^|\/)__pycache__(?:\/|$)/,
|
|
120
|
+
/\.(?:pyc|pyo)$/,
|
|
121
|
+
];
|
|
122
|
+
function isExactOrChildPath(value, names) {
|
|
123
|
+
return names.some((name) => value === name || value.startsWith(`${name}/`));
|
|
124
|
+
}
|
|
125
|
+
export function isEphemeralToolCachePath(filePath) {
|
|
126
|
+
const normalized = normalizePath(filePath);
|
|
127
|
+
if (!normalized)
|
|
128
|
+
return false;
|
|
129
|
+
return (isExactOrChildPath(normalized, EPHEMERAL_CACHE_DIRS) ||
|
|
130
|
+
EPHEMERAL_CACHE_DIR_PREFIXES.some((prefix) => normalized.startsWith(`${prefix}/`)) ||
|
|
131
|
+
EPHEMERAL_CACHE_FILES.has(normalized) ||
|
|
132
|
+
normalized.startsWith(".coverage.") ||
|
|
133
|
+
EPHEMERAL_CACHE_PATH_PATTERNS.some((pattern) => pattern.test(normalized)));
|
|
139
134
|
}
|
|
140
135
|
/** MVP post-run guard: only paths that changed during the shell node are checked. */
|
|
141
136
|
export function validateShellWriteGuard(input) {
|
|
@@ -24,6 +24,50 @@ export const frontendCommandCategorySchema = z.enum([
|
|
|
24
24
|
"unit-test",
|
|
25
25
|
"trace",
|
|
26
26
|
]);
|
|
27
|
+
/**
|
|
28
|
+
* Ordered string-blob rules. Order is the contract: the first matching rule wins,
|
|
29
|
+
* so rules that share keywords keep their original precedence. `nodeIdIncludes`
|
|
30
|
+
* and `blobIncludes` are alternatives within one rule (logical OR).
|
|
31
|
+
*/
|
|
32
|
+
const BLOB_RULES = [
|
|
33
|
+
{
|
|
34
|
+
result: "contract",
|
|
35
|
+
nodeIdIncludes: ["contract"],
|
|
36
|
+
blobIncludes: ["contract mismatch", "source binding"],
|
|
37
|
+
},
|
|
38
|
+
{ result: "trace", nodeIdIncludes: ["trace"], blobIncludes: ["trace:"] },
|
|
39
|
+
{ result: "review", nodeIdIncludes: ["review"] },
|
|
40
|
+
{
|
|
41
|
+
result: "unit-test",
|
|
42
|
+
nodeIdIncludes: ["behavior"],
|
|
43
|
+
blobIncludes: ["vitest", "jest", "npm test"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
result: "typecheck",
|
|
47
|
+
blobIncludes: ["typecheck", "tsc"],
|
|
48
|
+
blobPatterns: [/error ts\d+/i],
|
|
49
|
+
},
|
|
50
|
+
{ result: "lint", blobIncludes: ["eslint", "lint"] },
|
|
51
|
+
// "vite build"/"next build" are subsumed by "build".
|
|
52
|
+
{ result: "build", blobIncludes: ["build"] },
|
|
53
|
+
{
|
|
54
|
+
result: "component-test",
|
|
55
|
+
blobIncludes: ["testing-library", "component", "render("],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
result: "dependency",
|
|
59
|
+
blobIncludes: ["package.json", "unapproved dependency", "npm install"],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
result: "credential",
|
|
63
|
+
blobIncludes: ["credential", "api key", "secret"],
|
|
64
|
+
},
|
|
65
|
+
{ result: "deploy", blobIncludes: ["deploy", "production release"] },
|
|
66
|
+
{
|
|
67
|
+
result: "spec-unclear",
|
|
68
|
+
blobIncludes: ["spec unclear", "requirement ambiguous", "needs human"],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
27
71
|
export function classifyFrontendFailure(input) {
|
|
28
72
|
// AC-2: structured evidence takes priority over string-blob heuristics.
|
|
29
73
|
if (input.failureCategory === "write-guard") {
|
|
@@ -39,86 +83,21 @@ export function classifyFrontendFailure(input) {
|
|
|
39
83
|
return "trace";
|
|
40
84
|
}
|
|
41
85
|
if (input.commandCategory) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return "typecheck";
|
|
45
|
-
case "build":
|
|
46
|
-
return "build";
|
|
47
|
-
case "lint":
|
|
48
|
-
return "lint";
|
|
49
|
-
case "component-test":
|
|
50
|
-
return "component-test";
|
|
51
|
-
case "unit-test":
|
|
52
|
-
return "unit-test";
|
|
53
|
-
}
|
|
86
|
+
// "trace" already returned above, so the remaining categories map 1:1.
|
|
87
|
+
return input.commandCategory;
|
|
54
88
|
}
|
|
55
|
-
// Fallback: string-blob
|
|
56
|
-
//
|
|
89
|
+
// Fallback: ordered string-blob heuristics. Only the shell executor's
|
|
90
|
+
// structured category proves a write-boundary failure, so blob text never
|
|
91
|
+
// overrides the structured checks above.
|
|
57
92
|
const blob = `${input.nodeId}\n${input.failureCategory ?? ""}\n${input.stdout ?? ""}\n${input.stderr ?? ""}`.toLowerCase();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// the shell executor's structured category proves a write-boundary failure.
|
|
65
|
-
if (input.failureCategory === "write-guard") {
|
|
66
|
-
return "path";
|
|
67
|
-
}
|
|
68
|
-
if (input.nodeId.includes("trace") || blob.includes("trace:")) {
|
|
69
|
-
return "trace";
|
|
70
|
-
}
|
|
71
|
-
if (input.nodeId.includes("review")) {
|
|
72
|
-
return "review";
|
|
73
|
-
}
|
|
74
|
-
// Classify the failed verification command before inspecting its output.
|
|
75
|
-
// Full test output includes unrelated passing-test titles such as
|
|
76
|
-
// "secret-shaped" and "write-guard failure".
|
|
77
|
-
if (blob.includes("vitest") ||
|
|
78
|
-
blob.includes("jest") ||
|
|
79
|
-
blob.includes("npm test") ||
|
|
80
|
-
input.nodeId.includes("behavior")) {
|
|
81
|
-
return "unit-test";
|
|
82
|
-
}
|
|
83
|
-
if (blob.includes("typecheck") ||
|
|
84
|
-
blob.includes("tsc") ||
|
|
85
|
-
/error ts\d+/i.test(blob)) {
|
|
86
|
-
return "typecheck";
|
|
87
|
-
}
|
|
88
|
-
if (blob.includes("eslint") || blob.includes("lint")) {
|
|
89
|
-
return "lint";
|
|
90
|
-
}
|
|
91
|
-
if (blob.includes("build") ||
|
|
92
|
-
blob.includes("vite build") ||
|
|
93
|
-
blob.includes("next build")) {
|
|
94
|
-
return "build";
|
|
95
|
-
}
|
|
96
|
-
if (blob.includes("testing-library") ||
|
|
97
|
-
blob.includes("component") ||
|
|
98
|
-
blob.includes("render(")) {
|
|
99
|
-
return "component-test";
|
|
100
|
-
}
|
|
101
|
-
if (blob.includes("package.json") ||
|
|
102
|
-
blob.includes("unapproved dependency") ||
|
|
103
|
-
blob.includes("npm install")) {
|
|
104
|
-
return "dependency";
|
|
105
|
-
}
|
|
106
|
-
if (blob.includes("credential") ||
|
|
107
|
-
blob.includes("api key") ||
|
|
108
|
-
blob.includes("secret")) {
|
|
109
|
-
return "credential";
|
|
110
|
-
}
|
|
111
|
-
if (blob.includes("deploy") || blob.includes("production release")) {
|
|
112
|
-
return "deploy";
|
|
113
|
-
}
|
|
114
|
-
if (blob.includes("spec unclear") ||
|
|
115
|
-
blob.includes("requirement ambiguous") ||
|
|
116
|
-
blob.includes("needs human")) {
|
|
117
|
-
return "spec-unclear";
|
|
93
|
+
for (const rule of BLOB_RULES) {
|
|
94
|
+
if (rule.nodeIdIncludes?.some((keyword) => input.nodeId.includes(keyword)) ||
|
|
95
|
+
rule.blobIncludes?.some((keyword) => blob.includes(keyword)) ||
|
|
96
|
+
rule.blobPatterns?.some((pattern) => pattern.test(blob))) {
|
|
97
|
+
return rule.result;
|
|
98
|
+
}
|
|
118
99
|
}
|
|
119
100
|
if (input.failureCategory === "invalid-output")
|
|
120
101
|
return "contract";
|
|
121
|
-
if (input.failureCategory === "write-guard")
|
|
122
|
-
return "path";
|
|
123
102
|
return "unknown";
|
|
124
103
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tea-agent/loop-agent",
|
|
3
|
-
"version": "0.43.0-next.
|
|
3
|
+
"version": "0.43.0-next.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"loop-agent": "bin/loop-agent.js",
|
|
@@ -98,10 +98,13 @@
|
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@remixicon/react": "^4.9.0",
|
|
101
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
102
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
101
103
|
"@types/node": "^24.6.0",
|
|
102
104
|
"@types/react": "^19.1.8",
|
|
103
105
|
"@types/react-dom": "^19.1.6",
|
|
104
106
|
"@types/semver": "^7.7.1",
|
|
107
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
105
108
|
"react": "^19.1.0",
|
|
106
109
|
"react-dom": "^19.1.0",
|
|
107
110
|
"react-markdown": "^10.1.0",
|