@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.11
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 +114 -109
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -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 +107 -20
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/governance/checks.js +1 -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/dag-execution-receipt.js +20 -2
- 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 +133 -7
- 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 +186 -8
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +168 -16
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +28 -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 +239 -52
- 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 +267 -238
- 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
|
@@ -11,10 +11,10 @@ export function createNpmUpdateClient(options) {
|
|
|
11
11
|
const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
|
|
12
12
|
const cwd = options.cwd;
|
|
13
13
|
const env = options.env;
|
|
14
|
-
const
|
|
14
|
+
const distTagVersionCheck = async (channel) => {
|
|
15
15
|
let result;
|
|
16
16
|
try {
|
|
17
|
-
result = await runner(npmCommand, ["view", PACKAGE_NAME,
|
|
17
|
+
result = await runner(npmCommand, ["view", PACKAGE_NAME, `dist-tags.${channel}`, "--json"], { timeoutMs, cwd, env });
|
|
18
18
|
}
|
|
19
19
|
catch {
|
|
20
20
|
return { ok: false, reason: "spawn-failure" };
|
|
@@ -27,17 +27,19 @@ export function createNpmUpdateClient(options) {
|
|
|
27
27
|
if (result.exitCode !== 0) {
|
|
28
28
|
return { ok: false, reason: "command-failed" };
|
|
29
29
|
}
|
|
30
|
-
const version =
|
|
30
|
+
const version = parseNpmViewDistTag(result.stdout, channel);
|
|
31
31
|
return version
|
|
32
32
|
? { ok: true, version }
|
|
33
33
|
: { ok: false, reason: "invalid-response" };
|
|
34
34
|
};
|
|
35
|
+
const latestVersionCheck = () => distTagVersionCheck("latest");
|
|
35
36
|
return {
|
|
36
37
|
async latestVersion() {
|
|
37
38
|
const result = await latestVersionCheck();
|
|
38
39
|
return result.ok ? result.version : undefined;
|
|
39
40
|
},
|
|
40
41
|
latestVersionCheck,
|
|
42
|
+
distTagVersionCheck,
|
|
41
43
|
async canAutoInstall() {
|
|
42
44
|
return proveGlobalNpmInstall({
|
|
43
45
|
currentVersion: options.currentVersion,
|
|
@@ -49,10 +51,11 @@ export function createNpmUpdateClient(options) {
|
|
|
49
51
|
timeoutMs,
|
|
50
52
|
});
|
|
51
53
|
},
|
|
52
|
-
async installExact(version) {
|
|
53
|
-
if (!
|
|
54
|
+
async installExact(version, channel = "latest") {
|
|
55
|
+
if (!isAllowedChannelVersion(version, channel)) {
|
|
54
56
|
return { ok: false, reason: "invalid-version" };
|
|
55
|
-
|
|
57
|
+
}
|
|
58
|
+
const result = await runner(npmCommand, buildExactInstallArgs(version, channel), {
|
|
56
59
|
timeoutMs: Math.max(timeoutMs, 120000),
|
|
57
60
|
cwd,
|
|
58
61
|
env,
|
|
@@ -79,6 +82,13 @@ export function createNpmUpdateClient(options) {
|
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
export function parseNpmViewLatest(stdout) {
|
|
85
|
+
return parseNpmViewDistTag(stdout, "latest");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* `latest` stays stable-only. `next` is explicitly selected by a human and
|
|
89
|
+
* may legitimately be a SemVer prerelease, but remains an exact version.
|
|
90
|
+
*/
|
|
91
|
+
export function parseNpmViewDistTag(stdout, channel) {
|
|
82
92
|
const trimmed = stdout.trim();
|
|
83
93
|
let value = trimmed;
|
|
84
94
|
try {
|
|
@@ -89,11 +99,12 @@ export function parseNpmViewLatest(stdout) {
|
|
|
89
99
|
}
|
|
90
100
|
if (typeof value !== "string")
|
|
91
101
|
return undefined;
|
|
92
|
-
return
|
|
102
|
+
return isAllowedChannelVersion(value, channel) ? value : undefined;
|
|
93
103
|
}
|
|
94
|
-
export function buildExactInstallArgs(version) {
|
|
95
|
-
if (!
|
|
96
|
-
throw new Error(`invalid
|
|
104
|
+
export function buildExactInstallArgs(version, channel = "latest") {
|
|
105
|
+
if (!isAllowedChannelVersion(version, channel)) {
|
|
106
|
+
throw new Error(`invalid ${channel} version: ${version}`);
|
|
107
|
+
}
|
|
97
108
|
return ["install", "-g", `${PACKAGE_NAME}@${version}`, "--no-fund", "--no-audit"];
|
|
98
109
|
}
|
|
99
110
|
export function verifyNpmListVersion(stdout, expectedVersion) {
|
|
@@ -106,7 +117,13 @@ export function verifyNpmListVersion(stdout, expectedVersion) {
|
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
export function verifyPathVersion(stdout, expectedVersion) {
|
|
109
|
-
|
|
120
|
+
// `loop-agent --version` may append a build provenance marker. The release
|
|
121
|
+
// version itself must still match exactly; accepting no other trailing form
|
|
122
|
+
// keeps PATH verification bound to the requested stable package.
|
|
123
|
+
return new RegExp(`^${escapeRegExp(expectedVersion)}(?: \\(build [^)]+\\))?$`).test(stdout.trim());
|
|
124
|
+
}
|
|
125
|
+
function escapeRegExp(value) {
|
|
126
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
110
127
|
}
|
|
111
128
|
async function proveGlobalNpmInstall(options) {
|
|
112
129
|
const root = await options.runner(options.npmCommand, ["root", "-g"], {
|
|
@@ -142,6 +159,11 @@ function isStrictRelease(version) {
|
|
|
142
159
|
const parsed = semver.parse(version);
|
|
143
160
|
return Boolean(parsed && parsed.prerelease.length === 0);
|
|
144
161
|
}
|
|
162
|
+
function isAllowedChannelVersion(version, channel) {
|
|
163
|
+
if (channel === "latest")
|
|
164
|
+
return isStrictRelease(version);
|
|
165
|
+
return Boolean(semver.valid(version));
|
|
166
|
+
}
|
|
145
167
|
async function runCommand(command, args, options) {
|
|
146
168
|
return new Promise((resolve) => {
|
|
147
169
|
const child = spawn(command, [...args], {
|
|
@@ -147,6 +147,8 @@ export const backendTestConfigSchema = z
|
|
|
147
147
|
gapDoc: z.string().min(1).optional(),
|
|
148
148
|
/** gap-fill pytest execution scope: only affected items (default) or all eligible. */
|
|
149
149
|
executeScope: z.enum(["affected", "all"]).optional(),
|
|
150
|
+
/** Backend-test-only workspace mutation control; omitted means Git guard. */
|
|
151
|
+
workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
|
|
150
152
|
})
|
|
151
153
|
.strict()
|
|
152
154
|
.optional();
|
|
@@ -237,5 +239,13 @@ export const taskConfigSchema = z.preprocess((raw) => {
|
|
|
237
239
|
if ("executor" in obj || "cursorModel" in obj) {
|
|
238
240
|
throw new Error(CURSOR_TASK_FIELD_REMOVED_ERROR);
|
|
239
241
|
}
|
|
242
|
+
const backendTest = obj.backendTest;
|
|
243
|
+
if (backendTest &&
|
|
244
|
+
typeof backendTest === "object" &&
|
|
245
|
+
!Array.isArray(backendTest) &&
|
|
246
|
+
"workspaceControl" in backendTest &&
|
|
247
|
+
obj.taskKind !== "backend-test") {
|
|
248
|
+
throw new Error("backendTest.workspaceControl is only valid when taskKind is backend-test");
|
|
249
|
+
}
|
|
240
250
|
return raw;
|
|
241
251
|
}, taskConfigObjectSchema);
|
|
@@ -232,8 +232,11 @@ export async function applyTaskContract(input) {
|
|
|
232
232
|
// Re-parse through schema to ensure full TaskConfig shape for hash stability
|
|
233
233
|
const fullConfig = parseTaskConfigForRuntime(mergedConfig);
|
|
234
234
|
const taskConfigContent = serializeTaskConfigForDisk(fullConfig);
|
|
235
|
-
const requirementContent = projectRequirementMarkdown(input.draft);
|
|
235
|
+
const requirementContent = projectRequirementMarkdown(input.draft, input.requirementLedger);
|
|
236
236
|
const constraintsContent = projectConstraintsMarkdown(input.draft);
|
|
237
|
+
const requirementLedgerContent = input.requirementLedger !== undefined
|
|
238
|
+
? `${JSON.stringify(input.requirementLedger, null, 2)}\n`
|
|
239
|
+
: undefined;
|
|
237
240
|
// Validate references manifest hash if present (do not modify)
|
|
238
241
|
const paths = getTaskPaths(input.repoRoot, input.taskId);
|
|
239
242
|
const manifestText = await readFile(getSourceManifestPath(paths.sourceDir), "utf-8").catch(() => null);
|
|
@@ -241,6 +244,7 @@ export async function applyTaskContract(input) {
|
|
|
241
244
|
requirementBytes: requirementContent,
|
|
242
245
|
constraintsBytes: constraintsContent,
|
|
243
246
|
referenceManifestBytes: manifestText,
|
|
247
|
+
requirementLedgerBytes: requirementLedgerContent,
|
|
244
248
|
});
|
|
245
249
|
const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
|
|
246
250
|
const canonicalHash = computeCanonicalHash({
|
|
@@ -277,6 +281,9 @@ export async function applyTaskContract(input) {
|
|
|
277
281
|
constraintsContent,
|
|
278
282
|
taskConfigContent,
|
|
279
283
|
contractRef: nextRef,
|
|
284
|
+
...(requirementLedgerContent !== undefined
|
|
285
|
+
? { requirementLedgerContent }
|
|
286
|
+
: {}),
|
|
280
287
|
},
|
|
281
288
|
});
|
|
282
289
|
await appendRequestLedgerEntry({
|
|
@@ -312,6 +319,9 @@ export async function applyTaskContract(input) {
|
|
|
312
319
|
changedFiles: [
|
|
313
320
|
"source/需求.md",
|
|
314
321
|
"source/执行约束.md",
|
|
322
|
+
...(requirementLedgerContent !== undefined
|
|
323
|
+
? ["source/requirement-ledger.json"]
|
|
324
|
+
: []),
|
|
315
325
|
"task.json",
|
|
316
326
|
"contract.json",
|
|
317
327
|
],
|
|
@@ -29,3 +29,5 @@ export const TRANSACTIONS_DIRNAME = TRANSACTIONS_DIR_NAME;
|
|
|
29
29
|
export const REQUEST_LEDGER_BASENAME = REQUEST_LEDGER_FILE_NAME;
|
|
30
30
|
export const REQUIREMENT_FILE_NAME = "需求.md";
|
|
31
31
|
export const CONSTRAINTS_FILE_NAME = "执行约束.md";
|
|
32
|
+
/** Source fidelity ledger (20260824-frontend-source-fidelity-ledger-v1). */
|
|
33
|
+
export const REQUIREMENT_LEDGER_FILE_NAME = "requirement-ledger.json";
|
|
@@ -35,6 +35,9 @@ export function computeSourceHashes(input) {
|
|
|
35
35
|
if (input.referenceManifestBytes) {
|
|
36
36
|
sourceHashes.referenceManifestSha256 = sha256Utf8(input.referenceManifestBytes);
|
|
37
37
|
}
|
|
38
|
+
if (input.requirementLedgerBytes) {
|
|
39
|
+
sourceHashes.requirementLedgerSha256 = sha256Utf8(input.requirementLedgerBytes);
|
|
40
|
+
}
|
|
38
41
|
return sourceHashes;
|
|
39
42
|
}
|
|
40
43
|
export function refMatchesObserved(ref, observedCanonicalHash) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
|
|
3
|
+
import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, REQUIREMENT_LEDGER_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
|
|
4
4
|
import { computeCanonicalHash, computeSourceHashes, computeTaskConfigSha256, } from "./hash.js";
|
|
5
5
|
import { parseTaskContractRef } from "./schema.js";
|
|
6
6
|
import { getTaskPaths } from "../runtime.js";
|
|
@@ -33,11 +33,13 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
|
|
|
33
33
|
const paths = getTaskPaths(repoRoot, taskId);
|
|
34
34
|
const requirementPath = path.join(paths.sourceDir, REQUIREMENT_FILE_NAME);
|
|
35
35
|
const constraintsPath = path.join(paths.sourceDir, CONSTRAINTS_FILE_NAME);
|
|
36
|
-
const
|
|
36
|
+
const requirementLedgerPath = path.join(paths.sourceDir, REQUIREMENT_LEDGER_FILE_NAME);
|
|
37
|
+
const [requirementBytes, constraintsBytes, taskConfigText, manifestText, requirementLedgerBytes] = await Promise.all([
|
|
37
38
|
readTextIfExists(requirementPath),
|
|
38
39
|
readTextIfExists(constraintsPath),
|
|
39
40
|
readTextIfExists(paths.taskConfigPath),
|
|
40
41
|
readTextIfExists(getSourceManifestPath(paths.sourceDir)),
|
|
42
|
+
readTextIfExists(requirementLedgerPath),
|
|
41
43
|
]);
|
|
42
44
|
let taskConfigRaw = null;
|
|
43
45
|
let taskConfigSha256 = null;
|
|
@@ -52,6 +54,7 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
|
|
|
52
54
|
taskConfigRaw,
|
|
53
55
|
taskConfigSha256,
|
|
54
56
|
referenceManifestBytes: manifestText,
|
|
57
|
+
requirementLedgerBytes,
|
|
55
58
|
requirementPath,
|
|
56
59
|
constraintsPath,
|
|
57
60
|
taskConfigPath: paths.taskConfigPath,
|
|
@@ -164,7 +167,9 @@ export async function observeTaskContractState(repoRoot, taskId) {
|
|
|
164
167
|
message: error instanceof Error ? error.message : String(error),
|
|
165
168
|
});
|
|
166
169
|
}
|
|
167
|
-
const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId
|
|
170
|
+
const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId, {
|
|
171
|
+
ref: ref ?? undefined,
|
|
172
|
+
});
|
|
168
173
|
errors.push(...(observed.errors ?? []));
|
|
169
174
|
if (!ref) {
|
|
170
175
|
return {
|
|
@@ -226,7 +231,7 @@ export async function observeTaskContractState(repoRoot, taskId) {
|
|
|
226
231
|
observationErrors: errors,
|
|
227
232
|
};
|
|
228
233
|
}
|
|
229
|
-
async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
|
|
234
|
+
async function computeObservedCanonicalHashSafe(repoRoot, taskId, options) {
|
|
230
235
|
const errors = [];
|
|
231
236
|
try {
|
|
232
237
|
const bytes = await loadObservedTaskBytes(repoRoot, taskId);
|
|
@@ -256,10 +261,26 @@ async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
|
|
|
256
261
|
}
|
|
257
262
|
return { hash: null, errors };
|
|
258
263
|
}
|
|
264
|
+
// Legacy compatibility (AC-FIDELITY-004): only include the ledger bytes
|
|
265
|
+
// when the stored ref actually declares requirementLedgerSha256. A ref
|
|
266
|
+
// without the ledger field keeps the existing semantics and never
|
|
267
|
+
// misreports drift for tasks that predate the ledger.
|
|
268
|
+
const refLedgerHash = options?.ref?.sourceHashes.requirementLedgerSha256 ?? null;
|
|
269
|
+
if (refLedgerHash && bytes.requirementLedgerBytes === null) {
|
|
270
|
+
errors.push({
|
|
271
|
+
code: "MISSING_REQUIREMENT_LEDGER",
|
|
272
|
+
path: "source/requirement-ledger.json",
|
|
273
|
+
message: `contract records requirementLedgerSha256 but ${REQUIREMENT_LEDGER_FILE_NAME} is missing`,
|
|
274
|
+
});
|
|
275
|
+
return { hash: null, errors };
|
|
276
|
+
}
|
|
259
277
|
const sourceHashes = computeSourceHashes({
|
|
260
278
|
requirementBytes: bytes.requirementBytes,
|
|
261
279
|
constraintsBytes: bytes.constraintsBytes,
|
|
262
280
|
referenceManifestBytes: bytes.referenceManifestBytes,
|
|
281
|
+
...(refLedgerHash
|
|
282
|
+
? { requirementLedgerBytes: bytes.requirementLedgerBytes }
|
|
283
|
+
: {}),
|
|
263
284
|
});
|
|
264
285
|
const hash = computeCanonicalHash({
|
|
265
286
|
sourceHashes,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { getTaskPaths } from "../runtime.js";
|
|
3
|
-
import { CONTRACT_JSON_BASENAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
|
|
3
|
+
import { CONTRACT_JSON_BASENAME, REQUIREMENT_LEDGER_FILE_NAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
|
|
4
4
|
export function getTaskContractPaths(repoRoot, taskId) {
|
|
5
5
|
const base = getTaskPaths(repoRoot, taskId);
|
|
6
6
|
return {
|
|
@@ -15,5 +15,6 @@ export function getTaskContractPaths(repoRoot, taskId) {
|
|
|
15
15
|
transactionsDir: path.join(base.taskDir, TRANSACTIONS_DIRNAME),
|
|
16
16
|
requestLedgerPath: path.join(base.taskDir, REQUEST_LEDGER_BASENAME),
|
|
17
17
|
lockPath: path.join(base.taskDir, ".contract.lock"),
|
|
18
|
+
requirementLedgerPath: path.join(base.sourceDir, REQUIREMENT_LEDGER_FILE_NAME),
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { canonicalizePathArray, canonicalizeTextProjection, } from "./canonicalize.js";
|
|
2
2
|
/**
|
|
3
3
|
* Project Draft → source/需求.md (deterministic template, projectionVersion=1).
|
|
4
|
+
* 当携带 ledger 时追加 `## Source ledger` 区块:明确 需求.md 是派生导航视图,
|
|
5
|
+
* 语义权威为原始 requirement/acceptance 文档与受管 requirement-ledger.json。
|
|
4
6
|
*/
|
|
5
|
-
export function projectRequirementMarkdown(draft) {
|
|
7
|
+
export function projectRequirementMarkdown(draft, ledger) {
|
|
6
8
|
const lines = [
|
|
7
9
|
`# 需求:${draft.title}`,
|
|
8
10
|
"",
|
|
@@ -62,6 +64,18 @@ export function projectRequirementMarkdown(draft) {
|
|
|
62
64
|
lines.push(`- [${ref.role}] ${ref.ref}`);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
if (ledger) {
|
|
68
|
+
const gateStatus = ledger.stats.unresolvedCount > 0 ||
|
|
69
|
+
ledger.stats.conflictCount > 0 ||
|
|
70
|
+
ledger.stats.highRiskUncoveredCount > 0
|
|
71
|
+
? "blocked"
|
|
72
|
+
: "pass";
|
|
73
|
+
const ledgerLine = ledger.sourcePaths.length > 0
|
|
74
|
+
? `source/requirement-ledger.json (${ledger.sourcePaths.length} source document(s))`
|
|
75
|
+
: "source/requirement-ledger.json";
|
|
76
|
+
lines.push("", "## Source ledger", "", "> 本文件是派生导航视图,不替代原始 requirement/acceptance 文档。" +
|
|
77
|
+
" 语义权威为原始文档与 `source/requirement-ledger.json`(确定性切分账本)。", "", `- ledger: ${ledgerLine.length > 160 ? `${ledgerLine.slice(0, 160)}…` : ledgerLine}`, `- inputDigest: ${ledger.inputDigest}`, `- fragments: ${ledger.stats.fragmentCount}`, `- canonical requirements: ${ledger.stats.canonicalRequirementCount}`, `- status: ${gateStatus}`);
|
|
78
|
+
}
|
|
65
79
|
lines.push("");
|
|
66
80
|
return canonicalizeTextProjection(lines.join("\n"));
|
|
67
81
|
}
|
|
@@ -107,6 +107,8 @@ export const taskContractSourceHashesSchema = z
|
|
|
107
107
|
requirementSha256: sha256Schema,
|
|
108
108
|
constraintsSha256: sha256Schema,
|
|
109
109
|
referenceManifestSha256: sha256Schema.optional(),
|
|
110
|
+
/** Source fidelity ledger hash; present only when the task binds a ledger. */
|
|
111
|
+
requirementLedgerSha256: sha256Schema.optional(),
|
|
110
112
|
})
|
|
111
113
|
.strict();
|
|
112
114
|
export const taskContractRefSchema = z
|
|
@@ -21,6 +21,7 @@ export async function runJournaledContractCommit(input) {
|
|
|
21
21
|
const constraintsRel = `source/${CONSTRAINTS_FILE_NAME}`;
|
|
22
22
|
const taskConfigRel = "task.json";
|
|
23
23
|
const contractRel = CONTRACT_FILE_NAME;
|
|
24
|
+
const requirementLedgerRel = `source/requirement-ledger.json`;
|
|
24
25
|
const targetSpecs = [
|
|
25
26
|
{
|
|
26
27
|
kind: "requirement",
|
|
@@ -46,6 +47,17 @@ export async function runJournaledContractCommit(input) {
|
|
|
46
47
|
content: input.targets.contractRef,
|
|
47
48
|
isJson: true,
|
|
48
49
|
},
|
|
50
|
+
...(input.targets.requirementLedgerContent !== undefined
|
|
51
|
+
? [
|
|
52
|
+
{
|
|
53
|
+
kind: "requirement-ledger",
|
|
54
|
+
relativePath: requirementLedgerRel,
|
|
55
|
+
// Content is already serialized JSON text; stageText avoids double-encoding.
|
|
56
|
+
content: input.targets.requirementLedgerContent,
|
|
57
|
+
isJson: false,
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
49
61
|
];
|
|
50
62
|
const targetRecords = [];
|
|
51
63
|
for (const spec of targetSpecs) {
|
|
@@ -55,6 +55,25 @@ export function observationBlocksPrepare(state) {
|
|
|
55
55
|
}
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 精确 identity 绑定的人类 resolution 判定(AC-001 / AC-005):decidedBy==='human'、
|
|
60
|
+
* decision trim 后非空、sourceRef 与冲突 id 精确匹配三者同时成立才算有效;model/
|
|
61
|
+
* system 伪造判定、stale sourceRef(不匹配当前冲突 identity)或空/空白 decision 一律
|
|
62
|
+
* 不解锁。resolution 信息取自 draft 层(不重排流水线,listPrepareGaps 仍先于 ledger
|
|
63
|
+
* 构建执行)。
|
|
64
|
+
*/
|
|
65
|
+
export function hasValidHumanConflictResolution(draft, conflictId) {
|
|
66
|
+
for (const resolution of draft.sourceResolutions ?? []) {
|
|
67
|
+
if (resolution.sourceRef !== conflictId)
|
|
68
|
+
continue;
|
|
69
|
+
if (resolution.decidedBy !== "human")
|
|
70
|
+
continue;
|
|
71
|
+
if (!resolution.decision?.trim())
|
|
72
|
+
continue;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
58
77
|
export function listPrepareGaps(input) {
|
|
59
78
|
const gaps = [];
|
|
60
79
|
const risks = [];
|
|
@@ -108,6 +127,12 @@ export function listPrepareGaps(input) {
|
|
|
108
127
|
}
|
|
109
128
|
if (input.acceptanceConflicts && input.acceptanceConflicts.length > 0) {
|
|
110
129
|
for (const conflict of input.acceptanceConflicts) {
|
|
130
|
+
// AC-001:有效 human resolution(human + 非空 decision + sourceRef 与冲突 id
|
|
131
|
+
// 精确匹配)解除该 id 的 legacy blocking gap;无 resolution 或 resolution 不
|
|
132
|
+
// 满足三条件(AC-005:伪造判定 / stale sourceRef / 空 decision)仍发射阻断。
|
|
133
|
+
if (hasValidHumanConflictResolution(input.draft, conflict.id)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
111
136
|
gaps.push({
|
|
112
137
|
code: "ACCEPTANCE_ID_CONFLICT",
|
|
113
138
|
level: "blocking",
|
|
@@ -246,3 +271,166 @@ export function hasBlockingGaps(gaps) {
|
|
|
246
271
|
export function hasBlockingRisks(risks) {
|
|
247
272
|
return risks.some((risk) => risk.level === "blocking");
|
|
248
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Source fidelity ledger 分级 fail-closed 阻断(AC-003 / AC-FIDELITY-003):
|
|
276
|
+
* 高风险未映射/排除、unresolved、冲突返回稳定错误并阻断;低风险 context-only 仅当
|
|
277
|
+
* 理由完整且独立 reviewer pass 后继续;duplicate 保留全部来源(warning 可见)。
|
|
278
|
+
* 仅 taskKind 为 frontend-implementation 时全量阻断,其他 taskKind 保持 warning。
|
|
279
|
+
*/
|
|
280
|
+
export function appendLedgerGaps(input) {
|
|
281
|
+
const { gaps, risks } = input;
|
|
282
|
+
if (!input.ledgerSummary?.present)
|
|
283
|
+
return;
|
|
284
|
+
const blocking = input.taskKind === "frontend-implementation";
|
|
285
|
+
const level = blocking ? "blocking" : "warning";
|
|
286
|
+
const summary = input.ledgerSummary;
|
|
287
|
+
if (summary.unresolvedCount > 0) {
|
|
288
|
+
const gap = {
|
|
289
|
+
code: "SOURCE_FIDELITY_UNRESOLVED",
|
|
290
|
+
level,
|
|
291
|
+
message: `ledger 有 ${summary.unresolvedCount} 个 unresolved fragment(需人工 sourceResolutions 确认后才可继续)`,
|
|
292
|
+
details: {
|
|
293
|
+
fragmentIds: summary.exclusions
|
|
294
|
+
.filter((e) => /^REQ-SRC-/.test(e.fragmentId) || e.fragmentId.startsWith("AC-"))
|
|
295
|
+
.slice(0, 10)
|
|
296
|
+
.map((e) => e.fragmentId),
|
|
297
|
+
},
|
|
298
|
+
selfHealing: blocking
|
|
299
|
+
? {
|
|
300
|
+
action: "reimport-prd",
|
|
301
|
+
steps: [
|
|
302
|
+
"检查原始 PRD 中无法映射到 canonical AC 的可执行片段(unresolved)",
|
|
303
|
+
"补齐/修正 PRD 使每个可执行片段都能绑定 AC,或由人工在 sourceResolutions 中确认排除理由",
|
|
304
|
+
"重新 importPrd(同 taskId)后 task advance 重建 ledger",
|
|
305
|
+
],
|
|
306
|
+
}
|
|
307
|
+
: undefined,
|
|
308
|
+
};
|
|
309
|
+
gaps.push(gap);
|
|
310
|
+
risks.push({
|
|
311
|
+
code: "SOURCE_FIDELITY_UNRESOLVED",
|
|
312
|
+
level: blocking ? "blocking" : "elevated",
|
|
313
|
+
message: gap.message,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (summary.conflictCount > 0) {
|
|
317
|
+
const gap = {
|
|
318
|
+
code: "SOURCE_FIDELITY_CONFLICT",
|
|
319
|
+
level,
|
|
320
|
+
message: `ledger 检测到 ${summary.conflictCount} 个 AC 文本冲突(同一 ID 不同文本)`,
|
|
321
|
+
selfHealing: blocking
|
|
322
|
+
? {
|
|
323
|
+
action: "reimport-prd",
|
|
324
|
+
steps: [
|
|
325
|
+
"统一同一 AC ID 的文本后重新 importPrd(同 taskId)",
|
|
326
|
+
"或由人工在 sourceResolutions 中声明冲突取舍(conflict → decision)",
|
|
327
|
+
],
|
|
328
|
+
}
|
|
329
|
+
: undefined,
|
|
330
|
+
};
|
|
331
|
+
gaps.push(gap);
|
|
332
|
+
risks.push({
|
|
333
|
+
code: "SOURCE_FIDELITY_CONFLICT",
|
|
334
|
+
level: blocking ? "blocking" : "elevated",
|
|
335
|
+
message: gap.message,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (summary.failureCode === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") {
|
|
339
|
+
const gap = {
|
|
340
|
+
code: "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
|
|
341
|
+
level,
|
|
342
|
+
message: "ledger 存在高风险 acceptance 片段未映射到 canonical requirement",
|
|
343
|
+
selfHealing: blocking
|
|
344
|
+
? {
|
|
345
|
+
action: "reimport-prd",
|
|
346
|
+
steps: [
|
|
347
|
+
"为每个未覆盖的高风险片段补齐 canonical AC(或人工 sourceResolutions 确认)",
|
|
348
|
+
"重新 importPrd(同 taskId)后 task advance 重建 ledger",
|
|
349
|
+
],
|
|
350
|
+
}
|
|
351
|
+
: undefined,
|
|
352
|
+
};
|
|
353
|
+
gaps.push(gap);
|
|
354
|
+
risks.push({
|
|
355
|
+
code: "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
|
|
356
|
+
level: blocking ? "blocking" : "elevated",
|
|
357
|
+
message: gap.message,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
for (const error of input.ledgerErrors ?? []) {
|
|
361
|
+
if (error.code === "SOURCE_FIDELITY_LEDGER_MISSING") {
|
|
362
|
+
const gap = {
|
|
363
|
+
code: "SOURCE_FIDELITY_LEDGER_MISSING",
|
|
364
|
+
level,
|
|
365
|
+
message: error.message,
|
|
366
|
+
};
|
|
367
|
+
gaps.push(gap);
|
|
368
|
+
}
|
|
369
|
+
// AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据、来源引用
|
|
370
|
+
// 无效、fragment 无效均为 blocking gap(frontend task),保证 prepare/apply
|
|
371
|
+
// 以稳定码 fail closed(appendLedgerGaps 是 prepare 补洞的唯一入口)。
|
|
372
|
+
if (error.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED" ||
|
|
373
|
+
error.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
|
|
374
|
+
error.code === "SOURCE_FIDELITY_FRAGMENT_INVALID") {
|
|
375
|
+
const gap = {
|
|
376
|
+
code: error.code,
|
|
377
|
+
level,
|
|
378
|
+
message: error.message,
|
|
379
|
+
selfHealing: blocking
|
|
380
|
+
? {
|
|
381
|
+
action: "reimport-prd",
|
|
382
|
+
steps: [
|
|
383
|
+
"检查 PRD 中每个 canonical requirement 是否都有可审计的 fragment/sourceRef 证据",
|
|
384
|
+
"补齐缺失的验收标准映射或修正引用后重新 importPrd(同 taskId)",
|
|
385
|
+
"重新 importPrd(同 taskId)后 task advance 重建 ledger",
|
|
386
|
+
],
|
|
387
|
+
}
|
|
388
|
+
: undefined,
|
|
389
|
+
};
|
|
390
|
+
gaps.push(gap);
|
|
391
|
+
risks.push({
|
|
392
|
+
code: error.code,
|
|
393
|
+
level: blocking ? "blocking" : "elevated",
|
|
394
|
+
message: error.message,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (error.code === "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED" &&
|
|
398
|
+
blocking) {
|
|
399
|
+
// 低风险 context-only 仅独立 reviewer pass 放行;未通过则 frontend 任务阻断。
|
|
400
|
+
const gap = {
|
|
401
|
+
code: "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED",
|
|
402
|
+
level: "blocking",
|
|
403
|
+
message: error.message,
|
|
404
|
+
selfHealing: {
|
|
405
|
+
action: "reimport-prd",
|
|
406
|
+
steps: [
|
|
407
|
+
"检查 context-only 片段是否缺少完整排除理由,或由人工在 sourceResolutions 中确认",
|
|
408
|
+
"重新 importPrd(同 taskId)后 task advance 重建 ledger 并复核 reviewer 判定",
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
gaps.push(gap);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// 低风险 context-only:记录并提示排除理由/独立 reviewer 要求(不阻断普通任务)。
|
|
416
|
+
if (summary.contextOnlyCount > 0) {
|
|
417
|
+
gaps.push({
|
|
418
|
+
code: "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED",
|
|
419
|
+
level: "warning",
|
|
420
|
+
message: `ledger 有 ${summary.contextOnlyCount} 个 context-only fragment(低风险排除;frontend 任务需完整理由且独立 reviewer pass)`,
|
|
421
|
+
details: {
|
|
422
|
+
fragmentIds: summary.exclusions
|
|
423
|
+
.filter((e) => e.fragmentId.startsWith("REQ-SRC-"))
|
|
424
|
+
.slice(0, 10)
|
|
425
|
+
.map((e) => e.fragmentId),
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (summary.duplicateCount > 0) {
|
|
430
|
+
gaps.push({
|
|
431
|
+
code: "SOURCE_FIDELITY_DUPLICATE_PRESERVED",
|
|
432
|
+
level: "warning",
|
|
433
|
+
message: `ledger 保留 ${summary.duplicateCount} 个 duplicate fragment(duplicate 保留全部来源)`,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|