lucid-mem 0.4.0 → 0.5.0
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 +1 -1
- package/lib/cli.mjs +1 -1
- package/lib/commands.mjs +37 -11
- package/package.json +1 -1
- package/test/context.test.mjs +29 -7
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Memory lives in `~/agent-memory` — a real git repo you can read, diff, and poi
|
|
|
34
34
|
|
|
35
35
|
## Multi-repo memory
|
|
36
36
|
|
|
37
|
-
One memory repo serves every project. `projects/<name>-<hash>.md` files key off the repo's origin remote, so clones share memory and same-named repos don't collide. `amr
|
|
37
|
+
One memory repo serves every project. `projects/<name>-<hash>.md` files key off the repo's origin remote, so clones share memory and same-named repos don't collide. Inside a known repo, `amr context` is project-scoped — the project's latest 25 plus linked siblings (up to 8 projects). Outside one, it shows global bullets plus the latest 25 pooled across all projects. `private` policy keeps a project out of transitive auto-attach and the pooled digest; `noglobal` keeps it out of the pooled digest only.
|
|
38
38
|
|
|
39
39
|
## MCP
|
|
40
40
|
|
package/lib/cli.mjs
CHANGED
|
@@ -26,7 +26,7 @@ const USAGE = `amr: agent memory CLI (lucid-mem)
|
|
|
26
26
|
usage: amr <command> [args] [flags]
|
|
27
27
|
|
|
28
28
|
ensure create/verify the memory repo ($AMR_HOME, default ~/agent-memory)
|
|
29
|
-
context [dir]
|
|
29
|
+
context [dir] session digest: project-scoped inside a known repo; global + pooled projects outside
|
|
30
30
|
add "text" save a memory entry
|
|
31
31
|
update "match" "text" replace an existing entry in place
|
|
32
32
|
search "term" grep all memory files
|
package/lib/commands.mjs
CHANGED
|
@@ -77,14 +77,20 @@ export function cmdContext(flags) {
|
|
|
77
77
|
];
|
|
78
78
|
const cfg = loadConfig(home);
|
|
79
79
|
const entry = proj ? projectEntry(cfg, proj) : null;
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
// Two modes: inside a repo that has a memory file, the digest is
|
|
81
|
+
// project-scoped — this project plus its linked siblings only, no
|
|
82
|
+
// global bullets. Outside any known project (or a repo with no
|
|
83
|
+
// memory yet), it is global bullets plus a pooled digest of the
|
|
84
|
+
// latest entries across all projects.
|
|
85
|
+
const projHasFile =
|
|
86
|
+
!!proj &&
|
|
87
|
+
fs.existsSync(path.join(home, 'projects', `${proj.slug}.md`));
|
|
88
|
+
const inProject = projHasFile;
|
|
89
|
+
const globalLines = inProject ? [] : cap(bullets(idxSplit[0] || ''), 25);
|
|
84
90
|
const indexLinks = cap(bullets(idxSplit[1] || ''), 12);
|
|
85
91
|
|
|
86
92
|
// Attach list: the current project, then its config links
|
|
87
|
-
// (transitive, deduped, max
|
|
93
|
+
// (transitive, deduped, max 8 total) and any --also names.
|
|
88
94
|
// private blocks reachability from repos that never linked it:
|
|
89
95
|
// direct links (siblings) and explicit --also attach fine; it only
|
|
90
96
|
// refuses when reached transitively through another project's links.
|
|
@@ -92,7 +98,7 @@ export function cmdContext(flags) {
|
|
|
92
98
|
const attach = [];
|
|
93
99
|
const seen = new Set();
|
|
94
100
|
const push = (slug, linked) => {
|
|
95
|
-
if (seen.has(slug) || attach.length >=
|
|
101
|
+
if (seen.has(slug) || attach.length >= 8) return;
|
|
96
102
|
seen.add(slug);
|
|
97
103
|
attach.push({ slug, name: bySlug.get(slug)?.name || slug, linked });
|
|
98
104
|
};
|
|
@@ -106,7 +112,7 @@ export function cmdContext(flags) {
|
|
|
106
112
|
);
|
|
107
113
|
const refused = [];
|
|
108
114
|
const unknown = [];
|
|
109
|
-
while (queue.length && attach.length <
|
|
115
|
+
while (queue.length && attach.length < 8) {
|
|
110
116
|
const { k: raw, via } = queue.shift();
|
|
111
117
|
const key = String(raw).trim().toLowerCase();
|
|
112
118
|
if (!key) continue;
|
|
@@ -131,13 +137,33 @@ export function cmdContext(flags) {
|
|
|
131
137
|
for (const a of attach) {
|
|
132
138
|
const pf = path.join(home, 'projects', `${a.slug}.md`);
|
|
133
139
|
if (!fs.existsSync(pf)) continue;
|
|
134
|
-
const body = cap(
|
|
135
|
-
bullets(fs.readFileSync(pf, 'utf8')),
|
|
136
|
-
a.linked ? 12 : 25
|
|
137
|
-
).join('\n');
|
|
140
|
+
const body = cap(bullets(fs.readFileSync(pf, 'utf8')), 25).join('\n');
|
|
138
141
|
lines.push('', `## Project: ${a.name}${a.linked ? ' (linked)' : ''}`);
|
|
139
142
|
if (body) lines.push(body);
|
|
140
143
|
}
|
|
144
|
+
// Outside-mode digest: the latest 25 project entries pooled across
|
|
145
|
+
// every project file, newest first by added: date. private projects
|
|
146
|
+
// never contribute (must not leak into foreign sessions); noglobal
|
|
147
|
+
// projects opt out of the pooled view the same way.
|
|
148
|
+
if (!inProject) {
|
|
149
|
+
const pool = [];
|
|
150
|
+
for (const p of listProjects(home)) {
|
|
151
|
+
const e = projectEntry(cfg, p);
|
|
152
|
+
if (e?.private || e?.noglobal) continue;
|
|
153
|
+
const pf = path.join(home, 'projects', `${p.slug}.md`);
|
|
154
|
+
for (const b of bullets(fs.readFileSync(pf, 'utf8'))) {
|
|
155
|
+
const d = (b.match(/added: (\d{4}-\d{2}-\d{2})/) || [])[1] || '';
|
|
156
|
+
pool.push({
|
|
157
|
+
d,
|
|
158
|
+
b: /project:/.test(b) ? b : b.replace(/^- /, `- (${p.name}) `),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
pool.sort((a, b) => b.d.localeCompare(a.d));
|
|
163
|
+
const recent = cap(pool.map((x) => x.b), 25);
|
|
164
|
+
if (recent.length)
|
|
165
|
+
lines.push('', '## Recent project memory', recent.join('\n'));
|
|
166
|
+
}
|
|
141
167
|
if (indexLinks.length) lines.push('', '## Index', indexLinks.join('\n'));
|
|
142
168
|
if (refused.length)
|
|
143
169
|
lines.push(
|
package/package.json
CHANGED
package/test/context.test.mjs
CHANGED
|
@@ -82,16 +82,38 @@ test('unlink removes the auto-attach', () => {
|
|
|
82
82
|
assert.ok(!run(['context'], { amrHome: home, cwd: repoB }).stdout.includes('x1 fact'));
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
test('
|
|
85
|
+
test('context in a known project is project-scoped (no global section)', () => {
|
|
86
86
|
const home = tmpdir();
|
|
87
87
|
const repo = initRepo(path.join(tmpdir(), 'iso'), 'https://github.com/acme/iso.git');
|
|
88
88
|
run(['ensure'], { amrHome: home });
|
|
89
89
|
run(['add', 'global pref'], { amrHome: home });
|
|
90
90
|
seed(home, repo, 'iso fact');
|
|
91
|
-
const
|
|
92
|
-
assert.match(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
assert.
|
|
96
|
-
|
|
91
|
+
const r = run(['context'], { amrHome: home, cwd: repo });
|
|
92
|
+
assert.match(r.stdout, /iso fact/);
|
|
93
|
+
assert.ok(!r.stdout.includes('global pref'));
|
|
94
|
+
assert.ok(!r.stdout.includes('## Global'));
|
|
95
|
+
assert.ok(!r.stdout.includes('## Recent project memory'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('outside context pools latest project entries; private and noglobal excluded', () => {
|
|
99
|
+
const home = tmpdir();
|
|
100
|
+
const base = tmpdir();
|
|
101
|
+
const repoA = initRepo(path.join(base, 'pa'), 'https://github.com/acme/pa.git');
|
|
102
|
+
const repoB = initRepo(path.join(base, 'pb'), 'https://github.com/acme/pb.git');
|
|
103
|
+
const repoC = initRepo(path.join(base, 'pc'), 'https://github.com/acme/pc.git');
|
|
104
|
+
const repoD = initRepo(path.join(base, 'pd'), 'https://github.com/acme/pd.git');
|
|
105
|
+
run(['ensure'], { amrHome: home });
|
|
106
|
+
seed(home, repoA, 'pa pooled fact');
|
|
107
|
+
seed(home, repoB, 'pb pooled fact');
|
|
108
|
+
seed(home, repoC, 'pc secret fact');
|
|
109
|
+
seed(home, repoD, 'pd quiet fact');
|
|
110
|
+
run(['policy', 'pc', 'private', 'true'], { amrHome: home, cwd: repoC });
|
|
111
|
+
run(['policy', 'pd', 'noglobal', 'true'], { amrHome: home, cwd: repoD });
|
|
112
|
+
const r = run(['context'], { amrHome: home, cwd: tmpdir() });
|
|
113
|
+
assert.equal(r.status, 0, r.stderr);
|
|
114
|
+
assert.match(r.stdout, /## Recent project memory/);
|
|
115
|
+
assert.match(r.stdout, /pa pooled fact/);
|
|
116
|
+
assert.match(r.stdout, /pb pooled fact/);
|
|
117
|
+
assert.ok(!r.stdout.includes('pc secret fact'));
|
|
118
|
+
assert.ok(!r.stdout.includes('pd quiet fact'));
|
|
97
119
|
});
|