@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
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
repoBaseName,
|
|
35
35
|
parseMarkdownBlocks,
|
|
36
36
|
markdownLinkHref,
|
|
37
|
+
formatLedgerSummary,
|
|
37
38
|
} from "../format.js";
|
|
38
39
|
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
40
|
import {
|
|
@@ -94,6 +95,7 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
94
95
|
const linksEl = document.getElementById("task-links");
|
|
95
96
|
const evidenceEl = document.getElementById("task-evidence");
|
|
96
97
|
const runsEl = document.getElementById("task-runs");
|
|
98
|
+
const ledgerEl = document.getElementById("task-ledger");
|
|
97
99
|
if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
|
|
98
100
|
if (titleEl) titleEl.textContent = displayLabel;
|
|
99
101
|
mountViewState(metaEl, "loading", "加载 Task…");
|
|
@@ -252,9 +254,62 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
252
254
|
const active = ["running", "pending", "blocked", "stale"].includes(
|
|
253
255
|
(task.status ?? "").toLowerCase(),
|
|
254
256
|
);
|
|
257
|
+
if (ledgerEl) {
|
|
258
|
+
void renderTaskLedger(ledgerEl, taskId);
|
|
259
|
+
}
|
|
255
260
|
return active;
|
|
256
261
|
}
|
|
257
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Task 详情页 ledger 安全摘要(AC-009):从只读 GET /api/tasks/:id/ledger 拉取,
|
|
265
|
+
* 计数/状态/failure code + 有界排除理由;无 ledger 显示空态,失败安全降级。
|
|
266
|
+
*/
|
|
267
|
+
async function renderTaskLedger(ledgerEl, taskId) {
|
|
268
|
+
clearNode(ledgerEl);
|
|
269
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
270
|
+
mountViewState(ledgerEl, "loading", "加载 ledger…");
|
|
271
|
+
let body = null;
|
|
272
|
+
try {
|
|
273
|
+
body = await fetchJson(`/api/tasks/${encodeURIComponent(taskId)}/ledger`);
|
|
274
|
+
} catch {
|
|
275
|
+
body = null;
|
|
276
|
+
}
|
|
277
|
+
if (!body) {
|
|
278
|
+
mountViewState(ledgerEl, "error", "ledger 只读接口失败(安全降级)。");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (body.present !== true) {
|
|
282
|
+
mountViewState(
|
|
283
|
+
ledgerEl,
|
|
284
|
+
body.error ? "error" : "empty",
|
|
285
|
+
body.error
|
|
286
|
+
? `ledger 不可用(${body.error})。`
|
|
287
|
+
: "无 source fidelity ledger(任务无引用或 ledger 缺失,不伪造计数)。",
|
|
288
|
+
);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const pre = el(
|
|
292
|
+
"pre",
|
|
293
|
+
"cmd",
|
|
294
|
+
formatLedgerSummary({
|
|
295
|
+
present: true,
|
|
296
|
+
fragmentCount: body.fragmentCount,
|
|
297
|
+
canonicalRequirementCount: body.canonicalRequirementCount,
|
|
298
|
+
contextOnlyCount: body.contextOnlyCount,
|
|
299
|
+
duplicateCount: body.duplicateCount,
|
|
300
|
+
unresolvedCount: body.unresolvedCount,
|
|
301
|
+
conflictCount: body.conflictCount,
|
|
302
|
+
gateStatus: body.gateStatus,
|
|
303
|
+
failureCode: body.failureCode,
|
|
304
|
+
exclusions: body.exclusions ?? [],
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
pre.dataset.testid = "task-ledger-summary";
|
|
308
|
+
clearNode(ledgerEl);
|
|
309
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
310
|
+
ledgerEl.appendChild(pre);
|
|
311
|
+
}
|
|
312
|
+
|
|
258
313
|
export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
|
|
259
314
|
if (runsEl === undefined) {
|
|
260
315
|
runsEl = before;
|
|
@@ -148,9 +148,10 @@ export function checkMockStrategy(contract, allowedMockStrategies) {
|
|
|
148
148
|
}
|
|
149
149
|
return findings;
|
|
150
150
|
}
|
|
151
|
-
/** 4. Component choice must be precisely
|
|
152
|
-
* a specReference whose normalized path is a candidate and a non-empty
|
|
153
|
-
* `new`
|
|
151
|
+
/** 4. Component choice must be precisely evidence-referenced: `specified`
|
|
152
|
+
* requires a specReference whose normalized path is a candidate and a non-empty
|
|
153
|
+
* section; `new` requires a task-source reference with a non-empty section;
|
|
154
|
+
* `reuse-existing` may omit it. The
|
|
154
155
|
* candidate set is the full generation-frozen openspec candidate list (the
|
|
155
156
|
* shell passes openspecCandidatePaths with componentSpecCandidatePaths as the
|
|
156
157
|
* fallback), so directory-organized component libraries are never rejected.
|
|
@@ -199,12 +200,21 @@ export function checkComponentChoice(contract, componentSpecCandidatePaths) {
|
|
|
199
200
|
});
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
|
-
else if (choice.decision === "new"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
else if (choice.decision === "new") {
|
|
204
|
+
if (!choice.specReference || !choice.specReference.path) {
|
|
205
|
+
findings.push({
|
|
206
|
+
code: "component-new-source-reference-missing",
|
|
207
|
+
message: `new component choice #${index} is missing its task-source specReference`,
|
|
208
|
+
path: choice.purpose,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else if (!choice.specReference.section.trim()) {
|
|
212
|
+
findings.push({
|
|
213
|
+
code: "component-new-source-reference-missing",
|
|
214
|
+
message: `new component choice #${index} has an empty task-source reference section`,
|
|
215
|
+
path: choice.purpose,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
208
218
|
}
|
|
209
219
|
});
|
|
210
220
|
return findings;
|
|
@@ -13,7 +13,9 @@ import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
|
|
|
13
13
|
// only at call time, so the contract module and the policy module can depend
|
|
14
14
|
// on each other without module-init ordering issues.
|
|
15
15
|
import { checkDependencies, checkTargetPaths, checkUiDesignCoverage, checkUiStateAttribution, } from "./frontend-design-policy.js";
|
|
16
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
16
17
|
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
18
|
+
import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
|
|
17
19
|
import { isConfigurationVerificationFile, verificationSymbolMatchesContent, } from "./frontend-verification-trace.js";
|
|
18
20
|
export const frontendNormalizationActionSchema = z.enum([
|
|
19
21
|
"remove-trailing-commas",
|
|
@@ -286,13 +288,13 @@ const gap = z
|
|
|
286
288
|
* `specReference` is required (path/section/line hit).
|
|
287
289
|
* - `reuse-existing`: reuse an existing repo component/convention not named by
|
|
288
290
|
* the spec → `specReference` may be null, rationale explains the basis.
|
|
289
|
-
* - `new`:
|
|
290
|
-
*
|
|
291
|
+
* - `new`: the task source mandates a component absent from the repository →
|
|
292
|
+
* `specReference` is required and points at that task-source declaration;
|
|
293
|
+
* rationale explains the bounded addition.
|
|
291
294
|
*
|
|
292
|
-
* `specReference` is nullable + optional
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* identically.
|
|
295
|
+
* `specReference` is nullable + optional at the wire boundary because
|
|
296
|
+
* `stripNullValuesDeep` removes model-emitted nulls before validation. The
|
|
297
|
+
* decision-specific requirements are enforced by the superRefine below.
|
|
296
298
|
*/
|
|
297
299
|
const uiComponentSpecReferenceSchema = z
|
|
298
300
|
.object({
|
|
@@ -308,7 +310,8 @@ const uiComponentChoiceSchema = z
|
|
|
308
310
|
decision: z.enum(["specified", "reuse-existing", "new"]),
|
|
309
311
|
specReference: uiComponentSpecReferenceSchema.nullable().optional(),
|
|
310
312
|
// Optional: the decision enum already carries the semantic (specified →
|
|
311
|
-
// specReference, new →
|
|
313
|
+
// specReference, new → task-source traceability, reuse-existing → no
|
|
314
|
+
// rationale needed).
|
|
312
315
|
// Omit for reuse-existing to keep plan output small.
|
|
313
316
|
rationale: z.string().min(1).optional(),
|
|
314
317
|
})
|
|
@@ -338,49 +341,12 @@ function stripNullValuesDeep(value) {
|
|
|
338
341
|
}
|
|
339
342
|
return value;
|
|
340
343
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Normalize a model-emitted `uiComponentChoices` where a `new` choice carries a
|
|
343
|
-
* degenerate empty `specReference` object (`{ path: "", section: "", line:
|
|
344
|
-
* null }`) instead of the required `null`. The model intent is unambiguous
|
|
345
|
-
* (new components must not carry a specReference; the empty object is a format
|
|
346
|
-
* slip, not a real spec hit), so dropping the empty object before validation
|
|
347
|
-
* avoids a full plan retry on an otherwise-correct emission. A `specReference`
|
|
348
|
-
* with a non-empty `path` is preserved (it is a genuine `specified` hit).
|
|
349
|
-
*/
|
|
350
|
-
export function normalizeNewComponentSpecReference(value) {
|
|
351
|
-
if (!Array.isArray(value))
|
|
352
|
-
return value;
|
|
353
|
-
return value.map((item) => {
|
|
354
|
-
if (!item || typeof item !== "object")
|
|
355
|
-
return item;
|
|
356
|
-
const record = item;
|
|
357
|
-
if (record.decision !== "new")
|
|
358
|
-
return item;
|
|
359
|
-
const ref = record.specReference;
|
|
360
|
-
if (!ref || typeof ref !== "object")
|
|
361
|
-
return item;
|
|
362
|
-
const refRecord = ref;
|
|
363
|
-
const path = typeof refRecord.path === "string" ? refRecord.path.trim() : "";
|
|
364
|
-
if (path.length > 0)
|
|
365
|
-
return item;
|
|
366
|
-
// Empty/degenerate specReference on a new choice → absent (null intent).
|
|
367
|
-
const { specReference: _dropped, ...rest } = record;
|
|
368
|
-
return rest;
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
344
|
export const frontendImplementationContractSchema = z
|
|
372
345
|
.preprocess((value) => {
|
|
373
346
|
const record = asRecord(value);
|
|
374
347
|
if (!record)
|
|
375
348
|
return stripNullValuesDeep(value);
|
|
376
|
-
return stripNullValuesDeep(
|
|
377
|
-
...record,
|
|
378
|
-
...(record.uiComponentChoices !== undefined
|
|
379
|
-
? {
|
|
380
|
-
uiComponentChoices: normalizeNewComponentSpecReference(record.uiComponentChoices),
|
|
381
|
-
}
|
|
382
|
-
: {}),
|
|
383
|
-
});
|
|
349
|
+
return stripNullValuesDeep(record);
|
|
384
350
|
}, z
|
|
385
351
|
.object({
|
|
386
352
|
schemaVersion: z.literal(1),
|
|
@@ -391,8 +357,33 @@ export const frontendImplementationContractSchema = z
|
|
|
391
357
|
requirementSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
392
358
|
referencePaths: z.array(safePath),
|
|
393
359
|
requirementIds: z.array(id),
|
|
360
|
+
// Source binding v2 (frontend): bound ledger pointer + fragment map.
|
|
361
|
+
ledgerPath: safePath.optional(),
|
|
362
|
+
ledgerSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
363
|
+
inputDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
364
|
+
requirementToFragments: z
|
|
365
|
+
.record(z.string(), z.array(z.string()))
|
|
366
|
+
.optional(),
|
|
394
367
|
})
|
|
395
|
-
.strict()
|
|
368
|
+
.strict()
|
|
369
|
+
.superRefine((value, ctx) => {
|
|
370
|
+
// v2 all-or-none:任一 ledger 字段出现则四项必须齐全,防止 v1+optional 冒充 v2。
|
|
371
|
+
const anyLedgerField = value.ledgerPath !== undefined ||
|
|
372
|
+
value.ledgerSha256 !== undefined ||
|
|
373
|
+
value.inputDigest !== undefined ||
|
|
374
|
+
value.requirementToFragments !== undefined;
|
|
375
|
+
const allLedgerFields = value.ledgerPath !== undefined &&
|
|
376
|
+
value.ledgerSha256 !== undefined &&
|
|
377
|
+
value.inputDigest !== undefined &&
|
|
378
|
+
value.requirementToFragments !== undefined;
|
|
379
|
+
if (anyLedgerField && !allLedgerFields) {
|
|
380
|
+
ctx.addIssue({
|
|
381
|
+
code: z.ZodIssueCode.custom,
|
|
382
|
+
path: ["sourceBinding"],
|
|
383
|
+
message: "source binding v2 ledger fields (ledgerPath/ledgerSha256/inputDigest/requirementToFragments) must be provided together; partial or optional v1+ledger bindings are rejected",
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}),
|
|
396
387
|
riskLevel: z.enum(["small", "standard", "high-risk"]),
|
|
397
388
|
targets: z
|
|
398
389
|
.object({
|
|
@@ -408,6 +399,10 @@ export const frontendImplementationContractSchema = z
|
|
|
408
399
|
implementationTargets: z.array(safePath),
|
|
409
400
|
verificationTargetIds: z.array(z.string().min(1)),
|
|
410
401
|
evidenceGap: gap.optional(),
|
|
402
|
+
// Source fidelity ledger binding (AC-005/AC-006): 每个 requirement
|
|
403
|
+
// 携带来源 fragment 证据,prewrite 门禁校验其真实性与覆盖。
|
|
404
|
+
sourceFragmentIds: z.array(z.string().min(1)).optional(),
|
|
405
|
+
sourceRefs: z.array(safePath).optional(),
|
|
411
406
|
})
|
|
412
407
|
.strict()).min(1),
|
|
413
408
|
uiStates: z.array(z
|
|
@@ -544,6 +539,32 @@ export const frontendImplementationContractSchema = z
|
|
|
544
539
|
message: `unknown verification target ${targetId}`,
|
|
545
540
|
path: ["requirements"],
|
|
546
541
|
});
|
|
542
|
+
// Source fidelity ledger binding (AC-005/AC-006): 绑定携带
|
|
543
|
+
// requirementToFragments(ledger v2)时,每个 requirement 必须携带
|
|
544
|
+
// 非空 sourceFragmentIds,否则 fail closed(防引用伪造/缺失)。
|
|
545
|
+
// sourceRefs(fragment→path 的派生展示)不设硬门槛:模型从 binding
|
|
546
|
+
// 只能拿到 fragment id 拿不到权威 path,强制只会诱导编造;防伪造的
|
|
547
|
+
// 真实性校验由 fidelity gate 按 sourceFragmentIds 执行(存在性 +
|
|
548
|
+
// 与 ledger boundFragments 一致)。
|
|
549
|
+
const boundFragments = value.sourceBinding.requirementToFragments?.[requirement.id];
|
|
550
|
+
if (value.sourceBinding.ledgerPath &&
|
|
551
|
+
(!requirement.sourceFragmentIds ||
|
|
552
|
+
requirement.sourceFragmentIds.length === 0)) {
|
|
553
|
+
ctx.addIssue({
|
|
554
|
+
code: "custom",
|
|
555
|
+
message: `requirement ${requirement.id} must carry sourceFragmentIds when a source fidelity ledger is bound`,
|
|
556
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
else if (boundFragments !== undefined &&
|
|
560
|
+
(!requirement.sourceFragmentIds ||
|
|
561
|
+
requirement.sourceFragmentIds.length === 0)) {
|
|
562
|
+
ctx.addIssue({
|
|
563
|
+
code: "custom",
|
|
564
|
+
message: `requirement ${requirement.id} is bound to ledger fragments ${boundFragments.join(", ")} but declares none`,
|
|
565
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
566
|
+
});
|
|
567
|
+
}
|
|
547
568
|
}
|
|
548
569
|
for (const state of value.uiStates) {
|
|
549
570
|
if (state.applicable &&
|
|
@@ -573,10 +594,10 @@ export const frontendImplementationContractSchema = z
|
|
|
573
594
|
}
|
|
574
595
|
}
|
|
575
596
|
else if (choice.decision === "new") {
|
|
576
|
-
if (choice.specReference) {
|
|
597
|
+
if (!choice.specReference || !choice.specReference.path) {
|
|
577
598
|
ctx.addIssue({
|
|
578
599
|
code: "custom",
|
|
579
|
-
message: "new component choice
|
|
600
|
+
message: "new component choice requires a task-source specReference.path",
|
|
580
601
|
path: ["uiComponentChoices", index, "specReference"],
|
|
581
602
|
});
|
|
582
603
|
}
|
|
@@ -680,6 +701,29 @@ export async function assertFrontendSourceBindingFresh(input) {
|
|
|
680
701
|
throw new Error(`frontend source binding is stale: ${source.path}`);
|
|
681
702
|
}
|
|
682
703
|
}
|
|
704
|
+
// Source binding v2 ledger 新鲜度(AC-FIDELITY-REPAIR-001/002):v2 绑定携带的
|
|
705
|
+
// ledger 文件必须存在且 sha256 与绑定一致,否则 fail closed;v1 绑定无此路径。
|
|
706
|
+
if (isDagSourceBindingV2(input.binding)) {
|
|
707
|
+
const absolute = resolveDagTaskSourcePath({
|
|
708
|
+
workspaceRoot: input.workspaceRoot,
|
|
709
|
+
taskId: input.binding.taskId,
|
|
710
|
+
sourcePath: input.binding.ledgerPath,
|
|
711
|
+
});
|
|
712
|
+
let content;
|
|
713
|
+
try {
|
|
714
|
+
content = await readFile(absolute);
|
|
715
|
+
const info = await stat(absolute);
|
|
716
|
+
if (!info.isFile())
|
|
717
|
+
throw new Error("not a regular file");
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
throw new Error(`frontend source fidelity ledger unavailable: ${input.binding.ledgerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
721
|
+
}
|
|
722
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
723
|
+
if (actual !== input.binding.ledgerSha256) {
|
|
724
|
+
throw new Error(`frontend source fidelity ledger is stale or tampered: ${input.binding.ledgerPath} (bound ${input.binding.ledgerSha256}, observed ${actual})`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
683
727
|
}
|
|
684
728
|
const SECRET_KEY = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential|authorization)/i;
|
|
685
729
|
const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b)/;
|
|
@@ -854,7 +898,7 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
854
898
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
855
899
|
if (!requirement)
|
|
856
900
|
throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
|
|
857
|
-
|
|
901
|
+
const sourceBinding = {
|
|
858
902
|
taskId: binding.taskId,
|
|
859
903
|
requirementPath: requirement.path,
|
|
860
904
|
requirementSha256: requirement.sha256,
|
|
@@ -864,6 +908,15 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
864
908
|
.sort(),
|
|
865
909
|
requirementIds: binding.requirementIds.map(canonicalizeRequirementId),
|
|
866
910
|
};
|
|
911
|
+
// Source binding v2(schemaVersion=2 强制字段):透传 ledger 指针与
|
|
912
|
+
// requirement→fragment 映射。v1 绑定不得以 optional 字段冒充 v2。
|
|
913
|
+
if (isDagSourceBindingV2(binding)) {
|
|
914
|
+
sourceBinding.ledgerPath = binding.ledgerPath;
|
|
915
|
+
sourceBinding.ledgerSha256 = binding.ledgerSha256;
|
|
916
|
+
sourceBinding.inputDigest = binding.inputDigest;
|
|
917
|
+
sourceBinding.requirementToFragments = binding.requirementToFragments;
|
|
918
|
+
}
|
|
919
|
+
return sourceBinding;
|
|
867
920
|
}
|
|
868
921
|
function asRecord(value) {
|
|
869
922
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -1436,6 +1489,14 @@ export function coerceFrontendImplementationContractInput(value, canonicalBindin
|
|
|
1436
1489
|
verificationTargetIds: verificationTargetIds.length > 0
|
|
1437
1490
|
? verificationTargetIds
|
|
1438
1491
|
: defaultVerificationIds,
|
|
1492
|
+
// Source fidelity provenance 透传:兼容分支重建 requirement 时必须
|
|
1493
|
+
// 保留已声明的 sourceFragmentIds/sourceRefs(strict 分支通过
|
|
1494
|
+
// `...requirement` 保留,两边必须一致),否则 ledger-bound 契约在
|
|
1495
|
+
// designEvidence 缺失等情况下走兼容分支时丢失防伪造证据。
|
|
1496
|
+
...(Array.isArray(req.sourceFragmentIds)
|
|
1497
|
+
? { sourceFragmentIds: req.sourceFragmentIds }
|
|
1498
|
+
: {}),
|
|
1499
|
+
...(Array.isArray(req.sourceRefs) ? { sourceRefs: req.sourceRefs } : {}),
|
|
1439
1500
|
};
|
|
1440
1501
|
// Do not mark free-form realIntegrationGap as blocking; defer to FE-TEST/FINAL-VERIFY.
|
|
1441
1502
|
if (gapText) {
|
|
@@ -2075,11 +2136,17 @@ export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
|
2075
2136
|
sha256: canonicalArtifact.sha256,
|
|
2076
2137
|
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
2077
2138
|
};
|
|
2139
|
+
// Human-readable plan summary appended after the artifact pointer. The
|
|
2140
|
+
// pointer remains the machine authority (path/schema/sha256); the
|
|
2141
|
+
// rendered summary is a deterministic view of the canonical contract so
|
|
2142
|
+
// Console/reviewers can read the plan without opening the JSON. It never
|
|
2143
|
+
// participates in downstream validation.
|
|
2144
|
+
const planSummary = renderFrontendPlanMarkdown(analysis.canonical);
|
|
2078
2145
|
return {
|
|
2079
2146
|
ok: true,
|
|
2080
2147
|
contract: analysis.canonical,
|
|
2081
2148
|
artifact,
|
|
2082
|
-
normalizedText: formatStructuredArtifactPointer(artifact)
|
|
2149
|
+
normalizedText: `${formatStructuredArtifactPointer(artifact)}\n\n${planSummary}`,
|
|
2083
2150
|
};
|
|
2084
2151
|
}
|
|
2085
2152
|
catch (error) {
|
|
@@ -47,6 +47,27 @@ function componentSelection(contract) {
|
|
|
47
47
|
})
|
|
48
48
|
.join("\n");
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 有界 source fidelity ledger 指针(AC-007 / AC-FIDELITY-007):长原文不重新
|
|
52
|
+
* 全量内联到节点提示词;只注入紧凑 canonical contract 与 bound ledger 指针
|
|
53
|
+
* (路径/哈希/inputDigest),ledger coverage 证明长尾未丢失。
|
|
54
|
+
*/
|
|
55
|
+
function sourceFidelityLedger(contract) {
|
|
56
|
+
const binding = contract.sourceBinding;
|
|
57
|
+
if (!binding.ledgerPath)
|
|
58
|
+
return "_(not bound — v1 source binding)_";
|
|
59
|
+
const lines = [
|
|
60
|
+
`- ledger: ${binding.ledgerPath}`,
|
|
61
|
+
`- ledgerSha256: ${binding.ledgerSha256 ?? "(missing)"}`,
|
|
62
|
+
`- inputDigest: ${binding.inputDigest ?? "(missing)"}`,
|
|
63
|
+
"- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划只消费派生摘要与 bound ledger 指针,长原文不内联。",
|
|
64
|
+
];
|
|
65
|
+
const boundCount = Object.keys(binding.requirementToFragments ?? {}).length;
|
|
66
|
+
if (boundCount > 0) {
|
|
67
|
+
lines.push(`- requirement→fragment bindings: ${boundCount}`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
50
71
|
export function renderFrontendPlanMarkdown(contract) {
|
|
51
72
|
return [
|
|
52
73
|
"# Frontend Implementation Plan",
|
|
@@ -54,6 +75,12 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
54
75
|
"## Requirement Coverage",
|
|
55
76
|
requirementsCoverage(contract),
|
|
56
77
|
"",
|
|
78
|
+
"## Source Fidelity Ledger",
|
|
79
|
+
sourceFidelityLedger(contract),
|
|
80
|
+
"",
|
|
81
|
+
"## Implementation Steps",
|
|
82
|
+
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
83
|
+
"",
|
|
57
84
|
"## Target Files",
|
|
58
85
|
bulletList(contract.targets.files) || "_(none)_",
|
|
59
86
|
"",
|
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { OPENSPEC_SPEC_EXT_RE, isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
|
|
6
6
|
import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
|
|
7
7
|
import { countExcludedGovernanceSpecFiles, resolveOpenspecGovernanceRoot, } from "../../task/frontend-project-capability.js";
|
|
8
|
+
import { checkFrontendSourceFidelityLedger, } from "./frontend-source-fidelity-ledger.js";
|
|
8
9
|
import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
|
|
9
10
|
import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
|
|
10
11
|
import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
|
|
@@ -42,6 +43,14 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
|
|
|
42
43
|
"component-choices-missing",
|
|
43
44
|
"contract-too-large",
|
|
44
45
|
"revision-patch-unmergeable",
|
|
46
|
+
// Source fidelity ledger 类目(SOURCE_FIDELITY_*/source-* 稳定错误码)。
|
|
47
|
+
"source-fidelity-ledger-missing",
|
|
48
|
+
"source-fidelity-ledger-stale",
|
|
49
|
+
"source-fidelity-unresolved",
|
|
50
|
+
"source-fidelity-conflict",
|
|
51
|
+
"source-fidelity-high-risk-uncovered",
|
|
52
|
+
"source-fidelity-requirement-not-covered",
|
|
53
|
+
"source-fidelity-source-ref-invalid",
|
|
45
54
|
]);
|
|
46
55
|
export const frontendPrewriteResultV1Schema = z
|
|
47
56
|
.object({
|
|
@@ -503,6 +512,7 @@ async function finalizePrewrite(input, pending) {
|
|
|
503
512
|
openspecPolicy: pending.openspecPolicy,
|
|
504
513
|
};
|
|
505
514
|
}
|
|
515
|
+
export { checkFrontendSourceFidelityLedger } from "./frontend-source-fidelity-ledger.js";
|
|
506
516
|
export async function runFrontendPrewriteGate(input) {
|
|
507
517
|
const workspaceRoot = input.workspaceRoot ?? input.repoRoot;
|
|
508
518
|
// selectNode throws for a scheduling defect (no FINISHED plan/review). That
|
|
@@ -647,6 +657,33 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
647
657
|
}
|
|
648
658
|
throw error;
|
|
649
659
|
}
|
|
660
|
+
// Source fidelity ledger gate (20260824-frontend-source-fidelity-ledger-v1):
|
|
661
|
+
// 在 verdict/missing-requirement-ids/source-binding-not-fresh 之后插入 ledger
|
|
662
|
+
// 检查。缺失即 fail closed 且 writer 不获授权;所有检查通过才继续物化契约。
|
|
663
|
+
if (input.sourceBinding) {
|
|
664
|
+
const ledgerCheck = await checkFrontendSourceFidelityLedger({
|
|
665
|
+
workspaceRoot: workspaceRoot ?? "",
|
|
666
|
+
binding: input.sourceBinding,
|
|
667
|
+
contractRequirements: analysis.canonical.requirements.map((requirement) => ({
|
|
668
|
+
id: requirement.id,
|
|
669
|
+
sourceFragmentIds: requirement.sourceFragmentIds,
|
|
670
|
+
sourceRefs: requirement.sourceRefs,
|
|
671
|
+
})),
|
|
672
|
+
});
|
|
673
|
+
if (!ledgerCheck.ok && ledgerCheck.failureCode) {
|
|
674
|
+
return finalizePrewrite(input, {
|
|
675
|
+
...basePending,
|
|
676
|
+
verdict,
|
|
677
|
+
candidateJsonSha256: analysis.candidateJsonSha256,
|
|
678
|
+
normalizationActions: [],
|
|
679
|
+
mockStrategy: analysis.canonical.mockApi.strategy,
|
|
680
|
+
classification: "blocked",
|
|
681
|
+
failureReason: ledgerCheck.failureReason ??
|
|
682
|
+
"frontend source fidelity ledger gate failed",
|
|
683
|
+
failureCode: ledgerCheck.failureCode,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
650
687
|
// Size guard: a canonical contract above 64KB is a retryable-invalid defect
|
|
651
688
|
// (compress the contract) rather than a writer authorization failure.
|
|
652
689
|
const contractSizeBytes = Buffer.byteLength(serializeDeterministicJson(analysis.canonical), "utf8");
|
|
@@ -873,13 +910,17 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
873
910
|
normalizationActions: [],
|
|
874
911
|
mockStrategy,
|
|
875
912
|
classification: "retryable-invalid",
|
|
876
|
-
failureReason: "frontend prewrite gate: the canonical contract has UI-visible work but uiComponentChoices is missing or empty while the generation-frozen component/theme specification bucket is non-empty. 为每个 UI 用途声明组件选型(decision: specified | reuse-existing | new
|
|
913
|
+
failureReason: "frontend prewrite gate: the canonical contract has UI-visible work but uiComponentChoices is missing or empty while the generation-frozen component/theme specification bucket is non-empty. 为每个 UI 用途声明组件选型(decision: specified | reuse-existing | new);specified 必须给 OpenSpec specReference.path/section/line,new 必须给任务源 PRD specReference.path/section/line。",
|
|
877
914
|
failureCode: "component-choices-missing",
|
|
878
915
|
openspecReadPaths,
|
|
879
916
|
openspecCandidatePaths: candidatePaths,
|
|
880
917
|
});
|
|
881
918
|
}
|
|
919
|
+
// Only `specified` choices cite the frozen OpenSpec candidate bucket. A
|
|
920
|
+
// `new` choice instead cites its task-source/PRD declaration and must not be
|
|
921
|
+
// rejected merely because that source is outside the OpenSpec roots.
|
|
882
922
|
const componentSpecReferences = componentChoices
|
|
923
|
+
.filter((choice) => choice.decision === "specified")
|
|
883
924
|
.map((choice) => ({
|
|
884
925
|
raw: choice.specReference?.path ?? "",
|
|
885
926
|
normalized: choice.specReference?.path
|
|
@@ -1024,6 +1065,9 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
1024
1065
|
}
|
|
1025
1066
|
lines.push(`Prewrite result: ${result.prewriteResult.path}`);
|
|
1026
1067
|
lines.push(`Openspec policy: ${result.openspecPolicy}`);
|
|
1068
|
+
if (result.failureCode?.startsWith("source-fidelity-")) {
|
|
1069
|
+
lines.push(`Source fidelity ledger: blocked (${result.failureCode})`);
|
|
1070
|
+
}
|
|
1027
1071
|
if (result.failureReason)
|
|
1028
1072
|
lines.push(`Failure reason: ${result.failureReason}`);
|
|
1029
1073
|
if (result.openspecReadPaths.length > 0) {
|
|
@@ -15,6 +15,7 @@ export function projectLegacyOperationState(state) {
|
|
|
15
15
|
case "queued":
|
|
16
16
|
case "starting":
|
|
17
17
|
case "running":
|
|
18
|
+
case "restarting":
|
|
18
19
|
return { operationState: "active" };
|
|
19
20
|
case "succeeded":
|
|
20
21
|
return { operationState: "success", operationTerminal: "success" };
|