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.
- package/CHANGELOG.md +54 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +139 -16
- package/dist/cli/commands/compact-command.js +241 -1
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/container-commands.js +3 -3
- package/dist/cli/commands/core/skill-command.js +45 -10
- package/dist/cli/commands/cron-commands.js +2 -1
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +14 -5
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +14 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/reinject-command.d.ts +72 -0
- package/dist/cli/commands/reinject-command.js +174 -0
- package/dist/cli/commands/request-commands.js +14 -3
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/vm-commands.js +7 -7
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +24 -2
- package/dist/cli/commands/worktree-lease-commands.js +4 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/program.js +5 -0
- package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
- package/dist/services/adapter/adapter-registry.js +1 -1
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/artifact-service.js +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
- package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
- package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
- package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
- package/dist/services/code/auto-compact-lifecycle.js +180 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +166 -34
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/code/orchestrator-can-do.d.ts +4 -2
- package/dist/services/code/orchestrator-can-do.js +37 -5
- package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
- package/dist/services/codegraph/codegraph-process-runner.js +3 -2
- package/dist/services/compact/request-transition-hook.js +5 -2
- package/dist/services/compact-history/compact-history-service.d.ts +75 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/config/config-safety.d.ts +52 -0
- package/dist/services/config/config-safety.js +75 -1
- package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
- package/dist/services/context/auto-compact-dispatcher.js +113 -40
- package/dist/services/context/auto-compact-reader.d.ts +68 -28
- package/dist/services/context/auto-compact-reader.js +155 -1
- package/dist/services/context/auto-compact-types.d.ts +89 -12
- package/dist/services/context/auto-compact-types.js +16 -32
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/context/harness-window-config.d.ts +412 -0
- package/dist/services/context/harness-window-config.js +607 -0
- package/dist/services/context/main-session-monitor.d.ts +27 -0
- package/dist/services/context/main-session-monitor.js +32 -1
- package/dist/services/context/post-compact-reinjection.d.ts +221 -0
- package/dist/services/context/post-compact-reinjection.js +491 -0
- package/dist/services/dispatch/merge-back-runner.js +5 -5
- package/dist/services/dispatch/service-shutdown.js +3 -3
- package/dist/services/doc/doc-generator.js +2 -1
- package/dist/services/env/shell-probe.js +1 -1
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
- package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
- package/dist/services/hooks/auto-compact-hook-install.js +8 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
- package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
- package/dist/services/ide/ide-registry.d.ts +31 -0
- package/dist/services/ide/ide-registry.js +35 -0
- package/dist/services/ide/ide-types.d.ts +59 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/lint/detect-eslint.js +2 -2
- package/dist/services/lint/eslint-runner.js +3 -1
- package/dist/services/loop/evaluator-dispatcher.js +2 -1
- package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
- package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
- package/dist/services/memory/project-memory-service/store/paths.js +15 -6
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/best-practice-auto-trigger.js +1 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/release/version-precheck-service.d.ts +2 -1
- package/dist/services/release/version-precheck-service.js +82 -12
- package/dist/services/runtime/vendor-adapter.d.ts +29 -4
- package/dist/services/runtime/vendors/claude-code.js +1 -1
- package/dist/services/runtime/vendors/codex.js +1 -1
- package/dist/services/runtime/vendors/copilot.js +1 -1
- package/dist/services/sc/sc-service.js +1 -1
- package/dist/services/scan/diff-scope-service.js +2 -2
- package/dist/services/scan/file-size-scan.js +2 -2
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/scan/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +91 -14
- package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
- package/dist/services/skills/session-start-hook-constants.js +86 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +31 -12
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
- package/dist/services/workspace/workspace-service.js +8 -0
- package/dist/services/worktree/host-worktree-reconciler.js +1 -0
- package/dist/services/worktree/long-path-cleanup.js +3 -2
- package/dist/shared/process.js +1 -1
- package/package.json +6 -6
- package/scripts/install-skills.mjs +1 -0
- package/scripts/watch.mjs +3 -1
- package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +8 -6
- package/skills/bee/peaks-qa/SKILL.md +7 -7
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +10 -8
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +3 -3
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
- package/skills/peaks-content/SKILL.md +1 -1
- package/skills/peaks-doctor/SKILL.md +1 -1
- package/skills/peaks-final-review/SKILL.md +1 -1
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-sop/SKILL.md +1 -1
- package/skills/peaks-status/SKILL.md +1 -1
- 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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `src/services/workflow/
|
|
16
|
-
*
|
|
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
|
|
239
|
-
*
|
|
240
|
-
* `
|
|
241
|
-
*
|
|
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
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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 = '
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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.
|
|
80
|
-
*
|
|
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;
|