@agfpd/iapeer-memory 0.2.8 → 0.2.9
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 +2 -2
- package/src/cli.ts +8 -0
- package/src/commands/archive-stale.ts +87 -0
- package/src/commands/hook.ts +197 -28
- package/src/commands/render.ts +4 -1
- package/src/paths.ts +3 -0
- package/src/provision.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agfpd/iapeer-memory",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "iapeer-memory — peer memory for the iapeer ecosystem: vault, memoryd (index/search/MCP-http), layer-5 context fragments, role doctrines. The package IS the system; the claude/codex plugins are thin session sockets (docs/10-distribution.md, ADR-009).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agfpd/iapeer-memory-core": "0.2.
|
|
30
|
+
"@agfpd/iapeer-memory-core": "0.2.9"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/bun": "^1.2.0",
|
package/src/cli.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { cmdInstallBinary } from "./commands/install-binary.js";
|
|
|
25
25
|
import { cmdMemoryd } from "./commands/memoryd.js";
|
|
26
26
|
import { cmdMigrate } from "./commands/migrate.js";
|
|
27
27
|
import { cmdDreamCollect } from "./commands/dream-collect.js";
|
|
28
|
+
import { cmdArchiveStale } from "./commands/archive-stale.js";
|
|
28
29
|
import { cmdProvisionPeer, cmdUnprovisionPeer } from "./commands/provision-peer.js";
|
|
29
30
|
import { cmdRender } from "./commands/render.js";
|
|
30
31
|
import { cmdStatus } from "./commands/status.js";
|
|
@@ -68,6 +69,11 @@ Commands:
|
|
|
68
69
|
batched tasks, from the LIVE registry (read-only).
|
|
69
70
|
--gate: no output, exit 0 iff there is work (the
|
|
70
71
|
notifier check that decides if DreamWeaver wakes)
|
|
72
|
+
archive-stale [--commit] deliberate backlog archiver (lean §2.2a): move
|
|
73
|
+
pre-existing stale notes to the archive. Dry-run by
|
|
74
|
+
default (lists what would move); --commit executes.
|
|
75
|
+
03_Projects is excluded; memoryd archives ongoing
|
|
76
|
+
staleness incrementally on its own.
|
|
71
77
|
render index|fragment|doctrine|guide
|
|
72
78
|
render one artifact explicitly (memoryd does this
|
|
73
79
|
continuously; render is the manual/scripted path)
|
|
@@ -134,6 +140,8 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
134
140
|
return cmdInstallBinary(rest, egress);
|
|
135
141
|
case "dream-collect":
|
|
136
142
|
return cmdDreamCollect(rest, egress);
|
|
143
|
+
case "archive-stale":
|
|
144
|
+
return cmdArchiveStale(rest);
|
|
137
145
|
case "provision-peer":
|
|
138
146
|
return cmdProvisionPeer(rest, egress);
|
|
139
147
|
case "unprovision-peer":
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `iapeer-memory archive-stale [--commit]` — the DELIBERATE backlog archiver
|
|
3
|
+
* (lean §2.2a, decision boris 15.06).
|
|
4
|
+
*
|
|
5
|
+
* memoryd archives notes incrementally as they BECOME stale (an edit fires the
|
|
6
|
+
* change pass). Pre-existing stale notes are NOT swept on startup (that would
|
|
7
|
+
* be a mass move as a side-effect of a daemon boot — banned by §1: bulk
|
|
8
|
+
* actions are deliberate and verifiable). This verb is that deliberate path:
|
|
9
|
+
*
|
|
10
|
+
* archive-stale DRY-RUN — list what would move + a count
|
|
11
|
+
* archive-stale --commit actually move them
|
|
12
|
+
*
|
|
13
|
+
* `03_Projects` is excluded (a completed phase is a project record, not
|
|
14
|
+
* displaced knowledge — `isArchivableZone`). memoryd reindexes the moves on
|
|
15
|
+
* its next pass (or on restart); the verb itself only moves files.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import {
|
|
21
|
+
configFromEnv,
|
|
22
|
+
snapshotVault,
|
|
23
|
+
shouldArchive,
|
|
24
|
+
archiveTargetRel,
|
|
25
|
+
} from "@agfpd/iapeer-memory-core";
|
|
26
|
+
|
|
27
|
+
export function cmdArchiveStale(argv: string[]): number {
|
|
28
|
+
const commit = argv.includes("--commit");
|
|
29
|
+
const config = configFromEnv();
|
|
30
|
+
const vault = config.vaultPath;
|
|
31
|
+
const taxonomy = config.taxonomy;
|
|
32
|
+
|
|
33
|
+
// Candidates: notes in the monitored content folders carrying a final
|
|
34
|
+
// status, EXCLUDING 03_Projects (filtered by shouldArchive → isArchivableZone).
|
|
35
|
+
const snap = snapshotVault(vault, taxonomy);
|
|
36
|
+
const reserved = new Set<string>(); // archive targets claimed within this run
|
|
37
|
+
const moves: Array<{ from: string; to: string }> = [];
|
|
38
|
+
for (const rel of snap.keys()) {
|
|
39
|
+
let content: string;
|
|
40
|
+
try {
|
|
41
|
+
content = fs.readFileSync(path.join(vault, rel), "utf-8");
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!shouldArchive(rel, content, taxonomy)) continue;
|
|
46
|
+
const to = archiveTargetRel(
|
|
47
|
+
path.basename(rel),
|
|
48
|
+
taxonomy,
|
|
49
|
+
(r) => reserved.has(r) || fs.existsSync(path.join(vault, r)),
|
|
50
|
+
);
|
|
51
|
+
reserved.add(to);
|
|
52
|
+
moves.push({ from: rel, to });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (moves.length === 0) {
|
|
56
|
+
console.log("archive-stale: no stale notes outside the archive — nothing to do.");
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!commit) {
|
|
61
|
+
console.log(
|
|
62
|
+
`archive-stale (DRY-RUN): ${moves.length} stale note(s) WOULD move to ${taxonomy.folders.archive}/:`,
|
|
63
|
+
);
|
|
64
|
+
for (const m of moves) console.log(` ${m.from} → ${m.to}`);
|
|
65
|
+
console.log(
|
|
66
|
+
`\nPass --commit to move them. (03_Projects is excluded — completed phases stay project-local.)`,
|
|
67
|
+
);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let moved = 0;
|
|
72
|
+
for (const m of moves) {
|
|
73
|
+
const toAbs = path.join(vault, m.to);
|
|
74
|
+
try {
|
|
75
|
+
fs.mkdirSync(path.dirname(toAbs), { recursive: true });
|
|
76
|
+
fs.renameSync(path.join(vault, m.from), toAbs);
|
|
77
|
+
moved += 1;
|
|
78
|
+
console.log(` moved: ${m.from} → ${m.to}`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(` FAILED: ${m.from} (${String(err)})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.log(
|
|
84
|
+
`archive-stale: moved ${moved}/${moves.length}. memoryd reindexes on its next pass (or restart).`,
|
|
85
|
+
);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
package/src/commands/hook.ts
CHANGED
|
@@ -39,6 +39,13 @@ import {
|
|
|
39
39
|
getTaxonomy,
|
|
40
40
|
isLocaleId,
|
|
41
41
|
resolveAgentName,
|
|
42
|
+
resolveZone,
|
|
43
|
+
splitFrontmatter,
|
|
44
|
+
parseNoteTags,
|
|
45
|
+
parseDictionaryTags,
|
|
46
|
+
tagGateProblems,
|
|
47
|
+
tagsDictionarySourceRel,
|
|
48
|
+
type TaxonomyPreset,
|
|
42
49
|
} from "@agfpd/iapeer-memory-core";
|
|
43
50
|
import { memoryPaths, type MemoryPaths } from "../paths.js";
|
|
44
51
|
import { DEFAULT_HEARTBEAT_STALE_MS } from "./verify.js";
|
|
@@ -113,15 +120,6 @@ export type PostWriteResult = {
|
|
|
113
120
|
output: string | null;
|
|
114
121
|
};
|
|
115
122
|
|
|
116
|
-
export function reminderText(inboxFolder: string): string {
|
|
117
|
-
return (
|
|
118
|
-
"[iapeer-memory] New note in your agent memory. Check the guide's " +
|
|
119
|
-
"canon-vs-memory filter: does any part of it belong to the team's shared " +
|
|
120
|
-
`knowledge? If yes — also drop a draft into ${inboxFolder}/ and mention ` +
|
|
121
|
-
"this note inline as [[Title]] in the draft body; the Index will link them."
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
123
|
export function runPostWrite(
|
|
126
124
|
eventJson: string,
|
|
127
125
|
env: Record<string, string | undefined> = process.env,
|
|
@@ -182,26 +180,194 @@ export function runPostWrite(
|
|
|
182
180
|
stamp: true,
|
|
183
181
|
});
|
|
184
182
|
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
183
|
+
// TAG GATE (lean §3): validate the canon note's tags against the dictionary
|
|
184
|
+
// and teach the author to fix any problem (unknown tag / no tag). The guard
|
|
185
|
+
// stays SILENT on a clean write (§2.3) — output is non-null ONLY on a
|
|
186
|
+
// problem. RUNTIME-AGNOSTIC: codex supports PostToolUse `additionalContext`
|
|
187
|
+
// too (official codex hooks docs — the earlier «claude-only» was wrong), so
|
|
188
|
+
// the SAME schema reaches both runtimes. `files` already covers claude
|
|
189
|
+
// Write/Edit and codex apply_patch (multi-file).
|
|
190
|
+
const problems = collectTagProblems(files, vault, taxonomy);
|
|
191
|
+
const output = problems.length
|
|
192
|
+
? JSON.stringify({
|
|
193
|
+
hookSpecificOutput: {
|
|
194
|
+
hookEventName: "PostToolUse",
|
|
195
|
+
additionalContext: tagTeaching(problems),
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
: null;
|
|
202
199
|
return { stamped: true, output };
|
|
203
200
|
}
|
|
204
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Tag-gate problems across the just-written CANON files (lean §3). Reads the
|
|
204
|
+
* dictionary from the vault; FAIL-OPEN — an unreadable/empty dictionary (e.g.
|
|
205
|
+
* an evicted iCloud placeholder) yields no problems rather than rejecting
|
|
206
|
+
* every tag. Operative/inbox zones are not gated (canon only).
|
|
207
|
+
*/
|
|
208
|
+
export function collectTagProblems(
|
|
209
|
+
files: string[],
|
|
210
|
+
vault: string,
|
|
211
|
+
taxonomy: TaxonomyPreset,
|
|
212
|
+
): string[] {
|
|
213
|
+
const dictRel = tagsDictionarySourceRel(taxonomy);
|
|
214
|
+
let allow: Set<string> | null = null;
|
|
215
|
+
try {
|
|
216
|
+
const dict = fs.readFileSync(path.join(vault, dictRel), "utf-8");
|
|
217
|
+
if (dict.trim()) allow = new Set(parseDictionaryTags(dict));
|
|
218
|
+
} catch {
|
|
219
|
+
// fail-open
|
|
220
|
+
}
|
|
221
|
+
if (!allow) return [];
|
|
222
|
+
const out: string[] = [];
|
|
223
|
+
for (const f of files) {
|
|
224
|
+
if (resolveZone(f, vault, taxonomy) !== "permanent") continue;
|
|
225
|
+
let fm: string;
|
|
226
|
+
try {
|
|
227
|
+
fm = splitFrontmatter(fs.readFileSync(f, "utf-8"))[0];
|
|
228
|
+
} catch {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const problems = tagGateProblems(parseNoteTags(fm), allow, {
|
|
232
|
+
requireAtLeastOne: true,
|
|
233
|
+
dictionaryRel: dictRel,
|
|
234
|
+
});
|
|
235
|
+
for (const p of problems) out.push(`${path.basename(f)}: ${p}`);
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function tagTeaching(problems: string[]): string {
|
|
241
|
+
return (
|
|
242
|
+
"[iapeer-memory] tag check — fix so this canon note indexes cleanly:\n" +
|
|
243
|
+
problems.map((p) => `- ${p}`).join("\n")
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── dedup hint (lean §3a) ──────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/** Short fail-open budget for the dedup RPC — a slow/down memoryd must never
|
|
250
|
+
* hang a write-hook (same posture as the embedding circuit-breaker). */
|
|
251
|
+
export const DEDUP_TIMEOUT_MS = 1500;
|
|
252
|
+
|
|
253
|
+
type DedupResponse = {
|
|
254
|
+
enabled: boolean;
|
|
255
|
+
matches: Array<{ path: string; title: string; similarity: number }>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** POST the note body to memoryd's loopback /dedup RPC through the egress hub
|
|
259
|
+
* (loopback allowance — П6 topology). Fail-open: any error (timeout,
|
|
260
|
+
* connection refused, bad JSON) → null → the caller stays silent. */
|
|
261
|
+
async function dedupFetch(
|
|
262
|
+
egress: Egress,
|
|
263
|
+
env: Record<string, string | undefined>,
|
|
264
|
+
content: string,
|
|
265
|
+
): Promise<DedupResponse | null> {
|
|
266
|
+
const port = env.IAPEER_MEMORY_MCP_PORT || "8766";
|
|
267
|
+
const thr = env.IAPEER_MEMORY_DEDUP_THRESHOLD;
|
|
268
|
+
const body: { content: string; threshold?: number } = { content };
|
|
269
|
+
if (thr && !Number.isNaN(Number(thr))) body.threshold = Number(thr);
|
|
270
|
+
const controller = new AbortController();
|
|
271
|
+
const timer = setTimeout(() => controller.abort(), DEDUP_TIMEOUT_MS);
|
|
272
|
+
try {
|
|
273
|
+
const res = await egress.fetch(`http://127.0.0.1:${port}/dedup`, {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "Content-Type": "application/json" },
|
|
276
|
+
body: JSON.stringify(body),
|
|
277
|
+
signal: controller.signal,
|
|
278
|
+
});
|
|
279
|
+
if (!res.ok) return null;
|
|
280
|
+
return (await res.json()) as DedupResponse;
|
|
281
|
+
} catch {
|
|
282
|
+
return null; // fail-open
|
|
283
|
+
} finally {
|
|
284
|
+
clearTimeout(timer);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Dedup hint lines for a just-written CANON note (lean §3a). Re-parses the
|
|
290
|
+
* event independently of `runPostWrite` (keeps that sync contract intact).
|
|
291
|
+
* Claude-only (Write/Edit → additionalContext); canon-zone only; queries
|
|
292
|
+
* memoryd by the note BODY. Embeddings-off → memoryd returns enabled:false →
|
|
293
|
+
* no hint (silent).
|
|
294
|
+
*/
|
|
295
|
+
export async function collectDedupHints(
|
|
296
|
+
eventJson: string,
|
|
297
|
+
egress: Egress,
|
|
298
|
+
env: Record<string, string | undefined> = process.env,
|
|
299
|
+
): Promise<string[]> {
|
|
300
|
+
let event: {
|
|
301
|
+
tool_name?: string;
|
|
302
|
+
cwd?: string;
|
|
303
|
+
tool_input?: { file_path?: string };
|
|
304
|
+
tool_response?: unknown;
|
|
305
|
+
};
|
|
306
|
+
try {
|
|
307
|
+
event = JSON.parse(eventJson) as typeof event;
|
|
308
|
+
} catch {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
const tool = event.tool_name ?? "";
|
|
312
|
+
if (!POST_WRITE_TOOLS.has(tool)) return [];
|
|
313
|
+
const vault = env.IAPEER_MEMORY_VAULT_PATH ?? "";
|
|
314
|
+
if (!vault) return [];
|
|
315
|
+
const localeRaw = env.IAPEER_MEMORY_LOCALE || "en";
|
|
316
|
+
if (!isLocaleId(localeRaw)) return [];
|
|
317
|
+
const taxonomy = getTaxonomy(localeRaw);
|
|
318
|
+
// Candidate files: claude Write/Edit carry one file_path; codex apply_patch
|
|
319
|
+
// carries a patch over possibly many (RUNTIME-AGNOSTIC — codex receives the
|
|
320
|
+
// additionalContext hint via the same channel).
|
|
321
|
+
const candidates =
|
|
322
|
+
tool === "apply_patch" ? applyPatchPaths(event) : [event.tool_input?.file_path ?? ""];
|
|
323
|
+
const vaultPrefix = vault.endsWith(path.sep) ? vault : vault + path.sep;
|
|
324
|
+
const files = candidates.filter(
|
|
325
|
+
(p) => p.endsWith(".md") && p.startsWith(vaultPrefix) && fs.existsSync(p),
|
|
326
|
+
);
|
|
327
|
+
const hints: string[] = [];
|
|
328
|
+
for (const file of files) {
|
|
329
|
+
if (resolveZone(file, vault, taxonomy) !== "permanent") continue; // canon only (§3a)
|
|
330
|
+
let body: string;
|
|
331
|
+
try {
|
|
332
|
+
body = splitFrontmatter(fs.readFileSync(file, "utf-8"))[1];
|
|
333
|
+
} catch {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (!body.trim()) continue;
|
|
337
|
+
const result = await dedupFetch(egress, env, body);
|
|
338
|
+
if (!result?.enabled || !result.matches?.length) continue;
|
|
339
|
+
for (const m of result.matches) {
|
|
340
|
+
if (path.basename(m.path) === path.basename(file)) continue; // self-guard (belt + braces)
|
|
341
|
+
hints.push(`[[${m.title}]] (${Math.round(m.similarity * 100)}%)`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return hints;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Combine the sync tag-teaching output with async dedup hints into one
|
|
348
|
+
* additionalContext blob (or null when both are empty). */
|
|
349
|
+
export function mergeHookOutput(tagOutput: string | null, dedupHints: string[]): string | null {
|
|
350
|
+
let ctx = "";
|
|
351
|
+
if (tagOutput) {
|
|
352
|
+
try {
|
|
353
|
+
ctx = (JSON.parse(tagOutput).hookSpecificOutput?.additionalContext as string) ?? "";
|
|
354
|
+
} catch {
|
|
355
|
+
ctx = "";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (dedupHints.length) {
|
|
359
|
+
const dedupText =
|
|
360
|
+
"[iapeer-memory] possible duplicate(s) of this canon note — verify, then extend " +
|
|
361
|
+
"the existing note or keep only new material:\n" +
|
|
362
|
+
dedupHints.map((h) => `- ${h}`).join("\n");
|
|
363
|
+
ctx = ctx ? `${ctx}\n\n${dedupText}` : dedupText;
|
|
364
|
+
}
|
|
365
|
+
if (!ctx) return null;
|
|
366
|
+
return JSON.stringify({
|
|
367
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: ctx },
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
205
371
|
// ── session-start ────────────────────────────────────────────────────────────
|
|
206
372
|
|
|
207
373
|
export type SessionStartResult = {
|
|
@@ -298,8 +464,11 @@ export async function cmdHook(argv: string[], egress: Egress): Promise<number> {
|
|
|
298
464
|
try {
|
|
299
465
|
switch (event) {
|
|
300
466
|
case "post-write": {
|
|
301
|
-
const
|
|
302
|
-
|
|
467
|
+
const text = await Bun.stdin.text();
|
|
468
|
+
const result = runPostWrite(text); // sync: stamp + tag gate
|
|
469
|
+
const dedupHints = await collectDedupHints(text, egress); // async, fail-open §3a
|
|
470
|
+
const output = mergeHookOutput(result.output, dedupHints);
|
|
471
|
+
if (output) console.log(output);
|
|
303
472
|
return 0;
|
|
304
473
|
}
|
|
305
474
|
case "session-start": {
|
package/src/commands/render.ts
CHANGED
|
@@ -125,7 +125,10 @@ function renderFragment(argv: string[]): number {
|
|
|
125
125
|
logs: paths.logsDir,
|
|
126
126
|
},
|
|
127
127
|
authorIndexPath: indexFile,
|
|
128
|
-
|
|
128
|
+
// lean §3: the compact projection goes to EVERY peer (memoryd renders the
|
|
129
|
+
// projection file; a missing file is skipped gracefully by buildLayers).
|
|
130
|
+
tagsProjectionPath: paths.tagsProjectionPath,
|
|
131
|
+
tagsTitle: config.taxonomy.systemFiles.tagsDictionary,
|
|
129
132
|
};
|
|
130
133
|
const written = renderPeerFragment({ peerCwd, env });
|
|
131
134
|
console.log(`render fragment: ${written}`);
|
package/src/paths.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type MemoryPaths = {
|
|
|
32
32
|
heartbeatPath: string;
|
|
33
33
|
hashStatePath: string;
|
|
34
34
|
tagsMirrorPath: string;
|
|
35
|
+
/** Compact tags-dictionary projection, injected to all peers (lean §3). */
|
|
36
|
+
tagsProjectionPath: string;
|
|
35
37
|
/** Roles manifest (written by init, read by verify/render): role → peerCwd/template. */
|
|
36
38
|
rolesManifestPath: string;
|
|
37
39
|
/** Rendered author indexes (`<agent>-vault-index.md` + `-full` variant). */
|
|
@@ -92,6 +94,7 @@ export function memoryPaths(
|
|
|
92
94
|
heartbeatPath: path.join(stateDir, "memoryd.heartbeat"),
|
|
93
95
|
hashStatePath: path.join(stateDir, "memoryd.hashes.json"),
|
|
94
96
|
tagsMirrorPath: path.join(cacheDir, "tags-dictionary.md"),
|
|
97
|
+
tagsProjectionPath: path.join(cacheDir, "tags-projection.md"),
|
|
95
98
|
rolesManifestPath: path.join(stateDir, "roles.json"),
|
|
96
99
|
indexesDir: path.join(stateDir, "indexes"),
|
|
97
100
|
pidPath: path.join(stateDir, "memoryd.pid"),
|
package/src/provision.ts
CHANGED
|
@@ -176,6 +176,15 @@ export function defaultConfigContent(opts: ConfigContentOptions): string {
|
|
|
176
176
|
"# Curator personalities exempt from needs_review stamping (ADR-006).",
|
|
177
177
|
"# IAPEER_MEMORY_CURATOR_SET=index,scriber,dreamweaver",
|
|
178
178
|
"",
|
|
179
|
+
"# Lean §3: per-tag boundary budget in the injected dictionary projection",
|
|
180
|
+
"# (×whole fleet — keep tight). Boundary text over this many chars is clipped.",
|
|
181
|
+
"# IAPEER_MEMORY_TAGS_BOUNDARY_MAXLEN=160",
|
|
182
|
+
"",
|
|
183
|
+
"# Lean §3a: dedup hint on canon writes — raw cosine similarity threshold",
|
|
184
|
+
"# above which an existing canon note is surfaced as a possible duplicate.",
|
|
185
|
+
"# Needs embeddings (semantic); with embeddings off the hint is silent.",
|
|
186
|
+
"# IAPEER_MEMORY_DEDUP_THRESHOLD=0.82",
|
|
187
|
+
"",
|
|
179
188
|
"# Weekly dream-tick (deterministic pre-filter → DreamWeaver). Schedule is",
|
|
180
189
|
"# 5-field cron; the window is days BY TIME (not since-last-tick).",
|
|
181
190
|
"# IAPEER_MEMORY_DREAM_CRON=0 4 * * 1",
|