@tea-agent/loop-agent 0.39.0-beta.0 → 0.39.0-beta.10
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/AGENTS.md +4 -1
- package/CHANGELOG.md +248 -95
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2306 -61
- package/dist/executors/pi-executor.js +7 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +180 -33
- package/dist/executors/shell-executor.js +1249 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +298 -32
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +14 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- 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/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
- package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
- package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
- package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.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/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- 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 +534 -178
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +371 -393
- 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 +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -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-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -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 +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +984 -618
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +468 -34
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +321 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +45 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +7 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -200,14 +200,170 @@ export async function inspectBackendTestEnvironment(input) {
|
|
|
200
200
|
serverEntries,
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Filenames declared by the canonical `## Module Index` section of the backend
|
|
205
|
+
* README. Only a unique exact `## Module Index` heading is authoritative; a
|
|
206
|
+
* missing/duplicate section yields an empty declaration set (callers then fall
|
|
207
|
+
* back to the whole directory so legacy workspaces without a manifest keep
|
|
208
|
+
* their prior behaviour). Reads `[x](./x.md)`, `` `testcase/md/<x>.md` `` and
|
|
209
|
+
* inline `x.md` tokens inside the section; README.md itself is never a module.
|
|
210
|
+
*/
|
|
211
|
+
export function extractDeclaredModuleIndexFilenames(readme) {
|
|
212
|
+
const lines = readme
|
|
213
|
+
.replaceAll("\r\n", "\n")
|
|
214
|
+
.replaceAll("\r", "\n")
|
|
215
|
+
.split("\n");
|
|
216
|
+
const headings = lines.flatMap((line, index) => line.trim() === "## Module Index" ? [index] : []);
|
|
217
|
+
if (headings.length !== 1)
|
|
218
|
+
return [];
|
|
219
|
+
const start = headings[0] + 1;
|
|
220
|
+
const relativeEnd = lines
|
|
221
|
+
.slice(start)
|
|
222
|
+
.findIndex((line) => /^##\s+\S/.test(line.trim()));
|
|
223
|
+
const end = relativeEnd < 0 ? lines.length : start + relativeEnd;
|
|
224
|
+
const section = lines.slice(start, end).join("\n");
|
|
225
|
+
return unique([...section.matchAll(/`?[A-Za-z0-9_.-]+\.md`?/g)]
|
|
226
|
+
.map((match) => match[0].replaceAll("`", ""))
|
|
227
|
+
.filter((name) => name.toLowerCase() !== "readme.md"));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Count exact `## Module Index` headings in the backend README.
|
|
231
|
+
*/
|
|
232
|
+
function moduleIndexHeadingCountIn(readme) {
|
|
233
|
+
return readme
|
|
234
|
+
.replaceAll("\r\n", "\n")
|
|
235
|
+
.replaceAll("\r", "\n")
|
|
236
|
+
.split("\n")
|
|
237
|
+
.filter((line) => line.trim() === "## Module Index").length;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Machine-readable fail-closed finding for an invalid backend-test Module Index.
|
|
241
|
+
* These are distinct states (missing/unreadable/duplicate/empty-unparsable) so a
|
|
242
|
+
* stripped Module Index can never silently fall back to a whole-directory scan.
|
|
243
|
+
*/
|
|
244
|
+
export function backendManifestStatusFinding(manifestStatus, moduleIndexHeadingCount = 0) {
|
|
245
|
+
switch (manifestStatus) {
|
|
246
|
+
case "missing":
|
|
247
|
+
return "README is missing; backend-test current round fails closed without a declarative Module Index";
|
|
248
|
+
case "unreadable":
|
|
249
|
+
return "README is unreadable; backend-test current round fails closed without a usable Module Index";
|
|
250
|
+
case "duplicate":
|
|
251
|
+
return moduleIndexHeadingCount === 0
|
|
252
|
+
? "README Module Index heading is missing; backend-test current round fails closed without a declarative Module Index"
|
|
253
|
+
: `duplicate ## Module Index heading (${moduleIndexHeadingCount} occurrences); backend-test current round fails closed without a unique Module Index`;
|
|
254
|
+
case "empty-unparsable":
|
|
255
|
+
return "README Module Index is empty/unparsable; backend-test current round fails closed with no declared module";
|
|
256
|
+
case "valid":
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Resolve the current-round Markdown module manifest. The README `## Module
|
|
262
|
+
* Index` is the authoritative source for which module files belong to this
|
|
263
|
+
* round. Fail closed on every non-`valid` state (missing/unreadable/duplicate/
|
|
264
|
+
* empty-unparsable): the backend-test current round must NEVER re-scan the whole
|
|
265
|
+
* directory, or historical `.md` files could leak back into Case/coverage/
|
|
266
|
+
* partition facts.
|
|
267
|
+
*
|
|
268
|
+
* - `manifestStatus==="valid"` — unique heading with >= 1 declared `.md` file.
|
|
269
|
+
* - `missing` — README file does not exist.
|
|
270
|
+
* - `unreadable` — README exists but could not be read.
|
|
271
|
+
* - `duplicate` — README has 0 or duplicate `## Module Index`.
|
|
272
|
+
* - `empty-unparsable` — unique heading but no `.md` filename parsed.
|
|
273
|
+
* - `declaredMdFiles` — declared filenames that exist on disk (relative paths).
|
|
274
|
+
* - `declaredMissing` — declared filenames with no file on disk (fail-closed
|
|
275
|
+
* evidence, never silently dropped).
|
|
276
|
+
* - `unmanifested` — existing `.md` files that the Module Index does not
|
|
277
|
+
* declare (non-silent advisory; they must not enter Case/coverage/partition).
|
|
278
|
+
*/
|
|
279
|
+
export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
|
|
280
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
281
|
+
const readmePath = path.join(workspaceRoot, ...resolved.readmePath.split("/"));
|
|
282
|
+
const invalid = (manifestStatus, moduleIndexHeadingCount) => ({
|
|
283
|
+
hasManifest: false,
|
|
284
|
+
manifestStatus,
|
|
285
|
+
moduleIndexHeadingCount,
|
|
286
|
+
declaredStems: [],
|
|
287
|
+
declaredMdFiles: [],
|
|
288
|
+
declaredMissing: [],
|
|
289
|
+
unmanifested: [],
|
|
290
|
+
});
|
|
291
|
+
let readme = "";
|
|
292
|
+
if (await exists(readmePath)) {
|
|
293
|
+
try {
|
|
294
|
+
readme = await readFile(readmePath, "utf8");
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// Unreadable README is fail-closed (not silently treated as no manifest).
|
|
298
|
+
return invalid("unreadable", 0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
return invalid("missing", 0);
|
|
303
|
+
}
|
|
304
|
+
const moduleIndexHeadingCount = moduleIndexHeadingCountIn(readme);
|
|
305
|
+
if (moduleIndexHeadingCount !== 1) {
|
|
306
|
+
return invalid("duplicate", moduleIndexHeadingCount);
|
|
307
|
+
}
|
|
308
|
+
const declaredStems = extractDeclaredModuleIndexFilenames(readme);
|
|
309
|
+
if (declaredStems.length === 0) {
|
|
310
|
+
return invalid("empty-unparsable", moduleIndexHeadingCount);
|
|
311
|
+
}
|
|
312
|
+
const hasManifest = true;
|
|
313
|
+
const directory = path.join(workspaceRoot, ...resolved.markdownDir.split("/"));
|
|
314
|
+
let existingNames = [];
|
|
315
|
+
try {
|
|
316
|
+
existingNames = (await readdir(directory, { withFileTypes: true }))
|
|
317
|
+
.filter((entry) => entry.isFile() &&
|
|
318
|
+
entry.name.toLowerCase().endsWith(".md") &&
|
|
319
|
+
entry.name.toLowerCase() !== "readme.md")
|
|
320
|
+
.map((entry) => entry.name);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// missing markdown directory -> no existing modules
|
|
324
|
+
}
|
|
325
|
+
const declaredByLower = new Map();
|
|
326
|
+
for (const name of declaredStems) {
|
|
327
|
+
const base = path.posix.basename(name);
|
|
328
|
+
declaredByLower.set(base.toLowerCase(), base);
|
|
329
|
+
}
|
|
330
|
+
const declaredMdFiles = [];
|
|
331
|
+
const declaredMissing = [];
|
|
332
|
+
for (const base of declaredByLower.values()) {
|
|
333
|
+
const disk = existingNames.find((name) => name.toLowerCase() === base.toLowerCase());
|
|
334
|
+
if (disk)
|
|
335
|
+
declaredMdFiles.push(`${resolved.markdownDir}/${disk}`);
|
|
336
|
+
else
|
|
337
|
+
declaredMissing.push(`${resolved.markdownDir}/${base}`);
|
|
338
|
+
}
|
|
339
|
+
const unmanifested = existingNames
|
|
340
|
+
.filter((name) => !declaredByLower.has(name.toLowerCase()))
|
|
341
|
+
.map((name) => `${resolved.markdownDir}/${name}`);
|
|
342
|
+
return {
|
|
343
|
+
hasManifest,
|
|
344
|
+
manifestStatus: "valid",
|
|
345
|
+
moduleIndexHeadingCount,
|
|
346
|
+
declaredStems,
|
|
347
|
+
declaredMdFiles: declaredMdFiles.sort(),
|
|
348
|
+
declaredMissing: declaredMissing.sort(),
|
|
349
|
+
unmanifested: unmanifested.sort(),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
203
352
|
export async function markdownFiles(root, layout) {
|
|
204
353
|
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
205
354
|
const directory = path.join(root, ...resolved.markdownDir.split("/"));
|
|
206
355
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
207
|
-
|
|
356
|
+
const all = entries
|
|
208
357
|
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
|
|
209
358
|
.map((entry) => path.join(directory, entry.name))
|
|
210
359
|
.sort();
|
|
360
|
+
const manifest = await resolveBackendTestManifestModules(root, resolved);
|
|
361
|
+
if (manifest.manifestStatus !== "valid")
|
|
362
|
+
return [];
|
|
363
|
+
const declaredAbs = new Set(manifest.declaredMdFiles.map((rel) => path.join(root, ...rel.split("/")).toLowerCase()));
|
|
364
|
+
return all
|
|
365
|
+
.filter((file) => declaredAbs.has(file.toLowerCase()))
|
|
366
|
+
.sort();
|
|
211
367
|
}
|
|
212
368
|
function canonicalBackendCaseId(value) {
|
|
213
369
|
const upper = value.toUpperCase();
|
|
@@ -453,6 +609,17 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
453
609
|
const readme = path.join(input.workspaceRoot, ...layout.readmePath.split("/"));
|
|
454
610
|
if (!(await exists(readme)))
|
|
455
611
|
findings.push(`missing ${layout.readmePath}`);
|
|
612
|
+
const manifest = await resolveBackendTestManifestModules(input.workspaceRoot, layout);
|
|
613
|
+
// Fail closed on README Module Index entries that were declared but never
|
|
614
|
+
// materialized — never let a declared module silently vanish from the round.
|
|
615
|
+
if (manifest.declaredMissing.length > 0) {
|
|
616
|
+
findings.push(`declared Markdown module is missing (Module Index): ${manifest.declaredMissing.join(", ")}`);
|
|
617
|
+
}
|
|
618
|
+
// Fail closed on every non-valid Module Index state: without a valid unique
|
|
619
|
+
// Module Index the current round must not re-scan historical `.md` files.
|
|
620
|
+
if (manifest.manifestStatus !== "valid") {
|
|
621
|
+
findings.push(backendManifestStatusFinding(manifest.manifestStatus, manifest.moduleIndexHeadingCount));
|
|
622
|
+
}
|
|
456
623
|
const moduleFiles = files.filter((file) => path.basename(file).toLowerCase() !== "readme.md");
|
|
457
624
|
if (moduleFiles.length === 0)
|
|
458
625
|
findings.push(`no ${layout.markdownDir} module case file found`);
|
|
@@ -562,6 +729,7 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
562
729
|
`- Cases: ${caseCount}`,
|
|
563
730
|
`- Covered AC IDs: ${[...coveredAc].sort().join(", ") || "none"}`,
|
|
564
731
|
`- Environment evidence present: ${input.environmentMarkdown.trim() ? "yes" : "no"}`,
|
|
732
|
+
`- Manifest scope: ${manifest.manifestStatus === "valid" ? "declared-modules" : `fails-closed:${manifest.manifestStatus}`} (declared-missing=${manifest.declaredMissing.length}, unmanifested-advisory=${manifest.unmanifested.length}, module-index-heading-count=${manifest.moduleIndexHeadingCount})`,
|
|
565
733
|
`- Findings: ${findings.length}`,
|
|
566
734
|
"- Required sections: Preconditions/前置条件, Steps/操作步骤, Expected Results/预期结果",
|
|
567
735
|
"- Source-reference validity check: not applied",
|
|
@@ -583,6 +751,13 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
583
751
|
? safetyFindings.map((finding) => `- ${finding}`)
|
|
584
752
|
: ["- No secret-shaped values detected."]),
|
|
585
753
|
"",
|
|
754
|
+
"## Unmanifested Markdown Modules",
|
|
755
|
+
"",
|
|
756
|
+
"- Advisory: existing .md files not declared by the README Module Index are excluded from this round's Case/coverage/partition scope.",
|
|
757
|
+
...(manifest.unmanifested.length > 0
|
|
758
|
+
? manifest.unmanifested.map((item) => `- ${item}`)
|
|
759
|
+
: ["- None"]),
|
|
760
|
+
"",
|
|
586
761
|
"## Findings",
|
|
587
762
|
"",
|
|
588
763
|
...(findings.length > 0 ? findings.map((finding) => `- ${finding}`) : ["- None"]),
|
|
@@ -131,6 +131,23 @@ function normalizeHazardOccurrenceIdentity(line) {
|
|
|
131
131
|
// Unclosed quote or no out-of-string `#`: do not invent a comment boundary.
|
|
132
132
|
return trimmed;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Bare `__NNN_*` helper module names imported by a Python source snippet.
|
|
136
|
+
* Only the head token of an `import`/`from` statement is considered a module
|
|
137
|
+
* name; dotted submodules (`__551_manager_helpers.x`) collapse to the head.
|
|
138
|
+
*/
|
|
139
|
+
function importedBareHelperModules(source) {
|
|
140
|
+
const names = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
for (const match of normalizeSourceNewlines(source).matchAll(/\b(?:import|from)\s+([A-Za-z0-9_.]+)/g)) {
|
|
143
|
+
const head = match[1].split(".")[0];
|
|
144
|
+
if (/^__\d+[A-Za-z0-9_]*$/.test(head) && !seen.has(head)) {
|
|
145
|
+
seen.add(head);
|
|
146
|
+
names.push(head);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return names;
|
|
150
|
+
}
|
|
134
151
|
function listPatternOccurrences(source, patternSource, flags = "") {
|
|
135
152
|
const normalized = normalizeSourceNewlines(source);
|
|
136
153
|
const pattern = new RegExp(patternSource, flags.includes("g") ? flags : `${flags}g`);
|
|
@@ -221,6 +238,8 @@ export async function buildBackendPytestAssetInventory(workspaceRoot, mappedScri
|
|
|
221
238
|
const inputHashes = {};
|
|
222
239
|
const hazardOccurrenceCounts = {};
|
|
223
240
|
const hazardOccurrences = {};
|
|
241
|
+
const scriptBareImports = {};
|
|
242
|
+
const mappedScriptSet = new Set(normalizedScripts);
|
|
224
243
|
for (const file of assetFiles) {
|
|
225
244
|
const bytes = await readFile(path.resolve(workspaceRoot, file));
|
|
226
245
|
const source = bytes.toString("utf8");
|
|
@@ -228,8 +247,10 @@ export async function buildBackendPytestAssetInventory(workspaceRoot, mappedScri
|
|
|
228
247
|
const lists = scanRepairHazardOccurrenceLists(source);
|
|
229
248
|
hazardOccurrences[file] = lists;
|
|
230
249
|
hazardOccurrenceCounts[file] = Object.fromEntries(Object.entries(lists).map(([label, identities]) => [label, identities.length]));
|
|
250
|
+
if (mappedScriptSet.has(file))
|
|
251
|
+
scriptBareImports[file] = importedBareHelperModules(source);
|
|
231
252
|
}
|
|
232
|
-
return { mappedScripts: normalizedScripts, assetFiles, inputHashes, hazardOccurrenceCounts, hazardOccurrences };
|
|
253
|
+
return { mappedScripts: normalizedScripts, assetFiles, inputHashes, hazardOccurrenceCounts, hazardOccurrences, scriptBareImports };
|
|
233
254
|
}
|
|
234
255
|
function bounded(value) {
|
|
235
256
|
const normalized = value.replaceAll(/(Bearer|Basic)\s+[A-Za-z0-9._~+\/-]+/gi, "$1 [REDACTED]");
|
|
@@ -237,13 +258,14 @@ function bounded(value) {
|
|
|
237
258
|
? normalized
|
|
238
259
|
: `${normalized.slice(0, MAX_DIAGNOSTIC_CHARS)}\n...[truncated]`;
|
|
239
260
|
}
|
|
240
|
-
function collectionItems(stdout) {
|
|
261
|
+
function collectionItems(stdout, layout) {
|
|
262
|
+
const rootPrefix = `${(layout ?? resolveBackendTestLayout(undefined)).testRoot}/`;
|
|
241
263
|
return [...new Set(stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => {
|
|
242
264
|
if (!line.includes("::"))
|
|
243
265
|
return false;
|
|
244
266
|
if (/^(ERROR|FAILED|E\s+)/.test(line))
|
|
245
267
|
return false;
|
|
246
|
-
return
|
|
268
|
+
return line.replaceAll("\\", "/").startsWith(rootPrefix);
|
|
247
269
|
}).map((line) => line.replaceAll("\\", "/")))];
|
|
248
270
|
}
|
|
249
271
|
function escapeRegExp(value) {
|
|
@@ -308,8 +330,68 @@ function classifyFixtureResolutionFailure(output, layout) {
|
|
|
308
330
|
repairPaths: paths,
|
|
309
331
|
};
|
|
310
332
|
}
|
|
311
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Generated-local bare helper module signatures found in the bound pytest
|
|
335
|
+
* asset inventory. A file such as `testcase/__551_manager_helpers.py` in the
|
|
336
|
+
* test root/script dir is generated-local: when collection reports
|
|
337
|
+
* `No module named '__551_manager_helpers'`, the defect can be precisely
|
|
338
|
+
* attributed to the generated mapped script instead of a missing third-party
|
|
339
|
+
* package.
|
|
340
|
+
*/
|
|
341
|
+
function generatedLocalBareHelperNames(inventory) {
|
|
342
|
+
if (!inventory)
|
|
343
|
+
return [];
|
|
344
|
+
const names = [];
|
|
345
|
+
for (const file of inventory.assetFiles) {
|
|
346
|
+
const stem = path.posix.basename(file).replace(/\.py$/, "");
|
|
347
|
+
if (/^__\d+[A-Za-z0-9_]*$/.test(stem))
|
|
348
|
+
names.push(stem);
|
|
349
|
+
}
|
|
350
|
+
return [...new Set(names)];
|
|
351
|
+
}
|
|
352
|
+
function classifyCollectionFailure(output, evidence) {
|
|
312
353
|
const normalized = output.replaceAll("\\", "/");
|
|
354
|
+
const helperNames = generatedLocalBareHelperNames(evidence?.inventory);
|
|
355
|
+
const helperByLower = new Map(helperNames.map((name) => [name.toLowerCase(), name]));
|
|
356
|
+
// A bare `__NNN_...` module import that collection cannot find is only a
|
|
357
|
+
// candidate generated-local helper signature. REPAIRABLE requires two
|
|
358
|
+
// deterministic pieces of evidence: (1) a helper asset of the same name in
|
|
359
|
+
// the bound inventory, and (2) at least one mapped generated script whose
|
|
360
|
+
// source truly imports that bare module. Without both — or when attribution
|
|
361
|
+
// is impossible — fail closed as BLOCKED so an un-attributable dependency
|
|
362
|
+
// error is never widened into a repair. Genuine third-party modules (no
|
|
363
|
+
// `__NNN_` signature) stay BLOCKED below.
|
|
364
|
+
const missingModule = /No module named ['\"]([^'\"]+)['\"]/i.exec(normalized)?.[1];
|
|
365
|
+
if (missingModule && /^__\d+[A-Za-z0-9_]*$/.test(missingModule)) {
|
|
366
|
+
const helperAsset = helperByLower.get(missingModule.toLowerCase());
|
|
367
|
+
const inventory = evidence?.inventory;
|
|
368
|
+
const scriptBareImports = inventory?.scriptBareImports ?? {};
|
|
369
|
+
const missingLower = missingModule.toLowerCase();
|
|
370
|
+
const importedByMappedScript = inventory
|
|
371
|
+
? inventory.mappedScripts.some((script) => (scriptBareImports[script] ?? []).some((name) => name.toLowerCase() === missingLower))
|
|
372
|
+
: false;
|
|
373
|
+
if (helperAsset && importedByMappedScript && inventory) {
|
|
374
|
+
const repairPaths = [...new Set([
|
|
375
|
+
...(helperAsset
|
|
376
|
+
? inventory.assetFiles.filter((file) => path.posix.basename(file).replace(/\.py$/, "") === helperAsset)
|
|
377
|
+
: []),
|
|
378
|
+
...inventory.mappedScripts,
|
|
379
|
+
])].sort();
|
|
380
|
+
return {
|
|
381
|
+
status: "REPAIRABLE",
|
|
382
|
+
kind: "missing-generated-local-helper",
|
|
383
|
+
detail: `generated testcase asset imports missing bare generated-local helper module ${missingModule}; inline the helper or remove the generated shared dependency`,
|
|
384
|
+
repairPaths,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
status: "BLOCKED",
|
|
389
|
+
kind: "missing-generated-local-helper-without-evidence",
|
|
390
|
+
detail: helperAsset
|
|
391
|
+
? `missing bare generated-local helper module ${missingModule} has a name match but no mapped generated script imports it (no precise generated-local attribution)`
|
|
392
|
+
: `missing bare __NNN_* module ${missingModule} has no generated-local helper asset in the bound inventory (cannot be precisely attributed)`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
313
395
|
const blockedPatterns = [
|
|
314
396
|
[/No module named ['\"](?!testcase(?:\.|['\"]))/i, "missing-third-party-module"],
|
|
315
397
|
[/pytest(?:_|-)plugin|pluggy|unknown hook|unrecognized arguments/i, "pytest-plugin-or-config"],
|
|
@@ -335,7 +417,7 @@ function classifyCollectionFailure(output) {
|
|
|
335
417
|
return { status: "BLOCKED", kind: "unclassified-collection-error", detail: "collection failure cannot be safely attributed to generated testcase assets" };
|
|
336
418
|
}
|
|
337
419
|
export function assessBackendPytestCollection(input) {
|
|
338
|
-
const items = collectionItems(input.stdout);
|
|
420
|
+
const items = collectionItems(input.stdout, input.layout);
|
|
339
421
|
const fixtureResolution = input.fixtureResolution ?? { exitCode: 0, stdout: "fixture resolution assumed by direct assessor", stderr: "" };
|
|
340
422
|
if (input.exitCode === 0 && fixtureResolution.exitCode === 0) {
|
|
341
423
|
return backendPytestCollectionFactsSchema.parse({
|
|
@@ -401,7 +483,7 @@ export function assessBackendPytestCollection(input) {
|
|
|
401
483
|
fixtureStderrExcerpt: bounded(fixtureResolution.stderr),
|
|
402
484
|
});
|
|
403
485
|
}
|
|
404
|
-
const classification = classifyCollectionFailure(`${input.stdout}\n${input.stderr}
|
|
486
|
+
const classification = classifyCollectionFailure(`${input.stdout}\n${input.stderr}`, { layout: input.layout, inventory: input.inventory });
|
|
405
487
|
return backendPytestCollectionFactsSchema.parse({
|
|
406
488
|
schemaId: "backend-test-pytest-collection-v3",
|
|
407
489
|
phase: input.phase,
|
|
@@ -412,7 +494,9 @@ export function assessBackendPytestCollection(input) {
|
|
|
412
494
|
fixtureResolutionAttempted: false,
|
|
413
495
|
fixtureResolutionStatus: "NOT_RUN",
|
|
414
496
|
fixtureResolutionExitCode: null,
|
|
415
|
-
repairPaths:
|
|
497
|
+
repairPaths: classification.repairPaths && classification.repairPaths.length > 0
|
|
498
|
+
? classification.repairPaths
|
|
499
|
+
: testcasePythonPaths(`${input.stdout}\n${input.stderr}`),
|
|
416
500
|
mappedScripts: input.inventory.mappedScripts,
|
|
417
501
|
existingMappedScripts: input.inventory.mappedScripts,
|
|
418
502
|
missingMappedScripts: [],
|
|
@@ -657,7 +741,7 @@ export async function materializeBackendTestExecutionReadiness(input) {
|
|
|
657
741
|
if (input.effective.phase !== "effective" || input.effective.status !== "PASS" || input.effective.fixtureResolutionStatus !== "PASS") {
|
|
658
742
|
throw new Error("backend-test execution readiness requires effective collection and fixture-resolution PASS");
|
|
659
743
|
}
|
|
660
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts);
|
|
744
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts, { layout: input.layout });
|
|
661
745
|
assertSameInventory(input.effective, current);
|
|
662
746
|
const eligibility = input.eligibility ?? { eligibleItemIds: input.effective.collectedItemIds, excludedItems: [] };
|
|
663
747
|
const status = eligibility.eligibleItemIds.length === 0
|
|
@@ -756,7 +840,7 @@ async function firstUnreadableAssetPath(workspaceRoot, readiness) {
|
|
|
756
840
|
}
|
|
757
841
|
return undefined;
|
|
758
842
|
}
|
|
759
|
-
export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness) {
|
|
843
|
+
export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness, layout) {
|
|
760
844
|
if (!["PASS", "PARTIAL"].includes(readiness.status)) {
|
|
761
845
|
const counts = formatReadinessCounts(readiness);
|
|
762
846
|
const reasonTally = readiness.eligibleItemIds.length === 0 && readiness.excludedItems.length > 0
|
|
@@ -779,7 +863,7 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
|
|
|
779
863
|
}
|
|
780
864
|
let current;
|
|
781
865
|
try {
|
|
782
|
-
current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts);
|
|
866
|
+
current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts, { layout });
|
|
783
867
|
}
|
|
784
868
|
catch {
|
|
785
869
|
const fallbackPath = [...readiness.mappedScripts].sort()[0] ?? Object.keys(readiness.assetHashes).sort()[0] ?? "unknown";
|
|
@@ -814,11 +898,11 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
|
|
|
814
898
|
}
|
|
815
899
|
}
|
|
816
900
|
}
|
|
817
|
-
export async function assertBackendPytestCollectionFresh(workspaceRoot, effective) {
|
|
901
|
+
export async function assertBackendPytestCollectionFresh(workspaceRoot, effective, layout) {
|
|
818
902
|
if (effective.phase !== "effective" || effective.status !== "PASS") {
|
|
819
903
|
throw new Error("backend pytest execution requires effective collection PASS facts");
|
|
820
904
|
}
|
|
821
|
-
const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts);
|
|
905
|
+
const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts, { layout });
|
|
822
906
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
823
907
|
if (priorityOnly.length > 0)
|
|
824
908
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -826,7 +910,7 @@ export async function assertBackendPytestCollectionFresh(workspaceRoot, effectiv
|
|
|
826
910
|
}
|
|
827
911
|
export async function materializeEffectiveBackendPytestCollection(input) {
|
|
828
912
|
if (input.initial.status === "PASS") {
|
|
829
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts);
|
|
913
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts, { layout: input.layout });
|
|
830
914
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
831
915
|
if (priorityOnly.length > 0)
|
|
832
916
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -844,7 +928,7 @@ export async function materializeEffectiveBackendPytestCollection(input) {
|
|
|
844
928
|
if (input.final.status !== "PASS")
|
|
845
929
|
throw new Error("backend pytest collection still fails after bounded repair");
|
|
846
930
|
await assertBackendPytestRepairSafety(input.workspaceRoot, input.initial, input.final);
|
|
847
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts);
|
|
931
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts, { layout: input.layout });
|
|
848
932
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
849
933
|
if (priorityOnly.length > 0)
|
|
850
934
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -385,12 +385,17 @@ export const FORBIDDEN_GENERATED_IMPORT_NAMESPACES = [
|
|
|
385
385
|
"testcase.helpers",
|
|
386
386
|
"testcase.factories",
|
|
387
387
|
];
|
|
388
|
-
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b)/gm;
|
|
388
|
+
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b)/gm;
|
|
389
389
|
/**
|
|
390
|
-
* Scan Python source for imports from forbidden generated shared namespaces
|
|
391
|
-
* Anchored at line start with a word
|
|
392
|
-
* substring inside a docstring
|
|
393
|
-
*
|
|
390
|
+
* Scan Python source for imports from forbidden generated shared namespaces
|
|
391
|
+
* or bare generated-local helper modules. Anchored at line start with a word
|
|
392
|
+
* boundary so `testcase.helpers_other` or a substring inside a docstring is not
|
|
393
|
+
* falsely flagged. In addition to the `testcase_helpers`/`testcase.helpers`/
|
|
394
|
+
* `testcase.factories` namespaces, bare `__<digits>...` helper modules (for
|
|
395
|
+
* example `from __551_manager_helpers import ...` / `import __551_manager_helpers`)
|
|
396
|
+
* are generated-local shared dependencies: a self-contained pytest child must
|
|
397
|
+
* inline or remove them rather than depend on a generated shared module.
|
|
398
|
+
* Returns the list of offending module names (deduplicated, order-preserving).
|
|
394
399
|
*/
|
|
395
400
|
export function findForbiddenGeneratedImports(source) {
|
|
396
401
|
const hits = [];
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { structuredContractOutputSchemaIds, } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of node-level structured contract validators, keyed by the
|
|
4
|
+
* `structuredContractOutput.schemaId` declared on a DAG task.
|
|
5
|
+
*
|
|
6
|
+
* The executor looks the validator up here instead of hardcoding per-contract
|
|
7
|
+
* imports, so new contracts (backend-test manifests, coverage contracts, ...)
|
|
8
|
+
* can opt into producing-node self-validation without touching the executor.
|
|
9
|
+
*/
|
|
10
|
+
export const structuredContractValidationClassifications = [
|
|
11
|
+
"format",
|
|
12
|
+
"schema",
|
|
13
|
+
"truncated",
|
|
14
|
+
"semantic-repair",
|
|
15
|
+
"governance-blocked",
|
|
16
|
+
];
|
|
2
17
|
const validators = new Map();
|
|
3
18
|
export function registerStructuredContractValidator(schemaId, validator) {
|
|
4
19
|
if (validators.has(schemaId)) {
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* its schemaId in the contract output registry. Imported once by the node
|
|
4
4
|
* executor; keeps per-contract imports out of the executor.
|
|
5
5
|
*/
|
|
6
|
-
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput,
|
|
6
|
+
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
|
|
7
7
|
import { registerStructuredContractValidator } from "./contract-output-registry.js";
|
|
8
8
|
registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
|
|
9
9
|
registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
|
|
10
|
-
registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);
|
|
@@ -76,7 +76,7 @@ export async function executeDynamicLoopUntil(input) {
|
|
|
76
76
|
bodyIdMap,
|
|
77
77
|
}));
|
|
78
78
|
for (const child of children) {
|
|
79
|
-
assertValidMaterializedDagTask(child);
|
|
79
|
+
assertValidMaterializedDagTask(child, input.spec);
|
|
80
80
|
if (!input.tasksById.has(child.id)) {
|
|
81
81
|
input.tasksById.set(child.id, child);
|
|
82
82
|
input.spec.tasks.push(child);
|
|
@@ -170,7 +170,7 @@ export async function executeDynamicMapExpansion(input) {
|
|
|
170
170
|
nodeId,
|
|
171
171
|
}));
|
|
172
172
|
for (const child of children) {
|
|
173
|
-
assertValidMaterializedDagTask(child);
|
|
173
|
+
assertValidMaterializedDagTask(child, input.spec);
|
|
174
174
|
}
|
|
175
175
|
const writeSetConflicts = collectDynamicChildWriteSetConflicts(children);
|
|
176
176
|
if (writeSetConflicts.length > 0) {
|
|
@@ -1,118 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"human-required",
|
|
10
|
-
"human-rejected",
|
|
11
|
-
"shell-command",
|
|
12
|
-
"static-error",
|
|
13
|
-
"decision-envelope",
|
|
14
|
-
"skipped",
|
|
15
|
-
"unknown",
|
|
16
|
-
]);
|
|
2
|
+
import { DAG_NORMALIZED_FAILURE_CATEGORY_VALUES } from "../../shared/dag-failure-category.js";
|
|
3
|
+
// The pure classification core lives in src/shared/dag-failure-category.ts so
|
|
4
|
+
// the Operator Console projection can normalize raw CLI categories without
|
|
5
|
+
// importing the workflows kernel (console/workflows boundary test). This
|
|
6
|
+
// module keeps the schema surface for the DAG kernel's existing consumers.
|
|
7
|
+
export { isDagFailureCategory, normalizeDagFailureCategory, } from "../../shared/dag-failure-category.js";
|
|
8
|
+
export const dagNormalizedFailureCategorySchema = z.enum(DAG_NORMALIZED_FAILURE_CATEGORY_VALUES);
|
|
17
9
|
export const DAG_NORMALIZED_FAILURE_CATEGORIES = dagNormalizedFailureCategorySchema.options;
|
|
18
|
-
const RAW_TO_NORMALIZED = {
|
|
19
|
-
success: "success",
|
|
20
|
-
"write-guard": "write-guard",
|
|
21
|
-
timeout: "timeout",
|
|
22
|
-
// Clean implementer stall with zero writes — still a timeout class for report,
|
|
23
|
-
// but retryable under WRITER_TRANSPORT_RETRY_POLICY.
|
|
24
|
-
"writer-clean-timeout": "timeout",
|
|
25
|
-
"missing-api-key": "auth",
|
|
26
|
-
auth: "auth",
|
|
27
|
-
"human-rejected": "human-rejected",
|
|
28
|
-
"human-required": "human-required",
|
|
29
|
-
"decision-envelope-invalid": "decision-envelope",
|
|
30
|
-
"nonzero-exit": "shell-command",
|
|
31
|
-
"spawn-error": "shell-command",
|
|
32
|
-
"static-error": "static-error",
|
|
33
|
-
"invalid-output": "validation",
|
|
34
|
-
"path-violation": "validation",
|
|
35
|
-
"empty-output": "validation",
|
|
36
|
-
"verify-failure": "validation",
|
|
37
|
-
"test-failure": "validation",
|
|
38
|
-
"verification-plan-stale": "validation",
|
|
39
|
-
network: "executor",
|
|
40
|
-
quota: "executor",
|
|
41
|
-
"rate-limit": "executor",
|
|
42
|
-
unavailable: "executor",
|
|
43
|
-
unknown: "unknown",
|
|
44
|
-
};
|
|
45
|
-
function isFinishedStatus(status) {
|
|
46
|
-
return status === "FINISHED" || status === "finished";
|
|
47
|
-
}
|
|
48
|
-
function isInProgressStatus(status) {
|
|
49
|
-
return (status === "PENDING" ||
|
|
50
|
-
status === "RUNNING" ||
|
|
51
|
-
status === "pending" ||
|
|
52
|
-
status === "running");
|
|
53
|
-
}
|
|
54
|
-
function isFailureStatus(status) {
|
|
55
|
-
return (status === "ERROR" ||
|
|
56
|
-
status === "failed" ||
|
|
57
|
-
status === "partial_failed");
|
|
58
|
-
}
|
|
59
|
-
function mapRawCategory(raw) {
|
|
60
|
-
const direct = RAW_TO_NORMALIZED[raw];
|
|
61
|
-
if (direct)
|
|
62
|
-
return direct;
|
|
63
|
-
const lower = raw.toLowerCase();
|
|
64
|
-
if (lower.includes("timeout"))
|
|
65
|
-
return "timeout";
|
|
66
|
-
if (lower.includes("validation") ||
|
|
67
|
-
lower.includes("invalid") ||
|
|
68
|
-
lower.includes("verification-plan-stale") ||
|
|
69
|
-
lower.includes("plan-stale"))
|
|
70
|
-
return "validation";
|
|
71
|
-
if (lower.includes("auth") || lower.includes("api-key"))
|
|
72
|
-
return "auth";
|
|
73
|
-
if (lower.includes("write-guard") || lower.includes("write_guard"))
|
|
74
|
-
return "write-guard";
|
|
75
|
-
if (lower.includes("decision-envelope") || lower.includes("decision_envelope"))
|
|
76
|
-
return "decision-envelope";
|
|
77
|
-
if (lower.includes("human-rejected"))
|
|
78
|
-
return "human-rejected";
|
|
79
|
-
if (lower.includes("human-required"))
|
|
80
|
-
return "human-required";
|
|
81
|
-
if (lower.includes("static-error"))
|
|
82
|
-
return "static-error";
|
|
83
|
-
if (lower.includes("nonzero-exit") ||
|
|
84
|
-
lower.includes("spawn-error") ||
|
|
85
|
-
lower.includes("shell"))
|
|
86
|
-
return "shell-command";
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
export function isDagFailureCategory(value) {
|
|
90
|
-
return DAG_NORMALIZED_FAILURE_CATEGORIES.includes(value);
|
|
91
|
-
}
|
|
92
|
-
export function normalizeDagFailureCategory(raw, status) {
|
|
93
|
-
if (status === "SKIPPED") {
|
|
94
|
-
return "skipped";
|
|
95
|
-
}
|
|
96
|
-
if (raw !== undefined) {
|
|
97
|
-
const mapped = mapRawCategory(raw);
|
|
98
|
-
if (mapped) {
|
|
99
|
-
if (mapped === "success") {
|
|
100
|
-
return isFinishedStatus(status) ? "success" : "unknown";
|
|
101
|
-
}
|
|
102
|
-
return mapped;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (raw === undefined && isFinishedStatus(status)) {
|
|
106
|
-
return "success";
|
|
107
|
-
}
|
|
108
|
-
if (isInProgressStatus(status)) {
|
|
109
|
-
return "unknown";
|
|
110
|
-
}
|
|
111
|
-
if (isFailureStatus(status)) {
|
|
112
|
-
return "unknown";
|
|
113
|
-
}
|
|
114
|
-
if (raw === "success" && isFinishedStatus(status)) {
|
|
115
|
-
return "success";
|
|
116
|
-
}
|
|
117
|
-
return "unknown";
|
|
118
|
-
}
|