create-ai-memory 0.1.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/LICENSE +21 -0
- package/README.md +340 -0
- package/bin/create-ai-memory.js +41 -0
- package/create-ai-memory.plugin.zsh +5 -0
- package/hooks/claude/session-start.sh +16 -0
- package/hooks/claude/session-summary.sh +54 -0
- package/hooks/claude/settings.snippet.json +19 -0
- package/hooks/git/commit-msg +178 -0
- package/hooks/git/pre-push +19 -0
- package/install.sh +124 -0
- package/package.json +44 -0
- package/shell/adapters.zsh +75 -0
- package/shell/ai-mem.zsh +483 -0
- package/vault-template/_Global_Profile.md +33 -0
- package/vault-template/_Standards.md +29 -0
- package/vault-template/_projects/_project_template.md +26 -0
- package/vault-template/_session_logs/_session_template.md +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ram Christopher Baarde
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ai-memory
|
|
4
|
+
|
|
5
|
+
**Persistent, agent-agnostic session memory for AI coding CLIs. One Markdown vault, any agent.**
|
|
6
|
+
|
|
7
|
+
Give Claude Code, Codex, Gemini, Cursor, and opencode a shared second brain that
|
|
8
|
+
survives across sessions: durable profile, per-project context, and last-session carryover,
|
|
9
|
+
injected at launch. The vault is the source of truth; the chat is disposable.
|
|
10
|
+
|
|
11
|
+
No daemon, no database, no API key. Just zsh and Markdown files you can read.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+

|
|
15
|
+

|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## The problem
|
|
23
|
+
|
|
24
|
+
Every AI coding session starts from zero.
|
|
25
|
+
|
|
26
|
+
Claude Code, Codex, Gemini CLI. The moment a session ends, the agent forgets what
|
|
27
|
+
was built, the decisions that were made, the PRD, every architectural and codebase
|
|
28
|
+
choice. The context that mattered most evaporates with the chat thread, and the
|
|
29
|
+
next run begins blind.
|
|
30
|
+
|
|
31
|
+
You pay the same tax on every run:
|
|
32
|
+
|
|
33
|
+
- **Agent amnesia.** Accumulated project knowledge vanishes when the thread closes.
|
|
34
|
+
- **Lost decisions.** Why a choice was made is nowhere; the next run re-litigates it.
|
|
35
|
+
- **Reload overhead.** You re-explain the stack, constraints, and conventions from scratch.
|
|
36
|
+
- **No continuity.** Nothing carries "where I left off" into the next session.
|
|
37
|
+
|
|
38
|
+
Switching agents makes it worse. Each CLI is its own island with its own memory,
|
|
39
|
+
or none. Knowledge earned in Claude doesn't reach Codex.
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
Keep the memory outside the chat, in plain Markdown on disk, and inject it into
|
|
44
|
+
whichever agent you launch. Because it's just files, the vault doubles as an
|
|
45
|
+
[Obsidian](https://obsidian.md) folder with graph view, backlinks, and search;
|
|
46
|
+
nothing here requires Obsidian.
|
|
47
|
+
|
|
48
|
+
Memory sits in three layers, each injected at the right scope:
|
|
49
|
+
|
|
50
|
+
| Layer | Lives in | Injected | Holds |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| **Global** | `_Global_Profile.md`, `_Standards.md` | every session, every project | who you are, your rules, coding standards, commit policy |
|
|
53
|
+
| **Project** | `_projects/<repo>.md` | sessions in that repo | purpose, architecture, constraints, active decisions |
|
|
54
|
+
| **Session** | `_session_logs/<repo>/<timestamp>.md` | next session as carryover | what changed, blockers, next steps |
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
$ claude-start
|
|
58
|
+
│
|
|
59
|
+
├─ resolve project from the current git repo
|
|
60
|
+
├─ create a fresh session log from the template
|
|
61
|
+
├─ gather: Global Profile + Standards (who you are)
|
|
62
|
+
│ Project note (this repo)
|
|
63
|
+
│ latest prior session log (where you left off)
|
|
64
|
+
├─ ask which session skills to enable (optional)
|
|
65
|
+
└─ launch the agent with all of it as the first prompt
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
…you work…
|
|
69
|
+
│
|
|
70
|
+
▼
|
|
71
|
+
on exit, a hook writes an auto session log
|
|
72
|
+
(branch, commits made, uncommitted changes)
|
|
73
|
+
→ the NEXT session inherits it as carryover
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
One environment variable, `AI_MEM_ROOT`, points at the vault, so the system moves
|
|
77
|
+
between machines by pointing at the same folder.
|
|
78
|
+
|
|
79
|
+
## A session, start to finish
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
$ cd ~/code/checkout-api
|
|
83
|
+
$ claude-start
|
|
84
|
+
Use terse output this session? [y/N] n
|
|
85
|
+
|
|
86
|
+
# Claude launches pre-loaded with:
|
|
87
|
+
# • your profile + coding standards + commit policy (global)
|
|
88
|
+
# • checkout-api: purpose, architecture, decisions (project)
|
|
89
|
+
# • "Next: wire the refund webhook" (last session's carryover)
|
|
90
|
+
|
|
91
|
+
… you build the refund webhook, make a few commits …
|
|
92
|
+
|
|
93
|
+
$ ai-note "refund webhook live; still need idempotency keys" # jot mid-session
|
|
94
|
+
|
|
95
|
+
# On exit, a hook stamps the session log with the branch, the commits you made,
|
|
96
|
+
# and anything uncommitted. Tomorrow's claude-start picks up exactly there.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
No copy-pasting context. No re-explaining the stack. No "where were we."
|
|
100
|
+
|
|
101
|
+
## Quickstart
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npm create ai-memory@latest # copies the tool in and runs the setup, no git clone
|
|
105
|
+
exec zsh
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then, from inside any git repo:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
claude-start # or codex-start / gemini-start / cursor-start / opencode-start
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The agent opens already knowing your standards, this project, and where you left
|
|
115
|
+
off last time.
|
|
116
|
+
|
|
117
|
+
## Table of contents
|
|
118
|
+
|
|
119
|
+
<table>
|
|
120
|
+
<tr>
|
|
121
|
+
<td valign="top" width="33%">
|
|
122
|
+
|
|
123
|
+
**Overview**
|
|
124
|
+
|
|
125
|
+
- [The problem](#the-problem)
|
|
126
|
+
- [How it works](#how-it-works)
|
|
127
|
+
- [A session, start to finish](#a-session-start-to-finish)
|
|
128
|
+
|
|
129
|
+
**Getting started**
|
|
130
|
+
|
|
131
|
+
- [Quickstart](#quickstart)
|
|
132
|
+
- [Install](#install)
|
|
133
|
+
- [Commands](#commands)
|
|
134
|
+
|
|
135
|
+
</td>
|
|
136
|
+
<td valign="top" width="33%">
|
|
137
|
+
|
|
138
|
+
**Reference**
|
|
139
|
+
|
|
140
|
+
- [Session skills](#session-skills-optional)
|
|
141
|
+
- [Vault layout](#vault-layout)
|
|
142
|
+
- [Adding a new agent](#adding-a-new-agent)
|
|
143
|
+
- [Optional integrations](#optional-integrations)
|
|
144
|
+
- [Configuration](#configuration)
|
|
145
|
+
|
|
146
|
+
</td>
|
|
147
|
+
<td valign="top" width="33%">
|
|
148
|
+
|
|
149
|
+
**Project**
|
|
150
|
+
|
|
151
|
+
- [Tests](#tests)
|
|
152
|
+
- [Design notes](#design-notes)
|
|
153
|
+
- [Roadmap](#roadmap)
|
|
154
|
+
- [License](#license)
|
|
155
|
+
|
|
156
|
+
</td>
|
|
157
|
+
</tr>
|
|
158
|
+
</table>
|
|
159
|
+
|
|
160
|
+
## Install
|
|
161
|
+
|
|
162
|
+
Pick whichever fits how you manage your shell. All paths end at the same place.
|
|
163
|
+
|
|
164
|
+
**npm** (no git clone; the tool is bundled in the package):
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
npm create ai-memory@latest # into ~/ai-memory, then runs the setup
|
|
168
|
+
npx create-ai-memory ~/code/ai-memory # or a directory you choose
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**zsh plugin manager:**
|
|
172
|
+
|
|
173
|
+
```zsh
|
|
174
|
+
# zinit
|
|
175
|
+
zinit light rambaarde/create-ai-memory
|
|
176
|
+
|
|
177
|
+
# antidote (in your plugins file)
|
|
178
|
+
rambaarde/create-ai-memory
|
|
179
|
+
|
|
180
|
+
# oh-my-zsh: clone into custom/plugins, then add create-ai-memory to plugins=(...)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Plugin-manager installs only source the module. That's fine: the vault
|
|
184
|
+
auto-scaffolds from the shipped templates on first use, so `install.sh` is
|
|
185
|
+
optional. Set `AI_MEM_ROOT` in `~/.zshrc` first if you don't want the default
|
|
186
|
+
`~/.ai-memory/_Ai_Memory`.
|
|
187
|
+
|
|
188
|
+
**Clone and run** (the source of truth all paths reuse):
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
git clone https://github.com/rambaarde/create-ai-memory.git ~/ai-memory
|
|
192
|
+
~/ai-memory/install.sh
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
> **zsh only.** The module uses `print -r`, `${(s:|:)}`, and `select`. A bash
|
|
196
|
+
> port is welcome as a PR; see [Roadmap](#roadmap).
|
|
197
|
+
|
|
198
|
+
## Commands
|
|
199
|
+
|
|
200
|
+
| Command | What it does |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `claude-start` · `codex-start` · `gemini-start` · `cursor-start` · `opencode-start` | Launch an agent with full vault context and the session-skill picker |
|
|
203
|
+
| `ai-start [project]` | Prepare the session (project note and fresh log) without launching an agent |
|
|
204
|
+
| `ai-context [project]` | Print the vault context block for the current repo, and arm the git commit guard |
|
|
205
|
+
| `ai-note <text>` | Append a timestamped note to today's session log while you work |
|
|
206
|
+
|
|
207
|
+
Project is auto-resolved from the current git repo; pass a name to override.
|
|
208
|
+
|
|
209
|
+
## Session skills (optional)
|
|
210
|
+
|
|
211
|
+
You define your own per-session skills; ai-memory ships none. At launch it asks
|
|
212
|
+
y/n for each skill you registered, then injects the chosen instruction blocks into
|
|
213
|
+
the agent (and for Cursor, writes them as a managed always-apply rule). Register
|
|
214
|
+
nothing and every session is plain.
|
|
215
|
+
|
|
216
|
+
Add skills in `~/.zshrc` before sourcing the module. Each entry maps a `key` to
|
|
217
|
+
`prompt::instruction block`, and `AI_MEM_SKILL_ORDER` sets the ask order:
|
|
218
|
+
|
|
219
|
+
```zsh
|
|
220
|
+
typeset -gA AI_MEM_SKILLS
|
|
221
|
+
AI_MEM_SKILLS[terse]='Use terse output this session?::Respond tersely; drop filler and hedging.'
|
|
222
|
+
AI_MEM_SKILLS[design]='Use strict UI design discipline?::Apply careful frontend/UI design review to any design work.'
|
|
223
|
+
AI_MEM_SKILL_ORDER=(terse design)
|
|
224
|
+
|
|
225
|
+
source "$HOME/ai-memory/shell/ai-mem.zsh"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The injected block applies for the whole run, so a session's chosen skills persist
|
|
229
|
+
as instructions across every change the agent makes.
|
|
230
|
+
|
|
231
|
+
## Vault layout
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
$AI_MEM_ROOT/
|
|
235
|
+
_Global_Profile.md your cross-project rules (injected every session)
|
|
236
|
+
_Standards.md extra shared standards (injected every session)
|
|
237
|
+
_projects/
|
|
238
|
+
_project_template.md scaffold for new project notes
|
|
239
|
+
<repo>.md per-project durable context
|
|
240
|
+
_session_logs/
|
|
241
|
+
_session_template.md scaffold for new session logs
|
|
242
|
+
<repo>/
|
|
243
|
+
<repo>-<timestamp>.md one file per session
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Notes are created from templates on first use and never overwritten. Edit
|
|
247
|
+
`_Global_Profile.md` and `_Standards.md` to make them yours; the shipped versions
|
|
248
|
+
are sanitized placeholders.
|
|
249
|
+
|
|
250
|
+
## Adding a new agent
|
|
251
|
+
|
|
252
|
+
Launchers aren't hardcoded. Each agent is one small adapter, and the
|
|
253
|
+
`<name>-start` function is generated for you. `claude`, `codex`, `gemini`,
|
|
254
|
+
`cursor`, and `opencode` ship built in. To add another, say `aider`:
|
|
255
|
+
|
|
256
|
+
1. Define the adapter in `shell/adapters.zsh`. It receives `$1` memory prompt,
|
|
257
|
+
`$2` mode block, and `$3` onward extra args:
|
|
258
|
+
```zsh
|
|
259
|
+
_ai_adapter_aider() {
|
|
260
|
+
local memory_prompt="$1"
|
|
261
|
+
aider --message "$memory_prompt"
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
2. Register it in `~/.zshrc` before sourcing, or edit the default:
|
|
265
|
+
```zsh
|
|
266
|
+
export AI_MEM_AGENTS="claude codex gemini cursor opencode aider"
|
|
267
|
+
```
|
|
268
|
+
3. `aider-start` now exists. No core edits.
|
|
269
|
+
|
|
270
|
+
## Optional integrations
|
|
271
|
+
|
|
272
|
+
### Claude Code hooks
|
|
273
|
+
The files live in `hooks/claude/`. Record repo `HEAD` at session start, then on
|
|
274
|
+
exit write an auto block to the log with the branch, commits made this session, and
|
|
275
|
+
uncommitted changes, so the next session has real carryover instead of an empty
|
|
276
|
+
template. Merge `settings.snippet.json` into `~/.claude/settings.json`, replacing
|
|
277
|
+
`<AI_MEM_HOME>` with an absolute path. Both hooks no-op for plain `claude` runs;
|
|
278
|
+
they gate on `$AI_MEM_ACTIVE_SESSION_LOG`.
|
|
279
|
+
|
|
280
|
+
### Git commit guard
|
|
281
|
+
The files live in `hooks/git/`:
|
|
282
|
+
|
|
283
|
+
- **`commit-msg`** requires Conventional Commits and a structured body, and that
|
|
284
|
+
`ai-context` was loaded in the committing shell, matching a per-repo token. An
|
|
285
|
+
agent can't commit without the vault context loaded.
|
|
286
|
+
- **`pre-push`** blocks direct pushes to `main` unless `ALLOW_PUSH_TO_MAIN=1`.
|
|
287
|
+
|
|
288
|
+
Enable per repo:
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
cp ~/ai-memory/hooks/git/* <repo>/.githooks/
|
|
292
|
+
git -C <repo> config core.hooksPath .githooks
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Configuration
|
|
296
|
+
|
|
297
|
+
| Env var | Default | Purpose |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `AI_MEM_ROOT` | `$HOME/.ai-memory/_Ai_Memory` | Vault root. Point at any folder, including an existing Obsidian vault |
|
|
300
|
+
| `AI_MEM_AGENTS` | `claude codex gemini cursor opencode` | Space-separated agents to generate `-start` functions for |
|
|
301
|
+
| `AI_MEM_SKILLS` / `AI_MEM_SKILL_ORDER` | empty | Your per-session skills (see above) |
|
|
302
|
+
|
|
303
|
+
## Tests
|
|
304
|
+
|
|
305
|
+
Offline unit suite (throwaway vault + git repo, no network): path guarding,
|
|
306
|
+
project resolution, session prep, context prompt, skill picker, launcher
|
|
307
|
+
generation, adapter dispatch, the commit token, and `ai-note`.
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
zsh tests/run.sh # offline unit tests
|
|
311
|
+
zsh tests/smoke.sh # live: launches each agent headlessly, checks it responds
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`smoke.sh` makes real API calls, so each CLI must be installed and authed
|
|
315
|
+
(opencode defaults to DeepSeek; set it up or pass
|
|
316
|
+
`AIMEM_SMOKE_OPENCODE_MODEL=provider/model`).
|
|
317
|
+
|
|
318
|
+
## Design notes
|
|
319
|
+
|
|
320
|
+
- **The vault is the source of truth; the chat is disposable.** Durable state lives
|
|
321
|
+
in Markdown, not in any agent's memory.
|
|
322
|
+
- **Path-guarded writes.** Every file operation is checked to stay inside
|
|
323
|
+
`$AI_MEM_ROOT`, so an agent can't write outside the memory boundary.
|
|
324
|
+
- **Project = git repo.** Resolution prefers the repo you're standing in, so
|
|
325
|
+
`cd`-ing between projects in one shell never pins the wrong project.
|
|
326
|
+
- **Additive by default.** Templates fill in; notes are never clobbered.
|
|
327
|
+
|
|
328
|
+
## Roadmap
|
|
329
|
+
|
|
330
|
+
- Bash port of the shell module.
|
|
331
|
+
- Publish `create-ai-memory` to npm.
|
|
332
|
+
- More agent adapters shipped by default: opencode, aider.
|
|
333
|
+
- Optional cross-project index and search over session logs.
|
|
334
|
+
|
|
335
|
+
PRs welcome. Persistent AI memory shouldn't be a personal hack; it should be
|
|
336
|
+
something the whole community can install.
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
MIT © Ram Christopher Baarde
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-ai-memory: set up ai-memory with no git clone.
|
|
4
|
+
*
|
|
5
|
+
* The tool's files are bundled inside this npm package, so this script just
|
|
6
|
+
* copies them into place and hands off to the shell installer, which owns the
|
|
7
|
+
* interactive setup (banner, prompts, vault scaffold, ~/.zshrc lines). No git,
|
|
8
|
+
* no network beyond the npm download itself.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npm create ai-memory@latest # into ~/ai-memory
|
|
12
|
+
* npx create-ai-memory ~/code/ai-memory # into a directory you choose
|
|
13
|
+
*/
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const { spawnSync } = require('node:child_process');
|
|
17
|
+
const { cpSync, existsSync, mkdirSync } = require('node:fs');
|
|
18
|
+
const { join, resolve } = require('node:path');
|
|
19
|
+
const { homedir } = require('node:os');
|
|
20
|
+
|
|
21
|
+
const pkgRoot = resolve(__dirname, '..'); // bundled tool
|
|
22
|
+
const dest = resolve(process.argv[2] || join(homedir(), 'ai-memory'));
|
|
23
|
+
|
|
24
|
+
// Files that make up the tool; copied verbatim from the package into dest.
|
|
25
|
+
const ITEMS = ['shell', 'hooks', 'vault-template', 'install.sh', 'create-ai-memory.plugin.zsh', 'LICENSE'];
|
|
26
|
+
|
|
27
|
+
console.log(`create-ai-memory: installing into ${dest}`);
|
|
28
|
+
mkdirSync(dest, { recursive: true });
|
|
29
|
+
for (const item of ITEMS) {
|
|
30
|
+
const src = join(pkgRoot, item);
|
|
31
|
+
if (existsSync(src)) cpSync(src, join(dest, item), { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Hand off to the shell installer for the interactive setup.
|
|
35
|
+
const r = spawnSync('sh', [join(dest, 'install.sh')], { stdio: 'inherit' });
|
|
36
|
+
if (r.error) {
|
|
37
|
+
console.error(`create-ai-memory: could not run install.sh: ${r.error.message}`);
|
|
38
|
+
console.error(`Files are in ${dest}; run sh ${join(dest, 'install.sh')} yourself.`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
process.exit(r.status ?? 0);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Entry point for zsh plugin managers (zinit, antidote, oh-my-zsh, antigen),
|
|
2
|
+
# which load a repo by sourcing a root-level *.plugin.zsh. It just sources the
|
|
3
|
+
# real module, resolved relative to this file so it works wherever the manager
|
|
4
|
+
# clones the repo.
|
|
5
|
+
source "${0:A:h}/shell/ai-mem.zsh"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code SessionStart hook for the AI-memory vault.
|
|
3
|
+
#
|
|
4
|
+
# Records the repo HEAD at session start so the matching summary hook can show
|
|
5
|
+
# exactly which commits this session produced. No-ops unless the session was
|
|
6
|
+
# launched via claude-start (which exports AI_MEM_ACTIVE_SESSION_LOG), so plain
|
|
7
|
+
# `claude` runs are untouched.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
log="${AI_MEM_ACTIVE_SESSION_LOG:-}"
|
|
11
|
+
[ -n "$log" ] || exit 0
|
|
12
|
+
|
|
13
|
+
sha="$(git rev-parse HEAD 2>/dev/null || true)"
|
|
14
|
+
[ -n "$sha" ] || exit 0
|
|
15
|
+
|
|
16
|
+
printf '%s\n' "$sha" > "${log%.md}.startsha"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code Stop hook for the AI-memory vault.
|
|
3
|
+
#
|
|
4
|
+
# Idempotently writes a concrete "Auto Session Log" block (branch, commits made
|
|
5
|
+
# this session, and uncommitted changes) into the active session log, so the
|
|
6
|
+
# NEXT session has real carryover instead of an empty template. Runs on every
|
|
7
|
+
# Stop and rewrites a single block, so the log always reflects the latest state
|
|
8
|
+
# even if the session is later killed abruptly.
|
|
9
|
+
#
|
|
10
|
+
# No-ops unless the session was launched via claude-start (which exports
|
|
11
|
+
# AI_MEM_ACTIVE_SESSION_LOG).
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
log="${AI_MEM_ACTIVE_SESSION_LOG:-}"
|
|
15
|
+
[ -n "$log" ] || exit 0
|
|
16
|
+
[ -f "$log" ] || exit 0
|
|
17
|
+
|
|
18
|
+
marker="## Auto Session Log"
|
|
19
|
+
|
|
20
|
+
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '(no git)')"
|
|
21
|
+
repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
22
|
+
|
|
23
|
+
start_sha=""
|
|
24
|
+
[ -f "${log%.md}.startsha" ] && start_sha="$(cat "${log%.md}.startsha" 2>/dev/null || true)"
|
|
25
|
+
|
|
26
|
+
commits=""
|
|
27
|
+
if [ -n "$start_sha" ]; then
|
|
28
|
+
commits="$(git log --no-merges --format='- %h %s' "${start_sha}..HEAD" 2>/dev/null || true)"
|
|
29
|
+
fi
|
|
30
|
+
[ -n "$commits" ] || commits="- (no commits this session)"
|
|
31
|
+
|
|
32
|
+
changes="$(git status --short 2>/dev/null | sed 's/^/- /' || true)"
|
|
33
|
+
[ -n "$changes" ] || changes="- (working tree clean)"
|
|
34
|
+
|
|
35
|
+
stamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
36
|
+
|
|
37
|
+
block="$(cat <<EOF
|
|
38
|
+
${marker}
|
|
39
|
+
_Auto-generated ${stamp}. Edit the Session Outcome section above for durable notes._
|
|
40
|
+
|
|
41
|
+
* **Repo:** ${repo:-?}
|
|
42
|
+
* **Branch:** ${branch}
|
|
43
|
+
* **Commits this session:**
|
|
44
|
+
${commits}
|
|
45
|
+
* **Uncommitted changes at last checkpoint:**
|
|
46
|
+
${changes}
|
|
47
|
+
EOF
|
|
48
|
+
)"
|
|
49
|
+
|
|
50
|
+
# Strip any previous auto block (from marker to EOF), then append the fresh one.
|
|
51
|
+
tmp="$(mktemp)"
|
|
52
|
+
awk -v m="$marker" 'index($0, m)==1 { exit } { print }' "$log" > "$tmp"
|
|
53
|
+
printf '%s\n\n%s\n' "$(cat "$tmp")" "$block" > "$log"
|
|
54
|
+
rm -f "$tmp"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Merge into ~/.claude/settings.json. Replace <AI_MEM_HOME> with the absolute path to your ai-mem checkout (Claude Code does not expand env vars in hook commands). Both hooks no-op unless the session was launched via claude-start.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{ "type": "command", "command": "<AI_MEM_HOME>/hooks/claude/session-start.sh" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"Stop": [
|
|
12
|
+
{
|
|
13
|
+
"hooks": [
|
|
14
|
+
{ "type": "command", "command": "<AI_MEM_HOME>/hooks/claude/session-summary.sh" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Enforce Conventional Commits with a detailed body and an active vault-context
|
|
3
|
+
# token for this repository.
|
|
4
|
+
# Git-generated merge, revert, fixup, and squash commits are allowed so common
|
|
5
|
+
# history maintenance workflows still work without manual overrides.
|
|
6
|
+
set -eu
|
|
7
|
+
|
|
8
|
+
commit_msg_file=${1:-}
|
|
9
|
+
|
|
10
|
+
if [ -z "$commit_msg_file" ] || [ ! -f "$commit_msg_file" ]; then
|
|
11
|
+
echo "commit-msg: expected a commit message file path" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
first_subject=''
|
|
16
|
+
body_line_count=0
|
|
17
|
+
body_word_count=0
|
|
18
|
+
state=need_separator
|
|
19
|
+
subject_skipped=0
|
|
20
|
+
|
|
21
|
+
is_footer_line() {
|
|
22
|
+
case $1 in
|
|
23
|
+
BREAKING\ CHANGE:*|Signed-off-by:*|Co-authored-by:*|Reviewed-by:*|Acked-by:*|Refs\ *|Fixes\ *|Closes\ *|Resolves\ *)
|
|
24
|
+
return 0
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
require_vault_context() {
|
|
32
|
+
local repo_root project_name ready_dir ready_file ready_token
|
|
33
|
+
|
|
34
|
+
if [ -z "${AI_MEM_ROOT:-}" ]; then
|
|
35
|
+
echo "commit-msg: AI_MEM_ROOT is not set; run ai-context first" >&2
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
|
|
40
|
+
project_name="$(basename "$repo_root")"
|
|
41
|
+
ready_dir="$AI_MEM_ROOT/_session_logs/.context-ready"
|
|
42
|
+
ready_file="$ready_dir/$project_name.token"
|
|
43
|
+
|
|
44
|
+
if [ ! -f "$ready_file" ]; then
|
|
45
|
+
echo "commit-msg: load the vault context first with ai-context" >&2
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ -z "${AI_MEM_CONTEXT_TOKEN:-}" ]; then
|
|
50
|
+
echo "commit-msg: this shell has not loaded the vault context; run ai-context first" >&2
|
|
51
|
+
return 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
ready_token="$(sed -n '1p' "$ready_file")"
|
|
55
|
+
|
|
56
|
+
if [ "$ready_token" != "$AI_MEM_CONTEXT_TOKEN" ]; then
|
|
57
|
+
echo "commit-msg: this shell's vault context token does not match the active session; rerun ai-context" >&2
|
|
58
|
+
return 1
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
63
|
+
case $line in
|
|
64
|
+
'')
|
|
65
|
+
continue
|
|
66
|
+
;;
|
|
67
|
+
'#'*)
|
|
68
|
+
continue
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
first_subject=$line
|
|
72
|
+
break
|
|
73
|
+
done < "$commit_msg_file"
|
|
74
|
+
|
|
75
|
+
if [ -z "$first_subject" ]; then
|
|
76
|
+
echo "commit-msg: commit message is empty" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
case $first_subject in
|
|
81
|
+
Merge\ *|Revert\ *|fixup!\ *|squash!\ *)
|
|
82
|
+
exit 0
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
|
|
86
|
+
if printf '%s\n' "$first_subject" | grep -Eq '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([^()[:space:]][^()[:space:]]*\))?(!)?: [^[:space:]].*$'; then
|
|
87
|
+
:
|
|
88
|
+
else
|
|
89
|
+
cat >&2 <<'EOF'
|
|
90
|
+
Commit message rejected.
|
|
91
|
+
|
|
92
|
+
Use Conventional Commits:
|
|
93
|
+
type(scope): description
|
|
94
|
+
|
|
95
|
+
Use a structured body after a blank line:
|
|
96
|
+
- short summary paragraph
|
|
97
|
+
- optional Why / Cause paragraph
|
|
98
|
+
- `-` bullet concrete changes or defenses
|
|
99
|
+
- optional notes or follow-up
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
feat(tmux): add session color sync
|
|
103
|
+
fix(zsh): avoid duplicate prompt initialization
|
|
104
|
+
docs(memory): clarify commit policy
|
|
105
|
+
|
|
106
|
+
Allowed types:
|
|
107
|
+
feat, fix, docs, style, refactor, perf, test, chore, build, ci, revert
|
|
108
|
+
|
|
109
|
+
Git-generated Merge, Revert, fixup!, and squash! messages are allowed.
|
|
110
|
+
EOF
|
|
111
|
+
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
116
|
+
case $line in
|
|
117
|
+
'#'*)
|
|
118
|
+
continue
|
|
119
|
+
;;
|
|
120
|
+
esac
|
|
121
|
+
|
|
122
|
+
if [ "$subject_skipped" -eq 0 ]; then
|
|
123
|
+
case $line in
|
|
124
|
+
'')
|
|
125
|
+
continue
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
subject_skipped=1
|
|
129
|
+
continue
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [ "$state" = need_separator ]; then
|
|
133
|
+
if [ -z "$line" ]; then
|
|
134
|
+
state=body
|
|
135
|
+
continue
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
echo "commit-msg: add a blank line after the subject, then a detailed body" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if [ "$state" = footer ]; then
|
|
143
|
+
continue
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if [ -z "$line" ]; then
|
|
147
|
+
continue
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if is_footer_line "$line"; then
|
|
151
|
+
if [ "$body_line_count" -eq 0 ]; then
|
|
152
|
+
echo "commit-msg: add a detailed body before footers" >&2
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
state=footer
|
|
156
|
+
continue
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
body_line_count=$((body_line_count + 1))
|
|
160
|
+
words=$(printf '%s\n' "$line" | awk '{print NF}')
|
|
161
|
+
body_word_count=$((body_word_count + words))
|
|
162
|
+
done < "$commit_msg_file"
|
|
163
|
+
|
|
164
|
+
if [ "$body_line_count" -eq 0 ]; then
|
|
165
|
+
echo "commit-msg: commit message needs a structured body, not just a title" >&2
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [ "$body_line_count" -lt 2 ] && [ "$body_word_count" -lt 20 ]; then
|
|
170
|
+
echo "commit-msg: body is too short; add a summary and - bullet changes or defenses" >&2
|
|
171
|
+
exit 1
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if ! require_vault_context; then
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Block pushes that target the production branch unless an explicit override is
|
|
3
|
+
# set for an emergency release.
|
|
4
|
+
set -eu
|
|
5
|
+
|
|
6
|
+
allow_main_push=${ALLOW_PUSH_TO_MAIN:-}
|
|
7
|
+
|
|
8
|
+
while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
|
|
9
|
+
case $remote_ref in
|
|
10
|
+
refs/heads/main)
|
|
11
|
+
if [ "$allow_main_push" != "1" ]; then
|
|
12
|
+
echo "pre-push: push to main blocked; use a topic branch or set ALLOW_PUSH_TO_MAIN=1 for an explicit override" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
;;
|
|
16
|
+
esac
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
exit 0
|