@empirical-gauzza/cli 1.0.20 → 1.0.21
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 +1 -1
- package/lib/installers/coreMemoryBlock.js +91 -91
- package/lib/installers/skill-variants/SKILL.both.md +154 -154
- package/lib/installers/skill-variants/SKILL.cli.md +150 -150
- package/lib/installers/skill-variants/SKILL.mcp.md +134 -134
- package/lib/installers/skillVariants.js +59 -59
- package/lib/memoryCommands.js +45 -10
- package/lib/skillPaths.js +25 -25
- package/package.json +1 -1
|
@@ -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
|
+
};
|
package/lib/memoryCommands.js
CHANGED
|
@@ -78,11 +78,13 @@ async function withCliAnalyticsContext(context, fn) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
81
|
+
async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters) {
|
|
82
82
|
const queryUrl = new URL('/api/v1/memories/query', baseUrl).toString();
|
|
83
|
+
const body = { query: match, topK: 5 };
|
|
84
|
+
if (filters && Object.keys(filters).length) body.filters = filters;
|
|
83
85
|
let response;
|
|
84
86
|
try {
|
|
85
|
-
response = await httpJson(queryUrl, 'POST',
|
|
87
|
+
response = await httpJson(queryUrl, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
86
88
|
} catch (error) {
|
|
87
89
|
return { found: false, reason: 'error', message: error.message };
|
|
88
90
|
}
|
|
@@ -103,6 +105,19 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
|
103
105
|
|
|
104
106
|
scored.sort((a, b) => b.score - a.score);
|
|
105
107
|
|
|
108
|
+
// A caller-supplied filter (e.g. --tags) that narrows the server-side result set to exactly
|
|
109
|
+
// one memory is already a stronger disambiguation signal than the semantic score - trust it
|
|
110
|
+
// outright rather than falling through to the score check below. This matters because Atlas
|
|
111
|
+
// vector search has a real async indexing lag: a memory created moments ago can come back
|
|
112
|
+
// with score 0 (unindexed, not "not this one") even when a tag filter already proves it is
|
|
113
|
+
// the only candidate. Fixes a real bug found 2026-08-25 where delete --match failed on a
|
|
114
|
+
// memory `query` found instantly, because delete had no way to trust a filtered, unambiguous
|
|
115
|
+
// result over a stale/zero semantic score.
|
|
116
|
+
if (filters && Object.keys(filters).length && results.length === 1) {
|
|
117
|
+
const only = scored[0];
|
|
118
|
+
return { found: true, memory: { id: only.id, summary: only.summary, score: only.score } };
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
const top = scored[0];
|
|
107
122
|
const second = scored[1];
|
|
108
123
|
|
|
@@ -117,6 +132,23 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
|
117
132
|
return { found: true, memory: { id: top.id, summary: top.summary, score: top.score } };
|
|
118
133
|
}
|
|
119
134
|
|
|
135
|
+
// --match resolution depends on Atlas vector search, which has a real async indexing lag:
|
|
136
|
+
// a memory created moments ago can be invisible to $vectorSearch even though it is already
|
|
137
|
+
// findable by tag. Letting --match commands pass --tags narrows the candidate pool directly
|
|
138
|
+
// instead of hoping semantic ranking surfaces the right one - the fix for a real bug found
|
|
139
|
+
// 2026-08-25 where delete --match returned unrelated score-0 candidates for a memory that
|
|
140
|
+
// `query` found instantly. Deliberately --tags (plural) only, not --category: `delete` already
|
|
141
|
+
// uses singular --tag/--category to mean "bulk delete everything in this category", so adding
|
|
142
|
+
// --category here would silently reroute a --match delete into a bulk delete instead of
|
|
143
|
+
// narrowing it - --tags never collides with that bulk-mode gate.
|
|
144
|
+
function parseMatchFilters(args) {
|
|
145
|
+
const tagsRaw = parseOption(args, '--tags', '');
|
|
146
|
+
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
|
147
|
+
const filters = {};
|
|
148
|
+
if (tags.length) filters.tags = tags;
|
|
149
|
+
return filters;
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
function printMemoryResolveError(result) {
|
|
121
153
|
if (result.reason === 'no_results') {
|
|
122
154
|
warn(JSON.stringify({ error: true, code: 'no_results', message: 'No memories matched the query.' }));
|
|
@@ -340,12 +372,12 @@ async function runMemoryNote(args) {
|
|
|
340
372
|
const minScore = Number(parseOption(args, '--min-score', '0.35'));
|
|
341
373
|
|
|
342
374
|
if (!text) {
|
|
343
|
-
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]');
|
|
375
|
+
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--min-score 0.35]');
|
|
344
376
|
process.exit(1);
|
|
345
377
|
}
|
|
346
378
|
|
|
347
379
|
if (!match && !memoryId) {
|
|
348
|
-
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>)');
|
|
380
|
+
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>]');
|
|
349
381
|
process.exit(1);
|
|
350
382
|
}
|
|
351
383
|
|
|
@@ -354,7 +386,8 @@ async function runMemoryNote(args) {
|
|
|
354
386
|
|
|
355
387
|
let targetId = memoryId;
|
|
356
388
|
if (!targetId) {
|
|
357
|
-
const
|
|
389
|
+
const filters = parseMatchFilters(args);
|
|
390
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
358
391
|
if (!result.found) {
|
|
359
392
|
printMemoryResolveError(result);
|
|
360
393
|
process.exit(1);
|
|
@@ -383,12 +416,12 @@ async function runMemoryUpdate(args) {
|
|
|
383
416
|
const minScore = Number(parseOption(args, '--min-score', '0.35'));
|
|
384
417
|
|
|
385
418
|
if (!patchJson && !jsonFile) {
|
|
386
|
-
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
|
|
419
|
+
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
|
|
387
420
|
process.exit(1);
|
|
388
421
|
}
|
|
389
422
|
|
|
390
423
|
if (!match && !memoryId) {
|
|
391
|
-
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
|
|
424
|
+
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
|
|
392
425
|
process.exit(1);
|
|
393
426
|
}
|
|
394
427
|
|
|
@@ -414,7 +447,8 @@ async function runMemoryUpdate(args) {
|
|
|
414
447
|
|
|
415
448
|
let targetId = memoryId;
|
|
416
449
|
if (!targetId) {
|
|
417
|
-
const
|
|
450
|
+
const filters = parseMatchFilters(args);
|
|
451
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
418
452
|
if (!result.found) {
|
|
419
453
|
printMemoryResolveError(result);
|
|
420
454
|
process.exit(1);
|
|
@@ -484,7 +518,7 @@ async function runMemoryDelete(args) {
|
|
|
484
518
|
const confirm = parseFlag(args, '--confirm');
|
|
485
519
|
|
|
486
520
|
if (!match && !memoryId) {
|
|
487
|
-
warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]');
|
|
521
|
+
warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--confirm] [--min-score 0.35]');
|
|
488
522
|
warn(' or: empirical memory delete (--tag <t> | --category <c> | --all) --confirm');
|
|
489
523
|
process.exit(1);
|
|
490
524
|
}
|
|
@@ -496,7 +530,8 @@ async function runMemoryDelete(args) {
|
|
|
496
530
|
let targetSummary = '';
|
|
497
531
|
|
|
498
532
|
if (!targetId) {
|
|
499
|
-
const
|
|
533
|
+
const filters = parseMatchFilters(args);
|
|
534
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
500
535
|
if (!result.found) {
|
|
501
536
|
if (!confirm && (result.reason === 'ambiguous' || result.reason === 'low_score')) {
|
|
502
537
|
// Dry-run: return candidates info
|
package/lib/skillPaths.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Per-agent skill file path conventions, mirrored exactly from the hardcoded paths already used
|
|
4
|
-
// by installers/claude.js, installers/codex.js, installers/copilot.js (for the single
|
|
5
|
-
// empirical-memory skill) — parameterized by skill name instead of hardcoded to it.
|
|
6
|
-
//
|
|
7
|
-
// Split out of skillSync.js into its own dependency-free module so both skillSync.js and
|
|
8
|
-
// installers/nativePlugin.js (C-001's mode-aware Skill overwrite) can use the same path
|
|
9
|
-
// convention without creating a require cycle: skillSync.js -> installers/detect.js ->
|
|
10
|
-
// installers/claude.js|codex.js|copilot.js -> installers/nativePlugin.js would loop straight
|
|
11
|
-
// back into skillSync.js if nativePlugin.js required it directly.
|
|
12
|
-
const os = require('os');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
function skillFilePath(targetId, skillName) {
|
|
16
|
-
const home = os.homedir();
|
|
17
|
-
if (targetId === 'claude') return path.join(home, '.claude', 'skills', skillName, 'SKILL.md');
|
|
18
|
-
if (targetId === 'codex') return path.join(home, '.agents', 'skills', skillName, 'SKILL.md');
|
|
19
|
-
if (targetId === 'copilot') return path.join(home, '.copilot', 'skills', skillName, 'SKILL.md');
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = {
|
|
24
|
-
skillFilePath
|
|
25
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Per-agent skill file path conventions, mirrored exactly from the hardcoded paths already used
|
|
4
|
+
// by installers/claude.js, installers/codex.js, installers/copilot.js (for the single
|
|
5
|
+
// empirical-memory skill) — parameterized by skill name instead of hardcoded to it.
|
|
6
|
+
//
|
|
7
|
+
// Split out of skillSync.js into its own dependency-free module so both skillSync.js and
|
|
8
|
+
// installers/nativePlugin.js (C-001's mode-aware Skill overwrite) can use the same path
|
|
9
|
+
// convention without creating a require cycle: skillSync.js -> installers/detect.js ->
|
|
10
|
+
// installers/claude.js|codex.js|copilot.js -> installers/nativePlugin.js would loop straight
|
|
11
|
+
// back into skillSync.js if nativePlugin.js required it directly.
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
function skillFilePath(targetId, skillName) {
|
|
16
|
+
const home = os.homedir();
|
|
17
|
+
if (targetId === 'claude') return path.join(home, '.claude', 'skills', skillName, 'SKILL.md');
|
|
18
|
+
if (targetId === 'codex') return path.join(home, '.agents', 'skills', skillName, 'SKILL.md');
|
|
19
|
+
if (targetId === 'copilot') return path.join(home, '.copilot', 'skills', skillName, 'SKILL.md');
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
skillFilePath
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empirical-gauzza/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
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",
|