@tea-agent/loop-agent 0.39.0-beta.7 → 0.39.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +23 -5
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/commands/init.js +26 -0
- package/dist/executors/dag-pi-executor.js +326 -8
- package/dist/executors/pi-read-budget-policy.js +166 -0
- package/dist/executors/shell-executor.js +26 -30
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +15 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +123 -4
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-bPer1waP.js → abnfDiagram-N423BO3Z-D1-Z-BBa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BLdkwucW.js → arc-q8qAYm5m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw4wo4I1.js → architectureDiagram-T3A2C74G-CAu4_lve.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DUUkc0gr.js → blockDiagram-VBNYF7ZC-D5GhQvHm.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BC3O0nob.js → c4Diagram-5PPSVZJV-ClQjeLkO.js} +1 -1
- package/dist/worker/console/static/assets/channel-ClAuhSUL.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Dd4g2nti.js → chunk-2GRJ4B5K-DLqWlFbo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-s2EDm7-n.js → chunk-2Q5K7J3B-DkRT_usL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-GqtHM2yQ.js → chunk-5RXB4S5H-Cqp7Chi3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BzRFrN8j.js → chunk-5VM5RSS4-CedEdFB9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bw9nFjCJ.js → chunk-6Q2QTUOP-BHM8vP5x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B4n-03Qm.js → chunk-GF5L2VYU-D7EFr377.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dt75ZdBD.js → chunk-JWPE2WC7-DR38mTWV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B_afMhHB.js → chunk-KBJHAD2P-BFv2LsR3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Bd8Rqqg9.js → chunk-RYQCIY6F-BAvpJS5z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-9boOMji4.js → chunk-XXDRQBXY-eKI9TO72.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BtCycfCc.js → cose-bilkent-JH36ORCC-BMJXpgo4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-eTOts9L1.js → cynefin-VYW2F7L2-CKJ2ofZx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cibd2mjg.js → cynefinDiagram-MW4NZA55-CsKnsazm.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CDjDZtDV.js → dagre-VZM6K2ZE-DVQ46vIw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Btexn-JJ.js → diagram-7IWD3JNH-D_RuonYS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cyl8sdpM.js → diagram-B4RE2ZJO-D2Mt0nMu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Ev-83E1t.js → diagram-LBJQPF4R-DjBDuye6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-41LqE0Mc.js → diagram-Q27KOJAE-8VZkqpE_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BC39T5R3.js → diagram-UB23O5K3-D0izYxia.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DzcNRuri.js → ebnfDiagram-BXEA7PRR-CncG2I95.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B5Frvvnz.js → erDiagram-JOGREHBK-DiBUUXR8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BapxROIM.js → flowDiagram-UKHOOZJN-Dtc0fArb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cd4d4wQ0.js → ganttDiagram-PKOTCBZU-BPNFP5OZ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CrL6buFW.js → gitGraphDiagram-DS77QQ5N-CsgpxEEd.js} +1 -1
- package/dist/worker/console/static/assets/{index-DBh_x_Ek.js → index-Bai8Z25q.js} +81 -81
- package/dist/worker/console/static/assets/index-CH0Tr5hY.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BrSiNxyd.js → infoDiagram-6WML65LV-kHGxkJUS.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-gvlvY-n9.js → ishikawaDiagram-WSZJBQD7-CbLe9wrr.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-YaPzRQ.js → journeyDiagram-NVQOT4AX-BOkwxAcP.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BsmDv9E_.js → kanban-definition-27J2QSJJ-BHYQ2rDu.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UWaoDN3h.js → linear-CHT4ujob.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BlvpGVG8.js → mermaid.core-yL8sfqnY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CUCIP5CP.js → mindmap-definition-FAOFIHXS-DZx1coV-.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CB1aQUAK.js → pegDiagram-VL7TDLO6-DStWRCI8.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BwehHxhf.js → pieDiagram-7S7Q4E2Y-DSKZkwzK.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CFz30APB.js → quadrantDiagram-CIZ2JOQS-CmoEGOuW.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwNmw1i6.js → railroadDiagram-AXF67PYL-BLqzhKyq.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-jg-tPXlI.js → requirementDiagram-LRYGKXZP-gbHeUabM.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BlyReDKl.js → sankeyDiagram-W5VNT64P-CaKkBCKO.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DBUzW1f3.js → sequenceDiagram-SI44F4Z6-PUbi3Upu.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BYcI2RAr.js → sizeCapture-X5ZJPWSS-dm7S9Qfe.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D7AiyJdo.js → stateDiagram-OKZ733FA-C7WSzv4p.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-sQDv5AP3.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BHGt7Kkv.js → swimlanes-SLNWSIFB-CBKCiQwd.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BXwsmcPs.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-OPjooWcX.js → timeline-definition-Z64GVDOM-CvnUlylk.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-ggsWDvrO.js → vennDiagram-T6HMQDX7-xM0StY1O.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DjNe-BjZ.js → wardleyDiagram-T6FBY63Y-CWtJMagy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Byp63Hei.js → xychartDiagram-ELKLHX3M-BO_-MeK8.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +331 -35
- package/dist/worker/observe/static/views/dag-inspector.js +509 -78
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-design-policy.js +19 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +117 -50
- package/dist/workflows/dag/frontend-plan-render.js +27 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +45 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +1 -0
- package/dist/workflows/dag/frontend-review-context.js +102 -15
- package/dist/workflows/dag/frontend-review-findings.js +42 -10
- package/dist/workflows/dag/frontend-shadow-dual-write.js +36 -2
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +154 -0
- package/dist/workflows/dag/frontend-writer-admission.js +12 -0
- package/dist/workflows/dag/init-hybrid.js +297 -65
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +111 -1
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/rerun-feedback.js +31 -8
- 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 +34 -9
- package/dist/workflows/dag/runner.js +83 -16
- package/dist/workflows/dag/types.js +67 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +4 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +2 -3
- package/skills/frontend-implementation/references/design-spec.md +4 -3
- package/skills/frontend-implementation/references/node-contracts.md +4 -3
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-AbFtVnFm.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/index-DwOGauxU.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.js +0 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { access, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
6
6
|
import { assertValidDagSpec } from "./validate.js";
|
|
@@ -9,6 +9,7 @@ import { bindDagRerunFeedback } from "./rerun-feedback.js";
|
|
|
9
9
|
import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
10
10
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
11
11
|
import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, DEFAULT_FRONTEND_SPEC_ROOTS, extractTaskSourceFrontendSpecPaths, } from "../../shared/openspec-spec.js";
|
|
12
|
+
import { readFrontendSpecRegistry, scoreFrontendSpecCandidate, } from "../../task/frontend-project-capability.js";
|
|
12
13
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
13
14
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
14
15
|
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_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";
|
|
@@ -22,6 +23,9 @@ import { discoverProjectGovernancePresence } from "./project-governance-context.
|
|
|
22
23
|
import { CANONICAL_TASK_ID_PATTERN, getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
23
24
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
24
25
|
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
26
|
+
import { extractRequirementFactsFromMarkdown } from "../../task/source-prepare/parse-intent.js";
|
|
27
|
+
import { computeLedgerInputDigest, parseLedgerJson, recoverLedgerInputContract, } from "../../task/source-prepare/ledger.js";
|
|
28
|
+
import { REQUIREMENT_LEDGER_FILE_NAME } from "../../task/contract/constants.js";
|
|
25
29
|
import { dagHasWriterExecution } from "./task-contract-binding.js";
|
|
26
30
|
import { DEFAULT_VERIFY_TIMEOUT_MS, resolveVerifyPreset, } from "../../executors/shell-verification.js";
|
|
27
31
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
@@ -103,6 +107,13 @@ const FRONTEND_BOUNDED_IMPLEMENT_SKILLS = ["frontend-bounded-implement"];
|
|
|
103
107
|
const FRONTEND_DESIGN_REVIEW_SKILLS = ["frontend-design-review"];
|
|
104
108
|
const FRONTEND_REVIEW_SKILLS = ["frontend-review"];
|
|
105
109
|
const FRONTEND_VERIFICATION_SKILLS = ["frontend-verification"];
|
|
110
|
+
const FRONTEND_READ_BUDGETS = {
|
|
111
|
+
contract: { maxFiles: 24, maxBytes: 384 * 1024 },
|
|
112
|
+
scout: { maxFiles: 48, maxBytes: 768 * 1024 },
|
|
113
|
+
plan: { maxFiles: 32, maxBytes: 512 * 1024 },
|
|
114
|
+
designReview: { maxFiles: 16, maxBytes: 256 * 1024 },
|
|
115
|
+
finalReview: { maxFiles: 20, maxBytes: 320 * 1024 },
|
|
116
|
+
};
|
|
106
117
|
const IMPLEMENT_WRITESET_PLACEHOLDER = "REPLACE/WITH/NARROW/IMPLEMENT/PATHS/**";
|
|
107
118
|
/**
|
|
108
119
|
* Runtime contract stamped on every newly generated DAG. Capability fields are
|
|
@@ -1501,7 +1512,7 @@ export function extractTaskScopedRequirementIds(requirementMarkdown, ...fallback
|
|
|
1501
1512
|
.sort((a, b) => a - b);
|
|
1502
1513
|
return numbered.map((value) => `AC-${value}`);
|
|
1503
1514
|
}
|
|
1504
|
-
function
|
|
1515
|
+
function buildDagSourceBindingBase(sources) {
|
|
1505
1516
|
const sourceEntries = [
|
|
1506
1517
|
{
|
|
1507
1518
|
kind: "requirement",
|
|
@@ -1536,6 +1547,124 @@ function buildDagSourceBinding(sources) {
|
|
|
1536
1547
|
requirementIds: extractTaskScopedRequirementIds(sources.requirementMarkdown, sources.constraintMarkdown, ...(sources.referenceDocuments ?? []).map((reference) => reference.markdown)),
|
|
1537
1548
|
};
|
|
1538
1549
|
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Source binding v2(AC-001 / AC-FIDELITY-REPAIR-001):仅 frontend taskKind 且存在
|
|
1552
|
+
* 可解析 requirement/acceptance 引用时产出 schemaVersion=2 绑定(ledger 指针 +
|
|
1553
|
+
* requirement→fragment 映射全部必填)。绑定完全来自 task advance 已持久化的
|
|
1554
|
+
* source/requirement-ledger.json(intake 侧同一份受管账本的原始字节/hash/inputDigest),
|
|
1555
|
+
* 绝不从 materialized reference path 重建语义等价但 path/hash 不同的 ledger;
|
|
1556
|
+
* 缺失/损坏/漂移 fail closed(SOURCE_FIDELITY_LEDGER_MISSING / _STALE)。
|
|
1557
|
+
*/
|
|
1558
|
+
function buildDagSourceBinding(sources, taskKind) {
|
|
1559
|
+
const base = buildDagSourceBindingBase(sources);
|
|
1560
|
+
if (taskKind !== "frontend-implementation")
|
|
1561
|
+
return base;
|
|
1562
|
+
const ledgerBinding = loadPersistedFrontendSourceBindingLedger(sources);
|
|
1563
|
+
if (!ledgerBinding)
|
|
1564
|
+
return base;
|
|
1565
|
+
// AC-002 / AC-HARD-002:物化 REQ-SRC-* canonical id 确定性并入 v2 requirementIds。
|
|
1566
|
+
// 生成型 REQ-SRC-* id 无法从 markdown 文本导出(必须来自持久化 ledger),此处
|
|
1567
|
+
// 在 required requirement ids 侧确定性注入,确保 prewrite missing-requirement-ids
|
|
1568
|
+
// 不误伤(不要求文本可导出)也不漏检(物化 id 必须被契约覆盖)。
|
|
1569
|
+
const materializedRequirementIds = Object.keys(ledgerBinding.requirementToFragments)
|
|
1570
|
+
.filter((id) => /^REQ-SRC-/.test(id) && !base.requirementIds.includes(id))
|
|
1571
|
+
.sort();
|
|
1572
|
+
return {
|
|
1573
|
+
schemaVersion: 2,
|
|
1574
|
+
taskId: base.taskId,
|
|
1575
|
+
sources: base.sources,
|
|
1576
|
+
requirementIds: [
|
|
1577
|
+
...base.requirementIds,
|
|
1578
|
+
...materializedRequirementIds,
|
|
1579
|
+
],
|
|
1580
|
+
ledgerPath: ledgerBinding.ledgerPath,
|
|
1581
|
+
ledgerSha256: ledgerBinding.ledgerSha256,
|
|
1582
|
+
inputDigest: ledgerBinding.inputDigest,
|
|
1583
|
+
requirementToFragments: ledgerBinding.requirementToFragments,
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* 读取 Task Contract 已持久化的 source/requirement-ledger.json,核验字节 sha256
|
|
1588
|
+
* 与 inputDigest(与当前源输入 digest 一致)后产出 v2 绑定字段。缺失/损坏/漂移
|
|
1589
|
+
* 抛稳定错误(SOURCE_FIDELITY_LEDGER_MISSING / SOURCE_FIDELITY_LEDGER_STALE)。
|
|
1590
|
+
* 新鲜度侧不再"重新推导"权威输入,而是从持久化 ledger 恢复 build 侧的输入合同
|
|
1591
|
+
* (与 ledger build 使用完全相同的输入),避免 contract apply 重投影派生导航视图
|
|
1592
|
+
* 需求.md 导致重抽取漂移误判 stale(AC-DIGEST-001/002/003)。
|
|
1593
|
+
*/
|
|
1594
|
+
function loadPersistedFrontendSourceBindingLedger(sources) {
|
|
1595
|
+
const referenceDocs = (sources.referenceDocuments ?? []).filter((doc) => doc.markdown.trim().length > 0);
|
|
1596
|
+
if (referenceDocs.length === 0)
|
|
1597
|
+
return null;
|
|
1598
|
+
// 存在性守卫(保持既有 v1 回退行为):派生视图无可抽取 requirement/acceptance
|
|
1599
|
+
// 引用时不要求持久化 ledger,直接回退 base binding。此守卫只用于"是否 ledger
|
|
1600
|
+
// 任务"判定,绝不参与 digest 键计算——键输入完全来自持久化 ledger 恢复。
|
|
1601
|
+
const guardFacts = extractRequirementFactsFromMarkdown(sources.requirementMarkdown);
|
|
1602
|
+
if (guardFacts.acceptanceCriteria.length === 0)
|
|
1603
|
+
return null;
|
|
1604
|
+
const ledgerAbsolutePath = path.join(sources.taskDir, "source", REQUIREMENT_LEDGER_FILE_NAME);
|
|
1605
|
+
let raw;
|
|
1606
|
+
try {
|
|
1607
|
+
raw = readFileSync(ledgerAbsolutePath, "utf8");
|
|
1608
|
+
}
|
|
1609
|
+
catch (error) {
|
|
1610
|
+
throw new Error(`SOURCE_FIDELITY_LEDGER_MISSING: frontend task ${sources.taskId} has resolvable requirement/acceptance references but no persisted ${REQUIREMENT_LEDGER_FILE_NAME} at ${ledgerAbsolutePath}; run task advance (intake) to persist the ledger before dag init-hybrid (${error instanceof Error ? error.message : String(error)})`);
|
|
1611
|
+
}
|
|
1612
|
+
const ledgerSha256 = createHash("sha256").update(raw).digest("hex");
|
|
1613
|
+
let ledger;
|
|
1614
|
+
try {
|
|
1615
|
+
ledger = parseLedgerJson(raw);
|
|
1616
|
+
}
|
|
1617
|
+
catch (error) {
|
|
1618
|
+
throw new Error(`SOURCE_FIDELITY_LEDGER_MISSING: persisted ${REQUIREMENT_LEDGER_FILE_NAME} is not a valid v1 ledger: ${error instanceof Error ? error.message : String(error)}`);
|
|
1619
|
+
}
|
|
1620
|
+
// canonical 侧:ledger.canonicalRequirements 按 build 顺序原样保留输入 canonical
|
|
1621
|
+
// requirements(id/text 逐字节),过滤物化 REQ-SRC-*(AC-HARD-002 "物化不入键")
|
|
1622
|
+
// 即精确恢复 digest 输入集;派生导航视图 需求.md 投影不参与键计算。
|
|
1623
|
+
const contract = recoverLedgerInputContract(ledger);
|
|
1624
|
+
if (contract.canonicalRequirements.length === 0) {
|
|
1625
|
+
// AC-HARD-003:存在 v2 ledger 但 canonical requirements 为空时 fail closed,
|
|
1626
|
+
// 绝不回退 v1 base 绑定(buildDagSourceBinding 不降级)。空 canonical 的 ledger
|
|
1627
|
+
// 无法提供 requirement→fragment 映射证据,任何 v1 回退都会静默放行 fail-open。
|
|
1628
|
+
throw new Error(`SOURCE_FIDELITY_LEDGER_STALE: persisted ${REQUIREMENT_LEDGER_FILE_NAME} has an empty canonical requirement set; refusing to fall back to a v1 base binding; re-run task advance (intake) to rebuild the ledger`);
|
|
1629
|
+
}
|
|
1630
|
+
// source 侧:ledger.sourcePaths(build 时权威 sourceDocuments 的 repo-root
|
|
1631
|
+
// relative POSIX 路径集合)作为权威身份集合,对当前 materialized reference 文件
|
|
1632
|
+
// 做交集过滤;内容取当前字节(漂移检测)。权威路径缺失(删除/清空/重命名)→
|
|
1633
|
+
// fail-closed STALE;新增的非权威 role 文件被正确排除(不入键)。
|
|
1634
|
+
const currentByPath = new Map();
|
|
1635
|
+
for (const doc of referenceDocs) {
|
|
1636
|
+
currentByPath.set(toDagSourcePath(sources, doc.path), doc.markdown);
|
|
1637
|
+
}
|
|
1638
|
+
const missingAuthoritativePaths = contract.sourcePaths.filter((sourcePath) => !currentByPath.has(sourcePath));
|
|
1639
|
+
if (missingAuthoritativePaths.length > 0) {
|
|
1640
|
+
throw new Error(`SOURCE_FIDELITY_LEDGER_STALE: persisted ${REQUIREMENT_LEDGER_FILE_NAME} was built from authoritative source documents no longer present (${missingAuthoritativePaths.join(", ")}); re-run task advance (intake) to rebuild the ledger`);
|
|
1641
|
+
}
|
|
1642
|
+
// inputDigest 新鲜度:与当前源输入(canonical repo-root relative identity)一致。
|
|
1643
|
+
// digest 只绑定语义权威输入(规范化源文档 + canonical requirements +
|
|
1644
|
+
// schema/reconciler version);派生导航视图 需求.md 投影不参与键计算。
|
|
1645
|
+
const expectedDigest = computeLedgerInputDigest({
|
|
1646
|
+
sourceDocuments: contract.sourcePaths.map((sourcePath) => ({
|
|
1647
|
+
path: sourcePath,
|
|
1648
|
+
content: currentByPath.get(sourcePath),
|
|
1649
|
+
})),
|
|
1650
|
+
canonicalRequirements: contract.canonicalRequirements,
|
|
1651
|
+
});
|
|
1652
|
+
if (ledger.inputDigest !== expectedDigest) {
|
|
1653
|
+
throw new Error(`SOURCE_FIDELITY_LEDGER_STALE: persisted ${REQUIREMENT_LEDGER_FILE_NAME} inputDigest ${ledger.inputDigest} does not match current source input ${expectedDigest}; re-run task advance (intake) to rebuild the ledger`);
|
|
1654
|
+
}
|
|
1655
|
+
const requirementToFragments = {};
|
|
1656
|
+
for (const requirement of ledger.canonicalRequirements) {
|
|
1657
|
+
if (requirement.sourceFragmentIds.length > 0) {
|
|
1658
|
+
requirementToFragments[requirement.id] = requirement.sourceFragmentIds;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
ledgerPath: toDagSourcePath(sources, ledgerAbsolutePath),
|
|
1663
|
+
ledgerSha256,
|
|
1664
|
+
inputDigest: ledger.inputDigest,
|
|
1665
|
+
requirementToFragments,
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1539
1668
|
function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
1540
1669
|
const binding = buildDagSourceBinding(sources);
|
|
1541
1670
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
@@ -1552,7 +1681,10 @@ function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
|
1552
1681
|
requirementIds: binding.requirementIds,
|
|
1553
1682
|
};
|
|
1554
1683
|
}
|
|
1555
|
-
function buildSourceContextBlock(sources) {
|
|
1684
|
+
function buildSourceContextBlock(sources, options = {}) {
|
|
1685
|
+
const includeRequirementExcerpt = options.includeRequirementExcerpt ?? true;
|
|
1686
|
+
const includeConstraintExcerpt = options.includeConstraintExcerpt ?? true;
|
|
1687
|
+
const includeReferenceDocuments = options.includeReferenceDocuments ?? true;
|
|
1556
1688
|
const requirementRef = toDagSourcePath(sources, sources.requirementPath);
|
|
1557
1689
|
const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
|
|
1558
1690
|
sourceRef: requirementRef,
|
|
@@ -1561,7 +1693,7 @@ function buildSourceContextBlock(sources) {
|
|
|
1561
1693
|
const parts = [
|
|
1562
1694
|
`## Task source: 需求.md`,
|
|
1563
1695
|
`Bound readPath (use for Pi read-tool calls): ${requirementRef}`,
|
|
1564
|
-
requirementExcerpt.text,
|
|
1696
|
+
...(includeRequirementExcerpt ? [requirementExcerpt.text] : []),
|
|
1565
1697
|
];
|
|
1566
1698
|
if (sources.constraintMarkdown) {
|
|
1567
1699
|
const constraintRef = toDagSourcePath(sources, sources.constraintPath);
|
|
@@ -1569,9 +1701,11 @@ function buildSourceContextBlock(sources) {
|
|
|
1569
1701
|
sourceRef: constraintRef,
|
|
1570
1702
|
});
|
|
1571
1703
|
boundReadPaths.push(`- constraints: ${constraintRef}`);
|
|
1572
|
-
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, constraintExcerpt.text);
|
|
1704
|
+
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, ...(includeConstraintExcerpt ? [constraintExcerpt.text] : []));
|
|
1573
1705
|
}
|
|
1574
|
-
for (const reference of (
|
|
1706
|
+
for (const reference of (includeReferenceDocuments
|
|
1707
|
+
? sources.referenceDocuments ?? []
|
|
1708
|
+
: []).slice(0, MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS)) {
|
|
1575
1709
|
const relativePath = path
|
|
1576
1710
|
.relative(path.join(sources.taskDir, "source"), reference.path)
|
|
1577
1711
|
.replaceAll(path.sep, "/");
|
|
@@ -1859,6 +1993,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1859
1993
|
executor: "pi",
|
|
1860
1994
|
complexity: "MED",
|
|
1861
1995
|
writePolicy: "read-only",
|
|
1996
|
+
readBudget: FRONTEND_READ_BUDGETS.contract,
|
|
1862
1997
|
allowedPaths: taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"],
|
|
1863
1998
|
forbiddenPaths,
|
|
1864
1999
|
outputContract: "Plain Markdown implementation contract (10 lines or fewer); no file writes.",
|
|
@@ -1875,6 +2010,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1875
2010
|
executor: "pi",
|
|
1876
2011
|
complexity: scoutComplexity,
|
|
1877
2012
|
writePolicy: "read-only",
|
|
2013
|
+
readBudget: FRONTEND_READ_BUDGETS.scout,
|
|
1878
2014
|
allowedPaths: scoutPaths.srcPaths,
|
|
1879
2015
|
forbiddenPaths,
|
|
1880
2016
|
outputContract: "Plain Markdown source reconnaissance summary; no file writes.",
|
|
@@ -1891,6 +2027,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1891
2027
|
executor: "pi",
|
|
1892
2028
|
complexity: scoutComplexity,
|
|
1893
2029
|
writePolicy: "read-only",
|
|
2030
|
+
readBudget: FRONTEND_READ_BUDGETS.plan,
|
|
1894
2031
|
allowedPaths: scoutPaths.testPaths,
|
|
1895
2032
|
forbiddenPaths,
|
|
1896
2033
|
outputContract: "Plain Markdown test coverage reconnaissance summary; no file writes.",
|
|
@@ -2282,7 +2419,24 @@ export function filterRelevantOpenspecCandidates(input) {
|
|
|
2282
2419
|
}
|
|
2283
2420
|
}
|
|
2284
2421
|
// 保持候选发现顺序(确定性);mandatory 与相关候选都按原始顺序出现。
|
|
2285
|
-
|
|
2422
|
+
const maxCandidates = input.maxCandidates ?? 24;
|
|
2423
|
+
if (relevant.length <= maxCandidates)
|
|
2424
|
+
return relevant;
|
|
2425
|
+
const mandatoryRelevant = relevant.filter((candidate) => mandatory.has(candidate));
|
|
2426
|
+
const optionalRelevant = relevant.filter((candidate) => !mandatory.has(candidate));
|
|
2427
|
+
return [
|
|
2428
|
+
...mandatoryRelevant,
|
|
2429
|
+
...optionalRelevant.slice(0, Math.max(0, maxCandidates - mandatoryRelevant.length)),
|
|
2430
|
+
];
|
|
2431
|
+
}
|
|
2432
|
+
function buildFrontendSpecCandidateSummaries(input) {
|
|
2433
|
+
const mandatory = new Set(input.mandatoryPaths);
|
|
2434
|
+
return input.paths
|
|
2435
|
+
.map((candidate) => scoreFrontendSpecCandidate(candidate, "", {
|
|
2436
|
+
registryMode: input.registryModes.get(candidate),
|
|
2437
|
+
taskRelated: mandatory.has(candidate) || openspecCandidateMatchesSource(candidate, input.sourceMarkdown),
|
|
2438
|
+
}))
|
|
2439
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
2286
2440
|
}
|
|
2287
2441
|
/**
|
|
2288
2442
|
* 候选路径是否与任务源相关:从任务源提取文件名/路径 token(去扩展名、
|
|
@@ -2347,6 +2501,21 @@ async function resolveFrontendOpenspecGateConfig(sources) {
|
|
|
2347
2501
|
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"));
|
|
2348
2502
|
const taskSourceCited = extractTaskSourceFrontendSpecPaths(remappedSourceMarkdown, specRoots, governanceRoot);
|
|
2349
2503
|
const scanStrict = sources.frontendProjectCapability?.designEvidence.normativePaths ?? [];
|
|
2504
|
+
const registry = sources.repoRoot
|
|
2505
|
+
? await readFrontendSpecRegistry(sources.repoRoot)
|
|
2506
|
+
: null;
|
|
2507
|
+
const registryModes = new Map();
|
|
2508
|
+
for (const root of registry?.roots ?? []) {
|
|
2509
|
+
if (root.scope && !root.scope.includes("frontend"))
|
|
2510
|
+
continue;
|
|
2511
|
+
if (!root.mode)
|
|
2512
|
+
continue;
|
|
2513
|
+
for (const candidate of scanStrict) {
|
|
2514
|
+
if (candidate === root.path || candidate.startsWith(`${root.path}/`)) {
|
|
2515
|
+
registryModes.set(candidate, root.mode);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2350
2519
|
const dedupeSorted = (paths) => [...new Set(paths)].sort();
|
|
2351
2520
|
const openspecCandidateSources = {
|
|
2352
2521
|
declared: dedupeSorted(declared),
|
|
@@ -2356,16 +2525,30 @@ async function resolveFrontendOpenspecGateConfig(sources) {
|
|
|
2356
2525
|
// Candidate discovery is frozen independently from the eventual must-read
|
|
2357
2526
|
// set. The selector may mark candidates irrelevant, while explicit task
|
|
2358
2527
|
// declarations/source citations are never allowed to be downgraded.
|
|
2359
|
-
const openspecMandatoryPaths = dedupeSorted([
|
|
2360
|
-
|
|
2361
|
-
...
|
|
2362
|
-
...openspecMandatoryPaths,
|
|
2528
|
+
const openspecMandatoryPaths = dedupeSorted([
|
|
2529
|
+
...declared,
|
|
2530
|
+
...taskSourceCited,
|
|
2363
2531
|
]);
|
|
2532
|
+
// `cited` must stay demand-driven: a normative registry makes a path
|
|
2533
|
+
// discoverable, not implicitly task-mandatory. Only scan-strict may offer
|
|
2534
|
+
// the global discovery set to the bounded task-relevance selector.
|
|
2535
|
+
const openspecCandidatePaths = policy === "cited"
|
|
2536
|
+
? openspecMandatoryPaths
|
|
2537
|
+
: dedupeSorted([
|
|
2538
|
+
...openspecCandidateSources.scanStrict,
|
|
2539
|
+
...openspecMandatoryPaths,
|
|
2540
|
+
]);
|
|
2364
2541
|
const result = {
|
|
2365
2542
|
openspecPolicy: policy,
|
|
2366
2543
|
openspecCandidatePaths,
|
|
2367
2544
|
openspecCandidateSources,
|
|
2368
2545
|
openspecMandatoryPaths,
|
|
2546
|
+
openspecCandidateSummaries: buildFrontendSpecCandidateSummaries({
|
|
2547
|
+
paths: openspecCandidatePaths,
|
|
2548
|
+
mandatoryPaths: openspecMandatoryPaths,
|
|
2549
|
+
registryModes,
|
|
2550
|
+
sourceMarkdown: [sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"),
|
|
2551
|
+
}),
|
|
2369
2552
|
};
|
|
2370
2553
|
if (sources.repoRoot && openspecCandidatePaths.length > 0) {
|
|
2371
2554
|
result.openspecCandidateSnapshots = await Promise.all(openspecCandidatePaths.map(async (candidate) => ({
|
|
@@ -2381,10 +2564,10 @@ const frontendComponentConformanceInstruction = [
|
|
|
2381
2564
|
"## Component Selection conformance (uiComponentChoices; hard rule)",
|
|
2382
2565
|
"每个 UI 用途必须在契约的 uiComponentChoices[] 中声明组件选型:{ purpose, component, decision, specReference, rationale }。",
|
|
2383
2566
|
"- decision=specified:前端规范(候选组件/主题桶 + 任务源显式引用)已定义该用途组件 → 必须使用该组件,并给精确 specReference { path, section, line }(path 必须是 openspec/ai_workspace 受支持规范路径)。",
|
|
2384
|
-
"- decision=reuse-existing
|
|
2385
|
-
"- decision=new
|
|
2567
|
+
"- decision=reuse-existing:仅当该组件/惯例**确实已存在于仓库当前代码**(如复用现有 ActiveRunBadge 的 oc- class 惯例)→ specReference 可为 null,rationale 必须指明复用的具体现有组件/文件与依据。",
|
|
2568
|
+
"- decision=new:任务源/PRD 要求**新增**该组件(仓库当前不存在该组件文件)→ decision 必须为 new,不得标 reuse-existing;specReference 指向任务源 PRD 中定义该组件的 { path, section, line }(path 为 bound PRD 路径,如 .harness/tasks/<taskId>/source/references/prd.md),rationale 说明新增纯展示组件、复用既有 CSS 命名与主题变量约定;design-review 会复核 specReference 可追溯性。",
|
|
2386
2569
|
"不得静默替换规范组件或自创组件而无偏差声明;spec 已定义该用途组件时不得改选其它组件。",
|
|
2387
|
-
"prewrite gate
|
|
2570
|
+
"prewrite gate 确定性交叉校验:仅 decision=specified 的 specReference 必须是候选 OpenSpec 路径、在 typed decision ledger 中声明且有成功 read 事件;decision=new 的 PRD 引用走任务源可追溯性审查,不得按 OpenSpec 候选拒绝。候选桶非空且契约有 UI 可见工作而 uiComponentChoices 缺失/空 → component-choices-missing。",
|
|
2388
2571
|
].join("\n");
|
|
2389
2572
|
function resolveFrontendCapabilityContextBlock(sources) {
|
|
2390
2573
|
const risk = sources.frontendRisk;
|
|
@@ -2398,34 +2581,7 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2398
2581
|
}
|
|
2399
2582
|
if (capability) {
|
|
2400
2583
|
parts.push("", capability.adapterGuidance);
|
|
2401
|
-
|
|
2402
|
-
const bucketLines = [];
|
|
2403
|
-
const pushBucket = (label, paths) => {
|
|
2404
|
-
if (paths.length > 0)
|
|
2405
|
-
bucketLines.push(`${label}: ${paths.join(", ")}`);
|
|
2406
|
-
};
|
|
2407
|
-
pushBucket("schemas", classified.schemas);
|
|
2408
|
-
pushBucket("code-template", classified.codeTemplate);
|
|
2409
|
-
pushBucket("rule.api", classified.rule.api);
|
|
2410
|
-
pushBucket("rule.mock", classified.rule.mock);
|
|
2411
|
-
pushBucket("rule.router", classified.rule.router);
|
|
2412
|
-
pushBucket("rule.hooks", classified.rule.hooks);
|
|
2413
|
-
pushBucket("rule.utils", classified.rule.utils);
|
|
2414
|
-
pushBucket("rule.components", classified.rule.components);
|
|
2415
|
-
pushBucket("rule.other", classified.rule.other);
|
|
2416
|
-
pushBucket("theme", classified.theme);
|
|
2417
|
-
pushBucket("component", classified.component);
|
|
2418
|
-
pushBucket("ui-other", classified.uiOther);
|
|
2419
|
-
pushBucket("advisory-other", classified.advisoryOther);
|
|
2420
|
-
parts.push("## Classified openspec specification paths (role semantics)");
|
|
2421
|
-
if (bucketLines.length > 0) {
|
|
2422
|
-
parts.push(...bucketLines);
|
|
2423
|
-
}
|
|
2424
|
-
else {
|
|
2425
|
-
parts.push("(no openspec specification paths discovered — greenfield)");
|
|
2426
|
-
}
|
|
2427
|
-
parts.push("component / theme / rule.components 是「组件/主题规范」必读语义桶:规范已定义某用途组件时必须使用它(uiComponentChoices 用 decision=specified + 精确 path/section/line),不得静默替换为自认更合适的组件;无规范定义时才允许 reuse-existing 或 new(new 必须声明偏差 rationale)。这些路径在生成期冻结为 prewrite gate 的 componentSpecCandidatePaths。");
|
|
2428
|
-
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.");
|
|
2584
|
+
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.");
|
|
2429
2585
|
parts.push(`A11y capability: ${capability.a11y.status}` +
|
|
2430
2586
|
(capability.a11y.tools.length
|
|
2431
2587
|
? ` (${capability.a11y.tools.join(", ")})`
|
|
@@ -2658,7 +2814,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2658
2814
|
}),
|
|
2659
2815
|
shapeTransitionCapsule: resolveFrontendShapeCapsuleGenerationInput(sources),
|
|
2660
2816
|
});
|
|
2661
|
-
const frontendSourceBinding = buildDagSourceBinding(sources);
|
|
2817
|
+
const frontendSourceBinding = buildDagSourceBinding(sources, taskConfig.taskKind);
|
|
2662
2818
|
const frontendContractSkeleton = buildFrontendImplementationContractSkeleton({
|
|
2663
2819
|
sourceBinding: frontendSourceBinding,
|
|
2664
2820
|
riskLevel: frontendRisk.selectedRisk,
|
|
@@ -2682,7 +2838,9 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2682
2838
|
"",
|
|
2683
2839
|
"## Critical rules",
|
|
2684
2840
|
"- verificationTargets is a TOP-LEVEL required array",
|
|
2841
|
+
"- Every verificationTargets[] entry MUST carry uiStates. Use uiStates: [] when it does not verify a named UI state; never omit the key in a canonical contract.",
|
|
2685
2842
|
"- uiStates items use name/applicable/expectedBehavior/implementationTargets/verificationTargetIds/notApplicableReason",
|
|
2843
|
+
"- interactions items use name/trigger/expectedBehavior/implementationTargets/verificationTargetIds",
|
|
2686
2844
|
"- Use uiStates: [] for frontend logic changes with no user-visible UI state. Do not invent UI states.",
|
|
2687
2845
|
"- 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.",
|
|
2688
2846
|
"- mockApi.productionDefaultOff must always be true (including strategy: not-needed)",
|
|
@@ -2692,6 +2850,13 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2692
2850
|
"",
|
|
2693
2851
|
"## Bad / Good contract field examples",
|
|
2694
2852
|
"",
|
|
2853
|
+
"### Common model key aliases — BAD / GOOD:",
|
|
2854
|
+
'{"name":"error","applicable":false,"reason":"No failing request exists"} <-- BAD: use notApplicableReason, never reason',
|
|
2855
|
+
'{"name":"error","applicable":false,"notApplicableReason":"No failing request exists"} <-- GOOD',
|
|
2856
|
+
'{"id":"save-click","trigger":"User presses Save","expectedBehavior":"Persists the draft","implementationTargets":["src/save.ts"],"verificationTargetIds":["vt-1"]} <-- BAD: use name, never id',
|
|
2857
|
+
'{"name":"save-click","trigger":"User presses Save","expectedBehavior":"Persists the draft","implementationTargets":["src/save.ts"],"verificationTargetIds":["vt-1"]} <-- GOOD',
|
|
2858
|
+
'{"id":"vt-1","type":"unit","commandLabel":"npm run test:unit","file":"src/save.test.ts","requirementIds":["AC-001"]} <-- BAD: uiStates is required; use uiStates: [] when unbound',
|
|
2859
|
+
"",
|
|
2695
2860
|
"### verificationTargets - BAD (invented commandLabel, missing file):",
|
|
2696
2861
|
'{"id":"vt-1","type":"static","commandLabel":"lint","file":"","requirementIds":["AC-001"],"uiStates":[]} <-- REJECTED: commandLabel not in frozen command set; empty file path',
|
|
2697
2862
|
"",
|
|
@@ -2737,17 +2902,20 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2737
2902
|
"### uiComponentChoices - GOOD (specified with precise spec hit):",
|
|
2738
2903
|
'{"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"}',
|
|
2739
2904
|
"",
|
|
2740
|
-
"### uiComponentChoices - GOOD (new
|
|
2741
|
-
'{"purpose":"
|
|
2905
|
+
"### uiComponentChoices - GOOD (new component mandated by PRD; specReference points at the PRD section):",
|
|
2906
|
+
'{"purpose":"task source badge","component":"TaskSourceBadge","decision":"new","specReference":{"path":".harness/tasks/<taskId>/source/references/prd.md","section":"需求","line":4},"rationale":"PRD mandates a new TaskSourceBadge component (not present in the repo); build it as a pure presentational component reusing existing oc- scoped classes and theme variables"}',
|
|
2907
|
+
"",
|
|
2908
|
+
"### uiComponentChoices - GOOD (reuse-existing only for a component actually present in the repo):",
|
|
2909
|
+
'{"purpose":"status dot","component":"ActiveRunBadge","decision":"reuse-existing","specReference":null,"rationale":"ActiveRunBadge already exists in static-src and provides the oc- dot styling convention"}',
|
|
2742
2910
|
"",
|
|
2743
|
-
"### uiComponentChoices - BAD (
|
|
2744
|
-
'{"purpose":"primary action","component":"
|
|
2745
|
-
'{"purpose":"
|
|
2911
|
+
"### uiComponentChoices - BAD (mandated-new component marked reuse-existing, or new without PRD specReference):",
|
|
2912
|
+
'{"purpose":"primary action","component":"TaskSourceBadge","decision":"reuse-existing","specReference":null,"rationale":"reuse existing component"} <-- REJECTED: PRD mandates adding this component; it does not exist yet, so decision must be new with a PRD specReference',
|
|
2913
|
+
'{"purpose":"loading skeleton","component":"SkeletonCard","decision":"new","specReference":null,"rationale":"no spec or existing component covers skeleton"} <-- REJECTED: new must carry a task-source PRD specReference for traceability',
|
|
2746
2914
|
].join("\n");
|
|
2747
2915
|
})();
|
|
2748
2916
|
const frontendContractFieldSummary = [
|
|
2749
2917
|
"## Contract field summary (authoritative JSON; no plan prose)",
|
|
2750
|
-
"
|
|
2918
|
+
"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:",
|
|
2751
2919
|
"- requirements[]: id, expectedOutcome, implementationTargets, verificationTargetIds, evidenceGap",
|
|
2752
2920
|
"- uiStates[]: name, applicable, expectedBehavior, implementationTargets, verificationTargetIds, notApplicableReason",
|
|
2753
2921
|
"- interactions[]: name, trigger, expectedBehavior, implementationTargets, verificationTargetIds",
|
|
@@ -2757,14 +2925,36 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2757
2925
|
"- designEvidence: source, paths, conflicts; evidenceGaps[] (optional)",
|
|
2758
2926
|
"- optional: stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap",
|
|
2759
2927
|
"- uiComponentChoices[]: purpose, component, decision (specified|reuse-existing|new), specReference { path, section, line } | null, rationale",
|
|
2928
|
+
"- Common model aliases are never canonical: uiStates[].reason → uiStates[].notApplicableReason; interactions[].id → interactions[].name; verificationTargets[].uiStates must be an array (use [] when unbound).",
|
|
2760
2929
|
"Do not require or read a separate plan prose section; the contract JSON is the only plan surface.",
|
|
2761
2930
|
].join("\n");
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2931
|
+
// Do not carry every attachment through the whole frontend pipeline. The
|
|
2932
|
+
// contract node is the sole requirements/materials synthesis point; scout
|
|
2933
|
+
// and plan only need the canonical request plus constraints, while design
|
|
2934
|
+
// review consumes the materialized contract and only needs source provenance.
|
|
2935
|
+
// This prevents attachment content from accumulating on later review calls.
|
|
2936
|
+
const sourceContexts = {
|
|
2937
|
+
contract: [buildSourceContextBlock(sources), capabilityContextBlock]
|
|
2938
|
+
.filter(Boolean)
|
|
2939
|
+
.join("\n\n"),
|
|
2940
|
+
scout: [
|
|
2941
|
+
buildSourceContextBlock(sources, { includeReferenceDocuments: false }),
|
|
2942
|
+
capabilityContextBlock,
|
|
2943
|
+
]
|
|
2944
|
+
.filter(Boolean)
|
|
2945
|
+
.join("\n\n"),
|
|
2946
|
+
plan: [
|
|
2947
|
+
buildSourceContextBlock(sources, { includeReferenceDocuments: false }),
|
|
2948
|
+
capabilityContextBlock,
|
|
2949
|
+
]
|
|
2950
|
+
.filter(Boolean)
|
|
2951
|
+
.join("\n\n"),
|
|
2952
|
+
designReview: buildSourceContextBlock(sources, {
|
|
2953
|
+
includeRequirementExcerpt: false,
|
|
2954
|
+
includeConstraintExcerpt: false,
|
|
2955
|
+
includeReferenceDocuments: false,
|
|
2956
|
+
}),
|
|
2957
|
+
};
|
|
2768
2958
|
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
2769
2959
|
mockCapability.verifyCommands.length > 0;
|
|
2770
2960
|
const requirementIds = frontendSourceBinding.requirementIds;
|
|
@@ -2951,13 +3141,34 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2951
3141
|
// 引用)始终保留;scan-strict 候选按路径段是否命中任务源关键词过滤。
|
|
2952
3142
|
// runtime 的选型/read 门禁仍消费完整 openspecCandidatePaths——这里只
|
|
2953
3143
|
// 减小 prompt 体积,不改变门禁语义;未提及的候选 runtime 默认 irrelevant。
|
|
2954
|
-
const
|
|
2955
|
-
candidates: openspecGate.
|
|
3144
|
+
const promptCandidatePaths = filterRelevantOpenspecCandidates({
|
|
3145
|
+
candidates: openspecGate.openspecCandidateSummaries.map((candidate) => candidate.path),
|
|
2956
3146
|
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
2957
3147
|
sourceMarkdown: [
|
|
2958
3148
|
sources.requirementMarkdown,
|
|
2959
3149
|
sources.constraintMarkdown ?? "",
|
|
2960
3150
|
].join("\n"),
|
|
3151
|
+
maxCandidates: 24,
|
|
3152
|
+
});
|
|
3153
|
+
const candidateByPath = new Map(openspecGate.openspecCandidateSummaries.map((candidate) => [
|
|
3154
|
+
candidate.path,
|
|
3155
|
+
candidate,
|
|
3156
|
+
]));
|
|
3157
|
+
// Preserve the filter's mandatory/relevance order. Sorting then slicing here
|
|
3158
|
+
// used to be able to drop a mandatory path after it passed the Top-K filter.
|
|
3159
|
+
const promptCandidates = promptCandidatePaths.flatMap((candidatePath) => {
|
|
3160
|
+
const candidate = candidateByPath.get(candidatePath);
|
|
3161
|
+
return candidate
|
|
3162
|
+
? [
|
|
3163
|
+
{
|
|
3164
|
+
path: candidate.path,
|
|
3165
|
+
kind: candidate.kind,
|
|
3166
|
+
score: candidate.score,
|
|
3167
|
+
reasons: candidate.reasons,
|
|
3168
|
+
source: candidate.source,
|
|
3169
|
+
},
|
|
3170
|
+
]
|
|
3171
|
+
: [];
|
|
2961
3172
|
});
|
|
2962
3173
|
const openspecSelectionContext = JSON.stringify({
|
|
2963
3174
|
schemaVersion: 1,
|
|
@@ -2965,6 +3176,14 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2965
3176
|
candidates: promptCandidates,
|
|
2966
3177
|
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
2967
3178
|
});
|
|
3179
|
+
const scopedOpenspecContext = promptCandidates.length > 0
|
|
3180
|
+
? [
|
|
3181
|
+
"## Task-relevant OpenSpec Top-K (generation frozen)",
|
|
3182
|
+
`Policy: ${openspecGate.openspecPolicy}. This is the bounded prompt index; the deterministic gate retains ${openspecGate.openspecCandidatePaths.length} frozen candidates.`,
|
|
3183
|
+
...promptCandidates.map((candidate) => `- ${openspecGate.openspecMandatoryPaths.includes(candidate.path) ? "mandatory" : "candidate"}: ${candidate.path} (${candidate.kind}; ${candidate.source})`),
|
|
3184
|
+
"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.",
|
|
3185
|
+
].join("\n")
|
|
3186
|
+
: "";
|
|
2968
3187
|
// Generation-frozen component/theme specification bucket (ADR 0016). Derived
|
|
2969
3188
|
// from the classified component/theme/rule.components buckets; the prewrite
|
|
2970
3189
|
// gate consumes it to enforce uiComponentChoices presence and specReference
|
|
@@ -3022,18 +3241,20 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3022
3241
|
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.",
|
|
3023
3242
|
subtask_prompt: [
|
|
3024
3243
|
"Read task source and produce a concise frontend implementation contract as typed requirement facts plus narrative Markdown.",
|
|
3025
|
-
"Assign each requirement
|
|
3244
|
+
"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.",
|
|
3245
|
+
"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.",
|
|
3026
3246
|
"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.",
|
|
3027
3247
|
"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.",
|
|
3028
3248
|
"If the task is too large for one bounded writer, record a task split proposal instead of silently widening scope.",
|
|
3029
3249
|
"End the contract with a single disposition: ready, ready-with-assumptions (bounded assumptions that do not change product behavior), or blocked.",
|
|
3250
|
+
scopedOpenspecContext,
|
|
3030
3251
|
...(requiresOpenspecClassification ? [
|
|
3031
3252
|
"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.",
|
|
3032
3253
|
"Mandatory paths are enforced by the runtime from the frozen task configuration — do not enumerate them, do not downgrade them.",
|
|
3033
3254
|
openspecSelectionContext,
|
|
3034
3255
|
] : []),
|
|
3035
3256
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
3036
|
-
|
|
3257
|
+
sourceContexts.contract,
|
|
3037
3258
|
].join("\n\n"),
|
|
3038
3259
|
},
|
|
3039
3260
|
{
|
|
@@ -3053,7 +3274,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3053
3274
|
"Report target surface and design evidence as facts (no fixed section title required): entrypoint, routeOrMount, implementationPaths, testPaths, dataSource, allowedPathConflicts. Use repository-relative POSIX paths. 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.",
|
|
3054
3275
|
"Derive all file paths from this target workspace. Do not assume the project uses src/, test/, React, or the loop-agent repository layout.",
|
|
3055
3276
|
"Read-only: do not modify repository files.",
|
|
3056
|
-
|
|
3277
|
+
sourceContexts.scout,
|
|
3278
|
+
scopedOpenspecContext,
|
|
3057
3279
|
].join("\n\n"),
|
|
3058
3280
|
},
|
|
3059
3281
|
{
|
|
@@ -3090,7 +3312,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3090
3312
|
verificationTargetFileInstruction,
|
|
3091
3313
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
3092
3314
|
fixedVerificationContext,
|
|
3093
|
-
|
|
3315
|
+
sourceContexts.plan,
|
|
3316
|
+
scopedOpenspecContext,
|
|
3094
3317
|
mockContextBlock,
|
|
3095
3318
|
frontendContractSchemaBlock,
|
|
3096
3319
|
frontendComponentConformanceInstruction,
|
|
@@ -3166,6 +3389,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3166
3389
|
executor: "pi",
|
|
3167
3390
|
complexity: "MED",
|
|
3168
3391
|
writePolicy: "read-only",
|
|
3392
|
+
readBudget: FRONTEND_READ_BUDGETS.designReview,
|
|
3393
|
+
retryPolicy: DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
3169
3394
|
allowedPaths: readOnlyPaths,
|
|
3170
3395
|
forbiddenPaths,
|
|
3171
3396
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
@@ -3177,11 +3402,13 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3177
3402
|
"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).",
|
|
3178
3403
|
"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.",
|
|
3179
3404
|
"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.",
|
|
3180
|
-
"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;
|
|
3405
|
+
"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.",
|
|
3181
3406
|
"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.",
|
|
3182
3407
|
"Read-only: do not modify repository files.",
|
|
3408
|
+
"Canonical contract JSON: frontend-design-policy-shell prints its absolute output path in the upstream output (Contract: /abs/path/.harness/dag-runs/.../contracts/frontend-implementation-contract.json). Read exactly that path when you need the full contract — never resolve a bare contracts/... path against the repository root (it does not exist there), and do not hunt for substitutes. Implementation target files inside the writeSet (e.g. TaskSourceBadge.tsx or its test file) are created later by the implement node: do not read them and do not treat their absence as a design defect.",
|
|
3183
3409
|
fixedVerificationContext,
|
|
3184
|
-
|
|
3410
|
+
sourceContexts.designReview,
|
|
3411
|
+
scopedOpenspecContext,
|
|
3185
3412
|
frontendContractFieldSummary,
|
|
3186
3413
|
mockContextBlock,
|
|
3187
3414
|
].join("\n\n"),
|
|
@@ -3325,6 +3552,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3325
3552
|
executor: "pi",
|
|
3326
3553
|
complexity: "HIGH",
|
|
3327
3554
|
writePolicy: "read-only",
|
|
3555
|
+
readBudget: FRONTEND_READ_BUDGETS.finalReview,
|
|
3328
3556
|
retryPolicy: FRONTEND_REVIEW_TERMINAL_RETRY_POLICY,
|
|
3329
3557
|
allowedPaths: readOnlyPaths,
|
|
3330
3558
|
forbiddenPaths,
|
|
@@ -3335,7 +3563,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3335
3563
|
"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.",
|
|
3336
3564
|
"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.",
|
|
3337
3565
|
"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.",
|
|
3338
|
-
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds the validated implementation contract, frontend lint assessment when lint is configured, the effective verification trace, and the run-owned actual diff
|
|
3566
|
+
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds the validated implementation contract, frontend lint assessment when lint is configured, the effective verification trace, and the run-owned actual diff. 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/symbol binding only—not semantic correctness.",
|
|
3339
3567
|
"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.",
|
|
3340
3568
|
"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).",
|
|
3341
3569
|
"The contract embedded in 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 bound diff, and diff-referenced files when semantic review requires source code.",
|
|
@@ -3384,6 +3612,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3384
3612
|
...(spec.advisories ?? []),
|
|
3385
3613
|
`frontend-task-shape: ${frontendTaskShape.shape} (${frontendTaskShape.reason})`,
|
|
3386
3614
|
];
|
|
3615
|
+
// Freeze the frontend recovery continuation quota from task.json so the
|
|
3616
|
+
// runner can bound M6 auto-recovery without re-reading the task config.
|
|
3617
|
+
const frontendMaxContinuations = sources.taskConfig.frontendRecovery?.maxContinuations ?? 1;
|
|
3618
|
+
spec.frontendRecovery = { maxContinuations: frontendMaxContinuations };
|
|
3387
3619
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3388
3620
|
parseDagSpec(spec);
|
|
3389
3621
|
assertValidDagSpec(spec);
|
|
@@ -6716,7 +6948,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6716
6948
|
if (!spec.runtimeContract) {
|
|
6717
6949
|
spec.runtimeContract = GENERATED_DAG_RUNTIME_CONTRACT;
|
|
6718
6950
|
}
|
|
6719
|
-
spec.sourceBinding = buildDagSourceBinding(sources);
|
|
6951
|
+
spec.sourceBinding = buildDagSourceBinding(sources, template === "frontend-implementation" ? "frontend-implementation" : undefined);
|
|
6720
6952
|
spec.taskContractBinding = taskContractBinding;
|
|
6721
6953
|
assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
|
|
6722
6954
|
if (template === "standard-dag" ||
|