@kenkaiiii/ggcoder 5.70.0 → 5.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/assets/motion/bin/contact-sheet.mjs +9 -3
  2. package/assets/motion/bin/cues.mjs +337 -0
  3. package/assets/motion/bin/library.mjs +53 -10
  4. package/assets/motion/bin/motion-blur.mjs +943 -0
  5. package/assets/motion/bin/motion-check.mjs +354 -3
  6. package/assets/motion/bin/music-fit.mjs +436 -0
  7. package/assets/motion/bin/pdf-extract.mjs +22 -10
  8. package/assets/motion/bin/reference-study.mjs +346 -0
  9. package/assets/motion/bin/score-synth.mjs +1052 -93
  10. package/assets/motion/library/README.md +50 -14
  11. package/assets/motion/library/kit/moves.js +1981 -0
  12. package/assets/motion/library/library.json +232 -0
  13. package/assets/motion/library/pieces/camera-rig/meta.json +13 -0
  14. package/assets/motion/library/pieces/camera-rig/piece.html +153 -0
  15. package/assets/motion/library/pieces/camera-rig/preview.jpg +0 -0
  16. package/assets/motion/library/pieces/chain-knock/meta.json +13 -0
  17. package/assets/motion/library/pieces/chain-knock/piece.html +195 -0
  18. package/assets/motion/library/pieces/chain-knock/preview.jpg +0 -0
  19. package/assets/motion/library/pieces/gather-to-logo/meta.json +13 -0
  20. package/assets/motion/library/pieces/gather-to-logo/piece.html +159 -0
  21. package/assets/motion/library/pieces/gather-to-logo/preview.jpg +0 -0
  22. package/assets/motion/library/pieces/morph-carry/meta.json +13 -0
  23. package/assets/motion/library/pieces/morph-carry/piece.html +173 -0
  24. package/assets/motion/library/pieces/morph-carry/preview.jpg +0 -0
  25. package/assets/motion/library/pieces/one-shape-journey/meta.json +13 -0
  26. package/assets/motion/library/pieces/one-shape-journey/piece.html +195 -0
  27. package/assets/motion/library/pieces/one-shape-journey/preview.jpg +0 -0
  28. package/assets/motion/library/pieces/open-from-subject/meta.json +13 -0
  29. package/assets/motion/library/pieces/open-from-subject/piece.html +168 -0
  30. package/assets/motion/library/pieces/open-from-subject/preview.jpg +0 -0
  31. package/assets/motion/library/pieces/request-to-result/meta.json +13 -0
  32. package/assets/motion/library/pieces/request-to-result/piece.html +212 -0
  33. package/assets/motion/library/pieces/request-to-result/preview.jpg +0 -0
  34. package/assets/motion/library/pieces/scale-dive/meta.json +13 -0
  35. package/assets/motion/library/pieces/scale-dive/piece.html +321 -0
  36. package/assets/motion/library/pieces/scale-dive/preview.jpg +0 -0
  37. package/assets/motion/library/pieces/screen-replica-steps/meta.json +13 -0
  38. package/assets/motion/library/pieces/screen-replica-steps/piece.html +366 -0
  39. package/assets/motion/library/pieces/screen-replica-steps/preview.jpg +0 -0
  40. package/assets/motion/library/pieces/zoom-into-card/meta.json +13 -0
  41. package/assets/motion/library/pieces/zoom-into-card/piece.html +179 -0
  42. package/assets/motion/library/pieces/zoom-into-card/preview.jpg +0 -0
  43. package/assets/motion/library/sheets/diagram.jpg +0 -0
  44. package/assets/motion/library/sheets/frame.jpg +0 -0
  45. package/assets/motion/library/sheets/transition.jpg +0 -0
  46. package/assets/motion/library/sheets/ui.jpg +0 -0
  47. package/assets/motion/references/build-sheet.md +206 -0
  48. package/assets/motion/references/runtime/determinism-rules.md +1 -1
  49. package/assets/motion/references/runtime/gsap-easing-and-stagger.md +29 -29
  50. package/assets/motion/references/runtime/inputs-and-assets.md +7 -12
  51. package/assets/motion/references/runtime/lint-validate-inspect.md +3 -3
  52. package/assets/motion/references/runtime/minimal-composition.md +1 -1
  53. package/assets/motion/references/runtime/preview-render.md +3 -3
  54. package/assets/motion/skills/app-walkthrough/SKILL.md +66 -0
  55. package/assets/motion/skills/before-after/SKILL.md +53 -0
  56. package/assets/motion/skills/brand-kit/SKILL.md +3 -3
  57. package/assets/motion/skills/dev-tool-video/SKILL.md +57 -0
  58. package/assets/motion/skills/launch-video/SKILL.md +62 -0
  59. package/assets/motion/skills/match-reference/SKILL.md +58 -0
  60. package/assets/motion/skills/motion/SKILL.md +78 -85
  61. package/assets/motion/skills/source-ingest/SKILL.md +17 -6
  62. package/assets/motion/skills/website-video/SKILL.md +59 -0
  63. package/assets/skills/bulletproof/SKILL.md +36 -11
  64. package/assets/skills/bulletproof/references/agent-surface.md +19 -9
  65. package/assets/skills/bulletproof/references/audit-protocol.md +20 -5
  66. package/assets/skills/bulletproof/references/platform-playbooks.md +5 -4
  67. package/assets/skills/bulletproof/references/provenance.md +26 -1
  68. package/assets/skills/bulletproof/references/secure-defaults.md +6 -5
  69. package/assets/skills/bulletproof/references/supply-chain.md +21 -17
  70. package/assets/skills/bulletproof/references/threat-landscape.md +28 -26
  71. package/assets/skills/bulletproof/references/verification.md +2 -0
  72. package/assets/skills/clarify/SKILL.md +25 -16
  73. package/assets/skills/code-review/SKILL.md +71 -13
  74. package/assets/skills/code-review/references/agent-diffs.md +27 -0
  75. package/assets/skills/code-review/references/tests.md +19 -0
  76. package/assets/skills/compliance-guard/SKILL.md +20 -5
  77. package/assets/skills/compliance-guard/references/artifacts.md +1 -1
  78. package/assets/skills/compliance-guard/references/eu-uk.md +16 -16
  79. package/assets/skills/compliance-guard/references/lawsuit-vectors.md +5 -5
  80. package/assets/skills/compliance-guard/references/provenance.md +41 -2
  81. package/assets/skills/compliance-guard/references/sector-gates.md +3 -3
  82. package/assets/skills/compliance-guard/references/security-baseline.md +2 -2
  83. package/assets/skills/compliance-guard/references/trigger-map.md +5 -5
  84. package/assets/skills/compliance-guard/references/us.md +27 -21
  85. package/assets/skills/durable/SKILL.md +87 -79
  86. package/assets/skills/durable/references/agent-db-safety.md +69 -0
  87. package/assets/skills/durable/references/backups-and-runtime.md +19 -12
  88. package/assets/skills/durable/references/migrations-and-schema.md +13 -6
  89. package/assets/skills/evidence-led-ui/SKILL.md +69 -127
  90. package/assets/skills/evidence-led-ui/references/anti-defaults.md +107 -208
  91. package/assets/skills/evidence-led-ui/references/direction.md +124 -0
  92. package/assets/skills/evidence-led-ui/references/production-contract.md +8 -0
  93. package/assets/skills/evidence-led-ui/references/provenance.md +24 -1
  94. package/assets/skills/lean/SKILL.md +90 -71
  95. package/assets/skills/lean/references/memory-and-processes.md +3 -2
  96. package/assets/skills/lean/references/playbooks.md +37 -12
  97. package/assets/skills/refactoring/SKILL.md +24 -3
  98. package/assets/skills/refactoring/references/agent-pitfalls.md +4 -1
  99. package/assets/skills/refactoring/references/legacy.md +21 -0
  100. package/assets/skills/root-cause/SKILL.md +20 -10
  101. package/assets/skills/shared-language/SKILL.md +16 -14
  102. package/assets/skills/tdd/SKILL.md +27 -15
  103. package/dist/app-sidecar.js +45 -33
  104. package/dist/app-sidecar.js.map +1 -1
  105. package/dist/cli.js +0 -6
  106. package/dist/cli.js.map +1 -1
  107. package/dist/core/agent-session.d.ts +24 -71
  108. package/dist/core/agent-session.js +67 -427
  109. package/dist/core/agent-session.js.map +1 -1
  110. package/dist/core/mcp/content.js +6 -2
  111. package/dist/core/mcp/content.js.map +1 -1
  112. package/dist/core/session-history.d.ts +12 -0
  113. package/dist/core/session-history.js +27 -0
  114. package/dist/core/session-history.js.map +1 -1
  115. package/dist/core/settings-manager.d.ts +0 -1
  116. package/dist/core/settings-manager.js +2 -5
  117. package/dist/core/settings-manager.js.map +1 -1
  118. package/dist/core/skills.js +3 -1
  119. package/dist/core/skills.js.map +1 -1
  120. package/dist/core/verification-gate.d.ts +8 -100
  121. package/dist/core/verification-gate.js +26 -260
  122. package/dist/core/verification-gate.js.map +1 -1
  123. package/dist/motion-agent/motion-agent.d.ts +6 -2
  124. package/dist/motion-agent/motion-agent.js +5 -7
  125. package/dist/motion-agent/motion-agent.js.map +1 -1
  126. package/dist/motion-agent/motion-prompt.d.ts +1 -1
  127. package/dist/motion-agent/motion-prompt.js +14 -19
  128. package/dist/motion-agent/motion-prompt.js.map +1 -1
  129. package/dist/motion-agent/motion-review.d.ts +5 -1
  130. package/dist/motion-agent/motion-review.js +8 -3
  131. package/dist/motion-agent/motion-review.js.map +1 -1
  132. package/dist/motion-agent/motion-studio-context.js +1 -1
  133. package/dist/motion-agent/motion-studio-context.js.map +1 -1
  134. package/dist/system-prompt.js +3 -1
  135. package/dist/system-prompt.js.map +1 -1
  136. package/dist/tools/bash-read-evidence.d.ts +10 -0
  137. package/dist/tools/bash-read-evidence.js +133 -0
  138. package/dist/tools/bash-read-evidence.js.map +1 -0
  139. package/dist/tools/edit.js +12 -4
  140. package/dist/tools/edit.js.map +1 -1
  141. package/dist/tools/index.d.ts +7 -1
  142. package/dist/tools/index.js +5 -0
  143. package/dist/tools/index.js.map +1 -1
  144. package/dist/tools/read-tracker.d.ts +5 -0
  145. package/dist/tools/read-tracker.js +19 -9
  146. package/dist/tools/read-tracker.js.map +1 -1
  147. package/dist/tools/skill.js +5 -0
  148. package/dist/tools/skill.js.map +1 -1
  149. package/dist/ui/App.d.ts +0 -7
  150. package/dist/ui/App.js +6 -79
  151. package/dist/ui/App.js.map +1 -1
  152. package/dist/ui/hooks/useAgentLoop.d.ts +0 -7
  153. package/dist/ui/hooks/useAgentLoop.js +1 -119
  154. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  155. package/dist/ui/render.d.ts +0 -4
  156. package/dist/ui/render.js +0 -2
  157. package/dist/ui/render.js.map +1 -1
  158. package/dist/utils/image.d.ts +14 -0
  159. package/dist/utils/image.js +16 -0
  160. package/dist/utils/image.js.map +1 -1
  161. package/package.json +4 -4
  162. package/assets/motion/references/motion-language.md +0 -128
  163. package/assets/motion/skills/video-qa/SKILL.md +0 -89
  164. package/dist/core/ideal-review-subagent.d.ts +0 -56
  165. package/dist/core/ideal-review-subagent.js +0 -112
  166. package/dist/core/ideal-review-subagent.js.map +0 -1
  167. package/dist/core/ideal-review.d.ts +0 -82
  168. package/dist/core/ideal-review.js +0 -242
  169. package/dist/core/ideal-review.js.map +0 -1
  170. package/dist/motion-agent/motion-check-tool.d.ts +0 -42
  171. package/dist/motion-agent/motion-check-tool.js +0 -564
  172. package/dist/motion-agent/motion-check-tool.js.map +0 -1
@@ -24,10 +24,9 @@ import { ensureAppDirs } from "../config.js";
24
24
  import { buildSubAgentSystemPrompt, buildSystemPrompt, } from "../system-prompt.js";
25
25
  import { createTools, createWebSearchTool, } from "../tools/index.js";
26
26
  import { partitionToolsByTier } from "../tools/tool-tiers.js";
27
- import { formatImpactForVerification } from "./test-impact.js";
28
27
  import { autoBackgroundedId } from "../tools/bash.js";
29
28
  import { buildProcessCompletionFollowUp } from "./process-gate.js";
30
- import { buildSubAgentCompletionFollowUp, } from "./subagent-manager.js";
29
+ import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
31
30
  import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
32
31
  import { z } from "zod";
33
32
  import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
@@ -52,15 +51,13 @@ import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
52
51
  import { discoverAgents } from "./agents.js";
53
52
  import { enhancePrompt } from "../utils/prompt-enhancer.js";
54
53
  import { detectLanguages, detectProjectStack } from "./language-detector.js";
55
- import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageEscalationMessage, buildReviewCoverageMessage, MAX_REVIEW_COVERAGE_INJECTIONS, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
56
54
  import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
57
55
  import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
58
56
  import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, MAX_SEMANTIC_LOOP_CALLS, parseSemanticLoopVerdict, shouldRunSemanticLoopCheck, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
59
- import { buildIndependentReviewMessage, buildReviewerTask, INDEPENDENT_REVIEW_SCORE_THRESHOLD, parseReviewerFindings, REVIEWER_TOOLS, REVIEWER_TURN_TIMEOUT_MS, REVIEWER_WAIT_MS, } from "./ideal-review-subagent.js";
60
57
  import { buildEnvDeltaMessage } from "./env-delta.js";
61
58
  import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
62
59
  import { AgentNotificationQueue } from "./agent-notifications.js";
63
- import { VerificationGate, extractAddedLines, isCheckOwnFile, isCodeFilePath, VERIFICATION_STATE_KIND, isVerificationCommand, } from "./verification-gate.js";
60
+ import { VerificationGate, isCheckOwnFile, isCodeFilePath, VERIFICATION_STATE_KIND, isVerificationCommand, } from "./verification-gate.js";
64
61
  import { classifyVerificationCommand } from "./verification-evidence.js";
65
62
  import { captureVerificationSnapshot } from "./verification-snapshot.js";
66
63
  import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
@@ -75,14 +72,6 @@ import path from "node:path";
75
72
  * progressing — refuse to extend its turn budget.
76
73
  */
77
74
  const TURN_EXTENSION_MAX_FAILURE_RATIO = 0.5;
78
- /** Terminal subagent states — mirrors SubAgentManager's private isTerminal. */
79
- function isTerminalSubAgentState(state) {
80
- return (state === "completed" ||
81
- state === "failed" ||
82
- state === "interrupted" ||
83
- state === "closed" ||
84
- state === "reaped");
85
- }
86
75
  // ── Tool-result policy ─────────────────────────────────────
87
76
  /** Resolve the per-result cap passed to the agent loop for the active transport. */
88
77
  export function resolveSessionToolResultCharLimit(model, provider, accountId) {
@@ -151,20 +140,13 @@ export class AgentSession {
151
140
  /** Forgets every file read; called whenever the conversation is replaced or
152
141
  * rewound, so the model must re-read a file before changing it. */
153
142
  clearReadTracker;
143
+ recordBashReads;
154
144
  skills = [];
155
145
  cacheKeyLogged = false;
156
146
  // ── Self-correction hook state (mirrors the TUI's useAgentLoop refs) ──
157
147
  // Reset at the start of every run; observed from the event stream; read by
158
- // the loop-break (mid-loop) and ideal-review (pre-stop) callbacks.
159
- hookStats = {
160
- changedLines: 0,
161
- toolCalls: 0,
162
- toolFailures: 0,
163
- turns: 0,
164
- writeCalls: 0,
165
- editCalls: 0,
166
- bashCalls: 0,
167
- };
148
+ // the loop-break (mid-loop) callback.
149
+ hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
168
150
  hookText = "";
169
151
  hookConsecutiveFailures = 0;
170
152
  hookRepeatedNoProgressCalls = 0;
@@ -174,20 +156,6 @@ export class AgentSession {
174
156
  hookFileEditCounts = new Map();
175
157
  hookToolCalls = new Map();
176
158
  backgroundVerification = new Map();
177
- idealReviewPhase = "idle";
178
- /** Runtime-only suppression while Ken owns verification in autopilot mode. */
179
- idealReviewSuppressed = false;
180
- /** Mirror of the last `hook_armed` value broadcast this run, so the event
181
- * fires only on a real edge. */
182
- idealReviewArmed = false;
183
- /** Cached test-drift probe, keyed by the size of the edited-file set. Drift
184
- * depends only on WHICH files were edited and that set only grows, so this
185
- * keeps the arming check off the filesystem on most tool results — the probe
186
- * is several sync existsSync calls per edited file. */
187
- idealDriftProbe = null;
188
- reviewCoverage;
189
- /** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */
190
- reviewCoverageInjected = 0;
191
159
  /** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
192
160
  loopBreakInjected = 0;
193
161
  regroundingInjected = false;
@@ -197,8 +165,6 @@ export class AgentSession {
197
165
  * injection at the next steering poll; judge failures fail open (no
198
166
  * injection) and still consume budget + cooldown. */
199
167
  semanticLoop = { checksUsed: 0, lastCheckTurn: 0, pending: false, verdict: null, injected: false };
200
- /** Independent Ideal reviewer spawned once per run (score-gated). */
201
- independentReviewStarted = false;
202
168
  /**
203
169
  * The environment as the cached system prompt currently describes it.
204
170
  * Re-recorded on every prompt build, so a rebuild (e.g. `/add-dir`) needs no
@@ -209,11 +175,11 @@ export class AgentSession {
209
175
  runStartedAt = 0;
210
176
  /** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */
211
177
  processGateInjected = 0;
212
- /** Verification gate: code edited this run, nothing proved it since. */
178
+ /** Verification evidence: code edited this run, and what has proved it since.
179
+ * Passive tracking only — it feeds run status and autopilot, never a turn. */
213
180
  verificationGate = new VerificationGate();
214
- /** Mirror of the last verification `hook_armed` value, so the event fires
215
- * only on a real edge. */
216
- verificationArmed = false;
181
+ /** Mirror of the last `hook_armed` value, so the event fires only on an edge. */
182
+ preFinalArmed = false;
217
183
  compactionOccurred = false;
218
184
  /**
219
185
  * Re-grounding carry-over for post-turn compaction. `resetHookState` clears
@@ -264,7 +230,6 @@ export class AgentSession {
264
230
  steeringListeners = new Set();
265
231
  processManager;
266
232
  lspManager;
267
- testImpact;
268
233
  debugManager;
269
234
  subAgentManager;
270
235
  /**
@@ -369,7 +334,6 @@ export class AgentSession {
369
334
  this.provider = options.provider;
370
335
  this.model = options.model;
371
336
  this.cwd = options.cwd;
372
- this.reviewCoverage = new ReviewCoverageTracker(this.cwd);
373
337
  this.baseUrl = options.baseUrl;
374
338
  this.maxTokens = this.resolveMaxTokens(options.model);
375
339
  this.thinkingLevel = options.thinkingLevel;
@@ -435,7 +399,7 @@ export class AgentSession {
435
399
  : this.opts.globalSubagents
436
400
  ? await discoverAgents({ globalAgentsDir: paths.agentsDir })
437
401
  : [];
438
- const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, lspManager, testImpact, debugManager, subAgentManager, } = await createTools(this.cwd, {
402
+ const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, recordBashReads, lspManager, debugManager, subAgentManager, } = await createTools(this.cwd, {
439
403
  agents,
440
404
  skills: this.skills,
441
405
  contextLimits: this.contextLimits,
@@ -466,11 +430,9 @@ export class AgentSession {
466
430
  }),
467
431
  getUseExternalGrep: () => this.settingsManager.get("grepUseRipgrep"),
468
432
  authStorage: this.authStorage,
469
- onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
470
433
  onFileMutated: (filePath) => {
471
434
  const relative = path.relative(this.cwd, filePath) || path.basename(filePath);
472
435
  this.hookFileEditCounts.set(relative, (this.hookFileEditCounts.get(relative) ?? 0) + 1);
473
- this.reviewCoverage.recordChanged(filePath);
474
436
  },
475
437
  // Lazy — sessionId/model/provider can change after createTools() runs, so
476
438
  // sub-agent spawns read the current parent state at execution time.
@@ -526,9 +488,9 @@ export class AgentSession {
526
488
  }
527
489
  this.rebuildReadTool = rebuildReadTool;
528
490
  this.clearReadTracker = clearReadTracker;
491
+ this.recordBashReads = recordBashReads;
529
492
  this.processManager = processManager;
530
493
  this.lspManager = lspManager;
531
- this.testImpact = testImpact;
532
494
  this.debugManager = debugManager;
533
495
  this.subAgentManager = subAgentManager;
534
496
  this.bindManagerCancellation(this.opts.signal);
@@ -1116,15 +1078,7 @@ export class AgentSession {
1116
1078
  resetHookState(originalRequest) {
1117
1079
  this.opts.completionReview?.begin(originalRequest);
1118
1080
  this.lspManager?.clearPendingDiagnostics();
1119
- this.hookStats = {
1120
- changedLines: 0,
1121
- toolCalls: 0,
1122
- toolFailures: 0,
1123
- turns: 0,
1124
- writeCalls: 0,
1125
- editCalls: 0,
1126
- bashCalls: 0,
1127
- };
1081
+ this.hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
1128
1082
  this.hookText = "";
1129
1083
  this.hookConsecutiveFailures = 0;
1130
1084
  this.hookRepeatedNoProgressCalls = 0;
@@ -1133,13 +1087,6 @@ export class AgentSession {
1133
1087
  this.hookCyclicPattern = null;
1134
1088
  this.hookFileEditCounts.clear();
1135
1089
  this.hookToolCalls.clear();
1136
- this.reviewCoverage.reset();
1137
- this.reviewCoverageInjected = 0;
1138
- this.idealReviewPhase = "idle";
1139
- // No event here: clients reset their own hold on run_start.
1140
- this.idealReviewArmed = false;
1141
- this.verificationArmed = false;
1142
- this.idealDriftProbe = null;
1143
1090
  this.loopBreakInjected = 0;
1144
1091
  this.regroundingInjected = false;
1145
1092
  this.hookRecentCalls = [];
@@ -1150,7 +1097,6 @@ export class AgentSession {
1150
1097
  verdict: null,
1151
1098
  injected: false,
1152
1099
  };
1153
- this.independentReviewStarted = false;
1154
1100
  this.runStartedAt = Date.now();
1155
1101
  this.processGateInjected = 0;
1156
1102
  this.verificationGate.beginRun();
@@ -1171,8 +1117,8 @@ export class AgentSession {
1171
1117
  }
1172
1118
  /**
1173
1119
  * Fold one agent event into the hook stat accumulators. Pure bookkeeping —
1174
- * the same signals the TUI's useAgentLoop collects, so the loop-break and
1175
- * ideal-review decisions match across the CLI and the app.
1120
+ * the same signals the TUI's useAgentLoop collects, so loop-break decisions
1121
+ * match across the CLI and the app.
1176
1122
  */
1177
1123
  async trackHookEvent(event) {
1178
1124
  if (this.opts.completionReview) {
@@ -1215,7 +1161,7 @@ export class AgentSession {
1215
1161
  ...this.hookFileEditCounts.keys(),
1216
1162
  ]);
1217
1163
  if (call.sourceSnapshot === null)
1218
- this.verificationGate.requireFreshVerification(true, event.args.command);
1164
+ this.verificationGate.requireFreshVerification(true);
1219
1165
  }
1220
1166
  else if ((classification.accepted && event.args.persist !== true) ||
1221
1167
  (!classification.accepted && classification.mayMutate)) {
@@ -1225,7 +1171,7 @@ export class AgentSession {
1225
1171
  // or a persistent-shell run records nothing at the end, so
1226
1172
  // flagging it left verified work Unverified forever — and
1227
1173
  // autopilot silently refused every later turn.
1228
- this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate, event.args.command);
1174
+ this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate);
1229
1175
  }
1230
1176
  await this.persistVerificationState();
1231
1177
  }
@@ -1238,12 +1184,6 @@ export class AgentSession {
1238
1184
  this.hookStats.toolCalls += 1;
1239
1185
  if (event.isError)
1240
1186
  this.hookStats.toolFailures += 1;
1241
- if (name === "write")
1242
- this.hookStats.writeCalls += 1;
1243
- if (name === "edit")
1244
- this.hookStats.editCalls += 1;
1245
- if (name === "bash")
1246
- this.hookStats.bashCalls += 1;
1247
1187
  this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
1248
1188
  this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
1249
1189
  this.hookCyclicPattern = this.hookCycleDetector.record(name, args, event.result, event.isError);
@@ -1260,12 +1200,6 @@ export class AgentSession {
1260
1200
  if (this.hookRecentCalls.length > MAX_SEMANTIC_LOOP_CALLS) {
1261
1201
  this.hookRecentCalls.splice(0, this.hookRecentCalls.length - MAX_SEMANTIC_LOOP_CALLS);
1262
1202
  }
1263
- if (name === "edit" && !event.isError) {
1264
- const diff = event.details?.diff ?? event.result;
1265
- const added = (diff.match(/^\+[^+]/gm) ?? []).length;
1266
- const removed = (diff.match(/^-[^-]/gm) ?? []).length;
1267
- this.hookStats.changedLines += added + removed;
1268
- }
1269
1203
  // Only host-observed successful mutations and trustworthy check results
1270
1204
  // affect approval. The model's text is never evidence.
1271
1205
  let verificationChanged = false;
@@ -1273,13 +1207,10 @@ export class AgentSession {
1273
1207
  if (name === "edit" || name === "write") {
1274
1208
  const filePath = String(args.file_path ?? "");
1275
1209
  // Check-owning files (tsconfig.json, pytest.ini, vitest.config.ts …)
1276
- // are tracked even when they are not source code: editing one is how
1277
- // a red suite is turned green without fixing anything.
1210
+ // are tracked even when they are not source code: editing one
1211
+ // invalidates earlier check results.
1278
1212
  if (filePath && (isCodeFilePath(filePath) || isCheckOwnFile(filePath))) {
1279
- const addedText = name === "write"
1280
- ? String(args.content ?? "")
1281
- : extractAddedLines(event.details?.diff ?? event.result);
1282
- this.verificationGate.recordMutation(filePath, addedText);
1213
+ this.verificationGate.recordMutation(filePath);
1283
1214
  verificationChanged = true;
1284
1215
  }
1285
1216
  }
@@ -1307,7 +1238,7 @@ export class AgentSession {
1307
1238
  : {}),
1308
1239
  });
1309
1240
  else if (classification.snapshotEligible)
1310
- this.verificationGate.requireFreshVerification(true, command);
1241
+ this.verificationGate.requireFreshVerification(true);
1311
1242
  }
1312
1243
  else if (args.persist === true) {
1313
1244
  // Persistent-shell checks are not bounded evidence (steering can
@@ -1365,10 +1296,35 @@ export class AgentSession {
1365
1296
  // Tool results for this step are in the array and their side effects
1366
1297
  // already hit the filesystem. Flushing here is what makes a crash lose
1367
1298
  // at most the in-flight step instead of the entire turn.
1299
+ await this.creditBashReads();
1368
1300
  await this.flushPendingMessages();
1369
1301
  break;
1370
1302
  }
1371
1303
  }
1304
+ /**
1305
+ * Count full-file `cat` output from the step that just finished as reads, so
1306
+ * an edit after `cat` does not cost a second read. Uses the step's results as
1307
+ * stored in the transcript (after per-turn trimming): only bytes the model
1308
+ * actually received count.
1309
+ */
1310
+ async creditBashReads() {
1311
+ if (!this.recordBashReads)
1312
+ return;
1313
+ const messages = this.activeLoopMessages ?? this.messages;
1314
+ const toolMessage = messages.at(-1);
1315
+ const assistant = messages.at(-2);
1316
+ if (toolMessage?.role !== "tool" || assistant?.role !== "assistant")
1317
+ return;
1318
+ if (typeof assistant.content === "string")
1319
+ return;
1320
+ const calls = assistant.content.filter((part) => part.type === "tool_call");
1321
+ try {
1322
+ await this.recordBashReads(calls, toolMessage.content);
1323
+ }
1324
+ catch (err) {
1325
+ log("WARN", "agent-session", "Crediting bash reads failed", { error: String(err) });
1326
+ }
1327
+ }
1372
1328
  /**
1373
1329
  * Append every message added since the last flush to the session file.
1374
1330
  *
@@ -1431,7 +1387,7 @@ export class AgentSession {
1431
1387
  const diagnosticText = this.lspManager?.drainDiagnostics(this.getVerificationProblem() !== null, { deferUnverified: true });
1432
1388
  if (diagnosticText)
1433
1389
  this.eventBus.emit("diagnostics", { text: diagnosticText });
1434
- this.refreshVerificationArmed();
1390
+ this.refreshHookArming();
1435
1391
  const notified = this.notifications.drain();
1436
1392
  const notificationMessage = notified.length > 0 || diagnosticText
1437
1393
  ? {
@@ -1494,6 +1450,7 @@ export class AgentSession {
1494
1450
  }
1495
1451
  if (this.opts.selfCorrectionHooks === false)
1496
1452
  return null;
1453
+ // Legacy key: the user-facing switch for loop-break and re-grounding nudges.
1497
1454
  if (!this.settingsManager.get("idealReviewEnabled"))
1498
1455
  return null;
1499
1456
  // Deterministic stuck verdict, computed once and shared: the semantic
@@ -1693,83 +1650,6 @@ export class AgentSession {
1693
1650
  .join("\n")
1694
1651
  : "";
1695
1652
  }
1696
- /** Independent fresh-context review of the finished work (Codex Guardian
1697
- * pattern). Spawns a READ-ONLY child on the ACTIVE model, waits bounded,
1698
- * and returns findings for the acting agent to address — or nothing when
1699
- * the review passes, is unavailable, or fails (in-thread review remains the
1700
- * fallback; the feature degrades, never blocks).
1701
- *
1702
- * Runs inside the pre-stop poll, so the candidate final answer is already
1703
- * held by arming and this wait cannot race a streamed answer. */
1704
- async runIndependentReview(decision) {
1705
- if (!this.subAgentManager)
1706
- return [];
1707
- if (this.independentReviewStarted)
1708
- return [];
1709
- // An allow-listed session (a subagent worker itself) must not spawn
1710
- // harness-owned grandchildren the tool policy never granted.
1711
- if (this.opts.allowedTools && !this.opts.allowedTools.includes("spawn_agent"))
1712
- return [];
1713
- if (decision.score < INDEPENDENT_REVIEW_SCORE_THRESHOLD)
1714
- return [];
1715
- this.independentReviewStarted = true;
1716
- const taskName = `ideal-reviewer-${Math.random().toString(36).slice(2, 8)}`;
1717
- let agentId;
1718
- try {
1719
- const task = buildReviewerTask({
1720
- originalRequest: this.originalRequest,
1721
- changedFiles: [...this.hookFileEditCounts.keys()],
1722
- stats: this.hookStats,
1723
- triggerReasons: decision.reasons,
1724
- });
1725
- // Active model forced at spawn time — never routed to a fast/review model.
1726
- // The reviewer's own time limit ends it with a verdict on what it read;
1727
- // the wait below is only a backstop against a hung child.
1728
- const snapshot = await this.subAgentManager.spawn(taskName, task, undefined, {
1729
- model: this.model,
1730
- tools: REVIEWER_TOOLS,
1731
- turnTimeoutMs: REVIEWER_TURN_TIMEOUT_MS,
1732
- });
1733
- agentId = snapshot.agent_id;
1734
- const waited = await this.subAgentManager.wait([agentId], "all", REVIEWER_WAIT_MS);
1735
- const agent = waited.agents[0];
1736
- if (!agent || !isTerminalSubAgentState(agent.state)) {
1737
- // Timeout: collect the straggler so the completion gate cannot fire on
1738
- // it later, then fall back to the in-thread review.
1739
- await this.subAgentManager.interrupt(agentId, true).catch(() => { });
1740
- log("WARN", "ideal", "Independent reviewer timed out; falling back to in-thread review", {
1741
- agentId,
1742
- });
1743
- return [];
1744
- }
1745
- const findings = parseReviewerFindings(agent.output ?? "");
1746
- if (!findings) {
1747
- log("WARN", "ideal", "Independent reviewer output unparseable; falling back", {
1748
- agentId,
1749
- state: agent.state,
1750
- ...(agent.error ? { error: agent.error } : {}),
1751
- });
1752
- return [];
1753
- }
1754
- if (findings.clean) {
1755
- log("INFO", "ideal", "Independent reviewer verdict: clean", { agentId });
1756
- return [];
1757
- }
1758
- log("INFO", "ideal", "Independent reviewer flagged findings", {
1759
- agentId,
1760
- count: String(findings.findings.length),
1761
- });
1762
- return [buildIndependentReviewMessage(findings.findings)];
1763
- }
1764
- catch (error) {
1765
- if (agentId)
1766
- await this.subAgentManager.interrupt(agentId, true).catch(() => { });
1767
- log("WARN", "ideal", "Independent reviewer failed; falling back to in-thread review", {
1768
- error: error instanceof Error ? error.message : String(error),
1769
- });
1770
- return [];
1771
- }
1772
- }
1773
1653
  /**
1774
1654
  * Turn-budget extension gate. The loop consults this instead of stopping
1775
1655
  * mid-task when it exhausts `maxTurns`. Grant ONLY on evidence of progress —
@@ -1803,98 +1683,28 @@ export class AgentSession {
1803
1683
  });
1804
1684
  return granted;
1805
1685
  }
1806
- /**
1807
- * Would the stop AFTER the current turn inject the Ideal review? Same inputs
1808
- * as the pre-stop gate below, evaluated early so clients know a candidate
1809
- * final answer is a review draft BEFORE it streams.
1810
- *
1811
- * The turn count is looked ahead by one on purpose. `hookStats.turns` only
1812
- * advances at `turn_end`, so while the model is writing the draft the counter
1813
- * still reads the PREVIOUS turn; the real gate sees one more. Without the
1814
- * lookahead a run sitting on score 3 crosses to 4 on the draft's own
1815
- * `turn_end` — after the text already streamed — which is precisely the
1816
- * appear-then-vanish flash. Over-arming by one turn point costs only live
1817
- * token streaming on a final answer that then shows whole; under-arming costs
1818
- * the flash, so this errs toward arming.
1819
- */
1820
- wouldInjectIdealReview() {
1686
+ /** Would a stop right now inject a pre-final follow-up? Only queued LSP
1687
+ * diagnostics (real errors injected below) or a mode-owned completion
1688
+ * review can, so clients hold the candidate answer only then. */
1689
+ wouldInjectBeforeFinal() {
1821
1690
  if (this.opts.completionReview?.armed)
1822
1691
  return true;
1823
- if (this.opts.selfCorrectionHooks === false || this.idealReviewSuppressed)
1824
- return false;
1825
- // Mid-review a stop still injects: the coverage follow-up while files are
1826
- // unread, or its escalation once the budget is spent. Both make the model
1827
- // answer again, so the candidate answer is a draft exactly as it is before
1828
- // the review starts — without arming here it paints and the reviewed answer
1829
- // lands under it as a duplicate.
1830
- if (this.idealReviewPhase === "reviewing") {
1831
- return this.reviewCoverage.evidence().missing.length > 0;
1832
- }
1833
- if (this.idealReviewPhase !== "idle")
1834
- return false;
1835
- if (!this.settingsManager.get("idealReviewEnabled"))
1836
- return false;
1837
- if (evaluateIdealReview({ ...this.hookStats, turns: this.hookStats.turns + 1 }).shouldReview) {
1838
- return true;
1839
- }
1840
- const files = this.hookFileEditCounts.size;
1841
- if (files === 0)
1842
- return false;
1843
- if (this.idealDriftProbe?.files !== files) {
1844
- this.idealDriftProbe = {
1845
- files,
1846
- drifted: detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).length > 0,
1847
- };
1848
- }
1849
- return this.idealDriftProbe.drifted;
1850
- }
1851
- /** Would a stop right now inject the verification gate? Same conditions as
1852
- * the pre-stop branch below, so arming and injection cannot disagree. */
1853
- wouldInjectVerification() {
1854
- if (this.lspManager?.hasQueuedDiagnostics())
1855
- return true;
1856
- if (this.opts.selfCorrectionHooks === false)
1857
- return false;
1858
- if (!this.settingsManager.get("verificationGateEnabled"))
1859
- return false;
1860
- if (this.opts.allowedTools && !this.opts.allowedTools.includes("bash"))
1861
- return false;
1862
- return this.verificationGate.willInject();
1692
+ return this.lspManager?.hasQueuedDiagnostics() ?? false;
1863
1693
  }
1864
1694
  /** Broadcast pre-final hook arming on change. Both edges matter: armed=false
1865
- * after the hook fires is what lets a client stream the REVIEWED final
1866
- * answer live again.
1867
- *
1868
- * Callable before `initialize()`: the sidecar sets Ken's review suppression
1869
- * on a freshly constructed session, and every arming predicate below reads
1870
- * settings that `initialize()` has not loaded yet. Nothing can be armed
1871
- * before the session can run a turn, and the first `tool_result`/`turn_end`
1872
- * recomputes both edges — so skipping is the correct answer, not a patch. */
1695
+ * after the hook fires is what lets a client stream the final answer live
1696
+ * again. Callable before `initialize()`, when no manager exists yet. */
1873
1697
  refreshHookArming() {
1874
- if (!this.settingsManager)
1875
- return;
1876
- this.refreshIdealReviewArmed();
1877
- this.refreshVerificationArmed();
1878
- }
1879
- refreshVerificationArmed() {
1880
- if (!this.settingsManager)
1881
- return;
1882
- const armed = this.wouldInjectVerification();
1883
- if (armed === this.verificationArmed)
1698
+ const armed = this.wouldInjectBeforeFinal();
1699
+ if (armed === this.preFinalArmed)
1884
1700
  return;
1885
- this.verificationArmed = armed;
1701
+ this.preFinalArmed = armed;
1886
1702
  this.eventBus.emit("hook_armed", { kind: "verification", armed });
1887
1703
  }
1888
- refreshIdealReviewArmed() {
1889
- const armed = this.wouldInjectIdealReview();
1890
- if (armed === this.idealReviewArmed)
1891
- return;
1892
- this.idealReviewArmed = armed;
1893
- this.eventBus.emit("hook_armed", { kind: "ideal", armed });
1894
- }
1895
1704
  /**
1896
- * Pre-stop Ideal review phase machine. Once review starts, completion is
1897
- * blocked until harness-owned post-injection reads cover every changed file.
1705
+ * Pre-stop follow-ups: LSP errors, unread child agents and background
1706
+ * processes, and a mode-owned completion review. Verification evidence is
1707
+ * tracked passively for run status; it never forces another turn.
1898
1708
  */
1899
1709
  async getHookFollowUpMessages() {
1900
1710
  // Exit notifications and task_output refer to the same host process record.
@@ -1907,14 +1717,16 @@ export class AgentSession {
1907
1717
  if (backgroundChanged)
1908
1718
  await this.persistVerificationState();
1909
1719
  // Edits return immediately; only the completion boundary waits for remaining
1910
- // checks. Queued timeouts stay explicitly unverified, never a false all-clear.
1720
+ // checks. Only real errors cost another turn: a timed-out or unavailable
1721
+ // server proves nothing either way, and run status already reports
1722
+ // unverified changes without making the model answer twice.
1911
1723
  await this.lspManager?.flushDiagnostics(this.opts.signal);
1912
1724
  if (this.opts.signal?.aborted)
1913
1725
  return null;
1914
- const diagnosticText = this.lspManager?.drainDiagnostics(this.getVerificationProblem() !== null);
1726
+ const diagnosticText = this.lspManager?.drainDiagnostics(false);
1915
1727
  if (diagnosticText)
1916
1728
  this.eventBus.emit("diagnostics", { text: diagnosticText });
1917
- this.refreshVerificationArmed();
1729
+ this.refreshHookArming();
1918
1730
  const diagnosticMessages = diagnosticText
1919
1731
  ? [
1920
1732
  {
@@ -1938,48 +1750,6 @@ export class AgentSession {
1938
1750
  });
1939
1751
  return [...diagnosticMessages, ...processFollowUp];
1940
1752
  }
1941
- // Verification gate: code was edited but nothing verified since the last
1942
- // edit. Above the Ideal review so checks RUN before the read-based review
1943
- // starts; off for allow-listed sessions that cannot run commands at all.
1944
- if (this.opts.selfCorrectionHooks !== false &&
1945
- this.settingsManager.get("verificationGateEnabled") &&
1946
- (!this.opts.allowedTools || this.opts.allowedTools.includes("bash"))) {
1947
- const verificationReason = this.verificationGate.pendingReason();
1948
- const pendingFiles = this.verificationGate.pendingFiles();
1949
- const verificationFollowUp = this.verificationGate.followUp();
1950
- if (verificationFollowUp) {
1951
- // Name the tests that actually reach the unverified files, with the
1952
- // command that runs exactly those, so the check is targeted rather
1953
- // than guessed. Best-effort: no index or no runner leaves it unchanged.
1954
- if (this.testImpact &&
1955
- (verificationReason === "initial" || verificationReason === "recheck")) {
1956
- const impactLine = await this.testImpact
1957
- .impactFor(pendingFiles)
1958
- .then(formatImpactForVerification)
1959
- .catch(() => "");
1960
- const first = verificationFollowUp[0];
1961
- if (impactLine && first?.role === "user" && typeof first.content === "string") {
1962
- verificationFollowUp[0] = { ...first, content: first.content + impactLine };
1963
- }
1964
- }
1965
- log("INFO", "verification-gate", "Injecting verification follow-up", {});
1966
- // Announce, THEN disarm: clients release held text on disarm, so the
1967
- // reverse order paints the draft and immediately deletes it — the exact
1968
- // flash arming exists to prevent.
1969
- this.eventBus.emit("hook", {
1970
- kind: "verification",
1971
- ...(verificationReason === "tamper"
1972
- ? { verificationReason: "check_review" }
1973
- : verificationReason === "recheck"
1974
- ? { verificationReason }
1975
- : {}),
1976
- });
1977
- this.refreshHookArming();
1978
- return [...diagnosticMessages, ...verificationFollowUp];
1979
- }
1980
- }
1981
- // Address real errors before review; unavailable checks share the existing
1982
- // verification demand above instead of manufacturing a separate hook.
1983
1753
  if (diagnosticMessages.length > 0)
1984
1754
  return diagnosticMessages;
1985
1755
  if (this.opts.completionReview) {
@@ -1998,122 +1768,7 @@ export class AgentSession {
1998
1768
  }
1999
1769
  this.refreshHookArming();
2000
1770
  }
2001
- if (this.opts.selfCorrectionHooks === false || this.idealReviewSuppressed)
2002
- return null;
2003
- if (this.idealReviewPhase === "reviewing") {
2004
- const coverage = this.reviewCoverage.evidence();
2005
- const lspEvidence = this.reviewLspEvidence(coverage.expected);
2006
- log("INFO", "ideal", "Ideal review coverage check", {
2007
- covered: coverage.covered,
2008
- missing: coverage.missing,
2009
- lspLowConfidence: lspEvidence.lowConfidence,
2010
- lspMissing: lspEvidence.missing,
2011
- });
2012
- if (coverage.missing.length > 0) {
2013
- // Announce like any other pre-final injection: this follow-up makes the
2014
- // model answer again, so the answer it interrupts is a draft and the
2015
- // hook event is what tells clients to discard it. Injecting silently is
2016
- // what let the pre-coverage answer paint above the reviewed one.
2017
- this.eventBus.emit("hook", {
2018
- kind: "ideal",
2019
- coverageExpected: coverage.expected,
2020
- coverageMissing: coverage.missing,
2021
- });
2022
- if (this.reviewCoverageInjected < MAX_REVIEW_COVERAGE_INJECTIONS) {
2023
- this.reviewCoverageInjected += 1;
2024
- // Stays armed (coverage is still outstanding) — this call is here so a
2025
- // client that missed the earlier edge is armed before the next draft.
2026
- this.refreshIdealReviewArmed();
2027
- return [
2028
- this.withReviewLspEvidence(buildReviewCoverageMessage(coverage.missing), lspEvidence),
2029
- ];
2030
- }
2031
- // Budget spent: close the gate so the run cannot spin on a file that
2032
- // never becomes readable, and require the gap be reported to the user.
2033
- this.idealReviewPhase = "complete";
2034
- // The gate is shut, so this is the real disarm: the answer to the
2035
- // escalation is final and streams live.
2036
- this.refreshIdealReviewArmed();
2037
- log("INFO", "ideal", "Ideal review coverage escalated after retry budget", {
2038
- injected: String(this.reviewCoverageInjected),
2039
- missing: coverage.missing,
2040
- });
2041
- return [buildReviewCoverageEscalationMessage(coverage.missing)];
2042
- }
2043
- this.idealReviewPhase = "complete";
2044
- return null;
2045
- }
2046
- if (this.idealReviewPhase === "complete")
2047
- return null;
2048
- if (!this.settingsManager.get("idealReviewEnabled"))
2049
- return null;
2050
- const decision = evaluateIdealReview(this.hookStats);
2051
- // Test drift fires the review even on a small change the score would skip:
2052
- // a green-but-stale test is exactly what the volume gate sleeps through.
2053
- const driftedFiles = detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).slice(0, 5);
2054
- if (!decision.shouldReview && driftedFiles.length === 0)
2055
- return null;
2056
- // Independent reviewer first (async, bounded): its findings ride in the
2057
- // SAME follow-up batch as the in-thread review + coverage requirements, so
2058
- // addressing everything still costs one extra turn.
2059
- this.reviewCoverage.start(this.hookFileEditCounts.keys());
2060
- this.idealReviewPhase = "reviewing";
2061
- const coverage = this.reviewCoverage.evidence();
2062
- const lspEvidence = this.reviewLspEvidence(coverage.expected);
2063
- this.eventBus.emit("hook", {
2064
- kind: "ideal",
2065
- coverageExpected: coverage.expected,
2066
- coverageMissing: coverage.missing,
2067
- });
2068
- // Recompute strictly AFTER the hook event: clients release held text on
2069
- // disarm, so the reverse order would paint the draft and then delete it —
2070
- // the exact flash arming exists to prevent. Arming normally PERSISTS here,
2071
- // because review starts with every changed file uncovered and a stop while
2072
- // coverage is outstanding injects again. Disarm lands later, on the read
2073
- // that closes the last gap (or when the retry budget escalates).
2074
- this.refreshIdealReviewArmed();
2075
- // Announce the phase before the reviewer starts, not after its bounded wait.
2076
- const independentMessages = await this.runIndependentReview(decision);
2077
- log("INFO", "ideal", "Injecting ideal review before final response", {
2078
- coverageExpected: coverage.expected,
2079
- coverageMissing: coverage.missing,
2080
- lspLowConfidence: lspEvidence.lowConfidence,
2081
- lspMissing: lspEvidence.missing,
2082
- });
2083
- return [
2084
- ...independentMessages,
2085
- this.withReviewLspEvidence(withReviewCoverageRequirements(buildIdealReviewMessage(decision.reasons, driftedFiles), coverage.missing), lspEvidence),
2086
- ];
2087
- }
2088
- reviewLspEvidence(files) {
2089
- const lowConfidence = [];
2090
- const missing = [];
2091
- for (const filePath of files) {
2092
- const outcome = this.lspManager?.getLatestOutcome(filePath);
2093
- if (outcome?.kind === "low_confidence")
2094
- lowConfidence.push(filePath);
2095
- else if (outcome?.kind !== "clean" && outcome?.kind !== "diagnostics")
2096
- missing.push(filePath);
2097
- }
2098
- return { lowConfidence, missing };
2099
- }
2100
- withReviewLspEvidence(message, evidence) {
2101
- if (evidence.lowConfidence.length === 0 && evidence.missing.length === 0)
2102
- return message;
2103
- const notes = [
2104
- ...(evidence.lowConfidence.length > 0
2105
- ? [`Diagnostics are low confidence while indexing: ${evidence.lowConfidence.join(", ")}.`]
2106
- : []),
2107
- ...(evidence.missing.length > 0
2108
- ? [`Diagnostics evidence is unavailable or missing: ${evidence.missing.join(", ")}.`]
2109
- : []),
2110
- "Do not describe those files as compiler-clean without other evidence.",
2111
- ];
2112
- return {
2113
- role: "user",
2114
- provenance: message.provenance,
2115
- content: `${String(message.content)}\n\n${notes.join(" ")}`,
2116
- };
1771
+ return null;
2117
1772
  }
2118
1773
  /** Wraps the real run: cancels an in-flight cache prewarm and tracks run
2119
1774
  * activity / last real request time for {@link prewarm}. */
@@ -3288,21 +2943,6 @@ export class AgentSession {
3288
2943
  getPlanMode() {
3289
2944
  return this.planModeRef.current;
3290
2945
  }
3291
- /**
3292
- * Suppress only the pre-final Ideal self-review for this live session.
3293
- * Autopilot uses this while Ken independently owns verification; loop-break
3294
- * and post-compaction re-grounding remain active.
3295
- */
3296
- setIdealReviewSuppressed(suppressed) {
3297
- this.idealReviewSuppressed = suppressed;
3298
- if (suppressed) {
3299
- this.idealReviewPhase = "idle";
3300
- this.reviewCoverage.reset();
3301
- }
3302
- // Suppression flips mid-run (autopilot takes over verification), so a client
3303
- // holding a draft under a stale arming must be released.
3304
- this.refreshHookArming();
3305
- }
3306
2946
  /** Queue a user message (optionally with attachments) to be injected mid-run
3307
2947
  * as steering. Returns the new queue length. No-op semantics are the caller's
3308
2948
  * concern. */
@@ -3788,7 +3428,7 @@ export class AgentSession {
3788
3428
  ? await captureVerificationSnapshot(this.opts.cwd, [...this.hookFileEditCounts.keys()])
3789
3429
  : null;
3790
3430
  if (after === null || after !== check.sourceSnapshot) {
3791
- this.verificationGate.requireFreshVerification(true, check.command);
3431
+ this.verificationGate.requireFreshVerification(true);
3792
3432
  this.verificationGate.recordRejectedCheck(check.command, after === null
3793
3433
  ? "Workspace inputs could not be compared; run a read-only check after the command"
3794
3434
  : "Command changed workspace inputs; run checks against the changed source");