@jhizzard/termdeck 0.7.3 โ†’ 0.9.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.
@@ -0,0 +1,335 @@
1
+ # Orchestrator Guide
2
+
3
+ A first-class reference for orchestrating Claude Code with TermDeck. This is the *how to work* doc โ€” not just *how to run terminals*. If you came here because TermDeck looked like a fancier tmux, you're in the right place. The terminals are the substrate; the orchestration patterns below are the product.
4
+
5
+ The patterns documented here were shaped over months of running AI coding sprints at Anthropic-model scale. Most of them were paid for in lost sleep, broken builds, and "why didn't anyone press Enter on panel 4 at 3 AM" debugging. They're conventions worth knowing before you have to discover them yourself.
6
+
7
+ This Guide is also rendered in the dashboard right-rail (top-right of the TermDeck UI). Press the `๐Ÿ“– Guide` tab to open it; it will auto-expand the section relevant to whatever you're focused on.
8
+
9
+ ---
10
+
11
+ ## 1. What is the 4+1 pattern?
12
+
13
+ The 4+1 pattern runs **four parallel Claude Code worker sessions (T1โ€“T4)** plus a **fifth orchestrator session** that coordinates them. Each worker owns a single lane of the sprint. The orchestrator never edits code in worker lanes; it briefs, monitors, audits, and closes.
14
+
15
+ ### Why parallel beats serial
16
+
17
+ Serial coding sessions hit two ceilings: (a) Claude's context fills up faster than the work shrinks, and (b) the human supervisor becomes the bottleneck because every step waits on review. Splitting work across four context-isolated workers gives you:
18
+
19
+ - **Cleaner context per lane.** Each worker only loads the files it owns. No bleed.
20
+ - **Honest audit trail.** Workers post FINDING / FIX-PROPOSED / DONE in `STATUS.md`; the orchestrator can't conflate "I think I'm done" with "the sprint is correct."
21
+ - **Parallel throughput.** Four lanes means four 30-minute jobs finish in 30 minutes, not two hours.
22
+ - **Recoverable failure.** A bad lane can be re-run without rolling back the others.
23
+
24
+ ### When to use 4+1 vs. a single session
25
+
26
+ Use 4+1 when:
27
+ - The work splits cleanly into 3โ€“4 file-owned lanes.
28
+ - You can write a one-page brief per lane in advance.
29
+ - You want a paper trail (`STATUS.md` becomes the sprint's diary).
30
+
31
+ Skip 4+1 (use a single Claude Code session) when:
32
+ - The work is exploratory ("figure out why X is slow").
33
+ - One lane gates all the others โ€” parallelism buys you nothing.
34
+ - The whole task is under ~30 minutes of work.
35
+
36
+ > **See also:** `~/.claude/CLAUDE.md` ยง MANDATORY: 4+1 sprint orchestration
37
+
38
+ ---
39
+
40
+ ## 2. The inject mandate
41
+
42
+ > **Cardinal rule: never copy-paste boot prompts. Always inject.**
43
+
44
+ When you launch a 4+1 sprint, the orchestrator pushes each worker's boot prompt into its panel via TermDeck's input API โ€” `POST /api/sessions/:id/input`. The human's job is to open four Claude Code terminals and say "terminals open, inject." The orchestrator does the rest.
45
+
46
+ Copy-pasting four boot prompts by hand is friction. It's also fragile: a stray newline, a missed paste, an out-of-order panel โ€” and the sprint starts wrong.
47
+
48
+ ### The two-stage submit pattern
49
+
50
+ This is the part that bit hard enough to become a hard rule. Each worker boot prompt is a multi-line bracketed-paste block. **Do not** append `\r` to the same POST. Use two POSTs:
51
+
52
+ 1. **Paste** โ€” `\x1b[200~<text>\x1b[201~` (no submit byte).
53
+ 2. **Settle** ~400 ms โ€” long enough for the PTY to flush the paste to Claude Code's input handler.
54
+ 3. **Submit** โ€” `\r` alone, in its own POST.
55
+
56
+ Why: when the close marker `\x1b[201~` and the trailing `\r` ride in one PTY write, the OS-level chunk boundary is non-deterministic. Sometimes Claude Code's input parser eats the `\r` as the last paste byte rather than a submit keystroke. Symptom: 3 of 4 panels auto-fire, the 4th sits at a visually populated input box waiting for a human to press Enter. **The cardinal sin is leaving a panel waiting for a human Enter press.** That cost real broken sleep on more than one overnight orchestration.
57
+
58
+ Single-stage `<text>\x1b[201~\r` injection is **banned**. Two-stage is the only sanctioned form.
59
+
60
+ ### Recovery when a panel stays idle
61
+
62
+ After all submits land, verify per-panel: `GET /api/sessions/:id/buffer` should show `status: 'thinking'` and a fresh `lastActivity`. If any panel is still `active` (idle) after ~8 s, the submit didn't land. Recover via `POST /api/sessions/:id/poke` with `methods: ['cr-flood']`. Don't page the human; this is exactly what `/poke` exists for.
63
+
64
+ ### cURL examples
65
+
66
+ Paste stage:
67
+
68
+ ```bash
69
+ curl -X POST http://127.0.0.1:3000/api/sessions/$SID/input \
70
+ -H 'Content-Type: application/json' \
71
+ --data-binary @- <<EOF
72
+ {"text": "[200~Hello, T1.\nBoot sequence:\n1. memory_recall(...)\n[201~", "source": "orchestrator"}
73
+ EOF
74
+ ```
75
+
76
+ Settle ~400 ms, then submit stage:
77
+
78
+ ```bash
79
+ curl -X POST http://127.0.0.1:3000/api/sessions/$SID/input \
80
+ -H 'Content-Type: application/json' \
81
+ -d '{"text":"\r","source":"orchestrator"}'
82
+ ```
83
+
84
+ Recovery (if a panel is stuck idle):
85
+
86
+ ```bash
87
+ curl -X POST http://127.0.0.1:3000/api/sessions/$SID/poke \
88
+ -H 'Content-Type: application/json' \
89
+ -d '{"methods":["cr-flood"]}'
90
+ ```
91
+
92
+ > **See also:** `~/.claude/CLAUDE.md` ยง MANDATORY: 4+1 sprint orchestration; `packages/server/src/index.js` (`/api/sessions/:id/input`, `/api/sessions/:id/poke`).
93
+
94
+ ---
95
+
96
+ ## 3. CLAUDE.md hierarchy
97
+
98
+ Three layers, read in order at session start. Each layer has a different lifespan and a different audience.
99
+
100
+ | Layer | File | Contains | Audience |
101
+ |---|---|---|---|
102
+ | **Global** | `~/.claude/CLAUDE.md` | Cross-project mandates: time check, memory-first, inject mandate, never-copy-paste, project directory map | Every Claude Code session, every project |
103
+ | **Project** | `./CLAUDE.md` (in the repo) | Read-order router. Hard rules specific to this project. Pointers to the canonical task docs | Every session in this repo |
104
+ | **Session** | The boot prompt itself (or `RESTART-PROMPT-YYYY-MM-DD.md`) | What this specific session must do, the topic to `memory_recall`, the active sprint plan to read | The single session being booted |
105
+
106
+ ### What goes in each layer
107
+
108
+ **Global rules** are things that apply *everywhere*. "Always check the time before saying 'tonight.'" "Always inject 4+1 boot prompts, never paste." These don't belong in a single repo because you'd duplicate them across every repo and they'd drift.
109
+
110
+ **Project router** is intentionally short. It says: "Here are this project's hard rules (no TypeScript, vanilla JS client, etc). For deeper context, here's the task-doc table โ€” read the *one* that matches your task." It does **not** restate global rules.
111
+
112
+ **Session prompt** carries the immediate intent. It tells the new session which `memory_recall` to fire first, which sprint plan to read, which lane it owns. It's disposable; the next session reads a new one.
113
+
114
+ ### Read-order matters
115
+
116
+ A worker session should always:
117
+
118
+ 1. `memory_recall(project=<this-project>, query=<task topic>)`
119
+ 2. Read `~/.claude/CLAUDE.md`
120
+ 3. Read `./CLAUDE.md`
121
+ 4. Read the one task doc the project router points to.
122
+ 5. Begin.
123
+
124
+ Skipping memory or skipping the project router leads to the same failure mode: the session "discovers" something the user already documented two sprints ago and proposes a fix that contradicts a locked decision. Don't do that.
125
+
126
+ > **See also:** `./CLAUDE.md` (this repo's router); `~/.claude/CLAUDE.md` ยง MANDATORY: Check Memory First.
127
+
128
+ ---
129
+
130
+ ## 4. Memory-first discipline
131
+
132
+ Claude Code has a persistent long-term memory system (the Mnestra MCP server, in the TMR stack). It survives sessions. It survives projects. It is the single most valuable input to any session that is not literally the first one.
133
+
134
+ ### Always start with `memory_recall`
135
+
136
+ Before reading files, before analyzing code, before writing anything: call `memory_recall`. Twice:
137
+
138
+ 1. With a query about the current project + task topic.
139
+ 2. With a broader query about recent decisions, bugs, or preferences.
140
+
141
+ The first surfaces lane-specific intel. The second surfaces drift you might otherwise ignore โ€” "oh, we decided last sprint that X is out of scope" is exactly the kind of thing memory catches.
142
+
143
+ ### When to use `memory_remember`
144
+
145
+ Save to memory when:
146
+
147
+ - You make a non-obvious architectural decision (lock it).
148
+ - You fix a bug whose root cause would surprise the next reader.
149
+ - You discover a user preference (workflow, tone, naming).
150
+ - You hit context near a soft compaction boundary and want a safety net.
151
+
152
+ Don't save to memory when:
153
+
154
+ - The fact lives perfectly well in code or `git log`. (Don't duplicate things `git blame` already answers.)
155
+ - The fact is ephemeral ("currently the build is red"). Memory is for things that should outlive the session.
156
+
157
+ ### Memory vs. project files
158
+
159
+ Memory persists across sessions and projects. Project files persist in the repo. Use:
160
+
161
+ - **Memory** for cross-session reasoning aids: decisions, preferences, surprising bug fixes.
162
+ - **`docs/`** for things you'd want a *new contributor* to read. README, ARCHITECTURE, RELEASE.
163
+ - **`CONTRADICTIONS.md`** for the live contradictions ledger โ€” facts that conflict and need resolution.
164
+ - **`project_facts.md`** for the *factual snapshot* of the project that doesn't change weekly.
165
+
166
+ ### Cross-project search
167
+
168
+ Omit the `project` parameter in `memory_recall` to search across all projects. Useful for shared patterns: "how did we handle Supabase migrations" โ€” answer might come from a sibling project.
169
+
170
+ > **See also:** `~/.claude/CLAUDE.md` ยง MANDATORY: Check Memory First; `~/.claude/CLAUDE.md` ยง RAG Memory System.
171
+
172
+ ---
173
+
174
+ ## 5. Enforcement vs. convention
175
+
176
+ When the orchestrator surfaces a security or correctness gap mid-sprint, the **default response is enforcement** โ€” fix the underlying mechanism so the gap can't recur. Convention-only ("we should remember to do X") is the fallback, not the default.
177
+
178
+ ### Why default-to-enforcement
179
+
180
+ A convention-only fix asks every future session to re-discover the rule, read the doc, and choose to follow it. That works for stylistic preferences. It does not work for security boundaries, data integrity, or correctness invariants โ€” there, the *first time someone forgets the convention* is the bug.
181
+
182
+ Enforcement looks like:
183
+
184
+ - Adding a runtime check that throws on misuse.
185
+ - Adding a CI lint that fails the PR.
186
+ - Restructuring the API so the wrong call is impossible to type.
187
+
188
+ ### When convention-only is justified
189
+
190
+ Convention is acceptable when **all three** of these hold:
191
+
192
+ 1. The cost of enforcement is disproportionate to the risk.
193
+ 2. The rule is genuinely contextual (case-by-case judgment, not a hard invariant).
194
+ 3. There's a clear paper trail (memory entry, CLAUDE.md note, doc) so future sessions encounter the rule.
195
+
196
+ If even one fails, prefer enforcement.
197
+
198
+ > **See also:** memory `feedback_orchestrator_enforcement.md`; `~/.claude/CLAUDE.md` ยง orchestration discipline.
199
+
200
+ ---
201
+
202
+ ## 6. Sprint discipline inside a lane
203
+
204
+ You are T*n*. You own *one* lane. Stay in it.
205
+
206
+ ### Hard rules for workers
207
+
208
+ - **No version bumps.** Don't edit `package.json`'s `version` field. Orchestrator handles it at close.
209
+ - **No `CHANGELOG.md` edits.** Same reason.
210
+ - **No commits.** Work, save files, sign DONE. The orchestrator commits the sprint as a single audited unit.
211
+ - **No `git push`, no `npm publish`.** Same reason โ€” and `RELEASE.md` has separate strict rules.
212
+ - **No edits outside your declared file ownership.** If you find a bug in another lane's files, post a FINDING in STATUS.md describing it; the orchestrator routes it.
213
+
214
+ ### STATUS.md โ€” append-only
215
+
216
+ Each lane has a `## T<n>` section in `docs/sprint-N-<name>/STATUS.md`. Post entries in this format:
217
+
218
+ ```
219
+ ### FINDING โ€” YYYY-MM-DD HH:MM ET
220
+ <what you found>
221
+
222
+ ### FIX-PROPOSED โ€” YYYY-MM-DD HH:MM ET
223
+ <what you intend to do>
224
+
225
+ ### DONE โ€” YYYY-MM-DD HH:MM ET
226
+ <files changed, line counts, anything follow-up sprints need to know>
227
+ ```
228
+
229
+ **Append only.** Never edit prior entries (yours or another lane's). The chronology is the audit trail.
230
+
231
+ ### What "DONE" means
232
+
233
+ DONE means: lane work is complete to the briefing's acceptance criteria, files are saved, a smoke test (manual or automated) confirms the change works. It does **not** mean "I've reviewed myself and decided I'm done." The orchestrator will independently verify before closing the sprint.
234
+
235
+ > **See also:** active sprint plan at `docs/sprint-N-<name>/PLANNING.md`; the lane briefing at `docs/sprint-N-<name>/T<n>-<lane>.md`.
236
+
237
+ ---
238
+
239
+ ## 7. Restart-prompt rituals
240
+
241
+ When a session ends and the next session needs to pick up cleanly, the bridge is a `RESTART-PROMPT-YYYY-MM-DD.md` written before close.
242
+
243
+ ### When to write one
244
+
245
+ Write a restart prompt when:
246
+
247
+ - A multi-day initiative paused mid-flight.
248
+ - The session is wrapping up but the work isn't done โ€” context window, not work, ran out.
249
+ - A sprint just shipped and the next sprint should start cold but informed.
250
+
251
+ If the session was a one-off Q&A with no shipped artifacts and no follow-up, skip the doc. (But still draft the session-end email per `~/.claude/CLAUDE.md` ยง Session-End Email โ€” that lives in Gmail, not the repo.)
252
+
253
+ ### What it must contain
254
+
255
+ A good restart prompt is paste-ready and self-contained. It has:
256
+
257
+ 1. **Live state** โ€” what's deployed, what's published, current branch, current versions.
258
+ 2. **What shipped this session** โ€” concrete bullets, dated, with commit SHAs.
259
+ 3. **What's planned next** โ€” queued sprints, their `docs/sprint-N-<name>/` paths, deferred items and why.
260
+ 4. **Read order for the next session** โ€” explicit list: `memory_recall(...)`, `~/.claude/CLAUDE.md`, `./CLAUDE.md`, the relevant restart doc, the active sprint plan.
261
+ 5. **Paste-ready prompt block** โ€” the literal text the human will hand to the next Claude session at boot.
262
+
263
+ Where to put it: top of the repo at `RESTART-PROMPT-YYYY-MM-DD.md`. If multiple per day, suffix with `-<topic>`.
264
+
265
+ > **See also:** `~/.claude/CLAUDE.md` ยง MANDATORY: Session-End Email to Self.
266
+
267
+ ---
268
+
269
+ ## 8. Per-project scaffolding files
270
+
271
+ A well-configured project repo has these files at root or near-root. They give every Claude session a consistent, low-friction onramp.
272
+
273
+ | File | Purpose |
274
+ |---|---|
275
+ | `CLAUDE.md` | The project router. Hard rules, read-order, pointers to task docs. Short. |
276
+ | `CONTRADICTIONS.md` | Live contradictions ledger. Facts that conflict and aren't yet resolved. New sessions read this to avoid relitigating settled debates. |
277
+ | `project_facts.md` | Factual snapshot โ€” what this project *is*, who built it, what's deployed where, what's published where. Updated at major milestones, not weekly. |
278
+ | `docs/orchestration/` | Sprint plans, restart prompts, sprint STATUS.md files. Each sprint gets a directory: `docs/sprint-N-<name>/PLANNING.md`, `T<n>-<lane>.md`, `STATUS.md`. |
279
+ | `RESTART-PROMPT.md` template | Skeleton for the restart-prompt ritual (ยง 7). Filled in at session end with live values. |
280
+ | `.claude/settings.json` | Permission defaults โ€” which commands to allow without prompting, hook configurations, etc. |
281
+
282
+ You don't need to create these by hand. The `termdeck init --project <name>` subcommand scaffolds all of them from canonical templates. The eight files generated, in their final project-relative target paths:
283
+
284
+ 1. `CLAUDE.md` โ€” project router (the one you're writing for).
285
+ 2. `CONTRADICTIONS.md` โ€” live contradictions ledger.
286
+ 3. `project_facts.md` โ€” factual snapshot, updated at milestones.
287
+ 4. `README.md` โ€” public-facing overview (extend after generation).
288
+ 5. `docs/orchestration/README.md` โ€” how this project runs sprints.
289
+ 6. `docs/orchestration/RESTART-PROMPT.md.tmpl` โ€” restart-prompt skeleton, copy + fill at session-end.
290
+ 7. `.claude/settings.json` โ€” permission allow/deny + hook defaults.
291
+ 8. `.gitignore` โ€” sensible defaults so secrets and per-machine state don't ship.
292
+
293
+ The `packages/cli/templates/` directory is the source of truth for the templates themselves; the Guide does not duplicate their content. To inspect or customize a template, read it there.
294
+
295
+ The dashboard's project drawer also surfaces an *orchestration preview* (Sprint 37 lane T3) that shows what `init --project` would generate for a given project, before you commit to the scaffolding.
296
+
297
+ > **See also:** `packages/cli/templates/` (template source of truth); `packages/cli/src/init-project.js` (the scaffolder); the project drawer's orchestration-preview tab.
298
+
299
+ ---
300
+
301
+ ## 9. Channel inject patterns
302
+
303
+ The inject mandate (ยง 2) is about Claude Code panels. The same principle applies to *messaging humans*: don't stop at "here's a draft, paste into iMessage." Deliver the message into the platform's compose box, ready to send.
304
+
305
+ ### WhatsApp
306
+
307
+ ```bash
308
+ URL="wa.me/<E164-without-plus>?text=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))' "Hello there.")"
309
+ open "https://$URL"
310
+ ```
311
+
312
+ WhatsApp Desktop intercepts the deep link and pre-fills the message in the conversation. Use `python3` quoting so special characters survive intact.
313
+
314
+ ### iMessage / SMS
315
+
316
+ Use the `mcp__imessage__send_imessage` MCP tool. `service: "iMessage"` is the default; pass `service: "SMS"` for Android contacts. The tool dispatches via AppleScript through Messages.app โ€” no copy-paste, no compose-box context switch.
317
+
318
+ ### Self
319
+
320
+ Pass `to: "self"` in `send_imessage` to route to the operator's own number (resolved via the `IMESSAGE_SELF_ADDRESS` env var). Useful for shipping a wrap-up summary, restart prompt, or "remind me about X" note to your own phone.
321
+
322
+ Contact lookup is the orchestrator's job, not the human's. If you don't know a phone number, search memory (`memory_recall` for `CONTACT <name>`), grep `~/.claude/cache/`, find it. Do not ask "what's their number?" if it has been provided in any prior session.
323
+
324
+ > **See also:** `~/.claude/CLAUDE.md` ยง MANDATORY: Never present messages for copy-paste โ€” always inject.
325
+
326
+ ---
327
+
328
+ ## Where to go next
329
+
330
+ - **Run a sprint right now:** open the dashboard, click the layout button labeled `orch`, launch four Claude Code panels + a fifth orchestrator panel, and tell the orchestrator "terminals open, inject."
331
+ - **Scaffold a new project:** `termdeck init --project <name>` โ€” see ยง 8.
332
+ - **Read the canonical sources:** `~/.claude/CLAUDE.md` for the global mandates this Guide is distilled from.
333
+ - **Find a specific section in the dashboard:** open the right-rail Guide panel (top-right) and search.
334
+
335
+ This Guide is reference material โ€” read the section that matches your situation, skip the rest. The next time you orchestrate a sprint, the patterns above should feel like muscle memory, not a doc to consult.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck",
3
- "version": "0.7.3",
3
+ "version": "0.9.0",
4
4
  "description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
5
5
  "bin": {
6
6
  "termdeck": "./packages/cli/src/index.js"
@@ -8,11 +8,13 @@
8
8
  "main": "packages/cli/src/index.js",
9
9
  "files": [
10
10
  "packages/cli/src/**",
11
+ "packages/cli/templates/**",
11
12
  "packages/server/src/**",
12
13
  "packages/client/public/**",
13
14
  "config/config.example.yaml",
14
15
  "config/secrets.env.example",
15
16
  "config/transcript-migration.sql",
17
+ "docs/orchestrator-guide.md",
16
18
  "LICENSE",
17
19
  "README.md"
18
20
  ],
@@ -1,26 +1,32 @@
1
- // Detection helper for Sprint 24: should `termdeck` (no subcommand)
2
- // auto-route through stack.js? Pure function, isolated for testability โ€”
3
- // the dispatcher in index.js still owns the actual routing decision.
1
+ // Detection helper for default-entry routing โ€” does plain `termdeck`
2
+ // (no subcommand) route through stack.js? Pure function, isolated for
3
+ // testability โ€” the dispatcher in index.js still owns the actual
4
+ // routing decision.
5
+ //
6
+ // Sprint 24 policy (original): only auto-orchestrate when both
7
+ // ~/.termdeck/secrets.env and ~/.termdeck/config.yaml exist AND either
8
+ // mnestra.autoStart or rag.enabled is true. Fresh boxes fell through to
9
+ // Tier-1-only.
10
+ //
11
+ // Sprint 36 policy (current): always orchestrate by default. Acceptance
12
+ // criterion #2 of Sprint 36 (`docs/sprint-36-launcher-ui-parity/PLANNING.md`)
13
+ // requires `npx @jhizzard/termdeck` to match `scripts/start.sh` step-by-step
14
+ // on every machine, fresh or configured. stack.js handles the fresh-machine
15
+ // case via `ensureFirstRunConfig()` โ€” it auto-writes a minimal
16
+ // ~/.termdeck/config.yaml on first run, then proceeds through Step 1/4โ€“4/4
17
+ // with mostly-SKIP statuses (no secrets โ†’ SKIP, no mnestra binary โ†’ SKIP,
18
+ // no DATABASE_URL โ†’ SKIP, BOOT). That output mirrors what start.sh produces
19
+ // on a fresh box.
20
+ //
21
+ // The escape hatch is the explicit `--no-stack` flag handled in index.js.
22
+ //
23
+ // The function signature stays the same so callers and tests don't break;
24
+ // the body is just a constant now. Keeping the function (rather than
25
+ // inlining the boolean) leaves a hook for future telemetry โ€” e.g., emitting
26
+ // "why we orchestrated" reasons โ€” without another dispatcher rewrite.
4
27
 
5
- const fs = require('fs');
6
- const os = require('os');
7
- const path = require('path');
8
-
9
- function shouldAutoOrchestrate(homeDir) {
10
- const home = homeDir || os.homedir();
11
- const secretsPath = path.join(home, '.termdeck', 'secrets.env');
12
- const configPath = path.join(home, '.termdeck', 'config.yaml');
13
- if (!fs.existsSync(secretsPath) || !fs.existsSync(configPath)) return false;
14
- let parsed;
15
- try {
16
- const yaml = require('yaml');
17
- parsed = yaml.parse(fs.readFileSync(configPath, 'utf8')) || {};
18
- } catch (_e) {
19
- return false;
20
- }
21
- const mnestraAuto = parsed.mnestra && parsed.mnestra.autoStart === true;
22
- const ragEnabled = parsed.rag && parsed.rag.enabled === true;
23
- return Boolean(mnestraAuto || ragEnabled);
28
+ function shouldAutoOrchestrate(_homeDir) {
29
+ return true;
24
30
  }
25
31
 
26
32
  module.exports = { shouldAutoOrchestrate };
@@ -52,6 +52,23 @@ function reclaimStalePort(port) {
52
52
  }
53
53
 
54
54
  if (isTermDeck) {
55
+ // Liveness probe โ€” never kill a TermDeck that's actively serving requests.
56
+ // A responsive /api/sessions means it's the orchestrator's live server, and
57
+ // killing it cascades to every child PTY. This was the actual root cause of
58
+ // four Sprint 36 server-kill incidents on 2026-04-27 (a sibling reclaimPort
59
+ // in stack.js had the same flaw and was already patched; this twin in the
60
+ // CLI entry was missed). Mirror of stack.js:isTermDeckLive.
61
+ let alreadyLive = false;
62
+ try {
63
+ const probe = execSync(`curl -sf -m 1.5 -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/api/sessions 2>/dev/null`, { encoding: 'utf8' });
64
+ if (probe.trim() === '200') alreadyLive = true;
65
+ } catch (_e) { /* curl missing or non-200 โ†’ treat as stale */ }
66
+
67
+ if (alreadyLive) {
68
+ console.log(` \x1b[2m[port] :${port} held by live TermDeck (PIDs: ${pids.join(' ')}) โ€” not killing. Use --port <other> for a second instance.\x1b[0m`);
69
+ process.exit(0); // graceful exit; don't try to bind a port that's already serving
70
+ }
71
+
55
72
  console.log(` \x1b[2m[port] Reclaiming :${port} from stale TermDeck (PIDs: ${pids.join(' ')})\x1b[0m`);
56
73
  for (const pid of pids) {
57
74
  try { process.kill(parseInt(pid, 10), 'SIGTERM'); } catch (_e) {}
@@ -94,6 +111,18 @@ const args = process.argv.slice(2);
94
111
  if (args[0] === 'init') {
95
112
  const mode = args[1];
96
113
  const rest = args.slice(2);
114
+
115
+ // Sprint 37 T2: refuse mode-mixing. The dispatch picks args[1] as the
116
+ // single mode flag, but a user who writes `init --project foo --mnestra`
117
+ // probably intended only one of those. Surface the conflict instead of
118
+ // silently picking the first.
119
+ const MODES = ['--project', '--mnestra', '--rumen'];
120
+ const presentModes = MODES.filter((m) => args.slice(1).includes(m));
121
+ if (presentModes.length > 1) {
122
+ console.error(`[cli] init: pass only one of ${MODES.join(' | ')}; got ${presentModes.join(' + ')}`);
123
+ process.exit(1);
124
+ }
125
+
97
126
  const run = (modPath) => {
98
127
  const fn = require(modPath);
99
128
  return fn(rest).then((code) => process.exit(code || 0));
@@ -112,9 +141,19 @@ if (args[0] === 'init') {
112
141
  });
113
142
  return;
114
143
  }
115
- console.error('Usage: termdeck init --mnestra | --rumen');
116
- console.error(' termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)');
117
- console.error(' termdeck init --rumen Deploy Tier 3 async learning (Rumen)');
144
+ if (mode === '--project') {
145
+ // init-project takes the project name as its first positional arg, plus
146
+ // optional --dry-run / --force flags. Pass `rest` straight through.
147
+ run(path.join(__dirname, 'init-project.js')).catch((err) => {
148
+ console.error('[cli] init --project failed:', err && err.stack || err);
149
+ process.exit(1);
150
+ });
151
+ return;
152
+ }
153
+ console.error('Usage: termdeck init --mnestra | --rumen | --project <name>');
154
+ console.error(' termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)');
155
+ console.error(' termdeck init --rumen Deploy Tier 3 async learning (Rumen)');
156
+ console.error(' termdeck init --project <name> Scaffold a new project with CLAUDE.md + orchestration docs');
118
157
  process.exit(1);
119
158
  }
120
159
 
@@ -197,6 +236,7 @@ for (let i = 0; i < args.length; i++) {
197
236
  termdeck --session-logs Write per-session markdown logs to ~/.termdeck/sessions/
198
237
  termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)
199
238
  termdeck init --rumen Deploy Tier 3 async learning (Rumen)
239
+ termdeck init --project NAME Scaffold a new project with CLAUDE.md + orchestration docs (--dry-run, --force)
200
240
  termdeck forge Generate Claude skills from memories (experimental)
201
241
  termdeck doctor Diagnose stack โ€” npm versions + Supabase schema (use --no-schema to skip the DB probe)
202
242
 
@@ -232,6 +272,7 @@ const firstRun = !fs.existsSync(path.join(os.homedir(), '.termdeck', 'config.yam
232
272
 
233
273
  const config = loadConfig();
234
274
  if (flags.port) config.port = flags.port;
275
+ else if (process.env.TERMDECK_PORT) config.port = parseInt(process.env.TERMDECK_PORT, 10);
235
276
  if (flags.sessionLogs) {
236
277
  config.sessionLogs = { ...(config.sessionLogs || {}), enabled: true };
237
278
  console.log('[cli] session logs enabled โ€” writing to ~/.termdeck/sessions/ on panel exit');
@@ -262,17 +303,20 @@ if (!LOOPBACK.has(host)) {
262
303
  // Sprint 25 T4: non-blocking nudge when RAG is configured but the Supabase MCP
263
304
  // (T1's `@supabase/mcp-server-supabase` detection) isn't installed. Lazy-loads
264
305
  // T1's module so Tier 1 users with no RAG never pay the require cost. Silent
265
- // when RAG is off, when the MCP is detected, when ~/.claude/mcp.json already
266
- // declares a `supabase` server, or when anything below throws.
306
+ // when RAG is off, when the MCP is detected, when the MCP config (canonical
307
+ // ~/.claude.json or legacy ~/.claude/mcp.json) already declares a `supabase`
308
+ // server, or when anything below throws.
309
+ //
310
+ // Sprint 36 T2: read order is canonical โ†’ legacy. Claude Code v2.1.119+ reads
311
+ // only the canonical file; the legacy fallback covers users who haven't yet
312
+ // migrated and pinned other tooling to the old path.
267
313
  async function checkSupabaseMcpHint(cfg) {
268
314
  if (!cfg || !cfg.rag || cfg.rag.enabled !== true) return null;
269
315
  try {
270
- const claudeMcpPath = path.join(os.homedir(), '.claude', 'mcp.json');
271
- if (fs.existsSync(claudeMcpPath)) {
272
- try {
273
- const parsed = JSON.parse(fs.readFileSync(claudeMcpPath, 'utf8'));
274
- if (parsed && parsed.mcpServers && parsed.mcpServers.supabase) return null;
275
- } catch (_e) { /* malformed JSON โ€” fall through and let detectMcp decide */ }
316
+ const { CLAUDE_MCP_PATH_CANONICAL, CLAUDE_MCP_PATH_LEGACY, readMcpServers } = require('./mcp-config');
317
+ for (const candidate of [CLAUDE_MCP_PATH_CANONICAL, CLAUDE_MCP_PATH_LEGACY]) {
318
+ const read = readMcpServers(candidate);
319
+ if (read.servers && read.servers.supabase) return null;
276
320
  }
277
321
  const { detectMcp } = require(path.join(__dirname, '..', '..', 'server', 'src', 'setup', 'supabase-mcp.js'));
278
322
  const result = await detectMcp();