@pcircle/memesh 4.2.9 → 4.2.10
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/dashboard/dist/index.html +5 -5
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js +4 -3
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/consolidator.d.ts.map +1 -1
- package/dist/core/consolidator.js +4 -3
- package/dist/core/consolidator.js.map +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +4 -3
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +7 -6
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +4 -4
- package/dist/core/graph.js.map +1 -1
- package/dist/core/json-utils.d.ts +2 -0
- package/dist/core/json-utils.d.ts.map +1 -0
- package/dist/core/json-utils.js +37 -0
- package/dist/core/json-utils.js.map +1 -0
- package/dist/core/operations.d.ts +4 -0
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +6 -0
- package/dist/core/operations.js.map +1 -1
- package/dist/knowledge-graph.d.ts +1 -0
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +18 -12
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +17 -7
- package/dist/transports/cli/cli.d.ts +4 -1
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +38 -13
- 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 +18 -67
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +3 -9
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/package.json +2 -2
- package/scripts/hooks/_generated/core-paths.js +77 -0
- package/scripts/hooks/_generated/fts-index.js +25 -0
- package/scripts/hooks/_shared.js +33 -155
- package/scripts/hooks/session-summary.js +27 -2
package/scripts/hooks/_shared.js
CHANGED
|
@@ -1,148 +1,42 @@
|
|
|
1
1
|
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
-
import { spawn
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import {
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
7
|
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
10
8
|
// =============================================================================
|
|
11
|
-
// Path helpers —
|
|
9
|
+
// Path helpers + FTS primitives — GENERATED from src/core (do not hand-mirror)
|
|
12
10
|
// =============================================================================
|
|
13
11
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// rules in that file MUST be reflected here too.
|
|
12
|
+
// These were once a 965-line hand-mirror of `src/core`, kept in lockstep by
|
|
13
|
+
// human review — until the copies drifted and shipped the P0 FTS bug (a hook
|
|
14
|
+
// wrote an entity+observations but the mirror's reindex step diverged, leaving
|
|
15
|
+
// the memory unrecallable).
|
|
19
16
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// handle HOME="" environments. `os.homedir()` itself reads HOME on
|
|
42
|
-
// POSIX, so HOME="" makes it return "". `os.userInfo().homedir`
|
|
43
|
-
// reads pw_dir via getpwuid syscall, bypassing env vars entirely.
|
|
44
|
-
const home = process.env.HOME;
|
|
45
|
-
if (home && home.length > 0) return home;
|
|
46
|
-
const fromOs = homedir();
|
|
47
|
-
if (fromOs && fromOs.length > 0) return fromOs;
|
|
48
|
-
// userInfo is the final defence — re-import here to keep the
|
|
49
|
-
// top-of-file `import { homedir } from 'os'` line stable.
|
|
50
|
-
return require('os').userInfo().homedir;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Resolve the memesh data directory.
|
|
55
|
-
*
|
|
56
|
-
* Precedence: MEMESH_DIR env var > <home>/.memesh.
|
|
57
|
-
* Mirror of: src/core/paths.ts → memeshDir()
|
|
58
|
-
*
|
|
59
|
-
* No-arg to mirror the core helper exactly. Earlier drafts accepted a
|
|
60
|
-
* custom `env` parameter for symmetry with `getMemeshDirFromDbPath(env)`,
|
|
61
|
-
* but the inner `homeDir()` only ever read `process.env.HOME`, so a
|
|
62
|
-
* caller that passed `{HOME: '/tmp/x'}` would be silently ignored — a
|
|
63
|
-
* footgun. Tests redirect via `process.env.HOME`; that's the supported
|
|
64
|
-
* extension point.
|
|
65
|
-
*
|
|
66
|
-
* @returns {string}
|
|
67
|
-
*/
|
|
68
|
-
export function memeshDir() {
|
|
69
|
-
return process.env.MEMESH_DIR ?? join(homeDir(), '.memesh');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Resolve the active memesh DB path.
|
|
74
|
-
*
|
|
75
|
-
* Precedence: MEMESH_DB_PATH env var > <memeshDir>/knowledge-graph.db.
|
|
76
|
-
* Mirror of: src/core/paths.ts → getDbPath() — no-arg, see memeshDir().
|
|
77
|
-
*
|
|
78
|
-
* @returns {string}
|
|
79
|
-
*/
|
|
80
|
-
export function getDbPath() {
|
|
81
|
-
return process.env.MEMESH_DB_PATH ?? join(memeshDir(), 'knowledge-graph.db');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Derive the project name from a working directory.
|
|
86
|
-
*
|
|
87
|
-
* Hooks historically used `basename(data.cwd || process.cwd())`. Core
|
|
88
|
-
* had two variants (`basename(context.cwd)` and `basename(process.cwd())`).
|
|
89
|
-
* This helper unifies the contract — explicit cwd wins, falls through to
|
|
90
|
-
* process.cwd() — matching the most permissive caller's behaviour.
|
|
91
|
-
*
|
|
92
|
-
* Mirror of: src/core/paths.ts → getProjectName() + resolveProjectIdentity().
|
|
93
|
-
* The layered git resolution MUST stay identical to that file — a divergence
|
|
94
|
-
* means hooks (which write project tags) and core (which reads them) would
|
|
95
|
-
* disagree on identity, re-creating the split this change fixes.
|
|
96
|
-
*
|
|
97
|
-
* @param {string|null|undefined} [cwdInput]
|
|
98
|
-
* @returns {string}
|
|
99
|
-
*/
|
|
100
|
-
const _projectNameCache = new Map();
|
|
101
|
-
|
|
102
|
-
export function getProjectName(cwdInput) {
|
|
103
|
-
const cwd = cwdInput && cwdInput.length > 0 ? cwdInput : process.cwd();
|
|
104
|
-
const cached = _projectNameCache.get(cwd);
|
|
105
|
-
if (cached !== undefined) return cached;
|
|
106
|
-
const resolved = _resolveProjectIdentity(cwd);
|
|
107
|
-
_projectNameCache.set(cwd, resolved);
|
|
108
|
-
return resolved;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Layered identity: git remote slug > git repo root basename > cwd basename.
|
|
112
|
-
// See src/core/paths.ts resolveProjectIdentity for the full rationale. git
|
|
113
|
-
// failures at any layer fall through to the next; capture must never break.
|
|
114
|
-
function _resolveProjectIdentity(cwd) {
|
|
115
|
-
const remote = _tryGit(cwd, ['config', '--get', 'remote.origin.url']);
|
|
116
|
-
if (remote) {
|
|
117
|
-
const slug = slugFromRemoteUrl(remote);
|
|
118
|
-
if (slug) return slug;
|
|
119
|
-
}
|
|
120
|
-
const root = _tryGit(cwd, ['rev-parse', '--show-toplevel']);
|
|
121
|
-
if (root) return basename(root);
|
|
122
|
-
return basename(cwd);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function _tryGit(cwd, args) {
|
|
126
|
-
try {
|
|
127
|
-
const out = execFileSync('git', ['-C', cwd, ...args], {
|
|
128
|
-
encoding: 'utf8',
|
|
129
|
-
timeout: 2000,
|
|
130
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
131
|
-
});
|
|
132
|
-
const trimmed = out.trim();
|
|
133
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
17
|
+
// `src/core/paths.ts` and `src/storage/fts-index.ts` are runtime-LEAF modules
|
|
18
|
+
// (paths.ts imports only node builtins; fts-index.ts has only a type-only
|
|
19
|
+
// import), so `tsc` emits self-contained JS for them. `scripts/generate-hook-core.mjs`
|
|
20
|
+
// copies that compiled JS to `_generated/` at build time — committed, shipped in
|
|
21
|
+
// the tarball, and version-locked to its own install. So the hook path still
|
|
22
|
+
// survives a missing/stale `dist/` (the F5 constraint) exactly as the hand-mirror
|
|
23
|
+
// did, but the copy is byte-locked to core and CI-gated (`git diff` on rebuild +
|
|
24
|
+
// `tests/hooks/mirror-parity.test.ts`), making drift structurally impossible.
|
|
25
|
+
//
|
|
26
|
+
// Re-exported here so all 7 hooks keep importing these names from `_shared.js`
|
|
27
|
+
// unchanged.
|
|
28
|
+
import {
|
|
29
|
+
memeshDir,
|
|
30
|
+
getDbPath,
|
|
31
|
+
getMemeshDirFromDbPath,
|
|
32
|
+
getProjectName,
|
|
33
|
+
slugFromRemoteUrl,
|
|
34
|
+
} from './_generated/core-paths.js';
|
|
35
|
+
import { removeFromFts, insertFtsRow } from './_generated/fts-index.js';
|
|
36
|
+
|
|
37
|
+
export { memeshDir, getDbPath, getMemeshDirFromDbPath, getProjectName, slugFromRemoteUrl };
|
|
138
38
|
|
|
139
|
-
|
|
140
|
-
export function slugFromRemoteUrl(url) {
|
|
141
|
-
const cleaned = url.trim().replace(/\.git$/i, '').replace(/[/\\]+$/, '');
|
|
142
|
-
if (!cleaned) return null;
|
|
143
|
-
const seg = cleaned.split(/[/:\\]/).filter(Boolean).pop();
|
|
144
|
-
return seg && seg.length > 0 ? seg : null;
|
|
145
|
-
}
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
146
40
|
|
|
147
41
|
/**
|
|
148
42
|
* Resolve the package root from a hook file's `import.meta.url`.
|
|
@@ -672,17 +566,17 @@ export function captureEntity(db, { name, type, observations = [], tags = [] })
|
|
|
672
566
|
for (const tag of tags) insertTag.run(id, tag);
|
|
673
567
|
|
|
674
568
|
// Reindex FTS: delete the stale entry (if any) then insert the full,
|
|
675
|
-
// current observation set.
|
|
676
|
-
//
|
|
569
|
+
// current observation set. Uses the generated copy of src/storage/fts-index.ts
|
|
570
|
+
// so the contentless-FTS5 delete+insert dance can no longer drift from core.
|
|
677
571
|
if (prevObsText !== undefined) {
|
|
678
|
-
db
|
|
572
|
+
removeFromFts(db, id, name, prevObsText);
|
|
679
573
|
}
|
|
680
574
|
const allObsText = db
|
|
681
575
|
.prepare('SELECT content FROM observations WHERE entity_id = ?')
|
|
682
576
|
.all(id)
|
|
683
577
|
.map((o) => o.content)
|
|
684
578
|
.join(' ');
|
|
685
|
-
db
|
|
579
|
+
insertFtsRow(db, id, name, allObsText);
|
|
686
580
|
|
|
687
581
|
return { id, isNew };
|
|
688
582
|
}
|
|
@@ -690,22 +584,6 @@ export function captureEntity(db, { name, type, observations = [], tags = [] })
|
|
|
690
584
|
const PRIVATE_DIR_MODE = 0o700;
|
|
691
585
|
const PRIVATE_FILE_MODE = 0o600;
|
|
692
586
|
|
|
693
|
-
/**
|
|
694
|
-
* Resolve the directory containing the active DB file.
|
|
695
|
-
*
|
|
696
|
-
* When MEMESH_DB_PATH is set, returns its parent directory (used for
|
|
697
|
-
* sibling files next to the DB). Otherwise returns memeshDir().
|
|
698
|
-
* Mirror of: src/core/paths.ts → getMemeshDirFromDbPath()
|
|
699
|
-
*
|
|
700
|
-
* Renamed from the legacy `getMemeshDir` to match the core helper —
|
|
701
|
-
* sibling helper `memeshDir()` returns the GLOBAL data directory, so a
|
|
702
|
-
* second function called `getMemeshDir` was confusing. Callers updated
|
|
703
|
-
* in lockstep.
|
|
704
|
-
*/
|
|
705
|
-
export function getMemeshDirFromDbPath() {
|
|
706
|
-
return process.env.MEMESH_DB_PATH ? dirname(process.env.MEMESH_DB_PATH) : memeshDir();
|
|
707
|
-
}
|
|
708
|
-
|
|
709
587
|
export function ensurePrivateDir(dirPath) {
|
|
710
588
|
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
711
589
|
try {
|
|
@@ -424,8 +424,11 @@ process.stdin.on('end', async () => {
|
|
|
424
424
|
|
|
425
425
|
for (let i = 0; i < entityIds.length; i++) {
|
|
426
426
|
const name = (entityNames[i] || '').toLowerCase();
|
|
427
|
-
// Skip
|
|
428
|
-
|
|
427
|
+
// Skip names that carry no recall signal: too short, or a
|
|
428
|
+
// machine identifier (auto-capture entities) that can never
|
|
429
|
+
// substring-match prose. Scoring those would be a guaranteed
|
|
430
|
+
// unearned miss — see isMeasurableRecallName.
|
|
431
|
+
if (!isMeasurableRecallName(name)) continue;
|
|
429
432
|
if (isRecallHit(sessionText, name)) {
|
|
430
433
|
updateHit.run(entityIds[i]);
|
|
431
434
|
} else {
|
|
@@ -759,6 +762,28 @@ export function isRecallHit(sessionText, name) {
|
|
|
759
762
|
return String(sessionText ?? '').toLowerCase().includes(String(name).toLowerCase());
|
|
760
763
|
}
|
|
761
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Whether an injected entity's NAME can serve as a recall-effectiveness signal.
|
|
767
|
+
*
|
|
768
|
+
* Recall-effectiveness decides "was this injected memory used?" by substring-
|
|
769
|
+
* matching the entity NAME in the session transcript (isRecallHit). That only
|
|
770
|
+
* works for names a human might type. Auto-capture entities are named with
|
|
771
|
+
* machine identifiers — `session-<pid>-<ts>-files`, `commit-<hash>`,
|
|
772
|
+
* `pre-compact-<id>` — which never appear verbatim in conversation prose, so
|
|
773
|
+
* they take a `recall_miss` they didn't earn on every injection. Over repeated
|
|
774
|
+
* sessions that drags their Laplace-smoothed impact factor (scoring.ts, 10%
|
|
775
|
+
* weight) down and quietly suppresses auto-captured memories from future recall.
|
|
776
|
+
*
|
|
777
|
+
* We can't measure their usefulness by name, so we don't count them either way —
|
|
778
|
+
* they keep the neutral 0.5 impact. The prefix set is coupled to the auto-capture
|
|
779
|
+
* producers' `<kind>-<id>` naming (post-commit / session-summary / pre-compact);
|
|
780
|
+
* a new auto-capture producer should add its prefix here.
|
|
781
|
+
*/
|
|
782
|
+
export function isMeasurableRecallName(name) {
|
|
783
|
+
if (!name || name.length < 4) return false;
|
|
784
|
+
return !/^(session-|commit-|pre-compact-)/i.test(name);
|
|
785
|
+
}
|
|
786
|
+
|
|
762
787
|
export function maybeTriggerDream(projectName, config, pluginRoot) {
|
|
763
788
|
dreamTrigTrace('enter', { projectName, hasLlm: Boolean(config?.llm) });
|
|
764
789
|
if (!projectName || projectName === 'unknown') {
|