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/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 path18 from "path";
6
- import * as fs9 from "fs/promises";
7
- import YAML4 from "yaml";
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 path7 from "path";
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 fs4 from "fs/promises";
2406
- import * as path4 from "path";
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(path4.sep).join("/");
3125
+ return rel.split(path5.sep).join("/");
2428
3126
  }
2429
3127
  async function loadGitignorePatterns(rootDir) {
2430
3128
  try {
2431
- const gitignorePath = path4.join(rootDir, ".gitignore");
2432
- const content = await fs4.readFile(gitignorePath, "utf-8");
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 fs4.realpath(currentDir);
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 fs4.readdir(currentDir, { withFileTypes: true });
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 = path4.join(currentDir, entry.name);
2497
- const rawRelPath = path4.relative(root, fullPath);
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 = path4.extname(entry.name).toLowerCase();
3211
+ const ext = path5.extname(entry.name).toLowerCase();
2514
3212
  try {
2515
- const stat4 = await fs4.stat(fullPath);
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: stat4.size,
2522
- mtimeMs: stat4.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 = path4.resolve(this.rootDir, relativePath);
3263
+ const fullPath = path5.resolve(this.rootDir, relativePath);
2566
3264
  try {
2567
- const handle = await fs4.open(fullPath, "r");
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 fs5 from "fs/promises";
2994
- import * as path5 from "path";
2995
- import YAML2 from "yaml";
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 = path5.basename(relativePath, ".md").toLowerCase();
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 = YAML2.parse(yamlText);
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 = path5.dirname(doc.relativePath);
3416
- const resolved = dir === "." || path5.isAbsolute(rawTarget) ? rawTarget.replace(/^\.\//, "") : path5.normalize(path5.join(dir, rawTarget));
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 fs5.readFile(filePath, "utf-8");
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 execFileAsync = promisify(execFile);
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 execFileAsync("git", args, {
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 fs6 from "fs/promises";
4374
+ import * as fs7 from "fs/promises";
3677
4375
  import * as fsSync from "fs";
3678
- import * as path6 from "path";
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 = path6.join(path6.dirname(pkgJson), "out");
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
- path6.resolve(process.cwd(), "node_modules/tree-sitter-wasms/out"),
3741
- path6.resolve(process.cwd(), "../node_modules/tree-sitter-wasms/out")
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 = path6.join(wasmDir, wasmName);
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 fs6.readFile(filePath, "utf-8");
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 = path6.basename(relativePath);
4122
- const ext = path6.extname(relativePath).toLowerCase();
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: path7.basename(rootDir),
5018
+ projectName: path8.basename(rootDir),
4321
5019
  defaultScope: "project",
4322
- dataDir: path7.join(rootDir, ".bilmem")
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.${path7.basename(o.provenance.path || "doc", ".md")}.${o.provenance.line || "adr"}`;
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 YAML3 from "yaml";
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 YAML3.stringify(payload, { indent: 2, lineWidth: 100 });
5428
+ return YAML4.stringify(payload, { indent: 2, lineWidth: 100 });
4731
5429
  }
4732
5430
  async function importKnowledgeFromYaml(yamlContent, storage) {
4733
- const parsed = YAML3.parse(yamlContent);
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 YAML3.stringify(dump, { indent: 2, lineWidth: 120 });
5490
+ return YAML4.stringify(dump, { indent: 2, lineWidth: 120 });
4793
5491
  }
4794
5492
  async function restoreFromYaml(yamlContent, storage) {
4795
- const parsed = YAML3.parse(yamlContent);
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 path8 from "path";
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 execFile2 } from "child_process";
5349
- import { promisify as promisify2 } from "util";
5350
- var execFileAsync2 = promisify2(execFile2);
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 execFileAsync2(
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 rootDir = options.rootDir ? path8.resolve(options.rootDir) : process.cwd();
5548
- const storage = new Storage(rootDir);
5549
- const index = new SearchIndex();
5550
- let initialized = false;
5551
- async function ensureInitialized() {
5552
- if (!initialized) {
5553
- await storage.init();
5554
- const all = await storage.getAllKnowledge();
5555
- await index.buildIndex(all);
5556
- initialized = true;
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 ensureInitialized();
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 ensureInitialized();
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 ensureInitialized();
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 ensureInitialized();
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 ensureInitialized();
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 ensureInitialized();
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("Root directory to scan (defaults to project root)"),
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 targetDir = args.path ? path8.resolve(args.path) : rootDir;
5882
- const report = await scanRepository(targetDir, {
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 ensureInitialized();
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 path17 from "path";
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 path12 from "path";
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 path11 from "path";
5923
- import * as fs8 from "fs/promises";
6752
+ import * as path12 from "path";
6753
+ import * as fs9 from "fs/promises";
5924
6754
 
5925
6755
  // src/install/atomic.ts
5926
- import * as fs7 from "fs/promises";
5927
- import * as path9 from "path";
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 = path9.dirname(filePath);
5966
- await fs7.mkdir(dir, { recursive: true });
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 fs7.writeFile(tmpPath, content, "utf-8");
5970
- await fs7.rename(tmpPath, filePath);
6954
+ await fs8.writeFile(tmpPath, content, "utf-8");
6955
+ await fs8.rename(tmpPath, filePath);
5971
6956
  } catch (err) {
5972
6957
  try {
5973
- await fs7.unlink(tmpPath);
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 fs7.readFile(filePath, "utf-8");
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 fs7.readFile(filePath, "utf-8");
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 fs7.readFile(filePath, "utf-8");
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 fs7.unlink(filePath);
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 fs7.access(p);
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(path9.delimiter);
7154
+ const dirs = envPath.split(path10.delimiter);
6134
7155
  for (const dir of dirs) {
6135
7156
  if (!dir) continue;
6136
- const fullPath = path9.join(dir, command);
7157
+ const fullPath = path10.join(dir, command);
6137
7158
  try {
6138
- await fs7.access(fullPath, fs7.constants.X_OK);
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 path10 from "path";
7168
+ import * as path11 from "path";
6148
7169
  function getIntegrationsFilePath(rootDir) {
6149
- return path10.join(rootDir, ".bilmem", "integrations.json");
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
- mcpCommandMatches = b.command === targetCommand && Array.isArray(b.args) && b.args.length === targetArgs.length && b.args.every((v, i) => v === targetArgs[i]);
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 fs8.readFile(instructionPath, "utf-8");
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: path11.basename(context.projectDir) });
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: path11.relative(context.projectDir, configPath),
7334
+ mcpConfigPath: path12.relative(context.projectDir, configPath),
6281
7335
  instructions: true,
6282
- instructionPath: path11.relative(context.projectDir, 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 path12.join(context.projectDir, ".cursor", "mcp.json");
7476
+ return path13.join(context.projectDir, ".cursor", "mcp.json");
6393
7477
  }
6394
7478
  const home = context.globalDir || os.homedir();
6395
- return path12.join(home, ".cursor", "mcp.json");
7479
+ return path13.join(home, ".cursor", "mcp.json");
6396
7480
  }
6397
7481
  resolveInstructionPath(context) {
6398
7482
  if (context.scope === "project") {
6399
- return path12.join(context.projectDir, ".cursorrules");
7483
+ return path13.join(context.projectDir, ".cursorrules");
6400
7484
  }
6401
7485
  const home = context.globalDir || os.homedir();
6402
- return path12.join(home, ".cursorrules");
7486
+ return path13.join(home, ".cursorrules");
6403
7487
  }
6404
7488
  async detect(context) {
6405
7489
  const reasons = [];
6406
- const dotCursor = path12.join(context.projectDir, ".cursor");
6407
- const dotCursorrules = path12.join(context.projectDir, ".cursorrules");
6408
- const mcpConfig = path12.join(context.projectDir, ".cursor", "mcp.json");
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 = path12.join(globalHome, ".cursor");
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 path13 from "path";
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 path13.join(context.projectDir, ".claude", "mcp.json");
7524
+ return path14.join(context.projectDir, ".claude", "mcp.json");
6441
7525
  }
6442
7526
  const home = context.globalDir || os2.homedir();
6443
- return path13.join(home, ".claude", "mcp.json");
7527
+ return path14.join(home, ".claude", "mcp.json");
6444
7528
  }
6445
7529
  resolveInstructionPath(context) {
6446
7530
  if (context.scope === "project") {
6447
- return path13.join(context.projectDir, "CLAUDE.md");
7531
+ return path14.join(context.projectDir, "CLAUDE.md");
6448
7532
  }
6449
7533
  const home = context.globalDir || os2.homedir();
6450
- return path13.join(home, ".claude", "CLAUDE.md");
7534
+ return path14.join(home, ".claude", "CLAUDE.md");
6451
7535
  }
6452
7536
  async detect(context) {
6453
7537
  const reasons = [];
6454
- const dotClaude = path13.join(context.projectDir, ".claude");
6455
- const claudeMd = path13.join(context.projectDir, "CLAUDE.md");
6456
- const claudeJson = path13.join(context.projectDir, "claude.json");
6457
- const mcpConfig = path13.join(context.projectDir, ".claude", "mcp.json");
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 = path13.join(globalHome, ".claude");
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 path14 from "path";
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 path14.join(context.projectDir, ".codex", "mcp.json");
7575
+ return path15.join(context.projectDir, ".codex", "mcp.json");
6492
7576
  }
6493
7577
  const home = context.globalDir || os3.homedir();
6494
- return path14.join(home, ".codex", "mcp.json");
7578
+ return path15.join(home, ".codex", "mcp.json");
6495
7579
  }
6496
7580
  resolveInstructionPath(context) {
6497
7581
  if (context.scope === "project") {
6498
- return path14.join(context.projectDir, "CODEX.md");
7582
+ return path15.join(context.projectDir, "CODEX.md");
6499
7583
  }
6500
7584
  const home = context.globalDir || os3.homedir();
6501
- return path14.join(home, ".codex", "instructions.md");
7585
+ return path15.join(home, ".codex", "instructions.md");
6502
7586
  }
6503
7587
  async detect(context) {
6504
7588
  const reasons = [];
6505
- const dotCodex = path14.join(context.projectDir, ".codex");
6506
- const codexMd = path14.join(context.projectDir, "CODEX.md");
6507
- const codexJson = path14.join(context.projectDir, "codex.json");
6508
- const mcpConfig = path14.join(context.projectDir, ".codex", "mcp.json");
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 = path14.join(globalHome, ".codex");
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 path15 from "path";
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 path15.join(context.projectDir, ".gemini", "settings.json");
7626
+ return path16.join(context.projectDir, ".gemini", "settings.json");
6543
7627
  }
6544
7628
  const home = context.globalDir || os4.homedir();
6545
- return path15.join(home, ".gemini", "settings.json");
7629
+ return path16.join(home, ".gemini", "settings.json");
6546
7630
  }
6547
7631
  resolveInstructionPath(context) {
6548
7632
  if (context.scope === "project") {
6549
- return path15.join(context.projectDir, "GEMINI.md");
7633
+ return path16.join(context.projectDir, "GEMINI.md");
6550
7634
  }
6551
7635
  const home = context.globalDir || os4.homedir();
6552
- return path15.join(home, ".gemini", "GEMINI.md");
7636
+ return path16.join(home, ".gemini", "GEMINI.md");
6553
7637
  }
6554
7638
  async detect(context) {
6555
7639
  const reasons = [];
6556
- const dotGemini = path15.join(context.projectDir, ".gemini");
6557
- const geminiMd = path15.join(context.projectDir, "GEMINI.md");
6558
- const geminiJson = path15.join(context.projectDir, "gemini.json");
6559
- const settingsJson = path15.join(context.projectDir, ".gemini", "settings.json");
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 = path15.join(globalHome, ".gemini");
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 path16 from "path";
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: path16.basename(context.projectDir) });
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
- if (needsMcpFix || needsInstFix) {
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 = path17.resolve(options.projectDir || process.cwd());
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 ? path18.resolve(targetPath) : process.cwd();
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: path18.basename(cwd)
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 ? path18.resolve(targetPath) : process.cwd();
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(YAML4.stringify(output).trim());
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 = path18.resolve(targetPath);
7225
- const stats = await fs9.stat(resolvedPath);
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 fs9.readdir(resolvedPath);
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(path18.join(resolvedPath, f));
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 fs9.readFile(filePath, "utf-8");
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 fs9.writeFile(path18.resolve(opts.out), yamlStr, "utf-8");
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 = path18.resolve(file);
7273
- await fs9.writeFile(dest, dumpStr, "utf-8");
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 fs9.readFile(path18.resolve(file), "utf-8");
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 fs9.readFile(path18.resolve(trajectoryFile), "utf-8");
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 app = createBilmemMcpApp();
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(path18.resolve(targetFile));
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 fs9.readFile(f, "utf-8");
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 instructions into AI coding agents/editors (cursor, claude, codex, gemini)").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) => {
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) {