@tea-agent/loop-agent 0.39.0-beta.8 → 0.39.0-beta.9
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 +17 -5
- 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/commands/init.js +26 -0
- package/dist/executors/dag-pi-executor.js +326 -8
- package/dist/executors/pi-read-budget-policy.js +166 -0
- package/dist/executors/shell-executor.js +26 -30
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +123 -4
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -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 +25 -5
- 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-bPer1waP.js → abnfDiagram-N423BO3Z-D1-Z-BBa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BLdkwucW.js → arc-q8qAYm5m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw4wo4I1.js → architectureDiagram-T3A2C74G-CAu4_lve.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DUUkc0gr.js → blockDiagram-VBNYF7ZC-D5GhQvHm.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BC3O0nob.js → c4Diagram-5PPSVZJV-ClQjeLkO.js} +1 -1
- package/dist/worker/console/static/assets/channel-ClAuhSUL.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Dd4g2nti.js → chunk-2GRJ4B5K-DLqWlFbo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-s2EDm7-n.js → chunk-2Q5K7J3B-DkRT_usL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-GqtHM2yQ.js → chunk-5RXB4S5H-Cqp7Chi3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BzRFrN8j.js → chunk-5VM5RSS4-CedEdFB9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bw9nFjCJ.js → chunk-6Q2QTUOP-BHM8vP5x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B4n-03Qm.js → chunk-GF5L2VYU-D7EFr377.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Dt75ZdBD.js → chunk-JWPE2WC7-DR38mTWV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-B_afMhHB.js → chunk-KBJHAD2P-BFv2LsR3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Bd8Rqqg9.js → chunk-RYQCIY6F-BAvpJS5z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-9boOMji4.js → chunk-XXDRQBXY-eKI9TO72.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-cBgeCpVU.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BtCycfCc.js → cose-bilkent-JH36ORCC-BMJXpgo4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-eTOts9L1.js → cynefin-VYW2F7L2-CKJ2ofZx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Cibd2mjg.js → cynefinDiagram-MW4NZA55-CsKnsazm.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CDjDZtDV.js → dagre-VZM6K2ZE-DVQ46vIw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Btexn-JJ.js → diagram-7IWD3JNH-D_RuonYS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cyl8sdpM.js → diagram-B4RE2ZJO-D2Mt0nMu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Ev-83E1t.js → diagram-LBJQPF4R-DjBDuye6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-41LqE0Mc.js → diagram-Q27KOJAE-8VZkqpE_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BC39T5R3.js → diagram-UB23O5K3-D0izYxia.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DzcNRuri.js → ebnfDiagram-BXEA7PRR-CncG2I95.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B5Frvvnz.js → erDiagram-JOGREHBK-DiBUUXR8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BapxROIM.js → flowDiagram-UKHOOZJN-Dtc0fArb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cd4d4wQ0.js → ganttDiagram-PKOTCBZU-BPNFP5OZ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CrL6buFW.js → gitGraphDiagram-DS77QQ5N-CsgpxEEd.js} +1 -1
- package/dist/worker/console/static/assets/{index-DBh_x_Ek.js → index-Bai8Z25q.js} +81 -81
- package/dist/worker/console/static/assets/index-CH0Tr5hY.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BrSiNxyd.js → infoDiagram-6WML65LV-kHGxkJUS.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-gvlvY-n9.js → ishikawaDiagram-WSZJBQD7-CbLe9wrr.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-YaPzRQ.js → journeyDiagram-NVQOT4AX-BOkwxAcP.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BsmDv9E_.js → kanban-definition-27J2QSJJ-BHYQ2rDu.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UWaoDN3h.js → linear-CHT4ujob.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BlvpGVG8.js → mermaid.core-yL8sfqnY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CUCIP5CP.js → mindmap-definition-FAOFIHXS-DZx1coV-.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CB1aQUAK.js → pegDiagram-VL7TDLO6-DStWRCI8.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BwehHxhf.js → pieDiagram-7S7Q4E2Y-DSKZkwzK.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CFz30APB.js → quadrantDiagram-CIZ2JOQS-CmoEGOuW.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwNmw1i6.js → railroadDiagram-AXF67PYL-BLqzhKyq.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-jg-tPXlI.js → requirementDiagram-LRYGKXZP-gbHeUabM.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BlyReDKl.js → sankeyDiagram-W5VNT64P-CaKkBCKO.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DBUzW1f3.js → sequenceDiagram-SI44F4Z6-PUbi3Upu.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BYcI2RAr.js → sizeCapture-X5ZJPWSS-dm7S9Qfe.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D7AiyJdo.js → stateDiagram-OKZ733FA-C7WSzv4p.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-sQDv5AP3.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BHGt7Kkv.js → swimlanes-SLNWSIFB-CBKCiQwd.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BXwsmcPs.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-OPjooWcX.js → timeline-definition-Z64GVDOM-CvnUlylk.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-ggsWDvrO.js → vennDiagram-T6HMQDX7-xM0StY1O.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DjNe-BjZ.js → wardleyDiagram-T6FBY63Y-CWtJMagy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Byp63Hei.js → xychartDiagram-ELKLHX3M-BO_-MeK8.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/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 +331 -35
- package/dist/worker/observe/static/views/dag-inspector.js +509 -78
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-design-policy.js +19 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +109 -49
- package/dist/workflows/dag/frontend-plan-render.js +27 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +45 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +1 -0
- package/dist/workflows/dag/frontend-review-context.js +102 -15
- package/dist/workflows/dag/frontend-shadow-dual-write.js +36 -2
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +154 -0
- package/dist/workflows/dag/frontend-writer-admission.js +12 -0
- package/dist/workflows/dag/init-hybrid.js +293 -65
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +111 -1
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +26 -3
- package/dist/workflows/dag/runner.js +38 -8
- package/dist/workflows/dag/types.js +51 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +4 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +2 -3
- package/skills/frontend-implementation/references/design-spec.md +4 -3
- package/skills/frontend-implementation/references/node-contracts.md +4 -3
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-AbFtVnFm.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +0 -1
- package/dist/worker/console/static/assets/index-DwOGauxU.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.js +0 -8
|
@@ -8,11 +8,15 @@ import { loadTaskConfig } from "../runtime.js";
|
|
|
8
8
|
import { resolveFrontendSpecRootAliases } from "../frontend-project-capability.js";
|
|
9
9
|
import { DEFAULT_FRONTEND_SPEC_ROOTS, extractDeclaredFrontendSpecRoots, } from "../../shared/openspec-spec.js";
|
|
10
10
|
import { buildPrepareDraft } from "./build-draft.js";
|
|
11
|
-
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
|
|
11
|
+
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, appendLedgerGaps, } from "./completeness.js";
|
|
12
12
|
import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
|
|
13
13
|
import { extractRequirementFactsFromMarkdown, fillMissingRequirementFacts, mergeRequirementFacts, } from "./parse-intent.js";
|
|
14
14
|
import { draftReferencesFromManifest, validateImportedPrdReferences, } from "./reference-integrity.js";
|
|
15
15
|
import { readSourceManifest } from "../source-references.js";
|
|
16
|
+
import { buildRequirementLedger, classifyLedgerGate, effectiveSourceResolutions, summarizeLedger, validateRequirementLedger, } from "./ledger.js";
|
|
17
|
+
import { defaultLedgerReviewer, LEDGER_REVIEW_DEFAULT_BUDGET_CHARS, ledgerReviewItemsFromRecords, reviewLedgerFragments, } from "./ledger-review.js";
|
|
18
|
+
import { buildFragmentInventory } from "./fragment-inventory.js";
|
|
19
|
+
import { sha256Utf8 } from "../contract/hash.js";
|
|
16
20
|
import { PREPARE_MAX_FILE_BYTES } from "./types.js";
|
|
17
21
|
async function fileNonEmpty(filePath) {
|
|
18
22
|
try {
|
|
@@ -69,6 +73,81 @@ function isProjectedSourceResolutionsSection(sectionBody) {
|
|
|
69
73
|
return true;
|
|
70
74
|
return lines.every((line) => entryRe.test(line));
|
|
71
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Typed parse of one canonical Source Resolutions line (project.ts L63-68):
|
|
78
|
+
* `- ${sourceRef}: ${conflict} → ${decision} (${reason}; decidedBy=${decidedBy})`.
|
|
79
|
+
* Tail-anchor strategy (AC-008 / AC-HARD-002, exact inverse of the projector's
|
|
80
|
+
* left-to-right concatenation): sourceRef ends at the first `: `, conflict ends
|
|
81
|
+
* at the first ` → `, and the reason group is reversed from the tail — the line
|
|
82
|
+
* must close with `)`, the REAL `; decidedBy=` separator is the LAST occurrence
|
|
83
|
+
* (reason may itself contain `; decidedBy=`), and the reason group opener is the
|
|
84
|
+
* LAST ` (` before that anchor (decision may itself contain ` (`). decision/reason
|
|
85
|
+
* round-trip byte-exact, including spaces and ASCII parentheses — no truncation
|
|
86
|
+
* at the first ` (`. Any missing field or shape mismatch returns null — the
|
|
87
|
+
* caller must reject the whole section (fail-closed), never partially accept.
|
|
88
|
+
* decidedBy is additionally enforced to be exactly "human" here
|
|
89
|
+
* (schema z.enum(['human'])); model/system/hand-edited values are treated as
|
|
90
|
+
* hand-edited/model-decided and block with MANAGED_PROJECTION_INCOMPLETE (AC-004b).
|
|
91
|
+
*/
|
|
92
|
+
function parseSourceResolutionLine(line) {
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (!trimmed.startsWith("- "))
|
|
95
|
+
return null;
|
|
96
|
+
const rest = trimmed.slice(2);
|
|
97
|
+
const sourceRefEnd = rest.indexOf(": ");
|
|
98
|
+
if (sourceRefEnd <= 0)
|
|
99
|
+
return null;
|
|
100
|
+
const sourceRef = rest.slice(0, sourceRefEnd);
|
|
101
|
+
const afterSourceRef = rest.slice(sourceRefEnd + 2);
|
|
102
|
+
const arrow = afterSourceRef.indexOf(" → ");
|
|
103
|
+
if (arrow <= 0)
|
|
104
|
+
return null;
|
|
105
|
+
const conflict = afterSourceRef.slice(0, arrow);
|
|
106
|
+
const afterArrow = afterSourceRef.slice(arrow + 3);
|
|
107
|
+
// 尾部锚点:行必须以 `)` 收尾,真实分隔符取最后一次 `; decidedBy=`
|
|
108
|
+
// (reason 可能内嵌 ASCII `; decidedBy=`,首个锚点会截断 reason)。
|
|
109
|
+
if (!afterArrow.endsWith(")"))
|
|
110
|
+
return null;
|
|
111
|
+
const decidedByAnchor = "; decidedBy=";
|
|
112
|
+
const decidedIdx = afterArrow.lastIndexOf(decidedByAnchor);
|
|
113
|
+
if (decidedIdx < 0)
|
|
114
|
+
return null;
|
|
115
|
+
const decidedBy = afterArrow.slice(decidedIdx + decidedByAnchor.length, -1);
|
|
116
|
+
if (decidedBy !== "human")
|
|
117
|
+
return null;
|
|
118
|
+
// reason 起始:锚点之前最后一个 ` (`(decision 可能内嵌 ASCII ` (`,首个 ` (`
|
|
119
|
+
// 会截断 decision)。decision/reason 逐字节保留。
|
|
120
|
+
const reasonRegion = afterArrow.slice(0, decidedIdx);
|
|
121
|
+
const paren = reasonRegion.lastIndexOf(" (");
|
|
122
|
+
if (paren <= 0)
|
|
123
|
+
return null;
|
|
124
|
+
const decision = reasonRegion.slice(0, paren);
|
|
125
|
+
const reason = reasonRegion.slice(paren + 2);
|
|
126
|
+
if (!conflict.trim() || !decision.trim() || !reason.trim())
|
|
127
|
+
return null;
|
|
128
|
+
return { sourceRef, conflict, decision, reason, decidedBy };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Parse the whole `## Source Resolutions` section body into typed resolutions.
|
|
132
|
+
* Returns null when any line fails — the section is rejected as a whole, never
|
|
133
|
+
* partially accepted or silently downgraded (AC-004). Empty sections yield an
|
|
134
|
+
* empty array (kept semantics: isProjectedSourceResolutionsSection accepts
|
|
135
|
+
* empty sections).
|
|
136
|
+
*/
|
|
137
|
+
function parseSourceResolutionsSection(sectionBody) {
|
|
138
|
+
const lines = sectionBody
|
|
139
|
+
.split(/\r?\n/)
|
|
140
|
+
.map((line) => line.trim())
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
const resolutions = [];
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const parsed = parseSourceResolutionLine(line);
|
|
145
|
+
if (!parsed)
|
|
146
|
+
return null;
|
|
147
|
+
resolutions.push(parsed);
|
|
148
|
+
}
|
|
149
|
+
return resolutions;
|
|
150
|
+
}
|
|
72
151
|
/** Section titles the read-back validator may inspect (static whitelist). */
|
|
73
152
|
const MANAGED_PROJECTION_SECTION_TITLES = [
|
|
74
153
|
"References",
|
|
@@ -137,6 +216,22 @@ async function loadManagedProjectionDraft(input) {
|
|
|
137
216
|
" --from-draft <reviewed-draft.json> --json",
|
|
138
217
|
};
|
|
139
218
|
}
|
|
219
|
+
// Typed read-back (2026-08-24 L1): the shape regex above accepts any
|
|
220
|
+
// decidedBy value; the typed parser is the projector's exact inverse and
|
|
221
|
+
// additionally enforces decidedBy === 'human' (schema z.enum(['human'])).
|
|
222
|
+
// A regex-valid but non-canonical line (decidedBy=model/system/hand-edited)
|
|
223
|
+
// or any free-form line fails the whole section — never partial accept.
|
|
224
|
+
const parsedSourceResolutions = sourceResolutionsBody !== null
|
|
225
|
+
? parseSourceResolutionsSection(sourceResolutionsBody)
|
|
226
|
+
: null;
|
|
227
|
+
if (sourceResolutionsBody !== null && parsedSourceResolutions === null) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
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 " +
|
|
231
|
+
input.taskId +
|
|
232
|
+
" --from-draft <reviewed-draft.json> --json",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
140
235
|
const requirementFacts = extractRequirementFactsFromMarkdown(requirementRaw);
|
|
141
236
|
const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
|
|
142
237
|
if (!requirementFacts.objective?.trim()) {
|
|
@@ -186,6 +281,9 @@ async function loadManagedProjectionDraft(input) {
|
|
|
186
281
|
...(requirementFacts.assumptions.length > 0
|
|
187
282
|
? { assumptions: requirementFacts.assumptions }
|
|
188
283
|
: {}),
|
|
284
|
+
...(parsedSourceResolutions && parsedSourceResolutions.length > 0
|
|
285
|
+
? { sourceResolutions: parsedSourceResolutions }
|
|
286
|
+
: {}),
|
|
189
287
|
hardConstraints: config.hardConstraints,
|
|
190
288
|
},
|
|
191
289
|
};
|
|
@@ -637,6 +735,161 @@ export async function prepareTaskSource(input) {
|
|
|
637
735
|
sourceFilesPresent,
|
|
638
736
|
forceOverwriteSource: input.forceOverwriteSource,
|
|
639
737
|
});
|
|
738
|
+
// -------------------------------------------------------------------
|
|
739
|
+
// Source fidelity ledger (20260824-frontend-source-fidelity-ledger-v1)
|
|
740
|
+
// 在 buildPrepareDraft/listPrepareGaps 与 applyTaskContract 之间编排
|
|
741
|
+
// fragment-inventory + ledger 构建。原始 requirement/acceptance 文档保持语义
|
|
742
|
+
// 权威;派生 需求.md 仅作导航视图。reconciliation 失败返回稳定错误并停止,
|
|
743
|
+
// 不回退摘要。
|
|
744
|
+
// -------------------------------------------------------------------
|
|
745
|
+
let ledger;
|
|
746
|
+
let ledgerSummary;
|
|
747
|
+
if (hasImportedPrd &&
|
|
748
|
+
sourceIntegrity &&
|
|
749
|
+
sourceIntegrity.parseableDocuments.some((doc) => doc.role === "requirement" || doc.role === "acceptance")) {
|
|
750
|
+
try {
|
|
751
|
+
const authoritativeDocs = sourceIntegrity.parseableDocuments.filter((doc) => (doc.role === "requirement" || doc.role === "acceptance") &&
|
|
752
|
+
typeof doc.content === "string" &&
|
|
753
|
+
doc.content.trim().length > 0);
|
|
754
|
+
const sourceDocuments = authoritativeDocs.map((doc) => {
|
|
755
|
+
// Canonical source identity 单一边界(AC-001 / AC-FIDELITY-REPAIR-001):
|
|
756
|
+
// ledger sourceDocuments[].path 与 DAG 侧 serializeDagTaskSourcePath /
|
|
757
|
+
// resolveDagTaskSourcePath 一致(repo-root relative POSIX),路径转换只
|
|
758
|
+
// 发生在该边界,不再二次转换进入 digest 计算。
|
|
759
|
+
const absolute = path.resolve(contractPaths.sourceDir, doc.materializedPath);
|
|
760
|
+
return {
|
|
761
|
+
path: path
|
|
762
|
+
.relative(repoRoot, absolute)
|
|
763
|
+
.split(path.sep)
|
|
764
|
+
.join("/"),
|
|
765
|
+
content: doc.content,
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
if (sourceDocuments.length > 0) {
|
|
769
|
+
const requirementProjection = projectRequirementMarkdown(built.draft);
|
|
770
|
+
ledger = await buildRequirementLedger({
|
|
771
|
+
taskId,
|
|
772
|
+
requirementSha256: sha256Utf8(requirementProjection),
|
|
773
|
+
sourceDocuments,
|
|
774
|
+
canonicalRequirements: built.draft.requirement.acceptanceCriteria,
|
|
775
|
+
sourceResolutions: (built.draft.sourceResolutions ?? []).map((res) => ({
|
|
776
|
+
sourceRef: res.sourceRef,
|
|
777
|
+
conflict: res.conflict,
|
|
778
|
+
decision: res.decision,
|
|
779
|
+
reason: res.reason,
|
|
780
|
+
decidedBy: res.decidedBy,
|
|
781
|
+
})),
|
|
782
|
+
...(input.sourceFidelity?.reconcile
|
|
783
|
+
? { reconcile: input.sourceFidelity.reconcile }
|
|
784
|
+
: {}),
|
|
785
|
+
});
|
|
786
|
+
const classification = classifyLedgerGate(ledger, {
|
|
787
|
+
expectedInputDigest: ledger.inputDigest,
|
|
788
|
+
});
|
|
789
|
+
const ledgerErrors = classification.ok ? [] : [
|
|
790
|
+
{ code: classification.code, message: classification.message },
|
|
791
|
+
];
|
|
792
|
+
if (!classification.ok &&
|
|
793
|
+
classification.code !== "SOURCE_FIDELITY_LEDGER_STALE") {
|
|
794
|
+
ledgerErrors.push(...validateRequirementLedger(ledger).filter((error) => error.code !== classification.code));
|
|
795
|
+
}
|
|
796
|
+
// 独立 reviewer(AC-007 / AC-FIDELITY-REPAIR-007):context-only /
|
|
797
|
+
// duplicate / conflict 进入独立审核阶段(与 reconciler 分离);低风险
|
|
798
|
+
// context-only 仅 reviewer pass 放行,高风险 context-only 始终阻断。
|
|
799
|
+
// review 条目携带按 budgetChars 有界截断的原始片段文本与精确 fragment id
|
|
800
|
+
// (AC-004 / AC-HARD-004);canonicalRequirements 采用 ledger 物化后的
|
|
801
|
+
// canonicalRequirements(含 REQ-SRC-*,不再仅取 draft acceptance)。
|
|
802
|
+
const review = await reviewLedgerFragments({
|
|
803
|
+
fragments: ledgerReviewItemsFromRecords(ledger.fragments, {
|
|
804
|
+
fragments: buildFragmentInventory({
|
|
805
|
+
documents: sourceDocuments,
|
|
806
|
+
canonicalRequirements: built.draft.requirement.acceptanceCriteria,
|
|
807
|
+
}).fragments,
|
|
808
|
+
budgetChars: LEDGER_REVIEW_DEFAULT_BUDGET_CHARS,
|
|
809
|
+
}),
|
|
810
|
+
canonicalRequirements: ledger.canonicalRequirements.map((requirement) => ({
|
|
811
|
+
id: requirement.id,
|
|
812
|
+
text: requirement.text,
|
|
813
|
+
})),
|
|
814
|
+
reviewer: input.sourceFidelity?.reviewer ?? defaultLedgerReviewer,
|
|
815
|
+
conflictIds: new Set(ledger.conflicts.map((entry) => entry.id)),
|
|
816
|
+
resolvedConflictIds: effectiveSourceResolutions(ledger),
|
|
817
|
+
});
|
|
818
|
+
// 独立 reviewer 拒绝 → 全码透传:低风险 context-only / 高风险 context-only /
|
|
819
|
+
// duplicate / conflict 均以精确稳定码进入 ledgerErrors;review.code===null
|
|
820
|
+
// 时防御性回退 SOURCE_FIDELITY_RECONCILIATION_FAILED(fail-closed)。message
|
|
821
|
+
// 取有界 review.reasons.join("; ")。追加前跳过与既有 ledgerErrors 中 code 与
|
|
822
|
+
// message 均完全一致的条目(不得按 code 单独去重:同码不同 message 必须保留)。
|
|
823
|
+
const reviewRejectedCodes = [];
|
|
824
|
+
if (!review.approved) {
|
|
825
|
+
const code = review.code ?? "SOURCE_FIDELITY_RECONCILIATION_FAILED";
|
|
826
|
+
const message = review.reasons.join("; ");
|
|
827
|
+
const alreadyPresent = ledgerErrors.some((error) => error.code === code && error.message === message);
|
|
828
|
+
if (!alreadyPresent) {
|
|
829
|
+
ledgerErrors.push({ code, message });
|
|
830
|
+
reviewRejectedCodes.push(code);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
ledgerSummary = summarizeLedger(ledger, {
|
|
834
|
+
gateStatus: ledgerErrors.length > 0 ? "fail" : "pass",
|
|
835
|
+
failureCode: ledgerErrors[0]?.code ?? null,
|
|
836
|
+
});
|
|
837
|
+
appendLedgerGaps({
|
|
838
|
+
gaps,
|
|
839
|
+
risks,
|
|
840
|
+
ledgerSummary,
|
|
841
|
+
ledgerErrors,
|
|
842
|
+
taskKind: built.draft.taskKind,
|
|
843
|
+
});
|
|
844
|
+
// appendLedgerGaps 只消费 LEDGER_MISSING / CONTEXT_ONLY_UNREVIEWED 与摘要派生码
|
|
845
|
+
// (UNRESOLVED / CONFLICT / HIGH_RISK_UNCOVERED);reviewer 派生且未被消费的码
|
|
846
|
+
// (如 duplicate → SOURCE_FIDELITY_RECONCILIATION_FAILED)内联补 gap+risk,镜像
|
|
847
|
+
// catch 块 L917–L925 模式,保证 hasBlockingGaps(gaps) 对 frontend-implementation
|
|
848
|
+
// 触发、prepare/apply 真正 fail closed。追加前检查 gaps 中尚不存在同码同级别 gap
|
|
849
|
+
// (appendLedgerGaps 或 catch 块可能已产生),避免双条目。
|
|
850
|
+
const reviewerBlocking = built.draft.taskKind === "frontend-implementation";
|
|
851
|
+
const reviewerGapLevel = reviewerBlocking
|
|
852
|
+
? "blocking"
|
|
853
|
+
: "warning";
|
|
854
|
+
for (const code of reviewRejectedCodes) {
|
|
855
|
+
if (gaps.some((gap) => gap.code === code &&
|
|
856
|
+
gap.level === reviewerGapLevel)) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const message = ledgerErrors.find((error) => error.code === code)
|
|
860
|
+
?.message ??
|
|
861
|
+
`independent reviewer rejected (${code})`;
|
|
862
|
+
gaps.push({
|
|
863
|
+
code,
|
|
864
|
+
level: reviewerGapLevel,
|
|
865
|
+
message,
|
|
866
|
+
});
|
|
867
|
+
risks.push({
|
|
868
|
+
code,
|
|
869
|
+
level: reviewerBlocking ? "blocking" : "elevated",
|
|
870
|
+
message,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
catch (error) {
|
|
876
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
877
|
+
gaps.push({
|
|
878
|
+
code: "SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
879
|
+
level: "blocking",
|
|
880
|
+
message: `requirement ledger reconciliation failed: ${message}(不回退摘要)`,
|
|
881
|
+
});
|
|
882
|
+
risks.push({
|
|
883
|
+
code: "SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
884
|
+
level: "blocking",
|
|
885
|
+
message: `requirement ledger reconciliation failed: ${message}`,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const ledgerResult = {
|
|
890
|
+
...(ledger ? { ledger } : {}),
|
|
891
|
+
...(ledgerSummary ? { ledgerSummary } : {}),
|
|
892
|
+
};
|
|
640
893
|
if (managedProjectionError) {
|
|
641
894
|
gaps.push({
|
|
642
895
|
code: "MANAGED_PROJECTION_INCOMPLETE",
|
|
@@ -778,6 +1031,7 @@ export async function prepareTaskSource(input) {
|
|
|
778
1031
|
next,
|
|
779
1032
|
contractStatus: state.effectiveStatus,
|
|
780
1033
|
warnings,
|
|
1034
|
+
...ledgerResult,
|
|
781
1035
|
...(blocking
|
|
782
1036
|
? {
|
|
783
1037
|
error: {
|
|
@@ -812,6 +1066,7 @@ export async function prepareTaskSource(input) {
|
|
|
812
1066
|
next: [`loop-agent task advance ${taskId} --json`],
|
|
813
1067
|
contractStatus: state.effectiveStatus,
|
|
814
1068
|
warnings,
|
|
1069
|
+
...ledgerResult,
|
|
815
1070
|
error: {
|
|
816
1071
|
code: "TRANSACTION_INCOMPLETE",
|
|
817
1072
|
message: "unfinished transaction; re-run task advance (auto recover-contract)",
|
|
@@ -838,6 +1093,7 @@ export async function prepareTaskSource(input) {
|
|
|
838
1093
|
next,
|
|
839
1094
|
contractStatus: state.effectiveStatus,
|
|
840
1095
|
warnings,
|
|
1096
|
+
...ledgerResult,
|
|
841
1097
|
error: {
|
|
842
1098
|
code: blockingCodes.includes("DIRTY_SOURCE")
|
|
843
1099
|
? "DIRTY_SOURCE"
|
|
@@ -868,6 +1124,7 @@ export async function prepareTaskSource(input) {
|
|
|
868
1124
|
risks,
|
|
869
1125
|
projected,
|
|
870
1126
|
next: [`loop-agent task status ${taskId} --details --json`],
|
|
1127
|
+
...ledgerResult,
|
|
871
1128
|
error: {
|
|
872
1129
|
code: "OBSERVED_HASH_CONFLICT",
|
|
873
1130
|
message: "unable to determine expectedObservedHash",
|
|
@@ -888,6 +1145,7 @@ export async function prepareTaskSource(input) {
|
|
|
888
1145
|
requestId,
|
|
889
1146
|
requestPayloadSha256,
|
|
890
1147
|
payloadSha256Trusted: true,
|
|
1148
|
+
...(ledger ? { requirementLedger: ledger } : {}),
|
|
891
1149
|
});
|
|
892
1150
|
return {
|
|
893
1151
|
ok: true,
|
|
@@ -910,6 +1168,7 @@ export async function prepareTaskSource(input) {
|
|
|
910
1168
|
}),
|
|
911
1169
|
contractStatus: "managed",
|
|
912
1170
|
warnings,
|
|
1171
|
+
...ledgerResult,
|
|
913
1172
|
};
|
|
914
1173
|
}
|
|
915
1174
|
catch (error) {
|
|
@@ -930,6 +1189,7 @@ export async function prepareTaskSource(input) {
|
|
|
930
1189
|
`loop-agent task status ${taskId} --json`,
|
|
931
1190
|
`loop-agent task status ${taskId} --details --json`,
|
|
932
1191
|
],
|
|
1192
|
+
...ledgerResult,
|
|
933
1193
|
error: {
|
|
934
1194
|
code: error.code,
|
|
935
1195
|
message: error.message,
|
|
@@ -947,6 +1207,7 @@ export async function prepareTaskSource(input) {
|
|
|
947
1207
|
risks,
|
|
948
1208
|
projected,
|
|
949
1209
|
next: [],
|
|
1210
|
+
...ledgerResult,
|
|
950
1211
|
error: {
|
|
951
1212
|
code: "INTERNAL_ERROR",
|
|
952
1213
|
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}`,
|
|
@@ -441,8 +457,12 @@ async function buildPrompt(input) {
|
|
|
441
457
|
"Do NOT invent product behavior that is not supported by the documents.",
|
|
442
458
|
"You MAY recommend an optional engineering block when the repo layout hint supports it:",
|
|
443
459
|
"- allowedPaths / forbiddenPaths as minimal precise globs (e.g. source/packages/**/*.test.ts)",
|
|
444
|
-
"- verifyCommands as real package scripts (e.g. pnpm -C source test)",
|
|
445
|
-
"- taskKind when clearly implied
|
|
460
|
+
"- verifyCommands as real package scripts (e.g. pnpm -C source test); when acceptance criteria name a specific test file to run (e.g. 定向 vitest(test/.../xxx.test.ts)), emit the corresponding targeted command as its own entry (e.g. npx vitest run test/.../xxx.test.ts) instead of only the full-suite script — targeted commands are what a frontend implementation plan maps to behavior verification targets",
|
|
461
|
+
"- taskKind when clearly implied, choosing from {standard / frontend-implementation / frontend-test / backend-test / knowledge-sync}:",
|
|
462
|
+
" - frontend-implementation: the deliverable is a frontend component/page/UI feature (even when it ships with its own tests)",
|
|
463
|
+
" - frontend-test: the deliverable is a browser/UI automation test suite for EXISTING functionality (no implementation work)",
|
|
464
|
+
" - backend-test: pytest/backend test suite; standard: general repo implementation",
|
|
465
|
+
" A task that IMPLEMENTS a component and adds tests for it is frontend-implementation, never frontend-test.",
|
|
446
466
|
"Prefer smallest correct write boundary; do not invent product paths that are not in the layout hint.",
|
|
447
467
|
"Do NOT rewrite or suggest editing the original PRD files.",
|
|
448
468
|
"Prefer explicit text; use provenance derived for rephrasing; use inferred only when clearly implied — if you must invent ACs, leave acceptanceCriteria empty so the system can stop for clarification.",
|