@jhizzard/termdeck 0.8.0 โ 0.10.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.
- package/docs/orchestrator-guide.md +335 -0
- package/package.json +3 -1
- package/packages/cli/src/index.js +26 -3
- package/packages/cli/src/init-project.js +213 -0
- package/packages/cli/src/templates.js +84 -0
- package/packages/cli/templates/.claude-settings.json.tmpl +32 -0
- package/packages/cli/templates/.gitignore.tmpl +28 -0
- package/packages/cli/templates/CLAUDE.md.tmpl +35 -0
- package/packages/cli/templates/CONTRADICTIONS.md.tmpl +30 -0
- package/packages/cli/templates/README.md.tmpl +15 -0
- package/packages/cli/templates/RESTART-PROMPT.md.tmpl +38 -0
- package/packages/cli/templates/docs-orchestration-README.md.tmpl +29 -0
- package/packages/cli/templates/project_facts.md.tmpl +39 -0
- package/packages/client/public/app.js +781 -0
- package/packages/client/public/graph.html +104 -0
- package/packages/client/public/graph.js +683 -0
- package/packages/client/public/index.html +145 -0
- package/packages/client/public/style.css +1185 -0
- package/packages/server/src/graph-routes.js +555 -0
- package/packages/server/src/index.js +158 -5
- package/packages/server/src/orchestration-preview.js +256 -0
- package/packages/server/src/preflight.js +82 -0
- package/packages/server/src/rag.js +138 -0
- package/packages/server/src/setup/mnestra-migrations/009_memory_relationship_metadata.sql +126 -0
- package/packages/server/src/setup/mnestra-migrations/010_memory_recall_graph.sql +147 -0
- package/packages/server/src/setup/rumen/migrations/003_graph_inference_schedule.sql +49 -0
- package/packages/server/src/sprint-inject.js +156 -0
- package/packages/server/src/sprint-routes.js +503 -0
|
@@ -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.
|
|
3
|
+
"version": "0.10.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
|
],
|
|
@@ -111,6 +111,18 @@ const args = process.argv.slice(2);
|
|
|
111
111
|
if (args[0] === 'init') {
|
|
112
112
|
const mode = args[1];
|
|
113
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
|
+
|
|
114
126
|
const run = (modPath) => {
|
|
115
127
|
const fn = require(modPath);
|
|
116
128
|
return fn(rest).then((code) => process.exit(code || 0));
|
|
@@ -129,9 +141,19 @@ if (args[0] === 'init') {
|
|
|
129
141
|
});
|
|
130
142
|
return;
|
|
131
143
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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');
|
|
135
157
|
process.exit(1);
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -214,6 +236,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
214
236
|
termdeck --session-logs Write per-session markdown logs to ~/.termdeck/sessions/
|
|
215
237
|
termdeck init --mnestra Configure Tier 2 memory (Supabase + Mnestra)
|
|
216
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)
|
|
217
240
|
termdeck forge Generate Claude skills from memories (experimental)
|
|
218
241
|
termdeck doctor Diagnose stack โ npm versions + Supabase schema (use --no-schema to skip the DB probe)
|
|
219
242
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// `termdeck init --project <name>` โ Sprint 37 T2.
|
|
4
|
+
//
|
|
5
|
+
// Scaffolds a new project directory with the orchestration patterns TermDeck
|
|
6
|
+
// itself uses: CLAUDE.md (router), CONTRADICTIONS.md (audit trail),
|
|
7
|
+
// project_facts.md (stable facts), README.md, docs/orchestration/ (sprint +
|
|
8
|
+
// restart-prompt scaffolding), .claude/settings.json (sensible permission
|
|
9
|
+
// defaults), and .gitignore. All content comes from packages/cli/templates/
|
|
10
|
+
// rendered with {{placeholder}} substitution via packages/cli/src/templates.js.
|
|
11
|
+
//
|
|
12
|
+
// Public API (used by the CLI entry and by tests):
|
|
13
|
+
// initProject({ name, dryRun, force, cwd }) -> Promise<{ exitCode, files }>
|
|
14
|
+
//
|
|
15
|
+
// CLI shim (used by index.js dispatch):
|
|
16
|
+
// main(argv) -> Promise<exitCode>
|
|
17
|
+
//
|
|
18
|
+
// `argv` here is everything AFTER `init --project` in the original argv โ
|
|
19
|
+
// i.e. for `termdeck init --project hello --dry-run`, argv is
|
|
20
|
+
// `['hello', '--dry-run']`.
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const { listTemplates, renderTemplate, TEMPLATES_DIR } = require(path.join(__dirname, 'templates.js'));
|
|
28
|
+
|
|
29
|
+
// Project name validation: lowercase letters, digits, hyphens, optional
|
|
30
|
+
// scoped prefix (@org/name) is intentionally NOT supported here โ the user
|
|
31
|
+
// would clone the result and rename if they want a scoped npm package.
|
|
32
|
+
const NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
33
|
+
|
|
34
|
+
function validateName(name) {
|
|
35
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
36
|
+
return 'Project name is required.';
|
|
37
|
+
}
|
|
38
|
+
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
|
39
|
+
return `Project name "${name}" must not contain slashes or "..".`;
|
|
40
|
+
}
|
|
41
|
+
if (!NAME_RE.test(name)) {
|
|
42
|
+
return `Project name "${name}" must be lowercase letters, digits, and hyphens (no leading/trailing hyphen).`;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readTermdeckVersion() {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
50
|
+
return pkg.version || '0.0.0';
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
return '0.0.0';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildVars({ name, projectPath }) {
|
|
57
|
+
return {
|
|
58
|
+
project_name: name,
|
|
59
|
+
project_path: projectPath,
|
|
60
|
+
generated_at: new Date().toISOString(),
|
|
61
|
+
termdeck_version: readTermdeckVersion(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ensureDir(dir) {
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Returns true if the directory either does not exist or exists and is empty.
|
|
70
|
+
function isEmptyOrMissing(dir) {
|
|
71
|
+
if (!fs.existsSync(dir)) return true;
|
|
72
|
+
const stat = fs.statSync(dir);
|
|
73
|
+
if (!stat.isDirectory()) return false;
|
|
74
|
+
return fs.readdirSync(dir).length === 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function previewSnippet(content, headLines = 5) {
|
|
78
|
+
const lines = content.split('\n');
|
|
79
|
+
const head = lines.slice(0, headLines).join('\n');
|
|
80
|
+
const remaining = Math.max(0, lines.length - headLines);
|
|
81
|
+
return remaining === 0 ? head : `${head}\n... (${remaining} more line${remaining === 1 ? '' : 's'})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function initProject(opts) {
|
|
85
|
+
const { name, dryRun = false, force = false, cwd = process.cwd() } = opts || {};
|
|
86
|
+
|
|
87
|
+
const nameError = validateName(name);
|
|
88
|
+
if (nameError) {
|
|
89
|
+
process.stderr.write(`\n โ ${nameError}\n\n`);
|
|
90
|
+
return { exitCode: 1, files: [] };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const projectPath = path.resolve(cwd, name);
|
|
94
|
+
|
|
95
|
+
if (!dryRun && !force && !isEmptyOrMissing(projectPath)) {
|
|
96
|
+
process.stderr.write(`\n โ Target ${projectPath} exists and is not empty. Use --force to overwrite, or pick a new name.\n\n`);
|
|
97
|
+
return { exitCode: 1, files: [] };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const vars = buildVars({ name, projectPath });
|
|
101
|
+
const templates = listTemplates();
|
|
102
|
+
const written = [];
|
|
103
|
+
|
|
104
|
+
if (dryRun) {
|
|
105
|
+
process.stdout.write(`\n [dry-run] Would create ${projectPath}/ with ${templates.length} files:\n\n`);
|
|
106
|
+
} else {
|
|
107
|
+
ensureDir(projectPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const entry of templates) {
|
|
111
|
+
const dest = path.join(projectPath, entry.targetPath);
|
|
112
|
+
const rendered = renderTemplate(entry.name, vars);
|
|
113
|
+
|
|
114
|
+
if (dryRun) {
|
|
115
|
+
process.stdout.write(` โข ${entry.targetPath}\n`);
|
|
116
|
+
const indented = previewSnippet(rendered).split('\n').map((l) => ` ${l}`).join('\n');
|
|
117
|
+
process.stdout.write(`${indented}\n\n`);
|
|
118
|
+
written.push({ template: entry.file, name: entry.name, dest, bytes: Buffer.byteLength(rendered, 'utf8') });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ensureDir(path.dirname(dest));
|
|
123
|
+
fs.writeFileSync(dest, rendered);
|
|
124
|
+
written.push({ template: entry.file, name: entry.name, dest, bytes: Buffer.byteLength(rendered, 'utf8') });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (dryRun) {
|
|
128
|
+
process.stdout.write(` [dry-run] Nothing was written. Re-run without --dry-run to scaffold.\n\n`);
|
|
129
|
+
} else {
|
|
130
|
+
process.stdout.write(`
|
|
131
|
+
Created ${name}/ at ${projectPath}.
|
|
132
|
+
|
|
133
|
+
Next steps:
|
|
134
|
+
cd ${name}
|
|
135
|
+
git init
|
|
136
|
+
# Open ${name}/ in TermDeck โ it will pick up the .claude/settings.json automatically.
|
|
137
|
+
# Read CLAUDE.md to see the agent read-order for this project.
|
|
138
|
+
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { exitCode: 0, files: written };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// CLI shim. Parses argv and calls initProject(). The dispatch in
|
|
146
|
+
// packages/cli/src/index.js strips the leading `init --project` tokens.
|
|
147
|
+
async function main(argv) {
|
|
148
|
+
const args = argv || [];
|
|
149
|
+
|
|
150
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
151
|
+
process.stdout.write(HELP);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// First positional argument that isn't a flag is the project name.
|
|
156
|
+
let name = null;
|
|
157
|
+
let dryRun = false;
|
|
158
|
+
let force = false;
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < args.length; i++) {
|
|
161
|
+
const tok = args[i];
|
|
162
|
+
if (tok === '--dry-run') { dryRun = true; continue; }
|
|
163
|
+
if (tok === '--force') { force = true; continue; }
|
|
164
|
+
if (tok === '--name' && args[i + 1]) { name = args[i + 1]; i++; continue; }
|
|
165
|
+
if (tok.startsWith('--')) {
|
|
166
|
+
process.stderr.write(`\n โ Unknown flag: ${tok}\n${HELP}`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
if (name === null) {
|
|
170
|
+
name = tok;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
process.stderr.write(`\n โ Unexpected extra argument: ${tok}\n${HELP}`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!name) {
|
|
178
|
+
process.stderr.write(`\n โ Missing project name.\n${HELP}`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { exitCode } = await initProject({ name, dryRun, force, cwd: process.cwd() });
|
|
183
|
+
return exitCode;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const HELP = `
|
|
187
|
+
TermDeck Project Scaffolder
|
|
188
|
+
|
|
189
|
+
Usage: termdeck init --project <name> [flags]
|
|
190
|
+
|
|
191
|
+
Flags:
|
|
192
|
+
--dry-run Print what would be created; write nothing
|
|
193
|
+
--force Overwrite an existing non-empty target directory
|
|
194
|
+
--help, -h Print this message and exit
|
|
195
|
+
|
|
196
|
+
What this does:
|
|
197
|
+
Creates <name>/ in the current directory with a project skeleton:
|
|
198
|
+
CLAUDE.md Agent read-order router
|
|
199
|
+
CONTRADICTIONS.md Audit trail of changed facts/decisions
|
|
200
|
+
project_facts.md Stable per-project facts
|
|
201
|
+
README.md Human-facing intro
|
|
202
|
+
docs/orchestration/ Sprint + restart-prompt scaffolding
|
|
203
|
+
.claude/settings.json Sensible Claude Code permission defaults
|
|
204
|
+
.gitignore Standard Node + .DS_Store + .termdeck/
|
|
205
|
+
|
|
206
|
+
Templates live in packages/cli/templates/ and use {{placeholder}} substitution.
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
module.exports = main;
|
|
210
|
+
module.exports.initProject = initProject;
|
|
211
|
+
module.exports._validateName = validateName;
|
|
212
|
+
module.exports._buildVars = buildVars;
|
|
213
|
+
module.exports.TEMPLATES_DIR = TEMPLATES_DIR;
|