@tea-agent/loop-agent 0.42.0-next.9 → 0.42.1
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 +111 -45
- package/dist/application/dag/run-dag.js +8 -2
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +26 -8
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +2584 -600
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +135 -55
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +173 -94
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +208 -66
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +27 -7
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +21 -0
- package/dist/worker/console/chat/tools.js +13 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -5
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +431 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +51 -4
- package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +64 -22
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +194 -243
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +103 -17
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +21 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +146 -16
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +46 -7
- package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +70 -35
- package/dist/workflows/dag/node-execution.js +225 -226
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +96 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +246 -19
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +68 -4
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/harness.json +1 -1
- package/package.json +8 -6
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +22 -2
- package/skills/frontend-plan/references/decision-contract.md +114 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { copyFile, mkdir, readdir, readFile, rm, access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
|
|
3
4
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
4
5
|
import { sha256Hex } from "./frontend-implementation-contract.js";
|
|
5
6
|
import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_REL_DIR, FRONTEND_RECOVERY_STAGING_PREFIX, computeFrontendRecoveryPlan, computeFrontendRecoveryPlanForSource, } from "./frontend-recovery-plan.js";
|
|
6
|
-
import { dagRunDirExists, getDagRunDir, readDagRunSpec, readDagRunState, transferDagRunDir, writeDagRunState, } from "./lifecycle.js";
|
|
7
|
+
import { dagRunDirExists, getDagRunDir, locateDagRun, readDagRunSpec, readDagRunState, transferDagRunDir, writeDagRunState, } from "./lifecycle.js";
|
|
7
8
|
import { buildDagRunId } from "./runner.js";
|
|
9
|
+
import { buildFrontendTopologyBinding } from "./frontend-shape.js";
|
|
10
|
+
import { FRONTEND_SHAPE_BOUNDARIES, isCanonicalFrontendShapeDag, } from "./frontend-shape-facts.js";
|
|
8
11
|
import { prepareRunDir, writeNodeRecord } from "./run-store.js";
|
|
9
12
|
import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, readFrontendWriterAdmissionResult, } from "./scheduler.js";
|
|
10
13
|
import { topoSortToRanks } from "./topo.js";
|
|
@@ -273,34 +276,52 @@ async function casUpdateFrontendRecoveryState(parentRunDir, expectedRevision, de
|
|
|
273
276
|
await deps.writeParentState(parentRunDir, state);
|
|
274
277
|
}
|
|
275
278
|
async function materializeChildStaging(input) {
|
|
276
|
-
const { cwd, parentRunDir, parentRunId, parentSpec, parentState, recovery, requestId, childRunId, stagingDir, createdAt, } = input;
|
|
279
|
+
const { cwd, parentRunDir, parentRunId, parentSpec, parentState, recovery, recoveryPlan, requestId, childRunId, stagingDir, createdAt, } = input;
|
|
277
280
|
await prepareRunDir(stagingDir);
|
|
278
281
|
// Design-review rejection must be visible to the reset planner. Recovery
|
|
279
282
|
// children otherwise copy the old spec and only rerun design review, which
|
|
280
283
|
// deterministically re-rejects the unchanged plan. Bind the parent's typed
|
|
281
284
|
// findings into the child spec as ordinary rerun feedback.
|
|
282
285
|
let childSpec = parentSpec;
|
|
283
|
-
if (recovery.failureSource === "frontend-plan-pi"
|
|
286
|
+
if (recovery.failureSource === "frontend-plan-pi" ||
|
|
287
|
+
recovery.failureSource === "frontend-review-pi") {
|
|
288
|
+
const designRecovery = parentState.terminalReason === "design-request-changes";
|
|
289
|
+
const reviewRecovery = recovery.failureSource === "frontend-review-pi" ||
|
|
290
|
+
parentState.terminalReason === "review-request-changes";
|
|
291
|
+
let feedback;
|
|
284
292
|
try {
|
|
285
293
|
const { deriveDagRerunFeedback } = await import("./rerun-feedback.js");
|
|
286
294
|
const taskId = parentSpec.taskContractBinding?.taskId ??
|
|
287
295
|
parentSpec.sourceBinding?.taskId ??
|
|
288
296
|
parentRunId;
|
|
289
|
-
|
|
297
|
+
feedback = await deriveDagRerunFeedback({
|
|
290
298
|
parentRunId,
|
|
291
299
|
taskId,
|
|
292
300
|
runDir: parentRunDir,
|
|
293
301
|
spec: parentSpec,
|
|
294
302
|
state: parentState,
|
|
295
303
|
});
|
|
296
|
-
if (
|
|
297
|
-
|
|
304
|
+
if (reviewRecovery &&
|
|
305
|
+
feedback &&
|
|
306
|
+
feedback.sourceNodeId !== "frontend-review-pi") {
|
|
307
|
+
// Do not accidentally carry an older design/verify finding when a
|
|
308
|
+
// review-triggered continuation has lost its typed review fact.
|
|
309
|
+
feedback = undefined;
|
|
310
|
+
}
|
|
298
311
|
}
|
|
299
312
|
catch (error) {
|
|
300
|
-
|
|
313
|
+
if (designRecovery || reviewRecovery) {
|
|
314
|
+
throw new Error(`frontend recovery feedback unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (feedback) {
|
|
318
|
+
childSpec = { ...parentSpec, rerunFeedback: feedback };
|
|
319
|
+
}
|
|
320
|
+
else if (designRecovery || reviewRecovery) {
|
|
321
|
+
// A design/review rejection cannot be safely retried without its typed
|
|
301
322
|
// findings. Fail closed instead of deterministically re-running the
|
|
302
323
|
// unchanged plan and producing another rejection.
|
|
303
|
-
throw new Error(`frontend recovery feedback unavailable: ${
|
|
324
|
+
throw new Error(`frontend recovery feedback unavailable: no typed ${reviewRecovery ? "review" : "design"} findings were found for recovery`);
|
|
304
325
|
}
|
|
305
326
|
}
|
|
306
327
|
await writeJsonVerified(stagingDir, "run.json", childSpec);
|
|
@@ -308,7 +329,10 @@ async function materializeChildStaging(input) {
|
|
|
308
329
|
// (frontend-implement-pi) instead of the candidate producer; everything
|
|
309
330
|
// upstream is imported as verified parent facts.
|
|
310
331
|
let plan;
|
|
311
|
-
if (
|
|
332
|
+
if (recoveryPlan) {
|
|
333
|
+
plan = recoveryPlan;
|
|
334
|
+
}
|
|
335
|
+
else if (recovery.failureSource) {
|
|
312
336
|
plan = computeFrontendRecoveryPlanForSource({
|
|
313
337
|
spec: parentSpec,
|
|
314
338
|
parentRunId,
|
|
@@ -373,7 +397,25 @@ async function materializeChildStaging(input) {
|
|
|
373
397
|
newRunDir: stagingDir,
|
|
374
398
|
relativePath: FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT,
|
|
375
399
|
});
|
|
400
|
+
// The scheduler gates every shape boundary by its committed fact once the
|
|
401
|
+
// boundary's source node is FINISHED. Imported nodes stay FINISHED in the
|
|
402
|
+
// child, so their boundaries must inherit the parent's committed facts or
|
|
403
|
+
// the child hard-blocks at the first gated rank. Reset boundaries re-commit
|
|
404
|
+
// their own facts when their source nodes re-run.
|
|
405
|
+
const inheritedCommittedFacts = {};
|
|
406
|
+
if (isCanonicalFrontendShapeDag(new Map(childSpec.tasks.map((task) => [task.id, task])))) {
|
|
407
|
+
for (const entry of FRONTEND_SHAPE_BOUNDARIES) {
|
|
408
|
+
if (!importedSet.has(entry.sourceNodeId))
|
|
409
|
+
continue;
|
|
410
|
+
const parentFact = parentState.frontendShapeState?.committedFacts[entry.boundary];
|
|
411
|
+
if (parentFact) {
|
|
412
|
+
inheritedCommittedFacts[entry.boundary] = structuredClone(parentFact);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
376
416
|
const childState = {
|
|
417
|
+
...(parentState.budget ? { budget: structuredClone(parentState.budget) } : {}),
|
|
418
|
+
...(parentState.budgetLedger ? { budgetLedger: structuredClone(parentState.budgetLedger) } : {}),
|
|
377
419
|
version: 1,
|
|
378
420
|
title: parentSpec.title,
|
|
379
421
|
runId: childRunId,
|
|
@@ -382,7 +424,20 @@ async function materializeChildStaging(input) {
|
|
|
382
424
|
status: "pending",
|
|
383
425
|
ranks,
|
|
384
426
|
nodes,
|
|
427
|
+
// Mirror createInitialRunState: a canonical frontend child must carry its
|
|
428
|
+
// own frozen topology binding or executeDag's binding assert rejects the
|
|
429
|
+
// staged child before its first node runs.
|
|
430
|
+
...(isCanonicalFrontendShapeDag(new Map(childSpec.tasks.map((task) => [task.id, task])))
|
|
431
|
+
? {
|
|
432
|
+
frontendShapeState: {
|
|
433
|
+
frozenTopology: buildFrontendTopologyBinding(childSpec.tasks.map((task) => task.id)),
|
|
434
|
+
revision: 0,
|
|
435
|
+
committedFacts: inheritedCommittedFacts,
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
: {}),
|
|
385
439
|
frontendRecoveryState: {
|
|
440
|
+
...(parentState.budgetLedger ? { budgetLineage: { parentLedgerSha256: sha256OfCanonicalJson(parentState.budgetLedger), inheritedAt: createdAt, inheritedConsumed: structuredClone(parentState.budgetLedger.consumed) } } : {}),
|
|
386
441
|
schemaVersion: 1,
|
|
387
442
|
phase: "child-running",
|
|
388
443
|
requestId,
|
|
@@ -390,8 +445,11 @@ async function materializeChildStaging(input) {
|
|
|
390
445
|
parentRunId,
|
|
391
446
|
childRunId,
|
|
392
447
|
attemptId: recovery.attemptId,
|
|
393
|
-
attemptIndex: 1,
|
|
394
|
-
continuationCount: 1,
|
|
448
|
+
attemptIndex: recovery.attemptIndex + 1,
|
|
449
|
+
continuationCount: recovery.continuationCount + 1,
|
|
450
|
+
...(recovery.failureSource
|
|
451
|
+
? { failureSource: recovery.failureSource }
|
|
452
|
+
: {}),
|
|
395
453
|
revision: 0,
|
|
396
454
|
},
|
|
397
455
|
...(parentState.evaluation
|
|
@@ -460,7 +518,41 @@ export async function stageRecoveryChild(options) {
|
|
|
460
518
|
const parentRunDir = getDagRunDir(cwd, "active", parentRunId);
|
|
461
519
|
const parentState = await deps.readParentState(parentRunDir);
|
|
462
520
|
const parentSpec = await readDagRunSpec(parentRunDir);
|
|
463
|
-
|
|
521
|
+
let recovery = parentState.frontendRecoveryState;
|
|
522
|
+
if (options.recoveryIntent) {
|
|
523
|
+
const intent = options.recoveryIntent;
|
|
524
|
+
if (intent.parentRunId !== parentRunId ||
|
|
525
|
+
intent.requestId.length === 0 ||
|
|
526
|
+
intent.phase !== "child-staging" ||
|
|
527
|
+
intent.childRunId !== undefined ||
|
|
528
|
+
intent.revision !== 0) {
|
|
529
|
+
throw new Error("frontend recovery intent binding mismatch");
|
|
530
|
+
}
|
|
531
|
+
if (recovery?.requestId === intent.requestId) {
|
|
532
|
+
if (recovery.parentRunId !== intent.parentRunId ||
|
|
533
|
+
recovery.recoveryRootRunId !== intent.recoveryRootRunId ||
|
|
534
|
+
recovery.attemptIndex !== intent.attemptIndex ||
|
|
535
|
+
recovery.continuationCount !== intent.continuationCount ||
|
|
536
|
+
recovery.failureSource !== intent.failureSource) {
|
|
537
|
+
throw new Error("frontend recovery intent replay binding mismatch");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
const previousAttemptIndex = recovery?.attemptIndex ?? 0;
|
|
542
|
+
const previousContinuationCount = recovery?.continuationCount ?? 0;
|
|
543
|
+
const expectedRoot = recovery?.recoveryRootRunId ?? parentRunId;
|
|
544
|
+
if (intent.recoveryRootRunId !== expectedRoot ||
|
|
545
|
+
intent.attemptIndex !== previousAttemptIndex ||
|
|
546
|
+
intent.continuationCount !== previousContinuationCount) {
|
|
547
|
+
throw new Error("frontend recovery intent lineage mismatch");
|
|
548
|
+
}
|
|
549
|
+
recovery = structuredClone(intent);
|
|
550
|
+
await deps.writeParentState(parentRunDir, {
|
|
551
|
+
...parentState,
|
|
552
|
+
frontendRecoveryState: recovery,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
464
556
|
if (!recovery) {
|
|
465
557
|
throw new Error(`parent run ${parentRunId} has no frontend recovery intent`);
|
|
466
558
|
}
|
|
@@ -468,17 +560,49 @@ export async function stageRecoveryChild(options) {
|
|
|
468
560
|
if (!requestId) {
|
|
469
561
|
throw new Error(`parent run ${parentRunId} recovery intent has no requestId`);
|
|
470
562
|
}
|
|
563
|
+
if (options.recoveryPlan) {
|
|
564
|
+
if (options.recoveryPlan.parentRunId !== parentRunId ||
|
|
565
|
+
options.recoveryPlan.requestId !== requestId ||
|
|
566
|
+
options.recoveryPlan.failureSource !== recovery.failureSource) {
|
|
567
|
+
throw new Error("frontend recovery plan binding mismatch");
|
|
568
|
+
}
|
|
569
|
+
const expectedPlan = computeFrontendRecoveryPlanForSource({
|
|
570
|
+
spec: parentSpec,
|
|
571
|
+
parentRunId,
|
|
572
|
+
requestId,
|
|
573
|
+
failureSource: recovery.failureSource,
|
|
574
|
+
});
|
|
575
|
+
if (JSON.stringify(expectedPlan.resetNodeIds) !==
|
|
576
|
+
JSON.stringify(options.recoveryPlan.resetNodeIds) ||
|
|
577
|
+
JSON.stringify(expectedPlan.importedNodeIds) !==
|
|
578
|
+
JSON.stringify(options.recoveryPlan.importedNodeIds)) {
|
|
579
|
+
throw new Error("frontend recovery plan drifted from parent DAG");
|
|
580
|
+
}
|
|
581
|
+
}
|
|
471
582
|
// Idempotent short-circuit (D4.2): reserved childRunId + activated marker +
|
|
472
583
|
// existing child dir means the request was already fully committed.
|
|
473
584
|
if (recovery.childRunId) {
|
|
474
585
|
const existingMarker = await readFrontendRecoveryMarker(parentRunDir, requestId);
|
|
475
586
|
if (existingMarker) {
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
587
|
+
const existingChild = await locateDagRun(cwd, existingMarker.childRunId);
|
|
588
|
+
if (existingChild &&
|
|
589
|
+
(existingChild.lifecycle === "active" ||
|
|
590
|
+
existingChild.lifecycle === "paused" ||
|
|
591
|
+
existingChild.lifecycle === "completed")) {
|
|
592
|
+
await verifyThreeWayConsistency({
|
|
593
|
+
parentRunDir,
|
|
594
|
+
activeChildDir: existingChild.runDir,
|
|
595
|
+
marker: existingMarker,
|
|
596
|
+
parentRunId,
|
|
597
|
+
requestId,
|
|
598
|
+
recoveryRootRunId: recovery.recoveryRootRunId,
|
|
599
|
+
childRunId: existingMarker.childRunId,
|
|
600
|
+
deps,
|
|
601
|
+
});
|
|
478
602
|
return {
|
|
479
603
|
ok: true,
|
|
480
604
|
childRunId: existingMarker.childRunId,
|
|
481
|
-
childRunDir:
|
|
605
|
+
childRunDir: existingChild.runDir,
|
|
482
606
|
marker: existingMarker,
|
|
483
607
|
idempotentReplay: true,
|
|
484
608
|
};
|
|
@@ -489,8 +613,13 @@ export async function stageRecoveryChild(options) {
|
|
|
489
613
|
// buildDagRunId is deterministic for the same task title on the same day. A
|
|
490
614
|
// recovery child must never reuse its still-active parent directory, or the
|
|
491
615
|
// staging path aliases the parent and the required import manifest is absent.
|
|
616
|
+
// The default builder must receive `{ cwd }` (its options object), not the
|
|
617
|
+
// bare string: only the options form enables the collision-safe `-rN`
|
|
618
|
+
// suffix across active/completed/paused runs.
|
|
492
619
|
const childRunId = recovery.childRunId ?? await (async () => {
|
|
493
|
-
const generatedChildRunId =
|
|
620
|
+
const generatedChildRunId = options.buildChildRunId
|
|
621
|
+
? await options.buildChildRunId(parentSpec, cwd)
|
|
622
|
+
: await buildDagRunId(parentSpec, { cwd });
|
|
494
623
|
return generatedChildRunId === parentRunId
|
|
495
624
|
? `${generatedChildRunId}-recovery-${requestId.slice(0, 8)}`
|
|
496
625
|
: generatedChildRunId;
|
|
@@ -528,6 +657,7 @@ export async function stageRecoveryChild(options) {
|
|
|
528
657
|
parentSpec,
|
|
529
658
|
parentState,
|
|
530
659
|
recovery,
|
|
660
|
+
recoveryPlan: options.recoveryPlan,
|
|
531
661
|
requestId,
|
|
532
662
|
childRunId,
|
|
533
663
|
stagingDir,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile, realpath, lstat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
|
|
5
|
+
import { readCommittedEvents } from "./frontend-typed-event-store.js";
|
|
6
|
+
import { stageTypedEventFact, adoptTypedEventFact } from "./frontend-typed-event-transaction.js";
|
|
7
|
+
const invalidScope = (message) => Object.assign(new Error(`FRONTEND_REVIEW_SCOPE_INVALID: ${message}`), { code: "FRONTEND_REVIEW_SCOPE_INVALID" });
|
|
8
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
const isArtifactReference = (value) => isRecord(value)
|
|
10
|
+
&& typeof value.path === "string" && value.path.length > 0
|
|
11
|
+
&& typeof value.sha256 === "string" && /^[a-f0-9]{64}$/.test(value.sha256);
|
|
12
|
+
const hash = (text) => createHash("sha256").update(text).digest("hex");
|
|
13
|
+
/** The section index is navigation; reconstruct coverage against the canonical source before using it. */
|
|
14
|
+
export async function loadFrontendReviewScopes(runDir, phase, targetBytes) {
|
|
15
|
+
const root = await realpath(runDir);
|
|
16
|
+
const files = new Map();
|
|
17
|
+
async function read(relative, expected) {
|
|
18
|
+
const absolute = path.resolve(root, relative);
|
|
19
|
+
if (!absolute.startsWith(root + path.sep) || (await lstat(absolute)).isSymbolicLink() || !(await realpath(absolute)).startsWith(root + path.sep))
|
|
20
|
+
throw invalidScope("artifact escaped run");
|
|
21
|
+
const text = await readFile(absolute, "utf8");
|
|
22
|
+
const sha256 = hash(text);
|
|
23
|
+
if (expected && sha256 !== expected)
|
|
24
|
+
throw Error(`FRONTEND_REVIEW_SCOPE_DRIFT: ${relative}`);
|
|
25
|
+
files.set(relative, sha256);
|
|
26
|
+
return { id: relative, text, sha256 };
|
|
27
|
+
}
|
|
28
|
+
let indexArtifact;
|
|
29
|
+
try {
|
|
30
|
+
indexArtifact = await read("contracts/frontend-implementation-contract.index.json");
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === "ENOENT")
|
|
34
|
+
return undefined;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const index = JSON.parse(indexArtifact.text);
|
|
38
|
+
if (!isRecord(index) || !isArtifactReference(index.contract) || !Array.isArray(index.sections))
|
|
39
|
+
throw invalidScope("malformed contract index");
|
|
40
|
+
const canonicalArtifact = await read(index.contract.path, index.contract.sha256);
|
|
41
|
+
const canonical = JSON.parse(canonicalArtifact.text);
|
|
42
|
+
if (!isRecord(canonical))
|
|
43
|
+
throw invalidScope("canonical contract must be an object");
|
|
44
|
+
const covered = new Map();
|
|
45
|
+
const sections = [];
|
|
46
|
+
const ids = new Set();
|
|
47
|
+
for (const entry of index.sections) {
|
|
48
|
+
if (!isRecord(entry) || !isArtifactReference(entry) || !Array.isArray(entry.slices))
|
|
49
|
+
throw invalidScope("malformed section index entry");
|
|
50
|
+
if (ids.has(entry.path))
|
|
51
|
+
throw invalidScope("duplicate section identity");
|
|
52
|
+
ids.add(entry.path);
|
|
53
|
+
const section = await read(entry.path, entry.sha256);
|
|
54
|
+
const payload = JSON.parse(section.text);
|
|
55
|
+
if (!isRecord(payload) || !Array.isArray(payload.slices) || !isRecord(payload.data))
|
|
56
|
+
throw invalidScope(`malformed section: ${entry.path}`);
|
|
57
|
+
if (payload.contractSha256 !== canonicalArtifact.sha256 || sha256OfCanonicalJson(payload.slices) !== sha256OfCanonicalJson(entry.slices))
|
|
58
|
+
throw invalidScope("unbound section");
|
|
59
|
+
for (const slice of payload.slices) {
|
|
60
|
+
if (!isRecord(slice) || typeof slice.field !== "string" || !Object.hasOwn(canonical, slice.field) || !Object.hasOwn(payload.data, slice.field))
|
|
61
|
+
throw invalidScope(`missing or invalid field reference in ${entry.path}: ${String(slice?.field)}`);
|
|
62
|
+
const value = canonical[slice.field];
|
|
63
|
+
const positions = covered.get(slice.field) ?? new Set();
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
if (!Number.isInteger(slice.start) || !Number.isInteger(slice.end) || slice.start < 0 || slice.end < slice.start || slice.end > value.length || slice.total !== value.length)
|
|
66
|
+
throw invalidScope("invalid array coverage");
|
|
67
|
+
if ((value.length === 0 && covered.has(slice.field)) || (value.length > 0 && slice.start === slice.end))
|
|
68
|
+
throw invalidScope(`empty or duplicate array coverage: ${slice.field}`);
|
|
69
|
+
for (let i = slice.start; i < slice.end; i++) {
|
|
70
|
+
if (positions.has(i))
|
|
71
|
+
throw invalidScope("overlapping coverage");
|
|
72
|
+
positions.add(i);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (["start", "end", "total"].some(key => Object.hasOwn(slice, key)))
|
|
77
|
+
throw invalidScope(`scalar field has array range: ${slice.field}`);
|
|
78
|
+
if (positions.size)
|
|
79
|
+
throw invalidScope("duplicate field");
|
|
80
|
+
positions.add(0);
|
|
81
|
+
}
|
|
82
|
+
const range = Array.isArray(value) ? value.slice(slice.start, slice.end) : value;
|
|
83
|
+
if (sha256OfCanonicalJson(range) !== sha256OfCanonicalJson(payload.data[slice.field]))
|
|
84
|
+
throw invalidScope(`section differs from canonical obligation: ${slice.field}`);
|
|
85
|
+
covered.set(slice.field, positions);
|
|
86
|
+
}
|
|
87
|
+
const sectionFields = new Set(payload.slices.map(slice => slice.field));
|
|
88
|
+
if (Object.keys(payload.data).some(field => !sectionFields.has(field)))
|
|
89
|
+
throw invalidScope(`unindexed section data: ${entry.path}`);
|
|
90
|
+
sections.push(section);
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(canonical).some(field => !covered.has(field) || covered.get(field).size !== (Array.isArray(canonical[field]) ? canonical[field].length : 1)))
|
|
93
|
+
throw invalidScope("canonical field coverage incomplete");
|
|
94
|
+
const scopes = Buffer.byteLength(canonicalArtifact.text) <= targetBytes ? [canonicalArtifact] : sections;
|
|
95
|
+
if (phase === "review") {
|
|
96
|
+
const context = await read("contracts/frontend-review-context.json");
|
|
97
|
+
const payload = JSON.parse(context.text);
|
|
98
|
+
if (payload.contractRef.sha256 !== canonicalArtifact.sha256 || payload.contractRef.indexSha256 !== indexArtifact.sha256)
|
|
99
|
+
throw Error("FRONTEND_REVIEW_SCOPE_DRIFT: review context contract binding");
|
|
100
|
+
const diff = await read(payload.diff.reviewIndexPath);
|
|
101
|
+
const diffIndex = JSON.parse(diff.text);
|
|
102
|
+
await read(payload.diff.patchPath, payload.diff.patchSha256);
|
|
103
|
+
if (diffIndex.patchSha256 !== payload.diff.patchSha256)
|
|
104
|
+
throw Error("FRONTEND_REVIEW_SCOPE_DRIFT: review diff binding");
|
|
105
|
+
scopes.push(context);
|
|
106
|
+
for (const hunk of diffIndex.hunks)
|
|
107
|
+
scopes.push(await read(hunk.patchPath, hunk.sha256));
|
|
108
|
+
}
|
|
109
|
+
return { scopes, digest: sha256OfCanonicalJson([...files]), validate: async () => { for (const [relative, expected] of files)
|
|
110
|
+
await read(relative, expected); } };
|
|
111
|
+
}
|
|
112
|
+
/** Checkpoints attest explicit model completion of already supplied, hash-bound scopes. */
|
|
113
|
+
export function createFrontendReviewScopeProtocol(input) {
|
|
114
|
+
const kind = input.phase === "design" ? "design-scope-completed" : "review-scope-completed";
|
|
115
|
+
let active = new Set();
|
|
116
|
+
const completedScopeIds = () => new Set(readCommittedEvents(input.getStore(), input.attemptId).filter(r => r.fact.kind === kind).map(r => String(r.fact.id)));
|
|
117
|
+
const receipt = (details) => ({ content: [{ type: "text", text: JSON.stringify(details) }], details });
|
|
118
|
+
return {
|
|
119
|
+
setActiveScope(ids) { active = new Set(ids); },
|
|
120
|
+
completedScopeIds,
|
|
121
|
+
committedFacts: () => readCommittedEvents(input.getStore(), input.attemptId),
|
|
122
|
+
assertComplete() { const missing = input.inventory?.scopes.filter(s => !completedScopeIds().has(s.id)) ?? []; if (missing.length)
|
|
123
|
+
throw Object.assign(Error(`FRONTEND_REVIEW_SCOPE_INCOMPLETE: ${missing.map(s => s.id).join(", ")}`), { code: "FRONTEND_REVIEW_SCOPE_INCOMPLETE" }); },
|
|
124
|
+
customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1 } }, required: ["id"], additionalProperties: false },
|
|
125
|
+
async execute(callId, params) {
|
|
126
|
+
const scope = input.inventory.scopes.find(s => s.id === params.id);
|
|
127
|
+
if (!scope || !active.has(params.id))
|
|
128
|
+
return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed" });
|
|
129
|
+
await input.inventory.validate();
|
|
130
|
+
const store = input.getStore();
|
|
131
|
+
const requestId = `${input.attemptId}:${callId}:${randomUUID()}`;
|
|
132
|
+
const fact = { kind, id: scope.id, sha256: scope.sha256 };
|
|
133
|
+
const staged = stageTypedEventFact({ store, requestId, attemptId: input.attemptId, fact });
|
|
134
|
+
const committed = await adoptTypedEventFact({ store, requestId, attemptId: input.attemptId, fact, eventId: staged.eventId, expectedRevision: store.revision });
|
|
135
|
+
return receipt({ ok: true, kind, revision: committed.revision });
|
|
136
|
+
},
|
|
137
|
+
}],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -70,8 +70,10 @@ function blobOf(input) {
|
|
|
70
70
|
].join("\n");
|
|
71
71
|
}
|
|
72
72
|
function pathSpreadScore(paths) {
|
|
73
|
+
const auxiliaryRoot = /^(?:test|tests|__tests__|e2e|cypress|spec|specs|docs|documentation)$/i;
|
|
73
74
|
const tops = new Set(paths
|
|
74
75
|
.map((p) => p.replace(/\\/g, "/").split("/").filter(Boolean)[0] ?? "")
|
|
76
|
+
.filter((root) => !auxiliaryRoot.test(root))
|
|
75
77
|
.filter(Boolean));
|
|
76
78
|
return tops.size;
|
|
77
79
|
}
|