@vib795/agent-memory 0.6.0 → 0.6.1
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 +1 -1
- package/skills/recall/SKILL.md +1 -1
- package/src/cli.js +3 -0
- package/src/compact.js +43 -2
- package/src/digest.js +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vib795/agent-memory",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
package/skills/recall/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: recall
|
|
3
3
|
version: 0.1.0
|
|
4
|
-
description: "Durable project knowledge
|
|
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
|
-
|
|
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
|