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
@@ -7,22 +7,34 @@
7
7
  * gates plus the verify-pipeline, so the orchestrator no longer hand-writes
8
8
  * them per mechanical file-split (or similar) slice.
9
9
  *
10
- * The content markers emitted here are the exact strings the CLI gates +
11
- * verify-pipeline mechanically check — see
12
- * `src/services/artifacts/artifact-prerequisites.ts` (mustContain /
13
- * mustContainAny / headingMustContain) and
14
- * `src/services/workflow/pipeline-verify-gate-support.ts` +
15
- * `src/services/workflow/artifact-paths.ts` (suffixed security/performance
16
- * findings). Do NOT reword the marker lines.
10
+ * The content markers emitted here are the exact strings the CLI gates
11
+ * mechanically check — see `src/services/artifacts/artifact-prerequisites.ts`
12
+ * (mustContain / mustContainAny / headingMustContain — the authoritative
13
+ * table). `peaks workflow verify-pipeline` reads that same table for both the
14
+ * evidence paths it probes and the markers it enforces
15
+ * (`src/services/workflow/pipeline-verify-gate-support.ts#contractEvidencePaths`).
16
+ * Do NOT reword the marker lines.
17
+ *
18
+ * This paragraph used to send editors to `src/services/workflow/artifact-paths.ts`
19
+ * as the checker of "suffixed security/performance findings". That is no longer
20
+ * true: the `security-findings-<rid>.md` / `performance-findings-<rid>.md` gates
21
+ * were removed in rid `2026-09-14-verify-pipeline-contract-drift` (no
22
+ * `qa:verdict-issued` table names those paths; security and perf evidence is
23
+ * resolved on the RD side at `audit/security-<rid>.md` / `audit/perf-<rid>.md`),
24
+ * and `artifact-paths.ts` has had zero code consumers since. It is not a marker
25
+ * authority, so it is not a pointer worth keeping.
17
26
  *
18
27
  * Karpathy §2 (Simplicity First): a single generator function + a handful of
19
28
  * pure body builders. No speculative options, no validation beyond what the
20
29
  * reference prototype performs.
21
30
  */
22
- import { createHash } from 'node:crypto';
23
31
  import { mkdir, readdir, writeFile } from 'node:fs/promises';
24
32
  import { join } from 'node:path';
33
+ import { serializeHandoffFrontmatter } from '../prd/handoff-frontmatter.js';
34
+ import { handoffRelativePath, sha256OfBody } from '../prd/handoff-service.js';
25
35
  import { getSessionDir } from '../session/getSessionDir.js';
36
+ import { REQUEST_ID_PATTERN } from '../artifacts/request-artifact-service.js';
37
+ import { isUnsafePathInput } from '../../shared/path-safety.js';
26
38
  /** Comma/space tolerant `--files` splitter. */
27
39
  export function parseFiles(raw) {
28
40
  return raw
@@ -235,40 +247,20 @@ function buildQaRequest(rid, sid, files) {
235
247
  `;
236
248
  }
237
249
  /**
238
- * Build the `prd/handoff.md` frontmatter + body and compute the sha256
239
- * fingerprint. The hash is computed over the frontmatter (with the
240
- * `handoffHash` line left empty — `sha256:`) concatenated with the body,
241
- * matching the reference prototype exactly.
250
+ * Build the `prd/handoff-<rid>.md` frontmatter + body. The frontmatter comes from the
251
+ * ONE canonical serializer and `handoffHash === sha256(body)` — the same
252
+ * pairing `handoff-service.initHandoff` and `handoff-auto-regen.ts` use, so
253
+ * this producer cannot drift from them.
254
+ *
255
+ * This function used to hand-roll a third, divergent frontmatter: a
256
+ * `handoffHash: sha256:<hex>` value with **no line beginning `sha256:`**, over
257
+ * a hash computed on `frontmatter + body` rather than the body. The
258
+ * `AUDIT_REQUIRES_HANDOFF` gate (a substring check) accepted that file while
259
+ * `readAndVerifyHandoff` in both audit skills returned `null` — the very
260
+ * producer/consumer divergence rid `2026-09-14-handoff-writer-gate-divergence`
261
+ * exists to remove, surviving in a function the same slice edited.
242
262
  */
243
263
  function buildHandoff(rid, sid, title, files, lineCounts) {
244
- const bulletList = files.map((f) => ` - ${f}`).join('\n');
245
- const frontmatter = `---
246
- requestId: ${rid}
247
- scope:
248
- ${bulletList}
249
- files:
250
- ${bulletList}
251
- handoffPath: .peaks/_runtime/${sid}/prd/handoff.md
252
- handoffHash: sha256:PLACEHOLDER
253
- decisions:
254
- - id: D1
255
- summary: "Mechanical verbatim module split"
256
- rationale: "Satisfy the 800-line file-size gate."
257
- risks:
258
- - id: R1
259
- description: "Public exports may be imported elsewhere"
260
- mitigation: "Import sites updated; tsc clean."
261
- nextActions:
262
- - "peaks-qa validates the regression matrix"
263
- gateEvidence:
264
- projectScan: .peaks/project-scan/project-scan.md
265
- prdHandoff: .peaks/_runtime/${sid}/prd/handoff.md
266
- codeReview: .peaks/_runtime/${sid}/rd/code-review.md
267
- securityReview: .peaks/_runtime/${sid}/rd/security-review.md
268
- perfBaseline: .peaks/_runtime/${sid}/audit/perf.md
269
- schemaVersion: 2
270
- ---
271
- `;
272
264
  const body = `# PRD Handoff — ${rid}
273
265
 
274
266
  ${title}. Mechanical verbatim module split; behavior-preserving.
@@ -279,10 +271,19 @@ ${filesMd(files)}
279
271
  Line counts:
280
272
  ${lineCountsMd(lineCounts)}
281
273
  `;
282
- const hashInput = frontmatter.replace('sha256:PLACEHOLDER', 'sha256:') + body;
283
- const hash = createHash('sha256').update(hashInput, 'utf8').digest('hex');
284
- const content = frontmatter.replace('sha256:PLACEHOLDER', `sha256:${hash}`) + body;
285
- return { content, hash };
274
+ const handoffHash = sha256OfBody(body);
275
+ const frontmatter = {
276
+ requestId: rid,
277
+ sessionId: sid,
278
+ schemaVersion: '2',
279
+ handoffHash,
280
+ writtenAt: new Date().toISOString(),
281
+ goals: [],
282
+ acceptanceCriteria: [],
283
+ preservedBehavior: [],
284
+ handoffPath: handoffRelativePath(sid, rid)
285
+ };
286
+ return { content: `${serializeHandoffFrontmatter(frontmatter)}${body}`, hash: handoffHash };
286
287
  }
287
288
  /** Find the existing numbered QA request file for `rid`, else the default. */
288
289
  async function resolveQaRequestPath(qaDir, rid) {
@@ -300,6 +301,21 @@ async function resolveQaRequestPath(qaDir, rid) {
300
301
  }
301
302
  export async function generateEvidence(options) {
302
303
  const { projectRoot, rid, title, files, lineCounts, sessionId } = options;
304
+ // Both values become path segments below — the rid as a filename, the sid as
305
+ // the session directory. Guard them BEFORE the first mkdir so a rejected run
306
+ // leaves nothing behind. `REQUEST_ID_PATTERN` is the repo's own request-id
307
+ // control (`request-artifact-service.ts`, F-1 slice 025 security); the sid
308
+ // gets the segment check because `--session-id` has no pinned format.
309
+ // Measured before these lines existed: `--rid '../../../pwned'` wrote
310
+ // `.peaks/_runtime/pwned.md`, outside the per-session evidence dir, and a
311
+ // deeper rid wrote above the project root with the string echoed into the
312
+ // artifact body.
313
+ if (!REQUEST_ID_PATTERN.test(rid)) {
314
+ throw new Error(`Invalid request id: ${rid} (expected letters, digits, dots, underscores, or dashes)`);
315
+ }
316
+ if (isUnsafePathInput(sessionId)) {
317
+ throw new Error(`Invalid session id: ${sessionId} (must be a single path segment)`);
318
+ }
303
319
  const sessionRoot = getSessionDir(projectRoot, sessionId);
304
320
  const rdDir = join(sessionRoot, 'rd');
305
321
  const qaDir = join(sessionRoot, 'qa');
@@ -319,18 +335,39 @@ export async function generateEvidence(options) {
319
335
  }
320
336
  const qaRequestPath = await resolveQaRequestPath(qaDir, rid);
321
337
  const handoff = buildHandoff(rid, sessionId, title, files, lineCounts);
338
+ // Four of these twelve paths carry the rid because the `rd:qa-handoff` gate
339
+ // requires `<rid>` in them: every slice in a session shares `rd/` and
340
+ // `audit/`, so writing the ridless name silently destroyed the previous
341
+ // slice's evidence on 2026-09-13 while the gate stayed green (slice
342
+ // `2026-09-14-audit-artifact-rid-scoping`).
322
343
  const writes = [
323
- [join(rdDir, 'code-review.md'), buildCodeReview(rid, title, files, lineCounts)],
344
+ [join(rdDir, `code-review-${rid}.md`), buildCodeReview(rid, title, files, lineCounts)],
345
+ [join(auditDir, `security-${rid}.md`), buildSecurityReview(rid)],
346
+ // ...but the `config` type is the one whose `rd:qa-handoff` row is
347
+ // `SECURITY_REVIEW` — the genuinely ridless `rd/security-review.md` — and
348
+ // this generator is request-type-agnostic (no `--request-type`). So it
349
+ // writes BOTH names: the rid-scoped one the fanout types resolve
350
+ // (`AUDIT_SECURITY`) and the bare one `config` resolves. Nothing is lost
351
+ // by having both; a `config` slice has nothing else to fall back on, and
352
+ // dropping this write made a `config` slice fail its own gate while the
353
+ // generator reported success (repair round: measured `missing:
354
+ // ['rd/security-review.md']` with the real generator and the real gate).
324
355
  [join(rdDir, 'security-review.md'), buildSecurityReview(rid)],
325
- [join(rdDir, 'karpathy-review.md'), buildKarpathyReview(rid, lineCounts)],
356
+ [join(rdDir, `karpathy-review-${rid}.md`), buildKarpathyReview(rid, lineCounts)],
357
+ // `rd/tech-doc.md` stays ridless on purpose: the TECH_DOC prereq was
358
+ // removed in v2.11.0, so nothing gates it and there is no rid-scoped
359
+ // sibling that anything reads.
326
360
  [join(rdDir, 'tech-doc.md'), buildTechDoc(rid, title, files, lineCounts)],
327
- [join(auditDir, 'perf.md'), buildPerfAudit(rid)],
361
+ [join(auditDir, `perf-${rid}.md`), buildPerfAudit(rid)],
328
362
  [join(qaDir, 'test-cases', `${rid}.md`), buildTestCases(rid)],
329
363
  [join(qaDir, 'test-reports', `${rid}.md`), buildTestReport(rid)],
330
364
  [join(qaDir, `security-findings-${rid}.md`), buildSecurityFindings(rid)],
331
365
  [join(qaDir, `performance-findings-${rid}.md`), buildPerformanceFindings(rid)],
332
366
  [qaRequestPath, buildQaRequest(rid, sessionId, files)],
333
- [join(prdDir, 'handoff.md'), handoff.content]
367
+ // The handoff capsule carries the rid for the same reason as the four
368
+ // above (slice `2026-09-14-prd-capsule-rid-scoping`): one slot per
369
+ // session means the second slice's capsule overwrites the first's.
370
+ [join(prdDir, `handoff-${rid}.md`), handoff.content]
334
371
  ];
335
372
  const writtenFiles = [];
336
373
  for (const [path, content] of writes) {
@@ -341,7 +378,7 @@ export async function generateEvidence(options) {
341
378
  rid,
342
379
  sessionId,
343
380
  sessionRoot,
344
- handoffPath: join(prdDir, 'handoff.md'),
381
+ handoffPath: join(prdDir, `handoff-${rid}.md`),
345
382
  handoffHash: handoff.hash,
346
383
  writtenFiles,
347
384
  createdDirectories
@@ -252,6 +252,15 @@ interface EvidenceSource {
252
252
  readonly label: string;
253
253
  /** Path segments under `.peaks/_runtime/<sessionId>/`. */
254
254
  readonly segments: readonly string[];
255
+ /**
256
+ * An older location of the SAME artifact, tried only when `segments` is not
257
+ * on disk. Slice `2026-09-14-prd-capsule-rid-scoping` moved the PRD handoff
258
+ * capsule to `prd/handoff-<rid>.md`; sessions written before it hold only
259
+ * the bare `prd/handoff.md`, and this module's delivery gate keys on that
260
+ * source — so a source that goes missing does not fail the gate, it stops
261
+ * it (`enforceScopeContractDelivery` returns early on `missing`).
262
+ */
263
+ readonly legacySegments?: readonly string[];
255
264
  /** Dimensions this source can supply evidence for. */
256
265
  readonly supports: readonly DimensionKind[];
257
266
  /** What DELIVERED means for this source. See `isDelivered()` — every source
@@ -474,7 +474,10 @@ function evidenceSourcesFor(rid, prePostDiffAvailable) {
474
474
  {
475
475
  key: SCOPE_CONTRACT_SOURCE_KEY,
476
476
  label: 'PRD handoff (approved scope + non-goals)',
477
- segments: ['prd', 'handoff.md'],
477
+ // One capsule per slice since `2026-09-14-prd-capsule-rid-scoping`; the
478
+ // bare name is the pre-scoping tier and still lives on 3 sessions.
479
+ segments: ['prd', `handoff-${rid}.md`],
480
+ legacySegments: ['prd', 'handoff.md'],
478
481
  supports: ['functional-completeness', 'existing-functionality-intact'],
479
482
  delivery: { kind: 'whole' }
480
483
  }
@@ -508,22 +511,43 @@ function classifyReadFailure(error) {
508
511
  function readEvidence(projectRoot, sessionId, rid, prePostDiffAvailable) {
509
512
  const runtimeRoot = join(projectRoot, '.peaks', '_runtime', sessionId);
510
513
  return evidenceSourcesFor(rid, prePostDiffAvailable).map(source => {
511
- const relativePath = ['.peaks', '_runtime', sessionId, ...source.segments].join('/');
512
- const absolutePath = join(runtimeRoot, ...source.segments);
514
+ // The canonical location first; an older one is tried only when the
515
+ // canonical file is genuinely ABSENT (see `legacySegments`). A file that
516
+ // is present but unreadable stops the walk — falling through to an older
517
+ // copy would silently swap the evidence this run reports.
518
+ const attempts = [
519
+ source.segments,
520
+ ...(source.legacySegments !== undefined ? [source.legacySegments] : [])
521
+ ];
522
+ // The path reported when nothing resolved stays the CANONICAL one, so the
523
+ // operator is sent to where the artifact belongs, not to its old home.
524
+ let resolved = source.segments;
513
525
  let raw = null;
514
526
  let error = '';
515
- let read = 'ok';
516
- try {
517
- raw = readFileSync(absolutePath);
518
- }
519
- catch (err) {
520
- read = classifyReadFailure(err);
521
- error = err instanceof Error ? err.message : String(err);
527
+ let read = 'missing';
528
+ for (const segments of attempts) {
529
+ try {
530
+ raw = readFileSync(join(runtimeRoot, ...segments));
531
+ read = 'ok';
532
+ resolved = segments;
533
+ break;
534
+ }
535
+ catch (err) {
536
+ read = classifyReadFailure(err);
537
+ error = err instanceof Error ? err.message : String(err);
538
+ // A file that exists but cannot be read IS this source's file, so it
539
+ // is also the path the report must name — walking on to an older copy
540
+ // would silently swap the evidence this run reports.
541
+ if (read === 'unreadable') {
542
+ resolved = segments;
543
+ break;
544
+ }
545
+ }
522
546
  }
523
547
  return {
524
548
  source,
525
- relativePath,
526
- absolutePath,
549
+ relativePath: ['.peaks', '_runtime', sessionId, ...resolved].join('/'),
550
+ absolutePath: join(runtimeRoot, ...resolved),
527
551
  raw,
528
552
  read,
529
553
  error,
@@ -69,6 +69,7 @@ export async function pickFromList(options) {
69
69
  input: fzfInput,
70
70
  cwd: options.projectRoot,
71
71
  stdio: ['pipe', 'pipe', 'pipe'],
72
+ windowsHide: true,
72
73
  shell: false,
73
74
  timeout: SPAWN_TIMEOUT_MS,
74
75
  maxBuffer: MAX_BUFFER_BYTES
@@ -108,6 +109,7 @@ function checkFzfVersion(fzfBin) {
108
109
  stdout = execFileSync(fzfBin, ['--version'], {
109
110
  stdio: ['ignore', 'pipe', 'pipe'],
110
111
  shell: false,
112
+ windowsHide: true,
111
113
  timeout: 5_000
112
114
  }).toString('utf8');
113
115
  }
@@ -67,6 +67,14 @@ export declare const AUTO_COMPACT_HOOK_MARKER = "peaks:auto-compact-hook-do-not-
67
67
  * Default on-disk location — kept here (not in claude-settings-template)
68
68
  * because the auto-compact hook is a separate install surface from
69
69
  * the fact-forcing gate bypass template.
70
+ *
71
+ * This is Claude Code's path, and it is a DEFAULT only. A caller that
72
+ * resolved an IDE adapter MUST pass the adapter-declared location as
73
+ * `settingsPath`:
74
+ * `join(projectRoot, adapter.settings.dirName, adapter.settings.localSettingsFileName)`.
75
+ * The default remains for callers with no adapter in hand (and keeps the
76
+ * claude-code byte-stability contract); it is not a claim that every IDE
77
+ * stores local settings at `.claude/settings.local.json`.
70
78
  */
71
79
  export declare const AUTO_COMPACT_HOOK_SETTINGS_PATH = ".claude/settings.local.json";
72
80
  /**
@@ -100,7 +108,7 @@ export type AutoCompactHookRemoveResult = {
100
108
  */
101
109
  export declare function installAutoCompactHook(input: {
102
110
  readonly projectRoot: string;
103
- readonly settingsPath?: string;
111
+ readonly settingsPath?: string | undefined;
104
112
  }): AutoCompactHookInstallResult;
105
113
  /**
106
114
  * Remove the auto-compact PreToolUse hook. Idempotent: missing hook
@@ -112,5 +120,5 @@ export declare function installAutoCompactHook(input: {
112
120
  */
113
121
  export declare function removeAutoCompactHook(input: {
114
122
  readonly projectRoot: string;
115
- readonly settingsPath?: string;
123
+ readonly settingsPath?: string | undefined;
116
124
  }): AutoCompactHookRemoveResult;
@@ -70,6 +70,14 @@ export const AUTO_COMPACT_HOOK_MARKER = 'peaks:auto-compact-hook-do-not-edit';
70
70
  * Default on-disk location — kept here (not in claude-settings-template)
71
71
  * because the auto-compact hook is a separate install surface from
72
72
  * the fact-forcing gate bypass template.
73
+ *
74
+ * This is Claude Code's path, and it is a DEFAULT only. A caller that
75
+ * resolved an IDE adapter MUST pass the adapter-declared location as
76
+ * `settingsPath`:
77
+ * `join(projectRoot, adapter.settings.dirName, adapter.settings.localSettingsFileName)`.
78
+ * The default remains for callers with no adapter in hand (and keeps the
79
+ * claude-code byte-stability contract); it is not a claim that every IDE
80
+ * stores local settings at `.claude/settings.local.json`.
73
81
  */
74
82
  export const AUTO_COMPACT_HOOK_SETTINGS_PATH = '.claude/settings.local.json';
75
83
  function isObject(value) {
@@ -47,11 +47,16 @@ export declare const CONTEXT_WINDOW_TOKENS_ENV_VAR = "PEAKS_CONTEXT_WINDOW_TOKEN
47
47
  /**
48
48
  * Which layer produced a resolved context window:
49
49
  * - `env-override` — `PEAKS_CONTEXT_WINDOW_TOKENS`
50
+ * - `harness-env` — the window peaks-loop itself wrote into the
51
+ * harness's machine-local settings
52
+ * (`CLAUDE_CODE_AUTO_COMPACT_WINDOW`, declared by
53
+ * the adapter as `autoCompactWindowEnvVar`); see
54
+ * `harness-window-config.ts`
50
55
  * - `config` — `context.windowTokens` (`peaks config set`)
51
56
  * - `model-heuristic` — `[1M]` suffix / `ONE_MILLION_CONTEXT_MODELS`
52
57
  * - `default` — 200K safe default
53
58
  */
54
- export type ContextWindowSource = 'env-override' | 'config' | 'model-heuristic' | 'default';
59
+ export type ContextWindowSource = 'env-override' | 'harness-env' | 'config' | 'model-heuristic' | 'default';
55
60
  export interface ContextWindowResolution {
56
61
  readonly tokens: number;
57
62
  readonly source: ContextWindowSource;
@@ -61,9 +66,78 @@ export interface ContextWindowOverrides {
61
66
  readonly env?: NodeJS.ProcessEnv | undefined;
62
67
  /** Raw `context.windowTokens` value (unvalidated — validated here). */
63
68
  readonly configWindowTokens?: unknown;
69
+ /**
70
+ * The window peaks-loop itself configured for the harness — raw value read
71
+ * from the harness's machine-local settings `env` block (or the process env
72
+ * the harness populated from it). Resolved by the caller
73
+ * (`auto-compact-reader.ts` via `harness-window-config.ts`), NOT here: the
74
+ * settings path and the key name are per-IDE declarations.
75
+ *
76
+ * Slice 2026-09-13-auto-compact-trigger-ownership: this layer is what makes
77
+ * the ratio peaks-loop computes and the window the harness compacts against
78
+ * THE SAME NUMBER. Without it they are two independent resolutions that can
79
+ * drift 5× apart on a 1M-window model the model-name heuristic misses.
80
+ */
81
+ readonly harnessWindowTokens?: unknown;
82
+ /**
83
+ * Provenance of `harnessWindowTokens`: true when peaks-loop wrote that value
84
+ * itself, false/omitted when a human set it. Only a peaks-written value may
85
+ * be overruled by the late 1M rescue — see `resolveContextWindowTokens`.
86
+ */
87
+ readonly harnessWindowPeakWritten?: boolean;
64
88
  /** Warning sink for an invalid override (defaults to `console.warn`). */
65
89
  readonly onInvalidOverride?: ((message: string) => void) | undefined;
90
+ /**
91
+ * Warning sink for the E2 notice: a HUMAN pin (`env-override` / `config`)
92
+ * larger than peaks-loop's own model-window estimate. Deliberately separate
93
+ * from `onInvalidOverride` — that sink means "your value could not be read",
94
+ * this one means "your value was read, and peaks-loop cannot promise the
95
+ * harness will use it". Two meanings, two sinks. Defaults to `console.warn`.
96
+ *
97
+ * See `describeWindowAboveModelEstimate` for what the notice does and does
98
+ * not claim, and why it is a notice rather than a refusal.
99
+ */
100
+ readonly onAboveModelEstimate?: ((message: string) => void) | undefined;
66
101
  }
102
+ /**
103
+ * E2 (rid 2026-09-13-defects-e) — the notice for a pin larger than
104
+ * peaks-loop's model-window estimate. `null` when there is nothing to say.
105
+ *
106
+ * WHY THIS EXISTS. The harness does not take its auto-compact window on faith:
107
+ * it reduces the configured value with `Math.min(native, override)`, where
108
+ * `native` is the model's own context size. A pin ABOVE `native` is therefore
109
+ * accepted by peaks-loop, written to the harness's settings, and silently
110
+ * reduced by the harness — so peaks-loop's ratio divides by a window the
111
+ * harness is not compacting on. That is the A1 drift (two independent
112
+ * resolutions, one number meaning two things), arriving from the other side
113
+ * from E1: E1 is a value the harness refuses, E2 a value it quietly lowers.
114
+ *
115
+ * CAN WE READ THE NATIVE WINDOW? No, and the notice does not pretend otherwise.
116
+ * There is no API for it and no environment variable that carries it; the only
117
+ * local knowledge is `modelContextWindowTokens`'s NAME HEURISTIC (200_000, or
118
+ * 1_000_000 when the id says `1m` or matches the allowlist) — the very layer
119
+ * this slice demoted, because it is wrong for precisely the proxied /
120
+ * third-party models the pin exists for. It is used here ONLY as a disagreement
121
+ * DETECTOR, never as an authority, and the uncertainty that buys is one-sided:
122
+ * this notice means "peaks-loop's best guess of your model is smaller than the
123
+ * window you pinned", which a genuinely larger model ALSO produces. It cannot
124
+ * mean "the harness will definitely cap you" — only the harness knows that.
125
+ *
126
+ * WHY A NOTICE AND NOT A REFUSAL. The pin is the documented escape hatch for a
127
+ * model id the heuristic cannot see, so refusing would delete the feature. The
128
+ * notice keeps it and states the risk.
129
+ *
130
+ * WHY ONLY THE HUMAN PINS. The `harness-env` layer is a value peaks-loop FOUND
131
+ * in the harness's own file, not one this call is setting; its disagreements are
132
+ * already reported by the writer (`harnessWindowSyncWarning`), and warning here
133
+ * would fire on every probe of a project whose file legitimately says 1000000.
134
+ */
135
+ export declare function describeWindowAboveModelEstimate(input: {
136
+ readonly model: string;
137
+ readonly tokens: number;
138
+ /** Human-readable name of the pin, e.g. `PEAKS_CONTEXT_WINDOW_TOKENS="500000"`. */
139
+ readonly pin: string;
140
+ }): string | null;
67
141
  /**
68
142
  * Parse an explicit context-window override. Accepts a positive finite
69
143
  * integer only (number, or a numeric string so an env var works); anything
@@ -76,11 +150,41 @@ export declare function parseContextWindowOverride(raw: unknown): number | null;
76
150
  * (first hit wins):
77
151
  * 1. env `PEAKS_CONTEXT_WINDOW_TOKENS`
78
152
  * 2. config `context.windowTokens`
79
- * 3. model-name heuristic (`modelContextWindowTokens`)
80
- * 4. `DEFAULT_CONTEXT_WINDOW_TOKENS` (200_000)
153
+ * 3. `harnessWindowTokens` — the window peaks-loop configured for the
154
+ * harness (`autoCompactWindowEnvVar`)
155
+ * 4. model-name heuristic (`modelContextWindowTokens`)
156
+ * 5. `DEFAULT_CONTEXT_WINDOW_TOKENS` (200_000)
81
157
  *
82
158
  * An invalid explicit override is ignored with a warning and falls through
83
159
  * to the next layer — a typo must never crash or silently win the probe.
160
+ *
161
+ * Slice 2026-09-13-auto-compact-trigger-ownership — why layer 3 exists:
162
+ * The hard constraint is that the window peaks-loop divides by must BE the
163
+ * window it configured for the harness. Layer 3 is peaks-loop's own output
164
+ * read back from the harness's settings file, so the two sides reference one
165
+ * artifact rather than two independent resolutions. It sits ABOVE the model
166
+ * heuristic because a heuristic contradicting the number both sides already
167
+ * use is precisely how the 5× drift arose (a 1M-window model measured
168
+ * against a 200K guess).
169
+ *
170
+ * Why it sits BELOW the two explicit pins. Both are human declarations of
171
+ * intent, and a pin that loses to a value peaks-loop wrote earlier is a
172
+ * silently ignored setting: the user changes `context.windowTokens`, the
173
+ * stale harness key shadows it, and nothing appears to happen. Conflict is
174
+ * instead resolved by PROPAGATION — the probe that resolves the pin also
175
+ * syncs it into the harness (`syncHarnessWindowForProject`), so peaks-loop's
176
+ * ratio and the harness's window are back on one number before that command
177
+ * returns. The conflict is therefore transient and self-healing rather than
178
+ * either silent shadowing or permanent drift.
179
+ *
180
+ * Why layer 3 is not simply authoritative (the ratchet). Reading back a value
181
+ * peaks-loop wrote makes peaks-loop trust its own earlier resolution, so a
182
+ * first-time mis-resolution (an unrecognised model defaulting to 200_000)
183
+ * would be written to disk and then believed forever: the ratio stays
184
+ * saturated at 1.0 while the session grows past the pin, and the correction
185
+ * that exists for exactly that case — the late 1M rescue — was disabled for
186
+ * this layer. Self-locking. See `resolveContextWindowTokens` for how the
187
+ * rescue is let back in without letting it overwrite a human's own setting.
84
188
  */
85
189
  export declare function resolveContextWindow(model: string, overrides?: ContextWindowOverrides): ContextWindowResolution;
86
190
  export declare const CLAUDE_CODE_ADAPTER: IdeAdapter;