@tea-agent/loop-agent 0.1.0-fe-test.0
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 +114 -0
- package/CHANGELOG.md +2283 -0
- package/README.md +177 -0
- package/bin/agent-worker.js +22 -0
- package/bin/loop-agent.js +57 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +29 -0
- package/dist/adapters/loop-agent.js +192 -0
- package/dist/adapters/types.js +1 -0
- package/dist/application/context-usage/skill-resolution-stats.js +263 -0
- package/dist/application/dag/args.js +486 -0
- package/dist/application/dag/generate-task-dag.js +408 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +109 -0
- package/dist/application/dag/validate-dag.js +166 -0
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +69 -0
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +652 -0
- package/dist/application/loop/run-action.js +19 -0
- package/dist/application/task-lifecycle/advance.js +1204 -0
- package/dist/application/task-lifecycle/gates.js +130 -0
- package/dist/application/task-lifecycle/index.js +7 -0
- package/dist/application/task-lifecycle/observe.js +424 -0
- package/dist/application/task-lifecycle/plan-transitions.js +251 -0
- package/dist/application/task-lifecycle/recommendations.js +180 -0
- package/dist/application/task-lifecycle/record.js +73 -0
- package/dist/application/task-lifecycle/types.js +1 -0
- package/dist/build-stamp.json +6 -0
- package/dist/cli/catalog.js +24 -0
- package/dist/cli/command-definitions.js +659 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/program.js +647 -0
- package/dist/cli/router.js +13 -0
- package/dist/cli/update/init-surface-notifier.js +167 -0
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +92 -0
- package/dist/cli/update/runtime-activity.js +29 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/cli.js +57 -0
- package/dist/commands/client-recovery.js +1373 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/coverage-report.js +50 -0
- package/dist/commands/cursor-prompt.js +182 -0
- package/dist/commands/dag-approve.js +142 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +57 -0
- package/dist/commands/dag-reconcile-run.js +5 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +76 -0
- package/dist/commands/dag-request-interrupt.js +20 -0
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/dag-resume.js +35 -0
- package/dist/commands/dag-run-task.js +12 -0
- package/dist/commands/dag-validate.js +20 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +130 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +6 -0
- package/dist/commands/doctor.js +283 -0
- package/dist/commands/eval.js +1398 -0
- package/dist/commands/examples.js +90 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/import-prd.js +81 -0
- package/dist/commands/init-upgrade.js +3085 -0
- package/dist/commands/init.js +3630 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +223 -0
- package/dist/commands/knowledge.js +162 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +251 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/pi-prompt.js +174 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/plan.js +50 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag-progress.js +123 -0
- package/dist/commands/run-dag.js +22 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +113 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/task-advance.js +366 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/commands/task-source-prepare.js +479 -0
- package/dist/commands/task-status.js +133 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/executors/config-core.js +5 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/dag-pi-executor.js +1277 -0
- package/dist/executors/dag-static-executor.js +42 -0
- package/dist/executors/dag.js +3 -0
- package/dist/executors/index.js +6 -0
- package/dist/executors/model-routing.js +71 -0
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/pi-event-serializer.js +65 -0
- package/dist/executors/pi-executor.js +981 -0
- package/dist/executors/pi-playwright-cli-tool.js +1006 -0
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +722 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/playwright-cli-launcher.js +63 -0
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +3055 -0
- package/dist/executors/shell-presets.js +99 -0
- package/dist/executors/shell-verification.js +262 -0
- package/dist/executors/shell-write-guard.js +543 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +437 -0
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/governance/exec-plans.js +549 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +254 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +155 -0
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +50 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +20 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +108 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +215 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +386 -0
- package/dist/records/promotion.js +200 -0
- package/dist/shared/artifacts-core.js +107 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +172 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/openspec-spec.js +125 -0
- package/dist/shared/operator/capabilities.js +2659 -0
- package/dist/shared/operator/command-lifecycle.js +94 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +5 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +530 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/pi-retry-settings.js +23 -0
- package/dist/shared/playwright-cli-command-policy.js +41 -0
- package/dist/shared/preview.js +39 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +264 -0
- package/dist/shared/resilient-git.js +133 -0
- package/dist/shared/runtime-activity.js +6 -0
- package/dist/shared/timeout-policy.js +24 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/sidecars/cursor-prompt/executor.js +428 -0
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +235 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +30 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +185 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +153 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/delegate.js +208 -0
- package/dist/task/frontend-preflight.js +131 -0
- package/dist/task/frontend-project-capability.js +570 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +140 -0
- package/dist/task/runtime.js +708 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/build-draft.js +232 -0
- package/dist/task/source-prepare/completeness.js +248 -0
- package/dist/task/source-prepare/index.js +10 -0
- package/dist/task/source-prepare/parse-intent.js +496 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/placeholder-paths.js +77 -0
- package/dist/task/source-prepare/prepare.js +920 -0
- package/dist/task/source-prepare/reference-integrity.js +290 -0
- package/dist/task/source-prepare/semantic-intake.js +717 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/source-references.js +228 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +41 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/task-demand-routing.js +443 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +388 -0
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +957 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/console/app-data.js +316 -0
- package/dist/worker/console/chat/artifact-card.js +30 -0
- package/dist/worker/console/chat/assistant-content.js +121 -0
- package/dist/worker/console/chat/chat-event-store.js +981 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +45 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/model-resolver.js +215 -0
- package/dist/worker/console/chat/operation-card.js +92 -0
- package/dist/worker/console/chat/pi-console-config.js +376 -0
- package/dist/worker/console/chat/pi-runtime.js +2446 -0
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +125 -0
- package/dist/worker/console/chat/resource-loader.js +59 -0
- package/dist/worker/console/chat/resource-preferences-store.js +152 -0
- package/dist/worker/console/chat/routes.js +3137 -0
- package/dist/worker/console/chat/runtime-context.js +102 -0
- package/dist/worker/console/chat/runtime-selection.js +96 -0
- package/dist/worker/console/chat/semantic-activity.js +489 -0
- package/dist/worker/console/chat/session-store.js +484 -0
- package/dist/worker/console/chat/shortcuts.js +217 -0
- package/dist/worker/console/chat/tool-adapter.js +131 -0
- package/dist/worker/console/chat/tool-preview.js +247 -0
- package/dist/worker/console/chat/tools.js +212 -0
- package/dist/worker/console/chat/turn-execution-registry.js +82 -0
- package/dist/worker/console/chat/turn-process.js +346 -0
- package/dist/worker/console/chat/usage.js +165 -0
- package/dist/worker/console/chat/workspace-landing.js +103 -0
- package/dist/worker/console/dag-confirmation.js +347 -0
- package/dist/worker/console/dag-execution-receipt.js +393 -0
- package/dist/worker/console/doctor.js +230 -0
- package/dist/worker/console/draft-store.js +158 -0
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/index.js +17 -0
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/grill-me.js +277 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/night-aux-ticker.js +146 -0
- package/dist/worker/console/observe-health-match.js +179 -0
- package/dist/worker/console/observe-link.js +42 -0
- package/dist/worker/console/open-browser.js +134 -0
- package/dist/worker/console/operation-run-facts.js +190 -0
- package/dist/worker/console/operation-runner.js +353 -0
- package/dist/worker/console/operation-sse.js +184 -0
- package/dist/worker/console/operation-store.js +169 -0
- package/dist/worker/console/operation-wait.js +355 -0
- package/dist/worker/console/operator-actions.js +3768 -0
- package/dist/worker/console/operator-selection.js +105 -0
- package/dist/worker/console/operator-surface-health.js +24 -0
- package/dist/worker/console/operator-user-error.js +45 -0
- package/dist/worker/console/pi-readiness.js +288 -0
- package/dist/worker/console/prd-identity.js +102 -0
- package/dist/worker/console/prd-intake-bridge.js +441 -0
- package/dist/worker/console/prd-reference-discovery.js +124 -0
- package/dist/worker/console/recovery-cta.js +320 -0
- package/dist/worker/console/recovery-error-copy.js +198 -0
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/repo-fingerprint.js +35 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +115 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +475 -0
- package/dist/worker/console/security.js +170 -0
- package/dist/worker/console/server.js +357 -0
- package/dist/worker/console/sibling-controller.js +33 -0
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-BmOEx0gA.js +1 -0
- package/dist/worker/console/static/assets/arc-BGiVVe8b.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-nDdu4rgJ.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-Bb5u0BWf.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-BlhC0j94.js +10 -0
- package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-Ai8_qQXI.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B9gyCjqX.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-Bzbj3zGH.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-BT415PvT.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-D_A5_5QL.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-srJkaP3f.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-UYGvEP4l.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-B8luBrrz.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-DN7wXZSq.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-DXKyMuKS.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-dqvoynXj.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2--PcyL34A.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DvWs_hTY.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-DxeIxbyO.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-DPCG61Of.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-CmuU50-U.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-B61XLD_v.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BB7HTRRY.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-DvkboAIp.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-BdJ_y1fL.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-9bUBZTRi.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-Dw4L4RBa.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-Jr82N_QP.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-DAz9wFLr.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
- package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-CBB5Np7y.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-D8IOp94y.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-Dgd3AHGG.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BmYOxLE9.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-DfYTEF6J.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-BjuFPRqa.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS-CLyUViGd.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-BvX2Di4-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-BFj0_OLv.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-BIUrD_hx.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-CHBB48fj.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-sJrYw2Sf.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-BzHH6fPR.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-7QCnelCt.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-sDYSxn72.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-XpiUdwzm.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-DvqgNPzD.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-2TyOYNx0.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-B-nM6wM2.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-B86wPdbr.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-Dr1C4AQz.js +7 -0
- package/dist/worker/console/static/favicon.svg +37 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +15 -0
- package/dist/worker/console/static-src/app/console-types.js +184 -0
- package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +324 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +172 -0
- package/dist/worker/console/static-src/app/useRecoveryActions.js +289 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +425 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +296 -0
- package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
- package/dist/worker/console/static-src/chat-view-types.js +2 -0
- package/dist/worker/console/static-src/night/night-types.js +24 -0
- package/dist/worker/console/static-src/night/useNightBoard.js +94 -0
- package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
- package/dist/worker/console/static-src/night-prepare-result.js +118 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
- package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +420 -0
- package/dist/worker/console/static-src/operator-chat/format.js +71 -0
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +86 -0
- package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +38 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
- package/dist/worker/console/static-src/operator-chat/types.js +1 -0
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +495 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +780 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +280 -0
- package/dist/worker/console/static-src/operator-chat/useComposer.js +257 -0
- package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +148 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +358 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +35 -0
- package/dist/worker/console/static-src/prd-file-import.js +218 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/console/workflow-kinds.js +46 -0
- package/dist/worker/delivery/final-verification.js +302 -0
- package/dist/worker/delivery/git-transaction.js +497 -0
- package/dist/worker/delivery/package.js +537 -0
- package/dist/worker/delivery/verification-bundle.js +544 -0
- package/dist/worker/feature/acceptance-policy.js +227 -0
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/decision-loader.js +99 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/fullstack-validate.js +337 -0
- package/dist/worker/feature/next-action.js +140 -0
- package/dist/worker/feature/profile-schema.js +47 -0
- package/dist/worker/feature/ready-plan-projection.js +82 -0
- package/dist/worker/feature/reducer.js +137 -0
- package/dist/worker/feature/review.js +678 -0
- package/dist/worker/feature/run.js +390 -0
- package/dist/worker/feature/scaffold.js +812 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +273 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/command-result.js +1 -0
- package/dist/worker/loop-agent/controller-protocol.js +143 -0
- package/dist/worker/loop-agent/loop-agent-client.js +489 -0
- package/dist/worker/loop-agent/parse-json.js +14 -0
- package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
- package/dist/worker/materialize/harness-task-lineage.js +220 -0
- package/dist/worker/materialize/harness-task-materializer.js +648 -0
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/dag-execution-trajectory.js +591 -0
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/event-store.js +83 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/interrupt-eligibility.js +264 -0
- package/dist/worker/observability/progress-composite.js +34 -0
- package/dist/worker/observability/read-model.js +2499 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/dag-node-execution-output.js +180 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/health.js +58 -0
- package/dist/worker/observe/night-jobs.js +104 -0
- package/dist/worker/observe/node-input.js +441 -0
- package/dist/worker/observe/paths.js +174 -0
- package/dist/worker/observe/routes.js +1077 -0
- package/dist/worker/observe/server.js +117 -0
- package/dist/worker/observe/spec-evidence.js +398 -0
- package/dist/worker/observe/static/api.js +117 -0
- package/dist/worker/observe/static/app.js +169 -0
- package/dist/worker/observe/static/constants.js +182 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/custom-select.js +567 -0
- package/dist/worker/observe/static/dag-edge-routing.js +368 -0
- package/dist/worker/observe/static/dag-helpers.js +161 -0
- package/dist/worker/observe/static/dag-history-labels.js +95 -0
- package/dist/worker/observe/static/dag-layout.d.ts +36 -0
- package/dist/worker/observe/static/dag-layout.js +163 -0
- package/dist/worker/observe/static/dag-model.js +145 -0
- package/dist/worker/observe/static/dom.js +220 -0
- package/dist/worker/observe/static/favicon.svg +37 -0
- package/dist/worker/observe/static/format-pool.d.ts +71 -0
- package/dist/worker/observe/static/format-pool.js +153 -0
- package/dist/worker/observe/static/format.js +347 -0
- package/dist/worker/observe/static/index.html +409 -0
- package/dist/worker/observe/static/kpi.js +78 -0
- package/dist/worker/observe/static/markdown-render.js +124 -0
- package/dist/worker/observe/static/operator-chrome.css +517 -0
- package/dist/worker/observe/static/operator-chrome.d.ts +83 -0
- package/dist/worker/observe/static/operator-chrome.js +623 -0
- package/dist/worker/observe/static/relations.js +133 -0
- package/dist/worker/observe/static/router.js +156 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +65 -0
- package/dist/worker/observe/static/state.js +496 -0
- package/dist/worker/observe/static/styles.css +3567 -0
- package/dist/worker/observe/static/views/batch.js +227 -0
- package/dist/worker/observe/static/views/dag-graph.js +508 -0
- package/dist/worker/observe/static/views/dag-inspector.js +1722 -0
- package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
- package/dist/worker/observe/static/views/dag.js +483 -0
- package/dist/worker/observe/static/views/dags.js +877 -0
- package/dist/worker/observe/static/views/dashboard.js +747 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +492 -0
- package/dist/worker/observe/static/views/night.js +201 -0
- package/dist/worker/observe/static/views/pool.js +708 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +771 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +315 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/outcomes/adapters.js +153 -0
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +40 -0
- package/dist/worker/outcomes/projector.js +227 -0
- package/dist/worker/outcomes/registry.js +1 -0
- package/dist/worker/outcomes/store.js +131 -0
- package/dist/worker/outcomes/types.js +79 -0
- package/dist/worker/pool/attempt-identity.js +41 -0
- package/dist/worker/pool/attempt-lease.js +184 -0
- package/dist/worker/pool/attempt-transition.js +210 -0
- package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
- package/dist/worker/pool/begin-attempt.js +35 -0
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/failure-routing.js +182 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/pool/recovery-decision.js +163 -0
- package/dist/worker/pool/run-owner-store.js +126 -0
- package/dist/worker/pool/run-store.js +360 -0
- package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
- package/dist/worker/pool/state-projection.js +57 -0
- package/dist/worker/pool/types.js +17 -0
- package/dist/worker/pool/validation.js +144 -0
- package/dist/worker/preflight.js +157 -0
- package/dist/worker/profile-mapping.js +76 -0
- package/dist/worker/progress-reporter.js +63 -0
- package/dist/worker/report/morning-report.js +155 -0
- package/dist/worker/repos/repo-resolver.js +23 -0
- package/dist/worker/run-task/execute-prepared-task.js +178 -0
- package/dist/worker/run-task/run-task.js +912 -0
- package/dist/worker/runner/run-ready.js +561 -0
- package/dist/worker/runner/single-task-attempt.js +176 -0
- package/dist/worker/scheduler/admission.js +547 -0
- package/dist/worker/scheduler/auto-followup.js +99 -0
- package/dist/worker/scheduler/cli.js +644 -0
- package/dist/worker/scheduler/clock-install/darwin-launchd.js +238 -0
- package/dist/worker/scheduler/clock-install/linux-systemd-user.js +224 -0
- package/dist/worker/scheduler/clock-install/types.js +1 -0
- package/dist/worker/scheduler/clock-install/win32-schtasks.js +227 -0
- package/dist/worker/scheduler/clock-install.js +284 -0
- package/dist/worker/scheduler/clock.js +420 -0
- package/dist/worker/scheduler/dispatcher.js +503 -0
- package/dist/worker/scheduler/doctor.js +431 -0
- package/dist/worker/scheduler/evidence.js +170 -0
- package/dist/worker/scheduler/git-base.js +90 -0
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/worker/scheduler/lease.js +114 -0
- package/dist/worker/scheduler/lifecycle.js +348 -0
- package/dist/worker/scheduler/lock.js +80 -0
- package/dist/worker/scheduler/morning-window.js +191 -0
- package/dist/worker/scheduler/night-git-finalizer.js +114 -0
- package/dist/worker/scheduler/night-harvest.js +412 -0
- package/dist/worker/scheduler/paths.js +92 -0
- package/dist/worker/scheduler/prepared-attempt-recovery.js +471 -0
- package/dist/worker/scheduler/recovery.js +277 -0
- package/dist/worker/scheduler/reservation.js +146 -0
- package/dist/worker/scheduler/retry.js +199 -0
- package/dist/worker/scheduler/scheduler-loop.js +272 -0
- package/dist/worker/scheduler/store.js +275 -0
- package/dist/worker/scheduler/traceability.js +54 -0
- package/dist/worker/scheduler/trigger.js +258 -0
- package/dist/worker/scheduler/types.js +369 -0
- package/dist/worker/scheduler/workspace-adapter.js +128 -0
- package/dist/worker/task-graph/acceptance-schema.js +40 -0
- package/dist/worker/task-graph/ready-planner.js +267 -0
- package/dist/worker/task-graph/ready-queue.js +23 -0
- package/dist/worker/task-graph/task-graph-schema.js +59 -0
- package/dist/worker/task-graph/types.js +1 -0
- package/dist/worker/task-graph/validate.js +293 -0
- package/dist/worker/task-spec/complexity-mapping.js +8 -0
- package/dist/worker/task-spec/schema.js +125 -0
- package/dist/worker/task-spec/types.js +1 -0
- package/dist/worker/task-spec/validate.js +391 -0
- package/dist/worker/task-spec/workflow-routing.js +149 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +507 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1985 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +638 -0
- package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
- package/dist/workflows/dag/backend-test-contract-envelope.js +205 -0
- package/dist/workflows/dag/backend-test-coverage-contract.js +202 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +715 -0
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +178 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1984 -0
- package/dist/workflows/dag/backend-test-module-stem.js +26 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +859 -0
- package/dist/workflows/dag/backend-test-result-contract.js +1030 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +1670 -0
- package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
- package/dist/workflows/dag/backend-test-stability-contract.js +57 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +1058 -0
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +10 -0
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +596 -0
- package/dist/workflows/dag/decision-envelope.js +557 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +161 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +435 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +203 -0
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/executor-registry.js +23 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +118 -0
- package/dist/workflows/dag/failure-routing.js +105 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1911 -0
- package/dist/workflows/dag/frontend-lint-baseline.js +474 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +1011 -0
- package/dist/workflows/dag/frontend-project-capability.js +1 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +541 -0
- package/dist/workflows/dag/frontend-review-context.js +116 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +343 -0
- package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
- package/dist/workflows/dag/frontend-test-html-report.js +174 -0
- package/dist/workflows/dag/frontend-test-l5-report.js +177 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +487 -0
- package/dist/workflows/dag/frontend-verification-trace.js +275 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +201 -0
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +413 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +7345 -0
- package/dist/workflows/dag/interrupt-request.js +559 -0
- package/dist/workflows/dag/knowledge-curator.js +165 -0
- package/dist/workflows/dag/l5-report-metrics.js +33 -0
- package/dist/workflows/dag/lifecycle.js +855 -0
- package/dist/workflows/dag/liveness-policy.js +251 -0
- package/dist/workflows/dag/node-execution.js +1081 -0
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/dist/workflows/dag/output-protocol.js +356 -0
- package/dist/workflows/dag/project-governance-context.js +514 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +348 -0
- package/dist/workflows/dag/reconcile-run.js +145 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +329 -0
- package/dist/workflows/dag/repair-artifact.js +363 -0
- package/dist/workflows/dag/report.js +1229 -0
- package/dist/workflows/dag/rerun-plan.js +614 -0
- package/dist/workflows/dag/rerun-run.js +556 -0
- package/dist/workflows/dag/rerun-task.js +370 -0
- package/dist/workflows/dag/retry-policy.js +263 -0
- package/dist/workflows/dag/run-store.js +41 -0
- package/dist/workflows/dag/runner.js +1069 -0
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/scheduler.js +497 -0
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +478 -0
- package/dist/workflows/dag/skill-snapshot.js +553 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/task-demand-routing.js +1 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +1145 -0
- package/dist/workflows/dag/upstream-artifacts.js +98 -0
- package/dist/workflows/dag/validate.js +998 -0
- package/dist/workflows/dag/workspace-checkpoint.js +186 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +371 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +115 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions/dag-action.js +129 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +160 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +135 -0
- package/dist/workflows/loop/context.js +47 -0
- package/dist/workflows/loop/events.js +26 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/policy/auto-policy.js +112 -0
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +52 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +82 -0
- package/docs/architecture/README.md +35 -0
- package/docs/architecture/dag-execution.md +165 -0
- package/docs/architecture/evolution.md +108 -0
- package/docs/architecture/facts-and-state.md +74 -0
- package/docs/architecture/runtime-boundaries.md +226 -0
- package/docs/architecture/system-overview.md +100 -0
- package/docs/architecture/worker-and-feature.md +122 -0
- package/docs/governance/README.md +18 -0
- package/docs/governance/harness-methodology-debugging.md +177 -0
- package/docs/governance/harness-methodology-tdd.md +130 -0
- package/docs/governance/harness-methodology-verification.md +27 -0
- package/docs/init-surface.manifest.json +358 -0
- package/docs/operations/README.md +15 -0
- package/docs/operations/local-development-environment.md +77 -0
- package/docs/skills/README.md +7 -0
- package/docs/skills/vetted-skill-registry.md +49 -0
- package/docs/templates/README.md +67 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +473 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +190 -0
- package/docs/templates/agent-dag.final-verification.json +185 -0
- package/docs/templates/agent-dag.schema.json +590 -0
- package/docs/templates/agent-dag.supervised-implementation.json +640 -0
- package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
- package/docs/templates/backend-test-analysis.schema.json +37 -0
- package/docs/templates/backend-test-case-manifest.schema.json +223 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +41 -0
- package/docs/templates/backend-test-dag.json +671 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +165 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +36 -0
- package/docs/templates/backend-test-execution.schema.json +138 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +115 -0
- package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/exec-plan.md +66 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/frontend-design-contract.md +68 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +35 -0
- package/docs/templates/frontend-task-constraints.md +62 -0
- package/docs/templates/frontend-task-requirement.md +104 -0
- package/docs/templates/frontend-test-case-checklist.md +44 -0
- package/docs/templates/frontend-test-case.example.md +143 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +63 -0
- package/docs/templates/frontend-test-dag.json +409 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +21 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/frontend-test-standard-scenarios.v1.json +159 -0
- package/docs/templates/harness.schema.json +351 -0
- package/docs/templates/hybrid-dag.json +188 -0
- package/docs/templates/init-evolution-review.md +35 -0
- package/docs/templates/init-managed-agents.md +157 -0
- package/docs/templates/interactive-ui-round2-experiment.md +66 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +9 -0
- package/docs/templates/product-line/README.md +42 -0
- package/docs/templates/product-line/acceptance.yaml +23 -0
- package/docs/templates/product-line/closeout.yaml +9 -0
- package/docs/templates/product-line/design.md +13 -0
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/links.md +10 -0
- package/docs/templates/product-line/requirement.md +17 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/task-graph.yaml +23 -0
- package/docs/templates/product-line/task.yaml +68 -0
- package/docs/templates/product-line/test-plan.md +7 -0
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +27 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +48 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/templates/worker-dogfood-evidence.md +80 -0
- package/docs/templates/worker-dogfood-setup.md +68 -0
- package/examples/decision-gate-agent-dag.json +177 -0
- package/examples/example-dag.json +46 -0
- package/examples/hybrid-loop-agent-dag.json +189 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/examples/l5-report-demo.html +79 -0
- package/harness.json +90 -0
- package/package.json +98 -0
- package/scripts/kb-bootstrap-init-skeleton.sh +241 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-query.mjs +554 -0
- package/skills/agent-worker/SKILL.md +48 -0
- package/skills/agent-worker/references/agent-worker-operator.md +159 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/analyze-product-dependencies/SKILL.md +108 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +35 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +38 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +110 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +34 -0
- package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +76 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +460 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +194 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +200 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +90 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +138 -0
- package/skills/analyze-product-requirements/SKILL.md +145 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +87 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +70 -0
- package/skills/analyze-product-requirements/references/example.md +104 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +144 -0
- package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +41 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +42 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +66 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +501 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +100 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +175 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/frontend-bounded-implement/SKILL.md +53 -0
- package/skills/frontend-design-review/SKILL.md +79 -0
- package/skills/frontend-design-review/references/review-checklist.md +43 -0
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +55 -0
- package/skills/frontend-review/references/review-findings.md +50 -0
- package/skills/frontend-verification/SKILL.md +61 -0
- package/skills/frontend-verification/references/verification-checklist.md +48 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +80 -0
- package/skills/grill-with-docs/adr-format.md +58 -0
- package/skills/grill-with-docs/context-format.md +52 -0
- package/skills/init-capability-evolution/SKILL.md +70 -0
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/SKILL.md +68 -0
- package/skills/loop-agent/references/README.md +67 -0
- package/skills/loop-agent/references/command-reference.md +645 -0
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +289 -0
- package/skills/loop-agent/references/hybrid-dag.md +245 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/long-running-loop.md +57 -0
- package/skills/loop-agent/references/model-routing.md +45 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +84 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +175 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +54 -0
- package/skills/loop-agent/references/source-and-plan-practice.md +169 -0
- package/skills/loop-agent/references/task-workflow.md +104 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +148 -0
- package/skills/playwright-cli/SKILL.md +93 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +5 -0
- package/skills/playwright-cli/references/video-recording.md +5 -0
- package/skills/playwright-cli-case-generator/SKILL.md +108 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +312 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +27 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
- package/skills/webapp-testing/SKILL.md +19 -0
|
@@ -0,0 +1,1911 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
|
|
8
|
+
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
|
+
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
export const frontendNormalizationActionSchema = z.enum([
|
|
12
|
+
"remove-trailing-commas",
|
|
13
|
+
"strip-comments",
|
|
14
|
+
"canonicalize-requirement-ids",
|
|
15
|
+
"canonicalize-verification-alias",
|
|
16
|
+
"drop-unresolved-verification-symbols",
|
|
17
|
+
"inject-source-binding",
|
|
18
|
+
]);
|
|
19
|
+
export function sha256Hex(input) {
|
|
20
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
21
|
+
}
|
|
22
|
+
function sortKeysDeep(value) {
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return value.map(sortKeysDeep);
|
|
25
|
+
if (value && typeof value === "object") {
|
|
26
|
+
const record = value;
|
|
27
|
+
const sorted = {};
|
|
28
|
+
for (const key of Object.keys(record).sort()) {
|
|
29
|
+
sorted[key] = sortKeysDeep(record[key]);
|
|
30
|
+
}
|
|
31
|
+
return sorted;
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
/** Deterministic JSON serialization: fixed key order, 2-space indent, no
|
|
36
|
+
* trailing newline. Shared by candidate/canonical/digest hashes so the same
|
|
37
|
+
* input always reproduces the same digest. */
|
|
38
|
+
export function serializeDeterministicJson(value) {
|
|
39
|
+
return JSON.stringify(sortKeysDeep(value), null, 2);
|
|
40
|
+
}
|
|
41
|
+
export function deterministicSha256(value) {
|
|
42
|
+
return sha256Hex(serializeDeterministicJson(value));
|
|
43
|
+
}
|
|
44
|
+
/** A contract materialization failure carries its classification plus the
|
|
45
|
+
* audit context collected up to the failure point, so the prewrite gate can
|
|
46
|
+
* persist a complete frontend-prewrite-result-v1 even when the canonical
|
|
47
|
+
* contract is never materialized. */
|
|
48
|
+
export class FrontendContractFailure extends Error {
|
|
49
|
+
kind;
|
|
50
|
+
candidateRawSha256;
|
|
51
|
+
candidateJsonSha256;
|
|
52
|
+
normalizationActions;
|
|
53
|
+
constructor(options) {
|
|
54
|
+
super(options.message);
|
|
55
|
+
this.name = "FrontendContractFailure";
|
|
56
|
+
this.kind = options.kind;
|
|
57
|
+
this.candidateRawSha256 = options.candidateRawSha256;
|
|
58
|
+
this.candidateJsonSha256 = options.candidateJsonSha256 ?? null;
|
|
59
|
+
this.normalizationActions = options.normalizationActions ?? [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Write a JSON artifact with deterministic key order and return its sha256
|
|
63
|
+
* over the exact file bytes, so the canonical contract identity matches what
|
|
64
|
+
* downstream verify/repair/review re-read from disk. */
|
|
65
|
+
export async function writeDeterministicJsonArtifact(runDir, relativePath, value) {
|
|
66
|
+
const json = `${serializeDeterministicJson(value)}\n`;
|
|
67
|
+
const targetPath = path.join(runDir, relativePath);
|
|
68
|
+
await writeTextArtifactFile(targetPath, json);
|
|
69
|
+
return { path: targetPath, sha256: sha256Hex(json) };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extract frozen command labels from the DAG run spec (run.json).
|
|
73
|
+
* The run spec is written before any node executes, so it is always available
|
|
74
|
+
* when the prewrite gate materializes the contract.
|
|
75
|
+
*/
|
|
76
|
+
async function deriveFrozenCommandLabelsFromRun(runDir) {
|
|
77
|
+
const specPath = path.join(runDir, "run.json");
|
|
78
|
+
let raw;
|
|
79
|
+
try {
|
|
80
|
+
raw = await readFile(specPath, "utf8");
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// run.json is guaranteed to exist in production (DAG runner writes it
|
|
84
|
+
// before any node executes). When absent (e.g. test fixtures), there is
|
|
85
|
+
// no frozen set to validate against and the check is skipped downstream.
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
let spec;
|
|
89
|
+
try {
|
|
90
|
+
spec = JSON.parse(raw);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new Error(`cannot derive frozen command labels: run.json is not valid JSON at ${specPath}: ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
const labels = new Set();
|
|
96
|
+
for (const task of spec.tasks ?? []) {
|
|
97
|
+
const cmdLabels = task.shell?.verifyEvidence?.commandLabels;
|
|
98
|
+
if (cmdLabels) {
|
|
99
|
+
for (const label of cmdLabels)
|
|
100
|
+
labels.add(label);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [...labels];
|
|
104
|
+
}
|
|
105
|
+
export const FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID = "frontend-implementation-contract-v1";
|
|
106
|
+
export const FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID = "frontend-implementation-contract-plan-patch-v1";
|
|
107
|
+
/**
|
|
108
|
+
* Build the deterministic, run-owned portion of the frontend contract. The
|
|
109
|
+
* planner is allowed to fill semantic fields with an RFC 7386 merge patch but
|
|
110
|
+
* cannot redefine task identity, risk, or the implementation write boundary.
|
|
111
|
+
*/
|
|
112
|
+
export function buildFrontendImplementationContractSkeleton(input) {
|
|
113
|
+
return {
|
|
114
|
+
schemaVersion: 1,
|
|
115
|
+
sourceBinding: canonicalFrontendContractSourceBinding(input.sourceBinding),
|
|
116
|
+
riskLevel: input.riskLevel,
|
|
117
|
+
targets: { files: [...input.targetFiles] },
|
|
118
|
+
mockApi: { productionDefaultOff: true },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Load the canonical frontend-implementation-contract-v1 JSON Schema from the
|
|
123
|
+
* installed loop-agent package docs/templates/ path. Package-root discovery
|
|
124
|
+
* works from both the source module and the compiled dist module without
|
|
125
|
+
* relying on CommonJS globals in the ESM runtime.
|
|
126
|
+
*
|
|
127
|
+
* Validation is fail-closed: missing file, malformed JSON, mismatched $id,
|
|
128
|
+
* missing additionalProperties: false, or incomplete top-level required keys
|
|
129
|
+
* all throw before any DAG prompt is assembled.
|
|
130
|
+
*/
|
|
131
|
+
export function loadFrontendImplementationContractJsonSchema(startDir = path.dirname(fileURLToPath(import.meta.url))) {
|
|
132
|
+
const packageRoot = findPackageRoot(startDir);
|
|
133
|
+
if (!packageRoot) {
|
|
134
|
+
throw new Error(`cannot locate loop-agent package root from ${path.resolve(startDir)}`);
|
|
135
|
+
}
|
|
136
|
+
const schemaPath = path.join(packageRoot, "docs", "templates", "frontend-implementation-contract.schema.json");
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = readFileSync(schemaPath, "utf-8");
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new Error(`cannot load frontend-implementation-contract.schema.json from current loop-agent package at ${schemaPath}: ${error.code ?? String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(content);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
throw new Error(`frontend-implementation-contract.schema.json is not valid JSON: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
152
|
+
throw new Error("frontend-implementation-contract.schema.json root is not a JSON object");
|
|
153
|
+
}
|
|
154
|
+
const schema = parsed;
|
|
155
|
+
if (schema.$id !== FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID) {
|
|
156
|
+
throw new Error(`schema $id mismatch: expected ${FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID}, got ${String(schema.$id)}`);
|
|
157
|
+
}
|
|
158
|
+
if (schema.additionalProperties !== false) {
|
|
159
|
+
throw new Error("schema must have additionalProperties: false at top level");
|
|
160
|
+
}
|
|
161
|
+
const expectedRequired = [
|
|
162
|
+
"schemaVersion",
|
|
163
|
+
"sourceBinding",
|
|
164
|
+
"riskLevel",
|
|
165
|
+
"targets",
|
|
166
|
+
"requirements",
|
|
167
|
+
"uiStates",
|
|
168
|
+
"interactions",
|
|
169
|
+
"mockApi",
|
|
170
|
+
"designEvidence",
|
|
171
|
+
"verificationTargets",
|
|
172
|
+
"evidenceGaps",
|
|
173
|
+
];
|
|
174
|
+
const actualRequired = Array.isArray(schema.required) ? schema.required : [];
|
|
175
|
+
const missing = expectedRequired.filter((key) => !actualRequired.includes(key));
|
|
176
|
+
if (missing.length > 0) {
|
|
177
|
+
throw new Error(`schema required fields missing: ${missing.join(", ")}`);
|
|
178
|
+
}
|
|
179
|
+
const properties = schema.properties && typeof schema.properties === "object"
|
|
180
|
+
? schema.properties
|
|
181
|
+
: {};
|
|
182
|
+
const missingProperties = expectedRequired.filter((key) => !Object.hasOwn(properties, key));
|
|
183
|
+
if (missingProperties.length > 0) {
|
|
184
|
+
throw new Error(`schema properties missing: ${missingProperties.join(", ")}`);
|
|
185
|
+
}
|
|
186
|
+
const schemaVersion = properties.schemaVersion;
|
|
187
|
+
const mockApi = properties.mockApi;
|
|
188
|
+
const mockApiProperties = mockApi?.properties;
|
|
189
|
+
const productionDefaultOff = mockApiProperties?.productionDefaultOff;
|
|
190
|
+
if (schemaVersion?.const !== 1 || productionDefaultOff?.const !== true) {
|
|
191
|
+
throw new Error("schema fixed values are incomplete: schemaVersion.const must be 1 and mockApi.productionDefaultOff.const must be true");
|
|
192
|
+
}
|
|
193
|
+
return JSON.stringify(parsed);
|
|
194
|
+
}
|
|
195
|
+
const id = z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/);
|
|
196
|
+
const REQUIREMENT_ID_PATTERN = /^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/;
|
|
197
|
+
const safePath = z
|
|
198
|
+
.string()
|
|
199
|
+
.min(1)
|
|
200
|
+
.refine((value) => !value.startsWith("/") &&
|
|
201
|
+
!value.includes("\\") &&
|
|
202
|
+
!value.split("/").includes(".."), "must be a relative POSIX path without traversal");
|
|
203
|
+
const gap = z
|
|
204
|
+
.object({
|
|
205
|
+
requirementId: id.optional(),
|
|
206
|
+
description: z.string().min(1),
|
|
207
|
+
blocking: z.boolean(),
|
|
208
|
+
})
|
|
209
|
+
.strict();
|
|
210
|
+
/**
|
|
211
|
+
* Structured component/token selection contract (ADR 0016). Every user-visible
|
|
212
|
+
* UI purpose the plan touches must be declared here so the prewrite gate can
|
|
213
|
+
* deterministically cross-check that the selection comes from the frontend
|
|
214
|
+
* specification (openspec/ai_workspace component + theme specs) instead of
|
|
215
|
+
* relying on design-review's soft model judgment.
|
|
216
|
+
*
|
|
217
|
+
* - `specified`: the frontend spec mandates this component for the purpose →
|
|
218
|
+
* `specReference` is required (path/section/line hit).
|
|
219
|
+
* - `reuse-existing`: reuse an existing repo component/convention not named by
|
|
220
|
+
* the spec → `specReference` may be null, rationale explains the basis.
|
|
221
|
+
* - `new`: neither spec nor existing code fits → `specReference` must be null,
|
|
222
|
+
* rationale must declare the deviation (design-review approves it).
|
|
223
|
+
*
|
|
224
|
+
* `specReference` is nullable + optional because `stripNullValuesDeep` strips
|
|
225
|
+
* null object values before validation, so a `new` choice's `specReference:
|
|
226
|
+
* null` becomes an absent field; the superRefine below treats absent and null
|
|
227
|
+
* identically.
|
|
228
|
+
*/
|
|
229
|
+
const uiComponentSpecReferenceSchema = z
|
|
230
|
+
.object({
|
|
231
|
+
path: safePath,
|
|
232
|
+
section: z.string(),
|
|
233
|
+
line: z.number().int().nullable().optional(),
|
|
234
|
+
})
|
|
235
|
+
.strict();
|
|
236
|
+
const uiComponentChoiceSchema = z
|
|
237
|
+
.object({
|
|
238
|
+
purpose: z.string().min(1),
|
|
239
|
+
component: z.string().min(1),
|
|
240
|
+
decision: z.enum(["specified", "reuse-existing", "new"]),
|
|
241
|
+
specReference: uiComponentSpecReferenceSchema.nullable().optional(),
|
|
242
|
+
rationale: z.string().min(1),
|
|
243
|
+
})
|
|
244
|
+
.strict();
|
|
245
|
+
const uiComponentChoicesSchema = z.array(uiComponentChoiceSchema);
|
|
246
|
+
/**
|
|
247
|
+
* Normalize unstable model emissions before the strict schema sees them:
|
|
248
|
+
* recursively drop null values (object entries and array elements). A null for
|
|
249
|
+
* an optional field becomes "absent" (accepted); a null for a required field
|
|
250
|
+
* still fails, but with a clean Required issue instead of a confusing type
|
|
251
|
+
* mismatch. Unknown keys and misspellings remain strict rejections.
|
|
252
|
+
*/
|
|
253
|
+
function stripNullValuesDeep(value) {
|
|
254
|
+
if (Array.isArray(value)) {
|
|
255
|
+
return value
|
|
256
|
+
.filter((item) => item !== null)
|
|
257
|
+
.map((item) => stripNullValuesDeep(item));
|
|
258
|
+
}
|
|
259
|
+
if (value && typeof value === "object") {
|
|
260
|
+
const record = value;
|
|
261
|
+
const stripped = {};
|
|
262
|
+
for (const [key, item] of Object.entries(record)) {
|
|
263
|
+
if (item !== null)
|
|
264
|
+
stripped[key] = stripNullValuesDeep(item);
|
|
265
|
+
}
|
|
266
|
+
return stripped;
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
export const frontendImplementationContractSchema = z
|
|
271
|
+
.preprocess(stripNullValuesDeep, z
|
|
272
|
+
.object({
|
|
273
|
+
schemaVersion: z.literal(1),
|
|
274
|
+
sourceBinding: z
|
|
275
|
+
.object({
|
|
276
|
+
taskId: z.string().min(1),
|
|
277
|
+
requirementPath: safePath,
|
|
278
|
+
requirementSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
279
|
+
referencePaths: z.array(safePath),
|
|
280
|
+
requirementIds: z.array(id),
|
|
281
|
+
})
|
|
282
|
+
.strict(),
|
|
283
|
+
riskLevel: z.enum(["small", "standard", "high-risk"]),
|
|
284
|
+
targets: z
|
|
285
|
+
.object({
|
|
286
|
+
files: z.array(safePath).min(1),
|
|
287
|
+
routes: z.array(z.string().startsWith("/")).optional(),
|
|
288
|
+
publicApiChanges: z.array(z.string().min(1)).optional(),
|
|
289
|
+
})
|
|
290
|
+
.strict(),
|
|
291
|
+
requirements: z.array(z
|
|
292
|
+
.object({
|
|
293
|
+
id,
|
|
294
|
+
expectedOutcome: z.string().min(1),
|
|
295
|
+
implementationTargets: z.array(safePath),
|
|
296
|
+
verificationTargetIds: z.array(z.string().min(1)),
|
|
297
|
+
evidenceGap: gap.optional(),
|
|
298
|
+
})
|
|
299
|
+
.strict()).min(1),
|
|
300
|
+
uiStates: z.array(z
|
|
301
|
+
.object({
|
|
302
|
+
name: z.string().min(1),
|
|
303
|
+
applicable: z.boolean(),
|
|
304
|
+
expectedBehavior: z.string().min(1).optional(),
|
|
305
|
+
implementationTargets: z.array(safePath).optional(),
|
|
306
|
+
verificationTargetIds: z.array(z.string().min(1)).optional(),
|
|
307
|
+
notApplicableReason: z.string().min(1).optional(),
|
|
308
|
+
})
|
|
309
|
+
.strict()),
|
|
310
|
+
interactions: z.array(z
|
|
311
|
+
.object({
|
|
312
|
+
name: z.string().min(1),
|
|
313
|
+
trigger: z.string().min(1),
|
|
314
|
+
expectedBehavior: z.string().min(1),
|
|
315
|
+
implementationTargets: z.array(safePath),
|
|
316
|
+
verificationTargetIds: z.array(z.string().min(1)),
|
|
317
|
+
})
|
|
318
|
+
.strict()),
|
|
319
|
+
mockApi: z
|
|
320
|
+
.object({
|
|
321
|
+
strategy: z.enum([
|
|
322
|
+
"native",
|
|
323
|
+
"browser-intercept",
|
|
324
|
+
"request-adapter",
|
|
325
|
+
"not-needed",
|
|
326
|
+
]),
|
|
327
|
+
productionDefaultOff: z.literal(true),
|
|
328
|
+
activation: z.preprocess((value) => value === "" || value === null || value === undefined
|
|
329
|
+
? "explicit activation boundary"
|
|
330
|
+
: value, z.string().min(1)),
|
|
331
|
+
endpoints: z.array(z
|
|
332
|
+
.object({
|
|
333
|
+
method: z.enum([
|
|
334
|
+
"GET",
|
|
335
|
+
"POST",
|
|
336
|
+
"PUT",
|
|
337
|
+
"PATCH",
|
|
338
|
+
"DELETE",
|
|
339
|
+
"HEAD",
|
|
340
|
+
"OPTIONS",
|
|
341
|
+
]),
|
|
342
|
+
path: z.string().startsWith("/"),
|
|
343
|
+
// Models occasionally emit an empty fixture when Mock is
|
|
344
|
+
// intentionally not needed. Treat it like an omitted optional
|
|
345
|
+
// field; active Mock strategies still fail the refinement below.
|
|
346
|
+
fixture: z.preprocess((value) => (value === "" || value === null ? undefined : value), safePath.optional()),
|
|
347
|
+
consumer: safePath.optional(),
|
|
348
|
+
})
|
|
349
|
+
.strict()),
|
|
350
|
+
})
|
|
351
|
+
.strict(),
|
|
352
|
+
designEvidence: z
|
|
353
|
+
.object({
|
|
354
|
+
source: z.string().min(1),
|
|
355
|
+
paths: z.array(safePath),
|
|
356
|
+
conflicts: z.array(z.string().min(1)),
|
|
357
|
+
})
|
|
358
|
+
.strict(),
|
|
359
|
+
verificationTargets: z.array(z
|
|
360
|
+
.object({
|
|
361
|
+
id: z.string().min(1),
|
|
362
|
+
type: z.enum(["static", "unit", "component", "integration", "mock"]),
|
|
363
|
+
commandLabel: z.string().min(1),
|
|
364
|
+
file: safePath,
|
|
365
|
+
symbol: z.preprocess((value) => (value === "" || value === null ? undefined : value), z.string().min(1).optional()),
|
|
366
|
+
requirementIds: z.array(id),
|
|
367
|
+
uiStates: z.array(z.string().min(1)),
|
|
368
|
+
})
|
|
369
|
+
.strict()).min(1),
|
|
370
|
+
evidenceGaps: z.array(gap),
|
|
371
|
+
implementationSteps: z.array(z.string().min(1)).optional(),
|
|
372
|
+
stylingStrategy: z.string().min(1).optional(),
|
|
373
|
+
uiComponentChoices: uiComponentChoicesSchema.optional(),
|
|
374
|
+
dependencyPolicy: z.string().min(1).optional(),
|
|
375
|
+
residualRisks: z.array(z.string().min(1)).optional(),
|
|
376
|
+
realIntegrationGap: z.string().min(1).optional(),
|
|
377
|
+
})
|
|
378
|
+
.strict()
|
|
379
|
+
.superRefine((value, ctx) => {
|
|
380
|
+
const verificationIds = value.verificationTargets.map((target) => target.id);
|
|
381
|
+
if (new Set(verificationIds).size !== verificationIds.length)
|
|
382
|
+
ctx.addIssue({
|
|
383
|
+
code: "custom",
|
|
384
|
+
message: "duplicate verification target id",
|
|
385
|
+
path: ["verificationTargets"],
|
|
386
|
+
});
|
|
387
|
+
const known = new Set(value.sourceBinding.requirementIds);
|
|
388
|
+
const stateNames = new Set(value.uiStates.map((state) => state.name));
|
|
389
|
+
for (const target of value.verificationTargets) {
|
|
390
|
+
// Verification evidence may point to read-only project files such as
|
|
391
|
+
// tsconfig.json or a test entrypoint; implementation targets remain
|
|
392
|
+
// governed by targets.files and the writer writeSet.
|
|
393
|
+
for (const requirementId of target.requirementIds) {
|
|
394
|
+
if (!known.has(requirementId)) {
|
|
395
|
+
ctx.addIssue({
|
|
396
|
+
code: "custom",
|
|
397
|
+
message: `verification target references unknown requirement ${requirementId}`,
|
|
398
|
+
path: ["verificationTargets"],
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const stateName of target.uiStates) {
|
|
403
|
+
if (!stateNames.has(stateName)) {
|
|
404
|
+
ctx.addIssue({
|
|
405
|
+
code: "custom",
|
|
406
|
+
message: `verification target references unknown UI state ${stateName}`,
|
|
407
|
+
path: ["verificationTargets"],
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const requirement of value.requirements) {
|
|
413
|
+
if (!known.has(requirement.id))
|
|
414
|
+
ctx.addIssue({
|
|
415
|
+
code: "custom",
|
|
416
|
+
message: "unknown requirement id",
|
|
417
|
+
path: ["requirements"],
|
|
418
|
+
});
|
|
419
|
+
if ((!requirement.implementationTargets.length ||
|
|
420
|
+
!requirement.verificationTargetIds.length) &&
|
|
421
|
+
!requirement.evidenceGap)
|
|
422
|
+
ctx.addIssue({
|
|
423
|
+
code: "custom",
|
|
424
|
+
message: "requirement needs targets or evidenceGap",
|
|
425
|
+
path: ["requirements"],
|
|
426
|
+
});
|
|
427
|
+
for (const targetId of requirement.verificationTargetIds)
|
|
428
|
+
if (!verificationIds.includes(targetId))
|
|
429
|
+
ctx.addIssue({
|
|
430
|
+
code: "custom",
|
|
431
|
+
message: `unknown verification target ${targetId}`,
|
|
432
|
+
path: ["requirements"],
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
for (const state of value.uiStates) {
|
|
436
|
+
if (state.applicable &&
|
|
437
|
+
(!state.expectedBehavior ||
|
|
438
|
+
!state.implementationTargets?.length ||
|
|
439
|
+
!state.verificationTargetIds?.length))
|
|
440
|
+
ctx.addIssue({
|
|
441
|
+
code: "custom",
|
|
442
|
+
message: "applicable UI state requires behavior, implementation, and verification",
|
|
443
|
+
path: ["uiStates"],
|
|
444
|
+
});
|
|
445
|
+
if (!state.applicable && !state.notApplicableReason)
|
|
446
|
+
ctx.addIssue({
|
|
447
|
+
code: "custom",
|
|
448
|
+
message: "non-applicable UI state requires reason",
|
|
449
|
+
path: ["uiStates"],
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
for (const [index, choice] of (value.uiComponentChoices ?? []).entries()) {
|
|
453
|
+
if (choice.decision === "specified") {
|
|
454
|
+
if (!choice.specReference || !choice.specReference.path) {
|
|
455
|
+
ctx.addIssue({
|
|
456
|
+
code: "custom",
|
|
457
|
+
message: "specified component choice requires a non-empty specReference.path",
|
|
458
|
+
path: ["uiComponentChoices", index, "specReference"],
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else if (choice.decision === "new") {
|
|
463
|
+
if (choice.specReference) {
|
|
464
|
+
ctx.addIssue({
|
|
465
|
+
code: "custom",
|
|
466
|
+
message: "new component choice must not carry a specReference",
|
|
467
|
+
path: ["uiComponentChoices", index, "specReference"],
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (value.mockApi.strategy !== "not-needed") {
|
|
473
|
+
if (value.mockApi.endpoints.length === 0) {
|
|
474
|
+
ctx.addIssue({
|
|
475
|
+
code: "custom",
|
|
476
|
+
message: "Mock strategy requires at least one endpoint",
|
|
477
|
+
path: ["mockApi", "endpoints"],
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
value.mockApi.endpoints.forEach((endpoint, index) => {
|
|
481
|
+
if (!endpoint.fixture) {
|
|
482
|
+
ctx.addIssue({
|
|
483
|
+
code: "custom",
|
|
484
|
+
message: "Mock endpoint requires a fixture path",
|
|
485
|
+
path: ["mockApi", "endpoints", index, "fixture"],
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (!endpoint.consumer) {
|
|
489
|
+
ctx.addIssue({
|
|
490
|
+
code: "custom",
|
|
491
|
+
message: "Mock endpoint requires a consumer path",
|
|
492
|
+
path: ["mockApi", "endpoints", index, "consumer"],
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
for (const [field, file] of [["fixture", endpoint.fixture], ["consumer", endpoint.consumer]]) {
|
|
496
|
+
if (file && !value.targets.files.some((pattern) => pathMatchesPattern(file, pattern))) {
|
|
497
|
+
ctx.addIssue({
|
|
498
|
+
code: "custom",
|
|
499
|
+
message: `Mock ${field} is outside contract targets: ${file}`,
|
|
500
|
+
path: ["mockApi", "endpoints", index, field],
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}));
|
|
507
|
+
function isPlainObject(value) {
|
|
508
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Apply an RFC 7386 merge-patch on a target contract without mutating it.
|
|
512
|
+
* `null` removes the key; plain objects merge recursively when the target value
|
|
513
|
+
* is also a plain object; arrays and scalars replace the whole value. The
|
|
514
|
+
* result is re-validated by analyzeFrontendImplementationContract before it is
|
|
515
|
+
* ever materialized, so a patch that removes a required field still fails
|
|
516
|
+
* closed at the gate.
|
|
517
|
+
*/
|
|
518
|
+
export function applyFrontendContractMergePatch(target, patch) {
|
|
519
|
+
if (!isPlainObject(patch)) {
|
|
520
|
+
throw new Error("frontend contract patch must be a JSON object");
|
|
521
|
+
}
|
|
522
|
+
const merge = (base, delta) => {
|
|
523
|
+
if (delta === null)
|
|
524
|
+
return undefined;
|
|
525
|
+
if (!isPlainObject(delta))
|
|
526
|
+
return delta;
|
|
527
|
+
const result = isPlainObject(base)
|
|
528
|
+
? { ...base }
|
|
529
|
+
: {};
|
|
530
|
+
for (const [key, value] of Object.entries(delta)) {
|
|
531
|
+
if (value === null) {
|
|
532
|
+
delete result[key];
|
|
533
|
+
}
|
|
534
|
+
else if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
535
|
+
result[key] = merge(result[key], value);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
result[key] = value;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return result;
|
|
542
|
+
};
|
|
543
|
+
return merge(target, patch);
|
|
544
|
+
}
|
|
545
|
+
export function applyFrontendImplementationContractPatch(target, patch) {
|
|
546
|
+
return applyFrontendContractMergePatch(target, patch);
|
|
547
|
+
}
|
|
548
|
+
export async function assertFrontendSourceBindingFresh(input) {
|
|
549
|
+
for (const source of input.binding.sources) {
|
|
550
|
+
const absolute = resolveDagTaskSourcePath({
|
|
551
|
+
workspaceRoot: input.workspaceRoot,
|
|
552
|
+
taskId: input.binding.taskId,
|
|
553
|
+
sourcePath: source.path,
|
|
554
|
+
});
|
|
555
|
+
let content;
|
|
556
|
+
try {
|
|
557
|
+
content = await readFile(absolute);
|
|
558
|
+
const info = await stat(absolute);
|
|
559
|
+
if (!info.isFile())
|
|
560
|
+
throw new Error("not a regular file");
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
throw new Error(`frontend source binding file unavailable: ${source.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
564
|
+
}
|
|
565
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
566
|
+
if (actual !== source.sha256) {
|
|
567
|
+
throw new Error(`frontend source binding is stale: ${source.path}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const SECRET_KEY = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential|authorization)/i;
|
|
572
|
+
const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b)/;
|
|
573
|
+
function secretIssues(value, at = "$", issues = []) {
|
|
574
|
+
if (typeof value === "string" && SECRET_VALUE.test(value))
|
|
575
|
+
issues.push(`${at}: secret-like value is forbidden`);
|
|
576
|
+
if (Array.isArray(value))
|
|
577
|
+
value.forEach((child, index) => secretIssues(child, `${at}[${index}]`, issues));
|
|
578
|
+
else if (value && typeof value === "object")
|
|
579
|
+
for (const [key, child] of Object.entries(value)) {
|
|
580
|
+
if (SECRET_KEY.test(key))
|
|
581
|
+
issues.push(`${at}.${key}: secret-shaped field name is forbidden`);
|
|
582
|
+
secretIssues(child, `${at}.${key}`, issues);
|
|
583
|
+
}
|
|
584
|
+
return issues;
|
|
585
|
+
}
|
|
586
|
+
function extractFrontendImplementationJsonWithAudit(text) {
|
|
587
|
+
const trimmed = text.trim();
|
|
588
|
+
const actions = [];
|
|
589
|
+
const recordAction = (action) => {
|
|
590
|
+
if (!actions.includes(action))
|
|
591
|
+
actions.push(action);
|
|
592
|
+
};
|
|
593
|
+
const parse = (source) => {
|
|
594
|
+
try {
|
|
595
|
+
return JSON.parse(source);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
// Apply only deterministic, structure-preserving repairs before the
|
|
599
|
+
// quote repair below. These are common JSONC/model formatting defects,
|
|
600
|
+
// not a general parser relaxation.
|
|
601
|
+
const withoutComments = source
|
|
602
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
603
|
+
.replace(/^\s*\/\/.*$/gm, "");
|
|
604
|
+
const withoutTrailingCommas = withoutComments.replace(/,\s*([}\]])/g, "$1");
|
|
605
|
+
if (withoutComments !== source)
|
|
606
|
+
recordAction("strip-comments");
|
|
607
|
+
if (withoutTrailingCommas !== withoutComments)
|
|
608
|
+
recordAction("remove-trailing-commas");
|
|
609
|
+
try {
|
|
610
|
+
return JSON.parse(withoutTrailingCommas);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
// Continue with the narrower embedded-quote repair.
|
|
614
|
+
}
|
|
615
|
+
// Models sometimes put ordinary ASCII quotes inside a JSON string
|
|
616
|
+
// (for example: `reason: "支持..."`). Repair only quotes that are
|
|
617
|
+
// clearly not structural: a closing quote is followed by JSON
|
|
618
|
+
// punctuation, while an embedded quote is followed by content.
|
|
619
|
+
let repaired = "";
|
|
620
|
+
let inString = false;
|
|
621
|
+
let escaped = false;
|
|
622
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
623
|
+
const character = source[index];
|
|
624
|
+
if (character !== '"') {
|
|
625
|
+
repaired += character;
|
|
626
|
+
if (inString && character === "\\" && !escaped)
|
|
627
|
+
escaped = true;
|
|
628
|
+
else
|
|
629
|
+
escaped = false;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (escaped) {
|
|
633
|
+
repaired += character;
|
|
634
|
+
escaped = false;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (!inString) {
|
|
638
|
+
inString = true;
|
|
639
|
+
repaired += character;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const next = source.slice(index + 1).trimStart()[0];
|
|
643
|
+
if ([",", "}", "]", ":"].includes(next ?? "") || source.slice(index + 1).trim() === "") {
|
|
644
|
+
inString = false;
|
|
645
|
+
repaired += character;
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
repaired += "\\\"";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
return JSON.parse(repaired);
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const isContract = (candidate) => {
|
|
660
|
+
const record = asRecord(candidate);
|
|
661
|
+
return (record?.schemaVersion === 1 &&
|
|
662
|
+
asRecord(record.targets) !== null &&
|
|
663
|
+
Array.isArray(record.requirements) &&
|
|
664
|
+
Array.isArray(record.verificationTargets));
|
|
665
|
+
};
|
|
666
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}"))
|
|
667
|
+
return { value: parse(trimmed), actions };
|
|
668
|
+
const balancedObjects = [];
|
|
669
|
+
for (let start = 0; start < trimmed.length; start += 1) {
|
|
670
|
+
if (trimmed[start] !== "{")
|
|
671
|
+
continue;
|
|
672
|
+
let depth = 0;
|
|
673
|
+
let inString = false;
|
|
674
|
+
let escaped = false;
|
|
675
|
+
for (let index = start; index < trimmed.length; index += 1) {
|
|
676
|
+
const character = trimmed[index];
|
|
677
|
+
if (inString) {
|
|
678
|
+
if (escaped)
|
|
679
|
+
escaped = false;
|
|
680
|
+
else if (character === "\\")
|
|
681
|
+
escaped = true;
|
|
682
|
+
else if (character === '"')
|
|
683
|
+
inString = false;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (character === '"')
|
|
687
|
+
inString = true;
|
|
688
|
+
else if (character === "{")
|
|
689
|
+
depth += 1;
|
|
690
|
+
else if (character === "}" && --depth === 0) {
|
|
691
|
+
try {
|
|
692
|
+
const candidate = parse(trimmed.slice(start, index + 1));
|
|
693
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate))
|
|
694
|
+
balancedObjects.push(candidate);
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// Continue scanning for a later complete JSON object.
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const balancedContracts = balancedObjects.filter(isContract);
|
|
704
|
+
if (balancedContracts.length === 1)
|
|
705
|
+
return { value: balancedContracts[0], actions };
|
|
706
|
+
if (balancedObjects.length === 1)
|
|
707
|
+
return { value: balancedObjects[0], actions };
|
|
708
|
+
const blocks = [...trimmed.matchAll(/```json\s*\n([\s\S]*?)\n```/gi)];
|
|
709
|
+
if (blocks.length === 0)
|
|
710
|
+
throw new Error("output must contain exactly one fenced json object");
|
|
711
|
+
const candidates = [];
|
|
712
|
+
for (const block of blocks) {
|
|
713
|
+
try {
|
|
714
|
+
const candidate = parse(block[1]);
|
|
715
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate))
|
|
716
|
+
candidates.push(candidate);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
// Ignore incomplete model scratch blocks. A later complete contract
|
|
720
|
+
// block may still be deterministically recoverable.
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const contractCandidates = candidates.filter(isContract);
|
|
724
|
+
if (contractCandidates.length === 1)
|
|
725
|
+
return { value: contractCandidates[0], actions };
|
|
726
|
+
if (candidates.length === 1)
|
|
727
|
+
return { value: candidates[0], actions };
|
|
728
|
+
if (candidates.length === 0)
|
|
729
|
+
throw new Error("output must contain exactly one valid fenced json object (found 0)");
|
|
730
|
+
throw new Error(`output must contain exactly one valid frontend contract json object (found ${contractCandidates.length || candidates.length})`);
|
|
731
|
+
}
|
|
732
|
+
export function extractFrontendImplementationJson(text) {
|
|
733
|
+
return extractFrontendImplementationJsonWithAudit(text).value;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Build the authoritative frontend-implementation-contract sourceBinding from
|
|
737
|
+
* the DAG-owned binding. Model JSON must not invent taskId/path/sha256; the
|
|
738
|
+
* gate overwrites whatever the model emitted so identity cannot drift.
|
|
739
|
+
*/
|
|
740
|
+
export function canonicalFrontendContractSourceBinding(binding) {
|
|
741
|
+
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
742
|
+
if (!requirement)
|
|
743
|
+
throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
|
|
744
|
+
return {
|
|
745
|
+
taskId: binding.taskId,
|
|
746
|
+
requirementPath: requirement.path,
|
|
747
|
+
requirementSha256: requirement.sha256,
|
|
748
|
+
referencePaths: binding.sources
|
|
749
|
+
.filter((source) => source.kind === "reference")
|
|
750
|
+
.map((source) => source.path)
|
|
751
|
+
.sort(),
|
|
752
|
+
requirementIds: binding.requirementIds.map(canonicalizeRequirementId),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function asRecord(value) {
|
|
756
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
757
|
+
? value
|
|
758
|
+
: null;
|
|
759
|
+
}
|
|
760
|
+
function asString(value) {
|
|
761
|
+
return typeof value === "string" ? value.trim() : "";
|
|
762
|
+
}
|
|
763
|
+
function asStringArray(value) {
|
|
764
|
+
if (!Array.isArray(value))
|
|
765
|
+
return [];
|
|
766
|
+
return value
|
|
767
|
+
.map((item) => asString(item))
|
|
768
|
+
.filter((item) => item.length > 0);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Pass structured component choices through the free-form compatibility
|
|
772
|
+
* branch. The strict-shape branch already spreads unknown fields, so only this
|
|
773
|
+
* explicit constructor needs to re-emit `uiComponentChoices`; a missing/empty
|
|
774
|
+
* array normalizes to undefined (absent) so the optional schema field stays
|
|
775
|
+
* compatible with legacy free-form contracts.
|
|
776
|
+
*/
|
|
777
|
+
function normalizeUiComponentChoices(value) {
|
|
778
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
779
|
+
return undefined;
|
|
780
|
+
return value;
|
|
781
|
+
}
|
|
782
|
+
function normalizeFrontendRoute(value) {
|
|
783
|
+
const route = asString(value).replaceAll("\\", "/").replace(/^\/+/, "");
|
|
784
|
+
if (!route || route === "." || route.split("/").includes(".."))
|
|
785
|
+
return null;
|
|
786
|
+
return `/${route}`;
|
|
787
|
+
}
|
|
788
|
+
function normalizeFrontendContractOptionalFields(value) {
|
|
789
|
+
const record = asRecord(value);
|
|
790
|
+
if (!record)
|
|
791
|
+
return value;
|
|
792
|
+
const targets = asRecord(record.targets);
|
|
793
|
+
const mockApi = asRecord(record.mockApi);
|
|
794
|
+
return {
|
|
795
|
+
...record,
|
|
796
|
+
...(targets
|
|
797
|
+
? {
|
|
798
|
+
targets: {
|
|
799
|
+
...targets,
|
|
800
|
+
routes: Array.isArray(targets.routes)
|
|
801
|
+
? targets.routes.map(normalizeFrontendRoute).filter((route) => Boolean(route))
|
|
802
|
+
: targets.routes,
|
|
803
|
+
},
|
|
804
|
+
}
|
|
805
|
+
: {}),
|
|
806
|
+
...(mockApi
|
|
807
|
+
? {
|
|
808
|
+
mockApi: {
|
|
809
|
+
...mockApi,
|
|
810
|
+
endpoints: Array.isArray(mockApi.endpoints)
|
|
811
|
+
? mockApi.endpoints.map((item) => {
|
|
812
|
+
const endpoint = asRecord(item);
|
|
813
|
+
if (!endpoint)
|
|
814
|
+
return item;
|
|
815
|
+
return {
|
|
816
|
+
...endpoint,
|
|
817
|
+
fixture: asString(endpoint.fixture) || undefined,
|
|
818
|
+
consumer: asString(endpoint.consumer) || undefined,
|
|
819
|
+
};
|
|
820
|
+
})
|
|
821
|
+
: mockApi.endpoints,
|
|
822
|
+
},
|
|
823
|
+
}
|
|
824
|
+
: {}),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function isRequirementId(value) {
|
|
828
|
+
return REQUIREMENT_ID_PATTERN.test(value);
|
|
829
|
+
}
|
|
830
|
+
/** Normalize the compact IDs models commonly emit (for example AC1) before
|
|
831
|
+
* strict schema validation. This keeps governance strict while making the
|
|
832
|
+
* boundary tolerant of presentation-only formatting differences. */
|
|
833
|
+
function canonicalizeRequirementId(value) {
|
|
834
|
+
const trimmed = value.trim().toUpperCase();
|
|
835
|
+
if (REQUIREMENT_ID_PATTERN.test(trimmed))
|
|
836
|
+
return trimmed;
|
|
837
|
+
const compact = /^(REQ|BR|AC)(\d+)$/.exec(trimmed);
|
|
838
|
+
if (compact)
|
|
839
|
+
return `${compact[1]}-${compact[2]}`;
|
|
840
|
+
return value;
|
|
841
|
+
}
|
|
842
|
+
function canonicalizeRequirementIdsInPayload(value) {
|
|
843
|
+
if (Array.isArray(value))
|
|
844
|
+
return value.map(canonicalizeRequirementIdsInPayload);
|
|
845
|
+
if (!value || typeof value !== "object")
|
|
846
|
+
return value;
|
|
847
|
+
const record = value;
|
|
848
|
+
const out = {};
|
|
849
|
+
for (const [key, child] of Object.entries(record)) {
|
|
850
|
+
if (key === "id" || key === "requirementId") {
|
|
851
|
+
out[key] = typeof child === "string" ? canonicalizeRequirementId(child) : child;
|
|
852
|
+
}
|
|
853
|
+
else if (key === "requirementIds" && Array.isArray(child)) {
|
|
854
|
+
out[key] = child.map((item) => typeof item === "string" ? canonicalizeRequirementId(item) : item);
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
out[key] = canonicalizeRequirementIdsInPayload(child);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return out;
|
|
861
|
+
}
|
|
862
|
+
function canonicalizeVerificationTargetAliases(value) {
|
|
863
|
+
const record = asRecord(value);
|
|
864
|
+
if (!record || !Array.isArray(record.verificationTargets))
|
|
865
|
+
return value;
|
|
866
|
+
const targets = record.verificationTargets
|
|
867
|
+
.map((item) => asRecord(item))
|
|
868
|
+
.filter((item) => Boolean(item));
|
|
869
|
+
const aliases = new Map();
|
|
870
|
+
for (const target of targets) {
|
|
871
|
+
const id = asString(target.id);
|
|
872
|
+
const label = asString(target.commandLabel).toLowerCase();
|
|
873
|
+
if (!id)
|
|
874
|
+
continue;
|
|
875
|
+
aliases.set(id.toLowerCase(), id);
|
|
876
|
+
if (label.includes("typecheck"))
|
|
877
|
+
aliases.set("vt-typecheck", id);
|
|
878
|
+
if (label.includes("unified surface"))
|
|
879
|
+
aliases.set("vt-unified-surface", id);
|
|
880
|
+
}
|
|
881
|
+
const requirements = Array.isArray(record.requirements)
|
|
882
|
+
? record.requirements.map((item) => {
|
|
883
|
+
const requirement = asRecord(item);
|
|
884
|
+
if (!requirement || !Array.isArray(requirement.verificationTargetIds))
|
|
885
|
+
return item;
|
|
886
|
+
return {
|
|
887
|
+
...requirement,
|
|
888
|
+
verificationTargetIds: requirement.verificationTargetIds.map((id) => typeof id === "string" ? aliases.get(id.toLowerCase()) ?? id : id),
|
|
889
|
+
};
|
|
890
|
+
})
|
|
891
|
+
: record.requirements;
|
|
892
|
+
return { ...record, requirements };
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Model-written evidence gaps are hypotheses, not authoritative coverage
|
|
896
|
+
* facts. If the plan contains a real verification target that explicitly
|
|
897
|
+
* names a requirement, the executor can prove that the requirement has a
|
|
898
|
+
* verification path even when the model forgot to wire the target into the
|
|
899
|
+
* requirement entry or conservatively marked its gap as blocking.
|
|
900
|
+
*
|
|
901
|
+
* Keep gaps blocking when that proof cannot be derived. This is deliberately
|
|
902
|
+
* narrow: it uses only sourceBinding requirement IDs and structurally present
|
|
903
|
+
* verification targets, and never invents a command, file, or target.
|
|
904
|
+
*/
|
|
905
|
+
function deriveFrontendVerificationCoverage(value, canonicalBinding) {
|
|
906
|
+
const record = asRecord(value);
|
|
907
|
+
if (!record)
|
|
908
|
+
return value;
|
|
909
|
+
const rawTargets = Array.isArray(record.verificationTargets)
|
|
910
|
+
? record.verificationTargets
|
|
911
|
+
: [];
|
|
912
|
+
const targetIds = new Set(rawTargets
|
|
913
|
+
.map((item) => asRecord(item))
|
|
914
|
+
.filter((item) => Boolean(item))
|
|
915
|
+
.map((item) => asString(item.id))
|
|
916
|
+
.filter(Boolean));
|
|
917
|
+
const targetIdsByRequirement = new Map();
|
|
918
|
+
for (const item of rawTargets) {
|
|
919
|
+
const target = asRecord(item);
|
|
920
|
+
if (!target)
|
|
921
|
+
continue;
|
|
922
|
+
const targetId = asString(target.id);
|
|
923
|
+
const commandLabel = asString(target.commandLabel) || asString(target.command);
|
|
924
|
+
const file = asString(target.file);
|
|
925
|
+
// A target is usable evidence only when it has an identity, command and
|
|
926
|
+
// file. The strict schema will validate the final shape afterwards.
|
|
927
|
+
if (!targetId || !commandLabel || !file)
|
|
928
|
+
continue;
|
|
929
|
+
const targetRequirementIds = asStringArray(target.requirementIds);
|
|
930
|
+
const inferredRequirementIds = targetRequirementIds.length > 0
|
|
931
|
+
? targetRequirementIds
|
|
932
|
+
: rawTargets.length === 1
|
|
933
|
+
? canonicalBinding.requirementIds
|
|
934
|
+
: [];
|
|
935
|
+
for (const requirementId of inferredRequirementIds) {
|
|
936
|
+
const canonicalId = canonicalizeRequirementId(requirementId);
|
|
937
|
+
if (!canonicalBinding.requirementIds.includes(canonicalId))
|
|
938
|
+
continue;
|
|
939
|
+
const ids = targetIdsByRequirement.get(canonicalId) ?? [];
|
|
940
|
+
if (!ids.includes(targetId))
|
|
941
|
+
ids.push(targetId);
|
|
942
|
+
targetIdsByRequirement.set(canonicalId, ids);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const provenRequirementIds = new Set(targetIdsByRequirement.keys());
|
|
946
|
+
const requirements = Array.isArray(record.requirements)
|
|
947
|
+
? record.requirements.map((item) => {
|
|
948
|
+
const requirement = asRecord(item);
|
|
949
|
+
if (!requirement)
|
|
950
|
+
return item;
|
|
951
|
+
const requirementId = canonicalizeRequirementId(asString(requirement.id));
|
|
952
|
+
const inferredTargetIds = targetIdsByRequirement.get(requirementId) ?? [];
|
|
953
|
+
const existingTargetIds = asStringArray(requirement.verificationTargetIds)
|
|
954
|
+
.filter((id) => targetIds.has(id));
|
|
955
|
+
const verificationTargetIds = [...new Set([
|
|
956
|
+
...existingTargetIds,
|
|
957
|
+
...inferredTargetIds,
|
|
958
|
+
])];
|
|
959
|
+
const evidenceGap = asRecord(requirement.evidenceGap);
|
|
960
|
+
const hasProof = provenRequirementIds.has(requirementId);
|
|
961
|
+
return {
|
|
962
|
+
...requirement,
|
|
963
|
+
...(verificationTargetIds.length > 0 ? { verificationTargetIds } : {}),
|
|
964
|
+
...(hasProof
|
|
965
|
+
? (evidenceGap ? { evidenceGap: { ...evidenceGap, blocking: false } } : {})
|
|
966
|
+
: {
|
|
967
|
+
evidenceGap: {
|
|
968
|
+
requirementId,
|
|
969
|
+
description: `No executable verification target can be derived for ${requirementId}`,
|
|
970
|
+
blocking: true,
|
|
971
|
+
},
|
|
972
|
+
}),
|
|
973
|
+
};
|
|
974
|
+
})
|
|
975
|
+
: record.requirements;
|
|
976
|
+
const modelEvidenceGaps = Array.isArray(record.evidenceGaps)
|
|
977
|
+
? record.evidenceGaps.map((item) => {
|
|
978
|
+
const gap = asRecord(item);
|
|
979
|
+
if (!gap)
|
|
980
|
+
return item;
|
|
981
|
+
const requirementId = canonicalizeRequirementId(asString(gap.requirementId));
|
|
982
|
+
// Model gaps are advisory. Blocking status is reconstructed below
|
|
983
|
+
// from the source binding and executable verification targets.
|
|
984
|
+
// Drop a null/empty optional requirementId so the strict schema
|
|
985
|
+
// accepts model emissions (null is not a valid string).
|
|
986
|
+
const { requirementId: _rawRequirementId, ...rest } = gap;
|
|
987
|
+
return {
|
|
988
|
+
...rest,
|
|
989
|
+
...(requirementId ? { requirementId } : {}),
|
|
990
|
+
blocking: false,
|
|
991
|
+
};
|
|
992
|
+
})
|
|
993
|
+
: [];
|
|
994
|
+
const derivedBlockingGaps = canonicalBinding.requirementIds
|
|
995
|
+
.filter((requirementId) => !provenRequirementIds.has(requirementId))
|
|
996
|
+
.map((requirementId) => ({
|
|
997
|
+
requirementId,
|
|
998
|
+
description: `No executable verification target can be derived for ${requirementId}`,
|
|
999
|
+
blocking: true,
|
|
1000
|
+
}));
|
|
1001
|
+
return {
|
|
1002
|
+
...record,
|
|
1003
|
+
requirements,
|
|
1004
|
+
evidenceGaps: [...modelEvidenceGaps, ...derivedBlockingGaps],
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function assertFrontendContractPathsSafe(value) {
|
|
1008
|
+
const record = asRecord(value);
|
|
1009
|
+
if (!record)
|
|
1010
|
+
return;
|
|
1011
|
+
const pathFields = ["files", "file", "implementationTargets", "fixture", "consumer"];
|
|
1012
|
+
for (const [key, child] of Object.entries(record)) {
|
|
1013
|
+
if (pathFields.includes(key) && Array.isArray(child)) {
|
|
1014
|
+
for (const item of child) {
|
|
1015
|
+
if (typeof item === "string" && (item.startsWith("/") || item.includes("\\") || item.split("/").includes("..")))
|
|
1016
|
+
throw new Error(`invalid-output: unsafe frontend contract path ${item}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
else if (pathFields.includes(key) && typeof child === "string" && (child.startsWith("/") || child.includes("\\") || child.split("/").includes(".."))) {
|
|
1020
|
+
throw new Error(`invalid-output: unsafe frontend contract path ${child}`);
|
|
1021
|
+
}
|
|
1022
|
+
if (Array.isArray(child))
|
|
1023
|
+
child.forEach(assertFrontendContractPathsSafe);
|
|
1024
|
+
else if (child && typeof child === "object")
|
|
1025
|
+
assertFrontendContractPathsSafe(child);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function deriveContractRequirementIds(value) {
|
|
1029
|
+
const record = asRecord(value);
|
|
1030
|
+
if (!record)
|
|
1031
|
+
return [];
|
|
1032
|
+
const ids = [];
|
|
1033
|
+
const push = (candidate) => {
|
|
1034
|
+
const value = asString(candidate);
|
|
1035
|
+
const canonical = canonicalizeRequirementId(value);
|
|
1036
|
+
if (!canonical || !isRequirementId(canonical) || ids.includes(canonical))
|
|
1037
|
+
return;
|
|
1038
|
+
ids.push(canonical);
|
|
1039
|
+
};
|
|
1040
|
+
if (Array.isArray(record.requirements)) {
|
|
1041
|
+
for (const item of record.requirements)
|
|
1042
|
+
push(asRecord(item)?.id);
|
|
1043
|
+
}
|
|
1044
|
+
if (Array.isArray(record.requirementCoverage)) {
|
|
1045
|
+
for (const item of record.requirementCoverage)
|
|
1046
|
+
push(asRecord(item)?.id);
|
|
1047
|
+
}
|
|
1048
|
+
const rawVerification = Array.isArray(record.verificationTargets)
|
|
1049
|
+
? record.verificationTargets
|
|
1050
|
+
: asRecord(record.verificationTargets)
|
|
1051
|
+
? Object.values(asRecord(record.verificationTargets))
|
|
1052
|
+
: [];
|
|
1053
|
+
for (const item of rawVerification) {
|
|
1054
|
+
for (const requirementId of asStringArray(asRecord(item)?.requirementIds)) {
|
|
1055
|
+
push(requirementId);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
for (const item of Array.isArray(record.evidenceGaps) ? record.evidenceGaps : []) {
|
|
1059
|
+
push(asRecord(item)?.requirementId);
|
|
1060
|
+
}
|
|
1061
|
+
return ids;
|
|
1062
|
+
}
|
|
1063
|
+
function withDerivedRequirementIdsWhenUnscoped(binding, parsed) {
|
|
1064
|
+
if (binding.requirementIds.length > 0)
|
|
1065
|
+
return binding;
|
|
1066
|
+
const derivedIds = deriveContractRequirementIds(parsed);
|
|
1067
|
+
return derivedIds.length > 0
|
|
1068
|
+
? { ...binding, requirementIds: derivedIds }
|
|
1069
|
+
: binding;
|
|
1070
|
+
}
|
|
1071
|
+
function looksLikeStrictFrontendContract(value) {
|
|
1072
|
+
const record = asRecord(value);
|
|
1073
|
+
if (!record)
|
|
1074
|
+
return false;
|
|
1075
|
+
return (record.schemaVersion === 1 &&
|
|
1076
|
+
asRecord(record.targets) !== null &&
|
|
1077
|
+
Array.isArray(record.requirements) &&
|
|
1078
|
+
Array.isArray(record.uiStates) &&
|
|
1079
|
+
asRecord(record.mockApi) !== null &&
|
|
1080
|
+
asRecord(record.designEvidence) !== null);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Coerce common free-form plan JSON into frontend-implementation-contract-v1.
|
|
1084
|
+
* Near-schema payloads are left untouched so unknown-key fail-closed still holds.
|
|
1085
|
+
*/
|
|
1086
|
+
export function coerceFrontendImplementationContractInput(value, canonicalBinding) {
|
|
1087
|
+
const rawRecord = asRecord(value);
|
|
1088
|
+
const rawMockApi = asRecord(rawRecord?.mockApi);
|
|
1089
|
+
if (rawMockApi &&
|
|
1090
|
+
typeof rawMockApi.strategy === "string" &&
|
|
1091
|
+
!["native", "browser-intercept", "request-adapter", "not-needed"].includes(rawMockApi.strategy))
|
|
1092
|
+
return value;
|
|
1093
|
+
const verificationTargetIds = rawRecord && Array.isArray(rawRecord.verificationTargets)
|
|
1094
|
+
? new Set(rawRecord.verificationTargets
|
|
1095
|
+
.map((item) => asString(asRecord(item)?.id))
|
|
1096
|
+
.filter(Boolean))
|
|
1097
|
+
: undefined;
|
|
1098
|
+
const normalizedValue = rawRecord
|
|
1099
|
+
? {
|
|
1100
|
+
...rawRecord,
|
|
1101
|
+
requirements: verificationTargetIds && Array.isArray(rawRecord.requirements)
|
|
1102
|
+
? rawRecord.requirements.map((item) => {
|
|
1103
|
+
const requirement = asRecord(item);
|
|
1104
|
+
if (!requirement || !Array.isArray(requirement.verificationTargetIds))
|
|
1105
|
+
return item;
|
|
1106
|
+
const filtered = requirement.verificationTargetIds.filter((id) => typeof id === "string" && verificationTargetIds.has(id));
|
|
1107
|
+
return {
|
|
1108
|
+
...requirement,
|
|
1109
|
+
verificationTargetIds: filtered.length > 0 ? filtered : requirement.verificationTargetIds,
|
|
1110
|
+
};
|
|
1111
|
+
})
|
|
1112
|
+
: rawRecord.requirements,
|
|
1113
|
+
uiStates: Array.isArray(rawRecord.uiStates)
|
|
1114
|
+
? rawRecord.uiStates.map((item) => {
|
|
1115
|
+
const state = asRecord(item);
|
|
1116
|
+
if (!state)
|
|
1117
|
+
return item;
|
|
1118
|
+
if (state.applicable === true &&
|
|
1119
|
+
(state.notApplicableReason === null ||
|
|
1120
|
+
(typeof state.notApplicableReason === "string" &&
|
|
1121
|
+
state.notApplicableReason.trim() === ""))) {
|
|
1122
|
+
const { notApplicableReason: _emptyReason, ...withoutEmptyReason } = state;
|
|
1123
|
+
return withoutEmptyReason;
|
|
1124
|
+
}
|
|
1125
|
+
if (state.applicable === false &&
|
|
1126
|
+
(state.expectedBehavior === null ||
|
|
1127
|
+
(typeof state.expectedBehavior === "string" &&
|
|
1128
|
+
state.expectedBehavior.trim() === ""))) {
|
|
1129
|
+
const { expectedBehavior: _emptyBehavior, ...withoutEmptyBehavior } = state;
|
|
1130
|
+
return withoutEmptyBehavior;
|
|
1131
|
+
}
|
|
1132
|
+
return item;
|
|
1133
|
+
})
|
|
1134
|
+
: rawRecord.uiStates,
|
|
1135
|
+
}
|
|
1136
|
+
: value;
|
|
1137
|
+
const normalizedOptionalFields = normalizeFrontendContractOptionalFields(normalizedValue);
|
|
1138
|
+
// A payload can have the strict top-level shape while still containing
|
|
1139
|
+
// empty requirement coverage arrays. Do not trust shape alone: route such
|
|
1140
|
+
// payloads through the compatibility normalizer so targets and verification
|
|
1141
|
+
// references are deterministically filled from the contract context.
|
|
1142
|
+
if (looksLikeStrictFrontendContract(normalizedOptionalFields)) {
|
|
1143
|
+
const strictRecord = asRecord(normalizedOptionalFields);
|
|
1144
|
+
const strictMockApi = asRecord(strictRecord?.mockApi);
|
|
1145
|
+
if (strictMockApi &&
|
|
1146
|
+
typeof strictMockApi.strategy === "string" &&
|
|
1147
|
+
!["native", "browser-intercept", "request-adapter", "not-needed"].includes(strictMockApi.strategy))
|
|
1148
|
+
return normalizedValue;
|
|
1149
|
+
const targetFiles = asStringArray(asRecord(strictRecord?.targets)?.files);
|
|
1150
|
+
const verificationIds = Array.isArray(strictRecord?.verificationTargets)
|
|
1151
|
+
? strictRecord.verificationTargets.map((item) => asString(asRecord(item)?.id)).filter(Boolean)
|
|
1152
|
+
: [];
|
|
1153
|
+
if (Array.isArray(strictRecord?.requirements)) {
|
|
1154
|
+
const requirements = strictRecord.requirements.map((item) => {
|
|
1155
|
+
const requirement = asRecord(item);
|
|
1156
|
+
if (!requirement)
|
|
1157
|
+
return item;
|
|
1158
|
+
return {
|
|
1159
|
+
...requirement,
|
|
1160
|
+
implementationTargets: asStringArray(requirement.implementationTargets).length > 0
|
|
1161
|
+
? requirement.implementationTargets
|
|
1162
|
+
: targetFiles,
|
|
1163
|
+
verificationTargetIds: asStringArray(requirement.verificationTargetIds).length > 0
|
|
1164
|
+
? requirement.verificationTargetIds
|
|
1165
|
+
: verificationIds,
|
|
1166
|
+
};
|
|
1167
|
+
});
|
|
1168
|
+
const strictMockApi = asRecord(strictRecord.mockApi);
|
|
1169
|
+
const strictTargetFiles = asStringArray(asRecord(strictRecord.targets)?.files);
|
|
1170
|
+
const mockEndpoints = Array.isArray(strictMockApi?.endpoints)
|
|
1171
|
+
? strictMockApi.endpoints.map((item) => {
|
|
1172
|
+
const endpoint = asRecord(item);
|
|
1173
|
+
if (!endpoint)
|
|
1174
|
+
return item;
|
|
1175
|
+
return {
|
|
1176
|
+
...endpoint,
|
|
1177
|
+
consumer: asString(endpoint.consumer) ||
|
|
1178
|
+
strictTargetFiles[0],
|
|
1179
|
+
};
|
|
1180
|
+
})
|
|
1181
|
+
: strictMockApi?.endpoints;
|
|
1182
|
+
return {
|
|
1183
|
+
...strictRecord,
|
|
1184
|
+
requirements,
|
|
1185
|
+
...(strictMockApi ? { mockApi: { ...strictMockApi, endpoints: mockEndpoints } } : {}),
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const record = asRecord(normalizedOptionalFields);
|
|
1190
|
+
if (!record)
|
|
1191
|
+
return value;
|
|
1192
|
+
const implementation = asRecord(record.implementation);
|
|
1193
|
+
const mockApiIn = asRecord(record.mockApi) ?? {};
|
|
1194
|
+
const api = asRecord(record.api) ?? {};
|
|
1195
|
+
const component = asRecord(record.component);
|
|
1196
|
+
const targetFiles = [
|
|
1197
|
+
...asStringArray(asRecord(record.targets)?.files),
|
|
1198
|
+
...asStringArray(implementation?.targetFiles),
|
|
1199
|
+
...asStringArray(record.writeSet),
|
|
1200
|
+
...asStringArray(implementation?.writeSet),
|
|
1201
|
+
...asStringArray(asRecord(component?.paths)?.implementation ? [asRecord(component?.paths)?.implementation] : []),
|
|
1202
|
+
...asStringArray(asRecord(component?.paths)?.unitTests ? [asRecord(component?.paths)?.unitTests] : []),
|
|
1203
|
+
...asStringArray(asRecord(component?.paths)?.domHelper ? [asRecord(component?.paths)?.domHelper] : []),
|
|
1204
|
+
].filter((item, index, arr) => arr.indexOf(item) === index);
|
|
1205
|
+
if (targetFiles.length === 0) {
|
|
1206
|
+
throw new Error("frontend contract compatibility input must declare target files");
|
|
1207
|
+
}
|
|
1208
|
+
const riskRaw = asString(record.riskLevel) ||
|
|
1209
|
+
asString(record.risk) ||
|
|
1210
|
+
"standard";
|
|
1211
|
+
const riskLevel = riskRaw === "small" || riskRaw === "standard" || riskRaw === "high-risk"
|
|
1212
|
+
? riskRaw
|
|
1213
|
+
: riskRaw.includes("high")
|
|
1214
|
+
? "high-risk"
|
|
1215
|
+
: "standard";
|
|
1216
|
+
const verificationTargets = [];
|
|
1217
|
+
const rawVerification = Array.isArray(record.verificationTargets)
|
|
1218
|
+
? record.verificationTargets
|
|
1219
|
+
: asRecord(record.verificationTargets)
|
|
1220
|
+
? Object.entries(asRecord(record.verificationTargets)).map(([key, val]) => ({
|
|
1221
|
+
...(asRecord(val) ?? {}),
|
|
1222
|
+
id: key,
|
|
1223
|
+
}))
|
|
1224
|
+
: [];
|
|
1225
|
+
for (const [index, item] of rawVerification.entries()) {
|
|
1226
|
+
const vt = asRecord(item) ?? {};
|
|
1227
|
+
const id = asString(vt.id) || `VT-${String(index + 1).padStart(3, "0")}`;
|
|
1228
|
+
const typeRaw = asString(vt.type) || asString(vt.phase) || "unit";
|
|
1229
|
+
const type = ["static", "unit", "component", "integration", "mock"].includes(typeRaw)
|
|
1230
|
+
? typeRaw
|
|
1231
|
+
: typeRaw.includes("type")
|
|
1232
|
+
? "static"
|
|
1233
|
+
: "unit";
|
|
1234
|
+
const commandLabel = asString(vt.commandLabel) ||
|
|
1235
|
+
asString(vt.command) ||
|
|
1236
|
+
(type === "static" ? "npm run typecheck" : "npm run test:unit:fe");
|
|
1237
|
+
const file = asString(vt.file) ||
|
|
1238
|
+
(Array.isArray(vt.symbols) ? targetFiles.find((p) => p.includes("__tests__")) : "") ||
|
|
1239
|
+
targetFiles.find((p) => p.includes("__tests__")) ||
|
|
1240
|
+
targetFiles[0];
|
|
1241
|
+
const requirementIds = asStringArray(vt.requirementIds);
|
|
1242
|
+
const uiStateNames = asStringArray(vt.uiStates);
|
|
1243
|
+
verificationTargets.push({
|
|
1244
|
+
id,
|
|
1245
|
+
type,
|
|
1246
|
+
commandLabel,
|
|
1247
|
+
file,
|
|
1248
|
+
symbol: asString(vt.symbol) || undefined,
|
|
1249
|
+
requirementIds: requirementIds.length > 0
|
|
1250
|
+
? requirementIds
|
|
1251
|
+
: [...canonicalBinding.requirementIds],
|
|
1252
|
+
uiStates: uiStateNames.length > 0 ? uiStateNames : ["success", "error"],
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
if (verificationTargets.length === 0) {
|
|
1256
|
+
throw new Error("frontend contract compatibility input must declare verification targets");
|
|
1257
|
+
}
|
|
1258
|
+
const defaultVerificationIds = verificationTargets.map((item) => String(item.id));
|
|
1259
|
+
const requirements = [];
|
|
1260
|
+
const reqSource = Array.isArray(record.requirements)
|
|
1261
|
+
? record.requirements
|
|
1262
|
+
: Array.isArray(record.requirementCoverage)
|
|
1263
|
+
? record.requirementCoverage
|
|
1264
|
+
: [];
|
|
1265
|
+
for (const item of reqSource) {
|
|
1266
|
+
const req = asRecord(item) ?? {};
|
|
1267
|
+
const id = asString(req.id);
|
|
1268
|
+
if (!id)
|
|
1269
|
+
continue;
|
|
1270
|
+
const implementationTargetsRaw = asStringArray(req.implementationTargets);
|
|
1271
|
+
const implementationTargets = implementationTargetsRaw.length > 0
|
|
1272
|
+
? implementationTargetsRaw
|
|
1273
|
+
: targetFiles;
|
|
1274
|
+
const verificationTargetIdsRaw = asStringArray(req.verificationTargetIds);
|
|
1275
|
+
const verificationTargetIds = verificationTargetIdsRaw.length > 0
|
|
1276
|
+
? verificationTargetIdsRaw
|
|
1277
|
+
: defaultVerificationIds;
|
|
1278
|
+
const gapText = asString(asRecord(req.evidenceGap)?.description) ||
|
|
1279
|
+
asString(req.realIntegrationGap);
|
|
1280
|
+
const out = {
|
|
1281
|
+
id,
|
|
1282
|
+
expectedOutcome: asString(req.expectedOutcome),
|
|
1283
|
+
implementationTargets: implementationTargets.length > 0 ? implementationTargets : targetFiles,
|
|
1284
|
+
verificationTargetIds: verificationTargetIds.length > 0
|
|
1285
|
+
? verificationTargetIds
|
|
1286
|
+
: defaultVerificationIds,
|
|
1287
|
+
};
|
|
1288
|
+
// Do not mark free-form realIntegrationGap as blocking; defer to FE-TEST/FINAL-VERIFY.
|
|
1289
|
+
if (gapText) {
|
|
1290
|
+
out.evidenceGap = {
|
|
1291
|
+
description: gapText,
|
|
1292
|
+
blocking: false,
|
|
1293
|
+
requirementId: id,
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
requirements.push(out);
|
|
1297
|
+
}
|
|
1298
|
+
for (const id of canonicalBinding.requirementIds) {
|
|
1299
|
+
if (!requirements.some((item) => item.id === id)) {
|
|
1300
|
+
throw new Error(`frontend contract compatibility input does not cover ${id}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const uiStates = [];
|
|
1304
|
+
if (Array.isArray(record.uiStates)) {
|
|
1305
|
+
for (const item of record.uiStates) {
|
|
1306
|
+
const state = asRecord(item);
|
|
1307
|
+
if (!state)
|
|
1308
|
+
continue;
|
|
1309
|
+
uiStates.push(state);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
else if (asRecord(record.uiStates)) {
|
|
1313
|
+
for (const [name, raw] of Object.entries(asRecord(record.uiStates))) {
|
|
1314
|
+
const state = asRecord(raw);
|
|
1315
|
+
const text = asString(raw);
|
|
1316
|
+
const na = text.toUpperCase().includes("N/A") ||
|
|
1317
|
+
text.toLowerCase().includes("not applicable") ||
|
|
1318
|
+
text.toLowerCase() === "n/a-minimal";
|
|
1319
|
+
if (na || (state && asString(state.notApplicableReason))) {
|
|
1320
|
+
uiStates.push({
|
|
1321
|
+
name,
|
|
1322
|
+
applicable: false,
|
|
1323
|
+
notApplicableReason: asString(state?.notApplicableReason) || text || "not applicable",
|
|
1324
|
+
});
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
const expectedBehavior = (state &&
|
|
1328
|
+
[
|
|
1329
|
+
asString(state.expectedBehavior),
|
|
1330
|
+
asString(state.role) ? `role=${asString(state.role)}` : "",
|
|
1331
|
+
asString(state.ariaLive) ? `ariaLive=${asString(state.ariaLive)}` : "",
|
|
1332
|
+
asString(state.source) || asString(state.contentFrom),
|
|
1333
|
+
asString(state.textMatchHint),
|
|
1334
|
+
typeof state.silentFailure === "boolean"
|
|
1335
|
+
? `silentFailure=${state.silentFailure}`
|
|
1336
|
+
: "",
|
|
1337
|
+
]
|
|
1338
|
+
.filter(Boolean)
|
|
1339
|
+
.join("; ")) ||
|
|
1340
|
+
text ||
|
|
1341
|
+
`${name} state`;
|
|
1342
|
+
uiStates.push({
|
|
1343
|
+
name,
|
|
1344
|
+
applicable: true,
|
|
1345
|
+
expectedBehavior,
|
|
1346
|
+
implementationTargets: targetFiles,
|
|
1347
|
+
verificationTargetIds: defaultVerificationIds,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (uiStates.length === 0) {
|
|
1352
|
+
uiStates.push({
|
|
1353
|
+
name: "success",
|
|
1354
|
+
applicable: true,
|
|
1355
|
+
expectedBehavior: "visible success state",
|
|
1356
|
+
implementationTargets: targetFiles,
|
|
1357
|
+
verificationTargetIds: defaultVerificationIds,
|
|
1358
|
+
}, {
|
|
1359
|
+
name: "error",
|
|
1360
|
+
applicable: true,
|
|
1361
|
+
expectedBehavior: "visible error state",
|
|
1362
|
+
implementationTargets: targetFiles,
|
|
1363
|
+
verificationTargetIds: defaultVerificationIds,
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
const strategyRaw = asString(mockApiIn.strategy) || "not-needed";
|
|
1367
|
+
const allowedStrategies = [
|
|
1368
|
+
"native",
|
|
1369
|
+
"browser-intercept",
|
|
1370
|
+
"request-adapter",
|
|
1371
|
+
"not-needed",
|
|
1372
|
+
];
|
|
1373
|
+
if (!allowedStrategies.includes(strategyRaw)) {
|
|
1374
|
+
throw new Error(`frontend contract compatibility input has unsupported Mock strategy: ${strategyRaw}`);
|
|
1375
|
+
}
|
|
1376
|
+
const strategy = strategyRaw;
|
|
1377
|
+
const productionDefaultOff = mockApiIn.productionDefaultOff === true || mockApiIn.productionMockOff === true;
|
|
1378
|
+
if (!productionDefaultOff) {
|
|
1379
|
+
throw new Error("frontend contract compatibility input must prove production Mock is off");
|
|
1380
|
+
}
|
|
1381
|
+
const activation = asString(mockApiIn.activation) ||
|
|
1382
|
+
(strategy === "not-needed"
|
|
1383
|
+
? "production remains real fetch; unit tests may inject fetchImpl only"
|
|
1384
|
+
: "documented mock activation");
|
|
1385
|
+
const endpoints = [];
|
|
1386
|
+
for (const item of Array.isArray(mockApiIn.endpoints) ? mockApiIn.endpoints : []) {
|
|
1387
|
+
const ep = asRecord(item);
|
|
1388
|
+
if (!ep)
|
|
1389
|
+
continue;
|
|
1390
|
+
const method = asString(ep.method).toUpperCase() || "GET";
|
|
1391
|
+
const pathValue = asString(ep.path) || asString(api.path);
|
|
1392
|
+
if (!pathValue.startsWith("/"))
|
|
1393
|
+
continue;
|
|
1394
|
+
endpoints.push({
|
|
1395
|
+
method,
|
|
1396
|
+
path: pathValue,
|
|
1397
|
+
fixture: asString(ep.fixture) || undefined,
|
|
1398
|
+
consumer: asString(ep.consumer) || undefined,
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
if (endpoints.length === 0 && asString(api.path).startsWith("/")) {
|
|
1402
|
+
endpoints.push({
|
|
1403
|
+
method: asString(api.method).toUpperCase() || "GET",
|
|
1404
|
+
path: asString(api.path),
|
|
1405
|
+
consumer: targetFiles[0],
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
const evidenceGaps = [];
|
|
1409
|
+
for (const item of Array.isArray(record.evidenceGaps) ? record.evidenceGaps : []) {
|
|
1410
|
+
const gapItem = asRecord(item);
|
|
1411
|
+
if (!gapItem)
|
|
1412
|
+
continue;
|
|
1413
|
+
const description = asString(gapItem.description);
|
|
1414
|
+
if (!description)
|
|
1415
|
+
continue;
|
|
1416
|
+
evidenceGaps.push({
|
|
1417
|
+
description,
|
|
1418
|
+
blocking: gapItem.blocking === true,
|
|
1419
|
+
requirementId: asString(gapItem.requirementId) || undefined,
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
const realGap = asString(record.realIntegrationGap);
|
|
1423
|
+
if (realGap) {
|
|
1424
|
+
evidenceGaps.push({
|
|
1425
|
+
description: realGap,
|
|
1426
|
+
blocking: false,
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
for (const item of Array.isArray(record.residualRisks) ? record.residualRisks : []) {
|
|
1430
|
+
const text = asString(item) || asString(asRecord(item)?.description);
|
|
1431
|
+
if (!text)
|
|
1432
|
+
continue;
|
|
1433
|
+
evidenceGaps.push({ description: text, blocking: false });
|
|
1434
|
+
}
|
|
1435
|
+
const designPaths = [
|
|
1436
|
+
...canonicalBinding.referencePaths,
|
|
1437
|
+
...asStringArray(asRecord(record.designEvidence)?.paths),
|
|
1438
|
+
].filter((item, index, arr) => arr.indexOf(item) === index);
|
|
1439
|
+
return {
|
|
1440
|
+
schemaVersion: 1,
|
|
1441
|
+
sourceBinding: canonicalBinding,
|
|
1442
|
+
riskLevel,
|
|
1443
|
+
targets: {
|
|
1444
|
+
files: targetFiles,
|
|
1445
|
+
routes: asStringArray(asRecord(record.targets)?.routes),
|
|
1446
|
+
publicApiChanges: asStringArray(asRecord(record.targets)?.publicApiChanges),
|
|
1447
|
+
},
|
|
1448
|
+
requirements,
|
|
1449
|
+
uiStates,
|
|
1450
|
+
interactions: Array.isArray(record.interactions) ? record.interactions : [],
|
|
1451
|
+
mockApi: {
|
|
1452
|
+
strategy,
|
|
1453
|
+
productionDefaultOff: true,
|
|
1454
|
+
activation,
|
|
1455
|
+
endpoints,
|
|
1456
|
+
},
|
|
1457
|
+
designEvidence: {
|
|
1458
|
+
source: asString(asRecord(record.designEvidence)?.source) ||
|
|
1459
|
+
"repository-fallback+task-source",
|
|
1460
|
+
paths: designPaths.length > 0 ? designPaths : [canonicalBinding.requirementPath],
|
|
1461
|
+
conflicts: asStringArray(asRecord(record.designEvidence)?.conflicts),
|
|
1462
|
+
},
|
|
1463
|
+
verificationTargets,
|
|
1464
|
+
evidenceGaps,
|
|
1465
|
+
implementationSteps: asStringArray(record.implementationSteps),
|
|
1466
|
+
stylingStrategy: asString(record.stylingStrategy) || undefined,
|
|
1467
|
+
dependencyPolicy: asString(record.dependencyPolicy) || undefined,
|
|
1468
|
+
residualRisks: asStringArray(record.residualRisks),
|
|
1469
|
+
realIntegrationGap: asString(record.realIntegrationGap) || undefined,
|
|
1470
|
+
uiComponentChoices: normalizeUiComponentChoices(record.uiComponentChoices),
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Deterministic contract validation shared by the prewrite gate and the plan
|
|
1475
|
+
* node output self-check. Extracts, security-checks, coerces, and schema-parses
|
|
1476
|
+
* a model-emitted frontend contract, so schema/typo/null violations surface as
|
|
1477
|
+
* invalid-output at the producing node (where they can retry) instead of only
|
|
1478
|
+
* failing later at the prewrite gate.
|
|
1479
|
+
*/
|
|
1480
|
+
export async function analyzeFrontendImplementationContract(input) {
|
|
1481
|
+
if (!input.sourceBinding)
|
|
1482
|
+
throw new Error("frontend implementation contract gate requires DAG sourceBinding");
|
|
1483
|
+
let rawOutput;
|
|
1484
|
+
if (input.rawContractText !== undefined) {
|
|
1485
|
+
rawOutput = input.rawContractText;
|
|
1486
|
+
}
|
|
1487
|
+
else if (input.fromNodeId !== undefined) {
|
|
1488
|
+
const record = JSON.parse(await readFile(path.join(input.runDir, `${input.fromNodeId}.json`), "utf8"));
|
|
1489
|
+
rawOutput = record.assistantText ?? record.stdout ?? "";
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
throw new Error("analyzeFrontendImplementationContract requires fromNodeId or rawContractText");
|
|
1493
|
+
}
|
|
1494
|
+
const rawContractText = rawOutput.trim();
|
|
1495
|
+
const candidateRawSha256 = sha256Hex(rawOutput);
|
|
1496
|
+
const normalizationActions = [];
|
|
1497
|
+
const pushAction = (action) => {
|
|
1498
|
+
if (!normalizationActions.includes(action))
|
|
1499
|
+
normalizationActions.push(action);
|
|
1500
|
+
};
|
|
1501
|
+
function fail(kind, message, candidateJsonSha256) {
|
|
1502
|
+
throw new FrontendContractFailure({
|
|
1503
|
+
kind,
|
|
1504
|
+
message,
|
|
1505
|
+
candidateRawSha256,
|
|
1506
|
+
candidateJsonSha256,
|
|
1507
|
+
normalizationActions,
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
if (rawContractText.includes('"files":["/'))
|
|
1511
|
+
fail("blocked", "invalid-output: absolute frontend contract path is forbidden", null);
|
|
1512
|
+
if (/(?:"(?:files|file|implementationTargets|fixture|consumer)"\s*:\s*(?:\[\s*)?)"\//.test(rawContractText))
|
|
1513
|
+
fail("blocked", "invalid-output: absolute frontend contract path is forbidden", null);
|
|
1514
|
+
if (/(?:"strategy"\s*:\s*")(?!native\b|browser-intercept\b|request-adapter\b|not-needed\b)[^"]+"/.test(rawContractText))
|
|
1515
|
+
fail("blocked", "invalid-output: unsupported mock strategy", null);
|
|
1516
|
+
let parsed;
|
|
1517
|
+
try {
|
|
1518
|
+
const extracted = extractFrontendImplementationJsonWithAudit(rawContractText);
|
|
1519
|
+
parsed = extracted.value;
|
|
1520
|
+
for (const action of extracted.actions)
|
|
1521
|
+
pushAction(action);
|
|
1522
|
+
}
|
|
1523
|
+
catch (error) {
|
|
1524
|
+
fail("retryable-invalid", `invalid-output: ${error instanceof Error ? error.message : String(error)}`, null);
|
|
1525
|
+
}
|
|
1526
|
+
const candidateJsonSha256 = deterministicSha256(parsed);
|
|
1527
|
+
const secrets = secretIssues(parsed);
|
|
1528
|
+
if (secrets.length)
|
|
1529
|
+
fail("blocked", `invalid-output: ${secrets.join("; ")}`, candidateJsonSha256);
|
|
1530
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1531
|
+
fail("retryable-invalid", "invalid-output: frontend contract must be a JSON object", candidateJsonSha256);
|
|
1532
|
+
const parsedTargetObject = parsed.targets;
|
|
1533
|
+
const parsedFiles = parsedTargetObject && typeof parsedTargetObject === "object"
|
|
1534
|
+
? parsedTargetObject.files
|
|
1535
|
+
: undefined;
|
|
1536
|
+
if (Array.isArray(parsedFiles) && parsedFiles.some((file) => typeof file === "string" && file.startsWith("/")))
|
|
1537
|
+
fail("blocked", "invalid-output: absolute frontend contract path is forbidden", candidateJsonSha256);
|
|
1538
|
+
// Normalize model-emitted compact requirement IDs before deriving the
|
|
1539
|
+
// canonical binding or invoking the strict zod schema.
|
|
1540
|
+
const beforeIds = JSON.stringify(parsed);
|
|
1541
|
+
parsed = canonicalizeRequirementIdsInPayload(parsed);
|
|
1542
|
+
if (JSON.stringify(parsed) !== beforeIds)
|
|
1543
|
+
pushAction("canonicalize-requirement-ids");
|
|
1544
|
+
const beforeAliases = JSON.stringify(parsed);
|
|
1545
|
+
parsed = canonicalizeVerificationTargetAliases(parsed);
|
|
1546
|
+
if (JSON.stringify(parsed) !== beforeAliases)
|
|
1547
|
+
pushAction("canonicalize-verification-alias");
|
|
1548
|
+
try {
|
|
1549
|
+
assertFrontendContractPathsSafe(parsed);
|
|
1550
|
+
}
|
|
1551
|
+
catch (error) {
|
|
1552
|
+
fail("blocked", error instanceof Error ? error.message : String(error), candidateJsonSha256);
|
|
1553
|
+
}
|
|
1554
|
+
// Validate verificationTarget commandLabels against frozen command set.
|
|
1555
|
+
// The frozen set is derived from DAG verification shell task verifyEvidence.
|
|
1556
|
+
const frozenLabels = await deriveFrozenCommandLabelsFromRun(input.runDir);
|
|
1557
|
+
if (frozenLabels.length > 0) {
|
|
1558
|
+
const frozen = new Set(frozenLabels);
|
|
1559
|
+
const parsedVt = asRecord(parsed)?.verificationTargets;
|
|
1560
|
+
if (Array.isArray(parsedVt)) {
|
|
1561
|
+
for (const vt of parsedVt) {
|
|
1562
|
+
const label = asString(asRecord(vt)?.commandLabel);
|
|
1563
|
+
if (label && !frozen.has(label)) {
|
|
1564
|
+
fail("blocked", `invalid-output: verificationTarget commandLabel "${label}" is not in the frozen command set [${[...frozen].join(", ")}]`, candidateJsonSha256);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const parsedTargets = asRecord(parsed)?.targets;
|
|
1570
|
+
const parsedTargetFiles = asStringArray(asRecord(parsedTargets)?.files);
|
|
1571
|
+
if (parsedTargetFiles.some((file) => file.startsWith("/") || file.includes("\\")))
|
|
1572
|
+
fail("blocked", "invalid-output: frontend contract target paths must be relative POSIX paths", candidateJsonSha256);
|
|
1573
|
+
const parsedStates = asRecord(parsed)?.uiStates;
|
|
1574
|
+
if (Array.isArray(parsedStates) && parsedStates.some((item) => {
|
|
1575
|
+
const state = asRecord(item);
|
|
1576
|
+
return state?.applicable === true &&
|
|
1577
|
+
(!asString(state.expectedBehavior) || asStringArray(state.implementationTargets).length === 0 || asStringArray(state.verificationTargetIds).length === 0);
|
|
1578
|
+
}))
|
|
1579
|
+
fail("retryable-invalid", "invalid-output: applicable UI state requires behavior, implementation, and verification", candidateJsonSha256);
|
|
1580
|
+
const parsedMockApi = asRecord(parsed)?.mockApi;
|
|
1581
|
+
if (asRecord(parsedMockApi) &&
|
|
1582
|
+
typeof asRecord(parsedMockApi)?.strategy === "string" &&
|
|
1583
|
+
!["native", "browser-intercept", "request-adapter", "not-needed"].includes(String(asRecord(parsedMockApi)?.strategy)))
|
|
1584
|
+
fail("blocked", `invalid-output: unsupported mock strategy ${String(asRecord(parsedMockApi)?.strategy)}`, candidateJsonSha256);
|
|
1585
|
+
const baseCanonicalBinding = canonicalFrontendContractSourceBinding(input.sourceBinding);
|
|
1586
|
+
const canonicalBinding = withDerivedRequirementIdsWhenUnscoped(baseCanonicalBinding, parsed);
|
|
1587
|
+
// Always inject DAG-owned identity. Model-provided sourceBinding is advisory
|
|
1588
|
+
// only and must not fail a otherwise-valid contract. Record the action only
|
|
1589
|
+
// when the model's emitted binding actually differs from canonical identity.
|
|
1590
|
+
const modelBinding = asRecord(asRecord(parsed)?.sourceBinding);
|
|
1591
|
+
if (serializeDeterministicJson(modelBinding ?? null) !== serializeDeterministicJson(canonicalBinding))
|
|
1592
|
+
pushAction("inject-source-binding");
|
|
1593
|
+
// There is exactly one post-security candidate. A fallback candidate would
|
|
1594
|
+
// allow malformed raw fields to bypass the boundary checks above.
|
|
1595
|
+
const normalizedContract = coerceFrontendImplementationContractInput(parsed, canonicalBinding);
|
|
1596
|
+
const candidate = deriveFrontendVerificationCoverage({
|
|
1597
|
+
...(asRecord(normalizedContract) ?? parsed),
|
|
1598
|
+
sourceBinding: canonicalBinding,
|
|
1599
|
+
}, canonicalBinding);
|
|
1600
|
+
const result = frontendImplementationContractSchema.safeParse(candidate);
|
|
1601
|
+
if (!result.success)
|
|
1602
|
+
fail("retryable-invalid", `invalid-output: ${result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`, candidateJsonSha256);
|
|
1603
|
+
const blockingGaps = [
|
|
1604
|
+
...result.data.evidenceGaps,
|
|
1605
|
+
...result.data.requirements.flatMap((item) => item.evidenceGap ? [item.evidenceGap] : []),
|
|
1606
|
+
].filter((item) => item.blocking);
|
|
1607
|
+
if (blockingGaps.length > 0)
|
|
1608
|
+
fail("blocked", `frontend contract has blocking evidence gap: ${blockingGaps
|
|
1609
|
+
.map((item) => item.requirementId ?? item.description)
|
|
1610
|
+
.join(", ")}`, candidateJsonSha256);
|
|
1611
|
+
for (const requirementId of canonicalBinding.requirementIds)
|
|
1612
|
+
if (!result.data.requirements.some((item) => item.id === requirementId) &&
|
|
1613
|
+
!result.data.evidenceGaps.some((item) => item.requirementId === requirementId))
|
|
1614
|
+
fail("blocked", `frontend contract does not cover ${requirementId}`, candidateJsonSha256);
|
|
1615
|
+
return {
|
|
1616
|
+
canonical: result.data,
|
|
1617
|
+
candidateRawSha256,
|
|
1618
|
+
candidateJsonSha256,
|
|
1619
|
+
normalizationActions,
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
export async function writeFrontendImplementationContractArtifact(input) {
|
|
1623
|
+
const written = await writeDeterministicJsonArtifact(input.runDir, path.posix.join(input.outputDir, input.artifactName), input.canonical);
|
|
1624
|
+
return {
|
|
1625
|
+
path: written.path,
|
|
1626
|
+
sha256: written.sha256,
|
|
1627
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const FRONTEND_PLAN_PATCH_PROTECTED_PATHS = [
|
|
1631
|
+
"/schemaVersion",
|
|
1632
|
+
"/sourceBinding",
|
|
1633
|
+
"/riskLevel",
|
|
1634
|
+
"/targets/files",
|
|
1635
|
+
"/mockApi/productionDefaultOff",
|
|
1636
|
+
];
|
|
1637
|
+
function frontendPlanPatchProtectedPathViolations(patch) {
|
|
1638
|
+
const record = asRecord(patch);
|
|
1639
|
+
if (!record)
|
|
1640
|
+
return ["/"];
|
|
1641
|
+
const violations = [];
|
|
1642
|
+
for (const key of ["schemaVersion", "sourceBinding", "riskLevel"]) {
|
|
1643
|
+
if (Object.hasOwn(record, key))
|
|
1644
|
+
violations.push(`/${key}`);
|
|
1645
|
+
}
|
|
1646
|
+
const targets = asRecord(record.targets);
|
|
1647
|
+
if (targets && Object.hasOwn(targets, "files"))
|
|
1648
|
+
violations.push("/targets/files");
|
|
1649
|
+
const mockApi = asRecord(record.mockApi);
|
|
1650
|
+
if (mockApi && Object.hasOwn(mockApi, "productionDefaultOff"))
|
|
1651
|
+
violations.push("/mockApi/productionDefaultOff");
|
|
1652
|
+
return violations;
|
|
1653
|
+
}
|
|
1654
|
+
function workspaceRootFromDagRunDir(runDir) {
|
|
1655
|
+
const absolute = path.resolve(runDir);
|
|
1656
|
+
const segments = absolute.split(path.sep);
|
|
1657
|
+
const harnessIndex = segments.lastIndexOf(".harness");
|
|
1658
|
+
if (harnessIndex < 0 ||
|
|
1659
|
+
segments[harnessIndex + 1] !== "dag-runs" ||
|
|
1660
|
+
harnessIndex + 3 >= segments.length) {
|
|
1661
|
+
return null;
|
|
1662
|
+
}
|
|
1663
|
+
const joined = segments.slice(0, harnessIndex).join(path.sep);
|
|
1664
|
+
return joined || path.parse(absolute).root;
|
|
1665
|
+
}
|
|
1666
|
+
function verificationSymbolMatchesFile(symbol, content) {
|
|
1667
|
+
if (symbol.trim().toLowerCase() === "all describe blocks")
|
|
1668
|
+
return content.includes("describe(");
|
|
1669
|
+
const symbols = symbol.includes(" / ")
|
|
1670
|
+
? symbol
|
|
1671
|
+
.split(/[((]/, 1)[0]
|
|
1672
|
+
.split("/")
|
|
1673
|
+
.map((item) => item.trim())
|
|
1674
|
+
.filter(Boolean)
|
|
1675
|
+
: [symbol];
|
|
1676
|
+
return symbols.every((item) => {
|
|
1677
|
+
const describeTitle = item.match(/^describe\((?:['"])(.+?)(?:['"])/i)?.[1];
|
|
1678
|
+
return [item, describeTitle, describeTitle ? "describe(" : undefined]
|
|
1679
|
+
.filter((candidate) => Boolean(candidate))
|
|
1680
|
+
.some((candidate) => content.includes(candidate));
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
async function dropUnresolvedVerificationSymbols(input) {
|
|
1684
|
+
const workspaceRoot = workspaceRootFromDagRunDir(input.runDir);
|
|
1685
|
+
const targets = asRecord(input.contract)?.verificationTargets;
|
|
1686
|
+
if (!workspaceRoot || !Array.isArray(targets))
|
|
1687
|
+
return false;
|
|
1688
|
+
let changed = false;
|
|
1689
|
+
for (const targetValue of targets) {
|
|
1690
|
+
const target = asRecord(targetValue);
|
|
1691
|
+
const file = asString(target?.file);
|
|
1692
|
+
const symbol = asString(target?.symbol);
|
|
1693
|
+
if (!target || !file || !symbol)
|
|
1694
|
+
continue;
|
|
1695
|
+
const absolute = path.resolve(workspaceRoot, file);
|
|
1696
|
+
const relative = path.relative(workspaceRoot, absolute);
|
|
1697
|
+
if (relative.startsWith("..") ||
|
|
1698
|
+
path.isAbsolute(relative) ||
|
|
1699
|
+
relative.includes("..")) {
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
let content;
|
|
1703
|
+
try {
|
|
1704
|
+
content = await readFile(absolute, "utf8");
|
|
1705
|
+
}
|
|
1706
|
+
catch {
|
|
1707
|
+
// Missing files remain a trace-time hard failure; do not hide them by
|
|
1708
|
+
// changing the optional symbol during candidate normalization.
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
if (verificationSymbolMatchesFile(symbol, content))
|
|
1712
|
+
continue;
|
|
1713
|
+
delete target.symbol;
|
|
1714
|
+
changed = true;
|
|
1715
|
+
}
|
|
1716
|
+
return changed;
|
|
1717
|
+
}
|
|
1718
|
+
function extractSingleOpenspecCitationsBlock(text) {
|
|
1719
|
+
const blocks = text.match(/```openspec-citations[ \t]*\r?\n[\s\S]*?\r?\n```/g) ?? [];
|
|
1720
|
+
if (blocks.length !== 1) {
|
|
1721
|
+
throw new Error(`frontend plan patch output must include exactly one fenced openspec-citations block (found ${blocks.length})`);
|
|
1722
|
+
}
|
|
1723
|
+
return blocks[0];
|
|
1724
|
+
}
|
|
1725
|
+
async function writeFrontendPlanCandidateRaw(input) {
|
|
1726
|
+
const relativePath = path.posix.join("contracts", "candidates", input.nodeId, `attempt-${input.attempt}.raw.md`);
|
|
1727
|
+
await writeTextArtifactFile(path.join(input.runDir, relativePath), input.text.endsWith("\n") ? input.text : `${input.text}\n`);
|
|
1728
|
+
return relativePath;
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Compile the planner's editable RFC 7386 patch against the deterministic
|
|
1732
|
+
* runtime skeleton. Every attempt is preserved under contracts/candidates so
|
|
1733
|
+
* format/schema failures can be inspected and replayed without trusting model
|
|
1734
|
+
* prose. A successful result returns canonical full-contract text for all
|
|
1735
|
+
* downstream review and prewrite nodes.
|
|
1736
|
+
*/
|
|
1737
|
+
export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
1738
|
+
const nodeId = input.nodeId ?? "frontend-plan-pi";
|
|
1739
|
+
const attempt = input.attempt ?? 1;
|
|
1740
|
+
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
1741
|
+
const rawPath = await writeFrontendPlanCandidateRaw({
|
|
1742
|
+
runDir: input.runDir,
|
|
1743
|
+
nodeId,
|
|
1744
|
+
attempt,
|
|
1745
|
+
text: input.text,
|
|
1746
|
+
});
|
|
1747
|
+
const reportBase = {
|
|
1748
|
+
schemaVersion: 1,
|
|
1749
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID,
|
|
1750
|
+
nodeId,
|
|
1751
|
+
attempt,
|
|
1752
|
+
rawPath,
|
|
1753
|
+
protectedPaths: [...FRONTEND_PLAN_PATCH_PROTECTED_PATHS],
|
|
1754
|
+
candidateRawSha256: sha256Hex(input.text),
|
|
1755
|
+
};
|
|
1756
|
+
let extractedPatchPath;
|
|
1757
|
+
let extractedPatchSha256;
|
|
1758
|
+
const writeReport = async (value) => {
|
|
1759
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.validation.json`), {
|
|
1760
|
+
...reportBase,
|
|
1761
|
+
...(extractedPatchPath
|
|
1762
|
+
? { extractedPatchPath, extractedPatchSha256 }
|
|
1763
|
+
: {}),
|
|
1764
|
+
...value,
|
|
1765
|
+
});
|
|
1766
|
+
};
|
|
1767
|
+
try {
|
|
1768
|
+
if (!input.sourceBinding)
|
|
1769
|
+
throw new Error("frontend plan patch validator requires DAG sourceBinding");
|
|
1770
|
+
const skeleton = input.structuredContractOutput?.skeleton;
|
|
1771
|
+
if (!skeleton)
|
|
1772
|
+
throw new Error("frontend plan patch validator requires a deterministic runtime skeleton");
|
|
1773
|
+
const patch = extractFrontendImplementationJson(input.text);
|
|
1774
|
+
if (!isPlainObject(patch))
|
|
1775
|
+
throw new Error("frontend plan patch must extract to one JSON object");
|
|
1776
|
+
extractedPatchPath = path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`);
|
|
1777
|
+
const extractedArtifact = await writeDeterministicJsonArtifact(input.runDir, extractedPatchPath, patch);
|
|
1778
|
+
extractedPatchSha256 = extractedArtifact.sha256;
|
|
1779
|
+
const citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1780
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
1781
|
+
if (protectedViolations.length > 0)
|
|
1782
|
+
throw new Error(`frontend plan patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`);
|
|
1783
|
+
const merged = applyFrontendContractMergePatch(skeleton, patch);
|
|
1784
|
+
const droppedUnresolvedSymbols = await dropUnresolvedVerificationSymbols({
|
|
1785
|
+
runDir: input.runDir,
|
|
1786
|
+
contract: merged,
|
|
1787
|
+
});
|
|
1788
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
1789
|
+
runDir: input.runDir,
|
|
1790
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
1791
|
+
sourceBinding: input.sourceBinding,
|
|
1792
|
+
});
|
|
1793
|
+
const normalizedArtifact = await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
1794
|
+
await writeReport({
|
|
1795
|
+
classification: "accepted-normalized",
|
|
1796
|
+
normalizedContractPath: path.posix.relative(input.runDir.replaceAll(path.sep, "/"), normalizedArtifact.path.replaceAll(path.sep, "/")),
|
|
1797
|
+
normalizedContractSha256: normalizedArtifact.sha256,
|
|
1798
|
+
normalizationActions: [
|
|
1799
|
+
"apply-runtime-skeleton",
|
|
1800
|
+
...(droppedUnresolvedSymbols
|
|
1801
|
+
? ["drop-unresolved-verification-symbols"]
|
|
1802
|
+
: []),
|
|
1803
|
+
...analysis.normalizationActions,
|
|
1804
|
+
],
|
|
1805
|
+
errors: [],
|
|
1806
|
+
});
|
|
1807
|
+
return {
|
|
1808
|
+
ok: true,
|
|
1809
|
+
contract: analysis.canonical,
|
|
1810
|
+
normalizedText: [
|
|
1811
|
+
"Frontend implementation contract compiled from the runtime skeleton.",
|
|
1812
|
+
"",
|
|
1813
|
+
"```json",
|
|
1814
|
+
serializeDeterministicJson(analysis.canonical),
|
|
1815
|
+
"```",
|
|
1816
|
+
citationsBlock,
|
|
1817
|
+
].join("\n"),
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
catch (error) {
|
|
1821
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1822
|
+
await writeReport({
|
|
1823
|
+
classification: "retryable-invalid",
|
|
1824
|
+
normalizationActions: [],
|
|
1825
|
+
errors: [{ path: "$", code: "invalid-output", message: reason }],
|
|
1826
|
+
});
|
|
1827
|
+
return { ok: false, reason };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Node-output self-check for plan nodes that produce the implementation
|
|
1832
|
+
* contract. Mirrors the prewrite gate validation so schema/typo/null violations
|
|
1833
|
+
* are caught at the producing node and retried instead of failing the run later.
|
|
1834
|
+
* Skipped when no sourceBinding is available (the gate remains the authority).
|
|
1835
|
+
*/
|
|
1836
|
+
export async function validateFrontendContractNodeOutput(input) {
|
|
1837
|
+
if (!input.sourceBinding)
|
|
1838
|
+
return { ok: true };
|
|
1839
|
+
try {
|
|
1840
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
1841
|
+
runDir: input.runDir,
|
|
1842
|
+
rawContractText: input.text,
|
|
1843
|
+
sourceBinding: input.sourceBinding,
|
|
1844
|
+
});
|
|
1845
|
+
return { ok: true, contract: analysis.canonical };
|
|
1846
|
+
}
|
|
1847
|
+
catch (error) {
|
|
1848
|
+
return {
|
|
1849
|
+
ok: false,
|
|
1850
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Node-output self-check for the plan revision node, which emits an RFC 7386
|
|
1856
|
+
* merge-patch delta against the original contract instead of a full contract.
|
|
1857
|
+
* Deliberately loose and format-level: the output must contain exactly one
|
|
1858
|
+
* ```openspec-citations fenced block and must extract (after the citations
|
|
1859
|
+
* blocks are stripped) to exactly one JSON object. The delta carries no
|
|
1860
|
+
* schemaVersion/targets, so no full-contract schema/semantic checks run here;
|
|
1861
|
+
* content parsing, patch application, and merged-contract validation stay with
|
|
1862
|
+
* the prewrite gate, which remains the only authority.
|
|
1863
|
+
*/
|
|
1864
|
+
export async function validateFrontendRevisionPatchNodeOutput(input) {
|
|
1865
|
+
let citationsBlock;
|
|
1866
|
+
try {
|
|
1867
|
+
citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1868
|
+
}
|
|
1869
|
+
catch (error) {
|
|
1870
|
+
return {
|
|
1871
|
+
ok: false,
|
|
1872
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
// Strip the citation blocks before extraction: their per-row JSON objects
|
|
1876
|
+
// would otherwise count as competing balanced objects in the extractor.
|
|
1877
|
+
const textWithoutCitations = input.text.replace(citationsBlock, "");
|
|
1878
|
+
let patch;
|
|
1879
|
+
try {
|
|
1880
|
+
patch = extractFrontendImplementationJson(textWithoutCitations);
|
|
1881
|
+
}
|
|
1882
|
+
catch (error) {
|
|
1883
|
+
return {
|
|
1884
|
+
ok: false,
|
|
1885
|
+
reason: `revision patch output must contain exactly one valid JSON object: ${error instanceof Error ? error.message : String(error)}`,
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
|
|
1889
|
+
return {
|
|
1890
|
+
ok: false,
|
|
1891
|
+
reason: "revision patch output must extract to exactly one JSON object (RFC 7386 merge-patch delta)",
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
1895
|
+
if (protectedViolations.length > 0) {
|
|
1896
|
+
return {
|
|
1897
|
+
ok: false,
|
|
1898
|
+
reason: `frontend revision patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`,
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
return { ok: true, contract: patch };
|
|
1902
|
+
}
|
|
1903
|
+
export async function materializeFrontendImplementationContract(input) {
|
|
1904
|
+
const analysis = await analyzeFrontendImplementationContract(input);
|
|
1905
|
+
return writeFrontendImplementationContractArtifact({
|
|
1906
|
+
runDir: input.runDir,
|
|
1907
|
+
outputDir: input.outputDir,
|
|
1908
|
+
artifactName: input.artifactName,
|
|
1909
|
+
canonical: analysis.canonical,
|
|
1910
|
+
});
|
|
1911
|
+
}
|