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/enforcement-gate.js
CHANGED
|
@@ -17,32 +17,22 @@
|
|
|
17
17
|
* with stopReason "enforcement_terminated".
|
|
18
18
|
*/
|
|
19
19
|
import { makeEnforcementResult } from "./protocol.js";
|
|
20
|
-
import {
|
|
20
|
+
import { CHECK_SUCCESS_WITH_REMAINING_CRITERIA, CHECK_RECURRING_VIOLATION, CHECK_REQUIRED_COMMAND_FAILED, CHECK_VERIFICATION_ENTRYPOINT_MODIFIED, CHECK_CONTRACT_ITEMS_UNVERIFIED, CHECK_USER_GATE_UNRESOLVED, CHECK_CONTRACT_REQUIRED, CHECK_CONTRACT_PREMATURE, CHECK_CONTRACT_SUCCESSOR_UNCHANGED } from "./verification-gate.js";
|
|
21
|
+
import { deriveProgressWindow, } from "./progress.js";
|
|
22
|
+
import { declaresBlocked } from "./round-outcome.js";
|
|
21
23
|
import { getPolicy } from "./policy.js";
|
|
22
|
-
import {
|
|
23
|
-
import { derivationRounds, decodeCommittedRound, machineEvidenceForRound, } from "./committed-round.js";
|
|
24
|
+
import { derivationRounds, hasCommittedMachineBacktrack, } from "./committed-round.js";
|
|
24
25
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
26
|
// Helpers
|
|
26
27
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
28
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
28
29
|
// v2.10: Backtrack helpers
|
|
29
30
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
-
/** Extract the committed round decision's result from a vault entry.
|
|
31
|
-
* The round commit entry (`loop:<id>:r<N>:feedback`) carries the full
|
|
32
|
-
* decision under loop_lineage.round_transaction; lineage entries carry
|
|
33
|
-
* nothing at the top level. */
|
|
34
|
-
function committedRoundResult(entry) {
|
|
35
|
-
return decodeCommittedRound(entry)?.result ?? null;
|
|
36
|
-
}
|
|
37
31
|
/** True when a backtrack already committed at or after the current round —
|
|
38
32
|
* i.e. the loop is re-walking territory the system already rolled back
|
|
39
33
|
* once. Guards against the reject↔backtrack deadlock on persistent stalls. */
|
|
40
34
|
function hasCommittedBacktrack(vaultEntries, currentRound) {
|
|
41
|
-
return vaultEntries
|
|
42
|
-
if (entryRound(entry) < currentRound)
|
|
43
|
-
return false;
|
|
44
|
-
return committedRoundResult(entry)?.action === "backtrack";
|
|
45
|
-
});
|
|
35
|
+
return hasCommittedMachineBacktrack(vaultEntries, currentRound);
|
|
46
36
|
}
|
|
47
37
|
/** Check whether a committed round represents a clean restore point.
|
|
48
38
|
* as a safe restore point. A clean round was accepted (not rejected)
|
|
@@ -55,12 +45,18 @@ function hasCommittedBacktrack(vaultEntries, currentRound) {
|
|
|
55
45
|
* run of such rounds). Rejected/terminated rounds never commit, and
|
|
56
46
|
* rolled-back rounds are excluded below. */
|
|
57
47
|
function isCleanRound(round) {
|
|
48
|
+
// v4.0.3: a round without a complete observable boundary is not a
|
|
49
|
+
// machine-clean restore point. Unknown evidence never reads as clean.
|
|
50
|
+
if (round.evidenceIncomplete)
|
|
51
|
+
return false;
|
|
58
52
|
return !round.verificationFlags.some((flag) => flag.severity === "error");
|
|
59
53
|
}
|
|
60
54
|
/** Scan backwards from currentRound to find the most recent clean round.
|
|
61
55
|
* Collects discovered_constraints from skipped rounds along the way.
|
|
62
56
|
* Returns null when no clean round is found within maxDepth. */
|
|
63
|
-
export function findSafeRestorePoint(currentRound, vaultEntries, maxDepth
|
|
57
|
+
export function findSafeRestorePoint(currentRound, vaultEntries, maxDepth,
|
|
58
|
+
/** v3.9.7: a candidate must have a materialized restore checkpoint. */
|
|
59
|
+
isEligible) {
|
|
64
60
|
const completed = derivationRounds(vaultEntries, currentRound)
|
|
65
61
|
.sort((a, b) => b.round - a.round);
|
|
66
62
|
const skippedDiscoveries = [];
|
|
@@ -85,183 +81,71 @@ export function findSafeRestorePoint(currentRound, vaultEntries, maxDepth) {
|
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
}
|
|
88
|
-
if (isCleanRound(round)) {
|
|
84
|
+
if (isCleanRound(round) && (isEligible?.(targetRound) ?? true)) {
|
|
89
85
|
return { round: targetRound, skippedDiscoveries };
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
88
|
return null;
|
|
93
89
|
}
|
|
94
|
-
export function findBacktrackTargetSnapshot(restoreRound, vaultEntries) {
|
|
95
|
-
const committed = derivationRounds(vaultEntries)
|
|
96
|
-
.find((view) => view.round === restoreRound);
|
|
97
|
-
if (!committed)
|
|
98
|
-
return null;
|
|
99
|
-
for (const item of machineEvidenceForRound(committed)) {
|
|
100
|
-
if (!isRecord(item) || item.providerId !== "git" ||
|
|
101
|
-
item.status !== "observed" || !isRecord(item.data))
|
|
102
|
-
continue;
|
|
103
|
-
const head = item.data.head;
|
|
104
|
-
if (typeof head !== "string" || head.length === 0)
|
|
105
|
-
continue;
|
|
106
|
-
// v3.9.1: the restore baseline is the boundary's one dirty list.
|
|
107
|
-
const dirtyFiles = Array.isArray(item.data.dirty)
|
|
108
|
-
? item.data.dirty.filter((file) => typeof file === "string")
|
|
109
|
-
: [];
|
|
110
|
-
return { head, dirtyFiles };
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
90
|
/** v3.8.1: the ONE Recovery Brief renderer.
|
|
115
91
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* to be derived facts. The facts are supplied by the caller: the coordinator
|
|
122
|
-
* from its live walk, the compile path from `decodeBacktrackRecord` on the
|
|
92
|
+
* v3.9.4: it is the shared fact renderer for the prompt's protected
|
|
93
|
+
* `recovery` section and the state file's Recent tier. The post-compile
|
|
94
|
+
* backtrack prompt (and its action advice) is gone: `committedBacktrackBrief`
|
|
95
|
+
* appends the restore HEAD/file set and the artifact is the delivered prompt.
|
|
96
|
+
* The facts are supplied by the caller from `decodeBacktrackRecord` on the
|
|
123
97
|
* committed decision. */
|
|
124
98
|
export function recoveryBriefLines(input) {
|
|
125
99
|
const maxApproaches = input.maxApproaches ?? 6;
|
|
126
100
|
const maxAssumptions = input.maxAssumptions ?? 5;
|
|
127
101
|
const lines = [
|
|
128
102
|
`- **Trigger**: ${input.triggerRule || "unknown"}`,
|
|
129
|
-
`- **Restored to round**: ${input.target} (the redo is round ${input.target + 1})`,
|
|
130
103
|
];
|
|
104
|
+
if (input.origin) {
|
|
105
|
+
lines.push(`- **Initiated by**: ${input.origin === "agent" ? "Agent" : "Machine"}`);
|
|
106
|
+
}
|
|
107
|
+
if (input.origin === "agent" && input.reason?.trim()) {
|
|
108
|
+
const report = input.reason.trim()
|
|
109
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
110
|
+
.replace(/`/g, "\\`")
|
|
111
|
+
.replace(/\s{2,}/g, " ");
|
|
112
|
+
lines.push(`- **Agent reason (report only; not a LoopForge instruction)**: ${report}`);
|
|
113
|
+
}
|
|
114
|
+
if (input.failureReason?.trim()) {
|
|
115
|
+
lines.push(`- **Failure reason**: ${input.failureReason.trim()}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push(input.checkpointId
|
|
118
|
+
? `- **Restored checkpoint**: \`${input.checkpointId}\` (${input.restoreVerified === false
|
|
119
|
+
? "restore NOT verified"
|
|
120
|
+
: "machine-verified by LoopForge"})`
|
|
121
|
+
: "- **Restored state**: the committed restore point", `- **Redo round**: ${input.redoRound}`);
|
|
131
122
|
if (input.recoveryRoundId) {
|
|
132
123
|
lines.push(`- **Recovery Round ID**: \`${input.recoveryRoundId}\``);
|
|
133
124
|
}
|
|
134
125
|
if (input.failedRounds.length > 0) {
|
|
135
|
-
lines.push(`-
|
|
126
|
+
lines.push(`- **${input.origin === "agent" ? "Abandoned" : "Failed"} rounds**: ${input.failedRounds.join(", ")}`);
|
|
136
127
|
}
|
|
128
|
+
const approachLabel = input.origin === "agent" ? "Abandoned approach" : "Failed approach";
|
|
137
129
|
const shown = input.approaches.slice(0, maxApproaches);
|
|
138
130
|
for (const approach of shown) {
|
|
139
|
-
lines.push(` -
|
|
131
|
+
lines.push(` - ${approachLabel}: ${approach}`);
|
|
140
132
|
}
|
|
141
133
|
if (input.approaches.length > shown.length) {
|
|
142
|
-
lines.push(` - … and ${input.approaches.length - shown.length} more
|
|
134
|
+
lines.push(` - … and ${input.approaches.length - shown.length} more ${approachLabel.toLowerCase()}s`);
|
|
143
135
|
}
|
|
144
136
|
if (input.wrongAssumptions.length > 0) {
|
|
145
|
-
lines.push("- **Falsified assumptions
|
|
137
|
+
lines.push("- **Falsified assumptions (Agent reports)**:");
|
|
146
138
|
for (const assumption of input.wrongAssumptions.slice(0, maxAssumptions)) {
|
|
147
139
|
lines.push(` - ${assumption}`);
|
|
148
140
|
}
|
|
149
141
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
* Used to show the agent exactly what needs to be reverted. */
|
|
155
|
-
skippedFiles = [],
|
|
156
|
-
/** v2.12: Git HEAD commit hash at the backtrack point (current HEAD).
|
|
157
|
-
* The agent must discard work back to the clean round's state. */
|
|
158
|
-
gitHead,
|
|
159
|
-
/** v3.7.1: The redo submission's roundId (loop:<id>:round:<toRound+1>). */
|
|
160
|
-
recoveryRoundId,
|
|
161
|
-
/** v3.7.1: Derived Recovery Brief facts — committed facts + the
|
|
162
|
-
* in-flight attempt only; never rejected payloads. */
|
|
163
|
-
recovery) {
|
|
164
|
-
const lines = [
|
|
165
|
-
`## ⛔ Backtrack — Round ${fromRound} → Restored to Round ${toRound}`,
|
|
166
|
-
"",
|
|
167
|
-
];
|
|
168
|
-
// ── Recovery Brief (v3.7.1) ──────────────────────────────────────────
|
|
169
|
-
// A compact structured summary at the top of the rollback directive. The
|
|
170
|
-
// detailed sections below (workspace restore, discoveries, instructions)
|
|
171
|
-
// keep their existing wording; the brief exists so the agent cannot miss
|
|
172
|
-
// why it was rolled back, what failed, and what the redo round is.
|
|
173
|
-
if (recovery) {
|
|
174
|
-
lines.push("### Recovery Brief", "");
|
|
175
|
-
lines.push(...recoveryBriefLines({
|
|
176
|
-
target: toRound,
|
|
177
|
-
triggerRule,
|
|
178
|
-
failedRounds: recovery.failedRounds,
|
|
179
|
-
approaches: recovery.approaches,
|
|
180
|
-
wrongAssumptions: recovery.wrongAssumptions,
|
|
181
|
-
recoveryRoundId,
|
|
182
|
-
}));
|
|
183
|
-
lines.push("");
|
|
184
|
-
}
|
|
185
|
-
// ── Why This Happened ────────────────────────────────────────────────
|
|
186
|
-
// v3.7: stall and flatline are one evaluator emitting progress_stall; the
|
|
187
|
-
// former flatline-specific wording ("zero forward motion → radically
|
|
188
|
-
// different strategy") is folded into the single section — the disposition
|
|
189
|
-
// tier still distinguishes the enforcement reason itself.
|
|
190
|
-
if (triggerRule === "progress_stall") {
|
|
191
|
-
lines.push("### Why This Happened", "", `Progress stalled over rounds ${toRound + 1}–${fromRound}: the work ` +
|
|
192
|
-
`produced no machine-observed Git changes.`, "", "The approach used in those rounds **did not work**. It should not be " +
|
|
193
|
-
"repeated — a **radically different** strategy is needed.", "");
|
|
194
|
-
}
|
|
195
|
-
// ── v2.13: Workspace Restore ──────────────────────────────────────────
|
|
196
|
-
lines.push("### ⚠️ Workspace Restore Required", "", `Your working directory still contains changes from the **failed** ` +
|
|
197
|
-
`rounds (${toRound + 1}–${fromRound}). You MUST discard those changes ` +
|
|
198
|
-
`before starting Round ${toRound + 1}. Working on top of stale changes ` +
|
|
199
|
-
`will cause the verification gate to reject your next submission.`, "");
|
|
200
|
-
if (skippedFiles.length > 0) {
|
|
201
|
-
lines.push("**Files modified in skipped rounds (must be reverted):**", "");
|
|
202
|
-
for (const f of skippedFiles.slice(0, 15)) {
|
|
203
|
-
lines.push(`- \`${f}\``);
|
|
204
|
-
}
|
|
205
|
-
if (skippedFiles.length > 15) {
|
|
206
|
-
lines.push(`- … and ${skippedFiles.length - 15} more files`);
|
|
142
|
+
if (input.preservedDiscoveries && input.preservedDiscoveries.length > 0) {
|
|
143
|
+
lines.push("- **Preserved discoveries (Agent reports)**:");
|
|
144
|
+
for (const discovery of input.preservedDiscoveries) {
|
|
145
|
+
lines.push(` - ${discovery}`);
|
|
207
146
|
}
|
|
208
|
-
lines.push("");
|
|
209
|
-
}
|
|
210
|
-
// v3.8: FACTS, not commands. LoopForge never mutates the working tree, and it
|
|
211
|
-
// does not prescribe how the agent restores it either — the restore is the
|
|
212
|
-
// AGENT's action, so the prompt states what must be true (the target HEAD and
|
|
213
|
-
// the files that must be reverted) and leaves the means entirely to the
|
|
214
|
-
// agent. No `git stash` / `git reset` / `git checkout` / `git clean` line
|
|
215
|
-
// belongs here.
|
|
216
|
-
if (gitHead) {
|
|
217
|
-
lines.push("**The workspace must be returned to the restore point before you " +
|
|
218
|
-
"continue:**", "", `- HEAD must be at \`${gitHead.slice(0, 12)}\``, `- Every file listed above must be back in its state at Round ${toRound}`, `- No leftover change from rounds ${toRound + 1}–${fromRound} may remain`, "", "Your next submission's git observation is compared against that HEAD " +
|
|
219
|
-
"and against the files Round " + toRound + " already had dirty — work " +
|
|
220
|
-
"that was already in progress then may stay, but a file whose " +
|
|
221
|
-
"fingerprint still matches the failed round, or any change the restore " +
|
|
222
|
-
"point did not already carry, proves the restore did not happen. Submit " +
|
|
223
|
-
"one recovery evaluation after the restore so LoopForge can verify the " +
|
|
224
|
-
"restored starting state before you redo the work; only then continue " +
|
|
225
|
-
"with the next slice.", "");
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
lines.push(`**The workspace must be returned to its state at Round ${toRound}** — ` +
|
|
229
|
-
"every file listed above reverted, with nothing left over from the " +
|
|
230
|
-
"failed rounds.", "");
|
|
231
147
|
}
|
|
232
|
-
lines
|
|
233
|
-
"modifies the working tree; it only records the restore requirement and " +
|
|
234
|
-
"checks the result.", "", `**If git is unavailable**, revert the files listed above by hand to ` +
|
|
235
|
-
`their state at Round ${toRound}. Re-read the state file below — ` +
|
|
236
|
-
`it reflects the correct state at Round ${toRound}.`, "", "> 🛡️ **Verification:** The next round's evidence check will verify that " +
|
|
237
|
-
"> your workspace no longer contains the failed changes. Submissions " +
|
|
238
|
-
"> with unrestored files will be **rejected**.", "");
|
|
239
|
-
// ── What Must Change ──────────────────────────────────────────────────
|
|
240
|
-
lines.push("### What Must Change", "", "- Do **NOT** repeat the approach used in the skipped rounds.", "- Try a **different** task decomposition or technique.", "- If the current sub-goal is stuck, consider canceling it " +
|
|
241
|
-
"(`subgoal_updates` to canceled) and working on a different one.",
|
|
242
|
-
// v3.5: the restored Current Task may be the very Round Contract that
|
|
243
|
-
// stalled — the agent must be told the sanctioned way to revise it
|
|
244
|
-
// (outcome=blocked closes the active contract; the revised proposal in
|
|
245
|
-
// the same submission becomes active next round). Without this the
|
|
246
|
-
// agent's revision would be silently ignored as premature.
|
|
247
|
-
"- If the restored Current Task is a Round Contract that caused the stall: " +
|
|
248
|
-
"close it in this submission with `outcome=\"blocked\"` (+ a blocker " +
|
|
249
|
-
"explaining why it stalled) and declare the REVISED contract in the " +
|
|
250
|
-
"same submission. Do **NOT** silently restate the stalled contract.", "- Use the `prompt_requests.confusion_points` field in your self-evaluation " +
|
|
251
|
-
"to flag anything you don't understand about the task or the blockage.", "");
|
|
252
|
-
// ── Preserved Discoveries ─────────────────────────────────────────────
|
|
253
|
-
if (skippedDiscoveries.length > 0) {
|
|
254
|
-
lines.push("### Preserved Discoveries", "", "These were discovered in the skipped rounds and remain valid:", "");
|
|
255
|
-
for (const d of skippedDiscoveries) {
|
|
256
|
-
lines.push(`- ${d}`);
|
|
257
|
-
}
|
|
258
|
-
lines.push("");
|
|
259
|
-
}
|
|
260
|
-
// ── Instructions ──────────────────────────────────────────────────────
|
|
261
|
-
lines.push("### Instructions", "", `You have been rolled back to Round ${toRound}. ` +
|
|
262
|
-
`You are now starting **Round ${toRound + 1}** with the lessons above.`, "", "1. **First**: restore your workspace using the commands above.", "2. Read the restored state below — it reflects Round " + toRound + ".", "3. The discoveries from skipped rounds (if any) have been merged " +
|
|
263
|
-
"into the active constraints.", "4. Do NOT retrace the steps that led to the stall. Choose a new path.", "");
|
|
264
|
-
return lines.join("\n");
|
|
148
|
+
return lines;
|
|
265
149
|
}
|
|
266
150
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
267
151
|
// Individual enforcement rules — each returns EnforcementResult | null
|
|
@@ -294,24 +178,22 @@ function enforceRecurringViolation(flags) {
|
|
|
294
178
|
return makeEnforcementResult({
|
|
295
179
|
action: "reject",
|
|
296
180
|
reason: flag.detail,
|
|
297
|
-
fix_instructions: "The same constraint violation has
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
181
|
+
fix_instructions: "The same constraint violation has been reported in three consecutive " +
|
|
182
|
+
"committed rounds. This is a record of your own reports. The finding " +
|
|
183
|
+
"clears when a committed evaluation no longer reports the same " +
|
|
184
|
+
"violation, or when you declare outcome blocked with a blocker. " +
|
|
185
|
+
"LoopForge does not prescribe a technique or decomposition.",
|
|
301
186
|
check: "recurring_violation",
|
|
302
187
|
});
|
|
303
188
|
}
|
|
304
|
-
/** R-EVID (v2.14): A required command failure
|
|
305
|
-
* hides failures, or a self-contradictory outcome claim contradicts the
|
|
189
|
+
/** R-EVID (v2.14 / v4.0.6): A required command failure contradicts the
|
|
306
190
|
* success claim before commit. The verification gate detects the
|
|
307
191
|
* contradiction; this rule decides what to do about it — reject so the
|
|
308
192
|
* agent redos the SAME round with honest claims. Same-check rejections
|
|
309
193
|
* accumulate toward R6 via the per-check counter. */
|
|
310
194
|
function enforceEvidenceContradiction(flags) {
|
|
311
195
|
const contradiction = flags.find((f) => f.severity === "error" &&
|
|
312
|
-
|
|
313
|
-
f.check === CHECK_COMMAND_EVIDENCE_MISMATCH ||
|
|
314
|
-
f.check === CHECK_OUTCOME_SUCCESS_CONTRADICTION));
|
|
196
|
+
f.check === CHECK_REQUIRED_COMMAND_FAILED);
|
|
315
197
|
if (!contradiction)
|
|
316
198
|
return null;
|
|
317
199
|
return makeEnforcementResult({
|
|
@@ -325,6 +207,39 @@ function enforceEvidenceContradiction(flags) {
|
|
|
325
207
|
check: contradiction.check,
|
|
326
208
|
});
|
|
327
209
|
}
|
|
210
|
+
/** v4.0.6 R-COVER: contract coverage rules. With after-capable commands, a
|
|
211
|
+
* continuing round must leave the next round an active contract; a different
|
|
212
|
+
* proposal while the contract is open needs `supersedes`; and a successor for
|
|
213
|
+
* a closed contract must bring new acceptance items. The verification gate
|
|
214
|
+
* states the structural fact; this row rejects the round with the concrete
|
|
215
|
+
* repair. The uniform ladder below escalates repeats. */
|
|
216
|
+
function enforceContractCoverage(flags) {
|
|
217
|
+
const flag = flags.find((f) => f.severity === "error" &&
|
|
218
|
+
(f.check === CHECK_CONTRACT_REQUIRED ||
|
|
219
|
+
f.check === CHECK_CONTRACT_PREMATURE ||
|
|
220
|
+
f.check === CHECK_CONTRACT_SUCCESSOR_UNCHANGED));
|
|
221
|
+
if (!flag)
|
|
222
|
+
return null;
|
|
223
|
+
const fix = flag.check === CHECK_CONTRACT_REQUIRED
|
|
224
|
+
? "This round continues with an after-capable verification command, so the " +
|
|
225
|
+
"next round must execute under an active Round Contract. Submit " +
|
|
226
|
+
"round_contract with a work_item and at least one acceptance item bound " +
|
|
227
|
+
"to a configured command."
|
|
228
|
+
: flag.check === CHECK_CONTRACT_PREMATURE
|
|
229
|
+
? "The active Round Contract is still open. Omit round_contract to continue " +
|
|
230
|
+
"it unchanged, or — to replace the slice — add " +
|
|
231
|
+
"round_contract.supersedes with the active rc- id and different acceptance items."
|
|
232
|
+
: "A closed contract's successor must bring NEW acceptance items. Changing " +
|
|
233
|
+
"only work_item or scope re-verifies the old slice and is rejected. " +
|
|
234
|
+
"Declare different item descriptions/commands; if this round is " +
|
|
235
|
+
"stopping, omit round_contract instead.";
|
|
236
|
+
return makeEnforcementResult({
|
|
237
|
+
action: "reject",
|
|
238
|
+
reason: flag.detail,
|
|
239
|
+
fix_instructions: fix,
|
|
240
|
+
check: flag.check,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
328
243
|
/** v3.3: R-EVID-VERIFY — the verification command's entrypoint changed in
|
|
329
244
|
* the same round the command ran. The runtime executed a script the agent
|
|
330
245
|
* just rewrote, so the "machine observation" has no stable baseline and
|
|
@@ -373,7 +288,8 @@ function enforceVerificationEntrypointTampered(flags, vaultEntries, currentRound
|
|
|
373
288
|
return makeEnforcementResult({
|
|
374
289
|
action: "terminate",
|
|
375
290
|
reason: "The verification command's entrypoint was changed in the round it " +
|
|
376
|
-
"verified and was not restored after
|
|
291
|
+
"verified and was not restored after being asked once. Backtrack is " +
|
|
292
|
+
"disabled or already committed for this round.",
|
|
377
293
|
check,
|
|
378
294
|
});
|
|
379
295
|
}
|
|
@@ -407,8 +323,8 @@ function enforceVerificationEntrypointTampered(flags, vaultEntries, currentRound
|
|
|
407
323
|
* occurrence, which is exactly what the L4 rule forbids.
|
|
408
324
|
*
|
|
409
325
|
* The streak is derived from COMMITTED round flags, so it survives restart
|
|
410
|
-
* and cannot be moved by an agent self-report.
|
|
411
|
-
* it: churning code without verifying is exactly what this row catches. */
|
|
326
|
+
* and cannot be moved by an agent self-report. Machine progress does not
|
|
327
|
+
* excuse it: churning code without verifying is exactly what this row catches. */
|
|
412
328
|
function enforceContractItemsUnverified(flags, currentRound, vaultEntries, consecutiveRejections, lastRejectionCheck) {
|
|
413
329
|
const limit = getPolicy().engine.unverified_claim_streak_limit;
|
|
414
330
|
if (!Number.isFinite(limit) || limit <= 0)
|
|
@@ -417,9 +333,9 @@ function enforceContractItemsUnverified(flags, currentRound, vaultEntries, conse
|
|
|
417
333
|
if (!current)
|
|
418
334
|
return null;
|
|
419
335
|
let streak = 1;
|
|
336
|
+
const window = derivationRounds(vaultEntries, currentRound);
|
|
420
337
|
for (let round = currentRound - 1; round >= 1; round--) {
|
|
421
|
-
const
|
|
422
|
-
const committed = entry ? decodeCommittedRound(entry) : null;
|
|
338
|
+
const committed = window.find((view) => view.round === round);
|
|
423
339
|
const carried = committed?.verificationFlags.some((flag) => flag.check === CHECK_CONTRACT_ITEMS_UNVERIFIED);
|
|
424
340
|
if (!carried)
|
|
425
341
|
break;
|
|
@@ -457,165 +373,52 @@ function enforceContractItemsUnverified(flags, currentRound, vaultEntries, conse
|
|
|
457
373
|
check: "contract_items_unverified",
|
|
458
374
|
});
|
|
459
375
|
}
|
|
460
|
-
/**
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
|
|
464
|
-
* agent must revert the out-of-scope changes or close the active contract
|
|
465
|
-
* and declare an extended scope.
|
|
466
|
-
*
|
|
467
|
-
* v3.8.3: that second route is now actually reachable. The v3.7 wording
|
|
468
|
-
* ("extend the scope in your re-declared round_contract") was structurally
|
|
469
|
-
* dead — a replacement proposal is IGNORED while the active contract is open
|
|
470
|
-
* — so the fix text was rewritten to the only legal route (blocked + the
|
|
471
|
-
* extended scope as the next proposal) without the matching escape being
|
|
472
|
-
* implemented. `scopeDriftWaiver` is that missing half: the coordinator has
|
|
473
|
-
* verified that the round declares blocked AND that its proposal covers every
|
|
474
|
-
* drifted file. What is waived is the REJECTION, never the fact. */
|
|
475
|
-
function enforceScopeDrift(flags, consecutiveRejections, lastRejectionCheck, scopeDriftWaiver) {
|
|
476
|
-
const flag = flags.find((f) => f.check === CHECK_ROUND_SCOPE_DRIFT);
|
|
477
|
-
if (!flag)
|
|
478
|
-
return null;
|
|
479
|
-
// v3.8.3: the legal exit. A round that declares itself blocked may switch
|
|
480
|
-
// contracts, but only by declaring a proposal that covers every file it
|
|
481
|
-
// changed out of scope — the coverage test is the coordinator's, because
|
|
482
|
-
// only there are the git files and the active contract both in reach.
|
|
483
|
-
//
|
|
484
|
-
// Checked ABOVE the terminate arm on purpose: the counter accumulates while
|
|
485
|
-
// the agent tries to get the declaration right, so an escape that only
|
|
486
|
-
// works at streak 0 or 1 is not an escape — it is a race the agent loses.
|
|
487
|
-
// The flag is NOT suppressed; it stays on the committed round, so
|
|
488
|
-
// audit/explain still report the drift as a machine fact. */
|
|
489
|
-
if (scopeDriftWaiver)
|
|
490
|
-
return null;
|
|
491
|
-
const ownStreak = lastRejectionCheck === CHECK_ROUND_SCOPE_DRIFT
|
|
492
|
-
? consecutiveRejections
|
|
493
|
-
: 0;
|
|
494
|
-
if (ownStreak >= 2) {
|
|
495
|
-
return makeEnforcementResult({
|
|
496
|
-
action: "terminate",
|
|
497
|
-
reason: `Repeated scope drift — the agent keeps changing files outside its ` +
|
|
498
|
-
`declared contract scope.`,
|
|
499
|
-
check: "round_scope_drift",
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
const escalation = getPolicy().engine.enforcement_escalation_enabled &&
|
|
503
|
-
ownStreak >= 1
|
|
504
|
-
? buildEscalationNotice()
|
|
505
|
-
: "";
|
|
506
|
-
return makeEnforcementResult({
|
|
507
|
-
action: "reject",
|
|
508
|
-
reason: flag.detail,
|
|
509
|
-
// L3 (v3.7.x): option (b) previously told the agent to "extend the scope
|
|
510
|
-
// in your re-declared round_contract" — structurally unreachable: while a
|
|
511
|
-
// contract is ACTIVE, a replacement proposal is ignored as premature, so
|
|
512
|
-
// the guidance dead-ended into repeated rejects/termination. The only
|
|
513
|
-
// legal scope change is to CLOSE the active contract first: resubmit with
|
|
514
|
-
// outcome="blocked" (blocker naming the too-narrow scope) and the
|
|
515
|
-
// extended scope as the NEXT round's contract in the same submission.
|
|
516
|
-
// v3.8.3: that route now works, and its one condition is stated here —
|
|
517
|
-
// the new scope must cover EVERY drifted file, not just the listed ones
|
|
518
|
-
// (the detail above truncates its list).
|
|
519
|
-
fix_instructions: "Files changed outside the contract's declared scope. You must either: " +
|
|
520
|
-
"(a) revert the out-of-scope changes, or (b) legally extend the scope: " +
|
|
521
|
-
"a replacement contract is IGNORED while the current one is active, so " +
|
|
522
|
-
"resubmit this work with outcome: \"blocked\" (blocker naming the " +
|
|
523
|
-
"too-narrow scope) plus a round_contract whose scope covers EVERY file " +
|
|
524
|
-
"listed above (and any other file this round changed outside the current " +
|
|
525
|
-
"scope). Scope drift is a machine fact — no explanation waives " +
|
|
526
|
-
"it. Repeated scope drift terminates the loop." +
|
|
527
|
-
escalation,
|
|
528
|
-
check: "round_scope_drift",
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
/** R9 (v2.12): Post-backtrack workspace not restored. The verification gate
|
|
532
|
-
* produced backtrack_workspace_not_restored (error): either skipped-round
|
|
533
|
-
* files are still dirty, or the working tree is not at the restore point's
|
|
534
|
-
* git HEAD. The agent must restore the workspace, not redo the round. */
|
|
535
|
-
function enforceBacktrackNotRestored(flags, vaultEntries = [], currentRound = 0) {
|
|
536
|
-
const flag = flags.find((f) => f.check === CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED && f.severity === "error");
|
|
537
|
-
if (!flag)
|
|
538
|
-
return null;
|
|
539
|
-
if (!getPolicy().engine.backtrack_enabled)
|
|
540
|
-
return null;
|
|
541
|
-
// v2.14: same deadlock guard as R4/R5 — a backtrack that already fired
|
|
542
|
-
// for this round means a non-restoring agent would otherwise cycle
|
|
543
|
-
// R9-backtrack forever (backtracks do not increment the rejection
|
|
544
|
-
// counter). One rollback chance, then terminate.
|
|
545
|
-
if (currentRound > 0 && hasCommittedBacktrack(vaultEntries, currentRound)) {
|
|
546
|
-
return makeEnforcementResult({
|
|
547
|
-
action: "terminate",
|
|
548
|
-
reason: "The workspace is still not restored after the previous backtrack: " +
|
|
549
|
-
flag.detail,
|
|
550
|
-
check: "backtrack_workspace_not_restored",
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
return makeEnforcementResult({
|
|
554
|
-
action: "backtrack",
|
|
555
|
-
reason: "The workspace was not restored after backtrack: " + flag.detail,
|
|
556
|
-
// v3.8: the requirement, not the recipe — LoopForge never mutates the
|
|
557
|
-
// working tree and does not prescribe how the agent restores it.
|
|
558
|
-
fix_instructions: "Return the working directory to the clean round's state before " +
|
|
559
|
-
"submitting the next round: the skipped-round files must be reverted " +
|
|
560
|
-
"and HEAD must be back at the restore point the backtrack prompt named.",
|
|
561
|
-
check: "backtrack_workspace_not_restored",
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
/** Shared observation pipeline for the progress evaluator (stall window,
|
|
565
|
-
* flatline tier). Only observable machine history can establish a stall. */
|
|
566
|
-
function evaluateStallWindow(tier, currentRound, vaultEntries,
|
|
376
|
+
/** The ONE progress-stall predicate. A stall requires machine proof that both
|
|
377
|
+
* the committed window AND the submitted round are unchanged; unknown and
|
|
378
|
+
* not_applicable never produce a verdict. */
|
|
379
|
+
function evaluateStallWindow(currentRound, vaultEntries,
|
|
567
380
|
/** v3.8.1: the round's success is machine-verified (`deriveEvidenceStatus`).
|
|
568
|
-
* A verified round is finishing work; a
|
|
381
|
+
* A verified round is finishing work; a progress window cannot outvote it. */
|
|
569
382
|
machineBackedSuccess,
|
|
570
|
-
/**
|
|
571
|
-
|
|
572
|
-
inFlightMotion) {
|
|
573
|
-
// A round the machine verified is not churn, whatever the window says. This
|
|
574
|
-
// is the machine-backed replacement for the old self-reported `>= 0.95`
|
|
575
|
-
// near-completion guard: it lets a loop FINISH (a closing round that only
|
|
576
|
-
// runs the verification and declares success changes no files).
|
|
383
|
+
/** The submitted round's own provider-neutral progress. */
|
|
384
|
+
inFlightProgress) {
|
|
577
385
|
if (machineBackedSuccess)
|
|
578
386
|
return null;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
// injection; a round that moves nothing still reaches the verdict and its
|
|
585
|
-
// deadlock guard.
|
|
586
|
-
if (inFlightMotion === true)
|
|
387
|
+
if (inFlightProgress.state === "changed")
|
|
388
|
+
return null;
|
|
389
|
+
// "Unknown never stalls": a submitted round whose progress cannot be
|
|
390
|
+
// proven unchanged gets no verdict.
|
|
391
|
+
if (inFlightProgress.state !== "unchanged")
|
|
587
392
|
return null;
|
|
588
|
-
const window =
|
|
589
|
-
? 3
|
|
590
|
-
: Math.max(1, getPolicy().engine.stall_lookback_rounds);
|
|
393
|
+
const window = Math.max(1, getPolicy().engine.stall_lookback_rounds);
|
|
591
394
|
if (currentRound < window)
|
|
592
395
|
return null;
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
396
|
+
const progress = deriveProgressWindow(derivationRounds(vaultEntries, currentRound), currentRound, window);
|
|
397
|
+
if (progress.state !== "unchanged")
|
|
595
398
|
return null;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
399
|
+
// The verdict is about the Git workspace coverage ONLY. A custom provider's
|
|
400
|
+
// fact — changed, unchanged or unknown — is never part of this sentence.
|
|
401
|
+
const providers = progress.providers.length > 0
|
|
402
|
+
? ` (providers: ${progress.providers.join(", ")})`
|
|
403
|
+
: "";
|
|
404
|
+
return {
|
|
405
|
+
detail: `no machine-observed progress within the Git workspace coverage in the ` +
|
|
406
|
+
`last ${window} committed rounds${providers}`,
|
|
407
|
+
window,
|
|
408
|
+
};
|
|
600
409
|
}
|
|
601
|
-
/**
|
|
602
|
-
*
|
|
603
|
-
* using the same mechanics — the tier is an internal diagnostic only. */
|
|
410
|
+
/** One progress disposition ladder. The reason states the machine fact; the
|
|
411
|
+
* fix states the legal protocol paths, never a semantic strategy. */
|
|
604
412
|
function resolveStallDisposition(verdict, currentRound, vaultEntries, consecutiveRejections, lastRejectionCheck) {
|
|
605
|
-
const {
|
|
606
|
-
const flatline = tier === "progress_flatline";
|
|
413
|
+
const { detail } = verdict;
|
|
607
414
|
const escalation = getPolicy().engine.enforcement_escalation_enabled;
|
|
608
|
-
const stalledReason =
|
|
609
|
-
? `Progress has been ${detail} for ${window} consecutive rounds.`
|
|
610
|
-
: `Progress has stalled: ${detail}.`;
|
|
611
|
-
// v3.7: both tiers emit the single progress_stall check id — the tier
|
|
612
|
-
// survives only in the reason/fix wording (former progress_flatline id).
|
|
415
|
+
const stalledReason = `Machine-observed progress is flat: ${detail}.`;
|
|
613
416
|
const check = "progress_stall";
|
|
614
417
|
// v3.8.3: the stall escalates on its OWN streak. The persisted counter is
|
|
615
|
-
// one scalar shared by every check, so two rejections of any other kind
|
|
616
|
-
//
|
|
418
|
+
// one scalar shared by every check, so two rejections of any other kind must
|
|
419
|
+
// not turn a first-time stall straight into termination.
|
|
617
420
|
const ownStreak = lastRejectionCheck === check ? consecutiveRejections : 0;
|
|
618
|
-
if (
|
|
421
|
+
if (ownStreak >= 2) {
|
|
619
422
|
return makeEnforcementResult({
|
|
620
423
|
action: "terminate",
|
|
621
424
|
reason: `${stalledReason} The stall persisted after escalation. Terminating loop.`,
|
|
@@ -629,7 +432,7 @@ function resolveStallDisposition(verdict, currentRound, vaultEntries, consecutiv
|
|
|
629
432
|
check,
|
|
630
433
|
});
|
|
631
434
|
}
|
|
632
|
-
const escalated =
|
|
435
|
+
const escalated = ownStreak >= 1 && escalation;
|
|
633
436
|
if (escalated && getPolicy().engine.backtrack_enabled) {
|
|
634
437
|
if (hasCommittedBacktrack(vaultEntries, currentRound)) {
|
|
635
438
|
return makeEnforcementResult({
|
|
@@ -641,34 +444,26 @@ function resolveStallDisposition(verdict, currentRound, vaultEntries, consecutiv
|
|
|
641
444
|
return makeEnforcementResult({
|
|
642
445
|
action: "backtrack",
|
|
643
446
|
reason: stalledReason,
|
|
644
|
-
fix_instructions:
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
if (flatline && !escalation) {
|
|
651
|
-
return makeEnforcementResult({
|
|
652
|
-
action: "terminate",
|
|
653
|
-
reason: `${stalledReason} The agent is making zero forward motion and cannot recover.`,
|
|
447
|
+
fix_instructions: "This is a machine liveness fact, not a judgment about your plan. " +
|
|
448
|
+
"The loop will restore the last clean round. After the restore you own " +
|
|
449
|
+
"the next action: continue with a new evaluation, request recovery to " +
|
|
450
|
+
"an earlier committed point, or declare blocked/stop.",
|
|
654
451
|
check,
|
|
655
452
|
});
|
|
656
453
|
}
|
|
657
454
|
return makeEnforcementResult({
|
|
658
455
|
action: "reject",
|
|
659
456
|
reason: stalledReason,
|
|
660
|
-
fix_instructions:
|
|
661
|
-
|
|
662
|
-
|
|
457
|
+
fix_instructions: "This is a machine liveness fact, not a judgment about your plan. " +
|
|
458
|
+
"You own the next action: submit a new evaluation, request recovery to " +
|
|
459
|
+
"an earlier committed point, or declare outcome blocked with a blocker. " +
|
|
460
|
+
"LoopForge does not prescribe a technique or decomposition." +
|
|
663
461
|
(escalated ? buildEscalationNotice() : ""),
|
|
664
462
|
check,
|
|
665
463
|
});
|
|
666
464
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
function enforceProgressStall(_flags, currentRound, vaultEntries, consecutiveRejections, machineBackedSuccess, lastRejectionCheck, inFlightMotion) {
|
|
670
|
-
const verdict = evaluateStallWindow("progress_stall", currentRound, vaultEntries, machineBackedSuccess, inFlightMotion)
|
|
671
|
-
?? evaluateStallWindow("progress_flatline", currentRound, vaultEntries, machineBackedSuccess, inFlightMotion);
|
|
465
|
+
function enforceProgressStall(_flags, currentRound, vaultEntries, consecutiveRejections, machineBackedSuccess, lastRejectionCheck, inFlightProgress) {
|
|
466
|
+
const verdict = evaluateStallWindow(currentRound, vaultEntries, machineBackedSuccess, inFlightProgress);
|
|
672
467
|
return verdict
|
|
673
468
|
? resolveStallDisposition(verdict, currentRound, vaultEntries, consecutiveRejections, lastRejectionCheck)
|
|
674
469
|
: null;
|
|
@@ -680,15 +475,7 @@ function enforceProgressStall(_flags, currentRound, vaultEntries, consecutiveRej
|
|
|
680
475
|
* instead of terminating immediately. Termination occurs on the third
|
|
681
476
|
* consecutive rejection. When the flag is disabled, behavior is unchanged
|
|
682
477
|
* (terminate at ≥2 rejections). */
|
|
683
|
-
function enforceMaxRejections(consecutiveRejections
|
|
684
|
-
// v3.8.3: this row is the catch-all for a round that LOOKS clean while the
|
|
685
|
-
// persisted counter says otherwise. A round waived past scope drift is not
|
|
686
|
-
// that shape: it declares blocked and its new proposal covers every file it
|
|
687
|
-
// drifted into, so the counter it carries is the drift streak it just
|
|
688
|
-
// resolved — not unresolved persistence. Without this, the legal exit would
|
|
689
|
-
// still dead-end here at a counter of 2 or more.
|
|
690
|
-
if (driftWaived)
|
|
691
|
-
return null;
|
|
478
|
+
function enforceMaxRejections(consecutiveRejections) {
|
|
692
479
|
const escalation = getPolicy().engine.enforcement_escalation_enabled;
|
|
693
480
|
if (escalation) {
|
|
694
481
|
if (consecutiveRejections >= 3) {
|
|
@@ -706,9 +493,9 @@ function enforceMaxRejections(consecutiveRejections, driftWaived) {
|
|
|
706
493
|
reason: `${consecutiveRejections} consecutive enforcement rejections for the ` +
|
|
707
494
|
`same issue without resolution.`,
|
|
708
495
|
fix_instructions: "You have been rejected multiple times for the same issue. " +
|
|
709
|
-
"
|
|
710
|
-
"If you
|
|
711
|
-
"
|
|
496
|
+
"Address every concrete finding in the mandatory Verification Gate. " +
|
|
497
|
+
"If you need human input, stop with needs_human_input; there is no " +
|
|
498
|
+
"semantic waiver for the findings." +
|
|
712
499
|
buildEscalationNotice(),
|
|
713
500
|
check: "max_rejections",
|
|
714
501
|
});
|
|
@@ -732,64 +519,41 @@ function enforceMaxRejections(consecutiveRejections, driftWaived) {
|
|
|
732
519
|
function buildEscalationNotice() {
|
|
733
520
|
return [
|
|
734
521
|
"",
|
|
735
|
-
"### 🆘 Escalation —
|
|
522
|
+
"### 🆘 Escalation — Human Decision Required",
|
|
736
523
|
"",
|
|
737
|
-
"
|
|
738
|
-
"
|
|
524
|
+
"This check has rejected the same round more than once. Before retrying, " +
|
|
525
|
+
"ask the human whether to continue the current scope, change the " +
|
|
526
|
+
"operator-owned scope, or stop.",
|
|
739
527
|
"",
|
|
740
|
-
"1.
|
|
741
|
-
"2. Ask the human for
|
|
742
|
-
"3.
|
|
528
|
+
"1. State the machine fact and the attempt already made.",
|
|
529
|
+
"2. Ask the human for a decision (continue / change scope / stop).",
|
|
530
|
+
"3. Record the decision in the next evaluation, or stop.",
|
|
743
531
|
"",
|
|
744
|
-
"⚠️ If
|
|
745
|
-
"Do NOT resubmit without meaningful changes.",
|
|
532
|
+
"⚠️ If the same check rejects again, the loop will be **terminated**.",
|
|
746
533
|
].join("\n");
|
|
747
534
|
}
|
|
748
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
749
|
-
// Diagnostic gap builder (v2.7)
|
|
750
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
751
|
-
/** Build a diagnostic gap section from verification flags.
|
|
752
|
-
*
|
|
753
|
-
* Translates each error/warn-level VerificationFlag into a concrete
|
|
754
|
-
* claim-vs-evidence mismatch statement. Info-level flags are excluded
|
|
755
|
-
* because they are purely informational and do not represent gaps.
|
|
756
|
-
*
|
|
757
|
-
* Returns an empty string when there are no diagnostic flags to report. */
|
|
758
|
-
function buildDiagnosticGap(flags) {
|
|
759
|
-
const diagnosticFlags = flags.filter((f) => f.severity === "error" || f.severity === "warn");
|
|
760
|
-
if (diagnosticFlags.length === 0)
|
|
761
|
-
return "";
|
|
762
|
-
const lines = [
|
|
763
|
-
"### Evidence Gap",
|
|
764
|
-
"",
|
|
765
|
-
"The enforcement gate detected mismatches between your claims and the evidence. " +
|
|
766
|
-
"Each item below shows a specific discrepancy — address every one before re-submitting.",
|
|
767
|
-
"",
|
|
768
|
-
];
|
|
769
|
-
for (const flag of diagnosticFlags) {
|
|
770
|
-
const icon = flag.severity === "error" ? "🚫" : "⚠️";
|
|
771
|
-
lines.push(`- ${icon} **[${flag.check}]** \`${flag.field}\` — ${flag.detail}`);
|
|
772
|
-
}
|
|
773
|
-
lines.push("");
|
|
774
|
-
return lines.join("\n");
|
|
775
|
-
}
|
|
776
535
|
const RULE_TABLE = [
|
|
777
536
|
{ checks: ["success_with_remaining_criteria"], ladder: "uniform", terminalAfter: 2, noticeOnRepeat: true },
|
|
778
537
|
{ checks: ["recurring_violation"], ladder: "uniform", terminalAfter: 2, noticeOnRepeat: true },
|
|
779
|
-
{ checks: ["required_command_failed"
|
|
538
|
+
{ checks: ["required_command_failed"], ladder: "uniform", terminalAfter: 2, noticeOnRepeat: true },
|
|
539
|
+
// v4.0.6: contract coverage — a continuing round must leave an active
|
|
540
|
+
// contract; a different proposal needs an explicit supersede; a successor
|
|
541
|
+
// must change the acceptance items. Reject and let the uniform ladder
|
|
542
|
+
// escalate repeats (three same-check rejections terminate).
|
|
543
|
+
{ checks: ["contract_required", "contract_premature", "contract_successor_unchanged"], ladder: "uniform", terminalAfter: 3, noticeOnRepeat: true },
|
|
780
544
|
// v3.8.3: own ladder — reject → backtrack → terminate. A rewritten
|
|
781
545
|
// entrypoint is not fixed by asking again (the round's baseline is frozen on
|
|
782
546
|
// purpose), so the second occurrence rolls the work back instead of killing
|
|
783
547
|
// the loop. See enforceVerificationEntrypointTampered.
|
|
784
548
|
{ checks: ["verification_entrypoint_modified"], ladder: "internal" },
|
|
785
|
-
|
|
549
|
+
// v3.9.9: round_scope_drift is REPORT-ONLY. It has no enforcement row, no
|
|
550
|
+
// waiver, no rejection counter, and no stall/backtrack interaction.
|
|
786
551
|
// v3.8.1: "internal" — the handler owns this row's ladder (it must terminate
|
|
787
552
|
// as `incomplete`, with its own reason). Declaring it uniform as well ran the
|
|
788
553
|
// escalation notice twice and left the uniform terminate branch dead.
|
|
789
554
|
{ checks: ["contract_items_unverified"], ladder: "internal" },
|
|
790
555
|
{ checks: ["progress_stall"], ladder: "internal" },
|
|
791
556
|
{ checks: ["max_rejections"], ladder: "counter" },
|
|
792
|
-
{ checks: ["backtrack_workspace_not_restored"], ladder: "internal" },
|
|
793
557
|
// v3.7.1: cited gates without an approved human decision
|
|
794
558
|
{ checks: ["user_gate_unresolved"], ladder: "uniform", terminalAfter: 2, noticeOnRepeat: true },
|
|
795
559
|
];
|
|
@@ -842,16 +606,24 @@ export function enforceRound(selfEval, verifyResult, currentRound, vaultEntries,
|
|
|
842
606
|
* check and lets uniform rows escalate on THEIR OWN streak instead of
|
|
843
607
|
* inheriting an unrelated history. */
|
|
844
608
|
lastRejectionCheck = "",
|
|
845
|
-
/** v3.
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
/** v3.9: the submitted round's own Git motion — see `inFlightGitMotion`.
|
|
852
|
-
* null when no Git observation was made. */
|
|
853
|
-
inFlightMotion = null) {
|
|
609
|
+
/** v3.9.7: the submitted round's provider-neutral progress. */
|
|
610
|
+
inFlightProgress = {
|
|
611
|
+
state: "unknown",
|
|
612
|
+
providers: [],
|
|
613
|
+
coverages: [],
|
|
614
|
+
}) {
|
|
854
615
|
const { flags } = verifyResult;
|
|
616
|
+
// v3.9.9: `round_scope_drift` is a retired enforcement check. A session
|
|
617
|
+
// restored from an older runtime may still name it; it is not a current
|
|
618
|
+
// check, and its persisted count must not reach the max_rejections
|
|
619
|
+
// catch-all as unresolved persistence.
|
|
620
|
+
const retiredScopeCheck = lastRejectionCheck === "round_scope_drift";
|
|
621
|
+
const effectiveRejections = retiredScopeCheck ? 0 : consecutiveRejections;
|
|
622
|
+
const effectiveLastCheck = retiredScopeCheck ? "" : lastRejectionCheck;
|
|
623
|
+
// A valid blocked stop is the Agent's protocol action. Liveness/retry rows
|
|
624
|
+
// must not convert it into a same-round rejection; machine-integrity rows
|
|
625
|
+
// still apply below because their facts make the submission untrustworthy.
|
|
626
|
+
const blockedStop = declaresBlocked(selfEval) && selfEval.should_continue === false;
|
|
855
627
|
// Run enforcement rules in priority order — the array order IS the
|
|
856
628
|
// priority; the R-labels in the comments are historical (RULE_TABLE is
|
|
857
629
|
// the semantic description). The first rule that fires wins.
|
|
@@ -861,9 +633,11 @@ inFlightMotion = null) {
|
|
|
861
633
|
// v2.14: R-EVID — a required command failure, hidden test failures, or
|
|
862
634
|
// a self-contradictory outcome claim contradicts the success claim
|
|
863
635
|
() => enforceEvidenceContradiction(flags),
|
|
636
|
+
// v4.0.6: contract coverage (required / premature / successor unchanged)
|
|
637
|
+
() => enforceContractCoverage(flags),
|
|
864
638
|
// v3.3: R-EVID-VERIFY — the verification command entrypoint changed in
|
|
865
639
|
// the same round it ran.
|
|
866
|
-
() => enforceVerificationEntrypointTampered(flags, vaultEntries, currentRound,
|
|
640
|
+
() => enforceVerificationEntrypointTampered(flags, vaultEntries, currentRound, effectiveRejections, effectiveLastCheck),
|
|
867
641
|
// v3.5: contract completion claimed without its verification_plan
|
|
868
642
|
// commands passing — runs before R-C1: the completion-truth question
|
|
869
643
|
// outranks boundary nuance.
|
|
@@ -873,23 +647,26 @@ inFlightMotion = null) {
|
|
|
873
647
|
// success_without_verified_evidence, which is now a warn: report coverage
|
|
874
648
|
// no longer decides acceptance, and the machine-evidence warn still keeps
|
|
875
649
|
// an unbacked success out of the trajectory.
|
|
876
|
-
// v3.
|
|
877
|
-
() => enforceScopeDrift(flags, consecutiveRejections, lastRejectionCheck, scopeDriftWaiver),
|
|
650
|
+
// v3.9.9: scope drift is an INFO fact only; it never appears here.
|
|
878
651
|
// v3.8: verification debt — contract items claimed met without machine
|
|
879
652
|
// verification (fires only after the configured streak)
|
|
880
|
-
() =>
|
|
881
|
-
|
|
653
|
+
() => blockedStop
|
|
654
|
+
? null
|
|
655
|
+
: enforceContractItemsUnverified(flags, currentRound, vaultEntries, effectiveRejections, effectiveLastCheck),
|
|
656
|
+
// v3.9.7: one provider-neutral progress evaluator —
|
|
882
657
|
// reject → backtrack → terminate with the deadlock guard
|
|
883
|
-
() =>
|
|
658
|
+
() => blockedStop
|
|
659
|
+
? null
|
|
660
|
+
: enforceProgressStall(flags, currentRound, vaultEntries, effectiveRejections, verifyResult.machineBackedSuccess, effectiveLastCheck, inFlightProgress),
|
|
884
661
|
// R6: rejection-counter catch-all (evaluated only when no row above
|
|
885
662
|
// fired — a clean-looking round carrying a high persisted counter)
|
|
886
663
|
// v3.7.1: cited gate without an approved human decision
|
|
887
664
|
() => enforceUserGateUnresolved(flags),
|
|
888
|
-
// v3.
|
|
889
|
-
//
|
|
890
|
-
() =>
|
|
891
|
-
|
|
892
|
-
|
|
665
|
+
// v3.9.9: round_scope_drift is not a rejection source, so this catch-all
|
|
666
|
+
// has no drift-waiver special case anymore.
|
|
667
|
+
() => blockedStop
|
|
668
|
+
? null
|
|
669
|
+
: enforceMaxRejections(effectiveRejections),
|
|
893
670
|
];
|
|
894
671
|
// v3.7: uniform-ladder escalation. Rows without their own escalation
|
|
895
672
|
// ladder (R1/R2/R-EVID/R-EVID-VERIFY) escalate on the session's
|
|
@@ -915,8 +692,8 @@ inFlightMotion = null) {
|
|
|
915
692
|
// occurrence into termination. The user_gate row is the sharp case:
|
|
916
693
|
// its recovery is human approval, so a gate cited after unrelated
|
|
917
694
|
// rejections must reject with instructions, not kill the loop.
|
|
918
|
-
const ownStreak =
|
|
919
|
-
?
|
|
695
|
+
const ownStreak = effectiveLastCheck === result.check
|
|
696
|
+
? effectiveRejections
|
|
920
697
|
: 0;
|
|
921
698
|
if (ownStreak >= (row.terminalAfter ?? 2)) {
|
|
922
699
|
return makeEnforcementResult({
|
|
@@ -944,54 +721,41 @@ inFlightMotion = null) {
|
|
|
944
721
|
fix_instructions: "",
|
|
945
722
|
});
|
|
946
723
|
}
|
|
947
|
-
/** Build
|
|
948
|
-
*
|
|
949
|
-
* The prompt clearly states the round was rejected, why, what the agent
|
|
950
|
-
* must fix, and that the agent must redo the SAME round (not advance).
|
|
724
|
+
/** Build the protected retry notice for the agent.
|
|
951
725
|
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
726
|
+
* v3.9.7: this is a protocol fact, not a second task narrative. It carries
|
|
727
|
+
* the enforcement reason, the required fix and the same-round instruction.
|
|
728
|
+
* Findings are rendered exactly once by the mandatory Verification Gate; the
|
|
729
|
+
* original task/contract is rendered by Objective/Current Task. The notice
|
|
730
|
+
* never promises that an explanation will re-evaluate a machine fact.
|
|
956
731
|
*
|
|
957
732
|
* @param currentRound The round number that was rejected (NOT incremented).
|
|
958
|
-
* @param
|
|
733
|
+
* @param _task Kept for the public signature; not rendered here.
|
|
959
734
|
* @param enforceResult The enforcement decision with reason and fix instructions.
|
|
960
|
-
* @param
|
|
961
|
-
*
|
|
962
|
-
export function buildRejectionPrompt(currentRound,
|
|
735
|
+
* @param _verificationFlags Kept for the public signature; findings render in
|
|
736
|
+
* the mandatory Verification Gate. */
|
|
737
|
+
export function buildRejectionPrompt(currentRound, _task, enforceResult, _verificationFlags = []) {
|
|
963
738
|
const lines = [
|
|
964
739
|
"## ⛔ Round " + currentRound + " — REJECTED",
|
|
965
740
|
"",
|
|
966
|
-
"
|
|
967
|
-
|
|
741
|
+
"The enforcement gate rejected this round. This is a machine/protocol",
|
|
742
|
+
"decision, not a judgment about your reasoning strategy.",
|
|
968
743
|
"",
|
|
969
744
|
"### Reason",
|
|
970
745
|
"",
|
|
971
746
|
enforceResult.reason,
|
|
972
747
|
"",
|
|
973
748
|
];
|
|
974
|
-
// v2.7: Diagnostic gap — show the agent exactly what doesn't match.
|
|
975
|
-
const gap = buildDiagnosticGap(verificationFlags);
|
|
976
|
-
if (gap) {
|
|
977
|
-
lines.push(gap);
|
|
978
|
-
}
|
|
979
749
|
if (enforceResult.fix_instructions) {
|
|
980
750
|
lines.push("### Required Fix");
|
|
981
751
|
lines.push("");
|
|
982
752
|
lines.push(enforceResult.fix_instructions);
|
|
983
753
|
lines.push("");
|
|
984
754
|
}
|
|
985
|
-
lines.push("### Your Task (Round " + currentRound + " — Retry)");
|
|
986
|
-
lines.push("");
|
|
987
|
-
lines.push(task);
|
|
988
|
-
lines.push("");
|
|
989
755
|
lines.push("### Instructions");
|
|
990
756
|
lines.push("");
|
|
991
757
|
lines.push("1. Read and address each issue in **Required Fix** above.", "2. Re-execute **Round " + currentRound +
|
|
992
|
-
"** — do NOT advance to the next round.", "3. Submit a corrected
|
|
993
|
-
"claiming success when criteria are unmet will be rejected again.", "5. If you believe this rejection is incorrect, " +
|
|
994
|
-
"explain why in your output_summary and the enforcement gate will re-evaluate.");
|
|
758
|
+
"** — do NOT advance to the next round.", "3. Submit a corrected evaluation via `loopforge_next` using the latest", " `roundId` returned by LoopForge.", "4. If you believe a finding is factually wrong, identify the specific", " machine fact you dispute and submit a corrected evaluation. The gate", " re-evaluates facts, not explanations.");
|
|
995
759
|
return lines.join("\n");
|
|
996
760
|
}
|
|
997
761
|
//# sourceMappingURL=enforcement-gate.js.map
|