@vib795/agent-memory 0.6.3 → 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/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/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,
|