bilmem 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -20
- package/dist/{canonical-CglQQ0xc.d.ts → canonical-JS6HUWGc.d.ts} +24 -1
- package/dist/cli/index.js +1433 -200
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +52 -5
- package/dist/core/index.js +623 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{filesystem-B3qAej0l.d.ts → filesystem-CrAkhr85.d.ts} +1 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +1451 -303
- package/dist/index.js.map +1 -1
- package/dist/install/index.d.ts +59 -3
- package/dist/install/index.js +1245 -111
- package/dist/install/index.js.map +1 -1
- package/dist/jev/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +12 -1
- package/dist/mcp/index.js +846 -97
- package/dist/mcp/index.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/scanner/index.d.ts +2 -2
- package/dist/scanner/index.js +6 -1
- package/dist/scanner/index.js.map +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/stats/index.d.ts +3 -3
- package/dist/stats/index.js +45 -1
- package/dist/stats/index.js.map +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +6 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/{types-Nxq5Gzay.d.ts → types-DNlvHsj9.d.ts} +120 -3
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/mcp/server.ts
|
|
2
2
|
import { McpApp, z } from "mcponce";
|
|
3
|
-
import * as
|
|
3
|
+
import * as path9 from "path";
|
|
4
4
|
|
|
5
5
|
// src/storage/canonical.ts
|
|
6
6
|
import * as fs3 from "fs/promises";
|
|
@@ -1180,6 +1180,7 @@ function formatWhyHistory(knowledge) {
|
|
|
1180
1180
|
// src/core/resolver.ts
|
|
1181
1181
|
var RELATIVE_COSTS = {
|
|
1182
1182
|
USE_KNOWLEDGE: 1,
|
|
1183
|
+
REVALIDATE: 2,
|
|
1183
1184
|
INSPECT_TEST: 2,
|
|
1184
1185
|
INSPECT_FILE: 2,
|
|
1185
1186
|
INSPECT_SYMBOL: 2.5,
|
|
@@ -1520,6 +1521,634 @@ async function resolveEpistemicQuery(options, storage, retriever) {
|
|
|
1520
1521
|
};
|
|
1521
1522
|
}
|
|
1522
1523
|
|
|
1524
|
+
// src/core/preflight.ts
|
|
1525
|
+
import { execFile } from "child_process";
|
|
1526
|
+
import { promisify } from "util";
|
|
1527
|
+
import * as path4 from "path";
|
|
1528
|
+
import * as fs4 from "fs/promises";
|
|
1529
|
+
import YAML2 from "yaml";
|
|
1530
|
+
|
|
1531
|
+
// src/core/dependencies.ts
|
|
1532
|
+
function normalizeSourceKey(source) {
|
|
1533
|
+
return source.replace(/^(file|code|test|doc):/, "").trim().toLowerCase();
|
|
1534
|
+
}
|
|
1535
|
+
function checkKnowledgeStaleness(knowledge, changedSources) {
|
|
1536
|
+
const normChanged = new Set(changedSources.map(normalizeSourceKey));
|
|
1537
|
+
const allDeps = [
|
|
1538
|
+
...(knowledge.depends_on ?? []).map(normalizeSourceKey),
|
|
1539
|
+
...knowledge.evidence.map(normalizeSourceKey)
|
|
1540
|
+
];
|
|
1541
|
+
const changed = [];
|
|
1542
|
+
const unchanged = [];
|
|
1543
|
+
for (const dep of allDeps) {
|
|
1544
|
+
if (!dep) continue;
|
|
1545
|
+
const isChanged = normChanged.has(dep) || Array.from(normChanged).some((c) => dep.includes(c) || c.includes(dep));
|
|
1546
|
+
if (isChanged) {
|
|
1547
|
+
changed.push(dep);
|
|
1548
|
+
} else {
|
|
1549
|
+
unchanged.push(dep);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
if (changed.length > 0) {
|
|
1553
|
+
const reason = `Supporting dependency changed: ${changed.join(", ")}. Unchanged evidence: ${unchanged.join(", ") || "none"}`;
|
|
1554
|
+
return {
|
|
1555
|
+
knowledgeId: knowledge.id,
|
|
1556
|
+
affected: true,
|
|
1557
|
+
status: "needs_revalidation",
|
|
1558
|
+
changedSources: changed,
|
|
1559
|
+
unchangedSources: unchanged,
|
|
1560
|
+
reason
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
knowledgeId: knowledge.id,
|
|
1565
|
+
affected: false,
|
|
1566
|
+
status: knowledge.status,
|
|
1567
|
+
changedSources: [],
|
|
1568
|
+
unchangedSources: allDeps
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function markStaleKnowledge(knowledgeList, changedSources) {
|
|
1572
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1573
|
+
let affectedCount = 0;
|
|
1574
|
+
const updatedKnowledge = [];
|
|
1575
|
+
for (const k of knowledgeList) {
|
|
1576
|
+
if (k.status === "superseded" || k.status === "invalid" || k.status === "invalidated") {
|
|
1577
|
+
updatedKnowledge.push(k);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
const res = checkKnowledgeStaleness(k, changedSources);
|
|
1581
|
+
if (res.affected) {
|
|
1582
|
+
affectedCount++;
|
|
1583
|
+
const history = k.history ?? [];
|
|
1584
|
+
const updated = {
|
|
1585
|
+
...k,
|
|
1586
|
+
status: "needs_revalidation",
|
|
1587
|
+
updatedAt: now,
|
|
1588
|
+
history: [
|
|
1589
|
+
...history,
|
|
1590
|
+
{
|
|
1591
|
+
timestamp: now,
|
|
1592
|
+
status: "needs_revalidation",
|
|
1593
|
+
action: "source_changed",
|
|
1594
|
+
reason: res.reason
|
|
1595
|
+
}
|
|
1596
|
+
]
|
|
1597
|
+
};
|
|
1598
|
+
updatedKnowledge.push(updated);
|
|
1599
|
+
} else {
|
|
1600
|
+
updatedKnowledge.push(k);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return { updatedKnowledge, affectedCount };
|
|
1604
|
+
}
|
|
1605
|
+
function checkKnowledgeListStaleness(knowledgeList, changedSources) {
|
|
1606
|
+
const { updatedKnowledge, affectedCount } = markStaleKnowledge(knowledgeList, changedSources);
|
|
1607
|
+
const affectedKnowledge = updatedKnowledge.filter((k) => k.status === "needs_revalidation");
|
|
1608
|
+
return { affectedKnowledge, updatedKnowledge, affectedCount };
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/core/preflight.ts
|
|
1612
|
+
var execFileAsync = promisify(execFile);
|
|
1613
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1614
|
+
"add",
|
|
1615
|
+
"implement",
|
|
1616
|
+
"create",
|
|
1617
|
+
"support",
|
|
1618
|
+
"with",
|
|
1619
|
+
"the",
|
|
1620
|
+
"a",
|
|
1621
|
+
"an",
|
|
1622
|
+
"for",
|
|
1623
|
+
"in",
|
|
1624
|
+
"to",
|
|
1625
|
+
"and",
|
|
1626
|
+
"of",
|
|
1627
|
+
"on",
|
|
1628
|
+
"from",
|
|
1629
|
+
"using",
|
|
1630
|
+
"via",
|
|
1631
|
+
"into",
|
|
1632
|
+
"by",
|
|
1633
|
+
"our",
|
|
1634
|
+
"new",
|
|
1635
|
+
"use",
|
|
1636
|
+
"make",
|
|
1637
|
+
"allow",
|
|
1638
|
+
"enable",
|
|
1639
|
+
"provide",
|
|
1640
|
+
"setup",
|
|
1641
|
+
"set"
|
|
1642
|
+
]);
|
|
1643
|
+
var DOMAIN_KEYWORDS = {
|
|
1644
|
+
authentication: ["auth", "oauth", "login", "logout", "session", "jwt", "token", "credential", "sso", "password"],
|
|
1645
|
+
session: ["session", "cookie", "cookies", "expiry", "refresh", "token"],
|
|
1646
|
+
"user-identity": ["user", "account", "profile", "identity", "member", "role", "permission"],
|
|
1647
|
+
database: ["db", "database", "sql", "postgres", "sqlite", "migration", "schema", "table", "query"],
|
|
1648
|
+
storage: ["storage", "s3", "bucket", "file", "upload", "disk", "blob"],
|
|
1649
|
+
api: ["api", "endpoint", "http", "rest", "route", "controller", "middleware", "graphql"],
|
|
1650
|
+
caching: ["cache", "redis", "memcached", "ttl", "invalidation"],
|
|
1651
|
+
billing: ["payment", "stripe", "checkout", "billing", "subscription", "invoice", "credit"],
|
|
1652
|
+
"data-processing": ["import", "export", "csv", "bulk", "batch", "stream", "pipeline"]
|
|
1653
|
+
};
|
|
1654
|
+
function tokenizeGoal(goal) {
|
|
1655
|
+
return goal.toLowerCase().replace(/[^a-z0-9_-]/g, " ").split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
1656
|
+
}
|
|
1657
|
+
function inferLikelyDomains(tokens, knowledgeTags) {
|
|
1658
|
+
const domains = /* @__PURE__ */ new Set();
|
|
1659
|
+
for (const token of tokens) {
|
|
1660
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
1661
|
+
if (keywords.includes(token) || token.includes(domain)) {
|
|
1662
|
+
domains.add(domain);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
for (const tag of knowledgeTags) {
|
|
1667
|
+
const norm = tag.toLowerCase().trim();
|
|
1668
|
+
if (DOMAIN_KEYWORDS[norm]) {
|
|
1669
|
+
domains.add(norm);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return Array.from(domains);
|
|
1673
|
+
}
|
|
1674
|
+
function classifyUnknown(unknown, goalTokens, domainCues) {
|
|
1675
|
+
const text = `${unknown.id} ${unknown.statement || ""} ${unknown.question || unknown.next?.question || ""}`.toLowerCase();
|
|
1676
|
+
if (unknown.blocking === true) {
|
|
1677
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge|choice)\b/i.test(text);
|
|
1678
|
+
return {
|
|
1679
|
+
classification: "BLOCKING",
|
|
1680
|
+
isDecision: isDecision2 || true,
|
|
1681
|
+
reason: unknown.reason || "Blocking unknown identified for this goal"
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
if (unknown.blocking === false) {
|
|
1685
|
+
const isDecision2 = /\b(should|whether|how to handle|what happens when|policy|strategy|choice)\b/i.test(text);
|
|
1686
|
+
return {
|
|
1687
|
+
classification: "NON_BLOCKING",
|
|
1688
|
+
isDecision: isDecision2,
|
|
1689
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
const isNonBlocking = /\b(analytics|telemetry|metric|event name|logging level|log format|button color|styling|theme)\b/i.test(text);
|
|
1693
|
+
if (isNonBlocking) {
|
|
1694
|
+
return {
|
|
1695
|
+
classification: "NON_BLOCKING",
|
|
1696
|
+
isDecision: /\b(should|which name|choice)\b/i.test(text),
|
|
1697
|
+
reason: "Operational or instrumentation detail not blocking core functionality"
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
const isDecision = /\b(should|whether|how to handle|what happens when|policy|strategy|linking|collision|merge)\b/i.test(text);
|
|
1701
|
+
const isBlockingCandidate = /\b(link|linking|collision|duplicate|merge|security|permission|storage-engine|protocol|auth-flow|credentials)\b/i.test(text);
|
|
1702
|
+
const overlapsGoal = goalTokens.some((t) => text.includes(t)) || domainCues.some((d) => text.includes(d));
|
|
1703
|
+
if (isBlockingCandidate && (overlapsGoal || unknown.reason === "UNRESOLVABLE_LOCALLY")) {
|
|
1704
|
+
return {
|
|
1705
|
+
classification: "BLOCKING",
|
|
1706
|
+
isDecision: true,
|
|
1707
|
+
reason: "Unresolved architectural or behavioral decision critical to goal implementation"
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
return {
|
|
1711
|
+
classification: "IMPORTANT",
|
|
1712
|
+
isDecision,
|
|
1713
|
+
reason: isDecision ? "Product or behavior decision requiring attention" : "Important implementation detail"
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
function prioritizeQuestions(rawQuestions, blockingUnknowns) {
|
|
1717
|
+
const consolidated = /* @__PURE__ */ new Map();
|
|
1718
|
+
for (const rq of rawQuestions) {
|
|
1719
|
+
const qText = rq.question.trim();
|
|
1720
|
+
if (!qText) continue;
|
|
1721
|
+
const topic = rq.targetTopic || qText.toLowerCase().replace(/[^a-z0-9]/g, " ").slice(0, 30);
|
|
1722
|
+
let expectedResolutionValue = 1;
|
|
1723
|
+
if (rq.isBlocking) expectedResolutionValue += 2;
|
|
1724
|
+
if (rq.outcomeScore && rq.outcomeScore > 0) expectedResolutionValue += 1;
|
|
1725
|
+
if (rq.outcomeScore && rq.outcomeScore < 0) expectedResolutionValue -= 0.5;
|
|
1726
|
+
const interactionCost = 1;
|
|
1727
|
+
const priorityScore = Math.max(0.1, expectedResolutionValue) / interactionCost;
|
|
1728
|
+
const existing = consolidated.get(topic);
|
|
1729
|
+
if (!existing || priorityScore > (existing.priorityScore ?? 0)) {
|
|
1730
|
+
consolidated.set(topic, {
|
|
1731
|
+
question: qText,
|
|
1732
|
+
reason: rq.reason,
|
|
1733
|
+
targetTopic: rq.targetTopic,
|
|
1734
|
+
expectedResolutionValue,
|
|
1735
|
+
interactionCost,
|
|
1736
|
+
priorityScore
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return Array.from(consolidated.values()).sort((a, b) => (b.priorityScore ?? 0) - (a.priorityScore ?? 0));
|
|
1741
|
+
}
|
|
1742
|
+
async function executePreflight(options, storage, retriever, index) {
|
|
1743
|
+
const goal = options.goal.trim();
|
|
1744
|
+
const explicitPositive = (options.positive ?? []).map((p) => p.trim().toLowerCase());
|
|
1745
|
+
const negative = (options.negative ?? []).map((n) => n.trim().toLowerCase());
|
|
1746
|
+
const scope = options.scope ?? "project";
|
|
1747
|
+
const goalTokens = tokenizeGoal(goal);
|
|
1748
|
+
const allKnowledge = await storage.getAllKnowledge();
|
|
1749
|
+
const allTags = allKnowledge.flatMap((k) => k.tags ?? []);
|
|
1750
|
+
const likelyDomains = inferLikelyDomains(goalTokens, allTags);
|
|
1751
|
+
const contextualPositive = Array.from(/* @__PURE__ */ new Set([...explicitPositive, ...likelyDomains, ...goalTokens]));
|
|
1752
|
+
let candidatePool = [];
|
|
1753
|
+
if (retriever) {
|
|
1754
|
+
const jevCandidates = await retriever.lookup({
|
|
1755
|
+
query: goal,
|
|
1756
|
+
positive: contextualPositive,
|
|
1757
|
+
negative,
|
|
1758
|
+
scope,
|
|
1759
|
+
limit: options.maxCandidates ?? 25,
|
|
1760
|
+
includeRejected: true
|
|
1761
|
+
});
|
|
1762
|
+
for (const r of jevCandidates) {
|
|
1763
|
+
if (r.knowledge) candidatePool.push(r.knowledge);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
if (candidatePool.length === 0) {
|
|
1767
|
+
candidatePool = allKnowledge.filter((k) => k.status !== "invalid" && k.status !== "superseded");
|
|
1768
|
+
} else {
|
|
1769
|
+
for (const k of allKnowledge) {
|
|
1770
|
+
if (!candidatePool.some((c) => c.id === k.id) && k.status !== "invalid" && k.status !== "superseded") {
|
|
1771
|
+
const matchesToken = goalTokens.some((t) => k.id.toLowerCase().includes(t) || k.statement.toLowerCase().includes(t));
|
|
1772
|
+
const matchesCues = k.positive.some((p) => contextualPositive.includes(p));
|
|
1773
|
+
if (matchesToken || matchesCues) {
|
|
1774
|
+
candidatePool.push(k);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const jevResults = rerankCandidates(candidatePool, {
|
|
1780
|
+
query: goal,
|
|
1781
|
+
positive: contextualPositive,
|
|
1782
|
+
negative,
|
|
1783
|
+
scope
|
|
1784
|
+
});
|
|
1785
|
+
const matchingItems = [];
|
|
1786
|
+
const rejectedItems = [];
|
|
1787
|
+
for (const res of jevResults) {
|
|
1788
|
+
if (res.status === "REJECT") {
|
|
1789
|
+
const reason = res.explanation.negativeConflicts.length > 0 ? `Inapplicable: ${res.explanation.negativeConflicts.join(", ")} conflicts with current context` : res.explanation.reasons[0] || "Context mismatch";
|
|
1790
|
+
rejectedItems.push({ id: res.knowledge.id, reason });
|
|
1791
|
+
} else {
|
|
1792
|
+
matchingItems.push(res.knowledge);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (options.deep) {
|
|
1796
|
+
try {
|
|
1797
|
+
const { stdout: diffOutput } = await execFileAsync(
|
|
1798
|
+
"git",
|
|
1799
|
+
["diff", "--name-only", "HEAD~1"],
|
|
1800
|
+
{ cwd: storage.getBaseDir() ? path4.dirname(storage.getBaseDir()) : process.cwd() }
|
|
1801
|
+
);
|
|
1802
|
+
const changedFiles = diffOutput.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1803
|
+
if (changedFiles.length > 0) {
|
|
1804
|
+
const staleness = checkKnowledgeListStaleness(matchingItems, changedFiles);
|
|
1805
|
+
for (const affected of staleness.affectedKnowledge) {
|
|
1806
|
+
const matchIdx = matchingItems.findIndex((m) => m.id === affected.id);
|
|
1807
|
+
if (matchIdx >= 0) {
|
|
1808
|
+
matchingItems[matchIdx] = affected;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
} catch {
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
const constraints = [];
|
|
1816
|
+
const lessons = [];
|
|
1817
|
+
const pastFailures = [];
|
|
1818
|
+
const stale = [];
|
|
1819
|
+
const known = [];
|
|
1820
|
+
let conflictedCount = 0;
|
|
1821
|
+
const CONSTRAINT_PATTERN = /\b(must|must not|never|only|cannot|require|required|boundary|strictly|prohibited|forbidden)\b/i;
|
|
1822
|
+
const FAILURE_PATTERN = /(fail|bug|error|crash|duplicate write|race condition|pressure|leak|revert|timeout)/i;
|
|
1823
|
+
const rootDir = storage.getBaseDir() ? path4.dirname(storage.getBaseDir()) : process.cwd();
|
|
1824
|
+
for (const k of matchingItems) {
|
|
1825
|
+
const matchesContradiction = k.status === "contradicted" || k.contradicts && k.contradicts.some((cid) => matchingItems.some((m) => m.id === cid));
|
|
1826
|
+
if (matchesContradiction) {
|
|
1827
|
+
conflictedCount++;
|
|
1828
|
+
}
|
|
1829
|
+
let isStale = k.status === "needs_revalidation" || k.status === "stale";
|
|
1830
|
+
let staleReason = k.history?.[k.history.length - 1]?.reason || "Supporting source changed after last validation";
|
|
1831
|
+
const changedDeps = [];
|
|
1832
|
+
const candidateDeps = [...k.depends_on ?? [], ...k.evidence ?? []];
|
|
1833
|
+
const timestampStr = k.updated_at ?? k.updatedAt ?? k.createdAt;
|
|
1834
|
+
const knowledgeTime = new Date(timestampStr).getTime();
|
|
1835
|
+
for (const dep of candidateDeps) {
|
|
1836
|
+
const cleanPath = dep.replace(/^(file|code|test|doc):/, "").trim();
|
|
1837
|
+
if (!cleanPath) continue;
|
|
1838
|
+
const absPath = path4.isAbsolute(cleanPath) ? cleanPath : path4.resolve(rootDir, cleanPath);
|
|
1839
|
+
try {
|
|
1840
|
+
const stat4 = await fs4.stat(absPath);
|
|
1841
|
+
if (stat4.mtimeMs > knowledgeTime + 500) {
|
|
1842
|
+
isStale = true;
|
|
1843
|
+
changedDeps.push(cleanPath);
|
|
1844
|
+
staleReason = `Supporting source changed after last validation (${cleanPath})`;
|
|
1845
|
+
}
|
|
1846
|
+
} catch {
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
if (isStale) {
|
|
1850
|
+
stale.push({
|
|
1851
|
+
id: k.id,
|
|
1852
|
+
statement: k.statement,
|
|
1853
|
+
reason: staleReason,
|
|
1854
|
+
changedSources: changedDeps.length > 0 ? changedDeps : k.depends_on
|
|
1855
|
+
});
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
const isConstraint = k.type === "rule" || k.type === "decision" || k.type === "convention" || CONSTRAINT_PATTERN.test(k.statement);
|
|
1859
|
+
if (isConstraint) {
|
|
1860
|
+
constraints.push({
|
|
1861
|
+
id: k.id,
|
|
1862
|
+
statement: k.statement,
|
|
1863
|
+
type: k.type,
|
|
1864
|
+
confidence: k.confidence,
|
|
1865
|
+
confidenceTier: k.confidenceTier,
|
|
1866
|
+
source: k.evidence?.[0]
|
|
1867
|
+
});
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
const isFailure = k.type === "failure" || k.statement.startsWith("[FAILURE]") || k.statement.startsWith("[REVERT]") || FAILURE_PATTERN.test(k.statement) || k.metrics.failed_outcomes > 0;
|
|
1871
|
+
if (isFailure) {
|
|
1872
|
+
pastFailures.push({
|
|
1873
|
+
id: k.id,
|
|
1874
|
+
statement: k.statement,
|
|
1875
|
+
context: k.context || {
|
|
1876
|
+
positive: k.positive,
|
|
1877
|
+
negative: k.negative
|
|
1878
|
+
},
|
|
1879
|
+
failure: k.statement,
|
|
1880
|
+
lesson: k.lesson || k.reasoning || k.resolves?.[0],
|
|
1881
|
+
evidence: k.evidence
|
|
1882
|
+
});
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
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:");
|
|
1886
|
+
if (isLesson) {
|
|
1887
|
+
lessons.push({
|
|
1888
|
+
id: k.id,
|
|
1889
|
+
statement: k.statement,
|
|
1890
|
+
confidence: k.confidence,
|
|
1891
|
+
domain: k.tags?.[0],
|
|
1892
|
+
evidence: k.evidence
|
|
1893
|
+
});
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
if (k.status === "active" || k.stage === "active" || k.stage === "probation" || !k.status) {
|
|
1897
|
+
known.push({
|
|
1898
|
+
id: k.id,
|
|
1899
|
+
statement: k.statement,
|
|
1900
|
+
confidence: k.confidence,
|
|
1901
|
+
confidenceTier: k.confidenceTier,
|
|
1902
|
+
stage: k.stage
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const unknownsFromStorage = await storage.getAllUnknowns();
|
|
1907
|
+
const unknownKnowledge = allKnowledge.filter((k) => k.type === "unknown");
|
|
1908
|
+
const unifiedUnknowns = [];
|
|
1909
|
+
const seenUnknownIds = /* @__PURE__ */ new Set();
|
|
1910
|
+
for (const u of unknownsFromStorage) {
|
|
1911
|
+
seenUnknownIds.add(u.id);
|
|
1912
|
+
unifiedUnknowns.push({
|
|
1913
|
+
id: u.id,
|
|
1914
|
+
statement: u.statement || u.id,
|
|
1915
|
+
reason: u.reason,
|
|
1916
|
+
question: u.question || u.next?.question,
|
|
1917
|
+
next: u.next,
|
|
1918
|
+
blocking: u.blocking,
|
|
1919
|
+
positive: u.positive,
|
|
1920
|
+
negative: u.negative,
|
|
1921
|
+
checked: u.checked
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
for (const k of unknownKnowledge) {
|
|
1925
|
+
if (!seenUnknownIds.has(k.id)) {
|
|
1926
|
+
seenUnknownIds.add(k.id);
|
|
1927
|
+
unifiedUnknowns.push({
|
|
1928
|
+
id: k.id,
|
|
1929
|
+
statement: k.statement,
|
|
1930
|
+
reason: k.history?.[0]?.reason,
|
|
1931
|
+
question: k.question || (k.statement.includes("?") ? k.statement : void 0),
|
|
1932
|
+
next: {
|
|
1933
|
+
type: "ask",
|
|
1934
|
+
target: k.id,
|
|
1935
|
+
question: k.question || k.statement
|
|
1936
|
+
},
|
|
1937
|
+
blocking: k.blocking,
|
|
1938
|
+
positive: k.positive,
|
|
1939
|
+
negative: k.negative,
|
|
1940
|
+
checked: k.depends_on || k.evidence
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
const unknowns = [];
|
|
1945
|
+
const missing = [];
|
|
1946
|
+
const rawQuestions = [];
|
|
1947
|
+
for (const u of unifiedUnknowns) {
|
|
1948
|
+
const text = `${u.id} ${u.statement || ""} ${u.question || ""}`.toLowerCase();
|
|
1949
|
+
const overlaps = goalTokens.some((t) => text.includes(t)) || likelyDomains.some((d) => text.includes(d)) || u.positive && u.positive.some((p) => contextualPositive.includes(p));
|
|
1950
|
+
if (u.negative && u.negative.some((n) => contextualPositive.includes(n))) {
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (overlaps) {
|
|
1954
|
+
const { classification, isDecision, reason } = classifyUnknown(u, goalTokens, likelyDomains);
|
|
1955
|
+
const isBlocking = classification === "BLOCKING";
|
|
1956
|
+
unknowns.push({
|
|
1957
|
+
id: u.id,
|
|
1958
|
+
statement: u.statement,
|
|
1959
|
+
question: u.question || u.statement,
|
|
1960
|
+
classification,
|
|
1961
|
+
blocking: isBlocking,
|
|
1962
|
+
reason
|
|
1963
|
+
});
|
|
1964
|
+
if (isDecision) {
|
|
1965
|
+
missing.push({
|
|
1966
|
+
type: "decision",
|
|
1967
|
+
subject: u.id,
|
|
1968
|
+
question: u.question || u.statement,
|
|
1969
|
+
reason
|
|
1970
|
+
});
|
|
1971
|
+
} else {
|
|
1972
|
+
missing.push({
|
|
1973
|
+
type: "evidence",
|
|
1974
|
+
subject: u.id,
|
|
1975
|
+
question: u.question || u.statement,
|
|
1976
|
+
reason
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
const qText = u.question || (u.statement && u.statement.includes("?") ? u.statement : void 0);
|
|
1980
|
+
if (qText) {
|
|
1981
|
+
rawQuestions.push({
|
|
1982
|
+
question: qText,
|
|
1983
|
+
reason: `Unknown: ${u.id} (${reason})`,
|
|
1984
|
+
targetTopic: u.id,
|
|
1985
|
+
isBlocking
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
const questionKnowledge = allKnowledge.filter((k) => k.type === "question");
|
|
1991
|
+
for (const qk of questionKnowledge) {
|
|
1992
|
+
const matchesCues = qk.positive.some((p) => contextualPositive.includes(p));
|
|
1993
|
+
const conflicts = qk.negative.some((n) => negative.includes(n));
|
|
1994
|
+
if (matchesCues && !conflicts && qk.question) {
|
|
1995
|
+
rawQuestions.push({
|
|
1996
|
+
question: qk.question,
|
|
1997
|
+
reason: `Learned question for [${qk.positive.join(", ")}] uncertainty`,
|
|
1998
|
+
targetTopic: qk.resolves?.[0] || qk.id,
|
|
1999
|
+
outcomeScore: (qk.metrics?.successful_outcomes ?? 0) - (qk.metrics?.failed_outcomes ?? 0)
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
const questions = prioritizeQuestions(rawQuestions, unknowns);
|
|
2004
|
+
const receipts = await storage.getAllReceipts();
|
|
2005
|
+
const alreadyInvestigated = [];
|
|
2006
|
+
const investigatedSources = /* @__PURE__ */ new Set();
|
|
2007
|
+
for (const r of receipts) {
|
|
2008
|
+
const qTokens = r.question.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2009
|
+
const overlapsGoal = qTokens.some((t) => goalTokens.includes(t));
|
|
2010
|
+
const overlapsCues = r.positive && r.positive.some((p) => contextualPositive.includes(p));
|
|
2011
|
+
if (overlapsGoal || overlapsCues) {
|
|
2012
|
+
for (const checked of r.checked) {
|
|
2013
|
+
investigatedSources.add(checked.toLowerCase());
|
|
2014
|
+
alreadyInvestigated.push({
|
|
2015
|
+
source: checked,
|
|
2016
|
+
result: r.findings[0] || r.rejected[0]?.reason || "Did not establish requested behavior",
|
|
2017
|
+
nextSuggestion: "ASK_USER"
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
let readiness = "READY";
|
|
2023
|
+
const totalRelevantKnowledge = known.length + constraints.length + lessons.length + pastFailures.length + stale.length;
|
|
2024
|
+
const hasBlockingUnknown = unknowns.some((u) => u.blocking);
|
|
2025
|
+
const hasMissingDecision = missing.some((m) => m.type === "decision");
|
|
2026
|
+
if (totalRelevantKnowledge === 0 && unknowns.length === 0) {
|
|
2027
|
+
readiness = "INSUFFICIENT_KNOWLEDGE";
|
|
2028
|
+
} else if (conflictedCount > 0) {
|
|
2029
|
+
readiness = "CONFLICTED";
|
|
2030
|
+
} else if (hasBlockingUnknown || hasMissingDecision) {
|
|
2031
|
+
readiness = "NEEDS_CLARIFICATION";
|
|
2032
|
+
} else if (stale.length > 0) {
|
|
2033
|
+
readiness = "NEEDS_REVALIDATION";
|
|
2034
|
+
} else {
|
|
2035
|
+
readiness = "READY";
|
|
2036
|
+
}
|
|
2037
|
+
const next = [];
|
|
2038
|
+
for (const m of missing) {
|
|
2039
|
+
if (m.type === "decision") {
|
|
2040
|
+
next.push({
|
|
2041
|
+
type: "ASK_USER",
|
|
2042
|
+
target: m.subject,
|
|
2043
|
+
reason: m.question || m.reason,
|
|
2044
|
+
relativeCost: 1,
|
|
2045
|
+
expectedInfoGain: 0.95
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
for (const s of stale) {
|
|
2050
|
+
next.push({
|
|
2051
|
+
type: "REVALIDATE",
|
|
2052
|
+
target: s.id,
|
|
2053
|
+
reason: s.reason,
|
|
2054
|
+
relativeCost: 2,
|
|
2055
|
+
expectedInfoGain: 0.85
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
for (const m of missing) {
|
|
2059
|
+
if (m.type === "evidence") {
|
|
2060
|
+
const wasChecked = investigatedSources.has(m.subject.toLowerCase()) || alreadyInvestigated.length > 0;
|
|
2061
|
+
if (wasChecked) {
|
|
2062
|
+
if (!next.some((n) => n.type === "ASK_USER")) {
|
|
2063
|
+
next.push({
|
|
2064
|
+
type: "ASK_USER",
|
|
2065
|
+
target: m.subject,
|
|
2066
|
+
reason: `Already investigated (${Array.from(investigatedSources).join(", ")}); did not establish requested behavior. Clarification required.`,
|
|
2067
|
+
relativeCost: 1,
|
|
2068
|
+
expectedInfoGain: 0.9
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
} else {
|
|
2072
|
+
const isFilePath = m.subject.includes("/") || m.subject.includes(".");
|
|
2073
|
+
next.push({
|
|
2074
|
+
type: isFilePath ? "INSPECT_FILE" : "SEARCH_REPOSITORY",
|
|
2075
|
+
target: m.subject,
|
|
2076
|
+
reason: m.reason,
|
|
2077
|
+
relativeCost: isFilePath ? 2 : 2.5,
|
|
2078
|
+
expectedInfoGain: 0.8
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (alreadyInvestigated.length > 0 && !next.some((n) => n.type === "ASK_USER")) {
|
|
2084
|
+
next.push({
|
|
2085
|
+
type: "ASK_USER",
|
|
2086
|
+
target: alreadyInvestigated[0].source,
|
|
2087
|
+
reason: `Already investigated ${alreadyInvestigated[0].source}; clarification required`,
|
|
2088
|
+
relativeCost: 1,
|
|
2089
|
+
expectedInfoGain: 0.9
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
if (next.length === 0 && known.length > 0) {
|
|
2093
|
+
next.push({
|
|
2094
|
+
type: "USE_KNOWLEDGE",
|
|
2095
|
+
target: known[0].id,
|
|
2096
|
+
reason: "Proceed with implementation using verified knowledge and constraints",
|
|
2097
|
+
relativeCost: 1,
|
|
2098
|
+
expectedInfoGain: 1
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
const knowledgeImpact = {
|
|
2102
|
+
relevantKnowledge: totalRelevantKnowledge,
|
|
2103
|
+
validated: known.filter((k) => k.confidenceTier === "high").length + constraints.length,
|
|
2104
|
+
needsRevalidation: stale.length,
|
|
2105
|
+
conflicted: conflictedCount,
|
|
2106
|
+
blockingUnknowns: unknowns.filter((u) => u.blocking).length,
|
|
2107
|
+
relevantFailures: pastFailures.length
|
|
2108
|
+
};
|
|
2109
|
+
const similarWork = [];
|
|
2110
|
+
for (const l of lessons) {
|
|
2111
|
+
similarWork.push({
|
|
2112
|
+
topic: l.domain || l.id,
|
|
2113
|
+
lesson: l.statement
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
for (const f of pastFailures) {
|
|
2117
|
+
similarWork.push({
|
|
2118
|
+
topic: f.id,
|
|
2119
|
+
failure: f.statement
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
await storage.appendMetricEvent({
|
|
2123
|
+
type: "preflight",
|
|
2124
|
+
goal,
|
|
2125
|
+
readiness,
|
|
2126
|
+
blockingUnknownsCount: knowledgeImpact.blockingUnknowns,
|
|
2127
|
+
staleKnowledgeCount: stale.length,
|
|
2128
|
+
pastFailuresCount: pastFailures.length,
|
|
2129
|
+
questionsCount: questions.length,
|
|
2130
|
+
repeatedChecksAvoidedCount: alreadyInvestigated.length,
|
|
2131
|
+
timestamp: Date.now()
|
|
2132
|
+
});
|
|
2133
|
+
return {
|
|
2134
|
+
goal,
|
|
2135
|
+
readiness,
|
|
2136
|
+
known,
|
|
2137
|
+
constraints,
|
|
2138
|
+
lessons,
|
|
2139
|
+
past_failures: pastFailures,
|
|
2140
|
+
unknowns,
|
|
2141
|
+
stale,
|
|
2142
|
+
missing,
|
|
2143
|
+
questions,
|
|
2144
|
+
likely_domains: likelyDomains,
|
|
2145
|
+
similar_work: similarWork.length > 0 ? similarWork.slice(0, 5) : void 0,
|
|
2146
|
+
already_investigated: alreadyInvestigated.length > 0 ? alreadyInvestigated : void 0,
|
|
2147
|
+
knowledge_impact: knowledgeImpact,
|
|
2148
|
+
next
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
|
|
1523
2152
|
// src/core/knowledge.ts
|
|
1524
2153
|
import { createHash } from "crypto";
|
|
1525
2154
|
|
|
@@ -1666,7 +2295,7 @@ function createKnowledge(params) {
|
|
|
1666
2295
|
type: params.type ?? "lesson",
|
|
1667
2296
|
status,
|
|
1668
2297
|
stage: initialStage,
|
|
1669
|
-
statement: params.statement.trim(),
|
|
2298
|
+
statement: (params.statement ?? params.question ?? params.id).trim(),
|
|
1670
2299
|
positive: Array.from(new Set(params.positive?.map((p) => p.trim().toLowerCase()) ?? [])),
|
|
1671
2300
|
negative: Array.from(new Set(params.negative?.map((n) => n.trim().toLowerCase()) ?? [])),
|
|
1672
2301
|
relations: Array.from(new Set(params.relations?.map((r) => r.trim()) ?? [])),
|
|
@@ -1696,6 +2325,11 @@ function createKnowledge(params) {
|
|
|
1696
2325
|
},
|
|
1697
2326
|
history,
|
|
1698
2327
|
value: 0,
|
|
2328
|
+
lesson: params.lesson,
|
|
2329
|
+
failure_details: params.failure_details,
|
|
2330
|
+
context: params.context,
|
|
2331
|
+
contradicts: params.contradicts ? Array.from(new Set(params.contradicts)) : void 0,
|
|
2332
|
+
blocking: params.blocking,
|
|
1699
2333
|
createdAt: now,
|
|
1700
2334
|
updatedAt: now
|
|
1701
2335
|
};
|
|
@@ -2474,7 +3108,7 @@ function processUserCorrection(params) {
|
|
|
2474
3108
|
}
|
|
2475
3109
|
|
|
2476
3110
|
// src/scanner/engine.ts
|
|
2477
|
-
import * as
|
|
3111
|
+
import * as path8 from "path";
|
|
2478
3112
|
|
|
2479
3113
|
// src/core/relations.ts
|
|
2480
3114
|
var EvidenceGraph = class _EvidenceGraph {
|
|
@@ -2565,8 +3199,8 @@ var EvidenceGraph = class _EvidenceGraph {
|
|
|
2565
3199
|
};
|
|
2566
3200
|
|
|
2567
3201
|
// src/scanner/filesystem.ts
|
|
2568
|
-
import * as
|
|
2569
|
-
import * as
|
|
3202
|
+
import * as fs5 from "fs/promises";
|
|
3203
|
+
import * as path5 from "path";
|
|
2570
3204
|
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2571
3205
|
".git",
|
|
2572
3206
|
".bilmem",
|
|
@@ -2587,12 +3221,12 @@ var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2587
3221
|
"temp"
|
|
2588
3222
|
]);
|
|
2589
3223
|
function normalizeRelPath(rel) {
|
|
2590
|
-
return rel.split(
|
|
3224
|
+
return rel.split(path5.sep).join("/");
|
|
2591
3225
|
}
|
|
2592
3226
|
async function loadGitignorePatterns(rootDir) {
|
|
2593
3227
|
try {
|
|
2594
|
-
const gitignorePath =
|
|
2595
|
-
const content = await
|
|
3228
|
+
const gitignorePath = path5.join(rootDir, ".gitignore");
|
|
3229
|
+
const content = await fs5.readFile(gitignorePath, "utf-8");
|
|
2596
3230
|
const regexes = [];
|
|
2597
3231
|
for (const rawLine of content.split("\n")) {
|
|
2598
3232
|
const line = rawLine.trim();
|
|
@@ -2640,7 +3274,7 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2640
3274
|
return;
|
|
2641
3275
|
}
|
|
2642
3276
|
try {
|
|
2643
|
-
const real = await
|
|
3277
|
+
const real = await fs5.realpath(currentDir);
|
|
2644
3278
|
if (visitedRealPaths.has(real)) {
|
|
2645
3279
|
return;
|
|
2646
3280
|
}
|
|
@@ -2650,14 +3284,14 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2650
3284
|
}
|
|
2651
3285
|
let entries;
|
|
2652
3286
|
try {
|
|
2653
|
-
entries = await
|
|
3287
|
+
entries = await fs5.readdir(currentDir, { withFileTypes: true });
|
|
2654
3288
|
} catch {
|
|
2655
3289
|
return;
|
|
2656
3290
|
}
|
|
2657
3291
|
for (const entry of entries) {
|
|
2658
3292
|
if (fileList.length >= maxFiles) break;
|
|
2659
|
-
const fullPath =
|
|
2660
|
-
const rawRelPath =
|
|
3293
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
3294
|
+
const rawRelPath = path5.relative(root, fullPath);
|
|
2661
3295
|
const relPath = normalizeRelPath(rawRelPath);
|
|
2662
3296
|
const lowerName = entry.name.toLowerCase();
|
|
2663
3297
|
if (gitignoreRules.some((rule) => rule.test(relPath))) {
|
|
@@ -2673,16 +3307,16 @@ async function walkDirectory(dir, optionsOrRoot, legacyFileList) {
|
|
|
2673
3307
|
if (!includeHidden && entry.name.startsWith(".") && lowerName !== ".env.example") {
|
|
2674
3308
|
continue;
|
|
2675
3309
|
}
|
|
2676
|
-
const ext =
|
|
3310
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
2677
3311
|
try {
|
|
2678
|
-
const
|
|
3312
|
+
const stat4 = await fs5.stat(fullPath);
|
|
2679
3313
|
const fileInfo = {
|
|
2680
3314
|
path: fullPath,
|
|
2681
3315
|
relativePath: relPath,
|
|
2682
3316
|
name: entry.name,
|
|
2683
3317
|
ext,
|
|
2684
|
-
size:
|
|
2685
|
-
mtimeMs:
|
|
3318
|
+
size: stat4.size,
|
|
3319
|
+
mtimeMs: stat4.mtimeMs,
|
|
2686
3320
|
isDirectory: false
|
|
2687
3321
|
};
|
|
2688
3322
|
if (!options.filter || options.filter(fileInfo)) {
|
|
@@ -2725,9 +3359,9 @@ var ProjectContext = class {
|
|
|
2725
3359
|
if (this.textCache.has(key)) {
|
|
2726
3360
|
return this.textCache.get(key);
|
|
2727
3361
|
}
|
|
2728
|
-
const fullPath =
|
|
3362
|
+
const fullPath = path5.resolve(this.rootDir, relativePath);
|
|
2729
3363
|
try {
|
|
2730
|
-
const handle = await
|
|
3364
|
+
const handle = await fs5.open(fullPath, "r");
|
|
2731
3365
|
const buffer = Buffer.alloc(maxBytes);
|
|
2732
3366
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
2733
3367
|
await handle.close();
|
|
@@ -3153,12 +3787,12 @@ async function analyzeProjectStructure(files, rootDir, registry = defaultRegistr
|
|
|
3153
3787
|
}
|
|
3154
3788
|
|
|
3155
3789
|
// src/scanner/markdown.ts
|
|
3156
|
-
import * as
|
|
3157
|
-
import * as
|
|
3158
|
-
import
|
|
3790
|
+
import * as fs6 from "fs/promises";
|
|
3791
|
+
import * as path6 from "path";
|
|
3792
|
+
import YAML3 from "yaml";
|
|
3159
3793
|
function parseMarkdownDocument(content, filePath, relativePath) {
|
|
3160
3794
|
const lines = content.split("\n");
|
|
3161
|
-
const baseName =
|
|
3795
|
+
const baseName = path6.basename(relativePath, ".md").toLowerCase();
|
|
3162
3796
|
let frontmatter = {};
|
|
3163
3797
|
const headings = [];
|
|
3164
3798
|
const alerts = [];
|
|
@@ -3187,7 +3821,7 @@ function parseMarkdownDocument(content, filePath, relativePath) {
|
|
|
3187
3821
|
bodyStartIndex = i + 1;
|
|
3188
3822
|
try {
|
|
3189
3823
|
const yamlText = frontmatterLines.join("\n");
|
|
3190
|
-
const parsed =
|
|
3824
|
+
const parsed = YAML3.parse(yamlText);
|
|
3191
3825
|
if (parsed && typeof parsed === "object") {
|
|
3192
3826
|
frontmatter = parsed;
|
|
3193
3827
|
}
|
|
@@ -3575,8 +4209,8 @@ var BUILTIN_MARKDOWN_EXTRACTORS = [
|
|
|
3575
4209
|
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))) {
|
|
3576
4210
|
const rawTarget = target.split("#")[0].split("?")[0].trim();
|
|
3577
4211
|
if (rawTarget) {
|
|
3578
|
-
const dir =
|
|
3579
|
-
const resolved = dir === "." ||
|
|
4212
|
+
const dir = path6.dirname(doc.relativePath);
|
|
4213
|
+
const resolved = dir === "." || path6.isAbsolute(rawTarget) ? rawTarget.replace(/^\.\//, "") : path6.normalize(path6.join(dir, rawTarget));
|
|
3580
4214
|
relations.push({
|
|
3581
4215
|
from: doc.relativePath,
|
|
3582
4216
|
to: resolved,
|
|
@@ -3629,7 +4263,7 @@ for (const ext of BUILTIN_MARKDOWN_EXTRACTORS) {
|
|
|
3629
4263
|
async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkdownRegistry) {
|
|
3630
4264
|
let content = "";
|
|
3631
4265
|
try {
|
|
3632
|
-
content = await
|
|
4266
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
3633
4267
|
} catch {
|
|
3634
4268
|
return { observations: [], relations: [] };
|
|
3635
4269
|
}
|
|
@@ -3638,11 +4272,11 @@ async function parseMarkdownFile(filePath, relativePath, registry = defaultMarkd
|
|
|
3638
4272
|
}
|
|
3639
4273
|
|
|
3640
4274
|
// src/scanner/git.ts
|
|
3641
|
-
import { execFile } from "child_process";
|
|
3642
|
-
import { promisify } from "util";
|
|
3643
|
-
var
|
|
4275
|
+
import { execFile as execFile2 } from "child_process";
|
|
4276
|
+
import { promisify as promisify2 } from "util";
|
|
4277
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
3644
4278
|
async function runGit(args, cwd) {
|
|
3645
|
-
const { stdout } = await
|
|
4279
|
+
const { stdout } = await execFileAsync2("git", args, {
|
|
3646
4280
|
cwd,
|
|
3647
4281
|
maxBuffer: 10 * 1024 * 1024
|
|
3648
4282
|
});
|
|
@@ -3836,9 +4470,9 @@ async function scanGitHistory(cwd, options = {}) {
|
|
|
3836
4470
|
}
|
|
3837
4471
|
|
|
3838
4472
|
// src/scanner/source.ts
|
|
3839
|
-
import * as
|
|
4473
|
+
import * as fs7 from "fs/promises";
|
|
3840
4474
|
import * as fsSync from "fs";
|
|
3841
|
-
import * as
|
|
4475
|
+
import * as path7 from "path";
|
|
3842
4476
|
import { createRequire } from "module";
|
|
3843
4477
|
import Parser from "web-tree-sitter";
|
|
3844
4478
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -3892,7 +4526,7 @@ function getWasmDir() {
|
|
|
3892
4526
|
try {
|
|
3893
4527
|
const req = createRequire(import.meta.url);
|
|
3894
4528
|
const pkgJson = req.resolve("tree-sitter-wasms/package.json");
|
|
3895
|
-
const outDir =
|
|
4529
|
+
const outDir = path7.join(path7.dirname(pkgJson), "out");
|
|
3896
4530
|
if (fsSync.existsSync(outDir)) {
|
|
3897
4531
|
wasmBaseDir = outDir;
|
|
3898
4532
|
return wasmBaseDir;
|
|
@@ -3900,8 +4534,8 @@ function getWasmDir() {
|
|
|
3900
4534
|
} catch {
|
|
3901
4535
|
}
|
|
3902
4536
|
const candidateDirs = [
|
|
3903
|
-
|
|
3904
|
-
|
|
4537
|
+
path7.resolve(process.cwd(), "node_modules/tree-sitter-wasms/out"),
|
|
4538
|
+
path7.resolve(process.cwd(), "../node_modules/tree-sitter-wasms/out")
|
|
3905
4539
|
];
|
|
3906
4540
|
for (const dir of candidateDirs) {
|
|
3907
4541
|
if (fsSync.existsSync(dir)) {
|
|
@@ -3914,7 +4548,7 @@ function getWasmDir() {
|
|
|
3914
4548
|
async function getParserForWasm(wasmName) {
|
|
3915
4549
|
const wasmDir = getWasmDir();
|
|
3916
4550
|
if (!wasmDir) return null;
|
|
3917
|
-
const wasmPath =
|
|
4551
|
+
const wasmPath = path7.join(wasmDir, wasmName);
|
|
3918
4552
|
if (!fsSync.existsSync(wasmPath)) return null;
|
|
3919
4553
|
try {
|
|
3920
4554
|
if (!parserInitialized) {
|
|
@@ -4273,7 +4907,7 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4273
4907
|
const relations = [];
|
|
4274
4908
|
let content = "";
|
|
4275
4909
|
try {
|
|
4276
|
-
content = await
|
|
4910
|
+
content = await fs7.readFile(filePath, "utf-8");
|
|
4277
4911
|
} catch {
|
|
4278
4912
|
return { observations, relations };
|
|
4279
4913
|
}
|
|
@@ -4281,8 +4915,8 @@ async function analyzeSourceFile(filePath, relativePath) {
|
|
|
4281
4915
|
return { observations, relations };
|
|
4282
4916
|
}
|
|
4283
4917
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4284
|
-
const baseName =
|
|
4285
|
-
const ext =
|
|
4918
|
+
const baseName = path7.basename(relativePath);
|
|
4919
|
+
const ext = path7.extname(relativePath).toLowerCase();
|
|
4286
4920
|
const wasmName = EXT_TO_WASM[ext];
|
|
4287
4921
|
if (wasmName) {
|
|
4288
4922
|
try {
|
|
@@ -4480,9 +5114,9 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
4480
5114
|
const plannedActions = [];
|
|
4481
5115
|
const config = dryRun ? await storage.isInitialized() ? await storage.getConfig() : {
|
|
4482
5116
|
schemaVersion: 1,
|
|
4483
|
-
projectName:
|
|
5117
|
+
projectName: path8.basename(rootDir),
|
|
4484
5118
|
defaultScope: "project",
|
|
4485
|
-
dataDir:
|
|
5119
|
+
dataDir: path8.join(rootDir, ".bilmem")
|
|
4486
5120
|
} : await storage.init();
|
|
4487
5121
|
const incremental = options.incremental ?? false;
|
|
4488
5122
|
const lastScanCommit = incremental ? config.lastScanCommit : void 0;
|
|
@@ -4555,7 +5189,7 @@ async function scanRepository(rootDir = process.cwd(), options = {}) {
|
|
|
4555
5189
|
];
|
|
4556
5190
|
for (const o of mdObs) {
|
|
4557
5191
|
if (o.source.includes(":adr") || o.claim.startsWith("[WARNING]") || o.claim.startsWith("[CAUTION]")) {
|
|
4558
|
-
const id = `decision.${
|
|
5192
|
+
const id = `decision.${path8.basename(o.provenance.path || "doc", ".md")}.${o.provenance.line || "adr"}`;
|
|
4559
5193
|
candidateLessons.push({
|
|
4560
5194
|
id,
|
|
4561
5195
|
type: "decision",
|
|
@@ -4789,8 +5423,34 @@ async function computeStats(storage, events, options) {
|
|
|
4789
5423
|
}
|
|
4790
5424
|
};
|
|
4791
5425
|
const actions = {};
|
|
5426
|
+
const preflight = {
|
|
5427
|
+
runs: 0,
|
|
5428
|
+
ready: 0,
|
|
5429
|
+
needsClarification: 0,
|
|
5430
|
+
needsRevalidation: 0,
|
|
5431
|
+
conflicted: 0,
|
|
5432
|
+
insufficientKnowledge: 0,
|
|
5433
|
+
blockingUnknownsFound: 0,
|
|
5434
|
+
staleKnowledgeFound: 0,
|
|
5435
|
+
pastFailuresSurfaced: 0,
|
|
5436
|
+
questionsSuggested: 0,
|
|
5437
|
+
repeatedChecksAvoided: 0
|
|
5438
|
+
};
|
|
4792
5439
|
for (const e of events) {
|
|
4793
5440
|
switch (e.type) {
|
|
5441
|
+
case "preflight":
|
|
5442
|
+
preflight.runs++;
|
|
5443
|
+
if (e.readiness === "READY") preflight.ready++;
|
|
5444
|
+
else if (e.readiness === "NEEDS_CLARIFICATION") preflight.needsClarification++;
|
|
5445
|
+
else if (e.readiness === "NEEDS_REVALIDATION") preflight.needsRevalidation++;
|
|
5446
|
+
else if (e.readiness === "CONFLICTED") preflight.conflicted++;
|
|
5447
|
+
else if (e.readiness === "INSUFFICIENT_KNOWLEDGE") preflight.insufficientKnowledge++;
|
|
5448
|
+
preflight.blockingUnknownsFound += e.blockingUnknownsCount || 0;
|
|
5449
|
+
preflight.staleKnowledgeFound += e.staleKnowledgeCount || 0;
|
|
5450
|
+
preflight.pastFailuresSurfaced += e.pastFailuresCount || 0;
|
|
5451
|
+
preflight.questionsSuggested += e.questionsCount || 0;
|
|
5452
|
+
preflight.repeatedChecksAvoided += e.repeatedChecksAvoidedCount || 0;
|
|
5453
|
+
break;
|
|
4794
5454
|
case "knowledge_created":
|
|
4795
5455
|
learning.new++;
|
|
4796
5456
|
break;
|
|
@@ -4961,7 +5621,8 @@ async function computeStats(storage, events, options) {
|
|
|
4961
5621
|
questionResolutionRate,
|
|
4962
5622
|
jevRejectionRate
|
|
4963
5623
|
},
|
|
4964
|
-
health
|
|
5624
|
+
health,
|
|
5625
|
+
preflight: preflight.runs > 0 ? preflight : void 0
|
|
4965
5626
|
};
|
|
4966
5627
|
}
|
|
4967
5628
|
async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
@@ -5030,9 +5691,9 @@ async function computeKnowledgeStats(storage, knowledgeId, events) {
|
|
|
5030
5691
|
}
|
|
5031
5692
|
|
|
5032
5693
|
// src/stats/filter.ts
|
|
5033
|
-
import { execFile as
|
|
5034
|
-
import { promisify as
|
|
5035
|
-
var
|
|
5694
|
+
import { execFile as execFile3 } from "child_process";
|
|
5695
|
+
import { promisify as promisify3 } from "util";
|
|
5696
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
5036
5697
|
async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
5037
5698
|
const trimmed = since.trim();
|
|
5038
5699
|
const durationMatch = trimmed.match(/^(\d+)\s*([smhdw])$/i);
|
|
@@ -5064,7 +5725,7 @@ async function resolveSinceTimestamp(since, projectDir = process.cwd()) {
|
|
|
5064
5725
|
return parsedDate;
|
|
5065
5726
|
}
|
|
5066
5727
|
try {
|
|
5067
|
-
const { stdout } = await
|
|
5728
|
+
const { stdout } = await execFileAsync3(
|
|
5068
5729
|
"git",
|
|
5069
5730
|
["log", "-1", "--format=%ct", trimmed],
|
|
5070
5731
|
{ cwd: projectDir }
|
|
@@ -5108,17 +5769,56 @@ async function runStats(options = {}) {
|
|
|
5108
5769
|
|
|
5109
5770
|
// src/mcp/server.ts
|
|
5110
5771
|
function createBilmemMcpApp(options = {}) {
|
|
5111
|
-
const
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5772
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
5773
|
+
let lastActiveDir = options.rootDir ? path9.resolve(options.rootDir) : null;
|
|
5774
|
+
function isSystemRoot(dir) {
|
|
5775
|
+
const resolved = path9.resolve(dir);
|
|
5776
|
+
return resolved === "/" || path9.parse(resolved).root === resolved;
|
|
5777
|
+
}
|
|
5778
|
+
function resolveTargetDir(requestedPath) {
|
|
5779
|
+
if (requestedPath && requestedPath.trim()) {
|
|
5780
|
+
const resolved = path9.resolve(requestedPath.trim());
|
|
5781
|
+
lastActiveDir = resolved;
|
|
5782
|
+
return resolved;
|
|
5783
|
+
}
|
|
5784
|
+
if (lastActiveDir) {
|
|
5785
|
+
return lastActiveDir;
|
|
5786
|
+
}
|
|
5787
|
+
if (process.env.BILMEM_PROJECT_DIR) {
|
|
5788
|
+
const envDir = path9.resolve(process.env.BILMEM_PROJECT_DIR);
|
|
5789
|
+
lastActiveDir = envDir;
|
|
5790
|
+
return envDir;
|
|
5791
|
+
}
|
|
5792
|
+
const cwd = process.cwd();
|
|
5793
|
+
if (cwd && !isSystemRoot(cwd)) {
|
|
5794
|
+
lastActiveDir = cwd;
|
|
5795
|
+
return cwd;
|
|
5796
|
+
}
|
|
5797
|
+
throw new Error(
|
|
5798
|
+
`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", ... }).`
|
|
5799
|
+
);
|
|
5800
|
+
}
|
|
5801
|
+
async function getProjectContext(requestedPath) {
|
|
5802
|
+
const targetDir = resolveTargetDir(requestedPath);
|
|
5803
|
+
let ctx = contexts.get(targetDir);
|
|
5804
|
+
if (!ctx) {
|
|
5805
|
+
const storage = new Storage(targetDir);
|
|
5806
|
+
const index = new SearchIndex();
|
|
5807
|
+
ctx = {
|
|
5808
|
+
rootDir: targetDir,
|
|
5809
|
+
storage,
|
|
5810
|
+
index,
|
|
5811
|
+
initialized: false
|
|
5812
|
+
};
|
|
5813
|
+
contexts.set(targetDir, ctx);
|
|
5814
|
+
}
|
|
5815
|
+
if (!ctx.initialized) {
|
|
5816
|
+
await ctx.storage.init();
|
|
5817
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
5818
|
+
await ctx.index.buildIndex(all);
|
|
5819
|
+
ctx.initialized = true;
|
|
5121
5820
|
}
|
|
5821
|
+
return ctx;
|
|
5122
5822
|
}
|
|
5123
5823
|
const app = new McpApp({
|
|
5124
5824
|
name: options.name ?? "bilmem",
|
|
@@ -5128,6 +5828,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5128
5828
|
name: "bilmem_resolve",
|
|
5129
5829
|
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.",
|
|
5130
5830
|
inputSchema: {
|
|
5831
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5131
5832
|
question: z.string().describe("The question, goal, or uncertainty to resolve"),
|
|
5132
5833
|
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. server-session, node, postgres)"),
|
|
5133
5834
|
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. stateless-jwt, browser, sqlite)"),
|
|
@@ -5135,8 +5836,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5135
5836
|
maxDepth: z.number().optional().describe("Max candidates to inspect (default: 5)")
|
|
5136
5837
|
},
|
|
5137
5838
|
handler: async (args) => {
|
|
5138
|
-
await
|
|
5139
|
-
const retriever = new Retriever(index, storage);
|
|
5839
|
+
const ctx = await getProjectContext(args.path);
|
|
5840
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5140
5841
|
const res = await resolveEpistemicQuery(
|
|
5141
5842
|
{
|
|
5142
5843
|
question: args.question,
|
|
@@ -5145,7 +5846,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5145
5846
|
scope: args.scope,
|
|
5146
5847
|
maxDepth: args.maxDepth
|
|
5147
5848
|
},
|
|
5148
|
-
storage,
|
|
5849
|
+
ctx.storage,
|
|
5149
5850
|
retriever
|
|
5150
5851
|
);
|
|
5151
5852
|
return {
|
|
@@ -5162,10 +5863,53 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5162
5863
|
};
|
|
5163
5864
|
}
|
|
5164
5865
|
});
|
|
5866
|
+
app.tool({
|
|
5867
|
+
name: "bilmem_preflight",
|
|
5868
|
+
description: "Determine what must be known before beginning a proposed goal (surfaces constraints, relevant lessons, past failures, stale knowledge, and blocking unknowns).",
|
|
5869
|
+
inputSchema: {
|
|
5870
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5871
|
+
goal: z.string().describe("The proposed goal, feature, or work to analyze before implementation"),
|
|
5872
|
+
positive: z.array(z.string()).optional().describe("Applicable context cues (e.g. authentication, postgres)"),
|
|
5873
|
+
negative: z.array(z.string()).optional().describe("Inapplicable context cues (e.g. browser, sqlite)"),
|
|
5874
|
+
deep: z.boolean().optional().describe("Check targeted git diff / unindexed files for staleness")
|
|
5875
|
+
},
|
|
5876
|
+
handler: async (args) => {
|
|
5877
|
+
const ctx = await getProjectContext(args.path);
|
|
5878
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5879
|
+
const res = await executePreflight(
|
|
5880
|
+
{
|
|
5881
|
+
goal: args.goal,
|
|
5882
|
+
positive: args.positive,
|
|
5883
|
+
negative: args.negative,
|
|
5884
|
+
deep: args.deep
|
|
5885
|
+
},
|
|
5886
|
+
ctx.storage,
|
|
5887
|
+
retriever,
|
|
5888
|
+
ctx.index
|
|
5889
|
+
);
|
|
5890
|
+
return {
|
|
5891
|
+
goal: res.goal,
|
|
5892
|
+
readiness: res.readiness,
|
|
5893
|
+
known: res.known.slice(0, 5).map((k) => k.statement),
|
|
5894
|
+
constraints: res.constraints.map((c) => c.statement),
|
|
5895
|
+
lessons: res.lessons.slice(0, 5).map((l) => l.statement),
|
|
5896
|
+
pastFailures: res.past_failures.slice(0, 5).map((f) => f.statement),
|
|
5897
|
+
blockingUnknowns: res.unknowns.filter((u) => u.blocking).map((u) => u.question || u.statement || u.id),
|
|
5898
|
+
stale: res.stale.map((s) => s.id),
|
|
5899
|
+
questions: res.questions.slice(0, 5).map((q) => q.question),
|
|
5900
|
+
next: res.next.slice(0, 3).map((n) => ({
|
|
5901
|
+
type: n.type,
|
|
5902
|
+
target: n.target,
|
|
5903
|
+
reason: n.reason
|
|
5904
|
+
}))
|
|
5905
|
+
};
|
|
5906
|
+
}
|
|
5907
|
+
});
|
|
5165
5908
|
app.tool({
|
|
5166
5909
|
name: "bilmem_lookup",
|
|
5167
5910
|
description: "Lookup reusable knowledge, conventions, and lessons using JEV contextual reranking. Returns compact, highly applicable knowledge units.",
|
|
5168
5911
|
inputSchema: {
|
|
5912
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5169
5913
|
query: z.string().optional().describe("Query text, intent keywords, or problem description"),
|
|
5170
5914
|
positive: z.array(z.string()).optional().describe("Contextual positive cues (e.g. node, esm, server-session)"),
|
|
5171
5915
|
negative: z.array(z.string()).optional().describe("Contextual negative cues (e.g. commonjs, browser)"),
|
|
@@ -5173,8 +5917,8 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5173
5917
|
limit: z.number().optional().describe("Maximum number of results to return (default 5)")
|
|
5174
5918
|
},
|
|
5175
5919
|
handler: async (args) => {
|
|
5176
|
-
await
|
|
5177
|
-
const retriever = new Retriever(index, storage);
|
|
5920
|
+
const ctx = await getProjectContext(args.path);
|
|
5921
|
+
const retriever = new Retriever(ctx.index, ctx.storage);
|
|
5178
5922
|
const results = await retriever.lookup({
|
|
5179
5923
|
query: args.query,
|
|
5180
5924
|
positive: args.positive,
|
|
@@ -5201,6 +5945,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5201
5945
|
name: "bilmem_observe",
|
|
5202
5946
|
description: "Record a new observation from code, git, test execution, or environment. Preserves provenance.",
|
|
5203
5947
|
inputSchema: {
|
|
5948
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5204
5949
|
claim: z.string().describe("What was observed"),
|
|
5205
5950
|
source: z.string().describe("Source identifier (e.g. file:src/auth.ts#L42, git:commit:81ac23, user:correction)"),
|
|
5206
5951
|
confidence: z.number().optional().describe("Confidence 0.0 - 1.0 (default: 0.85)"),
|
|
@@ -5209,7 +5954,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5209
5954
|
details: z.record(z.any()).optional().describe("Optional structured metadata")
|
|
5210
5955
|
},
|
|
5211
5956
|
handler: async (args) => {
|
|
5212
|
-
await
|
|
5957
|
+
const ctx = await getProjectContext(args.path);
|
|
5213
5958
|
const obs = createObservation({
|
|
5214
5959
|
source: args.source,
|
|
5215
5960
|
claim: args.claim,
|
|
@@ -5223,10 +5968,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5223
5968
|
negative: args.negative,
|
|
5224
5969
|
details: args.details
|
|
5225
5970
|
});
|
|
5226
|
-
await storage.saveObservation(obs);
|
|
5971
|
+
await ctx.storage.saveObservation(obs);
|
|
5227
5972
|
const evidence = createEvidence(obs, "supports");
|
|
5228
|
-
await storage.saveEvidence(evidence);
|
|
5229
|
-
const existing = await storage.getAllKnowledge();
|
|
5973
|
+
await ctx.storage.saveEvidence(evidence);
|
|
5974
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5230
5975
|
const rec = reconcileKnowledge(
|
|
5231
5976
|
{
|
|
5232
5977
|
statement: obs.claim,
|
|
@@ -5247,10 +5992,10 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5247
5992
|
evidence: [evidence.id, obs.source]
|
|
5248
5993
|
},
|
|
5249
5994
|
existing,
|
|
5250
|
-
storage
|
|
5995
|
+
ctx.storage
|
|
5251
5996
|
);
|
|
5252
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
5253
|
-
await index.upsert(updatedKnowledge);
|
|
5997
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
5998
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
5254
5999
|
return {
|
|
5255
6000
|
observationId: obs.id,
|
|
5256
6001
|
evidenceId: evidence.id,
|
|
@@ -5264,6 +6009,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5264
6009
|
name: "bilmem_learn",
|
|
5265
6010
|
description: "Learn from a completed task trajectory or explicit user correction. Converts experiences into refined, reusable lessons.",
|
|
5266
6011
|
inputSchema: {
|
|
6012
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5267
6013
|
goal: z.string().describe("The task or goal that was pursued"),
|
|
5268
6014
|
taskId: z.string().optional().describe("Task identifier"),
|
|
5269
6015
|
outcome: z.enum(["success", "failure", "reverted", "neutral"]).describe("Outcome of the task"),
|
|
@@ -5279,16 +6025,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5279
6025
|
}).optional().describe("Explicit user correction, if any occurred")
|
|
5280
6026
|
},
|
|
5281
6027
|
handler: async (args) => {
|
|
5282
|
-
await
|
|
6028
|
+
const ctx = await getProjectContext(args.path);
|
|
5283
6029
|
if (args.userCorrection) {
|
|
5284
6030
|
const { observation, knowledge } = processUserCorrection(args.userCorrection);
|
|
5285
|
-
await storage.saveObservation(observation);
|
|
5286
|
-
await storage.saveEvidence(createEvidence(observation, "supports"));
|
|
5287
|
-
const existing = await storage.getAllKnowledge();
|
|
6031
|
+
await ctx.storage.saveObservation(observation);
|
|
6032
|
+
await ctx.storage.saveEvidence(createEvidence(observation, "supports"));
|
|
6033
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5288
6034
|
const rec = reconcileKnowledge(knowledge, existing);
|
|
5289
|
-
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, storage);
|
|
5290
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
5291
|
-
await index.upsert(updatedKnowledge);
|
|
6035
|
+
const { updatedKnowledge } = applyReconciliation(rec, knowledge, existing, ctx.storage);
|
|
6036
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
6037
|
+
await ctx.index.upsert(updatedKnowledge);
|
|
5292
6038
|
return {
|
|
5293
6039
|
type: "user_correction",
|
|
5294
6040
|
knowledgeId: updatedKnowledge.id,
|
|
@@ -5304,20 +6050,20 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5304
6050
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5305
6051
|
finalOutcome: args.outcome
|
|
5306
6052
|
};
|
|
5307
|
-
const existing = await storage.getAllKnowledge();
|
|
6053
|
+
const existing = await ctx.storage.getAllKnowledge();
|
|
5308
6054
|
const learningResult = learnFromTrajectory(trajectory, existing);
|
|
5309
6055
|
for (const obs of learningResult.observations) {
|
|
5310
|
-
await storage.saveObservation(obs);
|
|
6056
|
+
await ctx.storage.saveObservation(obs);
|
|
5311
6057
|
}
|
|
5312
6058
|
for (const ev of learningResult.evidence) {
|
|
5313
|
-
await storage.saveEvidence(ev);
|
|
6059
|
+
await ctx.storage.saveEvidence(ev);
|
|
5314
6060
|
}
|
|
5315
6061
|
let updatedKnowledge;
|
|
5316
6062
|
let action = "NEW";
|
|
5317
6063
|
for (const item of learningResult.reconciliations) {
|
|
5318
6064
|
if (item.appliedKnowledge) {
|
|
5319
|
-
await storage.saveKnowledge(item.appliedKnowledge);
|
|
5320
|
-
await index.upsert(item.appliedKnowledge);
|
|
6065
|
+
await ctx.storage.saveKnowledge(item.appliedKnowledge);
|
|
6066
|
+
await ctx.index.upsert(item.appliedKnowledge);
|
|
5321
6067
|
updatedKnowledge = item.appliedKnowledge;
|
|
5322
6068
|
action = item.action;
|
|
5323
6069
|
}
|
|
@@ -5329,9 +6075,9 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5329
6075
|
result: args.outcome,
|
|
5330
6076
|
context: { positive: updatedKnowledge.positive, negative: updatedKnowledge.negative }
|
|
5331
6077
|
});
|
|
5332
|
-
await storage.appendOutcome(out);
|
|
6078
|
+
await ctx.storage.appendOutcome(out);
|
|
5333
6079
|
applyOutcomeToKnowledge(updatedKnowledge, out);
|
|
5334
|
-
await storage.saveKnowledge(updatedKnowledge);
|
|
6080
|
+
await ctx.storage.saveKnowledge(updatedKnowledge);
|
|
5335
6081
|
return {
|
|
5336
6082
|
type: "trajectory_lesson",
|
|
5337
6083
|
action,
|
|
@@ -5353,12 +6099,13 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5353
6099
|
name: "bilmem_explain",
|
|
5354
6100
|
description: "Explain why a knowledge ID exists: statement, evidence provenance, confidence, support/contradiction history, and applicable context.",
|
|
5355
6101
|
inputSchema: {
|
|
6102
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5356
6103
|
id: z.string().describe("The knowledge ID to explain"),
|
|
5357
6104
|
history: z.boolean().optional().describe("Whether to format lifecycle transition history (--history)")
|
|
5358
6105
|
},
|
|
5359
6106
|
handler: async (args) => {
|
|
5360
|
-
await
|
|
5361
|
-
const k = await storage.getKnowledge(args.id);
|
|
6107
|
+
const ctx = await getProjectContext(args.path);
|
|
6108
|
+
const k = await ctx.storage.getKnowledge(args.id);
|
|
5362
6109
|
if (!k) {
|
|
5363
6110
|
return { error: `Knowledge not found: "${args.id}"` };
|
|
5364
6111
|
}
|
|
@@ -5392,6 +6139,7 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5392
6139
|
name: "bilmem_unknown",
|
|
5393
6140
|
description: "Query or record first-class unknowns (NOT_OBSERVED, INSUFFICIENT_EVIDENCE, CONFLICTING_EVIDENCE, UNRESOLVABLE_LOCALLY) to prevent repeating useless investigations.",
|
|
5394
6141
|
inputSchema: {
|
|
6142
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5395
6143
|
action: z.enum(["query", "check", "record", "resolve"]).describe("Action to perform"),
|
|
5396
6144
|
id: z.string().optional().describe("Unknown ID or query subject"),
|
|
5397
6145
|
reason: z.enum(["NOT_OBSERVED", "INSUFFICIENT_EVIDENCE", "CONFLICTING_EVIDENCE", "STALE", "UNRESOLVABLE_LOCALLY"]).optional(),
|
|
@@ -5400,16 +6148,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5400
6148
|
question: z.string().optional().describe("Clarifying question for the user or next agent")
|
|
5401
6149
|
},
|
|
5402
6150
|
handler: async (args) => {
|
|
5403
|
-
await
|
|
6151
|
+
const ctx = await getProjectContext(args.path);
|
|
5404
6152
|
if (args.action === "query") {
|
|
5405
|
-
const list = await storage.getAllUnknowns();
|
|
6153
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
5406
6154
|
return {
|
|
5407
6155
|
count: list.length,
|
|
5408
6156
|
unknowns: list
|
|
5409
6157
|
};
|
|
5410
6158
|
}
|
|
5411
6159
|
if (args.action === "check") {
|
|
5412
|
-
const list = await storage.getAllUnknowns();
|
|
6160
|
+
const list = await ctx.storage.getAllUnknowns();
|
|
5413
6161
|
const checkResult = hasBeenInvestigated(list, args.id || "");
|
|
5414
6162
|
return checkResult;
|
|
5415
6163
|
}
|
|
@@ -5423,12 +6171,12 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5423
6171
|
checked: args.checked ?? [],
|
|
5424
6172
|
next: args.question ? { type: "ask", question: args.question } : void 0
|
|
5425
6173
|
});
|
|
5426
|
-
await storage.saveUnknown(record);
|
|
6174
|
+
await ctx.storage.saveUnknown(record);
|
|
5427
6175
|
return { success: true, recorded: record };
|
|
5428
6176
|
}
|
|
5429
6177
|
if (args.action === "resolve") {
|
|
5430
6178
|
if (!args.id) throw new Error("ID is required to resolve an unknown");
|
|
5431
|
-
const deleted = await storage.deleteUnknown(args.id);
|
|
6179
|
+
const deleted = await ctx.storage.deleteUnknown(args.id);
|
|
5432
6180
|
return { success: deleted, resolvedId: args.id };
|
|
5433
6181
|
}
|
|
5434
6182
|
return { error: "Unknown action" };
|
|
@@ -5438,16 +6186,16 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5438
6186
|
name: "bilmem_scan",
|
|
5439
6187
|
description: "Trigger a language-independent scan of the repository filesystem, markdown documentation, and git history.",
|
|
5440
6188
|
inputSchema: {
|
|
5441
|
-
path: z.string().optional().describe("
|
|
6189
|
+
path: z.string().optional().describe("Project root directory to scan (defaults to active workspace or current working directory)"),
|
|
5442
6190
|
incremental: z.boolean().optional().describe("Whether to scan incrementally from last scanned git commit")
|
|
5443
6191
|
},
|
|
5444
6192
|
handler: async (args) => {
|
|
5445
|
-
const
|
|
5446
|
-
const report = await scanRepository(
|
|
6193
|
+
const ctx = await getProjectContext(args.path);
|
|
6194
|
+
const report = await scanRepository(ctx.rootDir, {
|
|
5447
6195
|
incremental: args.incremental ?? false
|
|
5448
6196
|
});
|
|
5449
|
-
const all = await storage.getAllKnowledge();
|
|
5450
|
-
await index.buildIndex(all);
|
|
6197
|
+
const all = await ctx.storage.getAllKnowledge();
|
|
6198
|
+
await ctx.index.buildIndex(all);
|
|
5451
6199
|
return report;
|
|
5452
6200
|
}
|
|
5453
6201
|
});
|
|
@@ -5455,13 +6203,14 @@ function createBilmemMcpApp(options = {}) {
|
|
|
5455
6203
|
name: "bilmem_stats",
|
|
5456
6204
|
description: "Inspect local-first statistics, knowledge health, and effectiveness metrics (measuring reuse, investigation avoidance, and JEV accuracy). Read-only.",
|
|
5457
6205
|
inputSchema: {
|
|
6206
|
+
path: z.string().optional().describe("Project root directory (defaults to active workspace or current working directory)"),
|
|
5458
6207
|
since: z.string().optional().describe("Time window (e.g. 24h, 7d, 30d) or Git revision (e.g. HEAD~20, commit-sha)"),
|
|
5459
6208
|
knowledgeId: z.string().optional().describe("Specific knowledge ID to inspect single-item lifecycle and diagnostics")
|
|
5460
6209
|
},
|
|
5461
6210
|
handler: async (args) => {
|
|
5462
|
-
await
|
|
6211
|
+
const ctx = await getProjectContext(args.path);
|
|
5463
6212
|
const res = await runStats({
|
|
5464
|
-
rootDir,
|
|
6213
|
+
rootDir: ctx.rootDir,
|
|
5465
6214
|
since: args.since,
|
|
5466
6215
|
knowledgeId: args.knowledgeId
|
|
5467
6216
|
});
|