loopforge 3.0.0 → 3.6.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 +126 -143
- package/dist/audit.d.ts +50 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +131 -0
- package/dist/audit.js.map +1 -0
- package/dist/canonical-state.d.ts +98 -23
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +257 -181
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +40 -33
- 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 +25 -33
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +38 -138
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +59 -0
- package/dist/committed-round.d.ts.map +1 -0
- package/dist/committed-round.js +222 -0
- package/dist/committed-round.js.map +1 -0
- package/dist/enforcement-gate.d.ts +78 -0
- package/dist/enforcement-gate.d.ts.map +1 -0
- package/dist/enforcement-gate.js +1109 -0
- package/dist/enforcement-gate.js.map +1 -0
- package/dist/engine.d.ts +111 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +887 -0
- package/dist/engine.js.map +1 -0
- package/dist/evidence-claims.d.ts +50 -0
- package/dist/evidence-claims.d.ts.map +1 -0
- package/dist/evidence-claims.js +116 -0
- package/dist/evidence-claims.js.map +1 -0
- package/dist/evidence-provider.d.ts +50 -23
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +167 -50
- package/dist/evidence-provider.js.map +1 -1
- package/dist/generate-schema.js +1 -84
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +19 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -15
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +58 -3
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +841 -304
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +2 -2
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +2 -2
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-projection.d.ts +6 -0
- package/dist/loop-projection.d.ts.map +1 -0
- package/dist/loop-projection.js +26 -0
- package/dist/loop-projection.js.map +1 -0
- package/dist/loop-store.d.ts +115 -24
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +365 -218
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +214 -0
- package/dist/mcp/round-lifecycle.d.ts.map +1 -0
- package/dist/mcp/round-lifecycle.js +935 -0
- package/dist/mcp/round-lifecycle.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -43
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +64 -101
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +433 -1110
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +20 -11
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +587 -413
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +12 -24
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +78 -184
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +61 -50
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +48 -146
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +37 -6
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +487 -278
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +1 -8
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +8 -26
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +398 -236
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +147 -17
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts +5 -13
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +32 -69
- 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 +23 -25
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +103 -92
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +13 -13
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +25 -14
- package/dist/round-driver.js.map +1 -1
- package/dist/round-transaction.d.ts +14 -18
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +37 -47
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +48 -0
- package/dist/self-eval.d.ts.map +1 -0
- package/dist/self-eval.js +248 -0
- package/dist/self-eval.js.map +1 -0
- package/dist/storage.d.ts +9 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +18 -6
- package/dist/storage.js.map +1 -1
- package/dist/token-utils.d.ts +15 -3
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +24 -5
- package/dist/token-utils.js.map +1 -1
- package/dist/verification-gate.d.ts +149 -0
- package/dist/verification-gate.d.ts.map +1 -0
- package/dist/verification-gate.js +1343 -0
- package/dist/verification-gate.js.map +1 -0
- 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 +37 -16
- package/package.json +5 -10
- package/skills/perception/SKILL.md +184 -202
- package/dist/canonical-event-writer.d.ts +0 -20
- package/dist/canonical-event-writer.d.ts.map +0 -1
- package/dist/canonical-event-writer.js +0 -131
- package/dist/canonical-event-writer.js.map +0 -1
- package/dist/compiler-subgoals.d.ts +0 -7
- package/dist/compiler-subgoals.d.ts.map +0 -1
- package/dist/compiler-subgoals.js +0 -93
- package/dist/compiler-subgoals.js.map +0 -1
- package/dist/derive-id.d.ts +0 -12
- package/dist/derive-id.d.ts.map +0 -1
- package/dist/derive-id.js +0 -31
- package/dist/derive-id.js.map +0 -1
- package/dist/mcp/session-projection.d.ts +0 -5
- package/dist/mcp/session-projection.d.ts.map +0 -1
- package/dist/mcp/session-projection.js +0 -18
- package/dist/mcp/session-projection.js.map +0 -1
- package/dist/mcp/session-queue.d.ts +0 -3
- package/dist/mcp/session-queue.d.ts.map +0 -1
- package/dist/mcp/session-queue.js +0 -18
- package/dist/mcp/session-queue.js.map +0 -1
- package/dist/mcp/session-types.d.ts +0 -72
- package/dist/mcp/session-types.d.ts.map +0 -1
- package/dist/mcp/session-types.js +0 -2
- package/dist/mcp/session-types.js.map +0 -1
- package/dist/round-commit-store.d.ts +0 -12
- package/dist/round-commit-store.d.ts.map +0 -1
- package/dist/round-commit-store.js +0 -13
- package/dist/round-commit-store.js.map +0 -1
- package/dist/round-gate.d.ts +0 -31
- package/dist/round-gate.d.ts.map +0 -1
- package/dist/round-gate.js +0 -259
- package/dist/round-gate.js.map +0 -1
- package/dist/round-recovery.d.ts +0 -17
- package/dist/round-recovery.d.ts.map +0 -1
- package/dist/round-recovery.js +0 -117
- package/dist/round-recovery.js.map +0 -1
- package/dist/round-report.d.ts +0 -16
- package/dist/round-report.d.ts.map +0 -1
- package/dist/round-report.js +0 -401
- package/dist/round-report.js.map +0 -1
- package/dist/runtime-services.d.ts +0 -59
- package/dist/runtime-services.d.ts.map +0 -1
- package/dist/runtime-services.js +0 -460
- package/dist/runtime-services.js.map +0 -1
- package/dist/vault-format.d.ts +0 -4
- package/dist/vault-format.d.ts.map +0 -1
- package/dist/vault-format.js +0 -4
- package/dist/vault-format.js.map +0 -1
- package/dist/vault-types.d.ts +0 -24
- package/dist/vault-types.d.ts.map +0 -1
- package/dist/vault-types.js +0 -2
- package/dist/vault-types.js.map +0 -1
- package/loopforge-policy.json +0 -340
package/dist/prompt-assembler.js
CHANGED
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
* are never truncated; budgets are soft and overflow is recorded.
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { jaccardSimilarity } from "./token-utils.js";
|
|
8
|
+
import { buildRoadmap, hashCanonicalState, milestoneHeading, trustBarLine } from "./canonical-state.js";
|
|
9
|
+
import { deriveItemId, STABLE_ID_RE, jaccardSimilarity } from "./token-utils.js";
|
|
11
10
|
import { getPolicy } from "./policy.js";
|
|
12
|
-
export const
|
|
13
|
-
export const BASE_PROMPT_VERSION = "
|
|
11
|
+
export const PROMPT_ARTIFACT_SCHEMA_VERSION = 1;
|
|
12
|
+
export const BASE_PROMPT_VERSION = "2.0.0";
|
|
14
13
|
export const DEFAULT_PROMPT_BUDGETS = {
|
|
15
14
|
l0: 3000,
|
|
16
15
|
l1: 7000,
|
|
@@ -31,22 +30,97 @@ function bulletsWithIds(values, prefix) {
|
|
|
31
30
|
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
32
31
|
if (!idEnabled)
|
|
33
32
|
return bullets(values);
|
|
34
|
-
const idRegex = /^(c|cr|sg)-[a-f0-9]{8}$/;
|
|
35
33
|
return values.map((v) => {
|
|
36
34
|
const trimmed = v.trim();
|
|
37
|
-
if (
|
|
35
|
+
if (STABLE_ID_RE.test(trimmed)) {
|
|
38
36
|
return `- [\`${trimmed}\`] ${trimmed}`;
|
|
39
37
|
}
|
|
40
38
|
const id = prefix + "-" + deriveItemId(trimmed);
|
|
41
39
|
return `- [\`${id}\`] ${trimmed}`;
|
|
42
40
|
}).join("\n");
|
|
43
41
|
}
|
|
44
|
-
|
|
42
|
+
/** v3.3.1: Render an identity-HETEROGENEOUS list — the merged active
|
|
43
|
+
* constraint set holds hard/plan/criteria/discovered texts together, so no
|
|
44
|
+
* single prefix fits. Each item's authoritative ID comes from constraint
|
|
45
|
+
* metadata (criteria texts carry cr-XXXXXXXX, everything else
|
|
46
|
+
* c-XXXXXXXX), keeping the active list in the same identity namespace as
|
|
47
|
+
* the item's own section — one text never renders under two prefixes in
|
|
48
|
+
* one prompt. Falls back to a c- derivation only when metadata is missing
|
|
49
|
+
* (defensive: metadata covers every active text by construction). */
|
|
50
|
+
function bulletsWithIdentities(values, metadata) {
|
|
51
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
52
|
+
if (!idEnabled)
|
|
53
|
+
return bullets(values);
|
|
54
|
+
const idByText = new Map(metadata.map((meta) => [meta.text, meta.id]));
|
|
55
|
+
return values.map((v) => {
|
|
56
|
+
const trimmed = v.trim();
|
|
57
|
+
if (STABLE_ID_RE.test(trimmed)) {
|
|
58
|
+
return `- [\`${trimmed}\`] ${trimmed}`;
|
|
59
|
+
}
|
|
60
|
+
const id = idByText.get(trimmed) ?? `c-${deriveItemId(trimmed)}`;
|
|
61
|
+
return `- [\`${id}\`] ${trimmed}`;
|
|
62
|
+
}).join("\n");
|
|
63
|
+
}
|
|
64
|
+
/** v3.2: Short imperative fix/action per verification check, appended to the
|
|
65
|
+
* L1/L2 Verification Gate lines. Deliberately shorter than the rejection-
|
|
66
|
+
* prompt wording in enforcement-gate.ts; keys are the plain check-name
|
|
67
|
+
* strings (NOT imported from verification-gate.ts — it already imports
|
|
68
|
+
* loop-compiler, which imports this module, so importing back would cycle).
|
|
69
|
+
* Full coverage of every CHECK_* constant is enforced by a test that
|
|
70
|
+
* iterates the verification-gate module's CHECK_ exports (no hardcoded
|
|
71
|
+
* list — a new constant can never silently lack an action entry). */
|
|
72
|
+
const VERIFICATION_ACTIONS = {
|
|
73
|
+
progress_regression: "Correct progress_estimate or explain the change in output_summary.",
|
|
74
|
+
empty_change_with_passing: "Report the actual files_changed, or set success=false with the real state.",
|
|
75
|
+
success_with_remaining_criteria: "Complete the remaining criteria before claiming success, or set success=false and report what remains.",
|
|
76
|
+
success_without_verified_evidence: "Back the success claim with machine-verifiable evidence: run tests or a required verification command and report the actual output.",
|
|
77
|
+
outcome_success_contradiction: "Reconcile the declared outcome with the success flag and resubmit an honest evaluation.",
|
|
78
|
+
success_claim_conflict: "Align the success claim with your own evaluation data before resubmitting.",
|
|
79
|
+
blocked_without_blocker: "State the concrete blocker, or drop the blocked outcome.",
|
|
80
|
+
retroactive_claim_bad_round: "Correct the referenced round number, or withdraw the retroactive claim.",
|
|
81
|
+
retroactive_claim_unverified: "Provide evidence for the retroactive claim, or withdraw it.",
|
|
82
|
+
duplicate_constraint_discovery: "Update the existing constraint entry instead of duplicating it.",
|
|
83
|
+
recurring_violation: "Explain why this violation keeps recurring and try a different approach — do not repeat the failed strategy.",
|
|
84
|
+
retract_fresh_constraint: "Justify the retraction explicitly, or restore the constraint.",
|
|
85
|
+
evidence_integrity: "Correct the mismatch between your claims and the recorded evidence.",
|
|
86
|
+
required_command_failed: "Re-run the required verification command and report its actual output before claiming success.",
|
|
87
|
+
command_evidence_mismatch: "Report the true command output and reconcile the mismatch.",
|
|
88
|
+
intent_drift: "Explain the pivot in drift_clarification with concrete references, or redo the round as promised.",
|
|
89
|
+
subgoal_drift: "Reconcile the sub-goal list with your reported work before resubmitting.",
|
|
90
|
+
backtrack_workspace_not_restored: "Restore the skipped-round files and the expected git HEAD, then resubmit.",
|
|
91
|
+
criteria_claims_unverified: "Provide evidence for each claimed success criterion.",
|
|
92
|
+
// v3.3 — Round Contract checks (and the three v3.3 checks that predated
|
|
93
|
+
// this table entry and fell back to the generic action).
|
|
94
|
+
verification_entrypoint_modified: "Re-run the verification command on the changed entrypoint, or revert the entrypoint change and re-run.",
|
|
95
|
+
test_files_modified: "Re-run the verification command after the test-file changes and report the fresh output.",
|
|
96
|
+
round_underspecified: "Declare this round's contract with a non-empty done_when list — what must be true when the round is done?",
|
|
97
|
+
round_unverifiable: "Name only configured, enabled evidence.commands in verification_plan, or drop done_when items that cannot be machine-verified.",
|
|
98
|
+
round_scope_drift: "Revert the out-of-scope file changes, or extend the contract scope to cover them and explain why in drift_clarification.",
|
|
99
|
+
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).",
|
|
100
|
+
// v3.5 — contract completion machine-backing + premature-replacement warn
|
|
101
|
+
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).",
|
|
102
|
+
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.",
|
|
103
|
+
};
|
|
104
|
+
/** Fallback for unmapped check names (forward-compat with future checks). */
|
|
105
|
+
const VERIFICATION_ACTION_FALLBACK = "Re-examine the flagged claim and correct your evaluation before resubmitting.";
|
|
106
|
+
/** v3.2: The actionable instruction for a verification check. Exported for
|
|
107
|
+
* the coverage test (which imports the CHECK_* constants). */
|
|
108
|
+
export function verificationActionFor(check) {
|
|
109
|
+
return VERIFICATION_ACTIONS[check] ?? VERIFICATION_ACTION_FALLBACK;
|
|
110
|
+
}
|
|
111
|
+
function verificationText(state, level) {
|
|
45
112
|
const lines = state.verificationFlags.map((flag) => {
|
|
46
113
|
const icon = flag.severity === "error"
|
|
47
114
|
? "🚫"
|
|
48
115
|
: flag.severity === "warn" ? "⚠️" : "ℹ️";
|
|
49
|
-
|
|
116
|
+
const base = `- ${icon} [${flag.check}] ${flag.detail}`;
|
|
117
|
+
// v3.2: error/warn flags get an actionable continuation line in L1/L2 —
|
|
118
|
+
// the verdict is translated into what to DO. L0 keeps the compact retry
|
|
119
|
+
// prompt byte-identical; info flags stay informational.
|
|
120
|
+
if (level === "l0" || flag.severity === "info")
|
|
121
|
+
return base;
|
|
122
|
+
const label = flag.severity === "error" ? "Fix" : "Action";
|
|
123
|
+
return `${base}\n → ${label}: ${verificationActionFor(flag.check)}`;
|
|
50
124
|
});
|
|
51
125
|
if (state.verificationFlags.some((flag) => flag.severity === "error")) {
|
|
52
126
|
lines.push("- Gate Verdict: CONTRADICTED — resolve every error before claiming success.");
|
|
@@ -57,6 +131,81 @@ function activeNonHardConstraints(state) {
|
|
|
57
131
|
const hard = new Set(state.hardConstraints);
|
|
58
132
|
return state.activeConstraints.filter((value) => !hard.has(value));
|
|
59
133
|
}
|
|
134
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
135
|
+
// v3.2: L1 diff-collapse helpers
|
|
136
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
137
|
+
/** v3.2: Collapse only when at least this many items are unchanged — a tiny
|
|
138
|
+
* unchanged set renders in full (nothing worth saving). */
|
|
139
|
+
export const COLLAPSE_MIN_UNCHANGED = 3;
|
|
140
|
+
/** v3.2: Recent-rounds items kept in full when older ones collapse. */
|
|
141
|
+
export const KEEP_RECENT_ROUNDS = 3;
|
|
142
|
+
/** v3.2: Stable ID of a constraint text as rendered by bulletsWithIds —
|
|
143
|
+
* metadata's own id when present, else the derived hash. */
|
|
144
|
+
function constraintIdOf(text, metadata) {
|
|
145
|
+
const meta = metadata.find((m) => m.text === text);
|
|
146
|
+
return meta?.id ?? `c-${deriveItemId(text)}`;
|
|
147
|
+
}
|
|
148
|
+
export function diffConstraints(baseline, activeTexts, metadata, round, emphasized) {
|
|
149
|
+
if (!baseline)
|
|
150
|
+
return { changed: activeTexts, unchangedCount: 0, removedCount: 0 };
|
|
151
|
+
const baselineIds = new Set(baseline.constraintIds);
|
|
152
|
+
const currentIds = new Set(activeTexts.map((text) => constraintIdOf(text, metadata)));
|
|
153
|
+
const changed = [];
|
|
154
|
+
let unchangedCount = 0;
|
|
155
|
+
for (const text of activeTexts) {
|
|
156
|
+
const id = constraintIdOf(text, metadata);
|
|
157
|
+
const violatedThisRound = metadata.find((m) => m.text === text)
|
|
158
|
+
?.last_violated_at_round === round;
|
|
159
|
+
if (!baselineIds.has(id) || violatedThisRound)
|
|
160
|
+
changed.push(text);
|
|
161
|
+
else
|
|
162
|
+
unchangedCount++;
|
|
163
|
+
}
|
|
164
|
+
let removedCount = 0;
|
|
165
|
+
if (emphasized.size > 0) {
|
|
166
|
+
const emphasizedIds = new Set([...emphasized].map((text) => constraintIdOf(text, metadata)));
|
|
167
|
+
for (const id of baselineIds) {
|
|
168
|
+
if (!currentIds.has(id) && !emphasizedIds.has(id))
|
|
169
|
+
removedCount++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
for (const id of baselineIds) {
|
|
174
|
+
if (!currentIds.has(id))
|
|
175
|
+
removedCount++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { changed, unchangedCount, removedCount };
|
|
179
|
+
}
|
|
180
|
+
export function diffSubGoals(baseline, activeSubs) {
|
|
181
|
+
if (!baseline)
|
|
182
|
+
return { changed: activeSubs, unchangedCount: 0, removedCount: 0 };
|
|
183
|
+
const baselineById = new Map(baseline.subGoals);
|
|
184
|
+
const currentIds = new Set(activeSubs.map((sg) => sg.id));
|
|
185
|
+
const changed = [];
|
|
186
|
+
let unchangedCount = 0;
|
|
187
|
+
for (const sg of activeSubs) {
|
|
188
|
+
const prevStatus = baselineById.get(sg.id);
|
|
189
|
+
if (prevStatus === undefined || prevStatus !== sg.status)
|
|
190
|
+
changed.push(sg);
|
|
191
|
+
else
|
|
192
|
+
unchangedCount++;
|
|
193
|
+
}
|
|
194
|
+
let removedCount = 0;
|
|
195
|
+
for (const [id] of baseline.subGoals) {
|
|
196
|
+
if (!currentIds.has(id))
|
|
197
|
+
removedCount++;
|
|
198
|
+
}
|
|
199
|
+
return { changed, unchangedCount, removedCount };
|
|
200
|
+
}
|
|
201
|
+
/** v3.2: True when a milestone boundary was crossed since the baseline —
|
|
202
|
+
* the Recent Rounds section then renders in full (a phase ended). */
|
|
203
|
+
export function milestoneBoundaryChanged(baseline, milestones) {
|
|
204
|
+
if (!baseline)
|
|
205
|
+
return false;
|
|
206
|
+
const baselineRanges = new Set(baseline.milestoneRanges.map(([s, e]) => `${s}:${e}`));
|
|
207
|
+
return milestones.some((m) => !baselineRanges.has(`${m.round_range.start}:${m.round_range.end}`));
|
|
208
|
+
}
|
|
60
209
|
function commonMandatorySections(state,
|
|
61
210
|
/** v2.11: L0 skips ID rendering to stay within tight budget. */
|
|
62
211
|
level) {
|
|
@@ -66,41 +215,12 @@ level) {
|
|
|
66
215
|
text: section("Objective", state.objective),
|
|
67
216
|
mandatory: true,
|
|
68
217
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
sections.push({
|
|
73
|
-
id: "current_phase",
|
|
74
|
-
text: section("Current Phase", `${phase.label} (${phase.status})\n${phase.outcome}`),
|
|
75
|
-
mandatory: false,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const focus = state.currentFocus || state.objective;
|
|
79
|
-
if (focus.trim()) {
|
|
80
|
-
sections.push({
|
|
81
|
-
id: "current_focus",
|
|
82
|
-
text: section("Current Focus", focus),
|
|
218
|
+
{
|
|
219
|
+
id: "current_task",
|
|
220
|
+
text: section("Current Task", state.currentTask),
|
|
83
221
|
mandatory: true,
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (state.todoProjection) {
|
|
87
|
-
sections.push({
|
|
88
|
-
id: "todo_projection",
|
|
89
|
-
text: section("Active Todo Projection", `${state.todoProjection.action}\nCompletion: ${state.todoProjection.completionSignal}`),
|
|
90
|
-
mandatory: false,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
sections.push({
|
|
94
|
-
id: "verification",
|
|
95
|
-
text: section("Verification Gate", verificationText(state) || "No verification contradiction is currently open."),
|
|
96
|
-
mandatory: true,
|
|
97
|
-
});
|
|
98
|
-
if (state.activeGate) {
|
|
99
|
-
const body = state.activeGate.kind === "user"
|
|
100
|
-
? [`Owner: user`, `Question: ${state.activeGate.question}`, `Blocked scope: ${state.activeGate.blockedScope.join("; ")}`, `Safe work: ${state.activeGate.allowedWork.join("; ")}`].join("\n")
|
|
101
|
-
: [`Owner: agent`, `Problem: ${state.activeGate.problem}`, `Required evidence: ${state.activeGate.requiredEvidence.join("; ")}`, `Resolution: ${state.activeGate.suggestedResolution}`].join("\n");
|
|
102
|
-
sections.push({ id: "active_gate", text: section("Active Gate", body), mandatory: true });
|
|
103
|
-
}
|
|
222
|
+
},
|
|
223
|
+
];
|
|
104
224
|
if (state.hardConstraints.length > 0) {
|
|
105
225
|
// v2.11: L0 uses plain bullets (tight budget); L1/L2 show constraint IDs
|
|
106
226
|
const body = level === "l0"
|
|
@@ -112,6 +232,13 @@ level) {
|
|
|
112
232
|
mandatory: true,
|
|
113
233
|
});
|
|
114
234
|
}
|
|
235
|
+
if (state.verificationFlags.length > 0) {
|
|
236
|
+
sections.push({
|
|
237
|
+
id: "verification",
|
|
238
|
+
text: section("Verification Gate", verificationText(state, level)),
|
|
239
|
+
mandatory: true,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
115
242
|
return sections;
|
|
116
243
|
}
|
|
117
244
|
function l0Sections(state) {
|
|
@@ -136,17 +263,10 @@ function l0Sections(state) {
|
|
|
136
263
|
}
|
|
137
264
|
return sections;
|
|
138
265
|
}
|
|
139
|
-
function l1Sections(state) {
|
|
266
|
+
function l1Sections(state, baseline, emphasized) {
|
|
140
267
|
const sections = commonMandatorySections(state, "l1");
|
|
141
|
-
const openRegression = state.regressionObligations.filter((obligation) => obligation.status !== "satisfied");
|
|
142
|
-
if (openRegression.length > 0) {
|
|
143
|
-
sections.push({
|
|
144
|
-
id: "regression_obligations",
|
|
145
|
-
text: section("Regression Obligations", bullets(openRegression.map((item) => `[${item.status}] ${item.description}`))),
|
|
146
|
-
mandatory: true,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
268
|
const active = activeNonHardConstraints(state);
|
|
269
|
+
const collapseEnabled = getPolicy().prompt.l1_collapse_enabled;
|
|
150
270
|
if (state.changesSinceLastRound.length > 0) {
|
|
151
271
|
sections.push({
|
|
152
272
|
id: "changes",
|
|
@@ -154,12 +274,36 @@ function l1Sections(state) {
|
|
|
154
274
|
mandatory: false,
|
|
155
275
|
});
|
|
156
276
|
}
|
|
277
|
+
// v3.2: Active Constraints — collapse unchanged items against the previous
|
|
278
|
+
// round's presentation; new and violated-this-round constraints render in
|
|
279
|
+
// full. The collapse line points at the state file (regenerated every
|
|
280
|
+
// round), so no information is lost — only presentation is compacted.
|
|
157
281
|
if (active.length > 0) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
282
|
+
const diff = diffConstraints(baseline, active, state.constraintMetadata, state.round, emphasized);
|
|
283
|
+
const renderFull = !collapseEnabled || !baseline
|
|
284
|
+
|| diff.unchangedCount < COLLAPSE_MIN_UNCHANGED;
|
|
285
|
+
if (renderFull) {
|
|
286
|
+
sections.push({
|
|
287
|
+
id: "active_constraints",
|
|
288
|
+
text: section("Active Constraints / Success Criteria", bulletsWithIdentities(active, state.constraintMetadata)),
|
|
289
|
+
mandatory: false,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const lines = diff.changed.map((text) => {
|
|
294
|
+
const id = constraintIdOf(text, state.constraintMetadata);
|
|
295
|
+
const violated = state.constraintMetadata.find((m) => m.text === text)
|
|
296
|
+
?.last_violated_at_round === state.round;
|
|
297
|
+
const suffix = violated ? " (violated this round)" : " (new this round)";
|
|
298
|
+
return `- [\`${id}\`] ${text}${suffix}`;
|
|
299
|
+
});
|
|
300
|
+
lines.push(`- … ${diff.unchangedCount} unchanged constraints, ${diff.removedCount} demoted since R${baseline.round} (see state file)`);
|
|
301
|
+
sections.push({
|
|
302
|
+
id: "active_constraints",
|
|
303
|
+
text: section("Active Constraints / Success Criteria", lines.join("\n")),
|
|
304
|
+
mandatory: false,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
163
307
|
}
|
|
164
308
|
if (state.remainingCriteria.length > 0) {
|
|
165
309
|
sections.push({
|
|
@@ -172,32 +316,47 @@ function l1Sections(state) {
|
|
|
172
316
|
sections.push({
|
|
173
317
|
id: "blockers",
|
|
174
318
|
text: section("Blockers", bullets(state.blockers)),
|
|
175
|
-
mandatory:
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
if (state.handoff.verifiedFacts.length > 0) {
|
|
179
|
-
sections.push({
|
|
180
|
-
id: "verified_facts",
|
|
181
|
-
text: section("Latest Verified Facts", bulletsWithIds(state.handoff.verifiedFacts, "cr")),
|
|
182
|
-
mandatory: true,
|
|
319
|
+
mandatory: false,
|
|
183
320
|
});
|
|
184
321
|
}
|
|
185
|
-
// v2.2: Sub-Goal Dashboard (compact — L1)
|
|
322
|
+
// v2.2: Sub-Goal Dashboard (compact — L1). v3.2: unchanged sub-goals
|
|
323
|
+
// collapse to a count line; new/transitioned ones render in full.
|
|
186
324
|
const activeSubs = state.subGoals.filter((sg) => sg.status === "in_progress" || sg.status === "pending");
|
|
187
325
|
if (activeSubs.length > 0) {
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
326
|
+
const diff = diffSubGoals(baseline, activeSubs);
|
|
327
|
+
const renderFull = !collapseEnabled || !baseline
|
|
328
|
+
|| diff.unchangedCount < COLLAPSE_MIN_UNCHANGED;
|
|
329
|
+
if (renderFull) {
|
|
330
|
+
const maxShow = 5;
|
|
331
|
+
const items = activeSubs.slice(0, maxShow).map((sg) => {
|
|
332
|
+
const icon = sg.status === "in_progress" ? "🔄" : "⏳";
|
|
333
|
+
return `${icon} [\`${sg.id}\`] ${sg.description}`;
|
|
334
|
+
});
|
|
335
|
+
if (activeSubs.length > maxShow) {
|
|
336
|
+
items.push(`... and ${activeSubs.length - maxShow} more`);
|
|
337
|
+
}
|
|
338
|
+
sections.push({
|
|
339
|
+
id: "sub_goals",
|
|
340
|
+
text: section("Active Sub-Goals", items.join("\n")),
|
|
341
|
+
mandatory: false,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
const transitionCount = diff.changed.filter((sg) => {
|
|
346
|
+
const prev = baseline.subGoals.find(([id]) => id === sg.id);
|
|
347
|
+
return prev !== undefined && prev[1] !== sg.status;
|
|
348
|
+
}).length;
|
|
349
|
+
const items = diff.changed.slice(0, 5).map((sg) => {
|
|
350
|
+
const icon = sg.status === "in_progress" ? "🔄" : "⏳";
|
|
351
|
+
return `${icon} [\`${sg.id}\`] ${sg.description}`;
|
|
352
|
+
});
|
|
353
|
+
items.push(`- … ${diff.unchangedCount} unchanged sub-goals, ${transitionCount + diff.removedCount} changed since R${baseline.round} (see state file)`);
|
|
354
|
+
sections.push({
|
|
355
|
+
id: "sub_goals",
|
|
356
|
+
text: section("Active Sub-Goals", items.join("\n")),
|
|
357
|
+
mandatory: false,
|
|
358
|
+
});
|
|
195
359
|
}
|
|
196
|
-
sections.push({
|
|
197
|
-
id: "sub_goals",
|
|
198
|
-
text: section("Active Sub-Goals", items.join("\n")),
|
|
199
|
-
mandatory: false,
|
|
200
|
-
});
|
|
201
360
|
}
|
|
202
361
|
if (state.discoveries.length > 0) {
|
|
203
362
|
sections.push({
|
|
@@ -206,24 +365,72 @@ function l1Sections(state) {
|
|
|
206
365
|
mandatory: false,
|
|
207
366
|
});
|
|
208
367
|
}
|
|
368
|
+
// v3.2: Recent Rounds — keep the newest KEEP_RECENT_ROUNDS in full, fold
|
|
369
|
+
// older ones into one pointer line (unless a milestone boundary crossed).
|
|
209
370
|
if (state.rollingOutcomes.length > 0) {
|
|
371
|
+
const boundaryChanged = milestoneBoundaryChanged(baseline, state.milestones);
|
|
372
|
+
const collapseable = collapseEnabled && baseline !== null
|
|
373
|
+
&& state.rollingOutcomes.length > KEEP_RECENT_ROUNDS && !boundaryChanged;
|
|
374
|
+
if (collapseable) {
|
|
375
|
+
const kept = state.rollingOutcomes.slice(-KEEP_RECENT_ROUNDS);
|
|
376
|
+
const earlier = state.rollingOutcomes.length - kept.length;
|
|
377
|
+
const lines = [
|
|
378
|
+
...kept,
|
|
379
|
+
`- … ${earlier} earlier round${earlier === 1 ? "" : "s"} (see state file)`,
|
|
380
|
+
];
|
|
381
|
+
sections.push({
|
|
382
|
+
id: "rolling_outcomes",
|
|
383
|
+
text: section("Recent Rounds", lines.join("\n")),
|
|
384
|
+
mandatory: false,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
sections.push({
|
|
389
|
+
id: "rolling_outcomes",
|
|
390
|
+
text: section("Recent Rounds", bullets(state.rollingOutcomes)),
|
|
391
|
+
mandatory: false,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// v3.2: L1 also renders the explicit external context (previously L2-only).
|
|
396
|
+
// Placed after the changes delta, within the soft budget.
|
|
397
|
+
if (state.externalContext) {
|
|
398
|
+
sections.push({
|
|
399
|
+
id: "external_context",
|
|
400
|
+
text: section("External Context", state.externalContext),
|
|
401
|
+
mandatory: false,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
// v3.3: Forward-looking roadmap (same buildRoadmap as state.md and L2).
|
|
405
|
+
const roadmapLines = buildRoadmap(state);
|
|
406
|
+
if (roadmapLines.length > 0) {
|
|
210
407
|
sections.push({
|
|
211
|
-
id: "
|
|
212
|
-
text: section("
|
|
408
|
+
id: "roadmap",
|
|
409
|
+
text: section("Roadmap", roadmapLines.join("\n")),
|
|
213
410
|
mandatory: false,
|
|
214
411
|
});
|
|
215
412
|
}
|
|
216
|
-
|
|
413
|
+
// v3.2: Recurring Issues carries the deterministic violation lessons (with
|
|
414
|
+
// counts and rounds) when available; falls back to the rolling window text.
|
|
415
|
+
if (state.recurringIssues.length > 0 || state.lessons.length > 0) {
|
|
416
|
+
const violationLessons = state.lessons
|
|
417
|
+
.filter((lesson) => lesson.kind === "constraint_violation")
|
|
418
|
+
.slice(0, 3);
|
|
419
|
+
const body = violationLessons.length > 0
|
|
420
|
+
? violationLessons
|
|
421
|
+
.map((lesson) => `- ${lesson.text} (violated ${lesson.count}×: R${lesson.rounds.join(", R")})`)
|
|
422
|
+
.join("\n")
|
|
423
|
+
: bullets(state.recurringIssues);
|
|
217
424
|
sections.push({
|
|
218
425
|
id: "recurring_issues",
|
|
219
|
-
text: section("Recurring Issues",
|
|
426
|
+
text: section("Recurring Issues", body),
|
|
220
427
|
mandatory: false,
|
|
221
428
|
});
|
|
222
429
|
}
|
|
223
|
-
if (state.
|
|
430
|
+
if (state.nextAction) {
|
|
224
431
|
sections.push({
|
|
225
|
-
id: "
|
|
226
|
-
text: section("
|
|
432
|
+
id: "next_action",
|
|
433
|
+
text: section("Next Action", state.nextAction),
|
|
227
434
|
mandatory: false,
|
|
228
435
|
});
|
|
229
436
|
}
|
|
@@ -231,14 +438,6 @@ function l1Sections(state) {
|
|
|
231
438
|
}
|
|
232
439
|
function l2Sections(state, fullStateMarkdown) {
|
|
233
440
|
const sections = commonMandatorySections(state, "l2");
|
|
234
|
-
const openRegression = state.regressionObligations.filter((obligation) => obligation.status !== "satisfied");
|
|
235
|
-
if (openRegression.length > 0) {
|
|
236
|
-
sections.push({
|
|
237
|
-
id: "regression_obligations",
|
|
238
|
-
text: section("Regression Obligations", bullets(openRegression.map((item) => `[${item.status}] ${item.description}`))),
|
|
239
|
-
mandatory: true,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
441
|
const fullState = fullStateMarkdown?.trim();
|
|
243
442
|
if (fullState) {
|
|
244
443
|
sections.push({
|
|
@@ -255,36 +454,71 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
255
454
|
["changes", "Changes Since Last Round", state.changesSinceLastRound, null],
|
|
256
455
|
["remaining", "Remaining", state.remainingCriteria, "cr"],
|
|
257
456
|
["blockers", "Blockers", state.blockers, null],
|
|
258
|
-
["verified_facts", "Latest Verified Facts", state.handoff.verifiedFacts, "cr"],
|
|
259
457
|
["discoveries", "Discoveries", state.discoveries, null],
|
|
260
458
|
["rolling_outcomes", "Cross-Round Outcomes", state.rollingOutcomes, null],
|
|
261
459
|
["recurring_issues", "Recurring Issues", state.recurringIssues, null],
|
|
262
460
|
["failed_patterns", "Failed Patterns", state.failedPatterns, null],
|
|
263
461
|
];
|
|
264
462
|
for (const [id, title, values, idPrefix] of groups) {
|
|
265
|
-
if (values.length
|
|
266
|
-
|
|
463
|
+
if (values.length === 0)
|
|
464
|
+
continue;
|
|
465
|
+
// v3.3.1: the active set is identity-heterogeneous (hard/plan/criteria/
|
|
466
|
+
// discovered merged) — render through constraint metadata so criteria
|
|
467
|
+
// texts keep their cr- identity here too (same namespace as their own
|
|
468
|
+
// Success Criteria section). Every other group is homogeneous and keeps
|
|
469
|
+
// its single-prefix renderer.
|
|
470
|
+
const body = id === "active_constraints"
|
|
471
|
+
? bulletsWithIdentities(values, state.constraintMetadata)
|
|
472
|
+
: idPrefix
|
|
267
473
|
? bulletsWithIds(values, idPrefix)
|
|
268
474
|
: bullets(values);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
475
|
+
sections.push({ id, text: section(title, body), mandatory: false });
|
|
476
|
+
}
|
|
477
|
+
// v3.2: Lessons learned — repeated violations / verification failures
|
|
478
|
+
// across the whole loop (not just the rolling window).
|
|
479
|
+
if (state.lessons.length > 0) {
|
|
480
|
+
const lessonLines = state.lessons.slice(0, 8).map((lesson) => {
|
|
481
|
+
const icon = lesson.kind === "constraint_violation"
|
|
482
|
+
? "🚫"
|
|
483
|
+
: lesson.kind === "verification_error" ? "⚠️" : "ℹ️";
|
|
484
|
+
return `- ${icon} ${lesson.text} — ${lesson.count}× (R${lesson.rounds.join(", R")})`;
|
|
485
|
+
});
|
|
486
|
+
if (state.lessons.length > 8) {
|
|
487
|
+
lessonLines.push(`- ... and ${state.lessons.length - 8} more`);
|
|
274
488
|
}
|
|
489
|
+
sections.push({
|
|
490
|
+
id: "lessons",
|
|
491
|
+
text: section("Lessons Learned", lessonLines.join("\n")),
|
|
492
|
+
mandatory: false,
|
|
493
|
+
});
|
|
275
494
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
495
|
+
// v3.3: Forward-looking roadmap — rendered right before the (backward-
|
|
496
|
+
// looking) Phase History so the agent sees where it is before where it's
|
|
497
|
+
// been. Same buildRoadmap as state.md and L1.
|
|
498
|
+
const roadmapLines = buildRoadmap(state);
|
|
499
|
+
if (roadmapLines.length > 0) {
|
|
500
|
+
sections.push({
|
|
501
|
+
id: "roadmap",
|
|
502
|
+
text: section("Roadmap", roadmapLines.join("\n")),
|
|
503
|
+
mandatory: false,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (state.milestones.length > 0) {
|
|
507
|
+
const milestoneText = state.milestones
|
|
508
|
+
.map((m) => {
|
|
509
|
+
const lines = [
|
|
510
|
+
milestoneHeading(m),
|
|
511
|
+
` Outcome: ${m.outcome}`,
|
|
512
|
+
];
|
|
513
|
+
if (m.resolved_constraints.length > 0) {
|
|
514
|
+
lines.push(` Resolved: ${m.resolved_constraints.slice(0, 3).join("; ")}`);
|
|
515
|
+
}
|
|
516
|
+
return lines.join("\n");
|
|
283
517
|
})
|
|
284
518
|
.join("\n\n");
|
|
285
519
|
sections.push({
|
|
286
|
-
id: "
|
|
287
|
-
text: section("Phase History",
|
|
520
|
+
id: "milestones",
|
|
521
|
+
text: section("Phase History", milestoneText),
|
|
288
522
|
mandatory: false,
|
|
289
523
|
});
|
|
290
524
|
}
|
|
@@ -332,17 +566,10 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
332
566
|
mandatory: false,
|
|
333
567
|
});
|
|
334
568
|
}
|
|
335
|
-
if (state.
|
|
336
|
-
sections.push({
|
|
337
|
-
id: "loop_synthesis",
|
|
338
|
-
text: section("Loop Summary", state.loopSynthesis),
|
|
339
|
-
mandatory: false,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
if (state.suggestedNextAction) {
|
|
569
|
+
if (state.nextAction) {
|
|
343
570
|
sections.push({
|
|
344
|
-
id: "
|
|
345
|
-
text: section("
|
|
571
|
+
id: "next_action",
|
|
572
|
+
text: section("Next Action", state.nextAction),
|
|
346
573
|
mandatory: false,
|
|
347
574
|
});
|
|
348
575
|
}
|
|
@@ -359,7 +586,8 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
359
586
|
state.progress.criteriaMet.length > 0 ||
|
|
360
587
|
state.progress.criteriaRemaining.length > 0 ||
|
|
361
588
|
state.progress.filesChanged.length > 0 ||
|
|
362
|
-
state.progress.tests !== null
|
|
589
|
+
state.progress.tests !== null ||
|
|
590
|
+
state.machineStatus !== undefined) {
|
|
363
591
|
const lines = [];
|
|
364
592
|
const total = state.progress.criteriaMet.length + state.progress.criteriaRemaining.length;
|
|
365
593
|
if (total > 0) {
|
|
@@ -367,6 +595,22 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
367
595
|
}
|
|
368
596
|
if (state.progress.estimate !== null) {
|
|
369
597
|
lines.push(`**Estimated Completion**: ${(state.progress.estimate * 100).toFixed(0)}%`);
|
|
598
|
+
// v3.3: honest labeling — the estimate is the agent's own number.
|
|
599
|
+
lines.push("**Signal source**: self-reported estimate (unverified until machine-backed)");
|
|
600
|
+
}
|
|
601
|
+
// v3.3: machine side of the comparison — git motion over committed
|
|
602
|
+
// rounds (v3.6: the object always carries definite values — no
|
|
603
|
+
// "unavailable" arm).
|
|
604
|
+
if (state.machineStatus) {
|
|
605
|
+
const ms = state.machineStatus;
|
|
606
|
+
const motion = ms.gitMotion
|
|
607
|
+
? `changes in ${ms.motionRounds}/${ms.windowRounds} recent committed rounds`
|
|
608
|
+
: `no git changes in the last ${ms.windowRounds} committed rounds`;
|
|
609
|
+
lines.push(`**Machine (git)**: ${motion}`);
|
|
610
|
+
}
|
|
611
|
+
if (state.criterionStatuses.length > 0) {
|
|
612
|
+
const metCount = state.criterionStatuses.filter((cs) => cs.status === "met").length;
|
|
613
|
+
lines.push(`**Machine (criteria)**: ${metCount}/${state.criterionStatuses.length} met across committed rounds`);
|
|
370
614
|
}
|
|
371
615
|
if (state.progress.tests) {
|
|
372
616
|
lines.push(`**Tests**: ${state.progress.tests.passed} passed, ` +
|
|
@@ -380,12 +624,54 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
380
624
|
lines.push(`- ... and ${state.progress.filesChanged.length - 10} more`);
|
|
381
625
|
}
|
|
382
626
|
}
|
|
627
|
+
// v3.2: Goal → criteria → evidence vertical view — per-criterion status
|
|
628
|
+
// with the round it was met and linked sub-goals. IDs follow
|
|
629
|
+
// constraint_id_enabled like every other ID-rendering path.
|
|
630
|
+
if (state.criterionStatuses.length > 0) {
|
|
631
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
632
|
+
lines.push("", "**Goal → Criteria**:");
|
|
633
|
+
for (const cs of state.criterionStatuses) {
|
|
634
|
+
const icon = cs.status === "met" ? "✅" : cs.status === "remaining" ? "⬜" : "❔";
|
|
635
|
+
const idTag = idEnabled ? ` [\`${cs.id}\`]` : "";
|
|
636
|
+
const met = cs.met_at_round !== undefined ? `(met R${cs.met_at_round})` : "";
|
|
637
|
+
const related = cs.related_subgoal_ids.length > 0
|
|
638
|
+
? ` [↔ ${cs.related_subgoal_ids.join(", ")}]`
|
|
639
|
+
: "";
|
|
640
|
+
lines.push(`- ${icon}${idTag} ${cs.text} ${met}${related}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
383
643
|
sections.push({
|
|
384
644
|
id: "progress",
|
|
385
645
|
text: section("Progress Dashboard", lines.join("\n")),
|
|
386
646
|
mandatory: false,
|
|
387
647
|
});
|
|
388
648
|
}
|
|
649
|
+
// v3.3: Per-round statistics — lets the agent calibrate round granularity
|
|
650
|
+
// (files per round, rejected attempts, self-reported progress deltas).
|
|
651
|
+
// v3.6: source-labeled — files/Δ are agent-reported; only rejected
|
|
652
|
+
// attempts (and the Machine (git) row above) are machine-recorded.
|
|
653
|
+
if (state.roundStats && state.roundStats.length > 0) {
|
|
654
|
+
const statLines = state.roundStats.map((stat) => {
|
|
655
|
+
const parts = [];
|
|
656
|
+
if (stat.filesChangedCount !== null) {
|
|
657
|
+
parts.push(`${stat.filesChangedCount} file${stat.filesChangedCount === 1 ? "" : "s"}`);
|
|
658
|
+
}
|
|
659
|
+
if (stat.rejectedAttempts !== null && stat.rejectedAttempts > 0) {
|
|
660
|
+
parts.push(`${stat.rejectedAttempts} rejected attempt${stat.rejectedAttempts === 1 ? "" : "s"}`);
|
|
661
|
+
}
|
|
662
|
+
if (stat.progressDelta !== null) {
|
|
663
|
+
parts.push(`Δ${stat.progressDelta >= 0 ? "+" : ""}${stat.progressDelta.toFixed(2)}`);
|
|
664
|
+
}
|
|
665
|
+
return `- R${stat.round}: ${parts.length > 0 ? parts.join(", ") : "no data"}`;
|
|
666
|
+
});
|
|
667
|
+
statLines.push("> files & Δ are agent-reported; rejected attempts are machine-recorded " +
|
|
668
|
+
"(the Machine (git) row above is machine-observed)");
|
|
669
|
+
sections.push({
|
|
670
|
+
id: "round_stats",
|
|
671
|
+
text: section("Round Stats", statLines.join("\n")),
|
|
672
|
+
mandatory: false,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
389
675
|
// Retired Constraints
|
|
390
676
|
if (state.retiredConstraints.length > 0) {
|
|
391
677
|
const lines = state.retiredConstraints.map((c) => `- ~${c}~ (retired)`);
|
|
@@ -419,11 +705,8 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
419
705
|
}
|
|
420
706
|
// Agent Trust Score + Trend
|
|
421
707
|
if (state.agentTrustScore !== undefined) {
|
|
422
|
-
const barLen = 10;
|
|
423
|
-
const filled = Math.round(state.agentTrustScore * barLen);
|
|
424
|
-
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
425
708
|
const lines = [
|
|
426
|
-
|
|
709
|
+
trustBarLine(state.agentTrustScore),
|
|
427
710
|
];
|
|
428
711
|
if (state.agentTrustTrend.length > 0) {
|
|
429
712
|
const avg = (state.agentTrustTrend.reduce((a, b) => a + b, 0)
|
|
@@ -439,27 +722,22 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
439
722
|
}
|
|
440
723
|
return sections;
|
|
441
724
|
}
|
|
442
|
-
function selectSections(input) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
: input.level === "l0" ? l0Sections(input.state)
|
|
446
|
-
: input.level === "l1" ? l1Sections(input.state)
|
|
447
|
-
: l2Sections(input.state, input.fullStateMarkdown);
|
|
448
|
-
if (input.agentIntent?.trim()) {
|
|
449
|
-
sections.push({
|
|
450
|
-
id: "agent_intent",
|
|
451
|
-
text: section("Agent Intent (non-authoritative)", `${input.agentIntent.trim()}\nRuntime-derived Focus, Todo and Suggested Next Action take precedence.`),
|
|
452
|
-
mandatory: false,
|
|
453
|
-
});
|
|
725
|
+
function selectSections(input, emphasized) {
|
|
726
|
+
if (input.mode === "full") {
|
|
727
|
+
return l2Sections(input.state, input.fullStateMarkdown);
|
|
454
728
|
}
|
|
455
|
-
|
|
729
|
+
if (input.level === "l0")
|
|
730
|
+
return l0Sections(input.state);
|
|
731
|
+
if (input.level === "l1") {
|
|
732
|
+
return l1Sections(input.state, input.presentedBaseline ?? null, emphasized);
|
|
733
|
+
}
|
|
734
|
+
return l2Sections(input.state, input.fullStateMarkdown);
|
|
456
735
|
}
|
|
457
736
|
function renderWithinBudget(sections, fixedText, budget) {
|
|
458
737
|
const mandatory = sections.filter((item) => item.mandatory);
|
|
459
738
|
const optional = sections.filter((item) => !item.mandatory);
|
|
460
739
|
let rendered = fixedText + mandatory.map((item) => item.text).join("");
|
|
461
740
|
const included = mandatory.filter((item) => item.text).map((item) => item.id);
|
|
462
|
-
const omitted = [];
|
|
463
741
|
for (const item of optional) {
|
|
464
742
|
if (!item.text)
|
|
465
743
|
continue;
|
|
@@ -467,11 +745,8 @@ function renderWithinBudget(sections, fixedText, budget) {
|
|
|
467
745
|
rendered += item.text;
|
|
468
746
|
included.push(item.id);
|
|
469
747
|
}
|
|
470
|
-
else {
|
|
471
|
-
omitted.push(item.id);
|
|
472
|
-
}
|
|
473
748
|
}
|
|
474
|
-
return { rendered, included
|
|
749
|
+
return { rendered, included };
|
|
475
750
|
}
|
|
476
751
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
477
752
|
// v2.9: Prompt Requests — model-expressed information needs
|
|
@@ -483,13 +758,12 @@ function renderWithinBudget(sections, fixedText, budget) {
|
|
|
483
758
|
function matchEmphasize(targets, candidates, cap) {
|
|
484
759
|
if (targets.length === 0 || candidates.length === 0)
|
|
485
760
|
return [];
|
|
486
|
-
const idRegex = /^(c|cr|sg)-[a-f0-9]{8}$/;
|
|
487
761
|
const scored = candidates.map((c) => {
|
|
488
762
|
let best = 0;
|
|
489
763
|
for (const t of targets) {
|
|
490
764
|
let s = 0;
|
|
491
765
|
// Phase 1: exact ID match (v2.11)
|
|
492
|
-
const idMatch = t.match(
|
|
766
|
+
const idMatch = t.match(STABLE_ID_RE);
|
|
493
767
|
if (idMatch) {
|
|
494
768
|
const candidateId = idMatch[1] + "-" + deriveItemId(c);
|
|
495
769
|
if (candidateId === t)
|
|
@@ -511,7 +785,7 @@ function matchEmphasize(targets, candidates, cap) {
|
|
|
511
785
|
}
|
|
512
786
|
/** Render the "Confusion Alerts" section that appears at the prompt top.
|
|
513
787
|
* Compiler auto-matches each confusion point against state sections. */
|
|
514
|
-
function renderConfusionAlerts(points, state,
|
|
788
|
+
function renderConfusionAlerts(points, state, fullDensity) {
|
|
515
789
|
if (points.length === 0)
|
|
516
790
|
return "";
|
|
517
791
|
const lines = [
|
|
@@ -530,9 +804,12 @@ function renderConfusionAlerts(points, state, expandAll) {
|
|
|
530
804
|
{ keyword: "criteria", label: "Success Criteria", ref: "## Success Criteria" },
|
|
531
805
|
{ keyword: "phase", label: "Phase History", ref: "## Phase History" },
|
|
532
806
|
{ keyword: "objective", label: "Loop Objective", ref: "## Loop Objective" },
|
|
533
|
-
{ keyword: "loop", label: "Loop Summary", ref: "## Loop Summary" },
|
|
534
807
|
];
|
|
535
|
-
|
|
808
|
+
// v2.14: L2 renders up to the policy cap (was: every confusion point,
|
|
809
|
+
// unbounded by max_confusion_points); L1 keeps a single alert.
|
|
810
|
+
const shown = fullDensity
|
|
811
|
+
? points.slice(0, getPolicy().prompt.max_confusion_points)
|
|
812
|
+
: points.slice(0, 1);
|
|
536
813
|
for (const point of shown) {
|
|
537
814
|
const truncated = point.length > 200 ? point.slice(0, 197) + "…" : point;
|
|
538
815
|
// Find best-matching state section
|
|
@@ -545,14 +822,14 @@ function renderConfusionAlerts(points, state, expandAll) {
|
|
|
545
822
|
bestSection = sec;
|
|
546
823
|
}
|
|
547
824
|
}
|
|
548
|
-
const pointer = bestSection && bestScore >=
|
|
825
|
+
const pointer = bestSection && bestScore >= getPolicy().prompt.confusion_section_threshold
|
|
549
826
|
? `→ Check **${bestSection.label}** (\`${bestSection.ref}\`) in the state file.`
|
|
550
827
|
: "→ Read the full state file for relevant context.";
|
|
551
828
|
lines.push(`- **"${truncated}"**`);
|
|
552
829
|
lines.push(` ${pointer}`);
|
|
553
830
|
lines.push("");
|
|
554
831
|
}
|
|
555
|
-
if (!
|
|
832
|
+
if (!fullDensity && points.length > 1) {
|
|
556
833
|
lines.push(`*… and ${points.length - 1} more confusion point(s) — see state file.*`);
|
|
557
834
|
lines.push("");
|
|
558
835
|
}
|
|
@@ -575,105 +852,6 @@ function renderCriticalContext(emphasized, state) {
|
|
|
575
852
|
lines.push("");
|
|
576
853
|
return lines.join("\n");
|
|
577
854
|
}
|
|
578
|
-
/** Expand a specific section to L2-level detail within a L1 prompt.
|
|
579
|
-
* Returns the expanded section text, or empty string if the section
|
|
580
|
-
* has no content to expand. */
|
|
581
|
-
function renderExpandedSection(sectionName, state) {
|
|
582
|
-
switch (sectionName) {
|
|
583
|
-
case "milestones": {
|
|
584
|
-
if (state.phaseHistory.length === 0)
|
|
585
|
-
return "";
|
|
586
|
-
const text = state.phaseHistory
|
|
587
|
-
.map((phase) => {
|
|
588
|
-
const range = phase.closedAtRound
|
|
589
|
-
? `R${phase.enteredAtRound}-R${phase.closedAtRound}`
|
|
590
|
-
: `R${phase.enteredAtRound}-current`;
|
|
591
|
-
return `**${phase.label}** (${range}, ${phase.status})\n Outcome: ${phase.outcome}`;
|
|
592
|
-
})
|
|
593
|
-
.join("\n\n");
|
|
594
|
-
return section("Expanded Phase History", text);
|
|
595
|
-
}
|
|
596
|
-
case "sub_goals": {
|
|
597
|
-
if (state.subGoals.length === 0)
|
|
598
|
-
return "";
|
|
599
|
-
const lines = [];
|
|
600
|
-
for (const sg of state.subGoals) {
|
|
601
|
-
const icon = sg.status === "in_progress" ? "🔄" :
|
|
602
|
-
sg.status === "done" ? "✅" :
|
|
603
|
-
sg.status === "blocked" ? "🚫" :
|
|
604
|
-
sg.status === "canceled" ? "❌" : "⏳";
|
|
605
|
-
const stale = sg.status === "pending" && state.round - sg.declared_at_round >= 10 ? " ⚠️ stale" : "";
|
|
606
|
-
const detail = sg.status === "done" && sg.completed_at_round
|
|
607
|
-
? ` (done, R${sg.completed_at_round})`
|
|
608
|
-
: sg.status === "in_progress" ? ` (since R${sg.status_changed_at_round})`
|
|
609
|
-
: sg.status === "pending" ? ` (since R${sg.declared_at_round})`
|
|
610
|
-
: sg.status === "blocked" ? ` (blocked R${sg.status_changed_at_round})`
|
|
611
|
-
: ` (canceled R${sg.status_changed_at_round})`;
|
|
612
|
-
lines.push(`- ${icon} [\`${sg.id}\`] ${sg.description}${detail}${stale}`);
|
|
613
|
-
}
|
|
614
|
-
return section("Expanded Sub-Goal Dashboard", lines.join("\n"));
|
|
615
|
-
}
|
|
616
|
-
case "constraint_lifecycle": {
|
|
617
|
-
if (state.inactiveConstraints.length === 0 && state.retiredConstraints.length === 0)
|
|
618
|
-
return "";
|
|
619
|
-
const lines = [];
|
|
620
|
-
if (state.inactiveConstraints.length > 0) {
|
|
621
|
-
lines.push("**Inactive (auto-decayed):**");
|
|
622
|
-
for (const c of state.inactiveConstraints) {
|
|
623
|
-
const meta = state.constraintMetadata.find((m) => m.text === c);
|
|
624
|
-
const age = meta ? state.round - (meta.last_violated_at_round || meta.discovered_at_round || 0) : "?";
|
|
625
|
-
lines.push(`- ${c} (${age} rounds inactive)`);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
if (state.retiredConstraints.length > 0) {
|
|
629
|
-
lines.push("**Retired:**");
|
|
630
|
-
for (const c of state.retiredConstraints)
|
|
631
|
-
lines.push(`- ~${c}~`);
|
|
632
|
-
}
|
|
633
|
-
return section("Constraint Lifecycle", lines.join("\n"));
|
|
634
|
-
}
|
|
635
|
-
case "agent_trust": {
|
|
636
|
-
if (state.agentTrustScore === undefined)
|
|
637
|
-
return "";
|
|
638
|
-
const barLen = 10;
|
|
639
|
-
const filled = Math.round(state.agentTrustScore * barLen);
|
|
640
|
-
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
641
|
-
const lines = [
|
|
642
|
-
`${bar} ${(state.agentTrustScore * 100).toFixed(0)}%`,
|
|
643
|
-
];
|
|
644
|
-
if (state.agentTrustTrend.length > 0) {
|
|
645
|
-
lines.push(`Trend: ${state.agentTrustTrend.join(" → ")}`);
|
|
646
|
-
}
|
|
647
|
-
return section("Agent Trust (Expanded)", lines.join("\n"));
|
|
648
|
-
}
|
|
649
|
-
case "progress": {
|
|
650
|
-
const p = state.progress;
|
|
651
|
-
if (p.estimate === null && p.criteriaMet.length === 0 && p.criteriaRemaining.length === 0)
|
|
652
|
-
return "";
|
|
653
|
-
const lines = [];
|
|
654
|
-
const total = p.criteriaMet.length + p.criteriaRemaining.length;
|
|
655
|
-
if (total > 0)
|
|
656
|
-
lines.push(`**Criteria**: ${p.criteriaMet.length}/${total} met`);
|
|
657
|
-
if (p.estimate !== null)
|
|
658
|
-
lines.push(`**Estimate**: ${(p.estimate * 100).toFixed(0)}%`);
|
|
659
|
-
if (p.tests)
|
|
660
|
-
lines.push(`**Tests**: ${p.tests.passed} passed, ${p.tests.failed} failed, ${p.tests.skipped} skipped`);
|
|
661
|
-
if (p.filesChanged.length > 0) {
|
|
662
|
-
lines.push("**Files Changed**:");
|
|
663
|
-
for (const f of p.filesChanged.slice(0, 10))
|
|
664
|
-
lines.push(`- ${f}`);
|
|
665
|
-
}
|
|
666
|
-
return section("Progress Dashboard (Expanded)", lines.join("\n"));
|
|
667
|
-
}
|
|
668
|
-
case "loop_synthesis": {
|
|
669
|
-
if (!state.loopSynthesis)
|
|
670
|
-
return "";
|
|
671
|
-
return section("Loop Summary", state.loopSynthesis);
|
|
672
|
-
}
|
|
673
|
-
default:
|
|
674
|
-
return "";
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
855
|
export function assemblePromptArtifact(input) {
|
|
678
856
|
const mode = input.mode ?? "adaptive";
|
|
679
857
|
const budgets = { ...DEFAULT_PROMPT_BUDGETS, ...input.budgets };
|
|
@@ -686,7 +864,6 @@ export function assemblePromptArtifact(input) {
|
|
|
686
864
|
// than relying on potentially-compacted conversation history.
|
|
687
865
|
const fullContextReasons = new Set([
|
|
688
866
|
"first_round", "plan_boundary", "checkpoint_boundary", "goal_changed",
|
|
689
|
-
"constraint_changed",
|
|
690
867
|
"missing_previous_state", "verification_contradicted",
|
|
691
868
|
"rejection_rehydrate", "recovery_boundary", "state_drift",
|
|
692
869
|
"periodic_refresh",
|
|
@@ -695,8 +872,13 @@ export function assemblePromptArtifact(input) {
|
|
|
695
872
|
|| input.mode === "pointer"
|
|
696
873
|
|| input.reasons.some((r) => fullContextReasons.has(r));
|
|
697
874
|
const stateFilePath = input.state.stateFilePath;
|
|
875
|
+
// v3.2: L1 annotates the pointer with the round the freshly-regenerated
|
|
876
|
+
// state file describes (it is rewritten every compile); L0/L2 keep the
|
|
877
|
+
// plain pointer byte-identical.
|
|
698
878
|
const stateFileRef = stateFilePath
|
|
699
|
-
?
|
|
879
|
+
? (input.level === "l1"
|
|
880
|
+
? `📄 Full state: \`${stateFilePath}\` (updated round ${input.state.round})\n`
|
|
881
|
+
: `📄 Full state: \`${stateFilePath}\`\n`)
|
|
700
882
|
: "";
|
|
701
883
|
const readInstruction = needsFullContext && stateFilePath
|
|
702
884
|
? [
|
|
@@ -716,13 +898,11 @@ export function assemblePromptArtifact(input) {
|
|
|
716
898
|
readInstruction,
|
|
717
899
|
"",
|
|
718
900
|
].join("\n");
|
|
719
|
-
const sections = selectSections(input);
|
|
720
901
|
const pointer = stateFilePath
|
|
721
902
|
? section("Full State", stateFilePath)
|
|
722
903
|
: "";
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
? `${reportBlock.trim()}\n`
|
|
904
|
+
const evaluation = input.selfEvaluationBlock.trim()
|
|
905
|
+
? `${input.selfEvaluationBlock.trim()}\n`
|
|
726
906
|
: "";
|
|
727
907
|
const howToRespond = [
|
|
728
908
|
"## How to Complete This Round",
|
|
@@ -730,20 +910,24 @@ export function assemblePromptArtifact(input) {
|
|
|
730
910
|
"1. Execute the task described above. Do NOT generate another prompt or plan — act.",
|
|
731
911
|
"2. When finished, call the MCP tool **`loopforge_next`** with these parameters:",
|
|
732
912
|
" - `sessionId`: the session ID from `loopforge_start`.",
|
|
733
|
-
" - `
|
|
913
|
+
" - `evaluation`: a structured self-assessment object (see schema below).",
|
|
734
914
|
"",
|
|
735
915
|
"### Evaluation Rules",
|
|
736
916
|
"",
|
|
737
|
-
"-
|
|
738
|
-
"
|
|
739
|
-
"-
|
|
740
|
-
"
|
|
917
|
+
"- `success`: Set to **`true` ONLY** when ALL hard constraints are met AND",
|
|
918
|
+
" the task goal is fully achieved. If anything remains incomplete, set `false`.",
|
|
919
|
+
"- `should_continue`: Set to **`false` ONLY** when the ENTIRE loop task is done.",
|
|
920
|
+
" Partial progress or completed subtasks → `true`.",
|
|
921
|
+
"- `constraint_violations`: Be honest. List every constraint you actually violated.",
|
|
922
|
+
"- `execution_evidence.files_changed`: List files you modified — used for verification.",
|
|
923
|
+
"- `execution_evidence.test_results`: Report actual test runner output.",
|
|
924
|
+
"- `progress_estimate`: A number 0.0–1.0 reflecting overall task completion.",
|
|
741
925
|
"",
|
|
742
926
|
"### If the Prompt Says \"REJECTED\"",
|
|
743
927
|
"",
|
|
744
928
|
"- Re-execute the **same round**. Do NOT advance the round counter.",
|
|
745
929
|
"- Read the Required Fix section, address every issue, then submit again.",
|
|
746
|
-
"- Use `loopforge_next` with a corrected
|
|
930
|
+
"- Use `loopforge_next` with a corrected evaluation.",
|
|
747
931
|
"",
|
|
748
932
|
evaluation,
|
|
749
933
|
].join("\n");
|
|
@@ -768,12 +952,45 @@ export function assemblePromptArtifact(input) {
|
|
|
768
952
|
...input.state.changesSinceLastRound,
|
|
769
953
|
], emphasizeCap)
|
|
770
954
|
: [];
|
|
955
|
+
// v2.14: emphasize is a pure reorder (zero token overhead) — the matched
|
|
956
|
+
// items are MOVED to the Critical Context section, not duplicated. Filter
|
|
957
|
+
// them out of the source arrays before section rendering; unmatched
|
|
958
|
+
// emphasizes are dropped (nothing to reorder → nothing rendered).
|
|
959
|
+
// v3.2.1: hard constraints and success criteria live in BOTH their own
|
|
960
|
+
// sections (Active Hard Constraints / Success Criteria) and the active
|
|
961
|
+
// constraint list (loop-compiler merges objective.hard_constraints and
|
|
962
|
+
// success_criteria into constraints_active), so they must be filtered
|
|
963
|
+
// from BOTH sources too — otherwise the emphasized item renders twice.
|
|
964
|
+
const emphasizedSet = new Set(emphasized);
|
|
965
|
+
const stateForSections = emphasizedSet.size > 0
|
|
966
|
+
? {
|
|
967
|
+
...input.state,
|
|
968
|
+
activeConstraints: input.state.activeConstraints.filter((item) => !emphasizedSet.has(item)),
|
|
969
|
+
hardConstraints: input.state.hardConstraints.filter((item) => !emphasizedSet.has(item)),
|
|
970
|
+
successCriteria: input.state.successCriteria.filter((item) => !emphasizedSet.has(item)),
|
|
971
|
+
discoveries: input.state.discoveries.filter((item) => !emphasizedSet.has(item)),
|
|
972
|
+
blockers: input.state.blockers.filter((item) => !emphasizedSet.has(item)),
|
|
973
|
+
changesSinceLastRound: input.state.changesSinceLastRound.filter((item) => !emphasizedSet.has(item)),
|
|
974
|
+
}
|
|
975
|
+
: input.state;
|
|
976
|
+
const sections = selectSections({ ...input, state: stateForSections }, emphasizedSet);
|
|
771
977
|
const criticalContextText = renderCriticalContext(emphasized, input.state);
|
|
772
|
-
//
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
978
|
+
// v3.2: L1 only — snapshot what this prompt actually presented, so the
|
|
979
|
+
// NEXT L1 compile can diff against it (persisted via PromptArtifact →
|
|
980
|
+
// lineage). L0/L2 leave it undefined: their presentation semantics differ
|
|
981
|
+
// (L2's dashboard includes done/canceled sub-goals, which would
|
|
982
|
+
// contaminate the L1 active-set diff).
|
|
983
|
+
let presentedState;
|
|
984
|
+
if (levelIsL1) {
|
|
985
|
+
const activeSubsForSnapshot = stateForSections.subGoals.filter((sg) => sg.status === "in_progress" || sg.status === "pending");
|
|
986
|
+
presentedState = {
|
|
987
|
+
round: input.state.round,
|
|
988
|
+
constraintIds: activeNonHardConstraints(stateForSections)
|
|
989
|
+
.map((text) => constraintIdOf(text, stateForSections.constraintMetadata)),
|
|
990
|
+
subGoals: activeSubsForSnapshot.map((sg) => [sg.id, sg.status]),
|
|
991
|
+
milestoneRanges: input.state.milestones.map((m) => [m.round_range.start, m.round_range.end]),
|
|
992
|
+
};
|
|
993
|
+
}
|
|
777
994
|
// Inject confusion alerts into the header area (before mandatory sections)
|
|
778
995
|
const fixedText = header + confusionText;
|
|
779
996
|
// Inject critical context as a mandatory section
|
|
@@ -790,19 +1007,18 @@ export function assemblePromptArtifact(input) {
|
|
|
790
1007
|
}
|
|
791
1008
|
const selected = mode === "pointer"
|
|
792
1009
|
? renderWithinBudget(augmentedSections.filter((item) => item.mandatory), fixedText, budget)
|
|
793
|
-
: renderWithinBudget(augmentedSections, fixedText, budget - footer.length
|
|
794
|
-
const renderedPrompt = selected.rendered +
|
|
1010
|
+
: renderWithinBudget(augmentedSections, fixedText, budget - footer.length);
|
|
1011
|
+
const renderedPrompt = selected.rendered + footer;
|
|
795
1012
|
const includedSections = [
|
|
796
1013
|
...selected.included,
|
|
797
1014
|
...(confusionText ? ["confusion_alerts"] : []),
|
|
798
1015
|
...(criticalContextText ? ["critical_context"] : []),
|
|
799
|
-
...(expandedSection ? [`expand:${pr?.expand?.[0] ?? ""}`] : []),
|
|
800
1016
|
...(pointer ? ["state_pointer"] : []),
|
|
801
1017
|
...(evaluation ? ["self_evaluation"] : []),
|
|
802
1018
|
];
|
|
803
1019
|
const promptHash = createHash("sha256").update(renderedPrompt).digest("hex");
|
|
804
1020
|
return {
|
|
805
|
-
|
|
1021
|
+
schemaVersion: PROMPT_ARTIFACT_SCHEMA_VERSION,
|
|
806
1022
|
roundId: `loop:${input.state.loopId}:round:${input.state.round}`,
|
|
807
1023
|
attempt,
|
|
808
1024
|
level: input.level,
|
|
@@ -812,18 +1028,11 @@ export function assemblePromptArtifact(input) {
|
|
|
812
1028
|
stateHash,
|
|
813
1029
|
basePromptVersion: BASE_PROMPT_VERSION,
|
|
814
1030
|
includedSections,
|
|
815
|
-
omittedSections: selected.omitted,
|
|
816
1031
|
budgetChars: budget,
|
|
817
1032
|
charCount: renderedPrompt.length,
|
|
818
1033
|
budgetExceeded: renderedPrompt.length > budget,
|
|
819
|
-
// lastL2Round / lastMeaningfulProgressRound / stateAgeRounds are NOT
|
|
820
|
-
// assembled here: the assembler has no vault history and can only guess,
|
|
821
|
-
// so the values were always immediately overwritten by the compiler's
|
|
822
|
-
// vault-derived backfill. They stay undefined at this layer.
|
|
823
|
-
phaseId: input.state.phaseHistory.at(-1)?.id,
|
|
824
|
-
focusId: input.state.todoProjection?.sourceId,
|
|
825
|
-
todoId: input.state.todoProjection?.id,
|
|
826
1034
|
generatedAt: Date.now(),
|
|
1035
|
+
...(presentedState ? { presentedState } : {}),
|
|
827
1036
|
};
|
|
828
1037
|
}
|
|
829
1038
|
//# sourceMappingURL=prompt-assembler.js.map
|