@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
|
@@ -131,6 +131,23 @@ function normalizeHazardOccurrenceIdentity(line) {
|
|
|
131
131
|
// Unclosed quote or no out-of-string `#`: do not invent a comment boundary.
|
|
132
132
|
return trimmed;
|
|
133
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Bare `__NNN_*` helper module names imported by a Python source snippet.
|
|
136
|
+
* Only the head token of an `import`/`from` statement is considered a module
|
|
137
|
+
* name; dotted submodules (`__551_manager_helpers.x`) collapse to the head.
|
|
138
|
+
*/
|
|
139
|
+
function importedBareHelperModules(source) {
|
|
140
|
+
const names = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
for (const match of normalizeSourceNewlines(source).matchAll(/\b(?:import|from)\s+([A-Za-z0-9_.]+)/g)) {
|
|
143
|
+
const head = match[1].split(".")[0];
|
|
144
|
+
if (/^__\d+[A-Za-z0-9_]*$/.test(head) && !seen.has(head)) {
|
|
145
|
+
seen.add(head);
|
|
146
|
+
names.push(head);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return names;
|
|
150
|
+
}
|
|
134
151
|
function listPatternOccurrences(source, patternSource, flags = "") {
|
|
135
152
|
const normalized = normalizeSourceNewlines(source);
|
|
136
153
|
const pattern = new RegExp(patternSource, flags.includes("g") ? flags : `${flags}g`);
|
|
@@ -221,6 +238,8 @@ export async function buildBackendPytestAssetInventory(workspaceRoot, mappedScri
|
|
|
221
238
|
const inputHashes = {};
|
|
222
239
|
const hazardOccurrenceCounts = {};
|
|
223
240
|
const hazardOccurrences = {};
|
|
241
|
+
const scriptBareImports = {};
|
|
242
|
+
const mappedScriptSet = new Set(normalizedScripts);
|
|
224
243
|
for (const file of assetFiles) {
|
|
225
244
|
const bytes = await readFile(path.resolve(workspaceRoot, file));
|
|
226
245
|
const source = bytes.toString("utf8");
|
|
@@ -228,8 +247,10 @@ export async function buildBackendPytestAssetInventory(workspaceRoot, mappedScri
|
|
|
228
247
|
const lists = scanRepairHazardOccurrenceLists(source);
|
|
229
248
|
hazardOccurrences[file] = lists;
|
|
230
249
|
hazardOccurrenceCounts[file] = Object.fromEntries(Object.entries(lists).map(([label, identities]) => [label, identities.length]));
|
|
250
|
+
if (mappedScriptSet.has(file))
|
|
251
|
+
scriptBareImports[file] = importedBareHelperModules(source);
|
|
231
252
|
}
|
|
232
|
-
return { mappedScripts: normalizedScripts, assetFiles, inputHashes, hazardOccurrenceCounts, hazardOccurrences };
|
|
253
|
+
return { mappedScripts: normalizedScripts, assetFiles, inputHashes, hazardOccurrenceCounts, hazardOccurrences, scriptBareImports };
|
|
233
254
|
}
|
|
234
255
|
function bounded(value) {
|
|
235
256
|
const normalized = value.replaceAll(/(Bearer|Basic)\s+[A-Za-z0-9._~+\/-]+/gi, "$1 [REDACTED]");
|
|
@@ -237,13 +258,14 @@ function bounded(value) {
|
|
|
237
258
|
? normalized
|
|
238
259
|
: `${normalized.slice(0, MAX_DIAGNOSTIC_CHARS)}\n...[truncated]`;
|
|
239
260
|
}
|
|
240
|
-
function collectionItems(stdout) {
|
|
261
|
+
function collectionItems(stdout, layout) {
|
|
262
|
+
const rootPrefix = `${(layout ?? resolveBackendTestLayout(undefined)).testRoot}/`;
|
|
241
263
|
return [...new Set(stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => {
|
|
242
264
|
if (!line.includes("::"))
|
|
243
265
|
return false;
|
|
244
266
|
if (/^(ERROR|FAILED|E\s+)/.test(line))
|
|
245
267
|
return false;
|
|
246
|
-
return
|
|
268
|
+
return line.replaceAll("\\", "/").startsWith(rootPrefix);
|
|
247
269
|
}).map((line) => line.replaceAll("\\", "/")))];
|
|
248
270
|
}
|
|
249
271
|
function escapeRegExp(value) {
|
|
@@ -308,8 +330,68 @@ function classifyFixtureResolutionFailure(output, layout) {
|
|
|
308
330
|
repairPaths: paths,
|
|
309
331
|
};
|
|
310
332
|
}
|
|
311
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Generated-local bare helper module signatures found in the bound pytest
|
|
335
|
+
* asset inventory. A file such as `testcase/__551_manager_helpers.py` in the
|
|
336
|
+
* test root/script dir is generated-local: when collection reports
|
|
337
|
+
* `No module named '__551_manager_helpers'`, the defect can be precisely
|
|
338
|
+
* attributed to the generated mapped script instead of a missing third-party
|
|
339
|
+
* package.
|
|
340
|
+
*/
|
|
341
|
+
function generatedLocalBareHelperNames(inventory) {
|
|
342
|
+
if (!inventory)
|
|
343
|
+
return [];
|
|
344
|
+
const names = [];
|
|
345
|
+
for (const file of inventory.assetFiles) {
|
|
346
|
+
const stem = path.posix.basename(file).replace(/\.py$/, "");
|
|
347
|
+
if (/^__\d+[A-Za-z0-9_]*$/.test(stem))
|
|
348
|
+
names.push(stem);
|
|
349
|
+
}
|
|
350
|
+
return [...new Set(names)];
|
|
351
|
+
}
|
|
352
|
+
function classifyCollectionFailure(output, evidence) {
|
|
312
353
|
const normalized = output.replaceAll("\\", "/");
|
|
354
|
+
const helperNames = generatedLocalBareHelperNames(evidence?.inventory);
|
|
355
|
+
const helperByLower = new Map(helperNames.map((name) => [name.toLowerCase(), name]));
|
|
356
|
+
// A bare `__NNN_...` module import that collection cannot find is only a
|
|
357
|
+
// candidate generated-local helper signature. REPAIRABLE requires two
|
|
358
|
+
// deterministic pieces of evidence: (1) a helper asset of the same name in
|
|
359
|
+
// the bound inventory, and (2) at least one mapped generated script whose
|
|
360
|
+
// source truly imports that bare module. Without both — or when attribution
|
|
361
|
+
// is impossible — fail closed as BLOCKED so an un-attributable dependency
|
|
362
|
+
// error is never widened into a repair. Genuine third-party modules (no
|
|
363
|
+
// `__NNN_` signature) stay BLOCKED below.
|
|
364
|
+
const missingModule = /No module named ['\"]([^'\"]+)['\"]/i.exec(normalized)?.[1];
|
|
365
|
+
if (missingModule && /^__\d+[A-Za-z0-9_]*$/.test(missingModule)) {
|
|
366
|
+
const helperAsset = helperByLower.get(missingModule.toLowerCase());
|
|
367
|
+
const inventory = evidence?.inventory;
|
|
368
|
+
const scriptBareImports = inventory?.scriptBareImports ?? {};
|
|
369
|
+
const missingLower = missingModule.toLowerCase();
|
|
370
|
+
const importedByMappedScript = inventory
|
|
371
|
+
? inventory.mappedScripts.some((script) => (scriptBareImports[script] ?? []).some((name) => name.toLowerCase() === missingLower))
|
|
372
|
+
: false;
|
|
373
|
+
if (helperAsset && importedByMappedScript && inventory) {
|
|
374
|
+
const repairPaths = [...new Set([
|
|
375
|
+
...(helperAsset
|
|
376
|
+
? inventory.assetFiles.filter((file) => path.posix.basename(file).replace(/\.py$/, "") === helperAsset)
|
|
377
|
+
: []),
|
|
378
|
+
...inventory.mappedScripts,
|
|
379
|
+
])].sort();
|
|
380
|
+
return {
|
|
381
|
+
status: "REPAIRABLE",
|
|
382
|
+
kind: "missing-generated-local-helper",
|
|
383
|
+
detail: `generated testcase asset imports missing bare generated-local helper module ${missingModule}; inline the helper or remove the generated shared dependency`,
|
|
384
|
+
repairPaths,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
status: "BLOCKED",
|
|
389
|
+
kind: "missing-generated-local-helper-without-evidence",
|
|
390
|
+
detail: helperAsset
|
|
391
|
+
? `missing bare generated-local helper module ${missingModule} has a name match but no mapped generated script imports it (no precise generated-local attribution)`
|
|
392
|
+
: `missing bare __NNN_* module ${missingModule} has no generated-local helper asset in the bound inventory (cannot be precisely attributed)`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
313
395
|
const blockedPatterns = [
|
|
314
396
|
[/No module named ['\"](?!testcase(?:\.|['\"]))/i, "missing-third-party-module"],
|
|
315
397
|
[/pytest(?:_|-)plugin|pluggy|unknown hook|unrecognized arguments/i, "pytest-plugin-or-config"],
|
|
@@ -335,7 +417,7 @@ function classifyCollectionFailure(output) {
|
|
|
335
417
|
return { status: "BLOCKED", kind: "unclassified-collection-error", detail: "collection failure cannot be safely attributed to generated testcase assets" };
|
|
336
418
|
}
|
|
337
419
|
export function assessBackendPytestCollection(input) {
|
|
338
|
-
const items = collectionItems(input.stdout);
|
|
420
|
+
const items = collectionItems(input.stdout, input.layout);
|
|
339
421
|
const fixtureResolution = input.fixtureResolution ?? { exitCode: 0, stdout: "fixture resolution assumed by direct assessor", stderr: "" };
|
|
340
422
|
if (input.exitCode === 0 && fixtureResolution.exitCode === 0) {
|
|
341
423
|
return backendPytestCollectionFactsSchema.parse({
|
|
@@ -401,7 +483,7 @@ export function assessBackendPytestCollection(input) {
|
|
|
401
483
|
fixtureStderrExcerpt: bounded(fixtureResolution.stderr),
|
|
402
484
|
});
|
|
403
485
|
}
|
|
404
|
-
const classification = classifyCollectionFailure(`${input.stdout}\n${input.stderr}
|
|
486
|
+
const classification = classifyCollectionFailure(`${input.stdout}\n${input.stderr}`, { layout: input.layout, inventory: input.inventory });
|
|
405
487
|
return backendPytestCollectionFactsSchema.parse({
|
|
406
488
|
schemaId: "backend-test-pytest-collection-v3",
|
|
407
489
|
phase: input.phase,
|
|
@@ -412,7 +494,9 @@ export function assessBackendPytestCollection(input) {
|
|
|
412
494
|
fixtureResolutionAttempted: false,
|
|
413
495
|
fixtureResolutionStatus: "NOT_RUN",
|
|
414
496
|
fixtureResolutionExitCode: null,
|
|
415
|
-
repairPaths:
|
|
497
|
+
repairPaths: classification.repairPaths && classification.repairPaths.length > 0
|
|
498
|
+
? classification.repairPaths
|
|
499
|
+
: testcasePythonPaths(`${input.stdout}\n${input.stderr}`),
|
|
416
500
|
mappedScripts: input.inventory.mappedScripts,
|
|
417
501
|
existingMappedScripts: input.inventory.mappedScripts,
|
|
418
502
|
missingMappedScripts: [],
|
|
@@ -657,7 +741,7 @@ export async function materializeBackendTestExecutionReadiness(input) {
|
|
|
657
741
|
if (input.effective.phase !== "effective" || input.effective.status !== "PASS" || input.effective.fixtureResolutionStatus !== "PASS") {
|
|
658
742
|
throw new Error("backend-test execution readiness requires effective collection and fixture-resolution PASS");
|
|
659
743
|
}
|
|
660
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts);
|
|
744
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.effective.mappedScripts, { layout: input.layout });
|
|
661
745
|
assertSameInventory(input.effective, current);
|
|
662
746
|
const eligibility = input.eligibility ?? { eligibleItemIds: input.effective.collectedItemIds, excludedItems: [] };
|
|
663
747
|
const status = eligibility.eligibleItemIds.length === 0
|
|
@@ -756,7 +840,7 @@ async function firstUnreadableAssetPath(workspaceRoot, readiness) {
|
|
|
756
840
|
}
|
|
757
841
|
return undefined;
|
|
758
842
|
}
|
|
759
|
-
export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness) {
|
|
843
|
+
export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, readiness, layout) {
|
|
760
844
|
if (!["PASS", "PARTIAL"].includes(readiness.status)) {
|
|
761
845
|
const counts = formatReadinessCounts(readiness);
|
|
762
846
|
const reasonTally = readiness.eligibleItemIds.length === 0 && readiness.excludedItems.length > 0
|
|
@@ -779,7 +863,7 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
|
|
|
779
863
|
}
|
|
780
864
|
let current;
|
|
781
865
|
try {
|
|
782
|
-
current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts);
|
|
866
|
+
current = await buildBackendPytestAssetInventory(workspaceRoot, readiness.mappedScripts, { layout });
|
|
783
867
|
}
|
|
784
868
|
catch {
|
|
785
869
|
const fallbackPath = [...readiness.mappedScripts].sort()[0] ?? Object.keys(readiness.assetHashes).sort()[0] ?? "unknown";
|
|
@@ -814,11 +898,11 @@ export async function assertBackendTestExecutionReadinessFresh(workspaceRoot, re
|
|
|
814
898
|
}
|
|
815
899
|
}
|
|
816
900
|
}
|
|
817
|
-
export async function assertBackendPytestCollectionFresh(workspaceRoot, effective) {
|
|
901
|
+
export async function assertBackendPytestCollectionFresh(workspaceRoot, effective, layout) {
|
|
818
902
|
if (effective.phase !== "effective" || effective.status !== "PASS") {
|
|
819
903
|
throw new Error("backend pytest execution requires effective collection PASS facts");
|
|
820
904
|
}
|
|
821
|
-
const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts);
|
|
905
|
+
const current = await buildBackendPytestAssetInventory(workspaceRoot, effective.mappedScripts, { layout });
|
|
822
906
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
823
907
|
if (priorityOnly.length > 0)
|
|
824
908
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -826,7 +910,7 @@ export async function assertBackendPytestCollectionFresh(workspaceRoot, effectiv
|
|
|
826
910
|
}
|
|
827
911
|
export async function materializeEffectiveBackendPytestCollection(input) {
|
|
828
912
|
if (input.initial.status === "PASS") {
|
|
829
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts);
|
|
913
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.initial.mappedScripts, { layout: input.layout });
|
|
830
914
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
831
915
|
if (priorityOnly.length > 0)
|
|
832
916
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -844,7 +928,7 @@ export async function materializeEffectiveBackendPytestCollection(input) {
|
|
|
844
928
|
if (input.final.status !== "PASS")
|
|
845
929
|
throw new Error("backend pytest collection still fails after bounded repair");
|
|
846
930
|
await assertBackendPytestRepairSafety(input.workspaceRoot, input.initial, input.final);
|
|
847
|
-
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts);
|
|
931
|
+
const current = await buildBackendPytestAssetInventory(input.workspaceRoot, input.final.mappedScripts, { layout: input.layout });
|
|
848
932
|
const priorityOnly = priorityOnlyBackendPytestScripts(current.assetFiles);
|
|
849
933
|
if (priorityOnly.length > 0)
|
|
850
934
|
throw new Error(`priority-only-pytest-module: ${priorityOnly.join(", ")}`);
|
|
@@ -385,12 +385,17 @@ export const FORBIDDEN_GENERATED_IMPORT_NAMESPACES = [
|
|
|
385
385
|
"testcase.helpers",
|
|
386
386
|
"testcase.factories",
|
|
387
387
|
];
|
|
388
|
-
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b)/gm;
|
|
388
|
+
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b)/gm;
|
|
389
389
|
/**
|
|
390
|
-
* Scan Python source for imports from forbidden generated shared namespaces
|
|
391
|
-
* Anchored at line start with a word
|
|
392
|
-
* substring inside a docstring
|
|
393
|
-
*
|
|
390
|
+
* Scan Python source for imports from forbidden generated shared namespaces
|
|
391
|
+
* or bare generated-local helper modules. Anchored at line start with a word
|
|
392
|
+
* boundary so `testcase.helpers_other` or a substring inside a docstring is not
|
|
393
|
+
* falsely flagged. In addition to the `testcase_helpers`/`testcase.helpers`/
|
|
394
|
+
* `testcase.factories` namespaces, bare `__<digits>...` helper modules (for
|
|
395
|
+
* example `from __551_manager_helpers import ...` / `import __551_manager_helpers`)
|
|
396
|
+
* are generated-local shared dependencies: a self-contained pytest child must
|
|
397
|
+
* inline or remove them rather than depend on a generated shared module.
|
|
398
|
+
* Returns the list of offending module names (deduplicated, order-preserving).
|
|
394
399
|
*/
|
|
395
400
|
export function findForbiddenGeneratedImports(source) {
|
|
396
401
|
const hits = [];
|
|
@@ -879,28 +884,23 @@ export function isBackendTestShardChildTask(task) {
|
|
|
879
884
|
task.id?.startsWith("generate-backend-pytest-case-"));
|
|
880
885
|
}
|
|
881
886
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
* must NOT require module files to exist at plan time.
|
|
887
|
+
* Historical classifier for the Markdown plan node. Current backend-test N2
|
|
888
|
+
* is read-only and persists a run-owned plan.md, so writer completeness never
|
|
889
|
+
* invokes the workspace README assessment below for newly generated DAGs.
|
|
886
890
|
*/
|
|
887
891
|
export function isBackendTestMdPlanTask(task) {
|
|
888
892
|
return task.id === "generate-backend-md-plan-pi";
|
|
889
893
|
}
|
|
890
894
|
/**
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
* scripts are produced by the downstream map_agent children, so the Gate
|
|
894
|
-
* must NOT require mapped pytest scripts to exist at plan time.
|
|
895
|
+
* Historical classifier for the former shared-resources pytest writer. Current
|
|
896
|
+
* N7 is static and every map child writes one self-contained pytest module.
|
|
895
897
|
*/
|
|
896
898
|
export function isBackendTestPytestPlanTask(task) {
|
|
897
899
|
return task.id === "generate-backend-pytest-plan-pi";
|
|
898
900
|
}
|
|
899
901
|
/**
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
* index), WITHOUT requiring the indexed module files to exist yet — those are
|
|
903
|
-
* written by the downstream map_agent shard children.
|
|
902
|
+
* Legacy workspace-plan completeness helper retained for isolated historical
|
|
903
|
+
* fixtures. New DAGs validate the run-owned plan artifact in N3 instead.
|
|
904
904
|
*/
|
|
905
905
|
export async function assessBackendTestMdPlanCompleteness(workspaceRoot, layout) {
|
|
906
906
|
const resolvedPlan = layout ?? resolveBackendTestLayout(undefined);
|
|
@@ -26,6 +26,7 @@ export function buildExpandedChildTask(input) {
|
|
|
26
26
|
allowedPaths: renderDynamicPatternList(child.allowedPaths, item, index, expansion.itemName) ?? [],
|
|
27
27
|
forbiddenPaths: renderDynamicPatternList(child.forbiddenPaths, item, index, expansion.itemName) ?? [],
|
|
28
28
|
writeSet: renderDynamicPatternList(child.writeSet, item, index, expansion.itemName),
|
|
29
|
+
readSet: renderDynamicPatternList(child.readSet, item, index, expansion.itemName),
|
|
29
30
|
outputContract: child.outputContract,
|
|
30
31
|
writerOutcomePolicy: child.writerOutcomePolicy,
|
|
31
32
|
writeGuardPolicy: child.writeGuardPolicy,
|
|
@@ -8,6 +8,7 @@ import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-sto
|
|
|
8
8
|
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
11
12
|
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, openspecCitationsRel, parseOpenspecCitationsBlock, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
12
13
|
export const frontendNormalizationActionSchema = z.enum([
|
|
13
14
|
"remove-trailing-commas",
|
|
@@ -279,8 +280,33 @@ export const frontendImplementationContractSchema = z
|
|
|
279
280
|
requirementSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
280
281
|
referencePaths: z.array(safePath),
|
|
281
282
|
requirementIds: z.array(id),
|
|
283
|
+
// Source binding v2 (frontend): bound ledger pointer + fragment map.
|
|
284
|
+
ledgerPath: safePath.optional(),
|
|
285
|
+
ledgerSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
286
|
+
inputDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
287
|
+
requirementToFragments: z
|
|
288
|
+
.record(z.string(), z.array(z.string()))
|
|
289
|
+
.optional(),
|
|
282
290
|
})
|
|
283
|
-
.strict()
|
|
291
|
+
.strict()
|
|
292
|
+
.superRefine((value, ctx) => {
|
|
293
|
+
// v2 all-or-none:任一 ledger 字段出现则四项必须齐全,防止 v1+optional 冒充 v2。
|
|
294
|
+
const anyLedgerField = value.ledgerPath !== undefined ||
|
|
295
|
+
value.ledgerSha256 !== undefined ||
|
|
296
|
+
value.inputDigest !== undefined ||
|
|
297
|
+
value.requirementToFragments !== undefined;
|
|
298
|
+
const allLedgerFields = value.ledgerPath !== undefined &&
|
|
299
|
+
value.ledgerSha256 !== undefined &&
|
|
300
|
+
value.inputDigest !== undefined &&
|
|
301
|
+
value.requirementToFragments !== undefined;
|
|
302
|
+
if (anyLedgerField && !allLedgerFields) {
|
|
303
|
+
ctx.addIssue({
|
|
304
|
+
code: z.ZodIssueCode.custom,
|
|
305
|
+
path: ["sourceBinding"],
|
|
306
|
+
message: "source binding v2 ledger fields (ledgerPath/ledgerSha256/inputDigest/requirementToFragments) must be provided together; partial or optional v1+ledger bindings are rejected",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
284
310
|
riskLevel: z.enum(["small", "standard", "high-risk"]),
|
|
285
311
|
targets: z
|
|
286
312
|
.object({
|
|
@@ -296,6 +322,10 @@ export const frontendImplementationContractSchema = z
|
|
|
296
322
|
implementationTargets: z.array(safePath),
|
|
297
323
|
verificationTargetIds: z.array(z.string().min(1)),
|
|
298
324
|
evidenceGap: gap.optional(),
|
|
325
|
+
// Source fidelity ledger binding (AC-005/AC-006): 每个 requirement
|
|
326
|
+
// 携带来源 fragment 证据,prewrite 门禁校验其真实性与覆盖。
|
|
327
|
+
sourceFragmentIds: z.array(z.string().min(1)).optional(),
|
|
328
|
+
sourceRefs: z.array(safePath).optional(),
|
|
299
329
|
})
|
|
300
330
|
.strict()).min(1),
|
|
301
331
|
uiStates: z.array(z
|
|
@@ -432,6 +462,30 @@ export const frontendImplementationContractSchema = z
|
|
|
432
462
|
message: `unknown verification target ${targetId}`,
|
|
433
463
|
path: ["requirements"],
|
|
434
464
|
});
|
|
465
|
+
// Source fidelity ledger binding (AC-005/AC-006): 绑定携带
|
|
466
|
+
// requirementToFragments(ledger v2)时,每个 requirement 必须携带
|
|
467
|
+
// 有效 sourceFragmentIds/sourceRefs,否则 fail closed(防引用伪造/缺失)。
|
|
468
|
+
const boundFragments = value.sourceBinding.requirementToFragments?.[requirement.id];
|
|
469
|
+
if (value.sourceBinding.ledgerPath &&
|
|
470
|
+
(!requirement.sourceFragmentIds ||
|
|
471
|
+
requirement.sourceFragmentIds.length === 0 ||
|
|
472
|
+
!requirement.sourceRefs ||
|
|
473
|
+
requirement.sourceRefs.length === 0)) {
|
|
474
|
+
ctx.addIssue({
|
|
475
|
+
code: "custom",
|
|
476
|
+
message: `requirement ${requirement.id} must carry sourceFragmentIds/sourceRefs when a source fidelity ledger is bound`,
|
|
477
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else if (boundFragments !== undefined &&
|
|
481
|
+
(!requirement.sourceFragmentIds ||
|
|
482
|
+
requirement.sourceFragmentIds.length === 0)) {
|
|
483
|
+
ctx.addIssue({
|
|
484
|
+
code: "custom",
|
|
485
|
+
message: `requirement ${requirement.id} is bound to ledger fragments ${boundFragments.join(", ")} but declares none`,
|
|
486
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
487
|
+
});
|
|
488
|
+
}
|
|
435
489
|
}
|
|
436
490
|
for (const state of value.uiStates) {
|
|
437
491
|
if (state.applicable &&
|
|
@@ -568,6 +622,29 @@ export async function assertFrontendSourceBindingFresh(input) {
|
|
|
568
622
|
throw new Error(`frontend source binding is stale: ${source.path}`);
|
|
569
623
|
}
|
|
570
624
|
}
|
|
625
|
+
// Source binding v2 ledger 新鲜度(AC-FIDELITY-REPAIR-001/002):v2 绑定携带的
|
|
626
|
+
// ledger 文件必须存在且 sha256 与绑定一致,否则 fail closed;v1 绑定无此路径。
|
|
627
|
+
if (isDagSourceBindingV2(input.binding)) {
|
|
628
|
+
const absolute = resolveDagTaskSourcePath({
|
|
629
|
+
workspaceRoot: input.workspaceRoot,
|
|
630
|
+
taskId: input.binding.taskId,
|
|
631
|
+
sourcePath: input.binding.ledgerPath,
|
|
632
|
+
});
|
|
633
|
+
let content;
|
|
634
|
+
try {
|
|
635
|
+
content = await readFile(absolute);
|
|
636
|
+
const info = await stat(absolute);
|
|
637
|
+
if (!info.isFile())
|
|
638
|
+
throw new Error("not a regular file");
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
throw new Error(`frontend source fidelity ledger unavailable: ${input.binding.ledgerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
642
|
+
}
|
|
643
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
644
|
+
if (actual !== input.binding.ledgerSha256) {
|
|
645
|
+
throw new Error(`frontend source fidelity ledger is stale or tampered: ${input.binding.ledgerPath} (bound ${input.binding.ledgerSha256}, observed ${actual})`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
571
648
|
}
|
|
572
649
|
const SECRET_KEY = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential|authorization)/i;
|
|
573
650
|
const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b)/;
|
|
@@ -742,7 +819,7 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
742
819
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
743
820
|
if (!requirement)
|
|
744
821
|
throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
|
|
745
|
-
|
|
822
|
+
const sourceBinding = {
|
|
746
823
|
taskId: binding.taskId,
|
|
747
824
|
requirementPath: requirement.path,
|
|
748
825
|
requirementSha256: requirement.sha256,
|
|
@@ -752,6 +829,15 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
752
829
|
.sort(),
|
|
753
830
|
requirementIds: binding.requirementIds.map(canonicalizeRequirementId),
|
|
754
831
|
};
|
|
832
|
+
// Source binding v2(schemaVersion=2 强制字段):透传 ledger 指针与
|
|
833
|
+
// requirement→fragment 映射。v1 绑定不得以 optional 字段冒充 v2。
|
|
834
|
+
if (isDagSourceBindingV2(binding)) {
|
|
835
|
+
sourceBinding.ledgerPath = binding.ledgerPath;
|
|
836
|
+
sourceBinding.ledgerSha256 = binding.ledgerSha256;
|
|
837
|
+
sourceBinding.inputDigest = binding.inputDigest;
|
|
838
|
+
sourceBinding.requirementToFragments = binding.requirementToFragments;
|
|
839
|
+
}
|
|
840
|
+
return sourceBinding;
|
|
755
841
|
}
|
|
756
842
|
function asRecord(value) {
|
|
757
843
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -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,9 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
54
75
|
"## Requirement Coverage",
|
|
55
76
|
requirementsCoverage(contract),
|
|
56
77
|
"",
|
|
78
|
+
"## Source Fidelity Ledger",
|
|
79
|
+
sourceFidelityLedger(contract),
|
|
80
|
+
"",
|
|
57
81
|
"## Implementation Steps",
|
|
58
82
|
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
59
83
|
"",
|