@tea-agent/loop-agent 0.42.0-next.2 → 0.42.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +242 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +942 -187
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +173 -7
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +30 -19
- package/dist/worker/console/chat/session-store.js +117 -70
- package/dist/worker/console/chat/shortcuts.js +1 -1
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CeznJ4Iw.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-B4u9u7SX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-oghpT81p.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-BPJ5Kvcj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-CoIu69pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-DmIXqJ6B.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-DtgiAsSc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-YSu_EVSD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-DaRO-NB6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-Dh0ZKiGA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DOrvbR-H.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-C3b22LN2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-CXlqO8l6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-BblTBrDu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-DswqPALe.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-CfTwVOlc.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-bLD8jVFo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-BvoG1fh5.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-DEPAdJu3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-DmrNvBLy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-CdwzIwvs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-CJ3hj-md.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-C6YJT2a4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-C_LPfjhX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-xvsSb5Ie.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-Rxp2tKcD.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-DRyLYmZh.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-TLSU_D2B.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DGqeTvHC.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-BZxyH2wh.js} +1 -1
- package/dist/worker/console/static/assets/index-C8V8K9b4.js +451 -0
- package/dist/worker/console/static/assets/index-Cnf_KxTQ.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-BsYtzx01.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-Qr-ezGv-.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-DYbB3ibo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-BHJHewZO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-CmPIPmCG.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-F--C1uV0.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-YTXfL1st.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-DdDMYXP9.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-C3nH4KVd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-kXyxjG7Y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-KNzbl-7w.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-CSwI6dG0.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-Bl1AyuXy.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-2y4EmXQa.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-E8v8YrMg.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-eGGlR856.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8e4J3at.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-B7g7xc07.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Do88I1Nb.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-DR3VCHBG.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-LUvapZIy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-C7GDcaXq.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CbNAqoGK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +907 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +677 -492
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +433 -126
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -3,16 +3,18 @@ import { access, readdir, readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { deflateRawSync } from "node:zlib";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
7
8
|
import { assertValidDagSpec } from "./validate.js";
|
|
8
9
|
import { DAG_AGENT_RUNTIME_PI_ONLY, DAG_REPAIR_WRITER_PROTOCOL_EXPLICIT_NODE_V1, DAG_RUNTIME_CONTRACT_SCHEMA_VERSION, DEFAULT_DAG_OUTPUT_LANGUAGE, DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
|
|
9
10
|
import { bindDagRerunFeedback } from "./rerun-feedback.js";
|
|
10
11
|
import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
11
12
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
12
|
-
import {
|
|
13
|
+
import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, DEFAULT_FRONTEND_SPEC_ROOTS, extractTaskSourceFrontendSpecPaths, } from "../../shared/openspec-spec.js";
|
|
14
|
+
import { readFrontendSpecRegistry, scoreFrontendSpecCandidate, } from "../../task/frontend-project-capability.js";
|
|
13
15
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
14
16
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
15
|
-
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY,
|
|
17
|
+
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, FRONTEND_SCOUT_COMPLETENESS_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, BACKEND_TEST_MARKDOWN_BINDING_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, FRONTEND_PLAN_LADDER_RETRY_POLICY, FRONTEND_REVIEW_TERMINAL_RETRY_POLICY, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY, isCanonicalFinalVerifyShellRetryCandidate, isSafeReadOnlyPiRetryCandidate, isTargetTemplateImplementPi, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
16
18
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
17
19
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
18
20
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -20,7 +22,7 @@ import { mergeDocumentIndexCompanions } from "../../governance/document-index-cl
|
|
|
20
22
|
import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
|
|
21
23
|
import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-embedded.js";
|
|
22
24
|
import { discoverProjectGovernancePresence } from "./project-governance-context.js";
|
|
23
|
-
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
25
|
+
import { CANONICAL_TASK_ID_PATTERN, getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
24
26
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
25
27
|
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
26
28
|
import { extractRequirementFactsFromMarkdown } from "../../task/source-prepare/parse-intent.js";
|
|
@@ -37,8 +39,10 @@ import { buildBackendTestOutcomeGateShellSnippet } from "./backend-test-result-c
|
|
|
37
39
|
import { buildBackendTestIntakeContext } from "./backend-test-intake-context.js";
|
|
38
40
|
import { buildFrontendTestOutcomeGateShellSnippet } from "./frontend-test-result-contract.js";
|
|
39
41
|
import { classifyFrontendRisk, } from "./frontend-risk.js";
|
|
40
|
-
import { discoverFrontendProjectCapability, } from "./frontend-project-capability.js";
|
|
41
|
-
import { buildFrontendImplementationContractSkeleton, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
42
|
+
import { discoverFrontendProjectCapability, resolveFrontendSpecRootAliases, } from "./frontend-project-capability.js";
|
|
43
|
+
import { buildFrontendImplementationContractSkeleton, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, } from "./frontend-implementation-contract.js";
|
|
44
|
+
import { computeFrontendShapeSourceDigest, parseFrontendShapeTransitionCapsule, resolveFrontendTaskShape, } from "./frontend-shape.js";
|
|
45
|
+
import { discoverLatestCommittedFrontendShapeCapsule } from "./frontend-shape-capsule-store.js";
|
|
42
46
|
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT } from "./frontend-verification-trace.js";
|
|
43
47
|
import { serializeDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
44
48
|
const REQUIREMENT_FILE = "需求.md";
|
|
@@ -154,7 +158,9 @@ const FRONTEND_SKILLS_BY_ROLE = {
|
|
|
154
158
|
verifier: [],
|
|
155
159
|
closeout: [],
|
|
156
160
|
};
|
|
157
|
-
const
|
|
161
|
+
const FRONTEND_CONTRACT_SKILLS = ["frontend-contract"];
|
|
162
|
+
const FRONTEND_SCOUT_SKILLS = ["frontend-scout"];
|
|
163
|
+
const FRONTEND_PLAN_SKILLS = ["frontend-plan"];
|
|
158
164
|
const FRONTEND_BOUNDED_IMPLEMENT_SKILLS = ["frontend-bounded-implement"];
|
|
159
165
|
const FRONTEND_DESIGN_REVIEW_SKILLS = ["frontend-design-review"];
|
|
160
166
|
const FRONTEND_REVIEW_SKILLS = ["frontend-review"];
|
|
@@ -275,6 +281,28 @@ async function hasDirectDependency(repoRoot, depName) {
|
|
|
275
281
|
return false;
|
|
276
282
|
}
|
|
277
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Collect the project's declared direct dependencies (dependencies +
|
|
286
|
+
* devDependencies names) from package.json. Used to freeze the frontend
|
|
287
|
+
* design-policy allowedDependencies set: the model's dependency policy must
|
|
288
|
+
* not be rejected for mentioning an already-declared dependency, and a
|
|
289
|
+
* dependency not present in the manifest is a genuine unauthorized addition.
|
|
290
|
+
* Returns [] when package.json is unreadable (no allowlist → the dependency
|
|
291
|
+
* check is skipped rather than rejecting prose tokens).
|
|
292
|
+
*/
|
|
293
|
+
async function collectDeclaredDependencies(repoRoot) {
|
|
294
|
+
try {
|
|
295
|
+
const raw = await readFile(path.join(repoRoot, "package.json"), "utf-8");
|
|
296
|
+
const pkg = JSON.parse(raw);
|
|
297
|
+
return [
|
|
298
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
299
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
278
306
|
/** Check whether handler/fixture/bootstrap files exist for known mock frameworks. */
|
|
279
307
|
async function discoverMockHandlerFiles(repoRoot, serviceRoot) {
|
|
280
308
|
const exactCandidates = [
|
|
@@ -1704,7 +1732,10 @@ function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
|
1704
1732
|
requirementIds: binding.requirementIds,
|
|
1705
1733
|
};
|
|
1706
1734
|
}
|
|
1707
|
-
function buildSourceContextBlock(sources) {
|
|
1735
|
+
function buildSourceContextBlock(sources, options = {}) {
|
|
1736
|
+
const includeRequirementExcerpt = options.includeRequirementExcerpt ?? true;
|
|
1737
|
+
const includeConstraintExcerpt = options.includeConstraintExcerpt ?? true;
|
|
1738
|
+
const includeReferenceDocuments = options.includeReferenceDocuments ?? true;
|
|
1708
1739
|
const requirementRef = toDagSourcePath(sources, sources.requirementPath);
|
|
1709
1740
|
const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
|
|
1710
1741
|
sourceRef: requirementRef,
|
|
@@ -1713,7 +1744,7 @@ function buildSourceContextBlock(sources) {
|
|
|
1713
1744
|
const parts = [
|
|
1714
1745
|
`## Task source: 需求.md`,
|
|
1715
1746
|
`Bound readPath (use for Pi read-tool calls): ${requirementRef}`,
|
|
1716
|
-
requirementExcerpt.text,
|
|
1747
|
+
...(includeRequirementExcerpt ? [requirementExcerpt.text] : []),
|
|
1717
1748
|
];
|
|
1718
1749
|
if (sources.constraintMarkdown) {
|
|
1719
1750
|
const constraintRef = toDagSourcePath(sources, sources.constraintPath);
|
|
@@ -1721,9 +1752,11 @@ function buildSourceContextBlock(sources) {
|
|
|
1721
1752
|
sourceRef: constraintRef,
|
|
1722
1753
|
});
|
|
1723
1754
|
boundReadPaths.push(`- constraints: ${constraintRef}`);
|
|
1724
|
-
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, constraintExcerpt.text);
|
|
1755
|
+
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, ...(includeConstraintExcerpt ? [constraintExcerpt.text] : []));
|
|
1725
1756
|
}
|
|
1726
|
-
for (const reference of (
|
|
1757
|
+
for (const reference of (includeReferenceDocuments
|
|
1758
|
+
? sources.referenceDocuments ?? []
|
|
1759
|
+
: []).slice(0, MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS)) {
|
|
1727
1760
|
const relativePath = path
|
|
1728
1761
|
.relative(path.join(sources.taskDir, "source"), reference.path)
|
|
1729
1762
|
.replaceAll(path.sep, "/");
|
|
@@ -1854,6 +1887,16 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
|
1854
1887
|
catch (error) {
|
|
1855
1888
|
throw new Error(`failed to load verification commands for task "${taskId}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1856
1889
|
}
|
|
1890
|
+
const frontendShapeTransitionCapsule = taskConfig.taskKind === "frontend-implementation" && CANONICAL_TASK_ID_PATTERN.test(taskId)
|
|
1891
|
+
? await discoverLatestCommittedFrontendShapeCapsule({
|
|
1892
|
+
repoRoot,
|
|
1893
|
+
taskId,
|
|
1894
|
+
sourceDigest: computeFrontendShapeSourceDigest({
|
|
1895
|
+
requirementMarkdown,
|
|
1896
|
+
constraintMarkdown: constraintMarkdown ?? "",
|
|
1897
|
+
}),
|
|
1898
|
+
})
|
|
1899
|
+
: undefined;
|
|
1857
1900
|
const sources = {
|
|
1858
1901
|
taskId,
|
|
1859
1902
|
repoRoot,
|
|
@@ -1870,6 +1913,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
|
1870
1913
|
verifyCommands,
|
|
1871
1914
|
sddEmbeddedSkills: await probeRepoLocalSddSkills(repoRoot),
|
|
1872
1915
|
projectGovernancePresent: await discoverProjectGovernancePresence(repoRoot),
|
|
1916
|
+
...(frontendShapeTransitionCapsule ? { autoloadedFrontendShapeTransitionCapsule: frontendShapeTransitionCapsule } : {}),
|
|
1873
1917
|
};
|
|
1874
1918
|
return sources;
|
|
1875
1919
|
}
|
|
@@ -1887,7 +1931,9 @@ async function prepareFrontendMockSources(sources, discoveredProjectCapability)
|
|
|
1887
1931
|
}
|
|
1888
1932
|
}
|
|
1889
1933
|
const projectCapability = discoveredProjectCapability ??
|
|
1890
|
-
(await discoverFrontendProjectCapability(repoRoot
|
|
1934
|
+
(await discoverFrontendProjectCapability(repoRoot, {
|
|
1935
|
+
specRoots: sources.taskConfig.frontendOpenspec?.specRoots,
|
|
1936
|
+
}));
|
|
1891
1937
|
const frontendRisk = classifyFrontendRisk({
|
|
1892
1938
|
title: sources.taskConfig.title,
|
|
1893
1939
|
requirementMarkdown: sources.requirementMarkdown,
|
|
@@ -2150,7 +2196,7 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
|
|
|
2150
2196
|
writePolicy: "read-only",
|
|
2151
2197
|
allowedPaths: readOnlyPaths,
|
|
2152
2198
|
forbiddenPaths,
|
|
2153
|
-
skills:
|
|
2199
|
+
skills: FRONTEND_PLAN_SKILLS,
|
|
2154
2200
|
firstProtocolLine: "MOCK_STRATEGY:",
|
|
2155
2201
|
outputContract: "Plain Markdown whose first line is MOCK_STRATEGY: native|browser-intercept|request-adapter|not-needed|blocked, followed by Mock Decision, API Contract Evidence, Specification Evidence, Service Evidence, Backend Readiness, Selection Evidence, Endpoint / Fixture Matrix, Activation, Target Files, Production Safety, Verification Plan, Real Integration Gap, and Blocking Issues. No file writes.",
|
|
2156
2202
|
subtask_prompt: [
|
|
@@ -2398,40 +2444,181 @@ function frontendMockStrategyMustBeNotNeeded(sources) {
|
|
|
2398
2444
|
capabilityStatus === "ambiguous" ||
|
|
2399
2445
|
!hasDeterministicMockVerification));
|
|
2400
2446
|
}
|
|
2401
|
-
|
|
2447
|
+
/**
|
|
2448
|
+
* 候选规范懒加载:prompt 只内联与任务相关的候选,不全部塞给模型。
|
|
2449
|
+
*
|
|
2450
|
+
* - mandatory(任务源显式声明/引用)始终保留——模型必须知道它们存在。
|
|
2451
|
+
* - scan-strict 候选按「路径段是否命中任务源关键词」过滤:列表页任务只
|
|
2452
|
+
* 带出列表相关组件/页面规范,创建页规范不进 prompt。
|
|
2453
|
+
* - runtime 的选型/read 门禁仍消费完整 openspecCandidatePaths(prewrite
|
|
2454
|
+
* gate 用全量);这里只缩小 prompt 体积,不改变门禁语义。未提及的候选
|
|
2455
|
+
* 由 runtime 默认 irrelevant,模型无需枚举。
|
|
2456
|
+
* - 真正读取发生在 plan 侧:模型对 required 选型调用 read 工具,prewrite
|
|
2457
|
+
* 门禁验证 read 事件——按需读取,不预加载正文。
|
|
2458
|
+
*/
|
|
2459
|
+
export function filterRelevantOpenspecCandidates(input) {
|
|
2460
|
+
const mandatory = new Set(input.mandatoryPaths);
|
|
2461
|
+
const relevant = [];
|
|
2462
|
+
for (const candidate of input.candidates) {
|
|
2463
|
+
if (mandatory.has(candidate)) {
|
|
2464
|
+
relevant.push(candidate);
|
|
2465
|
+
continue;
|
|
2466
|
+
}
|
|
2467
|
+
if (openspecCandidateMatchesSource(candidate, input.sourceMarkdown)) {
|
|
2468
|
+
relevant.push(candidate);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
// 保持候选发现顺序(确定性);mandatory 与相关候选都按原始顺序出现。
|
|
2472
|
+
const maxCandidates = input.maxCandidates ?? 24;
|
|
2473
|
+
if (relevant.length <= maxCandidates)
|
|
2474
|
+
return relevant;
|
|
2475
|
+
const mandatoryRelevant = relevant.filter((candidate) => mandatory.has(candidate));
|
|
2476
|
+
const optionalRelevant = relevant.filter((candidate) => !mandatory.has(candidate));
|
|
2477
|
+
return [
|
|
2478
|
+
...mandatoryRelevant,
|
|
2479
|
+
...optionalRelevant.slice(0, Math.max(0, maxCandidates - mandatoryRelevant.length)),
|
|
2480
|
+
];
|
|
2481
|
+
}
|
|
2482
|
+
function buildFrontendSpecCandidateSummaries(input) {
|
|
2483
|
+
const mandatory = new Set(input.mandatoryPaths);
|
|
2484
|
+
return input.paths
|
|
2485
|
+
.map((candidate) => scoreFrontendSpecCandidate(candidate, "", {
|
|
2486
|
+
registryMode: input.registryModes.get(candidate),
|
|
2487
|
+
taskRelated: mandatory.has(candidate) || openspecCandidateMatchesSource(candidate, input.sourceMarkdown),
|
|
2488
|
+
}))
|
|
2489
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* 候选路径是否与任务源相关:从任务源提取文件名/路径 token(去扩展名、
|
|
2493
|
+
* 去连字符/下划线),若候选的路径段包含任一 token 即视为相关。纯字符串
|
|
2494
|
+
* 判定,无 IO;找不到 token 时保守保留(避免漏掉模型可能需要的规范)。
|
|
2495
|
+
*/
|
|
2496
|
+
function openspecCandidateMatchesSource(candidate, sourceMarkdown) {
|
|
2497
|
+
const sourceTokens = extractOpenspecSourceTokens(sourceMarkdown);
|
|
2498
|
+
if (sourceTokens.size === 0)
|
|
2499
|
+
return true;
|
|
2500
|
+
const candidateLower = candidate.toLowerCase().replace(/\\/g, "/");
|
|
2501
|
+
const candidateSegments = candidateLower.split("/");
|
|
2502
|
+
const candidateBasename = candidateSegments[candidateSegments.length - 1] ?? "";
|
|
2503
|
+
for (const token of sourceTokens) {
|
|
2504
|
+
if (candidateBasename.includes(token) ||
|
|
2505
|
+
candidateLower.includes(`/${token}/`) ||
|
|
2506
|
+
candidateLower.includes(`${token}.`)) {
|
|
2507
|
+
return true;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
/** 从任务源 markdown 提取显著 token:反引号代码段、路径、组件/页面名。 */
|
|
2513
|
+
function extractOpenspecSourceTokens(markdown) {
|
|
2514
|
+
const tokens = new Set();
|
|
2515
|
+
const push = (raw) => {
|
|
2516
|
+
const cleaned = raw
|
|
2517
|
+
.trim()
|
|
2518
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "")
|
|
2519
|
+
.toLowerCase();
|
|
2520
|
+
if (cleaned.length >= 2 && cleaned.length <= 40)
|
|
2521
|
+
tokens.add(cleaned);
|
|
2522
|
+
};
|
|
2523
|
+
// 反引号内联代码(文件名/组件名)
|
|
2524
|
+
for (const match of markdown.matchAll(/`([^`\n]+)`/g)) {
|
|
2525
|
+
push(match[1] ?? "");
|
|
2526
|
+
}
|
|
2527
|
+
// markdown 链接文本
|
|
2528
|
+
for (const match of markdown.matchAll(/\[([^\]]+)\]\([^)]+\)/g)) {
|
|
2529
|
+
push(match[1] ?? "");
|
|
2530
|
+
}
|
|
2531
|
+
// 路径 token(openspec/ 或 xxx/xxx.md)
|
|
2532
|
+
for (const match of markdown.matchAll(/(?:[A-Za-z0-9_-]+\/)+[A-Za-z0-9_.-]+/g)) {
|
|
2533
|
+
const segments = (match[0] ?? "").split("/");
|
|
2534
|
+
const basename = segments[segments.length - 1] ?? "";
|
|
2535
|
+
push(basename.replace(/\.[a-z0-9]+$/i, ""));
|
|
2536
|
+
for (const segment of segments)
|
|
2537
|
+
push(segment);
|
|
2538
|
+
}
|
|
2539
|
+
return tokens;
|
|
2540
|
+
}
|
|
2541
|
+
async function resolveFrontendOpenspecGateConfig(sources) {
|
|
2402
2542
|
const taskConfig = sources.taskConfig;
|
|
2403
2543
|
const policy = taskConfig.frontendOpenspec?.policy ?? "cited";
|
|
2404
2544
|
const declared = taskConfig.frontendOpenspec?.requiredReadPaths ?? [];
|
|
2405
|
-
const
|
|
2545
|
+
const governanceRoot = sources.frontendProjectCapability?.openspecDiscovery?.governanceRoot ??
|
|
2546
|
+
DEFAULT_OPENSPEC_GOVERNANCE_ROOT;
|
|
2547
|
+
const specRoots = taskConfig.frontendOpenspec?.specRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
|
|
2548
|
+
const rootAliases = sources.repoRoot
|
|
2549
|
+
? await resolveFrontendSpecRootAliases(sources.repoRoot, [...specRoots])
|
|
2550
|
+
: [];
|
|
2551
|
+
const remappedSourceMarkdown = rootAliases.reduce((markdown, { alias, logicalRoot }) => markdown.replace(new RegExp(`(^|[^A-Za-z0-9_.-])${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`, "g"), `$1${logicalRoot}/`), [sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"));
|
|
2552
|
+
const taskSourceCited = extractTaskSourceFrontendSpecPaths(remappedSourceMarkdown, specRoots, governanceRoot);
|
|
2406
2553
|
const scanStrict = sources.frontendProjectCapability?.designEvidence.normativePaths ?? [];
|
|
2554
|
+
const registry = sources.repoRoot
|
|
2555
|
+
? await readFrontendSpecRegistry(sources.repoRoot)
|
|
2556
|
+
: null;
|
|
2557
|
+
const registryModes = new Map();
|
|
2558
|
+
for (const root of registry?.roots ?? []) {
|
|
2559
|
+
if (root.scope && !root.scope.includes("frontend"))
|
|
2560
|
+
continue;
|
|
2561
|
+
if (!root.mode)
|
|
2562
|
+
continue;
|
|
2563
|
+
for (const candidate of scanStrict) {
|
|
2564
|
+
if (candidate === root.path || candidate.startsWith(`${root.path}/`)) {
|
|
2565
|
+
registryModes.set(candidate, root.mode);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2407
2569
|
const dedupeSorted = (paths) => [...new Set(paths)].sort();
|
|
2408
2570
|
const openspecCandidateSources = {
|
|
2409
2571
|
declared: dedupeSorted(declared),
|
|
2410
2572
|
taskSourceCited: dedupeSorted(taskSourceCited),
|
|
2411
2573
|
scanStrict: dedupeSorted(scanStrict),
|
|
2412
2574
|
};
|
|
2575
|
+
// Candidate discovery is frozen independently from the eventual must-read
|
|
2576
|
+
// set. The selector may mark candidates irrelevant, while explicit task
|
|
2577
|
+
// declarations/source citations are never allowed to be downgraded.
|
|
2578
|
+
const openspecMandatoryPaths = dedupeSorted([
|
|
2579
|
+
...declared,
|
|
2580
|
+
...taskSourceCited,
|
|
2581
|
+
]);
|
|
2582
|
+
// `cited` must stay demand-driven: a normative registry makes a path
|
|
2583
|
+
// discoverable, not implicitly task-mandatory. Only scan-strict may offer
|
|
2584
|
+
// the global discovery set to the bounded task-relevance selector.
|
|
2413
2585
|
const openspecCandidatePaths = policy === "cited"
|
|
2414
|
-
?
|
|
2415
|
-
:
|
|
2416
|
-
|
|
2586
|
+
? openspecMandatoryPaths
|
|
2587
|
+
: dedupeSorted([
|
|
2588
|
+
...openspecCandidateSources.scanStrict,
|
|
2589
|
+
...openspecMandatoryPaths,
|
|
2590
|
+
]);
|
|
2591
|
+
const result = {
|
|
2417
2592
|
openspecPolicy: policy,
|
|
2418
2593
|
openspecCandidatePaths,
|
|
2419
2594
|
openspecCandidateSources,
|
|
2595
|
+
openspecMandatoryPaths,
|
|
2596
|
+
openspecCandidateSummaries: buildFrontendSpecCandidateSummaries({
|
|
2597
|
+
paths: openspecCandidatePaths,
|
|
2598
|
+
mandatoryPaths: openspecMandatoryPaths,
|
|
2599
|
+
registryModes,
|
|
2600
|
+
sourceMarkdown: [sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"),
|
|
2601
|
+
}),
|
|
2420
2602
|
};
|
|
2603
|
+
if (sources.repoRoot && openspecCandidatePaths.length > 0) {
|
|
2604
|
+
result.openspecCandidateSnapshots = await Promise.all(openspecCandidatePaths.map(async (candidate) => ({
|
|
2605
|
+
path: candidate,
|
|
2606
|
+
sha256: createHash("sha256")
|
|
2607
|
+
.update(await readFile(path.join(sources.repoRoot, candidate)))
|
|
2608
|
+
.digest("hex"),
|
|
2609
|
+
})));
|
|
2610
|
+
}
|
|
2611
|
+
return result;
|
|
2421
2612
|
}
|
|
2422
|
-
const openspecCitationInstruction = [
|
|
2423
|
-
"OpenSpec 引用块(citation block):在 fenced json 契约块之后,追加**恰好一个** ```openspec-citations 围栏代码块(三反引号 + openspec-citations)。",
|
|
2424
|
-
'该块内每行一个 JSON 对象 {"path":"<repo 相对 openspec 路径>","section":"<命中章节或空串>","line":<int 或 null>},必须逐条列出你在本计划中实际读取并应用的每个 openspec 规范文件。',
|
|
2425
|
-
"prewrite gate 会用真实 read 事件核验每条引用:引用存在但无成功 read 事件 → openspec-citation-not-read;契约冻结的必读候选未被引用 → openspec-not-cited;两者都 fail-closed。不要引用未读取的路径。",
|
|
2426
|
-
].join("\n");
|
|
2427
2613
|
const frontendComponentConformanceInstruction = [
|
|
2428
2614
|
"## Component Selection conformance (uiComponentChoices; hard rule)",
|
|
2429
2615
|
"每个 UI 用途必须在契约的 uiComponentChoices[] 中声明组件选型:{ purpose, component, decision, specReference, rationale }。",
|
|
2616
|
+
"purpose is the stable coverage key:它应精确匹配 interaction.name 或 uiState.name;职责语义由对应 interaction.expectedBehavior / uiState.expectedBehavior 与 rationale 表达。不得仅因 purpose 与 interaction 或 component 标识符相同而判缺陷。",
|
|
2430
2617
|
"- decision=specified:前端规范(候选组件/主题桶 + 任务源显式引用)已定义该用途组件 → 必须使用该组件,并给精确 specReference { path, section, line }(path 必须是 openspec/ai_workspace 受支持规范路径)。",
|
|
2431
|
-
"- decision=reuse-existing
|
|
2432
|
-
"- decision=new
|
|
2618
|
+
"- decision=reuse-existing:仅当该组件/惯例**确实已存在于仓库当前代码**(如复用现有 ActiveRunBadge 的 oc- class 惯例)→ specReference 可为 null,rationale 必须指明复用的具体现有组件/文件与依据。",
|
|
2619
|
+
"- decision=new:任务源/PRD 要求**新增**该组件(仓库当前不存在该组件文件)→ decision 必须为 new,不得标 reuse-existing;调用 record_component_choice 时传 sourceRequirementIds(关联的 frozen requirement ID)与 plan checklist 列出的 sourceFragmentId,runtime 校验其隶属关系并物化精确的任务源 PRD { path, section, line }。不要读取 PRD 或手填/猜测 specReference;rationale 说明新增纯展示组件、复用既有 CSS 命名与主题变量约定。",
|
|
2433
2620
|
"不得静默替换规范组件或自创组件而无偏差声明;spec 已定义该用途组件时不得改选其它组件。",
|
|
2434
|
-
"prewrite gate
|
|
2621
|
+
"prewrite gate 确定性交叉校验:仅 decision=specified 的 specReference 必须是候选 OpenSpec 路径、在 typed decision ledger 中声明且有成功 read 事件;decision=new 的 PRD 引用走任务源可追溯性审查,不得按 OpenSpec 候选拒绝。候选桶非空且契约有 UI 可见工作而 uiComponentChoices 缺失/空 → component-choices-missing。",
|
|
2435
2622
|
].join("\n");
|
|
2436
2623
|
function resolveFrontendCapabilityContextBlock(sources) {
|
|
2437
2624
|
const risk = sources.frontendRisk;
|
|
@@ -2445,34 +2632,7 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2445
2632
|
}
|
|
2446
2633
|
if (capability) {
|
|
2447
2634
|
parts.push("", capability.adapterGuidance);
|
|
2448
|
-
|
|
2449
|
-
const bucketLines = [];
|
|
2450
|
-
const pushBucket = (label, paths) => {
|
|
2451
|
-
if (paths.length > 0)
|
|
2452
|
-
bucketLines.push(`${label}: ${paths.join(", ")}`);
|
|
2453
|
-
};
|
|
2454
|
-
pushBucket("schemas", classified.schemas);
|
|
2455
|
-
pushBucket("code-template", classified.codeTemplate);
|
|
2456
|
-
pushBucket("rule.api", classified.rule.api);
|
|
2457
|
-
pushBucket("rule.mock", classified.rule.mock);
|
|
2458
|
-
pushBucket("rule.router", classified.rule.router);
|
|
2459
|
-
pushBucket("rule.hooks", classified.rule.hooks);
|
|
2460
|
-
pushBucket("rule.utils", classified.rule.utils);
|
|
2461
|
-
pushBucket("rule.components", classified.rule.components);
|
|
2462
|
-
pushBucket("rule.other", classified.rule.other);
|
|
2463
|
-
pushBucket("theme", classified.theme);
|
|
2464
|
-
pushBucket("component", classified.component);
|
|
2465
|
-
pushBucket("ui-other", classified.uiOther);
|
|
2466
|
-
pushBucket("advisory-other", classified.advisoryOther);
|
|
2467
|
-
parts.push("## Classified openspec specification paths (role semantics)");
|
|
2468
|
-
if (bucketLines.length > 0) {
|
|
2469
|
-
parts.push(...bucketLines);
|
|
2470
|
-
}
|
|
2471
|
-
else {
|
|
2472
|
-
parts.push("(no openspec specification paths discovered — greenfield)");
|
|
2473
|
-
}
|
|
2474
|
-
parts.push("component / theme / rule.components 是「组件/主题规范」必读语义桶:规范已定义某用途组件时必须使用它(uiComponentChoices 用 decision=specified + 精确 path/section/line),不得静默替换为自认更合适的组件;无规范定义时才允许 reuse-existing 或 new(new 必须声明偏差 rationale)。这些路径在生成期冻结为 prewrite gate 的 componentSpecCandidatePaths。");
|
|
2475
|
-
parts.push("Each consuming node MUST report in its output: applicable rules, the hit path/section/line number for every applied specification, and any conflicts or missing specifications. Missing or conflicting required specifications must fail closed rather than silently substituting nearby repository conventions.");
|
|
2635
|
+
parts.push("Task-relevant OpenSpec candidates are injected separately as a bounded Top-K index. The deterministic prewrite gate retains the complete frozen candidate set; unlisted candidates are neither silently required nor evidence of a missing specification.");
|
|
2476
2636
|
parts.push(`A11y capability: ${capability.a11y.status}` +
|
|
2477
2637
|
(capability.a11y.tools.length
|
|
2478
2638
|
? ` (${capability.a11y.tools.join(", ")})`
|
|
@@ -2481,17 +2641,102 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2481
2641
|
}
|
|
2482
2642
|
return parts.join("\n");
|
|
2483
2643
|
}
|
|
2644
|
+
function pruneFrontendTasksForMicro(tasks) {
|
|
2645
|
+
// Micro topology (7 nodes): contract-import-shell → writer-admission →
|
|
2646
|
+
// implement → verify → review-context → review → closeout. Drops scout/plan/
|
|
2647
|
+
// design-policy/design-review and replaces the model contract node with a
|
|
2648
|
+
// deterministic contract-import shell (§7.3: no model, no requirement
|
|
2649
|
+
// rewrites — micro consumes a pre-validated managed Contract).
|
|
2650
|
+
const drop = new Set([
|
|
2651
|
+
"frontend-contract-pi",
|
|
2652
|
+
"frontend-scout-pi",
|
|
2653
|
+
"frontend-plan-pi",
|
|
2654
|
+
"frontend-design-policy-shell",
|
|
2655
|
+
"frontend-design-review-pi",
|
|
2656
|
+
]);
|
|
2657
|
+
const contractPi = tasks.find((task) => task.id === "frontend-contract-pi");
|
|
2658
|
+
const contractImportShell = contractPi
|
|
2659
|
+
? {
|
|
2660
|
+
id: "frontend-contract-import-shell",
|
|
2661
|
+
role: "verifier",
|
|
2662
|
+
executor: "shell",
|
|
2663
|
+
complexity: "LOW",
|
|
2664
|
+
writePolicy: "read-only",
|
|
2665
|
+
depends_on: [],
|
|
2666
|
+
allowedPaths: contractPi.allowedPaths,
|
|
2667
|
+
forbiddenPaths: contractPi.forbiddenPaths,
|
|
2668
|
+
subtask_prompt: "Deterministic managed-Contract import + source/binding/schema freshness re-validation; no model, no requirement rewrite.",
|
|
2669
|
+
shell: {
|
|
2670
|
+
commands: [],
|
|
2671
|
+
frontendContractImport: {
|
|
2672
|
+
schemaVersion: 1,
|
|
2673
|
+
artifactName: "frontend-task-contract.json",
|
|
2674
|
+
outputDir: "contracts",
|
|
2675
|
+
requireSourceFreshness: true,
|
|
2676
|
+
},
|
|
2677
|
+
cwd: ".",
|
|
2678
|
+
timeoutMs: 60000,
|
|
2679
|
+
},
|
|
2680
|
+
}
|
|
2681
|
+
: undefined;
|
|
2682
|
+
const filtered = [
|
|
2683
|
+
...(contractImportShell ? [contractImportShell] : []),
|
|
2684
|
+
...tasks.filter((task) => !drop.has(task.id)),
|
|
2685
|
+
];
|
|
2686
|
+
const byId = new Map(filtered.map((task) => [task.id, task]));
|
|
2687
|
+
const remap = (deps) => {
|
|
2688
|
+
if (!deps)
|
|
2689
|
+
return [];
|
|
2690
|
+
const next = [];
|
|
2691
|
+
for (const dep of deps) {
|
|
2692
|
+
if (drop.has(dep))
|
|
2693
|
+
continue;
|
|
2694
|
+
if (byId.has(dep))
|
|
2695
|
+
next.push(dep);
|
|
2696
|
+
}
|
|
2697
|
+
return [...new Set(next)];
|
|
2698
|
+
};
|
|
2699
|
+
return filtered.map((task) => {
|
|
2700
|
+
const depends_on = remap(task.depends_on);
|
|
2701
|
+
if (task.id === "frontend-writer-admission-shell") {
|
|
2702
|
+
const admission = task.shell?.frontendWriterAdmission;
|
|
2703
|
+
return {
|
|
2704
|
+
...task,
|
|
2705
|
+
depends_on: ["frontend-contract-import-shell"],
|
|
2706
|
+
shell: admission
|
|
2707
|
+
? {
|
|
2708
|
+
...task.shell,
|
|
2709
|
+
commands: task.shell?.commands ?? [],
|
|
2710
|
+
frontendWriterAdmission: {
|
|
2711
|
+
...admission,
|
|
2712
|
+
designReviewFromNodeId: undefined,
|
|
2713
|
+
},
|
|
2714
|
+
}
|
|
2715
|
+
: task.shell,
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
return { ...task, depends_on };
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
function pruneFrontendTasksForSplitRequired(tasks) {
|
|
2722
|
+
const writerChain = new Set([
|
|
2723
|
+
"frontend-writer-admission-shell",
|
|
2724
|
+
"frontend-implement-pi",
|
|
2725
|
+
"frontend-verify-shell",
|
|
2726
|
+
"frontend-review-context-shell",
|
|
2727
|
+
"frontend-review-pi",
|
|
2728
|
+
"frontend-closeout-shell",
|
|
2729
|
+
]);
|
|
2730
|
+
return tasks.filter((task) => !writerChain.has(task.id));
|
|
2731
|
+
}
|
|
2484
2732
|
function pruneFrontendTasksForRisk(tasks, risk) {
|
|
2485
2733
|
if (risk.forceFullGates || risk.selectedRisk !== "small") {
|
|
2486
2734
|
return tasks;
|
|
2487
2735
|
}
|
|
2488
|
-
// Small topology keeps one design review and removes only the
|
|
2489
|
-
//
|
|
2490
|
-
//
|
|
2491
|
-
const drop = new Set([
|
|
2492
|
-
"frontend-plan-revision-pi",
|
|
2493
|
-
"frontend-final-design-review-pi",
|
|
2494
|
-
]);
|
|
2736
|
+
// Small topology keeps one design review and removes only the redundant
|
|
2737
|
+
// design-review node; the deterministic design-policy and writer-admission
|
|
2738
|
+
// shells consume the surviving plan directly.
|
|
2739
|
+
const drop = new Set(["frontend-design-review-pi"]);
|
|
2495
2740
|
const filtered = tasks.filter((task) => !drop.has(task.id));
|
|
2496
2741
|
const byId = new Map(filtered.map((task) => [task.id, task]));
|
|
2497
2742
|
const remap = (deps) => {
|
|
@@ -2499,11 +2744,8 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
2499
2744
|
return [];
|
|
2500
2745
|
const next = [];
|
|
2501
2746
|
for (const dep of deps) {
|
|
2502
|
-
if (dep === "frontend-
|
|
2503
|
-
if (byId.has("frontend-plan-pi"))
|
|
2504
|
-
next.push("frontend-plan-pi");
|
|
2747
|
+
if (dep === "frontend-design-review-pi")
|
|
2505
2748
|
continue;
|
|
2506
|
-
}
|
|
2507
2749
|
if (byId.has(dep) || dep === "frontend-implement-pi")
|
|
2508
2750
|
next.push(dep);
|
|
2509
2751
|
}
|
|
@@ -2511,30 +2753,27 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
2511
2753
|
};
|
|
2512
2754
|
return filtered.map((task) => {
|
|
2513
2755
|
const depends_on = remap(task.depends_on);
|
|
2514
|
-
if (task.id === "frontend-
|
|
2515
|
-
|
|
2756
|
+
if (task.id === "frontend-writer-admission-shell") {
|
|
2757
|
+
// Small topology: no design review node, so the admission shell drops
|
|
2758
|
+
// the design-review dependency and the verdict requirement.
|
|
2759
|
+
const admission = task.shell?.frontendWriterAdmission;
|
|
2516
2760
|
return {
|
|
2517
2761
|
...task,
|
|
2518
|
-
depends_on: ["frontend-
|
|
2519
|
-
|
|
2520
|
-
shell: gate
|
|
2762
|
+
depends_on: ["frontend-design-policy-shell"],
|
|
2763
|
+
shell: admission
|
|
2521
2764
|
? {
|
|
2522
2765
|
...task.shell,
|
|
2523
2766
|
commands: task.shell?.commands ?? [],
|
|
2524
|
-
|
|
2525
|
-
...
|
|
2526
|
-
|
|
2527
|
-
planFallbackFromNodeIds: [],
|
|
2528
|
-
reviewFromNodeId: "frontend-design-review-pi",
|
|
2529
|
-
reviewFallbackFromNodeIds: [],
|
|
2530
|
-
revisionPatch: false,
|
|
2767
|
+
frontendWriterAdmission: {
|
|
2768
|
+
...admission,
|
|
2769
|
+
designReviewFromNodeId: undefined,
|
|
2531
2770
|
},
|
|
2532
2771
|
}
|
|
2533
2772
|
: task.shell,
|
|
2534
2773
|
};
|
|
2535
2774
|
}
|
|
2536
2775
|
if (task.id === "frontend-implement-pi") {
|
|
2537
|
-
for (const need of ["frontend-
|
|
2776
|
+
for (const need of ["frontend-writer-admission-shell"]) {
|
|
2538
2777
|
if (byId.has(need) && !depends_on.includes(need))
|
|
2539
2778
|
depends_on.push(need);
|
|
2540
2779
|
}
|
|
@@ -2559,9 +2798,54 @@ function buildFrontendWriterNodeDefaults(input) {
|
|
|
2559
2798
|
allowedPaths: input.allowedPaths,
|
|
2560
2799
|
forbiddenPaths: input.forbiddenPaths,
|
|
2561
2800
|
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2562
|
-
writerOutcomePolicy: {
|
|
2801
|
+
writerOutcomePolicy: {
|
|
2802
|
+
type: input.writerOutcomePolicyType ?? "implementation-outcome-v1",
|
|
2803
|
+
},
|
|
2563
2804
|
};
|
|
2564
2805
|
}
|
|
2806
|
+
function resolveFrontendShapeCapsuleGenerationInput(sources) {
|
|
2807
|
+
const explicit = sources.frontendShapeTransitionCapsule == null
|
|
2808
|
+
? undefined
|
|
2809
|
+
: parseFrontendShapeTransitionCapsule(sources.frontendShapeTransitionCapsule);
|
|
2810
|
+
const autoloaded = sources.autoloadedFrontendShapeTransitionCapsule == null
|
|
2811
|
+
? undefined
|
|
2812
|
+
: parseFrontendShapeTransitionCapsule(sources.autoloadedFrontendShapeTransitionCapsule);
|
|
2813
|
+
if (explicit && autoloaded && explicit.capsuleDigest !== autoloaded.capsuleDigest) {
|
|
2814
|
+
throw new Error("explicit frontend shape transition capsule conflicts with runtime autoload");
|
|
2815
|
+
}
|
|
2816
|
+
return explicit ?? autoloaded;
|
|
2817
|
+
}
|
|
2818
|
+
/** Standard frontend DAG template (docs/templates/frontend-implementation-dag.json).
|
|
2819
|
+
* It is the topology source of truth: node set, execution order, and depends_on
|
|
2820
|
+
* come from the template; the runtime generator assembles each node's full
|
|
2821
|
+
* configuration (budgets, retry, skeleton, skills, dynamic prompt sections).
|
|
2822
|
+
* Resolution order: repoRoot copy (dev workspace) → bundled package copy. */
|
|
2823
|
+
export async function loadFrontendDagTemplate(repoRoot) {
|
|
2824
|
+
const candidates = [];
|
|
2825
|
+
if (repoRoot) {
|
|
2826
|
+
candidates.push(path.join(repoRoot, "docs", "templates", "frontend-implementation-dag.json"));
|
|
2827
|
+
}
|
|
2828
|
+
candidates.push(fileURLToPath(new URL("../../../docs/templates/frontend-implementation-dag.json", import.meta.url)));
|
|
2829
|
+
for (const candidate of candidates) {
|
|
2830
|
+
try {
|
|
2831
|
+
const parsed = JSON.parse(await readFile(candidate, "utf8"));
|
|
2832
|
+
if (!Array.isArray(parsed.tasks))
|
|
2833
|
+
continue;
|
|
2834
|
+
const tasks = parsed.tasks.filter((item) => typeof item === "object" &&
|
|
2835
|
+
item !== null &&
|
|
2836
|
+
typeof item.id === "string" &&
|
|
2837
|
+
Array.isArray(item.depends_on) &&
|
|
2838
|
+
typeof item.executor === "string");
|
|
2839
|
+
if (tasks.length === 0)
|
|
2840
|
+
continue;
|
|
2841
|
+
return { tasks };
|
|
2842
|
+
}
|
|
2843
|
+
catch {
|
|
2844
|
+
// try next candidate
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
return null;
|
|
2848
|
+
}
|
|
2565
2849
|
async function buildFrontendHybridDagFromTask(sources) {
|
|
2566
2850
|
const { taskConfig } = sources;
|
|
2567
2851
|
const mockCapability = sources.frontendMockCapability ?? {
|
|
@@ -2586,6 +2870,11 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2586
2870
|
const implementId = frontendImplementationNodeId();
|
|
2587
2871
|
const mockContextBlock = resolveFrontendMockContextBlock(frontendSources);
|
|
2588
2872
|
const capabilityContextBlock = resolveFrontendCapabilityContextBlock(frontendSources);
|
|
2873
|
+
// Freeze the design-policy dependency allowlist from the project manifest:
|
|
2874
|
+
// already-declared deps are authorized; anything else in the model's
|
|
2875
|
+
// dependency policy that looks like a package name is an unauthorized new
|
|
2876
|
+
// dependency (fail-closed at the policy shell and the plan pre-check).
|
|
2877
|
+
const declaredDependencies = await collectDeclaredDependencies(sources.repoRoot ?? process.cwd());
|
|
2589
2878
|
const frontendRisk = frontendSources.frontendRisk ??
|
|
2590
2879
|
classifyFrontendRisk({
|
|
2591
2880
|
title: taskConfig.title,
|
|
@@ -2594,138 +2883,63 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2594
2883
|
allowedPaths: taskConfig.allowedPaths,
|
|
2595
2884
|
complexity: taskConfig.complexity,
|
|
2596
2885
|
});
|
|
2886
|
+
const frontendTaskShape = resolveFrontendTaskShape({
|
|
2887
|
+
complexity: taskConfig.complexity,
|
|
2888
|
+
allowedPaths: taskConfig.allowedPaths,
|
|
2889
|
+
requirementMarkdown: sources.requirementMarkdown,
|
|
2890
|
+
constraintMarkdown: sources.constraintMarkdown ?? undefined,
|
|
2891
|
+
splitSignal: { splitRequired: false, runtimeSupportsSplit: true },
|
|
2892
|
+
taskId: sources.taskId,
|
|
2893
|
+
sourceDigest: computeFrontendShapeSourceDigest({
|
|
2894
|
+
requirementMarkdown: sources.requirementMarkdown,
|
|
2895
|
+
constraintMarkdown: sources.constraintMarkdown ?? "",
|
|
2896
|
+
}),
|
|
2897
|
+
shapeTransitionCapsule: resolveFrontendShapeCapsuleGenerationInput(sources),
|
|
2898
|
+
});
|
|
2597
2899
|
const frontendSourceBinding = buildDagSourceBinding(sources, taskConfig.taskKind);
|
|
2598
2900
|
const frontendContractSkeleton = buildFrontendImplementationContractSkeleton({
|
|
2599
2901
|
sourceBinding: frontendSourceBinding,
|
|
2600
2902
|
riskLevel: frontendRisk.selectedRisk,
|
|
2601
2903
|
targetFiles: implementPaths.writeSet,
|
|
2602
2904
|
});
|
|
2603
|
-
const frontendContractSchemaBlock = (() => {
|
|
2604
|
-
const schema = loadFrontendImplementationContractJsonSchema();
|
|
2605
|
-
return [
|
|
2606
|
-
`## Final ${FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID} JSON Schema (authoritative after runtime merge)`,
|
|
2607
|
-
schema,
|
|
2608
|
-
"",
|
|
2609
|
-
"## Runtime contract skeleton (deterministic and protected)",
|
|
2610
|
-
JSON.stringify(frontendContractSkeleton),
|
|
2611
|
-
"",
|
|
2612
|
-
"The initial planner emits an editable RFC 7386 patch against this skeleton. It MUST omit schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime merges and validates the final contract, then writes a hash-bound canonical JSON artifact; downstream review and prewrite consume that artifact path, not planner stdout.",
|
|
2613
|
-
"",
|
|
2614
|
-
"## Forbidden fields (these are NOT in the schema; do not emit)",
|
|
2615
|
-
"- schemaId",
|
|
2616
|
-
"- targetFiles",
|
|
2617
|
-
"- requirementCoverage",
|
|
2618
|
-
"",
|
|
2619
|
-
"## Critical rules",
|
|
2620
|
-
"- verificationTargets is a TOP-LEVEL required array",
|
|
2621
|
-
"- uiStates items use name/applicable/expectedBehavior/implementationTargets/verificationTargetIds/notApplicableReason",
|
|
2622
|
-
"- Use uiStates: [] for frontend logic changes with no user-visible UI state. Do not invent UI states.",
|
|
2623
|
-
"- For applicable=true, provide non-empty expectedBehavior plus non-empty implementationTargets and verificationTargetIds. For applicable=false, provide non-empty notApplicableReason and omit expectedBehavior instead of emitting an empty string.",
|
|
2624
|
-
"- mockApi.productionDefaultOff must always be true (including strategy: not-needed)",
|
|
2625
|
-
"- All implementation files, verification files, symbols, and commands must be discovered from the current target workspace and current task. Never copy paths, symbols, or commands from the loop-agent repository, an example task, or prior run output.",
|
|
2626
|
-
"- Use relative POSIX paths rooted at the target workspace. Do not assume a particular src/test directory layout; preserve the target project's actual app/, packages/, spec/, __tests__, or other layout.",
|
|
2627
|
-
"",
|
|
2628
|
-
"## Bad / Good contract field examples",
|
|
2629
|
-
"",
|
|
2630
|
-
"### verificationTargets - BAD (invented commandLabel, missing file):",
|
|
2631
|
-
'{"id":"vt-1","type":"static","commandLabel":"lint","file":"","requirementIds":["AC-001"],"uiStates":[]} <-- REJECTED: commandLabel not in frozen command set; empty file path',
|
|
2632
|
-
"",
|
|
2633
|
-
'### verificationTargets - GOOD (real frozen label, real file):',
|
|
2634
|
-
'{"id":"vt-1","type":"static","commandLabel":"npm run typecheck","file":"tsconfig.json","requirementIds":["AC-001"],"uiStates":[]} <-- Matches frozen command set; real file path',
|
|
2635
|
-
"",
|
|
2636
|
-
"### requirements - BAD (missing expectedOutcome):",
|
|
2637
|
-
'{"id":"AC-001","expectedOutcome":"","implementationTargets":["src/app.tsx"],"verificationTargetIds":["vt-1"]} <-- REJECTED: empty expectedOutcome',
|
|
2638
|
-
"",
|
|
2639
|
-
"### requirements - GOOD (concrete expectedOutcome):",
|
|
2640
|
-
'{"id":"AC-001","expectedOutcome":"TypeScript compilation exits with code 0 and produces no errors in dist/","implementationTargets":["src/app.tsx"],"verificationTargetIds":["vt-1"]}',
|
|
2641
|
-
"",
|
|
2642
|
-
"### interactions - BAD (empty trigger/expectedBehavior):",
|
|
2643
|
-
'{"name":"save-click","trigger":"","expectedBehavior":"","implementationTargets":["src/button.tsx"],"verificationTargetIds":["vt-3"]} <-- REJECTED: empty trigger and expectedBehavior',
|
|
2644
|
-
"",
|
|
2645
|
-
"### interactions - GOOD:",
|
|
2646
|
-
'{"name":"save-click","trigger":"User clicks the Save button in the editor toolbar","expectedBehavior":"POST /api/save is called with editor content; success toast appears; button enters disabled+spinner state until response","implementationTargets":["src/editor/save-button.tsx"],"verificationTargetIds":["vt-3"]}',
|
|
2647
|
-
"",
|
|
2648
|
-
"### uiStates - BAD (applicable=true but missing expectedBehavior):",
|
|
2649
|
-
'{"name":"loading","applicable":true,"expectedBehavior":"","implementationTargets":[],"verificationTargetIds":[]} <-- REJECTED: applicable UI state requires non-empty expectedBehavior, implementationTargets, and verificationTargetIds',
|
|
2650
|
-
"",
|
|
2651
|
-
"### uiStates - GOOD (applicable=true with complete fields):",
|
|
2652
|
-
'{"name":"loading","applicable":true,"expectedBehavior":"Skeleton placeholder visible while data fetches; aria-busy=true on the list container","implementationTargets":["src/dashboard/list-view.tsx"],"verificationTargetIds":["vt-3"]}',
|
|
2653
|
-
"",
|
|
2654
|
-
"### uiStates - BAD (applicable=false without notApplicableReason):",
|
|
2655
|
-
'{"name":"dark-mode","applicable":false} <-- REJECTED: non-applicable UI state requires notApplicableReason',
|
|
2656
|
-
"",
|
|
2657
|
-
"### uiStates - GOOD (applicable=false with reason):",
|
|
2658
|
-
'{"name":"dark-mode","applicable":false,"notApplicableReason":"Dark mode toggle is out of scope for this task; only light theme is targeted"}',
|
|
2659
|
-
"",
|
|
2660
|
-
"### mockApi.endpoints - BAD (strategy=native but empty endpoints):",
|
|
2661
|
-
'{"strategy":"native","productionDefaultOff":true,"activation":"env flag","endpoints":[]} <-- REJECTED: native strategy requires at least one endpoint with method, path, fixture, and consumer',
|
|
2662
|
-
"",
|
|
2663
|
-
"### mockApi.endpoints - GOOD (strategy=native with complete endpoint):",
|
|
2664
|
-
'{"strategy":"native","productionDefaultOff":true,"activation":"VITE_ENABLE_MOCK=true","endpoints":[{"method":"GET","path":"/api/users","fixture":"mocks/fixtures/users.json","consumer":"src/api/users.ts"}]}',
|
|
2665
|
-
"",
|
|
2666
|
-
"### optional plan fields - GOOD (all optional; omit when absent):",
|
|
2667
|
-
'{"implementationSteps":["confirm contract","sync tests"],"stylingStrategy":"reuse existing design tokens","dependencyPolicy":"no new runtime deps","residualRisks":["browser a11y not-run"],"realIntegrationGap":"FE-TEST owns live HTTP"}',
|
|
2668
|
-
"",
|
|
2669
|
-
"### optional plan fields - BAD (present-but-empty strings are rejected):",
|
|
2670
|
-
'{"stylingStrategy":"","dependencyPolicy":""} <-- REJECTED: optional string fields must be non-empty when present; omit them instead',
|
|
2671
|
-
"",
|
|
2672
|
-
"### uiComponentChoices - GOOD (specified with precise spec hit):",
|
|
2673
|
-
'{"purpose":"primary action button","component":"Button","decision":"specified","specReference":{"path":"openspec/schemas/button.md","section":"Variants","line":12},"rationale":"spec mandates Button for primary actions"}',
|
|
2674
|
-
"",
|
|
2675
|
-
"### uiComponentChoices - GOOD (new with deviation rationale; specReference null):",
|
|
2676
|
-
'{"purpose":"loading skeleton","component":"SkeletonCard","decision":"new","specReference":null,"rationale":"no spec or existing component covers skeleton; deviation pending design-review approval"}',
|
|
2677
|
-
"",
|
|
2678
|
-
"### uiComponentChoices - BAD (specified without specReference, or new with specReference):",
|
|
2679
|
-
'{"purpose":"primary action","component":"MyButton","decision":"specified","specReference":null,"rationale":"..."} <-- REJECTED: specified requires specReference',
|
|
2680
|
-
'{"purpose":"primary action","component":"MyButton","decision":"new","specReference":{"path":"openspec/schemas/button.md","section":"","line":null},"rationale":"..."} <-- REJECTED: new must not carry specReference',
|
|
2681
|
-
].join("\n");
|
|
2682
|
-
})();
|
|
2683
2905
|
const frontendContractFieldSummary = [
|
|
2684
2906
|
"## Contract field summary (authoritative JSON; no plan prose)",
|
|
2685
|
-
"
|
|
2907
|
+
"frontend-plan-pi records typed facts; frontend-design-policy-shell applies the runtime skeleton, validates, and materializes the canonical full contract JSON supplied here. There is no separate plan prose authority. Review these fields:",
|
|
2686
2908
|
"- requirements[]: id, expectedOutcome, implementationTargets, verificationTargetIds, evidenceGap",
|
|
2687
2909
|
"- uiStates[]: name, applicable, expectedBehavior, implementationTargets, verificationTargetIds, notApplicableReason",
|
|
2688
2910
|
"- interactions[]: name, trigger, expectedBehavior, implementationTargets, verificationTargetIds",
|
|
2689
|
-
"- targets:
|
|
2911
|
+
"- targets: routes, publicApiChanges (files are runtime-owned)",
|
|
2690
2912
|
"- mockApi: strategy, productionDefaultOff, activation, endpoints[]",
|
|
2691
|
-
"- verificationTargets[]: id, type, commandLabel, file,
|
|
2692
|
-
"- designEvidence: source, paths, conflicts; evidenceGaps[]",
|
|
2693
|
-
"- optional:
|
|
2913
|
+
"- verificationTargets[]: id (stable test-title trace token for non-static targets), type, commandLabel, file, requirementIds, uiStates",
|
|
2914
|
+
"- designEvidence: source, paths, conflicts; evidenceGaps[] (optional)",
|
|
2915
|
+
"- optional: stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap",
|
|
2694
2916
|
"- uiComponentChoices[]: purpose, component, decision (specified|reuse-existing|new), specReference { path, section, line } | null, rationale",
|
|
2695
2917
|
"Do not require or read a separate plan prose section; the contract JSON is the only plan surface.",
|
|
2696
2918
|
].join("\n");
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2919
|
+
// Do not carry every attachment through the whole frontend pipeline. The
|
|
2920
|
+
// contract node is the sole requirements/materials synthesis point; scout
|
|
2921
|
+
// and plan only need the canonical request plus constraints, while design
|
|
2922
|
+
// review consumes the materialized contract and only needs source provenance.
|
|
2923
|
+
// This prevents attachment content from accumulating on later review calls.
|
|
2924
|
+
const sourceContexts = {
|
|
2925
|
+
contract: [buildSourceContextBlock(sources), capabilityContextBlock]
|
|
2926
|
+
.filter(Boolean)
|
|
2927
|
+
.join("\n\n"),
|
|
2928
|
+
scout: [
|
|
2929
|
+
buildSourceContextBlock(sources, { includeReferenceDocuments: false }),
|
|
2930
|
+
capabilityContextBlock,
|
|
2931
|
+
]
|
|
2932
|
+
.filter(Boolean)
|
|
2933
|
+
.join("\n\n"),
|
|
2934
|
+
designReview: buildSourceContextBlock(sources, {
|
|
2935
|
+
includeRequirementExcerpt: false,
|
|
2936
|
+
includeConstraintExcerpt: false,
|
|
2937
|
+
includeReferenceDocuments: false,
|
|
2938
|
+
}),
|
|
2939
|
+
};
|
|
2703
2940
|
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
2704
2941
|
mockCapability.verifyCommands.length > 0;
|
|
2705
2942
|
const requirementIds = frontendSourceBinding.requirementIds;
|
|
2706
|
-
const requirementCoverageInstruction = requirementIds.length > 0
|
|
2707
|
-
? [
|
|
2708
|
-
`## Requirement Coverage (per-AC echo with bad/good examples)`,
|
|
2709
|
-
`For each requirement ID below, echo the ID verbatim and confirm: expectedOutcome (user-observable or logic-observable), implementation targets (files), and verification targets (commandLabel + file).`,
|
|
2710
|
-
`Do not skip any ID. Use the bad/good patterns below as reference for each field.`,
|
|
2711
|
-
``,
|
|
2712
|
-
`Bad example (empty expectedOutcome, empty targets -- REJECTED at contract materialization):`,
|
|
2713
|
-
`- AC-001: expectedOutcome="" implementationTargets=[] verificationTargets=[]`,
|
|
2714
|
-
``,
|
|
2715
|
-
`Good example (concrete expectedOutcome, real files, real verification targets):`,
|
|
2716
|
-
`- AC-001: expectedOutcome="TypeScript compilation exits with code 0 and produces no errors in dist/" implementationTargets=["src/app.tsx"] verificationTargets=["vt-typecheck":"npm run typecheck","tsconfig.json"]`,
|
|
2717
|
-
``,
|
|
2718
|
-
...requirementIds.map((id) => `- ${id}: [expectedOutcome] [implementation files] [verification targets]`),
|
|
2719
|
-
``,
|
|
2720
|
-
`Every requirement MUST have a non-empty expectedOutcome. Every interaction MUST have non-empty trigger and expectedBehavior. UI states with applicable=true MUST have non-empty expectedBehavior. Empty strings or omitted fields for these will cause contract rejection.`,
|
|
2721
|
-
].join("\n")
|
|
2722
|
-
: "";
|
|
2723
|
-
const verificationTargetFileInstruction = [
|
|
2724
|
-
`## Verification target file semantics`,
|
|
2725
|
-
`verificationTargets[].file is the code file that the target verifies (the file the writer changes), NOT where the command is defined.`,
|
|
2726
|
-
`Non-static targets (type unit/component/integration/mock) MUST set file to a concrete code file inside the implementation writeSet (task allowedPaths); the prewrite gate rejects any non-static target whose file falls outside the writeSet.`,
|
|
2727
|
-
`Command-level checks that run project-wide (all tests, typecheck, build, governance) MUST use type "static" and must NOT be bound as non-static targets with file=package.json/tsconfig.json/vite.config.ts/scripts/*. Static targets are exempt from the writeSet containment check.`,
|
|
2728
|
-
].join("\n");
|
|
2729
2943
|
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
2730
2944
|
const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
|
|
2731
2945
|
const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
|
|
@@ -2735,15 +2949,15 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2735
2949
|
? [`See 执行约束.md in task source (${sources.taskId})`]
|
|
2736
2950
|
: []),
|
|
2737
2951
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
2738
|
-
"Frontend
|
|
2739
|
-
"
|
|
2740
|
-
"
|
|
2741
|
-
"
|
|
2952
|
+
"Frontend design policy (frontend-design-policy-shell) and writer admission (frontend-writer-admission-shell) are the only write authorization; the writer runs only after both materialized the canonical contract and admitted a concrete writeSet.",
|
|
2953
|
+
"Design review verdict is consumed only as admission data input; it never drives branch selection.",
|
|
2954
|
+
"Verification failure is terminal for the run: frontend-verify-shell fails closed, routes to recovery, and never selects a same-run repair branch.",
|
|
2955
|
+
"Frontend review is decided exclusively by the committed typed terminal tools (approve_review / request_review_changes); response-text JSON verdicts and first-line VERDICT markers carry no control-flow weight.",
|
|
2742
2956
|
"Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
|
|
2743
2957
|
"Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
|
|
2744
2958
|
"Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
|
|
2745
2959
|
"frontend-implementation DAGs must complete deterministic static verification before final review. Behavior verification is also required when the task declares a behavior entrypoint or the implementation contract contains a non-static verification target; static-only contracts must map every target to the declared static entrypoint.",
|
|
2746
|
-
"
|
|
2960
|
+
"Frontend closeout renders only from committed facts; a weak status (failed / not-run / baseline-debt / mock-backed / pending) can never be rewritten into a stronger one (passed / real-integrated).",
|
|
2747
2961
|
`Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
|
|
2748
2962
|
frontendRisk.forceFullGates
|
|
2749
2963
|
? "High-risk or supervised: keep full design gates; do not weaken write boundaries."
|
|
@@ -2874,7 +3088,56 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2874
3088
|
frontendMockStrategyMustBeNotNeeded(frontendSources)) {
|
|
2875
3089
|
advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
|
|
2876
3090
|
}
|
|
2877
|
-
const openspecGate = resolveFrontendOpenspecGateConfig(sources);
|
|
3091
|
+
const openspecGate = await resolveFrontendOpenspecGateConfig(sources);
|
|
3092
|
+
const requiresOpenspecClassification = openspecGate.openspecPolicy === "cited" &&
|
|
3093
|
+
openspecGate.openspecCandidatePaths.length > 0;
|
|
3094
|
+
// Prompt 内联的候选只保留与任务相关的子集:mandatory(任务显式声明/
|
|
3095
|
+
// 引用)始终保留;scan-strict 候选按路径段是否命中任务源关键词过滤。
|
|
3096
|
+
// runtime 的选型/read 门禁仍消费完整 openspecCandidatePaths——这里只
|
|
3097
|
+
// 减小 prompt 体积,不改变门禁语义;未提及的候选 runtime 默认 irrelevant。
|
|
3098
|
+
const promptCandidatePaths = filterRelevantOpenspecCandidates({
|
|
3099
|
+
candidates: openspecGate.openspecCandidateSummaries.map((candidate) => candidate.path),
|
|
3100
|
+
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3101
|
+
sourceMarkdown: [
|
|
3102
|
+
sources.requirementMarkdown,
|
|
3103
|
+
sources.constraintMarkdown ?? "",
|
|
3104
|
+
].join("\n"),
|
|
3105
|
+
maxCandidates: 24,
|
|
3106
|
+
});
|
|
3107
|
+
const candidateByPath = new Map(openspecGate.openspecCandidateSummaries.map((candidate) => [
|
|
3108
|
+
candidate.path,
|
|
3109
|
+
candidate,
|
|
3110
|
+
]));
|
|
3111
|
+
// Preserve the filter's mandatory/relevance order. Sorting then slicing here
|
|
3112
|
+
// used to be able to drop a mandatory path after it passed the Top-K filter.
|
|
3113
|
+
const promptCandidates = promptCandidatePaths.flatMap((candidatePath) => {
|
|
3114
|
+
const candidate = candidateByPath.get(candidatePath);
|
|
3115
|
+
return candidate
|
|
3116
|
+
? [
|
|
3117
|
+
{
|
|
3118
|
+
path: candidate.path,
|
|
3119
|
+
kind: candidate.kind,
|
|
3120
|
+
score: candidate.score,
|
|
3121
|
+
reasons: candidate.reasons,
|
|
3122
|
+
source: candidate.source,
|
|
3123
|
+
},
|
|
3124
|
+
]
|
|
3125
|
+
: [];
|
|
3126
|
+
});
|
|
3127
|
+
const openspecSelectionContext = JSON.stringify({
|
|
3128
|
+
schemaVersion: 1,
|
|
3129
|
+
schemaId: "frontend-openspec-selection-v1",
|
|
3130
|
+
candidates: promptCandidates,
|
|
3131
|
+
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3132
|
+
});
|
|
3133
|
+
const scopedOpenspecContext = promptCandidates.length > 0
|
|
3134
|
+
? [
|
|
3135
|
+
"## Task-relevant OpenSpec Top-K (generation frozen)",
|
|
3136
|
+
`Policy: ${openspecGate.openspecPolicy}. This is the bounded prompt index; the deterministic gate retains ${openspecGate.openspecCandidatePaths.length} frozen candidates.`,
|
|
3137
|
+
...promptCandidates.map((candidate) => `- ${openspecGate.openspecMandatoryPaths.includes(candidate.path) ? "mandatory" : "candidate"}: ${candidate.path} (${candidate.kind}; ${candidate.source})`),
|
|
3138
|
+
"Apply or cite only paths relevant to the concrete contract. Report applied rules with path/section/line and surface conflicts or missing specifications; unlisted candidates default to irrelevant unless the deterministic gate requires them.",
|
|
3139
|
+
].join("\n")
|
|
3140
|
+
: "";
|
|
2878
3141
|
// Generation-frozen component/theme specification bucket (ADR 0016). Derived
|
|
2879
3142
|
// from the classified component/theme/rule.components buckets; the prewrite
|
|
2880
3143
|
// gate consumes it to enforce uiComponentChoices presence and specReference
|
|
@@ -2891,7 +3154,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2891
3154
|
advisories.push("openspec 策略 cited:契约声明的 requiredReadPaths 与任务源引用均为空,prewrite gate 不强制读取 openspec;如需增强规范门禁,请在 task.json.frontendOpenspec.requiredReadPaths 声明必读路径或在任务源中显式引用 openspec 文件。");
|
|
2892
3155
|
}
|
|
2893
3156
|
else {
|
|
2894
|
-
advisories.push(`openspec 策略 cited:候选 ${openspecGate.openspecCandidatePaths.length} 个(declared ${openspecGate.openspecCandidateSources.declared.length} / task-source-cited ${openspecGate.openspecCandidateSources.taskSourceCited.length}),plan
|
|
3157
|
+
advisories.push(`openspec 策略 cited:候选 ${openspecGate.openspecCandidatePaths.length} 个(declared ${openspecGate.openspecCandidateSources.declared.length} / task-source-cited ${openspecGate.openspecCandidateSources.taskSourceCited.length}),plan 必须在 typed decision ledger 的 uiComponentChoices.specReference 中声明并通过真实 read 事件佐证。`);
|
|
2895
3158
|
}
|
|
2896
3159
|
}
|
|
2897
3160
|
else {
|
|
@@ -2928,13 +3191,25 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2928
3191
|
writePolicy: "read-only",
|
|
2929
3192
|
allowedPaths: readOnlyPaths,
|
|
2930
3193
|
forbiddenPaths,
|
|
2931
|
-
skills:
|
|
2932
|
-
outputContract: "Markdown contract with
|
|
3194
|
+
skills: FRONTEND_CONTRACT_SKILLS,
|
|
3195
|
+
outputContract: "Typed requirement facts plus a concise Markdown contract. Submit through the incremental typed tools record_requirement / record_constraint / record_evidence_expectation / record_handoff_intent / record_open_question / record_split_proposal / record_openspec_selection, then call finalize_contract exactly once. Requirements use stable REQ/BR/AC identifiers with source spans and a disposition (explicit | repository-resolvable | assumption | blocking); each requirement registers evidence expectations across static/behavior/Mock/real-integration (required | optional | not-applicable), and UI-visible or interactive requirements register a non-blocking frontend-test handoff intent. End finalize_contract with a single contract disposition of ready | ready-with-assumptions | blocked. Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations; do not fix target files, components, or implementation methods as requirements. When OpenSpec candidates exist, classify only the ones you actually use: call record_openspec_selection once per required/relevant path; never enumerate irrelevant candidates (unmentioned defaults to irrelevant) and never emit a fenced selection JSON. No file writes.",
|
|
2933
3196
|
subtask_prompt: [
|
|
2934
|
-
"
|
|
2935
|
-
"
|
|
3197
|
+
"OUTPUT BUDGET DISCIPLINE (hard requirement, extreme-environment safe): the provider output window is small — NEVER attempt to emit the whole contract in one response; a single large JSON dump will be truncated and rejected. Incremental submission through the typed tools is the ONLY supported output mode. Start submitting with the FIRST tool call: after each read, call record_requirement for the requirements you have already confirmed, one or a few per call. Every tool-call round MUST make progress by submitting at least one record_* fact. Do not re-read the same source file that is already materialized in this session; read each file at most once.",
|
|
3198
|
+
"Read task source and produce a concise frontend implementation contract as typed requirement facts plus narrative Markdown.",
|
|
3199
|
+
"Assign each requirement the SAME id as the ledger canonical requirement it covers (sourceBinding.requirementIds, e.g. AC-001) — do NOT invent new REQ/BR prefixed ids for canonical requirements: the compiled contract must match the ledger canonical requirement ids exactly or schema validation rejects it (unknown requirement id). Requirements use the canonical id with a source span (task-source section or repository file:line). Label each requirement's disposition as explicit | repository-resolvable | assumption | blocking; a blocking requirement must name its owner (human-decision or external-state) and evidence refs.",
|
|
3200
|
+
"Source fidelity ledger: when the DAG sourceBinding carries a requirement→fragment mapping (requirementToFragments, e.g. REQ-SRC-* ids from the managed ledger), each record_requirement MUST declare the fragments that requirement is bound to: set sourceFragmentIds to the mapped fragment ids (the authoritative provenance evidence the design policy verifies). sourceRefs (fragment→path display refs) are optional — declare them only when you have the exact path from the materialized source; otherwise omit them rather than inventing paths. Declare exactly what the ledger binds — do not invent ids, do not omit them, and do not re-derive them from prose. A requirement that the ledger binds but the contract omits (or fabricates) fails writer admission.",
|
|
3201
|
+
"Register evidence expectations for each requirement across static, behavior, Mock, and real integration as required | optional | not-applicable; required must follow from user requirements, task risk, or project governance, never from model convenience. For UI-visible or interactive requirements, register a non-blocking frontend-test handoff intent.",
|
|
3202
|
+
"Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations. Do not fix target files, components, styling, or implementation methods as requirements; leave those to Scout and Plan.",
|
|
3203
|
+
"If the task is too large for one bounded writer, record a task split proposal instead of silently widening scope.",
|
|
3204
|
+
"End the contract with a single disposition: ready, ready-with-assumptions (bounded assumptions that do not change product behavior), or blocked.",
|
|
3205
|
+
scopedOpenspecContext,
|
|
3206
|
+
...(requiresOpenspecClassification ? [
|
|
3207
|
+
"Classify OpenSpec candidates incrementally while contracting — only the ones you actually use. Call record_openspec_selection once per path with disposition required (must be read and cited by the plan) or relevant (may inform planning). Never call it for irrelevant candidates and never list them: candidates you do not mention are treated as irrelevant by the runtime. Explicit task declarations / source citations are already required and must-read regardless; you never need to re-declare them.",
|
|
3208
|
+
"Mandatory paths are enforced by the runtime from the frozen task configuration — do not enumerate them, do not downgrade them.",
|
|
3209
|
+
openspecSelectionContext,
|
|
3210
|
+
] : []),
|
|
2936
3211
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2937
|
-
|
|
3212
|
+
sourceContexts.contract,
|
|
2938
3213
|
].join("\n\n"),
|
|
2939
3214
|
},
|
|
2940
3215
|
{
|
|
@@ -2944,17 +3219,19 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2944
3219
|
executor: "pi",
|
|
2945
3220
|
complexity: mapTaskComplexity(taskConfig.complexity),
|
|
2946
3221
|
writePolicy: "read-only",
|
|
3222
|
+
retryPolicy: FRONTEND_SCOUT_COMPLETENESS_RETRY_POLICY,
|
|
2947
3223
|
allowedPaths: readOnlyPaths,
|
|
2948
3224
|
forbiddenPaths,
|
|
2949
|
-
skills:
|
|
2950
|
-
outputContract: "Markdown scout report
|
|
3225
|
+
skills: FRONTEND_SCOUT_SKILLS,
|
|
3226
|
+
outputContract: "Markdown scout report covering target surface and design evidence (frontend stack, routes, components, styling system, existing design conventions, state/data flow, test entry points, reuse opportunities, risks). Submit through the incremental evidence tools record_target_surface / record_design_evidence. A complete target surface is mandatory before Plan; if it cannot be proven, commit blocked with unresolved paths so this Scout node retries rather than shifting discovery to Plan. No fixed TARGET_SURFACE section title is required — target surface and design evidence are reported as committed typed facts. No file writes.",
|
|
2951
3227
|
subtask_prompt: [
|
|
2952
3228
|
"Inspect frontend code, routing, components, styles, package scripts, and tests.",
|
|
2953
3229
|
"Return code and design observations, existing reuse opportunities, and verification entry points.",
|
|
2954
|
-
"
|
|
3230
|
+
"Report target surface and design evidence as facts (no fixed section title required): completeness, entrypoint, routeOrMount, implementationPaths, testPaths, dataSource, allowedPathConflicts, unresolvedPaths. Use repository-relative POSIX paths. A complete surface must name at least one proven entrypoint, implementation path, or test path and set unresolvedPaths to []; if any target ownership remains unknown, commit completeness=blocked with every unresolved path instead of guessing. implementationPaths and testPaths must name the existing files/directories that actually own the requested behavior; allowedPathConflicts must list every discovered path not covered by task allowedPaths, or [] when none exists.",
|
|
2955
3231
|
"Derive all file paths from this target workspace. Do not assume the project uses src/, test/, React, or the loop-agent repository layout.",
|
|
2956
3232
|
"Read-only: do not modify repository files.",
|
|
2957
|
-
|
|
3233
|
+
sourceContexts.scout,
|
|
3234
|
+
scopedOpenspecContext,
|
|
2958
3235
|
].join("\n\n"),
|
|
2959
3236
|
},
|
|
2960
3237
|
{
|
|
@@ -2964,239 +3241,196 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2964
3241
|
executor: "pi",
|
|
2965
3242
|
complexity: "MED",
|
|
2966
3243
|
writePolicy: "read-only",
|
|
2967
|
-
|
|
2968
|
-
|
|
3244
|
+
retryPolicy: FRONTEND_PLAN_LADDER_RETRY_POLICY,
|
|
3245
|
+
allowedPaths: readOnlyPaths,
|
|
3246
|
+
forbiddenPaths,
|
|
3247
|
+
skills: FRONTEND_PLAN_SKILLS,
|
|
2969
3248
|
structuredContractOutput: {
|
|
2970
|
-
schemaId:
|
|
3249
|
+
schemaId: "frontend-implementation-contract-plan-patch-v1",
|
|
2971
3250
|
retryOnInvalid: true,
|
|
2972
3251
|
skeleton: frontendContractSkeleton,
|
|
2973
3252
|
},
|
|
2974
|
-
|
|
2975
|
-
forbiddenPaths,
|
|
2976
|
-
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2977
|
-
outputContract: "JSON-only patch output: one-line lead-in, then exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. Immediately after it, append exactly one ```openspec-citations``` fenced citation block. The runtime applies the patch, validates it, and writes a hash-bound canonical JSON artifact for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.",
|
|
3253
|
+
outputContract: "Typed decision patch only: map frozen requirements to implementation/verification targets and select the needed component, state, data/Mock, styling, and dependency decisions. Use only the record_* tools needed to express those decisions, then call finalize_plan exactly once. Contract owns requirement semantics; Scout owns repository discovery; deterministic runtime owns schema, protected fields, path containment, and command validation. No Markdown narrative or file writes.",
|
|
2978
3254
|
subtask_prompt: [
|
|
2979
|
-
"
|
|
2980
|
-
"
|
|
2981
|
-
"
|
|
2982
|
-
"
|
|
2983
|
-
|
|
2984
|
-
"
|
|
2985
|
-
"
|
|
2986
|
-
"
|
|
2987
|
-
requirementCoverageInstruction,
|
|
2988
|
-
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2989
|
-
verificationTargetFileInstruction,
|
|
2990
|
-
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
3255
|
+
"Plan only the delta between the frozen frontend-contract-pi facts and frontend-scout-pi target surface. Do not reinterpret the task, repeat requirements, search the repository, or choose implementation order.",
|
|
3256
|
+
"Record only: requirement-to-file/verification coverage; component/styling choices; applicable UI state and interaction behavior; data/Mock strategy; and a dependency policy or genuine evidence gap. Reuse Scout paths. If scope is missing, record a blocking gap instead of inventing a path.",
|
|
3257
|
+
"Use the typed tool schemas as the field contract. Runtime owns schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff, aliases, command allowlisting, path containment, and final validation; do not restate those rules or emit a full JSON contract.",
|
|
3258
|
+
`Cover each frozen requirement ID exactly once: ${requirementIds.join(", ") || "(none)"}. Bind every verification target to a listed frozen command and a Scout-confirmed file. Define behavior-level non-static targets: one target may cover multiple related requirementIds when one observable test behavior proves them together; do not mechanically create one target per requirement. A non-static target id is the stable machine trace token. Use static targets for project-wide commands. Static targets are traced by file and command only.`,
|
|
3259
|
+
...(requiresOpenspecClassification ? ["When a component choice uses an OpenSpec selection, cite that selection; otherwise do not classify unrelated candidates."] : []),
|
|
3260
|
+
"Call finalize_plan exactly once after the necessary typed facts. Return no Markdown narrative.",
|
|
3261
|
+
"TOOL-ONLY PLAN: Do not read Contract/Scout stdout, task sources, or Scout-confirmed target files. Contract and Scout already own evidence discovery; use the injected upstream facts, record a genuine evidence gap when those facts are insufficient, and start committing record_* facts immediately. For decision=new, pass sourceRequirementIds to record_component_choice; runtime derives the exact PRD citation from the frozen ledger.",
|
|
3262
|
+
"Output budget protocol (hard, max output <=16K per turn): never enumerate-reason the whole requirement list before your first record_* call — that reasoning burns the entire output budget and the attempt dies with zero committed facts. Process requirements in order: think about ONE requirement briefly, immediately emit its record calls (up to 5 per message), then move to the next. If your budget runs low, stop recording and call finalize_plan with what is committed — the retry ladder continues the remainder in a fresh session.",
|
|
2991
3263
|
fixedVerificationContext,
|
|
2992
|
-
|
|
3264
|
+
scopedOpenspecContext,
|
|
2993
3265
|
mockContextBlock,
|
|
2994
|
-
|
|
3266
|
+
frontendContractFieldSummary,
|
|
2995
3267
|
frontendComponentConformanceInstruction,
|
|
2996
|
-
openspecCitationInstruction,
|
|
2997
3268
|
].join("\n\n"),
|
|
2998
3269
|
},
|
|
2999
3270
|
{
|
|
3000
|
-
id: "frontend-design-
|
|
3271
|
+
id: "frontend-design-policy-shell",
|
|
3001
3272
|
depends_on: ["frontend-plan-pi"],
|
|
3002
|
-
role: "
|
|
3003
|
-
executor: "
|
|
3004
|
-
complexity: "
|
|
3273
|
+
role: "verifier",
|
|
3274
|
+
executor: "shell",
|
|
3275
|
+
complexity: "LOW",
|
|
3005
3276
|
writePolicy: "read-only",
|
|
3006
3277
|
allowedPaths: readOnlyPaths,
|
|
3007
3278
|
forbiddenPaths,
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3279
|
+
outputContract: "Deterministic design policy: materialize the canonical frontend implementation contract from the plan patch, enforce requirement-id retention, Mock strategy/frozen-command binding, writeSet containment, source freshness, and openspec invariants, then evaluate the design policy (write policy result).",
|
|
3280
|
+
subtask_prompt: "Materialize the canonical contract and fail closed unless the deterministic design policy approves. The design verdict is not available at this stage; the writer admission shell enforces it.",
|
|
3281
|
+
shell: {
|
|
3282
|
+
commands: [],
|
|
3283
|
+
frontendDesignPolicy: {
|
|
3284
|
+
schemaVersion: 1,
|
|
3285
|
+
planFromNodeId: "frontend-plan-pi",
|
|
3286
|
+
requiredRequirementIds: requirementIds,
|
|
3287
|
+
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
|
|
3288
|
+
frontendMockStrategyMustBeNotNeeded(frontendSources)
|
|
3289
|
+
? ["not-needed"]
|
|
3290
|
+
: taskConfig.frontendMock?.policy === "required"
|
|
3291
|
+
? [
|
|
3292
|
+
"native",
|
|
3293
|
+
"browser-intercept",
|
|
3294
|
+
"request-adapter",
|
|
3295
|
+
]
|
|
3296
|
+
: [
|
|
3297
|
+
"native",
|
|
3298
|
+
"browser-intercept",
|
|
3299
|
+
"request-adapter",
|
|
3300
|
+
"not-needed",
|
|
3301
|
+
],
|
|
3302
|
+
mockCommandLabels: mockVerifyEvidence?.commandLabels ?? [],
|
|
3303
|
+
artifactName: "frontend-implementation-contract.json",
|
|
3304
|
+
outputDir: "contracts",
|
|
3305
|
+
requireSourceFreshness: true,
|
|
3306
|
+
implementationWriteSet: implementPaths.writeSet,
|
|
3307
|
+
openspecPolicy: openspecGate.openspecPolicy,
|
|
3308
|
+
openspecSpecRoots: taskConfig.frontendOpenspec?.specRoots ?? [
|
|
3309
|
+
...DEFAULT_FRONTEND_SPEC_ROOTS,
|
|
3310
|
+
],
|
|
3311
|
+
...(requiresOpenspecClassification
|
|
3312
|
+
? { openspecSelectionNodeId: "frontend-contract-pi" }
|
|
3313
|
+
: {}),
|
|
3314
|
+
openspecMandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3315
|
+
openspecCandidateSources: {
|
|
3316
|
+
declared: openspecGate.openspecCandidateSources.declared,
|
|
3317
|
+
taskSourceCited: openspecGate.openspecCandidateSources.taskSourceCited,
|
|
3318
|
+
scanStrict: openspecGate.openspecCandidateSources.scanStrict,
|
|
3319
|
+
},
|
|
3320
|
+
openspecCandidatePaths: openspecGate.openspecCandidatePaths,
|
|
3321
|
+
...(openspecGate.openspecCandidateSnapshots
|
|
3322
|
+
? {
|
|
3323
|
+
openspecCandidateSnapshots: openspecGate.openspecCandidateSnapshots,
|
|
3324
|
+
}
|
|
3325
|
+
: {}),
|
|
3326
|
+
componentSpecCandidatePaths,
|
|
3327
|
+
allowedDependencies: declaredDependencies,
|
|
3328
|
+
},
|
|
3329
|
+
cwd: ".",
|
|
3330
|
+
timeoutMs: 60000,
|
|
3037
3331
|
},
|
|
3038
|
-
allowedPaths: readOnlyPaths,
|
|
3039
|
-
forbiddenPaths,
|
|
3040
|
-
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
3041
|
-
outputContract: "When the initial design review requests revision, return a one-line lead-in followed by exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively). Immediately after it, append exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only; this node compiles the patch onto the original canonical artifact and writes a hash-bound revised contract. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block or raw JSON. No file writes.",
|
|
3042
|
-
subtask_prompt: [
|
|
3043
|
-
"Consume frontend-plan-pi (original contract JSON) and frontend-design-review-pi (first design review findings).",
|
|
3044
|
-
"This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce an RFC 7386 merge-patch delta against the original contract JSON that addresses every Required Plan Correction from the design findings.",
|
|
3045
|
-
"The patch delta may update editable contract fields such as requirements, implementationSteps, targets.routes/publicApiChanges, uiStates, interactions, mockApi.strategy/activation/endpoints, dependencyPolicy, stylingStrategy, uiComponentChoices, verificationTargets, evidenceGaps, residualRisks, and realIntegrationGap. It must not modify protected schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff. Only include fields you change; omit unchanged fields (this node applies the patch on the original canonical artifact). null deletes a key; arrays and scalars replace; plain objects merge recursively.",
|
|
3046
|
-
requirementCoverageInstruction,
|
|
3047
|
-
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
3048
|
-
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
3049
|
-
"Output in this exact order: (1) exactly one fenced json object containing the merge-patch delta — this node compiles it onto the original canonical artifact; (2) exactly one openspec-citations citation fenced block appended immediately after it. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only. Do not emit any raw JSON or JSON objects in prose. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block. Do not include secrets or unsafe paths.",
|
|
3050
|
-
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the effective (merged) contract; do not reduce behavior semantics to IDs and paths.",
|
|
3051
|
-
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
3052
|
-
verificationTargetFileInstruction,
|
|
3053
|
-
fixedVerificationContext,
|
|
3054
|
-
sourceContext,
|
|
3055
|
-
frontendContractSchemaBlock,
|
|
3056
|
-
mockContextBlock,
|
|
3057
|
-
frontendComponentConformanceInstruction,
|
|
3058
|
-
openspecCitationInstruction,
|
|
3059
|
-
].join("\n\n"),
|
|
3060
3332
|
},
|
|
3061
3333
|
{
|
|
3062
|
-
id: "frontend-
|
|
3063
|
-
depends_on: [
|
|
3064
|
-
"frontend-plan-revision-pi",
|
|
3065
|
-
"frontend-plan-pi",
|
|
3066
|
-
"frontend-design-review-pi",
|
|
3067
|
-
],
|
|
3068
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3069
|
-
runIf: "$.nodes['frontend-design-review-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
3334
|
+
id: "frontend-design-review-pi",
|
|
3335
|
+
depends_on: ["frontend-design-policy-shell"],
|
|
3070
3336
|
role: "reviewer",
|
|
3071
3337
|
executor: "pi",
|
|
3072
3338
|
complexity: "MED",
|
|
3073
3339
|
writePolicy: "read-only",
|
|
3340
|
+
retryPolicy: DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
3074
3341
|
allowedPaths: readOnlyPaths,
|
|
3075
3342
|
forbiddenPaths,
|
|
3076
3343
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
3077
|
-
|
|
3078
|
-
outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
|
|
3344
|
+
outputContract: "Authoritative typed design terminal via approve_design / request_design_changes tools. No JSON verdict; the committed typed design fact is the only authority. No file writes.",
|
|
3079
3345
|
subtask_prompt: [
|
|
3080
|
-
"Audit the
|
|
3081
|
-
"
|
|
3082
|
-
"
|
|
3083
|
-
"
|
|
3084
|
-
"
|
|
3085
|
-
"
|
|
3086
|
-
"
|
|
3346
|
+
"Audit the frontend plan before implementation. frontend-plan-pi is emitted to you as canonical full-contract JSON after the runtime applied and validated the planner's editable patch against its protected skeleton; there is no separate plan prose.",
|
|
3347
|
+
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_design or request_design_changes. Call exactly one of them; after calling one, do not call the other.",
|
|
3348
|
+
"request_design_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
3349
|
+
"Your verdict is consumed only as deterministic data input by frontend-writer-admission-shell; it no longer drives any branch or gate. request_design_changes blocks writer admission (terminal).",
|
|
3350
|
+
"Request design changes when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for a declared behavior target or selected Mock strategy. Mock strategies require Mock-backed evidence. A static-only contract is allowed only when every verification target is static and maps to a declared static entrypoint. not-needed otherwise requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
3351
|
+
"Also request design changes for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
|
|
3352
|
+
"Component selection conformance is a hard blocking condition: request_design_changes when the frontend spec (component/theme/rule.components bucket) already defines a component for a purpose but the plan selects another or self-invents one without a declared deviation; when uiComponentChoices is missing/empty for UI-visible work while the frozen component/theme bucket is non-empty; when a decision=specified specReference.path is missing a ledger OpenSpec reference or successful read event; or when a decision=new component lacks a traceable task-source/PRD specReference. A PRD reference for decision=new is not an OpenSpec citation and must not be rejected merely for lacking an OpenSpec read event.",
|
|
3353
|
+
"For uiComponentChoices, purpose is the stable coverage key and must match interaction.name or uiState.name. Responsibility is expressed by the matched expectedBehavior plus rationale; you must not reject it merely for matching an interaction or component identifier.",
|
|
3354
|
+
"You must NOT make authoritative assertions about the execution result of frozen verification commands (typecheck/test/build/lint/etc.). Predicting that a command will necessarily pass or fail, or declaring an acceptance criterion unreachable on that basis, is out of your authority: command results are deterministically established by frontend-verify-shell. Any concern about verification feasibility must be recorded only as a non-blocking verification concern in findings (severity must not be Critical, and it must never be the sole fatal basis for request_design_changes). Only semantic design defects (component selection, state flow, interaction contract, or conflicts with the specification) may be Critical. A pure command-will-fail prediction must not be classified as contract-requirement-gap.",
|
|
3087
3355
|
"Read-only: do not modify repository files.",
|
|
3356
|
+
"LARGE-FILE AUDIT (avoid full reads): style/theme audit files can be large (e.g. styles.css is often hundreds of KB). Prefer grep to locate the exact rules/variables you must verify (e.g. grep the oc- class, is-* modifier, or --oc- theme variables with their line numbers), then read only the narrow line range when surrounding context is needed. Do not read a large style/test file in full — a single full read can exhaust the read budget and fail the attempt.",
|
|
3357
|
+
"Canonical contract reading: frontend-design-policy-shell prints absolute paths for Contract, Contract index, and the non-blocking Capacity diagnostic. Read the capacity diagnostic first. When it recommends full-contract, read the exact Contract path. When it recommends indexed-sections, read the Contract index and its hash-bound section files instead of opening the full contract. Never resolve a bare contracts/... path against the repository root or hunt for substitutes. Implementation target files inside the writeSet are created later by the implement node: do not read them and do not treat their absence as a design defect.",
|
|
3088
3358
|
fixedVerificationContext,
|
|
3089
|
-
|
|
3359
|
+
sourceContexts.designReview,
|
|
3360
|
+
scopedOpenspecContext,
|
|
3090
3361
|
frontendContractFieldSummary,
|
|
3091
3362
|
mockContextBlock,
|
|
3092
3363
|
].join("\n\n"),
|
|
3093
3364
|
},
|
|
3094
3365
|
{
|
|
3095
|
-
id: "frontend-
|
|
3096
|
-
depends_on: [
|
|
3097
|
-
"frontend-final-design-review-pi",
|
|
3098
|
-
"frontend-design-review-pi",
|
|
3099
|
-
"frontend-plan-revision-pi",
|
|
3100
|
-
"frontend-plan-pi",
|
|
3101
|
-
],
|
|
3102
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3366
|
+
id: "frontend-writer-admission-shell",
|
|
3367
|
+
depends_on: ["frontend-design-policy-shell", "frontend-design-review-pi"],
|
|
3103
3368
|
role: "verifier",
|
|
3104
3369
|
executor: "shell",
|
|
3105
3370
|
complexity: "LOW",
|
|
3106
3371
|
writePolicy: "read-only",
|
|
3107
3372
|
allowedPaths: readOnlyPaths,
|
|
3108
3373
|
forbiddenPaths,
|
|
3109
|
-
outputContract: "Deterministic
|
|
3110
|
-
subtask_prompt: "Fail closed unless the
|
|
3374
|
+
outputContract: "Deterministic writer admission: require design review verdict pass, freeze the pre-writer worktree/lint baselines, derive the concrete writeSet + admission digest, and write contracts/frontend-writer-admission-result.json (schemaId frontend-writer-admission-shell-v1).",
|
|
3375
|
+
subtask_prompt: "Fail closed unless the design review approved and the deterministic admission derived a concrete, non-empty writeSet. The admission result is the only write authorization.",
|
|
3111
3376
|
shell: {
|
|
3112
3377
|
commands: [],
|
|
3113
|
-
|
|
3378
|
+
frontendWriterAdmission: {
|
|
3114
3379
|
schemaVersion: 1,
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3380
|
+
designReviewFromNodeId: "frontend-design-review-pi",
|
|
3381
|
+
frozenCommandLabels: [
|
|
3382
|
+
...staticVerifyEvidence.commandLabels,
|
|
3383
|
+
...behaviorVerifyEvidence.commandLabels,
|
|
3384
|
+
...(mockVerifyEvidence?.commandLabels ?? []),
|
|
3385
|
+
],
|
|
3121
3386
|
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
|
|
3122
3387
|
frontendMockStrategyMustBeNotNeeded(frontendSources)
|
|
3123
3388
|
? ["not-needed"]
|
|
3124
3389
|
: taskConfig.frontendMock?.policy === "required"
|
|
3125
|
-
? [
|
|
3390
|
+
? [
|
|
3391
|
+
"native",
|
|
3392
|
+
"browser-intercept",
|
|
3393
|
+
"request-adapter",
|
|
3394
|
+
]
|
|
3126
3395
|
: [
|
|
3127
3396
|
"native",
|
|
3128
3397
|
"browser-intercept",
|
|
3129
3398
|
"request-adapter",
|
|
3130
3399
|
"not-needed",
|
|
3131
3400
|
],
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
openspecPolicy: openspecGate.openspecPolicy,
|
|
3139
|
-
openspecCandidateSources: {
|
|
3140
|
-
declared: openspecGate.openspecCandidateSources.declared,
|
|
3141
|
-
taskSourceCited: openspecGate.openspecCandidateSources.taskSourceCited,
|
|
3142
|
-
scanStrict: openspecGate.openspecCandidateSources.scanStrict,
|
|
3143
|
-
},
|
|
3144
|
-
openspecCandidatePaths: openspecGate.openspecCandidatePaths,
|
|
3145
|
-
componentSpecCandidatePaths,
|
|
3401
|
+
...(lintShellCommands.length > 0 && lintVerifyEvidence
|
|
3402
|
+
? {
|
|
3403
|
+
lintCommands: lintShellCommands,
|
|
3404
|
+
lintEvidence: lintVerifyEvidence,
|
|
3405
|
+
}
|
|
3406
|
+
: {}),
|
|
3146
3407
|
},
|
|
3147
3408
|
cwd: ".",
|
|
3148
3409
|
timeoutMs: 60000,
|
|
3149
3410
|
},
|
|
3150
3411
|
},
|
|
3151
|
-
...(lintShellCommands.length > 0 && lintVerifyEvidence
|
|
3152
|
-
? [
|
|
3153
|
-
{
|
|
3154
|
-
id: "frontend-lint-baseline-shell",
|
|
3155
|
-
depends_on: ["frontend-prewrite-gate-shell"],
|
|
3156
|
-
role: "verifier",
|
|
3157
|
-
executor: "shell",
|
|
3158
|
-
complexity: "LOW",
|
|
3159
|
-
writePolicy: "read-only",
|
|
3160
|
-
allowedPaths: readOnlyPaths,
|
|
3161
|
-
forbiddenPaths,
|
|
3162
|
-
outputContract: "Capture writer-preceding lint output as frontend-lint-baseline-v1 without treating existing lint diagnostics as writer failure.",
|
|
3163
|
-
subtask_prompt: "Run the frozen lint commands read-only. Preserve raw output and mark the baseline unavailable on timeout, execution failure, unparseable output, or worktree mutation.",
|
|
3164
|
-
shell: {
|
|
3165
|
-
commands: lintShellCommands,
|
|
3166
|
-
frontendLintBaseline: {
|
|
3167
|
-
schemaVersion: 1,
|
|
3168
|
-
lintCommands: lintShellCommands,
|
|
3169
|
-
lintEvidence: lintVerifyEvidence,
|
|
3170
|
-
},
|
|
3171
|
-
cwd: ".",
|
|
3172
|
-
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3173
|
-
},
|
|
3174
|
-
},
|
|
3175
|
-
]
|
|
3176
|
-
: []),
|
|
3177
3412
|
{
|
|
3178
3413
|
id: implementId,
|
|
3179
|
-
depends_on: [
|
|
3180
|
-
"frontend-prewrite-gate-shell",
|
|
3181
|
-
...(lintShellCommands.length > 0
|
|
3182
|
-
? ["frontend-lint-baseline-shell"]
|
|
3183
|
-
: []),
|
|
3184
|
-
],
|
|
3414
|
+
depends_on: ["frontend-writer-admission-shell"],
|
|
3185
3415
|
...buildFrontendWriterNodeDefaults({
|
|
3186
3416
|
complexity: resolveWriterComplexity(taskConfig),
|
|
3187
3417
|
writeSet: implementPaths.writeSet,
|
|
3188
3418
|
allowedPaths: implementPaths.allowedPaths,
|
|
3189
3419
|
forbiddenPaths,
|
|
3420
|
+
writerOutcomePolicyType: "frontend-facts-v1",
|
|
3190
3421
|
}),
|
|
3191
|
-
outputContract: "
|
|
3422
|
+
outputContract: "The implementation status is derived by the executor from mechanical facts (write-tool events, run delta, write guard, requirement coverage, focused-check), not from any IMPLEMENTATION_OUTCOME first line. Deliver a Markdown summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
|
|
3192
3423
|
subtask_prompt: [
|
|
3193
|
-
"Implement against the validated run-owned Frontend Implementation Contract
|
|
3424
|
+
"Implement against the validated run-owned Frontend Implementation Contract materialized by frontend-design-policy-shell (path/schema/hash) and authorized by frontend-writer-admission-shell. Do not rebuild the contract from Markdown alone.",
|
|
3194
3425
|
"The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
|
|
3195
3426
|
"Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
|
|
3196
3427
|
"Map every requirement id, expectedOutcome, interaction trigger/expectedBehavior, and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
|
|
3197
|
-
"
|
|
3428
|
+
"For every non-static verification target, treat target.id as a stable trace token and include that exact token in a real describe/it/test literal title (for example, it('[VT-DASHBOARD-SHELL] renders the dashboard', ...)). One test title may carry multiple target ids when it proves multiple grouped behaviors; comments and ordinary strings do not count as trace evidence.",
|
|
3429
|
+
"Begin implementation after the contract and its target files are confirmed. Do not spend the turn collecting optional context. If the canonical contract lacks behavior needed to edit safely, stop and state the blocking reason in the summary instead of reopening broad discovery.",
|
|
3430
|
+
"Your implementation status is derived by the executor from mechanical facts (persisted write-tool events, run delta, write guard, requirement coverage, focused-check failures), never from any IMPLEMENTATION_OUTCOME first line. Do not emit an IMPLEMENTATION_OUTCOME first line.",
|
|
3431
|
+
"The node runs a bounded micro-loop: after each write attempt the executor re-runs frozen focused checks and records a per-round diff checkpoint; the write guard stays active every round. Only repair local issues attributable to the current diff (syntax/type/import/format/unit-assert/obvious omission). Never change requirements, design, writeSet, or verification strictness inside the loop.",
|
|
3198
3432
|
"Implement only the approved Mock strategy carried by the validated contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
|
|
3199
|
-
"frontend-
|
|
3433
|
+
"frontend-design-policy-shell materialized and validated the canonical contract; frontend-writer-admission-shell authorized the writeSet. Stay within writeSet and preserve unrelated files.",
|
|
3200
3434
|
"For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
|
|
3201
3435
|
"Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
|
|
3202
3436
|
"Edit existing files with the structured edit/write tools. NEVER rewrite Markdown (or any file with quoting/backticks/indentation-sensitive content) via bash sed/awk/echo redirection: escaping mistakes silently corrupt the file and self-repair loops burn the run.",
|
|
@@ -3211,7 +3445,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3211
3445
|
.join("\n\n"),
|
|
3212
3446
|
},
|
|
3213
3447
|
{
|
|
3214
|
-
id: "frontend-verify-
|
|
3448
|
+
id: "frontend-verify-shell",
|
|
3215
3449
|
depends_on: [implementId],
|
|
3216
3450
|
role: "verifier",
|
|
3217
3451
|
executor: "shell",
|
|
@@ -3219,8 +3453,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3219
3453
|
writePolicy: "read-only",
|
|
3220
3454
|
allowedPaths: readOnlyPaths,
|
|
3221
3455
|
forbiddenPaths,
|
|
3222
|
-
outputContract: "Run frozen Mock/static/behavior commands
|
|
3223
|
-
subtask_prompt: "Execute the frontend verification bundle. Preserve per-command evidence;
|
|
3456
|
+
outputContract: "Run frozen Mock/static/behavior commands and materialize the verification trace. Any failure is terminal: no same-run repair branch, failure ownership facts are materialized for recovery.",
|
|
3457
|
+
subtask_prompt: "Execute the frontend verification bundle. Preserve per-command evidence; a failure fails this node (terminal) and routes to recovery.",
|
|
3224
3458
|
shell: {
|
|
3225
3459
|
commands: [],
|
|
3226
3460
|
frontendVerificationBundle: {
|
|
@@ -3234,7 +3468,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3234
3468
|
staticEvidence: staticVerifyEvidence,
|
|
3235
3469
|
behaviorEvidence: behaviorVerifyEvidence,
|
|
3236
3470
|
lintBaselineNodeId: lintShellCommands.length > 0
|
|
3237
|
-
? "frontend-
|
|
3471
|
+
? "frontend-writer-admission-shell"
|
|
3238
3472
|
: undefined,
|
|
3239
3473
|
writerNodeIds: lintShellCommands.length > 0 ? [implementId] : [],
|
|
3240
3474
|
mode: "initial",
|
|
@@ -3243,85 +3477,17 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3243
3477
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3244
3478
|
},
|
|
3245
3479
|
},
|
|
3246
|
-
{
|
|
3247
|
-
id: "frontend-repair-pi",
|
|
3248
|
-
depends_on: ["frontend-verify-assess-shell", implementId],
|
|
3249
|
-
runIf: "$.nodes['frontend-verify-assess-shell'].json.eligible == true",
|
|
3250
|
-
...buildFrontendWriterNodeDefaults({
|
|
3251
|
-
complexity: resolveWriterComplexity(taskConfig),
|
|
3252
|
-
writeSet: implementPaths.writeSet,
|
|
3253
|
-
allowedPaths: implementPaths.allowedPaths,
|
|
3254
|
-
forbiddenPaths,
|
|
3255
|
-
}),
|
|
3256
|
-
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
|
|
3257
|
-
subtask_prompt: [
|
|
3258
|
-
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
3259
|
-
"This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
|
|
3260
|
-
"The repair assessment and canonical implementation contract are complete inputs for this phase. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat repository-wide discovery.",
|
|
3261
|
-
"Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
|
|
3262
|
-
"Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
|
|
3263
|
-
...(implementPaths.docIndexCompanions.length > 0
|
|
3264
|
-
? [
|
|
3265
|
-
`Doc index sync is MANDATORY: ${implementPaths.docIndexCompanions.join(", ")} are catalog index files for this writeSet. When your repair adds, renames, or removes any indexed file, update ${implementPaths.docIndexCompanions.join(" and ")} in the same run; verification runs check-doc-index and fails the run on a missing index entry.`,
|
|
3266
|
-
]
|
|
3267
|
-
: []),
|
|
3268
|
-
writerDeliveryContract(taskConfig),
|
|
3269
|
-
]
|
|
3270
|
-
.filter((value) => Boolean(value))
|
|
3271
|
-
.join("\n\n"),
|
|
3272
|
-
},
|
|
3273
|
-
{
|
|
3274
|
-
id: "frontend-reverify-shell",
|
|
3275
|
-
depends_on: ["frontend-repair-pi"],
|
|
3276
|
-
role: "verifier",
|
|
3277
|
-
executor: "shell",
|
|
3278
|
-
complexity: "LOW",
|
|
3279
|
-
writePolicy: "read-only",
|
|
3280
|
-
allowedPaths: readOnlyPaths,
|
|
3281
|
-
forbiddenPaths,
|
|
3282
|
-
outputContract: "Post-repair Mock/static/behavior re-verification plus refreshed canonical trace; any failure blocks review.",
|
|
3283
|
-
subtask_prompt: "Re-run the frozen frontend verification bundle after bounded repair and fail on any command or trace failure.",
|
|
3284
|
-
shell: {
|
|
3285
|
-
commands: [],
|
|
3286
|
-
frontendVerificationBundle: {
|
|
3287
|
-
schemaVersion: 1,
|
|
3288
|
-
mockCommands: mockShellCommands,
|
|
3289
|
-
lintCommands: lintShellCommands,
|
|
3290
|
-
staticCommands: staticShellCommands,
|
|
3291
|
-
behaviorCommands: behaviorShellCommands,
|
|
3292
|
-
mockEvidence: mockVerifyEvidence,
|
|
3293
|
-
lintEvidence: lintVerifyEvidence,
|
|
3294
|
-
staticEvidence: staticVerifyEvidence,
|
|
3295
|
-
behaviorEvidence: behaviorVerifyEvidence,
|
|
3296
|
-
lintBaselineNodeId: lintShellCommands.length > 0
|
|
3297
|
-
? "frontend-lint-baseline-shell"
|
|
3298
|
-
: undefined,
|
|
3299
|
-
writerNodeIds: lintShellCommands.length > 0
|
|
3300
|
-
? [implementId, "frontend-repair-pi"]
|
|
3301
|
-
: [],
|
|
3302
|
-
mode: "repair",
|
|
3303
|
-
},
|
|
3304
|
-
cwd: ".",
|
|
3305
|
-
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3306
|
-
},
|
|
3307
|
-
},
|
|
3308
3480
|
{
|
|
3309
3481
|
id: "frontend-review-context-shell",
|
|
3310
|
-
depends_on: [
|
|
3311
|
-
"frontend-reverify-shell",
|
|
3312
|
-
"frontend-repair-pi",
|
|
3313
|
-
"frontend-verify-assess-shell",
|
|
3314
|
-
implementId,
|
|
3315
|
-
],
|
|
3316
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3482
|
+
depends_on: ["frontend-verify-shell", implementId],
|
|
3317
3483
|
role: "verifier",
|
|
3318
3484
|
executor: "shell",
|
|
3319
3485
|
complexity: "LOW",
|
|
3320
3486
|
writePolicy: "read-only",
|
|
3321
3487
|
allowedPaths: readOnlyPaths,
|
|
3322
3488
|
forbiddenPaths,
|
|
3323
|
-
outputContract: "Canonical frontend review context containing
|
|
3324
|
-
subtask_prompt: "Capture the actual diff and bind it to the effective
|
|
3489
|
+
outputContract: "Canonical frontend review context containing a hash-bound contract reference and field index, lint assessment when configured, effective verification trace, optional verify-failure facts, and actual worktree diff.",
|
|
3490
|
+
subtask_prompt: "Capture the actual diff and bind it to the effective verification evidence for final review.",
|
|
3325
3491
|
shell: {
|
|
3326
3492
|
commands: [],
|
|
3327
3493
|
frontendReviewContext: { schemaVersion: 1, requireBaseline: true },
|
|
@@ -3336,79 +3502,98 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3336
3502
|
executor: "pi",
|
|
3337
3503
|
complexity: "HIGH",
|
|
3338
3504
|
writePolicy: "read-only",
|
|
3505
|
+
retryPolicy: FRONTEND_REVIEW_TERMINAL_RETRY_POLICY,
|
|
3339
3506
|
allowedPaths: readOnlyPaths,
|
|
3340
3507
|
forbiddenPaths,
|
|
3341
3508
|
skills: FRONTEND_REVIEW_SKILLS,
|
|
3342
|
-
outputContract: '
|
|
3343
|
-
outputProtocol: REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL,
|
|
3509
|
+
outputContract: 'Authoritative typed review terminal via approve_review / request_review_changes tools. No JSON verdict is required in the response text; the typed terminal fact is the only authority. No file writes.',
|
|
3344
3510
|
subtask_prompt: [
|
|
3345
3511
|
"Review the frontend implementation and verification evidence.",
|
|
3346
|
-
"
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds the validated implementation contract, frontend lint assessment when lint is configured, effective initial-or-post-repair verification trace, repair assessment, and the run-owned actual diff (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
|
|
3512
|
+
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_review or request_review_changes. Call it once and do not call the other afterwards.",
|
|
3513
|
+
"approve_review means the implementation passes; it must not carry Critical or Important findings. request_review_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
3514
|
+
"Do NOT emit an equivalent JSON verdict in the response text: the committed typed terminal fact is the only authority and no branch or gate reads response-text JSON verdicts.",
|
|
3515
|
+
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds a hash-verified canonical contract reference, a field-to-section index, frontend lint assessment when configured, the effective verification trace, and the run-owned actual diff. Read contractRef.capacityDiagnosticPath first: use contractRef.path only when full-contract is recommended; otherwise read only the hash-bound contractRef.sections needed for the changed surface and verification claims. Then read diff.reviewSummaryPath. The full artifacts/diff_patch.patch is retained only as audit evidence: do NOT read it in full. For semantic review, read only the named per-file diff fragment in the summary/index (in part order when needed) and then the current source file when necessary. Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/stable-target-id binding only—not semantic correctness.",
|
|
3351
3516
|
"Treat lint status exactly as passed | baseline-debt | failed | unavailable. baseline-debt may continue only with intact evidence and zero diagnostics on writer-changed files; report the tolerated debt count and never rewrite it as lint passed. Typecheck, build, and test still require successful final exits.",
|
|
3352
3517
|
"Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
|
|
3353
|
-
"The contract
|
|
3518
|
+
"The contract referenced and hash-bound by frontend-review-context.json is the effective plan materialized by frontend-design-policy-shell. Do not re-open task sources, OpenSpec, AI workspace, design-review, writer summary, or verification node prose. Inspect only the canonical review context, its indexed contract sections, its bound diff, and diff-referenced files when semantic review requires source code.",
|
|
3519
|
+
"For uiComponentChoices, purpose is the stable coverage key and must match interaction.name or uiState.name. Responsibility is expressed by the matched expectedBehavior plus rationale; you must not reject it merely for matching an interaction or component identifier.",
|
|
3354
3520
|
"Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
|
|
3355
|
-
"Inspect the frontend-verify-
|
|
3521
|
+
"Inspect the frontend-verify-shell evidence in the review context directly, including the production/default-real-path static check, and require Mock activation to be off for that check.",
|
|
3356
3522
|
"Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
|
|
3357
3523
|
"Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
|
|
3358
3524
|
].join("\n\n"),
|
|
3359
3525
|
},
|
|
3360
3526
|
{
|
|
3361
|
-
id: "frontend-
|
|
3362
|
-
depends_on: ["frontend-review-pi"],
|
|
3363
|
-
role: "
|
|
3527
|
+
id: "frontend-closeout-shell",
|
|
3528
|
+
depends_on: ["frontend-review-context-shell", "frontend-review-pi"],
|
|
3529
|
+
role: "closeout",
|
|
3364
3530
|
executor: "shell",
|
|
3365
3531
|
complexity: "LOW",
|
|
3366
3532
|
writePolicy: "read-only",
|
|
3367
3533
|
allowedPaths: readOnlyPaths,
|
|
3368
3534
|
forbiddenPaths,
|
|
3369
|
-
outputContract:
|
|
3370
|
-
subtask_prompt:
|
|
3535
|
+
outputContract: "Deterministic closeout rendered from committed facts only: coverage matrix, lint status, integration facts, typed review verdict, cumulative diff, browser/visual not-run, risks, and follow-up. No model summaries are re-interpreted.",
|
|
3536
|
+
subtask_prompt: "Render the closeout deterministically from frontend-review-context.json, the verification trace, and the committed typed review terminal fact.",
|
|
3371
3537
|
shell: {
|
|
3372
3538
|
commands: [],
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
routingAccept: ["request-revision"],
|
|
3377
|
-
label: "frontend review",
|
|
3378
|
-
source: "json-review-verdict",
|
|
3539
|
+
frontendCloseout: {
|
|
3540
|
+
schemaVersion: 1,
|
|
3541
|
+
reviewFromNodeId: "frontend-review-pi",
|
|
3379
3542
|
},
|
|
3380
3543
|
cwd: ".",
|
|
3381
3544
|
timeoutMs: 60000,
|
|
3382
3545
|
},
|
|
3383
3546
|
},
|
|
3384
|
-
{
|
|
3385
|
-
id: "frontend-closeout-pi",
|
|
3386
|
-
depends_on: [
|
|
3387
|
-
"frontend-review-context-shell",
|
|
3388
|
-
"frontend-review-gate-shell",
|
|
3389
|
-
],
|
|
3390
|
-
role: "closeout",
|
|
3391
|
-
executor: "pi",
|
|
3392
|
-
complexity: "MED",
|
|
3393
|
-
writePolicy: "read-only",
|
|
3394
|
-
allowedPaths: taskConfig.allowedPaths.length > 0
|
|
3395
|
-
? [...taskConfig.allowedPaths, "docs/**"]
|
|
3396
|
-
: ["**", "docs/**"],
|
|
3397
|
-
forbiddenPaths,
|
|
3398
|
-
skills: FRONTEND_VERIFICATION_SKILLS,
|
|
3399
|
-
outputContract: "Markdown closeout summary with Changes, Mock Decision / Strategy / Files / Verification / Production Boundary, Verification Evidence, Review Result, Frontend Status, Real Integration Status, Known Risks, and Follow-up. No file writes.",
|
|
3400
|
-
subtask_prompt: [
|
|
3401
|
-
"Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
|
|
3402
|
-
"Use only the canonical frontend-review-context.json plus the final frontend review gate verdict. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, design-review, writer, repair, or verification prose, and do not perform new repository research during closeout.",
|
|
3403
|
-
"Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Report lint separately as passed|baseline-debt|failed|unavailable; baseline-debt is explicit debt, not passed. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use contracts/frontend-review-context.json and the effective frontend-verify-assess-shell or frontend-reverify-shell facts; do not invent Browser evidence from component tests.",
|
|
3404
|
-
`When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
|
|
3405
|
-
`When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
|
|
3406
|
-
"Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
|
|
3407
|
-
].join("\n\n"),
|
|
3408
|
-
},
|
|
3409
3547
|
],
|
|
3410
3548
|
};
|
|
3411
|
-
|
|
3549
|
+
// The static DAG template is the topology source of truth: the generated
|
|
3550
|
+
// chain must match the template's node set and order, and the template's
|
|
3551
|
+
// depends_on must be a subset of the generated one (the runtime may add
|
|
3552
|
+
// dependencies, e.g. Mock-required planning depending on the contract
|
|
3553
|
+
// node's Mock facts). The template's own tasks carry simplified placeholder
|
|
3554
|
+
// configs; the generated node definitions (budgets, retry, skeleton,
|
|
3555
|
+
// skills, prompts) and any runtime-added dependencies win.
|
|
3556
|
+
const frontendTemplate = await loadFrontendDagTemplate(sources.repoRoot);
|
|
3557
|
+
if (frontendTemplate) {
|
|
3558
|
+
const byId = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
3559
|
+
const templateIds = frontendTemplate.tasks.map((task) => task.id);
|
|
3560
|
+
const missing = templateIds.filter((id) => !byId.has(id));
|
|
3561
|
+
if (missing.length > 0) {
|
|
3562
|
+
throw new Error(`frontend DAG template topology drift: generated chain is missing template node(s): ${missing.join(", ")}`);
|
|
3563
|
+
}
|
|
3564
|
+
for (const templateTask of frontendTemplate.tasks) {
|
|
3565
|
+
const generated = byId.get(templateTask.id);
|
|
3566
|
+
const generatedDeps = new Set(generated.depends_on);
|
|
3567
|
+
const templateOnly = templateTask.depends_on.filter((dep) => !generatedDeps.has(dep));
|
|
3568
|
+
if (templateOnly.length > 0) {
|
|
3569
|
+
throw new Error(`frontend DAG template topology drift: generated node ${templateTask.id} is missing template dependency ${templateOnly.join(", ")}`);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
const templateSet = new Set(templateIds);
|
|
3573
|
+
const ordered = templateIds.map((id) => byId.get(id));
|
|
3574
|
+
const extra = spec.tasks.filter((task) => !templateSet.has(task.id));
|
|
3575
|
+
spec.tasks = [...ordered, ...extra];
|
|
3576
|
+
}
|
|
3577
|
+
if (frontendTaskShape.shape === "split-required") {
|
|
3578
|
+
spec.tasks = pruneFrontendTasksForSplitRequired(spec.tasks);
|
|
3579
|
+
}
|
|
3580
|
+
else {
|
|
3581
|
+
const reshapeRaisedTopology = frontendTaskShape.signals.includes("shape-transition-reshape");
|
|
3582
|
+
if (!reshapeRaisedTopology) {
|
|
3583
|
+
spec.tasks = pruneFrontendTasksForRisk(spec.tasks, frontendRisk);
|
|
3584
|
+
}
|
|
3585
|
+
if (frontendTaskShape.shape === "micro") {
|
|
3586
|
+
spec.tasks = pruneFrontendTasksForMicro(spec.tasks);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
spec.advisories = [
|
|
3590
|
+
...(spec.advisories ?? []),
|
|
3591
|
+
`frontend-task-shape: ${frontendTaskShape.shape} (${frontendTaskShape.reason})`,
|
|
3592
|
+
];
|
|
3593
|
+
// Freeze the frontend recovery continuation quota from task.json so the
|
|
3594
|
+
// runner can bound M6 auto-recovery without re-reading the task config.
|
|
3595
|
+
const frontendMaxContinuations = sources.taskConfig.frontendRecovery?.maxContinuations ?? 1;
|
|
3596
|
+
spec.frontendRecovery = { maxContinuations: frontendMaxContinuations };
|
|
3412
3597
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3413
3598
|
stampGeneratedArtifactBindings(spec);
|
|
3414
3599
|
parseDagSpec(spec);
|