@jhizzard/termdeck 1.0.1 → 1.0.3
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/package.json +2 -1
- package/packages/cli/src/init-mnestra.js +175 -3
- package/packages/cli/src/init-rumen.js +35 -8
- package/packages/server/src/setup/audit-upgrade.js +126 -3
- package/packages/server/src/setup/mnestra-migrations/017_memory_sessions_session_metadata.sql +94 -0
- package/packages/stack-installer/README.md +73 -0
- package/packages/stack-installer/assets/hooks/README.md +172 -0
- package/packages/stack-installer/assets/hooks/memory-session-end.js +898 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# TermDeck session-end memory hook
|
|
2
|
+
|
|
3
|
+
The `@jhizzard/termdeck-stack` installer can drop `memory-session-end.js`
|
|
4
|
+
into `~/.claude/hooks/` and wire it into `~/.claude/settings.json` under
|
|
5
|
+
`hooks.Stop`. The installer prompts you before doing this; default is
|
|
6
|
+
yes.
|
|
7
|
+
|
|
8
|
+
## What the hook does
|
|
9
|
+
|
|
10
|
+
On every Claude Code session close, Claude Code fires its `Stop` hook
|
|
11
|
+
with a JSON payload on stdin:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "transcript_path": "/path/to/session.jsonl", "cwd": "/path/where/you/were/working", "session_id": "..." }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The hook:
|
|
18
|
+
|
|
19
|
+
1. Skips transcripts smaller than 5 KB (no signal in tiny sessions —
|
|
20
|
+
override via `TERMDECK_HOOK_MIN_BYTES`).
|
|
21
|
+
2. Validates env vars (`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`,
|
|
22
|
+
`OPENAI_API_KEY`); if any are missing, logs the missing list and
|
|
23
|
+
exits cleanly without blocking the session close.
|
|
24
|
+
3. Detects the project from `cwd` against a built-in regex table; falls
|
|
25
|
+
back to `"global"` when nothing matches. **The default table is
|
|
26
|
+
intentionally empty** — see "Customizing the project map" below to
|
|
27
|
+
add your own entries.
|
|
28
|
+
4. Builds a coarse session summary from the last ~30 messages of the
|
|
29
|
+
transcript (~7 KB cap to stay inside OpenAI's embedding-input
|
|
30
|
+
budget).
|
|
31
|
+
5. Embeds the summary via OpenAI `text-embedding-3-small` (1,536-dim).
|
|
32
|
+
6. POSTs **one row** to Supabase `/rest/v1/memory_items` with
|
|
33
|
+
`source_type='session_summary'`.
|
|
34
|
+
7. Logs every step to `~/.claude/hooks/memory-hook.log`.
|
|
35
|
+
|
|
36
|
+
The hook is **fail-soft**: any error (network, parse, env-var-missing,
|
|
37
|
+
malformed transcript) is logged and the hook exits 0. Claude Code's
|
|
38
|
+
session close is never blocked.
|
|
39
|
+
|
|
40
|
+
## Required environment
|
|
41
|
+
|
|
42
|
+
The hook needs three env vars at run time:
|
|
43
|
+
|
|
44
|
+
| Var | What | How to set |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `SUPABASE_URL` | Your Supabase project URL (e.g. `https://abc.supabase.co`) | `~/.termdeck/secrets.env` (Tier 2) |
|
|
47
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Service-role key with INSERT on `memory_items`. **Not the anon key.** | `~/.termdeck/secrets.env` |
|
|
48
|
+
| `OPENAI_API_KEY` | OpenAI key for embedding inference | `~/.termdeck/secrets.env` or your shell |
|
|
49
|
+
|
|
50
|
+
Claude Code propagates the parent shell's environment into hook
|
|
51
|
+
processes, so anything in your shell init or
|
|
52
|
+
`~/.termdeck/secrets.env` (sourced by `scripts/start.sh` /
|
|
53
|
+
`npx @jhizzard/termdeck`) is visible to the hook.
|
|
54
|
+
|
|
55
|
+
**From v0.17.0**, the TermDeck server also merges
|
|
56
|
+
`~/.termdeck/secrets.env` directly into every PTY-spawned shell — so any
|
|
57
|
+
Claude Code panel launched inside TermDeck inherits `SUPABASE_URL` /
|
|
58
|
+
`SUPABASE_SERVICE_ROLE_KEY` / `OPENAI_API_KEY` even if the user's
|
|
59
|
+
parent shell never sourced the file. Concrete values in
|
|
60
|
+
`process.env` still win (parent-shell env takes precedence over the
|
|
61
|
+
file fallback). Standalone Claude Code launches outside TermDeck
|
|
62
|
+
still rely on the parent shell having sourced the file — for those,
|
|
63
|
+
the wizard can offer a one-line `~/.zshrc` source addition.
|
|
64
|
+
|
|
65
|
+
If any of the three is missing the log line will name them:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
[2026-04-27T21:30:00.000Z] env-var-missing: OPENAI_API_KEY — set these in ~/.termdeck/secrets.env or your shell to enable Mnestra ingestion. Skipping.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Customizing the project map
|
|
72
|
+
|
|
73
|
+
The hook ships with an **empty `PROJECT_MAP`** by default — every
|
|
74
|
+
session lands under `project: 'global'` until you add entries. To add
|
|
75
|
+
your own:
|
|
76
|
+
|
|
77
|
+
1. Open `~/.claude/hooks/memory-session-end.js` after the installer
|
|
78
|
+
has dropped it.
|
|
79
|
+
2. Find the `PROJECT_MAP` array near the top of the file.
|
|
80
|
+
3. Add one entry per project; each entry is `{ pattern, project }`
|
|
81
|
+
where `pattern` is a regex matched against `cwd`.
|
|
82
|
+
|
|
83
|
+
### Order matters: most-specific-first
|
|
84
|
+
|
|
85
|
+
`detectProject(cwd)` returns the **first** matching entry. If a deep
|
|
86
|
+
project lives under a broader parent dir, the deep pattern must come
|
|
87
|
+
first or the parent will swallow it. This bug bit the TermDeck team in
|
|
88
|
+
Sprint 41 — every cwd under a `ChopinNashville/` parent was getting
|
|
89
|
+
tagged `chopin-nashville` because the parent-dir pattern came before
|
|
90
|
+
each sub-project's specific pattern.
|
|
91
|
+
|
|
92
|
+
Example showing the right ordering:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const PROJECT_MAP = [
|
|
96
|
+
// Specific code projects under a common parent — these MUST appear
|
|
97
|
+
// before the parent-dir catch-all below.
|
|
98
|
+
{ pattern: /\/MyOrg\/SideProjects\/widget-app/i, project: 'widget-app' },
|
|
99
|
+
{ pattern: /\/MyOrg\/SideProjects\/scheduler/i, project: 'scheduler' },
|
|
100
|
+
{ pattern: /\/MyOrg\/2026\/festival\/podium/i, project: 'podium' },
|
|
101
|
+
{ pattern: /\/MyOrg\/2026\/festival/i, project: 'festival' },
|
|
102
|
+
|
|
103
|
+
// Other top-level projects.
|
|
104
|
+
{ pattern: /\/PVB\//i, project: 'pvb' },
|
|
105
|
+
|
|
106
|
+
// Catch-all for the parent dir — only matches when no specific
|
|
107
|
+
// project above matched first.
|
|
108
|
+
{ pattern: /\/MyOrg(\/|$)/i, project: 'myorg-ops' },
|
|
109
|
+
];
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For a worked example of a real production taxonomy (with explicit
|
|
113
|
+
priority ordering, alias documentation, and a structural-invariant
|
|
114
|
+
test), see [`docs/PROJECT-TAXONOMY.md`](https://github.com/jhizzard/termdeck/blob/main/docs/PROJECT-TAXONOMY.md)
|
|
115
|
+
in the TermDeck repo.
|
|
116
|
+
|
|
117
|
+
### Other rules
|
|
118
|
+
|
|
119
|
+
- The map is local-only — it's never sent to any service. Editing it
|
|
120
|
+
takes effect on the next Claude Code session close (no restart
|
|
121
|
+
needed).
|
|
122
|
+
- Anything that doesn't match falls through to `'global'`.
|
|
123
|
+
- Adopt the module-export contract (`module.exports = { detectProject, PROJECT_MAP }`)
|
|
124
|
+
if you want to write a unit test that exercises your taxonomy. The
|
|
125
|
+
bundled hook already does this; if you copy-paste a custom hook,
|
|
126
|
+
preserve the `if (require.main === module)` guard around the stdin
|
|
127
|
+
reader so `require()` doesn't hang.
|
|
128
|
+
|
|
129
|
+
## Coexistence with Joshua's `rag-system` hook
|
|
130
|
+
|
|
131
|
+
If you have Joshua's private `rag-system` repo and his rag-system-based
|
|
132
|
+
session hook installed, this bundled hook and that one can coexist:
|
|
133
|
+
|
|
134
|
+
- The bundled hook writes `source_type='session_summary'` — one row
|
|
135
|
+
per session, summary-only.
|
|
136
|
+
- The `rag-system` hook writes `source_type='fact'` — multiple rows
|
|
137
|
+
per session via Claude Haiku fact extraction + dedup.
|
|
138
|
+
|
|
139
|
+
Different `source_type` values mean the two paths don't dedup against
|
|
140
|
+
each other. If both are installed at the same path
|
|
141
|
+
(`~/.claude/hooks/memory-session-end.js`) the installer will prompt
|
|
142
|
+
before overwriting; choose accordingly.
|
|
143
|
+
|
|
144
|
+
## How to disable
|
|
145
|
+
|
|
146
|
+
Two options:
|
|
147
|
+
|
|
148
|
+
1. Edit `~/.claude/settings.json` and remove the entry under
|
|
149
|
+
`hooks.Stop` that references `memory-session-end.js`. Leave the
|
|
150
|
+
file in place; it simply won't fire.
|
|
151
|
+
2. Or delete `~/.claude/hooks/memory-session-end.js` AND remove the
|
|
152
|
+
`settings.json` entry. (Removing only the file leaves a broken
|
|
153
|
+
`command` in settings — Claude Code will log a missing-file error
|
|
154
|
+
on every session close.)
|
|
155
|
+
|
|
156
|
+
Re-running `npx @jhizzard/termdeck-stack` after disabling will
|
|
157
|
+
re-prompt to install. Decline at the prompt to stay opted out.
|
|
158
|
+
|
|
159
|
+
## Optional flags
|
|
160
|
+
|
|
161
|
+
| Env var | Effect |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `TERMDECK_HOOK_DEBUG=1` | Verbose `[debug]` lines in the log |
|
|
164
|
+
| `TERMDECK_HOOK_MIN_BYTES=10000` | Override the 5 KB skip threshold |
|
|
165
|
+
|
|
166
|
+
## Log file
|
|
167
|
+
|
|
168
|
+
`~/.claude/hooks/memory-hook.log` accumulates one line per session
|
|
169
|
+
event (skips, errors, ingests). The hook never rotates it. If it
|
|
170
|
+
grows unwieldy you can truncate it
|
|
171
|
+
(`: > ~/.claude/hooks/memory-hook.log`) without affecting hook
|
|
172
|
+
behavior.
|