@tea-agent/loop-agent 0.40.0-next.2 → 0.40.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- 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/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -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 +19 -3
- 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-DLMldIE8.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DVOg1dSW.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-715v7joo.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtMEGtAQ.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-qUxXsdxu.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BYMag72f.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5jt5rtx.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DvZghfS2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CXutxAmY.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BkANIDWk.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bwy1wwlr.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3pWYVCG.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DRzjzmFD.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C4xXDxXL.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BngIZ90b.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-B40ACoFn.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BE-1ZRAI.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BoYbwVTh.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B5-KA0fK.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DJpCToT5.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-De8R04Ng.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BU1N8DiA.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BUUUzSYI.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-zLoT1RC4.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-_q4-4N8c.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CTqzoNSL.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMKSYKJM.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Dj6U8Sb7.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BfoxVYky.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-CA0nQEbb.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-T5NfvHGV.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BEhUMkut.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B2Ld6nNt.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-nV9T85yS.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIO1zDYh.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-B_ucAqLq.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BIA3VNcZ.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CNzLjNV3.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-PWytzfc_.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CS0wGoIq.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-C3Wsqph8.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DF6FDTYg.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BdQUJm6y.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bv9rppGg.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C3Hpvb_B.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CZOWLpno.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CPQRIZx5.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CkwM9BgA.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-wGSoDzYc.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-GNVas2gx.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C5USnHw7.js → xychartDiagram-ELKLHX3M-YGBjKAS5.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/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- 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 +338 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +531 -78
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +4 -3
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +125 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +42 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-BG6TLen_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dgsvn63q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DaNfG1BJ.js +0 -8
|
@@ -1,17 +1,39 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
4
5
|
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
|
|
5
6
|
import { frontendLintAssessmentArtifactSchema } from "./frontend-lint-baseline.js";
|
|
6
|
-
import { frontendImplementationContractSchema } from "./frontend-implementation-contract.js";
|
|
7
|
+
import { frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
7
8
|
import { frontendRepairAssessmentSchema } from "./frontend-repair.js";
|
|
9
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
+
import { ledgerStatsLine, parseLedgerJson, } from "../../task/source-prepare/ledger.js";
|
|
8
11
|
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
|
|
12
|
+
/**
|
|
13
|
+
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
14
|
+
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
15
|
+
* 匹配裸 Error 文本。message 保持人类可读(shell stderr 兼容),code 供机械化路由。
|
|
16
|
+
*/
|
|
17
|
+
export const FRONTEND_REVIEW_CONTEXT_FAILURE_CODES = [
|
|
18
|
+
"review-context-invalid-contract",
|
|
19
|
+
"review-context-verification-unbound",
|
|
20
|
+
"review-context-ledger-drift",
|
|
21
|
+
"review-context-manifest-missing",
|
|
22
|
+
];
|
|
23
|
+
export class FrontendReviewContextFailure extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = "FrontendReviewContextFailure";
|
|
28
|
+
this.code = code;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
9
31
|
async function readRequiredJson(runDir, relativePath) {
|
|
10
32
|
try {
|
|
11
33
|
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
12
34
|
}
|
|
13
|
-
catch {
|
|
14
|
-
throw new
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
37
|
}
|
|
16
38
|
}
|
|
17
39
|
async function readOptionalLintAssessment(runDir) {
|
|
@@ -32,8 +54,9 @@ async function readOptionalLintAssessment(runDir) {
|
|
|
32
54
|
}
|
|
33
55
|
function assertReviewEvidence(input) {
|
|
34
56
|
const contract = frontendImplementationContractSchema.safeParse(input.contract);
|
|
35
|
-
if (!contract.success)
|
|
36
|
-
throw new
|
|
57
|
+
if (!contract.success) {
|
|
58
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
59
|
+
}
|
|
37
60
|
const trace = input.verificationTrace;
|
|
38
61
|
const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
|
|
39
62
|
const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
|
|
@@ -69,17 +92,19 @@ function assertReviewEvidence(input) {
|
|
|
69
92
|
actualIds.size !== expectedTargets.size ||
|
|
70
93
|
!mockEvidenceBound ||
|
|
71
94
|
trace.mockStrategy !== contract.data.mockApi.strategy) {
|
|
72
|
-
throw new
|
|
95
|
+
throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
|
|
73
96
|
}
|
|
74
97
|
const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
|
|
75
|
-
if (!assessment.success)
|
|
76
|
-
throw new
|
|
98
|
+
if (!assessment.success) {
|
|
99
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid repair assessment");
|
|
100
|
+
}
|
|
77
101
|
}
|
|
78
102
|
export async function runFrontendReviewContextGate(input) {
|
|
79
103
|
const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
|
|
80
104
|
const parsedContract = frontendImplementationContractSchema.safeParse(contract);
|
|
81
|
-
if (!parsedContract.success)
|
|
82
|
-
throw new
|
|
105
|
+
if (!parsedContract.success) {
|
|
106
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
107
|
+
}
|
|
83
108
|
const diff = await runFrontendWorktreeDiffGate({
|
|
84
109
|
...input,
|
|
85
110
|
authorizedChangedPaths: parsedContract.data.targets.files,
|
|
@@ -88,6 +113,9 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
88
113
|
const repairAssessment = await readRequiredJson(input.runDir, "contracts/frontend-repair-assessment.json");
|
|
89
114
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
90
115
|
assertReviewEvidence({ contract, verificationTrace, repairAssessment });
|
|
116
|
+
// Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
|
|
117
|
+
// 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
|
|
118
|
+
const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
|
|
91
119
|
const payload = {
|
|
92
120
|
schemaVersion: 1,
|
|
93
121
|
schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
|
|
@@ -95,6 +123,7 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
95
123
|
verificationTrace,
|
|
96
124
|
repairAssessment,
|
|
97
125
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
126
|
+
sourceFidelity,
|
|
98
127
|
diff: {
|
|
99
128
|
schemaId: diff.schemaId,
|
|
100
129
|
patchPath: diff.patchPath,
|
|
@@ -107,6 +136,59 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
107
136
|
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
|
|
108
137
|
return { ok: true, artifactPath, diff };
|
|
109
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
|
|
141
|
+
* diff 越界、验证证据缺失均阻断 pass。清单以有界安全摘要注入提示词,不泄漏原文。
|
|
142
|
+
*/
|
|
143
|
+
async function buildReviewSourceFidelityChecklist(input, contract) {
|
|
144
|
+
const binding = contract.sourceBinding;
|
|
145
|
+
const checklist = [];
|
|
146
|
+
// 契约级 sourceBinding 的 v2 语义:四个 ledger 字段必须齐全(缺失即 v1 遗留绑定)。
|
|
147
|
+
const boundV2 = Boolean(binding.ledgerPath) &&
|
|
148
|
+
Boolean(binding.ledgerSha256) &&
|
|
149
|
+
Boolean(binding.inputDigest) &&
|
|
150
|
+
binding.requirementToFragments !== undefined;
|
|
151
|
+
if (!boundV2) {
|
|
152
|
+
return {
|
|
153
|
+
bound: false,
|
|
154
|
+
ledger: null,
|
|
155
|
+
checklist: [
|
|
156
|
+
"source fidelity ledger: not bound (v1 binding); 按现有语义评审",
|
|
157
|
+
"canonical contract 与实际 diff 逐项核对,验证证据齐全后 verdict 可为 pass",
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const absolute = resolveDagTaskSourcePath({
|
|
162
|
+
workspaceRoot: input.workspaceRoot,
|
|
163
|
+
taskId: contract.sourceBinding.taskId,
|
|
164
|
+
sourcePath: binding.ledgerPath,
|
|
165
|
+
});
|
|
166
|
+
let ledger = null;
|
|
167
|
+
try {
|
|
168
|
+
const raw = await readFile(absolute, "utf8");
|
|
169
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
170
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
171
|
+
throw new Error(`ledger sha256 mismatch (bound ${binding.ledgerSha256}, observed ${actualSha})`);
|
|
172
|
+
}
|
|
173
|
+
ledger = parseLedgerJson(raw);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// AC-011 / AC-HARD-005:v2 drift 返回结构化 failure code(稳定枚举 + reason),
|
|
177
|
+
// 不再裸 throw new Error;message 保持人类可读供 shell stderr。
|
|
178
|
+
throw new FrontendReviewContextFailure("review-context-ledger-drift", `frontend review context source fidelity ledger unavailable or drifted: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
|
+
}
|
|
180
|
+
checklist.push("ledger: 存在且绑定 sha256 一致(无缺失/漂移)", `ledger 覆盖统计: ${ledgerStatsLine(ledger)}`, "canonical contract requirements 必须覆盖 ledger 全部 canonical requirement IDs", "实际 diff 必须限制在契约 targets 内(越界即阻断 pass)", "验证证据必须绑定契约 verificationTargets(缺失即阻断 pass)", "仅满足派生摘要而遗漏 ledger requirement 时 verdict 不得为 pass");
|
|
181
|
+
return {
|
|
182
|
+
bound: true,
|
|
183
|
+
ledger: {
|
|
184
|
+
path: binding.ledgerPath,
|
|
185
|
+
sha256: binding.ledgerSha256,
|
|
186
|
+
inputDigest: binding.inputDigest,
|
|
187
|
+
stats: ledgerStatsLine(ledger),
|
|
188
|
+
},
|
|
189
|
+
checklist,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
110
192
|
export function formatFrontendReviewContextStdout(result) {
|
|
111
193
|
return [
|
|
112
194
|
"Frontend review context: pass",
|
|
@@ -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";
|
|
@@ -22,6 +22,9 @@ import { discoverProjectGovernancePresence } from "./project-governance-context.
|
|
|
22
22
|
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
23
23
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
24
24
|
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
25
|
+
import { extractRequirementFactsFromMarkdown } from "../../task/source-prepare/parse-intent.js";
|
|
26
|
+
import { computeLedgerInputDigest, parseLedgerJson, recoverLedgerInputContract, } from "../../task/source-prepare/ledger.js";
|
|
27
|
+
import { REQUIREMENT_LEDGER_FILE_NAME } from "../../task/contract/constants.js";
|
|
25
28
|
import { dagHasWriterExecution } from "./task-contract-binding.js";
|
|
26
29
|
import { DEFAULT_VERIFY_TIMEOUT_MS, resolveVerifyPreset, } from "../../executors/shell-verification.js";
|
|
27
30
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
@@ -1477,7 +1480,7 @@ export function extractTaskScopedRequirementIds(requirementMarkdown, ...fallback
|
|
|
1477
1480
|
.sort((a, b) => a - b);
|
|
1478
1481
|
return numbered.map((value) => `AC-${value}`);
|
|
1479
1482
|
}
|
|
1480
|
-
function
|
|
1483
|
+
function buildDagSourceBindingBase(sources) {
|
|
1481
1484
|
const sourceEntries = [
|
|
1482
1485
|
{
|
|
1483
1486
|
kind: "requirement",
|
|
@@ -1512,6 +1515,124 @@ function buildDagSourceBinding(sources) {
|
|
|
1512
1515
|
requirementIds: extractTaskScopedRequirementIds(sources.requirementMarkdown, sources.constraintMarkdown, ...(sources.referenceDocuments ?? []).map((reference) => reference.markdown)),
|
|
1513
1516
|
};
|
|
1514
1517
|
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Source binding v2(AC-001 / AC-FIDELITY-REPAIR-001):仅 frontend taskKind 且存在
|
|
1520
|
+
* 可解析 requirement/acceptance 引用时产出 schemaVersion=2 绑定(ledger 指针 +
|
|
1521
|
+
* requirement→fragment 映射全部必填)。绑定完全来自 task advance 已持久化的
|
|
1522
|
+
* source/requirement-ledger.json(intake 侧同一份受管账本的原始字节/hash/inputDigest),
|
|
1523
|
+
* 绝不从 materialized reference path 重建语义等价但 path/hash 不同的 ledger;
|
|
1524
|
+
* 缺失/损坏/漂移 fail closed(SOURCE_FIDELITY_LEDGER_MISSING / _STALE)。
|
|
1525
|
+
*/
|
|
1526
|
+
function buildDagSourceBinding(sources, taskKind) {
|
|
1527
|
+
const base = buildDagSourceBindingBase(sources);
|
|
1528
|
+
if (taskKind !== "frontend-implementation")
|
|
1529
|
+
return base;
|
|
1530
|
+
const ledgerBinding = loadPersistedFrontendSourceBindingLedger(sources);
|
|
1531
|
+
if (!ledgerBinding)
|
|
1532
|
+
return base;
|
|
1533
|
+
// AC-002 / AC-HARD-002:物化 REQ-SRC-* canonical id 确定性并入 v2 requirementIds。
|
|
1534
|
+
// 生成型 REQ-SRC-* id 无法从 markdown 文本导出(必须来自持久化 ledger),此处
|
|
1535
|
+
// 在 required requirement ids 侧确定性注入,确保 prewrite missing-requirement-ids
|
|
1536
|
+
// 不误伤(不要求文本可导出)也不漏检(物化 id 必须被契约覆盖)。
|
|
1537
|
+
const materializedRequirementIds = Object.keys(ledgerBinding.requirementToFragments)
|
|
1538
|
+
.filter((id) => /^REQ-SRC-/.test(id) && !base.requirementIds.includes(id))
|
|
1539
|
+
.sort();
|
|
1540
|
+
return {
|
|
1541
|
+
schemaVersion: 2,
|
|
1542
|
+
taskId: base.taskId,
|
|
1543
|
+
sources: base.sources,
|
|
1544
|
+
requirementIds: [
|
|
1545
|
+
...base.requirementIds,
|
|
1546
|
+
...materializedRequirementIds,
|
|
1547
|
+
],
|
|
1548
|
+
ledgerPath: ledgerBinding.ledgerPath,
|
|
1549
|
+
ledgerSha256: ledgerBinding.ledgerSha256,
|
|
1550
|
+
inputDigest: ledgerBinding.inputDigest,
|
|
1551
|
+
requirementToFragments: ledgerBinding.requirementToFragments,
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* 读取 Task Contract 已持久化的 source/requirement-ledger.json,核验字节 sha256
|
|
1556
|
+
* 与 inputDigest(与当前源输入 digest 一致)后产出 v2 绑定字段。缺失/损坏/漂移
|
|
1557
|
+
* 抛稳定错误(SOURCE_FIDELITY_LEDGER_MISSING / SOURCE_FIDELITY_LEDGER_STALE)。
|
|
1558
|
+
* 新鲜度侧不再"重新推导"权威输入,而是从持久化 ledger 恢复 build 侧的输入合同
|
|
1559
|
+
* (与 ledger build 使用完全相同的输入),避免 contract apply 重投影派生导航视图
|
|
1560
|
+
* 需求.md 导致重抽取漂移误判 stale(AC-DIGEST-001/002/003)。
|
|
1561
|
+
*/
|
|
1562
|
+
function loadPersistedFrontendSourceBindingLedger(sources) {
|
|
1563
|
+
const referenceDocs = (sources.referenceDocuments ?? []).filter((doc) => doc.markdown.trim().length > 0);
|
|
1564
|
+
if (referenceDocs.length === 0)
|
|
1565
|
+
return null;
|
|
1566
|
+
// 存在性守卫(保持既有 v1 回退行为):派生视图无可抽取 requirement/acceptance
|
|
1567
|
+
// 引用时不要求持久化 ledger,直接回退 base binding。此守卫只用于"是否 ledger
|
|
1568
|
+
// 任务"判定,绝不参与 digest 键计算——键输入完全来自持久化 ledger 恢复。
|
|
1569
|
+
const guardFacts = extractRequirementFactsFromMarkdown(sources.requirementMarkdown);
|
|
1570
|
+
if (guardFacts.acceptanceCriteria.length === 0)
|
|
1571
|
+
return null;
|
|
1572
|
+
const ledgerAbsolutePath = path.join(sources.taskDir, "source", REQUIREMENT_LEDGER_FILE_NAME);
|
|
1573
|
+
let raw;
|
|
1574
|
+
try {
|
|
1575
|
+
raw = readFileSync(ledgerAbsolutePath, "utf8");
|
|
1576
|
+
}
|
|
1577
|
+
catch (error) {
|
|
1578
|
+
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)})`);
|
|
1579
|
+
}
|
|
1580
|
+
const ledgerSha256 = createHash("sha256").update(raw).digest("hex");
|
|
1581
|
+
let ledger;
|
|
1582
|
+
try {
|
|
1583
|
+
ledger = parseLedgerJson(raw);
|
|
1584
|
+
}
|
|
1585
|
+
catch (error) {
|
|
1586
|
+
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)}`);
|
|
1587
|
+
}
|
|
1588
|
+
// canonical 侧:ledger.canonicalRequirements 按 build 顺序原样保留输入 canonical
|
|
1589
|
+
// requirements(id/text 逐字节),过滤物化 REQ-SRC-*(AC-HARD-002 "物化不入键")
|
|
1590
|
+
// 即精确恢复 digest 输入集;派生导航视图 需求.md 投影不参与键计算。
|
|
1591
|
+
const contract = recoverLedgerInputContract(ledger);
|
|
1592
|
+
if (contract.canonicalRequirements.length === 0) {
|
|
1593
|
+
// AC-HARD-003:存在 v2 ledger 但 canonical requirements 为空时 fail closed,
|
|
1594
|
+
// 绝不回退 v1 base 绑定(buildDagSourceBinding 不降级)。空 canonical 的 ledger
|
|
1595
|
+
// 无法提供 requirement→fragment 映射证据,任何 v1 回退都会静默放行 fail-open。
|
|
1596
|
+
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`);
|
|
1597
|
+
}
|
|
1598
|
+
// source 侧:ledger.sourcePaths(build 时权威 sourceDocuments 的 repo-root
|
|
1599
|
+
// relative POSIX 路径集合)作为权威身份集合,对当前 materialized reference 文件
|
|
1600
|
+
// 做交集过滤;内容取当前字节(漂移检测)。权威路径缺失(删除/清空/重命名)→
|
|
1601
|
+
// fail-closed STALE;新增的非权威 role 文件被正确排除(不入键)。
|
|
1602
|
+
const currentByPath = new Map();
|
|
1603
|
+
for (const doc of referenceDocs) {
|
|
1604
|
+
currentByPath.set(toDagSourcePath(sources, doc.path), doc.markdown);
|
|
1605
|
+
}
|
|
1606
|
+
const missingAuthoritativePaths = contract.sourcePaths.filter((sourcePath) => !currentByPath.has(sourcePath));
|
|
1607
|
+
if (missingAuthoritativePaths.length > 0) {
|
|
1608
|
+
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`);
|
|
1609
|
+
}
|
|
1610
|
+
// inputDigest 新鲜度:与当前源输入(canonical repo-root relative identity)一致。
|
|
1611
|
+
// digest 只绑定语义权威输入(规范化源文档 + canonical requirements +
|
|
1612
|
+
// schema/reconciler version);派生导航视图 需求.md 投影不参与键计算。
|
|
1613
|
+
const expectedDigest = computeLedgerInputDigest({
|
|
1614
|
+
sourceDocuments: contract.sourcePaths.map((sourcePath) => ({
|
|
1615
|
+
path: sourcePath,
|
|
1616
|
+
content: currentByPath.get(sourcePath),
|
|
1617
|
+
})),
|
|
1618
|
+
canonicalRequirements: contract.canonicalRequirements,
|
|
1619
|
+
});
|
|
1620
|
+
if (ledger.inputDigest !== expectedDigest) {
|
|
1621
|
+
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`);
|
|
1622
|
+
}
|
|
1623
|
+
const requirementToFragments = {};
|
|
1624
|
+
for (const requirement of ledger.canonicalRequirements) {
|
|
1625
|
+
if (requirement.sourceFragmentIds.length > 0) {
|
|
1626
|
+
requirementToFragments[requirement.id] = requirement.sourceFragmentIds;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
ledgerPath: toDagSourcePath(sources, ledgerAbsolutePath),
|
|
1631
|
+
ledgerSha256,
|
|
1632
|
+
inputDigest: ledger.inputDigest,
|
|
1633
|
+
requirementToFragments,
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1515
1636
|
function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
1516
1637
|
const binding = buildDagSourceBinding(sources);
|
|
1517
1638
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
@@ -2416,7 +2537,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2416
2537
|
allowedPaths: taskConfig.allowedPaths,
|
|
2417
2538
|
complexity: taskConfig.complexity,
|
|
2418
2539
|
});
|
|
2419
|
-
const frontendSourceBinding = buildDagSourceBinding(sources);
|
|
2540
|
+
const frontendSourceBinding = buildDagSourceBinding(sources, taskConfig.taskKind);
|
|
2420
2541
|
const frontendContractSkeleton = buildFrontendImplementationContractSkeleton({
|
|
2421
2542
|
sourceBinding: frontendSourceBinding,
|
|
2422
2543
|
riskLevel: frontendRisk.selectedRisk,
|
|
@@ -6563,7 +6684,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6563
6684
|
if (!spec.runtimeContract) {
|
|
6564
6685
|
spec.runtimeContract = GENERATED_DAG_RUNTIME_CONTRACT;
|
|
6565
6686
|
}
|
|
6566
|
-
spec.sourceBinding = buildDagSourceBinding(sources);
|
|
6687
|
+
spec.sourceBinding = buildDagSourceBinding(sources, template === "frontend-implementation" ? "frontend-implementation" : undefined);
|
|
6567
6688
|
spec.taskContractBinding = taskContractBinding;
|
|
6568
6689
|
assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
|
|
6569
6690
|
if (template === "standard-dag" ||
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
6
|
+
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
6
7
|
import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
|
|
7
8
|
import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
8
9
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
@@ -306,6 +307,35 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
306
307
|
"</retry_instruction>",
|
|
307
308
|
].join("\n");
|
|
308
309
|
}
|
|
310
|
+
const PROMPT_RESTART_CANDIDATE_MAX_BYTES = 64 * 1024;
|
|
311
|
+
function promptRestartCandidateForAttempt(input) {
|
|
312
|
+
if (input.attemptPrompt === input.baseRuntimePrompt) {
|
|
313
|
+
return input.taskPrompt;
|
|
314
|
+
}
|
|
315
|
+
const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
|
|
316
|
+
if (input.attemptPrompt.startsWith(appendedPrefix)) {
|
|
317
|
+
return [
|
|
318
|
+
input.taskPrompt,
|
|
319
|
+
input.attemptPrompt.slice(appendedPrefix.length),
|
|
320
|
+
]
|
|
321
|
+
.filter(Boolean)
|
|
322
|
+
.join("\n\n");
|
|
323
|
+
}
|
|
324
|
+
return input.attemptPrompt;
|
|
325
|
+
}
|
|
326
|
+
async function persistFailedPromptRestartCandidate(input) {
|
|
327
|
+
const redacted = redactSecrets(input.prompt);
|
|
328
|
+
const preview = truncateUtf8Preview(redacted, PROMPT_RESTART_CANDIDATE_MAX_BYTES);
|
|
329
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, `prompt-candidate-attempt-${input.attempt}.json`, {
|
|
330
|
+
schemaVersion: 1,
|
|
331
|
+
attempt: input.attempt,
|
|
332
|
+
failureCategory: input.failureCategory ?? "unknown",
|
|
333
|
+
prompt: preview,
|
|
334
|
+
truncated: preview !== redacted,
|
|
335
|
+
maxBytes: PROMPT_RESTART_CANDIDATE_MAX_BYTES,
|
|
336
|
+
sha256: createHash("sha256").update(redacted).digest("hex"),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
309
339
|
export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd, options) {
|
|
310
340
|
const policy = resolveContextPolicy(spec);
|
|
311
341
|
const skillNames = policy.resolveSkills(spec, task);
|
|
@@ -770,6 +800,7 @@ export async function executeDagNode(input) {
|
|
|
770
800
|
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
771
801
|
// ls/find and must not be attributed to the repair attempt).
|
|
772
802
|
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
803
|
+
let promptRestartCandidate = task.subtask_prompt;
|
|
773
804
|
let result;
|
|
774
805
|
try {
|
|
775
806
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -833,6 +864,11 @@ export async function executeDagNode(input) {
|
|
|
833
864
|
previousReason: previousProtocolReason,
|
|
834
865
|
});
|
|
835
866
|
}
|
|
867
|
+
promptRestartCandidate = promptRestartCandidateForAttempt({
|
|
868
|
+
taskPrompt: task.subtask_prompt,
|
|
869
|
+
baseRuntimePrompt: prompt,
|
|
870
|
+
attemptPrompt,
|
|
871
|
+
});
|
|
836
872
|
result = await executeNode({
|
|
837
873
|
task,
|
|
838
874
|
cwd,
|
|
@@ -960,6 +996,15 @@ export async function executeDagNode(input) {
|
|
|
960
996
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
961
997
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
962
998
|
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
999
|
+
if (task.executor === "pi" && !result.ok) {
|
|
1000
|
+
await persistFailedPromptRestartCandidate({
|
|
1001
|
+
runDir,
|
|
1002
|
+
nodeId,
|
|
1003
|
+
attempt: attemptNumber,
|
|
1004
|
+
failureCategory: result.failureCategory,
|
|
1005
|
+
prompt: promptRestartCandidate,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
963
1008
|
const attemptRecord = {
|
|
964
1009
|
attempt: attemptNumber,
|
|
965
1010
|
startedAt: attemptStartedAt,
|
|
@@ -890,20 +890,51 @@ export const dagEvaluationBindingSchema = z
|
|
|
890
890
|
.strict();
|
|
891
891
|
/** Eval Lab Campaign Budget (W3.4); enforced by runner ledger when mode=hard. */
|
|
892
892
|
export const dagBudgetSchema = campaignBudgetSchema;
|
|
893
|
-
export const
|
|
893
|
+
export const dagSourceBindingSourceSchema = z.object({
|
|
894
|
+
kind: z.enum(["requirement", "constraint", "reference"]),
|
|
895
|
+
path: z.string().min(1),
|
|
896
|
+
sha256: z
|
|
897
|
+
.string()
|
|
898
|
+
.regex(/^[a-f0-9]{64}$/, "sha256 must be lowercase hex"),
|
|
899
|
+
});
|
|
900
|
+
/**
|
|
901
|
+
* DagSourceBinding v1:无 ledger 字段(schemaVersion=1,.strict() 拒绝 ledger
|
|
902
|
+
* 字段混入,不得以 v1+optional 冒充 v2)。非 frontend task / 无可解析引用时使用。
|
|
903
|
+
*/
|
|
904
|
+
export const dagSourceBindingV1Schema = z
|
|
905
|
+
.object({
|
|
894
906
|
schemaVersion: z.literal(1),
|
|
895
907
|
taskId: z.string().min(1),
|
|
896
|
-
sources: z
|
|
897
|
-
.array(z.object({
|
|
898
|
-
kind: z.enum(["requirement", "constraint", "reference"]),
|
|
899
|
-
path: z.string().min(1),
|
|
900
|
-
sha256: z
|
|
901
|
-
.string()
|
|
902
|
-
.regex(/^[a-f0-9]{64}$/, "sha256 must be lowercase hex"),
|
|
903
|
-
}))
|
|
904
|
-
.min(1),
|
|
908
|
+
sources: z.array(dagSourceBindingSourceSchema).min(1),
|
|
905
909
|
requirementIds: z.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/)),
|
|
906
|
-
})
|
|
910
|
+
})
|
|
911
|
+
.strict();
|
|
912
|
+
/**
|
|
913
|
+
* DagSourceBinding v2(20260824-frontend-source-fidelity-ledger-v1):仅
|
|
914
|
+
* frontend-implementation 且存在可解析 requirement/acceptance 引用时产出;
|
|
915
|
+
* ledgerPath/ledgerSha256/inputDigest/requirementToFragments 全部必填且完整,
|
|
916
|
+
* 伪造或缺失任一字段被 schema/prewrite 拒绝(AC-002 / AC-FIDELITY-REPAIR-002)。
|
|
917
|
+
*/
|
|
918
|
+
export const dagSourceBindingV2Schema = z
|
|
919
|
+
.object({
|
|
920
|
+
schemaVersion: z.literal(2),
|
|
921
|
+
taskId: z.string().min(1),
|
|
922
|
+
sources: z.array(dagSourceBindingSourceSchema).min(1),
|
|
923
|
+
requirementIds: z.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/)),
|
|
924
|
+
/** 受管 requirement-ledger.json 的 DAG 可解析路径(.harness/tasks/…/source/…)。 */
|
|
925
|
+
ledgerPath: z.string().min(1),
|
|
926
|
+
ledgerSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
927
|
+
inputDigest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
928
|
+
requirementToFragments: z.record(z.string(), z.array(z.string())),
|
|
929
|
+
})
|
|
930
|
+
.strict();
|
|
931
|
+
export const dagSourceBindingSchema = z.discriminatedUnion("schemaVersion", [
|
|
932
|
+
dagSourceBindingV1Schema,
|
|
933
|
+
dagSourceBindingV2Schema,
|
|
934
|
+
]);
|
|
935
|
+
export function isDagSourceBindingV2(binding) {
|
|
936
|
+
return binding.schemaVersion === 2;
|
|
937
|
+
}
|
|
907
938
|
const dagRerunFeedbackEvidencePathSchema = z
|
|
908
939
|
.string()
|
|
909
940
|
.min(1)
|
package/docs/README.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
| 选择快速、标准或完整验证命令 | `governance/verification-matrix.md` |
|
|
28
28
|
| 理解 CLI、runtime 和初始化能力边界 | `runtime/loop-agent-harness.md` |
|
|
29
29
|
| 运行 DAG、查看报告或处理失败 | `runtime/agent-dag-runner.md`、`runtime/agent-dag-recovery-playbook.md`;Operator Chat P0 closeout 见 `reports/feature/2026-08-14-operator-chat-dag-recovery-p0.md` |
|
|
30
|
+
| 理解前端实现 DAG 与需求保真账本 | `runtime/frontend-implementation-workflow.md`(含 source fidelity ledger v2 绑定、顺序 reconciliation、独立 reviewer、human resolution 与 prewrite fail-closed);init-evolution 审查见 `reports/init-evolution/2026-08-24-frontend-source-fidelity-ledger-init-evolution-review.md` |
|
|
30
31
|
| 按 TDD、验证和调试纪律推进修复 | `governance/harness-methodology-tdd.md`、`governance/harness-methodology-verification.md`、`governance/harness-methodology-debugging.md` |
|
|
31
32
|
| 执行真实 backend-test provider 验证 | `runtime/backend-test-live-campaign.md` |
|
|
32
33
|
| 判断单任务 loop-agent Production Readiness | `operations/production-readiness.md` |
|
|
@@ -33,6 +33,7 @@ loop-agent CLI
|
|
|
33
33
|
| Worker readiness baseline | Attempt identity、immutable owner、lineage/revision、task-scoped lease、controller protocol、acceptance、reconcile | Agent Worker PR v1 Phase 1–7 / `0.32.0` |
|
|
34
34
|
| Operator Surface | 裸 `agent-worker console`;Operate、`/inspect/`、Operator Chat、recovery CTA、intake bridge | ADR 0005、0011–0013 |
|
|
35
35
|
| 自举与验证 | controller identity、skill snapshot、deterministic/live canary、verified tarball、`verify:tree` receipt | `runtime-boundaries.md`、verification matrix |
|
|
36
|
+
| Source fidelity ledger | 原始 requirement/acceptance 文档保持语义权威,派生 `source/需求.md` 仅作导航视图;确定性 fragment inventory、受管 `requirement-ledger.json`(进入 Task Contract transaction 与 canonical hash)、source binding v2(仅 frontend taskKind,schemaVersion=2 必填字段)、prewrite fail-closed 门禁(`SOURCE_FIDELITY_*`/`source-*` 稳定错误码)、顺序 reconciliation 与独立 reviewer、human resolution 生效、有界提示词与最终 reviewer 核对清单 | `20260824-frontend-source-fidelity-ledger-v1` + `2026-08-24-frontend-source-fidelity-repair-v2`(本页面下方 init-evolution 审查)、`CHANGELOG.md [Unreleased]` |
|
|
36
37
|
|
|
37
38
|
### 当前不变量
|
|
38
39
|
|
|
@@ -106,3 +107,47 @@ Evidence & Trust Plane
|
|
|
106
107
|
2. 证明成立后更新 current capability summary 和本页“当前架构”。
|
|
107
108
|
3. 未证明部分只保留在 active design/exec-plan,不能通过修改措辞升级状态。
|
|
108
109
|
4. 使用者短索引同步到 `website/docs/overview/roadmap.md`,避免复制长版本流水账。
|
|
110
|
+
|
|
111
|
+
## Init Evolution Review — 2026-08-24 frontend source fidelity ledger
|
|
112
|
+
|
|
113
|
+
Base/Head:见 `docs/reports/init-evolution/` 中对应 `*-init-evolution-review.md`(若已提交则含 Base/Head 字段;未提交工作区由 CI 非严格模式放行)。
|
|
114
|
+
|
|
115
|
+
### Changed Surface
|
|
116
|
+
|
|
117
|
+
- `src/task/source-prepare/**`:新增 `fragment-inventory.ts`、`ledger.ts`;扩展 `types.ts`、`prepare.ts`、`completeness.ts`、`parse-intent.ts`(无外部 API 变化)。
|
|
118
|
+
- `src/task/contract/**`:`requirement-ledger.json` 进入受管 transaction 与 canonical hash;`TaskContractSourceHashesV1` 新增可选 `requirementLedgerSha256`(legacy ref 向后兼容)。
|
|
119
|
+
- `src/workflows/dag/**`:`dagSourceBindingSchema` 拆为 v1/v2 discriminated union(v2 schemaVersion=2,四个 ledger 字段全部必填;v1 strict 拒绝 ledger 字段);`init-hybrid` 改读已持久化 ledger(缺失/漂移 fail closed);prewrite 门禁新增 ledger 检查与失败码。
|
|
120
|
+
- `src/task/source-prepare/**`:新增 `ledger-reconciliation.ts`(顺序 reconciliation)、`ledger-review.ts`(独立 reviewer);fragment inventory 表格行切分、跨文档稳定 REQ-SRC ID、V4 显式 AC 标题;human resolution 生效。
|
|
121
|
+
- `src/worker/observe/**`:新增只读 `GET /api/tasks/:id/ledger` 与展示(安全摘要,不泄漏原文)。
|
|
122
|
+
- `src/worker/console/**`:Draft/ContractPanel/InspectSplitPanel 安全摘要展示。
|
|
123
|
+
- `docs/templates/frontend-implementation-contract.schema.json`:requirements 增加可选 `sourceFragmentIds`/`sourceRefs`,sourceBinding 增加可选 ledger 字段(all-or-none)。
|
|
124
|
+
|
|
125
|
+
### Decision
|
|
126
|
+
|
|
127
|
+
**No init impact / Surface check only**。
|
|
128
|
+
|
|
129
|
+
Rationale:
|
|
130
|
+
|
|
131
|
+
- 新增均为模块内部实现;不新增 CLI 命令、不改 init 入口、不改变 target-project 初始化行为。
|
|
132
|
+
- `docs/templates/frontend-implementation-contract.schema.json` 已随 `docs/templates/` 目录 glob 打包(`package.json files`),无需新增 manifest 条目。
|
|
133
|
+
- 运行时加载 schema 的 `loadFrontendImplementationContractJsonSchema` 仅做结构性校验($id/required/properties/const),新增可选字段不破坏既有契约。
|
|
134
|
+
- 不新增任何运行时或开发依赖(node:crypto、既有 zod/Vitest/Vite/React 19)。
|
|
135
|
+
|
|
136
|
+
### Updates Made
|
|
137
|
+
|
|
138
|
+
- `docs/architecture/evolution.md`:本条目。
|
|
139
|
+
- `CHANGELOG.md [Unreleased]`、`CONTEXT.md`:glossary 术语。
|
|
140
|
+
- `docs/templates/frontend-implementation-contract.schema.json`:可选字段同步。
|
|
141
|
+
|
|
142
|
+
### Verification
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run typecheck && npm run console:typecheck
|
|
146
|
+
npx vitest run test/task-source-prepare.test.ts test/task-contract-canonicalize.test.ts test/task-contract-observe.test.ts test/dag-init-hybrid.test.ts test/frontend-implementation-contract.test.ts test/frontend-prewrite-gate.test.ts test/frontend-writer-admission.test.ts test/worker/observe/dag-inspector-node-input.test.ts test/worker/console/inspect-split-panel.test.ts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Residual Risk
|
|
150
|
+
|
|
151
|
+
- 长尾 24K 截断遗漏回归:以确定性分片 inventory + ledger 覆盖统计断言固定(AC-FIDELITY-001/007)。
|
|
152
|
+
- 账本漂移/篡改:inputDigest 复用失效 + prewrite fail closed(AC-FIDELITY-003/006)。
|
|
153
|
+
- 既有带引用 frontend task 重建路径变化:ledger 纳入 canonical hash 改变 `observeTaskContractState` 判定,legacy ref 向后兼容覆盖(AC-FIDELITY-004)。
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"docs/templates/frontend-task-requirement.md",
|
|
31
31
|
"docs/templates/frontend-task-constraints.md",
|
|
32
32
|
"docs/templates/frontend-design-contract.md",
|
|
33
|
+
"docs/templates/frontend-implementation-contract.schema.json",
|
|
33
34
|
"docs/templates/worker-dogfood-setup.md",
|
|
34
35
|
"docs/templates/worker-dogfood-evidence.md",
|
|
35
36
|
"docs/templates/harness.schema.json",
|
package/docs/templates/README.md
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
## Frontend
|
|
38
38
|
|
|
39
39
|
- `frontend-task-requirement.md`、`frontend-task-constraints.md`、`frontend-design-contract.md` — 前端任务输入与设计合同。
|
|
40
|
-
- `frontend-implementation-contract.schema.json` — 前端实现合同 schema
|
|
40
|
+
- `frontend-implementation-contract.schema.json` — 前端实现合同 schema(sourceBinding v2 ledger 字段 all-or-none:任一出现则四项齐全,禁止 v1+optional 冒充 v2)。`requirements[].sourceRefs[].items` 使用合法 `$ref: #/$defs/path`(相对路径、无 `..` 逃逸、无反斜杠),模板 JSON 无非法键,运行时加载器可直接编译(AC-003)。
|
|
41
41
|
- `frontend-test-dag.json` — frontend-test DAG 模板;其 deterministic checklist 会拒绝 alternative executable instructions,仅允许 allowlisted `playwright-cli` command。Pi generator/reviser 的 runtime writeSet 仅授权下列目标项目生成路径(不是本仓库文档索引目标):
|
|
42
42
|
|
|
43
43
|
~~~text
|