loopforge 3.2.1 → 3.7.0
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/README.md +74 -71
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +6 -38
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +71 -6
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +176 -29
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -26
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-facts.d.ts +17 -0
- package/dist/cognitive-facts.d.ts.map +1 -0
- package/dist/cognitive-facts.js +99 -0
- package/dist/cognitive-facts.js.map +1 -0
- package/dist/cognitive-governance.d.ts +0 -1
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +1 -4
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +100 -0
- package/dist/committed-round.d.ts.map +1 -0
- package/dist/committed-round.js +349 -0
- package/dist/committed-round.js.map +1 -0
- package/dist/enforcement-gate.d.ts +4 -2
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +385 -357
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +16 -4
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +65 -16
- package/dist/engine.js.map +1 -1
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +15 -39
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-provider.d.ts +35 -17
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +105 -108
- package/dist/evidence-provider.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +21 -10
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +232 -276
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-projection.d.ts +5 -25
- package/dist/loop-projection.d.ts.map +1 -1
- package/dist/loop-projection.js +18 -134
- package/dist/loop-projection.js.map +1 -1
- package/dist/loop-store.d.ts +16 -18
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +21 -63
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +13 -10
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +95 -68
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +17 -5
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +177 -54
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +161 -53
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +0 -7
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +15 -55
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +22 -31
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +24 -42
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +0 -3
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +124 -191
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/protocol.d.ts +61 -46
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -18
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +21 -22
- package/dist/replay.js.map +1 -1
- package/dist/round-contract.d.ts +64 -0
- package/dist/round-contract.d.ts.map +1 -0
- package/dist/round-contract.js +80 -0
- package/dist/round-contract.js.map +1 -0
- package/dist/round-coordinator.d.ts +5 -3
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +34 -29
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +0 -4
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +1 -11
- package/dist/round-driver.js.map +1 -1
- package/dist/round-transaction.d.ts +0 -1
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +0 -1
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +29 -34
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +112 -148
- package/dist/self-eval.js.map +1 -1
- package/dist/token-utils.d.ts +7 -3
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +18 -3
- package/dist/token-utils.js.map +1 -1
- package/dist/verification-gate.d.ts +72 -21
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +544 -157
- package/dist/verification-gate.js.map +1 -1
- package/dist/workspace.d.ts +29 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +61 -0
- package/dist/workspace.js.map +1 -0
- package/loop_policy.json +5 -17
- package/package.json +2 -2
- package/skills/perception/SKILL.md +137 -136
package/dist/prompt-assembler.js
CHANGED
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
* are never truncated; budgets are soft and overflow is recorded.
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { hashCanonicalState } from "./canonical-state.js";
|
|
8
|
+
import { buildRoadmap, hashCanonicalState, milestoneHeading, trustBarLine } from "./canonical-state.js";
|
|
9
9
|
import { deriveItemId, STABLE_ID_RE, jaccardSimilarity } from "./token-utils.js";
|
|
10
10
|
import { getPolicy } from "./policy.js";
|
|
11
11
|
export const PROMPT_ARTIFACT_SCHEMA_VERSION = 1;
|
|
12
|
-
export const BASE_PROMPT_VERSION = "2.0.0";
|
|
13
12
|
export const DEFAULT_PROMPT_BUDGETS = {
|
|
14
13
|
l0: 3000,
|
|
15
14
|
l1: 7000,
|
|
@@ -39,19 +38,40 @@ function bulletsWithIds(values, prefix) {
|
|
|
39
38
|
return `- [\`${id}\`] ${trimmed}`;
|
|
40
39
|
}).join("\n");
|
|
41
40
|
}
|
|
41
|
+
/** v3.3.1: Render an identity-HETEROGENEOUS list — the merged active
|
|
42
|
+
* constraint set holds hard/plan/criteria/discovered texts together, so no
|
|
43
|
+
* single prefix fits. Each item's authoritative ID comes from constraint
|
|
44
|
+
* metadata (criteria texts carry cr-XXXXXXXX, everything else
|
|
45
|
+
* c-XXXXXXXX), keeping the active list in the same identity namespace as
|
|
46
|
+
* the item's own section — one text never renders under two prefixes in
|
|
47
|
+
* one prompt. Falls back to a c- derivation only when metadata is missing
|
|
48
|
+
* (defensive: metadata covers every active text by construction). */
|
|
49
|
+
function bulletsWithIdentities(values, metadata) {
|
|
50
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
51
|
+
if (!idEnabled)
|
|
52
|
+
return bullets(values);
|
|
53
|
+
const idByText = new Map(metadata.map((meta) => [meta.text, meta.id]));
|
|
54
|
+
return values.map((v) => {
|
|
55
|
+
const trimmed = v.trim();
|
|
56
|
+
if (STABLE_ID_RE.test(trimmed)) {
|
|
57
|
+
return `- [\`${trimmed}\`] ${trimmed}`;
|
|
58
|
+
}
|
|
59
|
+
const id = idByText.get(trimmed) ?? `c-${deriveItemId(trimmed)}`;
|
|
60
|
+
return `- [\`${id}\`] ${trimmed}`;
|
|
61
|
+
}).join("\n");
|
|
62
|
+
}
|
|
42
63
|
/** v3.2: Short imperative fix/action per verification check, appended to the
|
|
43
64
|
* L1/L2 Verification Gate lines. Deliberately shorter than the rejection-
|
|
44
65
|
* prompt wording in enforcement-gate.ts; keys are the plain check-name
|
|
45
66
|
* strings (NOT imported from verification-gate.ts — it already imports
|
|
46
67
|
* loop-compiler, which imports this module, so importing back would cycle).
|
|
47
|
-
* Full coverage of
|
|
68
|
+
* Full coverage of every CHECK_* constant is enforced by a test that
|
|
69
|
+
* iterates the verification-gate module's CHECK_ exports (no hardcoded
|
|
70
|
+
* list — a new constant can never silently lack an action entry). */
|
|
48
71
|
const VERIFICATION_ACTIONS = {
|
|
49
|
-
progress_regression: "Correct progress_estimate or explain the change in output_summary.",
|
|
50
|
-
empty_change_with_passing: "Report the actual files_changed, or set success=false with the real state.",
|
|
51
72
|
success_with_remaining_criteria: "Complete the remaining criteria before claiming success, or set success=false and report what remains.",
|
|
52
|
-
success_without_verified_evidence: "Back the success claim with machine-verifiable evidence: run tests or a required verification command and report the actual output.",
|
|
73
|
+
success_without_verified_evidence: "Back the success claim with machine-verifiable evidence: run tests or a required verification command and report the actual output. If you genuinely performed no work, provide execution_evidence and declare no_change_reason.",
|
|
53
74
|
outcome_success_contradiction: "Reconcile the declared outcome with the success flag and resubmit an honest evaluation.",
|
|
54
|
-
success_claim_conflict: "Align the success claim with your own evaluation data before resubmitting.",
|
|
55
75
|
blocked_without_blocker: "State the concrete blocker, or drop the blocked outcome.",
|
|
56
76
|
retroactive_claim_bad_round: "Correct the referenced round number, or withdraw the retroactive claim.",
|
|
57
77
|
retroactive_claim_unverified: "Provide evidence for the retroactive claim, or withdraw it.",
|
|
@@ -65,6 +85,17 @@ const VERIFICATION_ACTIONS = {
|
|
|
65
85
|
subgoal_drift: "Reconcile the sub-goal list with your reported work before resubmitting.",
|
|
66
86
|
backtrack_workspace_not_restored: "Restore the skipped-round files and the expected git HEAD, then resubmit.",
|
|
67
87
|
criteria_claims_unverified: "Provide evidence for each claimed success criterion.",
|
|
88
|
+
// v3.3 — Round Contract checks (and the three v3.3 checks that predated
|
|
89
|
+
// this table entry and fell back to the generic action).
|
|
90
|
+
verification_entrypoint_modified: "Re-run the verification command on the changed entrypoint, or revert the entrypoint change and re-run.",
|
|
91
|
+
test_files_modified: "Re-run the verification command after the test-file changes and report the fresh output.",
|
|
92
|
+
round_underspecified: "Declare this round's contract with a non-empty done_when list — what must be true when the round is done?",
|
|
93
|
+
round_unverifiable: "Name only configured, enabled evidence.commands in verification_plan, or drop done_when items that cannot be machine-verified.",
|
|
94
|
+
round_scope_drift: "Revert the out-of-scope file changes, or extend the contract scope to cover them and explain why in drift_clarification.",
|
|
95
|
+
premature_boundary: "Run the contract's verification_plan commands and report real output before claiming done_when met; or set success=false and list the items in success_criteria_remaining (no_change_reason does not apply to contract claims).",
|
|
96
|
+
// v3.5 — contract completion machine-backing + premature-replacement warn
|
|
97
|
+
contract_completion_unverified: "Completion under the ACTIVE Round Contract requires its verification_plan commands to pass this round — fix the underlying failure so they pass and resubmit. Completion claims are not accepted without machine verification (no_change_reason does not apply).",
|
|
98
|
+
contract_premature: "The ACTIVE contract is still open — restate it unchanged to continue it; a different contract is ignored until the active one is completed or blocked.",
|
|
68
99
|
};
|
|
69
100
|
/** Fallback for unmapped check names (forward-compat with future checks). */
|
|
70
101
|
const VERIFICATION_ACTION_FALLBACK = "Re-examine the flagged claim and correct your evaluation before resubmitting.";
|
|
@@ -250,7 +281,7 @@ function l1Sections(state, baseline, emphasized) {
|
|
|
250
281
|
if (renderFull) {
|
|
251
282
|
sections.push({
|
|
252
283
|
id: "active_constraints",
|
|
253
|
-
text: section("Active Constraints / Success Criteria",
|
|
284
|
+
text: section("Active Constraints / Success Criteria", bulletsWithIdentities(active, state.constraintMetadata)),
|
|
254
285
|
mandatory: false,
|
|
255
286
|
});
|
|
256
287
|
}
|
|
@@ -366,6 +397,15 @@ function l1Sections(state, baseline, emphasized) {
|
|
|
366
397
|
mandatory: false,
|
|
367
398
|
});
|
|
368
399
|
}
|
|
400
|
+
// v3.3: Forward-looking roadmap (same buildRoadmap as state.md and L2).
|
|
401
|
+
const roadmapLines = buildRoadmap(state);
|
|
402
|
+
if (roadmapLines.length > 0) {
|
|
403
|
+
sections.push({
|
|
404
|
+
id: "roadmap",
|
|
405
|
+
text: section("Roadmap", roadmapLines.join("\n")),
|
|
406
|
+
mandatory: false,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
369
409
|
// v3.2: Recurring Issues carries the deterministic violation lessons (with
|
|
370
410
|
// counts and rounds) when available; falls back to the rolling window text.
|
|
371
411
|
if (state.recurringIssues.length > 0 || state.lessons.length > 0) {
|
|
@@ -416,12 +456,19 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
416
456
|
["failed_patterns", "Failed Patterns", state.failedPatterns, null],
|
|
417
457
|
];
|
|
418
458
|
for (const [id, title, values, idPrefix] of groups) {
|
|
419
|
-
if (values.length
|
|
420
|
-
|
|
459
|
+
if (values.length === 0)
|
|
460
|
+
continue;
|
|
461
|
+
// v3.3.1: the active set is identity-heterogeneous (hard/plan/criteria/
|
|
462
|
+
// discovered merged) — render through constraint metadata so criteria
|
|
463
|
+
// texts keep their cr- identity here too (same namespace as their own
|
|
464
|
+
// Success Criteria section). Every other group is homogeneous and keeps
|
|
465
|
+
// its single-prefix renderer.
|
|
466
|
+
const body = id === "active_constraints"
|
|
467
|
+
? bulletsWithIdentities(values, state.constraintMetadata)
|
|
468
|
+
: idPrefix
|
|
421
469
|
? bulletsWithIds(values, idPrefix)
|
|
422
470
|
: bullets(values);
|
|
423
|
-
|
|
424
|
-
}
|
|
471
|
+
sections.push({ id, text: section(title, body), mandatory: false });
|
|
425
472
|
}
|
|
426
473
|
// v3.2: Lessons learned — repeated violations / verification failures
|
|
427
474
|
// across the whole loop (not just the rolling window).
|
|
@@ -441,14 +488,22 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
441
488
|
mandatory: false,
|
|
442
489
|
});
|
|
443
490
|
}
|
|
491
|
+
// v3.3: Forward-looking roadmap — rendered right before the (backward-
|
|
492
|
+
// looking) Phase History so the agent sees where it is before where it's
|
|
493
|
+
// been. Same buildRoadmap as state.md and L1.
|
|
494
|
+
const roadmapLines = buildRoadmap(state);
|
|
495
|
+
if (roadmapLines.length > 0) {
|
|
496
|
+
sections.push({
|
|
497
|
+
id: "roadmap",
|
|
498
|
+
text: section("Roadmap", roadmapLines.join("\n")),
|
|
499
|
+
mandatory: false,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
444
502
|
if (state.milestones.length > 0) {
|
|
445
503
|
const milestoneText = state.milestones
|
|
446
504
|
.map((m) => {
|
|
447
|
-
const kindIcon = m.kind === "agent_declared"
|
|
448
|
-
? "🏁"
|
|
449
|
-
: m.kind === "criteria_milestone" ? "✅" : "📍";
|
|
450
505
|
const lines = [
|
|
451
|
-
|
|
506
|
+
milestoneHeading(m),
|
|
452
507
|
` Outcome: ${m.outcome}`,
|
|
453
508
|
];
|
|
454
509
|
if (m.resolved_constraints.length > 0) {
|
|
@@ -507,13 +562,6 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
507
562
|
mandatory: false,
|
|
508
563
|
});
|
|
509
564
|
}
|
|
510
|
-
if (state.loopSynthesis) {
|
|
511
|
-
sections.push({
|
|
512
|
-
id: "loop_synthesis",
|
|
513
|
-
text: section("Loop Summary", state.loopSynthesis),
|
|
514
|
-
mandatory: false,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
565
|
if (state.nextAction) {
|
|
518
566
|
sections.push({
|
|
519
567
|
id: "next_action",
|
|
@@ -534,7 +582,8 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
534
582
|
state.progress.criteriaMet.length > 0 ||
|
|
535
583
|
state.progress.criteriaRemaining.length > 0 ||
|
|
536
584
|
state.progress.filesChanged.length > 0 ||
|
|
537
|
-
state.progress.tests !== null
|
|
585
|
+
state.progress.tests !== null ||
|
|
586
|
+
state.machineStatus !== undefined) {
|
|
538
587
|
const lines = [];
|
|
539
588
|
const total = state.progress.criteriaMet.length + state.progress.criteriaRemaining.length;
|
|
540
589
|
if (total > 0) {
|
|
@@ -542,6 +591,22 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
542
591
|
}
|
|
543
592
|
if (state.progress.estimate !== null) {
|
|
544
593
|
lines.push(`**Estimated Completion**: ${(state.progress.estimate * 100).toFixed(0)}%`);
|
|
594
|
+
// v3.3: honest labeling — the estimate is the agent's own number.
|
|
595
|
+
lines.push("**Signal source**: self-reported estimate (unverified until machine-backed)");
|
|
596
|
+
}
|
|
597
|
+
// v3.3: machine side of the comparison — git motion over committed
|
|
598
|
+
// rounds (v3.6: the object always carries definite values — no
|
|
599
|
+
// "unavailable" arm).
|
|
600
|
+
if (state.machineStatus) {
|
|
601
|
+
const ms = state.machineStatus;
|
|
602
|
+
const motion = ms.gitMotion
|
|
603
|
+
? `changes in ${ms.motionRounds}/${ms.windowRounds} recent committed rounds`
|
|
604
|
+
: `no git changes in the last ${ms.windowRounds} committed rounds`;
|
|
605
|
+
lines.push(`**Machine (git)**: ${motion}`);
|
|
606
|
+
}
|
|
607
|
+
if (state.criterionStatuses.length > 0) {
|
|
608
|
+
const metCount = state.criterionStatuses.filter((cs) => cs.status === "met").length;
|
|
609
|
+
lines.push(`**Machine (criteria)**: ${metCount}/${state.criterionStatuses.length} met across committed rounds`);
|
|
545
610
|
}
|
|
546
611
|
if (state.progress.tests) {
|
|
547
612
|
lines.push(`**Tests**: ${state.progress.tests.passed} passed, ` +
|
|
@@ -577,6 +642,32 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
577
642
|
mandatory: false,
|
|
578
643
|
});
|
|
579
644
|
}
|
|
645
|
+
// v3.3: Per-round statistics — lets the agent calibrate round granularity
|
|
646
|
+
// (files per round, rejected attempts, self-reported progress deltas).
|
|
647
|
+
// v3.6: source-labeled — files/Δ are agent-reported; only rejected
|
|
648
|
+
// attempts (and the Machine (git) row above) are machine-recorded.
|
|
649
|
+
if (state.roundStats && state.roundStats.length > 0) {
|
|
650
|
+
const statLines = state.roundStats.map((stat) => {
|
|
651
|
+
const parts = [];
|
|
652
|
+
if (stat.filesChangedCount !== null) {
|
|
653
|
+
parts.push(`${stat.filesChangedCount} file${stat.filesChangedCount === 1 ? "" : "s"}`);
|
|
654
|
+
}
|
|
655
|
+
if (stat.rejectedAttempts !== null && stat.rejectedAttempts > 0) {
|
|
656
|
+
parts.push(`${stat.rejectedAttempts} rejected attempt${stat.rejectedAttempts === 1 ? "" : "s"}`);
|
|
657
|
+
}
|
|
658
|
+
if (stat.progressDelta !== null) {
|
|
659
|
+
parts.push(`Δ${stat.progressDelta >= 0 ? "+" : ""}${stat.progressDelta.toFixed(2)}`);
|
|
660
|
+
}
|
|
661
|
+
return `- R${stat.round}: ${parts.length > 0 ? parts.join(", ") : "no data"}`;
|
|
662
|
+
});
|
|
663
|
+
statLines.push("> files & Δ are agent-reported; rejected attempts are machine-recorded " +
|
|
664
|
+
"(the Machine (git) row above is machine-observed)");
|
|
665
|
+
sections.push({
|
|
666
|
+
id: "round_stats",
|
|
667
|
+
text: section("Round Stats", statLines.join("\n")),
|
|
668
|
+
mandatory: false,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
580
671
|
// Retired Constraints
|
|
581
672
|
if (state.retiredConstraints.length > 0) {
|
|
582
673
|
const lines = state.retiredConstraints.map((c) => `- ~${c}~ (retired)`);
|
|
@@ -586,35 +677,10 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
586
677
|
mandatory: false,
|
|
587
678
|
});
|
|
588
679
|
}
|
|
589
|
-
// Inactive Constraints (with decay metadata)
|
|
590
|
-
if (state.inactiveConstraints.length > 0) {
|
|
591
|
-
const lines = [
|
|
592
|
-
"> Demoted after prolonged inactivity. Auto-reactivated if violated again.",
|
|
593
|
-
"",
|
|
594
|
-
];
|
|
595
|
-
for (const c of state.inactiveConstraints) {
|
|
596
|
-
const meta = state.constraintMetadata.find((m) => m.text === c);
|
|
597
|
-
const age = meta
|
|
598
|
-
? state.round - (meta.last_violated_at_round || meta.discovered_at_round || 0)
|
|
599
|
-
: "?";
|
|
600
|
-
const lastV = meta?.last_violated_at_round
|
|
601
|
-
? `last violated R${meta.last_violated_at_round}`
|
|
602
|
-
: "never violated";
|
|
603
|
-
lines.push(`- ${c} (${lastV}, ${age} rounds inactive)`);
|
|
604
|
-
}
|
|
605
|
-
sections.push({
|
|
606
|
-
id: "inactive_constraints",
|
|
607
|
-
text: section("Inactive Constraints", lines.join("\n")),
|
|
608
|
-
mandatory: false,
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
680
|
// Agent Trust Score + Trend
|
|
612
681
|
if (state.agentTrustScore !== undefined) {
|
|
613
|
-
const barLen = 10;
|
|
614
|
-
const filled = Math.round(state.agentTrustScore * barLen);
|
|
615
|
-
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
616
682
|
const lines = [
|
|
617
|
-
|
|
683
|
+
trustBarLine(state.agentTrustScore),
|
|
618
684
|
];
|
|
619
685
|
if (state.agentTrustTrend.length > 0) {
|
|
620
686
|
const avg = (state.agentTrustTrend.reduce((a, b) => a + b, 0)
|
|
@@ -631,9 +697,6 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
631
697
|
return sections;
|
|
632
698
|
}
|
|
633
699
|
function selectSections(input, emphasized) {
|
|
634
|
-
if (input.mode === "full") {
|
|
635
|
-
return l2Sections(input.state, input.fullStateMarkdown);
|
|
636
|
-
}
|
|
637
700
|
if (input.level === "l0")
|
|
638
701
|
return l0Sections(input.state);
|
|
639
702
|
if (input.level === "l1") {
|
|
@@ -693,7 +756,7 @@ function matchEmphasize(targets, candidates, cap) {
|
|
|
693
756
|
}
|
|
694
757
|
/** Render the "Confusion Alerts" section that appears at the prompt top.
|
|
695
758
|
* Compiler auto-matches each confusion point against state sections. */
|
|
696
|
-
function renderConfusionAlerts(points, state,
|
|
759
|
+
function renderConfusionAlerts(points, state, fullDensity) {
|
|
697
760
|
if (points.length === 0)
|
|
698
761
|
return "";
|
|
699
762
|
const lines = [
|
|
@@ -712,11 +775,10 @@ function renderConfusionAlerts(points, state, expandAll) {
|
|
|
712
775
|
{ keyword: "criteria", label: "Success Criteria", ref: "## Success Criteria" },
|
|
713
776
|
{ keyword: "phase", label: "Phase History", ref: "## Phase History" },
|
|
714
777
|
{ keyword: "objective", label: "Loop Objective", ref: "## Loop Objective" },
|
|
715
|
-
{ keyword: "loop", label: "Loop Summary", ref: "## Loop Summary" },
|
|
716
778
|
];
|
|
717
779
|
// v2.14: L2 renders up to the policy cap (was: every confusion point,
|
|
718
780
|
// unbounded by max_confusion_points); L1 keeps a single alert.
|
|
719
|
-
const shown =
|
|
781
|
+
const shown = fullDensity
|
|
720
782
|
? points.slice(0, getPolicy().prompt.max_confusion_points)
|
|
721
783
|
: points.slice(0, 1);
|
|
722
784
|
for (const point of shown) {
|
|
@@ -738,7 +800,7 @@ function renderConfusionAlerts(points, state, expandAll) {
|
|
|
738
800
|
lines.push(` ${pointer}`);
|
|
739
801
|
lines.push("");
|
|
740
802
|
}
|
|
741
|
-
if (!
|
|
803
|
+
if (!fullDensity && points.length > 1) {
|
|
742
804
|
lines.push(`*… and ${points.length - 1} more confusion point(s) — see state file.*`);
|
|
743
805
|
lines.push("");
|
|
744
806
|
}
|
|
@@ -761,121 +823,15 @@ function renderCriticalContext(emphasized, state) {
|
|
|
761
823
|
lines.push("");
|
|
762
824
|
return lines.join("\n");
|
|
763
825
|
}
|
|
764
|
-
/** Expand a specific section to L2-level detail within a L1 prompt.
|
|
765
|
-
* Returns the expanded section text, or empty string if the section
|
|
766
|
-
* has no content to expand. */
|
|
767
|
-
function renderExpandedSection(sectionName, state) {
|
|
768
|
-
switch (sectionName) {
|
|
769
|
-
case "milestones": {
|
|
770
|
-
if (state.milestones.length === 0)
|
|
771
|
-
return "";
|
|
772
|
-
const text = state.milestones
|
|
773
|
-
.map((m) => {
|
|
774
|
-
const icon = m.kind === "agent_declared" ? "🏁" : m.kind === "criteria_milestone" ? "✅" : "📍";
|
|
775
|
-
const lines = [
|
|
776
|
-
`**${icon} ${m.label}** (R${m.round_range.start}–R${m.round_range.end}, ${(m.progress_at_boundary * 100).toFixed(0)}%)`,
|
|
777
|
-
` Outcome: ${m.outcome}`,
|
|
778
|
-
];
|
|
779
|
-
if (m.carried_constraints.length > 0)
|
|
780
|
-
lines.push(` Carried: ${m.carried_constraints.slice(0, 3).join("; ")}`);
|
|
781
|
-
if (m.resolved_constraints.length > 0)
|
|
782
|
-
lines.push(` Resolved: ${m.resolved_constraints.slice(0, 3).join("; ")}`);
|
|
783
|
-
return lines.join("\n");
|
|
784
|
-
})
|
|
785
|
-
.join("\n\n");
|
|
786
|
-
return section("Expanded Phase History", text);
|
|
787
|
-
}
|
|
788
|
-
case "sub_goals": {
|
|
789
|
-
if (state.subGoals.length === 0)
|
|
790
|
-
return "";
|
|
791
|
-
const lines = [];
|
|
792
|
-
for (const sg of state.subGoals) {
|
|
793
|
-
const icon = sg.status === "in_progress" ? "🔄" :
|
|
794
|
-
sg.status === "done" ? "✅" :
|
|
795
|
-
sg.status === "blocked" ? "🚫" :
|
|
796
|
-
sg.status === "canceled" ? "❌" : "⏳";
|
|
797
|
-
const stale = sg.status === "pending" && state.round - sg.declared_at_round >= 10 ? " ⚠️ stale" : "";
|
|
798
|
-
const detail = sg.status === "done" && sg.completed_at_round
|
|
799
|
-
? ` (done, R${sg.completed_at_round})`
|
|
800
|
-
: sg.status === "in_progress" ? ` (since R${sg.status_changed_at_round})`
|
|
801
|
-
: sg.status === "pending" ? ` (since R${sg.declared_at_round})`
|
|
802
|
-
: sg.status === "blocked" ? ` (blocked R${sg.status_changed_at_round})`
|
|
803
|
-
: ` (canceled R${sg.status_changed_at_round})`;
|
|
804
|
-
lines.push(`- ${icon} [\`${sg.id}\`] ${sg.description}${detail}${stale}`);
|
|
805
|
-
}
|
|
806
|
-
return section("Expanded Sub-Goal Dashboard", lines.join("\n"));
|
|
807
|
-
}
|
|
808
|
-
case "constraint_lifecycle": {
|
|
809
|
-
if (state.inactiveConstraints.length === 0 && state.retiredConstraints.length === 0)
|
|
810
|
-
return "";
|
|
811
|
-
const lines = [];
|
|
812
|
-
if (state.inactiveConstraints.length > 0) {
|
|
813
|
-
lines.push("**Inactive (auto-decayed):**");
|
|
814
|
-
for (const c of state.inactiveConstraints) {
|
|
815
|
-
const meta = state.constraintMetadata.find((m) => m.text === c);
|
|
816
|
-
const age = meta ? state.round - (meta.last_violated_at_round || meta.discovered_at_round || 0) : "?";
|
|
817
|
-
lines.push(`- ${c} (${age} rounds inactive)`);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
if (state.retiredConstraints.length > 0) {
|
|
821
|
-
lines.push("**Retired:**");
|
|
822
|
-
for (const c of state.retiredConstraints)
|
|
823
|
-
lines.push(`- ~${c}~`);
|
|
824
|
-
}
|
|
825
|
-
return section("Constraint Lifecycle", lines.join("\n"));
|
|
826
|
-
}
|
|
827
|
-
case "agent_trust": {
|
|
828
|
-
if (state.agentTrustScore === undefined)
|
|
829
|
-
return "";
|
|
830
|
-
const barLen = 10;
|
|
831
|
-
const filled = Math.round(state.agentTrustScore * barLen);
|
|
832
|
-
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
833
|
-
const lines = [
|
|
834
|
-
`${bar} ${(state.agentTrustScore * 100).toFixed(0)}%`,
|
|
835
|
-
];
|
|
836
|
-
if (state.agentTrustTrend.length > 0) {
|
|
837
|
-
lines.push(`Trend: ${state.agentTrustTrend.join(" → ")}`);
|
|
838
|
-
}
|
|
839
|
-
return section("Agent Trust (Expanded)", lines.join("\n"));
|
|
840
|
-
}
|
|
841
|
-
case "progress": {
|
|
842
|
-
const p = state.progress;
|
|
843
|
-
if (p.estimate === null && p.criteriaMet.length === 0 && p.criteriaRemaining.length === 0)
|
|
844
|
-
return "";
|
|
845
|
-
const lines = [];
|
|
846
|
-
const total = p.criteriaMet.length + p.criteriaRemaining.length;
|
|
847
|
-
if (total > 0)
|
|
848
|
-
lines.push(`**Criteria**: ${p.criteriaMet.length}/${total} met`);
|
|
849
|
-
if (p.estimate !== null)
|
|
850
|
-
lines.push(`**Estimate**: ${(p.estimate * 100).toFixed(0)}%`);
|
|
851
|
-
if (p.tests)
|
|
852
|
-
lines.push(`**Tests**: ${p.tests.passed} passed, ${p.tests.failed} failed, ${p.tests.skipped} skipped`);
|
|
853
|
-
if (p.filesChanged.length > 0) {
|
|
854
|
-
lines.push("**Files Changed**:");
|
|
855
|
-
for (const f of p.filesChanged.slice(0, 10))
|
|
856
|
-
lines.push(`- ${f}`);
|
|
857
|
-
}
|
|
858
|
-
return section("Progress Dashboard (Expanded)", lines.join("\n"));
|
|
859
|
-
}
|
|
860
|
-
case "loop_synthesis": {
|
|
861
|
-
if (!state.loopSynthesis)
|
|
862
|
-
return "";
|
|
863
|
-
return section("Loop Summary", state.loopSynthesis);
|
|
864
|
-
}
|
|
865
|
-
default:
|
|
866
|
-
return "";
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
826
|
export function assemblePromptArtifact(input) {
|
|
870
|
-
const mode = input.mode ?? "adaptive";
|
|
871
827
|
const budgets = { ...DEFAULT_PROMPT_BUDGETS, ...input.budgets };
|
|
872
828
|
const budget = budgets[input.level];
|
|
873
829
|
const stateHash = hashCanonicalState(input.state);
|
|
874
830
|
const attempt = Math.max(1, input.attempt ?? 1);
|
|
875
831
|
// ── State file reference + conditional read instruction ────────────
|
|
876
|
-
// L2 rounds (full rehydration), recovery, rejection, drift
|
|
877
|
-
//
|
|
878
|
-
//
|
|
832
|
+
// L2 rounds (full rehydration), recovery, rejection, and drift all
|
|
833
|
+
// benefit from the model reading the durable state file rather than
|
|
834
|
+
// relying on potentially-compacted conversation history.
|
|
879
835
|
const fullContextReasons = new Set([
|
|
880
836
|
"first_round", "plan_boundary", "checkpoint_boundary", "goal_changed",
|
|
881
837
|
"missing_previous_state", "verification_contradicted",
|
|
@@ -883,7 +839,6 @@ export function assemblePromptArtifact(input) {
|
|
|
883
839
|
"periodic_refresh",
|
|
884
840
|
]);
|
|
885
841
|
const needsFullContext = input.level === "l2"
|
|
886
|
-
|| input.mode === "pointer"
|
|
887
842
|
|| input.reasons.some((r) => fullContextReasons.has(r));
|
|
888
843
|
const stateFilePath = input.state.stateFilePath;
|
|
889
844
|
// v3.2: L1 annotates the pointer with the round the freshly-regenerated
|
|
@@ -1005,11 +960,6 @@ export function assemblePromptArtifact(input) {
|
|
|
1005
960
|
milestoneRanges: input.state.milestones.map((m) => [m.round_range.start, m.round_range.end]),
|
|
1006
961
|
};
|
|
1007
962
|
}
|
|
1008
|
-
// Expand: L1 only, max 1 section (L2 already expanded, L0 ignored)
|
|
1009
|
-
const expandCap = levelIsL1 ? policy.max_expand_l1 : 0;
|
|
1010
|
-
const expandedSection = (pr?.expand?.length && expandCap > 0)
|
|
1011
|
-
? renderExpandedSection(pr.expand[0], input.state)
|
|
1012
|
-
: "";
|
|
1013
963
|
// Inject confusion alerts into the header area (before mandatory sections)
|
|
1014
964
|
const fixedText = header + confusionText;
|
|
1015
965
|
// Inject critical context as a mandatory section
|
|
@@ -1024,34 +974,17 @@ export function assemblePromptArtifact(input) {
|
|
|
1024
974
|
...sections,
|
|
1025
975
|
];
|
|
1026
976
|
}
|
|
1027
|
-
const selected =
|
|
1028
|
-
|
|
1029
|
-
: renderWithinBudget(augmentedSections, fixedText, budget - footer.length - expandedSection.length);
|
|
1030
|
-
const renderedPrompt = selected.rendered + expandedSection + footer;
|
|
1031
|
-
const includedSections = [
|
|
1032
|
-
...selected.included,
|
|
1033
|
-
...(confusionText ? ["confusion_alerts"] : []),
|
|
1034
|
-
...(criticalContextText ? ["critical_context"] : []),
|
|
1035
|
-
...(expandedSection ? [`expand:${pr?.expand?.[0] ?? ""}`] : []),
|
|
1036
|
-
...(pointer ? ["state_pointer"] : []),
|
|
1037
|
-
...(evaluation ? ["self_evaluation"] : []),
|
|
1038
|
-
];
|
|
977
|
+
const selected = renderWithinBudget(augmentedSections, fixedText, budget - footer.length);
|
|
978
|
+
const renderedPrompt = selected.rendered + footer;
|
|
1039
979
|
const promptHash = createHash("sha256").update(renderedPrompt).digest("hex");
|
|
1040
980
|
return {
|
|
1041
981
|
schemaVersion: PROMPT_ARTIFACT_SCHEMA_VERSION,
|
|
1042
982
|
roundId: `loop:${input.state.loopId}:round:${input.state.round}`,
|
|
1043
983
|
attempt,
|
|
1044
984
|
level: input.level,
|
|
1045
|
-
levelReasons: [...input.reasons],
|
|
1046
985
|
renderedPrompt,
|
|
1047
986
|
promptHash,
|
|
1048
987
|
stateHash,
|
|
1049
|
-
basePromptVersion: BASE_PROMPT_VERSION,
|
|
1050
|
-
includedSections,
|
|
1051
|
-
budgetChars: budget,
|
|
1052
|
-
charCount: renderedPrompt.length,
|
|
1053
|
-
budgetExceeded: renderedPrompt.length > budget,
|
|
1054
|
-
generatedAt: Date.now(),
|
|
1055
988
|
...(presentedState ? { presentedState } : {}),
|
|
1056
989
|
};
|
|
1057
990
|
}
|