peaks-loop 4.0.7 → 4.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +46 -288
  2. package/dist/cli/commands/code-job-shape-commands.js +8 -11
  3. package/dist/cli/commands/code-mode-gate-commands.js +1 -43
  4. package/dist/cli/commands/code-runtime-commands.js +36 -14
  5. package/dist/cli/commands/core/skill-command.js +75 -13
  6. package/dist/cli/commands/core/standards-command.js +5 -50
  7. package/dist/cli/commands/dispatch-commands.d.ts +20 -0
  8. package/dist/cli/commands/dispatch-commands.js +39 -38
  9. package/dist/cli/commands/dispatch-from-dag.js +17 -0
  10. package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
  11. package/dist/cli/commands/heartbeat-commands.js +40 -0
  12. package/dist/cli/commands/hook-handle.js +5 -2
  13. package/dist/cli/commands/hooks-commands.js +9 -26
  14. package/dist/cli/commands/loop-eval-commands.js +5 -2
  15. package/dist/cli/commands/sub-agent-commands.js +0 -2
  16. package/dist/cli/commands/sub-agent-shared.d.ts +7 -9
  17. package/dist/cli/commands/workflow-commands.js +8 -0
  18. package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
  19. package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
  20. package/dist/cli/commands/workspace/init-command.js +19 -0
  21. package/dist/services/audit/backing-detector.d.ts +2 -19
  22. package/dist/services/audit/backing-detector.js +7 -66
  23. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
  24. package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
  25. package/dist/services/audit/enforcers/lint-audit-regression.js +1 -10
  26. package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
  27. package/dist/services/code/auto-compact-orchestrator.js +29 -9
  28. package/dist/services/context/build-dispatch-system-prompt.js +0 -18
  29. package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
  30. package/dist/services/dispatch/dispatch-record-writer.js +85 -15
  31. package/dist/services/dispatch/test-tool-detection.d.ts +0 -14
  32. package/dist/services/dispatch/test-tool-detection.js +0 -22
  33. package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
  34. package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
  35. package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
  36. package/dist/services/hooks/presence-marker-detector.js +48 -0
  37. package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
  38. package/dist/services/ide/adapters/codex-adapter.js +20 -1
  39. package/dist/services/ide/adapters/cursor-adapter.js +21 -1
  40. package/dist/services/ide/adapters/hermes-adapter.js +20 -1
  41. package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
  42. package/dist/services/ide/adapters/qoder-adapter.js +20 -1
  43. package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
  44. package/dist/services/ide/adapters/trae-adapter.js +21 -1
  45. package/dist/services/ide/adapters/zcode-adapter.js +19 -0
  46. package/dist/services/ide/ide-types.d.ts +15 -0
  47. package/dist/services/observability/observability-service.d.ts +2 -2
  48. package/dist/services/scan/archetype-service.js +2 -52
  49. package/dist/services/session/caller-binding-service.d.ts +21 -0
  50. package/dist/services/session/caller-binding-service.js +35 -0
  51. package/dist/services/session/caller-id-types.d.ts +62 -13
  52. package/dist/services/session/caller-id-types.js +25 -13
  53. package/dist/services/session/index.d.ts +4 -2
  54. package/dist/services/session/index.js +8 -2
  55. package/dist/services/session/platform-fallbacks.d.ts +25 -17
  56. package/dist/services/session/platform-fallbacks.js +26 -28
  57. package/dist/services/session/resolve-caller-id.d.ts +64 -29
  58. package/dist/services/session/resolve-caller-id.js +122 -48
  59. package/dist/services/session/session-manager.d.ts +0 -29
  60. package/dist/services/session/session-manager.js +0 -33
  61. package/dist/services/skills/hooks-settings-service.d.ts +0 -41
  62. package/dist/services/skills/hooks-settings-service.js +5 -61
  63. package/dist/services/skills/presence-lease-service.d.ts +73 -0
  64. package/dist/services/skills/presence-lease-service.js +358 -0
  65. package/dist/services/skills/presence-lease-types.d.ts +75 -0
  66. package/dist/services/skills/presence-lease-types.js +16 -0
  67. package/dist/services/skills/skill-presence-service.js +86 -22
  68. package/dist/services/slice/slice-check-service.js +0 -59
  69. package/dist/services/standards/project-standards-service.js +0 -44
  70. package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
  71. package/dist/services/workflow/workflow-graph-store.js +278 -0
  72. package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
  73. package/dist/services/workflow/workflow-graph-types.js +67 -0
  74. package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
  75. package/dist/services/workflow/workflow-inflight-probe.js +86 -0
  76. package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
  77. package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
  78. package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
  79. package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
  80. package/dist/services/workspace/reconcile-service.d.ts +31 -0
  81. package/dist/services/workspace/reconcile-service.js +132 -1
  82. package/package.json +4 -4
  83. package/skills/peaks-code/SKILL.md +5 -43
  84. package/skills/peaks-code/references/completion-handoff.md +1 -1
  85. package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
  86. package/dist/cli/commands/sub-agent-exec-command.d.ts +0 -25
  87. package/dist/cli/commands/sub-agent-exec-command.js +0 -132
  88. package/dist/services/standards/find-skills-integration.d.ts +0 -59
  89. package/dist/services/standards/find-skills-integration.js +0 -225
package/CHANGELOG.md CHANGED
@@ -1,294 +1,52 @@
1
1
  # Changelog
2
2
 
3
- ## 4.0.7 — 2026-08-02 (dogfood surface repairs on `ice-cola`)
4
-
5
- A 24h-mode dogfood pass on the downstream `ice-cola` monorepo (4
6
- sub-packages: admin / server / client / hermes-agent) surfaced 25 real
7
- CLI / runtime / documentation defects across peaks-loop 4.0.6, plus a
8
- meta-level flow bug (the LLM bypassed the peaks-code / peaks-rd /
9
- peaks-qa flow and routed 22 files / 632 lines of `Edit` calls
10
- directly). This release closes all 4 P0/P1 ship-blockers, the 5
11
- P2 batch fixes, the find-skills integration, and 4 PR-meta
12
- enforcement-layer fixes.
13
- release closes the 5 P0/P1 ship-blockers and reshapes SKILL.md's
14
- Drift Index. 4 P2 changes were documented in the changeset but only
15
- the documentation half (Drift Index refresh) ships in 4.0.7; the
16
- implementation half (monorepo-aware language detection, monorepo
17
- sub-agent prompt, slice check monorepo typecheck) is queued for 4.0.8.
18
-
19
- ### P0 — `peaks audit red-lines` no longer reports false-positive enforcerRef orphans on consumer projects
20
-
21
- - `src/services/audit/backing-detector.ts` and
22
- `src/services/audit/enforcers/lint-audit-regression.ts` now resolve
23
- `enforcerRef` paths against the peaks-loop source root (resolved via
24
- `import.meta.url` + upward walk to `package.json#name === 'peaks-loop'`),
25
- with an explicit `peaksLoopRoot` override for tests. Pre-rid every
26
- downstream audit reported all 26 enforcer files as "missing on disk"
27
- (88 enforcerFindings on ice-cola). Post-rid: 0.
28
- - New `resolvePeaksLoopRoot()` exported helper.
29
- - Tests: `tests/unit/audit/backing-detector-resolve-root.test.ts` (8
30
- cases including the ice-cola repro).
31
-
32
- ### P0 — `peaks code {detect-job,read-job-shape}`, `peaks workflow plan read`, and `peaks route` now resolve the canonical session id
33
-
34
- - New shared resolver `resolveActiveSessionId(projectRoot, override?)`
35
- in `src/services/session/session-manager.ts`, exported via
36
- `src/services/session/index.ts`. Composes the existing
37
- `getSessionIdCanonical` + `getSessionId` fan-out so the same
38
- `sessionId` flows through all 4 CLIs.
39
- - `src/cli/commands/code-job-shape-commands.ts` and
40
- `src/cli/commands/code-runtime-commands.ts` now use the shared
41
- resolver instead of the per-file `getSkillPresence` helper (which
42
- read `.peaks/.active-skill.json` — a different store from
43
- `.peaks/_runtime/session.json`).
44
- - Pre-rid the 4 CLIs reported `NO_ACTIVE_SESSION` for any session
45
- created via `peaks workspace init` that never had
46
- `peaks skill presence:set peaks-code` called on it (the common
47
- case for downstream consumer projects and 24h-mode sessions).
48
- - Tests: `tests/unit/session/resolve-active-session.test.ts` (7 cases).
49
-
50
- ### P1 — `peaks code should-pause` no longer false-positives in 24h-mode
51
-
52
- - `src/cli/commands/code-mode-gate-commands.ts` short-circuits the
53
- stale-presence check when `.peaks/_runtime/<sid>/24h-state.json`
54
- reads `state === '24H_ACTIVE'`. Without this, every 24h-mode
55
- session hit a `shouldPause: true` wall at Step 1 even though
56
- 24h-mode is explicitly designed to be a no-AskUserQuestion mode.
57
- - New local helper `is24hActive(projectRoot, sessionId)` reuses the
58
- existing `read24hState` from `services/24h-mode/store.ts`.
59
- - Tests: `tests/unit/code/should-pause-24h-bypass.test.ts` (9 cases).
60
-
61
- ### P2 #6 (enhancement) — `peaks standards {init,update} --suggest-skills` integrates find-skills
62
-
63
- - New `--suggest-skills` flag on `peaks standards init` and
64
- `peaks standards update` (off by default). When set, peaks-loop
65
- runs `npx skills find <query>` for each query derived from the
66
- detected ProjectContext (component library, build tool, CSS
67
- framework, notable deps, language) and emits the top-5 results
68
- in `nextActions` as copy-paste `npx skills add` commands.
69
-
70
- - Per user direction 2026-08-02: **no hardcoded verified-owner
71
- whitelist**. The service runs `npx skills find` with no
72
- `--owner` filter so the registry / leaderboard decides the
73
- ranking (this is exactly the anti-pattern find-skills
74
- SKILL.md Step 4 warns against — "do not recommend based solely
75
- on a hardcoded list; verify with install count + source
76
- reputation"). Recommendations are sorted by install count desc
77
- so the user sees the same top results they would see running
78
- `npx skills find` manually.
79
-
80
- - The service NEVER calls `npx skills add` itself. Installation
81
- is gated on the user passing `--suggest-skills` (off by
82
- default) and the install command is emitted as a single shell
83
- snippet the user reviews before pasting. The CLI caller owns
84
- the gating.
85
-
86
- - Failure mode: if `npx skills find` fails (network / registry
87
- down), the service silently degrades — init/update still
88
- completes, `nextActions` shows a "no matching skills found"
89
- hint, no error. The user can re-run with `--suggest-skills`
90
- later.
91
-
92
- - New file: `src/services/standards/find-skills-integration.ts`.
93
- Tests: `tests/unit/standards/find-skills-integration.test.ts`
94
- (12 cases covering queriesForContext, parseFindOutput,
95
- findSkillsForContext — all using a custom `SkillFindRunner`
96
- injection so the test never hits the network).
97
-
98
- ### P2 batch — 5 monorepo / sub-agent prompt fixes (all in this release)
99
-
100
- - **`peaks standards` / `detectLanguage` is monorepo-aware** (`project-standards-service.ts`).
101
- A monorepo root that has no `tsconfig.json` (only `tsconfig.base.json` + per-package
102
- `tsconfig.json`) is now classified as `typescript` when any sub-package in
103
- `packages/<name>/` has its own `tsconfig.json`. Pre-rid ice-cola (4-package
104
- TypeScript monorepo) was misclassified as `javascript` because the root scan
105
- saw no `tsconfig.json` and the root `package.json#devDependencies` had
106
- `peaks-loop` only (a `javascript` standards pack).
107
-
108
- - **`peaks scan archetype` is monorepo-aware** (`archetype-service.ts`).
109
- A 1-level walk into `packages/<name>/` merges each sub-package's
110
- `dependencies` / `devDependencies` / `peerDependencies` /
111
- `optionalDependencies` into the framework-detection map. Ice-cola
112
- now correctly reports `hasBackendFramework: true, backendFrameworks:
113
- ["@nestjs/core", "@nestjs/common"]` instead of
114
- `hasBackendFramework: false`.
115
-
116
- - **`peaks sub-agent dispatch` prompt includes a monorepo branch**
117
- when `<projectRoot>/pnpm-workspace.yaml` is present. The pre-rid
118
- static prompt had no monorepo handling, so sub-agents ran
119
- `pnpm test` at the root which fanned out to every sub-package
120
- (36-minute wall clock for a single-slice change). The new block
121
- tells the sub-agent to scope to one sub-package via
122
- `pnpm --filter @<scope>/<name> test <pattern>`.
123
-
124
- - **`peaks slice check` typecheck stage is monorepo-aware**
125
- (`slice-check-service.ts`). When `pnpm-workspace.yaml` is
126
- present, the typecheck stage runs `pnpm -r typecheck` first and
127
- falls back to `pnpm -r --parallel exec tsc --noEmit` if no
128
- per-package `typecheck` script exists. Per-package failures
129
- are captured in `data.failedPackages` so the operator can see
130
- which sub-package broke (the pre-rid "tsc exited with code 1"
131
- was a 2ms fake-fail with no actionable detail).
132
-
133
- - **`peaks sub-agent dispatch` context window block no longer
134
- misleads on fallback probes** (`build-dispatch-system-prompt.ts`).
135
- When the probe source is `conservative-fallback` (or any other
136
- untrusted value), the block short-circuits to "no probe
137
- available — treat the displayed ratio as unverified" instead
138
- of rendering "0.0% used (100.0% free)" as if it were a real
139
- measurement. Trusted sources are `token-counted`,
140
- `transcript-estimate`, and `statusline`; anything else triggers
141
- the fallback path.
142
-
143
- ### P1 — SKILL.md Drift Index refreshed for 4.0.6+
144
-
145
- - D-001 rewritten (the pre-4.0.7 entry was the inverse of 4.0.6's
146
- actual behavior — `peaks code detect-job` requires `--is-job`).
147
- - New D-013: `peaks code context-now --json` requires `--project`.
148
- - New D-014: documents the 4-CLI `NO_ACTIVE_SESSION` regression fixed
149
- by P0 above.
150
- - New D-015: documents the 24h-mode ↔ stale-presence interaction fixed
151
- by P1 above.
152
- - Step N+2 probe primitive example now includes `--project <path>`.
153
-
154
- ### Compatibility
155
-
156
- - All command names, options, and exit codes are preserved.
157
- - Auto-compact 0.85 / 0.95 contract unchanged.
158
- - `peaks audit red-lines` on peaks-loop itself shows 0 orphan findings
159
- (it had 88+ before the fix; now also on every consumer project).
160
- - `peaks code detect-job` / `read-job-shape` accept the same flags
161
- as 4.0.6.
162
-
163
- ### How to verify
164
-
165
- ```bash
166
- # Inside peaks-loop source root (after `pnpm build`):
167
- peaks audit red-lines --project . --json
168
- # expect: enforcerFindings: 0
169
-
170
- # Inside a downstream project that has peaks-loop as a devDep:
171
- peaks audit red-lines --project . --json
172
- # expect: enforcerFindings: 0 (was 88+ before)
173
-
174
- # 24h-mode + should-pause:
175
- peaks session 24h-mode transition --state 24H_ACTIVE
176
- peaks code should-pause --step step-1-mode-select --json
177
- # expect: shouldPause decision is NOT "stale-presence"
178
-
179
- # detect-job CLI:
180
- peaks code detect-job --is-job false --rationale "x" --suggested-job-id n-a
181
- # expect: ok, recorded to .peaks/_runtime/<sid>/job-shape.json
182
- ```
3
+ ## 4.0.8 — 2026-08-03 (presence-lease-graph)
4
+
5
+ **BREAKING: IdeAdapter interface requires `resolveCallerId(env?)`**. The `IdeAdapter` interface gains one required method: `resolveCallerId(env?: NodeJS.ProcessEnv) => string`. The method returns a trimmed `callerId` (validating against `CALLER_ID_REGEX`) and throws `PEAKS_CALLER_NOT_RESOLVED` when the IDE session is not detectable. **4.0.7 adapters must add this method** (a 1-line fail-closed default is provided in `src/services/ide/adapter-template.ts` if you need a starter). Slice 4.0.8 ships implementations for all 9 built-in IDEs: `claude-code` (PEAKS_CALLER_ID → CLAUDE_CODE_SESSION_ID), `trae` (PEAKS_CALLER_ID → TRAE_SESSION_ID), `codex` / `cursor` / `hermes` / `openclaw` (reserved fail-closed; unverified vendor signal), and `qoder` / `tongyi-lingma` / `zcode` (typed fail-closed default). The `PLATFORM_FALLBACKS` table is **DELETED** in 4.0.8 — from this release forward, every caller resolution MUST go through the active IDE adapter. Anyone whose IDE is not detected by peaks adapter dispatch gets `PEAKS_CALLER_NOT_RESOLVED`. This is the desired product contract (vendor-neutral).
6
+
7
+ **BREAKING: dispatch record schema v3.2 → v4.0.0**. The persisted dispatch record gains three new fields: `workflowId: string | null`, `graphNodeId: string | null`, `graphRef: string | null`. These are the graph-binding triple that ties a sub-agent dispatch to a prepared workflow node. **Back-compat readers** that pin the on-disk shape must update to handle the new optional fields; readers that ignore unknown fields are unaffected. The `version` literal narrows from `'3.2'` to `'4.0.0'`. The envelope writer `markCompleted` automatically transitions the bound graph node to `envelope-received` with `ackStatus=pending` once the artifact/envelope ↔ `dispatchRef` binding is validated, so the parent ack protocol is observable from the dispatch record itself. The `peaks sub-agent dispatch` CLI gains the required `--graph-node <id>` option (rejects missing / wrong-kind with `PEAKS_GRAPH_NODE_REQUIRED` / `PEAKS_GRAPH_NODE_KIND_INVALID`).
8
+
9
+ **24h mode `inFlightBatch` is now graph-backed** (RD §3 D4d). The production `inFlightBatch` signal in `auto-compact-orchestrator.ts` now calls `workflow-inflight-probe.probeInflightBatch()` (which reads the canonical graph store). The legacy `--in-flight-batch` CLI boolean is preserved as a TEST-ONLY seam gated by `process.env.PEAKS_TEST_SEAM === '1'`; a production run never consults the boolean as truth. The pure decision function `evaluateAutoCompactDecision` retains the boolean signature for the test seam and the red-line override (≥ 95% is still a synchronous hard-gate, regardless of in-flight state).
10
+
11
+ **`peaks skill presence:set` and `presence:clear` are fail-closed**. `presence:set` now refuses any write when no Peaks session is bound (`PEAKS_SESSION_NOT_BOUND`) or the active IDE adapter cannot resolve a valid callerId (`PEAKS_CALLER_NOT_RESOLVED`). The compat shim `setSkillPresence` is preserved for legacy statusline / hook / dashboard consumers; it routes through `presence-lease-service.setPresenceLease` when a session is bound. `presence:clear` no longer performs a raw unlink — workflow-bound leases route through `terminalizeWorkflow` so the graph terminal node, the lease, the caller index, and one observability event are all updated in one lifecycle lock (RD §3 D4c + DR). The compat shim still removes the legacy `active-skill.json` so a stale marker from a prior CLI version cannot resurrect; the canonical lease + index entries are not touched by the shim.
12
+
13
+ **`peaks skill lease gc --project <p>` is the manual lease sweep primitive** (RD §4 D4c). The GC predicate is `now - lastHeartbeat > 1h AND now - startedAt > 24h30m` (24h = peaks-loop "24h AI 编排器" product contract; 30min = boundary tolerance, not safety buffer). GC runs on `presence:set`, `peaks workspace init`, and the manual `peaks skill lease gc` CLI. Corrupt graphs surface as `PEAKS_GRAPH_REF_BROKEN` warnings and are excluded from removal. A follow-up workflow reclaims a cleared lease explicitly.
14
+
15
+ **Canonical presence lease + workflow graph are the source of truth** (RD §2 + §3). The 4.0.7 singleton `active-skill.json` and per-caller `active-skill-<callerId>.json` files are LEGACY READ INPUTS only. The canonical layout is:
16
+
17
+ | Path under `.peaks/_runtime/<sessionId>/` | Purpose |
18
+ |---|---|
19
+ | `graphs/<workflowId>.json` | Canonical workflow graph |
20
+ | `leases/presence-<callerId>-<workflowId>.json` | Canonical caller/workflow presence lease |
21
+ | `presence-index/<callerId>.json` | Additive read index (no lifecycle fields) |
22
+ | `migrations/presence-v1.json` | Idempotent reconcile receipt |
23
+
24
+ Core presence / graph services contain **zero vendor env var lookups**: `CLAUDE_CODE_SESSION_ID | TRAE_SESSION_ID | CURSOR_SESSION_ID | CODEX_SESSION_ID | HERMES_SESSION_ID | OPENCLAW_SESSION_ID` never appear in `src/services/{skills,workflow,code}/**`. The 4.0.7 hard-ban on `require('node:...')` in `src/services/**` is preserved (verified by the audit grep below).
25
+
26
+ ### Anti-fake-green verification
27
+
28
+ 1. `rg "require\(['\"]node:" src/services/` — 0 hits.
29
+ 2. `rg "CLAUDE_CODE_SESSION_ID|TRAE_SESSION_ID|CURSOR_SESSION|CODEX_SESSION|HERMES_SESSION|OPENCLAW_SESSION" src/services/skills/ src/services/workflow/` — 0 hits in non-adapter code.
30
+ 3. `rg "PLATFORM_FALLBACKS" src/services/` — 0 hits.
31
+ 4. Production ESM repro: `node -e "import('./dist/src/services/code/auto-compact-orchestrator.js').then(m => console.log(m.evaluateAutoCompactDecision({ratio: 0.86, inFlightBatch: { hasInFlightBatch: true }})?.action))"` → `'defer'`. (Production now treats `inFlightBatch: true` as a graph-backed signal from `probeInflightBatch`, NOT a CLI boolean. A `defer` at 0.86 closes the 4.0.4 24h偶发 auto-compact-bug.)
32
+ 5. Production ESM repro: `node -e "import('./dist/src/services/skills/presence-lease-service.js').then(m => console.log(typeof m.setPresenceLease, typeof m.gcStalePresenceLeases))"` — both functions exported and runtime-resolvable.
33
+ 6. Production ESM repro: `import presence-lease-service` with `callerId: ''` and no session → `PEAKS_CALLER_NOT_RESOLVED` (or `PEAKS_SESSION_NOT_BOUND`) thrown BEFORE any filesystem write.
34
+
35
+ ### Locked acceptance gate (13 QA test files, all green in 4.0.8)
183
36
 
184
- Source: `.peaks/memory/2026-08-02-ice-cola-dogfood-surface-probe.md`.
185
-
186
- ### PR-meta batch — flow-level enforcement (4 fixes addressing why the LLM bypassed peaks-code in this same session)
187
-
188
- The 4.0.7 dogfood pass surfaced a flow-level bug that the 25
189
- P0/P1/P2 fixes alone could not close: the LLM routed 22 files /
190
- 632 lines of `Edit` calls directly without a single
191
- `peaks sub-agent dispatch rd`. Six root causes were identified
192
- (internal-only debug report, 2026-08-02); the four below close
193
- the four most actionable layers with surgical changes.
194
-
195
- - **PR-meta-1: Edit-enforcement hook** (new
196
- `src/services/hooks/pre-tool-edit-enforcement.sh`).
197
- Opt-in via `peaks hooks install --with-edit-enforcement` (off by
198
- default to preserve the existing peaks-code behavior; the gate
199
- is a HARD layer, not a soft warning). When the hook fires on
200
- an `Edit` / `Write` / `MultiEdit` / `NotebookEdit` call
201
- targeting `src/**`, it checks for an active RD artifact under
202
- `.peaks/_runtime/<sid>/rd/requests/` and either allows the
203
- call (RD exists) or denies with a structured
204
- `permissionDecision: deny` envelope that points the LLM at the
205
- right next step (`peaks request init` + `peaks sub-agent dispatch
206
- rd`). Bypass conditions: tests / docs / RD artifacts / the
207
- hook scripts themselves, a `.peaks/standards/edit-bypass` file
208
- (gitignored escape hatch for hot-fixes), or the env var
209
- `PEAKS_EDIT_BYPASS_24H=1` (24h-mode opt-in).
210
-
211
- - **PR-meta-2: 24h mode scope explicit** (SKILL.md §"24h mode
212
- explicit scope"). 24h mode is `peaks session 24h-mode
213
- transition --state 24H_ACTIVE`. The pre-PR-meta-2 prose was
214
- implicit ("bypass AskUserQuestion") and the LLM over-extended
215
- it to **all** user instructions, including the Code-Change
216
- Red Line. The new section enumerates exactly which 11 steps
217
- 24h mode auto-proceeds at (mode select / step confirm / swarm
218
- pick / QA verdict / etc.) and which 6 rules it does **not**
219
- bypass (Code-Change Red Line, sub-agent dispatch, worktree
220
- authorization, request state machine, external side effects,
221
- 2.x → 1.x reverse migration). The single-sentence rule is
222
- reproducible at the top of the section.
223
-
224
- - **PR-meta-3: scope-counter fail-LOUD** (new
225
- `src/services/hooks/pre-tool-scope-counter.sh`). The
226
- harness-side `SCOPE WARNING: 27 files modified` was
227
- informational noise that the LLM learned to ignore
228
- (`.peaks/memory/2026-07-28-sub-agent-visibility-issue.md`
229
- documents the same friction). PR-meta-3 turns the warning
230
- into a checkpoint: the hook counts cumulative
231
- application-source edits per session in
232
- `.peaks/_runtime/<sid>/edit-counter.json` and emits a
233
- fail-LOUD reflection reminder at thresholds 5, 10, 20. The
234
- LLM cannot continue without writing to
235
- `.peaks/_runtime/<sid>/scope-reflection.md` once a
236
- threshold is hit. Off by default; opt in via
237
- `peaks hooks install --with-scope-counter`.
238
-
239
- - **PR-meta-4: dispatch `--emit-bash-script`** (new flag on
240
- `peaks sub-agent dispatch`). The dry-run architecture is
241
- preserved (the CLI is still the canonical dispatch surface),
242
- but the LLM no longer has to parse `toolCall` args from the
243
- JSON envelope and re-execute them via the Agent tool. The
244
- flag emits a ready-to-exec bash script alongside the
245
- envelope; the LLM runs the script via the Bash tool and
246
- the dispatch fires immediately. The script invokes the
247
- canonical `peaks sub-agent exec` surface, not a side-step.
248
- - **PR-meta-5: `peaks hooks install --with-edit-enforcement /
249
- --with-scope-counter`** (slice 4.0.7). The two opt-in hook
250
- entries added in PR-meta-1 / PR-meta-3 are now reachable via
251
- the standard install flow. Default behavior is unchanged
252
- (gate-enforce only); the flags activate the meta-layer
253
- hooks. The summary envelope's `entries` array now reflects
254
- the actual installed shape (gate-enforce + opt-in entries)
255
- so operators can dry-run + verify before applying.
256
- - **PR-meta-6: `peaks sub-agent exec`** (new sub-command).
257
- Companion to PR-meta-4's `--emit-bash-script`. The script
258
- emitted by the dispatch CLI invokes this surface; `exec`
259
- reads the dispatch record from disk and re-emits the
260
- canonical `toolCall` envelope (envelopeVersion 2.4.0 with
261
- `viaExec: true`). Idempotent: the same exec call can be
262
- replayed any number of times. Mismatch detection: when the
263
- caller-passed session-id / request-id / batch-id / role do
264
- not match the record, `exec` emits a warning but still uses
265
- the record as the canonical source of truth. Failure modes:
266
- `DISPATCH_RECORD_MISSING`, `DISPATCH_RECORD_INVALID`,
267
- `DISPATCH_RECORD_NO_TOOL_CALL`. Tests: 3 cases in
268
- `tests/unit/commands/sub-agent-exec.test.ts`.
269
- - **PR-meta-7: scope-counter fail-CLOSED**. The PR-meta-3
270
- scope counter emitted `additionalContext` (soft signal);
271
- the LLM continued editing without writing the reflection
272
- file. PR-meta-7 hardens the checkpoint: at thresholds 5 /
273
- 10 / 20, the hook now emits `permissionDecision: deny`
274
- until the LLM writes a non-empty reflection file at
275
- `.peaks/_runtime/<sid>/scope-reflection.md`. The next
276
- threshold requires a fresh reflection. Bypass: empty file
277
- at `.peaks/standards/scope-counter-bypass` (gitignored
278
- escape hatch).
279
-
280
- ### Compatibility
281
-
282
- - `peaks hooks install` default behavior is unchanged. The
283
- meta-1 / meta-3 hooks are opt-in (`--with-edit-enforcement`,
284
- `--with-scope-counter`); the meta-2 SKILL.md section is
285
- documentation-only; the meta-4 flag is opt-in
286
- (`--emit-bash-script`).
287
- - All command names, options, and exit codes are preserved.
288
- - Auto-compact 0.85 / 0.95 contract unchanged.
289
- - 24h mode scope is **narrowed**, not widened: same 11
290
- auto-proceed steps, but the previously-implicit bypass is
291
- now explicitly bounded to "AskUserQuestion gates" only.
37
+ - `tests/unit/skills/presence-lease-service.test.ts` — TC-SM-01..08, TC-SM-12 + TC-AG-01/04/05
38
+ - `tests/unit/workflow/workflow-graph-store.test.ts`
39
+ - `tests/unit/workflow/workflow-state-machine.test.ts` — TC-SM-01..12 + TC-AP-01..07
40
+ - `tests/unit/workflow/workflow-inflight-probe.test.ts` — TC-IF-01..09
41
+ - `tests/integration/workflow-presence-lease-lifecycle.test.ts`
42
+ - `tests/integration/workflow-node-ack-protocol.test.ts`
43
+ - `tests/integration/sub-agent-graph-binding.test.ts`
44
+ - `tests/integration/sub-agent-graph-heartbeat.test.ts`
45
+ - `tests/integration/skill-presence-lease-gc.test.ts`
46
+ - `tests/integration/workspace-reconcile-presence-leases.test.ts`
47
+ - `tests/integration/session-24h-graph-inflight.test.ts` — TC-IF-01..09 + TC-AG-06/07
48
+ - `tests/integration/workflow-terminalize-atomicity.test.ts`
49
+ - `tests/integration/ide-caller-resolution.test.ts`
292
50
 
293
51
  ## 4.0.6 — 2026-08-02 (postinstall auto-register outputStyle)
294
52
 
@@ -10,6 +10,7 @@ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
10
10
  import { fail, ok } from 'peaks-loop-shared/result';
11
11
  import { readJobShapeDecision, writeJobShapeDecision, JobShapeDecisionError, JOB_SHAPE_NOT_DECIDED, JOB_SHAPE_ALREADY_DECIDED } from '../../services/code/job-shape-decision.js';
12
12
  import { findProjectRoot } from '../../services/config/config-safety.js';
13
+ import { getSkillPresence } from '../../services/skills/skill-presence-service.js';
13
14
  export function registerCodeJobShapeCommands(code, io) {
14
15
  // v3.1.1 Step 0.8 — Job-shape decision recorder.
15
16
  // The CLI is a *recorder*, not a judge. The LLM supplies the verdict
@@ -147,19 +148,15 @@ export function registerCodeJobShapeCommands(code, io) {
147
148
  });
148
149
  }
149
150
  // Local helper (was `readActiveSid` in code-commands.ts before rid-024 split).
150
- // Slice 4.0.7-dogfood-PR-2: now uses the shared canonical resolver
151
- // (`resolveActiveSessionId` from services/session) so detect-job and
152
- // read-job-shape see the same session id that `peaks session info --active`
153
- // returns. The pre-rid helper read `getSkillPresence` (the
154
- // `.peaks/.active-skill.json` file), which was a different store from
155
- // `.peaks/_runtime/session.json` — any session created via
156
- // `peaks workspace init` that never had `peaks skill presence:set peaks-code`
157
- // called on it (the common case for downstream consumer projects and
158
- // 24h-mode sessions) reported `NO_ACTIVE_SESSION` from this file.
159
- import { resolveActiveSessionId } from '../../services/session/index.js';
151
+ // Both detect-job and read-job-shape need to read the active sid from
152
+ // presence. We re-import getSkillPresence here to avoid the cross-file
153
+ // helper import.
160
154
  function readActiveSidForJobShape(projectRoot) {
161
155
  try {
162
- return resolveActiveSessionId(projectRoot);
156
+ const presence = getSkillPresence(projectRoot);
157
+ if (presence === null || presence === undefined)
158
+ return null;
159
+ return presence.sessionId ?? null;
163
160
  }
164
161
  catch {
165
162
  return null;
@@ -100,22 +100,10 @@ export function registerCodeModeGateCommands(code, io) {
100
100
  // this only adds the structured `stale` reason + an
101
101
  // envelope-level `stalePresence` field so downstream tooling
102
102
  // (statusline, sub-agent dispatch) can act on it.
103
- //
104
- // Slice 4.0.7-dogfood-PR-3 (ice-cola surface probe 2026-08-02):
105
- // 24h-mode sessions explicitly opt out of AskUserQuestion
106
- // (the whole point of 24h is no-pause operation), so the
107
- // stale-presence gate short-circuits to "not stale" when the
108
- // session is in `24H_ACTIVE`. Without this bypass, every
109
- // 24h-mode session hit a `shouldPause: true` wall at Step 1
110
- // even though 24h-mode is designed to be a no-AskUserQuestion
111
- // mode.
112
103
  let stalePresence = null;
113
104
  if (opts.step === 'step-1-mode-select' && opts.ignoreStalePresence !== true) {
114
105
  const projectRoot = opts.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
115
- const sessionIdForGate = readActiveSidForModeGate(projectRoot);
116
- if (!is24hActive(projectRoot, sessionIdForGate)) {
117
- stalePresence = checkStalePresence({ projectRootOverride: projectRoot });
118
- }
106
+ stalePresence = checkStalePresence({ projectRootOverride: projectRoot });
119
107
  }
120
108
  if (stalePresence !== null && stalePresence.stale) {
121
109
  // Build the envelope manually so we can attach the extra
@@ -221,22 +209,9 @@ export function registerCodeModeGateCommands(code, io) {
221
209
  // The mode-gate stale-presence check needs the active sid for the
222
210
  // observability event; we re-import getSkillPresence here to avoid the
223
211
  // cross-file helper import.
224
- // Slice 4.0.7-dogfood-PR-2: also uses the shared canonical resolver
225
- // (resolveActiveSessionId) so the same id flows through detect-job,
226
- // read-job-shape, and should-pause. The pre-rid helper read
227
- // `getSkillPresence` (the `.peaks/.active-skill.json` file) which
228
- // was a different store from `.peaks/_runtime/session.json`.
229
212
  import { getSkillPresence } from '../../services/skills/skill-presence-service.js';
230
- import { resolveActiveSessionId } from '../../services/session/index.js';
231
- import { read24hState } from '../../services/24h-mode/store.js';
232
213
  function readActiveSidForModeGate(projectRoot) {
233
- // Prefer the canonical binding (works for any session created via
234
- // peaks workspace init); fall back to skill presence for sub-skill
235
- // activations that never wrote a binding (legacy edge case).
236
214
  try {
237
- const canonical = resolveActiveSessionId(projectRoot);
238
- if (canonical !== null)
239
- return canonical;
240
215
  const presence = getSkillPresence(projectRoot);
241
216
  if (presence === null || presence === undefined)
242
217
  return null;
@@ -246,20 +221,3 @@ function readActiveSidForModeGate(projectRoot) {
246
221
  return null;
247
222
  }
248
223
  }
249
- /**
250
- * Slice 4.0.7-dogfood-PR-3: returns true when the active session is
251
- * in 24H_ACTIVE state (per `.peaks/_runtime/<sid>/24h-state.json`).
252
- * In that state, the mode-gate's stale-presence check is a false
253
- * positive — 24h-mode explicitly opts out of AskUserQuestion.
254
- */
255
- function is24hActive(projectRoot, sessionId) {
256
- if (sessionId === null || sessionId.length === 0)
257
- return false;
258
- try {
259
- const snap = read24hState(projectRoot, sessionId);
260
- return snap.state === '24H_ACTIVE';
261
- }
262
- catch {
263
- return false;
264
- }
265
- }
@@ -13,6 +13,8 @@ import { runAutoCompact } from '../../services/code/auto-compact-orchestrator.js
13
13
  import { evaluateStep08, STEP_08_BACKUP_REGEX } from '../../services/code/step-08-gate.js';
14
14
  import { evaluateEmitHandoff, JOB_NOT_INITIALIZED, JOB_REMAINING_BLOCKED } from '../../services/code/emit-handoff.js';
15
15
  import { readJobShapeDecision, JobShapeDecisionError } from '../../services/code/job-shape-decision.js';
16
+ import { getSkillPresence } from '../../services/skills/skill-presence-service.js';
17
+ import { probeInFlightBatch } from '../../services/workflow/workflow-inflight-probe.js';
16
18
  export function registerCodeRuntimeCommands(code, io) {
17
19
  addJsonOption(code
18
20
  .command('post-compact-detect')
@@ -70,12 +72,38 @@ export function registerCodeRuntimeCommands(code, io) {
70
72
  process.exitCode = 1;
71
73
  return;
72
74
  }
75
+ // Slice 4.0.8 (D4d): production `inFlightBatch` MUST come from
76
+ // the workflow graph probe. The legacy `--in-flight-batch`
77
+ // boolean CLI flag is a TEST-ONLY seam (gated by
78
+ // `PEAKS_TEST_SEAM === '1'`). When the env flag is unset
79
+ // (the production case), we wire `probeInflightBatch` to the
80
+ // canonical `workflow-inflight-probe.ts` service so the
81
+ // production decision is graph-backed, not lease-age.
82
+ const isTestSeam = process.env.PEAKS_TEST_SEAM === '1';
73
83
  const result = await runAutoCompact({
74
84
  projectRoot: opts.project,
75
85
  sessionId: opts.sessionId ?? readActiveSid(opts.project) ?? undefined,
76
- inFlightBatch: opts.inFlightBatch === true
77
- ? { hasInFlightBatch: true }
78
- : undefined,
86
+ ...(isTestSeam && opts.inFlightBatch === true
87
+ ? { inFlightBatch: { hasInFlightBatch: true } }
88
+ : {}),
89
+ ...(!isTestSeam
90
+ ? {
91
+ probeInflightBatch: () => {
92
+ // Synchronous probe: the workflow-inflight-probe
93
+ // service is pure / synchronous (no I/O). The
94
+ // empty `graphs` array is the production CLI
95
+ // default — callers that want a richer fixture
96
+ // (e.g. `peaks session 24h-mode`) should hand-roll
97
+ // a probe and pass it via the orchestrator's
98
+ // input. When no graph is materialized, the
99
+ // probe returns `inFlightBatch: false`, matching
100
+ // the 4.0.7 zero-pause contract for stock
101
+ // projects.
102
+ const out = probeInFlightBatch({ now: new Date().toISOString(), graphs: [] });
103
+ return out.inFlightBatch === true;
104
+ },
105
+ }
106
+ : {}),
79
107
  force: opts.force === true,
80
108
  bypassRedLine: opts.bypassRedLine === true,
81
109
  mode: modeName
@@ -390,20 +418,14 @@ export function registerCodeRuntimeCommands(code, io) {
390
418
  // Local helper (was `readActiveSid` in code-commands.ts before rid-024 split).
391
419
  // Only the 5 runtime probes above use it; keeping it local avoids the
392
420
  // cross-file helper import.
393
- // Slice 4.0.7-dogfood-PR-2: now uses the shared canonical resolver
394
- // (`resolveActiveSessionId` from services/session) so context-now /
395
- // post-compact-detect / auto-compact / gate-step-08 / emit-handoff see
396
- // the same session id that `peaks session info --active` returns.
397
- // The pre-rid helper read `getSkillPresence` (the
398
- // `.peaks/.active-skill.json` file), which was a different store from
399
- // `.peaks/_runtime/session.json` — the two were kept in sync only when
400
- // `peaks skill presence:set` was called explicitly.
401
- import { resolveActiveSessionId } from '../../services/session/index.js';
402
421
  function readActiveSid(projectRoot) {
403
422
  try {
404
- return resolveActiveSessionId(projectRoot);
423
+ const presence = getSkillPresence(projectRoot);
424
+ if (presence === null || presence === undefined)
425
+ return null;
426
+ return presence.sessionId ?? null;
405
427
  }
406
- catch {
428
+ catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
407
429
  return null;
408
430
  }
409
431
  }