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/cli/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
5
|
+
import * as path20 from "path";
|
|
6
|
+
import * as fs11 from "fs/promises";
|
|
7
|
+
import YAML5 from "yaml";
|
|
8
8
|
|
|
9
9
|
// src/storage/canonical.ts
|
|
10
10
|
import * as fs3 from "fs/promises";
|
|
@@ -1236,6 +1236,7 @@ function formatWhyHistory(knowledge) {
|
|
|
1236
1236
|
// src/core/resolver.ts
|
|
1237
1237
|
var RELATIVE_COSTS = {
|
|
1238
1238
|
USE_KNOWLEDGE: 1,
|
|
1239
|
+
REVALIDATE: 2,
|
|
1239
1240
|
INSPECT_TEST: 2,
|
|
1240
1241
|
INSPECT_FILE: 2,
|
|
1241
1242
|
INSPECT_SYMBOL: 2.5,
|
|
@@ -1576,8 +1577,700 @@ async function resolveEpistemicQuery(options, storage, retriever) {
|
|
|
1576
1577
|
};
|
|
1577
1578
|
}
|
|
1578
1579
|
|
|
1580
|
+
// src/core/preflight.ts
|
|
1581
|
+
import { execFile } from "child_process";
|
|
1582
|
+
import { promisify } from "util";
|
|
1583
|
+
import * as path4 from "path";
|
|
1584
|
+
import * as fs4 from "fs/promises";
|
|
1585
|
+
import YAML2 from "yaml";
|
|
1586
|
+
|
|
1587
|
+
// src/core/dependencies.ts
|
|
1588
|
+
function normalizeSourceKey(source) {
|
|
1589
|
+
return source.replace(/^(file|code|test|doc):/, "").trim().toLowerCase();
|
|
1590
|
+
}
|
|
1591
|
+
function checkKnowledgeStaleness(knowledge, changedSources) {
|
|
1592
|
+
const normChanged = new Set(changedSources.map(normalizeSourceKey));
|
|
1593
|
+
const allDeps = [
|
|
1594
|
+
...(knowledge.depends_on ?? []).map(normalizeSourceKey),
|
|
1595
|
+
...knowledge.evidence.map(normalizeSourceKey)
|
|
1596
|
+
];
|
|
1597
|
+
const changed = [];
|
|
1598
|
+
const unchanged = [];
|
|
1599
|
+
for (const dep of allDeps) {
|
|
1600
|
+
if (!dep) continue;
|
|
1601
|
+
const isChanged = normChanged.has(dep) || Array.from(normChanged).some((c) => dep.includes(c) || c.includes(dep));
|
|
1602
|
+
if (isChanged) {
|
|
1603
|
+
changed.push(dep);
|
|
1604
|
+
} else {
|
|
1605
|
+
unchanged.push(dep);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (changed.length > 0) {
|
|
1609
|
+
const reason = `Supporting dependency changed: ${changed.join(", ")}. Unchanged evidence: ${unchanged.join(", ") || "none"}`;
|
|
1610
|
+
return {
|
|
1611
|
+
knowledgeId: knowledge.id,
|
|
1612
|
+
affected: true,
|
|
1613
|
+
status: "needs_revalidation",
|
|
1614
|
+
changedSources: changed,
|
|
1615
|
+
unchangedSources: unchanged,
|
|
1616
|
+
reason
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
return {
|
|
1620
|
+
knowledgeId: knowledge.id,
|
|
1621
|
+
affected: false,
|
|
1622
|
+
status: knowledge.status,
|
|
1623
|
+
changedSources: [],
|
|
1624
|
+
unchangedSources: allDeps
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
function markStaleKnowledge(knowledgeList, changedSources) {
|
|
1628
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1629
|
+
let affectedCount = 0;
|
|
1630
|
+
const updatedKnowledge = [];
|
|
1631
|
+
for (const k of knowledgeList) {
|
|
1632
|
+
if (k.status === "superseded" || k.status === "invalid" || k.status === "invalidated") {
|
|
1633
|
+
updatedKnowledge.push(k);
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
const res = checkKnowledgeStaleness(k, changedSources);
|
|
1637
|
+
if (res.affected) {
|
|
1638
|
+
affectedCount++;
|
|
1639
|
+
const history = k.history ?? [];
|
|
1640
|
+
const updated = {
|
|
1641
|
+
...k,
|
|
1642
|
+
status: "needs_revalidation",
|
|
1643
|
+
updatedAt: now,
|
|
1644
|
+
history: [
|
|
1645
|
+
...history,
|
|
1646
|
+
{
|
|
1647
|
+
timestamp: now,
|
|
1648
|
+
status: "needs_revalidation",
|
|
1649
|
+
action: "source_changed",
|
|
1650
|
+
reason: res.reason
|
|
1651
|
+
}
|
|
1652
|
+
]
|
|
1653
|
+
};
|
|
1654
|
+
updatedKnowledge.push(updated);
|
|
1655
|
+
} else {
|
|
1656
|
+
updatedKnowledge.push(k);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return { updatedKnowledge, affectedCount };
|
|
1660
|
+
}
|
|
1661
|
+
function checkKnowledgeListStaleness(knowledgeList, changedSources) {
|
|
1662
|
+
const { updatedKnowledge, affectedCount } = markStaleKnowledge(knowledgeList, changedSources);
|
|
1663
|
+
const affectedKnowledge = updatedKnowledge.filter((k) => k.status === "needs_revalidation");
|
|
1664
|
+
return { affectedKnowledge, updatedKnowledge, affectedCount };
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/core/preflight.ts
|
|
1668
|
+
var execFileAsync = promisify(execFile);
|
|
1669
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1670
|
+
"add",
|
|
1671
|
+
"implement",
|
|
1672
|
+
"create",
|
|
1673
|
+
"support",
|
|
1674
|
+
"with",
|
|
1675
|
+
"the",
|
|
1676
|
+
"a",
|
|
1677
|
+
"an",
|
|
1678
|
+
"for",
|
|
1679
|
+
"in",
|
|
1680
|
+
"to",
|
|
1681
|
+
"and",
|
|
1682
|
+
"of",
|
|
1683
|
+
"on",
|
|
1684
|
+
"from",
|
|
1685
|
+
"using",
|
|
1686
|
+
"via",
|
|
1687
|
+
"into",
|
|
1688
|
+
"by",
|
|
1689
|
+
"our",
|
|
1690
|
+
"new",
|
|
1691
|
+
"use",
|
|
1692
|
+
"make",
|
|
1693
|
+
"allow",
|
|
1694
|
+
"enable",
|
|
1695
|
+
"provide",
|
|
1696
|
+
"setup",
|
|
1697
|
+
"set"
|
|
1698
|
+
]);
|
|
1699
|
+
var DOMAIN_KEYWORDS = {
|
|
1700
|
+
authentication: ["auth", "oauth", "login", "logout", "session", "jwt", "token", "credential", "sso", "password"],
|
|
1701
|
+
session: ["session", "cookie", "cookies", "expiry", "refresh", "token"],
|
|
1702
|
+
"user-identity": ["user", "account", "profile", "identity", "member", "role", "permission"],
|
|
1703
|
+
database: ["db", "database", "sql", "postgres", "sqlite", "migration", "schema", "table", "query"],
|
|
1704
|
+
storage: ["storage", "s3", "bucket", "file", "upload", "disk", "blob"],
|
|
1705
|
+
api: ["api", "endpoint", "http", "rest", "route", "controller", "middleware", "graphql"],
|
|
1706
|
+
caching: ["cache", "redis", "memcached", "ttl", "invalidation"],
|
|
1707
|
+
billing: ["payment", "stripe", "checkout", "billing", "subscription", "invoice", "credit"],
|
|
1708
|
+
"data-processing": ["import", "export", "csv", "bulk", "batch", "stream", "pipeline"]
|
|
1709
|
+
};
|
|
1710
|
+
function tokenizeGoal(goal) {
|
|
1711
|
+
return goal.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
1712
|
+
}
|
|
1713
|
+
function inferLikelyDomains(tokens, knowledgeTags) {
|
|
1714
|
+
const domains = /* @__PURE__ */ new Set();
|
|
1715
|
+
for (const token of tokens) {
|
|
1716
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
1717
|
+
if (keywords.includes(token) || token.includes(domain)) {
|
|
1718
|
+
domains.add(domain);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
for (const tag of knowledgeTags) {
|
|
1723
|
+
const norm = tag.toLowerCase().trim();
|
|
1724
|
+
if (DOMAIN_KEYWORDS[norm]) {
|
|
1725
|
+
domains.add(norm);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
return Array.from(domains);
|
|
1729
|
+
}
|
|
1730
|
+
function classifyUnknown(unknown, goalTokens, domainCues) {
|
|
1731
|
+
const text = `${unknown.id} ${unknown.statement || ""} ${unknown.question || unknown.next?.question || ""}`.toLowerCase();
|
|
1732
|
+
if (unknown.blocking === true) {
|
|
1733
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge|choice)\b/i.test(text);
|
|
1734
|
+
return {
|
|
1735
|
+
classification: "BLOCKING",
|
|
1736
|
+
isDecision: isDecision2 || true,
|
|
1737
|
+
reason: unknown.reason || "Blocking unknown identified for this goal"
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
if (unknown.blocking === false) {
|
|
1741
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|choice)\b/i.test(text);
|
|
1742
|
+
return {
|
|
1743
|
+
classification: "NON_BLOCKING",
|
|
1744
|
+
isDecision: isDecision2,
|
|
1745
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
const isNonBlocking = /\b(analytics|telemetry|metric|event name|logging level|log format|button color|styling|theme)\b/i.test(text);
|
|
1749
|
+
if (isNonBlocking) {
|
|
1750
|
+
return {
|
|
1751
|
+
classification: "NON_BLOCKING",
|
|
1752
|
+
isDecision: /\b(should|which name|choice)\b/i.test(text),
|
|
1753
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
const isDecision = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge)\b/i.test(text);
|
|
1757
|
+
const isBlockingCandidate = /\b(link|linking|collision|duplicate|merge|security|permission|storage-engine|protocol|auth-flow|credentials)\b/i.test(text);
|
|
1758
|
+
const overlapsGoal = goalTokens.some((t) => text.includes(t)) || domainCues.some((d) => text.includes(d));
|
|
1759
|
+
if (isBlockingCandidate && (overlapsGoal || unknown.reason === "UNRESOLVABLE_LOCALLY")) {
|
|
1760
|
+
return {
|
|
1761
|
+
classification: "BLOCKING",
|
|
1762
|
+
isDecision: true,
|
|
1763
|
+
reason: "Unresolved architectural or behavioral decision critical to goal implementation"
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
return {
|
|
1767
|
+
classification: "IMPORTANT",
|
|
1768
|
+
isDecision,
|
|
1769
|
+
reason: isDecision ? "Product or behavior decision requiring attention" : "Important implementation detail"
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
function prioritizeQuestions(rawQuestions, blockingUnknowns) {
|
|
1773
|
+
const consolidated = /* @__PURE__ */ new Map();
|
|
1774
|
+
for (const rq of rawQuestions) {
|
|
1775
|
+
const qText = rq.question.trim();
|
|
1776
|
+
if (!qText) continue;
|
|
1777
|
+
const topic = rq.targetTopic || qText.toLowerCase().replace(/[^a-z0-9]/g, " ").slice(0, 30);
|
|
1778
|
+
let expectedResolutionValue = 1;
|
|
1779
|
+
if (rq.isBlocking) expectedResolutionValue += 2;
|
|
1780
|
+
if (rq.outcomeScore && rq.outcomeScore > 0) expectedResolutionValue += 1;
|
|
1781
|
+
if (rq.outcomeScore && rq.outcomeScore < 0) expectedResolutionValue -= 0.5;
|
|
1782
|
+
const interactionCost = 1;
|
|
1783
|
+
const priorityScore = Math.max(0.1, expectedResolutionValue) / interactionCost;
|
|
1784
|
+
const existing = consolidated.get(topic);
|
|
1785
|
+
if (!existing || priorityScore > (existing.priorityScore ?? 0)) {
|
|
1786
|
+
consolidated.set(topic, {
|
|
1787
|
+
question: qText,
|
|
1788
|
+
reason: rq.reason,
|
|
1789
|
+
targetTopic: rq.targetTopic,
|
|
1790
|
+
expectedResolutionValue,
|
|
1791
|
+
interactionCost,
|
|
1792
|
+
priorityScore
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return Array.from(consolidated.values()).sort((a, b) => (b.priorityScore ?? 0) - (a.priorityScore ?? 0));
|
|
1797
|
+
}
|
|
1798
|
+
async function executePreflight(options, storage, retriever, index) {
|
|
1799
|
+
const goal = options.goal.trim();
|
|
1800
|
+
const explicitPositive = (options.positive ?? []).map((p) => p.trim().toLowerCase());
|
|
1801
|
+
const negative = (options.negative ?? []).map((n) => n.trim().toLowerCase());
|
|
1802
|
+
const scope = options.scope ?? "project";
|
|
1803
|
+
const goalTokens = tokenizeGoal(goal);
|
|
1804
|
+
const allKnowledge = await storage.getAllKnowledge();
|
|
1805
|
+
const allTags = allKnowledge.flatMap((k) => k.tags ?? []);
|
|
1806
|
+
const likelyDomains = inferLikelyDomains(goalTokens, allTags);
|
|
1807
|
+
const contextualPositive = Array.from(/* @__PURE__ */ new Set([...explicitPositive, ...likelyDomains, ...goalTokens]));
|
|
1808
|
+
let candidatePool = [];
|
|
1809
|
+
if (retriever) {
|
|
1810
|
+
const jevCandidates = await retriever.lookup({
|
|
1811
|
+
query: goal,
|
|
1812
|
+
positive: contextualPositive,
|
|
1813
|
+
negative,
|
|
1814
|
+
scope,
|
|
1815
|
+
limit: options.maxCandidates ?? 25,
|
|
1816
|
+
includeRejected: true
|
|
1817
|
+
});
|
|
1818
|
+
for (const r of jevCandidates) {
|
|
1819
|
+
if (r.knowledge) candidatePool.push(r.knowledge);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if (candidatePool.length === 0) {
|
|
1823
|
+
candidatePool = allKnowledge.filter((k) => k.status !== "invalid" && k.status !== "superseded");
|
|
1824
|
+
} else {
|
|
1825
|
+
for (const k of allKnowledge) {
|
|
1826
|
+
if (!candidatePool.some((c) => c.id === k.id) && k.status !== "invalid" && k.status !== "superseded") {
|
|
1827
|
+
const matchesToken = goalTokens.some((t) => k.id.toLowerCase().includes(t) || k.statement.toLowerCase().includes(t));
|
|
1828
|
+
const matchesCues = k.positive.some((p) => contextualPositive.includes(p));
|
|
1829
|
+
if (matchesToken || matchesCues) {
|
|
1830
|
+
candidatePool.push(k);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
const jevResults = rerankCandidates(candidatePool, {
|
|
1836
|
+
query: goal,
|
|
1837
|
+
positive: contextualPositive,
|
|
1838
|
+
negative,
|
|
1839
|
+
scope
|
|
1840
|
+
});
|
|
1841
|
+
const matchingItems = [];
|
|
1842
|
+
const rejectedItems = [];
|
|
1843
|
+
for (const res of jevResults) {
|
|
1844
|
+
if (res.status === "REJECT") {
|
|
1845
|
+
const reason = res.explanation.negativeConflicts.length > 0 ? `Inapplicable: ${res.explanation.negativeConflicts.join(", ")} conflicts with current context` : res.explanation.reasons[0] || "Context mismatch";
|
|
1846
|
+
rejectedItems.push({ id: res.knowledge.id, reason });
|
|
1847
|
+
} else {
|
|
1848
|
+
matchingItems.push(res.knowledge);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (options.deep) {
|
|
1852
|
+
try {
|
|
1853
|
+
const { stdout: diffOutput } = await execFileAsync(
|
|
1854
|
+
"git",
|
|
1855
|
+
["diff", "--name-only", "HEAD~1"],
|
|
1856
|
+
{ cwd: storage.getBaseDir() ? path4.dirname(storage.getBaseDir()) : process.cwd() }
|
|
1857
|
+
);
|
|
1858
|
+
const changedFiles = diffOutput.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1859
|
+
if (changedFiles.length > 0) {
|
|
1860
|
+
const staleness = checkKnowledgeListStaleness(matchingItems, changedFiles);
|
|
1861
|
+
for (const affected of staleness.affectedKnowledge) {
|
|
1862
|
+
const matchIdx = matchingItems.findIndex((m) => m.id === affected.id);
|
|
1863
|
+
if (matchIdx >= 0) {
|
|
1864
|
+
matchingItems[matchIdx] = affected;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
} catch {
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
const constraints = [];
|
|
1872
|
+
const lessons = [];
|
|
1873
|
+
const pastFailures = [];
|
|
1874
|
+
const stale = [];
|
|
1875
|
+
const known = [];
|
|
1876
|
+
let conflictedCount = 0;
|
|
1877
|
+
const CONSTRAINT_PATTERN = /\b(must|must not|never|only|cannot|require|required|boundary|strictly|prohibited|forbidden)\b/i;
|
|
1878
|
+
const FAILURE_PATTERN = /(fail|bug|error|crash|duplicate write|race condition|pressure|leak|revert|timeout)/i;
|
|
1879
|
+
const rootDir = storage.getBaseDir() ? path4.dirname(storage.getBaseDir()) : process.cwd();
|
|
1880
|
+
for (const k of matchingItems) {
|
|
1881
|
+
const matchesContradiction = k.status === "contradicted" || k.contradicts && k.contradicts.some((cid) => matchingItems.some((m) => m.id === cid));
|
|
1882
|
+
if (matchesContradiction) {
|
|
1883
|
+
conflictedCount++;
|
|
1884
|
+
}
|
|
1885
|
+
let isStale = k.status === "needs_revalidation" || k.status === "stale";
|
|
1886
|
+
let staleReason = k.history?.[k.history.length - 1]?.reason || "Supporting source changed after last validation";
|
|
1887
|
+
const changedDeps = [];
|
|
1888
|
+
const candidateDeps = [...k.depends_on ?? [], ...k.evidence ?? []];
|
|
1889
|
+
const timestampStr = k.updated_at ?? k.updatedAt ?? k.createdAt;
|
|
1890
|
+
const knowledgeTime = new Date(timestampStr).getTime();
|
|
1891
|
+
for (const dep of candidateDeps) {
|
|
1892
|
+
const cleanPath = dep.replace(/^(file|code|test|doc):/, "").trim();
|
|
1893
|
+
if (!cleanPath) continue;
|
|
1894
|
+
const absPath = path4.isAbsolute(cleanPath) ? cleanPath : path4.resolve(rootDir, cleanPath);
|
|
1895
|
+
try {
|
|
1896
|
+
const stat5 = await fs4.stat(absPath);
|
|
1897
|
+
if (stat5.mtimeMs > knowledgeTime + 500) {
|
|
1898
|
+
isStale = true;
|
|
1899
|
+
changedDeps.push(cleanPath);
|
|
1900
|
+
staleReason = `Supporting source changed after last validation (${cleanPath})`;
|
|
1901
|
+
}
|
|
1902
|
+
} catch {
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (isStale) {
|
|
1906
|
+
stale.push({
|
|
1907
|
+
id: k.id,
|
|
1908
|
+
statement: k.statement,
|
|
1909
|
+
reason: staleReason,
|
|
1910
|
+
changedSources: changedDeps.length > 0 ? changedDeps : k.depends_on
|
|
1911
|
+
});
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
const isConstraint = k.type === "rule" || k.type === "decision" || k.type === "convention" || CONSTRAINT_PATTERN.test(k.statement);
|
|
1915
|
+
if (isConstraint) {
|
|
1916
|
+
constraints.push({
|
|
1917
|
+
id: k.id,
|
|
1918
|
+
statement: k.statement,
|
|
1919
|
+
type: k.type,
|
|
1920
|
+
confidence: k.confidence,
|
|
1921
|
+
confidenceTier: k.confidenceTier,
|
|
1922
|
+
source: k.evidence?.[0]
|
|
1923
|
+
});
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
const isFailure = k.type === "failure" || k.statement.startsWith("[FAILURE]") || k.statement.startsWith("[REVERT]") || FAILURE_PATTERN.test(k.statement) || k.metrics.failed_outcomes > 0;
|
|
1927
|
+
if (isFailure) {
|
|
1928
|
+
pastFailures.push({
|
|
1929
|
+
id: k.id,
|
|
1930
|
+
statement: k.statement,
|
|
1931
|
+
context: k.context || {
|
|
1932
|
+
positive: k.positive,
|
|
1933
|
+
negative: k.negative
|
|
1934
|
+
},
|
|
1935
|
+
failure: k.statement,
|
|
1936
|
+
lesson: k.lesson || k.reasoning || k.resolves?.[0],
|
|
1937
|
+
evidence: k.evidence
|
|
1938
|
+
});
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
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:");
|
|
1942
|
+
if (isLesson) {
|
|
1943
|
+
lessons.push({
|
|
1944
|
+
id: k.id,
|
|
1945
|
+
statement: k.statement,
|
|
1946
|
+
confidence: k.confidence,
|
|
1947
|
+
domain: k.tags?.[0],
|
|
1948
|
+
evidence: k.evidence
|
|
1949
|
+
});
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
if (k.status === "active" || k.stage === "active" || k.stage === "probation" || !k.status) {
|
|
1953
|
+
known.push({
|
|
1954
|
+
id: k.id,
|
|
1955
|
+
statement: k.statement,
|
|
1956
|
+
confidence: k.confidence,
|
|
1957
|
+
confidenceTier: k.confidenceTier,
|
|
1958
|
+
stage: k.stage
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
const unknownsFromStorage = await storage.getAllUnknowns();
|
|
1963
|
+
const unknownKnowledge = allKnowledge.filter((k) => k.type === "unknown");
|
|
1964
|
+
const unifiedUnknowns = [];
|
|
1965
|
+
const seenUnknownIds = /* @__PURE__ */ new Set();
|
|
1966
|
+
for (const u of unknownsFromStorage) {
|
|
1967
|
+
seenUnknownIds.add(u.id);
|
|
1968
|
+
unifiedUnknowns.push({
|
|
1969
|
+
id: u.id,
|
|
1970
|
+
statement: u.statement || u.id,
|
|
1971
|
+
reason: u.reason,
|
|
1972
|
+
question: u.question || u.next?.question,
|
|
1973
|
+
next: u.next,
|
|
1974
|
+
blocking: u.blocking,
|
|
1975
|
+
positive: u.positive,
|
|
1976
|
+
negative: u.negative,
|
|
1977
|
+
checked: u.checked
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
for (const k of unknownKnowledge) {
|
|
1981
|
+
if (!seenUnknownIds.has(k.id)) {
|
|
1982
|
+
seenUnknownIds.add(k.id);
|
|
1983
|
+
unifiedUnknowns.push({
|
|
1984
|
+
id: k.id,
|
|
1985
|
+
statement: k.statement,
|
|
1986
|
+
reason: k.history?.[0]?.reason,
|
|
1987
|
+
question: k.question || (k.statement.includes("?") ? k.statement : void 0),
|
|
1988
|
+
next: {
|
|
1989
|
+
type: "ask",
|
|
1990
|
+
target: k.id,
|
|
1991
|
+
question: k.question || k.statement
|
|
1992
|
+
},
|
|
1993
|
+
blocking: k.blocking,
|
|
1994
|
+
positive: k.positive,
|
|
1995
|
+
negative: k.negative,
|
|
1996
|
+
checked: k.depends_on || k.evidence
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
const unknowns = [];
|
|
2001
|
+
const missing = [];
|
|
2002
|
+
const rawQuestions = [];
|
|
2003
|
+
for (const u of unifiedUnknowns) {
|
|
2004
|
+
const text = `${u.id} ${u.statement || ""} ${u.question || ""}`.toLowerCase();
|
|
2005
|
+
const overlaps = goalTokens.some((t) => text.includes(t)) || likelyDomains.some((d) => text.includes(d)) || u.positive && u.positive.some((p) => contextualPositive.includes(p));
|
|
2006
|
+
if (u.negative && u.negative.some((n) => contextualPositive.includes(n))) {
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
if (overlaps) {
|
|
2010
|
+
const { classification, isDecision, reason } = classifyUnknown(u, goalTokens, likelyDomains);
|
|
2011
|
+
const isBlocking = classification === "BLOCKING";
|
|
2012
|
+
unknowns.push({
|
|
2013
|
+
id: u.id,
|
|
2014
|
+
statement: u.statement,
|
|
2015
|
+
question: u.question || u.statement,
|
|
2016
|
+
classification,
|
|
2017
|
+
blocking: isBlocking,
|
|
2018
|
+
reason
|
|
2019
|
+
});
|
|
2020
|
+
if (isDecision) {
|
|
2021
|
+
missing.push({
|
|
2022
|
+
type: "decision",
|
|
2023
|
+
subject: u.id,
|
|
2024
|
+
question: u.question || u.statement,
|
|
2025
|
+
reason
|
|
2026
|
+
});
|
|
2027
|
+
} else {
|
|
2028
|
+
missing.push({
|
|
2029
|
+
type: "evidence",
|
|
2030
|
+
subject: u.id,
|
|
2031
|
+
question: u.question || u.statement,
|
|
2032
|
+
reason
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
const qText = u.question || (u.statement && u.statement.includes("?") ? u.statement : void 0);
|
|
2036
|
+
if (qText) {
|
|
2037
|
+
rawQuestions.push({
|
|
2038
|
+
question: qText,
|
|
2039
|
+
reason: `Unknown: ${u.id} (${reason})`,
|
|
2040
|
+
targetTopic: u.id,
|
|
2041
|
+
isBlocking
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const questionKnowledge = allKnowledge.filter((k) => k.type === "question");
|
|
2047
|
+
for (const qk of questionKnowledge) {
|
|
2048
|
+
const matchesCues = qk.positive.some((p) => contextualPositive.includes(p));
|
|
2049
|
+
const conflicts = qk.negative.some((n) => negative.includes(n));
|
|
2050
|
+
if (matchesCues && !conflicts && qk.question) {
|
|
2051
|
+
rawQuestions.push({
|
|
2052
|
+
question: qk.question,
|
|
2053
|
+
reason: `Learned question for [${qk.positive.join(", ")}] uncertainty`,
|
|
2054
|
+
targetTopic: qk.resolves?.[0] || qk.id,
|
|
2055
|
+
outcomeScore: (qk.metrics?.successful_outcomes ?? 0) - (qk.metrics?.failed_outcomes ?? 0)
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const questions = prioritizeQuestions(rawQuestions, unknowns);
|
|
2060
|
+
const receipts = await storage.getAllReceipts();
|
|
2061
|
+
const alreadyInvestigated = [];
|
|
2062
|
+
const investigatedSources = /* @__PURE__ */ new Set();
|
|
2063
|
+
for (const r of receipts) {
|
|
2064
|
+
const qTokens = r.question.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2065
|
+
const overlapsGoal = qTokens.some((t) => goalTokens.includes(t));
|
|
2066
|
+
const overlapsCues = r.positive && r.positive.some((p) => contextualPositive.includes(p));
|
|
2067
|
+
if (overlapsGoal || overlapsCues) {
|
|
2068
|
+
for (const checked of r.checked) {
|
|
2069
|
+
investigatedSources.add(checked.toLowerCase());
|
|
2070
|
+
alreadyInvestigated.push({
|
|
2071
|
+
source: checked,
|
|
2072
|
+
result: r.findings[0] || r.rejected[0]?.reason || "Did not establish requested behavior",
|
|
2073
|
+
nextSuggestion: "ASK_USER"
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
let readiness = "READY";
|
|
2079
|
+
const totalRelevantKnowledge = known.length + constraints.length + lessons.length + pastFailures.length + stale.length;
|
|
2080
|
+
const hasBlockingUnknown = unknowns.some((u) => u.blocking);
|
|
2081
|
+
const hasMissingDecision = missing.some((m) => m.type === "decision");
|
|
2082
|
+
if (totalRelevantKnowledge === 0 && unknowns.length === 0) {
|
|
2083
|
+
readiness = "INSUFFICIENT_KNOWLEDGE";
|
|
2084
|
+
} else if (conflictedCount > 0) {
|
|
2085
|
+
readiness = "CONFLICTED";
|
|
2086
|
+
} else if (hasBlockingUnknown || hasMissingDecision) {
|
|
2087
|
+
readiness = "NEEDS_CLARIFICATION";
|
|
2088
|
+
} else if (stale.length > 0) {
|
|
2089
|
+
readiness = "NEEDS_REVALIDATION";
|
|
2090
|
+
} else {
|
|
2091
|
+
readiness = "READY";
|
|
2092
|
+
}
|
|
2093
|
+
const next = [];
|
|
2094
|
+
for (const m of missing) {
|
|
2095
|
+
if (m.type === "decision") {
|
|
2096
|
+
next.push({
|
|
2097
|
+
type: "ASK_USER",
|
|
2098
|
+
target: m.subject,
|
|
2099
|
+
reason: m.question || m.reason,
|
|
2100
|
+
relativeCost: 1,
|
|
2101
|
+
expectedInfoGain: 0.95
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
for (const s of stale) {
|
|
2106
|
+
next.push({
|
|
2107
|
+
type: "REVALIDATE",
|
|
2108
|
+
target: s.id,
|
|
2109
|
+
reason: s.reason,
|
|
2110
|
+
relativeCost: 2,
|
|
2111
|
+
expectedInfoGain: 0.85
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
for (const m of missing) {
|
|
2115
|
+
if (m.type === "evidence") {
|
|
2116
|
+
const wasChecked = investigatedSources.has(m.subject.toLowerCase()) || alreadyInvestigated.length > 0;
|
|
2117
|
+
if (wasChecked) {
|
|
2118
|
+
if (!next.some((n) => n.type === "ASK_USER")) {
|
|
2119
|
+
next.push({
|
|
2120
|
+
type: "ASK_USER",
|
|
2121
|
+
target: m.subject,
|
|
2122
|
+
reason: `Already investigated (${Array.from(investigatedSources).join(", ")}); did not establish requested behavior. Clarification required.`,
|
|
2123
|
+
relativeCost: 1,
|
|
2124
|
+
expectedInfoGain: 0.9
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
} else {
|
|
2128
|
+
const isFilePath = m.subject.includes("/") || m.subject.includes(".");
|
|
2129
|
+
next.push({
|
|
2130
|
+
type: isFilePath ? "INSPECT_FILE" : "SEARCH_REPOSITORY",
|
|
2131
|
+
target: m.subject,
|
|
2132
|
+
reason: m.reason,
|
|
2133
|
+
relativeCost: isFilePath ? 2 : 2.5,
|
|
2134
|
+
expectedInfoGain: 0.8
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
if (alreadyInvestigated.length > 0 && !next.some((n) => n.type === "ASK_USER")) {
|
|
2140
|
+
next.push({
|
|
2141
|
+
type: "ASK_USER",
|
|
2142
|
+
target: alreadyInvestigated[0].source,
|
|
2143
|
+
reason: `Already investigated ${alreadyInvestigated[0].source}; clarification required`,
|
|
2144
|
+
relativeCost: 1,
|
|
2145
|
+
expectedInfoGain: 0.9
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
if (next.length === 0 && known.length > 0) {
|
|
2149
|
+
next.push({
|
|
2150
|
+
type: "USE_KNOWLEDGE",
|
|
2151
|
+
target: known[0].id,
|
|
2152
|
+
reason: "Proceed with implementation using verified knowledge and constraints",
|
|
2153
|
+
relativeCost: 1,
|
|
2154
|
+
expectedInfoGain: 1
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
const knowledgeImpact = {
|
|
2158
|
+
relevantKnowledge: totalRelevantKnowledge,
|
|
2159
|
+
validated: known.filter((k) => k.confidenceTier === "high").length + constraints.length,
|
|
2160
|
+
needsRevalidation: stale.length,
|
|
2161
|
+
conflicted: conflictedCount,
|
|
2162
|
+
blockingUnknowns: unknowns.filter((u) => u.blocking).length,
|
|
2163
|
+
relevantFailures: pastFailures.length
|
|
2164
|
+
};
|
|
2165
|
+
const similarWork = [];
|
|
2166
|
+
for (const l of lessons) {
|
|
2167
|
+
similarWork.push({
|
|
2168
|
+
topic: l.domain || l.id,
|
|
2169
|
+
lesson: l.statement
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
for (const f of pastFailures) {
|
|
2173
|
+
similarWork.push({
|
|
2174
|
+
topic: f.id,
|
|
2175
|
+
failure: f.statement
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
await storage.appendMetricEvent({
|
|
2179
|
+
type: "preflight",
|
|
2180
|
+
goal,
|
|
2181
|
+
readiness,
|
|
2182
|
+
blockingUnknownsCount: knowledgeImpact.blockingUnknowns,
|
|
2183
|
+
staleKnowledgeCount: stale.length,
|
|
2184
|
+
pastFailuresCount: pastFailures.length,
|
|
2185
|
+
questionsCount: questions.length,
|
|
2186
|
+
repeatedChecksAvoidedCount: alreadyInvestigated.length,
|
|
2187
|
+
timestamp: Date.now()
|
|
2188
|
+
});
|
|
2189
|
+
return {
|
|
2190
|
+
goal,
|
|
2191
|
+
readiness,
|
|
2192
|
+
known,
|
|
2193
|
+
constraints,
|
|
2194
|
+
lessons,
|
|
2195
|
+
past_failures: pastFailures,
|
|
2196
|
+
unknowns,
|
|
2197
|
+
stale,
|
|
2198
|
+
missing,
|
|
2199
|
+
questions,
|
|
2200
|
+
likely_domains: likelyDomains,
|
|
2201
|
+
similar_work: similarWork.length > 0 ? similarWork.slice(0, 5) : void 0,
|
|
2202
|
+
already_investigated: alreadyInvestigated.length > 0 ? alreadyInvestigated : void 0,
|
|
2203
|
+
knowledge_impact: knowledgeImpact,
|
|
2204
|
+
next
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function formatPreflightReport(result) {
|
|
2208
|
+
const plainObject = {
|
|
2209
|
+
goal: result.goal,
|
|
2210
|
+
readiness: result.readiness
|
|
2211
|
+
};
|
|
2212
|
+
if (result.known.length > 0) {
|
|
2213
|
+
plainObject.known = result.known.map((k) => ({
|
|
2214
|
+
id: k.id,
|
|
2215
|
+
statement: k.statement,
|
|
2216
|
+
confidence: k.confidenceTier || (typeof k.confidence === "number" ? k.confidence >= 0.9 ? "high" : k.confidence >= 0.7 ? "medium" : "low" : k.confidence)
|
|
2217
|
+
}));
|
|
2218
|
+
}
|
|
2219
|
+
if (result.constraints.length > 0) {
|
|
2220
|
+
plainObject.constraints = result.constraints.map((c) => ({
|
|
2221
|
+
id: c.id,
|
|
2222
|
+
statement: c.statement
|
|
2223
|
+
}));
|
|
2224
|
+
}
|
|
2225
|
+
if (result.lessons.length > 0) {
|
|
2226
|
+
plainObject.lessons = result.lessons.map((l) => ({
|
|
2227
|
+
id: l.id,
|
|
2228
|
+
statement: l.statement
|
|
2229
|
+
}));
|
|
2230
|
+
}
|
|
2231
|
+
if (result.past_failures.length > 0) {
|
|
2232
|
+
plainObject.past_failures = result.past_failures.map((f) => ({
|
|
2233
|
+
id: f.id,
|
|
2234
|
+
statement: f.statement
|
|
2235
|
+
}));
|
|
2236
|
+
}
|
|
2237
|
+
if (result.unknowns.length > 0) {
|
|
2238
|
+
plainObject.unknowns = result.unknowns.map((u) => ({
|
|
2239
|
+
id: u.id,
|
|
2240
|
+
question: u.question || u.statement,
|
|
2241
|
+
blocking: u.blocking
|
|
2242
|
+
}));
|
|
2243
|
+
}
|
|
2244
|
+
if (result.stale.length > 0) {
|
|
2245
|
+
plainObject.stale = result.stale.map((s) => ({
|
|
2246
|
+
id: s.id,
|
|
2247
|
+
reason: s.reason
|
|
2248
|
+
}));
|
|
2249
|
+
}
|
|
2250
|
+
if (result.questions.length > 0) {
|
|
2251
|
+
plainObject.questions = result.questions.map((q) => ({
|
|
2252
|
+
question: q.question,
|
|
2253
|
+
reason: q.reason
|
|
2254
|
+
}));
|
|
2255
|
+
}
|
|
2256
|
+
if (result.likely_domains.length > 0) {
|
|
2257
|
+
plainObject.likely_domains = result.likely_domains;
|
|
2258
|
+
}
|
|
2259
|
+
if (result.already_investigated && result.already_investigated.length > 0) {
|
|
2260
|
+
plainObject.already_investigated = result.already_investigated;
|
|
2261
|
+
}
|
|
2262
|
+
if (result.next.length > 0) {
|
|
2263
|
+
plainObject.next = result.next.map((n) => ({
|
|
2264
|
+
type: n.type,
|
|
2265
|
+
target: n.target,
|
|
2266
|
+
reason: n.reason
|
|
2267
|
+
}));
|
|
2268
|
+
}
|
|
2269
|
+
return YAML2.stringify(plainObject);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
1579
2272
|
// src/scanner/engine.ts
|
|
1580
|
-
import * as
|
|
2273
|
+
import * as path8 from "path";
|
|
1581
2274
|
|
|
1582
2275
|
// src/core/knowledge.ts
|
|
1583
2276
|
import { createHash } from "crypto";
|
|
@@ -1725,7 +2418,7 @@ function createKnowledge(params) {
|
|
|
1725
2418
|
type: params.type ?? "lesson",
|
|
1726
2419
|
status,
|
|
1727
2420
|
stage: initialStage,
|
|
1728
|
-
statement: params.statement.trim(),
|
|
2421
|
+
statement: (params.statement ?? params.question ?? params.id).trim(),
|
|
1729
2422
|
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
1730
2423
|
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
1731
2424
|
relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
|
|
@@ -1755,6 +2448,11 @@ function createKnowledge(params) {
|
|
|
1755
2448
|
},
|
|
1756
2449
|
history,
|
|
1757
2450
|
value: 0,
|
|
2451
|
+
lesson: params.lesson,
|
|
2452
|
+
failure_details: params.failure_details,
|
|
2453
|
+
context: params.context,
|
|
2454
|
+
contradicts: params.contradicts ? Array.from(new Set(params.contradicts)) : void 0,
|
|
2455
|
+
blocking: params.blocking,
|
|
1758
2456
|
createdAt: now,
|
|
1759
2457
|
updatedAt: now
|
|
1760
2458
|
};
|
|
@@ -2402,8 +3100,8 @@ var EvidenceGraph = class _EvidenceGraph {
|
|
|
2402
3100
|
};
|
|
2403
3101
|
|
|
2404
3102
|
// src/scanner/filesystem.ts
|
|
2405
|
-
import * as
|
|
2406
|
-
import * as
|
|
3103
|
+
import * as fs5 from "fs/promises";
|
|
3104
|
+
import * as path5 from "path";
|
|
2407
3105
|
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2408
3106
|
".git",
|
|
2409
3107
|
".bilmem",
|
|
@@ -2424,12 +3122,12 @@ var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2424
3122
|
"temp"
|
|
2425
3123
|
]);
|
|
2426
3124
|
function normalizeRelPath(rel) {
|
|
2427
|
-
return rel.split(
|
|
3125
|
+
return rel.split(path5.sep).join("/");
|
|
2428
3126
|
}
|
|
2429
3127
|
async function loadGitignorePatterns(rootDir) {
|
|
2430
3128
|
try {
|
|
2431
|
-
const gitignorePath =
|
|
2432
|
-
const content = await
|
|
3129
|
+
const gitignorePath = path5.join(rootDir, ".gitignore");
|
|
3130
|
+
const content = await fs5.readFile(gitignorePath, "utf-8");
|
|
2433
3131
|
const regexes = [];
|
|
2434
3132
|
for (const rawLine of content.split("\n")) {
|
|
2435
3133
|
const line = rawLine.trim();
|
|
@@ -2477,7 +3175,7 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2477
3175
|
return;
|
|
2478
3176
|
}
|
|
2479
3177
|
try {
|
|
2480
|
-
const real = await
|
|
3178
|
+
const real = await fs5.realpath(currentDir);
|
|
2481
3179
|
if (visitedRealPaths.has(real)) {
|
|
2482
3180
|
return;
|
|
2483
3181
|
}
|
|
@@ -2487,14 +3185,14 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2487
3185
|
}
|
|
2488
3186
|
let entries;
|
|
2489
3187
|
try {
|
|
2490
|
-
entries = await
|
|
3188
|
+
entries = await fs5.readdir(currentDir, { withFileTypes: true });
|
|
2491
3189
|
} catch {
|
|
2492
3190
|
return;
|
|
2493
3191
|
}
|
|
2494
3192
|
for (const entry of entries) {
|
|
2495
3193
|
if (fileList.length >= maxFiles) break;
|
|
2496
|
-
const fullPath =
|
|
2497
|
-
const rawRelPath =
|
|
3194
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
3195
|
+
const rawRelPath = path5.relative(root, fullPath);
|
|
2498
3196
|
const relPath = normalizeRelPath(rawRelPath);
|
|
2499
3197
|
const lowerName = entry.name.toLowerCase();
|
|
2500
3198
|
if (gitignoreRules.some((rule) => rule.test(relPath))) {
|
|
@@ -2510,16 +3208,16 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2510
3208
|
if (!includeHidden && entry.name.startsWith(".") && lowerName !== ".env.example") {
|
|
2511
3209
|
continue;
|
|
2512
3210
|
}
|
|
2513
|
-
const ext =
|
|
3211
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
2514
3212
|
try {
|
|
2515
|
-
const
|
|
3213
|
+
const stat5 = await fs5.stat(fullPath);
|
|
2516
3214
|
const fileInfo = {
|
|
2517
3215
|
path: fullPath,
|
|
2518
3216
|
relativePath: relPath,
|
|
2519
3217
|
name: entry.name,
|
|
2520
3218
|
ext,
|
|
2521
|
-
size:
|
|
2522
|
-
mtimeMs:
|
|
3219
|
+
size: stat5.size,
|
|
3220
|
+
mtimeMs: stat5.mtimeMs,
|
|
2523
3221
|
isDirectory: false
|
|
2524
3222
|
};
|
|
2525
3223
|
if (!options.filter || options.filter(fileInfo)) {
|
|
@@ -2562,9 +3260,9 @@ var ProjectContext = class {
|
|
|
2562
3260
|
if (this.textCache.has(key)) {
|
|
2563
3261
|
return this.textCache.get(key);
|
|
2564
3262
|
}
|
|
2565
|
-
const fullPath =
|
|
3263
|
+
const fullPath = path5.resolve(this.rootDir, relativePath);
|
|
2566
3264
|
try {
|
|
2567
|
-
const handle = await
|
|
3265
|
+
const handle = await fs5.open(fullPath, "r");
|
|
2568
3266
|
const buffer = Buffer.alloc(maxBytes);
|
|
2569
3267
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
2570
3268
|
await handle.close();
|
|
@@ -2990,12 +3688,12 @@ async function analyzeProjectStructure(files, rootDir, registry = defaultRegistr
|
|
|
2990
3688
|
}
|
|
2991
3689
|
|
|
2992
3690
|
// src/scanner/markdown.ts
|
|
2993
|
-
import * as
|
|
2994
|
-
import * as
|
|
2995
|
-
import
|
|
3691
|
+
import * as fs6 from "fs/promises";
|
|
3692
|
+
import * as path6 from "path";
|
|
3693
|
+
import YAML3 from "yaml";
|
|
2996
3694
|
function parseMarkdownDocument(content, filePath, relativePath) {
|
|
2997
3695
|
const lines = content.split("\n");
|
|
2998
|
-
const baseName =
|
|
3696
|
+
const baseName = path6.basename(relativePath, ".md").toLowerCase();
|
|
2999
3697
|
let frontmatter = {};
|
|
3000
3698
|
const headings = [];
|
|
3001
3699
|
const alerts = [];
|
|
@@ -3024,7 +3722,7 @@ function parseMarkdownDocument(content, filePath, relativePath) {
|
|
|
3024
3722
|
bodyStartIndex = i + 1;
|
|
3025
3723
|
try {
|
|
3026
3724
|
const yamlText = frontmatterLines.join("\n");
|
|
3027
|
-
const parsed =
|
|
3725
|
+
const parsed = YAML3.parse(yamlText);
|
|
3028
3726
|
if (parsed && typeof parsed === "object") {
|
|
3029
3727
|
frontmatter = parsed;
|
|
3030
3728
|
}
|
|
@@ -3412,8 +4110,8 @@ var BUILTIN_MARKDOWN_EXTRACTORS = [
|
|
|
3412
4110
|
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))) {
|
|
3413
4111
|
const rawTarget = target.split("#")[0].split("?")[0].trim();
|
|
3414
4112
|
if (rawTarget) {
|
|
3415
|
-
const dir =
|
|
3416
|
-
const resolved = dir === "." ||
|
|
4113
|
+
const dir = path6.dirname(doc.relativePath);
|
|
4114
|
+
const resolved = dir === "." || path6.isAbsolute(rawTarget) ? rawTarget.replace(/^\.\//, "") : path6.normalize(path6.join(dir, rawTarget));
|
|
3417
4115
|
relations.push({
|
|
3418
4116
|
from: doc.relativePath,
|
|
3419
4117
|
to: resolved,
|
|
@@ -3466,7 +4164,7 @@ for (const ext of BUILTIN_MARKDOWN_EXTRACTORS) {
|
|
|
3466
4164
|
async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkdownRegistry) {
|
|
3467
4165
|
let content = "";
|
|
3468
4166
|
try {
|
|
3469
|
-
content = await
|
|
4167
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
3470
4168
|
} catch {
|
|
3471
4169
|
return { observations: [], relations: [] };
|
|
3472
4170
|
}
|
|
@@ -3475,11 +4173,11 @@ async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkd
|
|
|
3475
4173
|
}
|
|
3476
4174
|
|
|
3477
4175
|
// src/scanner/git.ts
|
|
3478
|
-
import { execFile } from "child_process";
|
|
3479
|
-
import { promisify } from "util";
|
|
3480
|
-
var
|
|
4176
|
+
import { execFile as execFile2 } from "child_process";
|
|
4177
|
+
import { promisify as promisify2 } from "util";
|
|
4178
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
3481
4179
|
async function runGit(args, cwd) {
|
|
3482
|
-
const { stdout } = await
|
|
4180
|
+
const { stdout } = await execFileAsync2("git", args, {
|
|
3483
4181
|
cwd,
|
|
3484
4182
|
maxBuffer: 10 * 1024 * 1024
|
|
3485
4183
|
});
|
|
@@ -3673,9 +4371,9 @@ async function scanGitHistory(cwd, options = {}) {
|
|
|
3673
4371
|
}
|
|
3674
4372
|
|
|
3675
4373
|
// src/scanner/source.ts
|
|
3676
|
-
import * as
|
|
4374
|
+
import * as fs7 from "fs/promises";
|
|
3677
4375
|
import * as fsSync from "fs";
|
|
3678
|
-
import * as
|
|
4376
|
+
import * as path7 from "path";
|
|
3679
4377
|
import { createRequire } from "module";
|
|
3680
4378
|
import Parser from "web-tree-sitter";
|
|
3681
4379
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -3729,7 +4427,7 @@ function getWasmDir() {
|
|
|
3729
4427
|
try {
|
|
3730
4428
|
const req = createRequire(import.meta.url);
|
|
3731
4429
|
const pkgJson = req.resolve("tree-sitter-wasms/package.json");
|
|
3732
|
-
const outDir =
|
|
4430
|
+
const outDir = path7.join(path7.dirname(pkgJson), "out");
|
|
3733
4431
|
if (fsSync.existsSync(outDir)) {
|
|
3734
4432
|
wasmBaseDir = outDir;
|
|
3735
4433
|
return wasmBaseDir;
|
|
@@ -3737,8 +4435,8 @@ function getWasmDir() {
|
|
|
3737
4435
|
} catch {
|
|
3738
4436
|
}
|
|
3739
4437
|
const candidateDirs = [
|
|
3740
|
-
|
|
3741
|
-
|
|
4438
|
+
path7.resolve(process.cwd(), "node_modules/tree-sitter-wasms/out"),
|
|
4439
|
+
path7.resolve(process.cwd(), "../node_modules/tree-sitter-wasms/out")
|
|
3742
4440
|
];
|
|
3743
4441
|
for (const dir of candidateDirs) {
|
|
3744
4442
|
if (fsSync.existsSync(dir)) {
|
|
@@ -3751,7 +4449,7 @@ function getWasmDir() {
|
|
|
3751
4449
|
async function getParserForWasm(wasmName) {
|
|
3752
4450
|
const wasmDir = getWasmDir();
|
|
3753
4451
|
if (!wasmDir) return null;
|
|
3754
|
-
const wasmPath =
|
|
4452
|
+
const wasmPath = path7.join(wasmDir, wasmName);
|
|
3755
4453
|
if (!fsSync.existsSync(wasmPath)) return null;
|
|
3756
4454
|
try {
|
|
3757
4455
|
if (!parserInitialized) {
|
|
@@ -4110,7 +4808,7 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4110
4808
|
const relations = [];
|
|
4111
4809
|
let content = "";
|
|
4112
4810
|
try {
|
|
4113
|
-
content = await
|
|
4811
|
+
content = await fs7.readFile(filePath, "utf-8");
|
|
4114
4812
|
} catch {
|
|
4115
4813
|
return { observations, relations };
|
|
4116
4814
|
}
|
|
@@ -4118,8 +4816,8 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4118
4816
|
return { observations, relations };
|
|
4119
4817
|
}
|
|
4120
4818
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4121
|
-
const baseName =
|
|
4122
|
-
const ext =
|
|
4819
|
+
const baseName = path7.basename(relativePath);
|
|
4820
|
+
const ext = path7.extname(relativePath).toLowerCase();
|
|
4123
4821
|
const wasmName = EXT_TO_WASM[ext];
|
|
4124
4822
|
if (wasmName) {
|
|
4125
4823
|
try {
|
|
@@ -4317,9 +5015,9 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
4317
5015
|
const plannedActions = [];
|
|
4318
5016
|
const config = dryRun ? await storage.isInitialized() ? await storage.getConfig() : {
|
|
4319
5017
|
schemaVersion: 1,
|
|
4320
|
-
projectName:
|
|
5018
|
+
projectName: path8.basename(rootDir),
|
|
4321
5019
|
defaultScope: "project",
|
|
4322
|
-
dataDir:
|
|
5020
|
+
dataDir: path8.join(rootDir, ".bilmem")
|
|
4323
5021
|
} : await storage.init();
|
|
4324
5022
|
const incremental = options.incremental ?? false;
|
|
4325
5023
|
const lastScanCommit = incremental ? config.lastScanCommit : void 0;
|
|
@@ -4392,7 +5090,7 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
4392
5090
|
];
|
|
4393
5091
|
for (const o of mdObs) {
|
|
4394
5092
|
if (o.source.includes(":adr") || o.claim.startsWith("[WARNING]") || o.claim.startsWith("[CAUTION]")) {
|
|
4395
|
-
const id = `decision.${
|
|
5093
|
+
const id = `decision.${path8.basename(o.provenance.path || "doc", ".md")}.${o.provenance.line || "adr"}`;
|
|
4396
5094
|
candidateLessons.push({
|
|
4397
5095
|
id,
|
|
4398
5096
|
type: "decision",
|
|
@@ -4696,7 +5394,7 @@ function processUserCorrection(params) {
|
|
|
4696
5394
|
}
|
|
4697
5395
|
|
|
4698
5396
|
// src/storage/portable.ts
|
|
4699
|
-
import
|
|
5397
|
+
import YAML4 from "yaml";
|
|
4700
5398
|
function exportKnowledgeToYaml(items, options = {}) {
|
|
4701
5399
|
let filtered = items;
|
|
4702
5400
|
if (options.scope) {
|
|
@@ -4727,10 +5425,10 @@ function exportKnowledgeToYaml(items, options = {}) {
|
|
|
4727
5425
|
}
|
|
4728
5426
|
}))
|
|
4729
5427
|
};
|
|
4730
|
-
return
|
|
5428
|
+
return YAML4.stringify(payload, { indent: 2, lineWidth: 100 });
|
|
4731
5429
|
}
|
|
4732
5430
|
async function importKnowledgeFromYaml(yamlContent, storage) {
|
|
4733
|
-
const parsed =
|
|
5431
|
+
const parsed = YAML4.parse(yamlContent);
|
|
4734
5432
|
if (!parsed || typeof parsed !== "object") {
|
|
4735
5433
|
throw new Error("Invalid YAML: root must be an object");
|
|
4736
5434
|
}
|
|
@@ -4789,10 +5487,10 @@ async function dumpToYaml(storage) {
|
|
|
4789
5487
|
relations,
|
|
4790
5488
|
metrics
|
|
4791
5489
|
};
|
|
4792
|
-
return
|
|
5490
|
+
return YAML4.stringify(dump, { indent: 2, lineWidth: 120 });
|
|
4793
5491
|
}
|
|
4794
5492
|
async function restoreFromYaml(yamlContent, storage) {
|
|
4795
|
-
const parsed =
|
|
5493
|
+
const parsed = YAML4.parse(yamlContent);
|
|
4796
5494
|
if (!parsed || parsed.schemaVersion !== 1) {
|
|
4797
5495
|
throw new Error(`Unsupported snapshot schema version: ${parsed?.schemaVersion}`);
|
|
4798
5496
|
}
|
|
@@ -4952,7 +5650,7 @@ async function compact(storage, options = {}) {
|
|
|
4952
5650
|
|
|
4953
5651
|
// src/mcp/server.ts
|
|
4954
5652
|
import { McpApp, z } from "mcponce";
|
|
4955
|
-
import * as
|
|
5653
|
+
import * as path9 from "path";
|
|
4956
5654
|
|
|
4957
5655
|
// src/core/outcome.ts
|
|
4958
5656
|
function createOutcome(params) {
|
|
@@ -5104,8 +5802,34 @@ async function computeStats(storage, events, options) {
|
|
|
5104
5802
|
}
|
|
5105
5803
|
};
|
|
5106
5804
|
const actions = {};
|
|
5805
|
+
const preflight = {
|
|
5806
|
+
runs: 0,
|
|
5807
|
+
ready: 0,
|
|
5808
|
+
needsClarification: 0,
|
|
5809
|
+
needsRevalidation: 0,
|
|
5810
|
+
conflicted: 0,
|
|
5811
|
+
insufficientKnowledge: 0,
|
|
5812
|
+
blockingUnknownsFound: 0,
|
|
5813
|
+
staleKnowledgeFound: 0,
|
|
5814
|
+
pastFailuresSurfaced: 0,
|
|
5815
|
+
questionsSuggested: 0,
|
|
5816
|
+
repeatedChecksAvoided: 0
|
|
5817
|
+
};
|
|
5107
5818
|
for (const e of events) {
|
|
5108
5819
|
switch (e.type) {
|
|
5820
|
+
case "preflight":
|
|
5821
|
+
preflight.runs++;
|
|
5822
|
+
if (e.readiness === "READY") preflight.ready++;
|
|
5823
|
+
else if (e.readiness === "NEEDS_CLARIFICATION") preflight.needsClarification++;
|
|
5824
|
+
else if (e.readiness === "NEEDS_REVALIDATION") preflight.needsRevalidation++;
|
|
5825
|
+
else if (e.readiness === "CONFLICTED") preflight.conflicted++;
|
|
5826
|
+
else if (e.readiness === "INSUFFICIENT_KNOWLEDGE") preflight.insufficientKnowledge++;
|
|
5827
|
+
preflight.blockingUnknownsFound += e.blockingUnknownsCount || 0;
|
|
5828
|
+
preflight.staleKnowledgeFound += e.staleKnowledgeCount || 0;
|
|
5829
|
+
preflight.pastFailuresSurfaced += e.pastFailuresCount || 0;
|
|
5830
|
+
preflight.questionsSuggested += e.questionsCount || 0;
|
|
5831
|
+
preflight.repeatedChecksAvoided += e.repeatedChecksAvoidedCount || 0;
|
|
5832
|
+
break;
|
|
5109
5833
|
case "knowledge_created":
|
|
5110
5834
|
learning.new++;
|
|
5111
5835
|
break;
|
|
@@ -5276,7 +6000,8 @@ async function computeStats(storage, events, options) {
|
|
|
5276
6000
|
questionResolutionRate,
|
|
5277
6001
|
jevRejectionRate
|
|
5278
6002
|
},
|
|
5279
|
-
health
|
|
6003
|
+
health,
|
|
6004
|
+
preflight: preflight.runs > 0 ? preflight : void 0
|
|
5280
6005
|
};
|
|
5281
6006
|
}
|
|
5282
6007
|
async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
@@ -5345,9 +6070,9 @@ async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
|
5345
6070
|
}
|
|
5346
6071
|
|
|
5347
6072
|
// src/stats/filter.ts
|
|
5348
|
-
import { execFile as
|
|
5349
|
-
import { promisify as
|
|
5350
|
-
var
|
|
6073
|
+
import { execFile as execFile3 } from "child_process";
|
|
6074
|
+
import { promisify as promisify3 } from "util";
|
|
6075
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
5351
6076
|
async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
5352
6077
|
const trimmed = since.trim();
|
|
5353
6078
|
const durationMatch = trimmed.match(/^(\d+)\s*([smhdw])$/i);
|
|
@@ -5379,7 +6104,7 @@ async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
|
5379
6104
|
return parsedDate;
|
|
5380
6105
|
}
|
|
5381
6106
|
try {
|
|
5382
|
-
const { stdout } = await
|
|
6107
|
+
const { stdout } = await execFileAsync3(
|
|
5383
6108
|
"git",
|
|
5384
6109
|
["log", "-1", "--format=%ct", trimmed],
|
|
5385
6110
|
{ cwd: projectDir }
|
|
@@ -5475,6 +6200,23 @@ function formatStatsReport(report) {
|
|
|
5475
6200
|
lines.push(padRow("Frequently rejected", report.health.frequentlyRejected));
|
|
5476
6201
|
lines.push(padRow("Frequently misleading", report.health.frequentlyMisleading));
|
|
5477
6202
|
lines.push(padRow("Unused", report.health.unused));
|
|
6203
|
+
if (report.preflight && report.preflight.runs > 0) {
|
|
6204
|
+
lines.push("");
|
|
6205
|
+
lines.push("Preflight");
|
|
6206
|
+
lines.push(padRow("Runs", report.preflight.runs));
|
|
6207
|
+
lines.push(padRow("Ready", report.preflight.ready));
|
|
6208
|
+
lines.push(padRow("Needs clarification", report.preflight.needsClarification));
|
|
6209
|
+
lines.push(padRow("Needs revalidation", report.preflight.needsRevalidation));
|
|
6210
|
+
lines.push(padRow("Conflicted", report.preflight.conflicted));
|
|
6211
|
+
lines.push(padRow("Insufficient knowledge", report.preflight.insufficientKnowledge));
|
|
6212
|
+
lines.push("");
|
|
6213
|
+
lines.push("Before implementation");
|
|
6214
|
+
lines.push(padRow("Blocking unknowns found", report.preflight.blockingUnknownsFound));
|
|
6215
|
+
lines.push(padRow("Stale knowledge found", report.preflight.staleKnowledgeFound));
|
|
6216
|
+
lines.push(padRow("Past failures surfaced", report.preflight.pastFailuresSurfaced));
|
|
6217
|
+
lines.push(padRow("Questions suggested", report.preflight.questionsSuggested));
|
|
6218
|
+
lines.push(padRow("Repeated checks avoided", report.preflight.repeatedChecksAvoided));
|
|
6219
|
+
}
|
|
5478
6220
|
return lines.join("\n");
|
|
5479
6221
|
}
|
|
5480
6222
|
function formatKnowledgeStatsReport(report) {
|
|
@@ -5544,17 +6286,56 @@ async function runStats(options = {}) {
|
|
|
5544
6286
|
|
|
5545
6287
|
// src/mcp/server.ts
|
|
5546
6288
|
function createBilmemMcpApp(options = {}) {
|
|
5547
|
-
const
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
6289
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
6290
|
+
let lastActiveDir = options.rootDir ? path9.resolve(options.rootDir) : null;
|
|
6291
|
+
function isSystemRoot(dir) {
|
|
6292
|
+
const resolved = path9.resolve(dir);
|
|
6293
|
+
return resolved === "/" || path9.parse(resolved).root === resolved;
|
|
6294
|
+
}
|
|
6295
|
+
function resolveTargetDir(requestedPath) {
|
|
6296
|
+
if (requestedPath && requestedPath.trim()) {
|
|
6297
|
+
const resolved = path9.resolve(requestedPath.trim());
|
|
6298
|
+
lastActiveDir = resolved;
|
|
6299
|
+
return resolved;
|
|
6300
|
+
}
|
|
6301
|
+
if (lastActiveDir) {
|
|
6302
|
+
return lastActiveDir;
|
|
6303
|
+
}
|
|
6304
|
+
if (process.env.BILMEM_PROJECT_DIR) {
|
|
6305
|
+
const envDir = path9.resolve(process.env.BILMEM_PROJECT_DIR);
|
|
6306
|
+
lastActiveDir = envDir;
|
|
6307
|
+
return envDir;
|
|
6308
|
+
}
|
|
6309
|
+
const cwd = process.cwd();
|
|
6310
|
+
if (cwd && !isSystemRoot(cwd)) {
|
|
6311
|
+
lastActiveDir = cwd;
|
|
6312
|
+
return cwd;
|
|
6313
|
+
}
|
|
6314
|
+
throw new Error(
|
|
6315
|
+
`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", ... }).`
|
|
6316
|
+
);
|
|
6317
|
+
}
|
|
6318
|
+
async function getProjectContext(requestedPath) {
|
|
6319
|
+
const targetDir = resolveTargetDir(requestedPath);
|
|
6320
|
+
let ctx = contexts.get(targetDir);
|
|
6321
|
+
if (!ctx) {
|
|
6322
|
+
const storage = new Storage(targetDir);
|
|
6323
|
+
const index = new SearchIndex();
|
|
6324
|
+
ctx = {
|
|
6325
|
+
rootDir: targetDir,
|
|
6326
|
+
storage,
|
|
6327
|
+
index,
|
|
6328
|
+
initialized: false
|
|
6329
|
+
};
|
|
6330
|
+
contexts.set(targetDir, ctx);
|
|
6331
|
+
}
|
|
6332
|
+
if (!ctx.initialized) {
|
|
6333
|
+
await ctx.storage.init();
|
|
6334
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
6335
|
+
await ctx.index.buildIndex(all);
|
|
6336
|
+
ctx.initialized = true;
|
|
5557
6337
|
}
|
|
6338
|
+
return ctx;
|
|
5558
6339
|
}
|
|
5559
6340
|
const app = new McpApp({
|
|
5560
6341
|
name: options.name ?? "bilmem",
|
|
@@ -5564,6 +6345,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5564
6345
|
name: "bilmem_resolve",
|
|
5565
6346
|
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.",
|
|
5566
6347
|
inputSchema: {
|
|
6348
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5567
6349
|
question: z.string().describe("The question, goal, or uncertainty to resolve"),
|
|
5568
6350
|
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. server-session, node, postgres)"),
|
|
5569
6351
|
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. stateless-jwt, browser, sqlite)"),
|
|
@@ -5571,8 +6353,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5571
6353
|
maxDepth: z.number().optional().describe("Max candidates to inspect (default: 5)")
|
|
5572
6354
|
},
|
|
5573
6355
|
handler: async (args) => {
|
|
5574
|
-
await
|
|
5575
|
-
const retriever = new Retriever(index, storage);
|
|
6356
|
+
const ctx = await getProjectContext(args.path);
|
|
6357
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5576
6358
|
const res = await resolveEpistemicQuery(
|
|
5577
6359
|
{
|
|
5578
6360
|
question: args.question,
|
|
@@ -5581,7 +6363,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5581
6363
|
scope: args.scope,
|
|
5582
6364
|
maxDepth: args.maxDepth
|
|
5583
6365
|
},
|
|
5584
|
-
storage,
|
|
6366
|
+
ctx.storage,
|
|
5585
6367
|
retriever
|
|
5586
6368
|
);
|
|
5587
6369
|
return {
|
|
@@ -5598,10 +6380,53 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5598
6380
|
};
|
|
5599
6381
|
}
|
|
5600
6382
|
});
|
|
6383
|
+
app.tool({
|
|
6384
|
+
name: "bilmem_preflight",
|
|
6385
|
+
description: "Determine what must be known before beginning a proposed goal (surfaces constraints, relevant lessons, past failures, stale knowledge, and blocking unknowns).",
|
|
6386
|
+
inputSchema: {
|
|
6387
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
6388
|
+
goal: z.string().describe("The proposed goal, feature, or work to analyze before implementation"),
|
|
6389
|
+
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. authentication, postgres)"),
|
|
6390
|
+
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. browser, sqlite)"),
|
|
6391
|
+
deep: z.boolean().optional().describe("Check targeted git diff / unindexed files for staleness")
|
|
6392
|
+
},
|
|
6393
|
+
handler: async (args) => {
|
|
6394
|
+
const ctx = await getProjectContext(args.path);
|
|
6395
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
6396
|
+
const res = await executePreflight(
|
|
6397
|
+
{
|
|
6398
|
+
goal: args.goal,
|
|
6399
|
+
positive: args.positive,
|
|
6400
|
+
negative: args.negative,
|
|
6401
|
+
deep: args.deep
|
|
6402
|
+
},
|
|
6403
|
+
ctx.storage,
|
|
6404
|
+
retriever,
|
|
6405
|
+
ctx.index
|
|
6406
|
+
);
|
|
6407
|
+
return {
|
|
6408
|
+
goal: res.goal,
|
|
6409
|
+
readiness: res.readiness,
|
|
6410
|
+
known: res.known.slice(0, 5).map((k) => k.statement),
|
|
6411
|
+
constraints: res.constraints.map((c) => c.statement),
|
|
6412
|
+
lessons: res.lessons.slice(0, 5).map((l) => l.statement),
|
|
6413
|
+
pastFailures: res.past_failures.slice(0, 5).map((f) => f.statement),
|
|
6414
|
+
blockingUnknowns: res.unknowns.filter((u) => u.blocking).map((u) => u.question || u.statement || u.id),
|
|
6415
|
+
stale: res.stale.map((s) => s.id),
|
|
6416
|
+
questions: res.questions.slice(0, 5).map((q) => q.question),
|
|
6417
|
+
next: res.next.slice(0, 3).map((n) => ({
|
|
6418
|
+
type: n.type,
|
|
6419
|
+
target: n.target,
|
|
6420
|
+
reason: n.reason
|
|
6421
|
+
}))
|
|
6422
|
+
};
|
|
6423
|
+
}
|
|
6424
|
+
});
|
|
5601
6425
|
app.tool({
|
|
5602
6426
|
name: "bilmem_lookup",
|
|
5603
6427
|
description: "Lookup reusable knowledge, conventions, and lessons using JEV contextual reranking. Returns compact, highly applicable knowledge units.",
|
|
5604
6428
|
inputSchema: {
|
|
6429
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5605
6430
|
query: z.string().optional().describe("Query text, intent keywords, or problem description"),
|
|
5606
6431
|
positive: z.array(z.string()).optional().describe("Contextual positive cues (e.g. node, esm, server-session)"),
|
|
5607
6432
|
negative: z.array(z.string()).optional().describe("Contextual negative cues (e.g. commonjs, browser)"),
|
|
@@ -5609,8 +6434,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5609
6434
|
limit: z.number().optional().describe("Maximum number of results to return (default 5)")
|
|
5610
6435
|
},
|
|
5611
6436
|
handler: async (args) => {
|
|
5612
|
-
await
|
|
5613
|
-
const retriever = new Retriever(index, storage);
|
|
6437
|
+
const ctx = await getProjectContext(args.path);
|
|
6438
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5614
6439
|
const results = await retriever.lookup({
|
|
5615
6440
|
query: args.query,
|
|
5616
6441
|
positive: args.positive,
|
|
@@ -5637,6 +6462,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5637
6462
|
name: "bilmem_observe",
|
|
5638
6463
|
description: "Record a new observation from code, git, test execution, or environment. Preserves provenance.",
|
|
5639
6464
|
inputSchema: {
|
|
6465
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5640
6466
|
claim: z.string().describe("What was observed"),
|
|
5641
6467
|
source: z.string().describe("Source identifier (e.g. file:src/auth.ts#L42, git:commit:81ac23, user:correction)"),
|
|
5642
6468
|
confidence: z.number().optional().describe("Confidence 0.0 - 1.0 (default: 0.85)"),
|
|
@@ -5645,7 +6471,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5645
6471
|
details: z.record(z.any()).optional().describe("Optional structured metadata")
|
|
5646
6472
|
},
|
|
5647
6473
|
handler: async (args) => {
|
|
5648
|
-
await
|
|
6474
|
+
const ctx = await getProjectContext(args.path);
|
|
5649
6475
|
const obs = createObservation({
|
|
5650
6476
|
source: args.source,
|
|
5651
6477
|
claim: args.claim,
|
|
@@ -5659,10 +6485,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5659
6485
|
negative: args.negative,
|
|
5660
6486
|
details: args.details
|
|
5661
6487
|
});
|
|
5662
|
-
await storage.saveObservation(obs);
|
|
6488
|
+
await ctx.storage.saveObservation(obs);
|
|
5663
6489
|
const evidence = createEvidence(obs, "supports");
|
|
5664
|
-
await storage.saveEvidence(evidence);
|
|
5665
|
-
const existing = await storage.getAllKnowledge();
|
|
6490
|
+
await ctx.storage.saveEvidence(evidence);
|
|
6491
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5666
6492
|
const rec = reconcileKnowledge(
|
|
5667
6493
|
{
|
|
5668
6494
|
statement: obs.claim,
|
|
@@ -5683,10 +6509,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5683
6509
|
evidence: [evidence.id, obs.source]
|
|
5684
6510
|
},
|
|
5685
6511
|
existing,
|
|
5686
|
-
storage
|
|
6512
|
+
ctx.storage
|
|
5687
6513
|
);
|
|
5688
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
5689
|
-
await index.upsert(updatedKnowledge);
|
|
6514
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6515
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
5690
6516
|
return {
|
|
5691
6517
|
observationId: obs.id,
|
|
5692
6518
|
evidenceId: evidence.id,
|
|
@@ -5700,6 +6526,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5700
6526
|
name: "bilmem_learn",
|
|
5701
6527
|
description: "Learn from a completed task trajectory or explicit user correction. Converts experiences into refined, reusable lessons.",
|
|
5702
6528
|
inputSchema: {
|
|
6529
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5703
6530
|
goal: z.string().describe("The task or goal that was pursued"),
|
|
5704
6531
|
taskId: z.string().optional().describe("Task identifier"),
|
|
5705
6532
|
outcome: z.enum(["success", "failure", "reverted", "neutral"]).describe("Outcome of the task"),
|
|
@@ -5715,16 +6542,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5715
6542
|
}).optional().describe("Explicit user correction, if any occurred")
|
|
5716
6543
|
},
|
|
5717
6544
|
handler: async (args) => {
|
|
5718
|
-
await
|
|
6545
|
+
const ctx = await getProjectContext(args.path);
|
|
5719
6546
|
if (args.userCorrection) {
|
|
5720
6547
|
const { observation, knowledge } = processUserCorrection(args.userCorrection);
|
|
5721
|
-
await storage.saveObservation(observation);
|
|
5722
|
-
await storage.saveEvidence(createEvidence(observation, "supports"));
|
|
5723
|
-
const existing = await storage.getAllKnowledge();
|
|
6548
|
+
await ctx.storage.saveObservation(observation);
|
|
6549
|
+
await ctx.storage.saveEvidence(createEvidence(observation, "supports"));
|
|
6550
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5724
6551
|
const rec = reconcileKnowledge(knowledge, existing);
|
|
5725
|
-
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, storage);
|
|
5726
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
5727
|
-
await index.upsert(updatedKnowledge);
|
|
6552
|
+
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, ctx.storage);
|
|
6553
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6554
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
5728
6555
|
return {
|
|
5729
6556
|
type: "user_correction",
|
|
5730
6557
|
knowledgeId: updatedKnowledge.id,
|
|
@@ -5740,20 +6567,20 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5740
6567
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5741
6568
|
finalOutcome: args.outcome
|
|
5742
6569
|
};
|
|
5743
|
-
const existing = await storage.getAllKnowledge();
|
|
6570
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5744
6571
|
const learningResult = learnFromTrajectory(trajectory, existing);
|
|
5745
6572
|
for (const obs of learningResult.observations) {
|
|
5746
|
-
await storage.saveObservation(obs);
|
|
6573
|
+
await ctx.storage.saveObservation(obs);
|
|
5747
6574
|
}
|
|
5748
6575
|
for (const ev of learningResult.evidence) {
|
|
5749
|
-
await storage.saveEvidence(ev);
|
|
6576
|
+
await ctx.storage.saveEvidence(ev);
|
|
5750
6577
|
}
|
|
5751
6578
|
let updatedKnowledge;
|
|
5752
6579
|
let action = "NEW";
|
|
5753
6580
|
for (const item of learningResult.reconciliations) {
|
|
5754
6581
|
if (item.appliedKnowledge) {
|
|
5755
|
-
await storage.saveKnowledge(item.appliedKnowledge);
|
|
5756
|
-
await index.upsert(item.appliedKnowledge);
|
|
6582
|
+
await ctx.storage.saveKnowledge(item.appliedKnowledge);
|
|
6583
|
+
await ctx.index.upsert(item.appliedKnowledge);
|
|
5757
6584
|
updatedKnowledge = item.appliedKnowledge;
|
|
5758
6585
|
action = item.action;
|
|
5759
6586
|
}
|
|
@@ -5765,9 +6592,9 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5765
6592
|
result: args.outcome,
|
|
5766
6593
|
context: { positive: updatedKnowledge.positive, negative: updatedKnowledge.negative }
|
|
5767
6594
|
});
|
|
5768
|
-
await storage.appendOutcome(out);
|
|
6595
|
+
await ctx.storage.appendOutcome(out);
|
|
5769
6596
|
applyOutcomeToKnowledge(updatedKnowledge, out);
|
|
5770
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
6597
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
5771
6598
|
return {
|
|
5772
6599
|
type: "trajectory_lesson",
|
|
5773
6600
|
action,
|
|
@@ -5789,12 +6616,13 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5789
6616
|
name: "bilmem_explain",
|
|
5790
6617
|
description: "Explain why a knowledge ID exists: statement, evidence provenance, confidence, support/contradiction history, and applicable context.",
|
|
5791
6618
|
inputSchema: {
|
|
6619
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5792
6620
|
id: z.string().describe("The knowledge ID to explain"),
|
|
5793
6621
|
history: z.boolean().optional().describe("Whether to format lifecycle transition history (--history)")
|
|
5794
6622
|
},
|
|
5795
6623
|
handler: async (args) => {
|
|
5796
|
-
await
|
|
5797
|
-
const k = await storage.getKnowledge(args.id);
|
|
6624
|
+
const ctx = await getProjectContext(args.path);
|
|
6625
|
+
const k = await ctx.storage.getKnowledge(args.id);
|
|
5798
6626
|
if (!k) {
|
|
5799
6627
|
return { error: `Knowledge not found: "${args.id}"` };
|
|
5800
6628
|
}
|
|
@@ -5828,6 +6656,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5828
6656
|
name: "bilmem_unknown",
|
|
5829
6657
|
description: "Query or record first-class unknowns (NOT_OBSERVED, INSUFFICIENT_EVIDENCE, CONFLICTING_EVIDENCE, UNRESOLVABLE_LOCALLY) to prevent repeating useless investigations.",
|
|
5830
6658
|
inputSchema: {
|
|
6659
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5831
6660
|
action: z.enum(["query", "check", "record", "resolve"]).describe("Action to perform"),
|
|
5832
6661
|
id: z.string().optional().describe("Unknown ID or query subject"),
|
|
5833
6662
|
reason: z.enum(["NOT_OBSERVED", "INSUFFICIENT_EVIDENCE", "CONFLICTING_EVIDENCE", "STALE", "UNRESOLVABLE_LOCALLY"]).optional(),
|
|
@@ -5836,16 +6665,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5836
6665
|
question: z.string().optional().describe("Clarifying question for the user or next agent")
|
|
5837
6666
|
},
|
|
5838
6667
|
handler: async (args) => {
|
|
5839
|
-
await
|
|
6668
|
+
const ctx = await getProjectContext(args.path);
|
|
5840
6669
|
if (args.action === "query") {
|
|
5841
|
-
const list = await storage.getAllUnknowns();
|
|
6670
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
5842
6671
|
return {
|
|
5843
6672
|
count: list.length,
|
|
5844
6673
|
unknowns: list
|
|
5845
6674
|
};
|
|
5846
6675
|
}
|
|
5847
6676
|
if (args.action === "check") {
|
|
5848
|
-
const list = await storage.getAllUnknowns();
|
|
6677
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
5849
6678
|
const checkResult = hasBeenInvestigated(list, args.id || "");
|
|
5850
6679
|
return checkResult;
|
|
5851
6680
|
}
|
|
@@ -5859,12 +6688,12 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5859
6688
|
checked: args.checked ?? [],
|
|
5860
6689
|
next: args.question ? { type: "ask", question: args.question } : void 0
|
|
5861
6690
|
});
|
|
5862
|
-
await storage.saveUnknown(record);
|
|
6691
|
+
await ctx.storage.saveUnknown(record);
|
|
5863
6692
|
return { success: true, recorded: record };
|
|
5864
6693
|
}
|
|
5865
6694
|
if (args.action === "resolve") {
|
|
5866
6695
|
if (!args.id) throw new Error("ID is required to resolve an unknown");
|
|
5867
|
-
const deleted = await storage.deleteUnknown(args.id);
|
|
6696
|
+
const deleted = await ctx.storage.deleteUnknown(args.id);
|
|
5868
6697
|
return { success: deleted, resolvedId: args.id };
|
|
5869
6698
|
}
|
|
5870
6699
|
return { error: "Unknown action" };
|
|
@@ -5874,16 +6703,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5874
6703
|
name: "bilmem_scan",
|
|
5875
6704
|
description: "Trigger a language-independent scan of the repository filesystem, markdown documentation, and git history.",
|
|
5876
6705
|
inputSchema: {
|
|
5877
|
-
path: z.string().optional().describe("
|
|
6706
|
+
path: z.string().optional().describe("Project root directory to scan (defaults to active workspace or current working directory)"),
|
|
5878
6707
|
incremental: z.boolean().optional().describe("Whether to scan incrementally from last scanned git commit")
|
|
5879
6708
|
},
|
|
5880
6709
|
handler: async (args) => {
|
|
5881
|
-
const
|
|
5882
|
-
const report = await scanRepository(
|
|
6710
|
+
const ctx = await getProjectContext(args.path);
|
|
6711
|
+
const report = await scanRepository(ctx.rootDir, {
|
|
5883
6712
|
incremental: args.incremental ?? false
|
|
5884
6713
|
});
|
|
5885
|
-
const all = await storage.getAllKnowledge();
|
|
5886
|
-
await index.buildIndex(all);
|
|
6714
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
6715
|
+
await ctx.index.buildIndex(all);
|
|
5887
6716
|
return report;
|
|
5888
6717
|
}
|
|
5889
6718
|
});
|
|
@@ -5891,13 +6720,14 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5891
6720
|
name: "bilmem_stats",
|
|
5892
6721
|
description: "Inspect local-first statistics, knowledge health, and effectiveness metrics (measuring reuse, investigation avoidance, and JEV accuracy). Read-only.",
|
|
5893
6722
|
inputSchema: {
|
|
6723
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5894
6724
|
since: z.string().optional().describe("Time window (e.g. 24h, 7d, 30d) or Git revision (e.g. HEAD~20, commit-sha)"),
|
|
5895
6725
|
knowledgeId: z.string().optional().describe("Specific knowledge ID to inspect single-item lifecycle and diagnostics")
|
|
5896
6726
|
},
|
|
5897
6727
|
handler: async (args) => {
|
|
5898
|
-
await
|
|
6728
|
+
const ctx = await getProjectContext(args.path);
|
|
5899
6729
|
const res = await runStats({
|
|
5900
|
-
rootDir,
|
|
6730
|
+
rootDir: ctx.rootDir,
|
|
5901
6731
|
since: args.since,
|
|
5902
6732
|
knowledgeId: args.knowledgeId
|
|
5903
6733
|
});
|
|
@@ -5911,20 +6741,20 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5911
6741
|
}
|
|
5912
6742
|
|
|
5913
6743
|
// src/install/index.ts
|
|
5914
|
-
import * as
|
|
6744
|
+
import * as path19 from "path";
|
|
5915
6745
|
import * as readline from "readline/promises";
|
|
5916
6746
|
|
|
5917
6747
|
// src/install/adapters/cursor.ts
|
|
5918
|
-
import * as
|
|
6748
|
+
import * as path13 from "path";
|
|
5919
6749
|
import * as os from "os";
|
|
5920
6750
|
|
|
5921
6751
|
// src/install/adapters/base.ts
|
|
5922
|
-
import * as
|
|
5923
|
-
import * as
|
|
6752
|
+
import * as path12 from "path";
|
|
6753
|
+
import * as fs9 from "fs/promises";
|
|
5924
6754
|
|
|
5925
6755
|
// src/install/atomic.ts
|
|
5926
|
-
import * as
|
|
5927
|
-
import * as
|
|
6756
|
+
import * as fs8 from "fs/promises";
|
|
6757
|
+
import * as path10 from "path";
|
|
5928
6758
|
|
|
5929
6759
|
// src/install/instructions.ts
|
|
5930
6760
|
var CURRENT_INTEGRATION_VERSION = 1;
|
|
@@ -5936,6 +6766,8 @@ function getAgentInstructionText(version = CURRENT_INTEGRATION_VERSION) {
|
|
|
5936
6766
|
## Bilmem Knowledge Runtime
|
|
5937
6767
|
Bilmem is available through MCP.
|
|
5938
6768
|
|
|
6769
|
+
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.
|
|
6770
|
+
|
|
5939
6771
|
Use \`bilmem_resolve\` when:
|
|
5940
6772
|
- uncertain about project-specific behavior,
|
|
5941
6773
|
- multiple plausible approaches exist,
|
|
@@ -5959,18 +6791,171 @@ function extractInstructionVersion(content) {
|
|
|
5959
6791
|
function hasBilmemInstruction(content) {
|
|
5960
6792
|
return content.includes(BILMEM_START_MARKER) && content.includes(BILMEM_END_MARKER);
|
|
5961
6793
|
}
|
|
6794
|
+
function getBilmemSkillContent(version = CURRENT_INTEGRATION_VERSION) {
|
|
6795
|
+
return `---
|
|
6796
|
+
name: bilmem
|
|
6797
|
+
description: >-
|
|
6798
|
+
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).
|
|
6799
|
+
---
|
|
6800
|
+
<!-- bilmem-version: ${version} -->
|
|
6801
|
+
|
|
6802
|
+
# Bilmem Epistemic Knowledge Runtime
|
|
6803
|
+
|
|
6804
|
+
Bilmem is an epistemic knowledge engine designed to answer:
|
|
6805
|
+
> **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?**
|
|
6806
|
+
|
|
6807
|
+
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.
|
|
6808
|
+
|
|
6809
|
+
---
|
|
6810
|
+
|
|
6811
|
+
## \u26A1 Core Epistemic Commitments
|
|
6812
|
+
|
|
6813
|
+
1. **Cheaper than not knowing:** Querying Bilmem or resolving uncertainty is significantly cheaper than re-investigating, guessing, or recovering from incorrect architectural assumptions.
|
|
6814
|
+
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.
|
|
6815
|
+
3. **No queries on trivial edits:** Do not query Bilmem for simple typo fixes, formatting, or obvious local syntax changes.
|
|
6816
|
+
4. **Receipts & Honest Confidence:** Bilmem uses explainable confidence tiers (\`low\`, \`medium\`, \`high\`) and returns receipts explaining why knowledge was accepted or rejected.
|
|
6817
|
+
|
|
6818
|
+
---
|
|
6819
|
+
|
|
6820
|
+
## \u{1F680} Progressive Disclosure: Tool Runbook
|
|
6821
|
+
|
|
6822
|
+
> [!TIP]
|
|
6823
|
+
> **Project Directory Target (\`path\` parameter)**:
|
|
6824
|
+
> 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.
|
|
6825
|
+
|
|
6826
|
+
### 1. Before Substantial Implementation: \`bilmem_preflight\`
|
|
6827
|
+
Run \`bilmem_preflight\` with your proposed goal before modifying architecture, core logic, or adding features:
|
|
6828
|
+
\`\`\`json
|
|
6829
|
+
{
|
|
6830
|
+
"path": "/path/to/project",
|
|
6831
|
+
"goal": "Add OAuth login with Google",
|
|
6832
|
+
"positive": ["auth", "oauth", "session"],
|
|
6833
|
+
"negative": ["stateless-jwt"]
|
|
6834
|
+
}
|
|
6835
|
+
\`\`\`
|
|
6836
|
+
**Returns:**
|
|
6837
|
+
- Applicable verified constraints & architectural rules.
|
|
6838
|
+
- Relevant lessons from past implementations or regressions.
|
|
6839
|
+
- Past failures in similar domains.
|
|
6840
|
+
- Blocking unknowns that must be investigated first.
|
|
6841
|
+
- Stale dependencies that need review.
|
|
6842
|
+
- Next recommended investigation actions.
|
|
6843
|
+
|
|
6844
|
+
### 2. When Uncertain or Resolving Contradictions: \`bilmem_resolve\`
|
|
6845
|
+
Use \`bilmem_resolve\` when encountering ambiguous codebase behavior, multiple plausible paths, or conflicting assumptions:
|
|
6846
|
+
\`\`\`json
|
|
6847
|
+
{
|
|
6848
|
+
"path": "/path/to/project",
|
|
6849
|
+
"question": "How are user sessions renewed and validated?",
|
|
6850
|
+
"positive": ["server-session", "postgres", "redis"],
|
|
6851
|
+
"negative": ["client-cookie-only"]
|
|
6852
|
+
}
|
|
6853
|
+
\`\`\`
|
|
6854
|
+
**Returns:**
|
|
6855
|
+
- \`state\`: \`resolved\` (sufficient facts known), \`needs_investigation\`, or \`contradiction\`.
|
|
6856
|
+
- \`known\`: Verified statements applicable to the current context.
|
|
6857
|
+
- \`rejected\`: Inapplicable knowledge units filtered out by negative cues.
|
|
6858
|
+
- \`missing\`: Critical gaps that need observation before proceeding.
|
|
6859
|
+
- \`next\`: The cheapest concrete investigation step.
|
|
6860
|
+
|
|
6861
|
+
### 3. Exact Knowledge Retrieval: \`bilmem_lookup\`
|
|
6862
|
+
Quickly look up specific conventions, lessons, or rules:
|
|
6863
|
+
\`\`\`json
|
|
6864
|
+
{
|
|
6865
|
+
"path": "/path/to/project",
|
|
6866
|
+
"query": "session timeout configuration",
|
|
6867
|
+
"limit": 5
|
|
6868
|
+
}
|
|
6869
|
+
\`\`\`
|
|
6870
|
+
|
|
6871
|
+
### 4. Recording Fresh Discoveries: \`bilmem_observe\`
|
|
6872
|
+
Record verified discoveries, configuration invariants, or environment realities:
|
|
6873
|
+
\`\`\`json
|
|
6874
|
+
{
|
|
6875
|
+
"path": "/path/to/project",
|
|
6876
|
+
"claim": "Production session cookies require SameSite=Strict and Secure flags",
|
|
6877
|
+
"source": "file:src/auth/session.ts#L42",
|
|
6878
|
+
"positive": ["production", "cookie", "auth"],
|
|
6879
|
+
"negative": ["development"]
|
|
6880
|
+
}
|
|
6881
|
+
\`\`\`
|
|
6882
|
+
|
|
6883
|
+
### 5. Learning from Corrections & Trajectories: \`bilmem_learn\`
|
|
6884
|
+
After resolving a difficult bug, fixing a regression, or receiving a critical user correction, distill the trajectory:
|
|
6885
|
+
\`\`\`json
|
|
6886
|
+
{
|
|
6887
|
+
"path": "/path/to/project",
|
|
6888
|
+
"goal": "Fix refresh token rotation loop",
|
|
6889
|
+
"outcome": "success",
|
|
6890
|
+
"userCorrection": {
|
|
6891
|
+
"topic": "session-auth",
|
|
6892
|
+
"incorrectAssumption": "Token refresh does not require a concurrent mutex",
|
|
6893
|
+
"correctFact": "Token refresh requires a concurrency mutex to avoid 401 race conditions"
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
\`\`\`
|
|
6897
|
+
Bilmem runs staged reconciliation: extracts observations, creates candidate lessons, evaluates polar contradictions, and reconciles into durable knowledge.
|
|
6898
|
+
|
|
6899
|
+
### 6. Recording Known Unknowns: \`bilmem_unknown\`
|
|
6900
|
+
When you realize critical context is missing, record an unknown instead of making a silent assumption:
|
|
6901
|
+
\`\`\`json
|
|
6902
|
+
{
|
|
6903
|
+
"path": "/path/to/project",
|
|
6904
|
+
"action": "record",
|
|
6905
|
+
"id": "staging.redis-cache",
|
|
6906
|
+
"question": "Does the staging database share the Redis cache cluster with production?"
|
|
6907
|
+
}
|
|
6908
|
+
\`\`\`
|
|
6909
|
+
|
|
6910
|
+
### 7. Auditing Effectiveness & Reuse: \`bilmem_stats\`
|
|
6911
|
+
Verify how well Bilmem is reducing repeated investigation:
|
|
6912
|
+
\`\`\`json
|
|
6913
|
+
{
|
|
6914
|
+
"path": "/path/to/project",
|
|
6915
|
+
"since": "7d"
|
|
6916
|
+
}
|
|
6917
|
+
\`\`\`
|
|
6918
|
+
|
|
6919
|
+
---
|
|
6920
|
+
|
|
6921
|
+
## \u{1F4BB} CLI Quick Reference
|
|
6922
|
+
|
|
6923
|
+
All tools are also available directly on the command line:
|
|
6924
|
+
|
|
6925
|
+
| Command | Purpose |
|
|
6926
|
+
| :--- | :--- |
|
|
6927
|
+
| \`bilmem preflight "<goal>"\` | Pre-implementation readiness & constraint analysis |
|
|
6928
|
+
| \`bilmem resolve "<question>"\` | Epistemic resolution with context cues & receipts |
|
|
6929
|
+
| \`bilmem lookup "<query>"\` | Fast contextual lookup of verified knowledge |
|
|
6930
|
+
| \`bilmem observe "<claim>" --source <src>\` | Record verified evidence or invariant |
|
|
6931
|
+
| \`bilmem learn --trajectory "<text>"\` | Reconcile candidate lessons into knowledge |
|
|
6932
|
+
| \`bilmem scan [--dry-run]\` | Conservative filesystem & convention discovery |
|
|
6933
|
+
| \`bilmem stats [--since 7d]\` | Measure knowledge reuse and effectiveness |
|
|
6934
|
+
| \`bilmem doctor [--fix]\` | Verify and repair integrations and storage |
|
|
6935
|
+
`;
|
|
6936
|
+
}
|
|
6937
|
+
function hasBilmemSkill(content) {
|
|
6938
|
+
return content.includes("name: bilmem") && content.includes("---");
|
|
6939
|
+
}
|
|
6940
|
+
function extractSkillVersion(content) {
|
|
6941
|
+
const match = content.match(/<!--\s*bilmem-version:\s*(\d+)\s*-->/);
|
|
6942
|
+
if (match && match[1]) {
|
|
6943
|
+
return parseInt(match[1], 10);
|
|
6944
|
+
}
|
|
6945
|
+
return null;
|
|
6946
|
+
}
|
|
5962
6947
|
|
|
5963
6948
|
// src/install/atomic.ts
|
|
5964
6949
|
async function writeAtomic(filePath, content) {
|
|
5965
|
-
const dir =
|
|
5966
|
-
await
|
|
6950
|
+
const dir = path10.dirname(filePath);
|
|
6951
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
5967
6952
|
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
5968
6953
|
try {
|
|
5969
|
-
await
|
|
5970
|
-
await
|
|
6954
|
+
await fs8.writeFile(tmpPath, content, "utf-8");
|
|
6955
|
+
await fs8.rename(tmpPath, filePath);
|
|
5971
6956
|
} catch (err) {
|
|
5972
6957
|
try {
|
|
5973
|
-
await
|
|
6958
|
+
await fs8.unlink(tmpPath);
|
|
5974
6959
|
} catch {
|
|
5975
6960
|
}
|
|
5976
6961
|
throw err;
|
|
@@ -5978,7 +6963,7 @@ async function writeAtomic(filePath, content) {
|
|
|
5978
6963
|
}
|
|
5979
6964
|
async function safeReadJson(filePath) {
|
|
5980
6965
|
try {
|
|
5981
|
-
const raw = await
|
|
6966
|
+
const raw = await fs8.readFile(filePath, "utf-8");
|
|
5982
6967
|
try {
|
|
5983
6968
|
const data = JSON.parse(raw);
|
|
5984
6969
|
return { exists: true, data, raw };
|
|
@@ -6058,7 +7043,7 @@ async function updateMarkdownInstructionFile(filePath, desiredBlock = getAgentIn
|
|
|
6058
7043
|
let existingContent = "";
|
|
6059
7044
|
let exists = false;
|
|
6060
7045
|
try {
|
|
6061
|
-
existingContent = await
|
|
7046
|
+
existingContent = await fs8.readFile(filePath, "utf-8");
|
|
6062
7047
|
exists = true;
|
|
6063
7048
|
} catch (err) {
|
|
6064
7049
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -6093,7 +7078,7 @@ async function updateMarkdownInstructionFile(filePath, desiredBlock = getAgentIn
|
|
|
6093
7078
|
async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
6094
7079
|
let content = "";
|
|
6095
7080
|
try {
|
|
6096
|
-
content = await
|
|
7081
|
+
content = await fs8.readFile(filePath, "utf-8");
|
|
6097
7082
|
} catch (err) {
|
|
6098
7083
|
if (err.code === "ENOENT") {
|
|
6099
7084
|
return { removed: false, fileDeleted: false };
|
|
@@ -6108,7 +7093,7 @@ async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
|
6108
7093
|
if (stripped.length === 0) {
|
|
6109
7094
|
if (!dryRun) {
|
|
6110
7095
|
try {
|
|
6111
|
-
await
|
|
7096
|
+
await fs8.unlink(filePath);
|
|
6112
7097
|
} catch {
|
|
6113
7098
|
}
|
|
6114
7099
|
}
|
|
@@ -6121,21 +7106,57 @@ async function removeMarkdownInstructionFile(filePath, dryRun = false) {
|
|
|
6121
7106
|
}
|
|
6122
7107
|
async function pathExists(p) {
|
|
6123
7108
|
try {
|
|
6124
|
-
await
|
|
7109
|
+
await fs8.access(p);
|
|
6125
7110
|
return true;
|
|
6126
7111
|
} catch {
|
|
6127
7112
|
return false;
|
|
6128
7113
|
}
|
|
6129
7114
|
}
|
|
7115
|
+
async function updateSkillFile(filePath, content, dryRun = false) {
|
|
7116
|
+
let existingContent = "";
|
|
7117
|
+
let exists = false;
|
|
7118
|
+
try {
|
|
7119
|
+
existingContent = await fs8.readFile(filePath, "utf-8");
|
|
7120
|
+
exists = true;
|
|
7121
|
+
} catch (err) {
|
|
7122
|
+
if (err.code !== "ENOENT") throw err;
|
|
7123
|
+
}
|
|
7124
|
+
if (exists && existingContent.trim() === content.trim()) {
|
|
7125
|
+
return { changed: false, created: false, originalExisted: true };
|
|
7126
|
+
}
|
|
7127
|
+
if (!dryRun) {
|
|
7128
|
+
await writeAtomic(filePath, content.trimEnd() + "\n");
|
|
7129
|
+
}
|
|
7130
|
+
return { changed: true, created: !exists, originalExisted: exists };
|
|
7131
|
+
}
|
|
7132
|
+
async function removeSkillFile(filePath, dryRun = false) {
|
|
7133
|
+
try {
|
|
7134
|
+
await fs8.access(filePath);
|
|
7135
|
+
} catch {
|
|
7136
|
+
return { removed: false, fileDeleted: false };
|
|
7137
|
+
}
|
|
7138
|
+
if (!dryRun) {
|
|
7139
|
+
try {
|
|
7140
|
+
await fs8.unlink(filePath);
|
|
7141
|
+
const parentDir = path10.dirname(filePath);
|
|
7142
|
+
const remaining = await fs8.readdir(parentDir);
|
|
7143
|
+
if (remaining.length === 0) {
|
|
7144
|
+
await fs8.rmdir(parentDir);
|
|
7145
|
+
}
|
|
7146
|
+
} catch {
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
return { removed: true, fileDeleted: true };
|
|
7150
|
+
}
|
|
6130
7151
|
async function isExecutableInPath(command) {
|
|
6131
7152
|
const envPath = process.env.PATH;
|
|
6132
7153
|
if (!envPath) return false;
|
|
6133
|
-
const dirs = envPath.split(
|
|
7154
|
+
const dirs = envPath.split(path10.delimiter);
|
|
6134
7155
|
for (const dir of dirs) {
|
|
6135
7156
|
if (!dir) continue;
|
|
6136
|
-
const fullPath =
|
|
7157
|
+
const fullPath = path10.join(dir, command);
|
|
6137
7158
|
try {
|
|
6138
|
-
await
|
|
7159
|
+
await fs8.access(fullPath, fs8.constants.X_OK);
|
|
6139
7160
|
return true;
|
|
6140
7161
|
} catch {
|
|
6141
7162
|
}
|
|
@@ -6144,9 +7165,9 @@ async function isExecutableInPath(command) {
|
|
|
6144
7165
|
}
|
|
6145
7166
|
|
|
6146
7167
|
// src/install/ownership.ts
|
|
6147
|
-
import * as
|
|
7168
|
+
import * as path11 from "path";
|
|
6148
7169
|
function getIntegrationsFilePath(rootDir) {
|
|
6149
|
-
return
|
|
7170
|
+
return path11.join(rootDir, ".bilmem", "integrations.json");
|
|
6150
7171
|
}
|
|
6151
7172
|
async function loadIntegrationsMetadata(rootDir) {
|
|
6152
7173
|
const metaPath = getIntegrationsFilePath(rootDir);
|
|
@@ -6188,6 +7209,7 @@ async function removeIntegration(rootDir, agentId, dryRun = false) {
|
|
|
6188
7209
|
|
|
6189
7210
|
// src/install/adapters/base.ts
|
|
6190
7211
|
var BaseAgentAdapter = class {
|
|
7212
|
+
aliases;
|
|
6191
7213
|
supportedScopes = ["project", "global"];
|
|
6192
7214
|
async inspect(context) {
|
|
6193
7215
|
const configPath = this.resolveConfigPath(context);
|
|
@@ -6201,12 +7223,14 @@ var BaseAgentAdapter = class {
|
|
|
6201
7223
|
if (read.exists && !read.error && read.data?.mcpServers?.bilmem) {
|
|
6202
7224
|
mcpConfigured = true;
|
|
6203
7225
|
const b = read.data.mcpServers.bilmem;
|
|
6204
|
-
|
|
7226
|
+
const targetMatch = b.command === targetCommand && Array.isArray(b.args) && b.args.length === targetArgs.length && b.args.every((v, i) => v === targetArgs[i]);
|
|
7227
|
+
const wrapperMatch = Array.isArray(b.args) && b.args.some((arg) => typeof arg === "string" && arg.includes("bilmem"));
|
|
7228
|
+
mcpCommandMatches = Boolean(targetMatch || wrapperMatch);
|
|
6205
7229
|
}
|
|
6206
7230
|
let instructionInstalled = false;
|
|
6207
7231
|
let instructionVersion;
|
|
6208
7232
|
try {
|
|
6209
|
-
const content = await
|
|
7233
|
+
const content = await fs9.readFile(instructionPath, "utf-8");
|
|
6210
7234
|
instructionInstalled = hasBilmemInstruction(content);
|
|
6211
7235
|
const extracted = extractInstructionVersion(content);
|
|
6212
7236
|
if (extracted !== null) {
|
|
@@ -6214,6 +7238,20 @@ var BaseAgentAdapter = class {
|
|
|
6214
7238
|
}
|
|
6215
7239
|
} catch {
|
|
6216
7240
|
}
|
|
7241
|
+
let skillInstalled = false;
|
|
7242
|
+
let skillVersion;
|
|
7243
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7244
|
+
if (skillPath) {
|
|
7245
|
+
try {
|
|
7246
|
+
const skillContent = await fs9.readFile(skillPath, "utf-8");
|
|
7247
|
+
skillInstalled = hasBilmemSkill(skillContent);
|
|
7248
|
+
const extracted = extractSkillVersion(skillContent);
|
|
7249
|
+
if (extracted !== null) {
|
|
7250
|
+
skillVersion = extracted;
|
|
7251
|
+
}
|
|
7252
|
+
} catch {
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
6217
7255
|
return {
|
|
6218
7256
|
id: this.id,
|
|
6219
7257
|
name: this.name,
|
|
@@ -6223,9 +7261,12 @@ var BaseAgentAdapter = class {
|
|
|
6223
7261
|
mcpCommandMatches,
|
|
6224
7262
|
instructionInstalled,
|
|
6225
7263
|
instructionVersion,
|
|
7264
|
+
skillInstalled: skillPath ? skillInstalled : void 0,
|
|
7265
|
+
skillVersion: skillPath ? skillVersion : void 0,
|
|
6226
7266
|
currentInstructionVersion: CURRENT_INTEGRATION_VERSION,
|
|
6227
7267
|
configPath,
|
|
6228
7268
|
instructionPath,
|
|
7269
|
+
skillPath,
|
|
6229
7270
|
details: {
|
|
6230
7271
|
rawConfigExisted: read.exists,
|
|
6231
7272
|
configError: read.error
|
|
@@ -6245,7 +7286,7 @@ var BaseAgentAdapter = class {
|
|
|
6245
7286
|
const storage = context.storage || new Storage(context.projectDir);
|
|
6246
7287
|
if (!await storage.isInitialized()) {
|
|
6247
7288
|
if (!options.dryRun) {
|
|
6248
|
-
await storage.init({ projectName:
|
|
7289
|
+
await storage.init({ projectName: path12.basename(context.projectDir) });
|
|
6249
7290
|
}
|
|
6250
7291
|
projectInitialized = true;
|
|
6251
7292
|
actionsTaken.push("Initialized .bilmem/ storage");
|
|
@@ -6270,6 +7311,19 @@ var BaseAgentAdapter = class {
|
|
|
6270
7311
|
if (instRes.changed) {
|
|
6271
7312
|
actionsTaken.push(`Installed/updated agent instruction block in ${instructionPath}`);
|
|
6272
7313
|
}
|
|
7314
|
+
let skillChanged = false;
|
|
7315
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7316
|
+
if (skillPath) {
|
|
7317
|
+
const skillRes = await updateSkillFile(
|
|
7318
|
+
skillPath,
|
|
7319
|
+
getBilmemSkillContent(CURRENT_INTEGRATION_VERSION),
|
|
7320
|
+
options.dryRun ?? false
|
|
7321
|
+
);
|
|
7322
|
+
skillChanged = skillRes.changed;
|
|
7323
|
+
if (skillRes.changed) {
|
|
7324
|
+
actionsTaken.push(`Installed/updated Antigravity skill in ${skillPath}`);
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
6273
7327
|
if (context.scope === "project") {
|
|
6274
7328
|
await recordIntegration(
|
|
6275
7329
|
context.projectDir,
|
|
@@ -6277,9 +7331,11 @@ var BaseAgentAdapter = class {
|
|
|
6277
7331
|
{
|
|
6278
7332
|
scope: options.scope,
|
|
6279
7333
|
mcp: true,
|
|
6280
|
-
mcpConfigPath:
|
|
7334
|
+
mcpConfigPath: path12.relative(context.projectDir, configPath),
|
|
6281
7335
|
instructions: true,
|
|
6282
|
-
instructionPath:
|
|
7336
|
+
instructionPath: path12.relative(context.projectDir, instructionPath),
|
|
7337
|
+
skill: skillPath ? true : void 0,
|
|
7338
|
+
skillPath: skillPath ? path12.relative(context.projectDir, skillPath) : void 0,
|
|
6283
7339
|
version: CURRENT_INTEGRATION_VERSION,
|
|
6284
7340
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6285
7341
|
},
|
|
@@ -6290,7 +7346,7 @@ var BaseAgentAdapter = class {
|
|
|
6290
7346
|
if (!resolvable && !options.dryRun) {
|
|
6291
7347
|
warnings.push(`Executable "${targetCommand}" is not in system PATH. Ensure it can be run by ${this.name}.`);
|
|
6292
7348
|
}
|
|
6293
|
-
const changesMade = mcpRes.changed || instRes.changed || projectInitialized;
|
|
7349
|
+
const changesMade = mcpRes.changed || instRes.changed || skillChanged || projectInitialized;
|
|
6294
7350
|
if (!changesMade) {
|
|
6295
7351
|
actionsTaken.push("Already up to date");
|
|
6296
7352
|
}
|
|
@@ -6301,6 +7357,8 @@ var BaseAgentAdapter = class {
|
|
|
6301
7357
|
scope: options.scope,
|
|
6302
7358
|
mcpChanged: mcpRes.changed,
|
|
6303
7359
|
instructionChanged: instRes.changed,
|
|
7360
|
+
skillChanged,
|
|
7361
|
+
skillPath,
|
|
6304
7362
|
projectInitialized,
|
|
6305
7363
|
dryRun: options.dryRun,
|
|
6306
7364
|
actionsTaken,
|
|
@@ -6320,10 +7378,19 @@ var BaseAgentAdapter = class {
|
|
|
6320
7378
|
if (instRes.removed) {
|
|
6321
7379
|
actionsTaken.push(`Removed Bilmem instruction block from ${instructionPath}`);
|
|
6322
7380
|
}
|
|
7381
|
+
let skillRemoved = false;
|
|
7382
|
+
const skillPath = this.resolveSkillPath ? this.resolveSkillPath(context) : void 0;
|
|
7383
|
+
if (skillPath) {
|
|
7384
|
+
const skillRes = await removeSkillFile(skillPath, options.dryRun ?? false);
|
|
7385
|
+
skillRemoved = skillRes.removed;
|
|
7386
|
+
if (skillRes.removed) {
|
|
7387
|
+
actionsTaken.push(`Removed Antigravity skill from ${skillPath}`);
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
6323
7390
|
if (context.scope === "project") {
|
|
6324
7391
|
await removeIntegration(context.projectDir, this.id, options.dryRun ?? false);
|
|
6325
7392
|
}
|
|
6326
|
-
const anyRemoved = mcpRes.removed || instRes.removed;
|
|
7393
|
+
const anyRemoved = mcpRes.removed || instRes.removed || skillRemoved;
|
|
6327
7394
|
if (!anyRemoved) {
|
|
6328
7395
|
actionsTaken.push("No Bilmem configuration found to remove");
|
|
6329
7396
|
}
|
|
@@ -6334,6 +7401,8 @@ var BaseAgentAdapter = class {
|
|
|
6334
7401
|
scope: options.scope,
|
|
6335
7402
|
mcpRemoved: mcpRes.removed,
|
|
6336
7403
|
instructionRemoved: instRes.removed,
|
|
7404
|
+
skillRemoved,
|
|
7405
|
+
skillPath,
|
|
6337
7406
|
dryRun: options.dryRun,
|
|
6338
7407
|
actionsTaken,
|
|
6339
7408
|
message: anyRemoved ? `Successfully uninstalled Bilmem from ${this.name}` : `No Bilmem configuration found for ${this.name}`
|
|
@@ -6345,6 +7414,7 @@ var BaseAgentAdapter = class {
|
|
|
6345
7414
|
const recommendations = [];
|
|
6346
7415
|
let mcpValid = false;
|
|
6347
7416
|
let instructionValid = false;
|
|
7417
|
+
let skillValid = true;
|
|
6348
7418
|
if (!state.mcpConfigured) {
|
|
6349
7419
|
issues.push(`MCP server "bilmem" is not configured in ${state.configPath}`);
|
|
6350
7420
|
recommendations.push(`Run "bilmem install ${this.id}" to configure MCP`);
|
|
@@ -6365,17 +7435,31 @@ var BaseAgentAdapter = class {
|
|
|
6365
7435
|
} else {
|
|
6366
7436
|
instructionValid = true;
|
|
6367
7437
|
}
|
|
7438
|
+
if (state.skillPath) {
|
|
7439
|
+
if (!state.skillInstalled) {
|
|
7440
|
+
skillValid = false;
|
|
7441
|
+
issues.push(`Skill file missing from ${state.skillPath}`);
|
|
7442
|
+
recommendations.push(`Run "bilmem install ${this.id}" to install the skill`);
|
|
7443
|
+
} else if (state.skillVersion !== CURRENT_INTEGRATION_VERSION) {
|
|
7444
|
+
skillValid = false;
|
|
7445
|
+
issues.push(
|
|
7446
|
+
`Skill file version (${state.skillVersion || "legacy"}) is outdated (current: ${CURRENT_INTEGRATION_VERSION})`
|
|
7447
|
+
);
|
|
7448
|
+
recommendations.push(`Run "bilmem doctor --fix" to update the skill`);
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
6368
7451
|
const targetCommand = context.bilmemCommand || "bilmem";
|
|
6369
7452
|
const commandResolvable = await isExecutableInPath(targetCommand);
|
|
6370
7453
|
if (!commandResolvable) {
|
|
6371
7454
|
issues.push(`Command "${targetCommand}" is not found in PATH`);
|
|
6372
7455
|
recommendations.push(`Ensure "${targetCommand}" is installed globally or in your PATH`);
|
|
6373
7456
|
}
|
|
6374
|
-
const valid = mcpValid && instructionValid && commandResolvable;
|
|
7457
|
+
const valid = mcpValid && instructionValid && skillValid && commandResolvable;
|
|
6375
7458
|
return {
|
|
6376
7459
|
valid,
|
|
6377
7460
|
mcpValid,
|
|
6378
7461
|
instructionValid,
|
|
7462
|
+
skillValid,
|
|
6379
7463
|
commandResolvable,
|
|
6380
7464
|
issues,
|
|
6381
7465
|
recommendations
|
|
@@ -6389,23 +7473,23 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6389
7473
|
name = "Cursor";
|
|
6390
7474
|
resolveConfigPath(context) {
|
|
6391
7475
|
if (context.scope === "project") {
|
|
6392
|
-
return
|
|
7476
|
+
return path13.join(context.projectDir, ".cursor", "mcp.json");
|
|
6393
7477
|
}
|
|
6394
7478
|
const home = context.globalDir || os.homedir();
|
|
6395
|
-
return
|
|
7479
|
+
return path13.join(home, ".cursor", "mcp.json");
|
|
6396
7480
|
}
|
|
6397
7481
|
resolveInstructionPath(context) {
|
|
6398
7482
|
if (context.scope === "project") {
|
|
6399
|
-
return
|
|
7483
|
+
return path13.join(context.projectDir, ".cursorrules");
|
|
6400
7484
|
}
|
|
6401
7485
|
const home = context.globalDir || os.homedir();
|
|
6402
|
-
return
|
|
7486
|
+
return path13.join(home, ".cursorrules");
|
|
6403
7487
|
}
|
|
6404
7488
|
async detect(context) {
|
|
6405
7489
|
const reasons = [];
|
|
6406
|
-
const dotCursor =
|
|
6407
|
-
const dotCursorrules =
|
|
6408
|
-
const mcpConfig =
|
|
7490
|
+
const dotCursor = path13.join(context.projectDir, ".cursor");
|
|
7491
|
+
const dotCursorrules = path13.join(context.projectDir, ".cursorrules");
|
|
7492
|
+
const mcpConfig = path13.join(context.projectDir, ".cursor", "mcp.json");
|
|
6409
7493
|
const hasDotCursor = await pathExists(dotCursor);
|
|
6410
7494
|
const hasCursorrules = await pathExists(dotCursorrules);
|
|
6411
7495
|
const hasMcp = await pathExists(mcpConfig);
|
|
@@ -6413,7 +7497,7 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6413
7497
|
if (hasCursorrules) reasons.push("Found .cursorrules file");
|
|
6414
7498
|
if (hasMcp) reasons.push("Found .cursor/mcp.json file");
|
|
6415
7499
|
const globalHome = context.globalDir || os.homedir();
|
|
6416
|
-
const globalCursor =
|
|
7500
|
+
const globalCursor = path13.join(globalHome, ".cursor");
|
|
6417
7501
|
const hasGlobalCursor = await pathExists(globalCursor);
|
|
6418
7502
|
if (hasGlobalCursor) reasons.push(`Found global directory at ${globalCursor}`);
|
|
6419
7503
|
const hasExecutable = await isExecutableInPath("cursor");
|
|
@@ -6430,31 +7514,31 @@ var CursorAdapter = class extends BaseAgentAdapter {
|
|
|
6430
7514
|
};
|
|
6431
7515
|
|
|
6432
7516
|
// src/install/adapters/claude.ts
|
|
6433
|
-
import * as
|
|
7517
|
+
import * as path14 from "path";
|
|
6434
7518
|
import * as os2 from "os";
|
|
6435
7519
|
var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
6436
7520
|
id = "claude";
|
|
6437
7521
|
name = "Claude Code";
|
|
6438
7522
|
resolveConfigPath(context) {
|
|
6439
7523
|
if (context.scope === "project") {
|
|
6440
|
-
return
|
|
7524
|
+
return path14.join(context.projectDir, ".claude", "mcp.json");
|
|
6441
7525
|
}
|
|
6442
7526
|
const home = context.globalDir || os2.homedir();
|
|
6443
|
-
return
|
|
7527
|
+
return path14.join(home, ".claude", "mcp.json");
|
|
6444
7528
|
}
|
|
6445
7529
|
resolveInstructionPath(context) {
|
|
6446
7530
|
if (context.scope === "project") {
|
|
6447
|
-
return
|
|
7531
|
+
return path14.join(context.projectDir, "CLAUDE.md");
|
|
6448
7532
|
}
|
|
6449
7533
|
const home = context.globalDir || os2.homedir();
|
|
6450
|
-
return
|
|
7534
|
+
return path14.join(home, ".claude", "CLAUDE.md");
|
|
6451
7535
|
}
|
|
6452
7536
|
async detect(context) {
|
|
6453
7537
|
const reasons = [];
|
|
6454
|
-
const dotClaude =
|
|
6455
|
-
const claudeMd =
|
|
6456
|
-
const claudeJson =
|
|
6457
|
-
const mcpConfig =
|
|
7538
|
+
const dotClaude = path14.join(context.projectDir, ".claude");
|
|
7539
|
+
const claudeMd = path14.join(context.projectDir, "CLAUDE.md");
|
|
7540
|
+
const claudeJson = path14.join(context.projectDir, "claude.json");
|
|
7541
|
+
const mcpConfig = path14.join(context.projectDir, ".claude", "mcp.json");
|
|
6458
7542
|
const hasDotClaude = await pathExists(dotClaude);
|
|
6459
7543
|
const hasClaudeMd = await pathExists(claudeMd);
|
|
6460
7544
|
const hasClaudeJson = await pathExists(claudeJson);
|
|
@@ -6464,7 +7548,7 @@ var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
|
6464
7548
|
if (hasClaudeJson) reasons.push("Found claude.json file");
|
|
6465
7549
|
if (hasMcp) reasons.push("Found .claude/mcp.json file");
|
|
6466
7550
|
const globalHome = context.globalDir || os2.homedir();
|
|
6467
|
-
const globalClaude =
|
|
7551
|
+
const globalClaude = path14.join(globalHome, ".claude");
|
|
6468
7552
|
const hasGlobalClaude = await pathExists(globalClaude);
|
|
6469
7553
|
if (hasGlobalClaude) reasons.push(`Found global directory at ${globalClaude}`);
|
|
6470
7554
|
const hasExecutable = await isExecutableInPath("claude");
|
|
@@ -6481,31 +7565,31 @@ var ClaudeAdapter = class extends BaseAgentAdapter {
|
|
|
6481
7565
|
};
|
|
6482
7566
|
|
|
6483
7567
|
// src/install/adapters/codex.ts
|
|
6484
|
-
import * as
|
|
7568
|
+
import * as path15 from "path";
|
|
6485
7569
|
import * as os3 from "os";
|
|
6486
7570
|
var CodexAdapter = class extends BaseAgentAdapter {
|
|
6487
7571
|
id = "codex";
|
|
6488
7572
|
name = "Codex";
|
|
6489
7573
|
resolveConfigPath(context) {
|
|
6490
7574
|
if (context.scope === "project") {
|
|
6491
|
-
return
|
|
7575
|
+
return path15.join(context.projectDir, ".codex", "mcp.json");
|
|
6492
7576
|
}
|
|
6493
7577
|
const home = context.globalDir || os3.homedir();
|
|
6494
|
-
return
|
|
7578
|
+
return path15.join(home, ".codex", "mcp.json");
|
|
6495
7579
|
}
|
|
6496
7580
|
resolveInstructionPath(context) {
|
|
6497
7581
|
if (context.scope === "project") {
|
|
6498
|
-
return
|
|
7582
|
+
return path15.join(context.projectDir, "CODEX.md");
|
|
6499
7583
|
}
|
|
6500
7584
|
const home = context.globalDir || os3.homedir();
|
|
6501
|
-
return
|
|
7585
|
+
return path15.join(home, ".codex", "instructions.md");
|
|
6502
7586
|
}
|
|
6503
7587
|
async detect(context) {
|
|
6504
7588
|
const reasons = [];
|
|
6505
|
-
const dotCodex =
|
|
6506
|
-
const codexMd =
|
|
6507
|
-
const codexJson =
|
|
6508
|
-
const mcpConfig =
|
|
7589
|
+
const dotCodex = path15.join(context.projectDir, ".codex");
|
|
7590
|
+
const codexMd = path15.join(context.projectDir, "CODEX.md");
|
|
7591
|
+
const codexJson = path15.join(context.projectDir, "codex.json");
|
|
7592
|
+
const mcpConfig = path15.join(context.projectDir, ".codex", "mcp.json");
|
|
6509
7593
|
const hasDotCodex = await pathExists(dotCodex);
|
|
6510
7594
|
const hasCodexMd = await pathExists(codexMd);
|
|
6511
7595
|
const hasCodexJson = await pathExists(codexJson);
|
|
@@ -6515,7 +7599,7 @@ var CodexAdapter = class extends BaseAgentAdapter {
|
|
|
6515
7599
|
if (hasCodexJson) reasons.push("Found codex.json file");
|
|
6516
7600
|
if (hasMcp) reasons.push("Found .codex/mcp.json file");
|
|
6517
7601
|
const globalHome = context.globalDir || os3.homedir();
|
|
6518
|
-
const globalCodex =
|
|
7602
|
+
const globalCodex = path15.join(globalHome, ".codex");
|
|
6519
7603
|
const hasGlobalCodex = await pathExists(globalCodex);
|
|
6520
7604
|
if (hasGlobalCodex) reasons.push(`Found global directory at ${globalCodex}`);
|
|
6521
7605
|
const hasExecutable = await isExecutableInPath("codex");
|
|
@@ -6532,31 +7616,31 @@ var CodexAdapter = class extends BaseAgentAdapter {
|
|
|
6532
7616
|
};
|
|
6533
7617
|
|
|
6534
7618
|
// src/install/adapters/gemini.ts
|
|
6535
|
-
import * as
|
|
7619
|
+
import * as path16 from "path";
|
|
6536
7620
|
import * as os4 from "os";
|
|
6537
7621
|
var GeminiAdapter = class extends BaseAgentAdapter {
|
|
6538
7622
|
id = "gemini";
|
|
6539
7623
|
name = "Gemini CLI";
|
|
6540
7624
|
resolveConfigPath(context) {
|
|
6541
7625
|
if (context.scope === "project") {
|
|
6542
|
-
return
|
|
7626
|
+
return path16.join(context.projectDir, ".gemini", "settings.json");
|
|
6543
7627
|
}
|
|
6544
7628
|
const home = context.globalDir || os4.homedir();
|
|
6545
|
-
return
|
|
7629
|
+
return path16.join(home, ".gemini", "settings.json");
|
|
6546
7630
|
}
|
|
6547
7631
|
resolveInstructionPath(context) {
|
|
6548
7632
|
if (context.scope === "project") {
|
|
6549
|
-
return
|
|
7633
|
+
return path16.join(context.projectDir, "GEMINI.md");
|
|
6550
7634
|
}
|
|
6551
7635
|
const home = context.globalDir || os4.homedir();
|
|
6552
|
-
return
|
|
7636
|
+
return path16.join(home, ".gemini", "GEMINI.md");
|
|
6553
7637
|
}
|
|
6554
7638
|
async detect(context) {
|
|
6555
7639
|
const reasons = [];
|
|
6556
|
-
const dotGemini =
|
|
6557
|
-
const geminiMd =
|
|
6558
|
-
const geminiJson =
|
|
6559
|
-
const settingsJson =
|
|
7640
|
+
const dotGemini = path16.join(context.projectDir, ".gemini");
|
|
7641
|
+
const geminiMd = path16.join(context.projectDir, "GEMINI.md");
|
|
7642
|
+
const geminiJson = path16.join(context.projectDir, "gemini.json");
|
|
7643
|
+
const settingsJson = path16.join(context.projectDir, ".gemini", "settings.json");
|
|
6560
7644
|
const hasDotGemini = await pathExists(dotGemini);
|
|
6561
7645
|
const hasGeminiMd = await pathExists(geminiMd);
|
|
6562
7646
|
const hasGeminiJson = await pathExists(geminiJson);
|
|
@@ -6566,7 +7650,7 @@ var GeminiAdapter = class extends BaseAgentAdapter {
|
|
|
6566
7650
|
if (hasGeminiJson) reasons.push("Found gemini.json file");
|
|
6567
7651
|
if (hasSettingsJson) reasons.push("Found .gemini/settings.json file");
|
|
6568
7652
|
const globalHome = context.globalDir || os4.homedir();
|
|
6569
|
-
const globalGemini =
|
|
7653
|
+
const globalGemini = path16.join(globalHome, ".gemini");
|
|
6570
7654
|
const hasGlobalGemini = await pathExists(globalGemini);
|
|
6571
7655
|
if (hasGlobalGemini) reasons.push(`Found global directory at ${globalGemini}`);
|
|
6572
7656
|
const hasExecutable = await isExecutableInPath("gemini");
|
|
@@ -6582,12 +7666,110 @@ var GeminiAdapter = class extends BaseAgentAdapter {
|
|
|
6582
7666
|
}
|
|
6583
7667
|
};
|
|
6584
7668
|
|
|
7669
|
+
// src/install/adapters/antigravity.ts
|
|
7670
|
+
import * as path17 from "path";
|
|
7671
|
+
import * as os5 from "os";
|
|
7672
|
+
import * as fs10 from "fs";
|
|
7673
|
+
function resolveAntigravityGlobalDir(context) {
|
|
7674
|
+
const home = context.globalDir || os5.homedir();
|
|
7675
|
+
if (path17.basename(home) === "config" && path17.basename(path17.dirname(home)) === ".gemini") {
|
|
7676
|
+
return home;
|
|
7677
|
+
}
|
|
7678
|
+
return path17.join(home, ".gemini", "config");
|
|
7679
|
+
}
|
|
7680
|
+
var AntigravityAdapter = class extends BaseAgentAdapter {
|
|
7681
|
+
id = "antigravity";
|
|
7682
|
+
name = "Antigravity";
|
|
7683
|
+
resolveConfigPath(context) {
|
|
7684
|
+
if (context.scope === "project") {
|
|
7685
|
+
const rootMcp = path17.join(context.projectDir, "mcp_config.json");
|
|
7686
|
+
if (fs10.existsSync(rootMcp)) {
|
|
7687
|
+
return rootMcp;
|
|
7688
|
+
}
|
|
7689
|
+
return path17.join(context.projectDir, ".agents", "mcp_config.json");
|
|
7690
|
+
}
|
|
7691
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7692
|
+
return path17.join(globalRoot, "mcp_config.json");
|
|
7693
|
+
}
|
|
7694
|
+
resolveSkillPath(context) {
|
|
7695
|
+
if (context.scope === "project") {
|
|
7696
|
+
return path17.join(context.projectDir, ".agents", "skills", "bilmem", "SKILL.md");
|
|
7697
|
+
}
|
|
7698
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7699
|
+
return path17.join(globalRoot, "skills", "bilmem", "SKILL.md");
|
|
7700
|
+
}
|
|
7701
|
+
resolveInstructionPath(context) {
|
|
7702
|
+
if (context.scope === "project") {
|
|
7703
|
+
const geminiMd = path17.join(context.projectDir, "GEMINI.md");
|
|
7704
|
+
const agentsMd = path17.join(context.projectDir, "AGENTS.md");
|
|
7705
|
+
const rulesBilmem = path17.join(context.projectDir, ".agents", "rules", "bilmem.md");
|
|
7706
|
+
if (fs10.existsSync(rulesBilmem)) {
|
|
7707
|
+
return rulesBilmem;
|
|
7708
|
+
}
|
|
7709
|
+
if (fs10.existsSync(geminiMd)) {
|
|
7710
|
+
return geminiMd;
|
|
7711
|
+
}
|
|
7712
|
+
if (fs10.existsSync(agentsMd)) {
|
|
7713
|
+
return agentsMd;
|
|
7714
|
+
}
|
|
7715
|
+
return path17.join(context.projectDir, ".agents", "rules", "bilmem.md");
|
|
7716
|
+
}
|
|
7717
|
+
const globalRoot = resolveAntigravityGlobalDir(context);
|
|
7718
|
+
return path17.join(globalRoot, "rules", "bilmem.md");
|
|
7719
|
+
}
|
|
7720
|
+
async detect(context) {
|
|
7721
|
+
const reasons = [];
|
|
7722
|
+
const dotAgents = path17.join(context.projectDir, ".agents");
|
|
7723
|
+
const skillsDir = path17.join(context.projectDir, ".agents", "skills");
|
|
7724
|
+
const agentsMcp = path17.join(context.projectDir, ".agents", "mcp_config.json");
|
|
7725
|
+
const rootMcp = path17.join(context.projectDir, "mcp_config.json");
|
|
7726
|
+
const geminiMd = path17.join(context.projectDir, "GEMINI.md");
|
|
7727
|
+
const agentsMd = path17.join(context.projectDir, "AGENTS.md");
|
|
7728
|
+
const hasDotAgents = await pathExists(dotAgents);
|
|
7729
|
+
const hasSkillsDir = await pathExists(skillsDir);
|
|
7730
|
+
const hasAgentsMcp = await pathExists(agentsMcp);
|
|
7731
|
+
const hasRootMcp = await pathExists(rootMcp);
|
|
7732
|
+
const hasGeminiMd = await pathExists(geminiMd);
|
|
7733
|
+
const hasAgentsMd = await pathExists(agentsMd);
|
|
7734
|
+
if (hasDotAgents) reasons.push("Found .agents/ directory");
|
|
7735
|
+
if (hasSkillsDir) reasons.push("Found .agents/skills/ directory");
|
|
7736
|
+
if (hasAgentsMcp) reasons.push("Found .agents/mcp_config.json");
|
|
7737
|
+
if (hasRootMcp) reasons.push("Found mcp_config.json file");
|
|
7738
|
+
if (hasGeminiMd) reasons.push("Found GEMINI.md rule file");
|
|
7739
|
+
if (hasAgentsMd) reasons.push("Found AGENTS.md rule file");
|
|
7740
|
+
const home = context.globalDir || os5.homedir();
|
|
7741
|
+
const globalConfig = resolveAntigravityGlobalDir(context);
|
|
7742
|
+
const globalIde = path17.join(home, ".gemini", "antigravity-ide");
|
|
7743
|
+
const globalCli = path17.join(home, ".gemini", "antigravity-cli");
|
|
7744
|
+
const globalAgy = path17.join(home, ".gemini", "antigravity");
|
|
7745
|
+
const hasGlobalConfig = await pathExists(globalConfig);
|
|
7746
|
+
const hasGlobalIde = await pathExists(globalIde);
|
|
7747
|
+
const hasGlobalCli = await pathExists(globalCli);
|
|
7748
|
+
const hasGlobalAgy = await pathExists(globalAgy);
|
|
7749
|
+
if (hasGlobalConfig) reasons.push(`Found Antigravity global config at ${globalConfig}`);
|
|
7750
|
+
if (hasGlobalIde) reasons.push(`Found Antigravity IDE at ${globalIde}`);
|
|
7751
|
+
if (hasGlobalCli) reasons.push(`Found Antigravity CLI at ${globalCli}`);
|
|
7752
|
+
if (hasGlobalAgy) reasons.push(`Found Antigravity directory at ${globalAgy}`);
|
|
7753
|
+
const hasAgyExec = await isExecutableInPath("agy");
|
|
7754
|
+
if (hasAgyExec) reasons.push('Found "agy" command in PATH');
|
|
7755
|
+
const detected = reasons.length > 0;
|
|
7756
|
+
const confidence = hasDotAgents || hasSkillsDir || hasAgentsMcp || hasRootMcp || hasGeminiMd || hasAgentsMd ? "high" : hasGlobalConfig || hasGlobalIde || hasGlobalCli || hasGlobalAgy || hasAgyExec ? "medium" : "low";
|
|
7757
|
+
return {
|
|
7758
|
+
detected,
|
|
7759
|
+
confidence: detected ? confidence : "low",
|
|
7760
|
+
reasons,
|
|
7761
|
+
supportedScopes: this.supportedScopes
|
|
7762
|
+
};
|
|
7763
|
+
}
|
|
7764
|
+
};
|
|
7765
|
+
|
|
6585
7766
|
// src/install/registry.ts
|
|
6586
7767
|
var adapters = [
|
|
6587
7768
|
new CursorAdapter(),
|
|
6588
7769
|
new ClaudeAdapter(),
|
|
6589
7770
|
new CodexAdapter(),
|
|
6590
|
-
new GeminiAdapter()
|
|
7771
|
+
new GeminiAdapter(),
|
|
7772
|
+
new AntigravityAdapter()
|
|
6591
7773
|
];
|
|
6592
7774
|
function getAllAdapters() {
|
|
6593
7775
|
return [...adapters];
|
|
@@ -6607,7 +7789,7 @@ async function detectAgents(context) {
|
|
|
6607
7789
|
}
|
|
6608
7790
|
|
|
6609
7791
|
// src/install/doctor.ts
|
|
6610
|
-
import * as
|
|
7792
|
+
import * as path18 from "path";
|
|
6611
7793
|
async function runDoctor(context) {
|
|
6612
7794
|
const storage = context.storage || new Storage(context.projectDir);
|
|
6613
7795
|
let isInit = false;
|
|
@@ -6699,6 +7881,26 @@ async function runDoctor(context) {
|
|
|
6699
7881
|
canFix = true;
|
|
6700
7882
|
}
|
|
6701
7883
|
}
|
|
7884
|
+
let skillStatus = "none";
|
|
7885
|
+
let skillDetails;
|
|
7886
|
+
if (state.skillPath) {
|
|
7887
|
+
if (state.skillInstalled) {
|
|
7888
|
+
if (state.skillVersion === CURRENT_INTEGRATION_VERSION) {
|
|
7889
|
+
skillStatus = "ok";
|
|
7890
|
+
} else {
|
|
7891
|
+
skillStatus = "outdated";
|
|
7892
|
+
skillDetails = `Version ${state.skillVersion || "legacy"} (current: ${CURRENT_INTEGRATION_VERSION})`;
|
|
7893
|
+
issuesCount++;
|
|
7894
|
+
canFix = true;
|
|
7895
|
+
}
|
|
7896
|
+
} else {
|
|
7897
|
+
skillStatus = "missing";
|
|
7898
|
+
if (state.detected) {
|
|
7899
|
+
issuesCount++;
|
|
7900
|
+
canFix = true;
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
}
|
|
6702
7904
|
let cmdStatus = "ok";
|
|
6703
7905
|
let cmdDetails;
|
|
6704
7906
|
if (!commandResolvable) {
|
|
@@ -6712,9 +7914,11 @@ async function runDoctor(context) {
|
|
|
6712
7914
|
scope: context.scope,
|
|
6713
7915
|
mcp: { status: mcpStatus, details: mcpDetails },
|
|
6714
7916
|
instructions: { status: instStatus, details: instDetails },
|
|
7917
|
+
skill: state.skillPath ? { status: skillStatus, details: skillDetails } : void 0,
|
|
6715
7918
|
command: { status: cmdStatus, details: cmdDetails },
|
|
6716
7919
|
configPath: state.configPath,
|
|
6717
|
-
instructionPath: state.instructionPath
|
|
7920
|
+
instructionPath: state.instructionPath,
|
|
7921
|
+
skillPath: state.skillPath
|
|
6718
7922
|
});
|
|
6719
7923
|
}
|
|
6720
7924
|
if (!core.storage.initialized) {
|
|
@@ -6734,18 +7938,19 @@ async function runDoctorFix(context, dryRun = false) {
|
|
|
6734
7938
|
const agentsFixed = {};
|
|
6735
7939
|
if (!await storage.isInitialized()) {
|
|
6736
7940
|
if (!dryRun) {
|
|
6737
|
-
await storage.init({ projectName:
|
|
7941
|
+
await storage.init({ projectName: path18.basename(context.projectDir) });
|
|
6738
7942
|
}
|
|
6739
7943
|
coreFixed.push("Initialized .bilmem/ storage repository");
|
|
6740
7944
|
}
|
|
6741
7945
|
const adapters2 = getAllAdapters();
|
|
6742
7946
|
for (const adapter of adapters2) {
|
|
6743
7947
|
const state = await adapter.inspect(context);
|
|
6744
|
-
const shouldFix = state.detected || state.mcpConfigured || state.instructionInstalled;
|
|
7948
|
+
const shouldFix = state.detected || state.mcpConfigured || state.instructionInstalled || Boolean(state.skillInstalled);
|
|
6745
7949
|
if (!shouldFix) continue;
|
|
6746
7950
|
const needsMcpFix = !state.mcpConfigured || !state.mcpCommandMatches;
|
|
6747
7951
|
const needsInstFix = !state.instructionInstalled || state.instructionVersion !== CURRENT_INTEGRATION_VERSION;
|
|
6748
|
-
|
|
7952
|
+
const needsSkillFix = Boolean(state.skillPath) && (!state.skillInstalled || state.skillVersion !== CURRENT_INTEGRATION_VERSION);
|
|
7953
|
+
if (needsMcpFix || needsInstFix || needsSkillFix) {
|
|
6749
7954
|
const fixResult = await adapter.install(context, {
|
|
6750
7955
|
scope: context.scope,
|
|
6751
7956
|
dryRun
|
|
@@ -6852,6 +8057,10 @@ function formatDoctorReport(report) {
|
|
|
6852
8057
|
lines.push(` MCP ${mcpMark}${agent.mcp.details ? ` (${agent.mcp.details})` : ""}`);
|
|
6853
8058
|
const instMark = agent.instructions.status === "ok" ? "\u2713" : agent.instructions.status === "outdated" ? "!" : agent.instructions.status === "missing" ? "\u2717" : "\u25CB";
|
|
6854
8059
|
lines.push(` Instructions ${instMark}${agent.instructions.details ? ` (${agent.instructions.details})` : ""}`);
|
|
8060
|
+
if (agent.skillPath) {
|
|
8061
|
+
const skillMark = agent.skill?.status === "ok" ? "\u2713" : agent.skill?.status === "outdated" ? "!" : agent.skill?.status === "missing" ? "\u2717" : "\u25CB";
|
|
8062
|
+
lines.push(` Skill ${skillMark}${agent.skill?.details ? ` (${agent.skill?.details})` : ""}`);
|
|
8063
|
+
}
|
|
6855
8064
|
const cmdMark = agent.command.status === "ok" ? "\u2713" : "!";
|
|
6856
8065
|
lines.push(` Connection ${cmdMark}${agent.command.details ? ` (${agent.command.details})` : ""}`);
|
|
6857
8066
|
}
|
|
@@ -6896,7 +8105,7 @@ function formatDoctorFixResult(fixed) {
|
|
|
6896
8105
|
|
|
6897
8106
|
// src/install/index.ts
|
|
6898
8107
|
function createInstallContext(options) {
|
|
6899
|
-
const projectDir =
|
|
8108
|
+
const projectDir = path19.resolve(options.projectDir || process.cwd());
|
|
6900
8109
|
const scope = options.global || options.scope === "global" ? "global" : "project";
|
|
6901
8110
|
return {
|
|
6902
8111
|
projectDir,
|
|
@@ -6917,7 +8126,7 @@ async function handleInstallCommand(target, options = {}) {
|
|
|
6917
8126
|
if (target) {
|
|
6918
8127
|
const adapter = getAdapter(target);
|
|
6919
8128
|
if (!adapter) {
|
|
6920
|
-
const valid = getAllAdapters().map((a) => a.id).join(", ");
|
|
8129
|
+
const valid = [...getAllAdapters().map((a) => a.id), "gemini"].join(", ");
|
|
6921
8130
|
throw new Error(`Unknown agent/editor target "${target}". Supported targets: ${valid}`);
|
|
6922
8131
|
}
|
|
6923
8132
|
const result = await adapter.install(context, installOptions);
|
|
@@ -6999,7 +8208,7 @@ async function handleUninstallCommand(target, options = {}) {
|
|
|
6999
8208
|
const context = createInstallContext(options);
|
|
7000
8209
|
const adapter = getAdapter(target);
|
|
7001
8210
|
if (!adapter) {
|
|
7002
|
-
const valid = getAllAdapters().map((a) => a.id).join(", ");
|
|
8211
|
+
const valid = [...getAllAdapters().map((a) => a.id), "gemini"].join(", ");
|
|
7003
8212
|
throw new Error(`Unknown agent/editor target "${target}". Supported targets: ${valid}`);
|
|
7004
8213
|
}
|
|
7005
8214
|
const uninstallOptions = {
|
|
@@ -7031,10 +8240,10 @@ async function getReadyStorage(cwd = process.cwd()) {
|
|
|
7031
8240
|
return storage;
|
|
7032
8241
|
}
|
|
7033
8242
|
program.command("init [path]").description("Initialize bilmem knowledge repository (.bilmem/)").option("--json", "Output result in JSON format").action(async (targetPath, opts) => {
|
|
7034
|
-
const cwd = targetPath ?
|
|
8243
|
+
const cwd = targetPath ? path20.resolve(targetPath) : process.cwd();
|
|
7035
8244
|
const storage = new Storage(cwd);
|
|
7036
8245
|
const config = await storage.init({
|
|
7037
|
-
projectName:
|
|
8246
|
+
projectName: path20.basename(cwd)
|
|
7038
8247
|
});
|
|
7039
8248
|
if (opts.json) {
|
|
7040
8249
|
console.log(JSON.stringify({ status: "initialized", config }, null, 2));
|
|
@@ -7044,7 +8253,7 @@ program.command("init [path]").description("Initialize bilmem knowledge reposito
|
|
|
7044
8253
|
}
|
|
7045
8254
|
});
|
|
7046
8255
|
program.command("scan [path]").description("Scan filesystem, markdown, and git history for reusable evidence and knowledge").option("-i, --incremental", "Incremental scan from last scanned git commit").option("--dry-run", "Preview findings and reconciliation actions without saving").option("--json", "Output result in JSON format").action(async (targetPath, opts) => {
|
|
7047
|
-
const cwd = targetPath ?
|
|
8256
|
+
const cwd = targetPath ? path20.resolve(targetPath) : process.cwd();
|
|
7048
8257
|
const report = await scanRepository(cwd, {
|
|
7049
8258
|
incremental: opts.incremental ?? false,
|
|
7050
8259
|
dryRun: opts.dryRun ?? false
|
|
@@ -7120,7 +8329,30 @@ program.command("resolve <question>").description("Resolve an epistemic question
|
|
|
7120
8329
|
if (result.fallback) {
|
|
7121
8330
|
output.fallback = result.fallback;
|
|
7122
8331
|
}
|
|
7123
|
-
console.log(
|
|
8332
|
+
console.log(YAML5.stringify(output).trim());
|
|
8333
|
+
});
|
|
8334
|
+
program.command("preflight <goal>").description("Analyze existing knowledge, constraints, lessons, and blocking unknowns before starting work").option("-p, --positive <cues...>", "Contextual positive cues (e.g. -p auth server)").option("-n, --negative <cues...>", "Contextual negative cues (e.g. -n client-side)").option("-s, --scope <scope>", "Knowledge scope (defaults to project)").option("--deep", "Perform targeted git diff / file staleness check").option("--json", "Output result in JSON format").action(async (goal, opts) => {
|
|
8335
|
+
const storage = await getReadyStorage();
|
|
8336
|
+
const all = await storage.getAllKnowledge();
|
|
8337
|
+
const index = new SearchIndex();
|
|
8338
|
+
await index.buildIndex(all);
|
|
8339
|
+
const retriever = new Retriever(index, storage);
|
|
8340
|
+
const result = await executePreflight(
|
|
8341
|
+
{
|
|
8342
|
+
goal,
|
|
8343
|
+
positive: opts.positive,
|
|
8344
|
+
negative: opts.negative,
|
|
8345
|
+
scope: opts.scope,
|
|
8346
|
+
deep: opts.deep ?? false
|
|
8347
|
+
},
|
|
8348
|
+
storage,
|
|
8349
|
+
retriever
|
|
8350
|
+
);
|
|
8351
|
+
if (opts.json) {
|
|
8352
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8353
|
+
return;
|
|
8354
|
+
}
|
|
8355
|
+
console.log(formatPreflightReport(result).trim());
|
|
7124
8356
|
});
|
|
7125
8357
|
program.command("lookup [query]").description("Lookup knowledge using candidate retrieval and JEV contextual reranking").option("-p, --positive <cues...>", "Contextual positive cues (e.g. -p node esm)").option("-n, --negative <cues...>", "Contextual negative cues (e.g. -n commonjs)").option("-s, --scope <scope>", "Query scope (defaults to project)").option("-l, --limit <n>", "Maximum results to return", "5").option("--all", "Include rejected candidates in results").option("--json", "Output result in JSON format").action(async (query, opts) => {
|
|
7126
8358
|
const storage = await getReadyStorage();
|
|
@@ -7221,14 +8453,14 @@ program.command("unknown").description("List first-class unknowns and unresolvab
|
|
|
7221
8453
|
});
|
|
7222
8454
|
program.command("import <targetPath>").description("Smart import knowledge from YAML file or directory with reconciliation").option("--dry-run", "Preview changes without saving").option("--json", "Output result in JSON format").action(async (targetPath, opts) => {
|
|
7223
8455
|
const storage = await getReadyStorage();
|
|
7224
|
-
const resolvedPath =
|
|
7225
|
-
const stats = await
|
|
8456
|
+
const resolvedPath = path20.resolve(targetPath);
|
|
8457
|
+
const stats = await fs11.stat(resolvedPath);
|
|
7226
8458
|
const filesToImport = [];
|
|
7227
8459
|
if (stats.isDirectory()) {
|
|
7228
|
-
const files = await
|
|
8460
|
+
const files = await fs11.readdir(resolvedPath);
|
|
7229
8461
|
for (const f of files) {
|
|
7230
8462
|
if (f.endsWith(".yml") || f.endsWith(".yaml")) {
|
|
7231
|
-
filesToImport.push(
|
|
8463
|
+
filesToImport.push(path20.join(resolvedPath, f));
|
|
7232
8464
|
}
|
|
7233
8465
|
}
|
|
7234
8466
|
} else {
|
|
@@ -7238,7 +8470,7 @@ program.command("import <targetPath>").description("Smart import knowledge from
|
|
|
7238
8470
|
let totalUpdated = 0;
|
|
7239
8471
|
const allResults = [];
|
|
7240
8472
|
for (const filePath of filesToImport) {
|
|
7241
|
-
const content = await
|
|
8473
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
7242
8474
|
const report = await importKnowledgeFromYaml(content, storage);
|
|
7243
8475
|
totalCreated += report.created.length;
|
|
7244
8476
|
totalUpdated += report.updated.length;
|
|
@@ -7260,7 +8492,7 @@ program.command("export").description("Export active knowledge to human-readable
|
|
|
7260
8492
|
const items = await storage.getAllKnowledge();
|
|
7261
8493
|
const yamlStr = exportKnowledgeToYaml(items, { scope: opts.scope });
|
|
7262
8494
|
if (opts.out) {
|
|
7263
|
-
await
|
|
8495
|
+
await fs11.writeFile(path20.resolve(opts.out), yamlStr, "utf-8");
|
|
7264
8496
|
console.log(`Exported ${items.length} items to ${opts.out}`);
|
|
7265
8497
|
} else {
|
|
7266
8498
|
process.stdout.write(yamlStr);
|
|
@@ -7269,13 +8501,13 @@ program.command("export").description("Export active knowledge to human-readable
|
|
|
7269
8501
|
program.command("dump <file>").description("Dump complete portable snapshot sufficient for restoration").action(async (file) => {
|
|
7270
8502
|
const storage = await getReadyStorage();
|
|
7271
8503
|
const dumpStr = await dumpToYaml(storage);
|
|
7272
|
-
const dest =
|
|
7273
|
-
await
|
|
8504
|
+
const dest = path20.resolve(file);
|
|
8505
|
+
await fs11.writeFile(dest, dumpStr, "utf-8");
|
|
7274
8506
|
console.log(`Complete bilmem snapshot dumped to ${dest}`);
|
|
7275
8507
|
});
|
|
7276
8508
|
program.command("restore <file>").description("Deterministic restoration from backup dump without reinterpreting").action(async (file) => {
|
|
7277
8509
|
const storage = await getReadyStorage();
|
|
7278
|
-
const content = await
|
|
8510
|
+
const content = await fs11.readFile(path20.resolve(file), "utf-8");
|
|
7279
8511
|
const result = await restoreFromYaml(content, storage);
|
|
7280
8512
|
console.log(`Restored ${result.restoredKnowledgeCount} knowledge items and ${result.restoredUnknownsCount} unknowns from ${file}`);
|
|
7281
8513
|
});
|
|
@@ -7297,7 +8529,7 @@ program.command("compact").description("Compact knowledge base: merge duplicates
|
|
|
7297
8529
|
});
|
|
7298
8530
|
program.command("learn <trajectoryFile>").description("Learn from an agent trajectory JSON following the staged pipeline (trajectory -> obs -> candidate -> reconcile -> knowledge)").option("--dry-run", "Preview extracted candidate lessons and planned reconciliation without modifying storage").option("--json", "Output result in JSON format").action(async (trajectoryFile, opts) => {
|
|
7299
8531
|
const storage = await getReadyStorage();
|
|
7300
|
-
const raw = await
|
|
8532
|
+
const raw = await fs11.readFile(path20.resolve(trajectoryFile), "utf-8");
|
|
7301
8533
|
const trajectory = JSON.parse(raw);
|
|
7302
8534
|
const existingKnowledge = await storage.getAllKnowledge();
|
|
7303
8535
|
const result = learnFromTrajectory(trajectory, existingKnowledge, {
|
|
@@ -7387,8 +8619,9 @@ program.command("status").description("Display bilmem status, metrics, and summa
|
|
|
7387
8619
|
console.log(`- Metrics: ${metrics.knowledgeHits} hits, ${metrics.knowledgeMisses} misses, ${metrics.knowledgeReused} reused, ${metrics.repeatedInvestigationsPrevented} investigations prevented`);
|
|
7388
8620
|
}
|
|
7389
8621
|
});
|
|
7390
|
-
program.command("mcp").description("Run bilmem MCP server powered by mcponce").allowUnknownOption(true).action(async () => {
|
|
7391
|
-
const
|
|
8622
|
+
program.command("mcp [path]").description("Run bilmem MCP server powered by mcponce").option("-r, --root <path>", "Root project directory for bilmem storage").allowUnknownOption(true).action(async (pathArg, opts) => {
|
|
8623
|
+
const rootDir = opts.root || pathArg;
|
|
8624
|
+
const app = createBilmemMcpApp({ rootDir });
|
|
7392
8625
|
await app.run();
|
|
7393
8626
|
});
|
|
7394
8627
|
var ruleCommand = program.command("rule").alias("rules").description("Manage declarative YAML rule sets and guardrails (.bilmem/rules/*.yml, rules.yml)");
|
|
@@ -7447,7 +8680,7 @@ ruleCommand.command("add <statement>").description("Quickly add a rule to a YAML
|
|
|
7447
8680
|
ruleCommand.command("validate [file]").description("Validate YAML rule set files for syntax and schema correctness").action(async (targetFile) => {
|
|
7448
8681
|
const filesToValidate = [];
|
|
7449
8682
|
if (targetFile) {
|
|
7450
|
-
filesToValidate.push(
|
|
8683
|
+
filesToValidate.push(path20.resolve(targetFile));
|
|
7451
8684
|
} else {
|
|
7452
8685
|
const storage = await getReadyStorage();
|
|
7453
8686
|
const discovered = await findRuleFiles(process.cwd(), storage.getRulesDir());
|
|
@@ -7460,7 +8693,7 @@ ruleCommand.command("validate [file]").description("Validate YAML rule set files
|
|
|
7460
8693
|
let allValid = true;
|
|
7461
8694
|
for (const f of filesToValidate) {
|
|
7462
8695
|
try {
|
|
7463
|
-
const content = await
|
|
8696
|
+
const content = await fs11.readFile(f, "utf-8");
|
|
7464
8697
|
const ruleSet = parseRuleSetYaml(content, f);
|
|
7465
8698
|
console.log(`\u2713 ${f} is valid (${ruleSet.rules.length} rules)`);
|
|
7466
8699
|
} catch (err) {
|
|
@@ -7473,7 +8706,7 @@ ruleCommand.command("validate [file]").description("Validate YAML rule set files
|
|
|
7473
8706
|
process.exitCode = 1;
|
|
7474
8707
|
}
|
|
7475
8708
|
});
|
|
7476
|
-
program.command("install [target]").description("Install Bilmem MCP runtime and
|
|
8709
|
+
program.command("install [target]").description("Install Bilmem MCP runtime, instructions, and skills into AI coding agents/editors (cursor, claude, codex, gemini, antigravity)").option("--detected", "Install into detected agents/editors").option("--all", "Attempt installation for all supported integrations").option("-y, --yes", "Automatically confirm prompts without interactive input").option("--global", "Target global configuration instead of project-local").option("--dry-run", "Preview configuration changes without modifying files").option("--json", "Output result in JSON format").action(async (target, opts) => {
|
|
7477
8710
|
try {
|
|
7478
8711
|
const scope = opts.global ? "global" : "project";
|
|
7479
8712
|
if (!opts.json) {
|