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/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-zepGuf8m.js"></script>
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.0",
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, /prd), 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.",
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
- single-PRD structure/sizing belongs to `/prd`, the engineering rules belong to `standards.md`.
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.** This is the part `/develop` owns that
52
- `/prd` doesn't: split a large ask into multiple PRDs and sequence them. For each PRD's
53
- *size, command-bounding, `NN` parallel group, and structure*, follow `~/.claude/skills/prd/SKILL.md`
54
- it is the canonical authority; do not restate its rules here. To pick `NN`, **compute the
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
- 4. **Emit each PRD** to the canonical path per `/prd`'s structure, then **append `## Engineering
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 reads `/prd` + `standards.md`, queues onto the
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
+ });
@@ -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
- broadcast('chat:run:error', {
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
- broadcast('chat:run:error', {
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
- broadcast('chat:run:needs-input', {
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
- broadcast('chat:run:complete', { tabId, sessionId, finalMessage: text });
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
- broadcast('chat:run:error', {
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
- broadcast('chat:run:error', {
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
- if (!gotResult && !killed) {
336
- const errDetail = stderrBuffer.trim()
337
- ? `: ${stderrBuffer.trim().slice(0, 300)}`
338
- : '';
339
- broadcast('chat:run:error', {
340
- tabId,
341
- sessionId,
342
- message: `process exited without a result event (code=${code} signal=${signal})${errDetail}`,
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.