@vib795/agent-memory 0.6.3 → 0.6.5
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 +20 -4
- package/package.json +1 -1
- package/skills/handoff/SKILL.md +18 -3
- package/skills/recall/SKILL.md +5 -0
- package/src/cli.js +84 -1
- package/src/setup.js +72 -12
package/README.md
CHANGED
|
@@ -427,15 +427,31 @@ GitHub, and it is the path to use behind a proxy that blocks or quarantines npm:
|
|
|
427
427
|
|
|
428
428
|
```bash
|
|
429
429
|
git clone https://github.com/vib795/agent-memory.git
|
|
430
|
-
|
|
430
|
+
cd agent-memory && npm pack
|
|
431
|
+
npm install -g ./vib795-agent-memory-*.tgz
|
|
431
432
|
agent-memory setup
|
|
432
433
|
```
|
|
433
434
|
|
|
434
|
-
**
|
|
435
|
+
**Pack first; do not install the directory.** `npm install -g ./agent-memory` looks
|
|
436
|
+
equivalent and is not: npm links the global install to that folder rather than copying
|
|
437
|
+
it, which shows up as an arrow in `npm list -g`:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
`-- @vib795/agent-memory@0.6.5 -> .\..\..\..\agent-memory
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Move or delete the clone afterwards and the global install points at nothing — the same
|
|
444
|
+
breakage as the git-URL case below, arriving later and harder to trace. Installing a
|
|
445
|
+
packed tarball copies, so the clone becomes disposable. Verified on npm 11.x.
|
|
446
|
+
|
|
447
|
+
**Do not install from the git URL directly either.** `npm install -g <git-url>` does not
|
|
435
448
|
work for this package: npm resolves a git install through
|
|
436
449
|
`~/.npm/_cacache/tmp/git-clone*` and then removes that directory, leaving the global
|
|
437
|
-
install pointing at a path that no longer exists.
|
|
438
|
-
|
|
450
|
+
install pointing at a path that no longer exists. Verified on npm 11.18.
|
|
451
|
+
|
|
452
|
+
**And run the installed binary, not the checkout.** Skill links resolve relative to the
|
|
453
|
+
code that creates them, so `npm run setup` inside a clone aims every link at that clone.
|
|
454
|
+
Use `agent-memory setup`, which runs the copy npm installed.
|
|
439
455
|
|
|
440
456
|
Every release is mirrored to **GitHub Packages**. Treat that as redundancy rather
|
|
441
457
|
than a second front door: GitHub Packages requires authentication even for public
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vib795/agent-memory",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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/handoff/SKILL.md
CHANGED
|
@@ -129,6 +129,12 @@ repos:
|
|
|
129
129
|
agent: copilot | claude-code
|
|
130
130
|
---
|
|
131
131
|
|
|
132
|
+
> **If the repository you are reading this in is not listed under `repos:` above, you are
|
|
133
|
+
> replicating this work, not continuing it.** Follow Execution protocol, re-derive every
|
|
134
|
+
> path, branch name and version from the repository you are actually in, and read Next
|
|
135
|
+
> action as a record of what happened elsewhere rather than as an instruction. Do not open
|
|
136
|
+
> the repository this was written in.
|
|
137
|
+
|
|
132
138
|
## Orientation
|
|
133
139
|
|
|
134
140
|
3 to 5 sentences. What this thread is trying to accomplish and where it stands.
|
|
@@ -193,7 +199,9 @@ These separate a useful handoff from a readable paragraph that still leaves ques
|
|
|
193
199
|
3. Never quote or paraphrase the transcript. Record conclusions, not the path to them.
|
|
194
200
|
4. Anchor claims to a file path or a decision number. "We refactored the service
|
|
195
201
|
layer" is a failure. "`src/services/order.ts:42` now returns `Result<T>` instead
|
|
196
|
-
of throwing" is not.
|
|
202
|
+
of throwing" is not. When the thread spans more than one repository, name the repo
|
|
203
|
+
alongside the path: an unqualified path in a two-repo thread is a path the reader
|
|
204
|
+
goes looking for in the wrong tree, and finding it there is worse than not finding it.
|
|
197
205
|
5. Record only what the conversation actually established. Prefix anything you
|
|
198
206
|
inferred with `inferred:` so the next agent knows to verify it.
|
|
199
207
|
6. Never inline a diff or a patch. List changed files with one line each.
|
|
@@ -296,8 +304,15 @@ request, which is the only reason this step belongs here rather than in its own
|
|
|
296
304
|
- Zero durable knowledge is a valid outcome. Writing nothing beats writing noise.
|
|
297
305
|
<!-- extraction-rules:end -->
|
|
298
306
|
|
|
299
|
-
Your Decisions table
|
|
300
|
-
|
|
307
|
+
Your Decisions table, Constraints section and **Execution protocol** are usually already
|
|
308
|
+
the durable part. Write the protocol as a `convention`: it is the section a second
|
|
309
|
+
repository actually needs, and the one most easily lost, because a sequence of steps
|
|
310
|
+
reads like status even when it describes how every run of this kind is done. A handoff
|
|
311
|
+
that records the protocol while the graph does not still leaves the next repository
|
|
312
|
+
guessing — the handoff is read once, by whoever was handed the path, and the graph is
|
|
313
|
+
what `/recall` reaches for afterwards.
|
|
314
|
+
|
|
315
|
+
The Current task state section never is durable.
|
|
301
316
|
|
|
302
317
|
### Write it (ONE terminal call)
|
|
303
318
|
|
package/skills/recall/SKILL.md
CHANGED
|
@@ -62,6 +62,11 @@ and what a regex does badly.
|
|
|
62
62
|
- Pick 1 to 3 ids. More than 3 means the question is really several questions.
|
|
63
63
|
- Always include a `constraint` that touches the subject, even when the user did not
|
|
64
64
|
ask about limits. Constraints are what stop an approach that cannot ship.
|
|
65
|
+
- When the question is about **doing** the work rather than understanding it, also
|
|
66
|
+
include the `convention` that governs how that kind of work is executed. A constraint
|
|
67
|
+
tells you which steps are forbidden; only a procedure tells you what order the allowed
|
|
68
|
+
ones go in. Branch choreography and deploy ordering live here, and they are what a
|
|
69
|
+
second repository gets wrong when nobody surfaces them.
|
|
65
70
|
- Nothing in the tree looks relevant → go to Step 4.
|
|
66
71
|
|
|
67
72
|
---
|
package/src/cli.js
CHANGED
|
@@ -15,7 +15,8 @@ import { compact, maybeCompact } from './compact.js';
|
|
|
15
15
|
import { staleness, currentRepo, reviewCandidates, captureGap } from './staleness.js';
|
|
16
16
|
import { setup as runSetup, unlinkSkills, danglingSkillLinks, SKILLS } from './setup.js';
|
|
17
17
|
import { detectTargets, installableTargets } from './targets.js';
|
|
18
|
-
import { join, dirname } from 'node:path';
|
|
18
|
+
import { join, dirname, resolve, sep } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { atomicWrite } from './atomic.js';
|
|
20
21
|
import { redactNodeForExport, buildReceipt, renderReceipt } from './pii.js';
|
|
21
22
|
|
|
@@ -29,6 +30,41 @@ import { redactNodeForExport, buildReceipt, renderReceipt } from './pii.js';
|
|
|
29
30
|
|
|
30
31
|
const MIN_NODE = [22, 5];
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Where this process is actually running from, and what version it is.
|
|
35
|
+
*
|
|
36
|
+
* "What am I running" is the first question in every install problem and used to need
|
|
37
|
+
* `npm list -g` to answer, which reports what npm believes rather than what is on PATH.
|
|
38
|
+
* These read the package next to the running code, so they answer for the copy that
|
|
39
|
+
* will actually execute.
|
|
40
|
+
*/
|
|
41
|
+
function packageRoot() {
|
|
42
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function installedVersion() {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(join(packageRoot(), 'package.json'), 'utf8')).version;
|
|
48
|
+
} catch {
|
|
49
|
+
return 'unknown';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether the running code lives outside any `node_modules` tree.
|
|
55
|
+
*
|
|
56
|
+
* `npm install -g <folder>` links rather than copies, so a global install can be a
|
|
57
|
+
* pointer at a checkout the user will eventually tidy away — and skill links, which
|
|
58
|
+
* resolve relative to this file, follow it there. Node resolves symlinks before it sets
|
|
59
|
+
* `import.meta.url`, so the link itself is already invisible from in here; what stays
|
|
60
|
+
* visible, and is the thing that actually matters, is that the code is not sitting in an
|
|
61
|
+
* installed package. Running from a working copy is legitimate, so this reports the
|
|
62
|
+
* condition rather than failing on it.
|
|
63
|
+
*/
|
|
64
|
+
function runningFromWorkingCopy() {
|
|
65
|
+
return !packageRoot().split(sep).includes('node_modules');
|
|
66
|
+
}
|
|
67
|
+
|
|
32
68
|
function parseArgs(argv) {
|
|
33
69
|
const opts = { _: [] };
|
|
34
70
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -93,6 +129,8 @@ const USAGE = `agent-memory — durable cross-repo knowledge for coding agents
|
|
|
93
129
|
engagement [show|list|use <name>] which client store this window writes to
|
|
94
130
|
[purge <name> --yes] delete one engagement's store entirely
|
|
95
131
|
|
|
132
|
+
--version this version, and the path it runs from
|
|
133
|
+
|
|
96
134
|
Add --json to any command for machine-readable output.
|
|
97
135
|
Engagement: ${ENGAGEMENT.name} (${ENGAGEMENT.source})
|
|
98
136
|
Store: ${paths.root}`;
|
|
@@ -164,6 +202,26 @@ function cmdSetup() {
|
|
|
164
202
|
);
|
|
165
203
|
}
|
|
166
204
|
|
|
205
|
+
// Naming what did not install is the whole point. A run that silently installs
|
|
206
|
+
// eleven of twelve reads as a success and sends the user back to an editor that is
|
|
207
|
+
// still missing a skill.
|
|
208
|
+
if (r.failed.length) {
|
|
209
|
+
lines.push('', ' Failed:');
|
|
210
|
+
for (const f of r.failed) lines.push(` [failed] ${f.path} — ${f.error}`);
|
|
211
|
+
lines.push(
|
|
212
|
+
'',
|
|
213
|
+
' A path that will not clear is usually a leftover link from an install that has',
|
|
214
|
+
' since moved. `agent-memory uninstall` reclaims those, then run setup again.',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (r.compactError) {
|
|
218
|
+
lines.push(
|
|
219
|
+
'',
|
|
220
|
+
` Skills are installed, but refreshing the /recall description failed: ${r.compactError}`,
|
|
221
|
+
' Run `agent-memory index` then `agent-memory compact` to retry just that step.',
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
167
225
|
return {
|
|
168
226
|
ok: true,
|
|
169
227
|
...r,
|
|
@@ -440,6 +498,25 @@ function cmdDoctor() {
|
|
|
440
498
|
// reading it against the wrong client is the mistake this is here to prevent.
|
|
441
499
|
add('engagement', true, `${ENGAGEMENT.name} (${ENGAGEMENT.source})`);
|
|
442
500
|
|
|
501
|
+
// Second, because "which version is this" preceded every other question in the one
|
|
502
|
+
// install failure this tool has actually been debugged through, and answering it
|
|
503
|
+
// needed a separate npm command that reports what npm believes rather than what ran.
|
|
504
|
+
add('version', true, `${installedVersion()} at ${packageRoot()}`);
|
|
505
|
+
|
|
506
|
+
// Skill links point at whatever copy of the code creates them. When that copy is a
|
|
507
|
+
// working directory rather than an installed package, deleting the directory dangles
|
|
508
|
+
// every link at once — which is exactly how this tool's own skill links were lost.
|
|
509
|
+
// Reported, not failed: running from a checkout is a normal thing to do deliberately.
|
|
510
|
+
add(
|
|
511
|
+
'runs from an installed package',
|
|
512
|
+
true,
|
|
513
|
+
runningFromWorkingCopy()
|
|
514
|
+
? `no — working copy at ${packageRoot()}; skill links will point here, so moving or ` +
|
|
515
|
+
'deleting it breaks them. For a durable install: `npm pack` then ' +
|
|
516
|
+
'`npm install -g <tgz>`, and re-run setup.'
|
|
517
|
+
: 'yes',
|
|
518
|
+
);
|
|
519
|
+
|
|
443
520
|
add('node version', nodeVersionOk(), `${process.versions.node} (need >= ${MIN_NODE.join('.')})`);
|
|
444
521
|
if (!nodeVersionOk()) {
|
|
445
522
|
return {
|
|
@@ -913,6 +990,12 @@ function main(argv) {
|
|
|
913
990
|
process.stdout.write(`${USAGE}\n`);
|
|
914
991
|
return 0;
|
|
915
992
|
}
|
|
993
|
+
if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
994
|
+
// Prints the path as well as the number. A version alone cannot tell you that the
|
|
995
|
+
// binary on PATH belongs to a different install than the one you just upgraded.
|
|
996
|
+
process.stdout.write(`${installedVersion()}\n${packageRoot()}\n`);
|
|
997
|
+
return 0;
|
|
998
|
+
}
|
|
916
999
|
const fn = COMMANDS[cmd];
|
|
917
1000
|
if (!fn) {
|
|
918
1001
|
process.stderr.write(`Unknown command ${JSON.stringify(cmd)}.\n\n${USAGE}\n`);
|
package/src/setup.js
CHANGED
|
@@ -55,11 +55,45 @@ function clear(path) {
|
|
|
55
55
|
// end stay put — which is exactly what passing `recursive` here would risk.
|
|
56
56
|
rmdirSync(path);
|
|
57
57
|
}
|
|
58
|
+
// `force` tells rmSync to swallow ENOENT, and a junction whose target is gone can
|
|
59
|
+
// answer the stat rmSync makes with ENOENT while the reparse point itself stays on
|
|
60
|
+
// disk. The removal then reports success and changes nothing, after which
|
|
61
|
+
// symlinkSync fails EEXIST and the copy fallback lands on a path that still
|
|
62
|
+
// exists. Checking is what turns that silent no-op into an error naming the path.
|
|
63
|
+
if (isLink(path)) rmdirSync(path);
|
|
58
64
|
} else if (existsSync(path)) {
|
|
59
65
|
rmSync(path, { recursive: true, force: true });
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Decide whether a skill link at a path we manage was put there by this tool.
|
|
71
|
+
*
|
|
72
|
+
* Identity cannot be "points at where we live right now". An install that has since
|
|
73
|
+
* moved, or a checkout that was deleted, leaves a link that test would disown — and a
|
|
74
|
+
* disowned link is unreclaimable: `uninstall` keeps it as "not ours" and `setup`
|
|
75
|
+
* cannot replace what it will not clear, so the user is left holding a broken link
|
|
76
|
+
* that no command in this tool will fix.
|
|
77
|
+
*/
|
|
78
|
+
function ownsLink(link, name) {
|
|
79
|
+
let dest;
|
|
80
|
+
try {
|
|
81
|
+
dest = resolve(readlinkSync(link));
|
|
82
|
+
} catch {
|
|
83
|
+
// A link sitting at one of our paths whose target cannot even be read is ours to
|
|
84
|
+
// clear. Nothing downstream can use it either.
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (dest === join(packagedSkillsDir(), name)) return true;
|
|
88
|
+
// Dangling. Nothing is lost by reclaiming a link that points nowhere, and leaving it
|
|
89
|
+
// is precisely what deadlocks both commands.
|
|
90
|
+
if (!existsSync(dest)) return true;
|
|
91
|
+
// Live, but pointing into some other agent-memory tree — an older global install, or
|
|
92
|
+
// a checkout the user set up from. The signature is that its parent holds all three
|
|
93
|
+
// of our skills, which a hand-written skill directory would not.
|
|
94
|
+
return SKILLS.every((s) => existsSync(join(dirname(dest), s, 'SKILL.md')));
|
|
95
|
+
}
|
|
96
|
+
|
|
63
97
|
/**
|
|
64
98
|
* Link one skill into one agent directory.
|
|
65
99
|
*
|
|
@@ -130,7 +164,6 @@ export function danglingSkillLinks() {
|
|
|
130
164
|
* that indexed them; removing them is a separate, deliberate act.
|
|
131
165
|
*/
|
|
132
166
|
export function unlinkSkills() {
|
|
133
|
-
const packaged = packagedSkillsDir();
|
|
134
167
|
const removed = [];
|
|
135
168
|
const kept = [];
|
|
136
169
|
|
|
@@ -141,11 +174,9 @@ export function unlinkSkills() {
|
|
|
141
174
|
if (!existsSync(link) && !isLink(link)) continue;
|
|
142
175
|
let owned = false;
|
|
143
176
|
try {
|
|
144
|
-
owned = isLink(link)
|
|
145
|
-
? resolve(readlinkSync(link)) === join(packaged, name)
|
|
146
|
-
: existsSync(join(link, 'SKILL.md'));
|
|
177
|
+
owned = isLink(link) ? ownsLink(link, name) : existsSync(join(link, 'SKILL.md'));
|
|
147
178
|
} catch {
|
|
148
|
-
// A
|
|
179
|
+
// A directory we cannot stat is one we cannot claim. Leave it.
|
|
149
180
|
owned = false;
|
|
150
181
|
}
|
|
151
182
|
if (owned) {
|
|
@@ -182,15 +213,30 @@ export function setup({ compactFn } = {}) {
|
|
|
182
213
|
const targets = installableTargets();
|
|
183
214
|
const installed = [];
|
|
184
215
|
const copies = [];
|
|
216
|
+
const failed = [];
|
|
185
217
|
|
|
186
218
|
for (const target of targets) {
|
|
187
219
|
for (const name of SKILLS) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
220
|
+
// Isolated per skill. One unwritable target used to abort the whole run and
|
|
221
|
+
// discard the report with it, so a user whose `.copilot` link was wedged got no
|
|
222
|
+
// output at all and no hint that the other eleven had been fine. A failure here
|
|
223
|
+
// is data to print, not a reason to stop.
|
|
224
|
+
try {
|
|
225
|
+
const r =
|
|
226
|
+
target.kind === 'skill-dir'
|
|
227
|
+
? linkSkill(name, target.dir)
|
|
228
|
+
: writePromptFile(name, target.dir);
|
|
229
|
+
installed.push({ ...r, target: target.id, label: target.label });
|
|
230
|
+
if (r.mode === 'copy') copies.push(r);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
failed.push({
|
|
233
|
+
name,
|
|
234
|
+
target: target.id,
|
|
235
|
+
label: target.label,
|
|
236
|
+
path: join(target.dir, target.kind === 'skill-dir' ? name : `${name}.prompt.md`),
|
|
237
|
+
error: err.message,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
194
240
|
}
|
|
195
241
|
}
|
|
196
242
|
|
|
@@ -209,11 +255,25 @@ export function setup({ compactFn } = {}) {
|
|
|
209
255
|
|
|
210
256
|
// compact is passed in so this module does not pull the database into memory just
|
|
211
257
|
// to make some symlinks.
|
|
212
|
-
|
|
258
|
+
//
|
|
259
|
+
// Its failure must not sink the run. Linking is already done and written to disk by
|
|
260
|
+
// this point, so throwing here would throw away an accurate report of work that
|
|
261
|
+
// actually happened and leave the user with no idea any of it succeeded. compact
|
|
262
|
+
// touches every registered skill path, which is exactly the set most likely to hold
|
|
263
|
+
// a stale entry, so it is the step most likely to throw.
|
|
264
|
+
let result = null;
|
|
265
|
+
let compactError = null;
|
|
266
|
+
try {
|
|
267
|
+
result = compactFn ? compactFn() : null;
|
|
268
|
+
} catch (err) {
|
|
269
|
+
compactError = err.message;
|
|
270
|
+
}
|
|
213
271
|
return {
|
|
214
272
|
targets,
|
|
215
273
|
installed,
|
|
216
274
|
copies,
|
|
275
|
+
failed,
|
|
276
|
+
compactError,
|
|
217
277
|
skillPaths,
|
|
218
278
|
home: agentHome(),
|
|
219
279
|
digest: result?.digest ?? null,
|