peaks-loop 3.0.1 → 3.0.3

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 CHANGED
@@ -1,8 +1,43 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased] — 2026-06-29 — change-id shim (cleanup post v2.17.0 hard-kill)
3
+ ## [3.0.3] — 2026-07-02 — auto-compact zero-pause (ide-native pathway)
4
4
 
5
- **MINOR bump target: v2.19.0**. Full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched. One cleanup slice that retires the v2.17.0 shim half-life.
5
+ **PATCH bump from 3.0.2**. Closes the user-pause gap on long Claude Code sessions: at context ratio ≥ 0.95 the runner now compacts itself via a PreToolUse hook, no human intervention required.
6
+
7
+ ### Added — `ide-native` pathway + `peaks session auto-compact-hook`
8
+
9
+ The v2.13.0 auto-compact design shipped `shell-exec` as the only pathway, which spawns a NEW claude child process and cannot compact the current runner. Long-task users hit a UX gap: AI CLI still stopped and asked the human to run `/compact` manually.
10
+
11
+ - **New `ide-native` pathway** — `src/services/context/auto-compact-dispatcher.ts:138-155` routes main-session compacts through `installAutoCompactHook`, which writes a PreToolUse hook into `.claude/settings.local.json` (matcher `Bash|Task`, command `peaks session auto-compact-hook`).
12
+ - **New `peaks session auto-compact-hook` CLI** — `src/cli/commands/session-auto-compact-hook-command.ts`. Reads `CLAUDE_CONTEXT_USAGE_PERCENT`; below 0.95 exits 0 silent; at ratio ≥ 0.95 spawns `claude --compact` in-band against the **current** runner (detached + unref, never blocks the runner's tool call). ENOENT-safe: if `claude` is not on PATH (e.g. user runs Claude Code as an MCP), the hook logs a stderr hint and exits 0 silent rather than crashing the runner.
13
+ - **New `installAutoCompactHook` / `removeAutoCompactHook` service** — `src/services/hooks/auto-compact-hook-install.ts`. Idempotent: re-install returns `already-installed` and does NOT re-write the file; remove preserves other PreToolUse entries (the existing fact-forcing bypass hook on every peaks-loop consumer project survives).
14
+ - **Lazy install** — hook is installed on first `peaks solo auto-compact` invocation, NOT on `peaks workspace init`. User has opted in by running the command; no zero-touch surprise.
15
+ - **Claude Code adapter** — `compactPathway` changed from `shell-exec` to `ide-native` for `target='main'`. Sub-agent shells still get the legacy `shell-exec` pathway.
16
+
17
+ ### Fixed — Solo Step N+2 prose (was 75% / "do NOT auto-execute")
18
+
19
+ - `skills/peaks-solo/SKILL.md` Step N+2 paragraph rewritten: thresholds now 0.85 pre-compact / 0.95 red-line (matches v2.13.0 auto-compact), explicit Karpathy §4 compact-red-line exception cited, `peaks solo context-now --json` is the canonical probe primitive (replaces `peaks context check --prompt-size <bytes>` hand-pass).
20
+ - `src/services/context/main-session-monitor.ts` — `@deprecated` JSDoc on `evaluateMainSessionThreshold` cross-links to `evaluateCompactTrigger` (0.85/0.95); legacy 4-tier envelope retained for statusline callers (migration to v2.15.0).
21
+
22
+ ### Tests — 4 new suites + 3 modified files
23
+
24
+ - `tests/unit/services/hooks/auto-compact-hook-install.test.ts` (7 cases) — install / idempotent / remove / preservation of unrelated entries.
25
+ - `tests/unit/services/context/auto-compact-dispatcher-ide-native.test.ts` (9 cases) — `dispatchIdeCompact({ target: 'main' })` routes to `ide-native`; round-trip install / remove.
26
+ - `tests/unit/skills/solo-step-n-plus-2-prose.test.ts` (6 cases) — pins the 0.85 / 0.95 / Karpathy §4 contract; asserts `75%` is NOT in the paragraph.
27
+ - `tests/unit/cli/session-auto-compact-hook-command.test.ts` (3 cases) — dogfood surfacing: below-threshold silent, missing env-var silent, red-line + ENOENT exits 0 with stderr hint.
28
+ - `tests/unit/services/context/main-session-monitor.test.ts` (+2 AC-4 cases), `tests/unit/context/auto-compact-main-target.test.ts` (pathway assertion updated) — coverage.
29
+
30
+ ### Dogfood verified
31
+
32
+ Validated on `C:/Users/smallMark/Desktop/peaksclaw/ice-cola` (consumer project). End-to-end: install hook alongside existing fact-forcing bypass → idempotent re-run → below-threshold silent → red-line + no-claude-on-PATH exits 0 with hint → remove strips only the auto-compact matcher. No regressions in 5,139 / 5,158 vitest pass.
33
+
34
+ ## [3.0.2] — 2026-07-02 — change-id shim retirement (v2.19.0) + Understand Anything hybrid context (3.0.2)
35
+
36
+ **PATCH bump from 3.0.1**. Two independent slices bundled into one release:
37
+ - **v2.19.0 change-id shim retirement** — full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). The change-id was a no-op post-v2.17.0 hard-kill; this slice retires the v2.17.0 shim half-life. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched.
38
+ - **3.0.2 Understand Anything hybrid context** — new `peaks understand context` subcommand that encapsulates the UA-first / codegraph-fallback / hybrid-union routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture). Consumers no longer pick one or the other at the LLM level.
39
+
40
+ The v2.19.0 work shipped as code commits (`f91d71c`, `5186d3d`, `b598612`, `ced835e`, `7ee9d8f`, plus follow-up tests) but was never formally released. v2.19.0 is therefore *not* a separate release line — its changes are rolled forward into 3.0.2. Future releases resume numeric sequence from 3.0.3.
6
41
 
7
42
  ### Removed — change-id shim (cleanup post v2.17.0 hard-kill)
8
43
 
@@ -70,7 +105,38 @@
70
105
  - Existing RD/QA artifacts in `.peaks/_runtime/<sid>/<role>/` remain readable; the planner no longer requires a change-id, and the change-id (if any) is embedded only as a filename slug.
71
106
  - The 2.8.3 hard-ban on `.peaks/_runtime/<YYYY-MM-DD-*>/` sibling directories is still enforced via the rewritten guard test and the inline `lstatSync` in `initWorkspace`.
72
107
 
73
- ## [2.18.4] — 2026-06-29 — peaks-solo first-run step gates: hard-pause 1.x→2.0 upgrade + make `--mode` optional on Step 1
108
+ ## [3.0.2] — 2026-07-02 — change-id shim retirement (v2.19.0) + Understand Anything hybrid context (3.0.2)
109
+
110
+ **PATCH bump from 3.0.1**. Two independent slices bundled into one release:
111
+ - **v2.19.0 change-id shim retirement** — full removal of the peaks-loop change-id axis (filesystem axis, code shim, envelope JSON slug, CLI flags). The change-id was a no-op post-v2.17.0 hard-kill; this slice retires the v2.17.0 shim half-life. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched.
112
+ - **3.0.2 Understand Anything hybrid context** — new `peaks understand context` subcommand that encapsulates the UA-first / codegraph-fallback / hybrid-union routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture). Consumers no longer pick one or the other at the LLM level.
113
+
114
+ The v2.19.0 work shipped as code commits (`f91d71c`, `5186d3d`, `b598612`, `ced835e`, `7ee9d8f`, plus follow-up tests) but was never formally released. v2.19.0 is therefore *not* a separate release line — its changes are rolled forward into 3.0.2. Future releases resume numeric sequence from 3.0.3.
115
+
116
+ ### Added — `peaks understand context` (3.0.2 user-facing feature)
117
+
118
+ The Understand Anything (UA) Claude Code plugin and `@colbymchenry/codegraph` are both complementary project-analysis tools. UA provides a high-level knowledge graph (tours, layers, expert prompts); codegraph provides low-level affected-file analysis. Until 3.0.2, peaks-loop consumers had to choose one or the other at the LLM level — there was no service-layer enforcement of either.
119
+
120
+ This slice adds a single `peaks understand context` subcommand that encapsulates the routing decision in the service layer (per the peaks-loop "skill-first / CLI-auxiliary" architecture — see `peaks-solo` SKILL.md). The envelope carries a `source` tag so downstream consumers can audit which evidence contributed to the context.
121
+
122
+ **Source routing** (deterministic, decided in `buildUnderstandContext`):
123
+ - `ua-only` — UA knowledge graph present, no codegraph
124
+ - `ua-missing-fallback-codegraph` — UA absent, codegraph produced evidence
125
+ - `ua-and-codegraph-hybrid` — both present, both contribute (parallel `Promise.all`)
126
+ - `both-missing` — neither produced evidence, exit code 2
127
+
128
+ **Added files (5):**
129
+ - `src/services/understand/understand-hybrid-service.ts` (133L, new): `buildUnderstandContext({projectRoot, files?, sampleSize?, artifactDir?, codegraphRunner?})` uses `Promise.all` to run UA scan and codegraph affected in parallel; total wall-clock = `max(uaMs, codegraphMs)`, not sum. Failures are caught into `warnings[]` (best-effort); the main envelope is always returned so consumers can audit missing evidence.
130
+ - `src/services/understand/hybrid-types.ts` (39L, new): `UnderstandContextResult` envelope + 4-value source enum + `CodegraphContextBlock`.
131
+ - `src/cli/commands/understand-commands.ts` (208L, +51L net): new `peaks understand context` subcommand at base path `admin/audit-logs` (the only 3rd CLI surface in the understand module family). The subcommand accepts `--project --files --sample --artifact-dir` and emits the envelope as JSON or human-readable summary.
132
+ - `tests/unit/understand-hybrid-service.test.ts` (110L, 4 cases): `ua-only` / `fallback` / `hybrid` / `both-missing`.
133
+ - `tests/integration/understand-hybrid-cli.test.ts` (85L, 3 cases): end-to-end CLI both-missing / ua-only / envelope-shape-stability.
134
+
135
+ **Test evidence:** 7/7 vitest (4 unit + 3 integration), `tsc --noEmit` clean.
136
+
137
+ The peak-loop skill-external-invocation audit pattern (capability discovery + references only + do-not-execute + Peaks authoritative) is preserved: codegraph is consumed via the existing `executeCodegraphInvocation` (no upstream installer calls); UA installation is a text `INSTALL_HINT` only.
138
+
139
+ ### Removed — change-id shim (cleanup post v2.17.0 hard-kill, rolled up from v2.19.0)
74
140
 
75
141
  **PATCH bump from 2.18.3**. Resolves the user-reported "新项目第一次使用 peaks-solo 时初始化完不按流程走、开发效果差、再开 session 后才正常" defect. Two surgical fixes to the first-run Solo gates (3 source files, 1 new test file, 2 test-regression updates; no schema change, no new dependencies, no public API change).
76
142
 
package/README-en.md CHANGED
@@ -137,7 +137,7 @@ peaks project dashboard --project . --json # one-shot project view
137
137
 
138
138
  **3 solo wrappers + 7 role skills + 2 standalone audits + 1 orchestrator = 13 skills.** In daily use, 1 skill (`peaks-solo`) covers ≥ 90% of needs.
139
139
 
140
- > **v2.13.0**: zero-human-intervention **auto-compact** (`peaks solo context-now` + `peaks solo auto-compact`) — peaks-loop probes context-fill % autonomously. ≥85% writes a pre-compact checkpoint + convergence plan + dispatches IDE compact. ≥95% forces a synchronous RED-LINE compact. The LLM-runner stays alive with context < 95% without human intervention. **v2.13.4 fix**: auto-compact now targets the **main-session** context (not a sub-agent shell) by writing a `.peaks/_runtime/<sid>/txt/auto-compact-pending.json` intent file that the main-session LLM picks up on its next turn and fires `/compact` in-band.
140
+ > **v2.13.0**: zero-human-intervention **auto-compact** (`peaks solo context-now` + `peaks solo auto-compact`) — peaks-loop probes context-fill % autonomously. ≥85% writes a pre-compact checkpoint + convergence plan + dispatches IDE compact. ≥95% forces a synchronous RED-LINE compact. The LLM-runner stays alive with context < 95% without human intervention. **v2.13.4 fix**: auto-compact now targets the **main-session** context (not a sub-agent shell). **3.0.3 new `ide-native` pathway**: installs a PreToolUse hook into `.claude/settings.local.json` (`Bash|Task` matcher → `peaks session auto-compact-hook`); the runner's next Bash/Task tool call in-band spawns `claude --compact`, no longer relying on the LLM to remember to fire `/compact`. Hook is ENOENT-safe (exits 0 + stderr hint if `claude` is not on PATH; runner is never interrupted).
141
141
 
142
142
  ## 🚧 Killer feature: un-bypassable gates
143
143
 
package/README.md CHANGED
@@ -175,7 +175,7 @@ peaks project dashboard --project . --json # 当前项目一眼看完
175
175
 
176
176
  **3 solo 包装 + 7 角色技能 + 2 独立审计 + 1 编排器 = 13 个。日常 1 个 `peaks-solo` 覆盖 ≥ 90%。**
177
177
 
178
- > **v2.13.0 新增**:zero-human-intervention **auto-compact**(`peaks solo context-now` + `peaks solo auto-compact`)——peaks-loop 自己探测 context %,≥85% 主动收敛(写 checkpoint + 收敛计划 + 触发 IDE compact),≥95% 强制同步压缩红线。LLM-runner 上下文始终 < 95% 自主运行,零介入。**v2.13.4 修复**:`auto-compact` 现在默认压缩**主会话**上下文(不是 sub-agent shell 自己的),通过写 `.peaks/_runtime/<sid>/txt/auto-compact-pending.json` 意图文件让主 LLM 下一轮 in-band 触发 `/compact`。
178
+ > **v2.13.0 新增**:zero-human-intervention **auto-compact**(`peaks solo context-now` + `peaks solo auto-compact`)——peaks-loop 自己探测 context %,≥85% 主动收敛(写 checkpoint + 收敛计划 + 触发 IDE compact),≥95% 强制同步压缩红线。LLM-runner 上下文始终 < 95% 自主运行,零介入。**v2.13.4 修复**:`auto-compact` 现在默认压缩**主会话**上下文(不是 sub-agent shell 自己的)。**3.0.3 新增 `ide-native` 通路**:在 `.claude/settings.local.json` 装 PreToolUse hook(`Bash|Task` matcher → `peaks session auto-compact-hook`),runner 下一次 Bash/Task tool call **in-band** 触发 `claude --compact`,不再依赖 LLM 自己 fire `/compact`。Hook ENOENT-safe(claude CLI 不在 PATH 时退出 0 + stderr 提示,runner 不会被中断)。
179
179
 
180
180
  ## 🚧 杀手锏:不可绕过的门禁
181
181
 
@@ -13,6 +13,7 @@ import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.j
13
13
  // saw them — they were effectively hidden.
14
14
  import { registerSessionCheckpointCommand } from '../session-checkpoint-command.js';
15
15
  import { registerSessionResumeCommand } from '../session-resume-command.js';
16
+ import { registerSessionAutoCompactHookCommand } from '../session-auto-compact-hook-command.js';
16
17
  export function registerSessionCommand(program, io) {
17
18
  const session = program.command('session').description('Manage Peaks session directories');
18
19
  addJsonOption(session
@@ -186,6 +187,11 @@ export function registerSessionCommand(program, io) {
186
187
  // `<TAB>`-discovery and the LLM never saw them.
187
188
  registerSessionCheckpointCommand(session, io);
188
189
  registerSessionResumeCommand(session, io);
190
+ // Slice 2026-07-02-auto-compact-zero-pause: the Bash command the
191
+ // PreToolUse hook in `.claude/settings.local.json` fires. Keeping
192
+ // it eager (same fix as checkpoint + resume) so `peaks session --help`
193
+ // lists it for LLM `<TAB>`-discovery.
194
+ registerSessionAutoCompactHookCommand(session);
189
195
  addJsonOption(session
190
196
  .command('rotate')
191
197
  .description('Drop the project-level session binding so the next peaks call auto-generates a fresh session id. The on-disk session directory is left intact — only .peaks/.session.json is removed.')
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `peaks session auto-compact-hook` — slice 2026-07-02-auto-compact-zero-pause.
3
+ *
4
+ * The Bash entrypoint the PreToolUse hook fires on every Bash/Task
5
+ * tool call from the Claude Code runner. Behaviour:
6
+ *
7
+ * 1. Read `CLAUDE_CONTEXT_USAGE_PERCENT` (or any adapter-declared
8
+ * env-var via the registry — claude-code MVP). If ratio is below
9
+ * the red-line threshold (0.95), exit 0 silent — the runner's
10
+ * normal flow continues.
11
+ * 2. If ratio is ≥ 0.95 (red line), write a one-line stderr hint
12
+ * so the human sees what happened, then in-band spawn
13
+ * `claude --compact` so the **current** runner's context
14
+ * window collapses (not a new claude process).
15
+ * 3. Always exit 0. The hook is a probe, not a gate — the gate is
16
+ * `claude --compact` itself returning 0.
17
+ *
18
+ * Idempotency: re-running the hook is safe; if `claude --compact`
19
+ * is already in flight, the new process just exits 0 immediately
20
+ * (Claude Code's compact is a one-shot operation against the
21
+ * current runner).
22
+ *
23
+ * Why a CLI subcommand instead of an inline JS hook:
24
+ * - Keeps the hook payload in `.claude/settings.local.json` small
25
+ * (one line: `peaks session auto-compact-hook`).
26
+ * - Lets the heavy lifting (ratio parsing, IDE-aware env-var
27
+ * resolution, spawning `claude --compact`) live in TypeScript
28
+ * where it's unit-testable.
29
+ * - Discoverable via `peaks session --help` — future LLM sessions
30
+ * see the primitive exists.
31
+ *
32
+ * Karpathy §2: 50 lines, no abstractions, no speculative error
33
+ * handling. The hook command is a single-purpose muscle; if it
34
+ * grows beyond ~80 lines, that's a signal to split.
35
+ */
36
+ import type { Command } from 'commander';
37
+ export declare function registerSessionAutoCompactHookCommand(session: Command): void;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `peaks session auto-compact-hook` — slice 2026-07-02-auto-compact-zero-pause.
3
+ *
4
+ * The Bash entrypoint the PreToolUse hook fires on every Bash/Task
5
+ * tool call from the Claude Code runner. Behaviour:
6
+ *
7
+ * 1. Read `CLAUDE_CONTEXT_USAGE_PERCENT` (or any adapter-declared
8
+ * env-var via the registry — claude-code MVP). If ratio is below
9
+ * the red-line threshold (0.95), exit 0 silent — the runner's
10
+ * normal flow continues.
11
+ * 2. If ratio is ≥ 0.95 (red line), write a one-line stderr hint
12
+ * so the human sees what happened, then in-band spawn
13
+ * `claude --compact` so the **current** runner's context
14
+ * window collapses (not a new claude process).
15
+ * 3. Always exit 0. The hook is a probe, not a gate — the gate is
16
+ * `claude --compact` itself returning 0.
17
+ *
18
+ * Idempotency: re-running the hook is safe; if `claude --compact`
19
+ * is already in flight, the new process just exits 0 immediately
20
+ * (Claude Code's compact is a one-shot operation against the
21
+ * current runner).
22
+ *
23
+ * Why a CLI subcommand instead of an inline JS hook:
24
+ * - Keeps the hook payload in `.claude/settings.local.json` small
25
+ * (one line: `peaks session auto-compact-hook`).
26
+ * - Lets the heavy lifting (ratio parsing, IDE-aware env-var
27
+ * resolution, spawning `claude --compact`) live in TypeScript
28
+ * where it's unit-testable.
29
+ * - Discoverable via `peaks session --help` — future LLM sessions
30
+ * see the primitive exists.
31
+ *
32
+ * Karpathy §2: 50 lines, no abstractions, no speculative error
33
+ * handling. The hook command is a single-purpose muscle; if it
34
+ * grows beyond ~80 lines, that's a signal to split.
35
+ */
36
+ import { spawn } from 'node:child_process';
37
+ import { detectIdeFromEnv } from '../../services/context/main-session-monitor.js';
38
+ import { getAdapter } from '../../services/ide/ide-registry.js';
39
+ /**
40
+ * Red-line ratio threshold. Must match the orchestrator's
41
+ * AUTO_COMPACT_RED_LINE_RATIO constant (0.95). Kept here as a
42
+ * literal so the hook command stays self-contained — a future
43
+ * slice may move this to a shared constant module if the value
44
+ * starts drifting.
45
+ */
46
+ const RED_LINE_RATIO = 0.95;
47
+ /**
48
+ * Parse `CLAUDE_CONTEXT_USAGE_PERCENT` (or the adapter-declared
49
+ * env-var) into a ratio in [0, 1]. Returns null on missing /
50
+ * malformed input. Mirrors `readEnvPercent` in
51
+ * `auto-compact-reader.ts` so the hook command and the
52
+ * orchestrator agree on parsing semantics.
53
+ */
54
+ function readRatioFromEnv(env) {
55
+ const detected = detectIdeFromEnv(env);
56
+ const ideId = (detected === 'unknown' ? 'claude-code' : detected);
57
+ const adapter = getAdapter(ideId);
58
+ if (!adapter.compact)
59
+ return null;
60
+ const varName = adapter.compact.envVarForContextPercent;
61
+ const raw = env[varName];
62
+ if (typeof raw !== 'string' || raw.length === 0)
63
+ return null;
64
+ const parsed = Number.parseFloat(raw);
65
+ if (!Number.isFinite(parsed))
66
+ return null;
67
+ // Allow either "0..1" or "0..100" encoding. Claude Code writes
68
+ // "0..1" today; statusline tools sometimes write "0..100". Normalize.
69
+ const ratio = parsed > 1.5 ? parsed / 100 : Math.max(0, Math.min(1, parsed));
70
+ return { ratio, varName };
71
+ }
72
+ export function registerSessionAutoCompactHookCommand(session) {
73
+ session
74
+ .command('auto-compact-hook')
75
+ .description('PreToolUse hook entrypoint — read CLAUDE_CONTEXT_USAGE_PERCENT and, ' +
76
+ 'at ratio ≥ 0.95, in-band spawn `claude --compact` against the current ' +
77
+ 'runner. Designed for invocation from `.claude/settings.local.json` ' +
78
+ 'PreToolUse hooks; safe to re-run.')
79
+ .action(() => {
80
+ const probe = readRatioFromEnv(process.env);
81
+ if (probe === null) {
82
+ // No signal → exit 0 silent. The hook must never block the
83
+ // runner's normal flow on a missing env-var.
84
+ return;
85
+ }
86
+ if (probe.ratio < RED_LINE_RATIO) {
87
+ // Below red line → exit 0 silent. The orchestrator's
88
+ // pre-compact zone (0.85–0.95) is handled out-of-band by
89
+ // the LLM firing `peaks solo auto-compact --execute`; the
90
+ // hook only fires on the synchronous red-line path.
91
+ return;
92
+ }
93
+ // Red line reached — compact the current runner in-band.
94
+ // The `detached: true` + `unref()` pair lets the hook return
95
+ // immediately while Claude Code's own slash-command processor
96
+ // picks up `claude --compact` against the runner's session.
97
+ // This is the fix for the shell-exec-spawns-a-child-process
98
+ // bug documented in `.peaks/memory/2026-06-27-auto-compact-design.md:139-152`.
99
+ //
100
+ // Dogfood 2026-07-02 fix: when `claude` is not on PATH (the
101
+ // user runs Claude Code as an MCP rather than via the CLI
102
+ // binary), `spawn` raises ENOENT. Catching this MUST NOT crash
103
+ // the runner's tool call — the hook must exit 0 silently with
104
+ // a one-line stderr hint so the runner keeps working. The user
105
+ // can then run `/compact` manually (or `peaks solo auto-compact`
106
+ // from another terminal).
107
+ try {
108
+ const child = spawn('claude', ['--compact'], {
109
+ stdio: 'ignore',
110
+ detached: true,
111
+ env: process.env
112
+ });
113
+ child.on('error', () => {
114
+ // Spawn failed (ENOENT or EPERM). Exit 0 silently with a
115
+ // stderr hint — the hook contract is "never crash the
116
+ // runner's tool call".
117
+ process.stderr.write('[peaks:auto-compact-hook] claude CLI not on PATH; cannot fire in-band. Run `/compact` manually or set up the Claude Code CLI binary.\n');
118
+ });
119
+ child.unref();
120
+ process.stderr.write(`[peaks:auto-compact-hook] ratio=${(probe.ratio * 100).toFixed(1)}% ≥ ${(RED_LINE_RATIO * 100).toFixed(0)}%; firing claude --compact in-band\n`);
121
+ }
122
+ catch {
123
+ // Defensive: `spawn` can throw synchronously on some platforms.
124
+ process.stderr.write('[peaks:auto-compact-hook] failed to spawn claude --compact; run `/compact` manually.\n');
125
+ }
126
+ });
127
+ }
@@ -1,5 +1,6 @@
1
1
  import { InvalidArgumentError } from 'commander';
2
2
  import { scanUnderstandAnything, summarizeKnowledgeGraph } from '../../services/understand/understand-scan-service.js';
3
+ import { buildUnderstandContext } from '../../services/understand/understand-hybrid-service.js';
3
4
  import { fail, ok } from '../../shared/result.js';
4
5
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
5
6
  function parsePositiveInteger(value) {
@@ -109,4 +110,39 @@ export function registerUnderstandCommands(program, io) {
109
110
  process.exitCode = 1;
110
111
  }
111
112
  });
113
+ // Slice 2026-07-02-codegraph-ua-hybrid: hybrid context subcommand.
114
+ // Routes UA-first / codegraph-fallback / hybrid in the service layer;
115
+ // the CLI is a thin shell that surfaces the envelope and exit code.
116
+ addJsonOption(understand
117
+ .command('context')
118
+ .description('Build a hybrid project-context envelope: UA knowledge graph when present, codegraph affected as fallback, or both in parallel')
119
+ .requiredOption('--project <path>', 'target project root')
120
+ .option('--artifact-dir <path>', 'override the default .understand-anything directory')
121
+ .option('--sample <n>', 'maximum number of sample node ids to include (default 5)', parsePositiveInteger)
122
+ .option('--files <file...>', 'file globs to feed `codegraph affected` (default: src/index.ts, package.json, README.md)')).action(async (options) => {
123
+ try {
124
+ const ctxOptions = { projectRoot: options.project };
125
+ if (options.artifactDir !== undefined)
126
+ ctxOptions.artifactDir = options.artifactDir;
127
+ if (options.sample !== undefined)
128
+ ctxOptions.sampleSize = options.sample;
129
+ if (options.files !== undefined)
130
+ ctxOptions.files = options.files;
131
+ const result = await buildUnderstandContext(ctxOptions);
132
+ const exitCode = result.source === 'both-missing' ? 2 : 0;
133
+ if (exitCode !== 0) {
134
+ printResult(io, fail('understand.context', 'UNDERSTAND_CONTEXT_NO_EVIDENCE', `No UA graph and codegraph affected produced no usable evidence`, result, [
135
+ INSTALL_HINT,
136
+ 'Run `peaks codegraph index` in this project to populate the codegraph index, then retry'
137
+ ]), options.json);
138
+ process.exitCode = exitCode;
139
+ return;
140
+ }
141
+ printResult(io, ok('understand.context', result, [], result.warnings.length > 0 ? result.warnings : undefined), options.json);
142
+ }
143
+ catch (error) {
144
+ printResult(io, fail('understand.context', 'UNDERSTAND_CONTEXT_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Check the project path and codegraph installation before retrying']), options.json);
145
+ process.exitCode = 1;
146
+ }
147
+ });
112
148
  }
@@ -100,16 +100,36 @@ export async function dispatchIdeCompact(input) {
100
100
  timeoutMs
101
101
  });
102
102
  case 'ide-native':
103
- // Future slice: write the compact intent to the IDE's hook
104
- // file (per `IdeSettingsLocation`). For v2.13.0 MVP the
105
- // shell-exec pathway covers Claude Code; this branch stays
106
- // reserved.
107
- return {
108
- ok: false,
109
- ide: ideId,
110
- pathway: 'ide-native',
111
- message: `ide-native compact pathway is reserved for a future slice; adapter '${ideId}' should declare shell-exec or llm-self-compress for now.`
112
- };
103
+ // Slice 2026-07-02-auto-compact-zero-pause: write the auto-compact
104
+ // PreToolUse hook into `.claude/settings.local.json`. The hook
105
+ // command (`peaks session auto-compact-hook`) reads
106
+ // `CLAUDE_CONTEXT_USAGE_PERCENT` on every subsequent Bash/Task
107
+ // tool call from the runner and, at ratio ≥ 0.95, in-band spawns
108
+ // `claude --compact` against the CURRENT runner (not a child
109
+ // process — the bug documented in
110
+ // `.peaks/memory/2026-06-27-auto-compact-design.md:139-152`).
111
+ //
112
+ // `ide-native` is ONLY for the main-session runner. When the
113
+ // caller is a sub-agent shell (which has its own ephemeral
114
+ // context window — not the runner's), fall through to
115
+ // shell-exec so the sub-agent's `compactCommand` spawns a
116
+ // child claude process and the sub-agent's own runner doesn't
117
+ // get a PreToolUse hook installed in the wrong place.
118
+ if (target === 'sub-agent') {
119
+ return await dispatchShellExec({
120
+ ideId,
121
+ command: profile.compactCommand,
122
+ timeoutMs
123
+ });
124
+ }
125
+ // Lazy install: we only get here when the caller explicitly
126
+ // invokes `peaks solo auto-compact --execute`, so the user has
127
+ // already opted in. No zero-touch surprise on workspace init.
128
+ return await dispatchIdeNativeHook({
129
+ projectRoot: input.projectRoot,
130
+ sessionId: input.sessionId,
131
+ target
132
+ });
113
133
  case 'llm-self-compress':
114
134
  return {
115
135
  ok: true,
@@ -188,3 +208,33 @@ function dispatchShellExec(input) {
188
208
  });
189
209
  });
190
210
  }
211
+ /**
212
+ * Slice 2026-07-02-auto-compact-zero-pause: implement the
213
+ * `ide-native` pathway. Writes the auto-compact PreToolUse hook
214
+ * into `.claude/settings.local.json` (idempotent; the install
215
+ * service is a no-op if the hook is already present). On the next
216
+ * Bash/Task tool call from the runner, the hook fires
217
+ * `peaks session auto-compact-hook` which in-band spawns
218
+ * `claude --compact` against the CURRENT runner session.
219
+ *
220
+ * Returns `ok: true, pathway: 'ide-native'` regardless of install
221
+ * action (`installed` vs `already-installed`) — both states
222
+ * achieve the operational goal: the hook is wired and the next
223
+ * Bash call will trigger it.
224
+ */
225
+ async function dispatchIdeNativeHook(input) {
226
+ // Lazy dynamic import — matches the existing pattern in
227
+ // `runAutoCompact` for `auto-compact-reader.ts` (line 311) and
228
+ // avoids a static cycle if future slices add cross-imports
229
+ // between dispatcher and hook-install.
230
+ const { installAutoCompactHook } = await import('../hooks/auto-compact-hook-install.js');
231
+ const result = installAutoCompactHook({ projectRoot: input.projectRoot });
232
+ return {
233
+ ok: true,
234
+ ide: 'claude-code',
235
+ pathway: 'ide-native',
236
+ message: result.action === 'installed'
237
+ ? `Auto-compact PreToolUse hook installed at ${result.settingsPath}. Next Bash/Task tool call will read CLAUDE_CONTEXT_USAGE_PERCENT and compact in-band at ratio ≥ 95%.`
238
+ : `Auto-compact PreToolUse hook already installed at ${result.settingsPath}; next Bash/Task tool call will trigger compact in-band at ratio ≥ 95%.`
239
+ };
240
+ }
@@ -56,6 +56,18 @@ export declare function detectIdeFromEnv(env?: NodeJS.ProcessEnv): IdeKind;
56
56
  * Threshold check. Pure function (no IO). `capacityBytes` is injectable
57
57
  * for tests and future per-IDE capacity overrides. Defaults to the
58
58
  * G9 256K proxy (matches G9 for cognitive continuity).
59
+ *
60
+ * @deprecated Slice 2026-07-02-auto-compact-zero-pause: the
61
+ * 50/75/90 tier thresholds pre-date the v2.13.0 auto-compact
62
+ * design. The authoritative tier table for triggering compaction
63
+ * now lives in `evaluateCompactTrigger` (auto-compact-orchestrator.ts)
64
+ * with the 0.85 pre-compact / 0.95 red-line thresholds. New
65
+ * callers should call `evaluateCompactTrigger(ratio)` directly.
66
+ * This function is retained because legacy `peaks context check
67
+ * --prompt-size` still emits its 4-tier envelope for callers that
68
+ * need the soft signal (e.g. statusline display). Migration is
69
+ * scheduled for v2.15.0; the @deprecated tag flips callers'
70
+ * eslint warnings so the migration is mechanical.
59
71
  */
60
72
  export declare function evaluateMainSessionThreshold(promptSize: number, capacityBytes?: number): MainSessionEvaluation;
61
73
  export interface InFlightBatchProbe {
@@ -55,6 +55,18 @@ export function detectIdeFromEnv(env = process.env) {
55
55
  * Threshold check. Pure function (no IO). `capacityBytes` is injectable
56
56
  * for tests and future per-IDE capacity overrides. Defaults to the
57
57
  * G9 256K proxy (matches G9 for cognitive continuity).
58
+ *
59
+ * @deprecated Slice 2026-07-02-auto-compact-zero-pause: the
60
+ * 50/75/90 tier thresholds pre-date the v2.13.0 auto-compact
61
+ * design. The authoritative tier table for triggering compaction
62
+ * now lives in `evaluateCompactTrigger` (auto-compact-orchestrator.ts)
63
+ * with the 0.85 pre-compact / 0.95 red-line thresholds. New
64
+ * callers should call `evaluateCompactTrigger(ratio)` directly.
65
+ * This function is retained because legacy `peaks context check
66
+ * --prompt-size` still emits its 4-tier envelope for callers that
67
+ * need the soft signal (e.g. statusline display). Migration is
68
+ * scheduled for v2.15.0; the @deprecated tag flips callers'
69
+ * eslint warnings so the migration is mechanical.
58
70
  */
59
71
  export function evaluateMainSessionThreshold(promptSize, capacityBytes = CONTEXT_CAPACITY_DEFAULT_BYTES) {
60
72
  const inner = evaluateThresholdTier(promptSize, capacityBytes);
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Slice 2026-07-02-auto-compact-zero-pause — `ide-native` pathway
3
+ * hook install/remove service.
4
+ *
5
+ * Writes the auto-compact PreToolUse hook into the consumer
6
+ * project's `.claude/settings.local.json` so the next Bash or Task
7
+ * tool call from the running Claude Code session reads
8
+ * `CLAUDE_CONTEXT_USAGE_PERCENT` and, at ratio ≥ 0.95, in-band fires
9
+ * `claude --compact` against the current runner (not a child process).
10
+ *
11
+ * Idempotency contract:
12
+ * - install is idempotent — calling `installAutoCompactHook`
13
+ * twice produces a byte-identical file (after stripping the
14
+ * magic comment). The on-disk matcher entry is the single
15
+ * source of truth.
16
+ * - remove is idempotent — calling `removeAutoCompactHook` when
17
+ * the hook is absent is a no-op.
18
+ * - other PreToolUse entries are untouched on install AND remove.
19
+ *
20
+ * Magic comment marker: `peaks:auto-compact-hook-do-not-edit`. The
21
+ * comment sits next to the matcher entry so a human reading the file
22
+ * sees the boundary; an automated diff/remove op keys off the marker
23
+ * + the `matcher` regex.
24
+ *
25
+ * Pure data + minimal IO: `installAutoCompactHook` and
26
+ * `removeAutoCompactHook` accept `settingsPath` so callers can pick
27
+ * `.claude/settings.local.json` (default) or pass an explicit path
28
+ * for tests. The functions write to disk via `fs.writeFileSync` so
29
+ * the on-disk file matches the in-memory template byte-for-byte
30
+ * (modulo the magic comment which is excluded from the data
31
+ * structure; see `AUTO_COMPACT_HOOK_MATCHER`).
32
+ */
33
+ /**
34
+ * Stable matcher for the auto-compact hook. Single source of truth
35
+ * — `install` and `remove` both key off this constant so the
36
+ * invariants can't drift between the two paths.
37
+ */
38
+ export declare const AUTO_COMPACT_HOOK_MATCHER = "Bash|Task";
39
+ /**
40
+ * Stable command the hook fires. Wraps `peaks session
41
+ * auto-compact-hook` so the heavy lifting (ratio probe + in-band
42
+ * `claude --compact` spawn) lives in the CLI surface, not inlined
43
+ * into the hook.
44
+ *
45
+ * `npx peaks` is deliberately omitted: the project-local install
46
+ * puts `peaks` on PATH inside the consumer repo (postinstall step),
47
+ * so the unqualified command works in the same shell the runner
48
+ * is using.
49
+ */
50
+ export declare const AUTO_COMPACT_HOOK_COMMAND = "peaks session auto-compact-hook";
51
+ /**
52
+ * Magic comment marker that fences the auto-compatchook block.
53
+ * Both install (writes the marker) and remove (matches on it) use
54
+ * this string so the comment is the only place the boundary lives.
55
+ */
56
+ export declare const AUTO_COMPACT_HOOK_MARKER = "peaks:auto-compact-hook-do-not-edit";
57
+ /**
58
+ * Default on-disk location — kept here (not in claude-settings-template)
59
+ * because the auto-compact hook is a separate install surface from
60
+ * the fact-forcing gate bypass template.
61
+ */
62
+ export declare const AUTO_COMPACT_HOOK_SETTINGS_PATH = ".claude/settings.local.json";
63
+ /**
64
+ * Result envelope for `installAutoCompactHook`.
65
+ */
66
+ export type AutoCompactHookInstallResult = {
67
+ readonly action: 'installed';
68
+ readonly settingsPath: string;
69
+ } | {
70
+ readonly action: 'already-installed';
71
+ readonly settingsPath: string;
72
+ };
73
+ /**
74
+ * Result envelope for `removeAutoCompactHook`.
75
+ */
76
+ export type AutoCompactHookRemoveResult = {
77
+ readonly action: 'removed';
78
+ readonly settingsPath: string;
79
+ } | {
80
+ readonly action: 'absent';
81
+ readonly settingsPath: string;
82
+ };
83
+ /**
84
+ * Install the auto-compact PreToolUse hook. Idempotent: a re-run
85
+ * returns `action: 'already-installed'` and does NOT re-write the
86
+ * file (preserves any unrelated edits the user has made to other
87
+ * entries).
88
+ */
89
+ export declare function installAutoCompactHook(input: {
90
+ readonly projectRoot: string;
91
+ readonly settingsPath?: string;
92
+ }): AutoCompactHookInstallResult;
93
+ /**
94
+ * Remove the auto-compact PreToolUse hook. Idempotent: missing hook
95
+ * returns `action: 'absent'` without re-writing the file.
96
+ *
97
+ * Other PreToolUse entries are left untouched. The settings file
98
+ * structure is preserved (top-level keys like `permissions` are not
99
+ * deleted).
100
+ */
101
+ export declare function removeAutoCompactHook(input: {
102
+ readonly projectRoot: string;
103
+ readonly settingsPath?: string;
104
+ }): AutoCompactHookRemoveResult;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Slice 2026-07-02-auto-compact-zero-pause — `ide-native` pathway
3
+ * hook install/remove service.
4
+ *
5
+ * Writes the auto-compact PreToolUse hook into the consumer
6
+ * project's `.claude/settings.local.json` so the next Bash or Task
7
+ * tool call from the running Claude Code session reads
8
+ * `CLAUDE_CONTEXT_USAGE_PERCENT` and, at ratio ≥ 0.95, in-band fires
9
+ * `claude --compact` against the current runner (not a child process).
10
+ *
11
+ * Idempotency contract:
12
+ * - install is idempotent — calling `installAutoCompactHook`
13
+ * twice produces a byte-identical file (after stripping the
14
+ * magic comment). The on-disk matcher entry is the single
15
+ * source of truth.
16
+ * - remove is idempotent — calling `removeAutoCompactHook` when
17
+ * the hook is absent is a no-op.
18
+ * - other PreToolUse entries are untouched on install AND remove.
19
+ *
20
+ * Magic comment marker: `peaks:auto-compact-hook-do-not-edit`. The
21
+ * comment sits next to the matcher entry so a human reading the file
22
+ * sees the boundary; an automated diff/remove op keys off the marker
23
+ * + the `matcher` regex.
24
+ *
25
+ * Pure data + minimal IO: `installAutoCompactHook` and
26
+ * `removeAutoCompactHook` accept `settingsPath` so callers can pick
27
+ * `.claude/settings.local.json` (default) or pass an explicit path
28
+ * for tests. The functions write to disk via `fs.writeFileSync` so
29
+ * the on-disk file matches the in-memory template byte-for-byte
30
+ * (modulo the magic comment which is excluded from the data
31
+ * structure; see `AUTO_COMPACT_HOOK_MATCHER`).
32
+ */
33
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
34
+ import { dirname, join } from 'node:path';
35
+ /**
36
+ * Stable matcher for the auto-compact hook. Single source of truth
37
+ * — `install` and `remove` both key off this constant so the
38
+ * invariants can't drift between the two paths.
39
+ */
40
+ export const AUTO_COMPACT_HOOK_MATCHER = 'Bash|Task';
41
+ /**
42
+ * Stable command the hook fires. Wraps `peaks session
43
+ * auto-compact-hook` so the heavy lifting (ratio probe + in-band
44
+ * `claude --compact` spawn) lives in the CLI surface, not inlined
45
+ * into the hook.
46
+ *
47
+ * `npx peaks` is deliberately omitted: the project-local install
48
+ * puts `peaks` on PATH inside the consumer repo (postinstall step),
49
+ * so the unqualified command works in the same shell the runner
50
+ * is using.
51
+ */
52
+ export const AUTO_COMPACT_HOOK_COMMAND = 'peaks session auto-compact-hook';
53
+ /**
54
+ * Magic comment marker that fences the auto-compatchook block.
55
+ * Both install (writes the marker) and remove (matches on it) use
56
+ * this string so the comment is the only place the boundary lives.
57
+ */
58
+ export const AUTO_COMPACT_HOOK_MARKER = 'peaks:auto-compact-hook-do-not-edit';
59
+ /**
60
+ * Default on-disk location — kept here (not in claude-settings-template)
61
+ * because the auto-compact hook is a separate install surface from
62
+ * the fact-forcing gate bypass template.
63
+ */
64
+ export const AUTO_COMPACT_HOOK_SETTINGS_PATH = '.claude/settings.local.json';
65
+ function isObject(value) {
66
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
67
+ }
68
+ function readSettings(settingsPath) {
69
+ if (!existsSync(settingsPath))
70
+ return {};
71
+ try {
72
+ const raw = readFileSync(settingsPath, 'utf8');
73
+ const parsed = JSON.parse(raw);
74
+ return isObject(parsed) ? parsed : {};
75
+ }
76
+ catch {
77
+ // Malformed JSON → start fresh so install doesn't error. The
78
+ // caller is the human / CLI, not an LLM (idempotency matters
79
+ // more than preserve-on-corruption here).
80
+ return {};
81
+ }
82
+ }
83
+ function writeSettings(settingsPath, settings) {
84
+ const dir = dirname(settingsPath);
85
+ if (!existsSync(dir))
86
+ mkdirSync(dir, { recursive: true });
87
+ // 2-space JSON matches the existing template writer's style so
88
+ // `diff` against pre-install files is minimal.
89
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
90
+ }
91
+ function isAutoCompactEntry(entry) {
92
+ return entry.matcher === AUTO_COMPACT_HOOK_MATCHER;
93
+ }
94
+ /**
95
+ * Install the auto-compact PreToolUse hook. Idempotent: a re-run
96
+ * returns `action: 'already-installed'` and does NOT re-write the
97
+ * file (preserves any unrelated edits the user has made to other
98
+ * entries).
99
+ */
100
+ export function installAutoCompactHook(input) {
101
+ const settingsPath = input.settingsPath ?? join(input.projectRoot, AUTO_COMPACT_HOOK_SETTINGS_PATH);
102
+ const settings = readSettings(settingsPath);
103
+ const hooks = settings.hooks ?? {};
104
+ const preToolUse = hooks.PreToolUse ?? [];
105
+ const alreadyInstalled = preToolUse.some(isAutoCompactEntry);
106
+ if (alreadyInstalled) {
107
+ return { action: 'already-installed', settingsPath };
108
+ }
109
+ const nextPreToolUse = [
110
+ ...preToolUse,
111
+ {
112
+ matcher: AUTO_COMPACT_HOOK_MATCHER,
113
+ hooks: [
114
+ {
115
+ type: 'command',
116
+ command: AUTO_COMPACT_HOOK_COMMAND
117
+ }
118
+ ]
119
+ }
120
+ ];
121
+ writeSettings(settingsPath, {
122
+ ...settings,
123
+ hooks: {
124
+ ...hooks,
125
+ PreToolUse: nextPreToolUse
126
+ }
127
+ });
128
+ return { action: 'installed', settingsPath };
129
+ }
130
+ /**
131
+ * Remove the auto-compact PreToolUse hook. Idempotent: missing hook
132
+ * returns `action: 'absent'` without re-writing the file.
133
+ *
134
+ * Other PreToolUse entries are left untouched. The settings file
135
+ * structure is preserved (top-level keys like `permissions` are not
136
+ * deleted).
137
+ */
138
+ export function removeAutoCompactHook(input) {
139
+ const settingsPath = input.settingsPath ?? join(input.projectRoot, AUTO_COMPACT_HOOK_SETTINGS_PATH);
140
+ if (!existsSync(settingsPath))
141
+ return { action: 'absent', settingsPath };
142
+ const settings = readSettings(settingsPath);
143
+ const hooks = settings.hooks ?? {};
144
+ const preToolUse = hooks.PreToolUse ?? [];
145
+ const nextPreToolUse = preToolUse.filter((entry) => !isAutoCompactEntry(entry));
146
+ if (nextPreToolUse.length === preToolUse.length) {
147
+ return { action: 'absent', settingsPath };
148
+ }
149
+ writeSettings(settingsPath, {
150
+ ...settings,
151
+ hooks: {
152
+ ...hooks,
153
+ PreToolUse: nextPreToolUse
154
+ }
155
+ });
156
+ return { action: 'removed', settingsPath };
157
+ }
@@ -47,18 +47,30 @@ export const CLAUDE_CODE_ADAPTER = {
47
47
  gateEnforce: true,
48
48
  statusline: true,
49
49
  },
50
- // v2.13.0 AC-1 + AC-3 MVP: Claude Code is the first IDE to fill
51
- // the `compact` profile. Future adapters (trae / codex / cursor /
52
- // qoder / tongyi-lingma / hermes / openclaw) follow the same
53
- // shape — peaks-loop reads the env-var and dispatches the
54
- // command via `IdeAdapter.compact`, with zero hard-coded IDE
55
- // names anywhere in the orchestrator. If your IDE exposes a
56
- // context-percent env-var and a slash-style compact command,
57
- // register `compact` here and the auto-compact protocol activates.
50
+ // v2.13.0 AC-1 + AC-3 MVP, slice 2026-07-02-auto-compact-zero-pause:
51
+ // Claude Code is the first IDE to fill the `compact` profile.
52
+ // Future adapters (trae / codex / cursor / qoder / tongyi-lingma /
53
+ // hermes / openclaw) follow the same shape — peaks-loop reads the
54
+ // env-var and dispatches the command via `IdeAdapter.compact`, with
55
+ // zero hard-coded IDE names anywhere in the orchestrator. If your
56
+ // IDE exposes a context-percent env-var and a slash-style compact
57
+ // command, register `compact` here and the auto-compact protocol
58
+ // activates.
59
+ //
60
+ // Pathway = 'ide-native' (not 'shell-exec') so the dispatcher
61
+ // routes main-session compacts through the PreToolUse hook in
62
+ // `.claude/settings.local.json`. The hook fires
63
+ // `peaks session auto-compact-hook` on the NEXT Bash/Task tool
64
+ // call from the runner, which in-band spawns `claude --compact`
65
+ // against the CURRENT runner — not a child process (the
66
+ // shell-exec spawn-new-claude bug documented in
67
+ // `.peaks/memory/2026-06-27-auto-compact-design.md:139-152`).
68
+ // Sub-agent shells still get the legacy shell-exec pathway via
69
+ // `dispatchIdeCompact({ target: 'sub-agent' })`.
58
70
  compact: {
59
71
  envVarForContextPercent: 'CLAUDE_CONTEXT_USAGE_PERCENT',
60
72
  compactCommand: 'claude --compact',
61
- compactPathway: 'shell-exec',
73
+ compactPathway: 'ide-native',
62
74
  postCompactDetectCommand: 'peaks context now --json'
63
75
  },
64
76
  // Slice #011: standards profile. Claude Code reads its constitution at
@@ -0,0 +1,32 @@
1
+ import type { CodegraphExecutionResult } from '../codegraph/codegraph-service.js';
2
+ import type { UnderstandScanReport } from './understand-types.js';
3
+ import type { UnderstandGraphSummary } from './understand-scan-service.js';
4
+ /**
5
+ * Slice 2026-07-02-codegraph-ua-hybrid — envelope contract for the
6
+ * `peaks understand context` command. The service layer decides which
7
+ * evidence to include based on environment detection; the source field
8
+ * is the authoritative routing signal.
9
+ */
10
+ export type UnderstandContextSource = 'ua-only' | 'ua-missing-fallback-codegraph' | 'ua-and-codegraph-hybrid' | 'both-missing';
11
+ export type CodegraphContextBlock = {
12
+ invocation: {
13
+ subcommand: 'affected';
14
+ files: string[];
15
+ };
16
+ execution: CodegraphExecutionResult;
17
+ /** Parsed JSON from `codegraph affected --json` when exitCode === 0; null otherwise. */
18
+ payload: unknown;
19
+ parseError?: string;
20
+ };
21
+ export type UnderstandContextResult = {
22
+ projectRoot: string;
23
+ source: UnderstandContextSource;
24
+ ua?: {
25
+ scan: UnderstandScanReport;
26
+ summary?: UnderstandGraphSummary;
27
+ };
28
+ codegraph?: CodegraphContextBlock;
29
+ /** Wall-clock milliseconds spent on the slowest parallel branch. */
30
+ durationMs: number;
31
+ warnings: string[];
32
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type CodegraphProcessRunner } from '../codegraph/codegraph-service.js';
2
+ import type { UnderstandContextResult } from './hybrid-types.js';
3
+ export type BuildUnderstandContextOptions = {
4
+ projectRoot: string;
5
+ files?: string[];
6
+ sampleSize?: number;
7
+ artifactDir?: string;
8
+ codegraphRunner?: CodegraphProcessRunner;
9
+ };
10
+ export declare function buildUnderstandContext(options: BuildUnderstandContextOptions): Promise<UnderstandContextResult>;
@@ -0,0 +1,100 @@
1
+ import { createCodegraphInvocation, executeCodegraphInvocation } from '../codegraph/codegraph-service.js';
2
+ import { scanUnderstandAnything, summarizeKnowledgeGraph } from './understand-scan-service.js';
3
+ import { getErrorMessage } from '../../shared/result.js';
4
+ const DEFAULT_FILES = ['src/index.ts', 'package.json', 'README.md'];
5
+ function resolveFiles(files) {
6
+ if (files !== undefined && files.length > 0) {
7
+ return [...files];
8
+ }
9
+ return [...DEFAULT_FILES];
10
+ }
11
+ async function safeSummarize(options) {
12
+ try {
13
+ return { summary: await summarizeKnowledgeGraph(options) };
14
+ }
15
+ catch (error) {
16
+ return { warning: `UA summary failed: ${getErrorMessage(error)}` };
17
+ }
18
+ }
19
+ async function runCodegraph(projectRoot, files, runner) {
20
+ try {
21
+ const invocation = createCodegraphInvocation({
22
+ subcommand: 'affected',
23
+ project: projectRoot,
24
+ files,
25
+ json: true
26
+ });
27
+ const execution = await executeCodegraphInvocation(invocation, runner);
28
+ if (execution.exitCode !== 0) {
29
+ return { warning: `codegraph affected exited with code ${execution.exitCode}: ${execution.stderr.slice(0, 200)}` };
30
+ }
31
+ let payload = null;
32
+ let parseError;
33
+ try {
34
+ payload = JSON.parse(execution.stdout);
35
+ }
36
+ catch (error) {
37
+ parseError = getErrorMessage(error);
38
+ }
39
+ return {
40
+ block: {
41
+ invocation: { subcommand: 'affected', files },
42
+ execution,
43
+ payload,
44
+ ...(parseError !== undefined ? { parseError } : {})
45
+ }
46
+ };
47
+ }
48
+ catch (error) {
49
+ return { warning: `codegraph affected failed: ${getErrorMessage(error)}` };
50
+ }
51
+ }
52
+ function decideSource(uaScan, hasCodegraph) {
53
+ const uaPresent = uaScan.exists && uaScan.graph.exists && uaScan.graph.parseError === undefined;
54
+ if (uaPresent && hasCodegraph)
55
+ return 'ua-and-codegraph-hybrid';
56
+ if (uaPresent)
57
+ return 'ua-only';
58
+ if (hasCodegraph)
59
+ return 'ua-missing-fallback-codegraph';
60
+ return 'both-missing';
61
+ }
62
+ export async function buildUnderstandContext(options) {
63
+ const startedAt = Date.now();
64
+ const files = resolveFiles(options.files);
65
+ const sampleSize = options.sampleSize ?? 5;
66
+ const warnings = [];
67
+ const scanOptions = { projectRoot: options.projectRoot };
68
+ if (options.artifactDir !== undefined) {
69
+ scanOptions.artifactDir = options.artifactDir;
70
+ }
71
+ const uaScan = await scanUnderstandAnything(scanOptions);
72
+ const uaSummaryPromise = uaScan.exists && uaScan.graph.exists
73
+ ? safeSummarize({ projectRoot: options.projectRoot, sampleSize, ...(options.artifactDir !== undefined ? { artifactDir: options.artifactDir } : {}) })
74
+ : Promise.resolve({});
75
+ const [uaSummary, codegraphResult] = await Promise.all([
76
+ uaSummaryPromise,
77
+ runCodegraph(options.projectRoot, files, options.codegraphRunner)
78
+ ]);
79
+ if (uaSummary.warning)
80
+ warnings.push(uaSummary.warning);
81
+ if (codegraphResult.warning)
82
+ warnings.push(codegraphResult.warning);
83
+ const source = decideSource(uaScan, codegraphResult.block !== undefined);
84
+ const result = {
85
+ projectRoot: options.projectRoot,
86
+ source,
87
+ durationMs: Date.now() - startedAt,
88
+ warnings
89
+ };
90
+ if (source === 'ua-only' || source === 'ua-and-codegraph-hybrid') {
91
+ result.ua = {
92
+ scan: uaScan,
93
+ ...(uaSummary.summary !== undefined ? { summary: uaSummary.summary } : {})
94
+ };
95
+ }
96
+ if (codegraphResult.block) {
97
+ result.codegraph = codegraphResult.block;
98
+ }
99
+ return result;
100
+ }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "3.0.1";
1
+ export declare const CLI_VERSION = "3.0.3";
@@ -1 +1 @@
1
- export const CLI_VERSION = "3.0.1";
1
+ export const CLI_VERSION = "3.0.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -5,22 +5,18 @@ description: Full-auto orchestration facade for the Peaks-Loop skill family. Use
5
5
 
6
6
  ## Single-scope-axis naming convention (2.7.1)
7
7
 
8
- > **Read once at the top of this file.**
9
-
10
8
  The `.peaks/` workspace has a **single scope axis** (session-id) plus a nested **sub-agent axis** under `.peaks/_sub_agents/<sessionId>/...`. Use `<sessionId>` (NEVER bare `<sid>`). The change-id axis is gone as of slice `2026-06-29-change-id-root-removal` — reviewable artifacts now live at `.peaks/_runtime/<sessionId>/<role>/...` (gitignored) with the change-id optionally embedded as a filename slug. CLI surface: session-id → `peaks session *`; sub-agent → `peaks sub-agent *`. OpenSpec's independent `openspec/changes/<change-id>/` vocabulary (L4) is preserved untouched.
11
9
 
12
10
  **2.7.1:** `recordBypass` / `isBypassLimitReached` had a legacy write path that bypassed `.peaks/_runtime/` (slice 2.7.1 fix in `request-commands.ts:410`). Now writes canonical `.peaks/_runtime/<sid>/`. `getChangeArtifactRoot` removed. Test `skills-skill-md-naming.test.ts` enforces (a) zero bare `<sid>`, (b) axis labels, (c) this callout.
13
11
 
14
- ## Hard ban (effective 2.8.3 — read every session, no exceptions)
15
-
16
- Never create `.peaks/_runtime/<YYYY-MM-DD-*>/` at the top level of `.peaks/`. The post-v2.19.0 single-scope-axis convention requires ALL reviewable artifact dirs to live under `.peaks/_runtime/<sessionId>/<role>/...` (gitignored) — never as siblings of `.peaks/_runtime/`. The `peaks workspace init` flow creates only `.peaks/_runtime/<sessionId>/session.json`; reviewable artifact files are placed under that same dir by the writer. The change-id is preserved as an optional filename slug inside `.peaks/_runtime/<sessionId>/<role>/requests/<rid>-<change-id>.md` but does NOT route filesystem writes. If you find yourself about to write a date-prefixed directory directly under `.peaks/`, STOP and reroute under `.peaks/_runtime/<sessionId>/`. The `.gitignore` rule `.peaks/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-*/` will block the write at commit time; the vitest guard at `tests/unit/workspace/top-level-change-id-guard.test.ts` (8 cases, including CLI help-text) will fail the suite if a regression sneaks through.
17
-
18
12
  ## Karpathy guidance (Slice 1/6 — karpathy prompt-injection-lift)
19
13
 
20
- > **Read once per Solo invocation; the 4 Karpathy guidelines are mandatory context for every sub-agent Solo dispatches.**
21
-
22
14
  Every sub-agent dispatch (`peaks-prd`, `peaks-rd`, `peaks-qa`, `peaks-ui`, `peaks-sc`, `peaks-txt`) MUST receive the 4 Karpathy guidelines. Append the verbatim block from `peaks-rd/references/rd-sub-agent-dispatch.md` §"Karpathy-guidelines context" to the dispatch prompt; never silently drop it. Canonical skill id: `andrej-karpathy-skills:karpathy-guidelines`. Summary: **#1 Think Before Coding** (surface assumptions, name tradeoffs), **#2 Simplicity First** (min code, no speculative features, 800-line file cap), **#3 Surgical Changes** (touch only what the request requires), **#4 Goal-Driven Execution** (verifiable ACs, plan + verify checkpoints).
23
15
 
16
+ ## Hard ban (effective 2.8.3 — read every session, no exceptions)
17
+
18
+ Never create `.peaks/_runtime/<YYYY-MM-DD-*>/` at the top level of `.peaks/`. The post-v2.19.0 single-scope-axis convention requires ALL reviewable artifact dirs to live under `.peaks/_runtime/<sessionId>/<role>/...` (gitignored) — never as siblings of `.peaks/_runtime/`. The `peaks workspace init` flow creates only `.peaks/_runtime/<sessionId>/session.json`; reviewable artifact files are placed under that same dir by the writer. The change-id is preserved as an optional filename slug inside `.peaks/_runtime/<sessionId>/<role>/requests/<rid>-<change-id>.md` but does NOT route filesystem writes. If you find yourself about to write a date-prefixed directory directly under `.peaks/`, STOP and reroute under `.peaks/_runtime/<sessionId>/`. The `.gitignore` rule `.peaks/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-*/` will block the write at commit time; the vitest guard at `tests/unit/workspace/top-level-change-id-guard.test.ts` (8 cases, including CLI help-text) will fail the suite if a regression sneaks through.
19
+
24
20
  # Peaks-Loop Solo
25
21
 
26
22
  Peaks-Loop Solo is the orchestration facade for the Peaks-Loop short skill family. Use it to identify the user scenario, recommend an execution mode, coordinate role skills, and produce the final handoff report. Do not collapse role responsibilities into this skill.
@@ -31,56 +27,25 @@ Peaks-Loop Solo is the orchestration facade for the Peaks-Loop short skill famil
31
27
 
32
28
  ## Skill-first architecture note (read once, internalise)
33
29
 
34
- This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary** — invoked by the skill prompt only when a primitive is the right tool (atomic side effect, machine-enforced gate, structured JSON for a downstream decision, or backstop the LLM cannot skip). Behaviour only an LLM in a skill prompt would use lives **here in the SKILL.md**, not as a new CLI command.
30
+ This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary** — invoked by the skill prompt only when a primitive is the right tool. Behaviour only an LLM in a skill prompt would use lives **here in the SKILL.md**.
35
31
 
36
32
  ## Code-Change Red Line (BLOCKING — read before ANY tool call)
37
33
 
38
- **Peaks-Loop Solo is an orchestrator, NOT an implementer. You MUST NOT write, edit, or modify any application source code directly.**
39
-
40
- Every code change — bugfix, feature, refactor, or config — MUST go through the full pipeline:
41
-
42
- ```
43
- peaks-solo (orchestrate only)
44
- → RD work ← ALL code changes happen HERE
45
- → Unit tests written + pass (Peaks-Loop Gate B2)
46
- → Karpathy standards enforced (file-size ≤800 lines, TypeScript rules)
47
- → Code review evidence (Peaks-Loop Gate B3)
48
- → Security review evidence (Peaks-Loop Gate B4)
49
- → QA work ← ALL validation happens HERE
50
- → Functional test execution (Peaks-Loop Gate A2)
51
- → Performance check (Peaks-Loop Gate A4)
52
- → Security test (Peaks-Loop Gate A3)
53
- → Browser E2E (when frontend; Peaks-Loop Gate D)
54
- → Verdict: pass | return-to-rd | blocked
55
- ```
56
-
57
- **Mechanism for "RD work" / "QA work" depends on the orchestration mode:** `full-auto`/`swarm` use `peaks sub-agent dispatch <role>` (IDE-agnostic dispatch primitive); `assisted`/`strict`/inline-fallback executes inline. In all modes, the work itself follows the same `peaks-rd` and `peaks-qa` contracts. **Never bypass the role contracts regardless of which path runs.**
58
-
59
- **Violations (BLOCKING — Solo must refuse to proceed):**
60
-
61
- 1. Writing implementation code directly instead of routing through the RD contract
62
- 2. Declaring work "done" without producing QA evidence after RD
63
- 3. Skipping unit tests ("it's a small change")
64
- 4. Skipping code review or security review
65
- 5. Skipping QA functional/performance/security validation
66
-
67
- **If you catch yourself about to write code in this skill, STOP. Hand off to the RD contract path immediately** (sub-agent Task in full-auto, inline execution in assisted/strict).
68
-
69
- **Before declaring workflow complete, run:** `peaks workflow verify-pipeline --rid <rid> --project <repo> --json`
34
+ **Peaks-Loop Solo is an orchestrator, NOT an implementer. You MUST NOT write, edit, or modify any application source code directly.** Every code change goes through `peaks-solo → RD → QA → verdict`. `full-auto`/`swarm` use `peaks sub-agent dispatch <role>`; `assisted`/`strict`/inline-fallback executes inline. Both follow `peaks-rd` + `peaks-qa` contracts. **If you catch yourself about to write code, STOP.** Hand off to RD. Before declaring workflow complete, run `peaks workflow verify-pipeline --rid <rid> --project <repo> --json`.
70
35
 
71
36
  ## Peaks-Loop Startup sequence (MANDATORY — execute in order)
72
37
 
73
38
  ### Peaks-Loop Slice 011 — workspace consolidate + session checkpoint/resume
74
39
 
75
- `peaks workspace consolidate` is the slice-011 umbrella primitive. `peaks session checkpoint` (proactive context-overflow defense) and `peaks session resume` (cross-session continuity) are surfaced in SKILL.md as Step 0.75 and Step N headings (slice 2.5.0, sub-fix A). See `references/cross-date-session-check.md` for the umbrella. IDE-agnostic.
40
+ `peaks workspace consolidate` is the slice-011 umbrella primitive. `peaks session checkpoint` (context-overflow defense) and `peaks session resume` surface as Step 0.75 + Step N. See `references/cross-date-session-check.md`.
76
41
 
77
42
  ### Peaks-Loop Step 0.75: Resume from checkpoint (BLOCKING on same-day re-invocation)
78
43
 
79
- When a NEW conversation opens on a session whose `lastActivity` is from today AND `.peaks/_runtime/<sessionId>/checkpoints/` contains at least one `*.json`, surface the most recent checkpoint. Run `peaks session info --active --json` to resolve the canonical session id, then `peaks session resume --from <path> --project <repo>` to emit a markdown "resume context" block. Prompt the user via IDE-native `AskUserQuestion` (resume / start fresh), and if "resume", prepend the emitted block to its own prompt. Honors the user choice on "fresh" — the on-disk checkpoint stays untouched. Step 0.75 is a no-op if any precondition fails (no sid, wrong day, no checkpoint). Full probe + decision tree: `references/checkpoint-resume.md`.
44
+ When a NEW conversation opens on a session whose `lastActivity` is from today AND `.peaks/_runtime/<sessionId>/checkpoints/` contains a `*.json`, surface via `peaks session info --active --json` + `peaks session resume --from <path> --project <repo>`. Prompt via `AskUserQuestion` (resume / fresh); if "resume", prepend the block. Honors "fresh" — on-disk checkpoint stays untouched. Step 0.75 is no-op on precondition fail. See `references/checkpoint-resume.md`.
80
45
 
81
46
  ### Peaks-Loop Step N: Periodic checkpoint (auto-fire, no user action)
82
47
 
83
- Proactive defense vs context overflow. CLI: `peaks session checkpoint [--reason <r>] [--session-id <sessionId>] [--project <p>] [--current-plan <text>] [--open-questions <list>] [--recent-decisions <list>] [--recent-artifact-paths <list>] [--git-status <text>] [--skills-active <list>] [--todo-state <list>] [--json]`. Fire `--reason periodic` every 20 tool calls (G1 hard-coded), `--reason artifact-written` per PRD/RD/QA/TXT write, `--reason context-fill` when full, `--reason user-pause` on save, `--reason user-close` before handoff. 20-call cadence locked here + `references/periodic-checkpoint.md` (otherwise `tests/unit/solo/checkpoint-periodic-frequency.test.ts` fails).
48
+ Proactive context-overflow defense. CLI: `peaks session checkpoint [--reason <r>] [...flags]`. Fire `--reason periodic` every 20 tool calls (G1 hard-coded), `--reason artifact-written` per PRD/RD/QA/TXT write, `--reason context-fill` / `user-pause` / `user-close` as appropriate. 20-call cadence locked; see `references/periodic-checkpoint.md` (otherwise `tests/unit/solo/checkpoint-periodic-frequency.test.ts` fails).
84
49
 
85
50
  ### Peaks-Loop Step 0.5: OpenSpec first-run opt-in (conditional)
86
51
 
@@ -90,21 +55,21 @@ Run when `openspec/` is absent and `.peaks/.peaks-openspec-opt-in.json` is missi
90
55
 
91
56
  ### Peaks-Loop Step 0.6: Audit + Goal (NEW)
92
57
 
93
- After human expresses need, invoke peaks-audit to summarize + multi-dim audit + propose goal. Display audit + goal to human for one-shot approval. Store approved goal at `.peaks/_runtime/<sessionId>/audit-goal/<rid>.json`. **All subsequent autonomous work requires an approved goal.**
94
-
95
- > **v2.15.0+ 校准:** 业务 goal 与技术 goal 必须分离,user 不被推到技术决策位。详见 `.peaks/memory/peaks-loop-user-role-and-tech-decision.md`。
58
+ Invoke peaks-audit after need expressed; store approved goal at `.peaks/_runtime/<sessionId>/audit-goal/<rid>.json`. **All autonomous work requires approved goal.** (v2.15.0+ business vs tech goal separated; see `.peaks/memory/peaks-loop-user-role-and-tech-decision.md`.)
96
59
 
97
- → see `references/audit-goal-gate.md` for the full Step 0.6 contract.
60
+ → see `references/audit-goal-gate.md`.
98
61
 
99
62
  ### Peaks-Loop Step N+1: Final Review (NEW)
100
63
 
101
- After all autonomous LLM work (RD, QA, security, perf) completes, invoke peaks-final-review to prepare 4-dim evidence. Display evidence to human for judgment. If all 4 dims pass → final delivery. If any fail → loop back with feedback.
64
+ After autonomous work (RD, QA, security, perf), invoke peaks-final-review for 4-dim evidence. All 4 pass → final delivery; else loop back. See `references/final-review-gate.md`.
65
+
66
+ ### Peaks-Loop Step N+2: Main-session context monitor (D6 + slice 2026-07-02)
102
67
 
103
- → see `references/final-review-gate.md` for the full Step N+1 contract.
68
+ After every 4th tool call, probe via `peaks solo context-now --project <repo> --json`. Thresholds: 50% / **0.85 pre-compact / 0.95 red-line** (v2.13.0).
104
69
 
105
- ### Peaks-Loop Step N+2: Main-session context monitor (v2.11.0 D6)
70
+ **≥ 0.85**, LLM MUST fire `peaks solo auto-compact --execute --project <repo>` autonomously (zero-human-intervention). **≥ 0.95** red line: next Bash/Task tool call fires `peaks session auto-compact-hook` (PreToolUse), which in-band spawns `claude --compact` against the **current** runner.
106
71
 
107
- After every 4th tool call (complement to Step N), probe via `peaks context check --prompt-size <bytes> --auto-trigger --json`. Thresholds: 50% soft warn / 75% user red line (return trigger path, do NOT auto-execute — Karpathy §4) / 90% emergency. Use `--in-flight-batch` to defer until batch converges (D6.e). Companion to Step N (soft signal); D6 is the hard signal.
72
+ **Karpathy §4 exception**: compact red line keeps runner alive → zero-intervention wins. LLM does NOT ask user to run `/compact`. Honor `--in-flight-batch` (D6.e).
108
73
 
109
74
  ### Peaks-Loop Step 0: Anchor the workflow (MANDATORY FIRST ACTIONS — no bail-out)
110
75
 
@@ -170,19 +135,15 @@ Peaks-Loop Solo must NOT silently install hooks, create agents, enable MCP serve
170
135
 
171
136
  ## Peaks-Loop GStack integration
172
137
 
173
- Map gstack stages to Peaks-Loop role artifacts; preserve confirmation gates. Do not delegate orchestration to gstack commands. RD and QA use Playwright MCP for browser E2E.
174
-
175
- → see `references/gstack-integration.md` and `references/browser-workflow.md`.
138
+ Map gstack stages to Peaks-Loop role artifacts; preserve confirmation gates. → `references/gstack-integration.md` + `references/browser-workflow.md`.
176
139
 
177
140
  ## Peaks-Loop Local intermediate artifact workspace (MANDATORY)
178
141
 
179
- The workspace is created in Step 0 as a mandatory first action. All intermediate artifacts land under `.peaks/_runtime/<sessionId>/`. NEVER write Peaks-Loop intermediate artifacts to the project root directory.
142
+ Step 0 creates the workspace; all intermediate artifacts land under `.peaks/_runtime/<sessionId>/`. NEVER write Peaks-Loop intermediate artifacts to the project root directory.
180
143
 
181
144
  ## Peaks-Loop Pre-RD project scan checklist (MANDATORY)
182
145
 
183
- Before handing off to `peaks-rd`, scan the project and record findings to `.peaks/_runtime/<sessionId>/rd/project-scan.md`. The project-scan checklist (archetype detection, build-tool / component-library / CSS-framework / state-routing-data tables, legacy signals, artifact template) lives in the references file.
184
-
185
- → see `references/project-scan-checklist.md` for the checklist.
146
+ Before handing off to `peaks-rd`, scan the project and record findings to `.peaks/_runtime/<sessionId>/rd/project-scan.md`. → `references/project-scan-checklist.md`.
186
147
 
187
148
  ## Peaks-Loop Frontend-only development mode
188
149
 
@@ -249,31 +210,59 @@ Five CLI commands harden the workflow against silent skips: `peaks request lint`
249
210
 
250
211
  After final validation, refresh project-local standards via `peaks standards init/update` (never hand-write). Use Peaks-Loop TXT for the compact handoff capsule: mode, validated decisions, artifact paths, standards deltas, open questions, next action. **Presence management is delegated to the last downstream skill** — peaks-solo does not call `peaks skill presence:clear` itself.
251
212
 
252
- ## Peaks-Loop External references and lifecycle
213
+ ## Peaks-Loop Step 11: Memory sediment (BLOCKING on workflow complete)
253
214
 
254
- 3rd-party integrations (codegraph, mattpocock/skills, shadcn/ui, MCPs, Context7) follow Discovery → Reference → Side effect through Peaks CLI only. Before naming external skills, run `peaks capabilities` for capability discovery; treat all external skills as reference material only. Peaks-Loop artifacts and ACs remain authoritative; do not execute upstream installers; MCP servers (Playwright / Chrome DevTools / Figma) are user-installed — check the tool list for `mcp__<server>__*` entries.
215
+ > **Hard rule.** Solo MUST NOT declare a workflow complete until Step 11 has produced ≥ 1 file in `.peaks/memory/` (the durable, LLM-authored memory store) OR the user has explicitly approved a no-sediment outcome via AskUserQuestion. This applies to **all modes** including `assisted` and `strict` — `assisted` previously skipped Step 10 because there was no `[CONFIRM]` gate; Step 11 fixes that.
255
216
 
256
- → see `references/external-references.md` and `references/external-skill-invocation.md`.
217
+ ### Substeps (BLOCKING)
257
218
 
258
- ## Codegraph orchestration context
219
+ **11a — Gate A (txt/ inventory):**
259
220
 
260
- `peaks codegraph affected` is an optional project-analysis enhancement; treat output as untrusted supporting evidence. Solo must not treat codegraph output as approval for scope, design, or QA verdict. Never mutate agent settings, Claude settings, or hooks from codegraph; do not commit `.codegraph/` artifacts. RD writes `.peaks/_runtime/<sessionId>/rd/codegraph-context.md`; QA / TXT consume the same envelope in the role handoff.
221
+ ```bash
222
+ find .peaks/_runtime/<sessionId>/txt/ -type f -name '*.md' | head
223
+ ```
261
224
 
262
- → see `references/codegraph-orchestration.md`.
225
+ If **0 files** → STOP. Dispatch `peaks-txt` first to write `handoff.md`, then return to 11c.
263
226
 
264
- ## Sub-agent context governance (G7 + G7.7 + G8 + G9 — slice #010)
227
+ **11b — Gate B (memory block embed scan, skill-side only):**
265
228
 
266
- Layer 3.5 context-governance push for sub-agent dispatch. Main LLM reducer sees metadata-only view (~200 chars/sub-agent); on-demand `Read` for full content. Threshold table: 50% soft warn, 75% `CONTEXT_NEAR_LIMIT`, 80% hard reject (CLI + hook double-guard). See `references/context-governance.md` for the full protocol.
229
+ ```bash
230
+ grep -c 'peaks-memory:start' .peaks/_runtime/<sessionId>/txt/handoff.md || true
231
+ ```
267
232
 
268
- ## Sub-agent cross-batch signal — G8.4 share / shared-read / await (slice 2026-06-23-audit-3rd)
233
+ If **0 AND this session surfaced a stable project fact** (decision / convention / approved refactor / hard rule), STOP and tell peaks-txt to embed at least one `<!-- peaks-memory:start -->` block first.
234
+
235
+ **11c — Canonical extract (the only CLI that writes `.peaks/memory/`):**
236
+
237
+ ```bash
238
+ peaks memory extract --project <repo> --artifact .peaks/_runtime/<sessionId>/txt/handoff.md --apply --json
239
+ ```
240
+
241
+ `--apply` is REQUIRED. Without it the command only previews — no files land in `.peaks/memory/`.
242
+
243
+ **11d — Gate C (zero-write outcome):** If `extractedCount === 0` after 11c, fire AskUserQuestion:
244
+
245
+ > "本次 solo 未沉淀任何 `.peaks/memory` 文件。可选: (a) 回去在 handoff.md 嵌入至少 1 个 `peaks-memory:start` block 后重试; (b) 显式接受 no-sediment 并记录为 lesson; (c) 取消完成。"
269
246
 
270
- Three CLI primitives let sibling sub-agents coordinate within a batch without peer-to-peer messaging. LLM-invoked from peaks-rd / peaks-qa / peaks-ui / peaks-txt skills.
247
+ Default option = (a). Solo MUST NOT silently accept (b) without user pick.
271
248
 
272
- - **`peaks sub-agent share --batch <id> --key <role>.<event> --value <json> [--from <role>]`** — write to per-batch channel (≤ 1KB warn, ≥ 64KB reject). Last-write-wins.
273
- - **`peaks sub-agent shared-read --batch <id> [--since <iso>] [--key <glob>]`** — read sibling entries (glob `*`).
274
- - **`peaks sub-agent await --batch <id> [--timeout <ms>]`** — block until batch finishes (claude-code 1.2 MVP; other IDEs LLM-side await).
249
+ **Why this step exists:** audit 2026-07-03 confirmed 2 consecutive sessions produced zero `.peaks/memory/` files despite completing RD + QA + handoff artifacts. Root cause: `assisted` mode silently skipped runbook Step 10 (no STOP condition). Step 11 makes the BLOCKING semantics explicit. **Why `peaks memory extract` (not `peaks project memories:extract`):** the artifact-scoped extract is the canonical command next to its capsule; the batch-scoped sibling is for non-handoff flows (out of scope). For Step 11, **always** use `peaks memory extract --apply`.
250
+
251
+ ## Peaks-Loop External references and lifecycle
252
+
253
+ 3rd-party integrations (codegraph, mattpocock/skills, shadcn/ui, MCPs, Context7) follow Discovery → Reference → Side effect through Peaks CLI only. Run `peaks capabilities` for capability discovery; treat external skills as reference material only. MCP servers (Playwright / Chrome DevTools / Figma) are user-installed — check the tool list for `mcp__<server>__*` entries. → `references/external-references.md` + `references/external-skill-invocation.md`.
254
+
255
+ ## Codegraph orchestration context
256
+
257
+ `peaks codegraph affected` is an optional project-analysis enhancement (untrusted supporting evidence) used during role handoff. Solo must not treat codegraph output as approval for scope, design, or QA verdict. Never mutate agent settings / Claude settings / hooks from codegraph; do not commit `.codegraph/` artifacts. RD writes `.peaks/_runtime/<sessionId>/rd/codegraph-context.md`; QA / TXT consume the same envelope. → `references/codegraph-orchestration.md`.
258
+
259
+ ## Sub-agent context governance (G7 + G7.7 + G8 + G9 — slice #010)
260
+
261
+ Main LLM reducer sees metadata-only view (~200 chars/sub-agent); on-demand `Read` for full content. Threshold table: 50% soft warn, 75% `CONTEXT_NEAR_LIMIT`, 80% hard reject (CLI + hook double-guard). → `references/context-governance.md`.
262
+
263
+ ## Sub-agent cross-batch signal — G8.4 share / shared-read / await (slice 2026-06-23-audit-3rd)
275
264
 
276
- Channel file gitignored under `.peaks/_sub_agents/<sessionId>/shared/`.
265
+ Three CLI primitives let sibling sub-agents coordinate within a batch without peer-to-peer messaging: `peaks sub-agent share / shared-read / await` (last-write-wins channel ≤ 1KB warn / ≥ 64KB reject). Channel file gitignored under `.peaks/_sub_agents/<sessionId>/shared/`.
277
266
 
278
267
  ## References
279
268
 
@@ -12,7 +12,12 @@ peaks-solo does NOT itself call `peaks skill presence:clear --project <repo>` at
12
12
 
13
13
  The user can continue with follow-up requirements naturally — no need to re-invoke `/peaks-solo` to do so. The header continues to display whatever skill is active; the user can `/peaks-solo` again to re-anchor.
14
14
 
15
- Before ending, extract durable memories from this session:
16
- ```bash
17
- peaks project memories:extract --session-id <session-id> --project <repo> --json
18
- ```
15
+ Before ending, extract durable memories from this session.
16
+
17
+ > **Owned by runbook Step 10 (TXT handoff) + Step 11 (Memory sediment in SKILL.md).** Do not duplicate the CLI invocation here — the canonical command is:
18
+ >
19
+ > ```bash
20
+ > peaks memory extract --project <repo> --artifact .peaks/_runtime/<sessionId>/txt/handoff.md --apply --json
21
+ > ```
22
+ >
23
+ > `peaks project memories:extract` is a different (batch-scoped, no-artifact) CLI used in non-handoff flows. Step 11 in SKILL.md enforces the artifact-scoped path; follow that.
@@ -68,4 +68,14 @@ checkpoint as often as it wants.
68
68
 
69
69
  ## Checkpoint IDE note
70
70
 
71
- This step is strictly IDE-agnostic. The LLM calls the peaks CLI; no IDE-specific tools or paths are involved.
71
+ This step is strictly IDE-agnostic. The LLM calls the peaks CLI; no IDE-specific tools or paths are involved.
72
+
73
+ ## Companion surface — auto-compact (zero-pause)
74
+
75
+ When the runner crosses the v2.13.0 auto-compact thresholds (0.85 pre-compact / 0.95 red-line), `peaks session checkpoint` is NOT enough on its own — the LLM must also fire `peaks solo auto-compact --execute` (or wait for the in-band PreToolUse hook at ratio ≥ 0.95 to compact the runner itself). See:
76
+
77
+ - `peaks solo auto-compact --execute` — orchestrator that writes the convergence plan + dispatches the compact pathway
78
+ - `peaks session auto-compact-hook` — the PreToolUse hook entrypoint fired by `.claude/settings.local.json` (slice 2026-07-02-auto-compact-zero-pause)
79
+ - `.peaks/memory/2026-06-27-auto-compact-design.md` — zero-human-intervention design rationale
80
+
81
+ The two surfaces compose: `peaks session checkpoint` persists the context state, `peaks solo auto-compact` collapses the runner's window. The LLM fires both in the 0.85–0.95 zone; the hook fires alone at ≥ 0.95.
@@ -154,10 +154,11 @@ peaks openspec validate <cid> --project <repo> --json
154
154
  peaks openspec archive <cid> --project <repo> --apply --json
155
155
  peaks workspace reconcile --project <repo> --apply --older-than 7
156
156
 
157
- # 10. Peaks-Loop TXT handoff — invoke peaks-txt which embeds memory markers and extracts
157
+ # 10. Peaks-Loop TXT handoff — invoke peaks-txt which embeds memory markers (BLOCKING)
158
158
  # peaks-txt writes the handoff capsule to .peaks/_runtime/<id>/txt/handoff.md. Inside the
159
159
  # capsule body, peaks-txt embeds <!-- peaks-memory:start --> blocks for every
160
- # stable project fact surfaced this session.
160
+ # stable project fact surfaced this session. Applies to ALL modes including
161
+ # `assisted` (audit 2026-07-03 found `assisted` previously skipped this step).
161
162
  #
162
163
  # 10a. Skill-side scan (do this BEFORE the AskUserQuestion below):
163
164
  # grep -n "peaks-memory:start" .peaks/_runtime/<id>/txt/handoff.md