peaks-loop 4.0.47 → 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 (104) hide show
  1. package/CHANGELOG.md +24 -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.js +48 -8
  9. package/dist/cli/commands/compact-command.js +112 -0
  10. package/dist/cli/commands/config-commands.js +15 -9
  11. package/dist/cli/commands/dashboard-long-run.js +6 -0
  12. package/dist/cli/commands/dispatch-commands.js +11 -1
  13. package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
  14. package/dist/cli/commands/hooks-commands.js +4 -4
  15. package/dist/cli/commands/job-commands.js +8 -0
  16. package/dist/cli/commands/loop-eval-commands.js +15 -0
  17. package/dist/cli/commands/perf-audit-commands.js +2 -0
  18. package/dist/cli/commands/playwright-commands.js +12 -0
  19. package/dist/cli/commands/prd-commands.js +1 -1
  20. package/dist/cli/commands/qa-commands.js +22 -0
  21. package/dist/cli/commands/request-commands.js +8 -0
  22. package/dist/cli/commands/scan-commands.js +1 -1
  23. package/dist/cli/commands/security-audit-commands.js +2 -0
  24. package/dist/cli/commands/slice-integrate-commands.js +5 -0
  25. package/dist/cli/commands/statusline-commands.js +44 -4
  26. package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
  27. package/dist/cli/commands/sub-agent/detached.js +47 -22
  28. package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
  29. package/dist/cli/commands/verdict-aggregate-command.js +95 -13
  30. package/dist/cli/commands/workflow-commands.js +1 -1
  31. package/dist/cli/index.js +5 -45
  32. package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
  33. package/dist/services/artifacts/artifact-prerequisites.js +130 -65
  34. package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
  35. package/dist/services/artifacts/request-artifact-service.js +18 -8
  36. package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
  37. package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
  38. package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
  39. package/dist/services/audit-independent/perf-audit-service.js +27 -5
  40. package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
  41. package/dist/services/audit-independent/security-audit-service.js +28 -6
  42. package/dist/services/code/auto-compact-lifecycle.d.ts +119 -0
  43. package/dist/services/code/auto-compact-lifecycle.js +169 -0
  44. package/dist/services/code/auto-compact-orchestrator.js +13 -2
  45. package/dist/services/code/compact-event-settle.d.ts +122 -0
  46. package/dist/services/code/compact-event-settle.js +219 -0
  47. package/dist/services/compact-history/compact-history-service.d.ts +14 -0
  48. package/dist/services/config/config-restore.d.ts +12 -1
  49. package/dist/services/config/config-restore.js +35 -4
  50. package/dist/services/config/config-rollback.js +6 -1
  51. package/dist/services/context/harness-context-witness.d.ts +310 -0
  52. package/dist/services/context/harness-context-witness.js +606 -0
  53. package/dist/services/evidence/evidence-generator.js +86 -49
  54. package/dist/services/final-review/final-review-service.d.ts +9 -0
  55. package/dist/services/final-review/final-review-service.js +36 -12
  56. package/dist/services/ide/ide-registry.d.ts +19 -0
  57. package/dist/services/ide/ide-registry.js +21 -0
  58. package/dist/services/job/job-state-store.js +7 -0
  59. package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
  60. package/dist/services/prd/handoff-auto-regen.js +31 -27
  61. package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
  62. package/dist/services/prd/handoff-frontmatter.js +75 -0
  63. package/dist/services/prd/handoff-service.d.ts +41 -2
  64. package/dist/services/prd/handoff-service.js +81 -8
  65. package/dist/services/prd/handoff-types.d.ts +3 -2
  66. package/dist/services/prd/handoff-types.js +3 -2
  67. package/dist/services/qa/qa-business-review-state.js +9 -0
  68. package/dist/services/scan/karpathy-service.js +2 -2
  69. package/dist/services/session/session-checkpoint-service.js +8 -0
  70. package/dist/services/skill/resume-detector.js +29 -11
  71. package/dist/services/skills/hooks-codegate-superpowers.d.ts +6 -0
  72. package/dist/services/skills/hooks-codegate-superpowers.js +61 -2
  73. package/dist/services/skills/hooks-settings-service.js +14 -4
  74. package/dist/services/skills/session-start-hook-constants.d.ts +45 -0
  75. package/dist/services/skills/session-start-hook-constants.js +45 -0
  76. package/dist/services/skills/skill-statusline-service.d.ts +14 -0
  77. package/dist/services/slice/slice-check-service.js +29 -11
  78. package/dist/services/slice/slice-review-state.js +8 -0
  79. package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
  80. package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
  81. package/dist/services/workflow/pipeline-verify-service.js +24 -23
  82. package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
  83. package/dist/services/workspace/claude-settings-template.d.ts +56 -8
  84. package/dist/services/workspace/claude-settings-template.js +98 -20
  85. package/dist/services/workspace/workspace-claude-settings-materializer.js +78 -7
  86. package/package.json +6 -6
  87. package/skills/bee/peaks-prd/SKILL.md +7 -5
  88. package/skills/bee/peaks-qa/SKILL.md +5 -5
  89. package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
  90. package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
  91. package/skills/bee/peaks-rd/SKILL.md +8 -6
  92. package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
  93. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
  94. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
  95. package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
  96. package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
  97. package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
  98. package/skills/peaks-code/SKILL.md +1 -1
  99. package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
  100. package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
  101. package/skills/peaks-code/references/resume-detection.md +13 -7
  102. package/skills/peaks-code/references/runbook.md +3 -2
  103. package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
  104. package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
@@ -15,7 +15,12 @@ export function planRollback() {
15
15
  export function executeRollback(opts) {
16
16
  const plan = planRollback();
17
17
  if (!plan.available) {
18
- throw new Error('NO_BACKUP: ~/.peaks/config.json.1.x.bak not found');
18
+ // rid 2026-09-13-two-decisions ①: a machine that never migrated has no
19
+ // `.bak`, and that is its normal state — `--apply` on such a machine is
20
+ // "nothing to roll back", not a failure. Returned instead of thrown so the
21
+ // exit status and the `available` key agree on every path; see
22
+ // `config-restore.ts` for the full rationale and the accepted cost.
23
+ return { ...plan, applied: false };
19
24
  }
20
25
  if (!opts.apply) {
21
26
  return { ...plan, applied: false };
@@ -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;