peaks-loop 4.0.46 → 4.0.48

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 (207) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/agents/karpathy-reviewer.md +11 -10
  5. package/dist/cli/cli-helpers.d.ts +34 -0
  6. package/dist/cli/cli-helpers.js +57 -0
  7. package/dist/cli/commands/code-job-shape-commands.js +8 -0
  8. package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
  9. package/dist/cli/commands/code-runtime-commands.js +139 -16
  10. package/dist/cli/commands/compact-command.js +241 -1
  11. package/dist/cli/commands/config-commands.js +15 -9
  12. package/dist/cli/commands/container-commands.js +3 -3
  13. package/dist/cli/commands/core/skill-command.js +45 -10
  14. package/dist/cli/commands/cron-commands.js +2 -1
  15. package/dist/cli/commands/dashboard-long-run.js +6 -0
  16. package/dist/cli/commands/dispatch-commands.js +11 -1
  17. package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
  18. package/dist/cli/commands/e2e-verify.js +3 -3
  19. package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
  20. package/dist/cli/commands/hooks-commands.js +14 -5
  21. package/dist/cli/commands/job-commands.js +8 -0
  22. package/dist/cli/commands/loop-commands.js +1 -0
  23. package/dist/cli/commands/loop-eval-commands.js +15 -0
  24. package/dist/cli/commands/perf-audit-commands.js +2 -0
  25. package/dist/cli/commands/playwright-commands.js +14 -1
  26. package/dist/cli/commands/prd-commands.js +1 -1
  27. package/dist/cli/commands/qa-commands.js +22 -0
  28. package/dist/cli/commands/reinject-command.d.ts +72 -0
  29. package/dist/cli/commands/reinject-command.js +174 -0
  30. package/dist/cli/commands/request-commands.js +14 -3
  31. package/dist/cli/commands/scan-commands.js +1 -1
  32. package/dist/cli/commands/security-audit-commands.js +2 -0
  33. package/dist/cli/commands/shadcn-commands.js +1 -0
  34. package/dist/cli/commands/slice-integrate-commands.js +5 -0
  35. package/dist/cli/commands/statusline-commands.js +44 -4
  36. package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
  37. package/dist/cli/commands/sub-agent/detached.js +47 -22
  38. package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
  39. package/dist/cli/commands/test-commands.js +2 -1
  40. package/dist/cli/commands/verdict-aggregate-command.js +95 -13
  41. package/dist/cli/commands/vm-commands.js +7 -7
  42. package/dist/cli/commands/workflow-commands.js +1 -1
  43. package/dist/cli/commands/workspace/init-command.js +24 -2
  44. package/dist/cli/commands/worktree-lease-commands.js +4 -4
  45. package/dist/cli/index.js +10 -3
  46. package/dist/cli/program.js +5 -0
  47. package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
  48. package/dist/services/adapter/adapter-registry.js +1 -1
  49. package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
  50. package/dist/services/artifacts/artifact-prerequisites.js +130 -65
  51. package/dist/services/artifacts/artifact-service.js +1 -1
  52. package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
  53. package/dist/services/artifacts/request-artifact-service.js +18 -8
  54. package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
  55. package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
  56. package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
  57. package/dist/services/audit-independent/perf-audit-service.js +27 -5
  58. package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
  59. package/dist/services/audit-independent/security-audit-service.js +28 -6
  60. package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
  61. package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
  62. package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
  63. package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
  64. package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
  65. package/dist/services/code/auto-compact-lifecycle.js +180 -4
  66. package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
  67. package/dist/services/code/auto-compact-orchestrator.js +166 -34
  68. package/dist/services/code/compact-event-settle.d.ts +122 -0
  69. package/dist/services/code/compact-event-settle.js +219 -0
  70. package/dist/services/code/orchestrator-can-do.d.ts +4 -2
  71. package/dist/services/code/orchestrator-can-do.js +37 -5
  72. package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
  73. package/dist/services/codegraph/codegraph-process-runner.js +3 -2
  74. package/dist/services/compact/request-transition-hook.js +5 -2
  75. package/dist/services/compact-history/compact-history-service.d.ts +75 -0
  76. package/dist/services/compact-history/compact-history-service.js +49 -0
  77. package/dist/services/config/config-restore.d.ts +12 -1
  78. package/dist/services/config/config-restore.js +35 -4
  79. package/dist/services/config/config-rollback.js +6 -1
  80. package/dist/services/config/config-safety.d.ts +52 -0
  81. package/dist/services/config/config-safety.js +75 -1
  82. package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
  83. package/dist/services/context/auto-compact-dispatcher.js +113 -40
  84. package/dist/services/context/auto-compact-reader.d.ts +68 -28
  85. package/dist/services/context/auto-compact-reader.js +155 -1
  86. package/dist/services/context/auto-compact-types.d.ts +89 -12
  87. package/dist/services/context/auto-compact-types.js +16 -32
  88. package/dist/services/context/harness-context-witness.d.ts +310 -0
  89. package/dist/services/context/harness-context-witness.js +606 -0
  90. package/dist/services/context/harness-window-config.d.ts +412 -0
  91. package/dist/services/context/harness-window-config.js +607 -0
  92. package/dist/services/context/main-session-monitor.d.ts +27 -0
  93. package/dist/services/context/main-session-monitor.js +32 -1
  94. package/dist/services/context/post-compact-reinjection.d.ts +221 -0
  95. package/dist/services/context/post-compact-reinjection.js +491 -0
  96. package/dist/services/dispatch/merge-back-runner.js +5 -5
  97. package/dist/services/dispatch/service-shutdown.js +3 -3
  98. package/dist/services/doc/doc-generator.js +2 -1
  99. package/dist/services/env/shell-probe.js +1 -1
  100. package/dist/services/evidence/evidence-generator.js +86 -49
  101. package/dist/services/final-review/final-review-service.d.ts +9 -0
  102. package/dist/services/final-review/final-review-service.js +36 -12
  103. package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
  104. package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
  105. package/dist/services/hooks/auto-compact-hook-install.js +8 -0
  106. package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
  107. package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
  108. package/dist/services/ide/ide-registry.d.ts +31 -0
  109. package/dist/services/ide/ide-registry.js +35 -0
  110. package/dist/services/ide/ide-types.d.ts +59 -0
  111. package/dist/services/job/job-state-store.js +7 -0
  112. package/dist/services/lint/detect-eslint.js +2 -2
  113. package/dist/services/lint/eslint-runner.js +3 -1
  114. package/dist/services/loop/evaluator-dispatcher.js +2 -1
  115. package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
  116. package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
  117. package/dist/services/memory/project-memory-service/store/paths.js +15 -6
  118. package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
  119. package/dist/services/prd/best-practice-auto-trigger.js +1 -0
  120. package/dist/services/prd/handoff-auto-regen.js +31 -27
  121. package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
  122. package/dist/services/prd/handoff-frontmatter.js +75 -0
  123. package/dist/services/prd/handoff-service.d.ts +41 -2
  124. package/dist/services/prd/handoff-service.js +81 -8
  125. package/dist/services/prd/handoff-types.d.ts +3 -2
  126. package/dist/services/prd/handoff-types.js +3 -2
  127. package/dist/services/qa/qa-business-review-state.js +9 -0
  128. package/dist/services/release/version-precheck-service.d.ts +2 -1
  129. package/dist/services/release/version-precheck-service.js +82 -12
  130. package/dist/services/runtime/vendor-adapter.d.ts +29 -4
  131. package/dist/services/runtime/vendors/claude-code.js +1 -1
  132. package/dist/services/runtime/vendors/codex.js +1 -1
  133. package/dist/services/runtime/vendors/copilot.js +1 -1
  134. package/dist/services/sc/sc-service.js +1 -1
  135. package/dist/services/scan/diff-scope-service.js +2 -2
  136. package/dist/services/scan/file-size-scan.js +2 -2
  137. package/dist/services/scan/karpathy-service.js +2 -2
  138. package/dist/services/scan/orphan-service.js +2 -1
  139. package/dist/services/scan/type-sanity-service.js +2 -2
  140. package/dist/services/session/session-checkpoint-service.js +8 -0
  141. package/dist/services/skill/resume-detector.js +29 -11
  142. package/dist/services/skillhub/tar-runtime.js +1 -0
  143. package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
  144. package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
  145. package/dist/services/skills/hooks-settings-service.d.ts +12 -0
  146. package/dist/services/skills/hooks-settings-service.js +91 -14
  147. package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
  148. package/dist/services/skills/session-start-hook-constants.js +86 -0
  149. package/dist/services/skills/skill-presence-service.js +9 -0
  150. package/dist/services/skills/skill-statusline-service.d.ts +14 -0
  151. package/dist/services/slice/slice-check-service.js +31 -12
  152. package/dist/services/slice/slice-decompose-runners.js +2 -1
  153. package/dist/services/slice/slice-review-state.js +8 -0
  154. package/dist/services/upgrade/upgrade-service.js +1 -0
  155. package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
  156. package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
  157. package/dist/services/workflow/pipeline-verify-service.js +24 -23
  158. package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
  159. package/dist/services/workflow/workflow-skip-service.js +2 -1
  160. package/dist/services/workspace/claude-settings-template.d.ts +56 -8
  161. package/dist/services/workspace/claude-settings-template.js +98 -20
  162. package/dist/services/workspace/migrate-service.js +1 -1
  163. package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
  164. package/dist/services/workspace/workspace-service.js +8 -0
  165. package/dist/services/worktree/host-worktree-reconciler.js +1 -0
  166. package/dist/services/worktree/long-path-cleanup.js +3 -2
  167. package/dist/shared/process.js +1 -1
  168. package/package.json +6 -6
  169. package/scripts/install-skills.mjs +1 -0
  170. package/scripts/watch.mjs +3 -1
  171. package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
  172. package/skills/bee/peaks-prd/SKILL.md +8 -6
  173. package/skills/bee/peaks-qa/SKILL.md +7 -7
  174. package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
  175. package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
  176. package/skills/bee/peaks-rd/SKILL.md +10 -8
  177. package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
  178. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
  179. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
  180. package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
  181. package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
  182. package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
  183. package/skills/bee/peaks-reviewer/SKILL.md +1 -1
  184. package/skills/bee/peaks-sc/SKILL.md +1 -1
  185. package/skills/bee/peaks-security-audit/SKILL.md +1 -1
  186. package/skills/bee/peaks-txt/SKILL.md +1 -1
  187. package/skills/bee/peaks-ui/SKILL.md +1 -1
  188. package/skills/peaks-audit/SKILL.md +1 -1
  189. package/skills/peaks-code/SKILL.md +3 -3
  190. package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
  191. package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
  192. package/skills/peaks-code/references/resume-detection.md +13 -7
  193. package/skills/peaks-code/references/runbook.md +3 -2
  194. package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
  195. package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
  196. package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
  197. package/skills/peaks-content/SKILL.md +1 -1
  198. package/skills/peaks-doctor/SKILL.md +1 -1
  199. package/skills/peaks-final-review/SKILL.md +1 -1
  200. package/skills/peaks-ide/SKILL.md +1 -1
  201. package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
  202. package/skills/peaks-resume/SKILL.md +1 -1
  203. package/skills/peaks-slice-decompose/SKILL.md +1 -1
  204. package/skills/peaks-solo/SKILL.md +1 -1
  205. package/skills/peaks-sop/SKILL.md +1 -1
  206. package/skills/peaks-status/SKILL.md +1 -1
  207. package/skills/peaks-test/SKILL.md +1 -1
@@ -8,7 +8,7 @@
8
8
  * The service owns:
9
9
  * - 5-state detection of the security-audit runtime
10
10
  * (handoff-missing / template-missing / dispatch-failed / template-malformed / ready)
11
- * - Loading + sha256 verification of the prd/handoff.md
11
+ * - Loading + sha256 verification of the slice's prd/handoff-<rid>.md
12
12
  * - Loading the project-level security-template.md
13
13
  * - Producing the audit envelope (verdict + violations) to write
14
14
  * to `.peaks/_runtime/<sid>/audit/security-<rid>.md`
@@ -36,7 +36,8 @@
36
36
  * 5-state detection result. Mirrors `detectEcc` in `services/code-review/ecc-bridge.ts`.
37
37
  *
38
38
  * - `ready` — handoff + template + project all present
39
- * - `handoff-missing` — `.peaks/_runtime/<sid>/prd/handoff.md` absent
39
+ * - `handoff-missing` — this slice's `.peaks/_runtime/<sid>/prd/handoff-<rid>.md`
40
+ * (or the pre-rid-scoping `prd/handoff.md`) absent
40
41
  * - `template-missing` — `.peaks/project-scan/security-template.md` absent
41
42
  * - `dispatch-failed` — parent LLM threw before returning the audit envelope
42
43
  * - `envelope-malformed` — parent LLM returned a value that fails `isSecurityAuditEnvelope`
@@ -107,6 +108,15 @@ export declare function readSecurityTemplate(projectRoot: string): string | null
107
108
  export declare function detectSecurityAudit(input: {
108
109
  readonly projectRoot: string;
109
110
  readonly sessionId: string;
111
+ /**
112
+ * The slice whose capsule this run audits. Slice
113
+ * `2026-09-14-prd-capsule-rid-scoping` put the rid in the capsule's
114
+ * filename, so a caller that knows it must pass it or the probe resolves
115
+ * only the pre-rid-scoping bare name. Both callers pass it:
116
+ * `runSecurityAudit` always did, and `peaks security-audit detect` forwards
117
+ * its long-standing `--rid` flag as of the post-verification repair round.
118
+ */
119
+ readonly requestId?: string;
110
120
  readonly dispatchError?: unknown;
111
121
  readonly envelope?: unknown;
112
122
  }): SecurityAuditDetectResult;
@@ -8,7 +8,7 @@
8
8
  * The service owns:
9
9
  * - 5-state detection of the security-audit runtime
10
10
  * (handoff-missing / template-missing / dispatch-failed / template-malformed / ready)
11
- * - Loading + sha256 verification of the prd/handoff.md
11
+ * - Loading + sha256 verification of the slice's prd/handoff-<rid>.md
12
12
  * - Loading the project-level security-template.md
13
13
  * - Producing the audit envelope (verdict + violations) to write
14
14
  * to `.peaks/_runtime/<sid>/audit/security-<rid>.md`
@@ -35,6 +35,8 @@
35
35
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
36
36
  import { join, resolve, isAbsolute } from 'node:path';
37
37
  import { createHash } from 'node:crypto';
38
+ import { REQUEST_ID_PATTERN } from '../artifacts/request-artifact-service.js';
39
+ import { resolveHandoffPath } from '../prd/handoff-service.js';
38
40
  /**
39
41
  * Validate a raw value as a SecurityAuditEnvelope. Mirrors the
40
42
  * `isEccEnvelope` strict-shape pattern.
@@ -145,8 +147,12 @@ export function readSecurityTemplate(projectRoot) {
145
147
  export function detectSecurityAudit(input) {
146
148
  const warnings = [];
147
149
  const nextActions = [];
148
- const handoffPath = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd', 'handoff.md');
149
- const handoffPresent = existsSync(handoffPath);
150
+ const handoffPath = resolveHandoffPath({
151
+ projectRoot: input.projectRoot,
152
+ sessionId: input.sessionId,
153
+ ...(input.requestId !== undefined ? { requestId: input.requestId } : {})
154
+ });
155
+ const handoffPresent = handoffPath !== null;
150
156
  const templatePath = join(input.projectRoot, '.peaks', 'project-scan', 'security-template.md');
151
157
  const templatePresent = existsSync(templatePath);
152
158
  if (!handoffPresent) {
@@ -154,7 +160,12 @@ export function detectSecurityAudit(input) {
154
160
  state: 'handoff-missing',
155
161
  handoffPresent: false,
156
162
  templatePresent,
157
- warnings: [`peaks-prd handoff not found at ${handoffPath}`],
163
+ warnings: [
164
+ `peaks-prd handoff not found under ${join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd')}`,
165
+ ...(input.requestId === undefined
166
+ ? ['No --rid was supplied, so only the pre-rid-scoping `prd/handoff.md` could be probed. Pass --rid to resolve this slice\'s `prd/handoff-<rid>.md`.']
167
+ : [])
168
+ ],
158
169
  nextActions: [
159
170
  'Run peaks-prd handoff init to produce a sha256-locked handoff before running peaks security-audit.',
160
171
  'Until the handoff exists, peaks-security-audit cannot start (gate fail).'
@@ -268,6 +279,12 @@ export function renderSecurityAuditArtifact(env, opts) {
268
279
  * Returns the absolute path on success.
269
280
  */
270
281
  export function writeSecurityAuditArtifact(projectRoot, sessionId, rid, body) {
282
+ // The rid is a filename below, and the write is tmp+rename, so an
283
+ // unvalidated rid can OVERWRITE an arbitrary `.md` rather than merely create
284
+ // one. Guarded here (not only at the CLI boundary) so no caller can skip it.
285
+ if (!REQUEST_ID_PATTERN.test(rid)) {
286
+ throw new Error(`Invalid request id: ${rid} (expected letters, digits, dots, underscores, or dashes)`);
287
+ }
271
288
  const targetDir = join(projectRoot, '.peaks', '_runtime', sessionId, 'audit');
272
289
  mkdirSync(targetDir, { recursive: true });
273
290
  const targetPath = join(targetDir, `security-${rid}.md`);
@@ -290,6 +307,7 @@ export function runSecurityAudit(input) {
290
307
  const detect = detectSecurityAudit({
291
308
  projectRoot: input.projectRoot,
292
309
  sessionId: input.sessionId,
310
+ requestId: input.rid,
293
311
  ...(input.dispatchError !== undefined ? { dispatchError: input.dispatchError } : {}),
294
312
  ...(input.envelope !== undefined ? { envelope: input.envelope } : {})
295
313
  });
@@ -298,8 +316,12 @@ export function runSecurityAudit(input) {
298
316
  }
299
317
  // detect.state === 'ready' implies input.envelope passed isSecurityAuditEnvelope.
300
318
  const env = input.envelope;
301
- const handoffPath = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'prd', 'handoff.md');
302
- const verified = readAndVerifyHandoff(handoffPath, input.projectRoot);
319
+ const handoffPath = resolveHandoffPath({
320
+ projectRoot: input.projectRoot,
321
+ sessionId: input.sessionId,
322
+ requestId: input.rid
323
+ });
324
+ const verified = handoffPath === null ? null : readAndVerifyHandoff(handoffPath, input.projectRoot);
303
325
  const handoffHash = verified?.frontmatter.sha256 ?? 'unknown';
304
326
  const rendered = renderSecurityAuditArtifact(env, {
305
327
  rid: input.rid,
@@ -16,7 +16,8 @@ export async function runJ01Contract(ctx) {
16
16
  try {
17
17
  const stdout = execFileSync('node', [bin, command, input], {
18
18
  cwd: ctx.projectRoot,
19
- env: { ...process.env, PEAKS_CALLER_ID: `guard-J01-${ctx.sessionId}` }
19
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J01-${ctx.sessionId}` },
20
+ windowsHide: true
20
21
  }).toString('utf8');
21
22
  const env = JSON.parse(stdout);
22
23
  if (!env.ok) {
@@ -6,10 +6,10 @@ const STATES = ['spec-locked', 'implemented', 'qa-handoff', 'handed-off'];
6
6
  export async function runJ02Contract(ctx, projectRoot = ctx.projectRoot) {
7
7
  const bin = join(ctx.projectRoot, 'bin', 'peaks.js');
8
8
  const tmp = mkdtempSync(join(tmpdir(), 'cbl-J02-'));
9
- const ws = execFileSync('node', [bin, 'workspace', 'init', '--project', tmp, '--json'], { cwd: tmp }).toString('utf8');
9
+ const ws = execFileSync('node', [bin, 'workspace', 'init', '--project', tmp, '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
10
10
  const { data: { sessionId } } = JSON.parse(ws);
11
11
  const rid = '2026-08-03-j02-fixture';
12
- const initOut = execFileSync('node', [bin, 'request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json'], { cwd: tmp }).toString('utf8');
12
+ const initOut = execFileSync('node', [bin, 'request', 'init', '--role', 'rd', '--id', rid, '--project', tmp, '--session-id', sessionId, '--apply', '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
13
13
  const initEnv = JSON.parse(initOut);
14
14
  // `request init` writes the file as `NNN-<id-slug>.md`. The transition CLI accepts
15
15
  // the file's basename (without .md) as the requestId. Derive it from data.path.
@@ -19,7 +19,7 @@ export async function runJ02Contract(ctx, projectRoot = ctx.projectRoot) {
19
19
  for (const s of STATES) {
20
20
  const out = execFileSync('node', [bin, 'request', 'transition', requestId, '--role', 'rd', '--state', s,
21
21
  '--project', tmp, '--session-id', sessionId, '--confirm', '--allow-incomplete',
22
- '--reason', 'J02 contract fixture', '--json'], { cwd: tmp }).toString('utf8');
22
+ '--reason', 'J02 contract fixture', '--json'], { cwd: tmp, windowsHide: true }).toString('utf8');
23
23
  const env = JSON.parse(out);
24
24
  last = env.data.state;
25
25
  }
@@ -10,7 +10,8 @@ export async function runJ04Contract(ctx) {
10
10
  try {
11
11
  const stdout = execFileSync('node', [bin, 'audit', 'goal', '--help'], {
12
12
  cwd: ctx.projectRoot,
13
- env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` }
13
+ env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
14
+ windowsHide: true
14
15
  }).toString('utf8');
15
16
  helpOk = stdout.length > 0 && !stdout.includes('unknown command');
16
17
  }
@@ -23,7 +24,8 @@ export async function runJ04Contract(ctx) {
23
24
  execFileSync('node', [bin, 'audit', 'goal', '--project', tmp, '--json'], {
24
25
  cwd: ctx.projectRoot,
25
26
  env: { ...process.env, PEAKS_CALLER_ID: `guard-J04-${ctx.sessionId}` },
26
- stdio: ['ignore', 'pipe', 'pipe']
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ windowsHide: true
27
29
  });
28
30
  }
29
31
  catch (e) {
@@ -19,7 +19,8 @@ export async function runJ07Contract(ctx) {
19
19
  const out = execFileSync('node', [bin, 'test', '--help'], {
20
20
  cwd: ctx.projectRoot,
21
21
  env: { ...process.env, PEAKS_CALLER_ID: `guard-J07-${ctx.sessionId}` },
22
- stdio: ['ignore', 'pipe', 'pipe']
22
+ stdio: ['ignore', 'pipe', 'pipe'],
23
+ windowsHide: true
23
24
  }).toString('utf8');
24
25
  helpOk = out.length > 0 && !out.includes('unknown command');
25
26
  }
@@ -126,6 +126,12 @@ export declare function summarizeLifecycleError(error: unknown): string;
126
126
  * `afterRatio: 0` there would publish a fabricated number;
127
127
  * - the ratio is still at or above the auto-fire threshold — the
128
128
  * compact has not landed, so the run stays open.
129
+ *
130
+ * Returns the settled record, or `null` when nothing settled. Slice
131
+ * 2026-09-13-auto-compact-trigger-ownership: the caller needs the settled
132
+ * `afterRatio` to append the "observed compaction point" row that makes the
133
+ * intent-vs-observed gap readable after a real session. The return value is
134
+ * telemetry only — callers that ignore it are unaffected.
129
135
  */
130
136
  export declare function settleOpenLifecycleRun(input: {
131
137
  readonly projectRoot: string;
@@ -134,5 +140,128 @@ export declare function settleOpenLifecycleRun(input: {
134
140
  readonly source: string;
135
141
  readonly autoFireThreshold: number;
136
142
  readonly onLifecycleStage?: ((stage: CompactLifecycleStage, record: CompactLifecycleRecord) => void) | undefined;
137
- }): void;
143
+ }): {
144
+ readonly runId: string;
145
+ readonly triggerRatio: number;
146
+ readonly afterRatio: number;
147
+ } | null;
148
+ /**
149
+ * rid `2026-09-13-compact-event-settle`: close out an open compact run because
150
+ * the HARNESS said one completed — `PostCompact` — rather than because a later
151
+ * probe noticed the ratio had fallen.
152
+ *
153
+ * WHY THIS IS A SECOND FUNCTION AND NOT A FLAG ON THE ONE ABOVE. The function
154
+ * above is defined by two MEASUREMENT gates: it refuses when nothing could be
155
+ * measured, and refuses when the number it got has not dropped far enough. Both
156
+ * are correct for a probe, whose ratio is an INFERENCE about whether something
157
+ * happened. Handed a harness event, both are wrong in the same direction —
158
+ * the harness has already stated that the compaction happened, so a probe that
159
+ * could not measure, or measured something larger, contradicts nothing. The
160
+ * event is the evidence; the ratio is a consequence.
161
+ *
162
+ * What survives from the probe path is the ATTRIBUTION gate, and only that:
163
+ * there must be an open run (`compacting` / `armed`) for this event to be
164
+ * about. A `PostCompact` on a session where peaks-loop never dispatched has
165
+ * nothing to settle — objectively, the run the event would complete does not
166
+ * exist. (`queued` / `preparing` are excluded for the probe path's reason: a
167
+ * run that died before dispatch never had a compaction to complete.)
168
+ *
169
+ * `afterRatio` is recorded ONLY when it is a genuine DROP below the ratio the
170
+ * dispatch was made at. Immediately after a compaction, `readContextPercent`
171
+ * prefers the statusline file, which may still hold the PRE-compact value; the
172
+ * one thing this row must not do is launder that stale reading into an
173
+ * `afterRatio` and publish "the context did not shrink" as a measurement. A
174
+ * `null` here means "no honest post-compact number was available at the moment
175
+ * the event fired" — and that is NOT self-healing: the record is left at
176
+ * `completed` with no number, `computeWindowCalibration` skips `observed` rows
177
+ * that carry none, and the probe path refuses a run that is no longer open. The
178
+ * pair is then closed by `fillEventSettledMeasurement` below — but only on the
179
+ * probes that reach it, which is not all of them: that call sits in the
180
+ * BELOW-THRESHOLD branch of `runAutoCompact` (`auto-compact-orchestrator.ts:567`
181
+ * guards it, `:589` calls it). A probe that instead commits to compacting does
182
+ * not merely defer the measurement: `advance('queued')` writes a fresh run to
183
+ * the same one-record-per-session store (`auto-compact-orchestrator.ts:668`), so
184
+ * the `completed`-without-`afterRatio` record this pair was owed is gone and the
185
+ * pair stays unmeasured. That loss is inherent rather than an oversight — once a
186
+ * second compaction has happened, no later ratio can be attributed to the first,
187
+ * so there is nothing honest left to fill — and it is visible as `unmeasured` in
188
+ * `peaks compact history` (QA residual R9). A fabricated number is not
189
+ * recoverable at all, which is why the stale reading is dropped rather than
190
+ * corrected.
191
+ *
192
+ * `verifying` is deliberately NOT emitted: its documented meaning is "we hold a
193
+ * measurement and are checking it", and on this path there may be no
194
+ * measurement at all. Emitting it would move the same untruth from the history
195
+ * row into the lifecycle record.
196
+ *
197
+ * Returns the settled facts, or `null` when there was nothing to settle. `null`
198
+ * means exactly ONE thing here — there was no OPEN run for this event to be
199
+ * about. A run that was open but whose record could not be written is not
200
+ * `null`: it returns the facts read off that run with `lifecycleWritten: false`,
201
+ * because a failed write is not an absent run, and a caller that cannot tell the
202
+ * two apart ends up telling the user a falsehood (see `compact-event-settle.ts`).
203
+ */
204
+ export declare function settleOpenLifecycleRunOnCompactEvent(input: {
205
+ readonly projectRoot: string;
206
+ readonly sessionId: string;
207
+ readonly measuredRatio: number | null;
208
+ readonly onLifecycleStage?: ((stage: CompactLifecycleStage, record: CompactLifecycleRecord) => void) | undefined;
209
+ /** Failure injection for the write below — the seam `CompactLifecyclePublisher` already takes. */
210
+ readonly failLifecycleWrite?: boolean | undefined;
211
+ }): {
212
+ readonly runId: string;
213
+ readonly triggerRatio: number;
214
+ readonly afterRatio: number | null;
215
+ /**
216
+ * `false` when the run could not be marked settled — `writeCompactLifecycle`
217
+ * threw. The three facts above were read off the OPEN run, so they stay true
218
+ * and a caller may still record the observation; what it must not do is report
219
+ * the run as settled. `settleOpenLifecycleRun` above does not suppress its
220
+ * returned record on a failed write either, so the two now agree that a write
221
+ * failure is not "nothing happened". This field exists because this function's
222
+ * caller, unlike the sibling's, renders a sentence about the outcome.
223
+ */
224
+ readonly lifecycleWritten: boolean;
225
+ } | null;
226
+ /**
227
+ * rid `2026-09-13-compact-event-settle` (repair R1): supply the measurement a
228
+ * harness-settled run was left owing.
229
+ *
230
+ * The function above deliberately refuses to launder a post-compact reading
231
+ * that has not dropped — and right after a compaction that refusal is the
232
+ * NORMAL case, because the statusline still holds the pre-compact value. The
233
+ * run is then closed at `completed` with no `afterRatio`, so the dispatch's
234
+ * calibration pair never closes and "intent vs observed" stays blank for
235
+ * exactly the compactions this slice exists to witness. This function is what
236
+ * makes the function above's promise payable.
237
+ *
238
+ * WHY NOT WIDEN `settleOpenLifecycleRun`. That one re-emits `verifying` before
239
+ * `completed`, which on an already-`completed` record is a backwards stage
240
+ * transition with no observer to serve. This is not a settlement — the run IS
241
+ * settled; only the number is owed. So no stage is rewritten here.
242
+ *
243
+ * WRITING `afterRatio` ONTO THE RECORD IS THE IDEMPOTENCE TOKEN: every later
244
+ * probe finds it present and returns `null`, so however many probes follow, one
245
+ * compaction yields exactly one late measurement.
246
+ *
247
+ * THE DROP GATE IS THE EVENT PATH'S OWN (`measuredRatio < triggerRatio`), not
248
+ * the probe path's `autoFireThreshold`. `afterRatio` has to mean "below the
249
+ * ratio this run was dispatched at" — the rule the event path already enforces
250
+ * — or a run dispatched under the threshold (a forced or banded dispatch) would
251
+ * let a NON-drop through the one path that can still write a `completed` record.
252
+ * `conservative-fallback` is refused for the probe path's reason: its `0` is
253
+ * the absence of a measurement, not an empty context.
254
+ *
255
+ * Returns the filled record, or `null` when no run is owed a measurement.
256
+ */
257
+ export declare function fillEventSettledMeasurement(input: {
258
+ readonly projectRoot: string;
259
+ readonly sessionId: string;
260
+ readonly measuredRatio: number;
261
+ readonly source: string;
262
+ }): {
263
+ readonly runId: string;
264
+ readonly triggerRatio: number;
265
+ readonly afterRatio: number;
266
+ } | null;
138
267
  export {};
@@ -214,28 +214,34 @@ export function summarizeLifecycleError(error) {
214
214
  * `afterRatio: 0` there would publish a fabricated number;
215
215
  * - the ratio is still at or above the auto-fire threshold — the
216
216
  * compact has not landed, so the run stays open.
217
+ *
218
+ * Returns the settled record, or `null` when nothing settled. Slice
219
+ * 2026-09-13-auto-compact-trigger-ownership: the caller needs the settled
220
+ * `afterRatio` to append the "observed compaction point" row that makes the
221
+ * intent-vs-observed gap readable after a real session. The return value is
222
+ * telemetry only — callers that ignore it are unaffected.
217
223
  */
218
224
  export function settleOpenLifecycleRun(input) {
219
225
  // A `conservative-fallback` probe means no signal was available at
220
226
  // all. Its `ratio: 0` is the absence of a measurement, so it can
221
227
  // never be evidence that the context shrank.
222
228
  if (input.source === 'conservative-fallback')
223
- return;
229
+ return null;
224
230
  if (input.measuredRatio >= input.autoFireThreshold)
225
- return;
231
+ return null;
226
232
  const prior = readOpenCompactLifecycle({
227
233
  projectRoot: input.projectRoot,
228
234
  sessionId: input.sessionId
229
235
  });
230
236
  if (prior === null)
231
- return;
237
+ return null;
232
238
  // Only a run that was actually dispatched can be completed by a
233
239
  // post-compact measurement. Both `compacting` (the in-band trigger is
234
240
  // satisfied) and `armed` (a trigger was registered and could fire at
235
241
  // any time) qualify: the measured drop is proof that SOME compact
236
242
  // landed, and this is the only open run to attribute it to.
237
243
  if (prior.stage !== 'compacting' && prior.stage !== 'armed')
238
- return;
244
+ return null;
239
245
  const emit = (stage, withAfterRatio) => {
240
246
  const record = {
241
247
  schemaVersion: 1,
@@ -267,4 +273,174 @@ export function settleOpenLifecycleRun(input) {
267
273
  emit('verifying', false);
268
274
  // `completed` = the measurement confirms the drop; publish it.
269
275
  emit('completed', true);
276
+ return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio };
277
+ }
278
+ /**
279
+ * rid `2026-09-13-compact-event-settle`: close out an open compact run because
280
+ * the HARNESS said one completed — `PostCompact` — rather than because a later
281
+ * probe noticed the ratio had fallen.
282
+ *
283
+ * WHY THIS IS A SECOND FUNCTION AND NOT A FLAG ON THE ONE ABOVE. The function
284
+ * above is defined by two MEASUREMENT gates: it refuses when nothing could be
285
+ * measured, and refuses when the number it got has not dropped far enough. Both
286
+ * are correct for a probe, whose ratio is an INFERENCE about whether something
287
+ * happened. Handed a harness event, both are wrong in the same direction —
288
+ * the harness has already stated that the compaction happened, so a probe that
289
+ * could not measure, or measured something larger, contradicts nothing. The
290
+ * event is the evidence; the ratio is a consequence.
291
+ *
292
+ * What survives from the probe path is the ATTRIBUTION gate, and only that:
293
+ * there must be an open run (`compacting` / `armed`) for this event to be
294
+ * about. A `PostCompact` on a session where peaks-loop never dispatched has
295
+ * nothing to settle — objectively, the run the event would complete does not
296
+ * exist. (`queued` / `preparing` are excluded for the probe path's reason: a
297
+ * run that died before dispatch never had a compaction to complete.)
298
+ *
299
+ * `afterRatio` is recorded ONLY when it is a genuine DROP below the ratio the
300
+ * dispatch was made at. Immediately after a compaction, `readContextPercent`
301
+ * prefers the statusline file, which may still hold the PRE-compact value; the
302
+ * one thing this row must not do is launder that stale reading into an
303
+ * `afterRatio` and publish "the context did not shrink" as a measurement. A
304
+ * `null` here means "no honest post-compact number was available at the moment
305
+ * the event fired" — and that is NOT self-healing: the record is left at
306
+ * `completed` with no number, `computeWindowCalibration` skips `observed` rows
307
+ * that carry none, and the probe path refuses a run that is no longer open. The
308
+ * pair is then closed by `fillEventSettledMeasurement` below — but only on the
309
+ * probes that reach it, which is not all of them: that call sits in the
310
+ * BELOW-THRESHOLD branch of `runAutoCompact` (`auto-compact-orchestrator.ts:567`
311
+ * guards it, `:589` calls it). A probe that instead commits to compacting does
312
+ * not merely defer the measurement: `advance('queued')` writes a fresh run to
313
+ * the same one-record-per-session store (`auto-compact-orchestrator.ts:668`), so
314
+ * the `completed`-without-`afterRatio` record this pair was owed is gone and the
315
+ * pair stays unmeasured. That loss is inherent rather than an oversight — once a
316
+ * second compaction has happened, no later ratio can be attributed to the first,
317
+ * so there is nothing honest left to fill — and it is visible as `unmeasured` in
318
+ * `peaks compact history` (QA residual R9). A fabricated number is not
319
+ * recoverable at all, which is why the stale reading is dropped rather than
320
+ * corrected.
321
+ *
322
+ * `verifying` is deliberately NOT emitted: its documented meaning is "we hold a
323
+ * measurement and are checking it", and on this path there may be no
324
+ * measurement at all. Emitting it would move the same untruth from the history
325
+ * row into the lifecycle record.
326
+ *
327
+ * Returns the settled facts, or `null` when there was nothing to settle. `null`
328
+ * means exactly ONE thing here — there was no OPEN run for this event to be
329
+ * about. A run that was open but whose record could not be written is not
330
+ * `null`: it returns the facts read off that run with `lifecycleWritten: false`,
331
+ * because a failed write is not an absent run, and a caller that cannot tell the
332
+ * two apart ends up telling the user a falsehood (see `compact-event-settle.ts`).
333
+ */
334
+ export function settleOpenLifecycleRunOnCompactEvent(input) {
335
+ const prior = readOpenCompactLifecycle({
336
+ projectRoot: input.projectRoot,
337
+ sessionId: input.sessionId
338
+ });
339
+ if (prior === null)
340
+ return null;
341
+ if (prior.stage !== 'compacting' && prior.stage !== 'armed')
342
+ return null;
343
+ const afterRatio = input.measuredRatio !== null && input.measuredRatio < prior.triggerRatio ? input.measuredRatio : null;
344
+ const record = {
345
+ schemaVersion: 1,
346
+ runId: prior.runId,
347
+ stage: 'completed',
348
+ updatedAt: new Date().toISOString(),
349
+ triggerRatio: prior.triggerRatio,
350
+ redLine: prior.redLine,
351
+ ...(afterRatio !== null ? { afterRatio } : {})
352
+ };
353
+ const facts = { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio };
354
+ try {
355
+ if (input.failLifecycleWrite)
356
+ throw new Error('lifecycle store unavailable');
357
+ writeCompactLifecycle({
358
+ projectRoot: input.projectRoot,
359
+ sessionId: input.sessionId,
360
+ record
361
+ });
362
+ }
363
+ catch {
364
+ // Deliberately NOT `null`. The run WAS open and the write FAILED; returning
365
+ // the same value as "no run is open" is the conflation the repo's own lint
366
+ // names at this exact line (`catch-return-null — caller cannot distinguish
367
+ // failure from success`), and it reached the user as "No compact run was
368
+ // open", which is false.
369
+ return { ...facts, lifecycleWritten: false };
370
+ }
371
+ try {
372
+ input.onLifecycleStage?.('completed', record);
373
+ }
374
+ catch {
375
+ // Observer failures are not ours to propagate.
376
+ }
377
+ return { ...facts, lifecycleWritten: true };
378
+ }
379
+ /**
380
+ * rid `2026-09-13-compact-event-settle` (repair R1): supply the measurement a
381
+ * harness-settled run was left owing.
382
+ *
383
+ * The function above deliberately refuses to launder a post-compact reading
384
+ * that has not dropped — and right after a compaction that refusal is the
385
+ * NORMAL case, because the statusline still holds the pre-compact value. The
386
+ * run is then closed at `completed` with no `afterRatio`, so the dispatch's
387
+ * calibration pair never closes and "intent vs observed" stays blank for
388
+ * exactly the compactions this slice exists to witness. This function is what
389
+ * makes the function above's promise payable.
390
+ *
391
+ * WHY NOT WIDEN `settleOpenLifecycleRun`. That one re-emits `verifying` before
392
+ * `completed`, which on an already-`completed` record is a backwards stage
393
+ * transition with no observer to serve. This is not a settlement — the run IS
394
+ * settled; only the number is owed. So no stage is rewritten here.
395
+ *
396
+ * WRITING `afterRatio` ONTO THE RECORD IS THE IDEMPOTENCE TOKEN: every later
397
+ * probe finds it present and returns `null`, so however many probes follow, one
398
+ * compaction yields exactly one late measurement.
399
+ *
400
+ * THE DROP GATE IS THE EVENT PATH'S OWN (`measuredRatio < triggerRatio`), not
401
+ * the probe path's `autoFireThreshold`. `afterRatio` has to mean "below the
402
+ * ratio this run was dispatched at" — the rule the event path already enforces
403
+ * — or a run dispatched under the threshold (a forced or banded dispatch) would
404
+ * let a NON-drop through the one path that can still write a `completed` record.
405
+ * `conservative-fallback` is refused for the probe path's reason: its `0` is
406
+ * the absence of a measurement, not an empty context.
407
+ *
408
+ * Returns the filled record, or `null` when no run is owed a measurement.
409
+ */
410
+ export function fillEventSettledMeasurement(input) {
411
+ if (input.source === 'conservative-fallback')
412
+ return null;
413
+ const prior = readOpenCompactLifecycle({
414
+ projectRoot: input.projectRoot,
415
+ sessionId: input.sessionId
416
+ });
417
+ if (prior === null)
418
+ return null;
419
+ // Exactly one shape is owed a number: the one the EVENT path leaves behind.
420
+ // `settleOpenLifecycleRun` never writes it (it always carries `afterRatio`),
421
+ // and a `failed` run never dispatched, so it has no row to pair with.
422
+ if (prior.stage !== 'completed' || prior.afterRatio !== undefined)
423
+ return null;
424
+ if (input.measuredRatio >= prior.triggerRatio)
425
+ return null;
426
+ const record = {
427
+ schemaVersion: 1,
428
+ runId: prior.runId,
429
+ stage: 'completed',
430
+ updatedAt: new Date().toISOString(),
431
+ triggerRatio: prior.triggerRatio,
432
+ redLine: prior.redLine,
433
+ afterRatio: input.measuredRatio
434
+ };
435
+ try {
436
+ writeCompactLifecycle({
437
+ projectRoot: input.projectRoot,
438
+ sessionId: input.sessionId,
439
+ record
440
+ });
441
+ }
442
+ catch {
443
+ return null;
444
+ }
445
+ return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio };
270
446
  }
@@ -31,7 +31,21 @@ export interface AutoCompactInput {
31
31
  * autonomously at 0.85+ with zero human / zero LLM intervention.
32
32
  */
33
33
  readonly force?: boolean | undefined;
34
- /** Skip the 95% red-line gate (test seam — never true in production). */
34
+ /**
35
+ * INERT — nothing reads this. It is threaded from the CLI's
36
+ * `--bypass-red-line` (a published flag, kept for backward compatibility;
37
+ * see the option's own note) down to `evaluateAutoCompactDecision`, where the
38
+ * red-line branch ignores it.
39
+ *
40
+ * Why nothing reads it any more: it used to skip a gate that refused to
41
+ * dispatch below 0.85. That gate was removed (slice
42
+ * 2026-09-13-auto-compact-trigger-ownership, T3/A1) — peaks-loop cannot
43
+ * compact a running session, so the refusal could not shorten the wait it
44
+ * was waiting for, and it deadlocked the runner at the worst moment. With the
45
+ * gate gone, the red line still ASKS the harness to compact (when it always
46
+ * did) and no longer needs a bypass. Kept as a field so the published
47
+ * signature does not change under callers that pass it.
48
+ */
35
49
  readonly bypassRedLine?: boolean | undefined;
36
50
  /** Current session id (default = resolve via session-id-service). */
37
51
  readonly sessionId?: string | undefined;
@@ -93,7 +107,7 @@ export interface AutoCompactTestHooks {
93
107
  *
94
108
  * - ratio < preCompact → 'none' or 'soft-warn'
95
109
  * - ratio ≥ preCompact → 'pre-compact' (async-friendly path)
96
- * - ratio ≥ redLine → 'red-line' (synchronous gate)
110
+ * - ratio ≥ redLine → 'red-line' (ask the harness; dispatch continues)
97
111
  *
98
112
  * Slice 2026-07-28 (rid-027): `mode` selects the threshold table.
99
113
  * Default `'standard'` (0.85/0.95). `'partial'` (0.70/0.85) is used
@@ -109,8 +123,13 @@ export declare function evaluateCompactTrigger(ratio: number, mode?: AutoCompact
109
123
  * - 0.85 ≤ ratio < 0.95 → pre-compact; if in-flight batch
110
124
  * present, defer (D6.e); else dispatch
111
125
  * IDE compact asynchronously.
112
- * - ratio ≥ 0.95 → red-line; ALWAYS dispatch synchronously
113
- * regardless of in-flight batch.
126
+ * - ratio ≥ 0.95 → red-line; ask the harness to compact
127
+ * regardless of in-flight batch. Nothing is
128
+ * gated — dispatch is NOT blocked (slice
129
+ * 2026-09-13-auto-compact-trigger-ownership:
130
+ * peaks-loop cannot compact a running session,
131
+ * so a "block" gated nothing and deadlocked the
132
+ * runner).
114
133
  */
115
134
  export declare function evaluateAutoCompactDecision(input: {
116
135
  ratio: number;
@@ -129,6 +148,7 @@ export declare function evaluateAutoCompactDecision(input: {
129
148
  */
130
149
  inflightBatch?: boolean | InFlightBatchProbe | undefined;
131
150
  force?: boolean | undefined;
151
+ /** Accepted and ignored — see `AutoCompactInput.bypassRedLine`. */
132
152
  bypassRedLine?: boolean | undefined;
133
153
  mode?: AutoCompactMode | undefined;
134
154
  /**
@@ -170,13 +190,14 @@ export declare function buildConvergencePlan(input: {
170
190
  * 5. If trigger.kind === 'pre-compact' AND in-flight batch → wait.
171
191
  * 6. If trigger.kind === 'pre-compact' → async dispatch (write
172
192
  * checkpoint + IDE compact; orchestrator returns immediately).
173
- * 7. If trigger.kind === 'red-line' → synchronous gate: refuse
174
- * sub-agent dispatch, dispatch IDE compact, mark `redLineGated`.
193
+ * 7. If trigger.kind === 'red-line' → dispatch the IDE compact, report
194
+ * that the harness has been asked and that dispatch continues.
175
195
  *
176
196
  * The caller (CLI or skill body) handles the actual return — D7's
177
- * post-compact-detect will pick up the checkpoint on the next turn.
178
- * For red-line, the caller MUST block further tool calls until the
179
- * post-compact probe confirms ratio < 0.85.
197
+ * post-compact-detect will pick up the checkpoint on the next turn. For
198
+ * red-line, the caller keeps working; it re-probes and, if the ratio is
199
+ * still rising with no compact from the harness, reports that instead of
200
+ * stalling.
180
201
  */
181
202
  export declare function runAutoCompact(input: AutoCompactInput): Promise<AutoCompactResult>;
182
203
  /** Re-export for callers that need to surface the trigger shape. */
@@ -200,6 +221,29 @@ export interface CompactHistoryEvent {
200
221
  readonly ok: boolean;
201
222
  readonly checkpointPath: string;
202
223
  readonly dispatchMessage: string;
224
+ /**
225
+ * Slice 2026-09-13-auto-compact-trigger-ownership (T4): the calibration
226
+ * instrument. `windowTokens` is the denominator peaks-loop divided by
227
+ * (`probe.capacityTokens`), so `beforeRatio * windowTokens` is the exact
228
+ * TOKEN POINT peaks-loop asked the harness to compact at. That number is
229
+ * the deliverable — NOT a hand-picked threshold: this slice could not run a
230
+ * real Claude Code session, so the real trigger point of
231
+ * `CLAUDE_CODE_AUTO_COMPACT_WINDOW` (documented as a window, observed to
232
+ * fire near the window's end) has no measured answer yet. Recording the
233
+ * intent lets the first real session produce one.
234
+ */
235
+ readonly windowTokens?: number | null;
236
+ /** Which layer produced `windowTokens` (see `ContextWindowSource`). */
237
+ readonly windowSource?: string | null;
238
+ /**
239
+ * `dispatch` (default, and the only kind earlier releases wrote) or
240
+ * `observed` — a row appended when a later probe MEASURED the ratio after
241
+ * a dispatched compact, proving one landed. The pair is what yields the
242
+ * intent-vs-observed delta.
243
+ */
244
+ readonly kind?: 'dispatch' | 'observed';
245
+ /** `observed` rows only: the measured post-compact ratio. */
246
+ readonly afterRatio?: number;
203
247
  }
204
248
  export declare function appendCompactHistoryEvent(input: {
205
249
  readonly projectRoot: string;