@tea-agent/loop-agent 0.39.0-beta.7 → 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 +23 -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/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-follow-up.js +138 -0
- 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/config-types.js +15 -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/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 +117 -50
- 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-review-findings.js +42 -10
- 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 +297 -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-feedback.js +31 -8
- 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 +34 -9
- package/dist/workflows/dag/runner.js +83 -16
- package/dist/workflows/dag/types.js +67 -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
|
@@ -7,4 +7,7 @@ export * from "./semantic-intake.js";
|
|
|
7
7
|
export * from "./reference-integrity.js";
|
|
8
8
|
export * from "./completeness.js";
|
|
9
9
|
export * from "./build-draft.js";
|
|
10
|
+
export * from "./ledger-reconciliation.js";
|
|
11
|
+
export * from "./ledger-review.js";
|
|
12
|
+
export * from "./source-fidelity-pi.js";
|
|
10
13
|
export * from "./prepare.js";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { normalizeFragmentText } from "./fragment-inventory.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// 顺序 reconciliation(AC-006 / AC-FIDELITY-REPAIR-006)
|
|
4
|
+
// 结构化确定性片段走代码映射(显式 AC ID / 文本精确匹配),绝不调用 executor;
|
|
5
|
+
// 仅叙述性/歧义 chunk 调用可注入 executor,按标题顺序串行、逐块有界处理
|
|
6
|
+
// (budgetChars 按 chunk 截断,绝不整文件裁 24K)。executor 超时/异常/无效
|
|
7
|
+
// 返回/identity 越界一律 fail closed(SOURCE_FIDELITY_RECONCILIATION_FAILED),
|
|
8
|
+
// 不回退 heuristics。
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const LEDGER_RECONCILIATION_DEFAULT_BUDGET_CHARS = 4000;
|
|
11
|
+
export const LEDGER_RECONCILIATION_FAILED_CODE = "SOURCE_FIDELITY_RECONCILIATION_FAILED";
|
|
12
|
+
/** reconciliation 失败(executor 错误/无效输出/identity 越界/重复失败)。 */
|
|
13
|
+
export class LedgerReconciliationError extends Error {
|
|
14
|
+
code = LEDGER_RECONCILIATION_FAILED_CODE;
|
|
15
|
+
constructor(message, options) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.name = "LedgerReconciliationError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const VALID_DISPOSITIONS = new Set([
|
|
21
|
+
"requirement",
|
|
22
|
+
"acceptance",
|
|
23
|
+
"constraint",
|
|
24
|
+
"non-goal",
|
|
25
|
+
"duplicate",
|
|
26
|
+
"context-only",
|
|
27
|
+
"unresolved",
|
|
28
|
+
]);
|
|
29
|
+
function isExclusionDisposition(disposition) {
|
|
30
|
+
return (disposition === "context-only" ||
|
|
31
|
+
disposition === "duplicate" ||
|
|
32
|
+
disposition === "unresolved");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 结构化确定性片段判定:显式 AC ID 存在于 canonical 集合,或文本与任一
|
|
36
|
+
* canonical requirement 规范化后精确一致。命中则走代码映射,不调 executor。
|
|
37
|
+
*/
|
|
38
|
+
export function isStructuredMappableFragment(item, canonicalById, canonicalTexts) {
|
|
39
|
+
if (item.canonicalRequirementIds.length > 0)
|
|
40
|
+
return true;
|
|
41
|
+
if (/^AC(?:-[A-Z0-9]+)+$/iu.test(item.id) && canonicalById.has(item.id))
|
|
42
|
+
return true;
|
|
43
|
+
const text = normalizeFragmentText(item.text);
|
|
44
|
+
return canonicalTexts.has(text);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 顺序 reconciliation:仅叙述性/歧义片段(无结构化映射)调用 executor,按输入
|
|
48
|
+
* 顺序(标题顺序)串行、逐块有界。返回更新后的片段与调用日志(fixture 断言顺序)。
|
|
49
|
+
*/
|
|
50
|
+
export async function reconcileLedgerFragments(input) {
|
|
51
|
+
const { canonicalRequirements, executor, budgetChars = LEDGER_RECONCILIATION_DEFAULT_BUDGET_CHARS, } = input;
|
|
52
|
+
const calls = input.calls ?? [];
|
|
53
|
+
const canonicalById = new Map(canonicalRequirements.map((req) => [req.id, req.text]));
|
|
54
|
+
const canonicalTexts = new Set(canonicalRequirements.map((req) => normalizeFragmentText(req.text)));
|
|
55
|
+
const canonicalIds = new Set(canonicalById.keys());
|
|
56
|
+
const reconciled = [];
|
|
57
|
+
for (const fragment of input.fragments) {
|
|
58
|
+
if (isStructuredMappableFragment(fragment, canonicalById, canonicalTexts)) {
|
|
59
|
+
reconciled.push(fragment);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const request = {
|
|
63
|
+
fragment: {
|
|
64
|
+
id: fragment.id,
|
|
65
|
+
path: fragment.path,
|
|
66
|
+
headingPath: fragment.headingPath,
|
|
67
|
+
lineRange: fragment.lineRange,
|
|
68
|
+
text: fragment.text.slice(0, budgetChars),
|
|
69
|
+
},
|
|
70
|
+
canonicalRequirements,
|
|
71
|
+
budgetChars,
|
|
72
|
+
};
|
|
73
|
+
let result;
|
|
74
|
+
try {
|
|
75
|
+
result = await executor(request);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw new LedgerReconciliationError(`reconciliation executor failed for fragment ${fragment.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
if (!result ||
|
|
81
|
+
typeof result !== "object" ||
|
|
82
|
+
!Array.isArray(result.mappedIds) ||
|
|
83
|
+
!VALID_DISPOSITIONS.has(result.disposition)) {
|
|
84
|
+
throw new LedgerReconciliationError(`reconciliation executor returned an invalid result for fragment ${fragment.id}`);
|
|
85
|
+
}
|
|
86
|
+
const outOfBounds = result.mappedIds.filter((id) => !canonicalIds.has(id));
|
|
87
|
+
if (outOfBounds.length > 0) {
|
|
88
|
+
throw new LedgerReconciliationError(`reconciliation executor returned out-of-bounds canonical ids for fragment ${fragment.id}: ${outOfBounds.join(", ")}`);
|
|
89
|
+
}
|
|
90
|
+
if (isExclusionDisposition(result.disposition) && !result.reason?.trim()) {
|
|
91
|
+
throw new LedgerReconciliationError(`reconciliation executor omitted the required exclusion reason for fragment ${fragment.id} (${result.disposition})`);
|
|
92
|
+
}
|
|
93
|
+
calls.push(fragment.id);
|
|
94
|
+
// 风险不降级(AC-003 / AC-HARD-003):风险保留为 max(原始 risk, disposition
|
|
95
|
+
// 派生 risk);context-only / duplicate 不再把高风险片段降为 low。
|
|
96
|
+
const derivedRisk = result.disposition === "context-only" ||
|
|
97
|
+
result.disposition === "duplicate"
|
|
98
|
+
? "low"
|
|
99
|
+
: "high";
|
|
100
|
+
const risk = fragment.risk === "high" || derivedRisk === "high"
|
|
101
|
+
? "high"
|
|
102
|
+
: "low";
|
|
103
|
+
reconciled.push({
|
|
104
|
+
...fragment,
|
|
105
|
+
disposition: result.disposition,
|
|
106
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
107
|
+
risk,
|
|
108
|
+
canonicalRequirementIds: result.mappedIds,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return { fragments: reconciled, calls };
|
|
112
|
+
}
|
|
113
|
+
/** 供 ledger.ts 接入:把 inventory records 与 raw fragments 合并为可 reconciliation 项。 */
|
|
114
|
+
export function mergeInventoryItems(input) {
|
|
115
|
+
const textById = new Map(input.fragments.map((fragment) => [fragment.id, fragment.text]));
|
|
116
|
+
return input.records.map((record) => ({
|
|
117
|
+
id: record.id,
|
|
118
|
+
path: record.path,
|
|
119
|
+
headingPath: record.headingPath,
|
|
120
|
+
lineRange: record.lineRange,
|
|
121
|
+
text: textById.get(record.id) ?? "",
|
|
122
|
+
disposition: record.disposition,
|
|
123
|
+
...(record.reason ? { reason: record.reason } : {}),
|
|
124
|
+
risk: record.risk,
|
|
125
|
+
canonicalRequirementIds: record.canonicalRequirementIds,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { mergeInventoryItems } from "./ledger-reconciliation.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// 独立 reviewer(AC-007 / AC-FIDELITY-REPAIR-007)
|
|
4
|
+
// context-only / duplicate / conflicts 由独立可注入 reviewer 审核,与 reconciler
|
|
5
|
+
// 是不同调用阶段(fixture 断言调用顺序,杜绝共享一次输出冒充独立审核)。
|
|
6
|
+
// 最多一次有界修订;review/revision 失败或第二次不通过 fail closed。低风险
|
|
7
|
+
// context-only 仅 reviewer pass 放行,高风险 context-only 始终阻断;duplicate
|
|
8
|
+
// 保留全部来源但审核失败(含修订后仍拒绝)以稳定码 fail closed。
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/** review 片段原文按 budget 有界截断(AC-004 / AC-HARD-004),禁止整文档入
|
|
11
|
+
* prompt;沿用 MAX_SOURCE_EXCERPT_CHARS 边界语义。 */
|
|
12
|
+
export const LEDGER_REVIEW_DEFAULT_BUDGET_CHARS = 2000;
|
|
13
|
+
export const LEDGER_REVIEW_MAX_REVISIONS = 1;
|
|
14
|
+
/** review 失败(reviewer 异常/第二次不通过)统一稳定错误码。 */
|
|
15
|
+
export class LedgerReviewError extends Error {
|
|
16
|
+
code = "SOURCE_FIDELITY_RECONCILIATION_FAILED";
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = "LedgerReviewError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 独立 reviewer 编排:仅 context-only(低风险)、duplicate 与 conflict 进入审核。
|
|
24
|
+
* - context-only high risk:始终阻断(不调 reviewer,fail closed)。
|
|
25
|
+
* - context-only low:仅 reviewer pass 放行;否则(一次修订后仍失败)阻断。
|
|
26
|
+
* - duplicate:进入审核并记录;duplicate 保留全部来源,失败不升级为阻断。
|
|
27
|
+
* - conflict:需 reviewer pass 或存在有效 human resolution,否则阻断。
|
|
28
|
+
* reviewer 异常即 fail closed,不回退。
|
|
29
|
+
*/
|
|
30
|
+
export async function reviewLedgerFragments(input) {
|
|
31
|
+
const { canonicalRequirements, reviewer, maxRevisions = LEDGER_REVIEW_MAX_REVISIONS, conflictIds = new Set(), resolvedConflictIds = new Set(), } = input;
|
|
32
|
+
const calls = input.calls ?? [];
|
|
33
|
+
const reasons = [];
|
|
34
|
+
const reviewed = [];
|
|
35
|
+
for (const fragment of input.fragments) {
|
|
36
|
+
let kind = null;
|
|
37
|
+
if (fragment.disposition === "context-only")
|
|
38
|
+
kind = "context-only";
|
|
39
|
+
else if (fragment.disposition === "duplicate")
|
|
40
|
+
kind = "duplicate";
|
|
41
|
+
else if (conflictIds.has(fragment.id))
|
|
42
|
+
kind = "conflict";
|
|
43
|
+
if (!kind)
|
|
44
|
+
continue;
|
|
45
|
+
// 高风险 context-only:始终阻断,不进入 reviewer。
|
|
46
|
+
if (kind === "context-only" && fragment.risk === "high") {
|
|
47
|
+
reviewed.push({
|
|
48
|
+
fragmentId: fragment.id,
|
|
49
|
+
kind,
|
|
50
|
+
approved: false,
|
|
51
|
+
revisionUsed: false,
|
|
52
|
+
});
|
|
53
|
+
reasons.push(`high-risk context-only fragment ${fragment.id} always blocks; reviewer cannot waive it`);
|
|
54
|
+
return {
|
|
55
|
+
approved: false,
|
|
56
|
+
code: "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
|
|
57
|
+
reasons,
|
|
58
|
+
reviewed,
|
|
59
|
+
calls,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const resolutionAvailable = kind === "conflict" && resolvedConflictIds.has(fragment.id);
|
|
63
|
+
// AC-002:已被绑定当前 inputDigest 的有效 human resolution 覆盖的 conflict
|
|
64
|
+
// (resolutionAvailable=true)直接 approved(revisionUsed=false),注入的
|
|
65
|
+
// reviewer(含模型 reviewer)零调用:calls 不出现该 fragment 条目,reviewed
|
|
66
|
+
// audit 保留该条目。其余 fragment(unresolved / context-only / duplicate /
|
|
67
|
+
// 未解决的 conflict)的 reviewer 调用、修订与审核行为完全不变。
|
|
68
|
+
if (resolutionAvailable) {
|
|
69
|
+
reviewed.push({
|
|
70
|
+
fragmentId: fragment.id,
|
|
71
|
+
kind,
|
|
72
|
+
approved: true,
|
|
73
|
+
revisionUsed: false,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let current = fragment;
|
|
78
|
+
let approved = false;
|
|
79
|
+
let revisionUsed = false;
|
|
80
|
+
let revisionsRemaining = maxRevisions;
|
|
81
|
+
for (;;) {
|
|
82
|
+
const request = {
|
|
83
|
+
kind,
|
|
84
|
+
fragment: current,
|
|
85
|
+
canonicalRequirements,
|
|
86
|
+
resolutionAvailable,
|
|
87
|
+
};
|
|
88
|
+
let response;
|
|
89
|
+
try {
|
|
90
|
+
response = await reviewer(request);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new LedgerReviewError(`independent reviewer failed for ${kind} fragment ${fragment.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
calls.push(fragment.id);
|
|
96
|
+
if (response?.approved === true) {
|
|
97
|
+
approved = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// 最多一次有界修订:仅当 reviewer 给出 revision 建议且还有配额时采纳。
|
|
101
|
+
if (revisionsRemaining > 0 && response?.revision) {
|
|
102
|
+
current = {
|
|
103
|
+
...current,
|
|
104
|
+
...(response.revision.disposition
|
|
105
|
+
? { disposition: response.revision.disposition }
|
|
106
|
+
: {}),
|
|
107
|
+
...(response.revision.reason
|
|
108
|
+
? { reason: response.revision.reason }
|
|
109
|
+
: {}),
|
|
110
|
+
};
|
|
111
|
+
revisionsRemaining -= 1;
|
|
112
|
+
revisionUsed = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
approved = false;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
reviewed.push({
|
|
119
|
+
fragmentId: fragment.id,
|
|
120
|
+
kind,
|
|
121
|
+
approved,
|
|
122
|
+
revisionUsed,
|
|
123
|
+
});
|
|
124
|
+
if (approved)
|
|
125
|
+
continue;
|
|
126
|
+
if (kind === "duplicate") {
|
|
127
|
+
// duplicate 保留全部来源,但审核失败(含 LEDGER_REVIEW_MAX_REVISIONS=1
|
|
128
|
+
// 单次有界修订后仍拒绝)即 fail closed(稳定码阻断,绝不静默放行未确认
|
|
129
|
+
// 的重复排除;ledger 保留全部 duplicate 来源记录不丢失)。
|
|
130
|
+
reasons.push(`duplicate fragment ${fragment.id} not approved by independent reviewer${revisionUsed ? " after one bounded revision" : ""}`);
|
|
131
|
+
return {
|
|
132
|
+
approved: false,
|
|
133
|
+
code: "SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
134
|
+
reasons,
|
|
135
|
+
reviewed,
|
|
136
|
+
calls,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
reasons.push(`${kind} fragment ${fragment.id} not approved by independent reviewer${revisionUsed ? " after one bounded revision" : ""}`);
|
|
140
|
+
return {
|
|
141
|
+
approved: false,
|
|
142
|
+
code: kind === "context-only"
|
|
143
|
+
? "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED"
|
|
144
|
+
: "SOURCE_FIDELITY_CONFLICT",
|
|
145
|
+
reasons,
|
|
146
|
+
reviewed,
|
|
147
|
+
calls,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { approved: true, code: null, reasons, reviewed, calls };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 仓库内确定性默认 reviewer(无模型依赖):context-only 需完整排除理由且低风险;
|
|
154
|
+
* duplicate 需完整理由;conflict 需有效 human resolution。prepare 在未注入外部
|
|
155
|
+
* reviewer 时使用该实现,保证 frontend task 的 ledger 评审语义 fail closed。
|
|
156
|
+
*/
|
|
157
|
+
export const defaultLedgerReviewer = async (request) => {
|
|
158
|
+
if (request.kind === "context-only") {
|
|
159
|
+
const complete = Boolean(request.fragment.reason?.trim());
|
|
160
|
+
return {
|
|
161
|
+
approved: complete && request.fragment.risk === "low",
|
|
162
|
+
reason: complete
|
|
163
|
+
? "context-only 排除理由完整且低风险"
|
|
164
|
+
: "context-only fragment 缺少完整排除理由",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (request.kind === "duplicate") {
|
|
168
|
+
const complete = Boolean(request.fragment.reason?.trim());
|
|
169
|
+
return {
|
|
170
|
+
approved: complete,
|
|
171
|
+
reason: complete
|
|
172
|
+
? "duplicate 排除理由完整(保留全部来源)"
|
|
173
|
+
: "duplicate fragment 缺少完整排除理由",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
approved: request.resolutionAvailable,
|
|
178
|
+
reason: request.resolutionAvailable
|
|
179
|
+
? "conflict 已被绑定当前 inputDigest 的有效 human resolution 覆盖"
|
|
180
|
+
: "conflict 无有效 human resolution 覆盖,保持阻断",
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
/** 供 prepare.ts 接入:从 ledger fragments 中挑选需审核的条目,复用
|
|
184
|
+
* mergeInventoryItems 的 id→原文映射,按 budgetChars 有界截断后传入 reviewer
|
|
185
|
+
* (AC-004 / AC-HARD-004):请求携带非空有界原文与精确 fragment id,完整文档
|
|
186
|
+
* 绝不进入下游 prompt;原文不可得时退化为 reason / id 保证非空。 */
|
|
187
|
+
export function ledgerReviewItemsFromRecords(records, input) {
|
|
188
|
+
const budgetChars = input?.budgetChars ?? LEDGER_REVIEW_DEFAULT_BUDGET_CHARS;
|
|
189
|
+
const merged = input?.fragments
|
|
190
|
+
? mergeInventoryItems({ records, fragments: input.fragments })
|
|
191
|
+
: records.map((record) => ({
|
|
192
|
+
id: record.id,
|
|
193
|
+
path: "",
|
|
194
|
+
headingPath: record.headingPath,
|
|
195
|
+
lineRange: record.lineRange,
|
|
196
|
+
text: "",
|
|
197
|
+
disposition: record.disposition,
|
|
198
|
+
...(record.reason ? { reason: record.reason } : {}),
|
|
199
|
+
risk: record.risk,
|
|
200
|
+
canonicalRequirementIds: record.canonicalRequirementIds,
|
|
201
|
+
}));
|
|
202
|
+
return merged.map((item) => {
|
|
203
|
+
const bounded = item.text.slice(0, budgetChars);
|
|
204
|
+
const text = bounded.trim() || item.reason?.trim() || item.id;
|
|
205
|
+
return {
|
|
206
|
+
id: item.id,
|
|
207
|
+
text,
|
|
208
|
+
disposition: item.disposition,
|
|
209
|
+
...(item.reason ? { reason: item.reason } : {}),
|
|
210
|
+
risk: item.risk,
|
|
211
|
+
canonicalRequirementIds: item.canonicalRequirementIds,
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
}
|