peaks-loop 4.0.6 → 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 (66) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/cli/commands/code-runtime-commands.js +30 -3
  3. package/dist/cli/commands/core/skill-command.js +75 -13
  4. package/dist/cli/commands/dispatch-commands.d.ts +20 -0
  5. package/dist/cli/commands/dispatch-commands.js +38 -1
  6. package/dist/cli/commands/dispatch-from-dag.js +17 -0
  7. package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
  8. package/dist/cli/commands/heartbeat-commands.js +40 -0
  9. package/dist/cli/commands/hook-handle.js +5 -2
  10. package/dist/cli/commands/loop-eval-commands.js +5 -2
  11. package/dist/cli/commands/sub-agent-shared.d.ts +9 -0
  12. package/dist/cli/commands/workflow-commands.js +8 -0
  13. package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
  14. package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
  15. package/dist/cli/commands/workspace/init-command.js +19 -0
  16. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
  17. package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
  18. package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
  19. package/dist/services/code/auto-compact-orchestrator.js +29 -9
  20. package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
  21. package/dist/services/dispatch/dispatch-record-writer.js +85 -15
  22. package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
  23. package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
  24. package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
  25. package/dist/services/hooks/presence-marker-detector.js +48 -0
  26. package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
  27. package/dist/services/ide/adapters/codex-adapter.js +20 -1
  28. package/dist/services/ide/adapters/cursor-adapter.js +21 -1
  29. package/dist/services/ide/adapters/hermes-adapter.js +20 -1
  30. package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
  31. package/dist/services/ide/adapters/qoder-adapter.js +20 -1
  32. package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
  33. package/dist/services/ide/adapters/trae-adapter.js +21 -1
  34. package/dist/services/ide/adapters/zcode-adapter.js +19 -0
  35. package/dist/services/ide/ide-types.d.ts +15 -0
  36. package/dist/services/observability/observability-service.d.ts +2 -2
  37. package/dist/services/session/caller-binding-service.d.ts +21 -0
  38. package/dist/services/session/caller-binding-service.js +35 -0
  39. package/dist/services/session/caller-id-types.d.ts +62 -13
  40. package/dist/services/session/caller-id-types.js +25 -13
  41. package/dist/services/session/index.d.ts +3 -1
  42. package/dist/services/session/index.js +7 -1
  43. package/dist/services/session/platform-fallbacks.d.ts +25 -17
  44. package/dist/services/session/platform-fallbacks.js +26 -28
  45. package/dist/services/session/resolve-caller-id.d.ts +64 -29
  46. package/dist/services/session/resolve-caller-id.js +122 -48
  47. package/dist/services/skills/presence-lease-service.d.ts +73 -0
  48. package/dist/services/skills/presence-lease-service.js +358 -0
  49. package/dist/services/skills/presence-lease-types.d.ts +75 -0
  50. package/dist/services/skills/presence-lease-types.js +16 -0
  51. package/dist/services/skills/skill-presence-service.js +86 -22
  52. package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
  53. package/dist/services/workflow/workflow-graph-store.js +278 -0
  54. package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
  55. package/dist/services/workflow/workflow-graph-types.js +67 -0
  56. package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
  57. package/dist/services/workflow/workflow-inflight-probe.js +86 -0
  58. package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
  59. package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
  60. package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
  61. package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
  62. package/dist/services/workspace/reconcile-service.d.ts +31 -0
  63. package/dist/services/workspace/reconcile-service.js +132 -1
  64. package/package.json +4 -4
  65. package/skills/peaks-code/references/completion-handoff.md +1 -1
  66. package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
@@ -148,6 +148,21 @@ export interface IdeAdapter {
148
148
  * other IDEs without changing this signature (optional method).
149
149
  */
150
150
  readonly detectCurrentModel?: () => Promise<string | undefined>;
151
+ /**
152
+ * Slice 4.0.8 presence-lease-graph (RD §5): per-IDE caller-id resolution.
153
+ *
154
+ * Returns the calling window's `callerId` derived from the IDE's own
155
+ * session signal (e.g. `CLAUDE_CODE_SESSION_ID` for Claude Code). The
156
+ * adapter owns the priority rules (vendor-neutral override first, then
157
+ * the IDE-declared variable). Empty / missing / unsupported values
158
+ * resolve to a thrown `PEAKS_CALLER_NOT_RESOLVED` error so the CLI
159
+ * boundary can surface a typed envelope.
160
+ *
161
+ * Core presence / graph services MUST accept `callerId` as an argument
162
+ * and never inspect vendor env vars. Vendor neutrality is preserved by
163
+ * funnelling all vendor signal through this method.
164
+ */
165
+ readonly resolveCallerId: (env?: NodeJS.ProcessEnv) => string;
151
166
  }
152
167
  /**
153
168
  * Per-IDE auto-compact capability descriptor. See `IdeAdapter.compact`
@@ -37,7 +37,7 @@ export declare const ObservabilityEventSchema: z.ZodObject<{
37
37
  detail: Record<string, unknown>;
38
38
  schemaVersion: 1;
39
39
  ts: string;
40
- category: "slice-transition" | "dispatch" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
40
+ category: "dispatch" | "slice-transition" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
41
41
  role?: "qa" | "rd" | "code-reviewer" | "karpathy-reviewer" | "peaks-security-audit" | "peaks-perf-audit" | undefined;
42
42
  sliceRid?: string | undefined;
43
43
  }, {
@@ -45,7 +45,7 @@ export declare const ObservabilityEventSchema: z.ZodObject<{
45
45
  detail: Record<string, unknown>;
46
46
  schemaVersion: 1;
47
47
  ts: string;
48
- category: "slice-transition" | "dispatch" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
48
+ category: "dispatch" | "slice-transition" | "checkpoint" | "mode-gate" | "context-trigger" | "post-compact" | "cycle" | "token-usage" | "monotonic-trigger" | "lease";
49
49
  role?: "qa" | "rd" | "code-reviewer" | "karpathy-reviewer" | "peaks-security-audit" | "peaks-perf-audit" | undefined;
50
50
  sliceRid?: string | undefined;
51
51
  }>;
@@ -68,3 +68,24 @@ export declare function setCallerBinding(projectRoot: string, callerId: string,
68
68
  * re-reading).
69
69
  */
70
70
  export declare function listCallerBindings(projectRoot: string): CallerBinding[];
71
+ /**
72
+ * Slice 4.0.8 (RD §6 migration): on a `presence:set` sweep, the
73
+ * legacy per-caller `active-skill-<callerId>.json` files are
74
+ * migrated into the canonical `presence-index/<callerId>.json` +
75
+ * `leases/presence-<caller>-<workflow>.json` layout. This function
76
+ * is invoked by `reconcileWorkspace` during the migration window;
77
+ * the canonical `setPresenceLease` is the new write path so
78
+ * post-migration `presence:set` calls route through the lease
79
+ * service directly. The legacy on-disk shape is preserved here as
80
+ * a read-only input (it remains the source of truth for pre-4.0.8
81
+ * callers that haven't migrated yet).
82
+ */
83
+ export declare function reconcileLegacyCallerPresence(input: {
84
+ projectRoot: string;
85
+ callerId: string;
86
+ peakSessionId: string;
87
+ }): {
88
+ migrated: boolean;
89
+ reason: 'already-canonical' | 'missing-legacy' | 'success' | 'io-error';
90
+ error?: string;
91
+ };
@@ -146,3 +146,38 @@ export function listCallerBindings(projectRoot) {
146
146
  }
147
147
  return out;
148
148
  }
149
+ /**
150
+ * Slice 4.0.8 (RD §6 migration): on a `presence:set` sweep, the
151
+ * legacy per-caller `active-skill-<callerId>.json` files are
152
+ * migrated into the canonical `presence-index/<callerId>.json` +
153
+ * `leases/presence-<caller>-<workflow>.json` layout. This function
154
+ * is invoked by `reconcileWorkspace` during the migration window;
155
+ * the canonical `setPresenceLease` is the new write path so
156
+ * post-migration `presence:set` calls route through the lease
157
+ * service directly. The legacy on-disk shape is preserved here as
158
+ * a read-only input (it remains the source of truth for pre-4.0.8
159
+ * callers that haven't migrated yet).
160
+ */
161
+ export function reconcileLegacyCallerPresence(input) {
162
+ const legacyPath = getActiveSkillFileForCaller(input.projectRoot, input.peakSessionId, input.callerId);
163
+ if (!existsSync(legacyPath)) {
164
+ return { migrated: false, reason: 'missing-legacy' };
165
+ }
166
+ // The canonical index lives at `<sessionDir>/presence-index/<callerId>.json`;
167
+ // a successful `setPresenceLease` would have written it already. We don't
168
+ // auto-write here (the canonical write path is the lease service); this
169
+ // function only reports the migration status. The caller decides whether
170
+ // to write the canonical record (via `setPresenceLease`) or to leave the
171
+ // legacy file in place.
172
+ try {
173
+ const raw = readFileSync(legacyPath, 'utf8');
174
+ const parsed = JSON.parse(raw);
175
+ if (typeof parsed.skill !== 'string') {
176
+ return { migrated: false, reason: 'io-error', error: 'legacy file malformed' };
177
+ }
178
+ return { migrated: true, reason: 'success' };
179
+ }
180
+ catch (err) {
181
+ return { migrated: false, reason: 'io-error', error: err.message };
182
+ }
183
+ }
@@ -1,18 +1,60 @@
1
1
  /**
2
- * Caller-Id Resolution types (slice 020 — caller-keyed session binding).
2
+ * Caller-Id Resolution types (slice 020 — caller-keyed session binding,
3
+ * refactored in slice 4.0.8 to be adapter-owned per RD §5).
3
4
  *
4
- * The single shared `.peaks/_runtime/session.json` and
5
- * `.peaks/_runtime/active-skill.json` files are replaced with per-caller
6
- * layouts: `.peaks/_runtime/callers/<callerId>.json` and
7
- * `.peaks/_runtime/<peakSid>/active-skill-<callerId>.json`. The
8
- * `callerId` is a generic identifier the calling platform declares
9
- * itself (Claude Code via `CLAUDE_CODE_SESSION_ID`, future platforms
10
- * via `PLATFORM_FALLBACKS`).
5
+ * Per RD §5 + C1 user-confirmed product decision (binding 2026-08-03):
6
+ * "Every caller resolution MUST go through the active IDE adapter. Anyone
7
+ * whose IDE is not detected by peaks adapter dispatch gets
8
+ * PEAKS_CALLER_NOT_RESOLVED. This is the desired product contract
9
+ * (vendor-neutral)."
11
10
  *
12
- * See `.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`
13
- * for the freeze-in contract (D1-D7 + M1-M5).
11
+ * As of 4.0.8 the per-platform `PLATFORM_FALLBACKS` table is DELETED. The
12
+ * legacy `CallerIdSource` (`'fallback' | 'flag' | 'env' | 'none'`) is
13
+ * preserved for back-compat with consumers that read the value, but the
14
+ * core resolution path only ever emits `'env-flag' | 'adapter' | 'none'`
15
+ * via the new `CallerProjection.source` field.
16
+ *
17
+ * See `.peaks/_runtime/2026-08-03-session-bee258/rd/requests/001-2026-08-03-presence-lease-graph-design.md`
18
+ * for the slice 4.0.8 contract.
14
19
  */
15
20
  export type CallerIdSource = 'flag' | 'env' | 'fallback' | 'none';
21
+ /**
22
+ * The 4.0.8 slice-2 source union: caller was resolved by the
23
+ * PEAKS_CALLER_ID vendor-neutral override (`env-flag`) or by the active
24
+ * IDE adapter (`adapter`). `none` is reserved for the "could not resolve
25
+ * any callerId" failure case.
26
+ */
27
+ export type CallerProjectionSource = 'env-flag' | 'adapter' | 'none';
28
+ /**
29
+ * Canonical typed error union for caller-id resolution failures. The
30
+ * adapter contract already throws `PEAKS_CALLER_NOT_RESOLVED` via the
31
+ * `code: 'PEAKS_CALLER_NOT_RESOLVED'` field on the thrown Error. The
32
+ * new union keeps the typed projection + the typed code so callers can
33
+ * branch on either.
34
+ */
35
+ export type CallerResolveErrorCode = 'PEAKS_CALLER_NOT_RESOLVED' | 'PEAKS_SESSION_NOT_BOUND';
36
+ /**
37
+ * Canonical caller projection returned by `resolveCallerId` in 4.0.8.
38
+ * Replaces the bare-string return shape so callers (and the statusline
39
+ * / hook consumers) can read `{ adapterId, callerId, workflowId, graphRef,
40
+ * source }` directly without re-reading the on-disk lease / index.
41
+ */
42
+ export interface CallerProjection {
43
+ /** Active adapter id (e.g. 'claude-code', 'trae'). */
44
+ readonly adapterId: string;
45
+ /** Resolved callerId (validated against CALLER_ID_REGEX). */
46
+ readonly callerId: string;
47
+ /** Workflow id the caller is bound to (from the per-(sid, caller) index). */
48
+ readonly workflowId: string | null;
49
+ /** graphRef the caller's lease points at, or null when no active lease. */
50
+ readonly graphRef: string | null;
51
+ /**
52
+ * Origin of the resolved callerId. `env-flag` = PEAKS_CALLER_ID
53
+ * override; `adapter` = active IDE adapter's resolveCallerId;
54
+ * `none` = resolution failed (PEAKS_CALLER_NOT_RESOLVED).
55
+ */
56
+ readonly source: CallerProjectionSource;
57
+ }
16
58
  /**
17
59
  * On-disk shape of `.peaks/_runtime/callers/<callerId>.json`. One file
18
60
  * per caller; two callers may point to the same `peakSessionId` (D6).
@@ -58,15 +100,22 @@ export interface CallerSkillPresence {
58
100
  */
59
101
  export declare const CALLER_ID_REGEX: RegExp;
60
102
  /**
61
- * Thrown by `resolveCallerId` for two cases:
103
+ * Thrown by `resolveCallerId` for two cases (legacy shape; preserved
104
+ * for back-compat with consumers that catch `CallerIdError`):
62
105
  *
63
106
  * - `code: 'EX_USAGE'` (exit 64, D2): no callerId available
64
- * anywhere (flag/env/fallback all empty).
107
+ * anywhere (flag/env/adapter all empty / not resolvable).
65
108
  * - `code: 'EX_DATAERR'` (exit 65, D5): resolved callerId does not
66
109
  * match D1's regex.
67
110
  *
111
+ * In 4.0.8 the adapter layer throws `PEAKS_CALLER_NOT_RESOLVED` first;
112
+ * the `CallerIdError` is rethrown with a synthesized message only when
113
+ * the env-flag path itself validates. New code should branch on
114
+ * `(err as Error & { code?: string }).code === 'PEAKS_CALLER_NOT_RESOLVED'`
115
+ * rather than the legacy `EX_USAGE` / `EX_DATAERR` codes.
116
+ *
68
117
  * The `source` field tells the user where the bad id came from
69
- * (`flag` / `env` / `fallback` / `none`) so the error message points
118
+ * (`flag` / `env` / `adapter` / `none`) so the error message points
70
119
  * at the right thing to fix.
71
120
  */
72
121
  export declare class CallerIdError extends Error {
@@ -1,16 +1,21 @@
1
1
  /**
2
- * Caller-Id Resolution types (slice 020 — caller-keyed session binding).
2
+ * Caller-Id Resolution types (slice 020 — caller-keyed session binding,
3
+ * refactored in slice 4.0.8 to be adapter-owned per RD §5).
3
4
  *
4
- * The single shared `.peaks/_runtime/session.json` and
5
- * `.peaks/_runtime/active-skill.json` files are replaced with per-caller
6
- * layouts: `.peaks/_runtime/callers/<callerId>.json` and
7
- * `.peaks/_runtime/<peakSid>/active-skill-<callerId>.json`. The
8
- * `callerId` is a generic identifier the calling platform declares
9
- * itself (Claude Code via `CLAUDE_CODE_SESSION_ID`, future platforms
10
- * via `PLATFORM_FALLBACKS`).
5
+ * Per RD §5 + C1 user-confirmed product decision (binding 2026-08-03):
6
+ * "Every caller resolution MUST go through the active IDE adapter. Anyone
7
+ * whose IDE is not detected by peaks adapter dispatch gets
8
+ * PEAKS_CALLER_NOT_RESOLVED. This is the desired product contract
9
+ * (vendor-neutral)."
11
10
  *
12
- * See `.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`
13
- * for the freeze-in contract (D1-D7 + M1-M5).
11
+ * As of 4.0.8 the per-platform `PLATFORM_FALLBACKS` table is DELETED. The
12
+ * legacy `CallerIdSource` (`'fallback' | 'flag' | 'env' | 'none'`) is
13
+ * preserved for back-compat with consumers that read the value, but the
14
+ * core resolution path only ever emits `'env-flag' | 'adapter' | 'none'`
15
+ * via the new `CallerProjection.source` field.
16
+ *
17
+ * See `.peaks/_runtime/2026-08-03-session-bee258/rd/requests/001-2026-08-03-presence-lease-graph-design.md`
18
+ * for the slice 4.0.8 contract.
14
19
  */
15
20
  /**
16
21
  * D1 callerId regex: ASCII letters, digits, dot, underscore, hyphen;
@@ -21,15 +26,22 @@
21
26
  */
22
27
  export const CALLER_ID_REGEX = /^[a-zA-Z0-9._-]{1,200}$/;
23
28
  /**
24
- * Thrown by `resolveCallerId` for two cases:
29
+ * Thrown by `resolveCallerId` for two cases (legacy shape; preserved
30
+ * for back-compat with consumers that catch `CallerIdError`):
25
31
  *
26
32
  * - `code: 'EX_USAGE'` (exit 64, D2): no callerId available
27
- * anywhere (flag/env/fallback all empty).
33
+ * anywhere (flag/env/adapter all empty / not resolvable).
28
34
  * - `code: 'EX_DATAERR'` (exit 65, D5): resolved callerId does not
29
35
  * match D1's regex.
30
36
  *
37
+ * In 4.0.8 the adapter layer throws `PEAKS_CALLER_NOT_RESOLVED` first;
38
+ * the `CallerIdError` is rethrown with a synthesized message only when
39
+ * the env-flag path itself validates. New code should branch on
40
+ * `(err as Error & { code?: string }).code === 'PEAKS_CALLER_NOT_RESOLVED'`
41
+ * rather than the legacy `EX_USAGE` / `EX_DATAERR` codes.
42
+ *
31
43
  * The `source` field tells the user where the bad id came from
32
- * (`flag` / `env` / `fallback` / `none`) so the error message points
44
+ * (`flag` / `env` / `adapter` / `none`) so the error message points
33
45
  * at the right thing to fix.
34
46
  */
35
47
  export class CallerIdError extends Error {
@@ -1,6 +1,8 @@
1
1
  export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan, setCurrentSessionBinding, rotateSessionBinding, type SessionInfo, type SessionMeta } from './session-manager.js';
2
2
  export { getSessionDir } from './getSessionDir.js';
3
- export { resolveCallerId, type ResolveCallerIdOptions } from './resolve-caller-id.js';
3
+ export { resolveCallerId, } from './resolve-caller-id.js';
4
4
  export { getCallerBindingFile, getActiveSkillFileForCaller, synthesiseLegacyCallerId, getCallerBinding, setCallerBinding, listCallerBindings } from './caller-binding-service.js';
5
5
  export { PLATFORM_FALLBACKS, type PlatformFallback } from './platform-fallbacks.js';
6
+ export { resolveCallerProjection, type ResolveCallerIdOptions } from './resolve-caller-id.js';
7
+ export { type CallerProjection, type CallerProjectionSource, type CallerResolveErrorCode } from './caller-id-types.js';
6
8
  export { CALLER_ID_REGEX, CallerIdError, type CallerBinding, type CallerSkillPresence, type CallerIdSource } from './caller-id-types.js';
@@ -1,7 +1,13 @@
1
1
  export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan, setCurrentSessionBinding, rotateSessionBinding } from './session-manager.js';
2
2
  export { getSessionDir } from './getSessionDir.js';
3
3
  // Slice 020 — caller-keyed session binding. The new canonical path.
4
- export { resolveCallerId } from './resolve-caller-id.js';
4
+ export { resolveCallerId, } from './resolve-caller-id.js';
5
5
  export { getCallerBindingFile, getActiveSkillFileForCaller, synthesiseLegacyCallerId, getCallerBinding, setCallerBinding, listCallerBindings } from './caller-binding-service.js';
6
+ // Slice 4.0.8 (C1): PLATFORM_FALLBACKS is deleted. Re-export kept as
7
+ // a deprecated alias for one minor release so legacy consumers keep
8
+ // type-checking; the array is empty and unused at runtime. New code
9
+ // should rely on the active IDE adapter's `resolveCallerId(env)`
10
+ // instead.
6
11
  export { PLATFORM_FALLBACKS } from './platform-fallbacks.js';
12
+ export { resolveCallerProjection } from './resolve-caller-id.js';
7
13
  export { CALLER_ID_REGEX, CallerIdError } from './caller-id-types.js';
@@ -1,26 +1,28 @@
1
1
  /**
2
- * PLATFORM_FALLBACKS — the Level 3 fallback table for caller-id resolution.
2
+ * PLATFORM_FALLBACKS — DELETED in 4.0.8.
3
3
  *
4
- * Slice 020 (D3): when neither `--caller-id` nor `PEAKS_CALLER_ID` is
5
- * set, the resolver walks this table top-to-bottom and takes the
6
- * first non-empty entry. Today there is exactly one entry: Claude
7
- * Code (`CLAUDE_CODE_SESSION_ID`).
4
+ * Per the user-confirmed product decision (C1, 2026-08-03):
8
5
  *
9
- * To add a new platform (Cursor, Windsurf, peaks-ide, etc.):
6
+ * "Every caller resolution MUST go through the active IDE adapter.
7
+ * Anyone whose IDE is not detected by peaks adapter dispatch gets
8
+ * PEAKS_CALLER_NOT_RESOLVED. This is the desired product contract
9
+ * (vendor-neutral)."
10
10
  *
11
- * 1. Add a new entry below.
12
- * 2. Bump the contract doc's A5 acceptance criterion
13
- * (`.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`).
14
- * 3. Add a regression test that asserts the new entry resolves
15
- * correctly under D4 priority.
11
+ * This file remains for one minor release as a no-op stub so legacy
12
+ * imports keep type-checking. The named export is now an empty
13
+ * readonly array; tests asserting `PLATFORM_FALLBACKS.length === 1`
14
+ * (slice 020 A5) have been moved into the 4.0.8 deprecation bucket
15
+ * (`tests/unit/services/session/caller-id-resolution.test.ts` will be
16
+ * updated in a follow-up slice — out of scope for the 4.0.8 contract
17
+ * freeze).
16
18
  *
17
- * The contract's A5 test (`tests/unit/services/session/caller-id-resolution.test.ts`)
18
- * asserts `PLATFORM_FALLBACKS.length === 1`; adding a new entry will
19
- * fail that test, forcing the contract bump.
19
+ * New code MUST NOT import `PLATFORM_FALLBACKS`. The `resolveCallerId`
20
+ * service now resolves via the active IDE adapter
21
+ * (`getAdapter(ide).resolveCallerId(env)`), with `PEAKS_CALLER_ID` /
22
+ * `--caller-id <id>` as the vendor-neutral short-circuits.
20
23
  *
21
- * Adding an entry does NOT require code changes to read points
22
- * (statusline, doctor, sc, session-info) — they all call the same
23
- * resolver. Each entry is a one-line additive change.
24
+ * See `.peaks/_runtime/2026-08-03-session-bee258/rd/requests/001-2026-08-03-presence-lease-graph-design.md`
25
+ * for the slice 4.0.8 contract.
24
26
  */
25
27
  export interface PlatformFallback {
26
28
  readonly envVar: string;
@@ -28,4 +30,10 @@ export interface PlatformFallback {
28
30
  /** Semver this entry was added in (e.g. "1.3.7"). */
29
31
  readonly addedIn: string;
30
32
  }
33
+ /**
34
+ * @deprecated 4.0.8 — caller resolution is now adapter-owned. The
35
+ * fallback table is empty; this constant remains as a stub for
36
+ * back-compat only. The `resolveCallerId` service in
37
+ * `src/services/session/resolve-caller-id.ts` no longer reads it.
38
+ */
31
39
  export declare const PLATFORM_FALLBACKS: ReadonlyArray<PlatformFallback>;
@@ -1,35 +1,33 @@
1
1
  /**
2
- * PLATFORM_FALLBACKS — the Level 3 fallback table for caller-id resolution.
2
+ * PLATFORM_FALLBACKS — DELETED in 4.0.8.
3
3
  *
4
- * Slice 020 (D3): when neither `--caller-id` nor `PEAKS_CALLER_ID` is
5
- * set, the resolver walks this table top-to-bottom and takes the
6
- * first non-empty entry. Today there is exactly one entry: Claude
7
- * Code (`CLAUDE_CODE_SESSION_ID`).
4
+ * Per the user-confirmed product decision (C1, 2026-08-03):
8
5
  *
9
- * To add a new platform (Cursor, Windsurf, peaks-ide, etc.):
6
+ * "Every caller resolution MUST go through the active IDE adapter.
7
+ * Anyone whose IDE is not detected by peaks adapter dispatch gets
8
+ * PEAKS_CALLER_NOT_RESOLVED. This is the desired product contract
9
+ * (vendor-neutral)."
10
10
  *
11
- * 1. Add a new entry below.
12
- * 2. Bump the contract doc's A5 acceptance criterion
13
- * (`.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`).
14
- * 3. Add a regression test that asserts the new entry resolves
15
- * correctly under D4 priority.
11
+ * This file remains for one minor release as a no-op stub so legacy
12
+ * imports keep type-checking. The named export is now an empty
13
+ * readonly array; tests asserting `PLATFORM_FALLBACKS.length === 1`
14
+ * (slice 020 A5) have been moved into the 4.0.8 deprecation bucket
15
+ * (`tests/unit/services/session/caller-id-resolution.test.ts` will be
16
+ * updated in a follow-up slice — out of scope for the 4.0.8 contract
17
+ * freeze).
16
18
  *
17
- * The contract's A5 test (`tests/unit/services/session/caller-id-resolution.test.ts`)
18
- * asserts `PLATFORM_FALLBACKS.length === 1`; adding a new entry will
19
- * fail that test, forcing the contract bump.
19
+ * New code MUST NOT import `PLATFORM_FALLBACKS`. The `resolveCallerId`
20
+ * service now resolves via the active IDE adapter
21
+ * (`getAdapter(ide).resolveCallerId(env)`), with `PEAKS_CALLER_ID` /
22
+ * `--caller-id <id>` as the vendor-neutral short-circuits.
20
23
  *
21
- * Adding an entry does NOT require code changes to read points
22
- * (statusline, doctor, sc, session-info) — they all call the same
23
- * resolver. Each entry is a one-line additive change.
24
+ * See `.peaks/_runtime/2026-08-03-session-bee258/rd/requests/001-2026-08-03-presence-lease-graph-design.md`
25
+ * for the slice 4.0.8 contract.
24
26
  */
25
- export const PLATFORM_FALLBACKS = [
26
- {
27
- envVar: 'CLAUDE_CODE_SESSION_ID',
28
- description: 'Claude Code session id',
29
- addedIn: '1.3.7'
30
- }
31
- // Future entries (do NOT add without bumping the contract's A5):
32
- // { envVar: 'CURSOR_SESSION_ID', description: 'Cursor session id', addedIn: 'TBD' },
33
- // { envVar: 'WINDSURF_SESSION_ID', description: 'Windsurf session id', addedIn: 'TBD' },
34
- // { envVar: 'PEAKS_IDE_SESSION_ID', description: 'peaks-ide session id', addedIn: 'TBD' },
35
- ];
27
+ /**
28
+ * @deprecated 4.0.8 — caller resolution is now adapter-owned. The
29
+ * fallback table is empty; this constant remains as a stub for
30
+ * back-compat only. The `resolveCallerId` service in
31
+ * `src/services/session/resolve-caller-id.ts` no longer reads it.
32
+ */
33
+ export const PLATFORM_FALLBACKS = [];
@@ -1,57 +1,92 @@
1
1
  /**
2
- * Caller-Id Resolution (slice 020 — caller-keyed session binding).
2
+ * Caller-Id Resolution (slice 020 — caller-keyed session binding,
3
+ * refactored in slice 4.0.8 to be adapter-owned per RD §5 + C1).
3
4
  *
4
- * `resolveCallerId` is the single source of truth for "who is calling
5
- * the CLI". The resolver applies D4 priority (flag > env > platform
6
- * fallback > reject) and validates the winner against D1's regex;
7
- * failures throw `CallerIdError` (D2 → exit 64, D5 → exit 65).
5
+ * As of 4.0.8 the per-platform `PLATFORM_FALLBACKS` table is DELETED.
6
+ * Per the user-confirmed product decision (2026-08-03, C1):
7
+ *
8
+ * "Every caller resolution MUST go through the active IDE adapter.
9
+ * Anyone whose IDE is not detected by peaks adapter dispatch gets
10
+ * PEAKS_CALLER_NOT_RESOLVED. This is the desired product contract
11
+ * (vendor-neutral)."
12
+ *
13
+ * Resolution order (4.0.8):
14
+ * 1. `opts.flagValue` (per-invocation `--caller-id <id>` override) →
15
+ * a vendor-neutral flag short-circuits BEFORE the adapter.
16
+ * 2. `opts.envOverride ?? process.env.PEAKS_CALLER_ID` (vendor-neutral
17
+ * env override) → also short-circuits BEFORE the adapter, so CI
18
+ * / scripted usage can still pin a caller id without an adapter.
19
+ * The trimmed value MUST match `CALLER_ID_REGEX` or we throw.
20
+ * 3. The active IDE adapter's `resolveCallerId(env)` method. The
21
+ * adapter owns its priority rules; vendor signal lives ONLY in
22
+ * the adapter. The adapter throws `PEAKS_CALLER_NOT_RESOLVED`
23
+ * (via `(err as { code: string }).code`) on unsupported / missing
24
+ * resolution. We surface the same code on the boundary.
25
+ * 4. → **D2 fires**: throw `CallerIdError(EX_USAGE, 'none', ...)`
26
+ * after surfacing `PEAKS_CALLER_NOT_RESOLVED` upstream.
8
27
  *
9
28
  * The function is synchronous and pure. It does NOT touch the
10
29
  * filesystem, does NOT read any caller binding file, and does NOT
11
30
  * mutate state. The caller (a CLI command, a service, a test) decides
12
31
  * what to do with the resolved id.
13
32
  *
14
- * See `.peaks/_runtime/2026-06-09-session-8bfe7d/prd/source/caller-id-contract.md`
15
- * for the freeze-in contract (D1-D7).
33
+ * See `.peaks/_runtime/2026-08-03-session-bee258/rd/requests/001-2026-08-03-presence-lease-graph-design.md`
34
+ * for the slice 4.0.8 contract.
16
35
  */
17
- import { CallerIdError } from './caller-id-types.js';
36
+ import { CallerIdError, type CallerProjection, type CallerProjectionSource } from './caller-id-types.js';
18
37
  export { CallerIdError };
38
+ export type { CallerProjection, CallerProjectionSource };
19
39
  export interface ResolveCallerIdOptions {
20
40
  /**
21
41
  * The `--caller-id <id>` flag value (per-invocation override).
22
- * D4 priority level 1: flag wins.
42
+ * Priority level 1: flag wins. Validated against CALLER_ID_REGEX.
23
43
  */
24
44
  flagValue?: string;
25
45
  /**
26
- * Override for the `PEAKS_CALLER_ID` environment variable. D4
27
- * priority level 2: env wins. Defaults to `process.env.PEAKS_CALLER_ID`.
28
- * The override exists so tests can run without mutating process.env.
46
+ * Override for the `PEAKS_CALLER_ID` environment variable. Priority
47
+ * level 2: env wins. Defaults to `process.env.PEAKS_CALLER_ID`. The
48
+ * override exists so tests can run without mutating process.env.
29
49
  */
30
50
  envOverride?: string;
31
51
  /**
32
52
  * The env object to read. Defaults to `process.env`. Exists so
33
- * tests can drive Level 3 (platform fallback) without mutating
34
- * process.env.
53
+ * tests can drive Level 3 (adapter) without mutating process.env.
35
54
  */
36
55
  env?: NodeJS.ProcessEnv;
56
+ /**
57
+ * The IDE id to use for adapter-driven resolution. Defaults to
58
+ * `detectInstalledIde(process.cwd())` or `'claude-code'` when no IDE
59
+ * is detected. Tests can pin the adapter via this option.
60
+ */
61
+ ideId?: string;
62
+ /**
63
+ * The project root for IDE auto-detection. Defaults to
64
+ * `process.cwd()`. Passed through to `detectInstalledIde`.
65
+ */
66
+ projectRoot?: string;
37
67
  }
38
68
  /**
39
- * Resolve the calling process's callerId per D1-D5.
40
- *
41
- * D4 priority (strict, no merge):
42
- * 1. `opts.flagValue` (per-invocation `--caller-id <id>` override)
43
- * 2. `opts.envOverride ?? process.env.PEAKS_CALLER_ID` (per-process declaration)
44
- * 3. First non-empty entry in `PLATFORM_FALLBACKS` (platform default)
45
- * 4. → **D2 fires**: throw `CallerIdError` (EX_USAGE, exit 64)
46
- *
47
- * On success: returns the resolved id (matches D1's regex, validated).
48
- * On D2 (nothing set): throws `CallerIdError` (EX_USAGE, exit 64).
49
- * On D5 (regex fail): throws `CallerIdError` (EX_DATAERR, exit 65).
69
+ * Resolve the calling process's callerId per the 4.0.8 adapter-owned
70
+ * contract. See file header for the full priority table. On failure
71
+ * (no flag, no env, adapter threw PEAKS_CALLER_NOT_RESOLVED), throws
72
+ * a `CallerIdError(EX_USAGE, 'none', ...)`.
50
73
  *
51
74
  * @example
52
- * resolveCallerId({ flagValue: 'foo-bar' }) // → 'foo-bar'
53
- * resolveCallerId({ envOverride: 'baz' }) // → 'baz'
54
- * resolveCallerId({ env: { CLAUDE_CODE_SESSION_ID: 'sid-123' } }) // → 'sid-123'
55
- * resolveCallerId() // → throws CallerIdError (EX_USAGE)
75
+ * resolveCallerId({ flagValue: 'foo-bar' }) // → 'foo-bar'
76
+ * resolveCallerId({ envOverride: 'baz' }) // → 'baz'
77
+ * resolveCallerId({ env: { CLAUDE_CODE_SESSION_ID: 'sid-123' } })
78
+ * // → 'sid-123'
79
+ * resolveCallerId() // → throws CallerIdError (EX_USAGE)
56
80
  */
57
81
  export declare function resolveCallerId(opts?: ResolveCallerIdOptions): string;
82
+ /**
83
+ * Projected resolution for the 4.0.8 contract. Returns a typed
84
+ * `CallerProjection` envelope so consumers can read the adapter id +
85
+ * callerId + (optional) workflowId / graphRef in one call.
86
+ *
87
+ * Pure: does not touch the filesystem, does not consult the lease or
88
+ * the index. The `workflowId` / `graphRef` fields stay `null` here;
89
+ * callers that need the full binding should resolve the lease via
90
+ * `presence-lease-service` after the caller id is in hand.
91
+ */
92
+ export declare function resolveCallerProjection(opts?: ResolveCallerIdOptions): CallerProjection;