@vib795/agent-memory 0.5.2 → 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/README.md CHANGED
@@ -1,17 +1,45 @@
1
1
  # agent-memory
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@vib795/agent-memory)](https://www.npmjs.com/package/@vib795/agent-memory)
4
- [![test](https://github.com/vib795/agent-memory/actions/workflows/test.yml/badge.svg)](https://github.com/vib795/agent-memory/actions/workflows/test.yml)
5
- [![release](https://github.com/vib795/agent-memory/actions/workflows/release.yml/badge.svg)](https://github.com/vib795/agent-memory/actions/workflows/release.yml)
6
- [![provenance](https://img.shields.io/badge/provenance-attested-brightgreen)](https://www.npmjs.com/package/@vib795/agent-memory)
7
- [![node](https://img.shields.io/badge/node-%3E%3D22.5-brightgreen)](https://nodejs.org)
8
- [![dependencies](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen)](package.json)
9
- [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
3
+ **Durable memory for Claude Code and GitHub Copilot — that survives an IT security review.**
4
+
5
+ Your agent forgets everything between windows. Most tools that fix that cannot be
6
+ installed where you actually work.
7
+
8
+ This one has **zero runtime dependencies, zero dev dependencies, and no install
9
+ script**. Nothing runs when you install it; granting it your agents is a second,
10
+ separate command. It has been [independently scanned](#independently-scanned) and
11
+ passed. `npm ls -g --depth 0` shows nothing underneath it, because there is nothing
12
+ underneath it.
10
13
 
11
- Give **GitHub Copilot** and **Claude Code** a memory that outlives the window, the
12
- repository, and the week — using nothing an IT security review would need to approve.
14
+ ## Install
13
15
 
14
- Three user-level Agent Skills over one local store:
16
+ Two commands, and the second one is not optional — it creates the store and installs
17
+ into every agent on the machine, in whatever format that tool reads:
18
+
19
+ ```bash
20
+ npm install -g @vib795/agent-memory
21
+ agent-memory setup
22
+ ```
23
+
24
+ Node >= 22.5, and nothing else.
25
+
26
+ In Claude Code you can take the three skills as a plugin instead of letting `setup`
27
+ link them:
28
+
29
+ ```
30
+ /plugin marketplace add vib795/agent-memory
31
+ /plugin install agent-memory@vib795
32
+ ```
33
+
34
+ The plugin carries the skills; `npm` carries the store. You want both — the skills
35
+ will tell you so if you only have one.
36
+
37
+ Behind a proxy that quarantines npm, or want the GitHub Packages mirror?
38
+ [Every install path is here](#install-1).
39
+
40
+ ## What you get
41
+
42
+ Three user-level Agent Skills over one local markdown store:
15
43
 
16
44
  | Skill | What it does |
17
45
  |---|---|
@@ -19,9 +47,23 @@ Three user-level Agent Skills over one local store:
19
47
  | `/remember` | Captures durable knowledge into a cross-repo graph — and fires on its own when a juncture passes |
20
48
  | `/recall` | Answers from that graph before deriving anything again |
21
49
 
22
- **New here? Read the [HOW-TO](HOWTO.md)** — install, first five minutes, and the
23
- per-tool notes for Claude Code, Codex and Copilot, written for people who do not
24
- want to read the rest of this file.
50
+ The store is plain markdown in `~/.agents/memory/`, outside every repository — which
51
+ is what lets a note written in one project be read from another. Nothing leaves the
52
+ machine: no daemon, no scheduled task, no telemetry.
53
+
54
+ **New here?** [HOWTO.md](HOWTO.md) — install, first five minutes, and the per-tool
55
+ notes for Claude Code, Codex and Copilot.
56
+
57
+ **How does it work?** [ARCHITECTURE.md](ARCHITECTURE.md) — the source-of-truth split,
58
+ the write and read paths, the tiered context cost, and the invariants underneath.
59
+
60
+ [![npm](https://img.shields.io/npm/v/@vib795/agent-memory)](https://www.npmjs.com/package/@vib795/agent-memory)
61
+ [![test](https://github.com/vib795/agent-memory/actions/workflows/test.yml/badge.svg)](https://github.com/vib795/agent-memory/actions/workflows/test.yml)
62
+ [![release](https://github.com/vib795/agent-memory/actions/workflows/release.yml/badge.svg)](https://github.com/vib795/agent-memory/actions/workflows/release.yml)
63
+ [![provenance](https://img.shields.io/badge/provenance-attested-brightgreen)](https://www.npmjs.com/package/@vib795/agent-memory)
64
+ [![node](https://img.shields.io/badge/node-%3E%3D22.5-brightgreen)](https://nodejs.org)
65
+ [![dependencies](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen)](package.json)
66
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
25
67
 
26
68
  ## Why this exists
27
69
 
@@ -472,7 +514,7 @@ Claude Code can take the same three as a plugin:
472
514
 
473
515
  ```
474
516
  /plugin marketplace add vib795/agent-memory
475
- /plugin install agent-memory@agent-memory
517
+ /plugin install agent-memory@vib795
476
518
  ```
477
519
 
478
520
  The same caveat applies: the plugin carries the skills, `npm` carries the store.
@@ -499,7 +541,7 @@ is generated state, and the committed value is only a placeholder.
499
541
 
500
542
  Needs Node 22.5 or newer; `doctor` says so plainly if the version is too old.
501
543
 
502
- Run `npm test` for the suite (88 tests, no dependencies). CI runs it on Linux,
544
+ Run `npm test` for the suite (91 tests, no dependencies). CI runs it on Linux,
503
545
  macOS and Windows across Node 22 and 24, and separately installs the packed tarball
504
546
  and exercises it end to end on all three.
505
547
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vib795/agent-memory",
3
- "version": "0.5.2",
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",
@@ -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
@@ -75,7 +75,7 @@ function git(args, cwd = process.cwd()) {
75
75
  const USAGE = `agent-memory — durable cross-repo knowledge for coding agents
76
76
 
77
77
  setup link all three skills, build the store
78
- (runs automatically on npm install)
78
+ (run this once, by hand, after install)
79
79
  uninstall remove the skill links; keeps every note
80
80
  init [--skills "<p1>,<p2>"] create the store; register skill files
81
81
  index rebuild index.db from notes/
@@ -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
package/src/promptfile.js CHANGED
@@ -15,7 +15,7 @@ export const GENERATED_MARKER =
15
15
 
16
16
  /** Split a leading `---` frontmatter block from the body. */
17
17
  function splitFrontmatter(text) {
18
- const src = String(text).replace(/^/, '').replace(/\r\n/g, '\n');
18
+ const src = String(text).replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
19
19
  if (!src.startsWith('---')) return { head: '', body: src.trim() };
20
20
  const end = src.indexOf('\n---', 3);
21
21
  if (end === -1) return { head: '', body: src.trim() };
package/src/store.js CHANGED
Binary file