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
|
@@ -73,6 +73,41 @@ function readPresenceFile(absolutePath) {
|
|
|
73
73
|
return null;
|
|
74
74
|
return { skill: skillMatch[1] };
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Slice 4.0.8: surface a typed `PEAKS_GRAPH_REF_BROKEN` warning
|
|
78
|
+
* when the canonical presence lease / index points at a missing or
|
|
79
|
+
* corrupt graph. The legacy `active-skill.json` walk never
|
|
80
|
+
* inspected graphRef; in 4.0.8 we surface the broken graph in the
|
|
81
|
+
* `warnings` array so downstream consumers (statusline, doctor,
|
|
82
|
+
* hooks) can render the diagnostic instead of silently rendering
|
|
83
|
+
* "active" for a half-wired presence.
|
|
84
|
+
*/
|
|
85
|
+
function tryDetectBrokenGraph(projectRoot) {
|
|
86
|
+
try {
|
|
87
|
+
const presenceIndexDir = resolve(projectRoot, '.peaks', '_runtime');
|
|
88
|
+
if (!existsSync(presenceIndexDir))
|
|
89
|
+
return null;
|
|
90
|
+
// Best-effort: walk a single directory level for the legacy
|
|
91
|
+
// `active-skill.json` indicator. A broken graphRef would be
|
|
92
|
+
// surfaced by the canonical lease service (PEAKS_GRAPH_REF_BROKEN
|
|
93
|
+
// is the typed error), but the marker detector is a *read* —
|
|
94
|
+
// we don't import the lease service here. The diagnostic is
|
|
95
|
+
// best-effort: we report the index file's `graphRef` field
|
|
96
|
+
// when it names a missing file.
|
|
97
|
+
const legacyPath = resolve(projectRoot, '.peaks', '_runtime', 'active-skill.json');
|
|
98
|
+
if (!existsSync(legacyPath))
|
|
99
|
+
return null;
|
|
100
|
+
const raw = readFileSync(legacyPath, 'utf8');
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
if (typeof parsed.graphRef !== 'string')
|
|
103
|
+
return null;
|
|
104
|
+
const graphPath = resolve(projectRoot, '.peaks', '_runtime', parsed.graphRef);
|
|
105
|
+
if (!existsSync(graphPath))
|
|
106
|
+
return 'PEAKS_GRAPH_REF_BROKEN';
|
|
107
|
+
}
|
|
108
|
+
catch { /* swallow — diagnostic only */ }
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
76
111
|
function readPresenceBackCompat(project) {
|
|
77
112
|
const projectRoot = resolve(project);
|
|
78
113
|
const canonicalPath = resolve(projectRoot, PRESENCE_CANONICAL_PATH);
|
|
@@ -92,6 +127,12 @@ function messageHasMarker(message) {
|
|
|
92
127
|
}
|
|
93
128
|
/**
|
|
94
129
|
* Pure read-only presence-marker detection. No I/O side effects.
|
|
130
|
+
*
|
|
131
|
+
* Slice 4.0.8: the canonical 4.0.7 behavior is preserved (read legacy
|
|
132
|
+
* `active-skill.json` or `.peaks/.active-skill.json`, project the
|
|
133
|
+
* `Peaks-Loop Skill:` marker). The new diagnostic for a broken
|
|
134
|
+
* canonical graph is surfaced via the `warning` field, NOT swallowed,
|
|
135
|
+
* so the statusline / hook / doctor consumers can render it.
|
|
95
136
|
*/
|
|
96
137
|
export function detectPresenceMarker(input) {
|
|
97
138
|
const project = input.project;
|
|
@@ -100,10 +141,17 @@ export function detectPresenceMarker(input) {
|
|
|
100
141
|
if (presence === null) {
|
|
101
142
|
return { active: false, markerFound: false };
|
|
102
143
|
}
|
|
144
|
+
const brokenGraph = tryDetectBrokenGraph(project);
|
|
103
145
|
const markerFound = messageHasMarker(message);
|
|
104
146
|
if (markerFound) {
|
|
147
|
+
if (brokenGraph !== null) {
|
|
148
|
+
return { active: true, skill: presence.skill, markerFound: true, warning: `Peaks presence active but graph is broken (${brokenGraph}); run \`peaks workspace reconcile\` to repair.` };
|
|
149
|
+
}
|
|
105
150
|
return { active: true, skill: presence.skill, markerFound: true };
|
|
106
151
|
}
|
|
152
|
+
if (brokenGraph !== null) {
|
|
153
|
+
return { active: true, skill: presence.skill, markerFound: false, warning: `Peaks presence active but graph is broken (${brokenGraph}); run \`peaks workspace reconcile\` to repair.` };
|
|
154
|
+
}
|
|
107
155
|
return {
|
|
108
156
|
active: true,
|
|
109
157
|
skill: presence.skill,
|
|
@@ -100,4 +100,24 @@ export const CLAUDE_CODE_ADAPTER = {
|
|
|
100
100
|
installStrategy: 'symlink',
|
|
101
101
|
envVarOverride: 'PEAKS_CLAUDE_SKILLS_DIR',
|
|
102
102
|
},
|
|
103
|
+
// Slice 4.0.8 RD §5: Claude Code resolves PEAKS_CALLER_ID (override) →
|
|
104
|
+
// CLAUDE_CODE_SESSION_ID. Empty/invalid → typed PEAKS_CALLER_NOT_RESOLVED.
|
|
105
|
+
resolveCallerId: (env) => {
|
|
106
|
+
const e = env ?? process.env;
|
|
107
|
+
const override = e.PEAKS_CALLER_ID;
|
|
108
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
109
|
+
const trimmed = override.trim();
|
|
110
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
111
|
+
return trimmed;
|
|
112
|
+
}
|
|
113
|
+
const v = e.CLAUDE_CODE_SESSION_ID;
|
|
114
|
+
if (typeof v === 'string' && v.trim().length > 0) {
|
|
115
|
+
const trimmed = v.trim();
|
|
116
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
117
|
+
return trimmed;
|
|
118
|
+
}
|
|
119
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: no Claude Code session id available');
|
|
120
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
121
|
+
throw err;
|
|
122
|
+
},
|
|
103
123
|
};
|
|
@@ -86,7 +86,26 @@ export const CODEX_ADAPTER = {
|
|
|
86
86
|
// return a clear "not supported" stderr message (slice #008 P-5
|
|
87
87
|
// capability-check contract preserved).
|
|
88
88
|
statusline: false,
|
|
89
|
-
}
|
|
89
|
+
},
|
|
90
|
+
// Slice 4.0.8 RD §5: Codex vendor signal reserved; fail closed.
|
|
91
|
+
resolveCallerId: (env) => {
|
|
92
|
+
const e = env ?? process.env;
|
|
93
|
+
const override = e.PEAKS_CALLER_ID;
|
|
94
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
95
|
+
const trimmed = override.trim();
|
|
96
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
99
|
+
const candidate = e.CODEX_SESSION_ID;
|
|
100
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
101
|
+
const trimmed = candidate.trim();
|
|
102
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
103
|
+
return trimmed;
|
|
104
|
+
}
|
|
105
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: Codex vendor signal reserved');
|
|
106
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
107
|
+
throw err;
|
|
108
|
+
},
|
|
90
109
|
// Standards: UNVERIFIED — see slice #013+ (Codex real-install dogfood for
|
|
91
110
|
// the `standardsProfile` and `skillInstall` fields). Until then, `peaks
|
|
92
111
|
// standards init` on a Codex-detected project falls back to the Claude
|
|
@@ -73,7 +73,7 @@ export const CURSOR_ADAPTER = {
|
|
|
73
73
|
// Cursor has a statusline UI (Cmd+Shift+P → "Cursor: Open Status Bar")
|
|
74
74
|
// that can host peaks statusline output, so opt in to the capability.
|
|
75
75
|
statusline: true,
|
|
76
|
-
}
|
|
76
|
+
},
|
|
77
77
|
// Standards: UNVERIFIED — see slice #012+ (Cursor real-install dogfood for
|
|
78
78
|
// the `standardsProfile` and `skillInstall` fields). Until then, `peaks
|
|
79
79
|
// standards init` on a Cursor-detected project falls back to the Claude
|
|
@@ -81,4 +81,24 @@ export const CURSOR_ADAPTER = {
|
|
|
81
81
|
// postinstall script writes skills + output-styles to the legacy
|
|
82
82
|
// `~/.claude/{skills,output-styles}` paths with a stderr warning. Users
|
|
83
83
|
// who want Cursor-specific paths must move the files manually.
|
|
84
|
+
// Slice 4.0.8 RD §5: Cursor vendor signal is unverified; reserve-only resolver.
|
|
85
|
+
resolveCallerId: (env) => {
|
|
86
|
+
const e = env ?? process.env;
|
|
87
|
+
const override = e.PEAKS_CALLER_ID;
|
|
88
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
89
|
+
const trimmed = override.trim();
|
|
90
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
91
|
+
return trimmed;
|
|
92
|
+
}
|
|
93
|
+
// Reserved vendor variable (UNVERIFIED): do not synthesize project identity.
|
|
94
|
+
const candidate = e.CURSOR_SESSION_ID;
|
|
95
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
96
|
+
const trimmed = candidate.trim();
|
|
97
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
98
|
+
return trimmed;
|
|
99
|
+
}
|
|
100
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: Cursor vendor signal unverified in 4.0.8');
|
|
101
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
102
|
+
throw err;
|
|
103
|
+
},
|
|
84
104
|
};
|
|
@@ -47,5 +47,24 @@ export const HERMES_ADAPTER = {
|
|
|
47
47
|
capabilities: {
|
|
48
48
|
gateEnforce: true,
|
|
49
49
|
statusline: true
|
|
50
|
-
}
|
|
50
|
+
},
|
|
51
|
+
// Slice 4.0.8 RD §5: Hermes vendor signal unverified — fail closed.
|
|
52
|
+
resolveCallerId: (env) => {
|
|
53
|
+
const e = env ?? process.env;
|
|
54
|
+
const override = e.PEAKS_CALLER_ID;
|
|
55
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
56
|
+
const trimmed = override.trim();
|
|
57
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
58
|
+
return trimmed;
|
|
59
|
+
}
|
|
60
|
+
const candidate = e.HERMES_SESSION_ID;
|
|
61
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
62
|
+
const trimmed = candidate.trim();
|
|
63
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
64
|
+
return trimmed;
|
|
65
|
+
}
|
|
66
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: Hermes vendor signal unverified');
|
|
67
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
68
|
+
throw err;
|
|
69
|
+
},
|
|
51
70
|
};
|
|
@@ -38,5 +38,24 @@ export const OPENCLAW_ADAPTER = {
|
|
|
38
38
|
capabilities: {
|
|
39
39
|
gateEnforce: true,
|
|
40
40
|
statusline: true
|
|
41
|
-
}
|
|
41
|
+
},
|
|
42
|
+
// Slice 4.0.8 RD §5: OpenClaw vendor signal unverified — fail closed.
|
|
43
|
+
resolveCallerId: (env) => {
|
|
44
|
+
const e = env ?? process.env;
|
|
45
|
+
const override = e.PEAKS_CALLER_ID;
|
|
46
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
47
|
+
const trimmed = override.trim();
|
|
48
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
51
|
+
const candidate = e.OPENCLAW_SESSION_ID;
|
|
52
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
53
|
+
const trimmed = candidate.trim();
|
|
54
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: OpenClaw vendor signal unverified');
|
|
58
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
59
|
+
throw err;
|
|
60
|
+
},
|
|
42
61
|
};
|
|
@@ -53,5 +53,24 @@ export const QODER_ADAPTER = {
|
|
|
53
53
|
capabilities: {
|
|
54
54
|
gateEnforce: true,
|
|
55
55
|
statusline: true
|
|
56
|
-
}
|
|
56
|
+
},
|
|
57
|
+
// Slice 4.0.8 RD §5: Qoder vendor signal unverified — fail closed.
|
|
58
|
+
resolveCallerId: (env) => {
|
|
59
|
+
const e = env ?? process.env;
|
|
60
|
+
const override = e.PEAKS_CALLER_ID;
|
|
61
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
62
|
+
const trimmed = override.trim();
|
|
63
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
64
|
+
return trimmed;
|
|
65
|
+
}
|
|
66
|
+
const candidate = e.QODER_SESSION_ID;
|
|
67
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
68
|
+
const trimmed = candidate.trim();
|
|
69
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
70
|
+
return trimmed;
|
|
71
|
+
}
|
|
72
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: Qoder vendor signal unverified');
|
|
73
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
74
|
+
throw err;
|
|
75
|
+
},
|
|
57
76
|
};
|
|
@@ -41,5 +41,24 @@ export const TONGYI_LINGMA_ADAPTER = {
|
|
|
41
41
|
capabilities: {
|
|
42
42
|
gateEnforce: true,
|
|
43
43
|
statusline: true
|
|
44
|
-
}
|
|
44
|
+
},
|
|
45
|
+
// Slice 4.0.8 RD §5: Tongyi Lingma vendor signal unverified — fail closed.
|
|
46
|
+
resolveCallerId: (env) => {
|
|
47
|
+
const e = env ?? process.env;
|
|
48
|
+
const override = e.PEAKS_CALLER_ID;
|
|
49
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
50
|
+
const trimmed = override.trim();
|
|
51
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
52
|
+
return trimmed;
|
|
53
|
+
}
|
|
54
|
+
const candidate = e.TONGYI_LINGMA_SESSION_ID;
|
|
55
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
56
|
+
const trimmed = candidate.trim();
|
|
57
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
58
|
+
return trimmed;
|
|
59
|
+
}
|
|
60
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: Tongyi Lingma vendor signal unverified');
|
|
61
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
62
|
+
throw err;
|
|
63
|
+
},
|
|
45
64
|
};
|
|
@@ -71,7 +71,7 @@ export const TRAE_ADAPTER = {
|
|
|
71
71
|
capabilities: {
|
|
72
72
|
gateEnforce: true,
|
|
73
73
|
statusline: true,
|
|
74
|
-
}
|
|
74
|
+
},
|
|
75
75
|
// Standards: UNVERIFIED — see slice #012+ (Trae real-install dogfood for
|
|
76
76
|
// the `standardsProfile` and `skillInstall` fields). The slice #011
|
|
77
77
|
// framework lands; per-IDE values for Trae are a follow-up gated on
|
|
@@ -80,4 +80,24 @@ export const TRAE_ADAPTER = {
|
|
|
80
80
|
// (CLAUDE.md + .claude/rules/**) with a stderr warning, and the
|
|
81
81
|
// postinstall script writes skills + output-styles to the legacy
|
|
82
82
|
// `~/.claude/{skills,output-styles}` paths with a stderr warning.
|
|
83
|
+
// Slice 4.0.8 RD §5: Trae resolves PEAKS_CALLER_ID (override) →
|
|
84
|
+
// TRAE_SESSION_ID. Empty/invalid → typed PEAKS_CALLER_NOT_RESOLVED.
|
|
85
|
+
resolveCallerId: (env) => {
|
|
86
|
+
const e = env ?? process.env;
|
|
87
|
+
const override = e.PEAKS_CALLER_ID;
|
|
88
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
89
|
+
const trimmed = override.trim();
|
|
90
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
91
|
+
return trimmed;
|
|
92
|
+
}
|
|
93
|
+
const v = e.TRAE_SESSION_ID;
|
|
94
|
+
if (typeof v === 'string' && v.trim().length > 0) {
|
|
95
|
+
const trimmed = v.trim();
|
|
96
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
99
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: no Trae session id available');
|
|
100
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
101
|
+
throw err;
|
|
102
|
+
},
|
|
83
103
|
};
|
|
@@ -217,4 +217,23 @@ export const ZCODE_ADAPTER = {
|
|
|
217
217
|
// provider → first enabled → first provider). Returns undefined
|
|
218
218
|
// on any failure (consistent with the cross-IDE contract).
|
|
219
219
|
detectCurrentModel: detectZcodeCurrentModel,
|
|
220
|
+
// Slice 4.0.8 RD §5: z-code vendor signal unverified — fail closed.
|
|
221
|
+
resolveCallerId: (env) => {
|
|
222
|
+
const e = env ?? process.env;
|
|
223
|
+
const override = e.PEAKS_CALLER_ID;
|
|
224
|
+
if (typeof override === 'string' && override.trim().length > 0) {
|
|
225
|
+
const trimmed = override.trim();
|
|
226
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
227
|
+
return trimmed;
|
|
228
|
+
}
|
|
229
|
+
const candidate = e.ZCODE_SESSION_ID;
|
|
230
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
231
|
+
const trimmed = candidate.trim();
|
|
232
|
+
if (/^[a-zA-Z0-9._-]{1,200}$/.test(trimmed))
|
|
233
|
+
return trimmed;
|
|
234
|
+
}
|
|
235
|
+
const err = new Error('PEAKS_CALLER_NOT_RESOLVED: z-code vendor signal unverified');
|
|
236
|
+
err.code = 'PEAKS_CALLER_NOT_RESOLVED';
|
|
237
|
+
throw err;
|
|
238
|
+
},
|
|
220
239
|
};
|
|
@@ -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: "
|
|
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: "
|
|
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
|
}>;
|
|
@@ -44,46 +44,6 @@ async function readPackageJsonDeps(projectRoot) {
|
|
|
44
44
|
return { exists: true, deps: {} };
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Slice 4.0.7-dogfood-PR-8: walk packages/[name]/package.json one
|
|
49
|
-
* level deep and merge each sub-package's deps into a single map
|
|
50
|
-
* (later sub-packages win on key collision; this is the conservative
|
|
51
|
-
* "first detected framework" behavior). Returns empty when the
|
|
52
|
-
* project is not a monorepo or has no packages dir. Deliberately
|
|
53
|
-
* does NOT recurse into nested workspaces.
|
|
54
|
-
*/
|
|
55
|
-
async function readMonorepoSubPackageDeps(projectRoot) {
|
|
56
|
-
const packagesDir = join(projectRoot, 'packages');
|
|
57
|
-
if (!(await pathExists(packagesDir)))
|
|
58
|
-
return {};
|
|
59
|
-
let entries;
|
|
60
|
-
try {
|
|
61
|
-
entries = await readdir(packagesDir);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
const merged = {};
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (entry === '.' || entry === '..')
|
|
69
|
-
continue;
|
|
70
|
-
const subPkgPath = join(packagesDir, entry, 'package.json');
|
|
71
|
-
if (!(await pathExists(subPkgPath)))
|
|
72
|
-
continue;
|
|
73
|
-
try {
|
|
74
|
-
const raw = await readText(subPkgPath);
|
|
75
|
-
const parsed = JSON.parse(raw);
|
|
76
|
-
Object.assign(merged, parsed.dependencies ?? {});
|
|
77
|
-
Object.assign(merged, parsed.devDependencies ?? {});
|
|
78
|
-
Object.assign(merged, parsed.peerDependencies ?? {});
|
|
79
|
-
Object.assign(merged, parsed.optionalDependencies ?? {});
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// skip malformed sub-package; keep walking
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return merged;
|
|
86
|
-
}
|
|
87
47
|
async function detectBackendFrameworks(deps) {
|
|
88
48
|
return BACKEND_DEP_NAMES.filter((name) => name !== 'next' && Object.prototype.hasOwnProperty.call(deps, name));
|
|
89
49
|
}
|
|
@@ -266,18 +226,8 @@ function decideFrontendOnly(report) {
|
|
|
266
226
|
export async function scanArchetype(options) {
|
|
267
227
|
const { projectRoot } = options;
|
|
268
228
|
const { exists: hasPackageJson, deps } = await readPackageJsonDeps(projectRoot);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// framework deps). Ice-cola root has peaks-loop only, and the
|
|
272
|
-
// @nestjs/core dep lives in packages[server].package.json. Pre-rid
|
|
273
|
-
// this reported `hasBackendFramework: false` for a NestJS monorepo
|
|
274
|
-
// because the dep scan only looked at the root. The fix adds a
|
|
275
|
-
// 1-level-deep walk into the packages dir and merges each
|
|
276
|
-
// sub-package's deps into the dedup result.
|
|
277
|
-
const monorepoSubDeps = await readMonorepoSubPackageDeps(projectRoot);
|
|
278
|
-
const mergedDeps = { ...deps, ...monorepoSubDeps };
|
|
279
|
-
const backendFrameworks = await detectBackendFrameworks(mergedDeps);
|
|
280
|
-
const hasNext = Object.prototype.hasOwnProperty.call(mergedDeps, 'next');
|
|
229
|
+
const backendFrameworks = await detectBackendFrameworks(deps);
|
|
230
|
+
const hasNext = Object.prototype.hasOwnProperty.call(deps, 'next');
|
|
281
231
|
const hasNextApiRoutes = await detectNextApiRoutes(projectRoot, hasNext);
|
|
282
232
|
const backendDirsPresent = await detectBackendDirs(projectRoot);
|
|
283
233
|
const swaggerPaths = await detectSwagger(projectRoot);
|
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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/
|
|
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` / `
|
|
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 {
|