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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-state.d.ts","sourceRoot":"","sources":["../src/canonical-state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"canonical-state.d.ts","sourceRoot":"","sources":["../src/canonical-state.ts"],"names":[],"mappings":"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"}
|
package/dist/canonical-state.js
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
* consume the same value so they cannot silently drift apart.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { deriveItemId,
|
|
8
|
-
import { unique } from "./token-utils.js";
|
|
7
|
+
import { deriveItemId, STABLE_ID_RE, unique } from "./token-utils.js";
|
|
9
8
|
import { getPolicy } from "./policy.js";
|
|
10
|
-
|
|
11
|
-
export const CANONICAL_STATE_FORMAT = "loopforge.state";
|
|
9
|
+
export const CANONICAL_STATE_SCHEMA_VERSION = 1;
|
|
12
10
|
// unique() imported from token-utils.ts
|
|
13
11
|
/** Deterministic JSON serialization used by state and prompt hashes. */
|
|
14
12
|
export function stableStringify(value) {
|
|
@@ -48,8 +46,7 @@ function addListWithIds(lines, title, values, prefix) {
|
|
|
48
46
|
if (!trimmed)
|
|
49
47
|
continue;
|
|
50
48
|
// If the value is already an ID, render it as-is
|
|
51
|
-
|
|
52
|
-
if (idRegex.test(trimmed)) {
|
|
49
|
+
if (STABLE_ID_RE.test(trimmed)) {
|
|
53
50
|
lines.push(`- [\`${trimmed}\`] ${trimmed}`);
|
|
54
51
|
}
|
|
55
52
|
else if (idEnabled) {
|
|
@@ -62,13 +59,144 @@ function addListWithIds(lines, title, values, prefix) {
|
|
|
62
59
|
}
|
|
63
60
|
lines.push("");
|
|
64
61
|
}
|
|
62
|
+
/** v3.3.1: Identity-aware variant of addListWithIds for the merged active
|
|
63
|
+
* constraint set (hard/plan/criteria/discovered texts together). Each
|
|
64
|
+
* item's ID comes from constraint metadata — criteria texts keep their
|
|
65
|
+
* cr-XXXXXXXX identity, matching the Success Criteria section and the
|
|
66
|
+
* verification gate's ID-first criterion matching. Keeps the state file in
|
|
67
|
+
* the same identity namespace as the prompts (module contract: both
|
|
68
|
+
* renderers cannot drift apart). */
|
|
69
|
+
function addListWithIdentities(lines, title, values, metadata) {
|
|
70
|
+
if (values.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
73
|
+
const idByText = new Map(metadata.map((meta) => [meta.text, meta.id]));
|
|
74
|
+
lines.push(`## ${title}`, "");
|
|
75
|
+
for (const value of values) {
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
if (!trimmed)
|
|
78
|
+
continue;
|
|
79
|
+
if (STABLE_ID_RE.test(trimmed)) {
|
|
80
|
+
lines.push(`- [\`${trimmed}\`] ${trimmed}`);
|
|
81
|
+
}
|
|
82
|
+
else if (idEnabled) {
|
|
83
|
+
const id = idByText.get(trimmed) ?? `c-${deriveItemId(trimmed)}`;
|
|
84
|
+
lines.push(`- [\`${id}\`] ${trimmed}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push(`- ${trimmed}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
65
92
|
/** Human/Agent-readable materialized view. It is always reproducible from the
|
|
66
93
|
* canonical state and is never consulted as transaction truth. */
|
|
94
|
+
/** v3.3: Forward-looking "roadmap" view derived from existing state — loop
|
|
95
|
+
* position, met/remaining criteria (with IDs), and sub-goal activity.
|
|
96
|
+
*
|
|
97
|
+
* Deliberately label-free: milestone labels/ranges are collapsed content in
|
|
98
|
+
* L1 (the diff-collapse invariant) and are rendered in full by Phase
|
|
99
|
+
* History everywhere else — the roadmap only reports "N rounds since the
|
|
100
|
+
* last milestone boundary", never the boundary's identity. Next action is
|
|
101
|
+
* likewise excluded: every level renders its own Next Action section.
|
|
102
|
+
* Shared by the state-file and prompt renderers so they cannot silently
|
|
103
|
+
* drift apart (module contract above). Returns [] — renders nothing —
|
|
104
|
+
* when the state carries none of these. Presentation only; never feeds
|
|
105
|
+
* enforcement. */
|
|
106
|
+
export function buildRoadmap(state) {
|
|
107
|
+
const lastMilestone = state.milestones.length > 0
|
|
108
|
+
? state.milestones[state.milestones.length - 1]
|
|
109
|
+
: null;
|
|
110
|
+
const roundsSinceMilestone = lastMilestone !== null &&
|
|
111
|
+
lastMilestone.round_range.end <= state.round
|
|
112
|
+
? state.round - lastMilestone.round_range.end
|
|
113
|
+
: null;
|
|
114
|
+
const met = state.criterionStatuses.filter((cs) => cs.status === "met").length;
|
|
115
|
+
const total = state.criterionStatuses.length;
|
|
116
|
+
const remaining = state.criterionStatuses.filter((cs) => cs.status === "remaining" || cs.status === "unknown");
|
|
117
|
+
const inProgress = state.subGoals.filter((sg) => sg.status === "in_progress").length;
|
|
118
|
+
const pending = state.subGoals.filter((sg) => sg.status === "pending").length;
|
|
119
|
+
// Bare position alone is not forward information — no milestone distance,
|
|
120
|
+
// criteria, remaining criteria, or sub-goal activity means nothing to
|
|
121
|
+
// look ahead to. Returns [] so empty states render no section at all.
|
|
122
|
+
if (roundsSinceMilestone === null && total === 0 &&
|
|
123
|
+
state.remainingCriteria.length === 0 && inProgress === 0 && pending === 0) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const lines = [];
|
|
127
|
+
let position = `Position: round ${state.round}/${state.maxRounds}`;
|
|
128
|
+
if (roundsSinceMilestone !== null) {
|
|
129
|
+
position += ` · ${roundsSinceMilestone} round${roundsSinceMilestone === 1 ? "" : "s"} since the last milestone boundary`;
|
|
130
|
+
}
|
|
131
|
+
lines.push(`- ${position}`);
|
|
132
|
+
if (total > 0) {
|
|
133
|
+
lines.push(`- Criteria: ${met}/${total} met · ${remaining.length} remaining`);
|
|
134
|
+
}
|
|
135
|
+
else if (state.remainingCriteria.length > 0) {
|
|
136
|
+
lines.push(`- Criteria: ${state.remainingCriteria.length} remaining`);
|
|
137
|
+
}
|
|
138
|
+
if (remaining.length > 0) {
|
|
139
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
140
|
+
for (const cs of remaining.slice(0, 8)) {
|
|
141
|
+
const idTag = idEnabled ? ` [\`${cs.id}\`]` : "";
|
|
142
|
+
const icon = cs.status === "remaining" ? "⬜" : "❔";
|
|
143
|
+
lines.push(` - ${icon}${idTag} ${cs.text}`);
|
|
144
|
+
}
|
|
145
|
+
if (remaining.length > 8) {
|
|
146
|
+
lines.push(` - ... and ${remaining.length - 8} more`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (inProgress > 0 || pending > 0) {
|
|
150
|
+
lines.push(`- Sub-goals: ${inProgress} in progress · ${pending} pending`);
|
|
151
|
+
}
|
|
152
|
+
return lines;
|
|
153
|
+
}
|
|
154
|
+
/** v3.3/v3.4: Render the ACTIVE Round Contract as the Current Task section
|
|
155
|
+
* body — the single formatting source shared by prompts and the state
|
|
156
|
+
* file (module contract above). The original objective is NOT here: it
|
|
157
|
+
* lives in the Objective section. Empty arrays render no line. */
|
|
158
|
+
export function formatRoundContract(contract) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
const heading = contract.work_item?.trim();
|
|
161
|
+
lines.push(`**${heading || "Round Contract"}**`);
|
|
162
|
+
for (const item of contract.done_when) {
|
|
163
|
+
lines.push(`- Done when: ${item}`);
|
|
164
|
+
}
|
|
165
|
+
for (const item of contract.verification_plan) {
|
|
166
|
+
lines.push(`- Verify via: ${item}`);
|
|
167
|
+
}
|
|
168
|
+
for (const item of contract.scope) {
|
|
169
|
+
lines.push(`- Scope: ${item}`);
|
|
170
|
+
}
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
174
|
+
// v3.3.1: Shared presentation atoms
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
|
+
/** The trust bar line ("██████░░░░ 60%"). The L2 Agent Trust section and the
|
|
177
|
+
* detailed L1 renderer used to carry private copies of this formula — shared
|
|
178
|
+
* here so a formatting change is made once (module contract: renderers must
|
|
179
|
+
* not silently drift apart). */
|
|
180
|
+
export function trustBarLine(score) {
|
|
181
|
+
const barLen = 10;
|
|
182
|
+
const filled = Math.round(score * barLen);
|
|
183
|
+
const bar = "█".repeat(filled) + "░".repeat(barLen - filled);
|
|
184
|
+
return `${bar} ${(score * 100).toFixed(0)}%`;
|
|
185
|
+
}
|
|
186
|
+
/** Milestone heading line ("**🏁 Round 7** (Rounds 3–7, 60%)"). Shared by the
|
|
187
|
+
* L2 Phase History and the detailed L1 renderer; the L1 copy previously
|
|
188
|
+
* rendered the range as "R3–R7", a format only this heading used. */
|
|
189
|
+
export function milestoneHeading(milestone) {
|
|
190
|
+
const kindIcon = milestone.kind === "agent_declared"
|
|
191
|
+
? "🏁"
|
|
192
|
+
: milestone.kind === "criteria_milestone" ? "✅" : "📍";
|
|
193
|
+
return `**${kindIcon} ${milestone.label}** (Rounds ${milestone.round_range.start}–${milestone.round_range.end}, ${(milestone.progress_at_boundary * 100).toFixed(0)}%)`;
|
|
194
|
+
}
|
|
67
195
|
export function renderCanonicalStateMarkdown(state) {
|
|
68
196
|
const lines = [
|
|
69
197
|
`# LoopForge State — ${state.loopId}`,
|
|
70
198
|
"",
|
|
71
|
-
`**
|
|
199
|
+
`**Schema**: ${state.schemaVersion}`,
|
|
72
200
|
`**Round**: ${state.round}/${state.maxRounds}`,
|
|
73
201
|
`**Goal ID**: ${state.goalId}`,
|
|
74
202
|
"",
|
|
@@ -76,34 +204,21 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
76
204
|
"",
|
|
77
205
|
state.objective,
|
|
78
206
|
"",
|
|
79
|
-
"## Current
|
|
80
|
-
"",
|
|
81
|
-
state.currentFocus,
|
|
207
|
+
"## Current Task",
|
|
82
208
|
"",
|
|
209
|
+
state.currentTask,
|
|
83
210
|
];
|
|
84
|
-
if (state.todoProjection) {
|
|
85
|
-
lines.push("## Active Todo Projection", "", state.todoProjection.action, "", `**Completion Signal**: ${state.todoProjection.completionSignal}`, "");
|
|
86
|
-
}
|
|
87
|
-
if (state.activeGate) {
|
|
88
|
-
lines.push("## Active Gate", "");
|
|
89
|
-
if (state.activeGate.kind === "user") {
|
|
90
|
-
lines.push(`**Owner**: user`, `**Question**: ${state.activeGate.question}`);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
lines.push(`**Owner**: agent`, `**Problem**: ${state.activeGate.problem}`);
|
|
94
|
-
}
|
|
95
|
-
lines.push("");
|
|
96
|
-
}
|
|
97
211
|
// v2.11: Success Criteria and Constraints render with stable IDs for exact agent matching
|
|
98
212
|
addListWithIds(lines, "Success Criteria", state.successCriteria, "cr");
|
|
99
213
|
addListWithIds(lines, "Hard Constraints", state.hardConstraints, "c");
|
|
100
|
-
|
|
214
|
+
addListWithIdentities(lines, "Active Constraints", state.activeConstraints, state.constraintMetadata);
|
|
101
215
|
addList(lines, "Changes Since Last Round", state.changesSinceLastRound);
|
|
102
216
|
if (state.progress.estimate !== null ||
|
|
103
217
|
state.progress.criteriaMet.length > 0 ||
|
|
104
218
|
state.progress.criteriaRemaining.length > 0 ||
|
|
105
219
|
state.progress.filesChanged.length > 0 ||
|
|
106
|
-
state.progress.tests !== null
|
|
220
|
+
state.progress.tests !== null ||
|
|
221
|
+
state.machineStatus !== undefined) {
|
|
107
222
|
const total = state.progress.criteriaMet.length + state.progress.criteriaRemaining.length;
|
|
108
223
|
lines.push("## Progress Dashboard", "");
|
|
109
224
|
if (total > 0) {
|
|
@@ -111,6 +226,21 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
111
226
|
}
|
|
112
227
|
if (state.progress.estimate !== null) {
|
|
113
228
|
lines.push(`**Estimated Completion**: ${(state.progress.estimate * 100).toFixed(0)}%`);
|
|
229
|
+
// v3.3: honest labeling — the estimate is the agent's own number.
|
|
230
|
+
lines.push("**Signal source**: self-reported estimate (unverified until machine-backed)");
|
|
231
|
+
}
|
|
232
|
+
// v3.3: machine side of the comparison — git motion over committed rounds
|
|
233
|
+
// (v3.6: the object always carries definite values — no "unavailable" arm).
|
|
234
|
+
if (state.machineStatus) {
|
|
235
|
+
const ms = state.machineStatus;
|
|
236
|
+
const motion = ms.gitMotion
|
|
237
|
+
? `changes observed in ${ms.motionRounds}/${ms.windowRounds} recent committed rounds`
|
|
238
|
+
: `no git changes in the last ${ms.windowRounds} committed rounds`;
|
|
239
|
+
lines.push(`**Machine (git)**: ${motion}`);
|
|
240
|
+
}
|
|
241
|
+
if (state.criterionStatuses.length > 0) {
|
|
242
|
+
const metCount = state.criterionStatuses.filter((cs) => cs.status === "met").length;
|
|
243
|
+
lines.push(`**Machine (criteria)**: ${metCount}/${state.criterionStatuses.length} met across committed rounds`);
|
|
114
244
|
}
|
|
115
245
|
if (state.progress.tests) {
|
|
116
246
|
lines.push(`**Tests**: ${state.progress.tests.passed} passed, ` +
|
|
@@ -123,13 +253,42 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
123
253
|
}
|
|
124
254
|
lines.push("");
|
|
125
255
|
}
|
|
256
|
+
// v3.2: Goal → criteria → evidence vertical view. IDs follow
|
|
257
|
+
// constraint_id_enabled like every other ID-rendering path.
|
|
258
|
+
if (state.criterionStatuses.length > 0) {
|
|
259
|
+
const idEnabled = getPolicy().evolution.constraint_id_enabled;
|
|
260
|
+
lines.push("## Goal → Criteria", "");
|
|
261
|
+
for (const cs of state.criterionStatuses) {
|
|
262
|
+
const icon = cs.status === "met" ? "✅" : cs.status === "remaining" ? "⬜" : "❔";
|
|
263
|
+
const idTag = idEnabled ? ` [\`${cs.id}\`]` : "";
|
|
264
|
+
const met = cs.met_at_round !== undefined ? ` (met R${cs.met_at_round})` : "";
|
|
265
|
+
const related = cs.related_subgoal_ids.length > 0
|
|
266
|
+
? ` (related: ${cs.related_subgoal_ids.join(", ")})`
|
|
267
|
+
: "";
|
|
268
|
+
lines.push(`- ${icon}${idTag} ${cs.text}${met}${related}`);
|
|
269
|
+
}
|
|
270
|
+
lines.push("");
|
|
271
|
+
}
|
|
126
272
|
addListWithIds(lines, "Remaining", state.remainingCriteria, "cr");
|
|
127
273
|
addList(lines, "Blockers", state.blockers);
|
|
128
274
|
addList(lines, "Discoveries", state.discoveries);
|
|
129
275
|
addList(lines, "Cross-Round Outcomes", state.rollingOutcomes);
|
|
130
276
|
addList(lines, "Recurring Issues", state.recurringIssues);
|
|
131
277
|
addList(lines, "Failed Patterns", state.failedPatterns);
|
|
132
|
-
|
|
278
|
+
// v3.2: Lessons learned — repeated violations / verification failures.
|
|
279
|
+
if (state.lessons.length > 0) {
|
|
280
|
+
lines.push("## Lessons Learned", "");
|
|
281
|
+
for (const lesson of state.lessons.slice(0, 8)) {
|
|
282
|
+
const icon = lesson.kind === "constraint_violation"
|
|
283
|
+
? "🚫"
|
|
284
|
+
: lesson.kind === "verification_error" ? "⚠️" : "ℹ️";
|
|
285
|
+
lines.push(`- ${icon} ${lesson.text} — ${lesson.count}× (R${lesson.rounds.join(", R")})`);
|
|
286
|
+
}
|
|
287
|
+
if (state.lessons.length > 8) {
|
|
288
|
+
lines.push(`- ... and ${state.lessons.length - 8} more`);
|
|
289
|
+
}
|
|
290
|
+
lines.push("");
|
|
291
|
+
}
|
|
133
292
|
addList(lines, "Retired Constraints", state.retiredConstraints);
|
|
134
293
|
if (state.inactiveConstraints.length > 0) {
|
|
135
294
|
lines.push("## Inactive Constraints", "");
|
|
@@ -150,6 +309,17 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
150
309
|
}
|
|
151
310
|
lines.push("");
|
|
152
311
|
}
|
|
312
|
+
if (state.nextAction) {
|
|
313
|
+
lines.push("## Next Action", "", state.nextAction, "");
|
|
314
|
+
}
|
|
315
|
+
// v3.3: Forward-looking roadmap — same buildRoadmap the prompts render.
|
|
316
|
+
const roadmap = buildRoadmap(state);
|
|
317
|
+
if (roadmap.length > 0) {
|
|
318
|
+
lines.push("## Roadmap", "");
|
|
319
|
+
for (const line of roadmap)
|
|
320
|
+
lines.push(line);
|
|
321
|
+
lines.push("");
|
|
322
|
+
}
|
|
153
323
|
if (state.verificationFlags.length > 0) {
|
|
154
324
|
lines.push("## Verification", "");
|
|
155
325
|
for (const flag of state.verificationFlags) {
|
|
@@ -157,13 +327,26 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
157
327
|
}
|
|
158
328
|
lines.push("");
|
|
159
329
|
}
|
|
160
|
-
if (state.
|
|
330
|
+
if (state.milestones.length > 0) {
|
|
161
331
|
lines.push("## Phase History", "");
|
|
162
|
-
for (const
|
|
163
|
-
const
|
|
164
|
-
?
|
|
165
|
-
:
|
|
166
|
-
lines.push(`### ${
|
|
332
|
+
for (const m of state.milestones) {
|
|
333
|
+
const kindIcon = m.kind === "agent_declared"
|
|
334
|
+
? "🏁"
|
|
335
|
+
: m.kind === "criteria_milestone" ? "✅" : "📍";
|
|
336
|
+
lines.push(`### ${kindIcon} ${m.label}`, `Rounds ${m.round_range.start}–${m.round_range.end} | ` +
|
|
337
|
+
`Progress: ${(m.progress_at_boundary * 100).toFixed(0)}%`, "", `**Outcome**: ${m.outcome}`, "");
|
|
338
|
+
if (m.carried_constraints.length > 0) {
|
|
339
|
+
lines.push("**Carried Constraints**:");
|
|
340
|
+
for (const c of m.carried_constraints)
|
|
341
|
+
lines.push(`- ${c}`);
|
|
342
|
+
lines.push("");
|
|
343
|
+
}
|
|
344
|
+
if (m.resolved_constraints.length > 0) {
|
|
345
|
+
lines.push("**Resolved During Phase**:");
|
|
346
|
+
for (const c of m.resolved_constraints)
|
|
347
|
+
lines.push(`- ${c}`);
|
|
348
|
+
lines.push("");
|
|
349
|
+
}
|
|
167
350
|
}
|
|
168
351
|
}
|
|
169
352
|
if (state.subGoals.length > 0) {
|
|
@@ -197,196 +380,89 @@ export function renderCanonicalStateMarkdown(state) {
|
|
|
197
380
|
: "—";
|
|
198
381
|
lines.push("## Agent Trust", "", `${bar} ${(state.agentTrustScore * 100).toFixed(0)}%`, `Trend (last ${state.agentTrustTrend.length}): ${state.agentTrustTrend.join(" → ")}`, `Average: ${avg}`, "");
|
|
199
382
|
}
|
|
200
|
-
if (state.loopSynthesis) {
|
|
201
|
-
lines.push("## Loop Summary", "", state.loopSynthesis, "");
|
|
202
|
-
}
|
|
203
383
|
if (state.externalContext) {
|
|
204
384
|
lines.push("## External Context", "", state.externalContext, "");
|
|
205
385
|
}
|
|
206
386
|
return lines.join("\n").trimEnd() + "\n";
|
|
207
387
|
}
|
|
208
|
-
export function createCanonicalLoopState(request, response, stateFilePath
|
|
209
|
-
|
|
388
|
+
export function createCanonicalLoopState(request, response, stateFilePath,
|
|
389
|
+
/** v3.3: Display-only derived data (round stats, machine git-motion).
|
|
390
|
+
* Optional 4th param — callers that predate v3.3 stay on 3-arg calls. */
|
|
391
|
+
derived) {
|
|
392
|
+
const last = request.last_round_result;
|
|
210
393
|
const objective = response.loop_objective;
|
|
211
394
|
const verificationFlags = request.verification_flags ?? [];
|
|
212
395
|
const rolling = response.rolling_summary;
|
|
213
|
-
const
|
|
396
|
+
const executionEvidence = last?.execution_evidence;
|
|
214
397
|
const changes = unique([
|
|
215
398
|
request.new_since_last_round,
|
|
216
|
-
last?.
|
|
217
|
-
...(last?.
|
|
399
|
+
last?.output_summary,
|
|
400
|
+
...(last?.wrong_assumptions ?? []).map((value) => `Corrected assumption: ${value}`),
|
|
218
401
|
]);
|
|
219
402
|
const discoveries = unique([
|
|
220
|
-
...(last?.
|
|
221
|
-
...(last?.
|
|
222
|
-
...(response.projection?.discoveredFacts ?? []),
|
|
403
|
+
...(last?.discovered_constraints ?? []),
|
|
404
|
+
...(last?.emerged_subtasks ?? []),
|
|
223
405
|
]);
|
|
224
406
|
const blockers = unique([
|
|
225
|
-
...(last?.
|
|
407
|
+
...(last?.constraint_violations ?? []),
|
|
408
|
+
last?.manual_fixes_needed,
|
|
226
409
|
request.rejection_notice,
|
|
227
|
-
...
|
|
410
|
+
...response.warnings,
|
|
228
411
|
]);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return normalizedFact === normalizedCriterion || normalizedFact === `cr-${deriveItemId(criterion)}`;
|
|
236
|
-
});
|
|
237
|
-
};
|
|
238
|
-
const remainingCriteria = successCriteria.filter((criterion) => !criterionMatches(criterion));
|
|
239
|
-
const criteriaMet = successCriteria.filter(criterionMatches);
|
|
240
|
-
const issueObligations = [...new Set(rolling?.recurring_issues ?? [])]
|
|
241
|
-
.filter((issue) => /regress|回归|compatib|兼容|must remain|不能丢/i.test(issue))
|
|
242
|
-
.map((description, index) => ({
|
|
243
|
-
id: `reg-${deriveItemId(description)}`,
|
|
244
|
-
description,
|
|
245
|
-
sourceRound: Math.max(1, request.round - index),
|
|
246
|
-
evidenceRefs: [],
|
|
247
|
-
status: "open",
|
|
248
|
-
}));
|
|
249
|
-
const regressionObligations = response.projection?.regressionObligations?.length
|
|
250
|
-
? response.projection.regressionObligations
|
|
251
|
-
: issueObligations;
|
|
252
|
-
const phaseHistory = (rolling?.milestones ?? []).map((milestone, index) => ({
|
|
253
|
-
id: `phase-${milestone.generated_at_round}-${index + 1}`,
|
|
254
|
-
label: milestone.label,
|
|
255
|
-
status: "completed",
|
|
256
|
-
enteredAtRound: milestone.round_range.start,
|
|
257
|
-
closedAtRound: milestone.round_range.end,
|
|
258
|
-
outcome: milestone.outcome,
|
|
259
|
-
evidenceRefs: [],
|
|
260
|
-
}));
|
|
261
|
-
const lastClosedPhase = phaseHistory.at(-1);
|
|
262
|
-
if (lastClosedPhase) {
|
|
263
|
-
phaseHistory.push({
|
|
264
|
-
id: `phase-active-${lastClosedPhase.closedAtRound + 1}`,
|
|
265
|
-
label: `After ${lastClosedPhase.label}`,
|
|
266
|
-
status: "active",
|
|
267
|
-
enteredAtRound: lastClosedPhase.closedAtRound + 1,
|
|
268
|
-
outcome: "Work after the latest verified phase boundary is in progress.",
|
|
269
|
-
evidenceRefs: [],
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
phaseHistory.push({
|
|
274
|
-
id: "phase-initial",
|
|
275
|
-
label: "Execution",
|
|
276
|
-
status: "active",
|
|
277
|
-
enteredAtRound: 1,
|
|
278
|
-
outcome: request.round <= 1 ? "Awaiting first accepted round." : "Work is in progress.",
|
|
279
|
-
evidenceRefs: [],
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
const verificationError = verificationFlags.find((flag) => flag.severity === "error");
|
|
283
|
-
const activeSubGoal = (response.sub_goals ?? []).find((subGoal) => subGoal.status === "in_progress") ?? (response.sub_goals ?? []).find((subGoal) => subGoal.status === "pending");
|
|
284
|
-
const currentFocus = verificationError?.detail
|
|
285
|
-
|| blockers[0]
|
|
286
|
-
|| activeSubGoal?.description
|
|
287
|
-
|| remainingCriteria[0]
|
|
288
|
-
|| objective?.objective
|
|
289
|
-
|| request.task;
|
|
290
|
-
const suggestedNextAction = verificationError
|
|
291
|
-
? `Resolve verification finding: ${verificationError.detail}`
|
|
292
|
-
: blockers[0]
|
|
293
|
-
? `Remove blocker with evidence: ${blockers[0]}`
|
|
294
|
-
: activeSubGoal
|
|
295
|
-
? `Advance sub-goal ${activeSubGoal.id}: ${activeSubGoal.description}`
|
|
296
|
-
: remainingCriteria[0]
|
|
297
|
-
? `Verify remaining criterion: ${remainingCriteria[0]}`
|
|
298
|
-
: `Produce verifiable progress toward: ${currentFocus}`;
|
|
299
|
-
const blockerGate = blockers[0] ? deriveGate(blockers[0], [blockers[0]]).gate : null;
|
|
300
|
-
const activeGate = blockerGate?.kind === "user"
|
|
301
|
-
? blockerGate
|
|
302
|
-
: verificationError
|
|
303
|
-
? {
|
|
304
|
-
kind: "agent",
|
|
305
|
-
problem: verificationError.detail,
|
|
306
|
-
requiredEvidence: [verificationError.check],
|
|
307
|
-
suggestedResolution: "Resolve the verification finding and rerun the affected check.",
|
|
308
|
-
}
|
|
309
|
-
: blockerGate;
|
|
310
|
-
const todoProjection = currentFocus
|
|
311
|
-
? {
|
|
312
|
-
id: deriveTodoId(currentFocus),
|
|
313
|
-
sourceType: verificationError
|
|
314
|
-
? "verification"
|
|
315
|
-
: blockers.length > 0
|
|
316
|
-
? "blocker"
|
|
317
|
-
: activeSubGoal
|
|
318
|
-
? "sub_goal"
|
|
319
|
-
: "criterion",
|
|
320
|
-
sourceId: verificationError?.check ?? activeSubGoal?.id ?? "objective",
|
|
321
|
-
action: suggestedNextAction || currentFocus,
|
|
322
|
-
completionSignal: remainingCriteria[0]
|
|
323
|
-
|| "Provide verifiable evidence for the current focus.",
|
|
324
|
-
evidenceRequirements: verificationError ? [verificationError.check] : [],
|
|
325
|
-
gate: activeGate?.kind ?? "none",
|
|
326
|
-
}
|
|
327
|
-
: null;
|
|
328
|
-
const handoff = {
|
|
329
|
-
goalId: response.goal_id,
|
|
330
|
-
roundId: `loop:${response.loop_id || request.loop_id}:round:${response.round || request.round}`,
|
|
331
|
-
phaseId: phaseHistory.at(-1)?.id,
|
|
332
|
-
focus: currentFocus,
|
|
333
|
-
verifiedFacts: criteriaMet,
|
|
334
|
-
unresolvedBlockers: blockers,
|
|
335
|
-
carriedConstraints: unique(response.constraints_active)
|
|
336
|
-
.slice(0, getPolicy().checkpoint.max_carried_constraints),
|
|
337
|
-
failedAttempts: unique(rolling?.key_outcomes ?? [])
|
|
338
|
-
.filter((outcome) => /\bfailed\b/i.test(outcome)),
|
|
339
|
-
wrongAssumptions: unique(last?.discoveries?.wrongAssumptions ?? []),
|
|
340
|
-
evidenceRefs: unique(response.projection?.verifiedEvidenceRefs ?? []),
|
|
341
|
-
regressionObligations,
|
|
342
|
-
suggestedNextAction: suggestedNextAction || undefined,
|
|
343
|
-
gate: activeGate ?? undefined,
|
|
344
|
-
};
|
|
412
|
+
// v3.4: The ACTIVE Round Contract (derived — loop-compiler computes it
|
|
413
|
+
// from committed rounds) drives the Current Task — the contract IS "what
|
|
414
|
+
// to do this round"; the original objective stays in the Objective
|
|
415
|
+
// section above. No active contract → the original task text is used,
|
|
416
|
+
// byte-identical to pre-v3.3 behavior.
|
|
417
|
+
const roundContract = derived?.roundContract ?? null;
|
|
345
418
|
return {
|
|
346
|
-
|
|
419
|
+
schemaVersion: CANONICAL_STATE_SCHEMA_VERSION,
|
|
347
420
|
loopId: response.loop_id || request.loop_id,
|
|
348
421
|
round: response.round || request.round,
|
|
349
|
-
maxRounds: request.max_rounds ??
|
|
422
|
+
maxRounds: request.max_rounds ?? 20,
|
|
350
423
|
goalId: response.goal_id,
|
|
351
424
|
objective: objective?.objective || request.task,
|
|
352
425
|
objectiveVersion: objective?.version ?? 1,
|
|
353
|
-
|
|
426
|
+
currentTask: roundContract ? formatRoundContract(roundContract) : request.task,
|
|
427
|
+
successCriteria: unique(objective?.success_criteria ?? []),
|
|
354
428
|
hardConstraints: unique(objective?.hard_constraints ?? []),
|
|
355
429
|
activeConstraints: unique(response.constraints_active),
|
|
356
430
|
retiredConstraints: unique(response.constraints_retired),
|
|
357
431
|
inactiveConstraints: unique(response.constraints_inactive ?? []),
|
|
358
432
|
constraintMetadata: response.constraint_metadata ?? [],
|
|
359
433
|
changesSinceLastRound: changes,
|
|
360
|
-
remainingCriteria,
|
|
434
|
+
remainingCriteria: unique(last?.execution_evidence?.success_criteria_remaining ?? []),
|
|
361
435
|
blockers,
|
|
362
436
|
verificationFlags,
|
|
363
437
|
discoveries,
|
|
438
|
+
nextAction: last?.next_action?.trim() || response.suggested_next_task,
|
|
364
439
|
rollingOutcomes: unique(rolling?.key_outcomes ?? []),
|
|
365
440
|
recurringIssues: unique(rolling?.recurring_issues ?? []),
|
|
366
441
|
failedPatterns: unique(rolling?.failed_patterns ?? []),
|
|
367
|
-
|
|
442
|
+
milestones: rolling?.milestones ?? [],
|
|
368
443
|
subGoals: response.sub_goals ?? [],
|
|
444
|
+
criterionStatuses: response.criterion_statuses ?? [],
|
|
445
|
+
lessons: response.lessons ?? [],
|
|
369
446
|
agentTrustScore: response.agent_trust_score,
|
|
370
447
|
agentTrustTrend: response.agent_trust_trend ?? [],
|
|
448
|
+
suggestedNextTask: response.suggested_next_task,
|
|
371
449
|
externalContext: request.external_context?.trim() ?? "",
|
|
372
450
|
stateFilePath,
|
|
373
451
|
progress: {
|
|
374
|
-
estimate:
|
|
375
|
-
criteriaMet,
|
|
376
|
-
criteriaRemaining:
|
|
377
|
-
filesChanged: unique(
|
|
378
|
-
tests:
|
|
452
|
+
estimate: executionEvidence?.progress_estimate ?? null,
|
|
453
|
+
criteriaMet: unique(executionEvidence?.success_criteria_met ?? []),
|
|
454
|
+
criteriaRemaining: unique(executionEvidence?.success_criteria_remaining ?? []),
|
|
455
|
+
filesChanged: unique(executionEvidence?.files_changed ?? []),
|
|
456
|
+
tests: executionEvidence?.test_results ?? null,
|
|
379
457
|
},
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
: last ? "stale" : "missing",
|
|
389
|
-
handoff,
|
|
458
|
+
// v3.3: Conditional presence — empty/absent derived data must not add
|
|
459
|
+
// keys, or every state hash would change for rounds without it.
|
|
460
|
+
...(derived?.roundStats && derived.roundStats.length > 0
|
|
461
|
+
? { roundStats: derived.roundStats }
|
|
462
|
+
: {}),
|
|
463
|
+
...(derived?.machineStatus ? { machineStatus: derived.machineStatus } : {}),
|
|
464
|
+
// v3.3: Conditional presence — no contract, no key (hash stability).
|
|
465
|
+
...(roundContract ? { roundContract } : {}),
|
|
390
466
|
};
|
|
391
467
|
}
|
|
392
468
|
//# sourceMappingURL=canonical-state.js.map
|