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
@@ -14,22 +14,28 @@
14
14
  * toolkit is "ready to use" so the
15
15
  * LLM doesn't lose context to a
16
16
  * last-second `/compact` panic.
17
- * - 95% RED LINE — peaks-loop refuses to dispatch any
18
- * further sub-agent and synchronously
19
- * triggers IDE-side compact. At 95%+
20
- * the context window is too tight to
21
- * continue safely; the LLM cannot
22
- * opt out. This is the compact red
23
- * line that guarantees the LLM-runner
24
- * keeps working with context < 95%.
17
+ * - 95% RED LINE — peaks-loop asks the harness to
18
+ * compact and says it is waiting.
19
+ * Nothing is blocked: peaks-loop has
20
+ * no executor for a running session,
21
+ * so it cannot gate dispatch — and
22
+ * claiming to was the deadlock. See
23
+ * the correction note below.
25
24
  *
26
25
  * Why 0.85 / 0.95 split: the LLM uses the 0.85–0.95 zone to do
27
26
  * intelligent convergence — wait for in-flight sub-agents, finish
28
27
  * the current todo row, persist a checkpoint, then compact. peaks-loop
29
- * provides the toolkit; the LLM picks the moment. At 0.95 the window
30
- * is gone and peaks-loop takes over synchronously to keep the runner
31
- * alive.
28
+ * provides the toolkit; the LLM picks the moment. At 0.95 peaks-loop
29
+ * requests the compact outright and says it is waiting.
30
+ *
31
+ * Slice 2026-09-13-auto-compact-trigger-ownership corrected two claims that
32
+ * used to head this file: the red line does NOT "refuse to dispatch any
33
+ * further sub-agent" (peaks-loop has no way to compact a running session, so
34
+ * such a refusal gated nothing and deadlocked the runner), and the window
35
+ * these ratios divide by is now the same one peaks-loop configures for the
36
+ * harness — see `harness-window-config.ts`.
32
37
  */
38
+ import type { HarnessWindowSyncResult } from './harness-window-config.js';
33
39
  export declare const AUTO_COMPACT_SOFT_WARN_RATIO = 0.5;
34
40
  export declare const AUTO_COMPACT_AUTO_FIRE_RATIO = 0.8;
35
41
  export declare const AUTO_COMPACT_PRE_COMPACT_RATIO = 0.85;
@@ -67,7 +73,10 @@ export type CompactTrigger = {
67
73
  message: string;
68
74
  toolkitReady: true;
69
75
  }
70
- /** Red line (ratio ≥ 0.95): peaks-loop forces synchronous compact; LLM cannot opt out. */
76
+ /**
77
+ * Red line (ratio ≥ 0.95): peaks-loop asks the harness to compact and says
78
+ * it is waiting. Dispatch is NOT blocked — see `redLineRequested`.
79
+ */
71
80
  | {
72
81
  kind: 'red-line';
73
82
  ratio: number;
@@ -97,6 +106,20 @@ export interface CompactDispatchResult {
97
106
  readonly pathway: 'ide-native' | 'llm-self-compress' | 'shell-exec' | 'noop';
98
107
  readonly message: string;
99
108
  }
109
+ /**
110
+ * Old → new map of envelope fields that were renamed but are still emitted
111
+ * under their old name as deprecated aliases.
112
+ *
113
+ * One definition, used both as the `deprecatedFields` value the envelopes
114
+ * carry and as the registry the type's `@deprecated` tags describe, so the
115
+ * alias and its record cannot drift apart into two lists.
116
+ *
117
+ * `redLineGated` → `redLineRequested` (slice
118
+ * 2026-09-13-auto-compact-trigger-ownership): nothing was ever gated. See
119
+ * `redLineGated` on the dispatch branches for why the alias is kept rather
120
+ * than deleted.
121
+ */
122
+ export declare const DEPRECATED_ENVELOPE_FIELDS: Readonly<Record<string, string>>;
100
123
  /** Final envelope returned by `runAutoCompact`. */
101
124
  export type AutoCompactResult = {
102
125
  readonly ok: false;
@@ -112,6 +135,13 @@ export type AutoCompactResult = {
112
135
  readonly ratio: number;
113
136
  readonly source: string;
114
137
  readonly decision: 'below-threshold' | 'in-flight-batch';
138
+ /**
139
+ * Slice 2026-09-13-auto-compact-trigger-ownership: what syncing the
140
+ * harness auto-compact window did on this probe. `null` = the active
141
+ * adapter declares no such knob. Present so the write is VISIBLE —
142
+ * the harness reports an override silently, so peaks-loop must not.
143
+ */
144
+ readonly harnessWindow?: HarnessWindowSyncResult | null;
115
145
  };
116
146
  } | {
117
147
  readonly ok: boolean;
@@ -139,7 +169,54 @@ export type AutoCompactResult = {
139
169
  * caller passes `--mode partial`.
140
170
  */
141
171
  readonly mode?: 'standard' | 'partial';
172
+ /**
173
+ * True when the ratio had crossed the red line and peaks-loop asked
174
+ * the harness to compact.
175
+ *
176
+ * Renamed from `redLineGated` in slice
177
+ * 2026-09-13-auto-compact-trigger-ownership: nothing is gated. The
178
+ * old name asserted a block peaks-loop cannot enforce (it has no
179
+ * executor for a running session), and acting on that assertion is
180
+ * what deadlocked the runner.
181
+ */
182
+ readonly redLineRequested?: boolean;
183
+ /**
184
+ * Deprecated alias of `redLineRequested` — same value, written by the
185
+ * same statement, so the two can never disagree.
186
+ *
187
+ * Kept because the old name SHIPPED: `redLineGated` is in every release
188
+ * from 2.13.0 through 4.0.46, so a script outside this repo that reads
189
+ * it exists in the wild. Dropping the key would hand that script
190
+ * `undefined` instead of the boolean it branches on — no error, no log,
191
+ * just a silently different branch. This is the same call this slice
192
+ * already made for `--bypass-red-line`: stop advertising a
193
+ * wrong-named surface, do not delete it out from under a published
194
+ * caller. That flag is likewise kept with nothing reading it.
195
+ *
196
+ * There is deliberately NO removal date here. A date would be a promise
197
+ * with no mechanism behind it; removing a published field is a MAJOR
198
+ * decision to be taken on purpose, not one this comment can schedule.
199
+ *
200
+ * `deprecatedFields` (below) is the part a runtime consumer can
201
+ * actually see — a `@deprecated` tag is not.
202
+ */
142
203
  readonly redLineGated?: boolean;
204
+ /**
205
+ * Old → new map of the envelope fields still emitted as deprecated
206
+ * aliases, so a consumer that parses JSON can discover the rename
207
+ * without reading this file.
208
+ *
209
+ * Why it has to exist: the consumers a rename breaks are precisely the
210
+ * ones that never read `auto-compact-types.ts`. A type comment reaches
211
+ * the compiler and the next editor, not a script parsing a CLI
212
+ * envelope, which is what made the rename silent in the first place.
213
+ * This puts the fact in the envelope they already read.
214
+ *
215
+ * Absent on the branches that carry no renamed field.
216
+ */
217
+ readonly deprecatedFields?: Readonly<Record<string, string>>;
218
+ /** See the `AUTO_COMPACT_SKIP` data shape above. */
219
+ readonly harnessWindow?: HarnessWindowSyncResult | null;
143
220
  };
144
221
  };
145
222
  /**
@@ -1,35 +1,3 @@
1
- /**
2
- * Auto-compact shared types (v2.13.0 AC-1..AC-4).
3
- *
4
- * Two-tier threshold model — peaks-loop is project-aware and the LLM
5
- * is the decision-maker:
6
- *
7
- * - 50% soft warn — log a one-line info row; continue.
8
- * - 85% pre-compact zone — peaks-loop prepares the convergence
9
- * toolkit (checkpoint + convergence
10
- * plan + auto-decisions log +
11
- * IDE-compact dispatcher). The LLM
12
- * DECIDES when (or whether) to fire
13
- * compact during this zone. The
14
- * toolkit is "ready to use" so the
15
- * LLM doesn't lose context to a
16
- * last-second `/compact` panic.
17
- * - 95% RED LINE — peaks-loop refuses to dispatch any
18
- * further sub-agent and synchronously
19
- * triggers IDE-side compact. At 95%+
20
- * the context window is too tight to
21
- * continue safely; the LLM cannot
22
- * opt out. This is the compact red
23
- * line that guarantees the LLM-runner
24
- * keeps working with context < 95%.
25
- *
26
- * Why 0.85 / 0.95 split: the LLM uses the 0.85–0.95 zone to do
27
- * intelligent convergence — wait for in-flight sub-agents, finish
28
- * the current todo row, persist a checkpoint, then compact. peaks-loop
29
- * provides the toolkit; the LLM picks the moment. At 0.95 the window
30
- * is gone and peaks-loop takes over synchronously to keep the runner
31
- * alive.
32
- */
33
1
  export const AUTO_COMPACT_SOFT_WARN_RATIO = 0.5;
34
2
  // Part 22: auto-fire threshold (was 0.85 pre-compact zone where
35
3
  // the LLM had to decide; LLM misjudged 0.85–0.95 and only fired
@@ -41,3 +9,19 @@ export const AUTO_COMPACT_AUTO_FIRE_RATIO = 0.80;
41
9
  export const AUTO_COMPACT_PRE_COMPACT_RATIO = 0.85;
42
10
  export const AUTO_COMPACT_RED_LINE_RATIO = 0.95;
43
11
  export const AUTO_COMPACT_THRESHOLD_RATIO = AUTO_COMPACT_AUTO_FIRE_RATIO;
12
+ /**
13
+ * Old → new map of envelope fields that were renamed but are still emitted
14
+ * under their old name as deprecated aliases.
15
+ *
16
+ * One definition, used both as the `deprecatedFields` value the envelopes
17
+ * carry and as the registry the type's `@deprecated` tags describe, so the
18
+ * alias and its record cannot drift apart into two lists.
19
+ *
20
+ * `redLineGated` → `redLineRequested` (slice
21
+ * 2026-09-13-auto-compact-trigger-ownership): nothing was ever gated. See
22
+ * `redLineGated` on the dispatch branches for why the alias is kept rather
23
+ * than deleted.
24
+ */
25
+ export const DEPRECATED_ENVELOPE_FIELDS = {
26
+ redLineGated: 'redLineRequested'
27
+ };
@@ -0,0 +1,310 @@
1
+ import type { StatusLineStdin } from '../skills/skill-statusline-service.js';
2
+ export declare const HARNESS_CONTEXT_WITNESS_FILE = "harness-context-witness.json";
3
+ export declare const WITNESS_SCHEMA_VERSION = 2;
4
+ /**
5
+ * Contributor 1 of the budget: the harness reports a rounded percentage.
6
+ * "Pre-calculated percentage of context window used" is not documented as
7
+ * fractional, so the conservative reading is an integer percent, i.e. up to
8
+ * ±0.5 percentage points = ±0.005 of the window. If the real payload turns out
9
+ * to carry decimals this term shrinks tenfold and the guard gets sharper; the
10
+ * assumption is visible in the first witness file, where
11
+ * `usageTokens / modelWindowTokens` and `usedPercentage` must agree.
12
+ */
13
+ export declare const WITNESS_PERCENT_ROUNDING_FRACTION = 0.005;
14
+ /**
15
+ * Contributor 2: the two sides do not sum exactly the same token quantities.
16
+ * MEASURED, not guessed — on 2026-09-13 the harness's own pre-compact count
17
+ * (963,306 tokens) exceeded peaks-loop's transcript estimate (961,658) by
18
+ * 1,648 tokens = 0.171%. Used as a fraction of the tokens, not of the window.
19
+ */
20
+ export declare const WITNESS_NUMERATOR_FRACTION = 0.0017;
21
+ /**
22
+ * The smallest window difference this guard claims to detect, and therefore
23
+ * the yardstick for "is this sample sharp enough to answer at all".
24
+ *
25
+ * 3% is not arbitrary: the harness compacts a native-1M model at ~967,000 by
26
+ * default while peaks-loop writes the model ceiling, so 1,000,000 vs 967,000 —
27
+ * a 3.3% difference — is the smallest real-world disagreement between the two
28
+ * denominators today. Anything the guard reports as "agree" while its own
29
+ * budget is wider than that difference is a claim it cannot support.
30
+ *
31
+ * NOTE (repair cycle 2): the constant is a floor on the SAMPLE, and the sample
32
+ * it floors is the WITNESS's, not peaks-loop's — a gap of this size leaves a
33
+ * residual proportional to the witness's ratio, so a stale (or low) witness
34
+ * shrinks the signal while the budget's rounding term does not shrink with it.
35
+ * See `sampleSupportsAgreement`, which is where this constant is applied. At
36
+ * zero skew it was already correctly calibrated (measured onset 0.176 of the
37
+ * window against a predicted 0.177); the fault was that only zero skew was
38
+ * correctly calibrated.
39
+ */
40
+ export declare const MIN_DETECTABLE_WINDOW_DIFFERENCE = 0.03;
41
+ /**
42
+ * Which of the two in-range readings of a raw percentage was taken, or that
43
+ * neither could be taken. `unestablished` is not a reading: the payload said a
44
+ * percentage and carried nothing that could settle which scale it is on.
45
+ */
46
+ export type PercentageUnit = 'fraction' | 'percent' | 'unestablished';
47
+ /**
48
+ * A harness context snapshot, as written to
49
+ * `<projectRoot>/.peaks/_runtime/<sessionId>/harness-context-witness.json`.
50
+ *
51
+ * The path is peaks-loop's own session runtime directory — deliberately NOT
52
+ * the harness's settings file or any other harness-owned location.
53
+ */
54
+ export interface HarnessContextWitness {
55
+ readonly schemaVersion: number;
56
+ /** When peaks-loop received the render payload. */
57
+ readonly capturedAt: string;
58
+ /**
59
+ * `context_window.used_percentage`, normalised to 0..1. `null` when this
60
+ * render's payload had no percentage this module could read — the record is
61
+ * still written, because "a render happened and carried nothing usable" is
62
+ * a different fact from "nothing rendered", and the two are told apart by
63
+ * exactly this file existing or not.
64
+ */
65
+ readonly usedPercentage: number | null;
66
+ /**
67
+ * The payload's `used_percentage` VERBATIM, before any normalisation. Kept
68
+ * so a human can tell a unit misread from a real disagreement: the number
69
+ * above is a 0..1 fraction and this one is not, and only this one shows what
70
+ * the harness actually said.
71
+ */
72
+ readonly usedPercentageRaw: number | null;
73
+ /** Which reading `usedPercentage` was normalised from, or why none was taken. */
74
+ readonly usedPercentageUnit: PercentageUnit | null;
75
+ /** `context_window.context_window_size`. Recorded, never the comparison's denominator. */
76
+ readonly modelWindowTokens: number | null;
77
+ /**
78
+ * Sum of the prompt-side `context_window.current_usage` components. This is
79
+ * the ALIGNMENT KEY: comparing it with a probe's `rawTokens` is how the
80
+ * guard learns whether the two numbers describe the same API response, and
81
+ * therefore how much of any difference is sampling skew rather than a real
82
+ * disagreement. `output_tokens` is excluded so the sum is the same quantity
83
+ * peaks-loop's transcript estimate sums.
84
+ */
85
+ readonly usageTokens: number | null;
86
+ /** The harness session id from the payload, for the foreign-session check. */
87
+ readonly outerSessionId: string | null;
88
+ }
89
+ export type WitnessVerdict = 'absent' | 'foreign-session' | 'unverifiable' | 'agree' | 'disagree';
90
+ /**
91
+ * Why there is no witness to compare. The first two leave the same file system
92
+ * state — no file — so the caller, which knows the session directory, resolves
93
+ * which applies and this module turns it into a sentence.
94
+ *
95
+ * The third (`unreadable`, repair cycle 3) is decided by the READ, not by the
96
+ * directory, and it is the reason `readHarnessWitness` returns a tagged union
97
+ * rather than `null`: a file that exists but cannot be used is evidence that a
98
+ * render DID happen, so calling it `not-rendered` names the wrong cause — the
99
+ * exact mis-attribution §17-B was written to remove.
100
+ */
101
+ export type WitnessAbsentCause = 'session-dir-missing' | 'not-rendered' | 'unreadable';
102
+ export interface HarnessWitnessComparison {
103
+ readonly verdict: WitnessVerdict;
104
+ /** Why the verdict is what it is, when it is not `agree` / `disagree`. */
105
+ readonly reason: string | null;
106
+ readonly harnessPct: number | null;
107
+ readonly peaksRatio: number;
108
+ /** `peaksRatio - harnessPct`; `null` when the harness side is unknown. */
109
+ readonly deviation: number | null;
110
+ /**
111
+ * The quantity the verdict is actually decided on: `deviation` minus the
112
+ * sampling skew the two token snapshots measure. Equal denominators put this
113
+ * at zero no matter how stale the witness is, so a non-zero residual is the
114
+ * window difference itself rather than the sample's age.
115
+ */
116
+ readonly residual: number | null;
117
+ /** The budget `residual` was tested against, as a fraction of the window. */
118
+ readonly tolerance: number | null;
119
+ readonly witnessedAt: string | null;
120
+ readonly witnessTokens: number | null;
121
+ readonly peaksTokens: number | null;
122
+ /** The harness's raw `used_percentage`, verbatim — see `usedPercentageRaw`. */
123
+ readonly witnessRawPercentage: number | null;
124
+ /** Which reading produced `harnessPct`, so a unit misread is visible. */
125
+ readonly witnessPercentageUnit: PercentageUnit | null;
126
+ }
127
+ /**
128
+ * Read the harness's context block out of a parsed statusline stdin payload.
129
+ * Returns `null` only when there was no payload at all (a render on a TTY, or
130
+ * a manual `peaks statusline`) — in which case there is nothing to record and
131
+ * nothing to say. A payload that arrived but carried no usable percentage is
132
+ * recorded, not dropped.
133
+ */
134
+ export declare function parseHarnessWitness(input: {
135
+ readonly stdin: StatusLineStdin | null;
136
+ readonly nowMs: number;
137
+ }): HarnessContextWitness | null;
138
+ export declare function harnessWitnessPath(projectRoot: string, sessionId: string): string;
139
+ /**
140
+ * Write this render's record. Returns whether a file was written.
141
+ *
142
+ * This is the ONLY side effect on the statusline render path. It is bounded
143
+ * (one small file, inside peaks-loop's own session directory), it cannot
144
+ * influence any decision peaks-loop makes (nothing except the diagnostic in
145
+ * `peaks code context-now` reads it), and it never throws — a statusline that
146
+ * fails to render because an observability file could not be written would be
147
+ * a worse failure than a missing observation. A failed write is not swallowed:
148
+ * the next probe reports `absent`, which is the visible symptom.
149
+ *
150
+ * No `mkdir`: the session directory is created by the session layer, and a
151
+ * statusline render is the wrong place to be creating directories. Its absence
152
+ * is one of the honest reasons the witness can be missing.
153
+ *
154
+ * Written via temp + rename, not in place. A reader runs in ANOTHER process
155
+ * (the probe) and treats unreadable JSON as "no witness" — so a torn read
156
+ * would not be an error, it would be a SILENT loss of the comparison, which is
157
+ * the failure mode this whole slice exists to remove. The rename makes the
158
+ * partial state unobservable. Same shape as `atomicWriteJson` in
159
+ * statusline-settings-service.ts.
160
+ *
161
+ * The rename is not always available: on Windows it throws EPERM while another
162
+ * process holds the target open, which is exactly the reader this guard is
163
+ * written for. Dropping the sample then would be the silent loss the temp
164
+ * rename was introduced to remove, so the write falls back to in-place. The
165
+ * fallback gives up atomicity for that one write — the reader may see a
166
+ * half-written file and call it "no witness" — which is a narrower failure
167
+ * than never recording the sample at all, and the temp path stays the normal
168
+ * one.
169
+ *
170
+ * A LOWER-INFORMATION RECORD DOES NOT REPLACE A HIGHER-INFORMATION ONE (repair
171
+ * cycle 3). The render that carries no readable percentage is still recorded
172
+ * when there is nothing better on disk — that is what tells "rendered, nothing
173
+ * usable" apart from "never rendered" (§17-B) — but it does NOT overwrite a
174
+ * record whose percentage IS readable. Overwriting silences a real comparison:
175
+ * measured 2026-09-14, a witness giving a real 3.3% window gap reported
176
+ * `disagree` with its sentence, and one render whose payload lacked
177
+ * `context_window` turned the same comparison into `unverifiable` with the
178
+ * sentence suppressed. A render that says less must not delete a sample that
179
+ * says more.
180
+ *
181
+ * AND THE PARSE IS INSIDE A TRY (repair cycle 3). The module's contract at the
182
+ * top of this comment — "it never throws" — was true only by inspection: the
183
+ * `parseHarnessWitness` call sat above the only `try`, and a payload shape the
184
+ * guards did not anticipate escaped the function and killed the render (see
185
+ * `parseHarnessWitness` for the measured case). A nested `try` rather than a
186
+ * wider one, because the outer catch says something different: it is the
187
+ * temp+rename FALLBACK, and a parse failure has no JSON to fall back TO.
188
+ * A payload this module cannot read is a missing observation, which is the
189
+ * outcome the contract already prefers.
190
+ */
191
+ export declare function writeHarnessWitness(input: {
192
+ readonly projectRoot: string | null;
193
+ readonly sessionId: string | null;
194
+ readonly stdin: StatusLineStdin | null;
195
+ readonly nowMs: number;
196
+ }): boolean;
197
+ /**
198
+ * What a read found.
199
+ *
200
+ * This used to be `HarnessContextWitness | null`, which collapsed FOUR file
201
+ * states — absent, unreadable, not JSON, not the shape this module writes —
202
+ * into one `null`. The caller then had to guess the cause from the directory
203
+ * alone and picked `not-rendered` whenever the directory existed, so a render
204
+ * whose record was merely unreadable was reported as "the statusline has not
205
+ * rendered here": the wrong-cause sentence §17-B was written to remove. The
206
+ * three states are named here instead of re-derived by a second look at the
207
+ * disk. Same shape, and deliberately the same words (`missing` / `valid` /
208
+ * `invalid`), as `CompactLifecycleRead` in
209
+ * `src/services/compact-statusline/compact-lifecycle-store.ts`, which reads a
210
+ * sibling file in the same directory for the same purpose.
211
+ */
212
+ export type HarnessWitnessRead = {
213
+ readonly kind: 'missing';
214
+ } | {
215
+ readonly kind: 'valid';
216
+ readonly witness: HarnessContextWitness;
217
+ } | {
218
+ readonly kind: 'invalid';
219
+ };
220
+ /**
221
+ * Read the record for a session. Anything that exists but cannot be used as a
222
+ * record reads as `invalid` — never as `missing`, which would erase the one
223
+ * fact the file's existence carries: a render happened here.
224
+ */
225
+ export declare function readHarnessWitness(input: {
226
+ readonly projectRoot: string;
227
+ readonly sessionId: string;
228
+ }): HarnessWitnessRead;
229
+ /**
230
+ * The budget, in tokens: what a difference between the two ratios can be
231
+ * explained by WITHOUT the two denominators being different, once the
232
+ * sampling skew has been taken out (see `compareHarnessWitness`).
233
+ *
234
+ * rounding 0.005 x window — the harness's percentage is rounded
235
+ * numerator 0.0017 x usedTokens — measured disagreement of the two sums
236
+ *
237
+ * Sample skew is deliberately NOT a term here. It is not a budget at all: it
238
+ * is MEASURED per sample from the two token counts and SUBTRACTED from the
239
+ * deviation, because under equal denominators the token difference and the
240
+ * ratio difference are the same number — see `compareHarnessWitness`.
241
+ */
242
+ export declare function witnessToleranceTokens(input: {
243
+ readonly windowTokens: number;
244
+ readonly usedTokens: number;
245
+ }): number;
246
+ /**
247
+ * Compare peaks-loop's ratio with the harness's own percentage.
248
+ *
249
+ * Two identifiers must match before any comparison is allowed:
250
+ * 1. the SESSION — a witness written by another harness session on the same
251
+ * project is not evidence about this one. Lenient in the same direction as
252
+ * the compact-event attribution: refused only when BOTH ids resolve and
253
+ * differ, because a guard that turns a missing field into a permanent
254
+ * "cannot tell" is the failure this whole slice exists to remove.
255
+ * 2. the MOMENT — a witness is a snapshot, and the harness documents that its
256
+ * percentage depends on when it was calculated. The token counts are what
257
+ * says whether the two numbers came from the same API response.
258
+ *
259
+ * THE MOMENT IS REMOVED, NOT BUDGETED (repair cycle 1). If the two ratios
260
+ * share a denominator W, the harness's count and peaks-loop's count differ by
261
+ * exactly the sampling skew, so
262
+ *
263
+ * peaksRatio - harnessPct == (peaksTokens - witnessTokens) / W
264
+ *
265
+ * holds identically — it is not a tolerance to be granted, it is an equality
266
+ * to be tested. Budgeting the skew instead (`tolerance += |skew|`) made the
267
+ * budget grow by `x` while the deviation grew by `x(1+g)`, so a real window
268
+ * difference `g` was cancelled for every skew large enough to absorb it: a
269
+ * measured 3.3% denominator gap read `agree` for skew in [12,400, 22,100]
270
+ * tokens, and gaps up to 5.3% never surfaced at all. Subtracting the skew from
271
+ * the deviation and testing the remainder against a budget that contains only
272
+ * rounding and numerator disagreement makes the test invariant to skew by
273
+ * construction, and leaves `-g x harnessPct/(1+g)` — the window difference
274
+ * itself — as the only thing the residual can be.
275
+ *
276
+ * AND THE RESIDUAL'S SIZE IS THE WITNESS'S, NOT PEAKS-LOOP'S (repair cycle 2).
277
+ * `-g x harnessPct/(1+g)` carries the witness's ratio, so a witness captured
278
+ * while the session was small cannot show a window difference that a bigger
279
+ * witness would. The three answers therefore do not share one gate: a residual
280
+ * past the budget is a `disagree` at any witness size, while `agree` needs the
281
+ * sample to be sharp enough to support it (`sampleSupportsAgreement`).
282
+ */
283
+ export declare function compareHarnessWitness(input: {
284
+ readonly witness: HarnessContextWitness | null;
285
+ readonly peaksRatio: number;
286
+ readonly peaksTokens: number | null;
287
+ readonly peaksWindowTokens: number | null;
288
+ readonly outerSessionId: string | null;
289
+ readonly absentCause?: WitnessAbsentCause;
290
+ }): HarnessWitnessComparison;
291
+ /**
292
+ * One-way sentence for a disagreeing witness. Advising, never asking: an
293
+ * auto-compact observation must never become an `AskUserQuestion` (see
294
+ * `.peaks/memory/auto-compact-threshold-policy.md`).
295
+ *
296
+ * The sentence states the quantity that decided it (the residual, after the
297
+ * measured skew was removed) and the raw harness value with the reading that
298
+ * was taken from it. Both are there so a reader can tell a real denominator
299
+ * difference from a unit misread without going back to the file.
300
+ */
301
+ export declare function describeHarnessWitness(comparison: HarnessWitnessComparison): string | null;
302
+ /** Convenience for the CLI: read + compare in one call. */
303
+ export declare function readAndCompareHarnessWitness(input: {
304
+ readonly projectRoot: string;
305
+ readonly sessionId: string;
306
+ readonly peaksRatio: number;
307
+ readonly peaksTokens: number | null;
308
+ readonly peaksWindowTokens: number | null;
309
+ readonly outerSessionId: string | null;
310
+ }): HarnessWitnessComparison;