@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.
Files changed (46) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dashboard/dist/index.html +5 -5
  4. package/dist/core/auto-tagger.d.ts.map +1 -1
  5. package/dist/core/auto-tagger.js +4 -3
  6. package/dist/core/auto-tagger.js.map +1 -1
  7. package/dist/core/consolidator.d.ts.map +1 -1
  8. package/dist/core/consolidator.js +4 -3
  9. package/dist/core/consolidator.js.map +1 -1
  10. package/dist/core/digest-validator.d.ts.map +1 -1
  11. package/dist/core/digest-validator.js +4 -3
  12. package/dist/core/digest-validator.js.map +1 -1
  13. package/dist/core/dreamer.d.ts.map +1 -1
  14. package/dist/core/dreamer.js +7 -6
  15. package/dist/core/dreamer.js.map +1 -1
  16. package/dist/core/graph.d.ts.map +1 -1
  17. package/dist/core/graph.js +4 -4
  18. package/dist/core/graph.js.map +1 -1
  19. package/dist/core/json-utils.d.ts +2 -0
  20. package/dist/core/json-utils.d.ts.map +1 -0
  21. package/dist/core/json-utils.js +37 -0
  22. package/dist/core/json-utils.js.map +1 -0
  23. package/dist/core/operations.d.ts +4 -0
  24. package/dist/core/operations.d.ts.map +1 -1
  25. package/dist/core/operations.js +6 -0
  26. package/dist/core/operations.js.map +1 -1
  27. package/dist/knowledge-graph.d.ts +1 -0
  28. package/dist/knowledge-graph.d.ts.map +1 -1
  29. package/dist/knowledge-graph.js +18 -12
  30. package/dist/knowledge-graph.js.map +1 -1
  31. package/dist/skills-manifest.json +17 -7
  32. package/dist/transports/cli/cli.d.ts +4 -1
  33. package/dist/transports/cli/cli.d.ts.map +1 -1
  34. package/dist/transports/cli/cli.js +38 -13
  35. package/dist/transports/cli/cli.js.map +1 -1
  36. package/dist/transports/http/server.d.ts.map +1 -1
  37. package/dist/transports/http/server.js +18 -67
  38. package/dist/transports/http/server.js.map +1 -1
  39. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  40. package/dist/transports/mcp/handlers.js +3 -9
  41. package/dist/transports/mcp/handlers.js.map +1 -1
  42. package/package.json +2 -2
  43. package/scripts/hooks/_generated/core-paths.js +77 -0
  44. package/scripts/hooks/_generated/fts-index.js +25 -0
  45. package/scripts/hooks/_shared.js +33 -155
  46. package/scripts/hooks/session-summary.js +27 -2
@@ -1,148 +1,42 @@
1
1
  import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
2
- import { spawn, execFileSync } from 'child_process';
2
+ import { spawn } from 'child_process';
3
3
  import { createRequire } from 'module';
4
4
  import { homedir } from 'os';
5
- import { basename, dirname, join } from 'path';
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 — MIRROR of src/core/paths.ts
9
+ // Path helpers + FTS primitives GENERATED from src/core (do not hand-mirror)
12
10
  // =============================================================================
13
11
  //
14
- // Hooks cannot import from `dist/` (the F5 security boundary `dist/` may
15
- // be stale or absent at hook execution time), so the path-resolution logic
16
- // is duplicated here. The contract MUST stay in lockstep with
17
- // `src/core/paths.ts`. Any change to the function shapes / precedence
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
- // Unlike the schema duplication (which has a build-time diff guard via
21
- // `scripts/check-schema-drift.mjs`), these helpers are short enough that
22
- // human review at code-review time is sufficient. If they grow, add a
23
- // programmatic guard.
24
-
25
- /**
26
- * Return the user's home directory, honoring HOME env var first.
27
- *
28
- * On POSIX, os.homedir() already consults HOME. On Windows, it ignores
29
- * env vars and reads GetUserProfileDirectoryW directly which makes
30
- * tests unable to redirect home-dir lookups to a tmp dir. Honoring HOME
31
- * first lets tests set HOME=<tmpdir> and have it actually take effect
32
- * across platforms. Production users on Windows almost never set HOME,
33
- * so this falls through to os.homedir() as before.
34
- *
35
- * Mirror of: src/core/paths.ts → homeDir()
36
- *
37
- * @returns {string}
38
- */
39
- function homeDir() {
40
- // Mirror src/core/paths.ts homeDir() same three-step fallback to
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
- /** Mirror of paths.ts slugFromRemoteUrl. */
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. Keep in lockstep with post-commit/pre-compact's
676
- // historical inline form and with src/storage/fts-index.ts.
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.prepare("INSERT INTO entities_fts(entities_fts, rowid, name, observations) VALUES('delete', ?, ?, ?)").run(id, name, prevObsText);
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.prepare('INSERT INTO entities_fts(rowid, name, observations) VALUES(?, ?, ?)').run(id, name, allObsText);
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 very short names to avoid false positives
428
- if (name.length < 4) continue;
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') {