bilmem 0.1.0 → 0.2.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 +163 -20
- package/dist/{canonical-CglQQ0xc.d.ts → canonical-JS6HUWGc.d.ts} +24 -1
- package/dist/cli/index.js +1433 -200
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +52 -5
- package/dist/core/index.js +623 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{filesystem-B3qAej0l.d.ts → filesystem-CrAkhr85.d.ts} +1 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +1451 -303
- package/dist/index.js.map +1 -1
- package/dist/install/index.d.ts +59 -3
- package/dist/install/index.js +1245 -111
- package/dist/install/index.js.map +1 -1
- package/dist/jev/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +12 -1
- package/dist/mcp/index.js +846 -97
- package/dist/mcp/index.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/scanner/index.d.ts +2 -2
- package/dist/scanner/index.js +6 -1
- package/dist/scanner/index.js.map +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/stats/index.d.ts +3 -3
- package/dist/stats/index.js +45 -1
- package/dist/stats/index.js.map +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +6 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/{types-Nxq5Gzay.d.ts → types-DNlvHsj9.d.ts} +120 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -316,7 +316,7 @@ function createKnowledge(params) {
|
|
|
316
316
|
type: params.type ?? "lesson",
|
|
317
317
|
status,
|
|
318
318
|
stage: initialStage,
|
|
319
|
-
statement: params.statement.trim(),
|
|
319
|
+
statement: (params.statement ?? params.question ?? params.id).trim(),
|
|
320
320
|
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
321
321
|
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
322
322
|
relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
|
|
@@ -346,6 +346,11 @@ function createKnowledge(params) {
|
|
|
346
346
|
},
|
|
347
347
|
history,
|
|
348
348
|
value: 0,
|
|
349
|
+
lesson: params.lesson,
|
|
350
|
+
failure_details: params.failure_details,
|
|
351
|
+
context: params.context,
|
|
352
|
+
contradicts: params.contradicts ? Array.from(new Set(params.contradicts)) : void 0,
|
|
353
|
+
blocking: params.blocking,
|
|
349
354
|
createdAt: now,
|
|
350
355
|
updatedAt: now
|
|
351
356
|
};
|
|
@@ -1710,6 +1715,7 @@ var Retriever = class {
|
|
|
1710
1715
|
// src/core/resolver.ts
|
|
1711
1716
|
var RELATIVE_COSTS = {
|
|
1712
1717
|
USE_KNOWLEDGE: 1,
|
|
1718
|
+
REVALIDATE: 2,
|
|
1713
1719
|
INSPECT_TEST: 2,
|
|
1714
1720
|
INSPECT_FILE: 2,
|
|
1715
1721
|
INSPECT_SYMBOL: 2.5,
|
|
@@ -2050,14 +2056,624 @@ async function resolveEpistemicQuery(options, storage, retriever) {
|
|
|
2050
2056
|
};
|
|
2051
2057
|
}
|
|
2052
2058
|
|
|
2059
|
+
// src/core/preflight.ts
|
|
2060
|
+
import { execFile } from "child_process";
|
|
2061
|
+
import { promisify } from "util";
|
|
2062
|
+
import * as path from "path";
|
|
2063
|
+
import * as fs from "fs/promises";
|
|
2064
|
+
import YAML from "yaml";
|
|
2065
|
+
var execFileAsync = promisify(execFile);
|
|
2066
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
2067
|
+
"add",
|
|
2068
|
+
"implement",
|
|
2069
|
+
"create",
|
|
2070
|
+
"support",
|
|
2071
|
+
"with",
|
|
2072
|
+
"the",
|
|
2073
|
+
"a",
|
|
2074
|
+
"an",
|
|
2075
|
+
"for",
|
|
2076
|
+
"in",
|
|
2077
|
+
"to",
|
|
2078
|
+
"and",
|
|
2079
|
+
"of",
|
|
2080
|
+
"on",
|
|
2081
|
+
"from",
|
|
2082
|
+
"using",
|
|
2083
|
+
"via",
|
|
2084
|
+
"into",
|
|
2085
|
+
"by",
|
|
2086
|
+
"our",
|
|
2087
|
+
"new",
|
|
2088
|
+
"use",
|
|
2089
|
+
"make",
|
|
2090
|
+
"allow",
|
|
2091
|
+
"enable",
|
|
2092
|
+
"provide",
|
|
2093
|
+
"setup",
|
|
2094
|
+
"set"
|
|
2095
|
+
]);
|
|
2096
|
+
var DOMAIN_KEYWORDS = {
|
|
2097
|
+
authentication: ["auth", "oauth", "login", "logout", "session", "jwt", "token", "credential", "sso", "password"],
|
|
2098
|
+
session: ["session", "cookie", "cookies", "expiry", "refresh", "token"],
|
|
2099
|
+
"user-identity": ["user", "account", "profile", "identity", "member", "role", "permission"],
|
|
2100
|
+
database: ["db", "database", "sql", "postgres", "sqlite", "migration", "schema", "table", "query"],
|
|
2101
|
+
storage: ["storage", "s3", "bucket", "file", "upload", "disk", "blob"],
|
|
2102
|
+
api: ["api", "endpoint", "http", "rest", "route", "controller", "middleware", "graphql"],
|
|
2103
|
+
caching: ["cache", "redis", "memcached", "ttl", "invalidation"],
|
|
2104
|
+
billing: ["payment", "stripe", "checkout", "billing", "subscription", "invoice", "credit"],
|
|
2105
|
+
"data-processing": ["import", "export", "csv", "bulk", "batch", "stream", "pipeline"]
|
|
2106
|
+
};
|
|
2107
|
+
function tokenizeGoal(goal) {
|
|
2108
|
+
return goal.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
2109
|
+
}
|
|
2110
|
+
function inferLikelyDomains(tokens, knowledgeTags) {
|
|
2111
|
+
const domains = /* @__PURE__ */ new Set();
|
|
2112
|
+
for (const token of tokens) {
|
|
2113
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
2114
|
+
if (keywords.includes(token) || token.includes(domain)) {
|
|
2115
|
+
domains.add(domain);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
for (const tag of knowledgeTags) {
|
|
2120
|
+
const norm = tag.toLowerCase().trim();
|
|
2121
|
+
if (DOMAIN_KEYWORDS[norm]) {
|
|
2122
|
+
domains.add(norm);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return Array.from(domains);
|
|
2126
|
+
}
|
|
2127
|
+
function classifyUnknown(unknown, goalTokens, domainCues) {
|
|
2128
|
+
const text = `${unknown.id} ${unknown.statement || ""} ${unknown.question || unknown.next?.question || ""}`.toLowerCase();
|
|
2129
|
+
if (unknown.blocking === true) {
|
|
2130
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge|choice)\b/i.test(text);
|
|
2131
|
+
return {
|
|
2132
|
+
classification: "BLOCKING",
|
|
2133
|
+
isDecision: isDecision2 || true,
|
|
2134
|
+
reason: unknown.reason || "Blocking unknown identified for this goal"
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
if (unknown.blocking === false) {
|
|
2138
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|choice)\b/i.test(text);
|
|
2139
|
+
return {
|
|
2140
|
+
classification: "NON_BLOCKING",
|
|
2141
|
+
isDecision: isDecision2,
|
|
2142
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
const isNonBlocking = /\b(analytics|telemetry|metric|event name|logging level|log format|button color|styling|theme)\b/i.test(text);
|
|
2146
|
+
if (isNonBlocking) {
|
|
2147
|
+
return {
|
|
2148
|
+
classification: "NON_BLOCKING",
|
|
2149
|
+
isDecision: /\b(should|which name|choice)\b/i.test(text),
|
|
2150
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
const isDecision = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge)\b/i.test(text);
|
|
2154
|
+
const isBlockingCandidate = /\b(link|linking|collision|duplicate|merge|security|permission|storage-engine|protocol|auth-flow|credentials)\b/i.test(text);
|
|
2155
|
+
const overlapsGoal = goalTokens.some((t) => text.includes(t)) || domainCues.some((d) => text.includes(d));
|
|
2156
|
+
if (isBlockingCandidate && (overlapsGoal || unknown.reason === "UNRESOLVABLE_LOCALLY")) {
|
|
2157
|
+
return {
|
|
2158
|
+
classification: "BLOCKING",
|
|
2159
|
+
isDecision: true,
|
|
2160
|
+
reason: "Unresolved architectural or behavioral decision critical to goal implementation"
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
return {
|
|
2164
|
+
classification: "IMPORTANT",
|
|
2165
|
+
isDecision,
|
|
2166
|
+
reason: isDecision ? "Product or behavior decision requiring attention" : "Important implementation detail"
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
function prioritizeQuestions(rawQuestions, blockingUnknowns) {
|
|
2170
|
+
const consolidated = /* @__PURE__ */ new Map();
|
|
2171
|
+
for (const rq of rawQuestions) {
|
|
2172
|
+
const qText = rq.question.trim();
|
|
2173
|
+
if (!qText) continue;
|
|
2174
|
+
const topic = rq.targetTopic || qText.toLowerCase().replace(/[^a-z0-9]/g, " ").slice(0, 30);
|
|
2175
|
+
let expectedResolutionValue = 1;
|
|
2176
|
+
if (rq.isBlocking) expectedResolutionValue += 2;
|
|
2177
|
+
if (rq.outcomeScore && rq.outcomeScore > 0) expectedResolutionValue += 1;
|
|
2178
|
+
if (rq.outcomeScore && rq.outcomeScore < 0) expectedResolutionValue -= 0.5;
|
|
2179
|
+
const interactionCost = 1;
|
|
2180
|
+
const priorityScore = Math.max(0.1, expectedResolutionValue) / interactionCost;
|
|
2181
|
+
const existing = consolidated.get(topic);
|
|
2182
|
+
if (!existing || priorityScore > (existing.priorityScore ?? 0)) {
|
|
2183
|
+
consolidated.set(topic, {
|
|
2184
|
+
question: qText,
|
|
2185
|
+
reason: rq.reason,
|
|
2186
|
+
targetTopic: rq.targetTopic,
|
|
2187
|
+
expectedResolutionValue,
|
|
2188
|
+
interactionCost,
|
|
2189
|
+
priorityScore
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return Array.from(consolidated.values()).sort((a, b) => (b.priorityScore ?? 0) - (a.priorityScore ?? 0));
|
|
2194
|
+
}
|
|
2195
|
+
async function executePreflight(options, storage, retriever, index) {
|
|
2196
|
+
const goal = options.goal.trim();
|
|
2197
|
+
const explicitPositive = (options.positive ?? []).map((p) => p.trim().toLowerCase());
|
|
2198
|
+
const negative = (options.negative ?? []).map((n) => n.trim().toLowerCase());
|
|
2199
|
+
const scope = options.scope ?? "project";
|
|
2200
|
+
const goalTokens = tokenizeGoal(goal);
|
|
2201
|
+
const allKnowledge = await storage.getAllKnowledge();
|
|
2202
|
+
const allTags = allKnowledge.flatMap((k) => k.tags ?? []);
|
|
2203
|
+
const likelyDomains = inferLikelyDomains(goalTokens, allTags);
|
|
2204
|
+
const contextualPositive = Array.from(/* @__PURE__ */ new Set([...explicitPositive, ...likelyDomains, ...goalTokens]));
|
|
2205
|
+
let candidatePool = [];
|
|
2206
|
+
if (retriever) {
|
|
2207
|
+
const jevCandidates = await retriever.lookup({
|
|
2208
|
+
query: goal,
|
|
2209
|
+
positive: contextualPositive,
|
|
2210
|
+
negative,
|
|
2211
|
+
scope,
|
|
2212
|
+
limit: options.maxCandidates ?? 25,
|
|
2213
|
+
includeRejected: true
|
|
2214
|
+
});
|
|
2215
|
+
for (const r of jevCandidates) {
|
|
2216
|
+
if (r.knowledge) candidatePool.push(r.knowledge);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (candidatePool.length === 0) {
|
|
2220
|
+
candidatePool = allKnowledge.filter((k) => k.status !== "invalid" && k.status !== "superseded");
|
|
2221
|
+
} else {
|
|
2222
|
+
for (const k of allKnowledge) {
|
|
2223
|
+
if (!candidatePool.some((c) => c.id === k.id) && k.status !== "invalid" && k.status !== "superseded") {
|
|
2224
|
+
const matchesToken = goalTokens.some((t) => k.id.toLowerCase().includes(t) || k.statement.toLowerCase().includes(t));
|
|
2225
|
+
const matchesCues = k.positive.some((p) => contextualPositive.includes(p));
|
|
2226
|
+
if (matchesToken || matchesCues) {
|
|
2227
|
+
candidatePool.push(k);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
const jevResults = rerankCandidates(candidatePool, {
|
|
2233
|
+
query: goal,
|
|
2234
|
+
positive: contextualPositive,
|
|
2235
|
+
negative,
|
|
2236
|
+
scope
|
|
2237
|
+
});
|
|
2238
|
+
const matchingItems = [];
|
|
2239
|
+
const rejectedItems = [];
|
|
2240
|
+
for (const res of jevResults) {
|
|
2241
|
+
if (res.status === "REJECT") {
|
|
2242
|
+
const reason = res.explanation.negativeConflicts.length > 0 ? `Inapplicable: ${res.explanation.negativeConflicts.join(", ")} conflicts with current context` : res.explanation.reasons[0] || "Context mismatch";
|
|
2243
|
+
rejectedItems.push({ id: res.knowledge.id, reason });
|
|
2244
|
+
} else {
|
|
2245
|
+
matchingItems.push(res.knowledge);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
if (options.deep) {
|
|
2249
|
+
try {
|
|
2250
|
+
const { stdout: diffOutput } = await execFileAsync(
|
|
2251
|
+
"git",
|
|
2252
|
+
["diff", "--name-only", "HEAD~1"],
|
|
2253
|
+
{ cwd: storage.getBaseDir() ? path.dirname(storage.getBaseDir()) : process.cwd() }
|
|
2254
|
+
);
|
|
2255
|
+
const changedFiles = diffOutput.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
2256
|
+
if (changedFiles.length > 0) {
|
|
2257
|
+
const staleness = checkKnowledgeListStaleness(matchingItems, changedFiles);
|
|
2258
|
+
for (const affected of staleness.affectedKnowledge) {
|
|
2259
|
+
const matchIdx = matchingItems.findIndex((m) => m.id === affected.id);
|
|
2260
|
+
if (matchIdx >= 0) {
|
|
2261
|
+
matchingItems[matchIdx] = affected;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
} catch {
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
const constraints = [];
|
|
2269
|
+
const lessons = [];
|
|
2270
|
+
const pastFailures = [];
|
|
2271
|
+
const stale = [];
|
|
2272
|
+
const known = [];
|
|
2273
|
+
let conflictedCount = 0;
|
|
2274
|
+
const CONSTRAINT_PATTERN = /\b(must|must not|never|only|cannot|require|required|boundary|strictly|prohibited|forbidden)\b/i;
|
|
2275
|
+
const FAILURE_PATTERN = /(fail|bug|error|crash|duplicate write|race condition|pressure|leak|revert|timeout)/i;
|
|
2276
|
+
const rootDir = storage.getBaseDir() ? path.dirname(storage.getBaseDir()) : process.cwd();
|
|
2277
|
+
for (const k of matchingItems) {
|
|
2278
|
+
const matchesContradiction = k.status === "contradicted" || k.contradicts && k.contradicts.some((cid) => matchingItems.some((m) => m.id === cid));
|
|
2279
|
+
if (matchesContradiction) {
|
|
2280
|
+
conflictedCount++;
|
|
2281
|
+
}
|
|
2282
|
+
let isStale = k.status === "needs_revalidation" || k.status === "stale";
|
|
2283
|
+
let staleReason = k.history?.[k.history.length - 1]?.reason || "Supporting source changed after last validation";
|
|
2284
|
+
const changedDeps = [];
|
|
2285
|
+
const candidateDeps = [...k.depends_on ?? [], ...k.evidence ?? []];
|
|
2286
|
+
const timestampStr = k.updated_at ?? k.updatedAt ?? k.createdAt;
|
|
2287
|
+
const knowledgeTime = new Date(timestampStr).getTime();
|
|
2288
|
+
for (const dep of candidateDeps) {
|
|
2289
|
+
const cleanPath = dep.replace(/^(file|code|test|doc):/, "").trim();
|
|
2290
|
+
if (!cleanPath) continue;
|
|
2291
|
+
const absPath = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(rootDir, cleanPath);
|
|
2292
|
+
try {
|
|
2293
|
+
const stat4 = await fs.stat(absPath);
|
|
2294
|
+
if (stat4.mtimeMs > knowledgeTime + 500) {
|
|
2295
|
+
isStale = true;
|
|
2296
|
+
changedDeps.push(cleanPath);
|
|
2297
|
+
staleReason = `Supporting source changed after last validation (${cleanPath})`;
|
|
2298
|
+
}
|
|
2299
|
+
} catch {
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (isStale) {
|
|
2303
|
+
stale.push({
|
|
2304
|
+
id: k.id,
|
|
2305
|
+
statement: k.statement,
|
|
2306
|
+
reason: staleReason,
|
|
2307
|
+
changedSources: changedDeps.length > 0 ? changedDeps : k.depends_on
|
|
2308
|
+
});
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
const isConstraint = k.type === "rule" || k.type === "decision" || k.type === "convention" || CONSTRAINT_PATTERN.test(k.statement);
|
|
2312
|
+
if (isConstraint) {
|
|
2313
|
+
constraints.push({
|
|
2314
|
+
id: k.id,
|
|
2315
|
+
statement: k.statement,
|
|
2316
|
+
type: k.type,
|
|
2317
|
+
confidence: k.confidence,
|
|
2318
|
+
confidenceTier: k.confidenceTier,
|
|
2319
|
+
source: k.evidence?.[0]
|
|
2320
|
+
});
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
const isFailure = k.type === "failure" || k.statement.startsWith("[FAILURE]") || k.statement.startsWith("[REVERT]") || FAILURE_PATTERN.test(k.statement) || k.metrics.failed_outcomes > 0;
|
|
2324
|
+
if (isFailure) {
|
|
2325
|
+
pastFailures.push({
|
|
2326
|
+
id: k.id,
|
|
2327
|
+
statement: k.statement,
|
|
2328
|
+
context: k.context || {
|
|
2329
|
+
positive: k.positive,
|
|
2330
|
+
negative: k.negative
|
|
2331
|
+
},
|
|
2332
|
+
failure: k.statement,
|
|
2333
|
+
lesson: k.lesson || k.reasoning || k.resolves?.[0],
|
|
2334
|
+
evidence: k.evidence
|
|
2335
|
+
});
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
const isLesson = k.type === "lesson" && (k.id.includes("lesson") || k.tags?.includes("lesson") || k.statement.toLowerCase().includes("lesson")) || k.statement.toLowerCase().startsWith("lesson:") || k.statement.toLowerCase().includes("lesson:");
|
|
2339
|
+
if (isLesson) {
|
|
2340
|
+
lessons.push({
|
|
2341
|
+
id: k.id,
|
|
2342
|
+
statement: k.statement,
|
|
2343
|
+
confidence: k.confidence,
|
|
2344
|
+
domain: k.tags?.[0],
|
|
2345
|
+
evidence: k.evidence
|
|
2346
|
+
});
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
if (k.status === "active" || k.stage === "active" || k.stage === "probation" || !k.status) {
|
|
2350
|
+
known.push({
|
|
2351
|
+
id: k.id,
|
|
2352
|
+
statement: k.statement,
|
|
2353
|
+
confidence: k.confidence,
|
|
2354
|
+
confidenceTier: k.confidenceTier,
|
|
2355
|
+
stage: k.stage
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const unknownsFromStorage = await storage.getAllUnknowns();
|
|
2360
|
+
const unknownKnowledge = allKnowledge.filter((k) => k.type === "unknown");
|
|
2361
|
+
const unifiedUnknowns = [];
|
|
2362
|
+
const seenUnknownIds = /* @__PURE__ */ new Set();
|
|
2363
|
+
for (const u of unknownsFromStorage) {
|
|
2364
|
+
seenUnknownIds.add(u.id);
|
|
2365
|
+
unifiedUnknowns.push({
|
|
2366
|
+
id: u.id,
|
|
2367
|
+
statement: u.statement || u.id,
|
|
2368
|
+
reason: u.reason,
|
|
2369
|
+
question: u.question || u.next?.question,
|
|
2370
|
+
next: u.next,
|
|
2371
|
+
blocking: u.blocking,
|
|
2372
|
+
positive: u.positive,
|
|
2373
|
+
negative: u.negative,
|
|
2374
|
+
checked: u.checked
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
for (const k of unknownKnowledge) {
|
|
2378
|
+
if (!seenUnknownIds.has(k.id)) {
|
|
2379
|
+
seenUnknownIds.add(k.id);
|
|
2380
|
+
unifiedUnknowns.push({
|
|
2381
|
+
id: k.id,
|
|
2382
|
+
statement: k.statement,
|
|
2383
|
+
reason: k.history?.[0]?.reason,
|
|
2384
|
+
question: k.question || (k.statement.includes("?") ? k.statement : void 0),
|
|
2385
|
+
next: {
|
|
2386
|
+
type: "ask",
|
|
2387
|
+
target: k.id,
|
|
2388
|
+
question: k.question || k.statement
|
|
2389
|
+
},
|
|
2390
|
+
blocking: k.blocking,
|
|
2391
|
+
positive: k.positive,
|
|
2392
|
+
negative: k.negative,
|
|
2393
|
+
checked: k.depends_on || k.evidence
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
const unknowns = [];
|
|
2398
|
+
const missing = [];
|
|
2399
|
+
const rawQuestions = [];
|
|
2400
|
+
for (const u of unifiedUnknowns) {
|
|
2401
|
+
const text = `${u.id} ${u.statement || ""} ${u.question || ""}`.toLowerCase();
|
|
2402
|
+
const overlaps = goalTokens.some((t) => text.includes(t)) || likelyDomains.some((d) => text.includes(d)) || u.positive && u.positive.some((p) => contextualPositive.includes(p));
|
|
2403
|
+
if (u.negative && u.negative.some((n) => contextualPositive.includes(n))) {
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (overlaps) {
|
|
2407
|
+
const { classification, isDecision, reason } = classifyUnknown(u, goalTokens, likelyDomains);
|
|
2408
|
+
const isBlocking = classification === "BLOCKING";
|
|
2409
|
+
unknowns.push({
|
|
2410
|
+
id: u.id,
|
|
2411
|
+
statement: u.statement,
|
|
2412
|
+
question: u.question || u.statement,
|
|
2413
|
+
classification,
|
|
2414
|
+
blocking: isBlocking,
|
|
2415
|
+
reason
|
|
2416
|
+
});
|
|
2417
|
+
if (isDecision) {
|
|
2418
|
+
missing.push({
|
|
2419
|
+
type: "decision",
|
|
2420
|
+
subject: u.id,
|
|
2421
|
+
question: u.question || u.statement,
|
|
2422
|
+
reason
|
|
2423
|
+
});
|
|
2424
|
+
} else {
|
|
2425
|
+
missing.push({
|
|
2426
|
+
type: "evidence",
|
|
2427
|
+
subject: u.id,
|
|
2428
|
+
question: u.question || u.statement,
|
|
2429
|
+
reason
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
const qText = u.question || (u.statement && u.statement.includes("?") ? u.statement : void 0);
|
|
2433
|
+
if (qText) {
|
|
2434
|
+
rawQuestions.push({
|
|
2435
|
+
question: qText,
|
|
2436
|
+
reason: `Unknown: ${u.id} (${reason})`,
|
|
2437
|
+
targetTopic: u.id,
|
|
2438
|
+
isBlocking
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
const questionKnowledge = allKnowledge.filter((k) => k.type === "question");
|
|
2444
|
+
for (const qk of questionKnowledge) {
|
|
2445
|
+
const matchesCues = qk.positive.some((p) => contextualPositive.includes(p));
|
|
2446
|
+
const conflicts = qk.negative.some((n) => negative.includes(n));
|
|
2447
|
+
if (matchesCues && !conflicts && qk.question) {
|
|
2448
|
+
rawQuestions.push({
|
|
2449
|
+
question: qk.question,
|
|
2450
|
+
reason: `Learned question for [${qk.positive.join(", ")}] uncertainty`,
|
|
2451
|
+
targetTopic: qk.resolves?.[0] || qk.id,
|
|
2452
|
+
outcomeScore: (qk.metrics?.successful_outcomes ?? 0) - (qk.metrics?.failed_outcomes ?? 0)
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
const questions = prioritizeQuestions(rawQuestions, unknowns);
|
|
2457
|
+
const receipts = await storage.getAllReceipts();
|
|
2458
|
+
const alreadyInvestigated = [];
|
|
2459
|
+
const investigatedSources = /* @__PURE__ */ new Set();
|
|
2460
|
+
for (const r of receipts) {
|
|
2461
|
+
const qTokens = r.question.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2462
|
+
const overlapsGoal = qTokens.some((t) => goalTokens.includes(t));
|
|
2463
|
+
const overlapsCues = r.positive && r.positive.some((p) => contextualPositive.includes(p));
|
|
2464
|
+
if (overlapsGoal || overlapsCues) {
|
|
2465
|
+
for (const checked of r.checked) {
|
|
2466
|
+
investigatedSources.add(checked.toLowerCase());
|
|
2467
|
+
alreadyInvestigated.push({
|
|
2468
|
+
source: checked,
|
|
2469
|
+
result: r.findings[0] || r.rejected[0]?.reason || "Did not establish requested behavior",
|
|
2470
|
+
nextSuggestion: "ASK_USER"
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
let readiness = "READY";
|
|
2476
|
+
const totalRelevantKnowledge = known.length + constraints.length + lessons.length + pastFailures.length + stale.length;
|
|
2477
|
+
const hasBlockingUnknown = unknowns.some((u) => u.blocking);
|
|
2478
|
+
const hasMissingDecision = missing.some((m) => m.type === "decision");
|
|
2479
|
+
if (totalRelevantKnowledge === 0 && unknowns.length === 0) {
|
|
2480
|
+
readiness = "INSUFFICIENT_KNOWLEDGE";
|
|
2481
|
+
} else if (conflictedCount > 0) {
|
|
2482
|
+
readiness = "CONFLICTED";
|
|
2483
|
+
} else if (hasBlockingUnknown || hasMissingDecision) {
|
|
2484
|
+
readiness = "NEEDS_CLARIFICATION";
|
|
2485
|
+
} else if (stale.length > 0) {
|
|
2486
|
+
readiness = "NEEDS_REVALIDATION";
|
|
2487
|
+
} else {
|
|
2488
|
+
readiness = "READY";
|
|
2489
|
+
}
|
|
2490
|
+
const next = [];
|
|
2491
|
+
for (const m of missing) {
|
|
2492
|
+
if (m.type === "decision") {
|
|
2493
|
+
next.push({
|
|
2494
|
+
type: "ASK_USER",
|
|
2495
|
+
target: m.subject,
|
|
2496
|
+
reason: m.question || m.reason,
|
|
2497
|
+
relativeCost: 1,
|
|
2498
|
+
expectedInfoGain: 0.95
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
for (const s of stale) {
|
|
2503
|
+
next.push({
|
|
2504
|
+
type: "REVALIDATE",
|
|
2505
|
+
target: s.id,
|
|
2506
|
+
reason: s.reason,
|
|
2507
|
+
relativeCost: 2,
|
|
2508
|
+
expectedInfoGain: 0.85
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
for (const m of missing) {
|
|
2512
|
+
if (m.type === "evidence") {
|
|
2513
|
+
const wasChecked = investigatedSources.has(m.subject.toLowerCase()) || alreadyInvestigated.length > 0;
|
|
2514
|
+
if (wasChecked) {
|
|
2515
|
+
if (!next.some((n) => n.type === "ASK_USER")) {
|
|
2516
|
+
next.push({
|
|
2517
|
+
type: "ASK_USER",
|
|
2518
|
+
target: m.subject,
|
|
2519
|
+
reason: `Already investigated (${Array.from(investigatedSources).join(", ")}); did not establish requested behavior. Clarification required.`,
|
|
2520
|
+
relativeCost: 1,
|
|
2521
|
+
expectedInfoGain: 0.9
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
} else {
|
|
2525
|
+
const isFilePath = m.subject.includes("/") || m.subject.includes(".");
|
|
2526
|
+
next.push({
|
|
2527
|
+
type: isFilePath ? "INSPECT_FILE" : "SEARCH_REPOSITORY",
|
|
2528
|
+
target: m.subject,
|
|
2529
|
+
reason: m.reason,
|
|
2530
|
+
relativeCost: isFilePath ? 2 : 2.5,
|
|
2531
|
+
expectedInfoGain: 0.8
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
if (alreadyInvestigated.length > 0 && !next.some((n) => n.type === "ASK_USER")) {
|
|
2537
|
+
next.push({
|
|
2538
|
+
type: "ASK_USER",
|
|
2539
|
+
target: alreadyInvestigated[0].source,
|
|
2540
|
+
reason: `Already investigated ${alreadyInvestigated[0].source}; clarification required`,
|
|
2541
|
+
relativeCost: 1,
|
|
2542
|
+
expectedInfoGain: 0.9
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
if (next.length === 0 && known.length > 0) {
|
|
2546
|
+
next.push({
|
|
2547
|
+
type: "USE_KNOWLEDGE",
|
|
2548
|
+
target: known[0].id,
|
|
2549
|
+
reason: "Proceed with implementation using verified knowledge and constraints",
|
|
2550
|
+
relativeCost: 1,
|
|
2551
|
+
expectedInfoGain: 1
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
const knowledgeImpact = {
|
|
2555
|
+
relevantKnowledge: totalRelevantKnowledge,
|
|
2556
|
+
validated: known.filter((k) => k.confidenceTier === "high").length + constraints.length,
|
|
2557
|
+
needsRevalidation: stale.length,
|
|
2558
|
+
conflicted: conflictedCount,
|
|
2559
|
+
blockingUnknowns: unknowns.filter((u) => u.blocking).length,
|
|
2560
|
+
relevantFailures: pastFailures.length
|
|
2561
|
+
};
|
|
2562
|
+
const similarWork = [];
|
|
2563
|
+
for (const l of lessons) {
|
|
2564
|
+
similarWork.push({
|
|
2565
|
+
topic: l.domain || l.id,
|
|
2566
|
+
lesson: l.statement
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
for (const f of pastFailures) {
|
|
2570
|
+
similarWork.push({
|
|
2571
|
+
topic: f.id,
|
|
2572
|
+
failure: f.statement
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
await storage.appendMetricEvent({
|
|
2576
|
+
type: "preflight",
|
|
2577
|
+
goal,
|
|
2578
|
+
readiness,
|
|
2579
|
+
blockingUnknownsCount: knowledgeImpact.blockingUnknowns,
|
|
2580
|
+
staleKnowledgeCount: stale.length,
|
|
2581
|
+
pastFailuresCount: pastFailures.length,
|
|
2582
|
+
questionsCount: questions.length,
|
|
2583
|
+
repeatedChecksAvoidedCount: alreadyInvestigated.length,
|
|
2584
|
+
timestamp: Date.now()
|
|
2585
|
+
});
|
|
2586
|
+
return {
|
|
2587
|
+
goal,
|
|
2588
|
+
readiness,
|
|
2589
|
+
known,
|
|
2590
|
+
constraints,
|
|
2591
|
+
lessons,
|
|
2592
|
+
past_failures: pastFailures,
|
|
2593
|
+
unknowns,
|
|
2594
|
+
stale,
|
|
2595
|
+
missing,
|
|
2596
|
+
questions,
|
|
2597
|
+
likely_domains: likelyDomains,
|
|
2598
|
+
similar_work: similarWork.length > 0 ? similarWork.slice(0, 5) : void 0,
|
|
2599
|
+
already_investigated: alreadyInvestigated.length > 0 ? alreadyInvestigated : void 0,
|
|
2600
|
+
knowledge_impact: knowledgeImpact,
|
|
2601
|
+
next
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function formatPreflightReport(result) {
|
|
2605
|
+
const plainObject = {
|
|
2606
|
+
goal: result.goal,
|
|
2607
|
+
readiness: result.readiness
|
|
2608
|
+
};
|
|
2609
|
+
if (result.known.length > 0) {
|
|
2610
|
+
plainObject.known = result.known.map((k) => ({
|
|
2611
|
+
id: k.id,
|
|
2612
|
+
statement: k.statement,
|
|
2613
|
+
confidence: k.confidenceTier || (typeof k.confidence === "number" ? k.confidence >= 0.9 ? "high" : k.confidence >= 0.7 ? "medium" : "low" : k.confidence)
|
|
2614
|
+
}));
|
|
2615
|
+
}
|
|
2616
|
+
if (result.constraints.length > 0) {
|
|
2617
|
+
plainObject.constraints = result.constraints.map((c) => ({
|
|
2618
|
+
id: c.id,
|
|
2619
|
+
statement: c.statement
|
|
2620
|
+
}));
|
|
2621
|
+
}
|
|
2622
|
+
if (result.lessons.length > 0) {
|
|
2623
|
+
plainObject.lessons = result.lessons.map((l) => ({
|
|
2624
|
+
id: l.id,
|
|
2625
|
+
statement: l.statement
|
|
2626
|
+
}));
|
|
2627
|
+
}
|
|
2628
|
+
if (result.past_failures.length > 0) {
|
|
2629
|
+
plainObject.past_failures = result.past_failures.map((f) => ({
|
|
2630
|
+
id: f.id,
|
|
2631
|
+
statement: f.statement
|
|
2632
|
+
}));
|
|
2633
|
+
}
|
|
2634
|
+
if (result.unknowns.length > 0) {
|
|
2635
|
+
plainObject.unknowns = result.unknowns.map((u) => ({
|
|
2636
|
+
id: u.id,
|
|
2637
|
+
question: u.question || u.statement,
|
|
2638
|
+
blocking: u.blocking
|
|
2639
|
+
}));
|
|
2640
|
+
}
|
|
2641
|
+
if (result.stale.length > 0) {
|
|
2642
|
+
plainObject.stale = result.stale.map((s) => ({
|
|
2643
|
+
id: s.id,
|
|
2644
|
+
reason: s.reason
|
|
2645
|
+
}));
|
|
2646
|
+
}
|
|
2647
|
+
if (result.questions.length > 0) {
|
|
2648
|
+
plainObject.questions = result.questions.map((q) => ({
|
|
2649
|
+
question: q.question,
|
|
2650
|
+
reason: q.reason
|
|
2651
|
+
}));
|
|
2652
|
+
}
|
|
2653
|
+
if (result.likely_domains.length > 0) {
|
|
2654
|
+
plainObject.likely_domains = result.likely_domains;
|
|
2655
|
+
}
|
|
2656
|
+
if (result.already_investigated && result.already_investigated.length > 0) {
|
|
2657
|
+
plainObject.already_investigated = result.already_investigated;
|
|
2658
|
+
}
|
|
2659
|
+
if (result.next.length > 0) {
|
|
2660
|
+
plainObject.next = result.next.map((n) => ({
|
|
2661
|
+
type: n.type,
|
|
2662
|
+
target: n.target,
|
|
2663
|
+
reason: n.reason
|
|
2664
|
+
}));
|
|
2665
|
+
}
|
|
2666
|
+
return YAML.stringify(plainObject);
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2053
2669
|
// src/storage/canonical.ts
|
|
2054
|
-
import * as
|
|
2055
|
-
import * as
|
|
2670
|
+
import * as fs4 from "fs/promises";
|
|
2671
|
+
import * as path4 from "path";
|
|
2056
2672
|
|
|
2057
2673
|
// src/rules/loader.ts
|
|
2058
|
-
import
|
|
2059
|
-
import * as
|
|
2060
|
-
import * as
|
|
2674
|
+
import YAML2 from "yaml";
|
|
2675
|
+
import * as fs2 from "fs/promises";
|
|
2676
|
+
import * as path2 from "path";
|
|
2061
2677
|
function validateRuleDefinition(rule, index = 0) {
|
|
2062
2678
|
const errors = [];
|
|
2063
2679
|
if (!rule || typeof rule !== "object") {
|
|
@@ -2126,7 +2742,7 @@ function validateRuleSet(data) {
|
|
|
2126
2742
|
function parseRuleSetYaml(content, sourcePath = "inline.yml") {
|
|
2127
2743
|
let parsed;
|
|
2128
2744
|
try {
|
|
2129
|
-
parsed =
|
|
2745
|
+
parsed = YAML2.parse(content);
|
|
2130
2746
|
} catch (err) {
|
|
2131
2747
|
throw new Error(`YAML syntax error in ${sourcePath}: ${err.message}`);
|
|
2132
2748
|
}
|
|
@@ -2137,7 +2753,7 @@ function parseRuleSetYaml(content, sourcePath = "inline.yml") {
|
|
|
2137
2753
|
}
|
|
2138
2754
|
if (Array.isArray(parsed)) {
|
|
2139
2755
|
return {
|
|
2140
|
-
name:
|
|
2756
|
+
name: path2.basename(sourcePath, path2.extname(sourcePath)),
|
|
2141
2757
|
rules: parsed
|
|
2142
2758
|
};
|
|
2143
2759
|
}
|
|
@@ -2149,7 +2765,7 @@ function parseRuleSetYaml(content, sourcePath = "inline.yml") {
|
|
|
2149
2765
|
}
|
|
2150
2766
|
const set = parsed;
|
|
2151
2767
|
return {
|
|
2152
|
-
name: set.name ??
|
|
2768
|
+
name: set.name ?? path2.basename(sourcePath, path2.extname(sourcePath)),
|
|
2153
2769
|
version: set.version ?? 1,
|
|
2154
2770
|
scope: set.scope ?? "project",
|
|
2155
2771
|
description: set.description,
|
|
@@ -2178,36 +2794,36 @@ function stringifyRuleSetYaml(ruleSet) {
|
|
|
2178
2794
|
if (r.details && Object.keys(r.details).length > 0) ruleObj.details = r.details;
|
|
2179
2795
|
return ruleObj;
|
|
2180
2796
|
});
|
|
2181
|
-
return
|
|
2797
|
+
return YAML2.stringify(payload, { indent: 2, lineWidth: 100 });
|
|
2182
2798
|
}
|
|
2183
2799
|
async function loadRuleSetFile(filePath) {
|
|
2184
|
-
const content = await
|
|
2800
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
2185
2801
|
return parseRuleSetYaml(content, filePath);
|
|
2186
2802
|
}
|
|
2187
2803
|
async function findRuleFiles(rootDir, storageRulesDir) {
|
|
2188
2804
|
const candidates = [];
|
|
2189
|
-
const rulesDir = storageRulesDir ||
|
|
2805
|
+
const rulesDir = storageRulesDir || path2.resolve(rootDir, ".bilmem", "rules");
|
|
2190
2806
|
try {
|
|
2191
|
-
const entries = await
|
|
2807
|
+
const entries = await fs2.readdir(rulesDir, { withFileTypes: true });
|
|
2192
2808
|
for (const entry of entries) {
|
|
2193
2809
|
if (entry.isFile() && (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml"))) {
|
|
2194
|
-
candidates.push(
|
|
2810
|
+
candidates.push(path2.join(rulesDir, entry.name));
|
|
2195
2811
|
}
|
|
2196
2812
|
}
|
|
2197
2813
|
} catch {
|
|
2198
2814
|
}
|
|
2199
2815
|
for (const name of ["rules.yml", "rules.yaml"]) {
|
|
2200
|
-
const p =
|
|
2816
|
+
const p = path2.resolve(rootDir, ".bilmem", name);
|
|
2201
2817
|
try {
|
|
2202
|
-
const st = await
|
|
2818
|
+
const st = await fs2.stat(p);
|
|
2203
2819
|
if (st.isFile()) candidates.push(p);
|
|
2204
2820
|
} catch {
|
|
2205
2821
|
}
|
|
2206
2822
|
}
|
|
2207
2823
|
for (const name of ["bilmem.rules.yml", "bilmem.rules.yaml", "rules.yml", "rules.yaml"]) {
|
|
2208
|
-
const p =
|
|
2824
|
+
const p = path2.resolve(rootDir, name);
|
|
2209
2825
|
try {
|
|
2210
|
-
const st = await
|
|
2826
|
+
const st = await fs2.stat(p);
|
|
2211
2827
|
if (st.isFile()) candidates.push(p);
|
|
2212
2828
|
} catch {
|
|
2213
2829
|
}
|
|
@@ -2231,17 +2847,17 @@ async function loadAllProjectRuleSets(rootDir, storageRulesDir) {
|
|
|
2231
2847
|
}
|
|
2232
2848
|
|
|
2233
2849
|
// src/stats/events.ts
|
|
2234
|
-
import * as
|
|
2235
|
-
import * as
|
|
2850
|
+
import * as fs3 from "fs/promises";
|
|
2851
|
+
import * as path3 from "path";
|
|
2236
2852
|
function getEventsFilePath(rootDir) {
|
|
2237
|
-
return
|
|
2853
|
+
return path3.join(rootDir, ".bilmem", "events.jsonl");
|
|
2238
2854
|
}
|
|
2239
2855
|
async function appendMetricEvent(rootDir, event) {
|
|
2240
2856
|
const filePath = getEventsFilePath(rootDir);
|
|
2241
2857
|
try {
|
|
2242
|
-
await
|
|
2858
|
+
await fs3.mkdir(path3.dirname(filePath), { recursive: true });
|
|
2243
2859
|
const line = JSON.stringify(event) + "\n";
|
|
2244
|
-
await
|
|
2860
|
+
await fs3.appendFile(filePath, line, "utf-8");
|
|
2245
2861
|
} catch {
|
|
2246
2862
|
}
|
|
2247
2863
|
}
|
|
@@ -2249,7 +2865,7 @@ async function readMetricEvents(rootDir, filter) {
|
|
|
2249
2865
|
const filePath = getEventsFilePath(rootDir);
|
|
2250
2866
|
let raw;
|
|
2251
2867
|
try {
|
|
2252
|
-
raw = await
|
|
2868
|
+
raw = await fs3.readFile(filePath, "utf-8");
|
|
2253
2869
|
} catch (err) {
|
|
2254
2870
|
if (err.code === "ENOENT") {
|
|
2255
2871
|
return [];
|
|
@@ -2288,8 +2904,8 @@ async function readMetricEvents(rootDir, filter) {
|
|
|
2288
2904
|
async function resetMetricEvents(rootDir) {
|
|
2289
2905
|
const filePath = getEventsFilePath(rootDir);
|
|
2290
2906
|
try {
|
|
2291
|
-
await
|
|
2292
|
-
await
|
|
2907
|
+
await fs3.mkdir(path3.dirname(filePath), { recursive: true });
|
|
2908
|
+
await fs3.writeFile(filePath, "", "utf-8");
|
|
2293
2909
|
} catch (err) {
|
|
2294
2910
|
if (err.code !== "ENOENT") throw err;
|
|
2295
2911
|
}
|
|
@@ -2326,62 +2942,62 @@ var Storage = class {
|
|
|
2326
2942
|
receiptsJsonlFile;
|
|
2327
2943
|
eventsFile;
|
|
2328
2944
|
constructor(rootDir = process.cwd()) {
|
|
2329
|
-
this.baseDir =
|
|
2330
|
-
this.knowledgeDir =
|
|
2331
|
-
this.evidenceDir =
|
|
2332
|
-
this.observationDir =
|
|
2333
|
-
this.unknownDir =
|
|
2334
|
-
this.rulesDir =
|
|
2335
|
-
this.receiptsDir =
|
|
2336
|
-
this.configFile =
|
|
2337
|
-
this.relationsFile =
|
|
2338
|
-
this.outcomesFile =
|
|
2339
|
-
this.metricsFile =
|
|
2340
|
-
this.revisionsFile =
|
|
2341
|
-
this.knowledgeJsonlFile =
|
|
2342
|
-
this.evidenceJsonlFile =
|
|
2343
|
-
this.observationJsonlFile =
|
|
2344
|
-
this.unknownsJsonlFile =
|
|
2345
|
-
this.receiptsJsonlFile =
|
|
2346
|
-
this.eventsFile =
|
|
2945
|
+
this.baseDir = path4.resolve(rootDir, ".bilmem");
|
|
2946
|
+
this.knowledgeDir = path4.join(this.baseDir, "knowledge");
|
|
2947
|
+
this.evidenceDir = path4.join(this.baseDir, "evidence");
|
|
2948
|
+
this.observationDir = path4.join(this.baseDir, "observations");
|
|
2949
|
+
this.unknownDir = path4.join(this.baseDir, "unknowns");
|
|
2950
|
+
this.rulesDir = path4.join(this.baseDir, "rules");
|
|
2951
|
+
this.receiptsDir = path4.join(this.baseDir, "receipts");
|
|
2952
|
+
this.configFile = path4.join(this.baseDir, "config.json");
|
|
2953
|
+
this.relationsFile = path4.join(this.baseDir, "relations.json");
|
|
2954
|
+
this.outcomesFile = path4.join(this.baseDir, "outcomes.jsonl");
|
|
2955
|
+
this.metricsFile = path4.join(this.baseDir, "metrics.json");
|
|
2956
|
+
this.revisionsFile = path4.join(this.baseDir, "revisions.jsonl");
|
|
2957
|
+
this.knowledgeJsonlFile = path4.join(this.baseDir, "knowledge.jsonl");
|
|
2958
|
+
this.evidenceJsonlFile = path4.join(this.baseDir, "evidence.jsonl");
|
|
2959
|
+
this.observationJsonlFile = path4.join(this.baseDir, "observations.jsonl");
|
|
2960
|
+
this.unknownsJsonlFile = path4.join(this.baseDir, "unknowns.jsonl");
|
|
2961
|
+
this.receiptsJsonlFile = path4.join(this.baseDir, "receipts.jsonl");
|
|
2962
|
+
this.eventsFile = path4.join(this.baseDir, "events.jsonl");
|
|
2347
2963
|
}
|
|
2348
2964
|
getBaseDir() {
|
|
2349
2965
|
return this.baseDir;
|
|
2350
2966
|
}
|
|
2351
2967
|
async isInitialized() {
|
|
2352
2968
|
try {
|
|
2353
|
-
await
|
|
2969
|
+
await fs4.access(this.configFile);
|
|
2354
2970
|
return true;
|
|
2355
2971
|
} catch {
|
|
2356
2972
|
return false;
|
|
2357
2973
|
}
|
|
2358
2974
|
}
|
|
2359
2975
|
async init(options = {}) {
|
|
2360
|
-
await
|
|
2361
|
-
await
|
|
2362
|
-
await
|
|
2363
|
-
await
|
|
2364
|
-
await
|
|
2365
|
-
await
|
|
2976
|
+
await fs4.mkdir(this.knowledgeDir, { recursive: true });
|
|
2977
|
+
await fs4.mkdir(this.evidenceDir, { recursive: true });
|
|
2978
|
+
await fs4.mkdir(this.observationDir, { recursive: true });
|
|
2979
|
+
await fs4.mkdir(this.unknownDir, { recursive: true });
|
|
2980
|
+
await fs4.mkdir(this.rulesDir, { recursive: true });
|
|
2981
|
+
await fs4.mkdir(this.receiptsDir, { recursive: true });
|
|
2366
2982
|
let config;
|
|
2367
2983
|
if (await this.isInitialized()) {
|
|
2368
2984
|
config = await this.getConfig();
|
|
2369
2985
|
} else {
|
|
2370
2986
|
config = {
|
|
2371
2987
|
schemaVersion: 1,
|
|
2372
|
-
projectName: options.projectName ??
|
|
2988
|
+
projectName: options.projectName ?? path4.basename(path4.dirname(this.baseDir)) ?? "project",
|
|
2373
2989
|
defaultScope: options.defaultScope ?? "project",
|
|
2374
2990
|
dataDir: this.baseDir
|
|
2375
2991
|
};
|
|
2376
2992
|
await this.saveConfig(config);
|
|
2377
2993
|
}
|
|
2378
2994
|
try {
|
|
2379
|
-
await
|
|
2995
|
+
await fs4.access(this.relationsFile);
|
|
2380
2996
|
} catch {
|
|
2381
|
-
await
|
|
2997
|
+
await fs4.writeFile(this.relationsFile, JSON.stringify([], null, 2), "utf-8");
|
|
2382
2998
|
}
|
|
2383
2999
|
try {
|
|
2384
|
-
await
|
|
3000
|
+
await fs4.access(this.metricsFile);
|
|
2385
3001
|
} catch {
|
|
2386
3002
|
const defaultMetrics = {
|
|
2387
3003
|
knowledgeHits: 0,
|
|
@@ -2395,17 +3011,17 @@ var Storage = class {
|
|
|
2395
3011
|
knowledgePromoted: 0,
|
|
2396
3012
|
knowledgeSuperseded: 0
|
|
2397
3013
|
};
|
|
2398
|
-
await
|
|
3014
|
+
await fs4.writeFile(this.metricsFile, JSON.stringify(defaultMetrics, null, 2), "utf-8");
|
|
2399
3015
|
}
|
|
2400
3016
|
return config;
|
|
2401
3017
|
}
|
|
2402
3018
|
// --- Config ---
|
|
2403
3019
|
async getConfig() {
|
|
2404
|
-
const raw = await
|
|
3020
|
+
const raw = await fs4.readFile(this.configFile, "utf-8");
|
|
2405
3021
|
return JSON.parse(raw);
|
|
2406
3022
|
}
|
|
2407
3023
|
async saveConfig(config) {
|
|
2408
|
-
await
|
|
3024
|
+
await fs4.writeFile(this.configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
2409
3025
|
}
|
|
2410
3026
|
async updateConfig(updates) {
|
|
2411
3027
|
const current = await this.getConfig();
|
|
@@ -2415,17 +3031,17 @@ var Storage = class {
|
|
|
2415
3031
|
}
|
|
2416
3032
|
// --- Knowledge ---
|
|
2417
3033
|
async saveKnowledge(knowledge) {
|
|
2418
|
-
await
|
|
3034
|
+
await fs4.mkdir(this.knowledgeDir, { recursive: true });
|
|
2419
3035
|
const filename = `${sanitizeFileName(knowledge.id)}.json`;
|
|
2420
|
-
const filePath =
|
|
2421
|
-
await
|
|
2422
|
-
await
|
|
3036
|
+
const filePath = path4.join(this.knowledgeDir, filename);
|
|
3037
|
+
await fs4.writeFile(filePath, JSON.stringify(knowledge, null, 2), "utf-8");
|
|
3038
|
+
await fs4.appendFile(this.knowledgeJsonlFile, JSON.stringify(knowledge) + "\n", "utf-8");
|
|
2423
3039
|
}
|
|
2424
3040
|
async getKnowledge(id) {
|
|
2425
3041
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2426
|
-
const filePath =
|
|
3042
|
+
const filePath = path4.join(this.knowledgeDir, filename);
|
|
2427
3043
|
try {
|
|
2428
|
-
const raw = await
|
|
3044
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2429
3045
|
return JSON.parse(raw);
|
|
2430
3046
|
} catch {
|
|
2431
3047
|
return void 0;
|
|
@@ -2433,11 +3049,11 @@ var Storage = class {
|
|
|
2433
3049
|
}
|
|
2434
3050
|
async getAllKnowledge() {
|
|
2435
3051
|
try {
|
|
2436
|
-
const files = await
|
|
3052
|
+
const files = await fs4.readdir(this.knowledgeDir);
|
|
2437
3053
|
const items = [];
|
|
2438
3054
|
for (const file of files) {
|
|
2439
3055
|
if (!file.endsWith(".json")) continue;
|
|
2440
|
-
const raw = await
|
|
3056
|
+
const raw = await fs4.readFile(path4.join(this.knowledgeDir, file), "utf-8");
|
|
2441
3057
|
try {
|
|
2442
3058
|
items.push(JSON.parse(raw));
|
|
2443
3059
|
} catch {
|
|
@@ -2450,9 +3066,9 @@ var Storage = class {
|
|
|
2450
3066
|
}
|
|
2451
3067
|
async deleteKnowledge(id) {
|
|
2452
3068
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2453
|
-
const filePath =
|
|
3069
|
+
const filePath = path4.join(this.knowledgeDir, filename);
|
|
2454
3070
|
try {
|
|
2455
|
-
await
|
|
3071
|
+
await fs4.unlink(filePath);
|
|
2456
3072
|
return true;
|
|
2457
3073
|
} catch {
|
|
2458
3074
|
return false;
|
|
@@ -2460,17 +3076,17 @@ var Storage = class {
|
|
|
2460
3076
|
}
|
|
2461
3077
|
// --- Evidence ---
|
|
2462
3078
|
async saveEvidence(evidence) {
|
|
2463
|
-
await
|
|
3079
|
+
await fs4.mkdir(this.evidenceDir, { recursive: true });
|
|
2464
3080
|
const filename = `${sanitizeFileName(evidence.id)}.json`;
|
|
2465
|
-
const filePath =
|
|
2466
|
-
await
|
|
2467
|
-
await
|
|
3081
|
+
const filePath = path4.join(this.evidenceDir, filename);
|
|
3082
|
+
await fs4.writeFile(filePath, JSON.stringify(evidence, null, 2), "utf-8");
|
|
3083
|
+
await fs4.appendFile(this.evidenceJsonlFile, JSON.stringify(evidence) + "\n", "utf-8");
|
|
2468
3084
|
}
|
|
2469
3085
|
async getEvidence(id) {
|
|
2470
3086
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2471
|
-
const filePath =
|
|
3087
|
+
const filePath = path4.join(this.evidenceDir, filename);
|
|
2472
3088
|
try {
|
|
2473
|
-
const raw = await
|
|
3089
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2474
3090
|
return JSON.parse(raw);
|
|
2475
3091
|
} catch {
|
|
2476
3092
|
return void 0;
|
|
@@ -2478,11 +3094,11 @@ var Storage = class {
|
|
|
2478
3094
|
}
|
|
2479
3095
|
async getAllEvidence() {
|
|
2480
3096
|
try {
|
|
2481
|
-
const files = await
|
|
3097
|
+
const files = await fs4.readdir(this.evidenceDir);
|
|
2482
3098
|
const items = [];
|
|
2483
3099
|
for (const file of files) {
|
|
2484
3100
|
if (!file.endsWith(".json")) continue;
|
|
2485
|
-
const raw = await
|
|
3101
|
+
const raw = await fs4.readFile(path4.join(this.evidenceDir, file), "utf-8");
|
|
2486
3102
|
try {
|
|
2487
3103
|
items.push(JSON.parse(raw));
|
|
2488
3104
|
} catch {
|
|
@@ -2495,17 +3111,17 @@ var Storage = class {
|
|
|
2495
3111
|
}
|
|
2496
3112
|
// --- Observations ---
|
|
2497
3113
|
async saveObservation(obs) {
|
|
2498
|
-
await
|
|
3114
|
+
await fs4.mkdir(this.observationDir, { recursive: true });
|
|
2499
3115
|
const filename = `${sanitizeFileName(obs.id)}.json`;
|
|
2500
|
-
const filePath =
|
|
2501
|
-
await
|
|
2502
|
-
await
|
|
3116
|
+
const filePath = path4.join(this.observationDir, filename);
|
|
3117
|
+
await fs4.writeFile(filePath, JSON.stringify(obs, null, 2), "utf-8");
|
|
3118
|
+
await fs4.appendFile(this.observationJsonlFile, JSON.stringify(obs) + "\n", "utf-8");
|
|
2503
3119
|
}
|
|
2504
3120
|
async getObservation(id) {
|
|
2505
3121
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2506
|
-
const filePath =
|
|
3122
|
+
const filePath = path4.join(this.observationDir, filename);
|
|
2507
3123
|
try {
|
|
2508
|
-
const raw = await
|
|
3124
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2509
3125
|
return JSON.parse(raw);
|
|
2510
3126
|
} catch {
|
|
2511
3127
|
return void 0;
|
|
@@ -2513,11 +3129,11 @@ var Storage = class {
|
|
|
2513
3129
|
}
|
|
2514
3130
|
async getAllObservations() {
|
|
2515
3131
|
try {
|
|
2516
|
-
const files = await
|
|
3132
|
+
const files = await fs4.readdir(this.observationDir);
|
|
2517
3133
|
const items = [];
|
|
2518
3134
|
for (const file of files) {
|
|
2519
3135
|
if (!file.endsWith(".json")) continue;
|
|
2520
|
-
const raw = await
|
|
3136
|
+
const raw = await fs4.readFile(path4.join(this.observationDir, file), "utf-8");
|
|
2521
3137
|
try {
|
|
2522
3138
|
items.push(JSON.parse(raw));
|
|
2523
3139
|
} catch {
|
|
@@ -2530,9 +3146,9 @@ var Storage = class {
|
|
|
2530
3146
|
}
|
|
2531
3147
|
async deleteObservation(id) {
|
|
2532
3148
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2533
|
-
const filePath =
|
|
3149
|
+
const filePath = path4.join(this.observationDir, filename);
|
|
2534
3150
|
try {
|
|
2535
|
-
await
|
|
3151
|
+
await fs4.unlink(filePath);
|
|
2536
3152
|
return true;
|
|
2537
3153
|
} catch {
|
|
2538
3154
|
return false;
|
|
@@ -2540,17 +3156,17 @@ var Storage = class {
|
|
|
2540
3156
|
}
|
|
2541
3157
|
// --- Unknowns ---
|
|
2542
3158
|
async saveUnknown(record) {
|
|
2543
|
-
await
|
|
3159
|
+
await fs4.mkdir(this.unknownDir, { recursive: true });
|
|
2544
3160
|
const filename = `${sanitizeFileName(record.id)}.json`;
|
|
2545
|
-
const filePath =
|
|
2546
|
-
await
|
|
2547
|
-
await
|
|
3161
|
+
const filePath = path4.join(this.unknownDir, filename);
|
|
3162
|
+
await fs4.writeFile(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
3163
|
+
await fs4.appendFile(this.unknownsJsonlFile, JSON.stringify(record) + "\n", "utf-8");
|
|
2548
3164
|
}
|
|
2549
3165
|
async getUnknown(id) {
|
|
2550
3166
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2551
|
-
const filePath =
|
|
3167
|
+
const filePath = path4.join(this.unknownDir, filename);
|
|
2552
3168
|
try {
|
|
2553
|
-
const raw = await
|
|
3169
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2554
3170
|
return JSON.parse(raw);
|
|
2555
3171
|
} catch {
|
|
2556
3172
|
return void 0;
|
|
@@ -2558,11 +3174,11 @@ var Storage = class {
|
|
|
2558
3174
|
}
|
|
2559
3175
|
async getAllUnknowns() {
|
|
2560
3176
|
try {
|
|
2561
|
-
const files = await
|
|
3177
|
+
const files = await fs4.readdir(this.unknownDir);
|
|
2562
3178
|
const items = [];
|
|
2563
3179
|
for (const file of files) {
|
|
2564
3180
|
if (!file.endsWith(".json")) continue;
|
|
2565
|
-
const raw = await
|
|
3181
|
+
const raw = await fs4.readFile(path4.join(this.unknownDir, file), "utf-8");
|
|
2566
3182
|
try {
|
|
2567
3183
|
items.push(JSON.parse(raw));
|
|
2568
3184
|
} catch {
|
|
@@ -2575,9 +3191,9 @@ var Storage = class {
|
|
|
2575
3191
|
}
|
|
2576
3192
|
async deleteUnknown(id) {
|
|
2577
3193
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2578
|
-
const filePath =
|
|
3194
|
+
const filePath = path4.join(this.unknownDir, filename);
|
|
2579
3195
|
try {
|
|
2580
|
-
await
|
|
3196
|
+
await fs4.unlink(filePath);
|
|
2581
3197
|
return true;
|
|
2582
3198
|
} catch {
|
|
2583
3199
|
return false;
|
|
@@ -2586,7 +3202,7 @@ var Storage = class {
|
|
|
2586
3202
|
// --- Outcomes (Append-oriented log) ---
|
|
2587
3203
|
async appendOutcome(outcome) {
|
|
2588
3204
|
const line = JSON.stringify(outcome) + "\n";
|
|
2589
|
-
await
|
|
3205
|
+
await fs4.appendFile(this.outcomesFile, line, "utf-8");
|
|
2590
3206
|
if (outcome.knowledgeId) {
|
|
2591
3207
|
await this.appendMetricEvent({
|
|
2592
3208
|
type: "outcome",
|
|
@@ -2605,7 +3221,7 @@ var Storage = class {
|
|
|
2605
3221
|
}
|
|
2606
3222
|
async getAllOutcomes() {
|
|
2607
3223
|
try {
|
|
2608
|
-
const raw = await
|
|
3224
|
+
const raw = await fs4.readFile(this.outcomesFile, "utf-8");
|
|
2609
3225
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
2610
3226
|
return lines.map((l) => JSON.parse(l));
|
|
2611
3227
|
} catch {
|
|
@@ -2615,30 +3231,30 @@ var Storage = class {
|
|
|
2615
3231
|
// --- Metric Events (Append-only local log) ---
|
|
2616
3232
|
async appendMetricEvent(event) {
|
|
2617
3233
|
try {
|
|
2618
|
-
await
|
|
2619
|
-
await
|
|
3234
|
+
await fs4.mkdir(this.baseDir, { recursive: true });
|
|
3235
|
+
await fs4.appendFile(this.eventsFile, JSON.stringify(event) + "\n", "utf-8");
|
|
2620
3236
|
} catch {
|
|
2621
3237
|
}
|
|
2622
3238
|
}
|
|
2623
3239
|
async getMetricEvents(filter) {
|
|
2624
|
-
return readMetricEvents(
|
|
3240
|
+
return readMetricEvents(path4.dirname(this.baseDir), filter);
|
|
2625
3241
|
}
|
|
2626
3242
|
async resetMetricEvents() {
|
|
2627
|
-
return resetMetricEvents(
|
|
3243
|
+
return resetMetricEvents(path4.dirname(this.baseDir));
|
|
2628
3244
|
}
|
|
2629
3245
|
// --- Investigation Receipts ---
|
|
2630
3246
|
async saveReceipt(receipt) {
|
|
2631
|
-
await
|
|
3247
|
+
await fs4.mkdir(this.receiptsDir, { recursive: true });
|
|
2632
3248
|
const filename = `${sanitizeFileName(receipt.id)}.json`;
|
|
2633
|
-
const filePath =
|
|
2634
|
-
await
|
|
2635
|
-
await
|
|
3249
|
+
const filePath = path4.join(this.receiptsDir, filename);
|
|
3250
|
+
await fs4.writeFile(filePath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
3251
|
+
await fs4.appendFile(this.receiptsJsonlFile, JSON.stringify(receipt) + "\n", "utf-8");
|
|
2636
3252
|
}
|
|
2637
3253
|
async getReceipt(id) {
|
|
2638
3254
|
const filename = `${sanitizeFileName(id)}.json`;
|
|
2639
|
-
const filePath =
|
|
3255
|
+
const filePath = path4.join(this.receiptsDir, filename);
|
|
2640
3256
|
try {
|
|
2641
|
-
const raw = await
|
|
3257
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2642
3258
|
return JSON.parse(raw);
|
|
2643
3259
|
} catch {
|
|
2644
3260
|
return void 0;
|
|
@@ -2646,11 +3262,11 @@ var Storage = class {
|
|
|
2646
3262
|
}
|
|
2647
3263
|
async getAllReceipts() {
|
|
2648
3264
|
try {
|
|
2649
|
-
const files = await
|
|
3265
|
+
const files = await fs4.readdir(this.receiptsDir);
|
|
2650
3266
|
const items = [];
|
|
2651
3267
|
for (const file of files) {
|
|
2652
3268
|
if (!file.endsWith(".json")) continue;
|
|
2653
|
-
const raw = await
|
|
3269
|
+
const raw = await fs4.readFile(path4.join(this.receiptsDir, file), "utf-8");
|
|
2654
3270
|
try {
|
|
2655
3271
|
items.push(JSON.parse(raw));
|
|
2656
3272
|
} catch {
|
|
@@ -2664,19 +3280,19 @@ var Storage = class {
|
|
|
2664
3280
|
// --- Relations ---
|
|
2665
3281
|
async getRelations() {
|
|
2666
3282
|
try {
|
|
2667
|
-
const raw = await
|
|
3283
|
+
const raw = await fs4.readFile(this.relationsFile, "utf-8");
|
|
2668
3284
|
return JSON.parse(raw);
|
|
2669
3285
|
} catch {
|
|
2670
3286
|
return [];
|
|
2671
3287
|
}
|
|
2672
3288
|
}
|
|
2673
3289
|
async saveRelations(relations) {
|
|
2674
|
-
await
|
|
3290
|
+
await fs4.writeFile(this.relationsFile, JSON.stringify(relations, null, 2), "utf-8");
|
|
2675
3291
|
}
|
|
2676
3292
|
// --- Metrics ---
|
|
2677
3293
|
async getMetrics() {
|
|
2678
3294
|
try {
|
|
2679
|
-
const raw = await
|
|
3295
|
+
const raw = await fs4.readFile(this.metricsFile, "utf-8");
|
|
2680
3296
|
return JSON.parse(raw);
|
|
2681
3297
|
} catch {
|
|
2682
3298
|
return {
|
|
@@ -2694,14 +3310,14 @@ var Storage = class {
|
|
|
2694
3310
|
}
|
|
2695
3311
|
}
|
|
2696
3312
|
async saveMetrics(metrics) {
|
|
2697
|
-
await
|
|
3313
|
+
await fs4.writeFile(this.metricsFile, JSON.stringify(metrics, null, 2), "utf-8");
|
|
2698
3314
|
}
|
|
2699
3315
|
// --- Rules ---
|
|
2700
3316
|
getRulesDir() {
|
|
2701
3317
|
return this.rulesDir;
|
|
2702
3318
|
}
|
|
2703
3319
|
async loadRuleSets() {
|
|
2704
|
-
const files = await findRuleFiles(
|
|
3320
|
+
const files = await findRuleFiles(path4.dirname(this.baseDir), this.rulesDir);
|
|
2705
3321
|
const sets = [];
|
|
2706
3322
|
for (const f of files) {
|
|
2707
3323
|
try {
|
|
@@ -2712,23 +3328,23 @@ var Storage = class {
|
|
|
2712
3328
|
return sets;
|
|
2713
3329
|
}
|
|
2714
3330
|
async saveRuleSet(ruleSet, fileName = "default.yml") {
|
|
2715
|
-
await
|
|
3331
|
+
await fs4.mkdir(this.rulesDir, { recursive: true });
|
|
2716
3332
|
const cleanName = fileName.endsWith(".yml") || fileName.endsWith(".yaml") ? fileName : `${fileName}.yml`;
|
|
2717
|
-
const targetPath =
|
|
3333
|
+
const targetPath = path4.join(this.rulesDir, cleanName);
|
|
2718
3334
|
const yaml = stringifyRuleSetYaml(ruleSet);
|
|
2719
|
-
await
|
|
3335
|
+
await fs4.writeFile(targetPath, yaml, "utf-8");
|
|
2720
3336
|
return targetPath;
|
|
2721
3337
|
}
|
|
2722
3338
|
async addRule(rule, fileName = "default.yml") {
|
|
2723
|
-
await
|
|
3339
|
+
await fs4.mkdir(this.rulesDir, { recursive: true });
|
|
2724
3340
|
const cleanName = fileName.endsWith(".yml") || fileName.endsWith(".yaml") ? fileName : `${fileName}.yml`;
|
|
2725
|
-
const targetPath =
|
|
3341
|
+
const targetPath = path4.join(this.rulesDir, cleanName);
|
|
2726
3342
|
let existingSet;
|
|
2727
3343
|
try {
|
|
2728
3344
|
existingSet = await loadRuleSetFile(targetPath);
|
|
2729
3345
|
} catch {
|
|
2730
3346
|
existingSet = {
|
|
2731
|
-
name:
|
|
3347
|
+
name: path4.basename(cleanName, path4.extname(cleanName)),
|
|
2732
3348
|
rules: []
|
|
2733
3349
|
};
|
|
2734
3350
|
}
|
|
@@ -2739,17 +3355,17 @@ var Storage = class {
|
|
|
2739
3355
|
existingSet.rules.push(rule);
|
|
2740
3356
|
}
|
|
2741
3357
|
const yaml = stringifyRuleSetYaml(existingSet);
|
|
2742
|
-
await
|
|
3358
|
+
await fs4.writeFile(targetPath, yaml, "utf-8");
|
|
2743
3359
|
return targetPath;
|
|
2744
3360
|
}
|
|
2745
3361
|
// --- Revisions & Incremental Learning Diff ---
|
|
2746
3362
|
async appendRevision(snapshot) {
|
|
2747
3363
|
const line = JSON.stringify(snapshot) + "\n";
|
|
2748
|
-
await
|
|
3364
|
+
await fs4.appendFile(this.revisionsFile, line, "utf-8");
|
|
2749
3365
|
}
|
|
2750
3366
|
async getRevisions() {
|
|
2751
3367
|
try {
|
|
2752
|
-
const raw = await
|
|
3368
|
+
const raw = await fs4.readFile(this.revisionsFile, "utf-8");
|
|
2753
3369
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
2754
3370
|
return lines.map((l) => JSON.parse(l));
|
|
2755
3371
|
} catch {
|
|
@@ -2888,7 +3504,7 @@ function formatKnowledgeDiff(diff) {
|
|
|
2888
3504
|
}
|
|
2889
3505
|
|
|
2890
3506
|
// src/storage/portable.ts
|
|
2891
|
-
import
|
|
3507
|
+
import YAML3 from "yaml";
|
|
2892
3508
|
function exportKnowledgeToYaml(items, options = {}) {
|
|
2893
3509
|
let filtered = items;
|
|
2894
3510
|
if (options.scope) {
|
|
@@ -2919,10 +3535,10 @@ function exportKnowledgeToYaml(items, options = {}) {
|
|
|
2919
3535
|
}
|
|
2920
3536
|
}))
|
|
2921
3537
|
};
|
|
2922
|
-
return
|
|
3538
|
+
return YAML3.stringify(payload, { indent: 2, lineWidth: 100 });
|
|
2923
3539
|
}
|
|
2924
3540
|
async function importKnowledgeFromYaml(yamlContent, storage) {
|
|
2925
|
-
const parsed =
|
|
3541
|
+
const parsed = YAML3.parse(yamlContent);
|
|
2926
3542
|
if (!parsed || typeof parsed !== "object") {
|
|
2927
3543
|
throw new Error("Invalid YAML: root must be an object");
|
|
2928
3544
|
}
|
|
@@ -2981,10 +3597,10 @@ async function dumpToYaml(storage) {
|
|
|
2981
3597
|
relations,
|
|
2982
3598
|
metrics
|
|
2983
3599
|
};
|
|
2984
|
-
return
|
|
3600
|
+
return YAML3.stringify(dump, { indent: 2, lineWidth: 120 });
|
|
2985
3601
|
}
|
|
2986
3602
|
async function restoreFromYaml(yamlContent, storage) {
|
|
2987
|
-
const parsed =
|
|
3603
|
+
const parsed = YAML3.parse(yamlContent);
|
|
2988
3604
|
if (!parsed || parsed.schemaVersion !== 1) {
|
|
2989
3605
|
throw new Error(`Unsupported snapshot schema version: ${parsed?.schemaVersion}`);
|
|
2990
3606
|
}
|
|
@@ -3143,8 +3759,8 @@ async function compact(storage, options = {}) {
|
|
|
3143
3759
|
}
|
|
3144
3760
|
|
|
3145
3761
|
// src/scanner/filesystem.ts
|
|
3146
|
-
import * as
|
|
3147
|
-
import * as
|
|
3762
|
+
import * as fs5 from "fs/promises";
|
|
3763
|
+
import * as path5 from "path";
|
|
3148
3764
|
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
3149
3765
|
".git",
|
|
3150
3766
|
".bilmem",
|
|
@@ -3165,12 +3781,12 @@ var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
3165
3781
|
"temp"
|
|
3166
3782
|
]);
|
|
3167
3783
|
function normalizeRelPath(rel) {
|
|
3168
|
-
return rel.split(
|
|
3784
|
+
return rel.split(path5.sep).join("/");
|
|
3169
3785
|
}
|
|
3170
3786
|
async function loadGitignorePatterns(rootDir) {
|
|
3171
3787
|
try {
|
|
3172
|
-
const gitignorePath =
|
|
3173
|
-
const content = await
|
|
3788
|
+
const gitignorePath = path5.join(rootDir, ".gitignore");
|
|
3789
|
+
const content = await fs5.readFile(gitignorePath, "utf-8");
|
|
3174
3790
|
const regexes = [];
|
|
3175
3791
|
for (const rawLine of content.split("\n")) {
|
|
3176
3792
|
const line = rawLine.trim();
|
|
@@ -3218,7 +3834,7 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
3218
3834
|
return;
|
|
3219
3835
|
}
|
|
3220
3836
|
try {
|
|
3221
|
-
const real = await
|
|
3837
|
+
const real = await fs5.realpath(currentDir);
|
|
3222
3838
|
if (visitedRealPaths.has(real)) {
|
|
3223
3839
|
return;
|
|
3224
3840
|
}
|
|
@@ -3228,14 +3844,14 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
3228
3844
|
}
|
|
3229
3845
|
let entries;
|
|
3230
3846
|
try {
|
|
3231
|
-
entries = await
|
|
3847
|
+
entries = await fs5.readdir(currentDir, { withFileTypes: true });
|
|
3232
3848
|
} catch {
|
|
3233
3849
|
return;
|
|
3234
3850
|
}
|
|
3235
3851
|
for (const entry of entries) {
|
|
3236
3852
|
if (fileList.length >= maxFiles) break;
|
|
3237
|
-
const fullPath =
|
|
3238
|
-
const rawRelPath =
|
|
3853
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
3854
|
+
const rawRelPath = path5.relative(root, fullPath);
|
|
3239
3855
|
const relPath = normalizeRelPath(rawRelPath);
|
|
3240
3856
|
const lowerName = entry.name.toLowerCase();
|
|
3241
3857
|
if (gitignoreRules.some((rule) => rule.test(relPath))) {
|
|
@@ -3251,16 +3867,16 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
3251
3867
|
if (!includeHidden && entry.name.startsWith(".") && lowerName !== ".env.example") {
|
|
3252
3868
|
continue;
|
|
3253
3869
|
}
|
|
3254
|
-
const ext =
|
|
3870
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
3255
3871
|
try {
|
|
3256
|
-
const
|
|
3872
|
+
const stat4 = await fs5.stat(fullPath);
|
|
3257
3873
|
const fileInfo = {
|
|
3258
3874
|
path: fullPath,
|
|
3259
3875
|
relativePath: relPath,
|
|
3260
3876
|
name: entry.name,
|
|
3261
3877
|
ext,
|
|
3262
|
-
size:
|
|
3263
|
-
mtimeMs:
|
|
3878
|
+
size: stat4.size,
|
|
3879
|
+
mtimeMs: stat4.mtimeMs,
|
|
3264
3880
|
isDirectory: false
|
|
3265
3881
|
};
|
|
3266
3882
|
if (!options.filter || options.filter(fileInfo)) {
|
|
@@ -3303,9 +3919,9 @@ var ProjectContext = class {
|
|
|
3303
3919
|
if (this.textCache.has(key)) {
|
|
3304
3920
|
return this.textCache.get(key);
|
|
3305
3921
|
}
|
|
3306
|
-
const fullPath =
|
|
3922
|
+
const fullPath = path5.resolve(this.rootDir, relativePath);
|
|
3307
3923
|
try {
|
|
3308
|
-
const handle = await
|
|
3924
|
+
const handle = await fs5.open(fullPath, "r");
|
|
3309
3925
|
const buffer = Buffer.alloc(maxBytes);
|
|
3310
3926
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
3311
3927
|
await handle.close();
|
|
@@ -3734,12 +4350,12 @@ async function analyzeProjectStructure(files, rootDir, registry = defaultRegistr
|
|
|
3734
4350
|
}
|
|
3735
4351
|
|
|
3736
4352
|
// src/scanner/markdown.ts
|
|
3737
|
-
import * as
|
|
3738
|
-
import * as
|
|
3739
|
-
import
|
|
4353
|
+
import * as fs6 from "fs/promises";
|
|
4354
|
+
import * as path6 from "path";
|
|
4355
|
+
import YAML4 from "yaml";
|
|
3740
4356
|
function parseMarkdownDocument(content, filePath, relativePath) {
|
|
3741
4357
|
const lines = content.split("\n");
|
|
3742
|
-
const baseName =
|
|
4358
|
+
const baseName = path6.basename(relativePath, ".md").toLowerCase();
|
|
3743
4359
|
let frontmatter = {};
|
|
3744
4360
|
const headings = [];
|
|
3745
4361
|
const alerts = [];
|
|
@@ -3768,7 +4384,7 @@ function parseMarkdownDocument(content, filePath, relativePath) {
|
|
|
3768
4384
|
bodyStartIndex = i + 1;
|
|
3769
4385
|
try {
|
|
3770
4386
|
const yamlText = frontmatterLines.join("\n");
|
|
3771
|
-
const parsed =
|
|
4387
|
+
const parsed = YAML4.parse(yamlText);
|
|
3772
4388
|
if (parsed && typeof parsed === "object") {
|
|
3773
4389
|
frontmatter = parsed;
|
|
3774
4390
|
}
|
|
@@ -4156,8 +4772,8 @@ var BUILTIN_MARKDOWN_EXTRACTORS = [
|
|
|
4156
4772
|
if (!target.startsWith("http://") && !target.startsWith("https://") && !target.startsWith("mailto:") && (target.startsWith(".") || target.includes("/") || /\.(md|ts|js|json|ya?ml|toml|rs|go|py)$/i.test(target))) {
|
|
4157
4773
|
const rawTarget = target.split("#")[0].split("?")[0].trim();
|
|
4158
4774
|
if (rawTarget) {
|
|
4159
|
-
const dir =
|
|
4160
|
-
const resolved = dir === "." ||
|
|
4775
|
+
const dir = path6.dirname(doc.relativePath);
|
|
4776
|
+
const resolved = dir === "." || path6.isAbsolute(rawTarget) ? rawTarget.replace(/^\.\//, "") : path6.normalize(path6.join(dir, rawTarget));
|
|
4161
4777
|
relations.push({
|
|
4162
4778
|
from: doc.relativePath,
|
|
4163
4779
|
to: resolved,
|
|
@@ -4213,7 +4829,7 @@ function registerMarkdownExtractor(extractor) {
|
|
|
4213
4829
|
async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkdownRegistry) {
|
|
4214
4830
|
let content = "";
|
|
4215
4831
|
try {
|
|
4216
|
-
content = await
|
|
4832
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
4217
4833
|
} catch {
|
|
4218
4834
|
return { observations: [], relations: [] };
|
|
4219
4835
|
}
|
|
@@ -4222,11 +4838,11 @@ async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkd
|
|
|
4222
4838
|
}
|
|
4223
4839
|
|
|
4224
4840
|
// src/scanner/git.ts
|
|
4225
|
-
import { execFile } from "child_process";
|
|
4226
|
-
import { promisify } from "util";
|
|
4227
|
-
var
|
|
4841
|
+
import { execFile as execFile2 } from "child_process";
|
|
4842
|
+
import { promisify as promisify2 } from "util";
|
|
4843
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
4228
4844
|
async function runGit(args, cwd) {
|
|
4229
|
-
const { stdout } = await
|
|
4845
|
+
const { stdout } = await execFileAsync2("git", args, {
|
|
4230
4846
|
cwd,
|
|
4231
4847
|
maxBuffer: 10 * 1024 * 1024
|
|
4232
4848
|
});
|
|
@@ -4420,9 +5036,9 @@ async function scanGitHistory(cwd, options = {}) {
|
|
|
4420
5036
|
}
|
|
4421
5037
|
|
|
4422
5038
|
// src/scanner/source.ts
|
|
4423
|
-
import * as
|
|
5039
|
+
import * as fs7 from "fs/promises";
|
|
4424
5040
|
import * as fsSync from "fs";
|
|
4425
|
-
import * as
|
|
5041
|
+
import * as path7 from "path";
|
|
4426
5042
|
import { createRequire } from "module";
|
|
4427
5043
|
import Parser from "web-tree-sitter";
|
|
4428
5044
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -4476,7 +5092,7 @@ function getWasmDir() {
|
|
|
4476
5092
|
try {
|
|
4477
5093
|
const req = createRequire(import.meta.url);
|
|
4478
5094
|
const pkgJson = req.resolve("tree-sitter-wasms/package.json");
|
|
4479
|
-
const outDir =
|
|
5095
|
+
const outDir = path7.join(path7.dirname(pkgJson), "out");
|
|
4480
5096
|
if (fsSync.existsSync(outDir)) {
|
|
4481
5097
|
wasmBaseDir = outDir;
|
|
4482
5098
|
return wasmBaseDir;
|
|
@@ -4484,8 +5100,8 @@ function getWasmDir() {
|
|
|
4484
5100
|
} catch {
|
|
4485
5101
|
}
|
|
4486
5102
|
const candidateDirs = [
|
|
4487
|
-
|
|
4488
|
-
|
|
5103
|
+
path7.resolve(process.cwd(), "node_modules/tree-sitter-wasms/out"),
|
|
5104
|
+
path7.resolve(process.cwd(), "../node_modules/tree-sitter-wasms/out")
|
|
4489
5105
|
];
|
|
4490
5106
|
for (const dir of candidateDirs) {
|
|
4491
5107
|
if (fsSync.existsSync(dir)) {
|
|
@@ -4498,7 +5114,7 @@ function getWasmDir() {
|
|
|
4498
5114
|
async function getParserForWasm(wasmName) {
|
|
4499
5115
|
const wasmDir = getWasmDir();
|
|
4500
5116
|
if (!wasmDir) return null;
|
|
4501
|
-
const wasmPath =
|
|
5117
|
+
const wasmPath = path7.join(wasmDir, wasmName);
|
|
4502
5118
|
if (!fsSync.existsSync(wasmPath)) return null;
|
|
4503
5119
|
try {
|
|
4504
5120
|
if (!parserInitialized) {
|
|
@@ -4857,7 +5473,7 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4857
5473
|
const relations = [];
|
|
4858
5474
|
let content = "";
|
|
4859
5475
|
try {
|
|
4860
|
-
content = await
|
|
5476
|
+
content = await fs7.readFile(filePath, "utf-8");
|
|
4861
5477
|
} catch {
|
|
4862
5478
|
return { observations, relations };
|
|
4863
5479
|
}
|
|
@@ -4865,8 +5481,8 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4865
5481
|
return { observations, relations };
|
|
4866
5482
|
}
|
|
4867
5483
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4868
|
-
const baseName =
|
|
4869
|
-
const ext =
|
|
5484
|
+
const baseName = path7.basename(relativePath);
|
|
5485
|
+
const ext = path7.extname(relativePath).toLowerCase();
|
|
4870
5486
|
const wasmName = EXT_TO_WASM[ext];
|
|
4871
5487
|
if (wasmName) {
|
|
4872
5488
|
try {
|
|
@@ -4892,7 +5508,7 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4892
5508
|
}
|
|
4893
5509
|
|
|
4894
5510
|
// src/scanner/engine.ts
|
|
4895
|
-
import * as
|
|
5511
|
+
import * as path8 from "path";
|
|
4896
5512
|
|
|
4897
5513
|
// src/rules/evaluator.ts
|
|
4898
5514
|
function globToRegex(glob) {
|
|
@@ -5074,9 +5690,9 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
5074
5690
|
const plannedActions = [];
|
|
5075
5691
|
const config = dryRun ? await storage.isInitialized() ? await storage.getConfig() : {
|
|
5076
5692
|
schemaVersion: 1,
|
|
5077
|
-
projectName:
|
|
5693
|
+
projectName: path8.basename(rootDir),
|
|
5078
5694
|
defaultScope: "project",
|
|
5079
|
-
dataDir:
|
|
5695
|
+
dataDir: path8.join(rootDir, ".bilmem")
|
|
5080
5696
|
} : await storage.init();
|
|
5081
5697
|
const incremental = options.incremental ?? false;
|
|
5082
5698
|
const lastScanCommit = incremental ? config.lastScanCommit : void 0;
|
|
@@ -5149,7 +5765,7 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
5149
5765
|
];
|
|
5150
5766
|
for (const o of mdObs) {
|
|
5151
5767
|
if (o.source.includes(":adr") || o.claim.startsWith("[WARNING]") || o.claim.startsWith("[CAUTION]")) {
|
|
5152
|
-
const id = `decision.${
|
|
5768
|
+
const id = `decision.${path8.basename(o.provenance.path || "doc", ".md")}.${o.provenance.line || "adr"}`;
|
|
5153
5769
|
candidateLessons.push({
|
|
5154
5770
|
id,
|
|
5155
5771
|
type: "decision",
|
|
@@ -5262,7 +5878,7 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
5262
5878
|
|
|
5263
5879
|
// src/mcp/server.ts
|
|
5264
5880
|
import { McpApp, z } from "mcponce";
|
|
5265
|
-
import * as
|
|
5881
|
+
import * as path9 from "path";
|
|
5266
5882
|
|
|
5267
5883
|
// src/stats/aggregator.ts
|
|
5268
5884
|
function formatPercentage(ratio) {
|
|
@@ -5387,8 +6003,34 @@ async function computeStats(storage, events, options) {
|
|
|
5387
6003
|
}
|
|
5388
6004
|
};
|
|
5389
6005
|
const actions = {};
|
|
6006
|
+
const preflight = {
|
|
6007
|
+
runs: 0,
|
|
6008
|
+
ready: 0,
|
|
6009
|
+
needsClarification: 0,
|
|
6010
|
+
needsRevalidation: 0,
|
|
6011
|
+
conflicted: 0,
|
|
6012
|
+
insufficientKnowledge: 0,
|
|
6013
|
+
blockingUnknownsFound: 0,
|
|
6014
|
+
staleKnowledgeFound: 0,
|
|
6015
|
+
pastFailuresSurfaced: 0,
|
|
6016
|
+
questionsSuggested: 0,
|
|
6017
|
+
repeatedChecksAvoided: 0
|
|
6018
|
+
};
|
|
5390
6019
|
for (const e of events) {
|
|
5391
6020
|
switch (e.type) {
|
|
6021
|
+
case "preflight":
|
|
6022
|
+
preflight.runs++;
|
|
6023
|
+
if (e.readiness === "READY") preflight.ready++;
|
|
6024
|
+
else if (e.readiness === "NEEDS_CLARIFICATION") preflight.needsClarification++;
|
|
6025
|
+
else if (e.readiness === "NEEDS_REVALIDATION") preflight.needsRevalidation++;
|
|
6026
|
+
else if (e.readiness === "CONFLICTED") preflight.conflicted++;
|
|
6027
|
+
else if (e.readiness === "INSUFFICIENT_KNOWLEDGE") preflight.insufficientKnowledge++;
|
|
6028
|
+
preflight.blockingUnknownsFound += e.blockingUnknownsCount || 0;
|
|
6029
|
+
preflight.staleKnowledgeFound += e.staleKnowledgeCount || 0;
|
|
6030
|
+
preflight.pastFailuresSurfaced += e.pastFailuresCount || 0;
|
|
6031
|
+
preflight.questionsSuggested += e.questionsCount || 0;
|
|
6032
|
+
preflight.repeatedChecksAvoided += e.repeatedChecksAvoidedCount || 0;
|
|
6033
|
+
break;
|
|
5392
6034
|
case "knowledge_created":
|
|
5393
6035
|
learning.new++;
|
|
5394
6036
|
break;
|
|
@@ -5559,7 +6201,8 @@ async function computeStats(storage, events, options) {
|
|
|
5559
6201
|
questionResolutionRate,
|
|
5560
6202
|
jevRejectionRate
|
|
5561
6203
|
},
|
|
5562
|
-
health
|
|
6204
|
+
health,
|
|
6205
|
+
preflight: preflight.runs > 0 ? preflight : void 0
|
|
5563
6206
|
};
|
|
5564
6207
|
}
|
|
5565
6208
|
async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
@@ -5628,9 +6271,9 @@ async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
|
5628
6271
|
}
|
|
5629
6272
|
|
|
5630
6273
|
// src/stats/filter.ts
|
|
5631
|
-
import { execFile as
|
|
5632
|
-
import { promisify as
|
|
5633
|
-
var
|
|
6274
|
+
import { execFile as execFile3 } from "child_process";
|
|
6275
|
+
import { promisify as promisify3 } from "util";
|
|
6276
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
5634
6277
|
async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
5635
6278
|
const trimmed = since.trim();
|
|
5636
6279
|
const durationMatch = trimmed.match(/^(\d+)\s*([smhdw])$/i);
|
|
@@ -5662,7 +6305,7 @@ async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
|
5662
6305
|
return parsedDate;
|
|
5663
6306
|
}
|
|
5664
6307
|
try {
|
|
5665
|
-
const { stdout } = await
|
|
6308
|
+
const { stdout } = await execFileAsync3(
|
|
5666
6309
|
"git",
|
|
5667
6310
|
["log", "-1", "--format=%ct", trimmed],
|
|
5668
6311
|
{ cwd: projectDir }
|
|
@@ -5758,6 +6401,23 @@ function formatStatsReport(report) {
|
|
|
5758
6401
|
lines.push(padRow("Frequently rejected", report.health.frequentlyRejected));
|
|
5759
6402
|
lines.push(padRow("Frequently misleading", report.health.frequentlyMisleading));
|
|
5760
6403
|
lines.push(padRow("Unused", report.health.unused));
|
|
6404
|
+
if (report.preflight && report.preflight.runs > 0) {
|
|
6405
|
+
lines.push("");
|
|
6406
|
+
lines.push("Preflight");
|
|
6407
|
+
lines.push(padRow("Runs", report.preflight.runs));
|
|
6408
|
+
lines.push(padRow("Ready", report.preflight.ready));
|
|
6409
|
+
lines.push(padRow("Needs clarification", report.preflight.needsClarification));
|
|
6410
|
+
lines.push(padRow("Needs revalidation", report.preflight.needsRevalidation));
|
|
6411
|
+
lines.push(padRow("Conflicted", report.preflight.conflicted));
|
|
6412
|
+
lines.push(padRow("Insufficient knowledge", report.preflight.insufficientKnowledge));
|
|
6413
|
+
lines.push("");
|
|
6414
|
+
lines.push("Before implementation");
|
|
6415
|
+
lines.push(padRow("Blocking unknowns found", report.preflight.blockingUnknownsFound));
|
|
6416
|
+
lines.push(padRow("Stale knowledge found", report.preflight.staleKnowledgeFound));
|
|
6417
|
+
lines.push(padRow("Past failures surfaced", report.preflight.pastFailuresSurfaced));
|
|
6418
|
+
lines.push(padRow("Questions suggested", report.preflight.questionsSuggested));
|
|
6419
|
+
lines.push(padRow("Repeated checks avoided", report.preflight.repeatedChecksAvoided));
|
|
6420
|
+
}
|
|
5761
6421
|
return lines.join("\n");
|
|
5762
6422
|
}
|
|
5763
6423
|
function formatKnowledgeStatsReport(report) {
|
|
@@ -5827,17 +6487,56 @@ async function runStats(options = {}) {
|
|
|
5827
6487
|
|
|
5828
6488
|
// src/mcp/server.ts
|
|
5829
6489
|
function createBilmemMcpApp(options = {}) {
|
|
5830
|
-
const
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
6490
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
6491
|
+
let lastActiveDir = options.rootDir ? path9.resolve(options.rootDir) : null;
|
|
6492
|
+
function isSystemRoot(dir) {
|
|
6493
|
+
const resolved = path9.resolve(dir);
|
|
6494
|
+
return resolved === "/" || path9.parse(resolved).root === resolved;
|
|
6495
|
+
}
|
|
6496
|
+
function resolveTargetDir(requestedPath) {
|
|
6497
|
+
if (requestedPath && requestedPath.trim()) {
|
|
6498
|
+
const resolved = path9.resolve(requestedPath.trim());
|
|
6499
|
+
lastActiveDir = resolved;
|
|
6500
|
+
return resolved;
|
|
6501
|
+
}
|
|
6502
|
+
if (lastActiveDir) {
|
|
6503
|
+
return lastActiveDir;
|
|
6504
|
+
}
|
|
6505
|
+
if (process.env.BILMEM_PROJECT_DIR) {
|
|
6506
|
+
const envDir = path9.resolve(process.env.BILMEM_PROJECT_DIR);
|
|
6507
|
+
lastActiveDir = envDir;
|
|
6508
|
+
return envDir;
|
|
6509
|
+
}
|
|
6510
|
+
const cwd = process.cwd();
|
|
6511
|
+
if (cwd && !isSystemRoot(cwd)) {
|
|
6512
|
+
lastActiveDir = cwd;
|
|
6513
|
+
return cwd;
|
|
6514
|
+
}
|
|
6515
|
+
throw new Error(
|
|
6516
|
+
`No project directory specified and the MCP server was started in system root ('/'). Please provide the 'path' parameter in your tool call (e.g. { path: "/path/to/project", ... }).`
|
|
6517
|
+
);
|
|
6518
|
+
}
|
|
6519
|
+
async function getProjectContext(requestedPath) {
|
|
6520
|
+
const targetDir = resolveTargetDir(requestedPath);
|
|
6521
|
+
let ctx = contexts.get(targetDir);
|
|
6522
|
+
if (!ctx) {
|
|
6523
|
+
const storage = new Storage(targetDir);
|
|
6524
|
+
const index = new SearchIndex();
|
|
6525
|
+
ctx = {
|
|
6526
|
+
rootDir: targetDir,
|
|
6527
|
+
storage,
|
|
6528
|
+
index,
|
|
6529
|
+
initialized: false
|
|
6530
|
+
};
|
|
6531
|
+
contexts.set(targetDir, ctx);
|
|
5840
6532
|
}
|
|
6533
|
+
if (!ctx.initialized) {
|
|
6534
|
+
await ctx.storage.init();
|
|
6535
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
6536
|
+
await ctx.index.buildIndex(all);
|
|
6537
|
+
ctx.initialized = true;
|
|
6538
|
+
}
|
|
6539
|
+
return ctx;
|
|
5841
6540
|
}
|
|
5842
6541
|
const app = new McpApp({
|
|
5843
6542
|
name: options.name ?? "bilmem",
|
|
@@ -5847,6 +6546,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5847
6546
|
name: "bilmem_resolve",
|
|
5848
6547
|
description: "Determine if enough is known to answer or act, what contextually does not apply, what is missing, and the cheapest next step to resolve uncertainty.",
|
|
5849
6548
|
inputSchema: {
|
|
6549
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5850
6550
|
question: z.string().describe("The question, goal, or uncertainty to resolve"),
|
|
5851
6551
|
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. server-session, node, postgres)"),
|
|
5852
6552
|
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. stateless-jwt, browser, sqlite)"),
|
|
@@ -5854,8 +6554,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5854
6554
|
maxDepth: z.number().optional().describe("Max candidates to inspect (default: 5)")
|
|
5855
6555
|
},
|
|
5856
6556
|
handler: async (args) => {
|
|
5857
|
-
await
|
|
5858
|
-
const retriever = new Retriever(index, storage);
|
|
6557
|
+
const ctx = await getProjectContext(args.path);
|
|
6558
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5859
6559
|
const res = await resolveEpistemicQuery(
|
|
5860
6560
|
{
|
|
5861
6561
|
question: args.question,
|
|
@@ -5864,7 +6564,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5864
6564
|
scope: args.scope,
|
|
5865
6565
|
maxDepth: args.maxDepth
|
|
5866
6566
|
},
|
|
5867
|
-
storage,
|
|
6567
|
+
ctx.storage,
|
|
5868
6568
|
retriever
|
|
5869
6569
|
);
|
|
5870
6570
|
return {
|
|
@@ -5881,10 +6581,53 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5881
6581
|
};
|
|
5882
6582
|
}
|
|
5883
6583
|
});
|
|
6584
|
+
app.tool({
|
|
6585
|
+
name: "bilmem_preflight",
|
|
6586
|
+
description: "Determine what must be known before beginning a proposed goal (surfaces constraints, relevant lessons, past failures, stale knowledge, and blocking unknowns).",
|
|
6587
|
+
inputSchema: {
|
|
6588
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
6589
|
+
goal: z.string().describe("The proposed goal, feature, or work to analyze before implementation"),
|
|
6590
|
+
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. authentication, postgres)"),
|
|
6591
|
+
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. browser, sqlite)"),
|
|
6592
|
+
deep: z.boolean().optional().describe("Check targeted git diff / unindexed files for staleness")
|
|
6593
|
+
},
|
|
6594
|
+
handler: async (args) => {
|
|
6595
|
+
const ctx = await getProjectContext(args.path);
|
|
6596
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
6597
|
+
const res = await executePreflight(
|
|
6598
|
+
{
|
|
6599
|
+
goal: args.goal,
|
|
6600
|
+
positive: args.positive,
|
|
6601
|
+
negative: args.negative,
|
|
6602
|
+
deep: args.deep
|
|
6603
|
+
},
|
|
6604
|
+
ctx.storage,
|
|
6605
|
+
retriever,
|
|
6606
|
+
ctx.index
|
|
6607
|
+
);
|
|
6608
|
+
return {
|
|
6609
|
+
goal: res.goal,
|
|
6610
|
+
readiness: res.readiness,
|
|
6611
|
+
known: res.known.slice(0, 5).map((k) => k.statement),
|
|
6612
|
+
constraints: res.constraints.map((c) => c.statement),
|
|
6613
|
+
lessons: res.lessons.slice(0, 5).map((l) => l.statement),
|
|
6614
|
+
pastFailures: res.past_failures.slice(0, 5).map((f) => f.statement),
|
|
6615
|
+
blockingUnknowns: res.unknowns.filter((u) => u.blocking).map((u) => u.question || u.statement || u.id),
|
|
6616
|
+
stale: res.stale.map((s) => s.id),
|
|
6617
|
+
questions: res.questions.slice(0, 5).map((q) => q.question),
|
|
6618
|
+
next: res.next.slice(0, 3).map((n) => ({
|
|
6619
|
+
type: n.type,
|
|
6620
|
+
target: n.target,
|
|
6621
|
+
reason: n.reason
|
|
6622
|
+
}))
|
|
6623
|
+
};
|
|
6624
|
+
}
|
|
6625
|
+
});
|
|
5884
6626
|
app.tool({
|
|
5885
6627
|
name: "bilmem_lookup",
|
|
5886
6628
|
description: "Lookup reusable knowledge, conventions, and lessons using JEV contextual reranking. Returns compact, highly applicable knowledge units.",
|
|
5887
6629
|
inputSchema: {
|
|
6630
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5888
6631
|
query: z.string().optional().describe("Query text, intent keywords, or problem description"),
|
|
5889
6632
|
positive: z.array(z.string()).optional().describe("Contextual positive cues (e.g. node, esm, server-session)"),
|
|
5890
6633
|
negative: z.array(z.string()).optional().describe("Contextual negative cues (e.g. commonjs, browser)"),
|
|
@@ -5892,8 +6635,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5892
6635
|
limit: z.number().optional().describe("Maximum number of results to return (default 5)")
|
|
5893
6636
|
},
|
|
5894
6637
|
handler: async (args) => {
|
|
5895
|
-
await
|
|
5896
|
-
const retriever = new Retriever(index, storage);
|
|
6638
|
+
const ctx = await getProjectContext(args.path);
|
|
6639
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5897
6640
|
const results = await retriever.lookup({
|
|
5898
6641
|
query: args.query,
|
|
5899
6642
|
positive: args.positive,
|
|
@@ -5920,6 +6663,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5920
6663
|
name: "bilmem_observe",
|
|
5921
6664
|
description: "Record a new observation from code, git, test execution, or environment. Preserves provenance.",
|
|
5922
6665
|
inputSchema: {
|
|
6666
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5923
6667
|
claim: z.string().describe("What was observed"),
|
|
5924
6668
|
source: z.string().describe("Source identifier (e.g. file:src/auth.ts#L42, git:commit:81ac23, user:correction)"),
|
|
5925
6669
|
confidence: z.number().optional().describe("Confidence 0.0 - 1.0 (default: 0.85)"),
|
|
@@ -5928,7 +6672,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5928
6672
|
details: z.record(z.any()).optional().describe("Optional structured metadata")
|
|
5929
6673
|
},
|
|
5930
6674
|
handler: async (args) => {
|
|
5931
|
-
await
|
|
6675
|
+
const ctx = await getProjectContext(args.path);
|
|
5932
6676
|
const obs = createObservation({
|
|
5933
6677
|
source: args.source,
|
|
5934
6678
|
claim: args.claim,
|
|
@@ -5942,10 +6686,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5942
6686
|
negative: args.negative,
|
|
5943
6687
|
details: args.details
|
|
5944
6688
|
});
|
|
5945
|
-
await storage.saveObservation(obs);
|
|
6689
|
+
await ctx.storage.saveObservation(obs);
|
|
5946
6690
|
const evidence = createEvidence(obs, "supports");
|
|
5947
|
-
await storage.saveEvidence(evidence);
|
|
5948
|
-
const existing = await storage.getAllKnowledge();
|
|
6691
|
+
await ctx.storage.saveEvidence(evidence);
|
|
6692
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5949
6693
|
const rec = reconcileKnowledge(
|
|
5950
6694
|
{
|
|
5951
6695
|
statement: obs.claim,
|
|
@@ -5966,10 +6710,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5966
6710
|
evidence: [evidence.id, obs.source]
|
|
5967
6711
|
},
|
|
5968
6712
|
existing,
|
|
5969
|
-
storage
|
|
6713
|
+
ctx.storage
|
|
5970
6714
|
);
|
|
5971
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
5972
|
-
await index.upsert(updatedKnowledge);
|
|
6715
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6716
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
5973
6717
|
return {
|
|
5974
6718
|
observationId: obs.id,
|
|
5975
6719
|
evidenceId: evidence.id,
|
|
@@ -5983,6 +6727,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5983
6727
|
name: "bilmem_learn",
|
|
5984
6728
|
description: "Learn from a completed task trajectory or explicit user correction. Converts experiences into refined, reusable lessons.",
|
|
5985
6729
|
inputSchema: {
|
|
6730
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5986
6731
|
goal: z.string().describe("The task or goal that was pursued"),
|
|
5987
6732
|
taskId: z.string().optional().describe("Task identifier"),
|
|
5988
6733
|
outcome: z.enum(["success", "failure", "reverted", "neutral"]).describe("Outcome of the task"),
|
|
@@ -5998,16 +6743,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5998
6743
|
}).optional().describe("Explicit user correction, if any occurred")
|
|
5999
6744
|
},
|
|
6000
6745
|
handler: async (args) => {
|
|
6001
|
-
await
|
|
6746
|
+
const ctx = await getProjectContext(args.path);
|
|
6002
6747
|
if (args.userCorrection) {
|
|
6003
6748
|
const { observation, knowledge } = processUserCorrection(args.userCorrection);
|
|
6004
|
-
await storage.saveObservation(observation);
|
|
6005
|
-
await storage.saveEvidence(createEvidence(observation, "supports"));
|
|
6006
|
-
const existing = await storage.getAllKnowledge();
|
|
6749
|
+
await ctx.storage.saveObservation(observation);
|
|
6750
|
+
await ctx.storage.saveEvidence(createEvidence(observation, "supports"));
|
|
6751
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
6007
6752
|
const rec = reconcileKnowledge(knowledge, existing);
|
|
6008
|
-
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, storage);
|
|
6009
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
6010
|
-
await index.upsert(updatedKnowledge);
|
|
6753
|
+
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, ctx.storage);
|
|
6754
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6755
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
6011
6756
|
return {
|
|
6012
6757
|
type: "user_correction",
|
|
6013
6758
|
knowledgeId: updatedKnowledge.id,
|
|
@@ -6023,20 +6768,20 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6023
6768
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6024
6769
|
finalOutcome: args.outcome
|
|
6025
6770
|
};
|
|
6026
|
-
const existing = await storage.getAllKnowledge();
|
|
6771
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
6027
6772
|
const learningResult = learnFromTrajectory(trajectory, existing);
|
|
6028
6773
|
for (const obs of learningResult.observations) {
|
|
6029
|
-
await storage.saveObservation(obs);
|
|
6774
|
+
await ctx.storage.saveObservation(obs);
|
|
6030
6775
|
}
|
|
6031
6776
|
for (const ev of learningResult.evidence) {
|
|
6032
|
-
await storage.saveEvidence(ev);
|
|
6777
|
+
await ctx.storage.saveEvidence(ev);
|
|
6033
6778
|
}
|
|
6034
6779
|
let updatedKnowledge;
|
|
6035
6780
|
let action = "NEW";
|
|
6036
6781
|
for (const item of learningResult.reconciliations) {
|
|
6037
6782
|
if (item.appliedKnowledge) {
|
|
6038
|
-
await storage.saveKnowledge(item.appliedKnowledge);
|
|
6039
|
-
await index.upsert(item.appliedKnowledge);
|
|
6783
|
+
await ctx.storage.saveKnowledge(item.appliedKnowledge);
|
|
6784
|
+
await ctx.index.upsert(item.appliedKnowledge);
|
|
6040
6785
|
updatedKnowledge = item.appliedKnowledge;
|
|
6041
6786
|
action = item.action;
|
|
6042
6787
|
}
|
|
@@ -6048,9 +6793,9 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6048
6793
|
result: args.outcome,
|
|
6049
6794
|
context: { positive: updatedKnowledge.positive, negative: updatedKnowledge.negative }
|
|
6050
6795
|
});
|
|
6051
|
-
await storage.appendOutcome(out);
|
|
6796
|
+
await ctx.storage.appendOutcome(out);
|
|
6052
6797
|
applyOutcomeToKnowledge(updatedKnowledge, out);
|
|
6053
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
6798
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6054
6799
|
return {
|
|
6055
6800
|
type: "trajectory_lesson",
|
|
6056
6801
|
action,
|
|
@@ -6072,12 +6817,13 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6072
6817
|
name: "bilmem_explain",
|
|
6073
6818
|
description: "Explain why a knowledge ID exists: statement, evidence provenance, confidence, support/contradiction history, and applicable context.",
|
|
6074
6819
|
inputSchema: {
|
|
6820
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
6075
6821
|
id: z.string().describe("The knowledge ID to explain"),
|
|
6076
6822
|
history: z.boolean().optional().describe("Whether to format lifecycle transition history (--history)")
|
|
6077
6823
|
},
|
|
6078
6824
|
handler: async (args) => {
|
|
6079
|
-
await
|
|
6080
|
-
const k = await storage.getKnowledge(args.id);
|
|
6825
|
+
const ctx = await getProjectContext(args.path);
|
|
6826
|
+
const k = await ctx.storage.getKnowledge(args.id);
|
|
6081
6827
|
if (!k) {
|
|
6082
6828
|
return { error: `Knowledge not found: "${args.id}"` };
|
|
6083
6829
|
}
|
|
@@ -6111,6 +6857,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6111
6857
|
name: "bilmem_unknown",
|
|
6112
6858
|
description: "Query or record first-class unknowns (NOT_OBSERVED, INSUFFICIENT_EVIDENCE, CONFLICTING_EVIDENCE, UNRESOLVABLE_LOCALLY) to prevent repeating useless investigations.",
|
|
6113
6859
|
inputSchema: {
|
|
6860
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
6114
6861
|
action: z.enum(["query", "check", "record", "resolve"]).describe("Action to perform"),
|
|
6115
6862
|
id: z.string().optional().describe("Unknown ID or query subject"),
|
|
6116
6863
|
reason: z.enum(["NOT_OBSERVED", "INSUFFICIENT_EVIDENCE", "CONFLICTING_EVIDENCE", "STALE", "UNRESOLVABLE_LOCALLY"]).optional(),
|
|
@@ -6119,16 +6866,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6119
6866
|
question: z.string().optional().describe("Clarifying question for the user or next agent")
|
|
6120
6867
|
},
|
|
6121
6868
|
handler: async (args) => {
|
|
6122
|
-
await
|
|
6869
|
+
const ctx = await getProjectContext(args.path);
|
|
6123
6870
|
if (args.action === "query") {
|
|
6124
|
-
const list = await storage.getAllUnknowns();
|
|
6871
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
6125
6872
|
return {
|
|
6126
6873
|
count: list.length,
|
|
6127
6874
|
unknowns: list
|
|
6128
6875
|
};
|
|
6129
6876
|
}
|
|
6130
6877
|
if (args.action === "check") {
|
|
6131
|
-
const list = await storage.getAllUnknowns();
|
|
6878
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
6132
6879
|
const checkResult = hasBeenInvestigated(list, args.id || "");
|
|
6133
6880
|
return checkResult;
|
|
6134
6881
|
}
|
|
@@ -6142,12 +6889,12 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6142
6889
|
checked: args.checked ?? [],
|
|
6143
6890
|
next: args.question ? { type: "ask", question: args.question } : void 0
|
|
6144
6891
|
});
|
|
6145
|
-
await storage.saveUnknown(record);
|
|
6892
|
+
await ctx.storage.saveUnknown(record);
|
|
6146
6893
|
return { success: true, recorded: record };
|
|
6147
6894
|
}
|
|
6148
6895
|
if (args.action === "resolve") {
|
|
6149
6896
|
if (!args.id) throw new Error("ID is required to resolve an unknown");
|
|
6150
|
-
const deleted = await storage.deleteUnknown(args.id);
|
|
6897
|
+
const deleted = await ctx.storage.deleteUnknown(args.id);
|
|
6151
6898
|
return { success: deleted, resolvedId: args.id };
|
|
6152
6899
|
}
|
|
6153
6900
|
return { error: "Unknown action" };
|
|
@@ -6157,16 +6904,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6157
6904
|
name: "bilmem_scan",
|
|
6158
6905
|
description: "Trigger a language-independent scan of the repository filesystem, markdown documentation, and git history.",
|
|
6159
6906
|
inputSchema: {
|
|
6160
|
-
path: z.string().optional().describe("
|
|
6907
|
+
path: z.string().optional().describe("Project root directory to scan (defaults to active workspace or current working directory)"),
|
|
6161
6908
|
incremental: z.boolean().optional().describe("Whether to scan incrementally from last scanned git commit")
|
|
6162
6909
|
},
|
|
6163
6910
|
handler: async (args) => {
|
|
6164
|
-
const
|
|
6165
|
-
const report = await scanRepository(
|
|
6911
|
+
const ctx = await getProjectContext(args.path);
|
|
6912
|
+
const report = await scanRepository(ctx.rootDir, {
|
|
6166
6913
|
incremental: args.incremental ?? false
|
|
6167
6914
|
});
|
|
6168
|
-
const all = await storage.getAllKnowledge();
|
|
6169
|
-
await index.buildIndex(all);
|
|
6915
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
6916
|
+
await ctx.index.buildIndex(all);
|
|
6170
6917
|
return report;
|
|
6171
6918
|
}
|
|
6172
6919
|
});
|
|
@@ -6174,13 +6921,14 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6174
6921
|
name: "bilmem_stats",
|
|
6175
6922
|
description: "Inspect local-first statistics, knowledge health, and effectiveness metrics (measuring reuse, investigation avoidance, and JEV accuracy). Read-only.",
|
|
6176
6923
|
inputSchema: {
|
|
6924
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
6177
6925
|
since: z.string().optional().describe("Time window (e.g. 24h, 7d, 30d) or Git revision (e.g. HEAD~20, commit-sha)"),
|
|
6178
6926
|
knowledgeId: z.string().optional().describe("Specific knowledge ID to inspect single-item lifecycle and diagnostics")
|
|
6179
6927
|
},
|
|
6180
6928
|
handler: async (args) => {
|
|
6181
|
-
await
|
|
6929
|
+
const ctx = await getProjectContext(args.path);
|
|
6182
6930
|
const res = await runStats({
|
|
6183
|
-
rootDir,
|
|
6931
|
+
rootDir: ctx.rootDir,
|
|
6184
6932
|
since: args.since,
|
|
6185
6933
|
knowledgeId: args.knowledgeId
|
|
6186
6934
|
});
|
|
@@ -6194,20 +6942,20 @@ function createBilmemMcpApp(options = {}) {
|
|
|
6194
6942
|
}
|
|
6195
6943
|
|
|
6196
6944
|
// src/install/index.ts
|
|
6197
|
-
import * as
|
|
6945
|
+
import * as path19 from "path";
|
|
6198
6946
|
import * as readline from "readline/promises";
|
|
6199
6947
|
|
|
6200
6948
|
// src/install/adapters/cursor.ts
|
|
6201
|
-
import * as
|
|
6949
|
+
import * as path13 from "path";
|
|
6202
6950
|
import * as os from "os";
|
|
6203
6951
|
|
|
6204
6952
|
// src/install/adapters/base.ts
|
|
6205
|
-
import * as
|
|
6206
|
-
import * as
|
|
6953
|
+
import * as path12 from "path";
|
|
6954
|
+
import * as fs9 from "fs/promises";
|
|
6207
6955
|
|
|
6208
6956
|
// src/install/atomic.ts
|
|
6209
|
-
import * as
|
|
6210
|
-
import * as
|
|
6957
|
+
import * as fs8 from "fs/promises";
|
|
6958
|
+
import * as path10 from "path";
|
|
6211
6959
|
|
|
6212
6960
|
// src/install/instructions.ts
|
|
6213
6961
|
var CURRENT_INTEGRATION_VERSION = 1;
|
|
@@ -6219,6 +6967,8 @@ function getAgentInstructionText(version = CURRENT_INTEGRATION_VERSION) {
|
|
|
6219
6967
|
## Bilmem Knowledge Runtime
|
|
6220
6968
|
Bilmem is available through MCP.
|
|
6221
6969
|
|
|
6970
|
+
Before substantial implementation work, use \`bilmem_preflight\` to surface existing constraints, relevant lessons, stale knowledge, and important unknowns. Do not run it for trivial edits or typo fixes.
|
|
6971
|
+
|
|
6222
6972
|
Use \`bilmem_resolve\` when:
|
|
6223
6973
|
- uncertain about project-specific behavior,
|
|
6224
6974
|
- multiple plausible approaches exist,
|
|
@@ -6242,18 +6992,171 @@ function extractInstructionVersion(content) {
|
|
|
6242
6992
|
function hasBilmemInstruction(content) {
|
|
6243
6993
|
return content.includes(BILMEM_START_MARKER) && content.includes(BILMEM_END_MARKER);
|
|
6244
6994
|
}
|
|
6995
|
+
function getBilmemSkillContent(version = CURRENT_INTEGRATION_VERSION) {
|
|
6996
|
+
return `---
|
|
6997
|
+
name: bilmem
|
|
6998
|
+
description: >-
|
|
6999
|
+
Epistemic runtime and project knowledge engine. Use when you need to inspect existing constraints or lessons before starting work (bilmem_preflight), resolve epistemic uncertainty or architectural decisions (bilmem_resolve), query verified knowledge (bilmem_lookup), record new observations (bilmem_observe), extract lessons from trajectories (bilmem_learn), track unknowns (bilmem_unknown), or check knowledge reuse and effectiveness (bilmem_stats).
|
|
7000
|
+
---
|
|
7001
|
+
<!-- bilmem-version: ${version} -->
|
|
7002
|
+
|
|
7003
|
+
# Bilmem Epistemic Knowledge Runtime
|
|
7004
|
+
|
|
7005
|
+
Bilmem is an epistemic knowledge engine designed to answer:
|
|
7006
|
+
> **Do I know enough to act, what do I know does not apply, what am I missing, and what is the cheapest next step to resolve uncertainty?**
|
|
7007
|
+
|
|
7008
|
+
Unlike traditional code graphs or vector memory, Bilmem focuses on verified facts, contextual applicability (via JEV positive/negative cues), staged learning from trajectories, and tracking unknowns as first-class citizens.
|
|
7009
|
+
|
|
7010
|
+
---
|
|
7011
|
+
|
|
7012
|
+
## \u26A1 Core Epistemic Commitments
|
|
7013
|
+
|
|
7014
|
+
1. **Cheaper than not knowing:** Querying Bilmem or resolving uncertainty is significantly cheaper than re-investigating, guessing, or recovering from incorrect architectural assumptions.
|
|
7015
|
+
2. **Never guess project invariants:** If uncertain whether caching is enabled in production, how auth tokens rotate, or why a specific pattern exists, resolve it through Bilmem instead of guessing.
|
|
7016
|
+
3. **No queries on trivial edits:** Do not query Bilmem for simple typo fixes, formatting, or obvious local syntax changes.
|
|
7017
|
+
4. **Receipts & Honest Confidence:** Bilmem uses explainable confidence tiers (\`low\`, \`medium\`, \`high\`) and returns receipts explaining why knowledge was accepted or rejected.
|
|
7018
|
+
|
|
7019
|
+
---
|
|
7020
|
+
|
|
7021
|
+
## \u{1F680} Progressive Disclosure: Tool Runbook
|
|
7022
|
+
|
|
7023
|
+
> [!TIP]
|
|
7024
|
+
> **Project Directory Target (\`path\` parameter)**:
|
|
7025
|
+
> In global MCP environments or multi-workspace IDEs, always pass the \`"path"\` parameter with your project root (e.g. \`"path": "/path/to/project"\` or active workspace directory) to target the proper repository and prevent root filesystem permission issues.
|
|
7026
|
+
|
|
7027
|
+
### 1. Before Substantial Implementation: \`bilmem_preflight\`
|
|
7028
|
+
Run \`bilmem_preflight\` with your proposed goal before modifying architecture, core logic, or adding features:
|
|
7029
|
+
\`\`\`json
|
|
7030
|
+
{
|
|
7031
|
+
"path": "/path/to/project",
|
|
7032
|
+
"goal": "Add OAuth login with Google",
|
|
7033
|
+
"positive": ["auth", "oauth", "session"],
|
|
7034
|
+
"negative": ["stateless-jwt"]
|
|
7035
|
+
}
|
|
7036
|
+
\`\`\`
|
|
7037
|
+
**Returns:**
|
|
7038
|
+
- Applicable verified constraints & architectural rules.
|
|
7039
|
+
- Relevant lessons from past implementations or regressions.
|
|
7040
|
+
- Past failures in similar domains.
|
|
7041
|
+
- Blocking unknowns that must be investigated first.
|
|
7042
|
+
- Stale dependencies that need review.
|
|
7043
|
+
- Next recommended investigation actions.
|
|
7044
|
+
|
|
7045
|
+
### 2. When Uncertain or Resolving Contradictions: \`bilmem_resolve\`
|
|
7046
|
+
Use \`bilmem_resolve\` when encountering ambiguous codebase behavior, multiple plausible paths, or conflicting assumptions:
|
|
7047
|
+
\`\`\`json
|
|
7048
|
+
{
|
|
7049
|
+
"path": "/path/to/project",
|
|
7050
|
+
"question": "How are user sessions renewed and validated?",
|
|
7051
|
+
"positive": ["server-session", "postgres", "redis"],
|
|
7052
|
+
"negative": ["client-cookie-only"]
|
|
7053
|
+
}
|
|
7054
|
+
\`\`\`
|
|
7055
|
+
**Returns:**
|
|
7056
|
+
- \`state\`: \`resolved\` (sufficient facts known), \`needs_investigation\`, or \`contradiction\`.
|
|
7057
|
+
- \`known\`: Verified statements applicable to the current context.
|
|
7058
|
+
- \`rejected\`: Inapplicable knowledge units filtered out by negative cues.
|
|
7059
|
+
- \`missing\`: Critical gaps that need observation before proceeding.
|
|
7060
|
+
- \`next\`: The cheapest concrete investigation step.
|
|
7061
|
+
|
|
7062
|
+
### 3. Exact Knowledge Retrieval: \`bilmem_lookup\`
|
|
7063
|
+
Quickly look up specific conventions, lessons, or rules:
|
|
7064
|
+
\`\`\`json
|
|
7065
|
+
{
|
|
7066
|
+
"path": "/path/to/project",
|
|
7067
|
+
"query": "session timeout configuration",
|
|
7068
|
+
"limit": 5
|
|
7069
|
+
}
|
|
7070
|
+
\`\`\`
|
|
7071
|
+
|
|
7072
|
+
### 4. Recording Fresh Discoveries: \`bilmem_observe\`
|
|
7073
|
+
Record verified discoveries, configuration invariants, or environment realities:
|
|
7074
|
+
\`\`\`json
|
|
7075
|
+
{
|
|
7076
|
+
"path": "/path/to/project",
|
|
7077
|
+
"claim": "Production session cookies require SameSite=Strict and Secure flags",
|
|
7078
|
+
"source": "file:src/auth/session.ts#L42",
|
|
7079
|
+
"positive": ["production", "cookie", "auth"],
|
|
7080
|
+
"negative": ["development"]
|
|
7081
|
+
}
|
|
7082
|
+
\`\`\`
|
|
7083
|
+
|
|
7084
|
+
### 5. Learning from Corrections & Trajectories: \`bilmem_learn\`
|
|
7085
|
+
After resolving a difficult bug, fixing a regression, or receiving a critical user correction, distill the trajectory:
|
|
7086
|
+
\`\`\`json
|
|
7087
|
+
{
|
|
7088
|
+
"path": "/path/to/project",
|
|
7089
|
+
"goal": "Fix refresh token rotation loop",
|
|
7090
|
+
"outcome": "success",
|
|
7091
|
+
"userCorrection": {
|
|
7092
|
+
"topic": "session-auth",
|
|
7093
|
+
"incorrectAssumption": "Token refresh does not require a concurrent mutex",
|
|
7094
|
+
"correctFact": "Token refresh requires a concurrency mutex to avoid 401 race conditions"
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
\`\`\`
|
|
7098
|
+
Bilmem runs staged reconciliation: extracts observations, creates candidate lessons, evaluates polar contradictions, and reconciles into durable knowledge.
|
|
7099
|
+
|
|
7100
|
+
### 6. Recording Known Unknowns: \`bilmem_unknown\`
|
|
7101
|
+
When you realize critical context is missing, record an unknown instead of making a silent assumption:
|
|
7102
|
+
\`\`\`json
|
|
7103
|
+
{
|
|
7104
|
+
"path": "/path/to/project",
|
|
7105
|
+
"action": "record",
|
|
7106
|
+
"id": "staging.redis-cache",
|
|
7107
|
+
"question": "Does the staging database share the Redis cache cluster with production?"
|
|
7108
|
+
}
|
|
7109
|
+
\`\`\`
|
|
7110
|
+
|
|
7111
|
+
### 7. Auditing Effectiveness & Reuse: \`bilmem_stats\`
|
|
7112
|
+
Verify how well Bilmem is reducing repeated investigation:
|
|
7113
|
+
\`\`\`json
|
|
7114
|
+
{
|
|
7115
|
+
"path": "/path/to/project",
|
|
7116
|
+
"since": "7d"
|
|
7117
|
+
}
|
|
7118
|
+
\`\`\`
|
|
7119
|
+
|
|
7120
|
+
---
|
|
7121
|
+
|
|
7122
|
+
## \u{1F4BB} CLI Quick Reference
|
|
7123
|
+
|
|
7124
|
+
All tools are also available directly on the command line:
|
|
7125
|
+
|
|
7126
|
+
| Command | Purpose |
|
|
7127
|
+
| :--- | :--- |
|
|
7128
|
+
| \`bilmem preflight "<goal>"\` | Pre-implementation readiness & constraint analysis |
|
|
7129
|
+
| \`bilmem resolve "<question>"\` | Epistemic resolution with context cues & receipts |
|
|
7130
|
+
| \`bilmem lookup "<query>"\` | Fast contextual lookup of verified knowledge |
|
|
7131
|
+
| \`bilmem observe "<claim>" --source <src>\` | Record verified evidence or invariant |
|
|
7132
|
+
| \`bilmem learn --trajectory "<text>"\` | Reconcile candidate lessons into knowledge |
|
|
7133
|
+
| \`bilmem scan [--dry-run]\` | Conservative filesystem & convention discovery |
|
|
7134
|
+
| \`bilmem stats [--since 7d]\` | Measure knowledge reuse and effectiveness |
|
|
7135
|
+
| \`bilmem doctor [--fix]\` | Verify and repair integrations and storage |
|
|
7136
|
+
`;
|
|
7137
|
+
}
|
|
7138
|
+
function hasBilmemSkill(content) {
|
|
7139
|
+
return content.includes("name: bilmem") && content.includes("---");
|
|
7140
|
+
}
|
|
7141
|
+
function extractSkillVersion(content) {
|
|
7142
|
+
const match = content.match(/<!--\s*bilmem-version:\s*(\d+)\s*-->/);
|
|
7143
|
+
if (match && match[1]) {
|
|
7144
|
+
return parseInt(match[1], 10);
|
|
7145
|
+
}
|
|
7146
|
+
return null;
|
|
7147
|
+
}
|
|
6245
7148
|
|
|
6246
7149
|
// src/install/atomic.ts
|
|
6247
7150
|
async function writeAtomic(filePath, content) {
|
|
6248
|
-
const dir =
|
|
6249
|
-
await
|
|
7151
|
+
const dir = path10.dirname(filePath);
|
|
7152
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
6250
7153
|
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
6251
7154
|
try {
|
|
6252
|
-
await
|
|
6253
|
-
await
|
|
7155
|
+
await fs8.writeFile(tmpPath, content, "utf-8");
|
|
7156
|
+
await fs8.rename(tmpPath, filePath);
|
|
6254
7157
|
} catch (err) {
|
|
6255
7158
|
try {
|
|
6256
|
-
await
|
|
7159
|
+
await fs8.unlink(tmpPath);
|
|
6257
7160
|
} catch {
|
|
6258
7161
|
}
|
|
6259
7162
|
throw err;
|
|
@@ -6261,7 +7164,7 @@ async function writeAtomic(filePath, content) {
|
|
|
6261
7164
|
}
|
|
6262
7165
|
async function safeReadJson(filePath) {
|
|
6263
7166
|
try {
|
|
6264
|
-
const raw = await
|
|
7167
|
+
const raw = await fs8.readFile(filePath, "utf-8");
|
|
6265
7168
|
try {
|
|
6266
7169
|
const data = JSON.parse(raw);
|
|
6267
7170
|
return { exists: true, data, raw };
|
|
@@ -6341,7 +7244,7 @@ async function updateMarkdownInstructionFile(filePath, desiredBlock = getAgentIn
|
|
|
6341
7244
|
let existingContent = "";
|
|
6342
7245
|
let exists = false;
|
|
6343
7246
|
try {
|
|
6344
|
-
existingContent = await
|
|
7247
|
+
existingContent = await fs8.readFile(filePath, "utf-8");
|
|
6345
7248
|
exists = true;
|
|
6346
7249
|
} catch (err) {
|
|
6347
7250
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -6376,7 +7279,7 @@ async function updateMarkdownInstructionFile(filePath, desiredBlock = getAgentIn
|
|
|
6376
7279
|
async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
6377
7280
|
let content = "";
|
|
6378
7281
|
try {
|
|
6379
|
-
content = await
|
|
7282
|
+
content = await fs8.readFile(filePath, "utf-8");
|
|
6380
7283
|
} catch (err) {
|
|
6381
7284
|
if (err.code === "ENOENT") {
|
|
6382
7285
|
return { removed: false, fileDeleted: false };
|
|
@@ -6391,7 +7294,7 @@ async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
|
6391
7294
|
if (stripped.length === 0) {
|
|
6392
7295
|
if (!dryRun) {
|
|
6393
7296
|
try {
|
|
6394
|
-
await
|
|
7297
|
+
await fs8.unlink(filePath);
|
|
6395
7298
|
} catch {
|
|
6396
7299
|
}
|
|
6397
7300
|
}
|
|
@@ -6404,21 +7307,57 @@ async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
|
6404
7307
|
}
|
|
6405
7308
|
async function pathExists(p) {
|
|
6406
7309
|
try {
|
|
6407
|
-
await
|
|
7310
|
+
await fs8.access(p);
|
|
6408
7311
|
return true;
|
|
6409
7312
|
} catch {
|
|
6410
7313
|
return false;
|
|
6411
7314
|
}
|
|
6412
7315
|
}
|
|
7316
|
+
async function updateSkillFile(filePath, content, dryRun = false) {
|
|
7317
|
+
let existingContent = "";
|
|
7318
|
+
let exists = false;
|
|
7319
|
+
try {
|
|
7320
|
+
existingContent = await fs8.readFile(filePath, "utf-8");
|
|
7321
|
+
exists = true;
|
|
7322
|
+
} catch (err) {
|
|
7323
|
+
if (err.code !== "ENOENT") throw err;
|
|
7324
|
+
}
|
|
7325
|
+
if (exists && existingContent.trim() === content.trim()) {
|
|
7326
|
+
return { changed: false, created: false, originalExisted: true };
|
|
7327
|
+
}
|
|
7328
|
+
if (!dryRun) {
|
|
7329
|
+
await writeAtomic(filePath, content.trimEnd() + "\n");
|
|
7330
|
+
}
|
|
7331
|
+
return { changed: true, created: !exists, originalExisted: exists };
|
|
7332
|
+
}
|
|
7333
|
+
async function removeSkillFile(filePath, dryRun = false) {
|
|
7334
|
+
try {
|
|
7335
|
+
await fs8.access(filePath);
|
|
7336
|
+
} catch {
|
|
7337
|
+
return { removed: false, fileDeleted: false };
|
|
7338
|
+
}
|
|
7339
|
+
if (!dryRun) {
|
|
7340
|
+
try {
|
|
7341
|
+
await fs8.unlink(filePath);
|
|
7342
|
+
const parentDir = path10.dirname(filePath);
|
|
7343
|
+
const remaining = await fs8.readdir(parentDir);
|
|
7344
|
+
if (remaining.length === 0) {
|
|
7345
|
+
await fs8.rmdir(parentDir);
|
|
7346
|
+
}
|
|
7347
|
+
} catch {
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
return { removed: true, fileDeleted: true };
|
|
7351
|
+
}
|
|
6413
7352
|
async function isExecutableInPath(command) {
|
|
6414
7353
|
const envPath = process.env.PATH;
|
|
6415
7354
|
if (!envPath) return false;
|
|
6416
|
-
const dirs = envPath.split(
|
|
7355
|
+
const dirs = envPath.split(path10.delimiter);
|
|
6417
7356
|
for (const dir of dirs) {
|
|
6418
7357
|
if (!dir) continue;
|
|
6419
|
-
const fullPath =
|
|
7358
|
+
const fullPath = path10.join(dir, command);
|
|
6420
7359
|
try {
|
|
6421
|
-
await
|
|
7360
|
+
await fs8.access(fullPath, fs8.constants.X_OK);
|
|
6422
7361
|
return true;
|
|
6423
7362
|
} catch {
|
|
6424
7363
|
}
|
|
@@ -6427,9 +7366,9 @@ async function isExecutableInPath(command) {
|
|
|
6427
7366
|
}
|
|
6428
7367
|
|
|
6429
7368
|
// src/install/ownership.ts
|
|
6430
|
-
import * as
|
|
7369
|
+
import * as path11 from "path";
|
|
6431
7370
|
function getIntegrationsFilePath(rootDir) {
|
|
6432
|
-
return
|
|
7371
|
+
return path11.join(rootDir, ".bilmem", "integrations.json");
|
|
6433
7372
|
}
|
|
6434
7373
|
async function loadIntegrationsMetadata(rootDir) {
|
|
6435
7374
|
const metaPath = getIntegrationsFilePath(rootDir);
|
|
@@ -6475,6 +7414,7 @@ async function getIntegrationRecord(rootDir, agentId) {
|
|
|
6475
7414
|
|
|
6476
7415
|
// src/install/adapters/base.ts
|
|
6477
7416
|
var BaseAgentAdapter = class {
|
|
7417
|
+
aliases;
|
|
6478
7418
|
supportedScopes = ["project", "global"];
|
|
6479
7419
|
async inspect(context) {
|
|
6480
7420
|
const configPath = this.resolveConfigPath(context);
|
|
@@ -6488,12 +7428,14 @@ var BaseAgentAdapter = class {
|
|
|
6488
7428
|
if (read.exists && !read.error && read.data?.mcpServers?.bilmem) {
|
|
6489
7429
|
mcpConfigured = true;
|
|
6490
7430
|
const b = read.data.mcpServers.bilmem;
|
|
6491
|
-
|
|
7431
|
+
const targetMatch = b.command === targetCommand && Array.isArray(b.args) && b.args.length === targetArgs.length && b.args.every((v, i) => v === targetArgs[i]);
|
|
7432
|
+
const wrapperMatch = Array.isArray(b.args) && b.args.some((arg) => typeof arg === "string" && arg.includes("bilmem"));
|
|
7433
|
+
mcpCommandMatches = Boolean(targetMatch || wrapperMatch);
|
|
6492
7434
|
}
|
|
6493
7435
|
let instructionInstalled = false;
|
|
6494
7436
|
let instructionVersion;
|
|
6495
7437
|
try {
|
|
6496
|
-
const content = await
|
|
7438
|
+
const content = await fs9.readFile(instructionPath, "utf-8");
|
|
6497
7439
|
instructionInstalled = hasBilmemInstruction(content);
|
|
6498
7440
|
const extracted = extractInstructionVersion(content);
|
|
6499
7441
|
if (extracted !== null) {
|
|
@@ -6501,6 +7443,20 @@ var BaseAgentAdapter = class {
|
|
|
6501
7443
|
}
|
|
6502
7444
|
} catch {
|
|
6503
7445
|
}
|
|
7446
|
+
let skillInstalled = false;
|
|
7447
|
+
let skillVersion;
|
|
7448
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7449
|
+
if (skillPath) {
|
|
7450
|
+
try {
|
|
7451
|
+
const skillContent = await fs9.readFile(skillPath, "utf-8");
|
|
7452
|
+
skillInstalled = hasBilmemSkill(skillContent);
|
|
7453
|
+
const extracted = extractSkillVersion(skillContent);
|
|
7454
|
+
if (extracted !== null) {
|
|
7455
|
+
skillVersion = extracted;
|
|
7456
|
+
}
|
|
7457
|
+
} catch {
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
6504
7460
|
return {
|
|
6505
7461
|
id: this.id,
|
|
6506
7462
|
name: this.name,
|
|
@@ -6510,9 +7466,12 @@ var BaseAgentAdapter = class {
|
|
|
6510
7466
|
mcpCommandMatches,
|
|
6511
7467
|
instructionInstalled,
|
|
6512
7468
|
instructionVersion,
|
|
7469
|
+
skillInstalled: skillPath ? skillInstalled : void 0,
|
|
7470
|
+
skillVersion: skillPath ? skillVersion : void 0,
|
|
6513
7471
|
currentInstructionVersion: CURRENT_INTEGRATION_VERSION,
|
|
6514
7472
|
configPath,
|
|
6515
7473
|
instructionPath,
|
|
7474
|
+
skillPath,
|
|
6516
7475
|
details: {
|
|
6517
7476
|
rawConfigExisted: read.exists,
|
|
6518
7477
|
configError: read.error
|
|
@@ -6532,7 +7491,7 @@ var BaseAgentAdapter = class {
|
|
|
6532
7491
|
const storage = context.storage || new Storage(context.projectDir);
|
|
6533
7492
|
if (!await storage.isInitialized()) {
|
|
6534
7493
|
if (!options.dryRun) {
|
|
6535
|
-
await storage.init({ projectName:
|
|
7494
|
+
await storage.init({ projectName: path12.basename(context.projectDir) });
|
|
6536
7495
|
}
|
|
6537
7496
|
projectInitialized = true;
|
|
6538
7497
|
actionsTaken.push("Initialized .bilmem/ storage");
|
|
@@ -6557,6 +7516,19 @@ var BaseAgentAdapter = class {
|
|
|
6557
7516
|
if (instRes.changed) {
|
|
6558
7517
|
actionsTaken.push(`Installed/updated agent instruction block in ${instructionPath}`);
|
|
6559
7518
|
}
|
|
7519
|
+
let skillChanged = false;
|
|
7520
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7521
|
+
if (skillPath) {
|
|
7522
|
+
const skillRes = await updateSkillFile(
|
|
7523
|
+
skillPath,
|
|
7524
|
+
getBilmemSkillContent(CURRENT_INTEGRATION_VERSION),
|
|
7525
|
+
options.dryRun ?? false
|
|
7526
|
+
);
|
|
7527
|
+
skillChanged = skillRes.changed;
|
|
7528
|
+
if (skillRes.changed) {
|
|
7529
|
+
actionsTaken.push(`Installed/updated Antigravity skill in ${skillPath}`);
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
6560
7532
|
if (context.scope === "project") {
|
|
6561
7533
|
await recordIntegration(
|
|
6562
7534
|
context.projectDir,
|
|
@@ -6564,9 +7536,11 @@ var BaseAgentAdapter = class {
|
|
|
6564
7536
|
{
|
|
6565
7537
|
scope: options.scope,
|
|
6566
7538
|
mcp: true,
|
|
6567
|
-
mcpConfigPath:
|
|
7539
|
+
mcpConfigPath: path12.relative(context.projectDir, configPath),
|
|
6568
7540
|
instructions: true,
|
|
6569
|
-
instructionPath:
|
|
7541
|
+
instructionPath: path12.relative(context.projectDir, instructionPath),
|
|
7542
|
+
skill: skillPath ? true : void 0,
|
|
7543
|
+
skillPath: skillPath ? path12.relative(context.projectDir, skillPath) : void 0,
|
|
6570
7544
|
version: CURRENT_INTEGRATION_VERSION,
|
|
6571
7545
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6572
7546
|
},
|
|
@@ -6577,7 +7551,7 @@ var BaseAgentAdapter = class {
|
|
|
6577
7551
|
if (!resolvable && !options.dryRun) {
|
|
6578
7552
|
warnings.push(`Executable "${targetCommand}" is not in system PATH. Ensure it can be run by ${this.name}.`);
|
|
6579
7553
|
}
|
|
6580
|
-
const changesMade = mcpRes.changed || instRes.changed || projectInitialized;
|
|
7554
|
+
const changesMade = mcpRes.changed || instRes.changed || skillChanged || projectInitialized;
|
|
6581
7555
|
if (!changesMade) {
|
|
6582
7556
|
actionsTaken.push("Already up to date");
|
|
6583
7557
|
}
|
|
@@ -6588,6 +7562,8 @@ var BaseAgentAdapter = class {
|
|
|
6588
7562
|
scope: options.scope,
|
|
6589
7563
|
mcpChanged: mcpRes.changed,
|
|
6590
7564
|
instructionChanged: instRes.changed,
|
|
7565
|
+
skillChanged,
|
|
7566
|
+
skillPath,
|
|
6591
7567
|
projectInitialized,
|
|
6592
7568
|
dryRun: options.dryRun,
|
|
6593
7569
|
actionsTaken,
|
|
@@ -6607,10 +7583,19 @@ var BaseAgentAdapter = class {
|
|
|
6607
7583
|
if (instRes.removed) {
|
|
6608
7584
|
actionsTaken.push(`Removed Bilmem instruction block from ${instructionPath}`);
|
|
6609
7585
|
}
|
|
7586
|
+
let skillRemoved = false;
|
|
7587
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7588
|
+
if (skillPath) {
|
|
7589
|
+
const skillRes = await removeSkillFile(skillPath, options.dryRun ?? false);
|
|
7590
|
+
skillRemoved = skillRes.removed;
|
|
7591
|
+
if (skillRes.removed) {
|
|
7592
|
+
actionsTaken.push(`Removed Antigravity skill from ${skillPath}`);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
6610
7595
|
if (context.scope === "project") {
|
|
6611
7596
|
await removeIntegration(context.projectDir, this.id, options.dryRun ?? false);
|
|
6612
7597
|
}
|
|
6613
|
-
const anyRemoved = mcpRes.removed || instRes.removed;
|
|
7598
|
+
const anyRemoved = mcpRes.removed || instRes.removed || skillRemoved;
|
|
6614
7599
|
if (!anyRemoved) {
|
|
6615
7600
|
actionsTaken.push("No Bilmem configuration found to remove");
|
|
6616
7601
|
}
|
|
@@ -6621,6 +7606,8 @@ var BaseAgentAdapter = class {
|
|
|
6621
7606
|
scope: options.scope,
|
|
6622
7607
|
mcpRemoved: mcpRes.removed,
|
|
6623
7608
|
instructionRemoved: instRes.removed,
|
|
7609
|
+
skillRemoved,
|
|
7610
|
+
skillPath,
|
|
6624
7611
|
dryRun: options.dryRun,
|
|
6625
7612
|
actionsTaken,
|
|
6626
7613
|
message: anyRemoved ? `Successfully uninstalled Bilmem from ${this.name}` : `No Bilmem configuration found for ${this.name}`
|
|
@@ -6632,6 +7619,7 @@ var BaseAgentAdapter = class {
|
|
|
6632
7619
|
const recommendations = [];
|
|
6633
7620
|
let mcpValid = false;
|
|
6634
7621
|
let instructionValid = false;
|
|
7622
|
+
let skillValid = true;
|
|
6635
7623
|
if (!state.mcpConfigured) {
|
|
6636
7624
|
issues.push(`MCP server "bilmem" is not configured in ${state.configPath}`);
|
|
6637
7625
|
recommendations.push(`Run "bilmem install ${this.id}" to configure MCP`);
|
|
@@ -6652,17 +7640,31 @@ var BaseAgentAdapter = class {
|
|
|
6652
7640
|
} else {
|
|
6653
7641
|
instructionValid = true;
|
|
6654
7642
|
}
|
|
7643
|
+
if (state.skillPath) {
|
|
7644
|
+
if (!state.skillInstalled) {
|
|
7645
|
+
skillValid = false;
|
|
7646
|
+
issues.push(`Skill file missing from ${state.skillPath}`);
|
|
7647
|
+
recommendations.push(`Run "bilmem install ${this.id}" to install the skill`);
|
|
7648
|
+
} else if (state.skillVersion !== CURRENT_INTEGRATION_VERSION) {
|
|
7649
|
+
skillValid = false;
|
|
7650
|
+
issues.push(
|
|
7651
|
+
`Skill file version (${state.skillVersion || "legacy"}) is outdated (current: ${CURRENT_INTEGRATION_VERSION})`
|
|
7652
|
+
);
|
|
7653
|
+
recommendations.push(`Run "bilmem doctor --fix" to update the skill`);
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
6655
7656
|
const targetCommand = context.bilmemCommand || "bilmem";
|
|
6656
7657
|
const commandResolvable = await isExecutableInPath(targetCommand);
|
|
6657
7658
|
if (!commandResolvable) {
|
|
6658
7659
|
issues.push(`Command "${targetCommand}" is not found in PATH`);
|
|
6659
7660
|
recommendations.push(`Ensure "${targetCommand}" is installed globally or in your PATH`);
|
|
6660
7661
|
}
|
|
6661
|
-
const valid = mcpValid && instructionValid && commandResolvable;
|
|
7662
|
+
const valid = mcpValid && instructionValid && skillValid && commandResolvable;
|
|
6662
7663
|
return {
|
|
6663
7664
|
valid,
|
|
6664
7665
|
mcpValid,
|
|
6665
7666
|
instructionValid,
|
|
7667
|
+
skillValid,
|
|
6666
7668
|
commandResolvable,
|
|
6667
7669
|
issues,
|
|
6668
7670
|
recommendations
|
|
@@ -6676,23 +7678,23 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6676
7678
|
name = "Cursor";
|
|
6677
7679
|
resolveConfigPath(context) {
|
|
6678
7680
|
if (context.scope === "project") {
|
|
6679
|
-
return
|
|
7681
|
+
return path13.join(context.projectDir, ".cursor", "mcp.json");
|
|
6680
7682
|
}
|
|
6681
7683
|
const home = context.globalDir || os.homedir();
|
|
6682
|
-
return
|
|
7684
|
+
return path13.join(home, ".cursor", "mcp.json");
|
|
6683
7685
|
}
|
|
6684
7686
|
resolveInstructionPath(context) {
|
|
6685
7687
|
if (context.scope === "project") {
|
|
6686
|
-
return
|
|
7688
|
+
return path13.join(context.projectDir, ".cursorrules");
|
|
6687
7689
|
}
|
|
6688
7690
|
const home = context.globalDir || os.homedir();
|
|
6689
|
-
return
|
|
7691
|
+
return path13.join(home, ".cursorrules");
|
|
6690
7692
|
}
|
|
6691
7693
|
async detect(context) {
|
|
6692
7694
|
const reasons = [];
|
|
6693
|
-
const dotCursor =
|
|
6694
|
-
const dotCursorrules =
|
|
6695
|
-
const mcpConfig =
|
|
7695
|
+
const dotCursor = path13.join(context.projectDir, ".cursor");
|
|
7696
|
+
const dotCursorrules = path13.join(context.projectDir, ".cursorrules");
|
|
7697
|
+
const mcpConfig = path13.join(context.projectDir, ".cursor", "mcp.json");
|
|
6696
7698
|
const hasDotCursor = await pathExists(dotCursor);
|
|
6697
7699
|
const hasCursorrules = await pathExists(dotCursorrules);
|
|
6698
7700
|
const hasMcp = await pathExists(mcpConfig);
|
|
@@ -6700,7 +7702,7 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6700
7702
|
if (hasCursorrules) reasons.push("Found .cursorrules file");
|
|
6701
7703
|
if (hasMcp) reasons.push("Found .cursor/mcp.json file");
|
|
6702
7704
|
const globalHome = context.globalDir || os.homedir();
|
|
6703
|
-
const globalCursor =
|
|
7705
|
+
const globalCursor = path13.join(globalHome, ".cursor");
|
|
6704
7706
|
const hasGlobalCursor = await pathExists(globalCursor);
|
|
6705
7707
|
if (hasGlobalCursor) reasons.push(`Found global directory at ${globalCursor}`);
|
|
6706
7708
|
const hasExecutable = await isExecutableInPath("cursor");
|
|
@@ -6717,31 +7719,31 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6717
7719
|
};
|
|
6718
7720
|
|
|
6719
7721
|
// src/install/adapters/claude.ts
|
|
6720
|
-
import * as
|
|
7722
|
+
import * as path14 from "path";
|
|
6721
7723
|
import * as os2 from "os";
|
|
6722
7724
|
var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
6723
7725
|
id = "claude";
|
|
6724
7726
|
name = "Claude Code";
|
|
6725
7727
|
resolveConfigPath(context) {
|
|
6726
7728
|
if (context.scope === "project") {
|
|
6727
|
-
return
|
|
7729
|
+
return path14.join(context.projectDir, ".claude", "mcp.json");
|
|
6728
7730
|
}
|
|
6729
7731
|
const home = context.globalDir || os2.homedir();
|
|
6730
|
-
return
|
|
7732
|
+
return path14.join(home, ".claude", "mcp.json");
|
|
6731
7733
|
}
|
|
6732
7734
|
resolveInstructionPath(context) {
|
|
6733
7735
|
if (context.scope === "project") {
|
|
6734
|
-
return
|
|
7736
|
+
return path14.join(context.projectDir, "CLAUDE.md");
|
|
6735
7737
|
}
|
|
6736
7738
|
const home = context.globalDir || os2.homedir();
|
|
6737
|
-
return
|
|
7739
|
+
return path14.join(home, ".claude", "CLAUDE.md");
|
|
6738
7740
|
}
|
|
6739
7741
|
async detect(context) {
|
|
6740
7742
|
const reasons = [];
|
|
6741
|
-
const dotClaude =
|
|
6742
|
-
const claudeMd =
|
|
6743
|
-
const claudeJson =
|
|
6744
|
-
const mcpConfig =
|
|
7743
|
+
const dotClaude = path14.join(context.projectDir, ".claude");
|
|
7744
|
+
const claudeMd = path14.join(context.projectDir, "CLAUDE.md");
|
|
7745
|
+
const claudeJson = path14.join(context.projectDir, "claude.json");
|
|
7746
|
+
const mcpConfig = path14.join(context.projectDir, ".claude", "mcp.json");
|
|
6745
7747
|
const hasDotClaude = await pathExists(dotClaude);
|
|
6746
7748
|
const hasClaudeMd = await pathExists(claudeMd);
|
|
6747
7749
|
const hasClaudeJson = await pathExists(claudeJson);
|
|
@@ -6751,7 +7753,7 @@ var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
|
6751
7753
|
if (hasClaudeJson) reasons.push("Found claude.json file");
|
|
6752
7754
|
if (hasMcp) reasons.push("Found .claude/mcp.json file");
|
|
6753
7755
|
const globalHome = context.globalDir || os2.homedir();
|
|
6754
|
-
const globalClaude =
|
|
7756
|
+
const globalClaude = path14.join(globalHome, ".claude");
|
|
6755
7757
|
const hasGlobalClaude = await pathExists(globalClaude);
|
|
6756
7758
|
if (hasGlobalClaude) reasons.push(`Found global directory at ${globalClaude}`);
|
|
6757
7759
|
const hasExecutable = await isExecutableInPath("claude");
|
|
@@ -6768,31 +7770,31 @@ var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
|
6768
7770
|
};
|
|
6769
7771
|
|
|
6770
7772
|
// src/install/adapters/codex.ts
|
|
6771
|
-
import * as
|
|
7773
|
+
import * as path15 from "path";
|
|
6772
7774
|
import * as os3 from "os";
|
|
6773
7775
|
var CodexAdapter = class extends BaseAgentAdapter {
|
|
6774
7776
|
id = "codex";
|
|
6775
7777
|
name = "Codex";
|
|
6776
7778
|
resolveConfigPath(context) {
|
|
6777
7779
|
if (context.scope === "project") {
|
|
6778
|
-
return
|
|
7780
|
+
return path15.join(context.projectDir, ".codex", "mcp.json");
|
|
6779
7781
|
}
|
|
6780
7782
|
const home = context.globalDir || os3.homedir();
|
|
6781
|
-
return
|
|
7783
|
+
return path15.join(home, ".codex", "mcp.json");
|
|
6782
7784
|
}
|
|
6783
7785
|
resolveInstructionPath(context) {
|
|
6784
7786
|
if (context.scope === "project") {
|
|
6785
|
-
return
|
|
7787
|
+
return path15.join(context.projectDir, "CODEX.md");
|
|
6786
7788
|
}
|
|
6787
7789
|
const home = context.globalDir || os3.homedir();
|
|
6788
|
-
return
|
|
7790
|
+
return path15.join(home, ".codex", "instructions.md");
|
|
6789
7791
|
}
|
|
6790
7792
|
async detect(context) {
|
|
6791
7793
|
const reasons = [];
|
|
6792
|
-
const dotCodex =
|
|
6793
|
-
const codexMd =
|
|
6794
|
-
const codexJson =
|
|
6795
|
-
const mcpConfig =
|
|
7794
|
+
const dotCodex = path15.join(context.projectDir, ".codex");
|
|
7795
|
+
const codexMd = path15.join(context.projectDir, "CODEX.md");
|
|
7796
|
+
const codexJson = path15.join(context.projectDir, "codex.json");
|
|
7797
|
+
const mcpConfig = path15.join(context.projectDir, ".codex", "mcp.json");
|
|
6796
7798
|
const hasDotCodex = await pathExists(dotCodex);
|
|
6797
7799
|
const hasCodexMd = await pathExists(codexMd);
|
|
6798
7800
|
const hasCodexJson = await pathExists(codexJson);
|
|
@@ -6802,7 +7804,7 @@ var CodexAdapter = class extends BaseAgentAdapter {
|
|
|
6802
7804
|
if (hasCodexJson) reasons.push("Found codex.json file");
|
|
6803
7805
|
if (hasMcp) reasons.push("Found .codex/mcp.json file");
|
|
6804
7806
|
const globalHome = context.globalDir || os3.homedir();
|
|
6805
|
-
const globalCodex =
|
|
7807
|
+
const globalCodex = path15.join(globalHome, ".codex");
|
|
6806
7808
|
const hasGlobalCodex = await pathExists(globalCodex);
|
|
6807
7809
|
if (hasGlobalCodex) reasons.push(`Found global directory at ${globalCodex}`);
|
|
6808
7810
|
const hasExecutable = await isExecutableInPath("codex");
|
|
@@ -6819,31 +7821,31 @@ var CodexAdapter = class extends BaseAgentAdapter {
|
|
|
6819
7821
|
};
|
|
6820
7822
|
|
|
6821
7823
|
// src/install/adapters/gemini.ts
|
|
6822
|
-
import * as
|
|
7824
|
+
import * as path16 from "path";
|
|
6823
7825
|
import * as os4 from "os";
|
|
6824
7826
|
var GeminiAdapter = class extends BaseAgentAdapter {
|
|
6825
7827
|
id = "gemini";
|
|
6826
7828
|
name = "Gemini CLI";
|
|
6827
7829
|
resolveConfigPath(context) {
|
|
6828
7830
|
if (context.scope === "project") {
|
|
6829
|
-
return
|
|
7831
|
+
return path16.join(context.projectDir, ".gemini", "settings.json");
|
|
6830
7832
|
}
|
|
6831
7833
|
const home = context.globalDir || os4.homedir();
|
|
6832
|
-
return
|
|
7834
|
+
return path16.join(home, ".gemini", "settings.json");
|
|
6833
7835
|
}
|
|
6834
7836
|
resolveInstructionPath(context) {
|
|
6835
7837
|
if (context.scope === "project") {
|
|
6836
|
-
return
|
|
7838
|
+
return path16.join(context.projectDir, "GEMINI.md");
|
|
6837
7839
|
}
|
|
6838
7840
|
const home = context.globalDir || os4.homedir();
|
|
6839
|
-
return
|
|
7841
|
+
return path16.join(home, ".gemini", "GEMINI.md");
|
|
6840
7842
|
}
|
|
6841
7843
|
async detect(context) {
|
|
6842
7844
|
const reasons = [];
|
|
6843
|
-
const dotGemini =
|
|
6844
|
-
const geminiMd =
|
|
6845
|
-
const geminiJson =
|
|
6846
|
-
const settingsJson =
|
|
7845
|
+
const dotGemini = path16.join(context.projectDir, ".gemini");
|
|
7846
|
+
const geminiMd = path16.join(context.projectDir, "GEMINI.md");
|
|
7847
|
+
const geminiJson = path16.join(context.projectDir, "gemini.json");
|
|
7848
|
+
const settingsJson = path16.join(context.projectDir, ".gemini", "settings.json");
|
|
6847
7849
|
const hasDotGemini = await pathExists(dotGemini);
|
|
6848
7850
|
const hasGeminiMd = await pathExists(geminiMd);
|
|
6849
7851
|
const hasGeminiJson = await pathExists(geminiJson);
|
|
@@ -6853,7 +7855,7 @@ var GeminiAdapter = class extends BaseAgentAdapter {
|
|
|
6853
7855
|
if (hasGeminiJson) reasons.push("Found gemini.json file");
|
|
6854
7856
|
if (hasSettingsJson) reasons.push("Found .gemini/settings.json file");
|
|
6855
7857
|
const globalHome = context.globalDir || os4.homedir();
|
|
6856
|
-
const globalGemini =
|
|
7858
|
+
const globalGemini = path16.join(globalHome, ".gemini");
|
|
6857
7859
|
const hasGlobalGemini = await pathExists(globalGemini);
|
|
6858
7860
|
if (hasGlobalGemini) reasons.push(`Found global directory at ${globalGemini}`);
|
|
6859
7861
|
const hasExecutable = await isExecutableInPath("gemini");
|
|
@@ -6869,12 +7871,110 @@ var GeminiAdapter = class extends BaseAgentAdapter {
|
|
|
6869
7871
|
}
|
|
6870
7872
|
};
|
|
6871
7873
|
|
|
7874
|
+
// src/install/adapters/antigravity.ts
|
|
7875
|
+
import * as path17 from "path";
|
|
7876
|
+
import * as os5 from "os";
|
|
7877
|
+
import * as fs10 from "fs";
|
|
7878
|
+
function resolveAntigravityGlobalDir(context) {
|
|
7879
|
+
const home = context.globalDir || os5.homedir();
|
|
7880
|
+
if (path17.basename(home) === "config" && path17.basename(path17.dirname(home)) === ".gemini") {
|
|
7881
|
+
return home;
|
|
7882
|
+
}
|
|
7883
|
+
return path17.join(home, ".gemini", "config");
|
|
7884
|
+
}
|
|
7885
|
+
var AntigravityAdapter = class extends BaseAgentAdapter {
|
|
7886
|
+
id = "antigravity";
|
|
7887
|
+
name = "Antigravity";
|
|
7888
|
+
resolveConfigPath(context) {
|
|
7889
|
+
if (context.scope === "project") {
|
|
7890
|
+
const rootMcp = path17.join(context.projectDir, "mcp_config.json");
|
|
7891
|
+
if (fs10.existsSync(rootMcp)) {
|
|
7892
|
+
return rootMcp;
|
|
7893
|
+
}
|
|
7894
|
+
return path17.join(context.projectDir, ".agents", "mcp_config.json");
|
|
7895
|
+
}
|
|
7896
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7897
|
+
return path17.join(globalRoot, "mcp_config.json");
|
|
7898
|
+
}
|
|
7899
|
+
resolveSkillPath(context) {
|
|
7900
|
+
if (context.scope === "project") {
|
|
7901
|
+
return path17.join(context.projectDir, ".agents", "skills", "bilmem", "SKILL.md");
|
|
7902
|
+
}
|
|
7903
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7904
|
+
return path17.join(globalRoot, "skills", "bilmem", "SKILL.md");
|
|
7905
|
+
}
|
|
7906
|
+
resolveInstructionPath(context) {
|
|
7907
|
+
if (context.scope === "project") {
|
|
7908
|
+
const geminiMd = path17.join(context.projectDir, "GEMINI.md");
|
|
7909
|
+
const agentsMd = path17.join(context.projectDir, "AGENTS.md");
|
|
7910
|
+
const rulesBilmem = path17.join(context.projectDir, ".agents", "rules", "bilmem.md");
|
|
7911
|
+
if (fs10.existsSync(rulesBilmem)) {
|
|
7912
|
+
return rulesBilmem;
|
|
7913
|
+
}
|
|
7914
|
+
if (fs10.existsSync(geminiMd)) {
|
|
7915
|
+
return geminiMd;
|
|
7916
|
+
}
|
|
7917
|
+
if (fs10.existsSync(agentsMd)) {
|
|
7918
|
+
return agentsMd;
|
|
7919
|
+
}
|
|
7920
|
+
return path17.join(context.projectDir, ".agents", "rules", "bilmem.md");
|
|
7921
|
+
}
|
|
7922
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7923
|
+
return path17.join(globalRoot, "rules", "bilmem.md");
|
|
7924
|
+
}
|
|
7925
|
+
async detect(context) {
|
|
7926
|
+
const reasons = [];
|
|
7927
|
+
const dotAgents = path17.join(context.projectDir, ".agents");
|
|
7928
|
+
const skillsDir = path17.join(context.projectDir, ".agents", "skills");
|
|
7929
|
+
const agentsMcp = path17.join(context.projectDir, ".agents", "mcp_config.json");
|
|
7930
|
+
const rootMcp = path17.join(context.projectDir, "mcp_config.json");
|
|
7931
|
+
const geminiMd = path17.join(context.projectDir, "GEMINI.md");
|
|
7932
|
+
const agentsMd = path17.join(context.projectDir, "AGENTS.md");
|
|
7933
|
+
const hasDotAgents = await pathExists(dotAgents);
|
|
7934
|
+
const hasSkillsDir = await pathExists(skillsDir);
|
|
7935
|
+
const hasAgentsMcp = await pathExists(agentsMcp);
|
|
7936
|
+
const hasRootMcp = await pathExists(rootMcp);
|
|
7937
|
+
const hasGeminiMd = await pathExists(geminiMd);
|
|
7938
|
+
const hasAgentsMd = await pathExists(agentsMd);
|
|
7939
|
+
if (hasDotAgents) reasons.push("Found .agents/ directory");
|
|
7940
|
+
if (hasSkillsDir) reasons.push("Found .agents/skills/ directory");
|
|
7941
|
+
if (hasAgentsMcp) reasons.push("Found .agents/mcp_config.json");
|
|
7942
|
+
if (hasRootMcp) reasons.push("Found mcp_config.json file");
|
|
7943
|
+
if (hasGeminiMd) reasons.push("Found GEMINI.md rule file");
|
|
7944
|
+
if (hasAgentsMd) reasons.push("Found AGENTS.md rule file");
|
|
7945
|
+
const home = context.globalDir || os5.homedir();
|
|
7946
|
+
const globalConfig = resolveAntigravityGlobalDir(context);
|
|
7947
|
+
const globalIde = path17.join(home, ".gemini", "antigravity-ide");
|
|
7948
|
+
const globalCli = path17.join(home, ".gemini", "antigravity-cli");
|
|
7949
|
+
const globalAgy = path17.join(home, ".gemini", "antigravity");
|
|
7950
|
+
const hasGlobalConfig = await pathExists(globalConfig);
|
|
7951
|
+
const hasGlobalIde = await pathExists(globalIde);
|
|
7952
|
+
const hasGlobalCli = await pathExists(globalCli);
|
|
7953
|
+
const hasGlobalAgy = await pathExists(globalAgy);
|
|
7954
|
+
if (hasGlobalConfig) reasons.push(`Found Antigravity global config at ${globalConfig}`);
|
|
7955
|
+
if (hasGlobalIde) reasons.push(`Found Antigravity IDE at ${globalIde}`);
|
|
7956
|
+
if (hasGlobalCli) reasons.push(`Found Antigravity CLI at ${globalCli}`);
|
|
7957
|
+
if (hasGlobalAgy) reasons.push(`Found Antigravity directory at ${globalAgy}`);
|
|
7958
|
+
const hasAgyExec = await isExecutableInPath("agy");
|
|
7959
|
+
if (hasAgyExec) reasons.push('Found "agy" command in PATH');
|
|
7960
|
+
const detected = reasons.length > 0;
|
|
7961
|
+
const confidence = hasDotAgents || hasSkillsDir || hasAgentsMcp || hasRootMcp || hasGeminiMd || hasAgentsMd ? "high" : hasGlobalConfig || hasGlobalIde || hasGlobalCli || hasGlobalAgy || hasAgyExec ? "medium" : "low";
|
|
7962
|
+
return {
|
|
7963
|
+
detected,
|
|
7964
|
+
confidence: detected ? confidence : "low",
|
|
7965
|
+
reasons,
|
|
7966
|
+
supportedScopes: this.supportedScopes
|
|
7967
|
+
};
|
|
7968
|
+
}
|
|
7969
|
+
};
|
|
7970
|
+
|
|
6872
7971
|
// src/install/registry.ts
|
|
6873
7972
|
var adapters = [
|
|
6874
7973
|
new CursorAdapter(),
|
|
6875
7974
|
new ClaudeAdapter(),
|
|
6876
7975
|
new CodexAdapter(),
|
|
6877
|
-
new GeminiAdapter()
|
|
7976
|
+
new GeminiAdapter(),
|
|
7977
|
+
new AntigravityAdapter()
|
|
6878
7978
|
];
|
|
6879
7979
|
function getAllAdapters() {
|
|
6880
7980
|
return [...adapters];
|
|
@@ -6894,7 +7994,7 @@ async function detectAgents(context) {
|
|
|
6894
7994
|
}
|
|
6895
7995
|
|
|
6896
7996
|
// src/install/doctor.ts
|
|
6897
|
-
import * as
|
|
7997
|
+
import * as path18 from "path";
|
|
6898
7998
|
async function runDoctor(context) {
|
|
6899
7999
|
const storage = context.storage || new Storage(context.projectDir);
|
|
6900
8000
|
let isInit = false;
|
|
@@ -6986,6 +8086,26 @@ async function runDoctor(context) {
|
|
|
6986
8086
|
canFix = true;
|
|
6987
8087
|
}
|
|
6988
8088
|
}
|
|
8089
|
+
let skillStatus = "none";
|
|
8090
|
+
let skillDetails;
|
|
8091
|
+
if (state.skillPath) {
|
|
8092
|
+
if (state.skillInstalled) {
|
|
8093
|
+
if (state.skillVersion === CURRENT_INTEGRATION_VERSION) {
|
|
8094
|
+
skillStatus = "ok";
|
|
8095
|
+
} else {
|
|
8096
|
+
skillStatus = "outdated";
|
|
8097
|
+
skillDetails = `Version ${state.skillVersion || "legacy"} (current: ${CURRENT_INTEGRATION_VERSION})`;
|
|
8098
|
+
issuesCount++;
|
|
8099
|
+
canFix = true;
|
|
8100
|
+
}
|
|
8101
|
+
} else {
|
|
8102
|
+
skillStatus = "missing";
|
|
8103
|
+
if (state.detected) {
|
|
8104
|
+
issuesCount++;
|
|
8105
|
+
canFix = true;
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
6989
8109
|
let cmdStatus = "ok";
|
|
6990
8110
|
let cmdDetails;
|
|
6991
8111
|
if (!commandResolvable) {
|
|
@@ -6999,9 +8119,11 @@ async function runDoctor(context) {
|
|
|
6999
8119
|
scope: context.scope,
|
|
7000
8120
|
mcp: { status: mcpStatus, details: mcpDetails },
|
|
7001
8121
|
instructions: { status: instStatus, details: instDetails },
|
|
8122
|
+
skill: state.skillPath ? { status: skillStatus, details: skillDetails } : void 0,
|
|
7002
8123
|
command: { status: cmdStatus, details: cmdDetails },
|
|
7003
8124
|
configPath: state.configPath,
|
|
7004
|
-
instructionPath: state.instructionPath
|
|
8125
|
+
instructionPath: state.instructionPath,
|
|
8126
|
+
skillPath: state.skillPath
|
|
7005
8127
|
});
|
|
7006
8128
|
}
|
|
7007
8129
|
if (!core.storage.initialized) {
|
|
@@ -7021,18 +8143,19 @@ async function runDoctorFix(context, dryRun = false) {
|
|
|
7021
8143
|
const agentsFixed = {};
|
|
7022
8144
|
if (!await storage.isInitialized()) {
|
|
7023
8145
|
if (!dryRun) {
|
|
7024
|
-
await storage.init({ projectName:
|
|
8146
|
+
await storage.init({ projectName: path18.basename(context.projectDir) });
|
|
7025
8147
|
}
|
|
7026
8148
|
coreFixed.push("Initialized .bilmem/ storage repository");
|
|
7027
8149
|
}
|
|
7028
8150
|
const adapters2 = getAllAdapters();
|
|
7029
8151
|
for (const adapter of adapters2) {
|
|
7030
8152
|
const state = await adapter.inspect(context);
|
|
7031
|
-
const shouldFix = state.detected || state.mcpConfigured || state.instructionInstalled;
|
|
8153
|
+
const shouldFix = state.detected || state.mcpConfigured || state.instructionInstalled || Boolean(state.skillInstalled);
|
|
7032
8154
|
if (!shouldFix) continue;
|
|
7033
8155
|
const needsMcpFix = !state.mcpConfigured || !state.mcpCommandMatches;
|
|
7034
8156
|
const needsInstFix = !state.instructionInstalled || state.instructionVersion !== CURRENT_INTEGRATION_VERSION;
|
|
7035
|
-
|
|
8157
|
+
const needsSkillFix = Boolean(state.skillPath) && (!state.skillInstalled || state.skillVersion !== CURRENT_INTEGRATION_VERSION);
|
|
8158
|
+
if (needsMcpFix || needsInstFix || needsSkillFix) {
|
|
7036
8159
|
const fixResult = await adapter.install(context, {
|
|
7037
8160
|
scope: context.scope,
|
|
7038
8161
|
dryRun
|
|
@@ -7139,6 +8262,10 @@ function formatDoctorReport(report) {
|
|
|
7139
8262
|
lines.push(` MCP ${mcpMark}${agent.mcp.details ? ` (${agent.mcp.details})` : ""}`);
|
|
7140
8263
|
const instMark = agent.instructions.status === "ok" ? "\u2713" : agent.instructions.status === "outdated" ? "!" : agent.instructions.status === "missing" ? "\u2717" : "\u25CB";
|
|
7141
8264
|
lines.push(` Instructions ${instMark}${agent.instructions.details ? ` (${agent.instructions.details})` : ""}`);
|
|
8265
|
+
if (agent.skillPath) {
|
|
8266
|
+
const skillMark = agent.skill?.status === "ok" ? "\u2713" : agent.skill?.status === "outdated" ? "!" : agent.skill?.status === "missing" ? "\u2717" : "\u25CB";
|
|
8267
|
+
lines.push(` Skill ${skillMark}${agent.skill?.details ? ` (${agent.skill?.details})` : ""}`);
|
|
8268
|
+
}
|
|
7142
8269
|
const cmdMark = agent.command.status === "ok" ? "\u2713" : "!";
|
|
7143
8270
|
lines.push(` Connection ${cmdMark}${agent.command.details ? ` (${agent.command.details})` : ""}`);
|
|
7144
8271
|
}
|
|
@@ -7183,7 +8310,7 @@ function formatDoctorFixResult(fixed) {
|
|
|
7183
8310
|
|
|
7184
8311
|
// src/install/index.ts
|
|
7185
8312
|
function createInstallContext(options) {
|
|
7186
|
-
const projectDir =
|
|
8313
|
+
const projectDir = path19.resolve(options.projectDir || process.cwd());
|
|
7187
8314
|
const scope = options.global || options.scope === "global" ? "global" : "project";
|
|
7188
8315
|
return {
|
|
7189
8316
|
projectDir,
|
|
@@ -7204,7 +8331,7 @@ async function handleInstallCommand(target, options = {}) {
|
|
|
7204
8331
|
if (target) {
|
|
7205
8332
|
const adapter = getAdapter(target);
|
|
7206
8333
|
if (!adapter) {
|
|
7207
|
-
const valid = getAllAdapters().map((a) => a.id).join(", ");
|
|
8334
|
+
const valid = [...getAllAdapters().map((a) => a.id), "gemini"].join(", ");
|
|
7208
8335
|
throw new Error(`Unknown agent/editor target "${target}". Supported targets: ${valid}`);
|
|
7209
8336
|
}
|
|
7210
8337
|
const result = await adapter.install(context, installOptions);
|
|
@@ -7286,7 +8413,7 @@ async function handleUninstallCommand(target, options = {}) {
|
|
|
7286
8413
|
const context = createInstallContext(options);
|
|
7287
8414
|
const adapter = getAdapter(target);
|
|
7288
8415
|
if (!adapter) {
|
|
7289
|
-
const valid = getAllAdapters().map((a) => a.id).join(", ");
|
|
8416
|
+
const valid = [...getAllAdapters().map((a) => a.id), "gemini"].join(", ");
|
|
7290
8417
|
throw new Error(`Unknown agent/editor target "${target}". Supported targets: ${valid}`);
|
|
7291
8418
|
}
|
|
7292
8419
|
const uninstallOptions = {
|
|
@@ -7308,7 +8435,7 @@ async function handleDoctorCommand(options = {}) {
|
|
|
7308
8435
|
}
|
|
7309
8436
|
|
|
7310
8437
|
// src/runtime.ts
|
|
7311
|
-
import * as
|
|
8438
|
+
import * as path20 from "path";
|
|
7312
8439
|
var Bilmem = class {
|
|
7313
8440
|
rootDir;
|
|
7314
8441
|
storage;
|
|
@@ -7316,7 +8443,7 @@ var Bilmem = class {
|
|
|
7316
8443
|
retriever;
|
|
7317
8444
|
initialized = false;
|
|
7318
8445
|
constructor(options = {}) {
|
|
7319
|
-
this.rootDir = options.rootDir ?
|
|
8446
|
+
this.rootDir = options.rootDir ? path20.resolve(options.rootDir) : process.cwd();
|
|
7320
8447
|
this.storage = new Storage(this.rootDir);
|
|
7321
8448
|
this.index = new SearchIndex();
|
|
7322
8449
|
this.retriever = new Retriever(this.index, this.storage);
|
|
@@ -7491,6 +8618,15 @@ ${item.result.reason}`
|
|
|
7491
8618
|
await this.ensureInitialized();
|
|
7492
8619
|
return resolveEpistemicQuery(options, this.storage, this.retriever);
|
|
7493
8620
|
}
|
|
8621
|
+
/**
|
|
8622
|
+
* Preflight analysis:
|
|
8623
|
+
* Answers "What must I know before I start this work?"
|
|
8624
|
+
* Analyzes existing constraints, relevant lessons, stale knowledge, and important unknowns.
|
|
8625
|
+
*/
|
|
8626
|
+
async preflight(options) {
|
|
8627
|
+
await this.ensureInitialized();
|
|
8628
|
+
return executePreflight(options, this.storage, this.retriever, this.index);
|
|
8629
|
+
}
|
|
7494
8630
|
/**
|
|
7495
8631
|
* Check knowledge staleness based on changed source files.
|
|
7496
8632
|
*/
|
|
@@ -7629,6 +8765,7 @@ function createBilmem(options) {
|
|
|
7629
8765
|
return new Bilmem(options);
|
|
7630
8766
|
}
|
|
7631
8767
|
export {
|
|
8768
|
+
AntigravityAdapter,
|
|
7632
8769
|
BILMEM_END_MARKER,
|
|
7633
8770
|
BILMEM_START_MARKER,
|
|
7634
8771
|
BUILTIN_DETECTORS,
|
|
@@ -7663,6 +8800,7 @@ export {
|
|
|
7663
8800
|
checkKnowledgeListStaleness,
|
|
7664
8801
|
checkKnowledgeStaleness,
|
|
7665
8802
|
classifyKnowledgeHealth,
|
|
8803
|
+
classifyUnknown,
|
|
7666
8804
|
compact,
|
|
7667
8805
|
computeKnowledgeStats,
|
|
7668
8806
|
computeStats,
|
|
@@ -7688,8 +8826,10 @@ export {
|
|
|
7688
8826
|
evaluatePromotion,
|
|
7689
8827
|
evaluateRule,
|
|
7690
8828
|
evaluateRuleSet,
|
|
8829
|
+
executePreflight,
|
|
7691
8830
|
exportKnowledgeToYaml,
|
|
7692
8831
|
extractInstructionVersion,
|
|
8832
|
+
extractSkillVersion,
|
|
7693
8833
|
findRuleFiles,
|
|
7694
8834
|
formatDoctorFixResult,
|
|
7695
8835
|
formatDoctorReport,
|
|
@@ -7697,6 +8837,7 @@ export {
|
|
|
7697
8837
|
formatJevExplanation,
|
|
7698
8838
|
formatKnowledgeDiff,
|
|
7699
8839
|
formatKnowledgeStatsReport,
|
|
8840
|
+
formatPreflightReport,
|
|
7700
8841
|
formatStatsReport,
|
|
7701
8842
|
formatUninstallResult,
|
|
7702
8843
|
formatWhyExplanation,
|
|
@@ -7704,6 +8845,7 @@ export {
|
|
|
7704
8845
|
getAdapter,
|
|
7705
8846
|
getAgentInstructionText,
|
|
7706
8847
|
getAllAdapters,
|
|
8848
|
+
getBilmemSkillContent,
|
|
7707
8849
|
getEventsFilePath,
|
|
7708
8850
|
getIntegrationRecord,
|
|
7709
8851
|
getIntegrationsFilePath,
|
|
@@ -7712,8 +8854,10 @@ export {
|
|
|
7712
8854
|
handleUninstallCommand,
|
|
7713
8855
|
hasBeenInvestigated,
|
|
7714
8856
|
hasBilmemInstruction,
|
|
8857
|
+
hasBilmemSkill,
|
|
7715
8858
|
hashString,
|
|
7716
8859
|
importKnowledgeFromYaml,
|
|
8860
|
+
inferLikelyDomains,
|
|
7717
8861
|
isExecutableInPath,
|
|
7718
8862
|
isGitRepository,
|
|
7719
8863
|
isSourceFile,
|
|
@@ -7727,6 +8871,7 @@ export {
|
|
|
7727
8871
|
parseMarkdownFile,
|
|
7728
8872
|
parseRuleSetYaml,
|
|
7729
8873
|
pathExists,
|
|
8874
|
+
prioritizeQuestions,
|
|
7730
8875
|
processUserCorrection,
|
|
7731
8876
|
readMetricEvents,
|
|
7732
8877
|
reconcileKnowledge,
|
|
@@ -7737,6 +8882,7 @@ export {
|
|
|
7737
8882
|
removeIntegration,
|
|
7738
8883
|
removeMarkdownInstructionFile,
|
|
7739
8884
|
removeMcpJsonConfig,
|
|
8885
|
+
removeSkillFile,
|
|
7740
8886
|
rerankCandidates,
|
|
7741
8887
|
resetMetricEvents,
|
|
7742
8888
|
resolveEpistemicQuery,
|
|
@@ -7754,8 +8900,10 @@ export {
|
|
|
7754
8900
|
scanRepository,
|
|
7755
8901
|
stringifyRuleSetYaml,
|
|
7756
8902
|
tierToConfidence,
|
|
8903
|
+
tokenizeGoal,
|
|
7757
8904
|
updateMarkdownInstructionFile,
|
|
7758
8905
|
updateMcpJsonConfig,
|
|
8906
|
+
updateSkillFile,
|
|
7759
8907
|
updateUnknown,
|
|
7760
8908
|
validateRuleDefinition,
|
|
7761
8909
|
validateRuleSet,
|