@vib795/agent-memory 0.6.2 → 0.6.4
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/handoff/SKILL.md +64 -8
- package/skills/remember/SKILL.md +14 -1
- package/src/cli.js +20 -0
- package/src/setup.js +72 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vib795/agent-memory",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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
|
@@ -55,6 +55,10 @@ Write-Output "--- GIT ---"
|
|
|
55
55
|
Write-Output "ROOT=$(git rev-parse --show-toplevel 2>$null)"
|
|
56
56
|
Write-Output "BRANCH=$(git branch --show-current 2>$null)"
|
|
57
57
|
Write-Output "HEAD=$(git rev-parse --short HEAD 2>$null)"
|
|
58
|
+
Write-Output "--- BRANCHES ---"
|
|
59
|
+
git branch --sort=-committerdate --format='%(refname:short)' 2>$null | Select-Object -First 10
|
|
60
|
+
Write-Output "--- LOG ---"
|
|
61
|
+
git log --oneline -8 2>$null
|
|
58
62
|
Write-Output "--- STATUS ---"
|
|
59
63
|
git status --porcelain 2>$null
|
|
60
64
|
Write-Output "UTC=$([DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ'))"
|
|
@@ -70,6 +74,8 @@ echo "--- GIT ---"
|
|
|
70
74
|
echo "ROOT=$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
71
75
|
echo "BRANCH=$(git branch --show-current 2>/dev/null)"
|
|
72
76
|
echo "HEAD=$(git rev-parse --short HEAD 2>/dev/null)"
|
|
77
|
+
echo "--- BRANCHES ---"; git branch --sort=-committerdate --format='%(refname:short)' 2>/dev/null | head -10
|
|
78
|
+
echo "--- LOG ---"; git log --oneline -8 2>/dev/null
|
|
73
79
|
echo "--- STATUS ---"; git status --porcelain 2>/dev/null
|
|
74
80
|
echo "UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
75
81
|
```
|
|
@@ -77,6 +83,12 @@ echo "UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
|
77
83
|
If the directory is not a git repository, `ROOT`/`BRANCH`/`HEAD` come back empty.
|
|
78
84
|
That is not an error. Record the working directory path and omit the git fields.
|
|
79
85
|
|
|
86
|
+
`BRANCHES` is there because the current branch is rarely the whole story. Work that
|
|
87
|
+
stages a change across two branches — one holding the old configuration for a first
|
|
88
|
+
run, another carrying the new one — leaves the second branch invisible to
|
|
89
|
+
`--show-current`, and a branch you were never shown is a branch you cannot record.
|
|
90
|
+
Read the list before writing Execution protocol.
|
|
91
|
+
|
|
80
92
|
---
|
|
81
93
|
|
|
82
94
|
## Step 2 — Identify the thread
|
|
@@ -132,6 +144,17 @@ Written for a reader with zero prior context.
|
|
|
132
144
|
Things not discoverable by reading the code: environment restrictions,
|
|
133
145
|
unavailable tooling, plan limits, deadlines, taste calls already settled.
|
|
134
146
|
|
|
147
|
+
## Execution protocol
|
|
148
|
+
|
|
149
|
+
The sequence this work is run in, when the sequence is not obvious from the code.
|
|
150
|
+
Branch choreography, pipeline order, which environment goes first, what has to be
|
|
151
|
+
staged before what, what must be true before a step is safe to repeat.
|
|
152
|
+
|
|
153
|
+
Write the shape of the operation, not this run's position in it. "Run the pipeline
|
|
154
|
+
from a branch holding the old configuration, then from the working branch with the
|
|
155
|
+
new one" belongs here. "Dev is done, UAT is next" does not — that is Current task
|
|
156
|
+
state. Mandatory. `None.` when the work genuinely has no ordering.
|
|
157
|
+
|
|
135
158
|
## Rejected approaches
|
|
136
159
|
|
|
137
160
|
What was tried and why it failed. Mandatory. This is the section that stops the
|
|
@@ -175,7 +198,8 @@ These separate a useful handoff from a readable paragraph that still leaves ques
|
|
|
175
198
|
inferred with `inferred:` so the next agent knows to verify it.
|
|
176
199
|
6. Never inline a diff or a patch. List changed files with one line each.
|
|
177
200
|
7. Soft target 150 lines. On overflow, move detail into `<id>.detail.md` and
|
|
178
|
-
reference it. Never drop Decisions or Rejected approaches
|
|
201
|
+
reference it. Never drop Decisions, Execution protocol, or Rejected approaches
|
|
202
|
+
to hit the target.
|
|
179
203
|
8. Empty sections say `None.` They are never deleted. A missing section reads as
|
|
180
204
|
an oversight; an explicit `None.` reads as a fact.
|
|
181
205
|
|
|
@@ -252,6 +276,13 @@ request, which is the only reason this step belongs here rather than in its own
|
|
|
252
276
|
<!-- extraction-rules:start -->
|
|
253
277
|
- A node is durable only if it will still be true next month. Task state is not
|
|
254
278
|
durable and belongs in a handoff file, not in the graph.
|
|
279
|
+
- A repeatable **procedure** is durable even though it reads like task state, and it
|
|
280
|
+
is the exception most often missed. "Run the pipeline from a branch holding the old
|
|
281
|
+
configuration, then from the working branch carrying the new one" is a `convention`:
|
|
282
|
+
it will be true the next time anyone does this. The test is whether the sentence
|
|
283
|
+
describes *this run* — not durable — or *how this kind of run is done* — durable.
|
|
284
|
+
Branch choreography, deploy ordering and staging sequences all pass that test and
|
|
285
|
+
are routinely dropped because the previous rule makes them look like status.
|
|
255
286
|
- Every `decision` node carries its why and its rejected alternatives, or it is not
|
|
256
287
|
written. Rationale is the thing that never survives re-explanation.
|
|
257
288
|
- Anything the conversation did not actually establish is `confidence: inferred`,
|
|
@@ -303,7 +334,19 @@ describe the same thing and the user should know.
|
|
|
303
334
|
|
|
304
335
|
---
|
|
305
336
|
|
|
306
|
-
## Step 8 — Print the pickup
|
|
337
|
+
## Step 8 — Print the pickup lines
|
|
338
|
+
|
|
339
|
+
Two different jobs pick up a handoff, and one line cannot serve both.
|
|
340
|
+
|
|
341
|
+
**Continuing** is the same work in another window: same repository, same branch, the
|
|
342
|
+
Next action is the next thing to do.
|
|
343
|
+
|
|
344
|
+
**Replicating** is the same *kind* of work in a different repository. There the Next
|
|
345
|
+
action is wrong by construction — it names branches, paths and environments that
|
|
346
|
+
belong to the repo this file was written in. An agent told to "follow the Next
|
|
347
|
+
action" in a different repo will either execute the wrong step or go read the
|
|
348
|
+
original repo off disk to work out what was meant, and both burn the user's credits
|
|
349
|
+
to arrive somewhere worse than a cold start.
|
|
307
350
|
|
|
308
351
|
End your reply with exactly this, and nothing after it:
|
|
309
352
|
|
|
@@ -311,21 +354,34 @@ End your reply with exactly this, and nothing after it:
|
|
|
311
354
|
Handoff written: <absolute path to id.md>
|
|
312
355
|
Thread: <id> (<created|updated>)
|
|
313
356
|
|
|
314
|
-
|
|
357
|
+
Continue in another window (same repo):
|
|
315
358
|
Read <absolute path to id.md> and continue this work. Follow the Next action.
|
|
359
|
+
|
|
360
|
+
Replicate in a different repo:
|
|
361
|
+
Read <absolute path to id.md>. It describes work done in <repo name>. Do the
|
|
362
|
+
equivalent here: follow Execution protocol, and re-derive every specific — branch
|
|
363
|
+
names, file paths, module versions — from this repository. Do not open <repo name>
|
|
364
|
+
and do not assume its Next action applies. Tell me the plan before you edit.
|
|
316
365
|
```
|
|
317
366
|
|
|
367
|
+
Print both. The user knows which window they are pasting into; you do not.
|
|
368
|
+
|
|
318
369
|
---
|
|
319
370
|
|
|
320
371
|
## Self-check before you write
|
|
321
372
|
|
|
322
|
-
Answer all
|
|
373
|
+
Answer all six. If any is no, fix the file before writing it.
|
|
323
374
|
|
|
324
375
|
1. Could a fresh agent execute **Next action** from this file alone?
|
|
325
|
-
2.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
376
|
+
2. Could an agent **in a different repository** replicate this work from this file
|
|
377
|
+
alone, without opening the repo it was written in? If it would have to go read
|
|
378
|
+
the original source to understand the shape of the change, Execution protocol is
|
|
379
|
+
too thin. This is the question that catches a handoff which reads well and is
|
|
380
|
+
still not portable.
|
|
381
|
+
3. Does every decision state why, and what was rejected?
|
|
382
|
+
4. Is Rejected approaches non-empty, or explicitly `None.`?
|
|
383
|
+
5. Is every claim anchored to a path or a decision number?
|
|
384
|
+
6. Is there a single secret, token, or connection string left in the body?
|
|
329
385
|
|
|
330
386
|
---
|
|
331
387
|
|
package/skills/remember/SKILL.md
CHANGED
|
@@ -41,8 +41,14 @@ Invoke this yourself the moment one of these has just happened, in the same turn
|
|
|
41
41
|
|
|
42
42
|
- a **decision** was settled, and the reasons and rejected options are still in view
|
|
43
43
|
- a **constraint** surfaced — a blocked tool, a policy, an environment restriction
|
|
44
|
+
- a **tool you reached for was not there** — not on `PATH`, not installed, not
|
|
45
|
+
permitted. A command that failed because the machine does not have it is a
|
|
46
|
+
`constraint`, and an unrecorded one is a tool call you will spend again in every
|
|
47
|
+
future session on that machine
|
|
44
48
|
- a **root cause** was found, as opposed to a symptom worked around
|
|
45
49
|
- a **convention** was agreed, or discovered by reading the code
|
|
50
|
+
- a **procedure** ran end to end and worked — the order of the steps is now known,
|
|
51
|
+
and it is about to be forgotten
|
|
46
52
|
|
|
47
53
|
Do not capture on a timer, on a tool count, or at every reply. Those produce volume,
|
|
48
54
|
and volume is what makes a graph useless — a store full of task chatter is worse than
|
|
@@ -78,6 +84,13 @@ most turns.
|
|
|
78
84
|
<!-- extraction-rules:start -->
|
|
79
85
|
- A node is durable only if it will still be true next month. Task state is not
|
|
80
86
|
durable and belongs in a handoff file, not in the graph.
|
|
87
|
+
- A repeatable **procedure** is durable even though it reads like task state, and it
|
|
88
|
+
is the exception most often missed. "Run the pipeline from a branch holding the old
|
|
89
|
+
configuration, then from the working branch carrying the new one" is a `convention`:
|
|
90
|
+
it will be true the next time anyone does this. The test is whether the sentence
|
|
91
|
+
describes *this run* — not durable — or *how this kind of run is done* — durable.
|
|
92
|
+
Branch choreography, deploy ordering and staging sequences all pass that test and
|
|
93
|
+
are routinely dropped because the previous rule makes them look like status.
|
|
81
94
|
- Every `decision` node carries its why and its rejected alternatives, or it is not
|
|
82
95
|
written. Rationale is the thing that never survives re-explanation.
|
|
83
96
|
- Anything the conversation did not actually establish is `confidence: inferred`,
|
|
@@ -97,7 +110,7 @@ Types:
|
|
|
97
110
|
|---|---|
|
|
98
111
|
| `system` | how a thing works, anchored to a `path:line` |
|
|
99
112
|
| `decision` | what was chosen, why, and what was rejected |
|
|
100
|
-
| `convention` | how this codebase does something, and the gotcha |
|
|
113
|
+
| `convention` | how this codebase does something, and the gotcha — including multi-step procedures, branch choreography, and deploy ordering |
|
|
101
114
|
| `constraint` | what the environment or the org forbids |
|
|
102
115
|
|
|
103
116
|
---
|
package/src/cli.js
CHANGED
|
@@ -164,6 +164,26 @@ function cmdSetup() {
|
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// Naming what did not install is the whole point. A run that silently installs
|
|
168
|
+
// eleven of twelve reads as a success and sends the user back to an editor that is
|
|
169
|
+
// still missing a skill.
|
|
170
|
+
if (r.failed.length) {
|
|
171
|
+
lines.push('', ' Failed:');
|
|
172
|
+
for (const f of r.failed) lines.push(` [failed] ${f.path} — ${f.error}`);
|
|
173
|
+
lines.push(
|
|
174
|
+
'',
|
|
175
|
+
' A path that will not clear is usually a leftover link from an install that has',
|
|
176
|
+
' since moved. `agent-memory uninstall` reclaims those, then run setup again.',
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (r.compactError) {
|
|
180
|
+
lines.push(
|
|
181
|
+
'',
|
|
182
|
+
` Skills are installed, but refreshing the /recall description failed: ${r.compactError}`,
|
|
183
|
+
' Run `agent-memory index` then `agent-memory compact` to retry just that step.',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
167
187
|
return {
|
|
168
188
|
ok: true,
|
|
169
189
|
...r,
|
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,
|