claude-code-session-manager 0.35.0 → 0.35.1
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/dist/assets/{TiptapBody-BqDK21Pr.js → TiptapBody-4v5sETwF.js} +1 -1
- package/dist/assets/{index-zepGuf8m.js → index-1DFsiqCu.js} +362 -364
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +95 -10
- package/plugins/session-manager-dev/skills/explain-to-me/SKILL.md +1 -1
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +89 -0
- package/src/main/chatRunner.cjs +42 -17
- package/src/main/lib/claudeBin.cjs +3 -0
- package/plugins/session-manager-dev/skills/prd/SKILL.md +0 -134
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-1DFsiqCu.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-CPTin6qz.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "session-manager-dev",
|
|
3
|
-
"description": "The default end-to-end development skillset for the Claude Code Session Manager: decompose work into scheduler PRDs (/develop
|
|
3
|
+
"description": "The default end-to-end development skillset for the Claude Code Session Manager: decompose work into scheduler PRDs (/develop), triage cross-project feedback (/process-feedback, /my-feedback), roll up operational status and optimize the North-Star KPI (/project-status, /optimize-kpi, /local-project-health), gate with review (/requesting-code-review, /security-review), and document how the project works (/explain-to-me). Together with the session-manager scheduler these form a closed develop → queue → track → review → status → improve loop.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stanislav Georgiev",
|
|
@@ -21,8 +21,7 @@ arrives via `/process-feedback`, which evaluates it and then calls this skill. E
|
|
|
21
21
|
here on is identical regardless of who asked.
|
|
22
22
|
|
|
23
23
|
**Never** hand-implement the work inline in chat, and never restate rules that live elsewhere:
|
|
24
|
-
|
|
25
|
-
Reference them; don't fork them.
|
|
24
|
+
the engineering rules belong to `standards.md`. Reference it; don't fork it.
|
|
26
25
|
|
|
27
26
|
## Standards (single source of truth)
|
|
28
27
|
|
|
@@ -48,12 +47,10 @@ can't load skills.
|
|
|
48
47
|
reuse (per the API-reuse standard — search before writing new code), the test command, and
|
|
49
48
|
any constraints. Capture exact file paths and signatures; they go straight into the PRDs.
|
|
50
49
|
|
|
51
|
-
3. **Decompose into a series of SMALL, bounded PRDs.**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
highest in-use number deterministically — never eyeball or narrow-grep the `ls`** (a
|
|
56
|
-
narrowed pattern like `'^10[0-9]'` silently misses `110+` and collides):
|
|
50
|
+
3. **Decompose into a series of SMALL, bounded PRDs.** Split a large ask into multiple PRDs and
|
|
51
|
+
sequence them. To pick each PRD's `NN` (parallel group), **compute the highest in-use number
|
|
52
|
+
deterministically — never eyeball or narrow-grep the `ls`** (a narrowed pattern like
|
|
53
|
+
`'^10[0-9]'` silently misses `110+` and collides):
|
|
57
54
|
```bash
|
|
58
55
|
ls ~/.claude/session-manager/scheduled-plans/prds/ | grep -oE '^[0-9]+' | sort -n | uniq | tail -5
|
|
59
56
|
```
|
|
@@ -62,7 +59,93 @@ can't load skills.
|
|
|
62
59
|
is unrelated to every existing group. Record each cross-PRD dependency in the dependent
|
|
63
60
|
PRD's notes.
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
### PRD structure and location
|
|
63
|
+
|
|
64
|
+
Each individual PRD must follow this structure — this is `/develop`'s single authority on
|
|
65
|
+
one PRD's structure, location, and scope sizing (the engineering rules stay separate, in
|
|
66
|
+
`standards.md`).
|
|
67
|
+
|
|
68
|
+
You are writing a PRD that will be executed by the user's session-manager scheduler — a
|
|
69
|
+
system that runs `claude -p <prd-body> --dangerously-skip-permissions` jobs around 5-hour
|
|
70
|
+
token-window resets, with auto-pause on rate-limit and auto-resume.
|
|
71
|
+
|
|
72
|
+
**Canonical location — non-negotiable.** PRDs MUST be written to:
|
|
73
|
+
```
|
|
74
|
+
~/.claude/session-manager/scheduled-plans/prds/<NN>-<kebab-slug>.md
|
|
75
|
+
```
|
|
76
|
+
**Anywhere else doesn't get scheduled.** If you write to `data/prds/`, `docs/prds/`, or the
|
|
77
|
+
project root, the scheduler will not see it and the user loses their token-budget-managed
|
|
78
|
+
execution. There is exactly one queue for all projects — that's intentional, because the
|
|
79
|
+
5-hour token budget is global across all of the user's Claude work. The `~` expands to
|
|
80
|
+
`os.homedir()` so the same convention works for any user on any machine.
|
|
81
|
+
|
|
82
|
+
**Filename rules.** `NN` is the 2-digit zero-padded parallel group (picked per the `ls`
|
|
83
|
+
command above — same `NN` as an independent sibling, or next free `NN` = max+1 when this PRD
|
|
84
|
+
hard-depends on prior work). `<kebab-slug>` is a short, descriptive kebab-case identifier
|
|
85
|
+
(e.g. `voice-commands-send-cancel`, `ticker-velocity-mcp`), kept under 60 chars. Verify your
|
|
86
|
+
chosen filename doesn't already exist before writing.
|
|
87
|
+
|
|
88
|
+
**Required frontmatter:**
|
|
89
|
+
```yaml
|
|
90
|
+
---
|
|
91
|
+
title: <one-line human-readable title>
|
|
92
|
+
cwd: <path to target project — where claude -p will run>
|
|
93
|
+
estimateMinutes: <integer wall-clock estimate>
|
|
94
|
+
---
|
|
95
|
+
```
|
|
96
|
+
`cwd` is critical — without it the job runs in the scheduler's default cwd (session-manager).
|
|
97
|
+
Always set it to the path of the project the work targets, written as `~/Projects/<repo>`
|
|
98
|
+
(the parser expands `~` to `os.homedir()` at ingest, so the same PRD works on any machine).
|
|
99
|
+
Avoid hardcoding an absolute home path (`/home/<you>/Projects/<repo>`); it breaks on any
|
|
100
|
+
machine with a different home directory.
|
|
101
|
+
|
|
102
|
+
**Required body sections, in this order:**
|
|
103
|
+
```markdown
|
|
104
|
+
# Goal
|
|
105
|
+
|
|
106
|
+
<2-4 sentences. What the executor will build and why it matters. NO "as a user I want to"
|
|
107
|
+
framing. Concrete: name the function, the file, the user-visible change.>
|
|
108
|
+
|
|
109
|
+
# Acceptance criteria
|
|
110
|
+
|
|
111
|
+
- [ ] <each line is a verifiable check the executor can run after building>
|
|
112
|
+
- [ ] <include explicit file paths, function names, expected behavior>
|
|
113
|
+
- [ ] a bounded test command passes, e.g. `timeout 300 npm run typecheck` / `pytest -x` /
|
|
114
|
+
`cargo check` (the run-before-done / never-end-on-red rule lives in standards.md →
|
|
115
|
+
Execution discipline; the AC just has to name the command).
|
|
116
|
+
|
|
117
|
+
# Implementation notes
|
|
118
|
+
|
|
119
|
+
<file paths the executor will need to read first; the architectural pattern to follow; any
|
|
120
|
+
non-obvious constraints. Be specific. Quote function signatures if it saves the executor a
|
|
121
|
+
Read call.>
|
|
122
|
+
|
|
123
|
+
# Out of scope
|
|
124
|
+
|
|
125
|
+
<short bulleted list of what NOT to build, to prevent scope creep>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Self-containment is load-bearing.** The executor (`claude -p`) starts with NO conversation
|
|
129
|
+
context — only the PRD body and the project files. So: include exact file paths (e.g.
|
|
130
|
+
`src/main/index.cjs:142`); quote function signatures or relevant code blocks if the executor
|
|
131
|
+
would have to grep for them; name the libraries/patterns to use (e.g. "use the existing
|
|
132
|
+
`validatePath` helper in `config.cjs`"); don't reference "the conversation we just had" or
|
|
133
|
+
"the design we discussed"; if a PRD depends on another PRD's output, say so in
|
|
134
|
+
`# Implementation notes` AND give it a higher `NN` so it queues after.
|
|
135
|
+
|
|
136
|
+
**Scope sizing — keep it SMALL (data-driven, 2026-06).** Across 400+ real runs the median
|
|
137
|
+
PRD finishes in **~7 minutes**, p90 **~21 min**, p99 **~66 min** — yet authored
|
|
138
|
+
`estimateMinutes` ran 5–8× too high. Oversized scoping anchors PRDs too big and pushes them
|
|
139
|
+
into the rare >60-min tail where ~100% of true hangs live (deploy poll-loops, unbounded e2e
|
|
140
|
+
suites). Target ~15 minutes of wall-clock work per PRD — **hard ceiling ~30 min; if you
|
|
141
|
+
project more, SPLIT** into sequential `NN` PRDs and document the dependency in each. Set
|
|
142
|
+
`estimateMinutes` realistically: **p50≈8, p90≈21** — don't write 60/90, it's almost always
|
|
143
|
+
wrong and hides real outliers. Each execution costs ~$0.50–$2; smaller PRDs = smaller blast
|
|
144
|
+
radius when a run is rate-limited, timed out, or killed. **`rateLimited` exit-1 is NOT a
|
|
145
|
+
failure** — it's the scheduler's designed auto-pause; the job auto-resumes at the next
|
|
146
|
+
window reset. Don't add retry logic for it.
|
|
147
|
+
|
|
148
|
+
4. **Emit each PRD** to the canonical path and structure above, then **append `## Engineering
|
|
66
149
|
standards` and paste the full contents of `standards.md` verbatim.** This is the
|
|
67
150
|
load-bearing step — it's the only way the standards (incl. Execution discipline) reach the
|
|
68
151
|
headless run. Honor the `PRD_AUTHORING.md` §10 pre-queue checklist.
|
|
@@ -99,7 +182,6 @@ single definition of "tracked to done" for both entry paths.
|
|
|
99
182
|
|
|
100
183
|
## References (reuse, don't duplicate)
|
|
101
184
|
|
|
102
|
-
- `~/.claude/skills/prd/SKILL.md` — canonical single-PRD structure, location, filename rules, scope sizing.
|
|
103
185
|
- `~/.claude/session-manager/scheduled-plans/PRD_AUTHORING.md` — the §1–§10 safety rules.
|
|
104
186
|
- `~/.claude/skills/develop/standards.md` — the engineering + execution-discipline rules inlined into every PRD.
|
|
105
187
|
- `test-driven-development`, `systematic-debugging` — interactive dev sessions.
|
|
@@ -110,3 +192,6 @@ single definition of "tracked to done" for both entry paths.
|
|
|
110
192
|
- Write PRD files directly, then confirm — don't draft them inline in chat for review first.
|
|
111
193
|
- Don't combine unrelated features into one PRD. One focused, completable unit each.
|
|
112
194
|
- Don't add a `parallelGroup` frontmatter key — the filename `NN-` prefix drives grouping.
|
|
195
|
+
- Don't write a PRD to `data/prds/`, `docs/prds/`, the project's own folder, or anywhere outside
|
|
196
|
+
the canonical path. The user has explicitly flagged this as a recurring problem.
|
|
197
|
+
- Don't leave `cwd` unset hoping for the default. Be explicit.
|
|
@@ -62,7 +62,7 @@ If you can't state it in the present tense without a date or a PRD, cut it.
|
|
|
62
62
|
- **The Skill Map** — `HUMAN_LEARN/SKILL_MAP.html`, a **separate, dedicated** page
|
|
63
63
|
(NOT a section of index.html) that visualizes the *local-development skill chain*:
|
|
64
64
|
the two intakes (interactive human prompt; agent feedback via `/process-feedback`)
|
|
65
|
-
converging on `/develop`, which
|
|
65
|
+
converging on `/develop`, which owns PRD authoring + reads `standards.md`, queues onto the
|
|
66
66
|
scheduler, and gates with review/verify — plus `/my-feedback` outbound. It is the
|
|
67
67
|
"how I build on this project" companion to index.html's "how this project works."
|
|
68
68
|
index.html links to it from the nav; it links back.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chat-cancel-terminal.test.cjs — regression for the "stuck thinking" hang.
|
|
3
|
+
*
|
|
4
|
+
* Bug (2026-07-05): cancelling an active chat run SIGTERM'd the child, but the
|
|
5
|
+
* exit handler's `if (!gotResult && !killed)` guard skipped every broadcast
|
|
6
|
+
* because `killed === true`. No terminal IPC event ever reached the renderer,
|
|
7
|
+
* so the chat store stayed `running:true` forever — disabled textarea, endless
|
|
8
|
+
* "running…" spinner, unresponsive chat UI.
|
|
9
|
+
*
|
|
10
|
+
* Fix: every run emits EXACTLY ONE terminal event (complete/needs-input/error),
|
|
11
|
+
* and the exit path emits a fallback `chat:run:error` when a run settled
|
|
12
|
+
* without one (cancel, crash, silent exit). This test drives the REAL spawn
|
|
13
|
+
* path (via SM_CLAUDE_BIN pointing at a stub process) and asserts a terminal
|
|
14
|
+
* event is broadcast on cancel.
|
|
15
|
+
*
|
|
16
|
+
* Run: timeout 120 node --test src/main/__tests__/chat-cancel-terminal.test.cjs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
delete process.env.SM_CHAT_CONCURRENCY;
|
|
22
|
+
|
|
23
|
+
const { test } = require('node:test');
|
|
24
|
+
const assert = require('node:assert/strict');
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const os = require('node:os');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
|
|
29
|
+
// A stub "claude" that ignores stdin/args and stays alive until killed —
|
|
30
|
+
// mimics a long-running headless run so cancel() has something to SIGTERM.
|
|
31
|
+
// Shebang + chmod makes it directly spawnable as a single executable (spawn
|
|
32
|
+
// does not split a path on spaces, so SM_CLAUDE_BIN must be one binary).
|
|
33
|
+
const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}.cjs`);
|
|
34
|
+
fs.writeFileSync(
|
|
35
|
+
stubPath,
|
|
36
|
+
`#!${process.execPath}\nsetInterval(() => {}, 1000);\n`,
|
|
37
|
+
{ mode: 0o755 },
|
|
38
|
+
);
|
|
39
|
+
process.env.SM_CLAUDE_BIN = stubPath;
|
|
40
|
+
|
|
41
|
+
const cr = require('../chatRunner.cjs');
|
|
42
|
+
|
|
43
|
+
const tick = () => new Promise((r) => setImmediate(r));
|
|
44
|
+
|
|
45
|
+
test('cancelling an ACTIVE run broadcasts a terminal event (unsticks the UI)', async () => {
|
|
46
|
+
const events = [];
|
|
47
|
+
// Fake BrowserWindow that captures every broadcast.
|
|
48
|
+
cr.attachWindow({
|
|
49
|
+
isDestroyed: () => false,
|
|
50
|
+
webContents: {
|
|
51
|
+
isDestroyed: () => false,
|
|
52
|
+
send: (channel, payload) => events.push({ channel, payload }),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
cr.run({ tabId: 'T', sessionId: 'S', prompt: 'hello', cwd: process.cwd(), resume: false });
|
|
57
|
+
|
|
58
|
+
// Let the spawn happen and started event fire.
|
|
59
|
+
for (let i = 0; i < 6; i++) await tick();
|
|
60
|
+
assert.ok(
|
|
61
|
+
events.some((e) => e.channel === 'chat:run:started'),
|
|
62
|
+
'started event should fire once the run launches',
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
cr.cancel('T');
|
|
66
|
+
|
|
67
|
+
// Wait for SIGTERM → child exit → fallback terminal broadcast.
|
|
68
|
+
for (let i = 0; i < 40 && !events.some((e) => isTerminal(e.channel)); i++) {
|
|
69
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const terminal = events.filter((e) => isTerminal(e.channel));
|
|
73
|
+
assert.equal(terminal.length, 1, 'exactly one terminal event is emitted');
|
|
74
|
+
assert.equal(terminal[0].channel, 'chat:run:error', 'cancel surfaces as an error turn');
|
|
75
|
+
assert.match(terminal[0].payload.message, /cancel/i, 'message names the cancellation');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function isTerminal(channel) {
|
|
79
|
+
return (
|
|
80
|
+
channel === 'chat:run:complete' ||
|
|
81
|
+
channel === 'chat:run:needs-input' ||
|
|
82
|
+
channel === 'chat:run:error'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test.after(() => {
|
|
87
|
+
try { fs.unlinkSync(stubPath); } catch { /* already gone */ }
|
|
88
|
+
delete process.env.SM_CLAUDE_BIN;
|
|
89
|
+
});
|
package/src/main/chatRunner.cjs
CHANGED
|
@@ -174,6 +174,20 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
174
174
|
resolve();
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
+
// Guarantees the renderer receives EXACTLY ONE terminal event
|
|
178
|
+
// (complete / needs-input / error) per run. Without this, a cancelled or
|
|
179
|
+
// killed run exits with `killed=true` and the old exit-guard broadcast
|
|
180
|
+
// nothing — leaving the chat store stuck at running:true forever (disabled
|
|
181
|
+
// textarea, "running…" spinner, unresponsive UI). Every terminal broadcast
|
|
182
|
+
// funnels through here so the exit path can emit a fallback only when the
|
|
183
|
+
// run settled without one.
|
|
184
|
+
let terminalSent = false;
|
|
185
|
+
const emitTerminal = (channel, payload) => {
|
|
186
|
+
if (terminalSent) return;
|
|
187
|
+
terminalSent = true;
|
|
188
|
+
broadcast(channel, payload);
|
|
189
|
+
};
|
|
190
|
+
|
|
177
191
|
const claudeBin = resolveClaudeBin();
|
|
178
192
|
const childEnv = cleanChildEnv({ PATH: pathWithUserBins() });
|
|
179
193
|
|
|
@@ -209,7 +223,7 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
209
223
|
detached: true, // own process group so killTree can SIGTERM descendants
|
|
210
224
|
});
|
|
211
225
|
} catch (err) {
|
|
212
|
-
|
|
226
|
+
emitTerminal('chat:run:error', {
|
|
213
227
|
tabId,
|
|
214
228
|
sessionId,
|
|
215
229
|
message: `spawn failed: ${err?.message ?? String(err)}`,
|
|
@@ -237,7 +251,7 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
237
251
|
|
|
238
252
|
// Hard wall-clock ceiling — SIGTERM + SIGKILL on expiry
|
|
239
253
|
const killTimer = setTimeout(() => {
|
|
240
|
-
|
|
254
|
+
emitTerminal('chat:run:error', {
|
|
241
255
|
tabId,
|
|
242
256
|
sessionId,
|
|
243
257
|
message: 'run exceeded 30-minute wall-clock ceiling',
|
|
@@ -253,7 +267,6 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
253
267
|
let lineBuffer = '';
|
|
254
268
|
let finalAssistantText = '';
|
|
255
269
|
let stderrBuffer = '';
|
|
256
|
-
let gotResult = false;
|
|
257
270
|
|
|
258
271
|
const processLine = (line) => {
|
|
259
272
|
if (!line) return;
|
|
@@ -273,7 +286,6 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
273
286
|
}
|
|
274
287
|
}
|
|
275
288
|
} else if (event.type === 'result') {
|
|
276
|
-
gotResult = true;
|
|
277
289
|
// Use the authoritative `result` field when available; fall back to
|
|
278
290
|
// accumulated assistant text (same content, different source).
|
|
279
291
|
const text = typeof event.result === 'string' ? event.result : finalAssistantText;
|
|
@@ -281,21 +293,21 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
281
293
|
if (event.subtype === 'success') {
|
|
282
294
|
const signal = parseStopSignal(text);
|
|
283
295
|
if (signal) {
|
|
284
|
-
|
|
296
|
+
emitTerminal('chat:run:needs-input', {
|
|
285
297
|
tabId,
|
|
286
298
|
sessionId,
|
|
287
299
|
questions: signal.questions,
|
|
288
300
|
raw: text,
|
|
289
301
|
});
|
|
290
302
|
} else {
|
|
291
|
-
|
|
303
|
+
emitTerminal('chat:run:complete', { tabId, sessionId, finalMessage: text });
|
|
292
304
|
// Record durable exchange off the hot path — UI must not wait on Haiku
|
|
293
305
|
recordExchange({ sessionId, cwd, prompt, result: text }).catch((err) => {
|
|
294
306
|
console.error('[chatRunner] recordExchange failed:', err?.message ?? err);
|
|
295
307
|
});
|
|
296
308
|
}
|
|
297
309
|
} else {
|
|
298
|
-
|
|
310
|
+
emitTerminal('chat:run:error', {
|
|
299
311
|
tabId,
|
|
300
312
|
sessionId,
|
|
301
313
|
message: `run ended with result subtype: ${event.subtype ?? 'unknown'}`,
|
|
@@ -319,7 +331,7 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
319
331
|
|
|
320
332
|
child.on('error', (err) => {
|
|
321
333
|
clearTimeout(killTimer);
|
|
322
|
-
|
|
334
|
+
emitTerminal('chat:run:error', {
|
|
323
335
|
tabId,
|
|
324
336
|
sessionId,
|
|
325
337
|
message: err?.message ?? String(err),
|
|
@@ -332,15 +344,28 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume }) {
|
|
|
332
344
|
// Flush any partial line that didn't end with \n
|
|
333
345
|
if (lineBuffer.trim()) processLine(lineBuffer.trim());
|
|
334
346
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
347
|
+
// Emit a fallback terminal event for any run that ended without one — a
|
|
348
|
+
// cancel/SIGTERM (killed=true), a crash before a result, or a silent
|
|
349
|
+
// exit. `emitTerminal` no-ops if the run already sent complete/needs-
|
|
350
|
+
// input/error, so the normal success path never double-fires. This is
|
|
351
|
+
// what unsticks the renderer's running flag after Cancel.
|
|
352
|
+
if (!terminalSent) {
|
|
353
|
+
if (killed) {
|
|
354
|
+
emitTerminal('chat:run:error', {
|
|
355
|
+
tabId,
|
|
356
|
+
sessionId,
|
|
357
|
+
message: 'run cancelled',
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
const errDetail = stderrBuffer.trim()
|
|
361
|
+
? `: ${stderrBuffer.trim().slice(0, 300)}`
|
|
362
|
+
: '';
|
|
363
|
+
emitTerminal('chat:run:error', {
|
|
364
|
+
tabId,
|
|
365
|
+
sessionId,
|
|
366
|
+
message: `process exited without a result event (code=${code} signal=${signal})${errDetail}`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
344
369
|
}
|
|
345
370
|
settle();
|
|
346
371
|
});
|
|
@@ -18,6 +18,9 @@ const os = require('node:os');
|
|
|
18
18
|
let cached = null;
|
|
19
19
|
|
|
20
20
|
function resolveClaudeBin() {
|
|
21
|
+
// Explicit override wins and is never cached — lets operators pin a binary
|
|
22
|
+
// and lets tests point the runner at a controllable stub process.
|
|
23
|
+
if (process.env.SM_CLAUDE_BIN) return process.env.SM_CLAUDE_BIN;
|
|
21
24
|
if (cached) return cached;
|
|
22
25
|
// Merged candidate list — was forked in scheduler vs pluginInstall before.
|
|
23
26
|
const home = os.homedir();
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: prd
|
|
3
|
-
description: >-
|
|
4
|
-
Write a PRD for the session-manager scheduler queue (the right place for
|
|
5
|
-
token-budget-managed work). Use this whenever the user says "make this a PRD",
|
|
6
|
-
"queue this up", "add to the scheduler", or asks for a PRD without specifying a
|
|
7
|
-
target location. Keywords: prd, queue, scheduler, scheduled-plans, token budget.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# /prd — author one scheduler PRD
|
|
11
|
-
|
|
12
|
-
**Role:** `/prd` is the single authority on one PRD's *structure, location, and scope sizing*. It does not own the engineering rules (that's `standards.md`) and does not decompose a large ask into multiple PRDs (that's `/develop`). Never restate rules that live in `standards.md` — reference and append them.
|
|
13
|
-
|
|
14
|
-
You are writing a PRD that will be executed by the user's session-manager scheduler — a system that runs `claude -p <prd-body> --dangerously-skip-permissions` jobs around 5-hour token-window resets, with auto-pause on rate-limit and auto-resume.
|
|
15
|
-
|
|
16
|
-
## Canonical location — non-negotiable
|
|
17
|
-
|
|
18
|
-
PRDs MUST be written to:
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
~/.claude/session-manager/scheduled-plans/prds/<NN>-<kebab-slug>.md
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Anywhere else doesn't get scheduled.** If you write to `data/prds/`, `docs/prds/`, or the project root, the scheduler will not see it and the user loses their token-budget-managed execution. There is exactly one queue for all projects — that's intentional, because the 5-hour token budget is global across all of the user's Claude work. The `~` expands to `os.homedir()` so the same convention works for any user on any machine.
|
|
25
|
-
|
|
26
|
-
## Filename rules
|
|
27
|
-
|
|
28
|
-
- `NN` is the **parallel group** (2-digit zero-padded). Jobs in the same group run in parallel up to a concurrency cap; groups run serially. Pick `NN` by reading the existing files in `~/.claude/session-manager/scheduled-plans/prds/` and choosing:
|
|
29
|
-
- The same `NN` as a sibling that's logically independent and can run in parallel.
|
|
30
|
-
- The next unused `NN` if this PRD has a hard dependency on prior work landing first.
|
|
31
|
-
- `<kebab-slug>` is a short, descriptive kebab-case identifier (e.g. `voice-commands-send-cancel`, `ticker-velocity-mcp`). Keep under 60 chars.
|
|
32
|
-
|
|
33
|
-
Compute the current max `NN` **deterministically** before writing — do NOT eyeball the `ls` or narrow the pattern (a grep like `'^10[0-9]'` silently misses `110+` and overwrites/mis-groups a PRD):
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
ls ~/.claude/session-manager/scheduled-plans/prds/ | grep -oE '^[0-9]+' | sort -n | uniq | tail -5
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The last line is the highest in use; the next free group is **max+1**. Use that for an independent/dependent PRD, or reuse a specific sibling's `NN` only when you deliberately want it in that parallel group. Verify your chosen filename doesn't already exist before writing.
|
|
40
|
-
|
|
41
|
-
## Required frontmatter
|
|
42
|
-
|
|
43
|
-
```yaml
|
|
44
|
-
---
|
|
45
|
-
title: <one-line human-readable title>
|
|
46
|
-
cwd: <path to target project — where claude -p will run>
|
|
47
|
-
estimateMinutes: <integer wall-clock estimate>
|
|
48
|
-
---
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
`cwd` is critical. Without it the job runs in the scheduler's default cwd (session-manager). Always set it to the path of the project the work targets. **Write it as `~/Projects/<repo>`** — the parser expands `~` to `os.homedir()` at ingest, so the same PRD works on Linux and macOS. Avoid hardcoding an absolute home path (`/home/<you>/Projects/<repo>`); it breaks on any machine with a different home directory.
|
|
52
|
-
|
|
53
|
-
## Required body sections (in this order)
|
|
54
|
-
|
|
55
|
-
```markdown
|
|
56
|
-
# Goal
|
|
57
|
-
|
|
58
|
-
<2-4 sentences. What the executor will build and why it matters. NO "as a user I want to" framing. Concrete: name the function, the file, the user-visible change.>
|
|
59
|
-
|
|
60
|
-
# Acceptance criteria
|
|
61
|
-
|
|
62
|
-
- [ ] <each line is a verifiable check the executor can run after building>
|
|
63
|
-
- [ ] <include explicit file paths, function names, expected behavior>
|
|
64
|
-
- [ ] a bounded test command passes, e.g. `timeout 300 npm run typecheck` / `pytest -x` / `cargo check` (the run-before-done / never-end-on-red rule lives in standards.md → Execution discipline; the AC just has to name the command).
|
|
65
|
-
|
|
66
|
-
# Implementation notes
|
|
67
|
-
|
|
68
|
-
<file paths the executor will need to read first; the architectural pattern to follow; any non-obvious constraints. Be specific. Quote function signatures if it saves the executor a Read call.>
|
|
69
|
-
|
|
70
|
-
# Out of scope
|
|
71
|
-
|
|
72
|
-
<short bulleted list of what NOT to build, to prevent scope creep>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Self-containment is load-bearing
|
|
76
|
-
|
|
77
|
-
The executor (`claude -p`) starts with NO conversation context — only this PRD body and the project files. So:
|
|
78
|
-
|
|
79
|
-
- Include exact file paths (`src/main/index.cjs:142`).
|
|
80
|
-
- Quote function signatures or relevant code blocks if the executor would have to grep for them.
|
|
81
|
-
- Name the libraries / patterns to use (e.g. "use the existing `validatePath` helper in `config.cjs`").
|
|
82
|
-
- Don't reference "the conversation we just had" or "the design we discussed."
|
|
83
|
-
- If the PRD depends on another PRD's output, say so in `# Implementation notes` AND give it a higher `NN` so it queues after.
|
|
84
|
-
|
|
85
|
-
## Scope sizing — keep it SMALL (data-driven, 2026-06)
|
|
86
|
-
|
|
87
|
-
Across 400+ real runs the median PRD finishes in **~7 minutes**, p90 **~21 min**, p99 **~66 min** — yet authored `estimateMinutes` ran **5–8× too high**. Oversized scoping is the root problem: it anchors PRDs too big and pushes them into the rare >60-min tail where ~100% of true hangs live (deploy poll-loops, unbounded e2e suites).
|
|
88
|
-
|
|
89
|
-
- **Target ~15 minutes of wall-clock work per PRD. Hard ceiling ~30 min — if you project more, SPLIT** into sequential `NN` PRDs and document the dependency in each.
|
|
90
|
-
- Set `estimateMinutes` realistically: **p50≈8, p90≈21**. Don't write 60/90 — it's almost always wrong and it hides real outliers.
|
|
91
|
-
- Each execution costs ~$0.50–$2; the 5-hour token window is global. Smaller PRDs = smaller blast radius when a run is rate-limited, timed out, or killed.
|
|
92
|
-
- **`rateLimited` exit-1 is NOT a failure** — it's the scheduler's designed auto-pause; the job auto-resumes at the next window reset. Don't add retry logic for it.
|
|
93
|
-
|
|
94
|
-
## Execution discipline — append it, don't restate it
|
|
95
|
-
|
|
96
|
-
The runtime rules (bound every command, verify-before-done, fail-loud, stay-in-AC,
|
|
97
|
-
negative-assertion-exits-0) have a single home: the **Execution discipline** section of
|
|
98
|
-
`~/.claude/skills/develop/standards.md`. Don't paraphrase them here — they drift.
|
|
99
|
-
|
|
100
|
-
Long hangs — not bad code — are the dominant *real* failure (the watchdog only fires at 4
|
|
101
|
-
hours, so an unbounded command burns hours), so these rules are load-bearing and MUST reach
|
|
102
|
-
the headless executor in the PRD body:
|
|
103
|
-
|
|
104
|
-
- If this PRD is being authored **via `/develop`**, that skill already appends `standards.md`
|
|
105
|
-
verbatim — nothing to do here.
|
|
106
|
-
- If you are authoring a PRD **directly** (`/prd` alone), **append the Execution discipline
|
|
107
|
-
section from `standards.md` to the PRD body** (under an `## Execution discipline` heading)
|
|
108
|
-
before queueing. That is the only way the bounded-command / verify-before-done rules reach
|
|
109
|
-
`claude -p`.
|
|
110
|
-
|
|
111
|
-
The one rule worth echoing inline because it shapes the AC: **wrap every test/build/deploy/poll
|
|
112
|
-
command in a hard timeout** (`timeout 300 npm run typecheck`, `timeout 120 npx playwright test
|
|
113
|
-
one.spec.ts` — shard e2e to one spec, never a full suite; `curl --max-time 15`; bound any
|
|
114
|
-
`until/while`). Never queue a publish that polls an endpoint until a condition — the #1 hang
|
|
115
|
-
offender (PRD_AUTHORING §1/§5).
|
|
116
|
-
|
|
117
|
-
## Workflow
|
|
118
|
-
|
|
119
|
-
1. Read existing PRDs in `~/.claude/session-manager/scheduled-plans/prds/` to pick `NN`.
|
|
120
|
-
2. Write the file with the structure above.
|
|
121
|
-
3. Confirm to the user:
|
|
122
|
-
- The filename (so they can see it queued).
|
|
123
|
-
- The chosen `parallelGroup` (NN) and the rationale (parallel-with-X / serial-after-Y).
|
|
124
|
-
- The chosen `cwd` (so they can correct if it's wrong).
|
|
125
|
-
- Estimated wall time + a token-cost ballpark.
|
|
126
|
-
4. Tell the user the PRD will fire when the scheduler's policy says — they can click "Run now" in the SchedulePanel to execute immediately, or wait for `when-available` polling.
|
|
127
|
-
|
|
128
|
-
## What NOT to do
|
|
129
|
-
|
|
130
|
-
- Don't write the PRD to `data/prds/`, `docs/prds/`, the project's own folder, or anywhere outside the canonical path. The user has explicitly flagged this as a recurring problem.
|
|
131
|
-
- Don't combine multiple unrelated features into one PRD. Each PRD is one focused, completable unit.
|
|
132
|
-
- Don't add a parallelGroup field to the frontmatter — the scheduler reads the filename prefix.
|
|
133
|
-
- Don't leave `cwd` unset hoping for the default. Be explicit.
|
|
134
|
-
- Don't write the PRD body inline in the chat for review *before* writing the file. Write the file directly, then confirm. The user wants the PRD queued, not draft-reviewed in chat.
|