@empirical-gauzza/cli 1.0.28 → 1.0.29

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/bin/empirical.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  'use strict';
4
4
 
@@ -110,37 +110,125 @@ function main() {
110
110
  process.exit(0);
111
111
  }
112
112
 
113
- var result = runMemoryQuery([
114
- 'memory', 'query',
115
- '--match', 'project context, conventions, recent decisions, user preferences',
116
- '--top-k', '8'
117
- ]);
118
-
119
- if (!result || result.status !== 0 || !result.stdout) {
120
- appendLog('memory query failed or returned no stdout (status=' + (result && result.status) + ', stderr=' + (result && result.stderr ? String(result.stderr).slice(0, 300) : '') + ')');
121
- process.exit(0);
113
+ // Declared inside main() on purpose: function declarations hoist, and other hook templates in
114
+ // this file have their own main() without the memory-query plumbing.
115
+
116
+ // WHAT PROJECT IS THIS, in priority order. Guessing from the directory name is the last resort,
117
+ // not the contract: a repo folder is not always what the corpus tags memories with.
118
+ // 1. EMPIRICAL_PROJECT_TAG / EMPIRICAL_TOPICS environment variables
119
+ // 2. .empirical.json in the working directory or any ancestor, up to 6 levels:
120
+ // { "project": "empirical_v2", "topics": ["search ranking", "deploy"] }
121
+ // 3. the working directory name, lowercased
122
+ function readProjectConfig() {
123
+ var config = { project: '', topics: [] };
124
+ try {
125
+ var dir = process.cwd();
126
+ for (var depth = 0; depth < 6; depth += 1) {
127
+ var candidate = path.join(dir, '.empirical.json');
128
+ if (fs.existsSync(candidate)) {
129
+ var parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
130
+ config.project = String(parsed && parsed.project ? parsed.project : '').trim().toLowerCase();
131
+ if (parsed && Array.isArray(parsed.topics)) {
132
+ config.topics = parsed.topics.map(function (t) { return String(t || '').trim(); }).filter(Boolean);
133
+ }
134
+ appendLog('loaded project config from ' + candidate);
135
+ return config;
136
+ }
137
+ var parent = path.dirname(dir);
138
+ if (!parent || parent === dir) { break; }
139
+ dir = parent;
140
+ }
141
+ } catch (e) {
142
+ appendLog('project config unreadable, continuing without it: ' + e.message);
143
+ }
144
+ return config;
122
145
  }
123
146
 
124
- var results;
125
- try {
126
- results = JSON.parse(result.stdout);
127
- } catch (e) {
128
- appendLog('failed to parse memory query stdout as JSON: ' + e.message);
129
- process.exit(0);
147
+ // requireAll selects --tags-all (AND) over --tags (OR). This matters: --tags is an OR match, so
148
+ // asking for 'empirical_v2,todo' returns every project's todos, which put a wellnessonbalance
149
+ // item into an empirical_v2 session during testing on 2026-09-16.
150
+ function fetchMemories(matchText, tags, topK, requireAll) {
151
+ var args = ['memory', 'query', '--match', matchText, '--top-k', String(topK)];
152
+ if (tags) { args.push(requireAll ? '--tags-all' : '--tags', tags); }
153
+ var queryResult = runMemoryQuery(args);
154
+ if (!queryResult || queryResult.status !== 0 || !queryResult.stdout) {
155
+ appendLog('memory query failed (tags=' + (tags || 'none') + ', status=' + (queryResult && queryResult.status) + ', stderr=' + (queryResult && queryResult.stderr ? String(queryResult.stderr).slice(0, 200) : '') + ')');
156
+ return [];
157
+ }
158
+ try {
159
+ var parsed = JSON.parse(queryResult.stdout);
160
+ return Array.isArray(parsed) ? parsed : [];
161
+ } catch (e) {
162
+ appendLog('failed to parse memory query stdout as JSON: ' + e.message);
163
+ return [];
164
+ }
165
+ }
166
+
167
+ var fileConfig = readProjectConfig();
168
+ var envTag = String(process.env.EMPIRICAL_PROJECT_TAG || '').trim().toLowerCase();
169
+ var envTopics = String(process.env.EMPIRICAL_TOPICS || '').split(',').map(function (t) { return t.trim(); }).filter(Boolean);
170
+
171
+ var tag = envTag || fileConfig.project;
172
+ var tagSource = envTag ? 'env' : (fileConfig.project ? 'config' : 'cwd');
173
+ if (!tag) {
174
+ try {
175
+ tag = String(path.basename(process.cwd() || '') || '').trim().toLowerCase();
176
+ } catch (e) {
177
+ tag = '';
178
+ }
179
+ }
180
+
181
+ var topics = envTopics.length ? envTopics : fileConfig.topics;
182
+ var contextMatch = topics.length
183
+ ? topics.join(', ')
184
+ : 'project context, conventions, recent decisions, user preferences';
185
+
186
+ appendLog('project tag=' + (tag || '(none)') + ' (source=' + tagSource + '), topics=' + (topics.length ? topics.join('|') : '(default)'));
187
+
188
+ // Scope every query to the project. Measured 2026-09-16: with no tag filter all 8 slots went to
189
+ // one unrelated project during a full session in a different repo, so the only automatic context
190
+ // rail in the product was spending every slot on the wrong thing. A tag filter also disables the
191
+ // server's agent-process-note exclusion, which is what makes a project's own scar tissue and
192
+ // work history reachable here at all.
193
+ //
194
+ // Open items get their OWN query instead of competing for rank. Tested 2026-09-16: a due-dated
195
+ // memory did not surface in the generic top-8 with or without a project filter, because dueness
196
+ // is not a semantic property. Anything that must be seen needs a reserved slot.
197
+ // AND, not OR: this must be THIS project's todos. No --status filter on purpose, because todos
198
+ // written without a data.status would silently vanish from the one place they get surfaced;
199
+ // the label tells the reader to confirm instead.
200
+ var todos = tag ? fetchMemories('open todo, next action, follow-up, due', tag + ',todo', 3, true) : [];
201
+ var context = tag ? fetchMemories(contextMatch, tag, 6) : [];
202
+
203
+ // A corpus whose tags do not match the resolved project must never end up worse off than before
204
+ // this change, so fall back to the original unscoped query.
205
+ if (context.length === 0) {
206
+ appendLog('project-scoped query returned nothing, falling back to unscoped');
207
+ context = fetchMemories(contextMatch, '', 8);
130
208
  }
131
209
 
132
- if (!Array.isArray(results) || results.length === 0) {
210
+ if (todos.length === 0 && context.length === 0) {
133
211
  appendLog('memory query returned zero results');
134
212
  process.exit(0);
135
213
  }
136
214
 
215
+ var openCount = todos.length;
216
+ var results = todos.concat(context);
217
+
137
218
  appendLog('memory query returned ' + results.length + ' result(s), injecting into session context');
138
219
 
139
- var lines = results.slice(0, 8).map(function (r) {
220
+ // Summaries in a mature corpus run to thousands of characters. Injecting eight in full is a wall
221
+ // of text that gets skimmed past, which fails the same way as never injecting them at all:
222
+ // presence in context is not attention. Truncate hard; the agent can query for the rest.
223
+ var lines = results.slice(0, 9).map(function (r, i) {
140
224
  var category = r && r.category ? r.category : 'memory';
141
- var summary = r && r.summary ? r.summary : '';
142
- return '- [' + category + '] ' + summary;
225
+ var summary = String(r && r.summary ? r.summary : '');
226
+ summary = summary.split('\\n').join(' ').split('\\r').join(' ').split('\\t').join(' ').trim();
227
+ if (summary.length > 240) { summary = summary.slice(0, 237) + '...'; }
228
+ var prefix = i < openCount ? '- [OPEN ITEM, confirm status] ' : '- [' + category + '] ';
229
+ return prefix + summary;
143
230
  });
231
+ lines.push('- Before risky or repeated work, ask first: empirical memory query --match "<what you are about to do>" --tags scar-tissue');
144
232
 
145
233
  console.log('Empirical memory context for this session:\\n' + lines.join('\\n'));
146
234
  process.exit(0);
@@ -259,40 +347,126 @@ function main() {
259
347
  process.exit(0);
260
348
  }
261
349
 
262
- var result = runMemoryQuery([
263
- 'memory', 'query',
264
- '--match', 'project context, conventions, recent decisions, user preferences',
265
- '--top-k', '8'
266
- ]);
350
+ // Declared inside main() on purpose: function declarations hoist, and other hook templates in
351
+ // this file have their own main() without the memory-query plumbing.
352
+
353
+ // WHAT PROJECT IS THIS, in priority order. Guessing from the directory name is the last resort,
354
+ // not the contract: a repo folder is not always what the corpus tags memories with.
355
+ // 1. EMPIRICAL_PROJECT_TAG / EMPIRICAL_TOPICS environment variables
356
+ // 2. .empirical.json in the working directory or any ancestor, up to 6 levels:
357
+ // { "project": "empirical_v2", "topics": ["search ranking", "deploy"] }
358
+ // 3. the working directory name, lowercased
359
+ function readProjectConfig() {
360
+ var config = { project: '', topics: [] };
361
+ try {
362
+ var dir = process.cwd();
363
+ for (var depth = 0; depth < 6; depth += 1) {
364
+ var candidate = path.join(dir, '.empirical.json');
365
+ if (fs.existsSync(candidate)) {
366
+ var parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
367
+ config.project = String(parsed && parsed.project ? parsed.project : '').trim().toLowerCase();
368
+ if (parsed && Array.isArray(parsed.topics)) {
369
+ config.topics = parsed.topics.map(function (t) { return String(t || '').trim(); }).filter(Boolean);
370
+ }
371
+ appendLog('loaded project config from ' + candidate);
372
+ return config;
373
+ }
374
+ var parent = path.dirname(dir);
375
+ if (!parent || parent === dir) { break; }
376
+ dir = parent;
377
+ }
378
+ } catch (e) {
379
+ appendLog('project config unreadable, continuing without it: ' + e.message);
380
+ }
381
+ return config;
382
+ }
267
383
 
268
- if (!result || result.status !== 0 || !result.stdout) {
269
- appendLog('memory query failed or returned no stdout (status=' + (result && result.status) + ', stderr=' + (result && result.stderr ? String(result.stderr).slice(0, 300) : '') + ')');
270
- emitContext('');
271
- process.exit(0);
384
+ // requireAll selects --tags-all (AND) over --tags (OR). This matters: --tags is an OR match, so
385
+ // asking for 'empirical_v2,todo' returns every project's todos, which put a wellnessonbalance
386
+ // item into an empirical_v2 session during testing on 2026-09-16.
387
+ function fetchMemories(matchText, tags, topK, requireAll) {
388
+ var args = ['memory', 'query', '--match', matchText, '--top-k', String(topK)];
389
+ if (tags) { args.push(requireAll ? '--tags-all' : '--tags', tags); }
390
+ var queryResult = runMemoryQuery(args);
391
+ if (!queryResult || queryResult.status !== 0 || !queryResult.stdout) {
392
+ appendLog('memory query failed (tags=' + (tags || 'none') + ', status=' + (queryResult && queryResult.status) + ', stderr=' + (queryResult && queryResult.stderr ? String(queryResult.stderr).slice(0, 200) : '') + ')');
393
+ return [];
394
+ }
395
+ try {
396
+ var parsed = JSON.parse(queryResult.stdout);
397
+ return Array.isArray(parsed) ? parsed : [];
398
+ } catch (e) {
399
+ appendLog('failed to parse memory query stdout as JSON: ' + e.message);
400
+ return [];
401
+ }
272
402
  }
273
403
 
274
- var results;
275
- try {
276
- results = JSON.parse(result.stdout);
277
- } catch (e) {
278
- appendLog('failed to parse memory query stdout as JSON: ' + e.message);
279
- emitContext('');
280
- process.exit(0);
404
+ var fileConfig = readProjectConfig();
405
+ var envTag = String(process.env.EMPIRICAL_PROJECT_TAG || '').trim().toLowerCase();
406
+ var envTopics = String(process.env.EMPIRICAL_TOPICS || '').split(',').map(function (t) { return t.trim(); }).filter(Boolean);
407
+
408
+ var tag = envTag || fileConfig.project;
409
+ var tagSource = envTag ? 'env' : (fileConfig.project ? 'config' : 'cwd');
410
+ if (!tag) {
411
+ try {
412
+ tag = String(path.basename(process.cwd() || '') || '').trim().toLowerCase();
413
+ } catch (e) {
414
+ tag = '';
415
+ }
416
+ }
417
+
418
+ var topics = envTopics.length ? envTopics : fileConfig.topics;
419
+ var contextMatch = topics.length
420
+ ? topics.join(', ')
421
+ : 'project context, conventions, recent decisions, user preferences';
422
+
423
+ appendLog('project tag=' + (tag || '(none)') + ' (source=' + tagSource + '), topics=' + (topics.length ? topics.join('|') : '(default)'));
424
+
425
+ // Scope every query to the project. Measured 2026-09-16: with no tag filter all 8 slots went to
426
+ // one unrelated project during a full session in a different repo, so the only automatic context
427
+ // rail in the product was spending every slot on the wrong thing. A tag filter also disables the
428
+ // server's agent-process-note exclusion, which is what makes a project's own scar tissue and
429
+ // work history reachable here at all.
430
+ //
431
+ // Open items get their OWN query instead of competing for rank. Tested 2026-09-16: a due-dated
432
+ // memory did not surface in the generic top-8 with or without a project filter, because dueness
433
+ // is not a semantic property. Anything that must be seen needs a reserved slot.
434
+ // AND, not OR: this must be THIS project's todos. No --status filter on purpose, because todos
435
+ // written without a data.status would silently vanish from the one place they get surfaced;
436
+ // the label tells the reader to confirm instead.
437
+ var todos = tag ? fetchMemories('open todo, next action, follow-up, due', tag + ',todo', 3, true) : [];
438
+ var context = tag ? fetchMemories(contextMatch, tag, 6) : [];
439
+
440
+ // A corpus whose tags do not match the resolved project must never end up worse off than before
441
+ // this change, so fall back to the original unscoped query.
442
+ if (context.length === 0) {
443
+ appendLog('project-scoped query returned nothing, falling back to unscoped');
444
+ context = fetchMemories(contextMatch, '', 8);
281
445
  }
282
446
 
283
- if (!Array.isArray(results) || results.length === 0) {
447
+ if (todos.length === 0 && context.length === 0) {
284
448
  appendLog('memory query returned zero results');
285
449
  emitContext('');
286
450
  process.exit(0);
287
451
  }
288
452
 
453
+ var openCount = todos.length;
454
+ var results = todos.concat(context);
455
+
289
456
  appendLog('memory query returned ' + results.length + ' result(s), injecting into session context');
290
457
 
291
- var lines = results.slice(0, 8).map(function (r) {
458
+ // Summaries in a mature corpus run to thousands of characters. Injecting eight in full is a wall
459
+ // of text that gets skimmed past, which fails the same way as never injecting them at all:
460
+ // presence in context is not attention. Truncate hard; the agent can query for the rest.
461
+ var lines = results.slice(0, 9).map(function (r, i) {
292
462
  var category = r && r.category ? r.category : 'memory';
293
- var summary = r && r.summary ? r.summary : '';
294
- return '- [' + category + '] ' + summary;
463
+ var summary = String(r && r.summary ? r.summary : '');
464
+ summary = summary.split('\\n').join(' ').split('\\r').join(' ').split('\\t').join(' ').trim();
465
+ if (summary.length > 240) { summary = summary.slice(0, 237) + '...'; }
466
+ var prefix = i < openCount ? '- [OPEN ITEM, confirm status] ' : '- [' + category + '] ';
467
+ return prefix + summary;
295
468
  });
469
+ lines.push('- Before risky or repeated work, ask first: empirical memory query --match "<what you are about to do>" --tags scar-tissue');
296
470
 
297
471
  console.log(JSON.stringify({
298
472
  hookSpecificOutput: {
@@ -400,38 +574,124 @@ function main() {
400
574
  process.exit(0);
401
575
  }
402
576
 
403
- var result = runMemoryQuery([
404
- 'memory', 'query',
405
- '--match', 'project context, conventions, recent decisions, user preferences',
406
- '--top-k', '8'
407
- ]);
577
+ // Declared inside main() on purpose: function declarations hoist, and other hook templates in
578
+ // this file have their own main() without the memory-query plumbing.
579
+
580
+ // WHAT PROJECT IS THIS, in priority order. Guessing from the directory name is the last resort,
581
+ // not the contract: a repo folder is not always what the corpus tags memories with.
582
+ // 1. EMPIRICAL_PROJECT_TAG / EMPIRICAL_TOPICS environment variables
583
+ // 2. .empirical.json in the working directory or any ancestor, up to 6 levels:
584
+ // { "project": "empirical_v2", "topics": ["search ranking", "deploy"] }
585
+ // 3. the working directory name, lowercased
586
+ function readProjectConfig() {
587
+ var config = { project: '', topics: [] };
588
+ try {
589
+ var dir = process.cwd();
590
+ for (var depth = 0; depth < 6; depth += 1) {
591
+ var candidate = path.join(dir, '.empirical.json');
592
+ if (fs.existsSync(candidate)) {
593
+ var parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
594
+ config.project = String(parsed && parsed.project ? parsed.project : '').trim().toLowerCase();
595
+ if (parsed && Array.isArray(parsed.topics)) {
596
+ config.topics = parsed.topics.map(function (t) { return String(t || '').trim(); }).filter(Boolean);
597
+ }
598
+ appendLog('loaded project config from ' + candidate);
599
+ return config;
600
+ }
601
+ var parent = path.dirname(dir);
602
+ if (!parent || parent === dir) { break; }
603
+ dir = parent;
604
+ }
605
+ } catch (e) {
606
+ appendLog('project config unreadable, continuing without it: ' + e.message);
607
+ }
608
+ return config;
609
+ }
408
610
 
409
- if (!result || result.status !== 0 || !result.stdout) {
410
- appendLog('memory query failed or returned no stdout (status=' + (result && result.status) + ', stderr=' + (result && result.stderr ? String(result.stderr).slice(0, 300) : '') + ')');
411
- console.log('{}');
412
- process.exit(0);
611
+ // requireAll selects --tags-all (AND) over --tags (OR). This matters: --tags is an OR match, so
612
+ // asking for 'empirical_v2,todo' returns every project's todos, which put a wellnessonbalance
613
+ // item into an empirical_v2 session during testing on 2026-09-16.
614
+ function fetchMemories(matchText, tags, topK, requireAll) {
615
+ var args = ['memory', 'query', '--match', matchText, '--top-k', String(topK)];
616
+ if (tags) { args.push(requireAll ? '--tags-all' : '--tags', tags); }
617
+ var queryResult = runMemoryQuery(args);
618
+ if (!queryResult || queryResult.status !== 0 || !queryResult.stdout) {
619
+ appendLog('memory query failed (tags=' + (tags || 'none') + ', status=' + (queryResult && queryResult.status) + ', stderr=' + (queryResult && queryResult.stderr ? String(queryResult.stderr).slice(0, 200) : '') + ')');
620
+ return [];
621
+ }
622
+ try {
623
+ var parsed = JSON.parse(queryResult.stdout);
624
+ return Array.isArray(parsed) ? parsed : [];
625
+ } catch (e) {
626
+ appendLog('failed to parse memory query stdout as JSON: ' + e.message);
627
+ return [];
628
+ }
413
629
  }
414
630
 
415
- var results;
416
- try {
417
- results = JSON.parse(result.stdout);
418
- } catch (e) {
419
- appendLog('failed to parse memory query stdout as JSON: ' + e.message);
420
- console.log('{}');
421
- process.exit(0);
631
+ var fileConfig = readProjectConfig();
632
+ var envTag = String(process.env.EMPIRICAL_PROJECT_TAG || '').trim().toLowerCase();
633
+ var envTopics = String(process.env.EMPIRICAL_TOPICS || '').split(',').map(function (t) { return t.trim(); }).filter(Boolean);
634
+
635
+ var tag = envTag || fileConfig.project;
636
+ var tagSource = envTag ? 'env' : (fileConfig.project ? 'config' : 'cwd');
637
+ if (!tag) {
638
+ try {
639
+ tag = String(path.basename(process.cwd() || '') || '').trim().toLowerCase();
640
+ } catch (e) {
641
+ tag = '';
642
+ }
422
643
  }
423
644
 
424
- if (!Array.isArray(results) || results.length === 0) {
645
+ var topics = envTopics.length ? envTopics : fileConfig.topics;
646
+ var contextMatch = topics.length
647
+ ? topics.join(', ')
648
+ : 'project context, conventions, recent decisions, user preferences';
649
+
650
+ appendLog('project tag=' + (tag || '(none)') + ' (source=' + tagSource + '), topics=' + (topics.length ? topics.join('|') : '(default)'));
651
+
652
+ // Scope every query to the project. Measured 2026-09-16: with no tag filter all 8 slots went to
653
+ // one unrelated project during a full session in a different repo, so the only automatic context
654
+ // rail in the product was spending every slot on the wrong thing. A tag filter also disables the
655
+ // server's agent-process-note exclusion, which is what makes a project's own scar tissue and
656
+ // work history reachable here at all.
657
+ //
658
+ // Open items get their OWN query instead of competing for rank. Tested 2026-09-16: a due-dated
659
+ // memory did not surface in the generic top-8 with or without a project filter, because dueness
660
+ // is not a semantic property. Anything that must be seen needs a reserved slot.
661
+ // AND, not OR: this must be THIS project's todos. No --status filter on purpose, because todos
662
+ // written without a data.status would silently vanish from the one place they get surfaced;
663
+ // the label tells the reader to confirm instead.
664
+ var todos = tag ? fetchMemories('open todo, next action, follow-up, due', tag + ',todo', 3, true) : [];
665
+ var context = tag ? fetchMemories(contextMatch, tag, 6) : [];
666
+
667
+ // A corpus whose tags do not match the resolved project must never end up worse off than before
668
+ // this change, so fall back to the original unscoped query.
669
+ if (context.length === 0) {
670
+ appendLog('project-scoped query returned nothing, falling back to unscoped');
671
+ context = fetchMemories(contextMatch, '', 8);
672
+ }
673
+
674
+ if (todos.length === 0 && context.length === 0) {
425
675
  appendLog('memory query returned zero results');
426
676
  console.log('{}');
427
677
  process.exit(0);
428
678
  }
429
679
 
430
- var lines = results.slice(0, 8).map(function (r) {
680
+ var openCount = todos.length;
681
+ var results = todos.concat(context);
682
+
683
+ // Summaries in a mature corpus run to thousands of characters. Injecting eight in full is a wall
684
+ // of text that gets skimmed past, which fails the same way as never injecting them at all:
685
+ // presence in context is not attention. Truncate hard; the agent can query for the rest.
686
+ var lines = results.slice(0, 9).map(function (r, i) {
431
687
  var category = r && r.category ? r.category : 'memory';
432
- var summary = r && r.summary ? r.summary : '';
433
- return '- [' + category + '] ' + summary;
688
+ var summary = String(r && r.summary ? r.summary : '');
689
+ summary = summary.split('\\n').join(' ').split('\\r').join(' ').split('\\t').join(' ').trim();
690
+ if (summary.length > 240) { summary = summary.slice(0, 237) + '...'; }
691
+ var prefix = i < openCount ? '- [OPEN ITEM, confirm status] ' : '- [' + category + '] ';
692
+ return prefix + summary;
434
693
  });
694
+ lines.push('- Before risky or repeated work, ask first: empirical memory query --match "<what you are about to do>" --tags scar-tissue');
435
695
 
436
696
  // Logged only - Copilot's sessionStart event has no field that surfaces this to the model.
437
697
  appendLog('memory query returned ' + results.length + ' result(s) (session-start has no output channel to inject them, logged only): ' + lines.join(' | '));
@@ -840,7 +1100,7 @@ function installClaudeHooks(pluginNames) {
840
1100
  for (const matcher of ['startup', 'resume', 'compact']) {
841
1101
  settings.hooks.SessionStart.push({
842
1102
  matcher,
843
- hooks: [{ type: 'command', command: buildHookCommand(sessionStartScriptPath), timeout: 15 }]
1103
+ hooks: [{ type: 'command', command: buildHookCommand(sessionStartScriptPath), timeout: 25 }]
844
1104
  });
845
1105
  }
846
1106
 
@@ -969,7 +1229,7 @@ function installCodexHooks(pluginNames) {
969
1229
  for (const matcher of ['startup', 'resume']) {
970
1230
  hooksFile.hooks.SessionStart.push({
971
1231
  matcher,
972
- hooks: [{ type: 'command', command: sessionStartCommand, commandWindows: sessionStartCommandWindows, timeout: 15 }]
1232
+ hooks: [{ type: 'command', command: sessionStartCommand, commandWindows: sessionStartCommandWindows, timeout: 25 }]
973
1233
  });
974
1234
  }
975
1235
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empirical-gauzza/cli",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
5
5
  "keywords": [
6
6
  "memory",