@memnexus-ai/cli 1.7.162 → 1.7.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ /**
3
+ * Hooks writer — install and remove MemNexus lifecycle hooks for AI agents.
4
+ *
5
+ * Phase 1 scope: Claude Code only.
6
+ *
7
+ * Copies bundled hook scripts to ~/.memnexus/hooks/ and merges hook entries
8
+ * into the agent's settings.json without overwriting existing hooks.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.installHooks = installHooks;
15
+ exports.removeHooks = removeHooks;
16
+ const node_fs_1 = require("node:fs");
17
+ const promises_1 = require("node:fs/promises");
18
+ const node_path_1 = __importDefault(require("node:path"));
19
+ const node_os_1 = __importDefault(require("node:os"));
20
+ const detector_js_1 = require("./detector.js");
21
+ // Resolve the hooks/ directory bundled alongside the compiled output.
22
+ // Compiled file lives at dist/lib/setup/hooks-writer.js → go up 3 levels
23
+ // to the package root, then into hooks/.
24
+ // __dirname is a CommonJS global — available because module: "commonjs" in tsconfig.
25
+ const BUNDLED_HOOKS_DIR = node_path_1.default.resolve(__dirname, '../../../hooks');
26
+ /** Destination directory for installed hook scripts. */
27
+ const INSTALLED_HOOKS_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.memnexus', 'hooks');
28
+ /** Hook script filenames managed by MemNexus. */
29
+ const HOOK_SCRIPTS = ['capture-precompact.sh', 'capture-session-end.sh'];
30
+ /** Build hook entries with absolute paths (resolved at install time, not tilde). */
31
+ function buildMemnexusHooks() {
32
+ const hooksDir = node_path_1.default.join(node_os_1.default.homedir(), '.memnexus', 'hooks');
33
+ return {
34
+ PreCompact: [
35
+ {
36
+ matcher: '',
37
+ hooks: [
38
+ {
39
+ type: 'command',
40
+ command: `bash ${node_path_1.default.join(hooksDir, 'capture-precompact.sh')}`,
41
+ },
42
+ ],
43
+ },
44
+ ],
45
+ Stop: [
46
+ {
47
+ hooks: [
48
+ {
49
+ type: 'command',
50
+ command: `bash ${node_path_1.default.join(hooksDir, 'capture-session-end.sh')}`,
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ /** Marker string used to identify MemNexus hook entries. */
58
+ const MEMNEXUS_MARKER = 'memnexus';
59
+ /**
60
+ * Resolve the settings.json path for a Claude Code installation.
61
+ * - global scope: ~/.claude/settings.json
62
+ * - project scope: <projectDir>/.claude/settings.json
63
+ */
64
+ function resolveSettingsPath(scope, projectDir) {
65
+ if (scope === 'project') {
66
+ if (!projectDir) {
67
+ throw new Error('Project directory required for project scope');
68
+ }
69
+ return node_path_1.default.join(projectDir, '.claude', 'settings.json');
70
+ }
71
+ return (0, detector_js_1.resolvePath)('~/.claude/settings.json');
72
+ }
73
+ /**
74
+ * Return true if a hook entry array contains a MemNexus command.
75
+ */
76
+ function arrayHasMemnexusHook(entries) {
77
+ return entries.some((entry) => {
78
+ if (typeof entry !== 'object' || entry === null)
79
+ return false;
80
+ const e = entry;
81
+ const subHooks = e.hooks;
82
+ if (!Array.isArray(subHooks))
83
+ return false;
84
+ return subHooks.some((h) => {
85
+ if (typeof h !== 'object' || h === null)
86
+ return false;
87
+ const cmd = h.command;
88
+ return typeof cmd === 'string' && cmd.includes(MEMNEXUS_MARKER);
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Replace any existing MemNexus entry in a hook array, or append if not present.
94
+ */
95
+ function upsertHookEntries(existing, newEntries) {
96
+ // Remove old memnexus entries
97
+ const filtered = existing.filter((entry) => {
98
+ if (typeof entry !== 'object' || entry === null)
99
+ return true;
100
+ const e = entry;
101
+ const subHooks = e.hooks;
102
+ if (!Array.isArray(subHooks))
103
+ return true;
104
+ return !subHooks.some((h) => {
105
+ if (typeof h !== 'object' || h === null)
106
+ return false;
107
+ const cmd = h.command;
108
+ return typeof cmd === 'string' && cmd.includes(MEMNEXUS_MARKER);
109
+ });
110
+ });
111
+ // Append the new entries
112
+ return [...filtered, ...newEntries];
113
+ }
114
+ /**
115
+ * Install MemNexus lifecycle hooks for a Claude Code installation.
116
+ *
117
+ * - Copies bundled hook scripts to ~/.memnexus/hooks/
118
+ * - Merges hook entries into the agent's settings.json
119
+ * - Idempotent: re-running updates scripts and entries in place
120
+ *
121
+ * Returns null for agents other than Claude Code (Phase 1 scope).
122
+ */
123
+ async function installHooks(options) {
124
+ const { agent, scope, projectDir, dryRun } = options;
125
+ // Phase 1: Claude Code only
126
+ if (agent.id !== 'claude-code') {
127
+ return null;
128
+ }
129
+ const scriptsCopied = [];
130
+ // ── 1. Copy hook scripts to ~/.memnexus/hooks/ ──────────────────────────
131
+ if (!dryRun) {
132
+ await (0, promises_1.mkdir)(INSTALLED_HOOKS_DIR, { recursive: true });
133
+ for (const scriptName of HOOK_SCRIPTS) {
134
+ const src = node_path_1.default.join(BUNDLED_HOOKS_DIR, scriptName);
135
+ if (!(0, node_fs_1.existsSync)(src)) {
136
+ // Bundled script missing — skip gracefully (shouldn't happen in production)
137
+ continue;
138
+ }
139
+ const dest = node_path_1.default.join(INSTALLED_HOOKS_DIR, scriptName);
140
+ await (0, promises_1.copyFile)(src, dest);
141
+ await (0, promises_1.chmod)(dest, 0o755);
142
+ scriptsCopied.push(scriptName);
143
+ }
144
+ }
145
+ else {
146
+ // Dry-run: report what would be copied
147
+ for (const scriptName of HOOK_SCRIPTS) {
148
+ const src = node_path_1.default.join(BUNDLED_HOOKS_DIR, scriptName);
149
+ if ((0, node_fs_1.existsSync)(src)) {
150
+ scriptsCopied.push(scriptName);
151
+ }
152
+ }
153
+ }
154
+ // ── 2. Merge hook entries into settings.json ─────────────────────────────
155
+ const configPath = resolveSettingsPath(scope, projectDir);
156
+ let backupPath;
157
+ let action;
158
+ let settings = {};
159
+ const fileExists = (0, node_fs_1.existsSync)(configPath);
160
+ if (fileExists) {
161
+ try {
162
+ const raw = await (0, promises_1.readFile)(configPath, 'utf-8');
163
+ settings = JSON.parse(raw);
164
+ }
165
+ catch (err) {
166
+ throw new Error(`Cannot parse ${configPath} — fix the JSON syntax and retry. ` +
167
+ `Original error: ${err.message}`);
168
+ }
169
+ if (!dryRun) {
170
+ backupPath = configPath + '.bak';
171
+ await (0, promises_1.copyFile)(configPath, backupPath);
172
+ }
173
+ action = 'updated';
174
+ }
175
+ else {
176
+ action = 'created';
177
+ }
178
+ // Ensure hooks object exists
179
+ if (!settings.hooks || typeof settings.hooks !== 'object') {
180
+ settings.hooks = {};
181
+ }
182
+ const hooks = settings.hooks;
183
+ // Merge PreCompact hooks
184
+ const preCompactEntries = Array.isArray(hooks.PreCompact) ? hooks.PreCompact : [];
185
+ hooks.PreCompact = upsertHookEntries(preCompactEntries, buildMemnexusHooks().PreCompact);
186
+ // Merge Stop hooks
187
+ const stopEntries = Array.isArray(hooks.Stop) ? hooks.Stop : [];
188
+ hooks.Stop = upsertHookEntries(stopEntries, buildMemnexusHooks().Stop);
189
+ // Check whether anything actually changed when updating
190
+ if (fileExists &&
191
+ action === 'updated' &&
192
+ Array.isArray(hooks.PreCompact) &&
193
+ Array.isArray(hooks.Stop) &&
194
+ arrayHasMemnexusHook(preCompactEntries) &&
195
+ arrayHasMemnexusHook(stopEntries) &&
196
+ JSON.stringify(hooks.PreCompact) === JSON.stringify(preCompactEntries) &&
197
+ JSON.stringify(hooks.Stop) === JSON.stringify(stopEntries)) {
198
+ // Nothing changed — already up to date
199
+ action = 'skipped';
200
+ backupPath = undefined;
201
+ }
202
+ if (!dryRun && action !== 'skipped') {
203
+ await (0, promises_1.mkdir)(node_path_1.default.dirname(configPath), { recursive: true });
204
+ await (0, promises_1.writeFile)(configPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
205
+ }
206
+ return { scriptsDir: INSTALLED_HOOKS_DIR, scriptsCopied, configPath, backupPath, action };
207
+ }
208
+ /**
209
+ * Remove MemNexus lifecycle hooks for a Claude Code installation.
210
+ *
211
+ * - Removes hook entries containing "memnexus" from settings.json
212
+ * - Deletes hook scripts from ~/.memnexus/hooks/
213
+ * - Cleans up empty hook arrays
214
+ *
215
+ * No-ops for agents other than Claude Code.
216
+ */
217
+ async function removeHooks(options) {
218
+ const { agent, scope, projectDir } = options;
219
+ // Phase 1: Claude Code only
220
+ if (agent.id !== 'claude-code') {
221
+ return;
222
+ }
223
+ // ── 1. Remove hook scripts ───────────────────────────────────────────────
224
+ for (const scriptName of HOOK_SCRIPTS) {
225
+ const scriptPath = node_path_1.default.join(INSTALLED_HOOKS_DIR, scriptName);
226
+ if ((0, node_fs_1.existsSync)(scriptPath)) {
227
+ await (0, promises_1.rm)(scriptPath, { force: true });
228
+ }
229
+ }
230
+ // ── 2. Remove hook entries from settings.json ────────────────────────────
231
+ const configPath = resolveSettingsPath(scope, projectDir);
232
+ if (!(0, node_fs_1.existsSync)(configPath))
233
+ return;
234
+ let settings;
235
+ try {
236
+ const raw = await (0, promises_1.readFile)(configPath, 'utf-8');
237
+ settings = JSON.parse(raw);
238
+ }
239
+ catch {
240
+ return; // Unparseable — leave it alone
241
+ }
242
+ if (!settings.hooks || typeof settings.hooks !== 'object')
243
+ return;
244
+ const hooks = settings.hooks;
245
+ for (const eventType of ['PreCompact', 'Stop']) {
246
+ const entries = hooks[eventType];
247
+ if (!Array.isArray(entries))
248
+ continue;
249
+ const filtered = entries.filter((entry) => {
250
+ if (typeof entry !== 'object' || entry === null)
251
+ return true;
252
+ const e = entry;
253
+ const subHooks = e.hooks;
254
+ if (!Array.isArray(subHooks))
255
+ return true;
256
+ return !subHooks.some((h) => {
257
+ if (typeof h !== 'object' || h === null)
258
+ return false;
259
+ const cmd = h.command;
260
+ return typeof cmd === 'string' && cmd.includes(MEMNEXUS_MARKER);
261
+ });
262
+ });
263
+ if (filtered.length === 0) {
264
+ delete hooks[eventType];
265
+ }
266
+ else {
267
+ hooks[eventType] = filtered;
268
+ }
269
+ }
270
+ // If hooks object is now empty, remove it
271
+ if (Object.keys(hooks).length === 0) {
272
+ delete settings.hooks;
273
+ }
274
+ await (0, promises_1.writeFile)(configPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
275
+ }
276
+ //# sourceMappingURL=hooks-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-writer.js","sourceRoot":"","sources":["../../../src/lib/setup/hooks-writer.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,7 @@ export { composeRules, parseSteeringVersion } from './rules-templates.js';
6
6
  export { getAgent, getAgentIds, getSortedAgents, AGENTS } from './registry.js';
7
7
  export { writeAllSkills, removeAllSkills, listSkills, resolveSkillsDir, resolveSkillPath, } from './skills-writer.js';
8
8
  export { SKILLS, parseSkillVersion } from './skills-templates.js';
9
+ export { installHooks, removeHooks } from './hooks-writer.js';
10
+ export type { HooksWriteResult } from './hooks-writer.js';
9
11
  export type * from './types.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/setup/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EACL,cAAc,EACd,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClE,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/setup/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EACL,cAAc,EACd,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,mBAAmB,YAAY,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSkillVersion = exports.SKILLS = exports.resolveSkillPath = exports.resolveSkillsDir = exports.listSkills = exports.removeAllSkills = exports.writeAllSkills = exports.AGENTS = exports.getSortedAgents = exports.getAgentIds = exports.getAgent = exports.parseSteeringVersion = exports.composeRules = exports.resolveSteeringPath = exports.getSteeringVersion = exports.removeSteeringSection = exports.writeSteeringFile = exports.verifyAll = exports.verifyAgent = exports.resolveConfigPath = exports.removeAgentConfig = exports.writeAgentConfig = exports.resolvePath = exports.detectAgents = void 0;
3
+ exports.removeHooks = exports.installHooks = exports.parseSkillVersion = exports.SKILLS = exports.resolveSkillPath = exports.resolveSkillsDir = exports.listSkills = exports.removeAllSkills = exports.writeAllSkills = exports.AGENTS = exports.getSortedAgents = exports.getAgentIds = exports.getAgent = exports.parseSteeringVersion = exports.composeRules = exports.resolveSteeringPath = exports.getSteeringVersion = exports.removeSteeringSection = exports.writeSteeringFile = exports.verifyAll = exports.verifyAgent = exports.resolveConfigPath = exports.removeAgentConfig = exports.writeAgentConfig = exports.resolvePath = exports.detectAgents = void 0;
4
4
  var detector_js_1 = require("./detector.js");
5
5
  Object.defineProperty(exports, "detectAgents", { enumerable: true, get: function () { return detector_js_1.detectAgents; } });
6
6
  Object.defineProperty(exports, "resolvePath", { enumerable: true, get: function () { return detector_js_1.resolvePath; } });
@@ -33,4 +33,7 @@ Object.defineProperty(exports, "resolveSkillPath", { enumerable: true, get: func
33
33
  var skills_templates_js_1 = require("./skills-templates.js");
34
34
  Object.defineProperty(exports, "SKILLS", { enumerable: true, get: function () { return skills_templates_js_1.SKILLS; } });
35
35
  Object.defineProperty(exports, "parseSkillVersion", { enumerable: true, get: function () { return skills_templates_js_1.parseSkillVersion; } });
36
+ var hooks_writer_js_1 = require("./hooks-writer.js");
37
+ Object.defineProperty(exports, "installHooks", { enumerable: true, get: function () { return hooks_writer_js_1.installHooks; } });
38
+ Object.defineProperty(exports, "removeHooks", { enumerable: true, get: function () { return hooks_writer_js_1.removeHooks; } });
36
39
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/setup/index.ts"],"names":[],"mappings":";;;AAAA,6CAA0D;AAAjD,2GAAA,YAAY,OAAA;AAAE,0GAAA,WAAW,OAAA;AAClC,uDAA4F;AAAnF,oHAAA,gBAAgB,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAC/D,6CAAuD;AAA9C,0GAAA,WAAW,OAAA;AAAE,wGAAA,SAAS,OAAA;AAC/B,qDAK2B;AAJzB,oHAAA,iBAAiB,OAAA;AACjB,wHAAA,qBAAqB,OAAA;AACrB,qHAAA,kBAAkB,OAAA;AAClB,sHAAA,mBAAmB,OAAA;AAErB,2DAA0E;AAAjE,kHAAA,YAAY,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC3C,6CAA+E;AAAtE,uGAAA,QAAQ,OAAA;AAAE,0GAAA,WAAW,OAAA;AAAE,8GAAA,eAAe,OAAA;AAAE,qGAAA,MAAM,OAAA;AACvD,uDAM4B;AAL1B,kHAAA,cAAc,OAAA;AACd,mHAAA,eAAe,OAAA;AACf,8GAAA,UAAU,OAAA;AACV,oHAAA,gBAAgB,OAAA;AAChB,oHAAA,gBAAgB,OAAA;AAElB,6DAAkE;AAAzD,6GAAA,MAAM,OAAA;AAAE,wHAAA,iBAAiB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/setup/index.ts"],"names":[],"mappings":";;;AAAA,6CAA0D;AAAjD,2GAAA,YAAY,OAAA;AAAE,0GAAA,WAAW,OAAA;AAClC,uDAA4F;AAAnF,oHAAA,gBAAgB,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAC/D,6CAAuD;AAA9C,0GAAA,WAAW,OAAA;AAAE,wGAAA,SAAS,OAAA;AAC/B,qDAK2B;AAJzB,oHAAA,iBAAiB,OAAA;AACjB,wHAAA,qBAAqB,OAAA;AACrB,qHAAA,kBAAkB,OAAA;AAClB,sHAAA,mBAAmB,OAAA;AAErB,2DAA0E;AAAjE,kHAAA,YAAY,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC3C,6CAA+E;AAAtE,uGAAA,QAAQ,OAAA;AAAE,0GAAA,WAAW,OAAA;AAAE,8GAAA,eAAe,OAAA;AAAE,qGAAA,MAAM,OAAA;AACvD,uDAM4B;AAL1B,kHAAA,cAAc,OAAA;AACd,mHAAA,eAAe,OAAA;AACf,8GAAA,UAAU,OAAA;AACV,oHAAA,gBAAgB,OAAA;AAChB,oHAAA,gBAAgB,OAAA;AAElB,6DAAkE;AAAzD,6GAAA,MAAM,OAAA;AAAE,wHAAA,iBAAiB,OAAA;AAClC,qDAA8D;AAArD,+GAAA,YAAY,OAAA;AAAE,8GAAA,WAAW,OAAA"}
@@ -0,0 +1,218 @@
1
+ #!/bin/bash
2
+ # capture-precompact.sh — Automatic memory capture before context compaction
3
+ #
4
+ # Installed at ~/.memnexus/hooks/ by `mx mcp install` for end-user lifecycle
5
+ # hook integration with Claude Code.
6
+ #
7
+ # The highest-value automatic capture signal: fires when ~95% of context window
8
+ # is full, meaning rich context (decisions, debugging sessions, architecture
9
+ # discussions) is about to be lost forever.
10
+ #
11
+ # Unlike session-end capture (which summarises what was done), this hook
12
+ # extracts structured knowledge — decisions, problems solved, patterns — and
13
+ # saves it as a permanent memory before compaction discards the context.
14
+ #
15
+ # Fires on: PreCompact
16
+ # Input: JSON on stdin with transcript_path, session_id, trigger
17
+ # Output: none (side-effect only — saves memory via mx CLI)
18
+ # Exit: always 0 (never blocks compaction)
19
+
20
+ # Fail-open: any error -> allow compaction without saving
21
+ trap 'exit 0' ERR
22
+
23
+ # ── 1. Check required tools ──────────────────────────────────────────────
24
+ command -v jq >/dev/null 2>&1 || exit 0
25
+ command -v mx >/dev/null 2>&1 || exit 0
26
+
27
+ # ── 2. Parse hook input ──────────────────────────────────────────────────
28
+ INPUT=$(cat)
29
+
30
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
31
+ _SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
32
+
33
+ # Skip if no transcript
34
+ if [[ -z "$TRANSCRIPT_PATH" || ! -f "$TRANSCRIPT_PATH" ]]; then
35
+ exit 0
36
+ fi
37
+
38
+ # Validate transcript path — must resolve to a real file, not a symlink to elsewhere
39
+ REAL_TRANSCRIPT=$(realpath "$TRANSCRIPT_PATH" 2>/dev/null) || exit 0
40
+ if [[ ! -f "$REAL_TRANSCRIPT" ]]; then
41
+ exit 0
42
+ fi
43
+ TRANSCRIPT_PATH="$REAL_TRANSCRIPT"
44
+
45
+ # ── 3. Derive project name from transcript path or cwd ───────────────────
46
+ # Transcript paths typically contain the project directory name
47
+ PROJECT_NAME=$(basename "$(dirname "$TRANSCRIPT_PATH")" 2>/dev/null)
48
+ if [[ -z "$PROJECT_NAME" || "$PROJECT_NAME" == "." ]]; then
49
+ PROJECT_NAME=$(basename "$(pwd)" 2>/dev/null || echo "unknown")
50
+ fi
51
+
52
+ # ── 4. Extract structured data from transcript ───────────────────────────
53
+
54
+ tmpdir=$(mktemp -d)
55
+ trap 'rm -rf "$tmpdir"; exit 0' EXIT ERR
56
+
57
+ # Total line count (proxy for session depth)
58
+ _TOTAL_LINES=$(wc -l < "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
59
+
60
+ # Files modified (Write/Edit tool calls)
61
+ FILES_MODIFIED=$(jq -r '
62
+ select(.type == "assistant") |
63
+ .message.content[]? |
64
+ select(.type == "tool_use" and (.name == "Write" or .name == "Edit")) |
65
+ .input.file_path // empty
66
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | grep -v '^$' | sort -u | head -30)
67
+
68
+ # Files read (Read tool calls — indicates areas of investigation)
69
+ FILES_READ=$(jq -r '
70
+ select(.type == "assistant") |
71
+ .message.content[]? |
72
+ select(.type == "tool_use" and .name == "Read") |
73
+ .input.file_path // empty
74
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | grep -v '^$' | sort | uniq -c | sort -rn | head -15 | awk '{print $2}')
75
+
76
+ # Bash commands run (significant actions)
77
+ BASH_COMMANDS=$(jq -r '
78
+ select(.type == "assistant") |
79
+ .message.content[]? |
80
+ select(.type == "tool_use" and .name == "Bash") |
81
+ .input.command // empty
82
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | grep -v '^$' | grep -vE '^(ls|pwd|echo|cat |head |tail )' | tail -20)
83
+
84
+ # Git activity
85
+ GIT_COMMITS=$(echo "$BASH_COMMANDS" | grep -oE 'git commit' | wc -l | tr -d ' ')
86
+ GIT_PUSHES=$(echo "$BASH_COMMANDS" | grep -oE 'git push' | wc -l | tr -d ' ')
87
+ PR_CREATES=$(echo "$BASH_COMMANDS" | grep -oE 'gh pr create' | wc -l | tr -d ' ')
88
+ PR_MERGES=$(echo "$BASH_COMMANDS" | grep -oE 'gh pr merge' | wc -l | tr -d ' ')
89
+
90
+ # PR and issue references from all text content
91
+ ALL_TEXT=$(jq -r '
92
+ select(.type == "user" or .type == "assistant") |
93
+ .message.content |
94
+ if type == "array" then
95
+ [.[] | select(.type == "text") | .text] | join("\n")
96
+ elif type == "string" then .
97
+ else empty end
98
+ ' "$TRANSCRIPT_PATH" 2>/dev/null)
99
+
100
+ GH_REFS=$(echo "$ALL_TEXT" | grep -oE '(PR |#)[0-9]+' 2>/dev/null | sort -u | head -15 | tr '\n' ', ' | sed 's/,$//')
101
+
102
+ # User messages (to understand intent and topics)
103
+ USER_MESSAGES=$(jq -r '
104
+ select(.type == "user") |
105
+ .message.content |
106
+ if type == "array" then
107
+ [.[] | select(.type == "text") | .text] | join("\n")
108
+ elif type == "string" then .
109
+ else empty end
110
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | head -c 3000)
111
+
112
+ # Last few assistant text blocks (conclusions/results)
113
+ RECENT_RESULTS=$(tail -100 "$TRANSCRIPT_PATH" | jq -r '
114
+ select(.type == "assistant") |
115
+ .message.content |
116
+ if type == "array" then
117
+ [.[] | select(.type == "text") | .text] | join("\n")
118
+ elif type == "string" then .
119
+ else empty end
120
+ ' 2>/dev/null | tail -c 2000)
121
+
122
+ # Tool usage summary
123
+ TOOL_SUMMARY=$(jq -r '
124
+ select(.type == "assistant") |
125
+ .message.content[]? |
126
+ select(.type == "tool_use") | .name
127
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | sort | uniq -c | sort -rn | head -10 | awk '{printf "%s(%d) ", $2, $1}')
128
+
129
+ # ── 5. Significance filter ───────────────────────────────────────────────
130
+ # Skip if session was trivial (very few tool calls, no files modified)
131
+ TOOL_COUNT=$(jq -r '
132
+ select(.type == "assistant") |
133
+ .message.content[]? |
134
+ select(.type == "tool_use") | .name
135
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | wc -l | tr -d ' ')
136
+
137
+ if [[ "$TOOL_COUNT" -lt 5 && -z "$FILES_MODIFIED" ]]; then
138
+ # Trivial session — not worth capturing
139
+ exit 0
140
+ fi
141
+
142
+ # ── 5b. Sanitize sensitive data ──────────────────────────────────────────
143
+ # Strip common secret patterns from extracted text before saving as memory
144
+ sanitize() {
145
+ sed -E \
146
+ -e 's/(api[_-]?key|token|secret|password|bearer|authorization)[[:space:]]*[:=][[:space:]]*[^[:space:],\"'"'"']+/\1=<REDACTED>/gi' \
147
+ -e 's/cmk_live_[A-Za-z0-9_-]+/cmk_live_<REDACTED>/g' \
148
+ -e 's/sk-[A-Za-z0-9_-]{20,}/sk-<REDACTED>/g' \
149
+ -e 's/ghp_[A-Za-z0-9]{36}/ghp_<REDACTED>/g' \
150
+ -e 's/gho_[A-Za-z0-9]{36}/gho_<REDACTED>/g' \
151
+ -e 's/-----BEGIN [A-Z ]*PRIVATE KEY-----/-----BEGIN REDACTED KEY-----/g'
152
+ }
153
+
154
+ USER_MESSAGES=$(echo "$USER_MESSAGES" | sanitize)
155
+ BASH_COMMANDS=$(echo "$BASH_COMMANDS" | sanitize)
156
+ RECENT_RESULTS=$(echo "$RECENT_RESULTS" | sanitize)
157
+
158
+ # ── 6. Build structured memory content ───────────────────────────────────
159
+ GIT_BRANCH=$(git -C "$(dirname "$TRANSCRIPT_PATH")" branch --show-current 2>/dev/null || \
160
+ git branch --show-current 2>/dev/null || echo "unknown")
161
+
162
+ # Build files list
163
+ MOD_LIST=""
164
+ if [[ -n "$FILES_MODIFIED" ]]; then
165
+ MOD_LIST=$(echo "$FILES_MODIFIED" | sed 's/^/- /')
166
+ fi
167
+
168
+ READ_LIST=""
169
+ if [[ -n "$FILES_READ" ]]; then
170
+ READ_LIST=$(echo "$FILES_READ" | head -10 | sed 's/^/- /')
171
+ fi
172
+
173
+ # Build activity summary
174
+ ACTIVITY=""
175
+ [[ "$GIT_COMMITS" -gt 0 ]] && ACTIVITY="${ACTIVITY}${GIT_COMMITS} commits, "
176
+ [[ "$GIT_PUSHES" -gt 0 ]] && ACTIVITY="${ACTIVITY}${GIT_PUSHES} pushes, "
177
+ [[ "$PR_CREATES" -gt 0 ]] && ACTIVITY="${ACTIVITY}${PR_CREATES} PRs created, "
178
+ [[ "$PR_MERGES" -gt 0 ]] && ACTIVITY="${ACTIVITY}${PR_MERGES} PRs merged, "
179
+ ACTIVITY="${ACTIVITY}${TOOL_COUNT} tool calls"
180
+
181
+ CONTENT="[Auto-Captured] Session context before compaction — ${PROJECT_NAME}
182
+
183
+ Branch: ${GIT_BRANCH}
184
+ Activity: ${ACTIVITY}
185
+ GitHub refs: ${GH_REFS:-none}
186
+
187
+ Files modified:
188
+ ${MOD_LIST:-None}
189
+
190
+ Files investigated:
191
+ ${READ_LIST:-None}
192
+
193
+ Tools used: ${TOOL_SUMMARY}
194
+
195
+ What the user was working on:
196
+ $(echo "$USER_MESSAGES" | head -c 1500)
197
+
198
+ Recent results/conclusions:
199
+ $(echo "$RECENT_RESULTS" | head -c 1500)"
200
+
201
+ # Truncate total content to ~5000 chars
202
+ CONTENT=$(echo "$CONTENT" | head -c 5000)
203
+
204
+ # ── 7. Save memory (with timeout — never block compaction) ───────────────
205
+ TIMEOUT_CMD=""
206
+ if command -v timeout >/dev/null 2>&1; then
207
+ TIMEOUT_CMD="timeout 10"
208
+ elif command -v gtimeout >/dev/null 2>&1; then
209
+ TIMEOUT_CMD="gtimeout 10"
210
+ fi
211
+
212
+ $TIMEOUT_CMD mx memories create \
213
+ --conversation-id "NEW" \
214
+ --content "$CONTENT" \
215
+ --topics "auto-captured,precompact" \
216
+ >/dev/null 2>&1 || true
217
+
218
+ exit 0