@empirical-gauzza/cli 1.0.27 → 1.0.28
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 +7 -1
- package/lib/alertBanner.js +44 -0
- package/lib/alertCommands.js +62 -0
- package/lib/constants.js +39 -0
- package/lib/http.js +192 -187
- package/lib/installers/hooks.js +1095 -1075
- package/lib/installers/skill-variants/SKILL.both.md +24 -0
- package/lib/installers/skill-variants/SKILL.cli.md +21 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +22 -0
- package/lib/memoryCommands.js +86 -14
- package/package.json +1 -1
|
@@ -48,6 +48,8 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
48
48
|
Add `--category` or `--tags` when known. Omit `--pretty` for routine recall because graph-rich
|
|
49
49
|
results can be large. A slow response alone is API latency, not an outage; report an outage
|
|
50
50
|
only when the CLI returns an actual error, and use `empirical doctor` to verify connectivity.
|
|
51
|
+
When the user asks for every record of a topic, add `--path list`; when they ask what a
|
|
52
|
+
memory connects to, add `--path graph`.
|
|
51
53
|
|
|
52
54
|
3. Scope recall to the project you are in. Derive a project tag from the repository name
|
|
53
55
|
(`git rev-parse --show-toplevel`, then the folder name) or, outside a repo, the working
|
|
@@ -61,6 +63,21 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
61
63
|
doc memory`) over a match query. For exhaustive topic retrieval, use the topic as a tag and
|
|
62
64
|
follow pagination.
|
|
63
65
|
|
|
66
|
+
5. A filtered query (category, tags, nodeType) that returns nothing is not proof the memory
|
|
67
|
+
doesn't exist — it only proves nothing matched that exact filter combination. A memory's
|
|
68
|
+
real category or tag convention often does not match how you or the user would naturally
|
|
69
|
+
describe it in conversation (e.g. described as "an idea" but stored under a different
|
|
70
|
+
category; tagged with a project's short name where you tried its full one). Before
|
|
71
|
+
concluding a memory doesn't exist, retry at least once with the filter relaxed or removed —
|
|
72
|
+
category dropped, tags loosened to an OR match, or a plain unfiltered query — rather than
|
|
73
|
+
only varying the search phrase.
|
|
74
|
+
|
|
75
|
+
6. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch,
|
|
76
|
+
a stale tag, a wrong category), stop there. Report that cause and move on — do not generalize
|
|
77
|
+
one miss into a theory about the memory system being broken and then go build measurement or
|
|
78
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining
|
|
79
|
+
a systemic explanation, not after.
|
|
80
|
+
|
|
64
81
|
## Follow the current user policy
|
|
65
82
|
|
|
66
83
|
The installer questionnaire configures a dynamic Empirical policy. It is user-scoped and may
|
|
@@ -113,6 +130,13 @@ Two rules that are not optional:
|
|
|
113
130
|
Both dedupe against near-duplicate existing memories and auto-link related ones by default; pass
|
|
114
131
|
`--no-dedupe` / `--no-auto-link` on the CLI only when you deliberately want to skip one of those.
|
|
115
132
|
|
|
133
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
134
|
+
worth keeping, record each as its own call rather than folding them into one large memory.
|
|
135
|
+
`record_graph_memory` (and `empirical memory record`) can and should be called multiple times
|
|
136
|
+
in the same turn — that is expected usage, not overhead to be minimized. A compound memory is
|
|
137
|
+
harder to find later (a query matching one idea in it won't surface the others as well) and
|
|
138
|
+
harder to link precisely into the graph than several atomic ones.
|
|
139
|
+
|
|
116
140
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
117
141
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
118
142
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
|
@@ -44,6 +44,8 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
44
44
|
Add `--category` or `--tags` when known. Omit `--pretty` for routine recall because graph-rich
|
|
45
45
|
results can be large. Use `empirical memory list --recent` (see `empirical doc memory` for the
|
|
46
46
|
exact flag) for "recent/latest/today" requests instead of a match query.
|
|
47
|
+
When the user asks for every record of a topic, add `--path list`; when they ask what a
|
|
48
|
+
memory connects to, add `--path graph`.
|
|
47
49
|
3. Scope recall to the project you are in. Derive a project tag from the repository name
|
|
48
50
|
(`git rev-parse --show-toplevel`, then the folder name) or, outside a repo, the working
|
|
49
51
|
directory name, lowercased with spaces as hyphens. Add `--tags <project>` to every
|
|
@@ -52,6 +54,18 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
52
54
|
facts about this one.
|
|
53
55
|
4. A slow response alone is API latency, not an outage; report an outage only when the CLI returns
|
|
54
56
|
an actual error, and use `empirical doctor` to verify connectivity.
|
|
57
|
+
5. A filtered query (`--category`, `--tags`, `--tags-all`, `--status`) that returns nothing is not
|
|
58
|
+
proof the memory doesn't exist — it only proves nothing matched that exact filter combination.
|
|
59
|
+
A memory's real category or tag convention often does not match how you or the user would
|
|
60
|
+
naturally describe it in conversation (e.g. described as "an idea" but stored under a different
|
|
61
|
+
category; tagged with a project's short name where you tried its full one). Before concluding a
|
|
62
|
+
memory doesn't exist, retry at least once with the filter relaxed or removed — category dropped,
|
|
63
|
+
tags loosened, or a plain unfiltered `--match` — rather than only varying the search phrase.
|
|
64
|
+
6. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch, a
|
|
65
|
+
stale tag, a wrong category), stop there. Report that cause and move on — do not generalize one
|
|
66
|
+
miss into a theory about the memory system being broken and then go build measurement or
|
|
67
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining a
|
|
68
|
+
systemic explanation, not after.
|
|
55
69
|
|
|
56
70
|
## Follow the current user policy
|
|
57
71
|
|
|
@@ -105,6 +119,13 @@ Two rules that are not optional:
|
|
|
105
119
|
first. Pass `--no-dedupe` or `--no-auto-link` only when you specifically want to bypass one of
|
|
106
120
|
those, which should be rare.
|
|
107
121
|
|
|
122
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
123
|
+
worth keeping, record each with its own `empirical memory record` call rather than folding them
|
|
124
|
+
into one large memory. Calling it multiple times in the same turn is expected usage, not
|
|
125
|
+
overhead to be minimized. A compound memory is harder to find later (a query matching one idea
|
|
126
|
+
in it won't surface the others as well) and harder to link precisely into the graph than several
|
|
127
|
+
atomic ones.
|
|
128
|
+
|
|
108
129
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
109
130
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
110
131
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
|
@@ -39,9 +39,24 @@ If you are not sure whether a task qualifies, treat it as if it does.
|
|
|
39
39
|
`nodeType`, and `monthKey` when they improve recall. For "recent/latest/today" requests, prefer
|
|
40
40
|
`list_memories` instead — it is the tool built for that shape of request. For exhaustive topic
|
|
41
41
|
retrieval, use the topic as a tag and follow pagination.
|
|
42
|
+
When the user asks for every record of a topic use `list_memories` with that topic as a tag,
|
|
43
|
+
and when they ask what a memory connects to use `get_memory_neighbors`.
|
|
42
44
|
5. A slow response alone is API latency, not an outage; report an outage only when a tool call
|
|
43
45
|
returns an actual error, and use `get_empirical_policy` or a retry to verify connectivity before
|
|
44
46
|
escalating.
|
|
47
|
+
6. A filtered `query_memories` call (`category`, `tags`, `nodeType`) that returns nothing is not
|
|
48
|
+
proof the memory doesn't exist — it only proves nothing matched that exact filter combination.
|
|
49
|
+
A memory's real category or tag convention often does not match how you or the user would
|
|
50
|
+
naturally describe it in conversation (e.g. described as "an idea" but stored under a
|
|
51
|
+
different category; tagged with a project's short name where you tried its full one). Before
|
|
52
|
+
concluding a memory doesn't exist, retry at least once with the filter relaxed or removed —
|
|
53
|
+
category dropped, tags loosened to an OR match, or a plain unfiltered query — rather than only
|
|
54
|
+
varying the search phrase.
|
|
55
|
+
7. If a specific recall miss turns out to have a narrow, demonstrable cause (a filter mismatch, a
|
|
56
|
+
stale tag, a wrong category), stop there. Report that cause and move on — do not generalize one
|
|
57
|
+
miss into a theory about the memory system being broken and then go build measurement or
|
|
58
|
+
debugging work around the theory. Confirm the narrow cause is insufficient before entertaining a
|
|
59
|
+
systemic explanation, not after.
|
|
45
60
|
|
|
46
61
|
## Follow the current user policy
|
|
47
62
|
|
|
@@ -82,6 +97,13 @@ Two rules that are not optional:
|
|
|
82
97
|
with the `record_graph_memory` MCP tool. It runs write-intent classification, dedupe, and
|
|
83
98
|
auto-linking automatically — you do not need to check for duplicates yourself first.
|
|
84
99
|
|
|
100
|
+
**One idea per memory, not a bundle.** When a turn produces several distinct facts or ideas
|
|
101
|
+
worth keeping, record each with its own `record_graph_memory` call rather than folding them
|
|
102
|
+
into one large memory. Calling it multiple times in the same turn is expected usage, not
|
|
103
|
+
overhead to be minimized. A compound memory is harder to find later (a query matching one idea
|
|
104
|
+
in it won't surface the others as well) and harder to link precisely into the graph than several
|
|
105
|
+
atomic ones.
|
|
106
|
+
|
|
85
107
|
**Record facts about the USER, not about your own work.** You are a scribe here. "Matt prefers
|
|
86
108
|
short status updates" is a memory; "refactored the cluster selector, tests pass" is your diary
|
|
87
109
|
and belongs in the work-history skill, tagged and with `mass: 1`. The distinction is the
|
package/lib/memoryCommands.js
CHANGED
|
@@ -6,7 +6,7 @@ const { log, warn } = require('./ui');
|
|
|
6
6
|
const { httpJson, printApiError } = require('./http');
|
|
7
7
|
const { parseOption, parseFlag } = require('./env');
|
|
8
8
|
const { requireAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
|
|
9
|
-
const { MEMORY_CATEGORIES, DOC_TOPICS } = require('./constants');
|
|
9
|
+
const { MEMORY_CATEGORIES, NODE_TYPES, DOC_TOPICS } = require('./constants');
|
|
10
10
|
const { withSelectedWorkspaceContext } = require('./workspaceContext');
|
|
11
11
|
|
|
12
12
|
// ─── Table rendering (--format table) ─────────────────────────────────────────
|
|
@@ -34,17 +34,29 @@ function renderMemoryResultsTable(items) {
|
|
|
34
34
|
id: m.id || m._id || '',
|
|
35
35
|
category: m.category || '',
|
|
36
36
|
summary: truncateForTable(m.summary, 60),
|
|
37
|
-
|
|
37
|
+
// The recall front door emits `score` (toQueryRow plus score in mcp/tools/memory.ts); the
|
|
38
|
+
// older /query route emitted rankScore/similarityScore. Read all three so the column is not
|
|
39
|
+
// blank against a real /recall row. On the list pathways score is deliberately absent, and
|
|
40
|
+
// an empty cell is the honest rendering of "this pathway does not rank".
|
|
41
|
+
score: m.score != null ? m.score : (m.rankScore != null ? m.rankScore : (m.similarityScore != null ? m.similarityScore : ''))
|
|
38
42
|
}));
|
|
39
43
|
return renderTable(rows, ['id', 'category', 'summary', 'score']);
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
// The retrieval pathways `memory query --path` may pin. Same four names the server's shared
|
|
47
|
+
// normalizer accepts (packages/server/src/services/recallParams.service.ts). Omitting --path
|
|
48
|
+
// leaves the classifier deciding, exactly as before.
|
|
49
|
+
const RECALL_PATHS = ['semantic', 'list', 'graph', 'hebbian'];
|
|
50
|
+
const RECALL_HOPS = [1, 2];
|
|
51
|
+
// Hops only mean something on a pathway that walks relationships.
|
|
52
|
+
const RECALL_HOP_PATHS = ['graph', 'hebbian'];
|
|
53
|
+
|
|
42
54
|
// ─── Scoped `--help` (C-005 item 5) ───────────────────────────────────────────
|
|
43
55
|
// `empirical memory --help` / `empirical memory <sub> --help` print scoped usage instead of
|
|
44
56
|
// the 90-line global help wall bin/empirical.js otherwise short-circuits every --help to.
|
|
45
57
|
|
|
46
58
|
const MEMORY_SUBCOMMAND_USAGE = {
|
|
47
|
-
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--pretty] [--format table]',
|
|
59
|
+
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--path semantic|list|graph|hebbian] [--hops 1|2] [--pretty] [--format table]',
|
|
48
60
|
get: 'Usage: empirical memory get --memory-id <id> [--pretty]',
|
|
49
61
|
list: 'Usage: empirical memory list [--category <cat>] [--tags <t1,t2>] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]',
|
|
50
62
|
record: 'Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link] [--author-type user|agent]',
|
|
@@ -179,7 +191,7 @@ function printMemoryResolveError(result) {
|
|
|
179
191
|
async function runMemoryQuery(args) {
|
|
180
192
|
const match = parseOption(args, '--match', '');
|
|
181
193
|
if (!match) {
|
|
182
|
-
warn(
|
|
194
|
+
warn(MEMORY_SUBCOMMAND_USAGE.query);
|
|
183
195
|
process.exit(1);
|
|
184
196
|
}
|
|
185
197
|
|
|
@@ -197,29 +209,79 @@ async function runMemoryQuery(args) {
|
|
|
197
209
|
const pretty = parseFlag(args, '--pretty');
|
|
198
210
|
const format = parseOption(args, '--format', 'json');
|
|
199
211
|
|
|
212
|
+
// --path / --hops are validated HERE, where the value is decided, so a typo costs no round
|
|
213
|
+
// trip and the message names the four pathways. The server validates the same way (the shared
|
|
214
|
+
// normalizer in packages/server/src/services/recallParams.service.ts); this is a fast local
|
|
215
|
+
// echo of that contract, never a second, looser one.
|
|
216
|
+
const pathRaw = parseOption(args, '--path', '');
|
|
217
|
+
let path = '';
|
|
218
|
+
if (pathRaw) {
|
|
219
|
+
path = String(pathRaw).trim().toLowerCase();
|
|
220
|
+
if (!RECALL_PATHS.includes(path)) {
|
|
221
|
+
warn(`--path must be one of: ${RECALL_PATHS.join(', ')} (received ${JSON.stringify(pathRaw)})`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const hopsRaw = parseOption(args, '--hops', '');
|
|
227
|
+
let hops = null;
|
|
228
|
+
if (hopsRaw) {
|
|
229
|
+
const parsedHops = Number(hopsRaw);
|
|
230
|
+
if (!RECALL_HOPS.includes(parsedHops)) {
|
|
231
|
+
warn(`--hops must be 1 or 2 (received ${JSON.stringify(hopsRaw)})`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
// Silently ignoring --hops on a ranked or list pathway would look like it took effect.
|
|
235
|
+
if (!RECALL_HOP_PATHS.includes(path)) {
|
|
236
|
+
warn(`--hops only applies with --path graph or --path hebbian${path ? ` (received --path ${path})` : ' (no --path given)'}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
hops = parsedHops;
|
|
240
|
+
}
|
|
241
|
+
|
|
200
242
|
const accessToken = requireAccessToken();
|
|
201
243
|
const baseUrl = resolveApiBaseUrl();
|
|
202
|
-
|
|
244
|
+
// The recall front door: the same answer the MCP query_memories tool gives, which /query never
|
|
245
|
+
// gave (classifier routing, the list block, the exact-tag retry, agent-note exclusion,
|
|
246
|
+
// retrievalPath). It takes its parameters FLAT, matching the shared normalizer, not nested
|
|
247
|
+
// under `filters` the way the older /query route did. Send one shape only.
|
|
248
|
+
const queryUrl = new URL('/api/v1/memories/recall', baseUrl).toString();
|
|
203
249
|
|
|
204
250
|
const body = { query: match, topK };
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
208
|
-
if (
|
|
209
|
-
if (
|
|
210
|
-
if (
|
|
251
|
+
if (category) body.category = category;
|
|
252
|
+
if (tags.length) body.tags = tags;
|
|
253
|
+
if (tagsAll.length) body.tagsAll = tagsAll;
|
|
254
|
+
if (status.length) body.status = status;
|
|
255
|
+
if (path) body.path = path;
|
|
256
|
+
if (hops !== null) body.hops = hops;
|
|
211
257
|
|
|
212
258
|
let response;
|
|
213
259
|
try {
|
|
214
260
|
response = await httpJson(queryUrl, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
215
261
|
} catch (error) {
|
|
262
|
+
// A 404 here means the server predates the recall front door. Say that plainly instead of
|
|
263
|
+
// rerouting to /query: that fallback would quietly answer through a different routing path
|
|
264
|
+
// with different defaults (topK 10 against 3) and keep the old route alive forever.
|
|
265
|
+
if (error && error.statusCode === 404) {
|
|
266
|
+
warn(JSON.stringify({
|
|
267
|
+
error: true,
|
|
268
|
+
code: 'recall_route_missing',
|
|
269
|
+
message: 'This server does not serve /api/v1/memories/recall yet. Upgrade the Empirical server, or use an older CLI against it.'
|
|
270
|
+
}));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
216
273
|
printApiError(error);
|
|
217
274
|
process.exit(1);
|
|
218
275
|
}
|
|
219
276
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
277
|
+
// The front door answers with data.memories; the older /query route answered with data.results.
|
|
278
|
+
// Both are accepted so a CLI pointed at a server that predates /recall still prints rows.
|
|
279
|
+
const data = response && response.data ? response.data : null;
|
|
280
|
+
const results = (data && Array.isArray(data.memories))
|
|
281
|
+
? data.memories
|
|
282
|
+
: ((data && Array.isArray(data.results))
|
|
283
|
+
? data.results
|
|
284
|
+
: (Array.isArray(response) ? response : []));
|
|
223
285
|
|
|
224
286
|
if (format === 'table') {
|
|
225
287
|
log(renderMemoryResultsTable(results));
|
|
@@ -353,6 +415,7 @@ async function runMemoryRecord(args) {
|
|
|
353
415
|
if (!category || !summary) {
|
|
354
416
|
warn('Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link] [--author-type user|agent]');
|
|
355
417
|
warn('Valid categories: ' + MEMORY_CATEGORIES.join(', '));
|
|
418
|
+
warn('Valid node types: ' + NODE_TYPES.join(', '));
|
|
356
419
|
process.exit(1);
|
|
357
420
|
}
|
|
358
421
|
|
|
@@ -369,6 +432,15 @@ async function runMemoryRecord(args) {
|
|
|
369
432
|
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
|
370
433
|
const nodeType = parseOption(args, '--node-type', 'observation');
|
|
371
434
|
|
|
435
|
+
if (!NODE_TYPES.includes(nodeType)) {
|
|
436
|
+
warn(JSON.stringify({
|
|
437
|
+
error: true,
|
|
438
|
+
code: 'invalid_node_type',
|
|
439
|
+
message: `Invalid nodeType "${nodeType}". Valid: ${NODE_TYPES.join(', ')}`
|
|
440
|
+
}));
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
372
444
|
payload = { category, summary, nodeType, data: { text: String(summary) } };
|
|
373
445
|
if (tags.length) payload.tags = tags;
|
|
374
446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empirical-gauzza/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
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",
|