loopforge 3.9.1 → 3.9.4
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 +11 -8
- package/dist/canonical-state.d.ts +39 -1
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +114 -32
- package/dist/canonical-state.js.map +1 -1
- package/dist/committed-round.d.ts +27 -4
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +87 -14
- package/dist/committed-round.js.map +1 -1
- package/dist/enforcement-gate.d.ts +9 -32
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +9 -122
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +13 -2
- package/dist/engine.js.map +1 -1
- package/dist/explain.d.ts +1 -0
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +1 -0
- package/dist/explain.js.map +1 -1
- package/dist/files-reconciliation.d.ts +18 -0
- package/dist/files-reconciliation.d.ts.map +1 -0
- package/dist/files-reconciliation.js +59 -0
- package/dist/files-reconciliation.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +17 -1
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +220 -119
- package/dist/loop-compiler.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +31 -1
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +102 -60
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +19 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +23 -5
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +1 -1
- package/dist/mcp/submission-boundary.d.ts.map +1 -1
- package/dist/mcp/submission-boundary.js +31 -8
- package/dist/mcp/submission-boundary.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +62 -6
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.d.ts +4 -0
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +17 -3
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +5 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +2 -2
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +8 -3
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +233 -240
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +45 -0
- package/dist/prompt-coverage.d.ts.map +1 -0
- package/dist/prompt-coverage.js +84 -0
- package/dist/prompt-coverage.js.map +1 -0
- package/dist/prompt-freshness.d.ts +36 -0
- package/dist/prompt-freshness.d.ts.map +1 -0
- package/dist/prompt-freshness.js +83 -0
- package/dist/prompt-freshness.js.map +1 -0
- package/dist/prompt-policy.d.ts +5 -2
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +10 -7
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +70 -8
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +7 -1
- package/dist/protocol.js.map +1 -1
- package/dist/round-coordinator.d.ts +4 -2
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +7 -7
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-facts.d.ts.map +1 -1
- package/dist/round-facts.js +9 -3
- package/dist/round-facts.js.map +1 -1
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +19 -1
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +23 -7
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +88 -16
- package/dist/self-eval.js.map +1 -1
- package/dist/submission-guidance.d.ts +36 -4
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +225 -78
- package/dist/submission-guidance.js.map +1 -1
- package/dist/submission-receipt.d.ts.map +1 -1
- package/dist/submission-receipt.js +6 -0
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +26 -0
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +56 -6
- package/dist/token-utils.js.map +1 -1
- package/dist/verification-actions.d.ts +13 -0
- package/dist/verification-actions.d.ts.map +1 -0
- package/dist/verification-actions.js +45 -0
- package/dist/verification-actions.js.map +1 -0
- package/dist/verification-gate.d.ts +5 -4
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +32 -45
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/loop_policy.json +58 -58
- package/package.json +68 -68
- package/skills/perception/SKILL.md +35 -11
package/dist/prompt-assembler.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
* alone are over it — truncation of mandatory content never happens.
|
|
11
11
|
*/
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
|
-
import {
|
|
13
|
+
import { buildPhaseLine, formatActiveSubGoals, formatCriterionRows, hashCanonicalState, milestoneHeading, partitionActiveConstraints, } from "./canonical-state.js";
|
|
14
14
|
import { PROMPT_ARTIFACT_SCHEMA_VERSION } from "./protocol.js";
|
|
15
|
-
import { deriveItemId, STABLE_ID_RE, normalizeText } from "./token-utils.js";
|
|
15
|
+
import { deriveCriterionId, deriveItemId, findingKey, STABLE_ID_RE, normalizeText, } from "./token-utils.js";
|
|
16
16
|
import { getPolicy } from "./policy.js";
|
|
17
|
+
import { verificationActionFor } from "./verification-actions.js";
|
|
18
|
+
import { assertPromptCoverage, derivePromptCoverage, } from "./prompt-coverage.js";
|
|
17
19
|
export const DEFAULT_PROMPT_BUDGETS = {
|
|
18
20
|
l0: 5000,
|
|
19
21
|
l1: 10000,
|
|
@@ -25,88 +27,19 @@ function section(title, body) {
|
|
|
25
27
|
function bullets(values, prefix = "- ") {
|
|
26
28
|
return values.map((value) => `${prefix}${value}`).join("\n");
|
|
27
29
|
}
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* are rendered as-is. v3.8.1: IDs are always rendered — the
|
|
33
|
-
* `constraint_id_enabled` escape hatch is gone. */
|
|
34
|
-
function bulletsWithIds(values, prefix) {
|
|
35
|
-
return values.map((v) => {
|
|
36
|
-
const trimmed = v.trim();
|
|
37
|
-
if (STABLE_ID_RE.test(trimmed)) {
|
|
38
|
-
return `- [\`${trimmed}\`] ${trimmed}`;
|
|
39
|
-
}
|
|
40
|
-
const id = prefix + "-" + deriveItemId(trimmed);
|
|
41
|
-
return `- [\`${id}\`] ${trimmed}`;
|
|
42
|
-
}).join("\n");
|
|
43
|
-
}
|
|
44
|
-
/** v3.3.1: Render an identity-HETEROGENEOUS list — the merged active
|
|
45
|
-
* constraint set holds hard/plan/criteria/discovered texts together, so no
|
|
46
|
-
* single prefix fits. Each item's authoritative ID comes from constraint
|
|
47
|
-
* metadata (criteria texts carry cr-XXXXXXXX, everything else
|
|
48
|
-
* c-XXXXXXXX), keeping the active list in the same identity namespace as
|
|
49
|
-
* the item's own section — one text never renders under two prefixes in
|
|
50
|
-
* one prompt. Falls back to a c- derivation only when metadata is missing
|
|
51
|
-
* (defensive: metadata covers every active text by construction). */
|
|
52
|
-
function bulletsWithIdentities(values, metadata) {
|
|
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
|
-
}
|
|
63
|
-
/** v3.2: Short imperative fix/action per verification check, appended to the
|
|
64
|
-
* L1/L2 Verification Gate lines. Deliberately shorter than the rejection-
|
|
65
|
-
* prompt wording in enforcement-gate.ts; keys are the plain check-name
|
|
66
|
-
* strings (NOT imported from verification-gate.ts — it already imports
|
|
67
|
-
* loop-compiler, which imports this module, so importing back would cycle).
|
|
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). */
|
|
71
|
-
const VERIFICATION_ACTIONS = {
|
|
72
|
-
success_with_remaining_criteria: "Complete the remaining criteria before claiming success, or set success=false and report what remains.",
|
|
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_report and declare no_change_reason.",
|
|
74
|
-
outcome_success_contradiction: "Reconcile the declared outcome with the success flag and resubmit an honest evaluation.",
|
|
75
|
-
blocked_without_blocker: "State the concrete blocker, or drop the blocked outcome.",
|
|
76
|
-
retroactive_claim_bad_round: "Correct the referenced round number, or withdraw the retroactive claim.",
|
|
77
|
-
retroactive_claim_unverified: "Provide evidence for the retroactive claim, or withdraw it.",
|
|
78
|
-
duplicate_constraint_discovery: "Update the existing constraint entry instead of duplicating it.",
|
|
79
|
-
recurring_violation: "Explain why this violation keeps recurring and try a different approach — do not repeat the failed strategy.",
|
|
80
|
-
retract_fresh_constraint: "Justify the retraction explicitly, or restore the constraint.",
|
|
81
|
-
evidence_integrity: "Correct the mismatch between your claims and the recorded evidence.",
|
|
82
|
-
required_command_failed: "Re-run the required verification command and report its actual output before claiming success.",
|
|
83
|
-
command_evidence_mismatch: "Report the true command output and reconcile the mismatch.",
|
|
84
|
-
backtrack_workspace_not_restored: "Restore the skipped-round files and the expected git HEAD, then resubmit.",
|
|
85
|
-
criteria_claims_unverified: "Provide evidence for each claimed success criterion.",
|
|
86
|
-
// v3.3 — Round Contract checks (and the three v3.3 checks that predated
|
|
87
|
-
// this table entry and fell back to the generic action).
|
|
88
|
-
verification_entrypoint_modified: "Re-run the verification command on the changed entrypoint, or revert the entrypoint change and re-run.",
|
|
89
|
-
test_files_modified: "Re-run the verification command after the test-file changes and report the fresh output.",
|
|
90
|
-
round_scope_drift: "Revert the out-of-scope file changes, or close the active contract and declare the extended scope in a new proposal. Scope drift is a machine fact and is not waivable by explanation.",
|
|
91
|
-
// v3.8 — item model: claimed-but-unverified debt
|
|
92
|
-
contract_items_unverified: "Run the bound command(s) for each claimed item so they are observed passing, or report the item as remaining (or declare outcome=\"blocked\").",
|
|
93
|
-
contract_premature: "The ACTIVE contract is still open — restate it unchanged to continue it; a different contract is ignored until every item is verified or the round reports blocked.",
|
|
94
|
-
// v3.7.1 — opt-in gate layer: cited gates need an approved human decision
|
|
95
|
-
user_gate_unresolved: "The round cites a high-risk action without an approved human decision. Run loopforge_gate_check, present the approval question, call loopforge_gate_resolve after the human decides, and resubmit with the approved gate id in evaluation.gate_ids.",
|
|
96
|
-
};
|
|
97
|
-
/** Fallback for unmapped check names (forward-compat with future checks). */
|
|
98
|
-
const VERIFICATION_ACTION_FALLBACK = "Re-examine the flagged claim and correct your evaluation before resubmitting.";
|
|
99
|
-
/** v3.2: The actionable instruction for a verification check. Exported for
|
|
100
|
-
* the coverage test (which imports the CHECK_* constants). */
|
|
101
|
-
export function verificationActionFor(check) {
|
|
102
|
-
return VERIFICATION_ACTIONS[check] ?? VERIFICATION_ACTION_FALLBACK;
|
|
103
|
-
}
|
|
30
|
+
/** 3.9.3: The action table lives in a neutral module so the evaluation-field
|
|
31
|
+
* guidance can quote the same wording without depending on a renderer.
|
|
32
|
+
* Re-exported here for the existing CHECK_* coverage test. */
|
|
33
|
+
export { verificationActionFor } from "./verification-actions.js";
|
|
104
34
|
function verificationText(state, level) {
|
|
105
35
|
const lines = state.verificationFlags.map((flag) => {
|
|
106
36
|
const icon = flag.severity === "error"
|
|
107
37
|
? "🚫"
|
|
108
38
|
: flag.severity === "warn" ? "⚠️" : "ℹ️";
|
|
109
|
-
const
|
|
39
|
+
const ref = flag.ref ? ` [ref: \`${flag.ref}\`]` : "";
|
|
40
|
+
// v3.9.4: the finding names the affected field AND the concrete subject;
|
|
41
|
+
// the repair surface is a fact, not a next-action instruction.
|
|
42
|
+
const base = `- ${icon} [${flag.check}] \`${flag.field}\` — ${flag.detail}${ref}`;
|
|
110
43
|
// v3.2: error/warn flags get an actionable continuation line in L1/L2 —
|
|
111
44
|
// the verdict is translated into what to DO. L0 keeps the compact retry
|
|
112
45
|
// prompt byte-identical; info flags stay informational.
|
|
@@ -120,43 +53,132 @@ function verificationText(state, level) {
|
|
|
120
53
|
}
|
|
121
54
|
return lines.join("\n");
|
|
122
55
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
56
|
+
/** v3.9.4: the ONE identity of a text in the prompt's normative namespaces.
|
|
57
|
+
* A criterion keeps its cr-* even when it also appears in an active list;
|
|
58
|
+
* metadata supplies the id for everything else. */
|
|
59
|
+
function identityForText(state, text) {
|
|
60
|
+
// Criteria identity wins even if a stale/hand-built metadata row disagrees.
|
|
61
|
+
const status = state.criterionStatuses.find((criterion) => normalizeText(criterion.text) === normalizeText(text));
|
|
62
|
+
if (status)
|
|
63
|
+
return status.id;
|
|
64
|
+
if (state.successCriteria.some((criterion) => normalizeText(criterion) === normalizeText(text))) {
|
|
65
|
+
return deriveCriterionId(text);
|
|
66
|
+
}
|
|
67
|
+
const meta = state.constraintMetadata.find((entry) => entry.text === text);
|
|
68
|
+
if (meta)
|
|
69
|
+
return meta.id;
|
|
70
|
+
return `c-${deriveItemId(text)}`;
|
|
126
71
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
72
|
+
/** v3.9.4: active constraint rows with their real ids and the machine fact
|
|
73
|
+
* that this round violated them. Shared by the hard and other partitions. */
|
|
74
|
+
function constraintLines(state, texts) {
|
|
75
|
+
return texts.map((text) => {
|
|
76
|
+
const violated = state.constraintMetadata.find((meta) => meta.text === text)
|
|
77
|
+
?.last_violated_at_round === state.round;
|
|
78
|
+
return `- [\`${identityForText(state, text)}\`] ${text}` +
|
|
79
|
+
(violated ? " (violated this round)" : "");
|
|
80
|
+
}).join("\n");
|
|
81
|
+
}
|
|
82
|
+
/** v3.9.4: every active normative object the current prompt MUST render. */
|
|
83
|
+
function activeNormativeIds(state, recoveryBrief) {
|
|
84
|
+
const out = [];
|
|
85
|
+
const partition = partitionActiveConstraints(state);
|
|
86
|
+
for (const text of partition.hard) {
|
|
87
|
+
out.push({ id: identityForText(state, text), kind: "constraint" });
|
|
88
|
+
}
|
|
89
|
+
for (const text of partition.other) {
|
|
90
|
+
out.push({ id: identityForText(state, text), kind: "constraint" });
|
|
91
|
+
}
|
|
92
|
+
for (const text of partition.criteria) {
|
|
93
|
+
out.push({ id: identityForText(state, text), kind: "criterion" });
|
|
94
|
+
}
|
|
95
|
+
if (state.roundContract) {
|
|
96
|
+
out.push({ id: state.roundContract.id, kind: "contract" });
|
|
97
|
+
for (const item of state.roundContract.items) {
|
|
98
|
+
out.push({ id: item.id, kind: "contract_item" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const subGoal of state.subGoals) {
|
|
102
|
+
if (subGoal.status !== "done" && subGoal.status !== "canceled") {
|
|
103
|
+
out.push({ id: subGoal.id, kind: "subgoal" });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const flag of state.verificationFlags) {
|
|
107
|
+
out.push({ id: findingKey(flag), kind: "finding" });
|
|
108
|
+
}
|
|
109
|
+
if (recoveryBrief && recoveryBrief.length > 0) {
|
|
110
|
+
out.push({ id: "recovery", kind: "recovery" });
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
135
113
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
114
|
+
/** v3.9.4: the normative sections shared by L0/L1/L2. Levels change ONLY the
|
|
115
|
+
* density of narrative sections; they never change which active normative
|
|
116
|
+
* ids are rendered. Every section here is mandatory (protected). */
|
|
117
|
+
function commonMandatorySections(state, level, recoveryBrief) {
|
|
118
|
+
const partition = partitionActiveConstraints(state);
|
|
139
119
|
const sections = [
|
|
140
120
|
{
|
|
141
121
|
id: "objective",
|
|
142
122
|
text: section("Objective", state.objective),
|
|
143
123
|
mandatory: true,
|
|
124
|
+
coverageIds: [],
|
|
144
125
|
},
|
|
145
126
|
{
|
|
146
127
|
id: "current_task",
|
|
147
128
|
text: section("Current Task", state.currentTask),
|
|
148
129
|
mandatory: true,
|
|
130
|
+
// Declare what this text actually carries. `createCanonicalLoopState`
|
|
131
|
+
// builds currentTask with `formatRoundContract`, so this is the
|
|
132
|
+
// structured source, not a text guess; an inconsistent hand-built state
|
|
133
|
+
// therefore fails closed instead of claiming an id it did not render.
|
|
134
|
+
coverageIds: state.roundContract
|
|
135
|
+
? [
|
|
136
|
+
state.roundContract.id,
|
|
137
|
+
...state.roundContract.items.map((item) => item.id),
|
|
138
|
+
].filter((id) => state.currentTask.includes(id))
|
|
139
|
+
: [],
|
|
149
140
|
},
|
|
150
141
|
];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
:
|
|
142
|
+
const criteria = formatCriterionRows(state.criterionStatuses, state.successCriteria);
|
|
143
|
+
if (criteria.lines.length > 0) {
|
|
144
|
+
sections.push({
|
|
145
|
+
id: "criteria",
|
|
146
|
+
text: section("Success Criteria", criteria.lines.join("\n")),
|
|
147
|
+
mandatory: true,
|
|
148
|
+
coverageIds: criteria.ids,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (partition.hard.length > 0) {
|
|
156
152
|
sections.push({
|
|
157
153
|
id: "hard_constraints",
|
|
158
|
-
text: section("Active Hard Constraints",
|
|
154
|
+
text: section("Active Hard Constraints", constraintLines(state, partition.hard)),
|
|
155
|
+
mandatory: true,
|
|
156
|
+
coverageIds: partition.hard.map((text) => identityForText(state, text)),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (partition.other.length > 0) {
|
|
160
|
+
sections.push({
|
|
161
|
+
id: "active_constraints",
|
|
162
|
+
text: section("Active Constraints", constraintLines(state, partition.other)),
|
|
159
163
|
mandatory: true,
|
|
164
|
+
coverageIds: partition.other.map((text) => identityForText(state, text)),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (state.subGoals.length > 0) {
|
|
168
|
+
const subGoals = formatActiveSubGoals(state.subGoals, state.round);
|
|
169
|
+
sections.push({
|
|
170
|
+
id: "sub_goals",
|
|
171
|
+
text: section("Active Sub-Goals", subGoals.lines.join("\n")),
|
|
172
|
+
mandatory: true,
|
|
173
|
+
coverageIds: subGoals.ids,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (recoveryBrief && recoveryBrief.length > 0) {
|
|
177
|
+
sections.push({
|
|
178
|
+
id: "recovery",
|
|
179
|
+
text: section("Recovery Brief", recoveryBrief.join("\n")),
|
|
180
|
+
mandatory: true,
|
|
181
|
+
coverageIds: ["recovery"],
|
|
160
182
|
});
|
|
161
183
|
}
|
|
162
184
|
if (state.verificationFlags.length > 0) {
|
|
@@ -164,21 +186,25 @@ level) {
|
|
|
164
186
|
id: "verification",
|
|
165
187
|
text: section("Verification Gate", verificationText(state, level)),
|
|
166
188
|
mandatory: true,
|
|
189
|
+
coverageIds: state.verificationFlags.map((flag) => findingKey(flag)),
|
|
167
190
|
});
|
|
168
191
|
}
|
|
169
192
|
return sections;
|
|
170
193
|
}
|
|
171
|
-
function l0Sections(state) {
|
|
194
|
+
function l0Sections(state, recoveryBrief) {
|
|
172
195
|
const retryRequirements = [
|
|
173
196
|
...state.blockers,
|
|
174
197
|
...state.verificationFlags.map((flag) => flag.detail),
|
|
175
198
|
];
|
|
176
|
-
const sections = commonMandatorySections(state, "l0");
|
|
199
|
+
const sections = commonMandatorySections(state, "l0", recoveryBrief);
|
|
177
200
|
if (retryRequirements.length > 0) {
|
|
178
201
|
sections.push({
|
|
179
202
|
id: "retry_requirements",
|
|
180
203
|
text: section("Retry Requirements", bullets(retryRequirements)),
|
|
181
204
|
mandatory: true,
|
|
205
|
+
// The same finding keys as the Verification Gate: either repair surface
|
|
206
|
+
// proves the finding was delivered, and both derive from the one list.
|
|
207
|
+
coverageIds: state.verificationFlags.map((flag) => findingKey(flag)),
|
|
182
208
|
});
|
|
183
209
|
}
|
|
184
210
|
if (state.changesSinceLastRound.length > 0) {
|
|
@@ -186,6 +212,7 @@ function l0Sections(state) {
|
|
|
186
212
|
id: "changes",
|
|
187
213
|
text: section("New Evidence / Changes", bullets(state.changesSinceLastRound)),
|
|
188
214
|
mandatory: false,
|
|
215
|
+
coverageIds: [],
|
|
189
216
|
});
|
|
190
217
|
}
|
|
191
218
|
return sections;
|
|
@@ -219,43 +246,14 @@ function activeWarnings(flags, currentRound) {
|
|
|
219
246
|
return `- ${icon} ${subject} — ${flag.count}×, latest R${Math.max(...flag.rounds)}`;
|
|
220
247
|
}).join("\n");
|
|
221
248
|
}
|
|
222
|
-
function l1Sections(state) {
|
|
223
|
-
const sections = commonMandatorySections(state, "l1");
|
|
224
|
-
const active = activeNonHardConstraints(state);
|
|
249
|
+
function l1Sections(state, recoveryBrief) {
|
|
250
|
+
const sections = commonMandatorySections(state, "l1", recoveryBrief);
|
|
225
251
|
if (state.changesSinceLastRound.length > 0) {
|
|
226
252
|
sections.push({
|
|
227
253
|
id: "changes",
|
|
228
254
|
text: section("Changes Since Last Round", bullets(state.changesSinceLastRound)),
|
|
229
255
|
mandatory: false,
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
// v3.8.1: Active Constraints render IN FULL. v3.2 folded unchanged items
|
|
233
|
-
// into "… N unchanged constraints (see state file)" pointer lines, which
|
|
234
|
-
// made the prompt's content depend on what the PREVIOUS prompt happened to
|
|
235
|
-
// show rather than on committed facts — and those pointer lines fed the
|
|
236
|
-
// prompt hash. The fold needed the previous round's persisted presentation
|
|
237
|
-
// snapshot, which is deleted.
|
|
238
|
-
if (active.length > 0) {
|
|
239
|
-
// The one thing the folded branch carried that a plain bullet list does
|
|
240
|
-
// not: the machine fact that this round broke the constraint. Kept so the
|
|
241
|
-
// annotation is not lost along with the fold.
|
|
242
|
-
const lines = active.map((text) => {
|
|
243
|
-
const violated = state.constraintMetadata.find((m) => m.text === text)
|
|
244
|
-
?.last_violated_at_round === state.round;
|
|
245
|
-
return `- [\`${constraintIdOf(text, state.constraintMetadata)}\`] ${text}` +
|
|
246
|
-
(violated ? " (violated this round)" : "");
|
|
247
|
-
});
|
|
248
|
-
sections.push({
|
|
249
|
-
id: "active_constraints",
|
|
250
|
-
text: section("Active Constraints / Success Criteria", lines.join("\n")),
|
|
251
|
-
mandatory: false,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
if (state.remainingCriteria.length > 0) {
|
|
255
|
-
sections.push({
|
|
256
|
-
id: "remaining",
|
|
257
|
-
text: section("Remaining", bulletsWithIds(state.remainingCriteria, "cr")),
|
|
258
|
-
mandatory: false,
|
|
256
|
+
coverageIds: [],
|
|
259
257
|
});
|
|
260
258
|
}
|
|
261
259
|
if (state.blockers.length > 0) {
|
|
@@ -263,24 +261,7 @@ function l1Sections(state) {
|
|
|
263
261
|
id: "blockers",
|
|
264
262
|
text: section("Blockers", bullets(state.blockers)),
|
|
265
263
|
mandatory: false,
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
// v2.2: Sub-Goal Dashboard (compact — L1). v3.7.1: only ACTIVE items
|
|
269
|
-
// (pending/in_progress/blocked) enter the view; done/canceled never render
|
|
270
|
-
// as rows. v3.8.1: rendered in full — the unchanged-count fold is gone.
|
|
271
|
-
const view = activeSubGoalView(state.subGoals, Number.POSITIVE_INFINITY);
|
|
272
|
-
const activeSubs = view.active;
|
|
273
|
-
if (activeSubs.length > 0) {
|
|
274
|
-
const iconOf = (sg) => sg.status === "in_progress" ? "🔄" : sg.status === "blocked" ? "🚫" : "⏳";
|
|
275
|
-
const maxShow = 5;
|
|
276
|
-
const items = activeSubs.slice(0, maxShow).map((sg) => `${iconOf(sg)} [\`${sg.id}\`] ${sg.description}`);
|
|
277
|
-
if (activeSubs.length > maxShow) {
|
|
278
|
-
items.push(`... and ${activeSubs.length - maxShow} more`);
|
|
279
|
-
}
|
|
280
|
-
sections.push({
|
|
281
|
-
id: "sub_goals",
|
|
282
|
-
text: section("Active Sub-Goals", items.join("\n")),
|
|
283
|
-
mandatory: false,
|
|
264
|
+
coverageIds: [],
|
|
284
265
|
});
|
|
285
266
|
}
|
|
286
267
|
if (state.discoveries.length > 0) {
|
|
@@ -288,6 +269,7 @@ function l1Sections(state) {
|
|
|
288
269
|
id: "discoveries",
|
|
289
270
|
text: section("New Discoveries", bullets(state.discoveries)),
|
|
290
271
|
mandatory: false,
|
|
272
|
+
coverageIds: [],
|
|
291
273
|
});
|
|
292
274
|
}
|
|
293
275
|
// v3.2: Recent Rounds. v3.8.1: rendered in full — the keep-newest-N fold and
|
|
@@ -297,6 +279,7 @@ function l1Sections(state) {
|
|
|
297
279
|
id: "rolling_outcomes",
|
|
298
280
|
text: section("Recent Rounds", bullets(state.rollingOutcomes)),
|
|
299
281
|
mandatory: false,
|
|
282
|
+
coverageIds: [],
|
|
300
283
|
});
|
|
301
284
|
}
|
|
302
285
|
// v3.2: L1 also renders the explicit external context (previously L2-only).
|
|
@@ -306,6 +289,7 @@ function l1Sections(state) {
|
|
|
306
289
|
id: "external_context",
|
|
307
290
|
text: section("External Context", state.externalContext),
|
|
308
291
|
mandatory: false,
|
|
292
|
+
coverageIds: [],
|
|
309
293
|
});
|
|
310
294
|
}
|
|
311
295
|
// v3.8.1: one compact phase line, not the Roadmap prose.
|
|
@@ -315,6 +299,7 @@ function l1Sections(state) {
|
|
|
315
299
|
id: "phase",
|
|
316
300
|
text: section("Phase", l1Phase),
|
|
317
301
|
mandatory: false,
|
|
302
|
+
coverageIds: [],
|
|
318
303
|
});
|
|
319
304
|
}
|
|
320
305
|
// v3.8.1: Active Warnings — the recent tail of the ONE recurring-fact
|
|
@@ -329,46 +314,42 @@ function l1Sections(state) {
|
|
|
329
314
|
id: "active_warnings",
|
|
330
315
|
text: section("Active Warnings", warnings),
|
|
331
316
|
mandatory: false,
|
|
317
|
+
coverageIds: [],
|
|
332
318
|
});
|
|
333
319
|
}
|
|
334
320
|
return sections;
|
|
335
321
|
}
|
|
336
|
-
function l2Sections(state, fullStateMarkdown) {
|
|
337
|
-
const sections = commonMandatorySections(state, "l2");
|
|
322
|
+
function l2Sections(state, fullStateMarkdown, recoveryBrief) {
|
|
323
|
+
const sections = commonMandatorySections(state, "l2", recoveryBrief);
|
|
338
324
|
const fullState = fullStateMarkdown?.trim();
|
|
339
325
|
if (fullState) {
|
|
340
326
|
sections.push({
|
|
341
327
|
id: "full_state",
|
|
342
328
|
text: section("Full Rehydrated State", fullState),
|
|
343
329
|
mandatory: false,
|
|
330
|
+
coverageIds: [],
|
|
344
331
|
});
|
|
345
332
|
return sections;
|
|
346
333
|
}
|
|
347
|
-
// v2.11:
|
|
334
|
+
// v2.11: narrative groups. v3.9.4: criteria, active constraints and active
|
|
335
|
+
// sub-goals moved to the shared mandatory sections, so they are not repeated
|
|
336
|
+
// here; these groups carry no normative ids.
|
|
348
337
|
const groups = [
|
|
349
|
-
["
|
|
350
|
-
["
|
|
351
|
-
["
|
|
352
|
-
["
|
|
353
|
-
["
|
|
354
|
-
["discoveries", "Discoveries", state.discoveries, null],
|
|
355
|
-
["rolling_outcomes", "Cross-Round Outcomes", state.rollingOutcomes, null],
|
|
356
|
-
["failed_patterns", "Failed Patterns", state.failedPatterns, null],
|
|
338
|
+
["changes", "Changes Since Last Round", state.changesSinceLastRound],
|
|
339
|
+
["blockers", "Blockers", state.blockers],
|
|
340
|
+
["discoveries", "Discoveries", state.discoveries],
|
|
341
|
+
["rolling_outcomes", "Cross-Round Outcomes", state.rollingOutcomes],
|
|
342
|
+
["failed_patterns", "Failed Patterns", state.failedPatterns],
|
|
357
343
|
];
|
|
358
|
-
for (const [id, title, values
|
|
344
|
+
for (const [id, title, values] of groups) {
|
|
359
345
|
if (values.length === 0)
|
|
360
346
|
continue;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
? bulletsWithIdentities(values, state.constraintMetadata)
|
|
368
|
-
: idPrefix
|
|
369
|
-
? bulletsWithIds(values, idPrefix)
|
|
370
|
-
: bullets(values);
|
|
371
|
-
sections.push({ id, text: section(title, body), mandatory: false });
|
|
347
|
+
sections.push({
|
|
348
|
+
id,
|
|
349
|
+
text: section(title, bullets(values)),
|
|
350
|
+
mandatory: false,
|
|
351
|
+
coverageIds: [],
|
|
352
|
+
});
|
|
372
353
|
}
|
|
373
354
|
// v3.8.1: Active Warnings — the recent tail of the ONE recurring-fact
|
|
374
355
|
// derivation (see l1Sections). The whole-history recurring set is the state
|
|
@@ -380,6 +361,7 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
380
361
|
id: "active_warnings",
|
|
381
362
|
text: section("Active Warnings", warnings),
|
|
382
363
|
mandatory: false,
|
|
364
|
+
coverageIds: [],
|
|
383
365
|
});
|
|
384
366
|
}
|
|
385
367
|
// v3.8.1: one compact phase line, not the Roadmap prose — rendered right
|
|
@@ -391,6 +373,7 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
391
373
|
id: "phase",
|
|
392
374
|
text: section("Phase", l2Phase),
|
|
393
375
|
mandatory: false,
|
|
376
|
+
coverageIds: [],
|
|
394
377
|
});
|
|
395
378
|
}
|
|
396
379
|
if (state.milestones.length > 0) {
|
|
@@ -410,34 +393,7 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
410
393
|
id: "milestones",
|
|
411
394
|
text: section("Phase History", milestoneText),
|
|
412
395
|
mandatory: false,
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
if (state.subGoals.length > 0) {
|
|
416
|
-
// v3.7.1: active-only rows ordered blocked → in_progress → pending
|
|
417
|
-
// (priority asc, recently changed first), capped by policy; done and
|
|
418
|
-
// canceled appear only in the stats line.
|
|
419
|
-
const cap = getPolicy().evolution.max_active_subgoals;
|
|
420
|
-
const view = activeSubGoalView(state.subGoals, cap);
|
|
421
|
-
const lines = [];
|
|
422
|
-
for (const sg of view.active) {
|
|
423
|
-
if (sg.status === "in_progress") {
|
|
424
|
-
lines.push(`🔄 [\`${sg.id}\`] ${sg.description} (since R${sg.status_changed_at_round})`);
|
|
425
|
-
}
|
|
426
|
-
else if (sg.status === "blocked") {
|
|
427
|
-
lines.push(`🚫 [\`${sg.id}\`] ${sg.description} (blocked since R${sg.status_changed_at_round})`);
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
const age = state.round - sg.declared_at_round;
|
|
431
|
-
const stale = age >= 10 ? " ⚠️ stale" : "";
|
|
432
|
-
lines.push(`⏳ [\`${sg.id}\`] ${sg.description} (pending ${age} rounds)${stale}`);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
// Stats line (full counts — rows may be capped)
|
|
436
|
-
lines.push(`─── ${view.total} total: ${view.activeTotal} active, ${view.done} done, ${view.canceled} canceled`);
|
|
437
|
-
sections.push({
|
|
438
|
-
id: "sub_goals_full",
|
|
439
|
-
text: section("Sub-Goal Dashboard", lines.join("\n")),
|
|
440
|
-
mandatory: false,
|
|
396
|
+
coverageIds: [],
|
|
441
397
|
});
|
|
442
398
|
}
|
|
443
399
|
if (state.externalContext) {
|
|
@@ -445,33 +401,28 @@ function l2Sections(state, fullStateMarkdown) {
|
|
|
445
401
|
id: "external_context",
|
|
446
402
|
text: section("External Context", state.externalContext),
|
|
447
403
|
mandatory: false,
|
|
404
|
+
coverageIds: [],
|
|
448
405
|
});
|
|
449
406
|
}
|
|
450
|
-
// ── v2.8: Path B gap fills (previously only in full state markdown) ──
|
|
451
|
-
// v3.8.1: the L2 Progress Dashboard and Round Stats sections are gone.
|
|
452
|
-
// The dashboard was `criterionStatuses` re-composed into a table and mixed
|
|
453
|
-
// with reported test counts; Round Stats
|
|
454
|
-
// re-listed per-round files and progress deltas, both self-reported. The
|
|
455
|
-
// criterion list and the machine git/criteria rows still render in the
|
|
456
|
-
// state file, where a human or external tool reads them — the prompt keeps
|
|
457
|
-
// only the current round’s own facts.
|
|
458
|
-
// Retired Constraints
|
|
459
407
|
if (state.retiredConstraints.length > 0) {
|
|
460
408
|
const lines = state.retiredConstraints.map((c) => `- ~${c}~ (retired)`);
|
|
461
409
|
sections.push({
|
|
462
410
|
id: "retired_constraints",
|
|
463
411
|
text: section("Retired Constraints", lines.join("\n")),
|
|
464
412
|
mandatory: false,
|
|
413
|
+
coverageIds: [],
|
|
465
414
|
});
|
|
466
415
|
}
|
|
467
416
|
return sections;
|
|
468
417
|
}
|
|
469
418
|
function selectSections(input) {
|
|
470
|
-
if (input.level === "l0")
|
|
471
|
-
return l0Sections(input.state);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
419
|
+
if (input.level === "l0") {
|
|
420
|
+
return l0Sections(input.state, input.recoveryBrief);
|
|
421
|
+
}
|
|
422
|
+
if (input.level === "l1") {
|
|
423
|
+
return l1Sections(input.state, input.recoveryBrief);
|
|
424
|
+
}
|
|
425
|
+
return l2Sections(input.state, input.fullStateMarkdown, input.recoveryBrief);
|
|
475
426
|
}
|
|
476
427
|
/** v3.8.1: render order, in ONE place.
|
|
477
428
|
*
|
|
@@ -486,33 +437,32 @@ function selectSections(input) {
|
|
|
486
437
|
* single large early section silently pushed out every later one.)
|
|
487
438
|
*/
|
|
488
439
|
const SECTION_PRIORITY = {
|
|
489
|
-
// ── protected
|
|
440
|
+
// ── protected (every active normative id lives here) ────────────────────
|
|
490
441
|
objective: 0,
|
|
491
442
|
current_task: 1,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
443
|
+
criteria: 2,
|
|
444
|
+
hard_constraints: 3,
|
|
445
|
+
active_constraints: 4,
|
|
446
|
+
sub_goals: 5,
|
|
447
|
+
recovery: 6,
|
|
448
|
+
verification: 7,
|
|
449
|
+
retry_requirements: 8,
|
|
495
450
|
// Zero-token pure reorder, but it is the ONLY place emphasized items render
|
|
496
451
|
// (they are MOVED out of their source sections), so dropping it would delete
|
|
497
452
|
// them rather than just deprioritize them.
|
|
498
|
-
critical_context:
|
|
499
|
-
// ── optional, most to least important
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
failed_patterns: 21,
|
|
512
|
-
retired_constraints: 22,
|
|
513
|
-
milestones: 23,
|
|
514
|
-
sub_goals_full: 24,
|
|
515
|
-
full_state: 25,
|
|
453
|
+
critical_context: 9,
|
|
454
|
+
// ── optional narrative, most to least important ─────────────────────────
|
|
455
|
+
blockers: 20,
|
|
456
|
+
changes: 21,
|
|
457
|
+
discoveries: 22,
|
|
458
|
+
rolling_outcomes: 23,
|
|
459
|
+
phase: 24,
|
|
460
|
+
active_warnings: 25,
|
|
461
|
+
external_context: 26,
|
|
462
|
+
failed_patterns: 27,
|
|
463
|
+
retired_constraints: 28,
|
|
464
|
+
milestones: 29,
|
|
465
|
+
full_state: 30,
|
|
516
466
|
};
|
|
517
467
|
const DEFAULT_SECTION_PRIORITY = 50;
|
|
518
468
|
/** v3.8.1: the smallest remaining room worth spending on a truncated
|
|
@@ -672,6 +622,16 @@ function renderConfusionAlerts(points, fullDensity) {
|
|
|
672
622
|
}
|
|
673
623
|
/** Render the "Critical Context" section — items the model emphasized.
|
|
674
624
|
* Pulled from active constraints, discoveries, and blocking issues. */
|
|
625
|
+
/** v3.9.4: an emphasized item that is also an active normative constraint
|
|
626
|
+
* reports its real id here, because it is MOVED out of its source section
|
|
627
|
+
* and this is the only place it renders. Narrative items carry no id. */
|
|
628
|
+
function normativeIdForText(state, text) {
|
|
629
|
+
const key = normalizeText(text);
|
|
630
|
+
const normative = state.activeConstraints.some((item) => normalizeText(item) === key) ||
|
|
631
|
+
state.successCriteria.some((item) => normalizeText(item) === key) ||
|
|
632
|
+
state.hardConstraints.some((item) => normalizeText(item) === key);
|
|
633
|
+
return normative ? identityForText(state, text) : null;
|
|
634
|
+
}
|
|
675
635
|
function renderCriticalContext(emphasized) {
|
|
676
636
|
if (emphasized.length === 0)
|
|
677
637
|
return "";
|
|
@@ -682,7 +642,9 @@ function renderCriticalContext(emphasized) {
|
|
|
682
642
|
"",
|
|
683
643
|
];
|
|
684
644
|
for (const item of emphasized) {
|
|
685
|
-
lines.push(
|
|
645
|
+
lines.push(item.id
|
|
646
|
+
? `- [\`${item.id}\`] ${item.text}`
|
|
647
|
+
: `- ${item.text}`);
|
|
686
648
|
}
|
|
687
649
|
lines.push("");
|
|
688
650
|
return lines.join("\n");
|
|
@@ -820,7 +782,13 @@ export function assemblePromptArtifact(input) {
|
|
|
820
782
|
}
|
|
821
783
|
: input.state;
|
|
822
784
|
const sections = selectSections({ ...input, state: stateForSections });
|
|
823
|
-
|
|
785
|
+
// The ID attached here comes from the ORIGINAL state: emphasize moved the
|
|
786
|
+
// item out of its source section, so this is the only renderer for it.
|
|
787
|
+
const emphasizedItems = emphasized.map((text) => ({
|
|
788
|
+
text,
|
|
789
|
+
id: normativeIdForText(input.state, text),
|
|
790
|
+
}));
|
|
791
|
+
const criticalContextText = renderCriticalContext(emphasizedItems);
|
|
824
792
|
// v3.8.1: no presentation snapshot. The artifact records what THIS prompt
|
|
825
793
|
// rendered (level, hashes, round identity, the sections it emitted) and
|
|
826
794
|
// nothing about what a LATER prompt should do differently — a prompt's
|
|
@@ -836,6 +804,7 @@ export function assemblePromptArtifact(input) {
|
|
|
836
804
|
id: "critical_context",
|
|
837
805
|
text: criticalContextText,
|
|
838
806
|
mandatory: true,
|
|
807
|
+
coverageIds: emphasizedItems.flatMap((item) => item.id ? [item.id] : []),
|
|
839
808
|
},
|
|
840
809
|
...sections,
|
|
841
810
|
];
|
|
@@ -844,6 +813,29 @@ export function assemblePromptArtifact(input) {
|
|
|
844
813
|
const selected = renderWithinBudget(augmentedSections, fixedText, sectionBudget);
|
|
845
814
|
const renderedPrompt = selected.rendered + footer;
|
|
846
815
|
const promptHash = createHash("sha256").update(renderedPrompt).digest("hex");
|
|
816
|
+
// v3.9.4: coverage is derived from the ORIGINAL state and the sections that
|
|
817
|
+
// actually rendered. It is not text-scanned and not a second fact source.
|
|
818
|
+
// An incomplete projection fails closed — no prompt or artifact escapes.
|
|
819
|
+
const active = activeNormativeIds(input.state, input.recoveryBrief);
|
|
820
|
+
const declaredIds = new Set();
|
|
821
|
+
for (const section of augmentedSections) {
|
|
822
|
+
for (const id of section.coverageIds)
|
|
823
|
+
declaredIds.add(id);
|
|
824
|
+
}
|
|
825
|
+
const renderedIds = new Set();
|
|
826
|
+
for (const sectionId of selected.included) {
|
|
827
|
+
const section = augmentedSections.find((entry) => entry.id === sectionId);
|
|
828
|
+
if (!section)
|
|
829
|
+
continue;
|
|
830
|
+
for (const id of section.coverageIds)
|
|
831
|
+
renderedIds.add(id);
|
|
832
|
+
}
|
|
833
|
+
const coverage = derivePromptCoverage({
|
|
834
|
+
active,
|
|
835
|
+
rendered: renderedIds,
|
|
836
|
+
declared: declaredIds,
|
|
837
|
+
});
|
|
838
|
+
assertPromptCoverage(coverage);
|
|
847
839
|
return {
|
|
848
840
|
schemaVersion: PROMPT_ARTIFACT_SCHEMA_VERSION,
|
|
849
841
|
roundId: `loop:${input.state.loopId}:round:${input.state.round}`,
|
|
@@ -862,6 +854,7 @@ export function assemblePromptArtifact(input) {
|
|
|
862
854
|
budget: sectionBudget,
|
|
863
855
|
renderedChars: selected.rendered.length,
|
|
864
856
|
promptRequestEffects,
|
|
857
|
+
coverage,
|
|
865
858
|
};
|
|
866
859
|
}
|
|
867
860
|
//# sourceMappingURL=prompt-assembler.js.map
|