@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.10
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 +106 -109
- 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/executors/dag-pi-executor.js +69 -7
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +43 -3
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +10 -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/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/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- 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/native-directory-picker.js +13 -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 +312 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +583 -172
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- 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/server.js +4 -0
- package/dist/worker/observe/static/api.js +29 -9
- package/dist/worker/observe/static/app.js +18 -70
- 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 +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- 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/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +174 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +533 -79
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +6 -4
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +29 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -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 +238 -51
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +62 -12
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -2,9 +2,12 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
5
6
|
import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
|
|
6
7
|
import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
|
|
7
8
|
import { countExcludedGovernanceSpecFiles, resolveOpenspecGovernanceRoot, } from "../../task/frontend-project-capability.js";
|
|
9
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
+
import { parseLedgerJson, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
|
|
8
11
|
import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
|
|
9
12
|
import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
|
|
10
13
|
import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
|
|
@@ -42,6 +45,14 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
|
|
|
42
45
|
"component-choices-missing",
|
|
43
46
|
"contract-too-large",
|
|
44
47
|
"revision-patch-unmergeable",
|
|
48
|
+
// Source fidelity ledger 类目(SOURCE_FIDELITY_*/source-* 稳定错误码)。
|
|
49
|
+
"source-fidelity-ledger-missing",
|
|
50
|
+
"source-fidelity-ledger-stale",
|
|
51
|
+
"source-fidelity-unresolved",
|
|
52
|
+
"source-fidelity-conflict",
|
|
53
|
+
"source-fidelity-high-risk-uncovered",
|
|
54
|
+
"source-fidelity-requirement-not-covered",
|
|
55
|
+
"source-fidelity-source-ref-invalid",
|
|
45
56
|
]);
|
|
46
57
|
export const frontendPrewriteResultV1Schema = z
|
|
47
58
|
.object({
|
|
@@ -442,6 +453,162 @@ async function finalizePrewrite(input, pending) {
|
|
|
442
453
|
openspecPolicy: pending.openspecPolicy,
|
|
443
454
|
};
|
|
444
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Source fidelity ledger 门禁检查(AC-FIDELITY-002/003/006):绑定携带 ledger 指针时,
|
|
458
|
+
* 依次核验 ledger 存在、绑定 hash 一致、inputDigest 新鲜、无 unresolved/conflict/
|
|
459
|
+
* 高风险未覆盖、canonical requirement 全覆盖、来源引用有效且非伪造。任一失败 fail closed。
|
|
460
|
+
*/
|
|
461
|
+
export async function checkFrontendSourceFidelityLedger(input) {
|
|
462
|
+
const { binding } = input;
|
|
463
|
+
// v1 绑定不携带 ledger 指针:非 frontend task / 无可解析引用 → 无 v2 校验路径。
|
|
464
|
+
if (!isDagSourceBindingV2(binding)) {
|
|
465
|
+
return { ok: true, failureCode: null, failureReason: null, ledger: null };
|
|
466
|
+
}
|
|
467
|
+
const absolute = resolveDagTaskSourcePath({
|
|
468
|
+
workspaceRoot: input.workspaceRoot,
|
|
469
|
+
taskId: binding.taskId,
|
|
470
|
+
sourcePath: binding.ledgerPath,
|
|
471
|
+
});
|
|
472
|
+
let raw;
|
|
473
|
+
try {
|
|
474
|
+
raw = await readFile(absolute, "utf8");
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
failureCode: "source-fidelity-ledger-missing",
|
|
480
|
+
failureReason: `frontend source fidelity ledger missing: ${binding.ledgerPath} (${error instanceof Error ? error.message : String(error)})`,
|
|
481
|
+
ledger: null,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
485
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
489
|
+
failureReason: `frontend source fidelity ledger stale or tampered: ${binding.ledgerPath} (bound ${binding.ledgerSha256}, observed ${actualSha})`,
|
|
490
|
+
ledger: null,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
let ledger;
|
|
494
|
+
try {
|
|
495
|
+
ledger = parseLedgerJson(raw);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
501
|
+
failureReason: `frontend source fidelity ledger is not a valid v1 ledger: ${error instanceof Error ? error.message : String(error)}`,
|
|
502
|
+
ledger: null,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (ledger.inputDigest !== binding.inputDigest) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
509
|
+
failureReason: `frontend source fidelity ledger inputDigest mismatch: bound ${binding.inputDigest}, ledger ${ledger.inputDigest}`,
|
|
510
|
+
ledger,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const validation = validateRequirementLedger(ledger);
|
|
514
|
+
const firstError = (code) => validation.find((error) => error.code === code)?.message ?? null;
|
|
515
|
+
if (ledger.stats.unresolvedCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_UNRESOLVED")) {
|
|
516
|
+
return {
|
|
517
|
+
ok: false,
|
|
518
|
+
failureCode: "source-fidelity-unresolved",
|
|
519
|
+
failureReason: firstError("SOURCE_FIDELITY_UNRESOLVED") ??
|
|
520
|
+
`frontend source fidelity ledger has ${ledger.stats.unresolvedCount} unresolved fragment(s)`,
|
|
521
|
+
ledger,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (ledger.stats.conflictCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_CONFLICT")) {
|
|
525
|
+
return {
|
|
526
|
+
ok: false,
|
|
527
|
+
failureCode: "source-fidelity-conflict",
|
|
528
|
+
failureReason: firstError("SOURCE_FIDELITY_CONFLICT") ??
|
|
529
|
+
`frontend source fidelity ledger has ${ledger.stats.conflictCount} conflict(s)`,
|
|
530
|
+
ledger,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
if (ledger.stats.highRiskUncoveredCount > 0 ||
|
|
534
|
+
validation.some((e) => e.code === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED")) {
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
failureCode: "source-fidelity-high-risk-uncovered",
|
|
538
|
+
failureReason: firstError("SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") ??
|
|
539
|
+
`frontend source fidelity ledger has ${ledger.stats.highRiskUncoveredCount} high-risk uncovered fragment(s)`,
|
|
540
|
+
ledger,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
// AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据(ledger 侧
|
|
544
|
+
// REQUIREMENT_NOT_COVERED)与来源引用/片段无效(SOURCE_REF_INVALID /
|
|
545
|
+
// FRAGMENT_INVALID)均为阻断性失败,结构化失败结果不得产生可继续执行的成功。
|
|
546
|
+
const notCoveredError = validation.find((e) => e.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED");
|
|
547
|
+
if (notCoveredError) {
|
|
548
|
+
return {
|
|
549
|
+
ok: false,
|
|
550
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
551
|
+
failureReason: notCoveredError.message,
|
|
552
|
+
ledger,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const refInvalidError = validation.find((e) => e.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
|
|
556
|
+
e.code === "SOURCE_FIDELITY_FRAGMENT_INVALID");
|
|
557
|
+
if (refInvalidError) {
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
561
|
+
failureReason: refInvalidError.message,
|
|
562
|
+
ledger,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const ledgerIds = new Set(ledger.canonicalRequirements.map((requirement) => requirement.id));
|
|
566
|
+
const contractIds = new Set(input.contractRequirements.map((r) => r.id));
|
|
567
|
+
const uncoveredRequirements = [...ledgerIds].filter((id) => !contractIds.has(id));
|
|
568
|
+
if (uncoveredRequirements.length > 0) {
|
|
569
|
+
return {
|
|
570
|
+
ok: false,
|
|
571
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
572
|
+
failureReason: `frontend contract requirements do not cover ledger canonical requirement ids: ${uncoveredRequirements.join(", ")}`,
|
|
573
|
+
ledger,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const fragmentById = new Map(ledger.fragments.map((fragment) => [fragment.id, fragment]));
|
|
577
|
+
for (const requirement of input.contractRequirements) {
|
|
578
|
+
if (!ledgerIds.has(requirement.id))
|
|
579
|
+
continue;
|
|
580
|
+
const declared = requirement.sourceFragmentIds ?? [];
|
|
581
|
+
if (declared.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
585
|
+
failureReason: `frontend contract requirement ${requirement.id} lacks sourceFragmentIds for the bound ledger`,
|
|
586
|
+
ledger,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const boundFragments = new Set(ledger.canonicalRequirements.find((r) => r.id === requirement.id)
|
|
590
|
+
?.sourceFragmentIds ?? []);
|
|
591
|
+
for (const fragmentId of declared) {
|
|
592
|
+
if (!fragmentById.has(fragmentId)) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
596
|
+
failureReason: `frontend contract requirement ${requirement.id} references unknown fragment ${fragmentId}`,
|
|
597
|
+
ledger,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (!boundFragments.has(fragmentId)) {
|
|
601
|
+
return {
|
|
602
|
+
ok: false,
|
|
603
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
604
|
+
failureReason: `frontend contract requirement ${requirement.id} declares fabricated fragment reference ${fragmentId} (not bound in ledger)`,
|
|
605
|
+
ledger,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return { ok: true, failureCode: null, failureReason: null, ledger };
|
|
611
|
+
}
|
|
445
612
|
export async function runFrontendPrewriteGate(input) {
|
|
446
613
|
const workspaceRoot = input.workspaceRoot ?? input.repoRoot;
|
|
447
614
|
// selectNode throws for a scheduling defect (no FINISHED plan/review). That
|
|
@@ -589,6 +756,33 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
589
756
|
}
|
|
590
757
|
throw error;
|
|
591
758
|
}
|
|
759
|
+
// Source fidelity ledger gate (20260824-frontend-source-fidelity-ledger-v1):
|
|
760
|
+
// 在 verdict/missing-requirement-ids/source-binding-not-fresh 之后插入 ledger
|
|
761
|
+
// 检查。缺失即 fail closed 且 writer 不获授权;所有检查通过才继续物化契约。
|
|
762
|
+
if (input.sourceBinding) {
|
|
763
|
+
const ledgerCheck = await checkFrontendSourceFidelityLedger({
|
|
764
|
+
workspaceRoot: workspaceRoot ?? "",
|
|
765
|
+
binding: input.sourceBinding,
|
|
766
|
+
contractRequirements: analysis.canonical.requirements.map((requirement) => ({
|
|
767
|
+
id: requirement.id,
|
|
768
|
+
sourceFragmentIds: requirement.sourceFragmentIds,
|
|
769
|
+
sourceRefs: requirement.sourceRefs,
|
|
770
|
+
})),
|
|
771
|
+
});
|
|
772
|
+
if (!ledgerCheck.ok && ledgerCheck.failureCode) {
|
|
773
|
+
return finalizePrewrite(input, {
|
|
774
|
+
...basePending,
|
|
775
|
+
verdict,
|
|
776
|
+
candidateJsonSha256: analysis.candidateJsonSha256,
|
|
777
|
+
normalizationActions: [],
|
|
778
|
+
mockStrategy: analysis.canonical.mockApi.strategy,
|
|
779
|
+
classification: "blocked",
|
|
780
|
+
failureReason: ledgerCheck.failureReason ??
|
|
781
|
+
"frontend source fidelity ledger gate failed",
|
|
782
|
+
failureCode: ledgerCheck.failureCode,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
592
786
|
// Size guard: a canonical contract above 64KB is a retryable-invalid defect
|
|
593
787
|
// (compress the contract) rather than a writer authorization failure.
|
|
594
788
|
const contractSizeBytes = Buffer.byteLength(serializeDeterministicJson(analysis.canonical), "utf8");
|
|
@@ -947,6 +1141,9 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
947
1141
|
}
|
|
948
1142
|
lines.push(`Prewrite result: ${result.prewriteResult.path}`);
|
|
949
1143
|
lines.push(`Openspec policy: ${result.openspecPolicy}`);
|
|
1144
|
+
if (result.failureCode?.startsWith("source-fidelity-")) {
|
|
1145
|
+
lines.push(`Source fidelity ledger: blocked (${result.failureCode})`);
|
|
1146
|
+
}
|
|
950
1147
|
if (result.failureReason)
|
|
951
1148
|
lines.push(`Failure reason: ${result.failureReason}`);
|
|
952
1149
|
if (result.openspecReadPaths.length > 0) {
|
|
@@ -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",
|