@tea-agent/loop-agent 0.40.0-next.2 → 0.40.0-next.4
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 +9 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/executors/dag-pi-executor.js +46 -5
- package/dist/executors/shell-executor.js +28 -0
- package/dist/executors/workspace-write-snapshot.js +68 -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/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DLMldIE8.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DVOg1dSW.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-715v7joo.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtMEGtAQ.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-qUxXsdxu.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BYMag72f.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5jt5rtx.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DvZghfS2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CXutxAmY.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BkANIDWk.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bwy1wwlr.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3pWYVCG.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DRzjzmFD.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C4xXDxXL.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BngIZ90b.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-B40ACoFn.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BE-1ZRAI.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BoYbwVTh.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B5-KA0fK.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DJpCToT5.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-De8R04Ng.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BU1N8DiA.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BUUUzSYI.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-zLoT1RC4.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-_q4-4N8c.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CTqzoNSL.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMKSYKJM.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Dj6U8Sb7.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BfoxVYky.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-CA0nQEbb.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-T5NfvHGV.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BEhUMkut.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B2Ld6nNt.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-nV9T85yS.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIO1zDYh.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-B_ucAqLq.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BIA3VNcZ.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CNzLjNV3.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-PWytzfc_.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CS0wGoIq.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-C3Wsqph8.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DF6FDTYg.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BdQUJm6y.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bv9rppGg.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C3Hpvb_B.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CZOWLpno.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CPQRIZx5.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CkwM9BgA.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-wGSoDzYc.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-GNVas2gx.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C5USnHw7.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +531 -78
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +4 -3
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +144 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +45 -12
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-BG6TLen_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dgsvn63q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DaNfG1BJ.js +0 -8
|
@@ -6,11 +6,15 @@ import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
|
6
6
|
import { getTaskContractPaths } from "../contract/paths.js";
|
|
7
7
|
import { loadTaskConfig } from "../runtime.js";
|
|
8
8
|
import { buildPrepareDraft } from "./build-draft.js";
|
|
9
|
-
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
|
|
9
|
+
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, appendLedgerGaps, } from "./completeness.js";
|
|
10
10
|
import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
|
|
11
11
|
import { extractRequirementFactsFromMarkdown, fillMissingRequirementFacts, mergeRequirementFacts, } from "./parse-intent.js";
|
|
12
12
|
import { draftReferencesFromManifest, validateImportedPrdReferences, } from "./reference-integrity.js";
|
|
13
13
|
import { readSourceManifest } from "../source-references.js";
|
|
14
|
+
import { buildRequirementLedger, classifyLedgerGate, effectiveSourceResolutions, summarizeLedger, validateRequirementLedger, } from "./ledger.js";
|
|
15
|
+
import { defaultLedgerReviewer, LEDGER_REVIEW_DEFAULT_BUDGET_CHARS, ledgerReviewItemsFromRecords, reviewLedgerFragments, } from "./ledger-review.js";
|
|
16
|
+
import { buildFragmentInventory } from "./fragment-inventory.js";
|
|
17
|
+
import { sha256Utf8 } from "../contract/hash.js";
|
|
14
18
|
import { PREPARE_MAX_FILE_BYTES } from "./types.js";
|
|
15
19
|
async function fileNonEmpty(filePath) {
|
|
16
20
|
try {
|
|
@@ -67,6 +71,81 @@ function isProjectedSourceResolutionsSection(sectionBody) {
|
|
|
67
71
|
return true;
|
|
68
72
|
return lines.every((line) => entryRe.test(line));
|
|
69
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Typed parse of one canonical Source Resolutions line (project.ts L63-68):
|
|
76
|
+
* `- ${sourceRef}: ${conflict} → ${decision} (${reason}; decidedBy=${decidedBy})`.
|
|
77
|
+
* Tail-anchor strategy (AC-008 / AC-HARD-002, exact inverse of the projector's
|
|
78
|
+
* left-to-right concatenation): sourceRef ends at the first `: `, conflict ends
|
|
79
|
+
* at the first ` → `, and the reason group is reversed from the tail — the line
|
|
80
|
+
* must close with `)`, the REAL `; decidedBy=` separator is the LAST occurrence
|
|
81
|
+
* (reason may itself contain `; decidedBy=`), and the reason group opener is the
|
|
82
|
+
* LAST ` (` before that anchor (decision may itself contain ` (`). decision/reason
|
|
83
|
+
* round-trip byte-exact, including spaces and ASCII parentheses — no truncation
|
|
84
|
+
* at the first ` (`. Any missing field or shape mismatch returns null — the
|
|
85
|
+
* caller must reject the whole section (fail-closed), never partially accept.
|
|
86
|
+
* decidedBy is additionally enforced to be exactly "human" here
|
|
87
|
+
* (schema z.enum(['human'])); model/system/hand-edited values are treated as
|
|
88
|
+
* hand-edited/model-decided and block with MANAGED_PROJECTION_INCOMPLETE (AC-004b).
|
|
89
|
+
*/
|
|
90
|
+
function parseSourceResolutionLine(line) {
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
if (!trimmed.startsWith("- "))
|
|
93
|
+
return null;
|
|
94
|
+
const rest = trimmed.slice(2);
|
|
95
|
+
const sourceRefEnd = rest.indexOf(": ");
|
|
96
|
+
if (sourceRefEnd <= 0)
|
|
97
|
+
return null;
|
|
98
|
+
const sourceRef = rest.slice(0, sourceRefEnd);
|
|
99
|
+
const afterSourceRef = rest.slice(sourceRefEnd + 2);
|
|
100
|
+
const arrow = afterSourceRef.indexOf(" → ");
|
|
101
|
+
if (arrow <= 0)
|
|
102
|
+
return null;
|
|
103
|
+
const conflict = afterSourceRef.slice(0, arrow);
|
|
104
|
+
const afterArrow = afterSourceRef.slice(arrow + 3);
|
|
105
|
+
// 尾部锚点:行必须以 `)` 收尾,真实分隔符取最后一次 `; decidedBy=`
|
|
106
|
+
// (reason 可能内嵌 ASCII `; decidedBy=`,首个锚点会截断 reason)。
|
|
107
|
+
if (!afterArrow.endsWith(")"))
|
|
108
|
+
return null;
|
|
109
|
+
const decidedByAnchor = "; decidedBy=";
|
|
110
|
+
const decidedIdx = afterArrow.lastIndexOf(decidedByAnchor);
|
|
111
|
+
if (decidedIdx < 0)
|
|
112
|
+
return null;
|
|
113
|
+
const decidedBy = afterArrow.slice(decidedIdx + decidedByAnchor.length, -1);
|
|
114
|
+
if (decidedBy !== "human")
|
|
115
|
+
return null;
|
|
116
|
+
// reason 起始:锚点之前最后一个 ` (`(decision 可能内嵌 ASCII ` (`,首个 ` (`
|
|
117
|
+
// 会截断 decision)。decision/reason 逐字节保留。
|
|
118
|
+
const reasonRegion = afterArrow.slice(0, decidedIdx);
|
|
119
|
+
const paren = reasonRegion.lastIndexOf(" (");
|
|
120
|
+
if (paren <= 0)
|
|
121
|
+
return null;
|
|
122
|
+
const decision = reasonRegion.slice(0, paren);
|
|
123
|
+
const reason = reasonRegion.slice(paren + 2);
|
|
124
|
+
if (!conflict.trim() || !decision.trim() || !reason.trim())
|
|
125
|
+
return null;
|
|
126
|
+
return { sourceRef, conflict, decision, reason, decidedBy };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Parse the whole `## Source Resolutions` section body into typed resolutions.
|
|
130
|
+
* Returns null when any line fails — the section is rejected as a whole, never
|
|
131
|
+
* partially accepted or silently downgraded (AC-004). Empty sections yield an
|
|
132
|
+
* empty array (kept semantics: isProjectedSourceResolutionsSection accepts
|
|
133
|
+
* empty sections).
|
|
134
|
+
*/
|
|
135
|
+
function parseSourceResolutionsSection(sectionBody) {
|
|
136
|
+
const lines = sectionBody
|
|
137
|
+
.split(/\r?\n/)
|
|
138
|
+
.map((line) => line.trim())
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
const resolutions = [];
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const parsed = parseSourceResolutionLine(line);
|
|
143
|
+
if (!parsed)
|
|
144
|
+
return null;
|
|
145
|
+
resolutions.push(parsed);
|
|
146
|
+
}
|
|
147
|
+
return resolutions;
|
|
148
|
+
}
|
|
70
149
|
/** Section titles the read-back validator may inspect (static whitelist). */
|
|
71
150
|
const MANAGED_PROJECTION_SECTION_TITLES = [
|
|
72
151
|
"References",
|
|
@@ -135,6 +214,22 @@ async function loadManagedProjectionDraft(input) {
|
|
|
135
214
|
" --from-draft <reviewed-draft.json> --json",
|
|
136
215
|
};
|
|
137
216
|
}
|
|
217
|
+
// Typed read-back (2026-08-24 L1): the shape regex above accepts any
|
|
218
|
+
// decidedBy value; the typed parser is the projector's exact inverse and
|
|
219
|
+
// additionally enforces decidedBy === 'human' (schema z.enum(['human'])).
|
|
220
|
+
// A regex-valid but non-canonical line (decidedBy=model/system/hand-edited)
|
|
221
|
+
// or any free-form line fails the whole section — never partial accept.
|
|
222
|
+
const parsedSourceResolutions = sourceResolutionsBody !== null
|
|
223
|
+
? parseSourceResolutionsSection(sourceResolutionsBody)
|
|
224
|
+
: null;
|
|
225
|
+
if (sourceResolutionsBody !== null && parsedSourceResolutions === null) {
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
message: "managed requirement projection contains hand-edited or model-decided Source Resolutions entries (only canonical decidedBy=human lines are accepted); use loop-agent task advance " +
|
|
229
|
+
input.taskId +
|
|
230
|
+
" --from-draft <reviewed-draft.json> --json",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
138
233
|
const requirementFacts = extractRequirementFactsFromMarkdown(requirementRaw);
|
|
139
234
|
const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
|
|
140
235
|
if (!requirementFacts.objective?.trim()) {
|
|
@@ -184,6 +279,9 @@ async function loadManagedProjectionDraft(input) {
|
|
|
184
279
|
...(requirementFacts.assumptions.length > 0
|
|
185
280
|
? { assumptions: requirementFacts.assumptions }
|
|
186
281
|
: {}),
|
|
282
|
+
...(parsedSourceResolutions && parsedSourceResolutions.length > 0
|
|
283
|
+
? { sourceResolutions: parsedSourceResolutions }
|
|
284
|
+
: {}),
|
|
187
285
|
hardConstraints: config.hardConstraints,
|
|
188
286
|
},
|
|
189
287
|
};
|
|
@@ -601,6 +699,161 @@ export async function prepareTaskSource(input) {
|
|
|
601
699
|
sourceFilesPresent,
|
|
602
700
|
forceOverwriteSource: input.forceOverwriteSource,
|
|
603
701
|
});
|
|
702
|
+
// -------------------------------------------------------------------
|
|
703
|
+
// Source fidelity ledger (20260824-frontend-source-fidelity-ledger-v1)
|
|
704
|
+
// 在 buildPrepareDraft/listPrepareGaps 与 applyTaskContract 之间编排
|
|
705
|
+
// fragment-inventory + ledger 构建。原始 requirement/acceptance 文档保持语义
|
|
706
|
+
// 权威;派生 需求.md 仅作导航视图。reconciliation 失败返回稳定错误并停止,
|
|
707
|
+
// 不回退摘要。
|
|
708
|
+
// -------------------------------------------------------------------
|
|
709
|
+
let ledger;
|
|
710
|
+
let ledgerSummary;
|
|
711
|
+
if (hasImportedPrd &&
|
|
712
|
+
sourceIntegrity &&
|
|
713
|
+
sourceIntegrity.parseableDocuments.some((doc) => doc.role === "requirement" || doc.role === "acceptance")) {
|
|
714
|
+
try {
|
|
715
|
+
const authoritativeDocs = sourceIntegrity.parseableDocuments.filter((doc) => (doc.role === "requirement" || doc.role === "acceptance") &&
|
|
716
|
+
typeof doc.content === "string" &&
|
|
717
|
+
doc.content.trim().length > 0);
|
|
718
|
+
const sourceDocuments = authoritativeDocs.map((doc) => {
|
|
719
|
+
// Canonical source identity 单一边界(AC-001 / AC-FIDELITY-REPAIR-001):
|
|
720
|
+
// ledger sourceDocuments[].path 与 DAG 侧 serializeDagTaskSourcePath /
|
|
721
|
+
// resolveDagTaskSourcePath 一致(repo-root relative POSIX),路径转换只
|
|
722
|
+
// 发生在该边界,不再二次转换进入 digest 计算。
|
|
723
|
+
const absolute = path.resolve(contractPaths.sourceDir, doc.materializedPath);
|
|
724
|
+
return {
|
|
725
|
+
path: path
|
|
726
|
+
.relative(repoRoot, absolute)
|
|
727
|
+
.split(path.sep)
|
|
728
|
+
.join("/"),
|
|
729
|
+
content: doc.content,
|
|
730
|
+
};
|
|
731
|
+
});
|
|
732
|
+
if (sourceDocuments.length > 0) {
|
|
733
|
+
const requirementProjection = projectRequirementMarkdown(built.draft);
|
|
734
|
+
ledger = await buildRequirementLedger({
|
|
735
|
+
taskId,
|
|
736
|
+
requirementSha256: sha256Utf8(requirementProjection),
|
|
737
|
+
sourceDocuments,
|
|
738
|
+
canonicalRequirements: built.draft.requirement.acceptanceCriteria,
|
|
739
|
+
sourceResolutions: (built.draft.sourceResolutions ?? []).map((res) => ({
|
|
740
|
+
sourceRef: res.sourceRef,
|
|
741
|
+
conflict: res.conflict,
|
|
742
|
+
decision: res.decision,
|
|
743
|
+
reason: res.reason,
|
|
744
|
+
decidedBy: res.decidedBy,
|
|
745
|
+
})),
|
|
746
|
+
...(input.sourceFidelity?.reconcile
|
|
747
|
+
? { reconcile: input.sourceFidelity.reconcile }
|
|
748
|
+
: {}),
|
|
749
|
+
});
|
|
750
|
+
const classification = classifyLedgerGate(ledger, {
|
|
751
|
+
expectedInputDigest: ledger.inputDigest,
|
|
752
|
+
});
|
|
753
|
+
const ledgerErrors = classification.ok ? [] : [
|
|
754
|
+
{ code: classification.code, message: classification.message },
|
|
755
|
+
];
|
|
756
|
+
if (!classification.ok &&
|
|
757
|
+
classification.code !== "SOURCE_FIDELITY_LEDGER_STALE") {
|
|
758
|
+
ledgerErrors.push(...validateRequirementLedger(ledger).filter((error) => error.code !== classification.code));
|
|
759
|
+
}
|
|
760
|
+
// 独立 reviewer(AC-007 / AC-FIDELITY-REPAIR-007):context-only /
|
|
761
|
+
// duplicate / conflict 进入独立审核阶段(与 reconciler 分离);低风险
|
|
762
|
+
// context-only 仅 reviewer pass 放行,高风险 context-only 始终阻断。
|
|
763
|
+
// review 条目携带按 budgetChars 有界截断的原始片段文本与精确 fragment id
|
|
764
|
+
// (AC-004 / AC-HARD-004);canonicalRequirements 采用 ledger 物化后的
|
|
765
|
+
// canonicalRequirements(含 REQ-SRC-*,不再仅取 draft acceptance)。
|
|
766
|
+
const review = await reviewLedgerFragments({
|
|
767
|
+
fragments: ledgerReviewItemsFromRecords(ledger.fragments, {
|
|
768
|
+
fragments: buildFragmentInventory({
|
|
769
|
+
documents: sourceDocuments,
|
|
770
|
+
canonicalRequirements: built.draft.requirement.acceptanceCriteria,
|
|
771
|
+
}).fragments,
|
|
772
|
+
budgetChars: LEDGER_REVIEW_DEFAULT_BUDGET_CHARS,
|
|
773
|
+
}),
|
|
774
|
+
canonicalRequirements: ledger.canonicalRequirements.map((requirement) => ({
|
|
775
|
+
id: requirement.id,
|
|
776
|
+
text: requirement.text,
|
|
777
|
+
})),
|
|
778
|
+
reviewer: input.sourceFidelity?.reviewer ?? defaultLedgerReviewer,
|
|
779
|
+
conflictIds: new Set(ledger.conflicts.map((entry) => entry.id)),
|
|
780
|
+
resolvedConflictIds: effectiveSourceResolutions(ledger),
|
|
781
|
+
});
|
|
782
|
+
// 独立 reviewer 拒绝 → 全码透传:低风险 context-only / 高风险 context-only /
|
|
783
|
+
// duplicate / conflict 均以精确稳定码进入 ledgerErrors;review.code===null
|
|
784
|
+
// 时防御性回退 SOURCE_FIDELITY_RECONCILIATION_FAILED(fail-closed)。message
|
|
785
|
+
// 取有界 review.reasons.join("; ")。追加前跳过与既有 ledgerErrors 中 code 与
|
|
786
|
+
// message 均完全一致的条目(不得按 code 单独去重:同码不同 message 必须保留)。
|
|
787
|
+
const reviewRejectedCodes = [];
|
|
788
|
+
if (!review.approved) {
|
|
789
|
+
const code = review.code ?? "SOURCE_FIDELITY_RECONCILIATION_FAILED";
|
|
790
|
+
const message = review.reasons.join("; ");
|
|
791
|
+
const alreadyPresent = ledgerErrors.some((error) => error.code === code && error.message === message);
|
|
792
|
+
if (!alreadyPresent) {
|
|
793
|
+
ledgerErrors.push({ code, message });
|
|
794
|
+
reviewRejectedCodes.push(code);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
ledgerSummary = summarizeLedger(ledger, {
|
|
798
|
+
gateStatus: ledgerErrors.length > 0 ? "fail" : "pass",
|
|
799
|
+
failureCode: ledgerErrors[0]?.code ?? null,
|
|
800
|
+
});
|
|
801
|
+
appendLedgerGaps({
|
|
802
|
+
gaps,
|
|
803
|
+
risks,
|
|
804
|
+
ledgerSummary,
|
|
805
|
+
ledgerErrors,
|
|
806
|
+
taskKind: built.draft.taskKind,
|
|
807
|
+
});
|
|
808
|
+
// appendLedgerGaps 只消费 LEDGER_MISSING / CONTEXT_ONLY_UNREVIEWED 与摘要派生码
|
|
809
|
+
// (UNRESOLVED / CONFLICT / HIGH_RISK_UNCOVERED);reviewer 派生且未被消费的码
|
|
810
|
+
// (如 duplicate → SOURCE_FIDELITY_RECONCILIATION_FAILED)内联补 gap+risk,镜像
|
|
811
|
+
// catch 块 L917–L925 模式,保证 hasBlockingGaps(gaps) 对 frontend-implementation
|
|
812
|
+
// 触发、prepare/apply 真正 fail closed。追加前检查 gaps 中尚不存在同码同级别 gap
|
|
813
|
+
// (appendLedgerGaps 或 catch 块可能已产生),避免双条目。
|
|
814
|
+
const reviewerBlocking = built.draft.taskKind === "frontend-implementation";
|
|
815
|
+
const reviewerGapLevel = reviewerBlocking
|
|
816
|
+
? "blocking"
|
|
817
|
+
: "warning";
|
|
818
|
+
for (const code of reviewRejectedCodes) {
|
|
819
|
+
if (gaps.some((gap) => gap.code === code &&
|
|
820
|
+
gap.level === reviewerGapLevel)) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const message = ledgerErrors.find((error) => error.code === code)
|
|
824
|
+
?.message ??
|
|
825
|
+
`independent reviewer rejected (${code})`;
|
|
826
|
+
gaps.push({
|
|
827
|
+
code,
|
|
828
|
+
level: reviewerGapLevel,
|
|
829
|
+
message,
|
|
830
|
+
});
|
|
831
|
+
risks.push({
|
|
832
|
+
code,
|
|
833
|
+
level: reviewerBlocking ? "blocking" : "elevated",
|
|
834
|
+
message,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
841
|
+
gaps.push({
|
|
842
|
+
code: "SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
843
|
+
level: "blocking",
|
|
844
|
+
message: `requirement ledger reconciliation failed: ${message}(不回退摘要)`,
|
|
845
|
+
});
|
|
846
|
+
risks.push({
|
|
847
|
+
code: "SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
848
|
+
level: "blocking",
|
|
849
|
+
message: `requirement ledger reconciliation failed: ${message}`,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const ledgerResult = {
|
|
854
|
+
...(ledger ? { ledger } : {}),
|
|
855
|
+
...(ledgerSummary ? { ledgerSummary } : {}),
|
|
856
|
+
};
|
|
604
857
|
if (managedProjectionError) {
|
|
605
858
|
gaps.push({
|
|
606
859
|
code: "MANAGED_PROJECTION_INCOMPLETE",
|
|
@@ -742,6 +995,7 @@ export async function prepareTaskSource(input) {
|
|
|
742
995
|
next,
|
|
743
996
|
contractStatus: state.effectiveStatus,
|
|
744
997
|
warnings,
|
|
998
|
+
...ledgerResult,
|
|
745
999
|
...(blocking
|
|
746
1000
|
? {
|
|
747
1001
|
error: {
|
|
@@ -776,6 +1030,7 @@ export async function prepareTaskSource(input) {
|
|
|
776
1030
|
next: [`loop-agent task advance ${taskId} --json`],
|
|
777
1031
|
contractStatus: state.effectiveStatus,
|
|
778
1032
|
warnings,
|
|
1033
|
+
...ledgerResult,
|
|
779
1034
|
error: {
|
|
780
1035
|
code: "TRANSACTION_INCOMPLETE",
|
|
781
1036
|
message: "unfinished transaction; re-run task advance (auto recover-contract)",
|
|
@@ -802,6 +1057,7 @@ export async function prepareTaskSource(input) {
|
|
|
802
1057
|
next,
|
|
803
1058
|
contractStatus: state.effectiveStatus,
|
|
804
1059
|
warnings,
|
|
1060
|
+
...ledgerResult,
|
|
805
1061
|
error: {
|
|
806
1062
|
code: blockingCodes.includes("DIRTY_SOURCE")
|
|
807
1063
|
? "DIRTY_SOURCE"
|
|
@@ -832,6 +1088,7 @@ export async function prepareTaskSource(input) {
|
|
|
832
1088
|
risks,
|
|
833
1089
|
projected,
|
|
834
1090
|
next: [`loop-agent task status ${taskId} --details --json`],
|
|
1091
|
+
...ledgerResult,
|
|
835
1092
|
error: {
|
|
836
1093
|
code: "OBSERVED_HASH_CONFLICT",
|
|
837
1094
|
message: "unable to determine expectedObservedHash",
|
|
@@ -852,6 +1109,7 @@ export async function prepareTaskSource(input) {
|
|
|
852
1109
|
requestId,
|
|
853
1110
|
requestPayloadSha256,
|
|
854
1111
|
payloadSha256Trusted: true,
|
|
1112
|
+
...(ledger ? { requirementLedger: ledger } : {}),
|
|
855
1113
|
});
|
|
856
1114
|
return {
|
|
857
1115
|
ok: true,
|
|
@@ -874,6 +1132,7 @@ export async function prepareTaskSource(input) {
|
|
|
874
1132
|
}),
|
|
875
1133
|
contractStatus: "managed",
|
|
876
1134
|
warnings,
|
|
1135
|
+
...ledgerResult,
|
|
877
1136
|
};
|
|
878
1137
|
}
|
|
879
1138
|
catch (error) {
|
|
@@ -894,6 +1153,7 @@ export async function prepareTaskSource(input) {
|
|
|
894
1153
|
`loop-agent task status ${taskId} --json`,
|
|
895
1154
|
`loop-agent task status ${taskId} --details --json`,
|
|
896
1155
|
],
|
|
1156
|
+
...ledgerResult,
|
|
897
1157
|
error: {
|
|
898
1158
|
code: error.code,
|
|
899
1159
|
message: error.message,
|
|
@@ -911,6 +1171,7 @@ export async function prepareTaskSource(input) {
|
|
|
911
1171
|
risks,
|
|
912
1172
|
projected,
|
|
913
1173
|
next: [],
|
|
1174
|
+
...ledgerResult,
|
|
914
1175
|
error: {
|
|
915
1176
|
code: "INTERNAL_ERROR",
|
|
916
1177
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -411,6 +411,24 @@ export async function buildRepoHint(repoRoot) {
|
|
|
411
411
|
}
|
|
412
412
|
return lines.join("\n");
|
|
413
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* 在 maxChars 附近回退到最近的标题边界再截断(AC-005 / AC-FIDELITY-REPAIR-005
|
|
416
|
+
* 回归点移除):避免把可执行项/列表项从中间切开吞掉。仅用于模型提示词预算;
|
|
417
|
+
* ledger fragment inventory 始终消费完整原文,不经过此裁剪。
|
|
418
|
+
*/
|
|
419
|
+
function clipAtSectionBoundary(content, maxChars) {
|
|
420
|
+
if (content.length <= maxChars)
|
|
421
|
+
return content;
|
|
422
|
+
const prefix = content.slice(0, maxChars);
|
|
423
|
+
const headingRe = /^(?:#{1,6})\s+.+$/gm;
|
|
424
|
+
let boundary = prefix.length;
|
|
425
|
+
let match;
|
|
426
|
+
while ((match = headingRe.exec(prefix)) !== null) {
|
|
427
|
+
if (match.index >= maxChars * 0.4)
|
|
428
|
+
boundary = match.index;
|
|
429
|
+
}
|
|
430
|
+
return `${content.slice(0, boundary)}\n\n[truncated at section boundary]`;
|
|
431
|
+
}
|
|
414
432
|
async function buildPrompt(input) {
|
|
415
433
|
const bodies = input.documents
|
|
416
434
|
.map((doc, index) => {
|
|
@@ -418,9 +436,7 @@ async function buildPrompt(input) {
|
|
|
418
436
|
const metaLine = meta.recognized
|
|
419
437
|
? `artifact_type=${meta.artifactType} status=${meta.requirementStatus ?? meta.analysisStatus ?? "n/a"}`
|
|
420
438
|
: "artifact_type=unknown";
|
|
421
|
-
const clipped = doc.content
|
|
422
|
-
? `${doc.content.slice(0, 24_000)}\n\n[truncated]`
|
|
423
|
-
: doc.content;
|
|
439
|
+
const clipped = clipAtSectionBoundary(doc.content, 24_000);
|
|
424
440
|
return [
|
|
425
441
|
`### Document ${index + 1}`,
|
|
426
442
|
`role: ${doc.role}`,
|