@vib795/agent-memory 0.6.0 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vib795/agent-memory",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Durable cross-repo knowledge graph for GitHub Copilot and Claude Code. Markdown source of truth, disposable SQLite index, zero runtime dependencies.",
5
5
  "keywords": [
6
6
  "github-copilot",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: recall
3
3
  version: 0.1.0
4
- description: "Durable project knowledge: 5 notes, 1 constraint across agent-memory. Topics: memory store is markdown with a disposable sqlite cache, chose node:sqlite over better-sqlite3, sqlite recursive cte is the graph engine. Use when you need to know how a system works, why a decision was made, what convention applies, or what the environment forbids."
4
+ description: "Durable project knowledge store, currently empty. Run /remember to capture the first note. Use when you need to know how a system works, why a decision was made, what convention applies, or what the environment forbids."
5
5
  license: MIT
6
6
  allowed-tools: Bash Read
7
7
  triggers:
package/src/cli.js CHANGED
@@ -424,6 +424,9 @@ function cmdCompact() {
424
424
  ...r.malformed.map((m) => `warning: unparseable ${m.path}`),
425
425
  `digest ${r.digestChars} chars`,
426
426
  ...r.skills.map((s) => `updated description in ${s}`),
427
+ ...(r.skipped || []).map(
428
+ (s) => `skipped ${s}: inside this package's git checkout, so the file is tracked`
429
+ ),
427
430
  ].join('\n');
428
431
  return { ok: true, ...r, text };
429
432
  }
package/src/compact.js CHANGED
@@ -1,4 +1,6 @@
1
- import { readFileSync, existsSync } from 'node:fs';
1
+ import { readFileSync, existsSync, realpathSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { join, sep } from 'node:path';
2
4
  import { loadConfig, paths } from './config.js';
3
5
  import { listNotes, archiveNote, contentHash, nowIso, serializeNote } from './store.js';
4
6
  import { atomicWrite } from './atomic.js';
@@ -149,8 +151,40 @@ function decay(active, cfg, now) {
149
151
  * reading its description, so regenerating that line is how the store advertises
150
152
  * what it now knows without costing anything at chat time.
151
153
  */
154
+ // The directory this package was installed into. `setup` links skill directories at
155
+ // `<package>/skills/<name>`, so every description write lands in the package's own
156
+ // files. That is correct for an installed package and wrong for a git checkout, where
157
+ // those files are tracked: one developer's digest gets committed and then published to
158
+ // everyone. It shipped that way for twenty releases, advertising one machine's five
159
+ // notes to every user who installed the plugin.
160
+ const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
161
+
162
+ /**
163
+ * Is this path inside the package's own git checkout?
164
+ *
165
+ * Resolved through `realpathSync` because the path arrives as a symlink planted by
166
+ * `setup`; the link sits outside the checkout even when its target is inside it, which
167
+ * is the whole reason this went unnoticed. The `.git` test separates a developer's
168
+ * working tree from an ordinary install, which has no `.git` and must keep being
169
+ * written to — that write is the Tier-1 mechanism, not a bug.
170
+ */
171
+ export function insideCheckout(file) {
172
+ if (!existsSync(join(PACKAGE_ROOT, '.git'))) return false;
173
+ let real;
174
+ let root;
175
+ try {
176
+ real = realpathSync(file);
177
+ root = realpathSync(PACKAGE_ROOT);
178
+ } catch {
179
+ return false;
180
+ }
181
+ if (root.endsWith(sep)) root = root.slice(0, -1);
182
+ return real === root || real.startsWith(root + sep);
183
+ }
184
+
152
185
  export function writeSkillDescription(skillPath, description) {
153
186
  if (!existsSync(skillPath)) return false;
187
+ if (insideCheckout(skillPath)) return false;
154
188
  const src = readFileSync(skillPath, 'utf8');
155
189
  if (!src.startsWith('---')) return false;
156
190
  const end = src.indexOf('\n---', 3);
@@ -185,10 +219,17 @@ function regenerate(db, cfg) {
185
219
  atomicWrite(paths.routing, routing);
186
220
 
187
221
  const skills = [];
222
+ const skipped = [];
188
223
  for (const p of cfg.skillPaths || []) {
224
+ // Reported, never swallowed. A description that silently did not update reads
225
+ // exactly like one that did, and this is the line that routes every recall.
226
+ if (insideCheckout(p)) {
227
+ skipped.push(p);
228
+ continue;
229
+ }
189
230
  if (writeSkillDescription(p, digest)) skills.push(p);
190
231
  }
191
- return { digest, digestChars: digest.length, routing: paths.routing, skills };
232
+ return { digest, digestChars: digest.length, routing: paths.routing, skills, skipped };
192
233
  }
193
234
 
194
235
  /**
package/src/digest.js CHANGED
@@ -59,7 +59,14 @@ export function buildDigest(db, { cfg = loadConfig() } = {}) {
59
59
  const cap = cfg.digestChars;
60
60
  const total = db.prepare('SELECT COUNT(*) AS c FROM nodes WHERE archived = 0').get().c;
61
61
  if (!total) {
62
- return 'Durable project knowledge store, currently empty. Run /remember to capture the first note.';
62
+ // USE_WHEN belongs here too. This branch used to return without it, which stripped
63
+ // the routing triggers from Tier 1 on exactly the machines that need them most: a
64
+ // fresh install, where the store is empty and the skill still has to earn its
65
+ // first use. An empty store is a reason to say when to call recall, not to stop.
66
+ return (
67
+ 'Durable project knowledge store, currently empty. ' +
68
+ `Run /remember to capture the first note. ${USE_WHEN}`
69
+ );
63
70
  }
64
71
 
65
72
  const constraints = db