@tea-agent/loop-agent 0.42.0-next.2 → 0.42.0-next.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 +242 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +942 -187
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +173 -7
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +30 -19
- package/dist/worker/console/chat/session-store.js +117 -70
- package/dist/worker/console/chat/shortcuts.js +1 -1
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CeznJ4Iw.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-B4u9u7SX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-oghpT81p.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-BPJ5Kvcj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-CoIu69pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-DmIXqJ6B.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-DtgiAsSc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-YSu_EVSD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-DaRO-NB6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-Dh0ZKiGA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DOrvbR-H.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-C3b22LN2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-CXlqO8l6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-BblTBrDu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-DswqPALe.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-CfTwVOlc.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-bLD8jVFo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-BvoG1fh5.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-DEPAdJu3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-DmrNvBLy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-CdwzIwvs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-CJ3hj-md.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-C6YJT2a4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-C_LPfjhX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-xvsSb5Ie.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-Rxp2tKcD.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-DRyLYmZh.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-TLSU_D2B.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DGqeTvHC.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-BZxyH2wh.js} +1 -1
- package/dist/worker/console/static/assets/index-C8V8K9b4.js +451 -0
- package/dist/worker/console/static/assets/index-Cnf_KxTQ.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-BsYtzx01.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-Qr-ezGv-.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-DYbB3ibo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-BHJHewZO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-CmPIPmCG.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-F--C1uV0.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-YTXfL1st.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-DdDMYXP9.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-C3nH4KVd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-kXyxjG7Y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-KNzbl-7w.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-CSwI6dG0.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-Bl1AyuXy.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-2y4EmXQa.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-E8v8YrMg.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-eGGlR856.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8e4J3at.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-B7g7xc07.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Do88I1Nb.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-DR3VCHBG.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-LUvapZIy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-C7GDcaXq.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CbNAqoGK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +907 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +677 -492
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +433 -126
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
6
|
-
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE, } from "./frontend-verification-trace.js";
|
|
7
|
-
export const FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID = "frontend-repair-assessment-v1";
|
|
2
|
+
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, } from "./frontend-verification-trace.js";
|
|
8
3
|
export const frontendRepairFailureClassSchema = z.enum([
|
|
9
4
|
"typecheck",
|
|
10
5
|
"build",
|
|
@@ -29,55 +24,6 @@ export const frontendCommandCategorySchema = z.enum([
|
|
|
29
24
|
"unit-test",
|
|
30
25
|
"trace",
|
|
31
26
|
]);
|
|
32
|
-
const REPAIRABLE = new Set([
|
|
33
|
-
"typecheck",
|
|
34
|
-
"build",
|
|
35
|
-
"lint",
|
|
36
|
-
"component-test",
|
|
37
|
-
"unit-test",
|
|
38
|
-
"trace",
|
|
39
|
-
]);
|
|
40
|
-
export function isFrontendRepairable(failureClass) {
|
|
41
|
-
return REPAIRABLE.has(failureClass);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Deterministic commandLabel → commandCategory mapping (AC-3).
|
|
45
|
-
* `targetType` comes from the contract verificationTargets[].type
|
|
46
|
-
* (static | unit | component | integration | mock); the literal commandLabel
|
|
47
|
-
* wins over type so a `static` typecheck target is never misread as a test.
|
|
48
|
-
*/
|
|
49
|
-
export function mapCommandLabelToCategory(commandLabel, targetType) {
|
|
50
|
-
const label = commandLabel.toLowerCase();
|
|
51
|
-
if (/(?:typecheck|check-types|type-check|\btsc\b)/.test(label))
|
|
52
|
-
return "typecheck";
|
|
53
|
-
if (/(?:vite\s+build|next\s+build|webpack|rollup|\bbuild\b)/.test(label))
|
|
54
|
-
return "build";
|
|
55
|
-
if (/(?:eslint|\blint\b)/.test(label))
|
|
56
|
-
return "lint";
|
|
57
|
-
if (targetType === "component")
|
|
58
|
-
return "component-test";
|
|
59
|
-
if (targetType === "unit")
|
|
60
|
-
return "unit-test";
|
|
61
|
-
if (/(?:component|testing-library|render\()/.test(label))
|
|
62
|
-
return "component-test";
|
|
63
|
-
return "unit-test";
|
|
64
|
-
}
|
|
65
|
-
export function isReviewRepairable(finding, implementWriteSet) {
|
|
66
|
-
const hasFile = Boolean(finding.file && finding.file.trim().length > 0);
|
|
67
|
-
const hasLocation = Boolean((finding.line !== undefined && finding.line > 0) ||
|
|
68
|
-
(finding.symbol && finding.symbol.trim().length > 0));
|
|
69
|
-
const hasRequirement = Boolean((finding.requirementId && finding.requirementId.trim().length > 0) ||
|
|
70
|
-
(finding.acceptanceCriteriaId &&
|
|
71
|
-
finding.acceptanceCriteriaId.trim().length > 0));
|
|
72
|
-
const fixScope = finding.fixScope ?? (hasFile ? [finding.file] : []);
|
|
73
|
-
const fixScopeSubset = fixScope.length > 0 &&
|
|
74
|
-
fixScope.every((entry) => isPathWithinWriteSet(entry, implementWriteSet));
|
|
75
|
-
return (hasFile &&
|
|
76
|
-
hasLocation &&
|
|
77
|
-
hasRequirement &&
|
|
78
|
-
fixScopeSubset &&
|
|
79
|
-
finding.requiresContractReplan !== true);
|
|
80
|
-
}
|
|
81
27
|
export function classifyFrontendFailure(input) {
|
|
82
28
|
// AC-2: structured evidence takes priority over string-blob heuristics.
|
|
83
29
|
if (input.failureCategory === "write-guard") {
|
|
@@ -176,380 +122,3 @@ export function classifyFrontendFailure(input) {
|
|
|
176
122
|
return "path";
|
|
177
123
|
return "unknown";
|
|
178
124
|
}
|
|
179
|
-
export const frontendRepairAssessmentSchema = z
|
|
180
|
-
.object({
|
|
181
|
-
schemaVersion: z.literal(1),
|
|
182
|
-
schemaId: z.literal(FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID),
|
|
183
|
-
contract: z
|
|
184
|
-
.object({
|
|
185
|
-
relativePath: z.string().min(1),
|
|
186
|
-
schemaId: z.string().min(1),
|
|
187
|
-
})
|
|
188
|
-
.strict(),
|
|
189
|
-
failedNodeIds: z.array(z.string().min(1)),
|
|
190
|
-
failureClass: frontendRepairFailureClassSchema,
|
|
191
|
-
eligible: z.boolean(),
|
|
192
|
-
reason: z.string().min(1),
|
|
193
|
-
attempt: z.number().int().min(1).max(2),
|
|
194
|
-
maxAttempts: z.literal(1),
|
|
195
|
-
allowedRepairWriteSet: z.array(z.string().min(1)),
|
|
196
|
-
evidenceRefs: z.array(z.string().min(1)),
|
|
197
|
-
browserStatus: z.literal("not-run"),
|
|
198
|
-
// AC-5 structured evidence fields (all optional so existing minimal
|
|
199
|
-
// fixtures and frontend-review-context safeParse stay compatible).
|
|
200
|
-
verificationTargetId: z.string().min(1).optional(),
|
|
201
|
-
commandLabel: z.string().min(1).optional(),
|
|
202
|
-
exitCode: z.number().int().optional(),
|
|
203
|
-
commandCategory: frontendCommandCategorySchema.optional(),
|
|
204
|
-
requirementIds: z.array(z.string().min(1)).optional(),
|
|
205
|
-
acceptanceCriteriaIds: z.array(z.string().min(1)).optional(),
|
|
206
|
-
fixScope: z.array(z.string().min(1)).optional(),
|
|
207
|
-
changedPaths: z.array(z.string().min(1)).optional(),
|
|
208
|
-
traceRef: z
|
|
209
|
-
.object({
|
|
210
|
-
relativePath: z.string().min(1),
|
|
211
|
-
schemaId: z.string().min(1),
|
|
212
|
-
})
|
|
213
|
-
.strict()
|
|
214
|
-
.optional(),
|
|
215
|
-
})
|
|
216
|
-
.strict();
|
|
217
|
-
function nodeHadFailure(record) {
|
|
218
|
-
if (record.status === "ERROR")
|
|
219
|
-
return true;
|
|
220
|
-
if (recordIndicatesCommandFailure(record))
|
|
221
|
-
return true;
|
|
222
|
-
const results = record.commandResults ?? [];
|
|
223
|
-
if (results.some((item) => item.ok === false))
|
|
224
|
-
return true;
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
function normalize(value) {
|
|
228
|
-
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Whether a concrete path or glob entry is contained by the implement writeSet.
|
|
232
|
-
* Exact glob equality is the norm (repair writeSet === implement writeSet);
|
|
233
|
-
* concrete (non-glob) paths may also be covered by an implement glob.
|
|
234
|
-
*/
|
|
235
|
-
export function isPathWithinWriteSet(entry, implementWriteSet) {
|
|
236
|
-
const normalizedEntry = normalize(entry);
|
|
237
|
-
const normalizedImplement = implementWriteSet.map(normalize);
|
|
238
|
-
if (normalizedImplement.includes(normalizedEntry))
|
|
239
|
-
return true;
|
|
240
|
-
if (!normalizedEntry.includes("*")) {
|
|
241
|
-
return implementWriteSet.some((pattern) => pathMatchesPattern(normalizedEntry, pattern));
|
|
242
|
-
}
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
export function assertWriteSetSubset(repairWriteSet, implementWriteSet) {
|
|
246
|
-
for (const entry of repairWriteSet) {
|
|
247
|
-
if (!isPathWithinWriteSet(entry, implementWriteSet)) {
|
|
248
|
-
throw new Error(`repair writeSet entry "${entry}" is not ⊆ implement writeSet`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async function loadImplementWriteSet(runDir, fallback) {
|
|
253
|
-
if (fallback.length > 0)
|
|
254
|
-
return fallback;
|
|
255
|
-
const candidates = ["run.json", "dag.json", "spec.json"];
|
|
256
|
-
for (const name of candidates) {
|
|
257
|
-
try {
|
|
258
|
-
const raw = JSON.parse(await readFile(path.join(runDir, name), "utf8"));
|
|
259
|
-
const tasks = raw.spec?.tasks ?? raw.tasks ?? [];
|
|
260
|
-
const implement = tasks.find((task) => task.id === "frontend-implement-pi");
|
|
261
|
-
if (implement?.writeSet?.length)
|
|
262
|
-
return implement.writeSet;
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
// continue
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return fallback;
|
|
269
|
-
}
|
|
270
|
-
function matchVerificationTarget(contract, commandLabel, command) {
|
|
271
|
-
for (const target of contract.verificationTargets) {
|
|
272
|
-
if (commandLabel && target.commandLabel === commandLabel)
|
|
273
|
-
return target;
|
|
274
|
-
if (command &&
|
|
275
|
-
(command.includes(target.commandLabel) ||
|
|
276
|
-
target.commandLabel.includes(command))) {
|
|
277
|
-
return target;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
function deriveFailureEvidence(input) {
|
|
283
|
-
const evidence = {};
|
|
284
|
-
for (const { record } of input.failed) {
|
|
285
|
-
const results = record.commandResults ?? [];
|
|
286
|
-
const failedResult = results.find((item) => item.ok === false) ?? results[0];
|
|
287
|
-
if (failedResult &&
|
|
288
|
-
evidence.exitCode === undefined &&
|
|
289
|
-
typeof failedResult.exitCode === "number") {
|
|
290
|
-
evidence.exitCode = failedResult.exitCode;
|
|
291
|
-
}
|
|
292
|
-
if (failedResult?.commandLabel && !evidence.commandLabel) {
|
|
293
|
-
evidence.commandLabel = failedResult.commandLabel;
|
|
294
|
-
}
|
|
295
|
-
// Structured trace facts passed by the verification bundle / trace gate.
|
|
296
|
-
const failedTraceTarget = (record.traceTargets ?? []).find((target) => target.status === "failed");
|
|
297
|
-
if (failedTraceTarget && !evidence.verificationTargetId) {
|
|
298
|
-
evidence.verificationTargetId = failedTraceTarget.id;
|
|
299
|
-
if (!evidence.commandLabel)
|
|
300
|
-
evidence.commandLabel = failedTraceTarget.commandLabel;
|
|
301
|
-
evidence.traceTarget = failedTraceTarget;
|
|
302
|
-
evidence.traceRef = {
|
|
303
|
-
relativePath: "contracts/frontend-verification-trace.json",
|
|
304
|
-
schemaId: "frontend-verification-trace-v1",
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
if (record.changedPaths?.length && !evidence.changedPaths) {
|
|
308
|
-
evidence.changedPaths = [...record.changedPaths];
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// Resolve commandLabel → contract verification target → category + scope.
|
|
312
|
-
const target = matchVerificationTarget(input.contract, evidence.commandLabel);
|
|
313
|
-
if (target) {
|
|
314
|
-
if (!evidence.verificationTargetId)
|
|
315
|
-
evidence.verificationTargetId = target.id;
|
|
316
|
-
if (!evidence.commandLabel)
|
|
317
|
-
evidence.commandLabel = target.commandLabel;
|
|
318
|
-
evidence.requirementIds ??= [...target.requirementIds];
|
|
319
|
-
evidence.acceptanceCriteriaIds ??= [...target.requirementIds];
|
|
320
|
-
evidence.fixScope ??= [target.file];
|
|
321
|
-
}
|
|
322
|
-
if (evidence.commandLabel) {
|
|
323
|
-
evidence.commandCategory ??= mapCommandLabelToCategory(evidence.commandLabel, target?.type);
|
|
324
|
-
}
|
|
325
|
-
return evidence;
|
|
326
|
-
}
|
|
327
|
-
export async function runFrontendFailureAssessGate(input) {
|
|
328
|
-
const attempt = input.attempt ?? 1;
|
|
329
|
-
const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
|
|
330
|
-
if (implementWriteSet.length === 0) {
|
|
331
|
-
throw new Error("assess: implement writeSet unavailable");
|
|
332
|
-
}
|
|
333
|
-
const contractRel = "contracts/frontend-implementation-contract.json";
|
|
334
|
-
const contractPath = path.join(input.runDir, contractRel);
|
|
335
|
-
let contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
|
|
336
|
-
let contract;
|
|
337
|
-
try {
|
|
338
|
-
const raw = JSON.parse(await readFile(contractPath, "utf8"));
|
|
339
|
-
const parsed = frontendImplementationContractSchema.safeParse(raw);
|
|
340
|
-
if (!parsed.success) {
|
|
341
|
-
throw new Error("invalid frontend implementation contract");
|
|
342
|
-
}
|
|
343
|
-
contract = parsed.data;
|
|
344
|
-
contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
// still allow assessment of failures without contract? fail-closed to contract
|
|
348
|
-
throw new Error("assess: missing or invalid contracts/frontend-implementation-contract.json");
|
|
349
|
-
}
|
|
350
|
-
const candidateNodeIds = [
|
|
351
|
-
"frontend-verify-assess-shell",
|
|
352
|
-
"frontend-reverify-shell",
|
|
353
|
-
"frontend-static-verify-shell",
|
|
354
|
-
"frontend-behavior-verify-shell",
|
|
355
|
-
"frontend-verification-trace-shell",
|
|
356
|
-
"frontend-static-reverify-shell",
|
|
357
|
-
"frontend-behavior-reverify-shell",
|
|
358
|
-
"frontend-verification-retrace-shell",
|
|
359
|
-
];
|
|
360
|
-
const failed = [
|
|
361
|
-
...(input.failureFacts ?? []).filter(({ record }) => nodeHadFailure(record) || recordIndicatesCommandFailure(record)),
|
|
362
|
-
];
|
|
363
|
-
for (const nodeId of candidateNodeIds) {
|
|
364
|
-
if (failed.some((item) => item.nodeId === nodeId))
|
|
365
|
-
continue;
|
|
366
|
-
const nodePath = path.join(input.runDir, `${nodeId}.json`);
|
|
367
|
-
try {
|
|
368
|
-
const record = JSON.parse(await readFile(nodePath, "utf8"));
|
|
369
|
-
if (nodeHadFailure(record) || recordIndicatesCommandFailure(record)) {
|
|
370
|
-
failed.push({ nodeId, record });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
// missing optional nodes ok
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (failed.length === 0) {
|
|
378
|
-
const assessment = {
|
|
379
|
-
schemaVersion: 1,
|
|
380
|
-
schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
|
|
381
|
-
contract: {
|
|
382
|
-
relativePath: contractRel,
|
|
383
|
-
schemaId: contractSchemaId,
|
|
384
|
-
},
|
|
385
|
-
failedNodeIds: [],
|
|
386
|
-
failureClass: "unknown",
|
|
387
|
-
eligible: false,
|
|
388
|
-
reason: "no failed frontend verify/trace nodes in current run",
|
|
389
|
-
attempt,
|
|
390
|
-
maxAttempts: 1,
|
|
391
|
-
allowedRepairWriteSet: [...implementWriteSet],
|
|
392
|
-
evidenceRefs: [contractRel],
|
|
393
|
-
browserStatus: "not-run",
|
|
394
|
-
};
|
|
395
|
-
await writeAssessment(input.runDir, assessment);
|
|
396
|
-
return assessment;
|
|
397
|
-
}
|
|
398
|
-
const primary = failed[0];
|
|
399
|
-
const evidence = deriveFailureEvidence({ contract, failed });
|
|
400
|
-
const failureClass = classifyFrontendFailure({
|
|
401
|
-
nodeId: primary.nodeId,
|
|
402
|
-
failureCategory: primary.record.failureCategory,
|
|
403
|
-
stdout: primary.record.stdout,
|
|
404
|
-
stderr: primary.record.stderr,
|
|
405
|
-
commandLabel: evidence.commandLabel,
|
|
406
|
-
commandCategory: evidence.commandCategory,
|
|
407
|
-
exitCode: evidence.exitCode,
|
|
408
|
-
traceTarget: evidence.traceTarget ?? null,
|
|
409
|
-
changedPaths: evidence.changedPaths,
|
|
410
|
-
});
|
|
411
|
-
// AC-5: repairable=true requires structured evidence (a resolved exitCode,
|
|
412
|
-
// commandCategory, or trace-bound verification target). review has its own
|
|
413
|
-
// stricter gate (AC-4) and is never unconditionally repairable.
|
|
414
|
-
const baseEligible = isFrontendRepairable(failureClass) && attempt <= 1;
|
|
415
|
-
let eligible;
|
|
416
|
-
let reason;
|
|
417
|
-
if (failureClass === "review") {
|
|
418
|
-
const finding = primary.record.reviewFinding ?? {};
|
|
419
|
-
eligible = attempt <= 1 && isReviewRepairable(finding, implementWriteSet);
|
|
420
|
-
reason = eligible
|
|
421
|
-
? `repairable review finding on ${primary.nodeId}`
|
|
422
|
-
: `review finding lacks file/line-symbol/requirement evidence or is outside writeSet on ${primary.nodeId}`;
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
const hasStructuredEvidence = evidence.exitCode !== undefined ||
|
|
426
|
-
evidence.commandCategory !== undefined ||
|
|
427
|
-
Boolean(evidence.verificationTargetId);
|
|
428
|
-
eligible = baseEligible && hasStructuredEvidence;
|
|
429
|
-
reason = eligible
|
|
430
|
-
? `repairable failureClass=${failureClass} on ${primary.nodeId}`
|
|
431
|
-
: !baseEligible
|
|
432
|
-
? `non-repairable or ineligible: failureClass=${failureClass} attempt=${attempt}`
|
|
433
|
-
: `missing structured evidence for failureClass=${failureClass} on ${primary.nodeId}`;
|
|
434
|
-
if (!eligible &&
|
|
435
|
-
`${primary.record.stderr ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
|
|
436
|
-
// Surface the actionable unavailable-verification reason in the
|
|
437
|
-
// assessment (and the non-repairable gate error) instead of a
|
|
438
|
-
// generic class label.
|
|
439
|
-
reason = FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// fixScope and changedPaths must stay inside the implement writeSet.
|
|
443
|
-
if (evidence.fixScope?.length) {
|
|
444
|
-
assertWriteSetSubset(evidence.fixScope, implementWriteSet);
|
|
445
|
-
}
|
|
446
|
-
if (evidence.changedPaths?.length) {
|
|
447
|
-
assertWriteSetSubset(evidence.changedPaths, implementWriteSet);
|
|
448
|
-
}
|
|
449
|
-
const assessment = {
|
|
450
|
-
schemaVersion: 1,
|
|
451
|
-
schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
|
|
452
|
-
contract: {
|
|
453
|
-
relativePath: contractRel,
|
|
454
|
-
schemaId: contractSchemaId,
|
|
455
|
-
},
|
|
456
|
-
failedNodeIds: failed.map((item) => item.nodeId),
|
|
457
|
-
failureClass,
|
|
458
|
-
eligible,
|
|
459
|
-
reason,
|
|
460
|
-
attempt,
|
|
461
|
-
maxAttempts: 1,
|
|
462
|
-
allowedRepairWriteSet: [...implementWriteSet],
|
|
463
|
-
evidenceRefs: [contractRel, ...failed.map((item) => `${item.nodeId}.json`)],
|
|
464
|
-
browserStatus: "not-run",
|
|
465
|
-
...(evidence.verificationTargetId
|
|
466
|
-
? { verificationTargetId: evidence.verificationTargetId }
|
|
467
|
-
: {}),
|
|
468
|
-
...(evidence.commandLabel ? { commandLabel: evidence.commandLabel } : {}),
|
|
469
|
-
...(evidence.exitCode !== undefined ? { exitCode: evidence.exitCode } : {}),
|
|
470
|
-
...(evidence.commandCategory
|
|
471
|
-
? { commandCategory: evidence.commandCategory }
|
|
472
|
-
: {}),
|
|
473
|
-
...(evidence.requirementIds?.length
|
|
474
|
-
? { requirementIds: evidence.requirementIds }
|
|
475
|
-
: {}),
|
|
476
|
-
...(evidence.acceptanceCriteriaIds?.length
|
|
477
|
-
? { acceptanceCriteriaIds: evidence.acceptanceCriteriaIds }
|
|
478
|
-
: {}),
|
|
479
|
-
...(evidence.fixScope?.length ? { fixScope: evidence.fixScope } : {}),
|
|
480
|
-
...(evidence.changedPaths?.length
|
|
481
|
-
? { changedPaths: evidence.changedPaths }
|
|
482
|
-
: {}),
|
|
483
|
-
...(evidence.traceRef ? { traceRef: evidence.traceRef } : {}),
|
|
484
|
-
};
|
|
485
|
-
await writeAssessment(input.runDir, assessment);
|
|
486
|
-
return assessment;
|
|
487
|
-
}
|
|
488
|
-
function recordIndicatesCommandFailure(record) {
|
|
489
|
-
// Prefer explicit commandResults when present
|
|
490
|
-
if (record.commandResults?.some((item) => item.ok === false))
|
|
491
|
-
return true;
|
|
492
|
-
// Node may be FINISHED with nonZeroExitPolicy=record; look for harness markers
|
|
493
|
-
const stdout = record.stdout ?? "";
|
|
494
|
-
if (/nonZeroExitPolicy=record/.test(stdout) &&
|
|
495
|
-
/exitCode=[1-9]/.test(stdout)) {
|
|
496
|
-
return true;
|
|
497
|
-
}
|
|
498
|
-
if (record.failureCategory && record.failureCategory !== "success") {
|
|
499
|
-
// recorded failures may still be FINISHED
|
|
500
|
-
if (record.status === "FINISHED") {
|
|
501
|
-
return (record.failureCategory === "nonzero-exit" ||
|
|
502
|
-
record.failureCategory === "invalid-output" ||
|
|
503
|
-
record.failureCategory === "timeout");
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
async function writeAssessment(runDir, assessment) {
|
|
509
|
-
const relative = "contracts/frontend-repair-assessment.json";
|
|
510
|
-
const absolute = path.join(runDir, relative);
|
|
511
|
-
await mkdir(path.dirname(absolute), { recursive: true });
|
|
512
|
-
await writeFile(absolute, `${JSON.stringify(assessment, null, 2)}\n`, "utf8");
|
|
513
|
-
return absolute;
|
|
514
|
-
}
|
|
515
|
-
export async function runFrontendRepairContractGate(input) {
|
|
516
|
-
const relative = "contracts/frontend-repair-assessment.json";
|
|
517
|
-
const absolute = path.join(input.runDir, relative);
|
|
518
|
-
const raw = JSON.parse(await readFile(absolute, "utf8"));
|
|
519
|
-
const parsed = frontendRepairAssessmentSchema.safeParse(raw);
|
|
520
|
-
if (!parsed.success) {
|
|
521
|
-
throw new Error(`repair-contract: invalid assessment: ${parsed.error.issues
|
|
522
|
-
.map((i) => i.message)
|
|
523
|
-
.join("; ")}`);
|
|
524
|
-
}
|
|
525
|
-
const assessment = parsed.data;
|
|
526
|
-
const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
|
|
527
|
-
assertWriteSetSubset(assessment.allowedRepairWriteSet, implementWriteSet.length
|
|
528
|
-
? implementWriteSet
|
|
529
|
-
: assessment.allowedRepairWriteSet);
|
|
530
|
-
if (assessment.attempt > assessment.maxAttempts) {
|
|
531
|
-
throw new Error("repair-contract: attempt exceeds maxAttempts");
|
|
532
|
-
}
|
|
533
|
-
if (assessment.failedNodeIds.length === 0) {
|
|
534
|
-
return { ok: true, assessment };
|
|
535
|
-
}
|
|
536
|
-
if (!assessment.eligible) {
|
|
537
|
-
throw new Error(`repair-contract: non-repairable failure (${assessment.failureClass}): ${assessment.reason}`);
|
|
538
|
-
}
|
|
539
|
-
if (!isFrontendRepairable(assessment.failureClass) &&
|
|
540
|
-
assessment.failureClass !== "review") {
|
|
541
|
-
throw new Error(`repair-contract: failureClass ${assessment.failureClass} is not repairable`);
|
|
542
|
-
}
|
|
543
|
-
return { ok: true, assessment };
|
|
544
|
-
}
|
|
545
|
-
export function formatFrontendFailureAssessStdout(assessment) {
|
|
546
|
-
return JSON.stringify(assessment);
|
|
547
|
-
}
|
|
548
|
-
export function formatFrontendRepairContractStdout(assessment) {
|
|
549
|
-
return [
|
|
550
|
-
"Frontend repair contract: pass",
|
|
551
|
-
`eligible=${assessment.eligible}`,
|
|
552
|
-
`failureClass=${assessment.failureClass}`,
|
|
553
|
-
`writeSetSize=${assessment.allowedRepairWriteSet.length}`,
|
|
554
|
-
].join("\n");
|
|
555
|
-
}
|