@tekmidian/pai 0.9.8 → 0.9.10

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.
Files changed (43) hide show
  1. package/ARCHITECTURE.md +73 -1
  2. package/README.md +54 -2
  3. package/dist/{auto-route-CruBrTf-.mjs → auto-route-DL4ViDKC.mjs} +2 -2
  4. package/dist/{auto-route-CruBrTf-.mjs.map → auto-route-DL4ViDKC.mjs.map} +1 -1
  5. package/dist/cli/index.mjs +246 -16
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/daemon/index.mjs +7 -6
  8. package/dist/daemon/index.mjs.map +1 -1
  9. package/dist/{daemon-WQY1kwOF.mjs → daemon-dYymOnwd.mjs} +39 -19
  10. package/dist/daemon-dYymOnwd.mjs.map +1 -0
  11. package/dist/daemon-mcp/index.mjs +46 -0
  12. package/dist/daemon-mcp/index.mjs.map +1 -1
  13. package/dist/{db-DdUperSl.mjs → db-CYmBWcjh.mjs} +35 -13
  14. package/dist/db-CYmBWcjh.mjs.map +1 -0
  15. package/dist/{detector-CNU3zCwP.mjs → detector-AzVtGLtL.mjs} +2 -2
  16. package/dist/{detector-CNU3zCwP.mjs.map → detector-AzVtGLtL.mjs.map} +1 -1
  17. package/dist/{factory-DKDPRhAN.mjs → factory-BDBIfTDC.mjs} +3 -3
  18. package/dist/{factory-DKDPRhAN.mjs.map → factory-BDBIfTDC.mjs.map} +1 -1
  19. package/dist/hooks/whisper-rules.mjs +15 -12
  20. package/dist/hooks/whisper-rules.mjs.map +2 -2
  21. package/dist/index.d.mts +3 -1
  22. package/dist/index.d.mts.map +1 -1
  23. package/dist/index.mjs +2 -2
  24. package/dist/kg-entity-D5v7RCDi.mjs +176 -0
  25. package/dist/kg-entity-D5v7RCDi.mjs.map +1 -0
  26. package/dist/{kg-extraction-BlGM40q7.mjs → kg-extraction-Uvr9fTlz.mjs} +56 -21
  27. package/dist/kg-extraction-Uvr9fTlz.mjs.map +1 -0
  28. package/dist/{search-DC1qhkKn.mjs → search-i2nlQ-JM.mjs} +48 -19
  29. package/dist/search-i2nlQ-JM.mjs.map +1 -0
  30. package/dist/{sqlite-BJrME_vg.mjs → sqlite-xUe94oUZ.mjs} +9 -2
  31. package/dist/sqlite-xUe94oUZ.mjs.map +1 -0
  32. package/dist/{tools-gMHdjmHC.mjs → tools-BNBvJNph.mjs} +285 -7
  33. package/dist/tools-BNBvJNph.mjs.map +1 -0
  34. package/package.json +1 -1
  35. package/src/hooks/ts/user-prompt/whisper-rules.ts +15 -12
  36. package/dist/daemon-WQY1kwOF.mjs.map +0 -1
  37. package/dist/db-DdUperSl.mjs.map +0 -1
  38. package/dist/kg-B5ysyRLC.mjs +0 -94
  39. package/dist/kg-B5ysyRLC.mjs.map +0 -1
  40. package/dist/kg-extraction-BlGM40q7.mjs.map +0 -1
  41. package/dist/search-DC1qhkKn.mjs.map +0 -1
  42. package/dist/sqlite-BJrME_vg.mjs.map +0 -1
  43. package/dist/tools-gMHdjmHC.mjs.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
2
+ import { n as cosineSimilarity } from "./embeddings-DGRAPAYb.mjs";
2
3
  import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
3
- import { i as searchMemoryHybrid, n as populateSlugs } from "./search-DC1qhkKn.mjs";
4
+ import { i as searchMemoryHybrid, n as populateSlugs, s as touchChunksLastAccessed } from "./search-i2nlQ-JM.mjs";
4
5
  import { r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
5
- import { i as kgQuery, n as kgContradictions, r as kgInvalidate, t as kgAdd } from "./kg-B5ysyRLC.mjs";
6
+ import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-D5v7RCDi.mjs";
6
7
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
7
8
  import { homedir } from "node:os";
8
9
  import { basename, isAbsolute, join, resolve } from "node:path";
@@ -94,7 +95,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
94
95
  }
95
96
  } else results = await federation.searchKeyword(params.query, searchOpts);
96
97
  else {
97
- const { searchMemory, searchMemorySemantic } = await import("./search-DC1qhkKn.mjs").then((n) => n.o);
98
+ const { searchMemory, searchMemorySemantic } = await import("./search-i2nlQ-JM.mjs").then((n) => n.o);
98
99
  if (mode === "keyword") results = searchMemory(federation, params.query, searchOpts);
99
100
  else if (mode === "semantic" || mode === "hybrid") {
100
101
  const { generateEmbedding } = await import("./embeddings-DGRAPAYb.mjs").then((n) => n.i);
@@ -103,6 +104,13 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
103
104
  else results = searchMemoryHybrid(federation, params.query, queryEmbedding, searchOpts);
104
105
  } else results = searchMemory(federation, params.query, searchOpts);
105
106
  }
107
+ try {
108
+ const chunkIds = results.map((r) => r.chunkId).filter((id) => id != null);
109
+ if (chunkIds.length > 0) {
110
+ const rawDb = !isBackend(federation) ? federation : federation.getSqliteDb?.() ?? null;
111
+ if (rawDb) touchChunksLastAccessed(rawDb, chunkIds);
112
+ }
113
+ } catch {}
106
114
  const shouldRerank = params.rerank ?? searchDefaults?.rerank ?? true;
107
115
  if (shouldRerank && results.length > 0) {
108
116
  const { rerankResults } = await import("./reranker-CMNZcfVx.mjs").then((n) => n.r);
@@ -110,7 +118,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
110
118
  }
111
119
  const recencyDays = params.recencyBoost ?? searchDefaults?.recencyBoostDays ?? 0;
112
120
  if (recencyDays > 0 && results.length > 0) {
113
- const { applyRecencyBoost } = await import("./search-DC1qhkKn.mjs").then((n) => n.o);
121
+ const { applyRecencyBoost } = await import("./search-i2nlQ-JM.mjs").then((n) => n.o);
114
122
  results = applyRecencyBoost(results, recencyDays);
115
123
  }
116
124
  const withSlugs = populateSlugs(results, registryDb);
@@ -653,7 +661,7 @@ function toolSessionList(registryDb, params) {
653
661
  */
654
662
  async function toolSessionRoute(registryDb, federation, params) {
655
663
  try {
656
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CruBrTf-.mjs");
664
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-DL4ViDKC.mjs");
657
665
  const result = await autoRoute(registryDb, federation, params.cwd, params.context);
658
666
  if (!result) return { content: [{
659
667
  type: "text",
@@ -1604,6 +1612,274 @@ async function toolMemoryTunnels(registryDb, backend, params) {
1604
1612
  }
1605
1613
  }
1606
1614
 
1615
+ //#endregion
1616
+ //#region src/mcp/tools/feedback.ts
1617
+ const FEEDBACK_ALPHA = .1;
1618
+ /**
1619
+ * Apply relevance feedback to memory chunks and associated KG entities.
1620
+ *
1621
+ * @param db Federation SQLite database
1622
+ * @param params Feedback parameters
1623
+ */
1624
+ function toolMemoryFeedback(db, params) {
1625
+ try {
1626
+ if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) return {
1627
+ content: [{
1628
+ type: "text",
1629
+ text: "memory_feedback error: chunk_ids must be a non-empty array"
1630
+ }],
1631
+ isError: true
1632
+ };
1633
+ const rating = params.rating;
1634
+ if (typeof rating !== "number" || rating < 1 || rating > 5) return {
1635
+ content: [{
1636
+ type: "text",
1637
+ text: "memory_feedback error: rating must be a number between 1 and 5"
1638
+ }],
1639
+ isError: true
1640
+ };
1641
+ const normalizedRating = (rating - 1) / 4;
1642
+ const placeholders = params.chunk_ids.map(() => "?").join(", ");
1643
+ const chunks = db.prepare(`SELECT id, text, relevance_score FROM memory_chunks WHERE id IN (${placeholders})`).all(...params.chunk_ids);
1644
+ if (chunks.length === 0) return {
1645
+ content: [{
1646
+ type: "text",
1647
+ text: "memory_feedback: no matching chunks found"
1648
+ }],
1649
+ isError: true
1650
+ };
1651
+ let updatedChunks = 0;
1652
+ const combinedText = [];
1653
+ const updateStmt = db.prepare("UPDATE memory_chunks SET relevance_score = ? WHERE id = ?");
1654
+ for (const chunk of chunks) {
1655
+ const oldScore = chunk.relevance_score ?? .5;
1656
+ const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);
1657
+ updateStmt.run(newScore, chunk.id);
1658
+ updatedChunks++;
1659
+ combinedText.push(chunk.text);
1660
+ }
1661
+ const entities = listKgEntities(db, params.tenant_id ?? "default", void 0, 500);
1662
+ const text = combinedText.join("\n").toLowerCase();
1663
+ let updatedEntities = 0;
1664
+ for (const entity of entities) if (text.includes(entity.name.toLowerCase())) {
1665
+ updateEntityFeedbackWeight(db, entity.entity_id, normalizedRating, FEEDBACK_ALPHA);
1666
+ updatedEntities++;
1667
+ }
1668
+ return { content: [{
1669
+ type: "text",
1670
+ text: JSON.stringify({
1671
+ updated_chunks: updatedChunks,
1672
+ updated_entities: updatedEntities,
1673
+ rating,
1674
+ normalized_rating: normalizedRating
1675
+ })
1676
+ }] };
1677
+ } catch (e) {
1678
+ return {
1679
+ content: [{
1680
+ type: "text",
1681
+ text: `memory_feedback error: ${String(e)}`
1682
+ }],
1683
+ isError: true
1684
+ };
1685
+ }
1686
+ }
1687
+
1688
+ //#endregion
1689
+ //#region src/memory/kg-search.ts
1690
+ /**
1691
+ * Find entity names (from the federation.db kg_entities table) that appear
1692
+ * as substrings in the given text chunks.
1693
+ *
1694
+ * Returns a deduplicated list of matching entity names.
1695
+ */
1696
+ function extractEntityMentions(federationDb, chunks, tenantId = "default") {
1697
+ const entities = listKgEntities(federationDb, tenantId, void 0, 500);
1698
+ if (entities.length === 0) return [];
1699
+ const combinedText = chunks.map((c) => c.snippet).join("\n").toLowerCase();
1700
+ const matched = /* @__PURE__ */ new Set();
1701
+ for (const entity of entities) if (combinedText.includes(entity.name.toLowerCase())) matched.add(entity.name);
1702
+ return Array.from(matched);
1703
+ }
1704
+ /**
1705
+ * Perform BFS over kg_triples to find neighbors of the given entity names.
1706
+ *
1707
+ * For each entity name, fetches triples where the entity is the subject
1708
+ * or the object (bi-directional expansion). Supports 1 or 2 hop depths.
1709
+ *
1710
+ * Returns all unique triples found in the BFS expansion.
1711
+ */
1712
+ async function bfsExpand(pool, entityNames, hops, projectId) {
1713
+ const seen = /* @__PURE__ */ new Set();
1714
+ const tripleMap = /* @__PURE__ */ new Map();
1715
+ const frontier = new Set(entityNames);
1716
+ for (let hop = 0; hop < hops; hop++) {
1717
+ const nextFrontier = /* @__PURE__ */ new Set();
1718
+ for (const name of frontier) {
1719
+ const asSubject = await kgQuery(pool, {
1720
+ subject: name,
1721
+ project_id: projectId
1722
+ });
1723
+ const asObject = await kgQuery(pool, {
1724
+ object: name,
1725
+ project_id: projectId
1726
+ });
1727
+ for (const triple of [...asSubject, ...asObject]) if (!seen.has(triple.id)) {
1728
+ seen.add(triple.id);
1729
+ tripleMap.set(triple.id, triple);
1730
+ nextFrontier.add(triple.subject);
1731
+ nextFrontier.add(triple.object);
1732
+ }
1733
+ }
1734
+ frontier.clear();
1735
+ for (const name of nextFrontier) if (!entityNames.includes(name)) {
1736
+ frontier.add(name);
1737
+ entityNames.push(name);
1738
+ }
1739
+ }
1740
+ return Array.from(tripleMap.values());
1741
+ }
1742
+ /**
1743
+ * Score each triple by cosine similarity to the query embedding.
1744
+ *
1745
+ * The triple text representation is: "subject predicate object"
1746
+ * We embed this on-the-fly using generateEmbedding(), which may use a cache.
1747
+ *
1748
+ * Returns triples sorted by relevance score descending.
1749
+ */
1750
+ async function rerankTriples(triples, queryVec, maxTriples) {
1751
+ if (triples.length === 0) return [];
1752
+ const { generateEmbedding } = await import("./embeddings-DGRAPAYb.mjs").then((n) => n.i);
1753
+ return (await Promise.all(triples.map(async (triple) => {
1754
+ const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
1755
+ try {
1756
+ const score = cosineSimilarity(queryVec, await generateEmbedding(text, false));
1757
+ return {
1758
+ ...triple,
1759
+ relevanceScore: score
1760
+ };
1761
+ } catch {
1762
+ return {
1763
+ ...triple,
1764
+ relevanceScore: 0
1765
+ };
1766
+ }
1767
+ }))).sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, maxTriples);
1768
+ }
1769
+ /**
1770
+ * Graph-completion retrieval combining vector search with KG neighborhood BFS.
1771
+ *
1772
+ * @param federationDb SQLite federation.db (for chunk/entity lookup)
1773
+ * @param pool Postgres connection pool (for kg_triples queries)
1774
+ * @param queryVec Pre-computed embedding for the search query
1775
+ * @param opts Configuration options
1776
+ */
1777
+ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1778
+ const { seedCount = 50, hops = 1, maxTriples = 20, projectId, tenantId = "default", searchOpts } = opts;
1779
+ const seedSearchOpts = {
1780
+ ...searchOpts,
1781
+ maxResults: seedCount,
1782
+ projectIds: projectId != null ? [projectId] : searchOpts?.projectIds
1783
+ };
1784
+ let seedChunks = [];
1785
+ try {
1786
+ const { searchMemorySemantic } = await import("./search-i2nlQ-JM.mjs").then((n) => n.o);
1787
+ seedChunks = searchMemorySemantic(federationDb, queryVec, seedSearchOpts);
1788
+ } catch (e) {
1789
+ process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\n`);
1790
+ }
1791
+ if (seedChunks.length === 0) return {
1792
+ triples: [],
1793
+ seedChunks: [],
1794
+ expandedEntities: []
1795
+ };
1796
+ const expandedEntities = extractEntityMentions(federationDb, seedChunks, tenantId);
1797
+ if (expandedEntities.length === 0) return {
1798
+ triples: [],
1799
+ seedChunks,
1800
+ expandedEntities: []
1801
+ };
1802
+ let expandedTriples = [];
1803
+ try {
1804
+ expandedTriples = await bfsExpand(pool, [...expandedEntities], hops, projectId);
1805
+ } catch (e) {
1806
+ process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\n`);
1807
+ }
1808
+ if (expandedTriples.length === 0) return {
1809
+ triples: [],
1810
+ seedChunks,
1811
+ expandedEntities
1812
+ };
1813
+ let rankedTriples = [];
1814
+ try {
1815
+ rankedTriples = await rerankTriples(expandedTriples, queryVec, maxTriples);
1816
+ } catch (e) {
1817
+ process.stderr.write(`[kg-search] Phase 4 rerank error: ${e}\n`);
1818
+ rankedTriples = expandedTriples.slice(0, maxTriples).map((t) => ({
1819
+ ...t,
1820
+ relevanceScore: 0
1821
+ }));
1822
+ }
1823
+ return {
1824
+ triples: rankedTriples,
1825
+ seedChunks,
1826
+ expandedEntities
1827
+ };
1828
+ }
1829
+
1830
+ //#endregion
1831
+ //#region src/mcp/tools/kg-search.ts
1832
+ /**
1833
+ * Graph-completion search: vector search + KG neighborhood BFS.
1834
+ *
1835
+ * @param federationDb SQLite federation.db for chunk/entity operations
1836
+ * @param pool Postgres pool for kg_triples BFS expansion
1837
+ * @param params Tool parameters
1838
+ */
1839
+ async function toolMemoryKgSearch(federationDb, pool, params) {
1840
+ try {
1841
+ if (!params.query || typeof params.query !== "string" || params.query.trim() === "") return {
1842
+ content: [{
1843
+ type: "text",
1844
+ text: "memory_kg_search error: query is required"
1845
+ }],
1846
+ isError: true
1847
+ };
1848
+ const { generateEmbedding } = await import("./embeddings-DGRAPAYb.mjs").then((n) => n.i);
1849
+ const result = await graphCompletionSearch(federationDb, pool, await generateEmbedding(params.query, true), {
1850
+ seedCount: params.wide_k ?? 50,
1851
+ hops: params.neighborhood_depth ?? 1,
1852
+ maxTriples: params.top_k ?? 20,
1853
+ projectId: params.project_id,
1854
+ tenantId: params.tenant_id ?? "default"
1855
+ });
1856
+ if (result.triples.length === 0) return { content: [{
1857
+ type: "text",
1858
+ text: `No KG triples found for query: "${params.query}"\nSeed chunks: ${result.seedChunks.length}, Expanded entities: ${result.expandedEntities.length}`
1859
+ }] };
1860
+ const tripleLines = result.triples.map((t, i) => `[${i + 1}] (score=${t.relevanceScore.toFixed(4)}) ${t.subject} — ${t.predicate} — ${t.object}`).join("\n");
1861
+ const entityList = result.expandedEntities.slice(0, 10).join(", ");
1862
+ return { content: [{
1863
+ type: "text",
1864
+ text: [
1865
+ `Found ${result.triples.length} KG triple(s) for "${params.query}"`,
1866
+ `Seed chunks: ${result.seedChunks.length}`,
1867
+ `Expanded entities (${result.expandedEntities.length}): ${entityList}`,
1868
+ "",
1869
+ tripleLines
1870
+ ].join("\n")
1871
+ }] };
1872
+ } catch (e) {
1873
+ return {
1874
+ content: [{
1875
+ type: "text",
1876
+ text: `memory_kg_search error: ${String(e)}`
1877
+ }],
1878
+ isError: true
1879
+ };
1880
+ }
1881
+ }
1882
+
1607
1883
  //#endregion
1608
1884
  //#region src/mcp/tools.ts
1609
1885
  var tools_exports = /* @__PURE__ */ __exportAll({
@@ -1615,7 +1891,9 @@ var tools_exports = /* @__PURE__ */ __exportAll({
1615
1891
  toolKgContradictions: () => toolKgContradictions,
1616
1892
  toolKgInvalidate: () => toolKgInvalidate,
1617
1893
  toolKgQuery: () => toolKgQuery,
1894
+ toolMemoryFeedback: () => toolMemoryFeedback,
1618
1895
  toolMemoryGet: () => toolMemoryGet,
1896
+ toolMemoryKgSearch: () => toolMemoryKgSearch,
1619
1897
  toolMemorySearch: () => toolMemorySearch,
1620
1898
  toolMemoryTaxonomy: () => toolMemoryTaxonomy,
1621
1899
  toolMemoryTunnels: () => toolMemoryTunnels,
@@ -1639,5 +1917,5 @@ var tools_exports = /* @__PURE__ */ __exportAll({
1639
1917
  });
1640
1918
 
1641
1919
  //#endregion
1642
- export { toolSessionList as a, toolProjectHealth as c, toolProjectTodo as d, toolMemoryGet as f, toolRegistrySearch as i, toolProjectInfo as l, toolMemoryTaxonomy as n, toolSessionRoute as o, toolMemorySearch as p, toolMemoryWakeup as r, toolProjectDetect as s, tools_exports as t, toolProjectList as u };
1643
- //# sourceMappingURL=tools-gMHdjmHC.mjs.map
1920
+ export { toolMemoryWakeup as a, toolSessionRoute as c, toolProjectInfo as d, toolProjectList as f, toolMemorySearch as h, toolMemoryTaxonomy as i, toolProjectDetect as l, toolMemoryGet as m, toolMemoryKgSearch as n, toolRegistrySearch as o, toolProjectTodo as p, toolMemoryFeedback as r, toolSessionList as s, tools_exports as t, toolProjectHealth as u };
1921
+ //# sourceMappingURL=tools-BNBvJNph.mjs.map