@pcircle/memesh 4.2.6 → 4.2.8
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.de.md +106 -2
- package/README.es.md +106 -2
- package/README.fr.md +106 -2
- package/README.ja.md +106 -2
- package/README.ko.md +106 -2
- package/README.md +89 -3
- package/README.pt.md +106 -2
- package/README.th.md +124 -6
- package/README.vi.md +106 -2
- package/README.zh-CN.md +105 -2
- package/README.zh-TW.md +105 -2
- package/dashboard/dist/index.html +7 -7
- package/dist/cli/view-live.d.ts.map +1 -1
- package/dist/cli/view-live.js +3 -1
- package/dist/cli/view-live.js.map +1 -1
- package/dist/core/analytics.d.ts +0 -31
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +0 -59
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/config.d.ts +0 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +25 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/digest-validator.d.ts +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +7 -2
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +6 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +121 -5
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +1 -0
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +14 -2
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +10 -1
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +16 -2
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/install-hooks.d.ts +6 -0
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +37 -0
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +12 -0
- package/dist/core/llm-telemetry.d.ts.map +1 -1
- package/dist/core/llm-telemetry.js +21 -3
- package/dist/core/llm-telemetry.js.map +1 -1
- package/dist/core/operations.d.ts +5 -0
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +16 -0
- package/dist/core/operations.js.map +1 -1
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/project-tags.d.ts +20 -0
- package/dist/core/project-tags.d.ts.map +1 -0
- package/dist/core/project-tags.js +42 -0
- package/dist/core/project-tags.js.map +1 -0
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +17 -1
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/skill-usage-log.d.ts +1 -1
- package/dist/core/skill-usage-log.d.ts.map +1 -1
- package/dist/core/skill-usage-log.js +2 -2
- package/dist/core/skill-usage-log.js.map +1 -1
- package/dist/core/verifier.d.ts.map +1 -1
- package/dist/core/verifier.js +1 -6
- package/dist/core/verifier.js.map +1 -1
- package/dist/skills-manifest.json +10 -10
- package/dist/transports/cli/cli.js +156 -9
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +7 -1
- package/dist/transports/http/server.js.map +1 -1
- package/package.json +1 -1
- package/scripts/hooks/_shared.js +79 -3
- package/scripts/hooks/pre-compact.js +32 -8
- package/scripts/hooks/session-start.js +168 -17
- package/scripts/hooks/session-summary.js +137 -23
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
decideAutoUpdateHook,
|
|
15
15
|
getMemeshDirFromDbPath,
|
|
16
16
|
getProjectName,
|
|
17
|
+
importFromPluginRoot,
|
|
17
18
|
isAutoCaptureEnabled,
|
|
18
19
|
openHookDb,
|
|
19
20
|
readUpdateCheckCache,
|
|
@@ -125,11 +126,23 @@ function parseTranscript(transcriptPath) {
|
|
|
125
126
|
// the prior version) that signalled review fatigue more than
|
|
126
127
|
// working logic.
|
|
127
128
|
} catch {
|
|
128
|
-
// Skip malformed JSONL lines
|
|
129
|
+
// Skip malformed JSONL lines — benign, per-line, deliberately not traced.
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
|
-
} catch {
|
|
132
|
-
//
|
|
132
|
+
} catch (err) {
|
|
133
|
+
// The transcript file itself could not be read, which empties this
|
|
134
|
+
// session's entire capture — filesEdited/errors/toolCallCount all return
|
|
135
|
+
// zero, so downstream `toolCallCount < 3` bails and no session insight,
|
|
136
|
+
// failure analysis or lesson is produced. An absent file is the normal
|
|
137
|
+
// "not written yet" case; anything else is a real fault worth a trace.
|
|
138
|
+
if (err?.code !== 'ENOENT') {
|
|
139
|
+
try {
|
|
140
|
+
process.stderr.write(
|
|
141
|
+
`[memesh session-summary] transcript ${transcriptPath} unreadable ` +
|
|
142
|
+
`(${err?.message || err}); session capture skipped this run.\n`,
|
|
143
|
+
);
|
|
144
|
+
} catch { /* stderr must never throw */ }
|
|
145
|
+
}
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
return { filesEdited: [...filesEdited], bashCommands, errorsEncountered, toolCallCount };
|
|
@@ -163,7 +176,6 @@ process.stdin.on('end', async () => {
|
|
|
163
176
|
const sessionId = inputData.session_id || 'unknown';
|
|
164
177
|
const transcriptPath = inputData.transcript_path;
|
|
165
178
|
const cwd = inputData.cwd || process.cwd();
|
|
166
|
-
const stopReason = inputData.stop_reason || 'unknown';
|
|
167
179
|
// Default-allow: when Claude Code's Stop payload omits
|
|
168
180
|
// `was_in_agentic_loop` (it has been silently absent in production
|
|
169
181
|
// for an unknown number of releases — symptom: zero session-insight
|
|
@@ -173,8 +185,18 @@ process.stdin.on('end', async () => {
|
|
|
173
185
|
// (default-deny) and the hook silently never captured anything.
|
|
174
186
|
const wasAgenticLoop = inputData.was_in_agentic_loop !== false;
|
|
175
187
|
|
|
176
|
-
// Guards: skip low-signal sessions
|
|
177
|
-
|
|
188
|
+
// Guards: skip low-signal sessions.
|
|
189
|
+
//
|
|
190
|
+
// A `stop_reason === 'user_interrupt'` guard used to live here, but
|
|
191
|
+
// Claude Code's Stop payload carries no `stop_reason` field — verified
|
|
192
|
+
// against the shipped cli.js bundle, whose Stop input is
|
|
193
|
+
// `{...base, hook_event_name:"Stop", stop_hook_active}` with no such key
|
|
194
|
+
// (the `stop_reason` that appears in the bundle is the Anthropic API
|
|
195
|
+
// message field, not a hook input). So the guard read `undefined`, was
|
|
196
|
+
// always false, and never skipped anything — a filter that looked active
|
|
197
|
+
// but did nothing, the exact sibling of the `was_in_agentic_loop` absence
|
|
198
|
+
// above. Removed; the `toolCallCount < 3` check below is the real
|
|
199
|
+
// low-signal filter.
|
|
178
200
|
if (!wasAgenticLoop) return exit0();
|
|
179
201
|
// Trace why we're skipping. Two failure modes:
|
|
180
202
|
// (a) transcript_path absent — schema flip, Claude Code stopped
|
|
@@ -251,6 +273,24 @@ process.stdin.on('end', async () => {
|
|
|
251
273
|
// Build and store session memories
|
|
252
274
|
const baseTags = ['source:auto-capture', `session:${sessionId}`, `project:${projectName}`];
|
|
253
275
|
|
|
276
|
+
// Producer for pre-edit-recall's Strategy 1 (`file:<name>` tag lookup).
|
|
277
|
+
// That read path queries both the full basename and the extension-less
|
|
278
|
+
// form (`file:auth.ts` OR `file:auth`), but nothing ever WROTE these
|
|
279
|
+
// tags — on every real DB the query returned zero rows and the strategy
|
|
280
|
+
// was dead. Emitting both forms here lights it up: a memory captured while
|
|
281
|
+
// editing a file becomes findable the next time that file is edited.
|
|
282
|
+
// filesEdited already holds basenames (see parseTranscript).
|
|
283
|
+
function fileTagsFor(files) {
|
|
284
|
+
const tags = new Set();
|
|
285
|
+
for (const f of files) {
|
|
286
|
+
if (!f) continue;
|
|
287
|
+
tags.add(`file:${f}`);
|
|
288
|
+
const noExt = f.replace(/\.[^.]+$/, '');
|
|
289
|
+
if (noExt && noExt !== f) tags.add(`file:${noExt}`);
|
|
290
|
+
}
|
|
291
|
+
return [...tags];
|
|
292
|
+
}
|
|
293
|
+
|
|
254
294
|
const insertEntity = db.prepare('INSERT OR IGNORE INTO entities (name, type) VALUES (?, ?)');
|
|
255
295
|
const selectEntity = db.prepare('SELECT id FROM entities WHERE name = ?');
|
|
256
296
|
const insertObs = db.prepare('INSERT INTO observations (entity_id, content) VALUES (?, ?)');
|
|
@@ -273,7 +313,7 @@ process.stdin.on('end', async () => {
|
|
|
273
313
|
`Session edited ${filesEdited.length} file(s): ${filesEdited.join(', ')}`,
|
|
274
314
|
`Total tool calls: ${toolCallCount}`,
|
|
275
315
|
],
|
|
276
|
-
baseTags
|
|
316
|
+
[...baseTags, ...fileTagsFor(filesEdited)]
|
|
277
317
|
);
|
|
278
318
|
}
|
|
279
319
|
|
|
@@ -286,7 +326,7 @@ process.stdin.on('end', async () => {
|
|
|
286
326
|
`Fixed ${errorsEncountered.length} error(s) by editing ${filesEdited.join(', ')}`,
|
|
287
327
|
...errorsEncountered.slice(0, 3).map(e => `Error: ${e.slice(0, 100)}`),
|
|
288
328
|
],
|
|
289
|
-
[...baseTags, 'type:bugfix']
|
|
329
|
+
[...baseTags, 'type:bugfix', ...fileTagsFor(filesEdited)]
|
|
290
330
|
);
|
|
291
331
|
}
|
|
292
332
|
|
|
@@ -366,15 +406,16 @@ process.stdin.on('end', async () => {
|
|
|
366
406
|
// Check if recall_hits column exists (v4.0+ migration)
|
|
367
407
|
const colCheck = db.prepare("PRAGMA table_info(entities)").all();
|
|
368
408
|
if (colCheck.some(c => c.name === 'recall_hits')) {
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
409
|
+
// Drop the records Claude Code created FROM our own hook
|
|
410
|
+
// output before matching. One SessionStart injection lands in
|
|
411
|
+
// the transcript 2+ times (hook_success + hook_additional_context),
|
|
412
|
+
// so any count-based discount depends on guessing an
|
|
413
|
+
// undocumented internal — get it wrong and every entity scores
|
|
414
|
+
// a hit instead of a miss. Structural removal is copy-count
|
|
415
|
+
// and encoding independent.
|
|
416
|
+
const sessionText = stripHookEchoes(readFileSync(transcriptPath, 'utf8')).toLowerCase();
|
|
417
|
+
|
|
418
|
+
// Hit/miss decision lives in `isRecallHit` (exported, unit-tested).
|
|
378
419
|
|
|
379
420
|
const updateHit = db.prepare(
|
|
380
421
|
'UPDATE entities SET recall_hits = COALESCE(recall_hits, 0) + 1 WHERE id = ?'
|
|
@@ -387,7 +428,7 @@ process.stdin.on('end', async () => {
|
|
|
387
428
|
const name = (entityNames[i] || '').toLowerCase();
|
|
388
429
|
// Skip very short names to avoid false positives
|
|
389
430
|
if (name.length < 4) continue;
|
|
390
|
-
if (
|
|
431
|
+
if (isRecallHit(sessionText, name)) {
|
|
391
432
|
updateHit.run(entityIds[i]);
|
|
392
433
|
} else {
|
|
393
434
|
updateMiss.run(entityIds[i]);
|
|
@@ -417,13 +458,13 @@ process.stdin.on('end', async () => {
|
|
|
417
458
|
// F5: derive pluginRoot strictly from this file's location.
|
|
418
459
|
// See `resolvePluginRoot` for the full reasoning.
|
|
419
460
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
420
|
-
const configMod = await
|
|
461
|
+
const configMod = await importFromPluginRoot(pluginRoot, 'dist/core/config.js');
|
|
421
462
|
const config = configMod.readConfig();
|
|
422
463
|
|
|
423
464
|
if (config.llm) {
|
|
424
|
-
const { openDatabase, closeDatabase } = await
|
|
425
|
-
const { analyzeFailure } = await
|
|
426
|
-
const { createLesson } = await
|
|
465
|
+
const { openDatabase, closeDatabase } = await importFromPluginRoot(pluginRoot, 'dist/db.js');
|
|
466
|
+
const { analyzeFailure } = await importFromPluginRoot(pluginRoot, 'dist/core/failure-analyzer.js');
|
|
467
|
+
const { createLesson } = await importFromPluginRoot(pluginRoot, 'dist/core/lesson-engine.js');
|
|
427
468
|
|
|
428
469
|
openDatabase();
|
|
429
470
|
try {
|
|
@@ -452,7 +493,7 @@ process.stdin.on('end', async () => {
|
|
|
452
493
|
// logic and dream-history.json schema.
|
|
453
494
|
try {
|
|
454
495
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
455
|
-
const configMod = await
|
|
496
|
+
const configMod = await importFromPluginRoot(pluginRoot, 'dist/core/config.js');
|
|
456
497
|
const config = configMod.readConfig();
|
|
457
498
|
maybeTriggerDream(projectName, config, pluginRoot);
|
|
458
499
|
} catch (dreamErr) {
|
|
@@ -647,6 +688,79 @@ function countEpisodicEntities(projectName) {
|
|
|
647
688
|
* the detached background runner. Pure side effect — no return value
|
|
648
689
|
* used by callers.
|
|
649
690
|
*/
|
|
691
|
+
/**
|
|
692
|
+
* Attachment record types Claude Code uses to persist a hook's own output
|
|
693
|
+
* into the transcript. Anything memesh injected reaches the transcript
|
|
694
|
+
* through one of these, so they must be removed before asking "did the
|
|
695
|
+
* session reference this memory?".
|
|
696
|
+
*
|
|
697
|
+
* Verified against Claude Code v2.1.19: ONE SessionStart injection lands in
|
|
698
|
+
* the transcript at least twice — once as `hook_success` (carrying the raw
|
|
699
|
+
* hook stdout) and once as `hook_additional_context` (the parsed payload).
|
|
700
|
+
*/
|
|
701
|
+
const HOOK_ECHO_ATTACHMENT_TYPES = new Set([
|
|
702
|
+
'hook_success',
|
|
703
|
+
'hook_additional_context',
|
|
704
|
+
'hook_system_message',
|
|
705
|
+
]);
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Remove memesh's own injected text from a raw JSONL transcript.
|
|
709
|
+
*
|
|
710
|
+
* Counting occurrences and subtracting the injected copies does NOT work:
|
|
711
|
+
* it depends on knowing exactly how many times Claude Code echoes a hook
|
|
712
|
+
* payload, which is an undocumented internal that has already been observed
|
|
713
|
+
* at 2+ copies (and 16 in one real transcript). Guessing that constant is
|
|
714
|
+
* how "every entity is a miss" becomes "every entity is a hit" — equally
|
|
715
|
+
* useless, and invisible to a hand-built test fixture.
|
|
716
|
+
*
|
|
717
|
+
* Dropping the hook-echo records structurally is independent of both the
|
|
718
|
+
* copy count and the JSON escaping.
|
|
719
|
+
*/
|
|
720
|
+
export function stripHookEchoes(rawTranscript) {
|
|
721
|
+
const kept = [];
|
|
722
|
+
for (const line of String(rawTranscript ?? '').split('\n')) {
|
|
723
|
+
if (!line.trim()) continue;
|
|
724
|
+
let entry;
|
|
725
|
+
try {
|
|
726
|
+
entry = JSON.parse(line);
|
|
727
|
+
} catch {
|
|
728
|
+
// Unparseable line: keep it. Losing a line can only cause a false
|
|
729
|
+
// MISS (we under-count references), which is the safe direction —
|
|
730
|
+
// it never manufactures a hit the session did not earn.
|
|
731
|
+
kept.push(line);
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const type = entry?.attachment?.type ?? entry?.type;
|
|
735
|
+
if (typeof type === 'string' && HOOK_ECHO_ATTACHMENT_TYPES.has(type)) continue;
|
|
736
|
+
kept.push(line);
|
|
737
|
+
}
|
|
738
|
+
return kept.join('\n');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Did the session actually USE the memory named `name`, or does the name only
|
|
743
|
+
* appear because memesh injected it at session start?
|
|
744
|
+
*
|
|
745
|
+
* The caller passes `sessionText` with memesh's own SessionStart injection
|
|
746
|
+
* already stripped structurally (see `stripHookEchoes` — matches on
|
|
747
|
+
* `attachment.type`, so it is independent of JSON escaping and of how many
|
|
748
|
+
* times Claude Code echoes one injection). That removal is what stops an
|
|
749
|
+
* injected name from scoring a false hit; once the echo is gone, a plain
|
|
750
|
+
* substring match is the whole test.
|
|
751
|
+
*
|
|
752
|
+
* (This replaced an earlier `transcript.replace(injectedBlob, '')` + match,
|
|
753
|
+
* which silently failed on JSON-encoded transcripts and scored every entity a
|
|
754
|
+
* hit — see the callsite comment.)
|
|
755
|
+
*
|
|
756
|
+
* Self-contained for its unit tests: lowercases both sides and ignores names
|
|
757
|
+
* shorter than 4 chars (too generic to match reliably).
|
|
758
|
+
*/
|
|
759
|
+
export function isRecallHit(sessionText, name) {
|
|
760
|
+
if (!name || name.length < 4) return false;
|
|
761
|
+
return String(sessionText ?? '').toLowerCase().includes(String(name).toLowerCase());
|
|
762
|
+
}
|
|
763
|
+
|
|
650
764
|
export function maybeTriggerDream(projectName, config, pluginRoot) {
|
|
651
765
|
dreamTrigTrace('enter', { projectName, hasLlm: Boolean(config?.llm) });
|
|
652
766
|
if (!projectName || projectName === 'unknown') {
|