loopforge 3.9.2 → 4.0.7
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 +236 -112
- package/dist/audit.d.ts +73 -15
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +131 -61
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +152 -40
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +382 -206
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -12
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +0 -2
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +0 -4
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +105 -141
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +249 -372
- package/dist/committed-round.js.map +1 -1
- package/dist/constraint-lifecycle.d.ts +73 -0
- package/dist/constraint-lifecycle.d.ts.map +1 -0
- package/dist/constraint-lifecycle.js +174 -0
- package/dist/constraint-lifecycle.js.map +1 -0
- package/dist/context-boundary.d.ts +83 -0
- package/dist/context-boundary.d.ts.map +1 -0
- package/dist/context-boundary.js +101 -0
- package/dist/context-boundary.js.map +1 -0
- package/dist/contract-items.d.ts +12 -8
- package/dist/contract-items.d.ts.map +1 -1
- package/dist/contract-items.js +31 -26
- package/dist/contract-items.js.map +1 -1
- package/dist/contract-requirement.d.ts +47 -0
- package/dist/contract-requirement.d.ts.map +1 -0
- package/dist/contract-requirement.js +51 -0
- package/dist/contract-requirement.js.map +1 -0
- package/dist/criterion-status.d.ts +35 -0
- package/dist/criterion-status.d.ts.map +1 -0
- package/dist/criterion-status.js +89 -0
- package/dist/criterion-status.js.map +1 -0
- package/dist/enforcement-gate.d.ts +32 -67
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +203 -439
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +9 -103
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +28 -887
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-claims.d.ts +33 -31
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +58 -72
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-posture-view.d.ts +19 -0
- package/dist/evidence-posture-view.d.ts.map +1 -0
- package/dist/evidence-posture-view.js +116 -0
- package/dist/evidence-posture-view.js.map +1 -0
- package/dist/evidence-posture.d.ts +73 -0
- package/dist/evidence-posture.d.ts.map +1 -0
- package/dist/evidence-posture.js +281 -0
- package/dist/evidence-posture.js.map +1 -0
- package/dist/evidence-provider.d.ts +55 -37
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +182 -332
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +167 -27
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +373 -58
- package/dist/explain.js.map +1 -1
- package/dist/field-accounting.d.ts.map +1 -1
- package/dist/field-accounting.js +3 -22
- package/dist/field-accounting.js.map +1 -1
- package/dist/generate-schema.js +13 -8
- package/dist/generate-schema.js.map +1 -1
- package/dist/git-boundary.d.ts +128 -0
- package/dist/git-boundary.d.ts.map +1 -0
- package/dist/git-boundary.js +625 -0
- package/dist/git-boundary.js.map +1 -0
- package/dist/git-facts.d.ts +22 -0
- package/dist/git-facts.d.ts.map +1 -0
- package/dist/git-facts.js +29 -0
- package/dist/git-facts.js.map +1 -0
- package/dist/git-motion.d.ts +37 -0
- package/dist/git-motion.d.ts.map +1 -0
- package/dist/git-motion.js +59 -0
- package/dist/git-motion.js.map +1 -0
- package/dist/history.d.ts +44 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +16 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +80 -14
- package/dist/init.js.map +1 -1
- package/dist/loop-compiler.d.ts +51 -19
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +423 -507
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +6 -5
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +153 -35
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +285 -81
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +1629 -574
- 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 +61 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +81 -39
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +963 -271
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +23 -31
- package/dist/mcp/submission-boundary.d.ts.map +1 -1
- package/dist/mcp/submission-boundary.js +242 -82
- package/dist/mcp/submission-boundary.js.map +1 -1
- package/dist/mcp/submission-handle.d.ts +66 -0
- package/dist/mcp/submission-handle.d.ts.map +1 -0
- package/dist/mcp/submission-handle.js +173 -0
- package/dist/mcp/submission-handle.js.map +1 -0
- package/dist/mcp/tools.d.ts +17 -7
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +763 -244
- package/dist/mcp/tools.js.map +1 -1
- package/dist/open-question.d.ts +7 -0
- package/dist/open-question.d.ts.map +1 -0
- package/dist/open-question.js +14 -0
- package/dist/open-question.js.map +1 -0
- package/dist/policy-metrics.d.ts +31 -21
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +117 -64
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +15 -40
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +13 -13
- package/dist/policy.js.map +1 -1
- package/dist/progress.d.ts +45 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +136 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-assembler.d.ts +28 -9
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +592 -603
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +39 -0
- package/dist/prompt-coverage.d.ts.map +1 -0
- package/dist/prompt-coverage.js +80 -0
- package/dist/prompt-coverage.js.map +1 -0
- package/dist/prompt-freshness.d.ts +25 -0
- package/dist/prompt-freshness.d.ts.map +1 -0
- package/dist/prompt-freshness.js +88 -0
- package/dist/prompt-freshness.js.map +1 -0
- package/dist/prompt-policy.d.ts +14 -4
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +29 -10
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +875 -419
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +73 -99
- package/dist/protocol.js.map +1 -1
- package/dist/recovery-guidance.d.ts +13 -0
- package/dist/recovery-guidance.d.ts.map +1 -0
- package/dist/recovery-guidance.js +15 -0
- package/dist/recovery-guidance.js.map +1 -0
- package/dist/recovery.d.ts +68 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +234 -0
- package/dist/recovery.js.map +1 -0
- package/dist/replay.d.ts +42 -8
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +311 -84
- package/dist/replay.js.map +1 -1
- package/dist/result-facts.d.ts +45 -0
- package/dist/result-facts.d.ts.map +1 -0
- package/dist/result-facts.js +86 -0
- package/dist/result-facts.js.map +1 -0
- package/dist/round-contract.d.ts +38 -8
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +71 -31
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +45 -91
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +254 -271
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +56 -16
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +225 -39
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +33 -52
- package/dist/round-facts.d.ts.map +1 -1
- package/dist/round-facts.js +54 -157
- package/dist/round-facts.js.map +1 -1
- package/dist/round-guidance.d.ts +56 -0
- package/dist/round-guidance.d.ts.map +1 -0
- package/dist/round-guidance.js +161 -0
- package/dist/round-guidance.js.map +1 -0
- package/dist/round-outcome.d.ts +21 -0
- package/dist/round-outcome.d.ts.map +1 -0
- package/dist/round-outcome.js +31 -0
- package/dist/round-outcome.js.map +1 -0
- package/dist/round-transaction.d.ts +168 -21
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +856 -39
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +40 -60
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +455 -384
- package/dist/self-eval.js.map +1 -1
- package/dist/storage.d.ts +16 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +54 -4
- package/dist/storage.js.map +1 -1
- package/dist/subgoal-state.d.ts +2 -3
- package/dist/subgoal-state.d.ts.map +1 -1
- package/dist/subgoal-state.js +2 -3
- package/dist/subgoal-state.js.map +1 -1
- package/dist/submission-guidance.d.ts +43 -21
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +368 -262
- package/dist/submission-guidance.js.map +1 -1
- package/dist/submission-receipt.d.ts +16 -8
- package/dist/submission-receipt.d.ts.map +1 -1
- package/dist/submission-receipt.js +146 -51
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +47 -7
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +66 -15
- package/dist/token-utils.js.map +1 -1
- package/dist/transaction-identity.d.ts +9 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +69 -0
- package/dist/transaction-identity.js.map +1 -0
- package/dist/verification-actions.d.ts +13 -0
- package/dist/verification-actions.d.ts.map +1 -0
- package/dist/verification-actions.js +44 -0
- package/dist/verification-actions.js.map +1 -0
- package/dist/verification-gate.d.ts +26 -46
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +218 -427
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +187 -0
- package/dist/working-memory.d.ts.map +1 -0
- package/dist/working-memory.js +767 -0
- package/dist/working-memory.js.map +1 -0
- package/dist/workspace-checkpoint.d.ts +88 -0
- package/dist/workspace-checkpoint.d.ts.map +1 -0
- package/dist/workspace-checkpoint.js +326 -0
- package/dist/workspace-checkpoint.js.map +1 -0
- package/dist/workspace-restore.d.ts +68 -0
- package/dist/workspace-restore.d.ts.map +1 -0
- package/dist/workspace-restore.js +604 -0
- package/dist/workspace-restore.js.map +1 -0
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +0 -17
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +5 -19
- package/package.json +2 -2
- package/skills/forge/SKILL.md +212 -0
- package/skills/forge/references/evidence-and-contracts.md +204 -0
- package/skills/forge/references/recovery-and-views.md +188 -0
- package/dist/cognitive-facts.d.ts +0 -30
- package/dist/cognitive-facts.d.ts.map +0 -1
- package/dist/cognitive-facts.js +0 -113
- package/dist/cognitive-facts.js.map +0 -1
- package/dist/loop-extras-parser.d.ts +0 -81
- package/dist/loop-extras-parser.d.ts.map +0 -1
- package/dist/loop-extras-parser.js +0 -213
- package/dist/loop-extras-parser.js.map +0 -1
- package/dist/loop-projection.d.ts +0 -6
- package/dist/loop-projection.d.ts.map +0 -1
- package/dist/loop-projection.js +0 -28
- package/dist/loop-projection.js.map +0 -1
- package/skills/perception/SKILL.md +0 -370
package/dist/canonical-state.js
CHANGED
|
@@ -4,11 +4,33 @@
|
|
|
4
4
|
* consume the same value so they cannot silently drift apart.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { deriveItemId, STABLE_ID_RE, unique } from "./token-utils.js";
|
|
7
|
+
import { deriveCriterionId, deriveItemId, normalizeText, STABLE_ID_RE, unique, uniqueNormalized, } from "./token-utils.js";
|
|
8
8
|
import { claimedMetCriteria, claimedRemainingCriteria } from "./self-eval.js";
|
|
9
9
|
import { getPolicy } from "./policy.js";
|
|
10
10
|
import { deriveConfiguredCapability } from "./policy.js";
|
|
11
|
-
|
|
11
|
+
import { formatWorkingMemoryState, hasWorkingMemoryContent, } from "./working-memory.js";
|
|
12
|
+
import { isOpenQuestionId } from "./open-question.js";
|
|
13
|
+
import { MACHINE_EVIDENCE_AUTHORITY, formatEvidencePostureLines, } from "./evidence-posture-view.js";
|
|
14
|
+
import { formatContextBoundaryLabel } from "./round-guidance.js";
|
|
15
|
+
/** v4.0.3: schema 7 adds the previous/retry evidence posture, explicitly split
|
|
16
|
+
* reported progress/issues/warnings, compile context and Working Memory
|
|
17
|
+
* source counts. v4.0.4: schema 8 records the renamed context vocabulary
|
|
18
|
+
* (`fresh_invocation`) in the derived declaration and stateHash. v4.0.5:
|
|
19
|
+
* schema 9 adds the optional `currentUserInstruction` projection; schema 10
|
|
20
|
+
* (v4.0.6) makes the objective the WORKING objective, falls back to it for
|
|
21
|
+
* the Current Task, and removes the Agent-reported test counts. The state
|
|
22
|
+
* file is regenerated every round; an older file is a stale view, not a
|
|
23
|
+
* legacy document to decode. */
|
|
24
|
+
export const CANONICAL_STATE_SCHEMA_VERSION = 10;
|
|
25
|
+
/** v4.0.5: the ONE wording for "the host supplied no instruction". Prompt and
|
|
26
|
+
* state file both render it; neither infers an instruction from the old task
|
|
27
|
+
* or transcript. */
|
|
28
|
+
export const CURRENT_USER_INSTRUCTION_NOT_PROVIDED = "not provided for this invocation (the Objective and Current Task remain operative).";
|
|
29
|
+
/** v4.0.5: the protected section's fixed authority note. The instruction is
|
|
30
|
+
* invocation input; it never edits the loop's objective or acceptance. */
|
|
31
|
+
export const CURRENT_USER_INSTRUCTION_AUTHORITY = "Host-provided current user instruction for this invocation. It does not " +
|
|
32
|
+
"rewrite the Objective, Success Criteria, hard constraints, or the Round " +
|
|
33
|
+
"Contract; a changed objective or acceptance requires a new loop.";
|
|
12
34
|
/** v3.7.1: Presentation view of sub-goals shared by prompts and the state
|
|
13
35
|
* file (one derivation, no second copy). Only ACTIVE items render as rows
|
|
14
36
|
* — pending/in_progress/blocked — ordered blocked → in_progress → pending
|
|
@@ -31,6 +53,97 @@ export function activeSubGoalView(subGoals, cap) {
|
|
|
31
53
|
total: subGoals.length,
|
|
32
54
|
};
|
|
33
55
|
}
|
|
56
|
+
export function partitionActiveConstraints(state) {
|
|
57
|
+
const criteriaKeys = new Set(state.successCriteria.map((text) => normalizeText(text)));
|
|
58
|
+
const hardKeys = new Set(state.hardConstraints.map((text) => normalizeText(text)));
|
|
59
|
+
const hard = [];
|
|
60
|
+
const other = [];
|
|
61
|
+
for (const text of state.activeConstraints) {
|
|
62
|
+
const key = normalizeText(text);
|
|
63
|
+
if (criteriaKeys.has(key))
|
|
64
|
+
continue;
|
|
65
|
+
if (hardKeys.has(key))
|
|
66
|
+
hard.push(text);
|
|
67
|
+
else
|
|
68
|
+
other.push(text);
|
|
69
|
+
}
|
|
70
|
+
return { criteria: [...state.successCriteria], hard, other };
|
|
71
|
+
}
|
|
72
|
+
function criterionIcon(status) {
|
|
73
|
+
switch (status) {
|
|
74
|
+
case "verified": return "✅";
|
|
75
|
+
case "claimed": return "🟡";
|
|
76
|
+
case "insufficient": return "🟠";
|
|
77
|
+
case "contradicted": return "⛔";
|
|
78
|
+
case "remaining": return "⬜";
|
|
79
|
+
default: return "❔";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** v3.9.4: the ONE criterion row renderer for the normative prompt section.
|
|
83
|
+
* It consumes the same `successCriteria`/`criterionStatuses` pair the state
|
|
84
|
+
* file and submission boundary use, and reports the exact ids it emitted so
|
|
85
|
+
* prompt coverage can prove them. */
|
|
86
|
+
export function formatCriterionRows(statuses, successCriteria) {
|
|
87
|
+
const byText = new Map(statuses.map((status) => [normalizeText(status.text), status]));
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const lines = [];
|
|
90
|
+
const ids = [];
|
|
91
|
+
for (const text of successCriteria) {
|
|
92
|
+
const status = byText.get(normalizeText(text));
|
|
93
|
+
const id = status?.id ?? deriveCriterionId(text);
|
|
94
|
+
if (seen.has(id))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(id);
|
|
97
|
+
ids.push(id);
|
|
98
|
+
lines.push(`- ${criterionIcon(status?.status ?? "unknown")} [\`${id}\`] ${text}`);
|
|
99
|
+
}
|
|
100
|
+
if (lines.length > 0) {
|
|
101
|
+
// v4.0.3: the icons are meaningless without their provenance.
|
|
102
|
+
lines.push("> ✅ machine verified · 🟡 Agent claim · 🟠 insufficient · " +
|
|
103
|
+
"⛔ contradicted · ⬜ Agent-reported remaining · ❔ unknown");
|
|
104
|
+
}
|
|
105
|
+
return { lines, ids };
|
|
106
|
+
}
|
|
107
|
+
/** v3.9.4: the ONE active-sub-goal formatter shared by the prompt and the
|
|
108
|
+
* state file. Rows past `cap` lose their descriptions but NEVER their ids:
|
|
109
|
+
* the overflow line lists every remaining `sg-*`. */
|
|
110
|
+
export function formatActiveSubGoals(subGoals, round, cap = getPolicy().evolution.max_active_subgoals) {
|
|
111
|
+
const view = activeSubGoalView([...subGoals], Number.POSITIVE_INFINITY);
|
|
112
|
+
const visible = view.active;
|
|
113
|
+
const shown = visible.slice(0, Math.max(0, cap));
|
|
114
|
+
const hidden = visible.slice(shown.length);
|
|
115
|
+
const lines = [];
|
|
116
|
+
for (const sg of shown) {
|
|
117
|
+
const statusIcon = sg.status === "in_progress" ? "🔄" :
|
|
118
|
+
sg.status === "blocked" ? "🚫" : "⏳";
|
|
119
|
+
const stale = sg.status === "pending" &&
|
|
120
|
+
round - sg.declared_at_round >= 10 ? " ⚠️ stale" : "";
|
|
121
|
+
const detail = sg.status === "in_progress"
|
|
122
|
+
? ` (since R${sg.status_changed_at_round})`
|
|
123
|
+
: sg.status === "pending"
|
|
124
|
+
? ` (since R${sg.declared_at_round})`
|
|
125
|
+
: ` (blocked R${sg.status_changed_at_round})`;
|
|
126
|
+
lines.push(`- ${statusIcon} [\`${sg.id}\`] ${sg.description}${detail}${stale}`);
|
|
127
|
+
}
|
|
128
|
+
if (hidden.length > 0) {
|
|
129
|
+
lines.push(`- also active (ids only): ${hidden.map((sg) => `\`${sg.id}\``).join(", ")}`);
|
|
130
|
+
}
|
|
131
|
+
lines.push(`> ${view.total} total · ${view.activeTotal} active · ` +
|
|
132
|
+
`${view.done} done · ${view.canceled} canceled`);
|
|
133
|
+
return { lines, ids: visible.map((sg) => sg.id) };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The active contract is the Current Task only when the runtime has a
|
|
137
|
+
* machine-verifiable contract capability and this invocation did not provide
|
|
138
|
+
* an explicit instruction. The contract remains a separately rendered
|
|
139
|
+
* committed fact in every other case; this helper keeps prompt and state-file
|
|
140
|
+
* projections on the same rule as state construction.
|
|
141
|
+
*/
|
|
142
|
+
export function contractDrivesCurrentTask(state) {
|
|
143
|
+
return state.roundContract !== undefined &&
|
|
144
|
+
state.currentUserInstruction === undefined &&
|
|
145
|
+
state.capability.contractVerificationAvailable;
|
|
146
|
+
}
|
|
34
147
|
// unique() imported from token-utils.ts
|
|
35
148
|
/** Deterministic JSON serialization used by state and prompt hashes. */
|
|
36
149
|
export function stableStringify(value) {
|
|
@@ -102,132 +215,132 @@ function addListWithIds(lines, title, values, prefix) {
|
|
|
102
215
|
function addListWithIdentities(lines, title, values, metadata) {
|
|
103
216
|
if (values.length === 0)
|
|
104
217
|
return;
|
|
105
|
-
const
|
|
218
|
+
const metaByText = new Map(metadata.map((meta) => [meta.text, meta]));
|
|
106
219
|
lines.push(`## ${title}`, "");
|
|
107
220
|
for (const value of values) {
|
|
108
221
|
const trimmed = value.trim();
|
|
109
222
|
if (!trimmed)
|
|
110
223
|
continue;
|
|
224
|
+
const meta = metaByText.get(trimmed);
|
|
111
225
|
if (STABLE_ID_RE.test(trimmed)) {
|
|
112
226
|
lines.push(`- [\`${trimmed}\`] ${trimmed}`);
|
|
113
227
|
}
|
|
114
228
|
else {
|
|
115
|
-
const id =
|
|
116
|
-
|
|
229
|
+
const id = meta?.id ?? `c-${deriveItemId(trimmed)}`;
|
|
230
|
+
const provenance = constraintProvenanceLabel(meta);
|
|
231
|
+
lines.push(`- [\`${id}\`] ${trimmed}${provenance}`);
|
|
117
232
|
}
|
|
118
233
|
}
|
|
119
234
|
lines.push("");
|
|
120
235
|
}
|
|
121
|
-
/** v3.
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
* in full by the sections that own them. Returns "" when no milestone exists,
|
|
127
|
-
* so the caller renders no section at all — bare position is already in the
|
|
128
|
-
* prompt header.
|
|
129
|
-
*
|
|
130
|
-
* Shared by the state-file and prompt renderers so they cannot drift apart
|
|
131
|
-
* (module contract above). Presentation only; never feeds enforcement. */
|
|
132
|
-
export function buildPhaseLine(state) {
|
|
133
|
-
const lastMilestone = state.milestones.length > 0
|
|
134
|
-
? state.milestones[state.milestones.length - 1]
|
|
135
|
-
: null;
|
|
136
|
-
// No phase boundary yet → no phase to report. Position alone ("round 3/200")
|
|
137
|
-
// is already in the prompt header, so rendering it again would be noise.
|
|
138
|
-
if (!lastMilestone)
|
|
236
|
+
/** v3.9.7: the ONE constraint provenance label shared by the state file and
|
|
237
|
+
* the prompt. A discovered/preserved constraint is an Agent report, never a
|
|
238
|
+
* machine-verified fact. */
|
|
239
|
+
export function constraintProvenanceLabel(meta) {
|
|
240
|
+
if (!meta)
|
|
139
241
|
return "";
|
|
140
|
-
const
|
|
141
|
-
?
|
|
142
|
-
:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
242
|
+
const round = meta.source_round !== undefined && meta.source_round >= 1
|
|
243
|
+
? ` R${meta.source_round}`
|
|
244
|
+
: "";
|
|
245
|
+
switch (meta.source) {
|
|
246
|
+
case "discovered":
|
|
247
|
+
return ` (Agent-declared${round}; report, not machine-verified)`;
|
|
248
|
+
case "preserved":
|
|
249
|
+
return ` (Agent discovery preserved by rollback${round}; report)`;
|
|
250
|
+
case "plan":
|
|
251
|
+
return ` (plan/carried${round})`;
|
|
252
|
+
case "unrecorded":
|
|
253
|
+
return " (origin not recorded in committed rounds)";
|
|
254
|
+
default:
|
|
255
|
+
return "";
|
|
149
256
|
}
|
|
150
|
-
return parts.join(" · ");
|
|
151
257
|
}
|
|
152
|
-
|
|
153
|
-
* body — the single formatting source shared by prompts and the state
|
|
154
|
-
* file (module contract above). The original objective is NOT here: it
|
|
155
|
-
* lives in the Objective section. Empty arrays render no line. */
|
|
156
|
-
export function formatRoundContract(contract,
|
|
258
|
+
export function formatRoundContractWithIds(contract,
|
|
157
259
|
/** v3.8: the derived item statuses. When given, each item renders with its
|
|
158
260
|
* machine status — the agent sees the verification debt in its own task. */
|
|
159
261
|
statuses) {
|
|
160
262
|
const lines = [];
|
|
263
|
+
const emittedIds = [contract.id];
|
|
161
264
|
const heading = contract.work_item?.trim();
|
|
162
265
|
lines.push(`**${heading || "Round Contract"}** (${contract.id})`);
|
|
163
266
|
const statusById = new Map((statuses?.items ?? []).map((item) => [item.itemId, item]));
|
|
164
|
-
|
|
267
|
+
const items = contract.items;
|
|
268
|
+
if (items.length > 0) {
|
|
269
|
+
lines.push("- Acceptance conditions:");
|
|
270
|
+
}
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
emittedIds.push(item.id);
|
|
165
273
|
const machine = statusById.get(item.id);
|
|
166
274
|
const mark = machine?.status === "verified" ? "✅"
|
|
167
275
|
: machine?.status === "insufficient" ? "🟠"
|
|
168
276
|
: machine?.status === "contradicted" ? "⛔"
|
|
169
277
|
: machine?.status === "pending" ? "⬜"
|
|
170
278
|
: "";
|
|
171
|
-
|
|
279
|
+
const status = machine ? ` — ${machine.status}` : "";
|
|
280
|
+
lines.push(` - ${mark ? `${mark} ` : ""}[\`${item.id}\`] ${item.description}${status}`);
|
|
172
281
|
if (item.verify_with.length > 0) {
|
|
173
|
-
lines.push(`
|
|
282
|
+
lines.push(` - Verify via: ${item.verify_with.join(", ")}`);
|
|
174
283
|
}
|
|
175
|
-
if (item.criterion_refs.length > 0) {
|
|
176
|
-
lines.push(`
|
|
284
|
+
if ((item.criterion_refs ?? []).length > 0) {
|
|
285
|
+
lines.push(` - Criteria: ${(item.criterion_refs ?? []).join(", ")}`);
|
|
177
286
|
}
|
|
178
287
|
if (machine?.reasons.length) {
|
|
179
|
-
lines.push(`
|
|
288
|
+
lines.push(` - Unverified because: ${machine.reasons[0]}`);
|
|
180
289
|
}
|
|
181
290
|
}
|
|
182
|
-
|
|
183
|
-
|
|
291
|
+
if (contract.scope.length > 0) {
|
|
292
|
+
// v3.9.9: scope is the Agent's declared intent, not a permission.
|
|
293
|
+
lines.push("- Declared scope (Agent report; not enforced):");
|
|
294
|
+
for (const entry of contract.scope) {
|
|
295
|
+
lines.push(` - ${entry}`);
|
|
296
|
+
}
|
|
184
297
|
}
|
|
185
|
-
return lines.join("\n");
|
|
298
|
+
return { text: lines.join("\n"), emittedIds };
|
|
186
299
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
/** Milestone heading line ("**🏁 Round 7** (Rounds 3–7)"). Shared by the
|
|
191
|
-
* L2 Phase History and the detailed L1 renderer; the L1 copy previously
|
|
192
|
-
* rendered the range as "R3–R7", a format only this heading used. */
|
|
193
|
-
export function milestoneHeading(milestone) {
|
|
194
|
-
const kindIcon = milestone.kind === "agent_declared"
|
|
195
|
-
? "🏁"
|
|
196
|
-
: milestone.kind === "criteria_milestone" ? "✅" : "📍";
|
|
197
|
-
return `**${kindIcon} ${milestone.label}** (Rounds ${milestone.round_range.start}–${milestone.round_range.end})`;
|
|
300
|
+
/** Backwards-compatible text-only view used by the state file. */
|
|
301
|
+
export function formatRoundContract(contract, statuses) {
|
|
302
|
+
return formatRoundContractWithIds(contract, statuses).text;
|
|
198
303
|
}
|
|
199
304
|
const STATE_SECTION_TIER = {
|
|
200
305
|
// Current — what the next round must hold in working memory.
|
|
201
306
|
"Loop Objective": "Current",
|
|
202
307
|
"Current Task": "Current",
|
|
308
|
+
"Active Round Contract": "Current",
|
|
309
|
+
"Current User Instruction": "Current",
|
|
203
310
|
"Success Criteria": "Current",
|
|
204
311
|
"Hard Constraints": "Current",
|
|
205
312
|
"Active Constraints": "Current",
|
|
206
|
-
|
|
207
|
-
|
|
313
|
+
// v4.0.3: Agent-reported blockers and remaining claims are one report
|
|
314
|
+
// surface; compile warnings are a separate machine/compile surface.
|
|
315
|
+
"Agent-Reported Blockers": "Current",
|
|
316
|
+
"Agent-Reported Remaining Criteria": "Current",
|
|
317
|
+
"Agent-Reported Resolutions": "Recent",
|
|
208
318
|
"Sub-Goal Dashboard": "Current",
|
|
209
|
-
"External Context": "Current",
|
|
210
319
|
// v3.8.1: the one-line phase position (the only fact the deleted Roadmap
|
|
211
320
|
// section carried that no other section renders). It is orientation for the
|
|
212
321
|
// NEXT round, so it belongs with the working-memory tier — and it must be
|
|
213
322
|
// listed here at all: an unlisted title is not a heading, so `## Phase` was
|
|
214
323
|
// absorbed into whatever section happened to precede it (its body even kept
|
|
215
324
|
// a raw `## ` line) and inherited that section's tier.
|
|
216
|
-
Phase: "Current",
|
|
217
325
|
// Recent — this round's boundary facts (and, in a recovery window, the
|
|
218
326
|
// derived Recovery Brief injected at the top of this tier).
|
|
219
327
|
"Progress Dashboard": "Recent",
|
|
220
328
|
Verification: "Recent",
|
|
221
|
-
|
|
222
|
-
|
|
329
|
+
// v4.0.3: the retry/governance facts and machine evidence live next to the
|
|
330
|
+
// round boundary they describe.
|
|
331
|
+
"Retry Requirements": "Recent",
|
|
332
|
+
"Compile Warnings": "Recent",
|
|
333
|
+
"Machine Evidence": "Recent",
|
|
334
|
+
"Rejected Attempt Evidence": "Recent",
|
|
335
|
+
// v4.0.2: bounded committed working memory. It replaces the former
|
|
336
|
+
// Corrections/Discoveries sections; discovered constraints remain visible
|
|
337
|
+
// through active-constraint provenance.
|
|
338
|
+
"Working Memory": "Recent",
|
|
223
339
|
// Historical Summary — the loop's memory skeleton, kept under existing
|
|
224
340
|
// milestone/window caps.
|
|
225
341
|
"Goal → Criteria": "Historical Summary",
|
|
226
|
-
"Cross-Round Outcomes": "Historical Summary",
|
|
227
342
|
"Recurring Flags": "Historical Summary",
|
|
228
|
-
"Failed Patterns": "Historical Summary",
|
|
229
343
|
"Retired Constraints": "Historical Summary",
|
|
230
|
-
"Phase History": "Historical Summary",
|
|
231
344
|
};
|
|
232
345
|
const STATE_TIER_ORDER = ["Current", "Recent", "Historical Summary"];
|
|
233
346
|
/** Human/Agent-readable materialized view. It is always reproducible from the
|
|
@@ -237,20 +350,34 @@ const STATE_TIER_ORDER = ["Current", "Recent", "Historical Summary"];
|
|
|
237
350
|
* criterion list, round stats, the FULL recurring-flag history and the phase
|
|
238
351
|
* history. The prompt carries only what the next round needs. */
|
|
239
352
|
export function renderCanonicalStateMarkdown(state, options) {
|
|
353
|
+
const context = state.context ?? options?.context;
|
|
240
354
|
const preamble = [
|
|
241
355
|
`# LoopForge State — ${state.loopId}`,
|
|
242
356
|
"",
|
|
243
357
|
`**Schema**: ${state.schemaVersion}`,
|
|
244
358
|
`**Round**: ${state.round}/${state.maxRounds}`,
|
|
245
359
|
`**Goal ID**: ${state.goalId}`,
|
|
360
|
+
...(state.currentUserInstruction
|
|
361
|
+
? []
|
|
362
|
+
: [`**Current user instruction**: ${CURRENT_USER_INSTRUCTION_NOT_PROVIDED}`]),
|
|
363
|
+
...(context
|
|
364
|
+
? [
|
|
365
|
+
`**Context**: ${formatContextBoundaryLabel(context.mode, context.reason, { recoveryBoundary: (state.recoveryBrief?.length ?? 0) > 0 })}, attempt ${context.attempt}`,
|
|
366
|
+
]
|
|
367
|
+
: []),
|
|
246
368
|
"**Derived**: true",
|
|
247
|
-
`**Source**: round ${state.round} (attempt ${options?.attempt ?? 1})`,
|
|
369
|
+
`**Source**: round ${state.round} (attempt ${context?.attempt ?? options?.attempt ?? 1})`,
|
|
370
|
+
"**Authority**: committed typed rounds are the factual source; this file is a derived view.",
|
|
371
|
+
"**Round commit**: a LoopForge round commit is not a Git commit; it does not verify every workspace change.",
|
|
372
|
+
"**Evidence**: machine evidence = providers, command observations and verified contract items. Agent reports = outcome/files/claims.",
|
|
373
|
+
"**Working memory**: bounded; it may be abbreviated; full history via replay/explain or this file when enabled.",
|
|
248
374
|
`**State hash**: ${hashCanonicalState(state).slice(0, 12)}`,
|
|
249
375
|
"",
|
|
250
376
|
];
|
|
251
377
|
const sections = flatStateSections(state);
|
|
252
|
-
|
|
253
|
-
|
|
378
|
+
const recoveryBrief = state.recoveryBrief ?? options?.recoveryBrief;
|
|
379
|
+
if (recoveryBrief && recoveryBrief.length > 0) {
|
|
380
|
+
sections.unshift({ title: "Recovery Brief", body: recoveryBrief });
|
|
254
381
|
}
|
|
255
382
|
const byTier = new Map();
|
|
256
383
|
for (const tier of STATE_TIER_ORDER)
|
|
@@ -280,42 +407,78 @@ export function renderCanonicalStateMarkdown(state, options) {
|
|
|
280
407
|
function flatStateSections(state) {
|
|
281
408
|
const lines = [];
|
|
282
409
|
lines.push("## Loop Objective", "", state.objective, "", "## Current Task", "", state.currentTask);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (state.
|
|
289
|
-
state.
|
|
290
|
-
state.
|
|
291
|
-
state.
|
|
292
|
-
|
|
293
|
-
|
|
410
|
+
if (state.roundContract && !contractDrivesCurrentTask(state)) {
|
|
411
|
+
lines.push("## Active Round Contract", "", formatRoundContract(state.roundContract, state.contractItemStatuses ?? null));
|
|
412
|
+
}
|
|
413
|
+
// The host's invocation instruction renders as its own section only when
|
|
414
|
+
// it is not already visible in Current Task or Objective.
|
|
415
|
+
if (state.currentUserInstruction &&
|
|
416
|
+
state.currentUserInstruction !== state.currentTask.trim() &&
|
|
417
|
+
state.currentUserInstruction !== state.objective.trim()) {
|
|
418
|
+
lines.push("## Current User Instruction", "", state.currentUserInstruction, "", `> ${CURRENT_USER_INSTRUCTION_AUTHORITY}`);
|
|
419
|
+
}
|
|
420
|
+
// v2.11: Success Criteria and Constraints render with stable IDs for exact
|
|
421
|
+
// agent matching. v3.9.4: the three groups are the SAME mutually exclusive
|
|
422
|
+
// partition the prompt uses, so one text never appears with two prefixes.
|
|
423
|
+
const constraintPartition = partitionActiveConstraints(state);
|
|
424
|
+
addListWithIds(lines, "Success Criteria", constraintPartition.criteria, "cr");
|
|
425
|
+
addListWithIdentities(lines, "Hard Constraints", constraintPartition.hard, state.constraintMetadata);
|
|
426
|
+
addListWithIdentities(lines, "Active Constraints", constraintPartition.other, state.constraintMetadata);
|
|
427
|
+
if (state.rejectionNotice) {
|
|
428
|
+
lines.push("## Retry Requirements", "", state.rejectionNotice.trim(), "");
|
|
429
|
+
}
|
|
430
|
+
addList(lines, "Compile Warnings", state.warnings);
|
|
431
|
+
if (state.previousEvidencePosture) {
|
|
432
|
+
lines.push("## Machine Evidence", "", ...formatEvidencePostureLines(state.previousEvidencePosture), "", `> ${MACHINE_EVIDENCE_AUTHORITY}`, "");
|
|
433
|
+
}
|
|
434
|
+
if (state.retryEvidencePosture) {
|
|
435
|
+
lines.push("## Rejected Attempt Evidence", "", "> Not committed; it exists only for the current retry.", "", ...formatEvidencePostureLines(state.retryEvidencePosture), "");
|
|
436
|
+
}
|
|
437
|
+
if (hasWorkingMemoryContent(state.workingMemory)) {
|
|
438
|
+
const working = formatWorkingMemoryState(state.workingMemory);
|
|
439
|
+
lines.push("## Working Memory", "", ...working.lines, "");
|
|
440
|
+
}
|
|
441
|
+
const reported = state.reportedProgress;
|
|
442
|
+
const posture = state.previousEvidencePosture;
|
|
443
|
+
if (reported.criteriaMet.length > 0 ||
|
|
444
|
+
reported.criteriaRemaining.length > 0 ||
|
|
445
|
+
reported.filesChanged.length > 0 ||
|
|
446
|
+
state.machineStatus !== undefined ||
|
|
447
|
+
posture !== undefined) {
|
|
448
|
+
const total = reported.criteriaMet.length + reported.criteriaRemaining.length;
|
|
294
449
|
lines.push("## Progress Dashboard", "");
|
|
295
450
|
if (total > 0) {
|
|
296
|
-
lines.push(`**
|
|
451
|
+
lines.push(`**Agent-reported criteria**: ${reported.criteriaMet.length}/${total}`);
|
|
452
|
+
}
|
|
453
|
+
if (state.criterionStatuses.length > 0) {
|
|
454
|
+
const verifiedCount = state.criterionStatuses.filter((criterion) => criterion.status === "verified").length;
|
|
455
|
+
lines.push(`**Machine-verified criteria**: ${verifiedCount}/${state.criterionStatuses.length}`);
|
|
456
|
+
}
|
|
457
|
+
if (reported.filesChanged.length > 0) {
|
|
458
|
+
lines.push("", "**Agent-reported files**:");
|
|
459
|
+
for (const file of reported.filesChanged)
|
|
460
|
+
lines.push(`- ${file}`);
|
|
461
|
+
}
|
|
462
|
+
if (posture) {
|
|
463
|
+
const observed = posture.workspace.status === "observed"
|
|
464
|
+
? `${posture.workspace.changedPathCount}`
|
|
465
|
+
: posture.workspace.status === "unknown" ? "unknown" : "not applicable";
|
|
466
|
+
lines.push(`**Observed changes (last committed round)**: ${observed}`);
|
|
467
|
+
lines.push(`**Files reconciliation**: ${posture.files?.reconciliation ?? "not_applicable"}`);
|
|
297
468
|
}
|
|
298
469
|
// v3.3: machine side of the comparison — git motion over committed rounds
|
|
299
470
|
// (v3.6: the object always carries definite values — no "unavailable" arm).
|
|
300
471
|
if (state.machineStatus) {
|
|
301
472
|
const ms = state.machineStatus;
|
|
302
|
-
const
|
|
303
|
-
? `
|
|
304
|
-
:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (state.progress.tests) {
|
|
312
|
-
lines.push(`**Tests**: ${state.progress.tests.passed} passed, ` +
|
|
313
|
-
`${state.progress.tests.failed} failed, ${state.progress.tests.skipped} skipped`);
|
|
314
|
-
}
|
|
315
|
-
if (state.progress.filesChanged.length > 0) {
|
|
316
|
-
lines.push("", "**Files Changed**:");
|
|
317
|
-
for (const file of state.progress.filesChanged)
|
|
318
|
-
lines.push(`- ${file}`);
|
|
473
|
+
const providers = ms.providers.length > 0
|
|
474
|
+
? ` (providers: ${ms.providers.join(", ")})`
|
|
475
|
+
: "";
|
|
476
|
+
const progress = ms.machineProgress === "changed"
|
|
477
|
+
? `changes observed in ${ms.changedRounds}/${ms.windowRounds} recent committed rounds${providers}`
|
|
478
|
+
: ms.machineProgress === "unknown"
|
|
479
|
+
? `not observable in the last ${ms.windowRounds} committed rounds${providers}`
|
|
480
|
+
: `no machine-observed progress in the last ${ms.windowRounds} committed rounds${providers}`;
|
|
481
|
+
lines.push(`**Machine progress**: ${progress}`);
|
|
319
482
|
}
|
|
320
483
|
lines.push("");
|
|
321
484
|
}
|
|
@@ -329,20 +492,35 @@ function flatStateSections(state) {
|
|
|
329
492
|
: cs.status === "contradicted" ? "⛔"
|
|
330
493
|
: cs.status === "remaining" ? "⬜" : "❔";
|
|
331
494
|
const idTag = ` [\`${cs.id}\`]`;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
495
|
+
// v4.0.3: the round and the provenance are separate facts. A claimed
|
|
496
|
+
// row names the latest Agent claim; a verified/contradicted/insufficient
|
|
497
|
+
// row names the machine round that reached it.
|
|
498
|
+
const at = cs.status_at_round !== undefined ? ` R${cs.status_at_round}` : "";
|
|
499
|
+
const label = cs.status === "verified" ? `machine verified${at}`
|
|
500
|
+
: cs.status === "claimed" ? `agent claim${at}`
|
|
501
|
+
: cs.status === "insufficient" ? `machine insufficient${at}`
|
|
502
|
+
: cs.status === "contradicted" ? `machine contradicted${at}`
|
|
503
|
+
: cs.status === "remaining" ? `agent reported remaining${at}`
|
|
504
|
+
: "unknown";
|
|
505
|
+
lines.push(`- ${icon}${idTag} ${cs.text} (${label})`);
|
|
506
|
+
}
|
|
507
|
+
lines.push("");
|
|
508
|
+
}
|
|
509
|
+
if (reported.agentReportedRemaining.length > 0) {
|
|
510
|
+
addListWithIds(lines, "Agent-Reported Remaining Criteria", reported.agentReportedRemaining, "cr");
|
|
511
|
+
}
|
|
512
|
+
addList(lines, "Agent-Reported Blockers", state.reportedIssues);
|
|
513
|
+
if (state.resolvedOpenQuestionIds && state.resolvedOpenQuestionIds.length > 0) {
|
|
514
|
+
const shown = state.resolvedOpenQuestionIds.slice(0, 6);
|
|
515
|
+
lines.push("## Agent-Reported Resolutions", "", `- ${state.resolvedOpenQuestionIds.length} open question(s) resolved in the ` +
|
|
516
|
+
"last committed round (Agent report):", ...shown.map((id) => ` - \`${id}\``));
|
|
517
|
+
if (state.resolvedOpenQuestionIds.length > shown.length) {
|
|
518
|
+
lines.push(` - ... and ${state.resolvedOpenQuestionIds.length - shown.length} more ` +
|
|
519
|
+
"(replay/explain shows every id)");
|
|
337
520
|
}
|
|
338
521
|
lines.push("");
|
|
339
522
|
}
|
|
340
|
-
addListWithIds(lines, "Remaining", state.remainingCriteria, "cr");
|
|
341
|
-
addList(lines, "Blockers", state.blockers);
|
|
342
523
|
const recurring = recurringOnly(state.recurringFlags);
|
|
343
|
-
addList(lines, "Discoveries", state.discoveries);
|
|
344
|
-
addList(lines, "Cross-Round Outcomes", state.rollingOutcomes);
|
|
345
|
-
addList(lines, "Failed Patterns", state.failedPatterns);
|
|
346
524
|
// v3.8.1: ONE recurring-facts section. "Recurring Issues" (the last 5
|
|
347
525
|
// rounds' raw violations, no threshold) and "Lessons Learned" (the whole
|
|
348
526
|
// history, count >= 2) were two windows over one fact set rendered as two
|
|
@@ -355,78 +533,40 @@ function flatStateSections(state) {
|
|
|
355
533
|
? "🚫"
|
|
356
534
|
: flag.kind === "verification_error" ? "⚠️" : "ℹ️";
|
|
357
535
|
const subject = flag.ref ? `${flag.subject} [${flag.ref}]` : flag.subject;
|
|
358
|
-
|
|
536
|
+
const source = flag.kind === "constraint_violation"
|
|
537
|
+
? "Agent-reported violation"
|
|
538
|
+
: "machine finding";
|
|
539
|
+
lines.push(`- ${icon} ${subject} — ${flag.count}× (R${flag.rounds.join(", R")}) — ${source}`);
|
|
359
540
|
}
|
|
360
541
|
if (recurring.length > RECURRING_RENDER_CAP) {
|
|
361
542
|
lines.push(`- ... and ${recurring.length - RECURRING_RENDER_CAP} more`);
|
|
362
543
|
}
|
|
363
544
|
lines.push("");
|
|
364
545
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
lines.push("## Phase", "", `- ${phaseLine}`);
|
|
546
|
+
if (state.retiredConstraints.length > 0) {
|
|
547
|
+
lines.push("## Retired Constraints", "");
|
|
548
|
+
for (const constraint of state.retiredConstraints) {
|
|
549
|
+
lines.push(`- ${constraint} (Agent report)`);
|
|
550
|
+
}
|
|
371
551
|
lines.push("");
|
|
372
552
|
}
|
|
373
553
|
if (state.verificationFlags.length > 0) {
|
|
374
554
|
lines.push("## Verification", "");
|
|
375
555
|
for (const flag of state.verificationFlags) {
|
|
376
|
-
lines.push(`- [${flag.severity}] [${flag.check}] ${flag.detail}`
|
|
556
|
+
lines.push(`- [${flag.severity}] [${flag.check}] \`${flag.field}\` — ${flag.detail}` +
|
|
557
|
+
(flag.ref ? ` [ref: \`${flag.ref}\`]` : ""));
|
|
377
558
|
}
|
|
378
559
|
lines.push("");
|
|
379
560
|
}
|
|
380
|
-
if (state.milestones.length > 0) {
|
|
381
|
-
lines.push("## Phase History", "");
|
|
382
|
-
for (const m of state.milestones) {
|
|
383
|
-
const kindIcon = m.kind === "agent_declared"
|
|
384
|
-
? "🏁"
|
|
385
|
-
: m.kind === "criteria_milestone" ? "✅" : "📍";
|
|
386
|
-
lines.push(`### ${kindIcon} ${m.label}`, `Rounds ${m.round_range.start}–${m.round_range.end}`, "", `**Outcome**: ${m.outcome}`, "");
|
|
387
|
-
if (m.carried_constraints.length > 0) {
|
|
388
|
-
lines.push("**Carried Constraints**:");
|
|
389
|
-
for (const c of m.carried_constraints)
|
|
390
|
-
lines.push(`- ${c}`);
|
|
391
|
-
lines.push("");
|
|
392
|
-
}
|
|
393
|
-
if (m.resolved_constraints.length > 0) {
|
|
394
|
-
lines.push("**Resolved During Phase**:");
|
|
395
|
-
for (const c of m.resolved_constraints)
|
|
396
|
-
lines.push(`- ${c}`);
|
|
397
|
-
lines.push("");
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
561
|
if (state.subGoals.length > 0) {
|
|
402
|
-
// v3.
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
const view = activeSubGoalView(state.subGoals, cap);
|
|
562
|
+
// v3.9.4: the SAME formatter the prompt uses. Rows past the display cap
|
|
563
|
+
// keep their ids in an overflow line, so the state file's active id set
|
|
564
|
+
// can never be narrower than the prompt's.
|
|
406
565
|
// L8: the heading is emitted whenever sub-goals exist — with every
|
|
407
566
|
// sub-goal terminal (0 active rows) the counts line must still live
|
|
408
567
|
// under its own section instead of leaking into the previous one.
|
|
409
568
|
lines.push("## Sub-Goal Dashboard", "");
|
|
410
|
-
|
|
411
|
-
for (const sg of view.active) {
|
|
412
|
-
const statusIcon = sg.status === "in_progress" ? "🔄" :
|
|
413
|
-
sg.status === "blocked" ? "🚫" : "⏳";
|
|
414
|
-
const stale = sg.status === "pending" &&
|
|
415
|
-
state.round - sg.declared_at_round >= 10 ? " ⚠️ stale" : "";
|
|
416
|
-
const detail = sg.status === "in_progress"
|
|
417
|
-
? ` (since R${sg.status_changed_at_round})`
|
|
418
|
-
: sg.status === "pending"
|
|
419
|
-
? ` (since R${sg.declared_at_round})`
|
|
420
|
-
: ` (blocked R${sg.status_changed_at_round})`;
|
|
421
|
-
lines.push(`- ${statusIcon} [\`${sg.id}\`] ${sg.description}${detail}${stale}`);
|
|
422
|
-
}
|
|
423
|
-
lines.push("");
|
|
424
|
-
}
|
|
425
|
-
lines.push(`> ${view.total} total · ${view.activeTotal} active · ` +
|
|
426
|
-
`${view.done} done · ${view.canceled} canceled`, "");
|
|
427
|
-
}
|
|
428
|
-
if (state.externalContext) {
|
|
429
|
-
lines.push("## External Context", "", state.externalContext, "");
|
|
569
|
+
lines.push(...formatActiveSubGoals(state.subGoals, state.round).lines, "");
|
|
430
570
|
}
|
|
431
571
|
// Split the flat markdown into titled sections for tier grouping. Every
|
|
432
572
|
// builder above emits `## <Title>` headings over their content — but only
|
|
@@ -469,32 +609,63 @@ export function createCanonicalLoopState(request, response, stateFilePath,
|
|
|
469
609
|
/** v3.3: Display-only derived data. Optional 4th param — callers that
|
|
470
610
|
* predate v3.3 stay on 3-arg calls. */
|
|
471
611
|
derived) {
|
|
472
|
-
const last =
|
|
612
|
+
const last = derived?.previousRound?.result ?? null;
|
|
473
613
|
const objective = response.loop_objective;
|
|
474
|
-
const verificationFlags =
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
614
|
+
const verificationFlags = derived?.previousRound?.verificationFlags ??
|
|
615
|
+
request.verification_flags ?? [];
|
|
616
|
+
// v3.9.5: only committed previous-round reports enter the projection. The
|
|
617
|
+
// compiler no longer has an uncommitted fallback at all.
|
|
618
|
+
const committed = derived?.previousRound?.committed === true;
|
|
619
|
+
// Keep the report projection behind the same boundary as corrections and
|
|
620
|
+
// blockers. The fallback result may still be consumed by compiler
|
|
621
|
+
// evolution, but an uncommitted/rejected attempt cannot become durable
|
|
622
|
+
// progress, discovery, or criterion state.
|
|
623
|
+
const executionReport = committed ? last?.execution_report : undefined;
|
|
624
|
+
// v4.0.3: blockers are Agent reports; warnings are compiler facts. They
|
|
625
|
+
// were previously merged into one `blockers` list.
|
|
626
|
+
const reportedIssues = unique([
|
|
627
|
+
...(committed ? (last?.constraint_violations ?? []) : []),
|
|
628
|
+
...(committed && last?.blocker ? [last.blocker] : []),
|
|
481
629
|
]);
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
//
|
|
493
|
-
// from committed rounds)
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
630
|
+
const warnings = unique(response.warnings);
|
|
631
|
+
const committedPosture = committed ? derived?.previousRound?.view?.evidencePosture ?? null : null;
|
|
632
|
+
const retryPosture = request.retry_evidence_posture ?? null;
|
|
633
|
+
// v4.0.3: the Agent's resolved-question report is bounded and rendered as a
|
|
634
|
+
// separate provenance line; it never silently disappears into Working
|
|
635
|
+
// Memory's active-set filtering.
|
|
636
|
+
const resolvedOpenQuestionIds = committed
|
|
637
|
+
? unique(last?.resolved_open_question_ids ?? [])
|
|
638
|
+
.filter(isOpenQuestionId)
|
|
639
|
+
: [];
|
|
640
|
+
// v4.0.6: The Agent still chooses when a round ends. The ACTIVE Round
|
|
641
|
+
// Contract (derived from committed rounds) supplies the verifiable work
|
|
642
|
+
// slice for this invocation when verification capability exists. An
|
|
643
|
+
// explicit invocation instruction has priority; without contract capability
|
|
644
|
+
// the Current Task falls back to the working objective.
|
|
497
645
|
const roundContract = derived?.roundFacts?.activeContract ?? null;
|
|
646
|
+
const capability = deriveConfiguredCapability(getPolicy());
|
|
647
|
+
// v4.0.5: the host's explicit invocation input. Empty/whitespace is "not
|
|
648
|
+
// provided", never a second task. Conditional presence keeps the state hash
|
|
649
|
+
// byte-identical when no instruction was given.
|
|
650
|
+
const currentUserInstruction = request.current_user_instruction?.trim() || undefined;
|
|
651
|
+
// v3.9.4: the rendered criterion/hard sets are ACTIVE sets. The compile
|
|
652
|
+
// response's `criterion_statuses` is the active criterion reference space
|
|
653
|
+
// (loop-compiler filters it); `constraints_active` is the active constraint
|
|
654
|
+
// set. A direct caller that omits `criterion_statuses` gets the objective
|
|
655
|
+
// fallback so library-level state construction still works.
|
|
656
|
+
const activeConstraints = uniqueNormalized(response.constraints_active);
|
|
657
|
+
const activeConstraintKeys = new Set(activeConstraints.map((text) => normalizeText(text)));
|
|
658
|
+
const observedCompile = response.criterion_statuses !== undefined;
|
|
659
|
+
const successCriteria = observedCompile
|
|
660
|
+
? uniqueNormalized(response.criterion_statuses.map((criterion) => criterion.text))
|
|
661
|
+
: uniqueNormalized(objective?.success_criteria ?? []);
|
|
662
|
+
const criteriaKeys = new Set(successCriteria.map((text) => normalizeText(text)));
|
|
663
|
+
const hardConstraints = uniqueNormalized(objective?.hard_constraints ?? [])
|
|
664
|
+
.filter((text) => {
|
|
665
|
+
const key = normalizeText(text);
|
|
666
|
+
return !criteriaKeys.has(key) &&
|
|
667
|
+
(!observedCompile || activeConstraintKeys.has(key));
|
|
668
|
+
});
|
|
498
669
|
return {
|
|
499
670
|
schemaVersion: CANONICAL_STATE_SCHEMA_VERSION,
|
|
500
671
|
loopId: response.loop_id || request.loop_id,
|
|
@@ -502,47 +673,52 @@ derived) {
|
|
|
502
673
|
maxRounds: request.max_rounds ?? getPolicy().engine.max_rounds,
|
|
503
674
|
goalId: response.goal_id,
|
|
504
675
|
objective: objective?.objective || request.task,
|
|
505
|
-
|
|
506
|
-
currentTask: roundContract
|
|
676
|
+
currentTask: currentUserInstruction ?? (roundContract && capability.contractVerificationAvailable
|
|
507
677
|
? formatRoundContract(roundContract, derived?.roundFacts?.itemStatuses ?? null)
|
|
508
|
-
: request.task,
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
678
|
+
: objective?.objective ?? request.task),
|
|
679
|
+
...(currentUserInstruction ? { currentUserInstruction } : {}),
|
|
680
|
+
successCriteria,
|
|
681
|
+
hardConstraints,
|
|
682
|
+
activeConstraints,
|
|
512
683
|
retiredConstraints: unique(response.constraints_retired),
|
|
513
684
|
constraintMetadata: response.constraint_metadata ?? [],
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
685
|
+
rejectionNotice: request.rejection_notice.trim() ? request.rejection_notice : null,
|
|
686
|
+
// v4.0.3: conditional presence keeps hashes stable for a first round.
|
|
687
|
+
...(committedPosture ? { previousEvidencePosture: committedPosture } : {}),
|
|
688
|
+
...(retryPosture ? { retryEvidencePosture: retryPosture } : {}),
|
|
689
|
+
reportedIssues,
|
|
690
|
+
warnings,
|
|
517
691
|
verificationFlags,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
692
|
+
// v4.0.2: conditional presence — no committed working memory, no key.
|
|
693
|
+
...(hasWorkingMemoryContent(derived?.workingMemory)
|
|
694
|
+
? { workingMemory: derived.workingMemory }
|
|
695
|
+
: {}),
|
|
522
696
|
subGoals: response.sub_goals ?? [],
|
|
523
697
|
criterionStatuses: response.criterion_statuses ?? [],
|
|
524
698
|
recurringFlags: response.recurring_flags ?? [],
|
|
525
|
-
externalContext: request.external_context?.trim() ?? "",
|
|
526
699
|
stateFilePath,
|
|
527
|
-
|
|
700
|
+
reportedProgress: {
|
|
528
701
|
criteriaMet: unique(claimedMetCriteria(executionReport)),
|
|
529
702
|
criteriaRemaining: unique(claimedRemainingCriteria(executionReport)),
|
|
703
|
+
agentReportedRemaining: unique(claimedRemainingCriteria(executionReport)),
|
|
530
704
|
filesChanged: unique(executionReport?.files_changed ?? []),
|
|
531
|
-
tests: executionReport?.tests_reported ?? null,
|
|
532
705
|
},
|
|
706
|
+
...(resolvedOpenQuestionIds.length > 0
|
|
707
|
+
? { resolvedOpenQuestionIds }
|
|
708
|
+
: {}),
|
|
709
|
+
...(derived?.context ? { context: derived.context } : {}),
|
|
533
710
|
// v3.3: Conditional presence — empty/absent derived data must not add
|
|
534
711
|
// keys, or every state hash would change for rounds without it.
|
|
712
|
+
...(derived?.recoveryBrief && derived.recoveryBrief.length > 0
|
|
713
|
+
? { recoveryBrief: [...derived.recoveryBrief] }
|
|
714
|
+
: {}),
|
|
535
715
|
...(derived?.machineStatus ? { machineStatus: derived.machineStatus } : {}),
|
|
536
716
|
// v3.3: Conditional presence — no contract, no key (hash stability).
|
|
537
717
|
...(roundContract ? { roundContract } : {}),
|
|
538
718
|
...(derived?.roundFacts?.itemStatuses
|
|
539
719
|
? { contractItemStatuses: derived.roundFacts.itemStatuses }
|
|
540
720
|
: {}),
|
|
541
|
-
|
|
542
|
-
derived.roundFacts.verifiedSubGoals.length > 0
|
|
543
|
-
? { verifiedSubGoals: [...derived.roundFacts.verifiedSubGoals] }
|
|
544
|
-
: {}),
|
|
545
|
-
capability: deriveConfiguredCapability(getPolicy()),
|
|
721
|
+
capability,
|
|
546
722
|
};
|
|
547
723
|
}
|
|
548
724
|
//# sourceMappingURL=canonical-state.js.map
|