claude-code-session-manager 0.37.1 → 0.38.0

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 (60) hide show
  1. package/bin/cli.cjs +12 -1
  2. package/dist/assets/{TiptapBody-DXQDiOUx.js → TiptapBody-GMk5LS9Y.js} +1 -1
  3. package/dist/assets/index-CD_LuJZF.css +32 -0
  4. package/dist/assets/{index--a8m5_ET.js → index-CQqSHpIq.js} +444 -436
  5. package/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/plugins/session-manager-dev/skills/develop/SKILL.md +134 -36
  8. package/plugins/session-manager-dev/skills/develop/standards.md +25 -0
  9. package/plugins/session-manager-dev/skills/issue-address/0-select/0-fetch-pool/SKILL.md +38 -0
  10. package/plugins/session-manager-dev/skills/issue-address/0-select/1-reject-fixed/SKILL.md +64 -0
  11. package/plugins/session-manager-dev/skills/issue-address/0-select/2-reject-claimed/SKILL.md +71 -0
  12. package/plugins/session-manager-dev/skills/issue-address/0-select/3-rank-impact/SKILL.md +39 -0
  13. package/plugins/session-manager-dev/skills/issue-address/0-select/SKILL.md +75 -0
  14. package/plugins/session-manager-dev/skills/issue-address/1-confirm-open/SKILL.md +39 -0
  15. package/plugins/session-manager-dev/skills/issue-address/2-claim/SKILL.md +57 -0
  16. package/plugins/session-manager-dev/skills/issue-address/3-reproduce/SKILL.md +40 -0
  17. package/plugins/session-manager-dev/skills/issue-address/4-fix/SKILL.md +33 -0
  18. package/plugins/session-manager-dev/skills/issue-address/5-verify/SKILL.md +40 -0
  19. package/plugins/session-manager-dev/skills/issue-address/SKILL.md +120 -0
  20. package/plugins/session-manager-dev/skills/pr-review-sweep/0-fetch/SKILL.md +41 -0
  21. package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +45 -0
  22. package/plugins/session-manager-dev/skills/pr-review-sweep/2-check-fixed/SKILL.md +49 -0
  23. package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +50 -0
  24. package/plugins/session-manager-dev/skills/pr-review-sweep/4-land-and-resolve/SKILL.md +60 -0
  25. package/plugins/session-manager-dev/skills/pr-review-sweep/SKILL.md +124 -0
  26. package/plugins/session-manager-dev/skills/pr-signal/SKILL.md +67 -0
  27. package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +20 -17
  28. package/scripts/lib/watchdogHelpers.cjs +296 -166
  29. package/src/main/__tests__/chat-cancel-terminal.test.cjs +31 -0
  30. package/src/main/__tests__/chat-exit-close-race.test.cjs +140 -0
  31. package/src/main/__tests__/classifyTranscriptLine.test.cjs +90 -0
  32. package/src/main/__tests__/docEdit.test.cjs +164 -2
  33. package/src/main/__tests__/prdCreate.test.cjs +265 -25
  34. package/src/main/__tests__/scheduler-admin-routes.test.cjs +150 -0
  35. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +172 -0
  36. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +64 -0
  37. package/src/main/browserAgentServer.cjs +11 -10
  38. package/src/main/chatRunner.cjs +58 -14
  39. package/src/main/docEdit.cjs +125 -5
  40. package/src/main/health.cjs +15 -0
  41. package/src/main/index.cjs +12 -6
  42. package/src/main/ipcSchemas.cjs +16 -3
  43. package/src/main/lib/__tests__/localAdminHttp.test.cjs +120 -0
  44. package/src/main/lib/classifyTranscriptLine.cjs +89 -0
  45. package/src/main/lib/localAdminHttp.cjs +157 -0
  46. package/src/main/lib/personaImportHealth.cjs +161 -0
  47. package/src/main/lib/prdCreate.cjs +107 -17
  48. package/src/main/lib/rcaFeedbackHook.cjs +2 -2
  49. package/src/main/lib/singleInstanceGuard.cjs +20 -0
  50. package/src/main/scheduler.cjs +223 -56
  51. package/src/main/sessionsStore.cjs +4 -5
  52. package/src/main/templates/PRD_AUTHORING.md +4 -4
  53. package/src/main/transcripts.cjs +1 -85
  54. package/src/main/webRemote.cjs +3 -3
  55. package/src/preload/api.d.ts +16 -6
  56. package/src/preload/index.cjs +9 -2
  57. package/dist/assets/index-CTTjT08J.css +0 -32
  58. package/plugins/session-manager-dev/skills/security-review/SKILL.md +0 -105
  59. package/src/main/__tests__/adminServer.test.cjs +0 -380
  60. package/src/main/adminServer.cjs +0 -242
@@ -1,242 +0,0 @@
1
- /**
2
- * adminServer.cjs — loopback-only HTTP admin API for the scheduler.
3
- *
4
- * Foundation for PRD 449 (an MCP server wraps this HTTP API). Exposes a
5
- * narrow, token-authed surface so a same-machine script/tool can reset a
6
- * stuck scheduler job or list jobs without hand-editing queue.json from
7
- * outside the running app (which would race the in-process mutate() queue
8
- * — see scheduler.cjs's serialized-mutation comment).
9
- *
10
- * Security posture:
11
- * - Binds 127.0.0.1 only, OS-assigned ephemeral port. Never reachable
12
- * off-box.
13
- * - Bearer token, regenerated every app boot, written to
14
- * ~/.claude/session-manager/admin-api.json with 0600 perms.
15
- * - Token compared with crypto.timingSafeEqual to avoid a timing
16
- * side-channel on the comparison itself.
17
- * - Three routes: reset-job (flips fields on an already-vetted job),
18
- * jobs (read-only list), and create-prd (PRD 549 — authors a BRAND-NEW
19
- * file that the scheduler will later run as `claude -p
20
- * --dangerously-skip-permissions` in a caller-chosen, home-dir-bounded
21
- * cwd; a materially larger blast radius than reset-job, justified only
22
- * because it unblocks external automation with no other queueing path).
23
- * cwd is validated via config.cjs's validatePath; title/cwd are
24
- * newline-blocked at the zod boundary (ipcSchemas.cjs) to prevent
25
- * frontmatter injection into the hand-rolled PRD serializer
26
- * (prdCreate.cjs). pause/resume/cancel/update remain intentionally NOT
27
- * exposed here.
28
- */
29
-
30
- 'use strict';
31
-
32
- const http = require('node:http');
33
- const os = require('node:os');
34
- const path = require('node:path');
35
- const crypto = require('node:crypto');
36
- const fsp = require('node:fs/promises');
37
- const config = require('./config.cjs');
38
- const { expandHome } = require('./lib/expandHome.cjs');
39
- const { schemas } = require('./ipcSchemas.cjs');
40
- const prdCreate = require('./lib/prdCreate.cjs');
41
-
42
- const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
43
-
44
- function timingSafeEqualStrings(a, b) {
45
- const bufA = Buffer.from(String(a ?? ''));
46
- const bufB = Buffer.from(String(b ?? ''));
47
- if (bufA.length !== bufB.length) {
48
- // Compare against a same-length buffer anyway so the failure path still
49
- // takes constant time relative to the (fixed) token length, rather than
50
- // returning early on a length mismatch.
51
- crypto.timingSafeEqual(bufA, bufA);
52
- return false;
53
- }
54
- return crypto.timingSafeEqual(bufA, bufB);
55
- }
56
-
57
- function readBody(req, maxBytes = 1024 * 1024) {
58
- return new Promise((resolve, reject) => {
59
- let total = 0;
60
- const chunks = [];
61
- req.on('data', (chunk) => {
62
- total += chunk.length;
63
- if (total > maxBytes) {
64
- reject(new Error('body too large'));
65
- req.destroy();
66
- return;
67
- }
68
- chunks.push(chunk);
69
- });
70
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
71
- req.on('error', reject);
72
- });
73
- }
74
-
75
- function sendJson(res, status, obj) {
76
- const body = JSON.stringify(obj);
77
- res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
78
- res.end(body);
79
- }
80
-
81
- /**
82
- * Create the admin HTTP server. `remote` is scheduler.cjs's remote object
83
- * (injected, not required directly, so this module stays testable without
84
- * booting Electron).
85
- */
86
- function createAdminServer(remote) {
87
- let server = null;
88
- let token = null;
89
-
90
- async function ensureToken() {
91
- token = crypto.randomBytes(32).toString('hex');
92
- await config.writeJson(TOKEN_PATH, { port: null, token });
93
- try { await fsp.chmod(TOKEN_PATH, 0o600); } catch { /* best-effort on platforms without POSIX perms */ }
94
- return token;
95
- }
96
-
97
- async function persistPort(port) {
98
- await config.writeJson(TOKEN_PATH, { port, token });
99
- try { await fsp.chmod(TOKEN_PATH, 0o600); } catch { /* */ }
100
- }
101
-
102
- function authorized(req) {
103
- const header = req.headers.authorization || '';
104
- const match = /^Bearer (.+)$/.exec(header);
105
- if (!match) return false;
106
- return timingSafeEqualStrings(match[1], token);
107
- }
108
-
109
- async function handleRequest(req, res) {
110
- if (!authorized(req)) {
111
- sendJson(res, 401, { ok: false, error: 'unauthorized' });
112
- return;
113
- }
114
- try {
115
- if (req.method === 'GET' && req.url === '/admin/scheduler/jobs') {
116
- const jobs = await remote.listJobs();
117
- sendJson(res, 200, jobs);
118
- return;
119
- }
120
- if (req.method === 'POST' && req.url === '/admin/scheduler/reset-job') {
121
- const raw = await readBody(req);
122
- let parsed;
123
- try {
124
- parsed = raw ? JSON.parse(raw) : {};
125
- } catch {
126
- sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
127
- return;
128
- }
129
- const slug = typeof parsed.slug === 'string' ? parsed.slug : null;
130
- if (!slug) {
131
- sendJson(res, 400, { ok: false, error: 'missing slug' });
132
- return;
133
- }
134
- const result = await remote.resetJob(slug);
135
- sendJson(res, 200, result);
136
- return;
137
- }
138
- if (req.method === 'POST' && req.url === '/admin/scheduler/create-prd') {
139
- const raw = await readBody(req);
140
- let parsed;
141
- try {
142
- parsed = raw ? JSON.parse(raw) : {};
143
- } catch {
144
- sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
145
- return;
146
- }
147
-
148
- let input;
149
- try {
150
- input = schemas.schedulerCreatePrd.parse(parsed);
151
- } catch (e) {
152
- sendJson(res, 400, { ok: false, error: 'invalid PRD payload', details: e?.issues ?? e?.message });
153
- return;
154
- }
155
-
156
- // cwd is untrusted: this is the first *creating* mutation on a
157
- // token-authed API whose product is "a command that will later run
158
- // with --dangerously-skip-permissions in a chosen cwd". Route it
159
- // through config.cjs's validatePath (allowedRoots = home dir) —
160
- // same boundary every other fs-touching IPC handler uses — never a
161
- // bespoke check here.
162
- try {
163
- config.validatePath(expandHome(input.cwd));
164
- } catch (e) {
165
- sendJson(res, 400, { ok: false, error: `cwd rejected: ${e?.message ?? 'outside allowed roots'}` });
166
- return;
167
- }
168
-
169
- const slug = input.slug || prdCreate.deriveSlugFromTitle(input.title);
170
- if (!slug || !prdCreate.PRD_CREATE_SLUG_RE.test(slug)) {
171
- sendJson(res, 400, { ok: false, error: 'could not derive a valid kebab-case slug from title; supply "slug" explicitly' });
172
- return;
173
- }
174
-
175
- // NN allocation is delegated to allocateParallelGroup() (PRD 548) via
176
- // the injected remote — never re-derived here — unless the caller
177
- // opted into an existing group explicitly.
178
- const nn = input.parallelGroup ?? await remote.allocateParallelGroup();
179
- const filenameSlug = `${nn}-${slug}`;
180
-
181
- // An explicit `parallelGroup` bypasses allocateParallelGroup()'s
182
- // collision-proof reservation, so re-check for an existing file at
183
- // this exact destination before writing — remote.writePrd itself has
184
- // no existence guard (by design, it doubles as the edit-in-place
185
- // path for Scheduler UI PRD edits), so "create" must not silently
186
- // clobber an existing job's PRD.
187
- const existing = await remote.readPrd(filenameSlug);
188
- if (existing?.ok) {
189
- sendJson(res, 409, { ok: false, error: `PRD already exists: ${filenameSlug}.md` });
190
- return;
191
- }
192
-
193
- let standardsText;
194
- try {
195
- standardsText = await prdCreate.readStandards();
196
- } catch (e) {
197
- sendJson(res, 500, { ok: false, error: `could not read engineering standards: ${e?.message}` });
198
- return;
199
- }
200
-
201
- const body = prdCreate.buildPrdBody(input, standardsText);
202
- const writeResult = await remote.writePrd(filenameSlug, body);
203
- if (!writeResult?.ok) {
204
- sendJson(res, 500, { ok: false, error: writeResult?.error ?? 'write failed' });
205
- return;
206
- }
207
-
208
- sendJson(res, 200, { nn, filename: `${filenameSlug}.md`, status: 'queued' });
209
- return;
210
- }
211
- sendJson(res, 404, { ok: false, error: 'not found' });
212
- } catch (e) {
213
- sendJson(res, 500, { ok: false, error: e?.message ?? 'internal error' });
214
- }
215
- }
216
-
217
- async function start() {
218
- await ensureToken();
219
- server = http.createServer((req, res) => {
220
- handleRequest(req, res).catch(() => {
221
- try { sendJson(res, 500, { ok: false, error: 'internal error' }); } catch { /* */ }
222
- });
223
- });
224
- await new Promise((resolve, reject) => {
225
- server.once('error', reject);
226
- server.listen(0, '127.0.0.1', resolve);
227
- });
228
- const { port } = server.address();
229
- await persistPort(port);
230
- return { port, token };
231
- }
232
-
233
- async function stop() {
234
- if (!server) return;
235
- await new Promise((resolve) => server.close(() => resolve()));
236
- server = null;
237
- }
238
-
239
- return { start, stop, get token() { return token; }, get server() { return server; } };
240
- }
241
-
242
- module.exports = { createAdminServer, TOKEN_PATH };