@empirical-gauzza/cli 1.0.20 → 1.0.22

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.
@@ -1,59 +1,59 @@
1
- 'use strict';
2
-
3
- // C-001 (docs/prd-cli-and-plugin-fixes.md): mode-aware empirical-memory Skill content.
4
- //
5
- // The plugin marketplace (mattgauzza/empirical-memory-plugin) is the source of truth for Skill
6
- // content, but its manifest schema (see meta/manifests.json / build-manifests.mjs in that repo)
7
- // has no field that selects among sibling SKILL.*.md files at install time -- every host's native
8
- // plugin mechanism resolves a plugin's skill from the literal filename `SKILL.md`. So the
9
- // marketplace always ships the "both" variant as SKILL.md, and this CLI is what actually makes
10
- // the user's chosen integration mode matter: it vendors its own copy of all three variants and
11
- // overwrites the on-disk SKILL.md for each host after a native plugin install/update (see
12
- // nativePlugin.js's applySkillModeVariant) and after a Kit sync (see skillSync.js).
13
- //
14
- // These files are copied from, and must be kept in sync with,
15
- // M:\Projects\empirical-memory-plugin\skills\empirical-memory\SKILL.{mcp,cli,both}.md. There is no
16
- // automated sync between the two repos; a human updates both when skill content changes.
17
- const fs = require('fs');
18
- const path = require('path');
19
-
20
- const VARIANTS_DIR = path.join(__dirname, 'skill-variants');
21
-
22
- const VARIANT_FILES = {
23
- mcp: 'SKILL.mcp.md',
24
- cli: 'SKILL.cli.md',
25
- both: 'SKILL.both.md'
26
- };
27
-
28
- // installWizard.js's `integrationMode`: 1 = MCP, 2 = CLI-direct, 3 = both.
29
- function variantNameForIntegrationMode(integrationMode) {
30
- if (integrationMode === 2) return 'cli';
31
- if (integrationMode === 3) return 'both';
32
- return 'mcp';
33
- }
34
-
35
- // Returns the variant's markdown content, or null if the vendored file is missing (e.g. a stale
36
- // CLI release published before a variant existed). Never throws -- callers must treat a missing
37
- // variant as "leave whatever is already installed" per the PRD's own risk mitigation, not as a
38
- // fatal error.
39
- function readSkillVariantContent(variantName) {
40
- const fileName = VARIANT_FILES[variantName];
41
- if (!fileName) return null;
42
- const filePath = path.join(VARIANTS_DIR, fileName);
43
- try {
44
- return fs.readFileSync(filePath, 'utf8');
45
- } catch (_error) {
46
- return null;
47
- }
48
- }
49
-
50
- function readSkillVariantForIntegrationMode(integrationMode) {
51
- return readSkillVariantContent(variantNameForIntegrationMode(integrationMode));
52
- }
53
-
54
- module.exports = {
55
- VARIANT_FILES,
56
- variantNameForIntegrationMode,
57
- readSkillVariantContent,
58
- readSkillVariantForIntegrationMode
59
- };
1
+ 'use strict';
2
+
3
+ // C-001 (docs/prd-cli-and-plugin-fixes.md): mode-aware empirical-memory Skill content.
4
+ //
5
+ // The plugin marketplace (mattgauzza/empirical-memory-plugin) is the source of truth for Skill
6
+ // content, but its manifest schema (see meta/manifests.json / build-manifests.mjs in that repo)
7
+ // has no field that selects among sibling SKILL.*.md files at install time -- every host's native
8
+ // plugin mechanism resolves a plugin's skill from the literal filename `SKILL.md`. So the
9
+ // marketplace always ships the "both" variant as SKILL.md, and this CLI is what actually makes
10
+ // the user's chosen integration mode matter: it vendors its own copy of all three variants and
11
+ // overwrites the on-disk SKILL.md for each host after a native plugin install/update (see
12
+ // nativePlugin.js's applySkillModeVariant) and after a Kit sync (see skillSync.js).
13
+ //
14
+ // These files are copied from, and must be kept in sync with,
15
+ // M:\Projects\empirical-memory-plugin\skills\empirical-memory\SKILL.{mcp,cli,both}.md. There is no
16
+ // automated sync between the two repos; a human updates both when skill content changes.
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const VARIANTS_DIR = path.join(__dirname, 'skill-variants');
21
+
22
+ const VARIANT_FILES = {
23
+ mcp: 'SKILL.mcp.md',
24
+ cli: 'SKILL.cli.md',
25
+ both: 'SKILL.both.md'
26
+ };
27
+
28
+ // installWizard.js's `integrationMode`: 1 = MCP, 2 = CLI-direct, 3 = both.
29
+ function variantNameForIntegrationMode(integrationMode) {
30
+ if (integrationMode === 2) return 'cli';
31
+ if (integrationMode === 3) return 'both';
32
+ return 'mcp';
33
+ }
34
+
35
+ // Returns the variant's markdown content, or null if the vendored file is missing (e.g. a stale
36
+ // CLI release published before a variant existed). Never throws -- callers must treat a missing
37
+ // variant as "leave whatever is already installed" per the PRD's own risk mitigation, not as a
38
+ // fatal error.
39
+ function readSkillVariantContent(variantName) {
40
+ const fileName = VARIANT_FILES[variantName];
41
+ if (!fileName) return null;
42
+ const filePath = path.join(VARIANTS_DIR, fileName);
43
+ try {
44
+ return fs.readFileSync(filePath, 'utf8');
45
+ } catch (_error) {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function readSkillVariantForIntegrationMode(integrationMode) {
51
+ return readSkillVariantContent(variantNameForIntegrationMode(integrationMode));
52
+ }
53
+
54
+ module.exports = {
55
+ VARIANT_FILES,
56
+ variantNameForIntegrationMode,
57
+ readSkillVariantContent,
58
+ readSkillVariantForIntegrationMode
59
+ };
@@ -55,6 +55,7 @@ const MEMORY_SUBCOMMAND_USAGE = {
55
55
  merge: 'Usage: empirical memory merge --canonical-id <id> --duplicate-id <id> [--no-merge-tags] [--no-merge-relationships]',
56
56
  export: 'Usage: empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]',
57
57
  import: 'Usage: empirical memory import <file> [--dry-run]',
58
+ 'bulk-import': 'Usage: empirical memory bulk-import <file> [--dry-run]\nFile format: { "memories": [{ "localKey": "...", "category": "...", "nodeType": "...", "summary": "...", "data": {...} }], "relationships": [{ "fromLocalKey": "...", "toLocalKey": "...", "type": "..." }] }',
58
59
  stats: 'Usage: empirical memory stats [--format table]',
59
60
  compact: 'Usage: empirical memory compact [--apply] [--category <cat>]'
60
61
  };
@@ -78,11 +79,13 @@ async function withCliAnalyticsContext(context, fn) {
78
79
  }
79
80
  }
80
81
 
81
- async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
82
+ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters) {
82
83
  const queryUrl = new URL('/api/v1/memories/query', baseUrl).toString();
84
+ const body = { query: match, topK: 5 };
85
+ if (filters && Object.keys(filters).length) body.filters = filters;
83
86
  let response;
84
87
  try {
85
- response = await httpJson(queryUrl, 'POST', { query: match, topK: 5 }, buildApiAuthHeaders(accessToken));
88
+ response = await httpJson(queryUrl, 'POST', body, buildApiAuthHeaders(accessToken));
86
89
  } catch (error) {
87
90
  return { found: false, reason: 'error', message: error.message };
88
91
  }
@@ -103,6 +106,19 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
103
106
 
104
107
  scored.sort((a, b) => b.score - a.score);
105
108
 
109
+ // A caller-supplied filter (e.g. --tags) that narrows the server-side result set to exactly
110
+ // one memory is already a stronger disambiguation signal than the semantic score - trust it
111
+ // outright rather than falling through to the score check below. This matters because Atlas
112
+ // vector search has a real async indexing lag: a memory created moments ago can come back
113
+ // with score 0 (unindexed, not "not this one") even when a tag filter already proves it is
114
+ // the only candidate. Fixes a real bug found 2026-08-25 where delete --match failed on a
115
+ // memory `query` found instantly, because delete had no way to trust a filtered, unambiguous
116
+ // result over a stale/zero semantic score.
117
+ if (filters && Object.keys(filters).length && results.length === 1) {
118
+ const only = scored[0];
119
+ return { found: true, memory: { id: only.id, summary: only.summary, score: only.score } };
120
+ }
121
+
106
122
  const top = scored[0];
107
123
  const second = scored[1];
108
124
 
@@ -117,6 +133,23 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
117
133
  return { found: true, memory: { id: top.id, summary: top.summary, score: top.score } };
118
134
  }
119
135
 
136
+ // --match resolution depends on Atlas vector search, which has a real async indexing lag:
137
+ // a memory created moments ago can be invisible to $vectorSearch even though it is already
138
+ // findable by tag. Letting --match commands pass --tags narrows the candidate pool directly
139
+ // instead of hoping semantic ranking surfaces the right one - the fix for a real bug found
140
+ // 2026-08-25 where delete --match returned unrelated score-0 candidates for a memory that
141
+ // `query` found instantly. Deliberately --tags (plural) only, not --category: `delete` already
142
+ // uses singular --tag/--category to mean "bulk delete everything in this category", so adding
143
+ // --category here would silently reroute a --match delete into a bulk delete instead of
144
+ // narrowing it - --tags never collides with that bulk-mode gate.
145
+ function parseMatchFilters(args) {
146
+ const tagsRaw = parseOption(args, '--tags', '');
147
+ const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
148
+ const filters = {};
149
+ if (tags.length) filters.tags = tags;
150
+ return filters;
151
+ }
152
+
120
153
  function printMemoryResolveError(result) {
121
154
  if (result.reason === 'no_results') {
122
155
  warn(JSON.stringify({ error: true, code: 'no_results', message: 'No memories matched the query.' }));
@@ -340,12 +373,12 @@ async function runMemoryNote(args) {
340
373
  const minScore = Number(parseOption(args, '--min-score', '0.35'));
341
374
 
342
375
  if (!text) {
343
- warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]');
376
+ warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--min-score 0.35]');
344
377
  process.exit(1);
345
378
  }
346
379
 
347
380
  if (!match && !memoryId) {
348
- warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>)');
381
+ warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>]');
349
382
  process.exit(1);
350
383
  }
351
384
 
@@ -354,7 +387,8 @@ async function runMemoryNote(args) {
354
387
 
355
388
  let targetId = memoryId;
356
389
  if (!targetId) {
357
- const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl);
390
+ const filters = parseMatchFilters(args);
391
+ const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
358
392
  if (!result.found) {
359
393
  printMemoryResolveError(result);
360
394
  process.exit(1);
@@ -383,12 +417,12 @@ async function runMemoryUpdate(args) {
383
417
  const minScore = Number(parseOption(args, '--min-score', '0.35'));
384
418
 
385
419
  if (!patchJson && !jsonFile) {
386
- warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
420
+ warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
387
421
  process.exit(1);
388
422
  }
389
423
 
390
424
  if (!match && !memoryId) {
391
- warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
425
+ warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
392
426
  process.exit(1);
393
427
  }
394
428
 
@@ -414,7 +448,8 @@ async function runMemoryUpdate(args) {
414
448
 
415
449
  let targetId = memoryId;
416
450
  if (!targetId) {
417
- const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl);
451
+ const filters = parseMatchFilters(args);
452
+ const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
418
453
  if (!result.found) {
419
454
  printMemoryResolveError(result);
420
455
  process.exit(1);
@@ -484,7 +519,7 @@ async function runMemoryDelete(args) {
484
519
  const confirm = parseFlag(args, '--confirm');
485
520
 
486
521
  if (!match && !memoryId) {
487
- warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]');
522
+ warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--confirm] [--min-score 0.35]');
488
523
  warn(' or: empirical memory delete (--tag <t> | --category <c> | --all) --confirm');
489
524
  process.exit(1);
490
525
  }
@@ -496,7 +531,8 @@ async function runMemoryDelete(args) {
496
531
  let targetSummary = '';
497
532
 
498
533
  if (!targetId) {
499
- const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl);
534
+ const filters = parseMatchFilters(args);
535
+ const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
500
536
  if (!result.found) {
501
537
  if (!confirm && (result.reason === 'ambiguous' || result.reason === 'low_score')) {
502
538
  // Dry-run: return candidates info
@@ -727,6 +763,53 @@ async function runMemoryImport(args) {
727
763
  log(JSON.stringify({ imported, failed, total: memories.length, errors: errors.slice(0, 20) }));
728
764
  }
729
765
 
766
+ async function runMemoryBulkImport(args) {
767
+ const filePath = args.find((a) => a && !a.startsWith('--')) || '';
768
+ const dryRun = parseFlag(args, '--dry-run');
769
+
770
+ if (!filePath) {
771
+ warn('Usage: empirical memory bulk-import <file> [--dry-run]');
772
+ process.exit(1);
773
+ }
774
+
775
+ let plan;
776
+ try {
777
+ plan = JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf8'));
778
+ } catch (error) {
779
+ warn(JSON.stringify({ error: true, code: 'json_file_error', message: error.message }));
780
+ process.exit(1);
781
+ }
782
+
783
+ const memories = Array.isArray(plan && plan.memories) ? plan.memories : null;
784
+ if (!memories) {
785
+ warn(JSON.stringify({
786
+ error: true,
787
+ code: 'invalid_format',
788
+ message: 'File must have a top-level "memories" array. See `empirical memory bulk-import --help`.'
789
+ }));
790
+ process.exit(1);
791
+ }
792
+ const relationships = Array.isArray(plan.relationships) ? plan.relationships : [];
793
+
794
+ if (dryRun) {
795
+ log(JSON.stringify({ dryRun: true, wouldImportMemories: memories.length, wouldImportRelationships: relationships.length }));
796
+ return;
797
+ }
798
+
799
+ const accessToken = requireAccessToken();
800
+ const baseUrl = resolveApiBaseUrl();
801
+ const bulkUrl = new URL('/api/v1/memories/bulk', baseUrl).toString();
802
+
803
+ try {
804
+ const response = await httpJson(bulkUrl, 'POST', { memories, relationships }, buildApiAuthHeaders(accessToken));
805
+ const data = (response && response.data) ? response.data : response;
806
+ log(JSON.stringify(data));
807
+ } catch (error) {
808
+ printApiError(error);
809
+ process.exit(1);
810
+ }
811
+ }
812
+
730
813
  async function runMemoryStats(args) {
731
814
  const format = parseOption(args, '--format', 'json');
732
815
 
@@ -807,6 +890,7 @@ async function runMemoryCommand(subArgs) {
807
890
  if (sub === 'merge') return runMemoryMerge(rest);
808
891
  if (sub === 'export') return runMemoryExport(rest);
809
892
  if (sub === 'import') return runMemoryImport(rest);
893
+ if (sub === 'bulk-import') return runMemoryBulkImport(rest);
810
894
  if (sub === 'stats') return runMemoryStats(rest);
811
895
  if (sub === 'compact') return runMemoryCompact(rest);
812
896
 
@@ -832,6 +916,7 @@ module.exports = {
832
916
  runMemoryMerge,
833
917
  runMemoryExport,
834
918
  runMemoryImport,
919
+ runMemoryBulkImport,
835
920
  runMemoryStats,
836
921
  runMemoryCompact,
837
922
  runMemoryDeleteBulk,