eminiar 0.7.0 → 0.8.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eminiar",
3
3
  "displayName": "Eminiar",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "description": "Local-first context, memory and knowledge graph for Claude Code: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
6
6
  "author": {
7
7
  "name": "Ryan Dick"
package/dist/cli.js CHANGED
@@ -35516,6 +35516,7 @@ __export(store_exports, {
35516
35516
  isValidName: () => isValidName,
35517
35517
  list: () => list,
35518
35518
  memoriesDir: () => memoriesDir,
35519
+ memoriesRel: () => memoriesRel,
35519
35520
  memoryLinks: () => memoryLinks,
35520
35521
  normalizeRelated: () => normalizeRelated,
35521
35522
  read: () => read,
@@ -43457,8 +43458,454 @@ var init_history_api = __esm({
43457
43458
  }
43458
43459
  });
43459
43460
 
43461
+ // src/history/lineage.ts
43462
+ function mergedBranchName(subject) {
43463
+ const pr = PR_MERGE.exec(subject);
43464
+ if (pr) return { name: pr[2], pr: Number(pr[1]) };
43465
+ const br = BRANCH_MERGE.exec(subject);
43466
+ if (br) return { name: br[1].replace(/^origin\//, "") };
43467
+ return void 0;
43468
+ }
43469
+ function firstParents(m, sha, stop, max) {
43470
+ const out = [];
43471
+ let cur = sha ? m.bySha.get(sha) : void 0;
43472
+ while (cur && !stop.has(cur.sha) && out.length < max) {
43473
+ out.push(cur);
43474
+ cur = cur.parents[0] ? m.bySha.get(cur.parents[0]) : void 0;
43475
+ }
43476
+ return out;
43477
+ }
43478
+ function branchLineage(m, opts = {}) {
43479
+ const max = opts.maxPerBranch ?? 500;
43480
+ const def = m.branches.find((b) => b.isDefault && !b.remote) ?? m.branches.find((b) => b.isDefault);
43481
+ const defaultName = def ? def.remote ? def.name.replace(/^[^/]+\//, "") : def.name : m.base ?? "main";
43482
+ const mainline = firstParents(m, def?.sha ?? m.head, /* @__PURE__ */ new Set(), 1e5);
43483
+ const onMain = new Set(mainline.map((c) => c.sha));
43484
+ const byName = /* @__PURE__ */ new Map();
43485
+ const entry = (name) => {
43486
+ let b = byName.get(name);
43487
+ if (!b) {
43488
+ b = {
43489
+ name,
43490
+ commits: [],
43491
+ tipAt: 0,
43492
+ isDefault: name === defaultName,
43493
+ live: false,
43494
+ merged: false,
43495
+ prs: [],
43496
+ merges: []
43497
+ };
43498
+ byName.set(name, b);
43499
+ }
43500
+ return b;
43501
+ };
43502
+ const own2 = /* @__PURE__ */ new Map();
43503
+ const add = (name, cs) => {
43504
+ const set2 = own2.get(name) ?? /* @__PURE__ */ new Set();
43505
+ for (const c of cs) if (!c.merge) set2.add(c.sha);
43506
+ own2.set(name, set2);
43507
+ const b = entry(name);
43508
+ for (const c of cs) b.tipAt = Math.max(b.tipAt, c.at);
43509
+ };
43510
+ add(defaultName, mainline);
43511
+ entry(defaultName).live = !!def;
43512
+ for (const c of mainline) {
43513
+ if (!c.merge || c.parents.length < 2) continue;
43514
+ const found = mergedBranchName(c.subject);
43515
+ const name = found?.name ?? (c.pr ? `PR #${c.pr}` : `merge ${c.short}`);
43516
+ if (name === defaultName) continue;
43517
+ add(name, firstParents(m, c.parents[1], onMain, max));
43518
+ const b = entry(name);
43519
+ b.merged = true;
43520
+ b.merges.push(c.sha);
43521
+ const pr = found?.pr ?? c.pr;
43522
+ if (pr && !b.prs.includes(pr)) b.prs.push(pr);
43523
+ }
43524
+ const locals = new Set(m.branches.filter((b) => !b.remote).map((b) => b.name));
43525
+ for (const ref of [...m.branches].sort((a, b) => Number(a.remote) - Number(b.remote))) {
43526
+ if (ref.isDefault) continue;
43527
+ const name = ref.remote ? ref.name.replace(/^[^/]+\//, "") : ref.name;
43528
+ if (ref.remote && (locals.has(name) || name === "HEAD" || !name)) continue;
43529
+ if (name === defaultName) continue;
43530
+ add(name, firstParents(m, ref.sha, onMain, max));
43531
+ const b = entry(name);
43532
+ b.live = true;
43533
+ b.merged ||= ref.merged;
43534
+ if (!b.tipAt) b.tipAt = ref.at;
43535
+ }
43536
+ for (const [name, set2] of own2) {
43537
+ entry(name).commits = [...set2].map((s) => m.bySha.get(s)).sort((a, b) => b.at - a.at).map((c) => c.sha);
43538
+ }
43539
+ const branches = [...byName.values()];
43540
+ const ownerOf = /* @__PURE__ */ new Map();
43541
+ for (const b of branches)
43542
+ for (const s of b.commits) ownerOf.set(s, [...ownerOf.get(s) ?? [], b.name]);
43543
+ return { defaultName, branches, ownerOf };
43544
+ }
43545
+ var PR_MERGE, BRANCH_MERGE;
43546
+ var init_lineage = __esm({
43547
+ "src/history/lineage.ts"() {
43548
+ "use strict";
43549
+ init_esm_shims();
43550
+ PR_MERGE = /^Merge pull request #(\d+) from [^/\s]+\/(\S+)/;
43551
+ BRANCH_MERGE = /^Merge (?:remote-tracking )?branch '([^']+)'/;
43552
+ }
43553
+ });
43554
+
43555
+ // src/ui/history-graph-api.ts
43556
+ import { existsSync as existsSync24 } from "fs";
43557
+ import { join as join28 } from "path";
43558
+ function parseList(raw) {
43559
+ if (!raw) return [];
43560
+ try {
43561
+ const v = JSON.parse(raw);
43562
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
43563
+ } catch {
43564
+ return [];
43565
+ }
43566
+ }
43567
+ function metaOf3(raw) {
43568
+ if (!raw) return {};
43569
+ try {
43570
+ return JSON.parse(raw);
43571
+ } catch {
43572
+ return {};
43573
+ }
43574
+ }
43575
+ function historyGraphKnowledgePayload(ctx, opts = {}) {
43576
+ const db = getStorage(ctx);
43577
+ const m = getHistory(ctx, db);
43578
+ if (!m) return NO_GIT2;
43579
+ reconcile(ctx, db);
43580
+ try {
43581
+ ensureDocsGraph(ctx, db);
43582
+ } catch {
43583
+ }
43584
+ return buildHistoryGraph(ctx, db, m, opts);
43585
+ }
43586
+ function buildHistoryGraph(ctx, db, m, opts = {}) {
43587
+ const nBranches = Math.max(1, Math.min(50, opts.branches ?? 8));
43588
+ const perBranch = Math.max(1, Math.min(200, opts.commitsPerBranch ?? 25));
43589
+ const perCommit = Math.max(1, Math.min(200, opts.filesPerCommit ?? 12));
43590
+ const limit = Math.max(20, Math.min(2e3, opts.limit ?? 400));
43591
+ const focus = opts.focus?.trim() || null;
43592
+ const { defaultName, branches, ownerOf } = branchLineage(m);
43593
+ const branchBy = new Map(branches.map((b) => [b.name, b]));
43594
+ const memDir = `${memoriesRel(ctx)}/`;
43595
+ const memoryOfPath = (p) => p.startsWith(memDir) && p.endsWith(".md") ? p.slice(memDir.length, -3) : void 0;
43596
+ const memRows = new Map(
43597
+ db.memoryList({ limit: 1e5 }).filter((r) => r.name).map((r) => [r.name, r])
43598
+ );
43599
+ const memFiles = /* @__PURE__ */ new Map();
43600
+ for (const [name, row] of memRows) {
43601
+ const paths2 = /* @__PURE__ */ new Set();
43602
+ for (const id of parseList(row.node_ids)) {
43603
+ const f = fileOf(id);
43604
+ if (f) paths2.add(f);
43605
+ }
43606
+ if (paths2.size) memFiles.set(name, [...paths2]);
43607
+ }
43608
+ const nonMerge = (cs) => cs.filter((c) => !c.merge);
43609
+ const touching = (paths2, cap) => {
43610
+ const out = /* @__PURE__ */ new Map();
43611
+ for (const p of paths2) for (const c of nonMerge(commitsForFile(m, p))) out.set(c.sha, c);
43612
+ return [...out.values()].sort((a, b) => b.at - a.at).slice(0, cap);
43613
+ };
43614
+ const commitSet = /* @__PURE__ */ new Map();
43615
+ const branchSet = /* @__PURE__ */ new Set();
43616
+ let onlyFiles = null;
43617
+ const addCommits = (cs) => {
43618
+ for (const c of cs) commitSet.set(c.sha, c);
43619
+ };
43620
+ let focusId = null;
43621
+ let followOwners = true;
43622
+ const kind = focus ? parseId(focus).kind : null;
43623
+ if (!focus) {
43624
+ followOwners = false;
43625
+ const feature = branches.filter((b) => !b.isDefault && b.commits.length).sort((a, b) => b.tipAt - a.tipAt).slice(0, nBranches);
43626
+ for (const b of feature) {
43627
+ branchSet.add(b.name);
43628
+ addCommits(b.commits.slice(0, perBranch).map((s) => m.bySha.get(s)));
43629
+ }
43630
+ const def = branchBy.get(defaultName);
43631
+ if (def) {
43632
+ branchSet.add(def.name);
43633
+ const oldest = Math.min(...feature.map((b) => b.tipAt), Date.now());
43634
+ addCommits(
43635
+ def.commits.map((s) => m.bySha.get(s)).filter((c, i) => i < perBranch && (feature.length < nBranches || c.at >= oldest))
43636
+ );
43637
+ }
43638
+ } else if (kind === "branch") {
43639
+ const name = focus.slice("branch:".length);
43640
+ const b = branchBy.get(name) ?? branches.find((x) => x.name.endsWith(`/${name}`));
43641
+ if (b) {
43642
+ focusId = branchId(b.name);
43643
+ branchSet.add(b.name);
43644
+ if (b.merged) branchSet.add(defaultName);
43645
+ addCommits(b.commits.slice(0, 150).map((s) => m.bySha.get(s)));
43646
+ }
43647
+ followOwners = false;
43648
+ } else if (kind === "commit") {
43649
+ const c = resolveCommit(m, focus.slice("commit:".length));
43650
+ if (c) {
43651
+ focusId = commitId(c.sha);
43652
+ addCommits([c]);
43653
+ addCommits([...c.origins, ...c.blamedBy].flatMap((s) => m.bySha.get(s) ?? []));
43654
+ }
43655
+ } else if (kind === "memory") {
43656
+ const name = focus.slice("memory:".length);
43657
+ focusId = memoryId(name);
43658
+ const paths2 = memFiles.get(name) ?? [];
43659
+ onlyFiles = new Set(paths2);
43660
+ const own2 = `${memDir}${name}.md`;
43661
+ addCommits([...touching(paths2, 60), ...touching([own2], 20)]);
43662
+ } else if (kind === "doc") {
43663
+ focusId = focus;
43664
+ const paths2 = db.graphEdgesFrom([focus], ["documents"]).map((e) => fileOf(e.dst)).filter((p) => !!p);
43665
+ onlyFiles = new Set(paths2);
43666
+ addCommits(touching(paths2, 60));
43667
+ } else {
43668
+ const path3 = fileOf(focus) ?? focus.replace(/^file:/, "");
43669
+ focusId = fileId(path3);
43670
+ onlyFiles = /* @__PURE__ */ new Set([path3]);
43671
+ addCommits(touching([path3], 60));
43672
+ }
43673
+ if (followOwners)
43674
+ for (const sha of commitSet.keys())
43675
+ for (const name of ownerOf.get(sha) ?? []) branchSet.add(name);
43676
+ const edges = /* @__PURE__ */ new Map();
43677
+ const addEdge = (src, dst, kind2, weight = 1) => {
43678
+ if (src === dst) return;
43679
+ const key = `${src}|${dst}|${kind2}`;
43680
+ if (!edges.has(key)) edges.set(key, { src, dst, kind: kind2, weight });
43681
+ };
43682
+ for (const name of branchSet) {
43683
+ const b = branchBy.get(name);
43684
+ if (!b) continue;
43685
+ for (const sha of b.commits)
43686
+ if (commitSet.has(sha)) addEdge(branchId(name), commitId(sha), "contains");
43687
+ if (b.merged && !b.isDefault && branchSet.has(defaultName))
43688
+ addEdge(branchId(name), branchId(defaultName), "merged_into");
43689
+ }
43690
+ const touchCount = /* @__PURE__ */ new Map();
43691
+ const commitFiles = /* @__PURE__ */ new Map();
43692
+ const recorded = /* @__PURE__ */ new Map();
43693
+ for (const c of commitSet.values()) {
43694
+ const mems = [];
43695
+ const paths2 = [];
43696
+ for (const f of [...c.files].sort((a, b) => b.added + b.deleted - (a.added + a.deleted))) {
43697
+ const mem = memoryOfPath(f.path);
43698
+ if (mem) {
43699
+ mems.push(mem);
43700
+ continue;
43701
+ }
43702
+ if (LOCKFILE.test(f.path)) continue;
43703
+ if (onlyFiles && !onlyFiles.has(f.path)) continue;
43704
+ paths2.push(f.path);
43705
+ }
43706
+ const kept2 = paths2.slice(0, perCommit);
43707
+ commitFiles.set(c.sha, kept2);
43708
+ for (const p of kept2) touchCount.set(p, (touchCount.get(p) ?? 0) + 1);
43709
+ if (mems.length) recorded.set(c.sha, mems);
43710
+ }
43711
+ for (const p of onlyFiles ?? []) if (!touchCount.has(p)) touchCount.set(p, 0);
43712
+ const fileBudget = Math.max(10, limit - branchSet.size - commitSet.size - 40);
43713
+ const files = new Set(
43714
+ [...touchCount.entries()].sort(
43715
+ (a, b) => Number(onlyFiles?.has(b[0]) ?? false) - Number(onlyFiles?.has(a[0]) ?? false) || b[1] - a[1] || (m.files.get(b[0])?.risk ?? 0) - (m.files.get(a[0])?.risk ?? 0) || a[0].localeCompare(b[0])
43716
+ ).slice(0, fileBudget).map(([p]) => p)
43717
+ );
43718
+ for (const [sha, paths2] of commitFiles)
43719
+ for (const p of paths2) if (files.has(p)) addEdge(commitId(sha), fileId(p), "touches");
43720
+ const memories = /* @__PURE__ */ new Set();
43721
+ for (const [sha, mems] of recorded)
43722
+ for (const name of mems) {
43723
+ memories.add(name);
43724
+ addEdge(commitId(sha), memoryId(name), "records");
43725
+ }
43726
+ for (const [name, paths2] of memFiles)
43727
+ for (const p of paths2)
43728
+ if (files.has(p)) {
43729
+ memories.add(name);
43730
+ addEdge(memoryId(name), fileId(p), "mentions");
43731
+ }
43732
+ if (kind === "memory" && focus) memories.add(focus.slice("memory:".length));
43733
+ const docs = /* @__PURE__ */ new Set();
43734
+ if (files.size)
43735
+ for (const e of db.graphEdgesTo(
43736
+ [...files].map((p) => fileId(p)),
43737
+ ["documents"]
43738
+ )) {
43739
+ docs.add(e.src);
43740
+ addEdge(e.src, e.dst, "documents");
43741
+ }
43742
+ if (kind === "doc" && focus) docs.add(focus);
43743
+ for (const l of m.links)
43744
+ if (commitSet.has(l.fix) && commitSet.has(l.origin))
43745
+ addEdge(commitId(l.fix), commitId(l.origin), "fixes", Math.min(3, 1 + l.lines / 20));
43746
+ const nodes = [];
43747
+ for (const name of branchSet) {
43748
+ const b = branchBy.get(name);
43749
+ if (!b) continue;
43750
+ const id = branchId(name);
43751
+ nodes.push({
43752
+ id,
43753
+ label: name,
43754
+ kind: "branch",
43755
+ group: "branch",
43756
+ path: null,
43757
+ line: null,
43758
+ root: id === focusId,
43759
+ at: b.tipAt,
43760
+ commits: b.commits.length,
43761
+ merged: b.merged,
43762
+ live: b.live,
43763
+ isDefault: b.isDefault,
43764
+ prs: b.prs.length ? b.prs : void 0
43765
+ });
43766
+ }
43767
+ for (const c of commitSet.values()) {
43768
+ const id = commitId(c.sha);
43769
+ nodes.push({
43770
+ id,
43771
+ label: c.subject,
43772
+ kind: "commit",
43773
+ group: "commit",
43774
+ path: null,
43775
+ line: null,
43776
+ root: id === focusId,
43777
+ sub: c.kind,
43778
+ bucket: c.bucket,
43779
+ at: c.at,
43780
+ author: c.author,
43781
+ short: c.short,
43782
+ risk: c.risk
43783
+ });
43784
+ }
43785
+ for (const p of files) {
43786
+ const id = fileId(p);
43787
+ const h = m.files.get(p);
43788
+ nodes.push({
43789
+ id,
43790
+ label: p,
43791
+ kind: "file",
43792
+ group: "file",
43793
+ path: p,
43794
+ line: null,
43795
+ root: id === focusId,
43796
+ missing: existsSync24(join28(ctx.root, p)) ? void 0 : true,
43797
+ risk: h?.risk,
43798
+ fixes: h?.fixes
43799
+ });
43800
+ }
43801
+ for (const name of memories) {
43802
+ const id = memoryId(name);
43803
+ const row = memRows.get(name);
43804
+ nodes.push({
43805
+ id,
43806
+ label: name,
43807
+ kind: "memory",
43808
+ group: "memory",
43809
+ path: row?.path ?? null,
43810
+ line: null,
43811
+ root: id === focusId,
43812
+ missing: row ? void 0 : true,
43813
+ sub: row?.kind,
43814
+ status: row?.status ?? (row?.kind === "decision" ? "accepted" : void 0),
43815
+ detail: name.includes("/") ? `${name.split("/")[0]}/` : void 0
43816
+ });
43817
+ }
43818
+ const docRows = new Map(db.graphGetNodes([...docs]).map((n) => [n.id, n]));
43819
+ for (const id of docs) {
43820
+ const row = docRows.get(id);
43821
+ const meta4 = metaOf3(row?.meta);
43822
+ const cat = typeof meta4.category === "string" ? meta4.category : void 0;
43823
+ nodes.push({
43824
+ id,
43825
+ label: row?.name ?? id.slice("doc:".length),
43826
+ kind: "doc",
43827
+ group: "doc",
43828
+ path: row?.path ?? null,
43829
+ line: null,
43830
+ root: id === focusId,
43831
+ missing: row ? void 0 : true,
43832
+ sub: cat,
43833
+ detail: cat ? categoryInfo(cat).label : void 0
43834
+ });
43835
+ }
43836
+ if (focusId && !nodes.some((n) => n.id === focusId)) {
43837
+ const p = parseId(focusId);
43838
+ nodes.push({
43839
+ id: focusId,
43840
+ label: p.rest,
43841
+ kind: p.kind,
43842
+ group: ["branch", "commit", "memory", "doc"].includes(p.kind) ? p.kind : "file",
43843
+ path: p.kind === "file" ? p.rest : null,
43844
+ line: null,
43845
+ root: true,
43846
+ missing: true
43847
+ });
43848
+ }
43849
+ for (const n of nodes) if (n.root) n.seed = true;
43850
+ const deg = /* @__PURE__ */ new Map();
43851
+ for (const e of edges.values()) {
43852
+ deg.set(e.src, (deg.get(e.src) ?? 0) + 1);
43853
+ deg.set(e.dst, (deg.get(e.dst) ?? 0) + 1);
43854
+ }
43855
+ const RANK = {
43856
+ branch: 1,
43857
+ commit: 2,
43858
+ memory: 3,
43859
+ doc: 3,
43860
+ file: 4
43861
+ };
43862
+ nodes.sort(
43863
+ (a, b) => (a.root ? 0 : RANK[a.group]) - (b.root ? 0 : RANK[b.group]) || (b.at ?? 0) - (a.at ?? 0) || (deg.get(b.id) ?? 0) - (deg.get(a.id) ?? 0) || a.id.localeCompare(b.id)
43864
+ );
43865
+ const total = nodes.length;
43866
+ const kept = nodes.slice(0, limit);
43867
+ const keep = new Set(kept.map((n) => n.id));
43868
+ const keptEdges = [...edges.values()].filter((e) => keep.has(e.src) && keep.has(e.dst));
43869
+ return {
43870
+ git: true,
43871
+ focus: focusId ?? focus,
43872
+ defaultBranch: defaultName,
43873
+ nodes: kept,
43874
+ edges: keptEdges,
43875
+ truncated: total > kept.length,
43876
+ total,
43877
+ /** Every branch with own commits, newest first, for the focus picker. */
43878
+ branches: branches.filter((b) => b.commits.length).sort((a, b) => Number(b.isDefault) - Number(a.isDefault) || b.tipAt - a.tipAt).map((b) => ({
43879
+ name: b.name,
43880
+ commits: b.commits.length,
43881
+ tipAt: b.tipAt,
43882
+ merged: b.merged,
43883
+ live: b.live,
43884
+ isDefault: b.isDefault
43885
+ }))
43886
+ };
43887
+ }
43888
+ var LOCKFILE, branchId, commitId, NO_GIT2;
43889
+ var init_history_graph_api = __esm({
43890
+ "src/ui/history-graph-api.ts"() {
43891
+ "use strict";
43892
+ init_esm_shims();
43893
+ init_category();
43894
+ init_graph();
43895
+ init_ids();
43896
+ init_lineage();
43897
+ init_model();
43898
+ init_store();
43899
+ init_storage();
43900
+ LOCKFILE = /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|go\.sum|poetry\.lock|uv\.lock|composer\.lock|Gemfile\.lock)$/;
43901
+ branchId = (name) => `branch:${name}`;
43902
+ commitId = (sha) => `commit:${sha}`;
43903
+ NO_GIT2 = { git: false, error: "not a git repository (or no commits yet)" };
43904
+ }
43905
+ });
43906
+
43460
43907
  // src/ui/knowledge-api.ts
43461
- function metaOf3(row) {
43908
+ function metaOf4(row) {
43462
43909
  if (!row?.meta) return {};
43463
43910
  try {
43464
43911
  return JSON.parse(row.meta);
@@ -43496,7 +43943,7 @@ function buildKnowledge(ctx, db, opts = {}) {
43496
43943
  const hubsOf = (id) => {
43497
43944
  const n = rows.get(id);
43498
43945
  if (!n) return [];
43499
- const m = metaOf3(n);
43946
+ const m = metaOf4(n);
43500
43947
  const out = [];
43501
43948
  if (n.kind === "memory") {
43502
43949
  const row = memRows.get(n.name);
@@ -43626,7 +44073,7 @@ function buildKnowledge(ctx, db, opts = {}) {
43626
44073
  }
43627
44074
  const row = graphRows.get(id);
43628
44075
  const base = vis(row, id, id === focus);
43629
- const m = metaOf3(row);
44076
+ const m = metaOf4(row);
43630
44077
  const node2 = {
43631
44078
  ...base,
43632
44079
  kind: row ? row.kind : kindOfMissing(id),
@@ -43705,9 +44152,9 @@ __export(server_exports, {
43705
44152
  uiDir: () => uiDir
43706
44153
  });
43707
44154
  import { spawn as spawn2 } from "child_process";
43708
- import { existsSync as existsSync24, readFileSync as readFileSync22, statSync as statSync11 } from "fs";
44155
+ import { existsSync as existsSync25, readFileSync as readFileSync22, statSync as statSync11 } from "fs";
43709
44156
  import { createServer } from "http";
43710
- import { extname as extname3, join as join28, normalize as normalize6 } from "path";
44157
+ import { extname as extname3, join as join29, normalize as normalize6 } from "path";
43711
44158
  import { URL as URL2 } from "url";
43712
44159
  function json2(res, status, body) {
43713
44160
  res.writeHead(status, {
@@ -43729,7 +44176,7 @@ function strList(v) {
43729
44176
  return void 0;
43730
44177
  }
43731
44178
  function uiDir() {
43732
- return process.env.EMINIAR_UI_DIR || join28(pluginRoot(), "dist", "ui");
44179
+ return process.env.EMINIAR_UI_DIR || join29(pluginRoot(), "dist", "ui");
43733
44180
  }
43734
44181
  function originAllowed(req, port) {
43735
44182
  const host = req.headers.host ?? "";
@@ -43753,14 +44200,14 @@ function originAllowed(req, port) {
43753
44200
  function serveStatic(res, p) {
43754
44201
  const dir = uiDir();
43755
44202
  const rel = p === "/" ? "index.html" : p.replace(/^\/+/, "");
43756
- const file3 = normalize6(join28(dir, rel));
44203
+ const file3 = normalize6(join29(dir, rel));
43757
44204
  if (!file3.startsWith(normalize6(dir))) return false;
43758
- if (!existsSync24(file3) || !statSync11(file3).isFile()) return false;
44205
+ if (!existsSync25(file3) || !statSync11(file3).isFile()) return false;
43759
44206
  const type = MIME[extname3(file3).toLowerCase()] ?? "application/octet-stream";
43760
44207
  if (rel === "index.html") {
43761
44208
  const stamp = (name) => {
43762
44209
  try {
43763
- return `/${name}?v=${Math.floor(statSync11(join28(dir, name)).mtimeMs)}`;
44210
+ return `/${name}?v=${Math.floor(statSync11(join29(dir, name)).mtimeMs)}`;
43764
44211
  } catch {
43765
44212
  return `/${name}`;
43766
44213
  }
@@ -44090,6 +44537,18 @@ async function serve(opts) {
44090
44537
  }
44091
44538
  if (p === "/api/history/graph")
44092
44539
  return json2(res, 200, historyGraphPayload(ctx(), num2("limit", 150)));
44540
+ if (p === "/api/history/knowledge")
44541
+ return json2(
44542
+ res,
44543
+ 200,
44544
+ historyGraphKnowledgePayload(ctx(), {
44545
+ focus: str2("focus"),
44546
+ branches: num2("branches", 8),
44547
+ commitsPerBranch: num2("commits", 25),
44548
+ filesPerCommit: num2("files", 12),
44549
+ limit: num2("limit", 400)
44550
+ })
44551
+ );
44093
44552
  if (p === "/api/history/risk")
44094
44553
  return json2(res, 200, historyRiskPayload(ctx(), strList(q.get("paths"))));
44095
44554
  if (p === "/api/history/symbol") {
@@ -44144,6 +44603,7 @@ var init_server = __esm({
44144
44603
  init_api2();
44145
44604
  init_graph_api();
44146
44605
  init_history_api();
44606
+ init_history_graph_api();
44147
44607
  init_knowledge_api();
44148
44608
  MIME = {
44149
44609
  ".html": "text/html; charset=utf-8",
@@ -44167,7 +44627,7 @@ var statusline_exports = {};
44167
44627
  __export(statusline_exports, {
44168
44628
  renderStatusline: () => renderStatusline
44169
44629
  });
44170
- import { existsSync as existsSync25, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
44630
+ import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
44171
44631
  function renderStatusline(raw) {
44172
44632
  let input2 = {};
44173
44633
  try {
@@ -44176,7 +44636,7 @@ function renderStatusline(raw) {
44176
44636
  }
44177
44637
  const cache3 = paths().statuslineCache;
44178
44638
  try {
44179
- if (existsSync25(cache3)) {
44639
+ if (existsSync26(cache3)) {
44180
44640
  const c = JSON.parse(readFileSync23(cache3, "utf8"));
44181
44641
  if (Date.now() - c.at < 5e3 && c.key === (input2.session_id ?? "")) return c.line;
44182
44642
  }
package/dist/server.js CHANGED
@@ -18023,6 +18023,7 @@ __export(store_exports, {
18023
18023
  isValidName: () => isValidName,
18024
18024
  list: () => list,
18025
18025
  memoriesDir: () => memoriesDir,
18026
+ memoriesRel: () => memoriesRel,
18026
18027
  memoryLinks: () => memoryLinks,
18027
18028
  normalizeRelated: () => normalizeRelated,
18028
18029
  read: () => read,