claude-compass 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # claude-compass
2
+
3
+ Automatic `CLAUDE.md` lifecycle management for [Claude Code](https://claude.ai/code).
4
+
5
+ Installs hooks and an agent into your project so that `CLAUDE.md` stays accurate as your codebase evolves — silently, without interrupting your workflow.
6
+
7
+ ## How it works
8
+
9
+ ```
10
+ You write code in Claude Code
11
+
12
+ PostToolUse hook fires after significant file writes
13
+
14
+ One-line note appended to .claude/pending-updates.md
15
+ (pure JS, zero LLM tokens)
16
+
17
+ Session ends → SessionEnd hook prints a summary if 3+ changes logged
18
+
19
+ You type "update CLAUDE.md" in chat (or run claude-compass update)
20
+
21
+ claude-md-updater agent reads pending-updates.md + CLAUDE.md
22
+ Makes surgical updates, clears the log
23
+ Prints: "CLAUDE.md updated — 2 entries added, 1 updated, 9 skipped"
24
+ ```
25
+
26
+ ## Why the update step is intentional
27
+
28
+ The two parts of claude-compass are split deliberately:
29
+
30
+ - **Logging is fully automatic.** Every file write is captured by the hook with zero effort and zero cost. You never think about it.
31
+ - **Updating is manual.** You decide when to sync — typically at a natural stopping point, once a day, or when finishing a feature.
32
+
33
+ This split matters because most file edits don't change anything worth documenting. Running the agent after every session would spend tokens deciding "nothing changed" the majority of the time. Triggering it yourself costs about the same tokens but only when it actually has something useful to do.
34
+
35
+ ## Token usage
36
+
37
+ | Part | Tokens | Frequency |
38
+ |---|---|---|
39
+ | PostToolUse hook | **0** | Every file write |
40
+ | SessionEnd hook | **0** | Every session end |
41
+ | `claude-md-updater` agent | ~2,000–4,000 | Only when you trigger it |
42
+
43
+ At current API rates, one full sync costs less than **$0.01**. Running it once a day for a year is roughly $2–3 total.
44
+
45
+ ## Requirements
46
+
47
+ - [Claude Code](https://claude.ai/code) CLI
48
+ - Node.js 18+
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ npm install -g claude-compass
54
+ ```
55
+
56
+ ## Setup
57
+
58
+ Run once per project, from the project root:
59
+
60
+ ```bash
61
+ claude-compass init
62
+ ```
63
+
64
+ This will:
65
+ - Create `.claude/pending-updates.md` and `.claude/context/`
66
+ - Copy hooks into `.claude/hooks/`
67
+ - Register hooks in `~/.claude/settings.json`
68
+ - Install the `claude-md-updater` agent into `.claude/agents/`
69
+ - Create a `CLAUDE.md` stub if one doesn't exist (backs up any existing one first)
70
+
71
+ ## Commands
72
+
73
+ ### `claude-compass init`
74
+ First-time setup. Safe to re-run — won't overwrite an existing `CLAUDE.md` without backing it up first.
75
+
76
+ ### `claude-compass status`
77
+ Shows the current state of your project context.
78
+
79
+ ```
80
+ ✦ claude-compass status
81
+
82
+ Project: my-project
83
+ CLAUDE.md last updated 2 days ago (Apr 1)
84
+ Pending updates: 4 changes waiting
85
+ Last session: logged changes (today 3:45 PM)
86
+ Agent: claude-md-updater ✓ installed
87
+ Hooks: post-tool-use ✓ session-end ✓
88
+ ```
89
+
90
+ ### `claude-compass update`
91
+ Tells you how to trigger the `claude-md-updater` agent from inside Claude Code. The actual update is done by the agent (requires a Claude Code session).
92
+
93
+ ### `claude-compass reset`
94
+ Clears `pending-updates.md` without updating `CLAUDE.md`. Asks for confirmation. Use `-y` to skip.
95
+
96
+ ```bash
97
+ claude-compass reset -y
98
+ ```
99
+
100
+ ## File structure after init
101
+
102
+ ```
103
+ your-project/
104
+ ├── CLAUDE.md ← stable project context
105
+ └── .claude/
106
+ ├── pending-updates.md ← append-only change log (auto-managed)
107
+ ├── agents/
108
+ │ └── claude-md-updater.md ← the updater agent
109
+ ├── hooks/
110
+ │ ├── post-tool-use.js ← logs significant file writes
111
+ │ └── session-end.js ← prints session summary
112
+ └── context/ ← optional reference files
113
+ ```
114
+
115
+ ## What gets logged
116
+
117
+ The `post-tool-use` hook fires after every `Write`, `Edit`, or `MultiEdit` tool call in Claude Code. It ignores:
118
+
119
+ - `node_modules/`, `.git/`, `dist/`, `build/`, `.next/`
120
+ - Lockfiles (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, etc.)
121
+
122
+ Everything else gets a one-line entry in `pending-updates.md`.
123
+
124
+ ## Updating CLAUDE.md
125
+
126
+ When the session-end hook reminds you there are pending changes, either:
127
+
128
+ 1. Type `update CLAUDE.md` in the Claude Code chat, or
129
+ 2. Run `claude-compass update` in the terminal — it will show you exactly what to type
130
+
131
+ The `claude-md-updater` agent reads the pending log, decides what's significant enough to document, makes surgical edits to `CLAUDE.md`, and clears the log.
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const [,, command, ...args] = process.argv;
5
+
6
+ const commands = {
7
+ init: () => require('../src/init.js').run(args),
8
+ status: () => require('../src/status.js').run(args),
9
+ update: () => require('../src/update.js').run(args),
10
+ reset: () => require('../src/reset.js').run(args),
11
+ };
12
+
13
+ if (!command || command === '--help' || command === '-h') {
14
+ printHelp();
15
+ process.exit(0);
16
+ }
17
+
18
+ if (command === '--version' || command === '-v') {
19
+ const pkg = require('../package.json');
20
+ console.log(pkg.version);
21
+ process.exit(0);
22
+ }
23
+
24
+ if (!commands[command]) {
25
+ console.error(`claude-compass: unknown command '${command}'`);
26
+ console.error(`Run 'claude-compass --help' for usage.`);
27
+ process.exit(1);
28
+ }
29
+
30
+ commands[command]().catch(err => {
31
+ console.error(`claude-compass: ${err.message}`);
32
+ process.exit(1);
33
+ });
34
+
35
+ function printHelp() {
36
+ console.log(`claude-compass — CLAUDE.md lifecycle management for Claude Code
37
+
38
+ Usage:
39
+ claude-compass init First-time setup. Creates structure, installs hooks and agent.
40
+ claude-compass status Show current state of pending updates and hooks.
41
+ claude-compass update Trigger the claude-md-updater agent to sync CLAUDE.md.
42
+ claude-compass reset Clear pending-updates.md without updating CLAUDE.md.
43
+
44
+ Options:
45
+ -h, --help Show this help message
46
+ -v, --version Show version number
47
+ `);
48
+ }
package/package.json CHANGED
@@ -1 +1,25 @@
1
- {"name":"claude-compass","version":"0.0.1"}
1
+ {
2
+ "name": "claude-compass",
3
+ "version": "0.1.1",
4
+ "description": "Automatic CLAUDE.md lifecycle management for Claude Code",
5
+ "bin": {
6
+ "claude-compass": "bin/claude-compass.js"
7
+ },
8
+ "keywords": [
9
+ "claude",
10
+ "claude-code",
11
+ "anthropic",
12
+ "ai",
13
+ "developer-tools"
14
+ ],
15
+ "author": "codemode001",
16
+ "license": "MIT",
17
+ "engines": {
18
+ "node": ">=18.0.0"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "src",
23
+ "templates"
24
+ ]
25
+ }
package/src/init.js ADDED
@@ -0,0 +1,161 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+
8
+ const PKG_DIR = path.join(__dirname, '..');
9
+
10
+ async function run() {
11
+ const cwd = process.cwd();
12
+ const claudeDir = path.join(cwd, '.claude');
13
+ const contextDir = path.join(claudeDir, 'context');
14
+ const pendingPath = path.join(claudeDir, 'pending-updates.md');
15
+ const agentsDir = path.join(claudeDir, 'agents');
16
+ const hooksDir = path.join(claudeDir, 'hooks');
17
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
18
+
19
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
20
+
21
+ // Warn if not a git repo
22
+ if (!fs.existsSync(path.join(cwd, '.git'))) {
23
+ console.warn(' Warning: current directory does not appear to be a git repository.');
24
+ console.warn(' claude-compass works best inside a git repo, but continuing anyway.\n');
25
+ }
26
+
27
+ // Backup existing CLAUDE.md
28
+ if (fs.existsSync(claudeMdPath)) {
29
+ const backupPath = claudeMdPath + '.backup';
30
+ fs.copyFileSync(claudeMdPath, backupPath);
31
+ console.log(` Backed up existing CLAUDE.md → CLAUDE.md.backup`);
32
+ }
33
+
34
+ // Create .claude directories
35
+ fs.mkdirSync(contextDir, { recursive: true });
36
+ fs.mkdirSync(agentsDir, { recursive: true });
37
+ fs.mkdirSync(hooksDir, { recursive: true });
38
+
39
+ // Create pending-updates.md if missing
40
+ if (!fs.existsSync(pendingPath)) {
41
+ fs.writeFileSync(pendingPath, '', 'utf8');
42
+ }
43
+
44
+ // Install project-level hooks (copies of templates)
45
+ const hookSrc = path.join(PKG_DIR, 'templates', 'hooks');
46
+ installHook(path.join(hookSrc, 'post-tool-use.js'), path.join(hooksDir, 'post-tool-use.js'));
47
+ installHook(path.join(hookSrc, 'session-end.js'), path.join(hooksDir, 'session-end.js'));
48
+
49
+ // Register hooks in ~/.claude/settings.json
50
+ registerHooksInSettings(settingsPath, cwd);
51
+
52
+ // Install agent
53
+ const agentSrc = path.join(PKG_DIR, 'templates', 'agents', 'claude-md-updater.md');
54
+ const agentDest = path.join(agentsDir, 'claude-md-updater.md');
55
+ fs.copyFileSync(agentSrc, agentDest);
56
+
57
+ // Create CLAUDE.md if missing
58
+ if (!fs.existsSync(claudeMdPath)) {
59
+ const stub = buildClaudeMdStub(cwd);
60
+ fs.writeFileSync(claudeMdPath, stub, 'utf8');
61
+ }
62
+
63
+ // Print confirmation
64
+ console.log(`\n✦ claude-compass initialized\n`);
65
+ console.log(` CLAUDE.md ✓ ready`);
66
+ console.log(` pending-updates ✓ ready`);
67
+ console.log(` hooks ✓ post-tool-use, session-end registered`);
68
+ console.log(` agent ✓ claude-md-updater installed`);
69
+ console.log(`\n Changes will be logged silently during sessions.`);
70
+ console.log(` Run: claude-compass status to check anytime.\n`);
71
+ }
72
+
73
+ function installHook(src, dest) {
74
+ fs.copyFileSync(src, dest);
75
+ fs.chmodSync(dest, 0o755);
76
+ }
77
+
78
+ function registerHooksInSettings(settingsPath, projectCwd) {
79
+ // Ensure ~/.claude directory exists
80
+ const claudeGlobal = path.join(os.homedir(), '.claude');
81
+ fs.mkdirSync(claudeGlobal, { recursive: true });
82
+
83
+ let settings = {};
84
+ if (fs.existsSync(settingsPath)) {
85
+ try {
86
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
87
+ } catch {
88
+ settings = {};
89
+ }
90
+ }
91
+
92
+ if (!settings.hooks) settings.hooks = {};
93
+
94
+ const postToolUsePath = path.join(projectCwd, '.claude', 'hooks', 'post-tool-use.js');
95
+ const sessionEndPath = path.join(projectCwd, '.claude', 'hooks', 'session-end.js');
96
+
97
+ // PostToolUse hook — correct Claude Code format: { matcher, hooks: [{ type, command }] }
98
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
99
+ const postHookCmd = `node "${postToolUsePath}"`;
100
+ const hasPost = settings.hooks.PostToolUse.some(entry =>
101
+ Array.isArray(entry.hooks) && entry.hooks.some(h => h.command === postHookCmd)
102
+ );
103
+ if (!hasPost) {
104
+ settings.hooks.PostToolUse.push({
105
+ matcher: 'Write|Edit|MultiEdit',
106
+ hooks: [{ type: 'command', command: postHookCmd }],
107
+ });
108
+ }
109
+
110
+ // Stop hook (session end)
111
+ if (!settings.hooks.Stop) settings.hooks.Stop = [];
112
+ const stopHookCmd = `node "${sessionEndPath}"`;
113
+ const hasStop = settings.hooks.Stop.some(entry =>
114
+ Array.isArray(entry.hooks) && entry.hooks.some(h => h.command === stopHookCmd)
115
+ );
116
+ if (!hasStop) {
117
+ settings.hooks.Stop.push({
118
+ matcher: '',
119
+ hooks: [{ type: 'command', command: stopHookCmd }],
120
+ });
121
+ }
122
+
123
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
124
+ }
125
+
126
+ function buildClaudeMdStub(cwd) {
127
+ const projectName = path.basename(cwd);
128
+ const date = new Date().toISOString().slice(0, 10);
129
+ return `# ${projectName}
130
+
131
+ ## Purpose
132
+
133
+ <!-- Describe what this project does in 1-2 sentences -->
134
+
135
+ ## Tech Stack
136
+
137
+ <!-- List main languages, frameworks, and key libraries -->
138
+
139
+ ## Architecture
140
+
141
+ <!-- Key architectural decisions and patterns -->
142
+
143
+ ## Coding Conventions
144
+
145
+ <!-- Naming conventions, patterns, things to know -->
146
+
147
+ ## Folder Structure
148
+
149
+ <!-- Brief overview of important directories -->
150
+
151
+ ## Critical Gotchas
152
+
153
+ <!-- Things that are easy to get wrong or that burned you before -->
154
+
155
+ ## Last Updated
156
+
157
+ ${date}
158
+ `;
159
+ }
160
+
161
+ module.exports = { run };
package/src/reset.js ADDED
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+
7
+ async function run(args) {
8
+ const cwd = process.cwd();
9
+ const pendingPath = path.join(cwd, '.claude', 'pending-updates.md');
10
+
11
+ if (!fs.existsSync(pendingPath)) {
12
+ console.error(`claude-compass: .claude/pending-updates.md not found.`);
13
+ console.error(`Run 'claude-compass init' first.`);
14
+ process.exit(1);
15
+ }
16
+
17
+ const content = fs.readFileSync(pendingPath, 'utf8').trim();
18
+ const lines = content ? content.split('\n').filter(l => l.trim()) : [];
19
+
20
+ if (lines.length === 0) {
21
+ console.log(`✦ claude-compass: pending-updates.md is already empty. Nothing to reset.`);
22
+ process.exit(0);
23
+ }
24
+
25
+ // --yes / -y skips confirmation
26
+ const skipConfirm = args.includes('--yes') || args.includes('-y');
27
+
28
+ if (!skipConfirm) {
29
+ console.log(`\n This will discard ${lines.length} pending change${lines.length === 1 ? '' : 's'} without updating CLAUDE.md.\n`);
30
+ const confirmed = await confirm(` Are you sure? [y/N] `);
31
+ if (!confirmed) {
32
+ console.log(` Aborted. Nothing was changed.\n`);
33
+ process.exit(0);
34
+ }
35
+ }
36
+
37
+ fs.writeFileSync(pendingPath, '', 'utf8');
38
+ console.log(`\n✦ claude-compass: pending-updates.md cleared (${lines.length} entr${lines.length === 1 ? 'y' : 'ies'} discarded).\n`);
39
+ }
40
+
41
+ function confirm(prompt) {
42
+ return new Promise(resolve => {
43
+ const rl = readline.createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout,
46
+ });
47
+ rl.question(prompt, answer => {
48
+ rl.close();
49
+ resolve(answer.trim().toLowerCase() === 'y');
50
+ });
51
+ });
52
+ }
53
+
54
+ module.exports = { run };
package/src/status.js ADDED
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ async function run() {
8
+ const cwd = process.cwd();
9
+ const projectName = path.basename(cwd);
10
+ const claudeDir = path.join(cwd, '.claude');
11
+ const pendingPath = path.join(claudeDir, 'pending-updates.md');
12
+ const agentPath = path.join(claudeDir, 'agents', 'claude-md-updater.md');
13
+ const postHookPath = path.join(claudeDir, 'hooks', 'post-tool-use.js');
14
+ const sessionHookPath = path.join(claudeDir, 'hooks', 'session-end.js');
15
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
16
+
17
+ console.log(`\n✦ claude-compass status\n`);
18
+ console.log(` Project: ${projectName}`);
19
+
20
+ // CLAUDE.md age
21
+ if (fs.existsSync(claudeMdPath)) {
22
+ const stat = fs.statSync(claudeMdPath);
23
+ const age = formatAge(stat.mtime);
24
+ console.log(` CLAUDE.md last updated ${age}`);
25
+ } else {
26
+ console.log(` CLAUDE.md ✗ not found — run claude-compass init`);
27
+ }
28
+
29
+ // Pending updates
30
+ if (fs.existsSync(pendingPath)) {
31
+ const content = fs.readFileSync(pendingPath, 'utf8').trim();
32
+ const lines = content ? content.split('\n').filter(l => l.trim()) : [];
33
+ if (lines.length === 0) {
34
+ console.log(` Pending updates: none`);
35
+ } else {
36
+ console.log(` Pending updates: ${lines.length} change${lines.length === 1 ? '' : 's'} waiting`);
37
+ }
38
+
39
+ // Last session: find last timestamp
40
+ if (lines.length > 0) {
41
+ const lastLine = lines[lines.length - 1];
42
+ const match = lastLine.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\]/);
43
+ if (match) {
44
+ const ts = new Date(match[1]);
45
+ const age = formatAge(ts);
46
+ console.log(` Last session: logged changes (${age})`);
47
+ }
48
+ }
49
+ } else {
50
+ console.log(` Pending updates: ✗ not found — run claude-compass init`);
51
+ }
52
+
53
+ // Agent
54
+ const agentInstalled = fs.existsSync(agentPath);
55
+ console.log(` Agent: claude-md-updater ${agentInstalled ? '✓ installed' : '✗ not found'}`);
56
+
57
+ // Hooks
58
+ const postOk = fs.existsSync(postHookPath);
59
+ const sessionOk = fs.existsSync(sessionHookPath);
60
+ const hooksStr = [
61
+ `post-tool-use ${postOk ? '✓' : '✗'}`,
62
+ `session-end ${sessionOk ? '✓' : '✗'}`,
63
+ ].join(' ');
64
+ console.log(` Hooks: ${hooksStr}`);
65
+
66
+ if (!postOk || !sessionOk || !agentInstalled) {
67
+ console.log(`\n Some components are missing. Run: claude-compass init`);
68
+ }
69
+
70
+ console.log('');
71
+ }
72
+
73
+ function formatAge(date) {
74
+ const now = Date.now();
75
+ const diff = now - date.getTime();
76
+ const minutes = Math.floor(diff / 60000);
77
+ const hours = Math.floor(diff / 3600000);
78
+ const days = Math.floor(diff / 86400000);
79
+
80
+ const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
81
+ const dateStr = date.toLocaleDateString([], { month: 'short', day: 'numeric' });
82
+
83
+ if (minutes < 1) return 'just now';
84
+ if (minutes < 60) return `${minutes} minute${minutes === 1 ? '' : 's'} ago (today ${timeStr})`;
85
+ if (hours < 24) return `${hours} hour${hours === 1 ? '' : 's'} ago (today ${timeStr})`;
86
+ if (days === 1) return `1 day ago (${dateStr})`;
87
+ return `${days} days ago (${dateStr})`;
88
+ }
89
+
90
+ module.exports = { run };
package/src/update.js ADDED
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ async function run() {
7
+ const cwd = process.cwd();
8
+ const pendingPath = path.join(cwd, '.claude', 'pending-updates.md');
9
+
10
+ if (!fs.existsSync(pendingPath)) {
11
+ console.error(`claude-compass: .claude/pending-updates.md not found.`);
12
+ console.error(`Run 'claude-compass init' first.`);
13
+ process.exit(1);
14
+ }
15
+
16
+ const content = fs.readFileSync(pendingPath, 'utf8').trim();
17
+ const lines = content ? content.split('\n').filter(l => l.trim()) : [];
18
+
19
+ if (lines.length === 0) {
20
+ console.log(`✦ claude-compass: nothing to update — pending-updates.md is empty`);
21
+ process.exit(0);
22
+ }
23
+
24
+ // When invoked as a CLI command from inside Claude Code, Claude Code itself
25
+ // will see this output and can invoke the agent. The update command just
26
+ // signals intent — the actual LLM work is done by the claude-md-updater agent.
27
+ console.log(`\n✦ claude-compass: ${lines.length} pending change${lines.length === 1 ? '' : 's'} found`);
28
+ console.log(`\n To sync CLAUDE.md, type the following in your Claude Code chat:`);
29
+ console.log(`\n update CLAUDE.md\n`);
30
+ console.log(` The claude-md-updater agent will read pending-updates.md and make`);
31
+ console.log(` surgical updates to CLAUDE.md, then clear the pending log.\n`);
32
+ }
33
+
34
+ module.exports = { run };
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: claude-md-updater
3
+ description: Use this agent when the user runs `claude-compass update`, explicitly asks to update CLAUDE.md, asks to sync project context, or says "update claude compass" / "sync claude compass". Also triggers when pending-updates.md has 10 or more entries. Does NOT trigger during normal coding work, file edits, debugging sessions, or any task that was not an explicit request to update CLAUDE.md.
4
+ ---
5
+
6
+ You are the claude-md-updater agent. Your only job is to make surgical, accurate updates to CLAUDE.md based on what actually changed during recent coding sessions.
7
+
8
+ ## Your inputs
9
+
10
+ 1. `.claude/pending-updates.md` — an append-only log of file writes that happened since the last update
11
+ 2. `CLAUDE.md` — the project context file you will update
12
+
13
+ ## Process
14
+
15
+ **Step 1 — Read both files in full.**
16
+
17
+ Read `.claude/pending-updates.md` and `CLAUDE.md`. If pending-updates.md is empty or missing, print:
18
+
19
+ ```
20
+ ✦ claude-compass: nothing to update — pending-updates.md is empty
21
+ ```
22
+
23
+ Then stop.
24
+
25
+ **Step 2 — Evaluate each pending entry.**
26
+
27
+ For each line in pending-updates.md, decide whether it represents a change significant enough to update CLAUDE.md. Apply these criteria strictly:
28
+
29
+ - New file in a key directory (new route, new service, new migration, new component) → **yes, update**
30
+ - Change to an existing file that's already documented in CLAUDE.md AND the change alters the documented behavior → **maybe, update only if the documented fact is now wrong**
31
+ - Lockfile, build output, config tweak, minor edit to an already-documented file → **no, skip**
32
+ - Changes to test files → **only if they reveal a new testing convention not yet documented**
33
+
34
+ When in doubt, skip. CLAUDE.md should contain stable facts, not a changelog.
35
+
36
+ **Step 3 — Make surgical edits to CLAUDE.md.**
37
+
38
+ - Add new lines where appropriate — under the relevant section heading
39
+ - Update existing lines only if the documented fact is now incorrect
40
+ - Never rewrite entire sections wholesale
41
+ - Never delete existing content unless it is demonstrably wrong
42
+ - Keep CLAUDE.md concise — it should be scannable in under 2 minutes
43
+ - Update the `## Last Updated` line at the bottom with today's date
44
+
45
+ **Step 4 — Clear pending-updates.md.**
46
+
47
+ Write an empty string to `.claude/pending-updates.md` (overwrite with empty content).
48
+
49
+ **Step 5 — Print a one-line summary.**
50
+
51
+ ```
52
+ ✦ CLAUDE.md updated — X entries added, Y updated, Z skipped
53
+ ```
54
+
55
+ ## Hard constraints
56
+
57
+ - Do not invent facts. Only document what is directly evidenced by the pending entries or already in CLAUDE.md.
58
+ - Do not add speculative architecture, planned features, or inferred conventions unless you have direct evidence.
59
+ - Do not add verbose explanations — CLAUDE.md entries should be 1-2 lines each.
60
+ - This agent must not trigger during normal coding. It only runs when explicitly invoked.
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // PostToolUse hook for claude-compass
5
+ // Fires after every Claude Code tool use.
6
+ // Appends one line to .claude/pending-updates.md for significant file writes.
7
+ // Zero LLM calls. Pure JavaScript.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ function main() {
13
+ let input = '';
14
+
15
+ process.stdin.setEncoding('utf8');
16
+ process.stdin.on('data', chunk => { input += chunk; });
17
+ process.stdin.on('end', () => {
18
+ try {
19
+ run(input.trim());
20
+ } catch (err) {
21
+ // Hooks must never crash Claude Code — exit silently
22
+ process.exit(0);
23
+ }
24
+ });
25
+ }
26
+
27
+ function run(rawInput) {
28
+ let event;
29
+ try {
30
+ event = JSON.parse(rawInput);
31
+ } catch {
32
+ process.exit(0);
33
+ }
34
+
35
+ const toolName = event?.tool_name || event?.tool || '';
36
+ const toolInput = event?.tool_input || event?.input || {};
37
+
38
+ // Only act on file-write tools
39
+ const writeTools = ['Write', 'Edit', 'MultiEdit'];
40
+ if (!writeTools.includes(toolName)) {
41
+ process.exit(0);
42
+ }
43
+
44
+ // Get the file path written
45
+ const filePath = toolInput?.file_path || toolInput?.path || '';
46
+ if (!filePath) {
47
+ process.exit(0);
48
+ }
49
+
50
+ // Ignore noisy paths
51
+ const ignoredSegments = [
52
+ 'node_modules/',
53
+ '.git/',
54
+ '/dist/',
55
+ '/build/',
56
+ '/.next/',
57
+ '/out/',
58
+ '/coverage/',
59
+ '/__pycache__/',
60
+ '/vendor/',
61
+ ];
62
+ const normalised = filePath.replace(/\\/g, '/');
63
+ if (ignoredSegments.some(seg => normalised.includes(seg))) {
64
+ process.exit(0);
65
+ }
66
+
67
+ // Ignore lockfiles
68
+ const lockfiles = [
69
+ 'package-lock.json',
70
+ 'yarn.lock',
71
+ 'pnpm-lock.yaml',
72
+ 'bun.lockb',
73
+ 'Gemfile.lock',
74
+ 'poetry.lock',
75
+ 'Pipfile.lock',
76
+ 'composer.lock',
77
+ 'go.sum',
78
+ ];
79
+ const basename = path.basename(filePath);
80
+ if (lockfiles.includes(basename)) {
81
+ process.exit(0);
82
+ }
83
+
84
+ // Build a brief description from context
85
+ const description = buildDescription(toolName, toolInput, filePath);
86
+
87
+ // Resolve pending-updates.md relative to cwd (project root)
88
+ const pendingPath = path.join(process.cwd(), '.claude', 'pending-updates.md');
89
+
90
+ // If the file doesn't exist yet the project isn't initialised — skip silently
91
+ if (!fs.existsSync(pendingPath)) {
92
+ process.exit(0);
93
+ }
94
+
95
+ const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
96
+ const line = `[${timestamp}] FILE_WRITE: ${filePath} — ${description}\n`;
97
+
98
+ fs.appendFileSync(pendingPath, line, 'utf8');
99
+ process.exit(0);
100
+ }
101
+
102
+ function buildDescription(toolName, toolInput, filePath) {
103
+ if (toolName === 'Write') {
104
+ return `file written`;
105
+ }
106
+ if (toolName === 'Edit') {
107
+ return `file edited`;
108
+ }
109
+ if (toolName === 'MultiEdit') {
110
+ const count = (toolInput?.edits || []).length;
111
+ return count > 1 ? `${count} edits applied` : `file edited`;
112
+ }
113
+ return `modified`;
114
+ }
115
+
116
+ main();
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // SessionEnd hook for claude-compass
5
+ // Fires when a Claude Code session ends.
6
+ // Prints a summary if 3+ changes were logged during the session.
7
+ // Zero LLM calls. Pure JavaScript.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ function main() {
13
+ try {
14
+ run();
15
+ } catch {
16
+ process.exit(0);
17
+ }
18
+ }
19
+
20
+ function run() {
21
+ const pendingPath = path.join(process.cwd(), '.claude', 'pending-updates.md');
22
+
23
+ if (!fs.existsSync(pendingPath)) {
24
+ process.exit(0);
25
+ }
26
+
27
+ const content = fs.readFileSync(pendingPath, 'utf8').trim();
28
+ if (!content) {
29
+ process.exit(0);
30
+ }
31
+
32
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
33
+ const count = lines.length;
34
+
35
+ if (count < 3) {
36
+ process.exit(0);
37
+ }
38
+
39
+ if (count >= 10) {
40
+ console.log(`\n✦ claude-compass: ${count} changes logged — CLAUDE.md may be out of date`);
41
+ console.log(` run: claude-compass update\n`);
42
+ } else {
43
+ console.log(`\n✦ claude-compass: ${count} changes logged — run claude-compass update to sync CLAUDE.md\n`);
44
+ }
45
+
46
+ process.exit(0);
47
+ }
48
+
49
+ main();
@@ -1,217 +0,0 @@
1
- claude-memory — Full Product Spec
2
- What It Is
3
- claude-memory is an npm package that extends Claude Code with automatic CLAUDE.md lifecycle management. It installs hooks and agents into Claude Code's existing infrastructure so that project context stays accurate over time — silently, without interrupting the developer's workflow.
4
- It works exclusively with Claude Code. No separate runtime, no background processes, no cloud dependency.
5
-
6
- How It Works — The Full Flow
7
- Developer works in Claude Code
8
-
9
- PostToolUse hook fires after significant file writes
10
-
11
- Hook appends one-line note to .claude/pending-updates.md
12
- (pure bash, zero LLM tokens)
13
-
14
- Session ends
15
-
16
- SessionEnd hook prints summary:
17
- "✦ claude-memory: 3 changes logged"
18
-
19
- Developer finishes their day or a feature
20
-
21
- claude-md-updater agent runs (manually or on threshold)
22
-
23
- Reads pending-updates.md + CLAUDE.md
24
- Makes surgical updates to CLAUDE.md
25
- Clears pending-updates.md
26
- Prints: "CLAUDE.md updated — 2 conventions added, 1 gotcha recorded"
27
-
28
- npm Package Structure
29
- claude-memory/
30
- ├── package.json
31
- ├── bin/
32
- │ └── claude-memory.js ← CLI entry point
33
- ├── src/
34
- │ ├── install.js ← installs hooks + agents into ~/.claude
35
- │ ├── status.js ← reads and displays current state
36
- │ ├── update.js ← manually triggers claude-md-updater
37
- │ └── init.js ← creates CLAUDE.md + folder structure
38
- ├── templates/
39
- │ ├── hooks/
40
- │ │ ├── post-tool-use.js ← the hook that logs changes
41
- │ │ └── session-end.js ← the hook that prints summary
42
- │ └── agents/
43
- │ └── claude-md-updater.md ← the updater agent
44
- └── README.md
45
-
46
- Installation Experience
47
- User runs:
48
- bashnpm install -g claude-memory
49
- claude-memory init
50
- claude-memory init does the following in order:
51
-
52
- Detects if the current directory is a git repo — if not, warns but continues
53
- Checks if CLAUDE.md already exists — if yes, backs it up to CLAUDE.md.backup before touching anything
54
- Creates .claude/context/ directory
55
- Creates .claude/pending-updates.md with empty state
56
- Installs hooks into ~/.claude/hooks/ (global) or .claude/hooks/ (project)
57
- Installs claude-md-updater agent into .claude/agents/
58
- If no CLAUDE.md exists, runs codebase-explorer agent to generate one
59
- Prints:
60
-
61
- ✦ claude-memory initialized
62
-
63
- CLAUDE.md ✓ ready
64
- pending-updates ✓ ready
65
- hooks ✓ post-tool-use, session-end registered
66
- agent ✓ claude-md-updater installed
67
-
68
- Changes will be logged silently during sessions.
69
- Run: claude-memory status to check anytime.
70
-
71
- File Architecture
72
- CLAUDE.md — stable core
73
- Only contains things that rarely change:
74
-
75
- Project purpose (1-2 sentences)
76
- Tech stack
77
- Key architectural decisions
78
- Coding conventions
79
- Folder structure overview
80
- Critical gotchas
81
- A ## Last Updated line at the bottom with date
82
-
83
- .claude/pending-updates.md
84
- Append-only log during sessions. Format:
85
- [2026-04-03 14:23] FILE_WRITE: server/routes/auth.js — new OAuth route added
86
- [2026-04-03 14:31] FILE_WRITE: server/db/migrations/004_add_sessions.sql — new sessions table
87
- [2026-04-03 15:12] AGENT_NOTE: discovered that session tokens must be rotated on each request
88
- Gets cleared after each successful CLAUDE.md update.
89
- .claude/context/ — optional reference files
90
- Only created if needed. Never auto-loaded — agents read them explicitly when relevant:
91
- .claude/context/
92
- ├── decisions.md ← architectural decisions and why
93
- ├── known-issues.md ← current bugs and gotchas
94
- └── recent-changes.md ← what changed in last 2 weeks
95
-
96
- Hook Design
97
- PostToolUse Hook — post-tool-use.js
98
- Fires after every tool use in Claude Code.
99
- Logic:
100
-
101
- If tool is NOT a file write (Write, Edit, MultiEdit) → exit immediately, do nothing
102
- If file written is in node_modules/, .git/, dist/, build/ → exit, do nothing
103
- If file written is a lockfile (package-lock.json, yarn.lock) → exit, do nothing
104
- Otherwise → append one line to .claude/pending-updates.md:
105
-
106
- [timestamp] FILE_WRITE: path/to/file — {brief description from tool context}
107
- This is pure JavaScript/bash. Zero LLM calls. Costs nothing.
108
- SessionEnd Hook — session-end.js
109
- Fires when Claude Code session ends.
110
- Logic:
111
-
112
- Read .claude/pending-updates.md
113
- Count lines
114
- If 0 lines → print nothing
115
- If 1-2 lines → print nothing (too minor to surface)
116
- If 3+ lines → print:
117
-
118
- ✦ claude-memory: 4 changes logged — run claude-memory update to sync CLAUDE.md
119
-
120
- If 10+ lines → print with urgency:
121
-
122
- ✦ claude-memory: 12 changes logged — CLAUDE.md may be out of date
123
- run: claude-memory update
124
-
125
- The claude-md-updater Agent
126
- This is the only part that uses LLM tokens.
127
- Trigger: Either manually via claude-memory update CLI command, or when developer explicitly asks in Claude Code: "update CLAUDE.md" or "sync claude memory."
128
- Agent description for Claude Code:
129
- Use this agent when the user runs claude-memory update, asks to update
130
- CLAUDE.md, asks to sync project context, or says "update claude memory".
131
- Also triggers when pending-updates.md has 10 or more entries. Does NOT
132
- trigger during normal coding work.
133
- What the agent does:
134
-
135
- Reads .claude/pending-updates.md in full
136
- Reads current CLAUDE.md in full
137
- For each entry in pending-updates, decides: does this represent a change significant enough to update CLAUDE.md? Criteria:
138
-
139
- New file in a key directory (new route, new service, new migration) → yes
140
- Change to existing file that's already documented → maybe, only if it changes the documented behavior
141
- Lockfile, config tweak, minor edit → no
142
-
143
-
144
- Makes only surgical edits to CLAUDE.md — adds lines, updates existing lines, never rewrites sections wholesale
145
- Updates the ## Last Updated line
146
- Clears .claude/pending-updates.md (writes empty file)
147
- Prints a one-line summary:
148
-
149
- ✦ CLAUDE.md updated — 2 entries added, 1 updated, 9 skipped
150
- Token cost estimate: ~2,000-4,000 tokens per run. At typical API rates, less than $0.01 per update. Run it once a day — negligible.
151
-
152
- CLI Commands
153
- claude-memory init
154
- First-time setup. Run once per project.
155
-
156
- Creates folder structure
157
- Installs hooks and agent
158
- Generates CLAUDE.md if missing
159
- Safe to re-run — won't overwrite existing CLAUDE.md without confirmation
160
-
161
- claude-memory status
162
- Shows current state. Safe to run anytime.
163
- ✦ claude-memory status
164
-
165
- Project: my-project
166
- CLAUDE.md last updated 2 days ago (Apr 1)
167
- Pending updates: 4 changes waiting
168
- Last session: logged 2 changes (today 3:45pm)
169
- Agent: claude-md-updater ✓ installed
170
- Hooks: post-tool-use ✓ session-end ✓
171
- claude-memory update
172
- Manually triggers the claude-md-updater agent inside Claude Code.
173
-
174
- Only works when run from inside a Claude Code session
175
- If run outside Claude Code, prints:
176
-
177
- ✦ claude-memory: open Claude Code and run this command from inside a session,
178
- or type "update CLAUDE.md" in your Claude Code chat.
179
- claude-memory reset
180
- Clears pending-updates.md without updating CLAUDE.md.
181
-
182
- Asks for confirmation first
183
- Useful if pending changes are all irrelevant
184
-
185
-
186
- package.json
187
- json{
188
- "name": "claude-memory",
189
- "version": "0.1.0",
190
- "description": "Automatic CLAUDE.md lifecycle management for Claude Code",
191
- "bin": {
192
- "claude-memory": "./bin/claude-memory.js"
193
- },
194
- "keywords": [
195
- "claude",
196
- "claude-code",
197
- "anthropic",
198
- "ai",
199
- "developer-tools"
200
- ],
201
- "engines": {
202
- "node": ">=18.0.0"
203
- }
204
- }
205
-
206
- What To Build First — Prioritized Order
207
- Tell Claude Code to build in this order so you have something working at each step:
208
-
209
- npm package skeleton — package.json, bin/claude-memory.js, basic CLI routing
210
- claude-memory init — folder creation, file scaffolding, confirmation output
211
- claude-memory status — reads state and displays it
212
- PostToolUse hook — the change logger
213
- SessionEnd hook — the session summary
214
- claude-md-updater agent — the CLAUDE.md syncer
215
- claude-memory update — triggers the agent
216
- claude-memory reset — clears pending updates
217
- Polish — error handling, edge cases, first-run experience