@tea-agent/loop-agent 0.44.0-next.7 → 0.44.0-next.9
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 +100 -0
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +25 -41
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +519 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +198 -118
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +76 -11
- package/dist/executors/shell-executor.js +88 -12
- package/dist/infrastructure/console/operation-store.js +2 -0
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/task/config-types.js +22 -9
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -8
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/operator-actions.js +0 -1
- package/dist/worker/console/operator-user-error.js +1 -1
- package/dist/worker/console/prd-intake-bridge.js +5 -15
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DTuku_-7.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CQbCsSqb.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C-xUntTR.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DhNlwGAl.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B7j3nx8o.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BCrcxEh2.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CsaePDb3.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H--SRQc1rN.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C5BFY2Po.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Caso6uN7.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-AY2ZSAx2.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DiMqZQ_8.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-gpvvriOp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUdyoJhI.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bp1xW3T4.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-D-JIcOIT.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C-NKFflx.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CSOcvOAA.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BQSBltMy.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0LVC45G.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cypg3nqy.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BBDU88XJ.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BZusLqSb.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Cfolotcr.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CjfyUy0m.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DJdz7txE.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CQqgPO6y.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BOpGfS2m.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-C6UCu0qx.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/{index-DWKejNVJ.js → index-CTMZsDmh.js} +98 -98
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWeaVEUB.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C8gmlBbk.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DF3bm8Bp.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D2fG2Vxi.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BGKgMc2T.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DF6mMPJ9.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-AMhePheC.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DN1Cl0W0.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Cx9ngUeN.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BcYPTQJb.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BqHUZWnH.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CmqWZ0-B.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P--4YetH1y.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DGD4VMfD.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-QVw9q7rh.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CuemV9pe.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-C7oKzZKs.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-C2Of60nT.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DHtpQHyL.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-COi9OhSy.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DPHQfRrt.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -59
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +11 -10
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -6
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +2 -1
- package/dist/workflows/dag/init-hybrid.js +108 -37
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -67
- package/dist/workflows/dag/runner.js +58 -12
- package/dist/workflows/dag/types.js +15 -8
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/templates/backend-test-dag.json +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +1 -1
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/dist/worker/console/static/assets/channel-5_os7t_x.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/index-CUT440A8.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BdlGdIJw.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DjjbHbde.js +0 -8
|
@@ -1,59 +1,217 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
4
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
3
5
|
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
export const frontendTestObservationSchema = z.object({
|
|
7
|
+
scheme: z.literal("vitest-json-v1"),
|
|
8
|
+
commandId: z.string().optional(),
|
|
9
|
+
status: z.enum(["supported", "deferred", "unsupported"]),
|
|
10
|
+
commandSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
11
|
+
cwd: z.string(),
|
|
12
|
+
packageScript: z.object({ file: z.string(), name: z.string(), sha256: z.string().nullable() }).optional(),
|
|
13
|
+
reason: z.string().optional(),
|
|
14
|
+
}).strict();
|
|
15
|
+
const hash = (text) => createHash("sha256").update(text).digest("hex");
|
|
16
|
+
const quote = (text) => `'${text.replace(/'/g, `'"'"'`)}'`;
|
|
17
|
+
/** Literal shell words only. Keep spans to replace a wrapper payload as ONE
|
|
18
|
+
* shell argument; never splice nested quotes into an already quoted payload. */
|
|
19
|
+
function words(command) {
|
|
20
|
+
const result = [];
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < command.length) {
|
|
23
|
+
if (/[\r\n]/.test(command[i]))
|
|
24
|
+
throw Error("multiple shell lines are unsupported");
|
|
25
|
+
if (/\s/.test(command[i])) {
|
|
26
|
+
i++;
|
|
27
|
+
continue;
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
const start = i;
|
|
30
|
+
if (command.slice(i, i + 2) === "&&") {
|
|
31
|
+
result.push({ value: "&&", start, end: i += 2, operator: true });
|
|
32
|
+
continue;
|
|
19
33
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
let value = "";
|
|
35
|
+
let quoted;
|
|
36
|
+
while (i < command.length) {
|
|
37
|
+
const c = command[i];
|
|
38
|
+
if (quoted === "'") {
|
|
39
|
+
if (c === "'")
|
|
40
|
+
quoted = undefined;
|
|
41
|
+
else
|
|
42
|
+
value += c;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (c === "\\") {
|
|
47
|
+
const next = command[++i];
|
|
48
|
+
if (!next || /[\r\n]/.test(next))
|
|
49
|
+
throw Error("invalid shell escape");
|
|
50
|
+
if (quoted === '"' && !['$', '`', '"', '\\'].includes(next))
|
|
51
|
+
value += "\\";
|
|
52
|
+
value += next;
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (quoted === '"') {
|
|
57
|
+
if (c === '"')
|
|
58
|
+
quoted = undefined;
|
|
59
|
+
else if (c === "$" || c === "`")
|
|
60
|
+
throw Error("shell expansion is unsupported");
|
|
61
|
+
else
|
|
62
|
+
value += c;
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (/\s/.test(c) || command.slice(i, i + 2) === "&&")
|
|
67
|
+
break;
|
|
68
|
+
if (c === "'" || c === '"') {
|
|
69
|
+
quoted = c;
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (/[;&|<>`$(){}*?\[\]#~]/.test(c))
|
|
74
|
+
throw Error("shell operators or expansion are unsupported");
|
|
75
|
+
value += c;
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
if (quoted)
|
|
79
|
+
throw Error("unterminated shell quote");
|
|
80
|
+
if (i === start)
|
|
81
|
+
throw Error("unsupported shell syntax");
|
|
82
|
+
result.push({ value, start, end: i });
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
function literalCommand(command, cwd, depth = 0) {
|
|
87
|
+
const tokens = words(command);
|
|
88
|
+
let offset = 0;
|
|
89
|
+
if (tokens[0]?.value === "cd") {
|
|
90
|
+
if (tokens[1]?.operator || tokens[2]?.value !== "&&" || !tokens[2].operator)
|
|
91
|
+
throw Error("only a leading literal cd is supported");
|
|
92
|
+
cwd = path.resolve(cwd, tokens[1].value);
|
|
93
|
+
offset = 3;
|
|
94
|
+
}
|
|
95
|
+
if (tokens.slice(offset).some(t => t.operator))
|
|
96
|
+
throw Error("compound commands are unsupported");
|
|
97
|
+
while (tokens[offset] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(command.slice(tokens[offset].start, tokens[offset].end)))
|
|
98
|
+
offset++;
|
|
99
|
+
const argv = tokens.slice(offset).map(t => t.value);
|
|
100
|
+
if (argv[0] === "bash") {
|
|
101
|
+
// `bash -lc <payload>` is a wrapper: unwrap it and keep resolving the
|
|
102
|
+
// payload. Repeated layers are unwrapped too, matching the
|
|
103
|
+
// normalization the DAG generator already applies
|
|
104
|
+
// (normalizeVerifyCommandArgs in init-hybrid). Depth stays bounded so a
|
|
105
|
+
// pathological self-nesting payload terminates.
|
|
106
|
+
if (argv.length === 3 && argv[1] === "-lc") {
|
|
107
|
+
if (depth >= 8)
|
|
108
|
+
throw Error("unsupported bash wrapper");
|
|
109
|
+
const payload = tokens[offset + 2];
|
|
110
|
+
const inner = literalCommand(argv[2], cwd, depth + 1);
|
|
111
|
+
return { ...inner, append: suffix => command.slice(0, payload.start) + quote(inner.append(suffix)) + command.slice(payload.end) };
|
|
112
|
+
}
|
|
113
|
+
// A plain `bash <script> [args…]` invocation is NOT a wrapper: it is an
|
|
114
|
+
// ordinary command that simply is not a vitest/npm-test adapter. The
|
|
115
|
+
// frozen command for a declared repo script is
|
|
116
|
+
// `… 'bash' '-lc' 'bash scripts/check-repo.sh'`, so the payload lands
|
|
117
|
+
// here; throwing made the caller record
|
|
118
|
+
// FRONTEND_TEST_OBSERVATION_INVALID and blocked writer admission before
|
|
119
|
+
// implement could run. Returning the argv lets the normal capability
|
|
120
|
+
// check classify it as unsupported (non-fatal) instead.
|
|
121
|
+
if (argv.length >= 2)
|
|
122
|
+
return { argv, cwd, append: suffix => command.trimEnd() + suffix };
|
|
123
|
+
throw Error("unsupported bash wrapper");
|
|
124
|
+
}
|
|
125
|
+
return { argv, cwd, append: suffix => command.trimEnd() + suffix };
|
|
126
|
+
}
|
|
127
|
+
function directVitest(argv) {
|
|
128
|
+
const args = [...argv];
|
|
129
|
+
if (["npx"].includes(args[0] ?? ""))
|
|
130
|
+
args.shift();
|
|
131
|
+
else if (["pnpm", "yarn"].includes(args[0] ?? "") && args[1] === "exec")
|
|
132
|
+
args.splice(0, 2);
|
|
133
|
+
return args[0] === "vitest" && args[1] === "run";
|
|
134
|
+
}
|
|
135
|
+
function hasReporter(argv) { return argv.some(arg => /^--(?:reporter|outputFile)(?:[.=]|$)/.test(arg)); }
|
|
136
|
+
/** Shared read-only capability resolver, used at generation, admission and
|
|
137
|
+
* immediately before execution. It never executes or rewrites an npm script. */
|
|
138
|
+
export async function inspectFrontendTestCommand(input) {
|
|
139
|
+
const root = path.resolve(input.workspaceRoot ?? input.cwd);
|
|
140
|
+
let cwd = path.resolve(input.cwd);
|
|
141
|
+
const base = { scheme: "vitest-json-v1", status: "unsupported", commandSha256: hash(input.command), cwd: path.relative(root, cwd).replace(/\\/g, "/") || "." };
|
|
142
|
+
try {
|
|
143
|
+
const parsed = literalCommand(input.command, cwd);
|
|
144
|
+
cwd = parsed.cwd;
|
|
145
|
+
base.cwd = path.relative(root, cwd).replace(/\\/g, "/") || ".";
|
|
146
|
+
if (base.cwd === ".." || base.cwd.startsWith("../") || path.isAbsolute(base.cwd))
|
|
147
|
+
throw Error("test cwd is outside the workspace");
|
|
148
|
+
// Reject symlink escapes too, while a future directory remains governed
|
|
149
|
+
// by the existing cwd/future-delivery preflight.
|
|
150
|
+
const physicalRoot = await realpath(root).catch(() => root);
|
|
151
|
+
const physicalCwd = await realpath(cwd).catch(() => cwd);
|
|
152
|
+
const physicalRelative = path.relative(physicalRoot, physicalCwd);
|
|
153
|
+
if (physicalRelative === ".." || physicalRelative.startsWith(`..${path.sep}`) || path.isAbsolute(physicalRelative))
|
|
154
|
+
throw Error("test cwd resolves outside the workspace");
|
|
155
|
+
if (hasReporter(parsed.argv))
|
|
156
|
+
throw Error("frozen command already owns reporter/outputFile flags");
|
|
157
|
+
let separator = "";
|
|
158
|
+
if (!directVitest(parsed.argv)) {
|
|
159
|
+
const argv = parsed.argv;
|
|
160
|
+
const name = argv[0] === "npm" ? argv[1] === "test" ? "test" : argv[1] === "run" ? argv[2] : undefined : undefined;
|
|
161
|
+
if (!name || !/^[\w:-]+$/.test(name))
|
|
162
|
+
throw Error("no supported test reporter adapter for this command");
|
|
163
|
+
const rest = argv.slice(argv[1] === "test" ? 2 : 3);
|
|
164
|
+
if (rest.length && rest[0] !== "--")
|
|
165
|
+
throw Error("npm script arguments require --");
|
|
166
|
+
const file = path.relative(root, path.join(cwd, "package.json")).replace(/\\/g, "/");
|
|
167
|
+
let manifest = {};
|
|
168
|
+
try {
|
|
169
|
+
manifest = JSON.parse(await readFile(path.join(cwd, "package.json"), "utf8"));
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error.code !== "ENOENT")
|
|
173
|
+
throw Error("package.json is unreadable or invalid");
|
|
174
|
+
}
|
|
175
|
+
const script = manifest?.scripts?.[name];
|
|
176
|
+
base.packageScript = { file, name, sha256: typeof script === "string" ? hash(JSON.stringify({ script, pre: manifest.scripts?.[`pre${name}`] ?? null, post: manifest.scripts?.[`post${name}`] ?? null })) : null };
|
|
177
|
+
if (script === undefined) {
|
|
178
|
+
const authorized = input.allowedPaths?.some(pattern => pathMatchesPattern(file, pattern)) && !input.forbiddenPaths?.some(pattern => pathMatchesPattern(file, pattern));
|
|
179
|
+
if (!authorized)
|
|
180
|
+
throw Error(`missing npm script ${name}; ${file} is not an authorized delivery`);
|
|
181
|
+
return { observation: { ...base, status: "deferred", reason: `Awaiting authorized ${file} script ${name} with vitest-json-v1 capability` }, effectiveCwd: cwd };
|
|
182
|
+
}
|
|
183
|
+
if (typeof script !== "string")
|
|
184
|
+
throw Error("npm script must be a string");
|
|
185
|
+
const scriptWords = words(script);
|
|
186
|
+
if (scriptWords.some(word => word.operator) || !directVitest(scriptWords.map(w => w.value)) || hasReporter(scriptWords.map(w => w.value)))
|
|
187
|
+
throw Error("npm script has no supported Vitest JSON reporter capability");
|
|
188
|
+
separator = rest.length ? "" : " --";
|
|
189
|
+
}
|
|
190
|
+
return { observation: { ...base, status: "supported" }, effectiveCwd: cwd,
|
|
191
|
+
withReporter: reportPath => parsed.append(`${separator} --reporter=json --outputFile=${quote(reportPath)}`) };
|
|
26
192
|
}
|
|
27
|
-
|
|
193
|
+
catch (error) {
|
|
194
|
+
return { observation: { ...base, status: "unsupported", reason: error instanceof Error ? error.message : String(error) }, effectiveCwd: cwd };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function compareFrontendTestObservation(frozen, observed, phase, writeSet = []) {
|
|
198
|
+
if (frozen.status === "unsupported")
|
|
199
|
+
return frozen.reason ?? "unsupported test observation scheme";
|
|
200
|
+
if (observed.status === "unsupported")
|
|
201
|
+
return observed.reason ?? "unsupported test observation scheme";
|
|
202
|
+
if (frozen.scheme !== observed.scheme || frozen.commandSha256 !== observed.commandSha256 || frozen.cwd !== observed.cwd || frozen.packageScript?.file !== observed.packageScript?.file || frozen.packageScript?.name !== observed.packageScript?.name)
|
|
203
|
+
return "frozen test command/cwd/script binding changed";
|
|
204
|
+
if (frozen.status === "supported" && (observed.status !== "supported" || frozen.packageScript?.sha256 !== observed.packageScript?.sha256))
|
|
205
|
+
return "frozen npm test script changed";
|
|
206
|
+
if (frozen.status === "deferred" && phase === "admission" && (!frozen.packageScript || !writeSet.includes(frozen.packageScript.file)))
|
|
207
|
+
return "future test script is outside the admitted concrete writeSet";
|
|
208
|
+
if (phase === "execution" && observed.status !== "supported")
|
|
209
|
+
return "future test reporter capability is still unavailable";
|
|
210
|
+
return undefined;
|
|
28
211
|
}
|
|
29
212
|
/** Observe supported frozen Vitest commands without changing their selection. */
|
|
30
213
|
export async function withFrontendTestReporter(input) {
|
|
31
|
-
|
|
32
|
-
// a different shell command, change selection, or override a user reporter.
|
|
33
|
-
if (/[;&|<>`\n\r$]/.test(input.command) || /--(?:reporter|outputFile)/.test(input.command))
|
|
34
|
-
return undefined;
|
|
35
|
-
// Frozen verify commands are env-prefixed bash wrappers —
|
|
36
|
-
// `HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 'bash' '-lc' 'npm test'` — so the JSON
|
|
37
|
-
// reporter must be injected INSIDE the quoted payload; appending it to the
|
|
38
|
-
// wrapper would target bash's own CLI and produce no test report at all
|
|
39
|
-
// (smoke r54: every behavior verification target then failed the trace
|
|
40
|
-
// gate with "test execution report unavailable").
|
|
41
|
-
const bashWrapped = /^((?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|[^\s'"]+)\s+)*)'bash'\s+'-lc'\s+'([^']*)'\s*$/.exec(input.command);
|
|
42
|
-
if (bashWrapped) {
|
|
43
|
-
const inner = bashWrapped[2];
|
|
44
|
-
if (/[;&|<>`\n\r$]/.test(inner) || /--(?:reporter|outputFile)/.test(inner))
|
|
45
|
-
return undefined;
|
|
46
|
-
const adaptedInner = await adaptPlainTestCommand(inner, input.cwd);
|
|
47
|
-
if (!adaptedInner)
|
|
48
|
-
return undefined;
|
|
49
|
-
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
50
|
-
return input.command.replace(`'${inner}'`, `'${adaptedInner} --reporter=json --outputFile=${quote(input.reportPath)}'`);
|
|
51
|
-
}
|
|
52
|
-
const adapted = await adaptPlainTestCommand(input.command, input.cwd);
|
|
53
|
-
if (!adapted)
|
|
54
|
-
return undefined;
|
|
55
|
-
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
56
|
-
return `${adapted} --reporter=json --outputFile=${quote(input.reportPath)}`;
|
|
214
|
+
return (await inspectFrontendTestCommand(input)).withReporter?.(input.reportPath);
|
|
57
215
|
}
|
|
58
216
|
const resultSchema = z.object({
|
|
59
217
|
testResults: z.array(z.object({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeRepairAssertions } from "./frontend-repair-assertions.js";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
|
|
@@ -196,13 +197,17 @@ export const reviewFindingSchema = z
|
|
|
196
197
|
line: z.number().int().positive().optional(),
|
|
197
198
|
issue: z.string().min(1),
|
|
198
199
|
requiredChange: z.string().min(1).optional(),
|
|
199
|
-
repairAssertions: z.
|
|
200
|
-
|
|
201
|
-
token: z.string().min(1), source: z.string(),
|
|
202
|
-
expectedRequirementIds: z.array(z.string().min(1)).optional(),
|
|
203
|
-
}).strict()).optional(),
|
|
200
|
+
repairAssertions: z.unknown().optional(),
|
|
201
|
+
repairAssertionDiagnostics: z.array(z.string()).optional(),
|
|
204
202
|
})
|
|
205
|
-
.strict()
|
|
203
|
+
.strict().transform(finding => {
|
|
204
|
+
const { repairAssertions, ...base } = finding;
|
|
205
|
+
if (repairAssertions === undefined)
|
|
206
|
+
return base;
|
|
207
|
+
const sanitized = sanitizeRepairAssertions(repairAssertions);
|
|
208
|
+
const diagnostics = [...(finding.repairAssertionDiagnostics ?? []), ...sanitized.diagnostics];
|
|
209
|
+
return { ...finding, repairAssertions: sanitized.assertions, ...(diagnostics.length ? { repairAssertionDiagnostics: [...new Set(diagnostics)] } : {}) };
|
|
210
|
+
});
|
|
206
211
|
/** `approve_review` terminal fact payload. Approval may carry informational
|
|
207
212
|
* findings (Minor/Info) but never Critical/Important. */
|
|
208
213
|
export const approveReviewFactSchema = z
|
|
@@ -261,6 +261,8 @@ export async function evaluateFrontendWriterTracePreflight(input) {
|
|
|
261
261
|
...(target.symbol ? { symbol: target.symbol } : {}),
|
|
262
262
|
status: fileTrace.issues.length ? "failed" : "ok",
|
|
263
263
|
evidence: fileTrace.issues.length ? "unavailable" : "file-exists",
|
|
264
|
+
plannedEvidenceLevel: target.evidenceLevel ?? "unknown",
|
|
265
|
+
...(target.boundary ? { boundary: target.boundary } : {}),
|
|
264
266
|
behaviorCoverage: "unconfirmed",
|
|
265
267
|
matchedNodeIds: [],
|
|
266
268
|
issues: fileTrace.issues,
|
|
@@ -322,6 +324,8 @@ export async function evaluateVerificationTargets(input) {
|
|
|
322
324
|
evidence: issues.length
|
|
323
325
|
? "unavailable"
|
|
324
326
|
: target.mode === "static" ? "command-execution" : "test-file-execution",
|
|
327
|
+
plannedEvidenceLevel: target.evidenceLevel ?? "unknown",
|
|
328
|
+
...(target.boundary ? { boundary: target.boundary } : {}),
|
|
325
329
|
behaviorCoverage: "unconfirmed",
|
|
326
330
|
matchedNodeIds: owners ?? [],
|
|
327
331
|
issues,
|
|
@@ -494,6 +498,7 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
494
498
|
contractSchemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
495
499
|
browserStatus: "not-run",
|
|
496
500
|
visualStatus: "not-run",
|
|
501
|
+
testExecutionEvidence: input.evidence?.behavior.testExecutionEvidence ?? [],
|
|
497
502
|
targets,
|
|
498
503
|
mockStrategy: contract.mockApi.strategy,
|
|
499
504
|
mockCommandLabels,
|
|
@@ -515,6 +520,7 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
515
520
|
mockCommandNodeId: result.mockCommandNodeId,
|
|
516
521
|
mockCommandTexts: result.mockCommandTexts,
|
|
517
522
|
targets: result.targets,
|
|
523
|
+
testExecutionEvidence: result.testExecutionEvidence,
|
|
518
524
|
}, null, 2)}\n`, "utf8");
|
|
519
525
|
result.artifactPath = artifactPath;
|
|
520
526
|
}
|
|
@@ -163,7 +163,7 @@ export function deriveWriteSet(contract, options) {
|
|
|
163
163
|
const candidates = [
|
|
164
164
|
...contract.targets.files,
|
|
165
165
|
...contract.verificationTargets
|
|
166
|
-
.filter((target) => target.mode !== "static")
|
|
166
|
+
.filter((target) => target.mode !== "static" && !options?.existingVerificationPaths?.includes(target.file))
|
|
167
167
|
.map((target) => target.file),
|
|
168
168
|
];
|
|
169
169
|
// Expand glob entries (e.g. `src/**` inherited from the skeleton) against
|
|
@@ -249,6 +249,7 @@ export function deriveFrontendWriterAdmissionShell(input) {
|
|
|
249
249
|
}
|
|
250
250
|
const derived = deriveWriteSet(input.contract, {
|
|
251
251
|
workspaceFiles: input.workspaceFiles,
|
|
252
|
+
existingVerificationPaths: input.existingVerificationPaths,
|
|
252
253
|
});
|
|
253
254
|
findings.push(...derived.findings);
|
|
254
255
|
const baseline = freezeBaseline(input.baseline);
|