@getbourdon/participants 0.2.0 → 0.2.1
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/dist/index.cjs +3194 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +327 -5
- package/dist/index.d.ts +970 -33
- package/dist/index.js +6509 -41
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
- package/dist/base.d.ts +0 -88
- package/dist/base.js +0 -52
- package/dist/network-base.d.ts +0 -108
- package/dist/network-base.js +0 -248
- package/dist/readers/cascade.d.ts +0 -49
- package/dist/readers/cascade.js +0 -428
- package/dist/readers/claude-code.d.ts +0 -33
- package/dist/readers/claude-code.js +0 -552
- package/dist/readers/copilot-cli.d.ts +0 -45
- package/dist/readers/copilot-cli.js +0 -416
- package/dist/readers/copilot.d.ts +0 -59
- package/dist/readers/copilot.js +0 -413
- package/dist/readers/cursor.d.ts +0 -50
- package/dist/readers/cursor.js +0 -670
- package/dist/readers/github-copilot.d.ts +0 -34
- package/dist/readers/github-copilot.js +0 -242
- package/dist/readers/hermes.d.ts +0 -37
- package/dist/readers/hermes.js +0 -426
- package/dist/readers/openclaw.d.ts +0 -78
- package/dist/readers/openclaw.js +0 -386
- package/dist/registry.d.ts +0 -40
- package/dist/registry.js +0 -85
- package/dist/sqlite-base.d.ts +0 -65
- package/dist/sqlite-base.js +0 -163
|
@@ -1,552 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code participant — reads claude-brain + auto-memory + MCP knowledge
|
|
3
|
-
* graph. Port of `participants/claude_code.py`.
|
|
4
|
-
*
|
|
5
|
-
* Three optional sources (the participant degrades gracefully):
|
|
6
|
-
* 1. claude-brain/ — git-synced markdown records
|
|
7
|
-
* 2. ~/.claude/projects/<ws>/memory/ — per-machine auto-memory frontmatter
|
|
8
|
-
* 3. ~/claude-memory/memory.jsonl — MCP knowledge-graph JSONL
|
|
9
|
-
*
|
|
10
|
-
* Privacy: entities typed `person` (+ family/contact variants) and observations
|
|
11
|
-
* containing credential-like strings default to PRIVATE and are filtered before
|
|
12
|
-
* federation. Entities are deduped by case-insensitive name and sorted
|
|
13
|
-
* alphabetically.
|
|
14
|
-
*/
|
|
15
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
16
|
-
import { homedir, hostname } from "node:os";
|
|
17
|
-
import { basename, join } from "node:path";
|
|
18
|
-
import { parse as yamlParse } from "yaml";
|
|
19
|
-
import { Visibility, filterForFederation, makeAgentInfo, makeEntity, makeManifest, makeSession, makeVisibilityPolicy, } from "@getbourdon/l5";
|
|
20
|
-
import { containsSecret } from "@getbourdon/redaction";
|
|
21
|
-
import { ParticipantDiscoveryError, SPEC_VERSION, } from "../base.js";
|
|
22
|
-
const AGENT_ID = "claude-code";
|
|
23
|
-
const AGENT_TYPE = "code-assistant";
|
|
24
|
-
const ROLE_NARRATIVE = "Agentic manager and code-assistant. Coordinates the RADLAB agent fleet, " +
|
|
25
|
-
"reviews PRs, and consults on architectural decisions. Capable of authoring " +
|
|
26
|
-
"code but typically reviews + delegates to specialised code-assistants like " +
|
|
27
|
-
"Codex for prime-code execution.";
|
|
28
|
-
const DEFAULT_POLICY = makeVisibilityPolicy({
|
|
29
|
-
default: Visibility.PUBLIC,
|
|
30
|
-
private_tags: ["personal", "financial", "credential", "health", "family", "legal"],
|
|
31
|
-
team_tags: ["internal-roadmap"],
|
|
32
|
-
});
|
|
33
|
-
/** Entity-type slugs that default to PRIVATE unless explicitly promoted. */
|
|
34
|
-
const PRIVATE_BY_DEFAULT_TYPES = new Set([
|
|
35
|
-
"person",
|
|
36
|
-
"user",
|
|
37
|
-
"individual",
|
|
38
|
-
"contact",
|
|
39
|
-
"family-member",
|
|
40
|
-
"family_member",
|
|
41
|
-
]);
|
|
42
|
-
const MAX_SUMMARY_CHARS = 500;
|
|
43
|
-
const MAX_OBSERVATIONS_IN_SUMMARY = 2;
|
|
44
|
-
// -- Path resolution ----------------------------------------------------------
|
|
45
|
-
function isDir(p) {
|
|
46
|
-
try {
|
|
47
|
-
return statSync(p).isDirectory();
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function isFile(p) {
|
|
54
|
-
try {
|
|
55
|
-
return statSync(p).isFile();
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function resolveClaudeBrainPath() {
|
|
62
|
-
const envOverride = process.env.CLAUDE_BRAIN;
|
|
63
|
-
if (envOverride && isDir(envOverride))
|
|
64
|
-
return envOverride;
|
|
65
|
-
const homeCandidate = join(homeDir(), "claude-brain");
|
|
66
|
-
if (isDir(homeCandidate))
|
|
67
|
-
return homeCandidate;
|
|
68
|
-
const cwdCandidate = join(process.cwd(), "claude-brain");
|
|
69
|
-
if (isDir(cwdCandidate))
|
|
70
|
-
return cwdCandidate;
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
function homeDir() {
|
|
74
|
-
// os.homedir() can throw under some sandboxes; stay defensive so import +
|
|
75
|
-
// construction never crash even when HOME points at an unreadable path.
|
|
76
|
-
try {
|
|
77
|
-
return homedir();
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function resolveAutoMemoryPath() {
|
|
84
|
-
try {
|
|
85
|
-
const base = join(homeDir(), ".claude", "projects");
|
|
86
|
-
if (!isDir(base))
|
|
87
|
-
return null;
|
|
88
|
-
for (const child of readdirSync(base)) {
|
|
89
|
-
const memMarker = join(base, child, "memory", "MEMORY.md");
|
|
90
|
-
if (isFile(memMarker))
|
|
91
|
-
return join(base, child, "memory");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
function resolveKnowledgeGraphPath() {
|
|
100
|
-
const candidate = join(homeDir(), "claude-memory", "memory.jsonl");
|
|
101
|
-
return isFile(candidate) ? candidate : null;
|
|
102
|
-
}
|
|
103
|
-
// -- Privacy scrubbing --------------------------------------------------------
|
|
104
|
-
function isPrivateType(entityType) {
|
|
105
|
-
if (!entityType)
|
|
106
|
-
return false;
|
|
107
|
-
const normalized = entityType.split("/").pop()?.trim().toLowerCase() ?? "";
|
|
108
|
-
return PRIVATE_BY_DEFAULT_TYPES.has(normalized);
|
|
109
|
-
}
|
|
110
|
-
// -- YAML frontmatter ---------------------------------------------------------
|
|
111
|
-
const FRONTMATTER_OPEN = "---\n";
|
|
112
|
-
const FRONTMATTER_CLOSE = "\n---\n";
|
|
113
|
-
function parseFrontmatter(text) {
|
|
114
|
-
if (!text.startsWith(FRONTMATTER_OPEN))
|
|
115
|
-
return { frontmatter: {}, body: text };
|
|
116
|
-
const end = text.indexOf(FRONTMATTER_CLOSE, FRONTMATTER_OPEN.length);
|
|
117
|
-
if (end === -1)
|
|
118
|
-
return { frontmatter: {}, body: text };
|
|
119
|
-
const fmText = text.slice(FRONTMATTER_OPEN.length, end);
|
|
120
|
-
const body = text.slice(end + FRONTMATTER_CLOSE.length);
|
|
121
|
-
let parsed;
|
|
122
|
-
try {
|
|
123
|
-
parsed = yamlParse(fmText);
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return { frontmatter: {}, body: text };
|
|
127
|
-
}
|
|
128
|
-
const fm = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
129
|
-
? parsed
|
|
130
|
-
: {};
|
|
131
|
-
return { frontmatter: fm, body };
|
|
132
|
-
}
|
|
133
|
-
// -- Parser: claude-brain PROJECTS + LOG --------------------------------------
|
|
134
|
-
const H1_RE = /^#[ \t]+(.+?)[ \t]*$/m;
|
|
135
|
-
const STATUS_SECTION_RE = /^##[ \t]+(?:Current[ \t]+)?Status[^\n]*$/im;
|
|
136
|
-
const STATUS_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/;
|
|
137
|
-
const TITLE_SPLIT_RE = /\s*[-—–:]+\s+/;
|
|
138
|
-
function extractH1Title(body) {
|
|
139
|
-
const match = H1_RE.exec(body);
|
|
140
|
-
if (!match)
|
|
141
|
-
return null;
|
|
142
|
-
const title = (match[1] ?? "").trim();
|
|
143
|
-
const head = title.split(TITLE_SPLIT_RE)[0] ?? title;
|
|
144
|
-
return head.trim();
|
|
145
|
-
}
|
|
146
|
-
function extractFirstParagraph(body, maxChars = MAX_SUMMARY_CHARS) {
|
|
147
|
-
const paragraph = [];
|
|
148
|
-
for (const line of body.split(/\r?\n/)) {
|
|
149
|
-
const stripped = line.trim();
|
|
150
|
-
if (!stripped) {
|
|
151
|
-
if (paragraph.length)
|
|
152
|
-
break;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (stripped.startsWith("#"))
|
|
156
|
-
continue;
|
|
157
|
-
paragraph.push(stripped);
|
|
158
|
-
if (paragraph.reduce((acc, s) => acc + s.length + 1, 0) > maxChars)
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
return paragraph.join(" ").trim().slice(0, maxChars).trim();
|
|
162
|
-
}
|
|
163
|
-
function extractStatusTag(body) {
|
|
164
|
-
const match = STATUS_SECTION_RE.exec(body);
|
|
165
|
-
if (!match)
|
|
166
|
-
return [];
|
|
167
|
-
const region = body.slice(match.index, match.index + 400).toLowerCase();
|
|
168
|
-
const tags = [];
|
|
169
|
-
for (const keyword of ["archived", "canceled", "cancelled", "active", "shipped", "blocked"]) {
|
|
170
|
-
if (region.includes(keyword))
|
|
171
|
-
tags.push(keyword.replace("cancelled", "canceled"));
|
|
172
|
-
}
|
|
173
|
-
return [...new Set(tags)];
|
|
174
|
-
}
|
|
175
|
-
const END_OF_LIFE_TAGS = new Set(["archived", "canceled"]);
|
|
176
|
-
function extractStatusDate(body) {
|
|
177
|
-
const match = STATUS_SECTION_RE.exec(body);
|
|
178
|
-
if (!match)
|
|
179
|
-
return null;
|
|
180
|
-
const region = body.slice(match.index, match.index + 400);
|
|
181
|
-
const dateMatch = STATUS_DATE_RE.exec(region);
|
|
182
|
-
return dateMatch ? (dateMatch[1] ?? null) : null;
|
|
183
|
-
}
|
|
184
|
-
function parseProjectOverview(overviewPath) {
|
|
185
|
-
let text;
|
|
186
|
-
try {
|
|
187
|
-
text = readFileSync(overviewPath, "utf8");
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
const { body } = parseFrontmatter(text);
|
|
193
|
-
const title = extractH1Title(body) || basename(join(overviewPath, ".."));
|
|
194
|
-
const summary = extractFirstParagraph(body);
|
|
195
|
-
const tags = extractStatusTag(body);
|
|
196
|
-
let validTo = null;
|
|
197
|
-
if (tags.some((t) => END_OF_LIFE_TAGS.has(t))) {
|
|
198
|
-
validTo = extractStatusDate(body);
|
|
199
|
-
if (validTo === null) {
|
|
200
|
-
try {
|
|
201
|
-
const mtime = statSync(overviewPath).mtime;
|
|
202
|
-
validTo = mtime.toISOString().slice(0, 10);
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
validTo = null;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return makeEntity({
|
|
210
|
-
name: title,
|
|
211
|
-
type: "project",
|
|
212
|
-
summary: summary || undefined,
|
|
213
|
-
tags,
|
|
214
|
-
valid_to: validTo ?? undefined,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function parseProjectsDir(brainPath) {
|
|
218
|
-
const projectsDir = join(brainPath, "PROJECTS");
|
|
219
|
-
if (!isDir(projectsDir))
|
|
220
|
-
return [];
|
|
221
|
-
const entities = [];
|
|
222
|
-
for (const name of readdirSync(projectsDir).sort()) {
|
|
223
|
-
const projectDir = join(projectsDir, name);
|
|
224
|
-
if (!isDir(projectDir))
|
|
225
|
-
continue;
|
|
226
|
-
const overview = join(projectDir, "OVERVIEW.md");
|
|
227
|
-
if (!isFile(overview))
|
|
228
|
-
continue;
|
|
229
|
-
const entity = parseProjectOverview(overview);
|
|
230
|
-
if (entity)
|
|
231
|
-
entities.push(entity);
|
|
232
|
-
}
|
|
233
|
-
return entities;
|
|
234
|
-
}
|
|
235
|
-
const LOG_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})(?:-(\w+?))?(?:-.*)?\.md$/;
|
|
236
|
-
function parseLogFile(logPath) {
|
|
237
|
-
const m = LOG_FILENAME_RE.exec(basename(logPath));
|
|
238
|
-
if (!m)
|
|
239
|
-
return null;
|
|
240
|
-
const sessionDate = m[1] ?? "";
|
|
241
|
-
let text;
|
|
242
|
-
try {
|
|
243
|
-
text = readFileSync(logPath, "utf8");
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
const { body } = parseFrontmatter(text);
|
|
249
|
-
const headline = extractFirstParagraph(body, 250);
|
|
250
|
-
return makeSession({
|
|
251
|
-
date: sessionDate,
|
|
252
|
-
key_actions: headline ? [headline] : [],
|
|
253
|
-
files_touched: [`LOG/${basename(logPath)}`],
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
function parseLogsDir(brainPath, since, limit = 100) {
|
|
257
|
-
const logDir = join(brainPath, "LOG");
|
|
258
|
-
if (!isDir(logDir))
|
|
259
|
-
return [];
|
|
260
|
-
const candidates = [];
|
|
261
|
-
for (const fname of readdirSync(logDir)) {
|
|
262
|
-
if (!fname.endsWith(".md"))
|
|
263
|
-
continue;
|
|
264
|
-
const m = LOG_FILENAME_RE.exec(fname);
|
|
265
|
-
if (!m)
|
|
266
|
-
continue;
|
|
267
|
-
const d = m[1] ?? "";
|
|
268
|
-
if (since) {
|
|
269
|
-
if (d < since.toISOString().slice(0, 10))
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
candidates.push([d, join(logDir, fname)]);
|
|
273
|
-
}
|
|
274
|
-
candidates.sort((a, b) => (a[0] < b[0] ? 1 : a[0] > b[0] ? -1 : 0)); // newest first
|
|
275
|
-
const sessions = [];
|
|
276
|
-
for (const [, logFile] of candidates.slice(0, limit)) {
|
|
277
|
-
const session = parseLogFile(logFile);
|
|
278
|
-
if (session)
|
|
279
|
-
sessions.push(session);
|
|
280
|
-
}
|
|
281
|
-
return sessions;
|
|
282
|
-
}
|
|
283
|
-
// -- Parser: auto-memory ------------------------------------------------------
|
|
284
|
-
function parseAutoMemoryEntity(mdPath) {
|
|
285
|
-
let text;
|
|
286
|
-
try {
|
|
287
|
-
text = readFileSync(mdPath, "utf8");
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
const { frontmatter, body } = parseFrontmatter(text);
|
|
293
|
-
const name = frontmatter["name"] ||
|
|
294
|
-
extractH1Title(body) ||
|
|
295
|
-
basename(mdPath).replace(/\.md$/, "");
|
|
296
|
-
const entityType = frontmatter["type"];
|
|
297
|
-
const summaryRaw = frontmatter["description"] || extractFirstParagraph(body);
|
|
298
|
-
const rawTags = frontmatter["tags"];
|
|
299
|
-
const tags = Array.isArray(rawTags) ? rawTags.map((t) => String(t)) : [];
|
|
300
|
-
const visibility = isPrivateType(entityType) ? Visibility.PRIVATE : undefined;
|
|
301
|
-
return makeEntity({
|
|
302
|
-
name: String(name),
|
|
303
|
-
type: entityType ? String(entityType) : undefined,
|
|
304
|
-
summary: summaryRaw ? String(summaryRaw).slice(0, MAX_SUMMARY_CHARS).trim() : undefined,
|
|
305
|
-
tags,
|
|
306
|
-
visibility,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
function parseAutoMemory(memoryPath) {
|
|
310
|
-
if (!isDir(memoryPath))
|
|
311
|
-
return [];
|
|
312
|
-
const entities = [];
|
|
313
|
-
for (const fname of readdirSync(memoryPath).sort()) {
|
|
314
|
-
if (!fname.endsWith(".md") || fname === "MEMORY.md")
|
|
315
|
-
continue;
|
|
316
|
-
const entity = parseAutoMemoryEntity(join(memoryPath, fname));
|
|
317
|
-
if (entity)
|
|
318
|
-
entities.push(entity);
|
|
319
|
-
}
|
|
320
|
-
return entities;
|
|
321
|
-
}
|
|
322
|
-
// -- Parser: knowledge graph JSONL --------------------------------------------
|
|
323
|
-
function graphEntityToBourdonEntity(record) {
|
|
324
|
-
const name = record["name"];
|
|
325
|
-
if (!name || typeof name !== "string")
|
|
326
|
-
return null;
|
|
327
|
-
const entityTypeSlug = record["entityType"] || "";
|
|
328
|
-
const normalizedType = entityTypeSlug ? (entityTypeSlug.split("/").pop() ?? null) : null;
|
|
329
|
-
const observations = Array.isArray(record["observations"])
|
|
330
|
-
? record["observations"]
|
|
331
|
-
: [];
|
|
332
|
-
const safeObs = [];
|
|
333
|
-
for (const obs of observations.slice(0, MAX_OBSERVATIONS_IN_SUMMARY)) {
|
|
334
|
-
if (typeof obs !== "string")
|
|
335
|
-
continue;
|
|
336
|
-
safeObs.push(containsSecret(obs) ? "[redacted -- contains credential-like content]" : obs);
|
|
337
|
-
}
|
|
338
|
-
const joined = safeObs.join(" | ").slice(0, MAX_SUMMARY_CHARS).trim();
|
|
339
|
-
const summary = joined || undefined;
|
|
340
|
-
const hasCredObs = observations.some((o) => typeof o === "string" && containsSecret(o));
|
|
341
|
-
const visibility = isPrivateType(entityTypeSlug) || hasCredObs ? Visibility.PRIVATE : undefined;
|
|
342
|
-
const tags = normalizedType && normalizedType !== name.toLowerCase() ? [normalizedType] : [];
|
|
343
|
-
return makeEntity({
|
|
344
|
-
name,
|
|
345
|
-
type: normalizedType ?? undefined,
|
|
346
|
-
summary,
|
|
347
|
-
tags,
|
|
348
|
-
visibility,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
function parseKnowledgeGraph(graphPath) {
|
|
352
|
-
if (!isFile(graphPath))
|
|
353
|
-
return [];
|
|
354
|
-
let raw;
|
|
355
|
-
try {
|
|
356
|
-
raw = readFileSync(graphPath, "utf8");
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
return [];
|
|
360
|
-
}
|
|
361
|
-
const entities = [];
|
|
362
|
-
for (const lineRaw of raw.split(/\r?\n/)) {
|
|
363
|
-
const line = lineRaw.trim();
|
|
364
|
-
if (!line)
|
|
365
|
-
continue;
|
|
366
|
-
let record;
|
|
367
|
-
try {
|
|
368
|
-
record = JSON.parse(line);
|
|
369
|
-
}
|
|
370
|
-
catch {
|
|
371
|
-
continue; // skip malformed JSONL (logged at WARNING in the oracle)
|
|
372
|
-
}
|
|
373
|
-
if (typeof record !== "object" || record === null)
|
|
374
|
-
continue;
|
|
375
|
-
const rec = record;
|
|
376
|
-
if (rec["type"] !== "entity")
|
|
377
|
-
continue;
|
|
378
|
-
const entity = graphEntityToBourdonEntity(rec);
|
|
379
|
-
if (entity)
|
|
380
|
-
entities.push(entity);
|
|
381
|
-
}
|
|
382
|
-
return entities;
|
|
383
|
-
}
|
|
384
|
-
// -- Dedupe + merge -----------------------------------------------------------
|
|
385
|
-
const VIS_ORDER = {
|
|
386
|
-
[Visibility.PRIVATE]: 0,
|
|
387
|
-
[Visibility.TEAM]: 1,
|
|
388
|
-
[Visibility.PUBLIC]: 2,
|
|
389
|
-
none: 3,
|
|
390
|
-
};
|
|
391
|
-
function visRank(v) {
|
|
392
|
-
return v == null ? VIS_ORDER["none"] : VIS_ORDER[v];
|
|
393
|
-
}
|
|
394
|
-
function mergeEntities(into, other) {
|
|
395
|
-
if (!into.type && other.type)
|
|
396
|
-
into.type = other.type;
|
|
397
|
-
if (other.summary && other.summary.trim() && other.summary.length > (into.summary?.length ?? 0)) {
|
|
398
|
-
into.summary = other.summary;
|
|
399
|
-
}
|
|
400
|
-
into.tags = [...new Set([...(into.tags ?? []), ...(other.tags ?? [])])];
|
|
401
|
-
if (visRank(other.visibility) < visRank(into.visibility)) {
|
|
402
|
-
into.visibility = other.visibility;
|
|
403
|
-
}
|
|
404
|
-
return into;
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Merge multiple entity lists into one, deduping by case-insensitive name.
|
|
408
|
-
* NOTE the faithful oracle quirk: the fresh copy DROPS valid_from / valid_to, so
|
|
409
|
-
* an archived project keeps its `archived` tag but emits no `valid_to`. Do not
|
|
410
|
-
* "fix" this — parity breaks otherwise.
|
|
411
|
-
*/
|
|
412
|
-
function dedupeEntities(sourceLists) {
|
|
413
|
-
const byKey = new Map();
|
|
414
|
-
for (const source of sourceLists) {
|
|
415
|
-
for (const entity of source) {
|
|
416
|
-
const key = entity.name.trim().toLowerCase();
|
|
417
|
-
if (!key)
|
|
418
|
-
continue;
|
|
419
|
-
const existing = byKey.get(key);
|
|
420
|
-
if (existing) {
|
|
421
|
-
mergeEntities(existing, entity);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
byKey.set(key, makeEntity({
|
|
425
|
-
name: entity.name,
|
|
426
|
-
type: entity.type,
|
|
427
|
-
aliases: [...(entity.aliases ?? [])],
|
|
428
|
-
summary: entity.summary,
|
|
429
|
-
last_touched: entity.last_touched,
|
|
430
|
-
tags: [...(entity.tags ?? [])],
|
|
431
|
-
visibility: entity.visibility,
|
|
432
|
-
// valid_from / valid_to intentionally dropped (oracle behavior).
|
|
433
|
-
}));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
return [...byKey.values()].sort((a, b) => {
|
|
438
|
-
const an = a.name.toLowerCase();
|
|
439
|
-
const bn = b.name.toLowerCase();
|
|
440
|
-
return an < bn ? -1 : an > bn ? 1 : 0;
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
// -- Participant --------------------------------------------------------------
|
|
444
|
-
/** External participant for Anthropic's Claude Code CLI. */
|
|
445
|
-
export class ClaudeCodeParticipant {
|
|
446
|
-
agentId = AGENT_ID;
|
|
447
|
-
agentType = AGENT_TYPE;
|
|
448
|
-
nativePath;
|
|
449
|
-
/** Public so a hermetic test can seed the discovered sources directly (path
|
|
450
|
-
* RESOLUTION is not the parity contract — only the export output shape is). */
|
|
451
|
-
brainPath;
|
|
452
|
-
autoMemoryPath;
|
|
453
|
-
knowledgeGraphPath;
|
|
454
|
-
static defaultNativePath(home) {
|
|
455
|
-
return join(home ?? homeDir(), ".claude");
|
|
456
|
-
}
|
|
457
|
-
constructor() {
|
|
458
|
-
this.nativePath = join(homeDir(), "claude-brain"); // primary anchor
|
|
459
|
-
this.brainPath = resolveClaudeBrainPath();
|
|
460
|
-
this.autoMemoryPath = resolveAutoMemoryPath();
|
|
461
|
-
this.knowledgeGraphPath = resolveKnowledgeGraphPath();
|
|
462
|
-
}
|
|
463
|
-
discover() {
|
|
464
|
-
const sources = {
|
|
465
|
-
claude_brain: this.brainPath,
|
|
466
|
-
auto_memory: this.autoMemoryPath,
|
|
467
|
-
knowledge_graph: this.knowledgeGraphPath,
|
|
468
|
-
};
|
|
469
|
-
if (!Object.values(sources).some((v) => v)) {
|
|
470
|
-
throw new ParticipantDiscoveryError("No Claude Code memory sources found. Expected one of: ~/claude-brain/, " +
|
|
471
|
-
"~/.claude/projects/*/memory/, ~/claude-memory/memory.jsonl.");
|
|
472
|
-
}
|
|
473
|
-
return {
|
|
474
|
-
path: this.nativePath,
|
|
475
|
-
version: "claude-code-memory-v1",
|
|
476
|
-
metadata: { sources },
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
exportL5(since) {
|
|
480
|
-
const store = this.discover(); // re-raises on missing
|
|
481
|
-
const sources = store.metadata["sources"];
|
|
482
|
-
const capabilities = Object.entries(sources)
|
|
483
|
-
.filter(([, v]) => v)
|
|
484
|
-
.map(([k]) => k)
|
|
485
|
-
.sort();
|
|
486
|
-
const autoMemoryEntities = this.autoMemoryPath ? parseAutoMemory(this.autoMemoryPath) : [];
|
|
487
|
-
const graphEntities = this.knowledgeGraphPath
|
|
488
|
-
? parseKnowledgeGraph(this.knowledgeGraphPath)
|
|
489
|
-
: [];
|
|
490
|
-
const projectEntities = this.brainPath ? parseProjectsDir(this.brainPath) : [];
|
|
491
|
-
// Priority: auto-memory > graph > brain projects (first wins on collision).
|
|
492
|
-
const allEntities = dedupeEntities([autoMemoryEntities, graphEntities, projectEntities]);
|
|
493
|
-
// Visibility filter — private entities never leave this function.
|
|
494
|
-
const visibleEntities = filterForFederation(allEntities, DEFAULT_POLICY);
|
|
495
|
-
const sessions = this.brainPath ? parseLogsDir(this.brainPath, since) : [];
|
|
496
|
-
return makeManifest({
|
|
497
|
-
spec_version: SPEC_VERSION,
|
|
498
|
-
agent: makeAgentInfo({
|
|
499
|
-
id: this.agentId,
|
|
500
|
-
type: this.agentType,
|
|
501
|
-
instance: hostname() || "unknown",
|
|
502
|
-
spec_version_compat: `>=${SPEC_VERSION}`,
|
|
503
|
-
role_narrative: ROLE_NARRATIVE,
|
|
504
|
-
}),
|
|
505
|
-
last_updated: new Date().toISOString(),
|
|
506
|
-
capabilities,
|
|
507
|
-
recent_sessions: sessions,
|
|
508
|
-
known_entities: visibleEntities,
|
|
509
|
-
visibility_policy: DEFAULT_POLICY,
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
exportSessions(since, limit = 100) {
|
|
513
|
-
if (!this.brainPath)
|
|
514
|
-
return [];
|
|
515
|
-
return parseLogsDir(this.brainPath, since, limit);
|
|
516
|
-
}
|
|
517
|
-
healthCheck() {
|
|
518
|
-
try {
|
|
519
|
-
const present = [this.brainPath, this.autoMemoryPath, this.knowledgeGraphPath].filter((p) => p !== null).length;
|
|
520
|
-
const details = {
|
|
521
|
-
claude_brain: this.brainPath ?? "missing",
|
|
522
|
-
auto_memory: this.autoMemoryPath ?? "missing",
|
|
523
|
-
knowledge_graph: this.knowledgeGraphPath ?? "missing",
|
|
524
|
-
};
|
|
525
|
-
if (present === 3)
|
|
526
|
-
return { status: "ok", details };
|
|
527
|
-
if (present === 0) {
|
|
528
|
-
return {
|
|
529
|
-
status: "blocked",
|
|
530
|
-
reason: "No Claude Code memory sources found on this machine",
|
|
531
|
-
details,
|
|
532
|
-
proposedFix: "Run `bourdon setup` to wire the SessionEnd hook and bootstrap " +
|
|
533
|
-
"~/agent-library/, then open Claude Code once to populate ~/.claude/projects/.",
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
return {
|
|
537
|
-
status: "degraded",
|
|
538
|
-
reason: `${present}/3 Claude Code memory sources found`,
|
|
539
|
-
details,
|
|
540
|
-
proposedFix: "Run `bourdon setup` to (re-)wire missing sources, or set the CLAUDE_BRAIN " +
|
|
541
|
-
"env var to point at your claude-brain checkout.",
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
catch (err) {
|
|
545
|
-
return {
|
|
546
|
-
status: "degraded",
|
|
547
|
-
reason: `unexpected error: ${String(err)}`,
|
|
548
|
-
details: {},
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Copilot CLI participant — normalize the `copilot` terminal agent's
|
|
3
|
-
* SQLite session store into L5. Port of `participants/copilot_cli.py`.
|
|
4
|
-
*
|
|
5
|
-
* Copilot CLI stores session history in a SQLite database at
|
|
6
|
-
* `~/.copilot/session-store.db` (override `$COPILOT_CLI_HOME`). This is distinct
|
|
7
|
-
* from:
|
|
8
|
-
* - the VS Code Copilot Chat extension (`github-copilot` network reader)
|
|
9
|
-
* - the `~/.copilot-bourdon/memory.md` convention-file participant (`copilot`)
|
|
10
|
-
* - GitHub-embedded Copilot (no local state)
|
|
11
|
-
*
|
|
12
|
-
* The schema (schema_version 4) carries `sessions` / `turns` / `checkpoints`
|
|
13
|
-
* plus `session_files` / `session_refs` / `dynamic_context_items`. The reader
|
|
14
|
-
* opens the DB read-only (`mode=ro` via {@link tryOpenReadonly}) so a live
|
|
15
|
-
* writer's lock can never block the export, probes each table defensively (a
|
|
16
|
-
* missing table degrades to "no rows" rather than raising), redacts every native
|
|
17
|
-
* string, applies the visibility filter BEFORE emission, and is deterministic.
|
|
18
|
-
* `healthCheck` never throws.
|
|
19
|
-
*/
|
|
20
|
-
import { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
|
|
21
|
-
import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
|
|
22
|
-
/**
|
|
23
|
-
* Conventional `~/.copilot/` directory. Respects the `COPILOT_CLI_HOME`
|
|
24
|
-
* environment-variable override (mirrors `default_copilot_cli_dir`).
|
|
25
|
-
*/
|
|
26
|
-
export declare function defaultCopilotCliDir(): string;
|
|
27
|
-
/**
|
|
28
|
-
* External participant for GitHub Copilot CLI (`~/.copilot/session-store.db`).
|
|
29
|
-
* Reads the SQLite database read-only (never locks the live db).
|
|
30
|
-
*/
|
|
31
|
-
export declare class CopilotCliParticipant implements BourdonParticipant {
|
|
32
|
-
agentId: string;
|
|
33
|
-
agentType: string;
|
|
34
|
-
displayName: string;
|
|
35
|
-
nativePath: string;
|
|
36
|
-
private readonly copilotDir;
|
|
37
|
-
/** Conventional `~/.copilot` dir used by the setup wizard's detection. */
|
|
38
|
-
static defaultNativePath(home?: string): string;
|
|
39
|
-
constructor(copilotDir?: string);
|
|
40
|
-
discover(): AgentStore;
|
|
41
|
-
exportSessions(since?: Date, limit?: number): SessionModel[];
|
|
42
|
-
exportL5(since?: Date): L5ManifestModel;
|
|
43
|
-
healthCheck(): HealthStatus;
|
|
44
|
-
private emptyManifest;
|
|
45
|
-
}
|