claude-mem-lite 2.69.0 → 2.71.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -3
- package/cli.mjs +1 -1
- package/hook-context.mjs +20 -16
- package/hook-handoff.mjs +25 -7
- package/hook-llm.mjs +95 -19
- package/hook-optimize.mjs +45 -7
- package/hook-precompact.mjs +44 -0
- package/hook.mjs +79 -19
- package/hooks/hooks.json +12 -0
- package/lib/deferred-work.mjs +171 -0
- package/lib/git-state.mjs +15 -0
- package/lib/import-jsonl.mjs +225 -0
- package/lib/scrub-record.mjs +63 -0
- package/lib/upgrade-banner.mjs +31 -0
- package/mem-cli.mjs +235 -12
- package/package.json +6 -1
- package/schema.mjs +33 -1
- package/server.mjs +132 -21
- package/source-files.mjs +17 -1
- package/tool-schemas.mjs +107 -4
package/server.mjs
CHANGED
|
@@ -11,9 +11,10 @@ import { resolveProject as _resolveProjectShared } from './project-utils.mjs';
|
|
|
11
11
|
import { ensureDb, DB_PATH, REGISTRY_DB_PATH } from './schema.mjs';
|
|
12
12
|
import { reRankWithContext, markSuperseded, autoBoostIfNeeded, runIdleCleanup, buildServerInstructions } from './server-internals.mjs';
|
|
13
13
|
import { searchObservationsHybrid, findFtsAnchor } from './search-engine.mjs';
|
|
14
|
+
import { scrubRecord } from './lib/scrub-record.mjs';
|
|
14
15
|
import { effectiveQuiet } from './hook-shared.mjs';
|
|
15
16
|
import { computeTier, TIER_CASE_SQL, tierSqlParams } from './tier.mjs';
|
|
16
|
-
import { memSearchSchema, memRecentSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema, memMaintainSchema, memOptimizeSchema, memUpdateSchema, memExportSchema, memRecallSchema, memFtsCheckSchema, memRegistrySchema, memBrowseSchema, memUseSchema, tools as TOOL_DEFS } from './tool-schemas.mjs';
|
|
17
|
+
import { memSearchSchema, memRecentSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema, memMaintainSchema, memOptimizeSchema, memUpdateSchema, memExportSchema, memRecallSchema, memFtsCheckSchema, memRegistrySchema, memBrowseSchema, memUseSchema, memDeferSchema, memDeferListSchema, memDeferDropSchema, tools as TOOL_DEFS } from './tool-schemas.mjs';
|
|
17
18
|
|
|
18
19
|
// Lookup helper: all user-facing tool descriptions live in tool-schemas.mjs
|
|
19
20
|
// (discouragement-style, Task 5). This keeps server.mjs from drifting.
|
|
@@ -30,6 +31,10 @@ import { ensureRegistryDb, upsertResource } from './registry.mjs';
|
|
|
30
31
|
import { searchResources } from './registry-retriever.mjs';
|
|
31
32
|
import { probeOtherSources as probeIdSources, parseIdToken, bucketIdTokens } from './lib/id-routing.mjs';
|
|
32
33
|
import { saveObservation } from './lib/save-observation.mjs';
|
|
34
|
+
import {
|
|
35
|
+
insertDeferred, listOpenWithOrdinal, dropDeferred,
|
|
36
|
+
resolveDeferredIds, closeDeferredItems,
|
|
37
|
+
} from './lib/deferred-work.mjs';
|
|
33
38
|
import { getVocabulary, rebuildVocabulary, _resetVocabCache, computeVector } from './tfidf.mjs';
|
|
34
39
|
import { createRequire } from 'module';
|
|
35
40
|
|
|
@@ -907,22 +912,121 @@ server.registerTool(
|
|
|
907
912
|
safeHandler(async (args) => {
|
|
908
913
|
if (args.project) args = { ...args, project: resolveProject(args.project) };
|
|
909
914
|
const project = args.project || inferProject();
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
915
|
+
|
|
916
|
+
let closesIds = null;
|
|
917
|
+
let result;
|
|
918
|
+
try {
|
|
919
|
+
result = db.transaction(() => {
|
|
920
|
+
const r = saveObservation(db, {
|
|
921
|
+
content: args.content,
|
|
922
|
+
title: args.title,
|
|
923
|
+
type: args.type || 'discovery',
|
|
924
|
+
importance: args.importance,
|
|
925
|
+
project,
|
|
926
|
+
files: args.files || [],
|
|
927
|
+
lesson_learned: args.lesson_learned,
|
|
928
|
+
});
|
|
929
|
+
if (r.kind === 'duplicate') return r; // dedup short-circuits BEFORE resolver — replay is idempotent
|
|
930
|
+
// Resolve INSIDE tx + after dedup check so duplicate replays don't throw on
|
|
931
|
+
// already-closed items. Mirrors mem-cli.mjs cmdSave shape.
|
|
932
|
+
if (args.closes_deferred && args.closes_deferred.length > 0) {
|
|
933
|
+
closesIds = resolveDeferredIds(db, project, args.closes_deferred);
|
|
934
|
+
closeDeferredItems(db, closesIds, r.id);
|
|
935
|
+
}
|
|
936
|
+
return r;
|
|
937
|
+
})();
|
|
938
|
+
} catch (e) {
|
|
939
|
+
if (args.closes_deferred && args.closes_deferred.length > 0) {
|
|
940
|
+
// Re-throw with a clearer prefix so MCP error response names the
|
|
941
|
+
// contract failure — gate on caller intent (args.closes_deferred) since
|
|
942
|
+
// closesIds is closure-scoped and may not have been assigned before throw.
|
|
943
|
+
throw new Error(`mem_save with closes_deferred failed: ${e.message}`, { cause: e });
|
|
944
|
+
}
|
|
945
|
+
throw e; // unwrapped — preserves original message + stack
|
|
946
|
+
}
|
|
919
947
|
|
|
920
948
|
if (result.kind === 'duplicate') {
|
|
921
949
|
return { content: [{ type: 'text', text: `Skipped: similar to existing #${result.existingId} in project "${project}". Use mem_get(ids=[${result.existingId}]) to review.` }] };
|
|
922
950
|
}
|
|
923
951
|
|
|
924
952
|
const lessonNote = result.lessonCaptured ? ` 💡lesson captured` : '';
|
|
925
|
-
|
|
953
|
+
const closedNote = closesIds && closesIds.length > 0
|
|
954
|
+
? ` Closed deferred: ${closesIds.map(i => `D#${i}`).join(', ')}.`
|
|
955
|
+
: '';
|
|
956
|
+
return { content: [{ type: 'text', text: `Saved as observation #${result.id} [${result.type}] in project "${project}".${lessonNote}${closedNote}` }] };
|
|
957
|
+
})
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
// ─── Tool: mem_defer ────────────────────────────────────────────────────────
|
|
961
|
+
|
|
962
|
+
server.registerTool(
|
|
963
|
+
'mem_defer',
|
|
964
|
+
{
|
|
965
|
+
description: descriptionOf('mem_defer'),
|
|
966
|
+
inputSchema: memDeferSchema,
|
|
967
|
+
},
|
|
968
|
+
safeHandler(async (args) => {
|
|
969
|
+
if (args.project) args = { ...args, project: resolveProject(args.project) };
|
|
970
|
+
const project = args.project || inferProject();
|
|
971
|
+
const r = insertDeferred(db, {
|
|
972
|
+
project,
|
|
973
|
+
title: args.title,
|
|
974
|
+
priority: args.priority ?? 2,
|
|
975
|
+
detail: args.detail ?? null,
|
|
976
|
+
files: args.files ?? null,
|
|
977
|
+
});
|
|
978
|
+
// Compute the ordinal for the freshly-inserted row so the response is
|
|
979
|
+
// immediately actionable ("ok, I deferred this as item 1").
|
|
980
|
+
const open = listOpenWithOrdinal(db, project, 50);
|
|
981
|
+
const ord = open.find(o => o.id === r.id)?.ordinal ?? null;
|
|
982
|
+
return { content: [{ type: 'text', text:
|
|
983
|
+
`Deferred as D#${r.id} (item ${ord ?? '?'}) in project "${project}" — surfaces in next SessionStart banner.` }] };
|
|
984
|
+
})
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
// ─── Tool: mem_defer_list ───────────────────────────────────────────────────
|
|
988
|
+
|
|
989
|
+
server.registerTool(
|
|
990
|
+
'mem_defer_list',
|
|
991
|
+
{
|
|
992
|
+
description: descriptionOf('mem_defer_list'),
|
|
993
|
+
inputSchema: memDeferListSchema,
|
|
994
|
+
},
|
|
995
|
+
safeHandler(async (args) => {
|
|
996
|
+
if (args.project) args = { ...args, project: resolveProject(args.project) };
|
|
997
|
+
const project = args.project || inferProject();
|
|
998
|
+
const list = listOpenWithOrdinal(db, project, args.limit ?? 10);
|
|
999
|
+
if (list.length === 0) {
|
|
1000
|
+
return { content: [{ type: 'text', text: `No open deferred items in project "${project}".` }] };
|
|
1001
|
+
}
|
|
1002
|
+
const lines = [`Open deferred items (project "${project}"):`];
|
|
1003
|
+
for (const r of list) {
|
|
1004
|
+
const pTag = r.priority === 3 ? '🔴' : r.priority === 1 ? '⚪' : '🟡';
|
|
1005
|
+
lines.push(`${r.ordinal}. ${pTag} [P${r.priority}] ${r.title} (D#${r.id})`);
|
|
1006
|
+
}
|
|
1007
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
1008
|
+
})
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
// ─── Tool: mem_defer_drop ───────────────────────────────────────────────────
|
|
1012
|
+
|
|
1013
|
+
server.registerTool(
|
|
1014
|
+
'mem_defer_drop',
|
|
1015
|
+
{
|
|
1016
|
+
description: descriptionOf('mem_defer_drop'),
|
|
1017
|
+
inputSchema: memDeferDropSchema,
|
|
1018
|
+
},
|
|
1019
|
+
safeHandler(async (args) => {
|
|
1020
|
+
if (args.project) args = { ...args, project: resolveProject(args.project) };
|
|
1021
|
+
const project = args.project || inferProject();
|
|
1022
|
+
// Resolve id (accept D#N or ordinal int) via resolveDeferredIds with a
|
|
1023
|
+
// single-element array — reuses the same project + status validation.
|
|
1024
|
+
const [realId] = resolveDeferredIds(db, project, [args.id]);
|
|
1025
|
+
const r = dropDeferred(db, realId, args.reason);
|
|
1026
|
+
if (r.changed === 0) {
|
|
1027
|
+
return { content: [{ type: 'text', text: `D#${realId} was not in 'open' status — drop is a no-op.` }] };
|
|
1028
|
+
}
|
|
1029
|
+
return { content: [{ type: 'text', text: `Dropped D#${realId} in project "${project}". Reason: ${args.reason}` }] };
|
|
926
1030
|
})
|
|
927
1031
|
);
|
|
928
1032
|
|
|
@@ -1145,8 +1249,11 @@ server.registerTool(
|
|
|
1145
1249
|
VALUES (?, ?, ?, ?, ?, 'active')
|
|
1146
1250
|
`).run(sessionId, sessionId, proj, now.toISOString(), now.getTime());
|
|
1147
1251
|
|
|
1252
|
+
// Defense-in-depth: source rows already scrubbed at original ingest,
|
|
1253
|
+
// but the new compressed narrative is constructed here and re-persisted.
|
|
1254
|
+
const safe = scrubRecord('observations', { text: narrative, title, narrative });
|
|
1148
1255
|
const summaryResult = insertSummary.run(
|
|
1149
|
-
sessionId, proj,
|
|
1256
|
+
sessionId, proj, safe.text, dominantType, safe.title, safe.narrative,
|
|
1150
1257
|
medianDate.toISOString(), medianEpoch
|
|
1151
1258
|
);
|
|
1152
1259
|
const summaryId = Number(summaryResult.lastInsertRowid);
|
|
@@ -2038,11 +2145,15 @@ server.registerTool(
|
|
|
2038
2145
|
);
|
|
2039
2146
|
|
|
2040
2147
|
// ─── Hidden tool filter ─────────────────────────────────────────────────────
|
|
2041
|
-
// All
|
|
2042
|
-
//
|
|
2043
|
-
//
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2148
|
+
// All tools are registered (so `tools/call <name>` still resolves for scripts
|
|
2149
|
+
// and direct MCP clients), but only the core tools appear in the `tools/list`
|
|
2150
|
+
// response. Hiding the maintenance/admin tools keeps Claude Code's startup
|
|
2151
|
+
// context small while preserving the contract that the plugin dogfoods (see
|
|
2152
|
+
// CLAUDE.md §Mem usage contract and adopt-content.mjs).
|
|
2153
|
+
// Surface counts as of v2.70.0: 9 core (mem_search/recent/timeline/get/save/
|
|
2154
|
+
// recall + mem_defer/mem_defer_list/mem_defer_drop) + 11 hidden (maintenance/
|
|
2155
|
+
// admin/specialized) = 20 registered; tests/tool-schemas.test.mjs is the
|
|
2156
|
+
// authoritative count.
|
|
2046
2157
|
//
|
|
2047
2158
|
// Safe because:
|
|
2048
2159
|
// - Protocol-layer override: we replace the mcp.js default ListTools
|
|
@@ -2055,9 +2166,9 @@ const HIDDEN_TOOL_NAMES = new Set(
|
|
|
2055
2166
|
);
|
|
2056
2167
|
|
|
2057
2168
|
// Opt-out: setting CLAUDE_MEM_ALL_TOOLS=1 restores pre-v2.34.0 behavior where
|
|
2058
|
-
//
|
|
2059
|
-
// autonomously invoking the now-hidden maintenance tools can use this as
|
|
2060
|
-
// immediate escape hatch while adopting the CLI entry points documented in
|
|
2169
|
+
// every registered tool is visible in `tools/list`. Users who relied on Claude
|
|
2170
|
+
// Code autonomously invoking the now-hidden maintenance tools can use this as
|
|
2171
|
+
// an immediate escape hatch while adopting the CLI entry points documented in
|
|
2061
2172
|
// adopt-content.mjs / README.
|
|
2062
2173
|
const EXPOSE_ALL_TOOLS = process.env.CLAUDE_MEM_ALL_TOOLS === '1';
|
|
2063
2174
|
|
|
@@ -2080,7 +2191,7 @@ if (!EXPOSE_ALL_TOOLS) {
|
|
|
2080
2191
|
// harnesses stay silent.
|
|
2081
2192
|
if (!effectiveQuiet()) {
|
|
2082
2193
|
const status = EXPOSE_ALL_TOOLS
|
|
2083
|
-
?
|
|
2194
|
+
? `all ${TOOL_DEFS.length} tools exposed via CLAUDE_MEM_ALL_TOOLS=1`
|
|
2084
2195
|
: `tools/list narrowed to ${TOOL_DEFS.length - HIDDEN_TOOL_NAMES.size} core tools (${HIDDEN_TOOL_NAMES.size} hidden but callable by exact name; unset CLAUDE_MEM_ALL_TOOLS to keep, set =1 to restore all)`;
|
|
2085
2196
|
process.stderr.write(`[claude-mem-lite v${PKG_VERSION}] ${status}\n`);
|
|
2086
2197
|
}
|
package/source-files.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export const SOURCE_FILES = [
|
|
|
9
9
|
'cli.mjs', 'server.mjs', 'server-internals.mjs', 'search-engine.mjs', 'tool-schemas.mjs',
|
|
10
10
|
'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
|
|
11
11
|
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs',
|
|
12
|
-
'hook-update.mjs', 'hook-optimize.mjs',
|
|
12
|
+
'hook-update.mjs', 'hook-optimize.mjs', 'hook-precompact.mjs',
|
|
13
13
|
'plugin-cache-guard.mjs',
|
|
14
14
|
'haiku-client.mjs', 'utils.mjs', 'schema.mjs',
|
|
15
15
|
'package.json', 'package-lock.json', 'skill.md',
|
|
@@ -63,6 +63,22 @@ export const SOURCE_FILES = [
|
|
|
63
63
|
// mem-cli.mjs::cmdSave and server.mjs::mem_save. Statically imported from both
|
|
64
64
|
// entry points; missing it from the manifest broke MCP saves on auto-update.
|
|
65
65
|
'lib/save-observation.mjs',
|
|
66
|
+
// v2.70 deferred-work: carry-forward TODO primitives. Statically imported by
|
|
67
|
+
// server.mjs (mem_defer family) and mem-cli.mjs (defer subcommand).
|
|
68
|
+
'lib/deferred-work.mjs',
|
|
69
|
+
// v2.70 one-shot upgrade banner. Split out of hook.mjs because hook.mjs has
|
|
70
|
+
// module-level `process.exit(0)` side effects that abort vitest workers on
|
|
71
|
+
// direct import. Statically imported by hook.mjs SessionStart handler.
|
|
72
|
+
'lib/upgrade-banner.mjs',
|
|
73
|
+
// Per-table scrub helper for defense-in-depth at text-write INSERT paths.
|
|
74
|
+
// Statically imported by hook-llm, hook-handoff, hook-optimize, hook,
|
|
75
|
+
// mem-cli; reached transitively from server.mjs and cli.mjs.
|
|
76
|
+
'lib/scrub-record.mjs',
|
|
77
|
+
// Cold-start backfill: parses ~/.claude/projects/<encoded>/<uuid>.jsonl
|
|
78
|
+
// transcripts into user_prompts + observations. Dynamic-imported by
|
|
79
|
+
// mem-cli.mjs::cmdImportJsonl; listed here so source-files-sync.test.mjs
|
|
80
|
+
// and the npm tarball ship it on every release.
|
|
81
|
+
'lib/import-jsonl.mjs',
|
|
66
82
|
];
|
|
67
83
|
|
|
68
84
|
/**
|
package/tool-schemas.mjs
CHANGED
|
@@ -140,6 +140,31 @@ export const memDeleteSchema = {
|
|
|
140
140
|
confirm: coerceBool.describe('false=preview what will be deleted, true=execute deletion'),
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
+
// Coerce closes_deferred input — accepts mixed array of integers (ordinals) and
|
|
144
|
+
// "D#<n>" strings (raw ids). Numeric strings are coerced to numbers (so MCP
|
|
145
|
+
// bridges that JSON-stringify ints don't drop them); D#-prefixed strings stay
|
|
146
|
+
// strings for the resolver. Other string shapes reject early at schema layer.
|
|
147
|
+
const coerceDeferredTokens = z.preprocess(
|
|
148
|
+
(v) => {
|
|
149
|
+
if (Array.isArray(v)) {
|
|
150
|
+
return v.map(x => {
|
|
151
|
+
if (typeof x === 'number') return x;
|
|
152
|
+
if (typeof x === 'string') {
|
|
153
|
+
const s = x.trim();
|
|
154
|
+
if (/^-?\d+$/.test(s)) return parseInt(s, 10);
|
|
155
|
+
return s;
|
|
156
|
+
}
|
|
157
|
+
return x;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return v;
|
|
161
|
+
},
|
|
162
|
+
z.array(z.union([
|
|
163
|
+
z.number().int().positive(),
|
|
164
|
+
z.string().regex(/^D#\d+$/, 'expected D#N (raw id) or positive integer (ordinal)'),
|
|
165
|
+
])).min(1).max(20)
|
|
166
|
+
);
|
|
167
|
+
|
|
143
168
|
export const memSaveSchema = {
|
|
144
169
|
content: z.string().min(1).max(50000).describe('Memory content to save'),
|
|
145
170
|
title: z.string().optional().describe('Short title'),
|
|
@@ -148,6 +173,7 @@ export const memSaveSchema = {
|
|
|
148
173
|
importance: coerceInt.pipe(z.number().int().min(1).max(3)).optional().describe('Importance level: 1=routine, 2=notable, 3=critical (default: 2 for explicit saves)'),
|
|
149
174
|
files: coerceStringArray.optional().describe('File paths associated with this observation'),
|
|
150
175
|
lesson_learned: z.string().max(500).optional().describe('Key lesson or takeaway (for bugfix: root cause & fix; for decision: rationale)'),
|
|
176
|
+
closes_deferred: coerceDeferredTokens.optional().describe('Close one or more deferred_work items in the same project. Mixed array: bare integer = ordinal-within-project, "D#<n>" string = raw id. Transactional with the obs insert — a single invalid id rolls back the whole save.'),
|
|
151
177
|
};
|
|
152
178
|
|
|
153
179
|
export const memStatsSchema = {
|
|
@@ -252,6 +278,28 @@ export const memBrowseSchema = {
|
|
|
252
278
|
limit: coerceInt.pipe(z.number().int().min(1).max(100)).optional().describe('Max entries per tier (default 5, or 20 when filtering by tier)'),
|
|
253
279
|
};
|
|
254
280
|
|
|
281
|
+
export const memDeferSchema = {
|
|
282
|
+
title: z.string().min(1).max(200).describe('One-line subject of the deferred item'),
|
|
283
|
+
priority: coerceInt.pipe(z.number().int().min(1).max(3)).optional().describe('1=low, 2=normal, 3=urgent (default: 2)'),
|
|
284
|
+
detail: z.string().max(2000).optional().describe('Optional longer description / constraint / why deferred'),
|
|
285
|
+
files: coerceStringArray.optional().describe('Optional file paths this deferred item concerns'),
|
|
286
|
+
project: z.string().optional().describe('Project name (default: inferred from CWD)'),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export const memDeferListSchema = {
|
|
290
|
+
project: z.string().optional().describe('Project name (default: inferred from CWD)'),
|
|
291
|
+
limit: coerceInt.pipe(z.number().int().min(1).max(50)).optional().describe('Max results (default 10)'),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export const memDeferDropSchema = {
|
|
295
|
+
id: z.union([
|
|
296
|
+
coerceInt.pipe(z.number().int().positive()),
|
|
297
|
+
z.string().regex(/^D#\d+$/, 'expected D#N or positive integer'),
|
|
298
|
+
]).describe('Deferred item id — accepts D#N (raw id) or positive integer (ordinal-within-project)'),
|
|
299
|
+
reason: z.string().min(1).max(500).describe('Why this item is being dropped (required for audit trail)'),
|
|
300
|
+
project: z.string().optional().describe('Project name (default: inferred from CWD)'),
|
|
301
|
+
};
|
|
302
|
+
|
|
255
303
|
// ────────────────────────────────────────────────────────────────────────────
|
|
256
304
|
// Tool descriptions — discouragement style (Task 5, v2.31)
|
|
257
305
|
//
|
|
@@ -265,14 +313,16 @@ export const memBrowseSchema = {
|
|
|
265
313
|
// 40-60% vs. encouragement-style ("use this to..."). See tests/tool-schemas.test.mjs
|
|
266
314
|
// for the invariants this list must satisfy.
|
|
267
315
|
//
|
|
268
|
-
// Core vs hidden (v2.34.0): only
|
|
316
|
+
// Core vs hidden (v2.34.0, expanded v2.70.0): only 9 tools are exposed via MCP
|
|
317
|
+
// `tools/list` (the original 6 + mem_defer/mem_defer_list/mem_defer_drop). The
|
|
269
318
|
// remaining 11 stay registered — and are still callable by name at the MCP
|
|
270
319
|
// protocol level (`tools/call` by exact name) — but are omitted from the list
|
|
271
320
|
// response so they don't bloat every agent's startup context. The core set
|
|
272
321
|
// covers the hot paths the invited-memory contract promises (recall before
|
|
273
|
-
// Edit, save after bugfix, search/recent/timeline/get for retrieval
|
|
274
|
-
// tools are either maintenance
|
|
275
|
-
//
|
|
322
|
+
// Edit, save after bugfix, search/recent/timeline/get for retrieval, defer
|
|
323
|
+
// for cross-session carry-forward). Hidden tools are either maintenance
|
|
324
|
+
// (compress/maintain/optimize/fts_check), admin/infra
|
|
325
|
+
// (stats/export/update/delete), or specialized browsers
|
|
276
326
|
// (browse/registry/use) — all of which have CLI equivalents documented in
|
|
277
327
|
// `adopt-content.mjs`.
|
|
278
328
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -599,4 +649,57 @@ export const tools = [
|
|
|
599
649
|
inputSchema: memBrowseSchema,
|
|
600
650
|
hidden: true,
|
|
601
651
|
},
|
|
652
|
+
{
|
|
653
|
+
name: 'mem_defer',
|
|
654
|
+
description:
|
|
655
|
+
'Save a future-session TODO to the project carry-forward list. Surfaces in the next SessionStart `### Deferred Work` banner with an ordinal (e.g. "1") so user can say "处理1" / "handle item 1".\n' +
|
|
656
|
+
'\n' +
|
|
657
|
+
'DO NOT use when:\n' +
|
|
658
|
+
' - Work is in-flight this session (just do it; do not defer mid-task)\n' +
|
|
659
|
+
' - This-PR follow-up cleanup (file in tasks/, not deferred_work)\n' +
|
|
660
|
+
' - Already saved a bugfix obs for it (use mem_save closes_deferred instead)\n' +
|
|
661
|
+
'\n' +
|
|
662
|
+
'USE when:\n' +
|
|
663
|
+
' - User says "下次/next session/留给下个会话/defer to next round"\n' +
|
|
664
|
+
' - Wrap-up phase enumerates follow-up items for the next session\n' +
|
|
665
|
+
' - Bug surfaces but root cause is out of this session\'s scope\n' +
|
|
666
|
+
'\n' +
|
|
667
|
+
'Equivalent CLI: claude-mem-lite defer add "<title>" [--priority 1|2|3] [--detail "..."] [--files a.mjs,b.mjs]',
|
|
668
|
+
inputSchema: memDeferSchema,
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
name: 'mem_defer_list',
|
|
672
|
+
description:
|
|
673
|
+
'List open deferred_work items for a project, ordered by priority DESC then age. Each item carries a per-project ordinal (1, 2, 3...) for "处理1"-style reference.\n' +
|
|
674
|
+
'\n' +
|
|
675
|
+
'DO NOT use when:\n' +
|
|
676
|
+
' - The SessionStart `### Deferred Work` block already shows the items (reuse that — same data)\n' +
|
|
677
|
+
' - You only need one item by id (use mem_get on the obs that closed it, or look up the row directly)\n' +
|
|
678
|
+
'\n' +
|
|
679
|
+
'USE when:\n' +
|
|
680
|
+
' - User asks "what was on the deferred list" / "what did I leave for next time"\n' +
|
|
681
|
+
' - About to refer to "item N" and need to confirm what N points to\n' +
|
|
682
|
+
' - Auditing carry-forward state across multiple sessions\n' +
|
|
683
|
+
'\n' +
|
|
684
|
+
'Equivalent CLI: claude-mem-lite defer list [--project X] [--limit 10]',
|
|
685
|
+
inputSchema: memDeferListSchema,
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: 'mem_defer_drop',
|
|
689
|
+
description:
|
|
690
|
+
'Mark a deferred_work item as dropped (no fix, no longer relevant) with a required reason. For real fixes, prefer mem_save({type:"bugfix", closes_deferred:[N]}) — establishes audit trail.\n' +
|
|
691
|
+
'\n' +
|
|
692
|
+
'DO NOT use when:\n' +
|
|
693
|
+
' - The item was actually fixed (use mem_save closes_deferred — audit linkage)\n' +
|
|
694
|
+
' - You want to delete the row (drops are soft — status flips, row stays)\n' +
|
|
695
|
+
' - The reason is trivial — drop reason is the only audit trail for "why no fix"\n' +
|
|
696
|
+
'\n' +
|
|
697
|
+
'USE when:\n' +
|
|
698
|
+
' - Originally-deferred item turns out to be a flaky test, not a real bug\n' +
|
|
699
|
+
' - Scope changed and the work is no longer needed\n' +
|
|
700
|
+
' - User explicitly says "drop the deferred X, never mind"\n' +
|
|
701
|
+
'\n' +
|
|
702
|
+
'Equivalent CLI: claude-mem-lite defer drop <D#N|ordinal> --reason "..."',
|
|
703
|
+
inputSchema: memDeferDropSchema,
|
|
704
|
+
},
|
|
602
705
|
];
|