@tea-agent/loop-agent 0.44.0-next.7 → 0.44.0-next.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/CHANGELOG.md +100 -0
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +25 -41
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +519 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +198 -118
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +76 -11
- package/dist/executors/shell-executor.js +88 -12
- package/dist/infrastructure/console/operation-store.js +2 -0
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/task/config-types.js +22 -9
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -8
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/operator-actions.js +0 -1
- package/dist/worker/console/operator-user-error.js +1 -1
- package/dist/worker/console/prd-intake-bridge.js +5 -15
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DTuku_-7.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CQbCsSqb.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C-xUntTR.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DhNlwGAl.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B7j3nx8o.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BCrcxEh2.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CsaePDb3.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H--SRQc1rN.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C5BFY2Po.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Caso6uN7.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-AY2ZSAx2.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DiMqZQ_8.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-gpvvriOp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUdyoJhI.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bp1xW3T4.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-D-JIcOIT.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C-NKFflx.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CSOcvOAA.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BQSBltMy.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0LVC45G.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cypg3nqy.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BBDU88XJ.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BZusLqSb.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Cfolotcr.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CjfyUy0m.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DJdz7txE.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CQqgPO6y.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BOpGfS2m.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-C6UCu0qx.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/{index-DWKejNVJ.js → index-CTMZsDmh.js} +98 -98
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWeaVEUB.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C8gmlBbk.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DF3bm8Bp.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D2fG2Vxi.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BGKgMc2T.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DF6mMPJ9.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-AMhePheC.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DN1Cl0W0.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Cx9ngUeN.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BcYPTQJb.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BqHUZWnH.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CmqWZ0-B.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P--4YetH1y.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DGD4VMfD.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-QVw9q7rh.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CuemV9pe.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-C7oKzZKs.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-C2Of60nT.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DHtpQHyL.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-COi9OhSy.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DPHQfRrt.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -59
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +11 -10
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -6
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +2 -1
- package/dist/workflows/dag/init-hybrid.js +108 -37
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -67
- package/dist/workflows/dag/runner.js +58 -12
- package/dist/workflows/dag/types.js +15 -8
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/templates/backend-test-dag.json +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +1 -1
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/dist/worker/console/static/assets/channel-5_os7t_x.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/index-CUT440A8.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BdlGdIJw.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DjjbHbde.js +0 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityBoundarySchema } from "../config-types.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { backendTestConfigSchema, frontendOpenspecConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
|
|
3
4
|
import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
|
|
@@ -84,14 +85,7 @@ export const taskContractDraftSchema = z
|
|
|
84
85
|
})
|
|
85
86
|
.strict())
|
|
86
87
|
.optional(),
|
|
87
|
-
capabilityBoundary:
|
|
88
|
-
.object({
|
|
89
|
-
allowed: z.array(z.string().min(1)).optional(),
|
|
90
|
-
forbidden: z.array(z.string().min(1)).optional(),
|
|
91
|
-
requiredClauses: z.array(z.string().min(1)).optional(),
|
|
92
|
-
})
|
|
93
|
-
.strict()
|
|
94
|
-
.optional(),
|
|
88
|
+
capabilityBoundary: capabilityBoundarySchema.optional(),
|
|
95
89
|
interactionIds: z.array(z.string().min(1)).optional(),
|
|
96
90
|
interviewAssessmentRef: z
|
|
97
91
|
.object({
|
|
@@ -2,7 +2,7 @@ import { mkdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { executePiStep } from "../executors/pi-executor.js";
|
|
5
|
-
import { resolveDagPiModelConfig } from "../executors/dag-pi-
|
|
5
|
+
import { resolveDagPiModelConfig } from "../executors/dag-pi/model-config.js";
|
|
6
6
|
import { writeJsonAtomic } from "../infrastructure/harness/atomic-write.js";
|
|
7
7
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
8
8
|
import { loadHarnessManifest } from "../governance/harness.js";
|
|
@@ -261,8 +261,37 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
261
261
|
let current = { key: "other", lines: [] };
|
|
262
262
|
sections.push(current);
|
|
263
263
|
let acCounter = options?.acCounterStart ?? 1;
|
|
264
|
+
// Ids the source states explicitly anywhere in the document (as a bullet
|
|
265
|
+
// `- AC-002: …`, a `#### AC-002 …` heading, or a list/heading under a
|
|
266
|
+
// requirement section). Auto-numbering must not hand one of these ids to an
|
|
267
|
+
// unrelated unlabeled bullet: doing so silently REPLACES the authored
|
|
268
|
+
// criterion with the auto-assigned one, so the requirement the PRD actually
|
|
269
|
+
// stated disappears while the import still looks complete. That is how a
|
|
270
|
+
// `## 需求` list of AC-001..AC-004 became three acceptance bullets taken
|
|
271
|
+
// from `## 验收`, with AC-001/AC-002 overwritten and only AC-003/AC-004
|
|
272
|
+
// reported as dropped.
|
|
273
|
+
const explicitlyClaimedIds = new Set();
|
|
274
|
+
for (const line of lines) {
|
|
275
|
+
const heading = isAtxHeading(line);
|
|
276
|
+
if (heading) {
|
|
277
|
+
const embedded = parseEmbeddedAcHeading(heading.text);
|
|
278
|
+
if (embedded)
|
|
279
|
+
explicitlyClaimedIds.add(embedded.id);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const item = parseListItem(line);
|
|
283
|
+
if (!item)
|
|
284
|
+
continue;
|
|
285
|
+
const labeled = /^(AC[-_]?\d+)\s*[::]/iu.exec(item);
|
|
286
|
+
if (labeled)
|
|
287
|
+
explicitlyClaimedIds.add(normalizeAcceptanceId(labeled[1]));
|
|
288
|
+
}
|
|
264
289
|
const nextId = () => {
|
|
265
|
-
|
|
290
|
+
let id = `AC-${String(acCounter).padStart(3, "0")}`;
|
|
291
|
+
while (explicitlyClaimedIds.has(id)) {
|
|
292
|
+
acCounter += 1;
|
|
293
|
+
id = `AC-${String(acCounter).padStart(3, "0")}`;
|
|
294
|
+
}
|
|
266
295
|
acCounter += 1;
|
|
267
296
|
return id;
|
|
268
297
|
};
|
|
@@ -277,7 +306,15 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
277
306
|
key: "acceptance",
|
|
278
307
|
lines: [`- ${embeddedAc.id}: ${embeddedAc.text}`],
|
|
279
308
|
});
|
|
280
|
-
|
|
309
|
+
// The body under an embedded AC heading must not become an extra
|
|
310
|
+
// criterion, but it must not be thrown away either: the derived
|
|
311
|
+
// projection and the frontend shape classifier read requirement
|
|
312
|
+
// prose, so a capability signal stated only in the body (for
|
|
313
|
+
// example a required remote /api/… fetch) would otherwise be
|
|
314
|
+
// invisible at generation time and only resurface at
|
|
315
|
+
// post-Contract as `reshape-required`. Attribute the body to its
|
|
316
|
+
// heading instead.
|
|
317
|
+
current = { key: "acBody", lines: [], acBodyId: embeddedAc.id };
|
|
281
318
|
sections.push(current);
|
|
282
319
|
continue;
|
|
283
320
|
}
|
|
@@ -295,6 +332,9 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
295
332
|
const invariants = [];
|
|
296
333
|
const openQuestions = [];
|
|
297
334
|
const assumptions = [];
|
|
335
|
+
// Requirement ids that a requirement list loses when its first item is
|
|
336
|
+
// lifted into the Objective (see the objective branch below).
|
|
337
|
+
const droppedRequirementIds = [];
|
|
298
338
|
for (const section of sections) {
|
|
299
339
|
if (section.key === "objective" && !objective) {
|
|
300
340
|
const paragraph = firstParagraph(section.lines);
|
|
@@ -324,6 +364,14 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
324
364
|
acceptanceCriteria.push(ac);
|
|
325
365
|
}
|
|
326
366
|
}
|
|
367
|
+
else if (section.key === "acBody") {
|
|
368
|
+
const body = section.lines.join("\n").trim();
|
|
369
|
+
if (!body || !section.acBodyId)
|
|
370
|
+
continue;
|
|
371
|
+
const owner = acceptanceCriteria.find((criterion) => criterion.id === section.acBodyId);
|
|
372
|
+
if (owner)
|
|
373
|
+
owner.body = owner.body ? `${owner.body}\n${body}` : body;
|
|
374
|
+
}
|
|
327
375
|
else if (section.key === "constraints") {
|
|
328
376
|
invariants.push(...collectListItems(section.lines));
|
|
329
377
|
}
|
|
@@ -343,6 +391,30 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
343
391
|
paragraph.length > OBJECTIVE_MAX_CHARS
|
|
344
392
|
? paragraph.slice(0, OBJECTIVE_MAX_CHARS)
|
|
345
393
|
: paragraph;
|
|
394
|
+
// Requirement prose living in an unrecognized section (a bare
|
|
395
|
+
// `## 需求` is not a known heading alias) is scraped for the objective
|
|
396
|
+
// and then abandoned: the remaining AC-shaped bullets never reach
|
|
397
|
+
// `acceptanceCriteria`. A later `requirementOwnership` declaration
|
|
398
|
+
// referencing one of those ids then fails at plan finalize with
|
|
399
|
+
// `unknown requirement id`. Surface the ids so callers can warn.
|
|
400
|
+
//
|
|
401
|
+
// A bullet counts as dropped when it carries an AC id that no
|
|
402
|
+
// criterion claims. That includes the bullet scraped into the
|
|
403
|
+
// objective: it survives only as free objective prose, not as a
|
|
404
|
+
// requirement any plan fact can reference.
|
|
405
|
+
const accepted = new Set(acceptanceCriteria.map((criterion) => criterion.id));
|
|
406
|
+
for (const line of lines) {
|
|
407
|
+
const item = parseListItem(line);
|
|
408
|
+
if (!item)
|
|
409
|
+
continue;
|
|
410
|
+
const demoted = /^(AC[-_]?\d+)\s*[::]/iu.exec(item);
|
|
411
|
+
if (!demoted)
|
|
412
|
+
continue;
|
|
413
|
+
const id = normalizeAcceptanceId(demoted[1]);
|
|
414
|
+
if (accepted.has(id))
|
|
415
|
+
continue;
|
|
416
|
+
droppedRequirementIds.push(id);
|
|
417
|
+
}
|
|
346
418
|
}
|
|
347
419
|
}
|
|
348
420
|
const acceptance = dedupeAcceptance(acceptanceCriteria);
|
|
@@ -357,6 +429,7 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
357
429
|
assumptions: dedupeText(assumptions),
|
|
358
430
|
sourceLabels: options?.sourceLabel ? [options.sourceLabel] : [],
|
|
359
431
|
acceptanceConflicts: acceptance.conflicts,
|
|
432
|
+
droppedRequirementIds: [...new Set(droppedRequirementIds)],
|
|
360
433
|
};
|
|
361
434
|
}
|
|
362
435
|
function dedupeText(items) {
|
|
@@ -373,6 +446,7 @@ function dedupeText(items) {
|
|
|
373
446
|
}
|
|
374
447
|
function dedupeAcceptance(items) {
|
|
375
448
|
const byId = new Map();
|
|
449
|
+
const bodyById = new Map();
|
|
376
450
|
const order = [];
|
|
377
451
|
const conflicts = new Map();
|
|
378
452
|
for (const item of items) {
|
|
@@ -380,12 +454,25 @@ function dedupeAcceptance(items) {
|
|
|
380
454
|
const text = item.text.trim();
|
|
381
455
|
if (!id || !text)
|
|
382
456
|
continue;
|
|
457
|
+
const body = item.body?.trim();
|
|
383
458
|
const existing = byId.get(id);
|
|
384
459
|
if (existing === undefined) {
|
|
385
460
|
byId.set(id, text);
|
|
461
|
+
if (body)
|
|
462
|
+
bodyById.set(id, body);
|
|
386
463
|
order.push(id);
|
|
387
464
|
continue;
|
|
388
465
|
}
|
|
466
|
+
// A duplicate id keeps the first text (or records a conflict) but still
|
|
467
|
+
// accumulates the criterion body, which carries the requirement prose the
|
|
468
|
+
// derived projection and shape classifier depend on.
|
|
469
|
+
if (body) {
|
|
470
|
+
const prior = bodyById.get(id);
|
|
471
|
+
if (!prior)
|
|
472
|
+
bodyById.set(id, body);
|
|
473
|
+
else if (!prior.includes(body))
|
|
474
|
+
bodyById.set(id, `${prior}\n${body}`);
|
|
475
|
+
}
|
|
389
476
|
if (existing !== text) {
|
|
390
477
|
const texts = conflicts.get(id) ?? new Set([existing]);
|
|
391
478
|
texts.add(text);
|
|
@@ -393,12 +480,16 @@ function dedupeAcceptance(items) {
|
|
|
393
480
|
}
|
|
394
481
|
}
|
|
395
482
|
return {
|
|
396
|
-
items: order.map((id) =>
|
|
483
|
+
items: order.map((id) => {
|
|
484
|
+
const body = bodyById.get(id);
|
|
485
|
+
return body ? { id, text: byId.get(id), body } : { id, text: byId.get(id) };
|
|
486
|
+
}),
|
|
397
487
|
conflicts: [...conflicts].map(([id, texts]) => ({ id, texts: [...texts] })),
|
|
398
488
|
};
|
|
399
489
|
}
|
|
400
490
|
export function mergeAcceptanceCriteria(parts) {
|
|
401
491
|
const byId = new Map();
|
|
492
|
+
const bodyById = new Map();
|
|
402
493
|
const order = [];
|
|
403
494
|
for (const group of parts) {
|
|
404
495
|
for (const item of group) {
|
|
@@ -411,16 +502,29 @@ export function mergeAcceptanceCriteria(parts) {
|
|
|
411
502
|
order.push(id);
|
|
412
503
|
}
|
|
413
504
|
byId.get(id)?.add(text);
|
|
505
|
+
// Bodies accumulate across documents; a conflict on the statement
|
|
506
|
+
// text must not discard the prose that carries the requirement's
|
|
507
|
+
// substance.
|
|
508
|
+
const body = item.body?.trim();
|
|
509
|
+
if (body) {
|
|
510
|
+
const prior = bodyById.get(id);
|
|
511
|
+
if (!prior)
|
|
512
|
+
bodyById.set(id, body);
|
|
513
|
+
else if (!prior.includes(body))
|
|
514
|
+
bodyById.set(id, `${prior}\n${body}`);
|
|
515
|
+
}
|
|
414
516
|
}
|
|
415
517
|
}
|
|
416
518
|
const conflicts = [];
|
|
417
519
|
const items = [];
|
|
418
520
|
for (const id of order) {
|
|
419
521
|
const texts = [...(byId.get(id) ?? [])];
|
|
522
|
+
const body = bodyById.get(id);
|
|
420
523
|
if (texts.length === 1) {
|
|
421
524
|
const [onlyText] = texts;
|
|
422
|
-
if (onlyText !== undefined)
|
|
423
|
-
items.push({ id, text: onlyText });
|
|
525
|
+
if (onlyText !== undefined) {
|
|
526
|
+
items.push(body ? { id, text: onlyText, body } : { id, text: onlyText });
|
|
527
|
+
}
|
|
424
528
|
}
|
|
425
529
|
else if (texts.length > 1) {
|
|
426
530
|
conflicts.push({ id, texts });
|
|
@@ -536,6 +640,21 @@ export function parseVerifyTimeoutFlag(raw) {
|
|
|
536
640
|
}
|
|
537
641
|
return { label, timeoutMs };
|
|
538
642
|
}
|
|
643
|
+
/** Parse `<label>:<static|behavior>` for `--verify-lane`. Declaring the lane
|
|
644
|
+
* overrides text classification; it is the operator's escape hatch for a
|
|
645
|
+
* command that provably cannot emit a test report. */
|
|
646
|
+
export function parseVerifyLaneFlag(raw) {
|
|
647
|
+
const idx = raw.indexOf(":");
|
|
648
|
+
if (idx <= 0 || idx === raw.length - 1) {
|
|
649
|
+
throw new Error(`invalid --verify-lane value "${raw}"; expected <label>:<static|behavior>, e.g. --verify-lane "repo check:static"`);
|
|
650
|
+
}
|
|
651
|
+
const label = raw.slice(0, idx).trim();
|
|
652
|
+
const mode = raw.slice(idx + 1).trim().toLowerCase();
|
|
653
|
+
if (mode !== "static" && mode !== "behavior") {
|
|
654
|
+
throw new Error(`invalid --verify-lane value "${raw}"; lane must be "static" or "behavior"`);
|
|
655
|
+
}
|
|
656
|
+
return { label, mode };
|
|
657
|
+
}
|
|
539
658
|
export function parseAcceptanceFlag(raw) {
|
|
540
659
|
const idx = raw.indexOf(":");
|
|
541
660
|
if (idx <= 0 || idx === raw.length - 1) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { observeSourceExecution } from "./source-execution.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { lstat, readFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
@@ -463,6 +464,9 @@ function emptyObservedHashPlaceholder() {
|
|
|
463
464
|
* No LLM. Production md writes only via applyTaskContract projection.
|
|
464
465
|
*/
|
|
465
466
|
export async function prepareTaskSource(input) {
|
|
467
|
+
return observeSourceExecution({ repoRoot: input.repoRoot, taskId: input.taskId, taskKind: input.flags.taskKind, phase: "prepare", dryRun: input.mode === "dry-run", inputIdentity: { intent: input.intent, flags: input.flags } }, () => prepareTaskSourceInExecution(input));
|
|
468
|
+
}
|
|
469
|
+
async function prepareTaskSourceInExecution(input) {
|
|
466
470
|
const { repoRoot, taskId } = input;
|
|
467
471
|
const contractPaths = getTaskContractPaths(repoRoot, taskId);
|
|
468
472
|
const state = await observeTaskContractState(repoRoot, taskId);
|
|
@@ -798,13 +802,14 @@ export async function prepareTaskSource(input) {
|
|
|
798
802
|
let ledgerSummary;
|
|
799
803
|
const hasAuthoritativeImportedDocs = Boolean(hasImportedPrd &&
|
|
800
804
|
sourceIntegrity?.parseableDocuments.some((doc) => doc.role === "requirement" || doc.role === "acceptance"));
|
|
801
|
-
// Frontend tasks created from text
|
|
805
|
+
// Frontend tasks created from text or structured drafts need the same source-fidelity
|
|
802
806
|
// contract as imported-PRD tasks. In that case the projected requirement
|
|
803
807
|
// document is the canonical, task-owned source that the DAG can bind to;
|
|
804
808
|
// without it init-hybrid would silently emit a v1 binding and the first
|
|
805
809
|
// frontend-contract node would fail before making a model call.
|
|
806
810
|
if (built.draft.taskKind === "frontend-implementation" &&
|
|
807
811
|
(hasAuthoritativeImportedDocs ||
|
|
812
|
+
input.intent.kind === "draft" ||
|
|
808
813
|
(input.intent.kind === "facts" && Boolean(input.intent.text)))) {
|
|
809
814
|
try {
|
|
810
815
|
const authoritativeDocs = sourceIntegrity?.parseableDocuments.filter((doc) => (doc.role === "requirement" || doc.role === "acceptance") &&
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { observeSourceExecution } from "./source-execution.js";
|
|
2
|
+
import { openTaskSourceProviderBudget } from "./source-provider-budget.js";
|
|
3
|
+
import { sha256OfCanonicalJson } from "../contract/hash.js";
|
|
1
4
|
import { randomUUID } from "node:crypto";
|
|
2
5
|
import { observeFrontendSession } from "../../workflows/dag/frontend-session-budget.js";
|
|
3
6
|
/**
|
|
@@ -13,12 +16,12 @@ import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
|
13
16
|
import path from "node:path";
|
|
14
17
|
import { z } from "zod";
|
|
15
18
|
import { executePiStep } from "../../executors/pi-executor.js";
|
|
16
|
-
import { resolveDagPiModelConfig } from "../../executors/dag-pi-
|
|
19
|
+
import { resolveDagPiModelConfig } from "../../executors/dag-pi/model-config.js";
|
|
17
20
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
18
21
|
import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
|
|
19
22
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
20
23
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
21
|
-
import { getTaskPaths } from "../runtime.js";
|
|
24
|
+
import { getTaskPaths, loadTaskConfig } from "../runtime.js";
|
|
22
25
|
import { detectProductArtifactMeta } from "./artifact-meta.js";
|
|
23
26
|
import { filterPlaceholderPaths } from "./placeholder-paths.js";
|
|
24
27
|
export const SEMANTIC_INTAKE_ARTIFACT_REL = "artifacts/intake/semantic-draft.json";
|
|
@@ -47,6 +50,7 @@ const engineeringRecommendationSchema = z
|
|
|
47
50
|
label: z.string().min(1),
|
|
48
51
|
command: z.string().min(1),
|
|
49
52
|
timeoutMs: z.number().int().positive().optional(),
|
|
53
|
+
mode: z.enum(["static", "behavior"]).optional(),
|
|
50
54
|
}))
|
|
51
55
|
.optional(),
|
|
52
56
|
})
|
|
@@ -521,6 +525,9 @@ async function writeAttemptMarker(repoRoot, taskId, payload) {
|
|
|
521
525
|
* per advance invocation). Writes artifacts under taskDir/artifacts/intake/.
|
|
522
526
|
*/
|
|
523
527
|
export async function runSemanticIntake(input) {
|
|
528
|
+
return observeSourceExecution({ repoRoot: input.repoRoot, taskId: input.taskId, taskKind: input.taskKind ?? input.flags.taskKind, phase: "semantic-intake", inputIdentity: { documents: input.documents, flags: input.flags, title: input.title } }, () => runSemanticIntakeInExecution(input));
|
|
529
|
+
}
|
|
530
|
+
async function runSemanticIntakeInExecution(input) {
|
|
524
531
|
const paths = getTaskPaths(input.repoRoot, input.taskId);
|
|
525
532
|
const intakeDir = path.join(paths.taskDir, "artifacts", "intake");
|
|
526
533
|
await mkdir(intakeDir, { recursive: true });
|
|
@@ -593,6 +600,11 @@ export async function runSemanticIntake(input) {
|
|
|
593
600
|
message: "semantic intake has no MED/HIGH/LOW or Pi-supported models to try",
|
|
594
601
|
};
|
|
595
602
|
}
|
|
603
|
+
const config = await loadTaskConfig(input.repoRoot, input.taskId);
|
|
604
|
+
const taskKind = input.taskKind ?? input.flags.taskKind ?? config.taskKind;
|
|
605
|
+
const reserveProviderRequest = taskKind === "frontend-implementation"
|
|
606
|
+
? await openTaskSourceProviderBudget({ repoRoot: input.repoRoot, taskId: input.taskId, maxProviderRequests: config.frontendRecovery?.maxProviderRequests ?? 1024 })
|
|
607
|
+
: undefined;
|
|
596
608
|
// Complete text is already inline; attaching the same documents duplicates it.
|
|
597
609
|
const prompt = await buildPrompt({
|
|
598
610
|
taskId: input.taskId,
|
|
@@ -606,10 +618,11 @@ export async function runSemanticIntake(input) {
|
|
|
606
618
|
for (const modelRef of queue) {
|
|
607
619
|
try {
|
|
608
620
|
const result = await observeFrontendSession({
|
|
609
|
-
phase: "semantic-intake", userMessage: "Structure the imported requirement documents into the required JSON only.", prompt, model: modelRef, taskId: input.taskId,
|
|
621
|
+
phase: "semantic-intake", sourceDigest: sha256OfCanonicalJson(usableDocs), userMessage: "Structure the imported requirement documents into the required JSON only.", prompt, model: modelRef, taskId: input.taskId,
|
|
610
622
|
artifactPath: path.join(getTaskPaths(input.repoRoot, input.taskId).taskDir, "artifacts", "session-budget", `${randomUUID()}.json`),
|
|
611
623
|
}, observer => execute({
|
|
612
624
|
onAttemptObservation: observer,
|
|
625
|
+
reserveProviderRequest,
|
|
613
626
|
attachedFiles: [],
|
|
614
627
|
modelConfig: resolveDagPiModelConfig(modelRef),
|
|
615
628
|
prompt,
|
|
@@ -642,6 +655,8 @@ export async function runSemanticIntake(input) {
|
|
|
642
655
|
}
|
|
643
656
|
lastFailure =
|
|
644
657
|
`${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000);
|
|
658
|
+
if (result.failureCategory === "budget_breach")
|
|
659
|
+
break;
|
|
645
660
|
if (result.stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory)) {
|
|
646
661
|
lastFailure = `SEMANTIC_INTAKE_CAPACITY_EXHAUSTED: refine the complete source into independently reviewable documents; ${lastFailure}`;
|
|
647
662
|
break;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { mkdir } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
7
|
+
import { observeTaskContractState } from "../contract/observe.js";
|
|
8
|
+
import { sha256OfCanonicalJson } from "../contract/hash.js";
|
|
9
|
+
import { getTaskPaths, loadTaskConfig } from "../runtime.js";
|
|
10
|
+
const context = new AsyncLocalStorage();
|
|
11
|
+
export function currentSourceExecution(taskId) {
|
|
12
|
+
const current = context.getStore();
|
|
13
|
+
return current && current.taskId === taskId ? current.executionId : undefined;
|
|
14
|
+
}
|
|
15
|
+
/** An invocation identity, independent of request/session identities and of
|
|
16
|
+
* input equality. Persist before requests; a crash leaves an incomplete record. */
|
|
17
|
+
export async function observeSourceExecution(input, execute) {
|
|
18
|
+
if (input.dryRun)
|
|
19
|
+
return execute();
|
|
20
|
+
let taskKind = input.taskKind;
|
|
21
|
+
if (!taskKind) {
|
|
22
|
+
try {
|
|
23
|
+
taskKind = (await loadTaskConfig(input.repoRoot, input.taskId)).taskKind;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return execute();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (taskKind !== "frontend-implementation")
|
|
30
|
+
return execute();
|
|
31
|
+
// Preserve a missing-task result without creating a phantom artifact tree.
|
|
32
|
+
try {
|
|
33
|
+
await loadTaskConfig(input.repoRoot, input.taskId);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return execute();
|
|
37
|
+
}
|
|
38
|
+
const contract = async () => {
|
|
39
|
+
const state = await observeTaskContractState(input.repoRoot, input.taskId);
|
|
40
|
+
return { revision: state.ref?.revision ?? null, canonicalHash: state.ref?.canonicalHash ?? null, observedCanonicalHash: state.observedCanonicalHash ?? null };
|
|
41
|
+
};
|
|
42
|
+
const executionId = randomUUID();
|
|
43
|
+
const directory = path.join(getTaskPaths(input.repoRoot, input.taskId).taskDir, "artifacts/source-executions");
|
|
44
|
+
await mkdir(directory, { recursive: true });
|
|
45
|
+
const file = path.join(directory, `${executionId}.json`);
|
|
46
|
+
const initial = { schemaVersion: 1, executionId, taskId: input.taskId, phase: input.phase,
|
|
47
|
+
inputSha256: sha256OfCanonicalJson(input.inputIdentity), beforeContract: await contract(),
|
|
48
|
+
startedAt: new Date().toISOString(), completion: "incomplete" };
|
|
49
|
+
await writeJsonAtomic(file, initial, { repoRoot: input.repoRoot });
|
|
50
|
+
let outcome = "threw";
|
|
51
|
+
try {
|
|
52
|
+
const result = await context.run({ executionId, taskId: input.taskId }, execute);
|
|
53
|
+
outcome = result !== null && typeof result === "object" && "ok" in result ? result.ok === true ? "succeeded" : "failed" : "returned";
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await writeJsonAtomic(file, { ...initial, completion: "complete", outcome, finishedAt: new Date().toISOString(), afterContract: await contract() }, { repoRoot: input.repoRoot });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const contractSnapshotSchema = z.object({ revision: z.number().int().nonnegative().nullable(), canonicalHash: z.string().regex(/^[a-f0-9]{64}$/).nullable(), observedCanonicalHash: z.string().regex(/^[a-f0-9]{64}$/).nullable() }).strict();
|
|
61
|
+
export const completedSourceExecutionSchema = z.object({
|
|
62
|
+
schemaVersion: z.literal(1), executionId: z.string().uuid(), taskId: z.string().min(1), phase: z.enum(["prepare", "semantic-intake"]),
|
|
63
|
+
inputSha256: z.string().regex(/^[a-f0-9]{64}$/), beforeContract: contractSnapshotSchema, afterContract: contractSnapshotSchema,
|
|
64
|
+
startedAt: z.string().datetime(), finishedAt: z.string().datetime(), completion: z.literal("complete"), outcome: z.enum(["succeeded", "failed", "threw", "returned"]),
|
|
65
|
+
}).strict().refine(value => Date.parse(value.finishedAt) >= Date.parse(value.startedAt));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { openTaskSourceProviderBudget } from "./source-provider-budget.js";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
import { observeFrontendSession } from "../../workflows/dag/frontend-session-budget.js";
|
|
4
5
|
/**
|
|
@@ -11,7 +12,7 @@ import { observeFrontendSession } from "../../workflows/dag/frontend-session-bud
|
|
|
11
12
|
* - review:context-only / duplicate / conflict 的独立 reviewer(与 reconciler
|
|
12
13
|
* 是不同调用阶段,最多一次有界修订,LEDGER_REVIEW_MAX_REVISIONS=1)。
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
+
* 模型队列仅使用 MED→HIGH→LOW harness 显式档位,不追加 Pi SDK 全局目录,
|
|
15
16
|
* 单次尝试沿用有界超时(SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS),工具面仅只读
|
|
16
17
|
* read/grep/find/ls(无写工具)。所有模型输出经 validateOutput + zod 严格校验:
|
|
17
18
|
* 无效 JSON/schema、越界 canonical ID、排除类 disposition 缺理由一律抛
|
|
@@ -21,7 +22,7 @@ import { observeFrontendSession } from "../../workflows/dag/frontend-session-bud
|
|
|
21
22
|
*/
|
|
22
23
|
import { z } from "zod";
|
|
23
24
|
import { executePiStep } from "../../executors/pi-executor.js";
|
|
24
|
-
import { resolveDagPiModelConfig } from "../../executors/dag-pi-
|
|
25
|
+
import { resolveDagPiModelConfig } from "../../executors/dag-pi/model-config.js";
|
|
25
26
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
26
27
|
import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
|
|
27
28
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -72,12 +73,12 @@ const reviewResponseSchema = z.object({
|
|
|
72
73
|
.nullish(),
|
|
73
74
|
});
|
|
74
75
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* Only configured MED→HIGH→LOW routes authorize a provider attempt. Global
|
|
77
|
+
* catalog availability or credentials do not authorize another model. An empty
|
|
78
|
+
* configured queue fails closed.
|
|
78
79
|
*/
|
|
79
80
|
export function buildSourceFidelityModelQueue(input) {
|
|
80
|
-
return buildSemanticIntakeModelQueue(input);
|
|
81
|
+
return buildSemanticIntakeModelQueue({ harness: input.harness });
|
|
81
82
|
}
|
|
82
83
|
async function buildProductionModelQueue(repoRoot, listAvailableModels) {
|
|
83
84
|
const manifest = await loadHarnessManifest(repoRoot);
|
|
@@ -248,13 +249,16 @@ export async function runProductionLedgerReconciliation(request, options) {
|
|
|
248
249
|
const execute = options.executePi ?? executePiStep;
|
|
249
250
|
const timeoutMs = options.timeoutMs ?? SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS;
|
|
250
251
|
let lastFailure = "";
|
|
252
|
+
let attemptedModels = 0;
|
|
251
253
|
for (const modelRef of queue) {
|
|
254
|
+
attemptedModels++;
|
|
252
255
|
try {
|
|
253
256
|
const result = await observeFrontendSession({
|
|
254
257
|
phase: "source-reconciliation", userMessage: "Reconcile the source fragment into the required JSON only.", sourceDigest: createHash("sha256").update(JSON.stringify(request)).digest("hex"), scopeIds: [request.fragment.id], prompt, model: modelRef, taskId: options.taskId, sink: options.observationSink,
|
|
255
258
|
artifactPath: options.taskId ? path.join(getTaskPaths(options.repoRoot, options.taskId).taskDir, "artifacts", "session-budget", `${randomUUID()}.json`) : undefined,
|
|
256
259
|
}, observer => execute({
|
|
257
260
|
onAttemptObservation: observer,
|
|
261
|
+
reserveProviderRequest: options.reserveProviderRequest,
|
|
258
262
|
attachedFiles: [],
|
|
259
263
|
modelConfig: resolveDagPiModelConfig(modelRef),
|
|
260
264
|
prompt,
|
|
@@ -277,6 +281,8 @@ export async function runProductionLedgerReconciliation(request, options) {
|
|
|
277
281
|
if (!result.ok) {
|
|
278
282
|
lastFailure =
|
|
279
283
|
`${result.failureCategory}: ${(result.stderr || result.assistantText).slice(0, 2000)}`;
|
|
284
|
+
if (result.failureCategory === "budget_breach")
|
|
285
|
+
break;
|
|
280
286
|
if (result.stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory)) {
|
|
281
287
|
lastFailure = `SOURCE_FIDELITY_CAPACITY_EXHAUSTED: refine this complete source fragment without losing conditions; ${lastFailure}`;
|
|
282
288
|
break;
|
|
@@ -297,7 +303,7 @@ export async function runProductionLedgerReconciliation(request, options) {
|
|
|
297
303
|
`unknown: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2000);
|
|
298
304
|
}
|
|
299
305
|
}
|
|
300
|
-
throw new LedgerReconciliationError(`source fidelity reconciliation pi failed after ${
|
|
306
|
+
throw new LedgerReconciliationError(`source fidelity reconciliation pi failed after ${attemptedModels} model(s): ${lastFailure || "no attempt produced valid output"}`);
|
|
301
307
|
}
|
|
302
308
|
/**
|
|
303
309
|
* 执行一次生产独立 review:模型队列串行尝试(默认真实 executePiStep),
|
|
@@ -315,13 +321,16 @@ export async function runProductionLedgerReview(request, options) {
|
|
|
315
321
|
const execute = options.executePi ?? executePiStep;
|
|
316
322
|
const timeoutMs = options.timeoutMs ?? SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS;
|
|
317
323
|
let lastFailure = "";
|
|
324
|
+
let attemptedModels = 0;
|
|
318
325
|
for (const modelRef of queue) {
|
|
326
|
+
attemptedModels++;
|
|
319
327
|
try {
|
|
320
328
|
const result = await observeFrontendSession({
|
|
321
329
|
phase: "source-review", userMessage: "Review the fragment disposition into the required JSON only.", sourceDigest: createHash("sha256").update(JSON.stringify(request)).digest("hex"), scopeIds: [request.fragment.id], prompt, model: modelRef, taskId: options.taskId, sink: options.observationSink,
|
|
322
330
|
artifactPath: options.taskId ? path.join(getTaskPaths(options.repoRoot, options.taskId).taskDir, "artifacts", "session-budget", `${randomUUID()}.json`) : undefined,
|
|
323
331
|
}, observer => execute({
|
|
324
332
|
onAttemptObservation: observer,
|
|
333
|
+
reserveProviderRequest: options.reserveProviderRequest,
|
|
325
334
|
attachedFiles: [],
|
|
326
335
|
modelConfig: resolveDagPiModelConfig(modelRef),
|
|
327
336
|
prompt,
|
|
@@ -344,6 +353,8 @@ export async function runProductionLedgerReview(request, options) {
|
|
|
344
353
|
if (!result.ok) {
|
|
345
354
|
lastFailure =
|
|
346
355
|
`${result.failureCategory}: ${(result.stderr || result.assistantText).slice(0, 2000)}`;
|
|
356
|
+
if (result.failureCategory === "budget_breach")
|
|
357
|
+
break;
|
|
347
358
|
if (result.stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory)) {
|
|
348
359
|
lastFailure = `SOURCE_FIDELITY_CAPACITY_EXHAUSTED: refine this complete source fragment without losing conditions; ${lastFailure}`;
|
|
349
360
|
break;
|
|
@@ -363,7 +374,7 @@ export async function runProductionLedgerReview(request, options) {
|
|
|
363
374
|
`unknown: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2000);
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
|
-
throw new LedgerReviewError(`source fidelity review pi failed after ${
|
|
377
|
+
throw new LedgerReviewError(`source fidelity review pi failed after ${attemptedModels} model(s): ${lastFailure || "no attempt produced valid output"}`);
|
|
367
378
|
}
|
|
368
379
|
/**
|
|
369
380
|
* 生产适配器工厂:返回 prepare 可注入的 reconcile executor(budgetChars=4000)
|
|
@@ -399,5 +410,7 @@ export async function resolveProductionSourceFidelityPi(input) {
|
|
|
399
410
|
}
|
|
400
411
|
if (effectiveTaskKind !== SOURCE_FIDELITY_TASK_KIND)
|
|
401
412
|
return undefined;
|
|
402
|
-
|
|
413
|
+
const config = await loadTaskConfig(input.repoRoot, input.taskId);
|
|
414
|
+
const reserveProviderRequest = await openTaskSourceProviderBudget({ ...input, maxProviderRequests: config.frontendRecovery?.maxProviderRequests ?? 1024 });
|
|
415
|
+
return createProductionSourceFidelityPi({ repoRoot: input.repoRoot, taskId: input.taskId, reserveProviderRequest });
|
|
403
416
|
}
|