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.
- package/CHANGELOG.md +46 -288
- package/dist/cli/commands/code-job-shape-commands.js +8 -11
- package/dist/cli/commands/code-mode-gate-commands.js +1 -43
- package/dist/cli/commands/code-runtime-commands.js +36 -14
- package/dist/cli/commands/core/skill-command.js +75 -13
- package/dist/cli/commands/core/standards-command.js +5 -50
- package/dist/cli/commands/dispatch-commands.d.ts +20 -0
- package/dist/cli/commands/dispatch-commands.js +39 -38
- package/dist/cli/commands/dispatch-from-dag.js +17 -0
- package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
- package/dist/cli/commands/heartbeat-commands.js +40 -0
- package/dist/cli/commands/hook-handle.js +5 -2
- package/dist/cli/commands/hooks-commands.js +9 -26
- package/dist/cli/commands/loop-eval-commands.js +5 -2
- package/dist/cli/commands/sub-agent-commands.js +0 -2
- package/dist/cli/commands/sub-agent-shared.d.ts +7 -9
- package/dist/cli/commands/workflow-commands.js +8 -0
- package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
- package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
- package/dist/cli/commands/workspace/init-command.js +19 -0
- package/dist/services/audit/backing-detector.d.ts +2 -19
- package/dist/services/audit/backing-detector.js +7 -66
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
- package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
- package/dist/services/audit/enforcers/lint-audit-regression.js +1 -10
- package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
- package/dist/services/code/auto-compact-orchestrator.js +29 -9
- package/dist/services/context/build-dispatch-system-prompt.js +0 -18
- package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
- package/dist/services/dispatch/dispatch-record-writer.js +85 -15
- package/dist/services/dispatch/test-tool-detection.d.ts +0 -14
- package/dist/services/dispatch/test-tool-detection.js +0 -22
- package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
- package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
- package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
- package/dist/services/hooks/presence-marker-detector.js +48 -0
- package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
- package/dist/services/ide/adapters/codex-adapter.js +20 -1
- package/dist/services/ide/adapters/cursor-adapter.js +21 -1
- package/dist/services/ide/adapters/hermes-adapter.js +20 -1
- package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
- package/dist/services/ide/adapters/qoder-adapter.js +20 -1
- package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
- package/dist/services/ide/adapters/trae-adapter.js +21 -1
- package/dist/services/ide/adapters/zcode-adapter.js +19 -0
- package/dist/services/ide/ide-types.d.ts +15 -0
- package/dist/services/observability/observability-service.d.ts +2 -2
- package/dist/services/scan/archetype-service.js +2 -52
- package/dist/services/session/caller-binding-service.d.ts +21 -0
- package/dist/services/session/caller-binding-service.js +35 -0
- package/dist/services/session/caller-id-types.d.ts +62 -13
- package/dist/services/session/caller-id-types.js +25 -13
- package/dist/services/session/index.d.ts +4 -2
- package/dist/services/session/index.js +8 -2
- package/dist/services/session/platform-fallbacks.d.ts +25 -17
- package/dist/services/session/platform-fallbacks.js +26 -28
- package/dist/services/session/resolve-caller-id.d.ts +64 -29
- package/dist/services/session/resolve-caller-id.js +122 -48
- package/dist/services/session/session-manager.d.ts +0 -29
- package/dist/services/session/session-manager.js +0 -33
- package/dist/services/skills/hooks-settings-service.d.ts +0 -41
- package/dist/services/skills/hooks-settings-service.js +5 -61
- package/dist/services/skills/presence-lease-service.d.ts +73 -0
- package/dist/services/skills/presence-lease-service.js +358 -0
- package/dist/services/skills/presence-lease-types.d.ts +75 -0
- package/dist/services/skills/presence-lease-types.js +16 -0
- package/dist/services/skills/skill-presence-service.js +86 -22
- package/dist/services/slice/slice-check-service.js +0 -59
- package/dist/services/standards/project-standards-service.js +0 -44
- package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
- package/dist/services/workflow/workflow-graph-store.js +278 -0
- package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
- package/dist/services/workflow/workflow-graph-types.js +67 -0
- package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
- package/dist/services/workflow/workflow-inflight-probe.js +86 -0
- package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
- package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
- package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
- package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
- package/dist/services/workspace/reconcile-service.d.ts +31 -0
- package/dist/services/workspace/reconcile-service.js +132 -1
- package/package.json +4 -4
- package/skills/peaks-code/SKILL.md +5 -43
- package/skills/peaks-code/references/completion-handoff.md +1 -1
- package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
- package/dist/cli/commands/sub-agent-exec-command.d.ts +0 -25
- package/dist/cli/commands/sub-agent-exec-command.js +0 -132
- package/dist/services/standards/find-skills-integration.d.ts +0 -59
- 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.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
the
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
`
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
-
|
|
196
|
-
|
|
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
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
? { hasInFlightBatch: true }
|
|
78
|
-
:
|
|
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
|
-
|
|
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
|
}
|