eminiar 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -26524,9 +26524,9 @@ var require_parse2 = __commonJS({
26524
26524
  const idx = prev.value.lastIndexOf("[");
26525
26525
  const pre = prev.value.slice(0, idx);
26526
26526
  const rest3 = prev.value.slice(idx + 2);
26527
- const posix = POSIX_REGEX_SOURCE[rest3];
26528
- if (posix) {
26529
- prev.value = pre + posix;
26527
+ const posix2 = POSIX_REGEX_SOURCE[rest3];
26528
+ if (posix2) {
26529
+ prev.value = pre + posix2;
26530
26530
  state.backtrack = true;
26531
26531
  advance();
26532
26532
  if (!bos.output && tokens.indexOf(prev) === 1) {
@@ -27048,7 +27048,7 @@ var require_picomatch = __commonJS({
27048
27048
  throw new TypeError("Expected pattern to be a non-empty string");
27049
27049
  }
27050
27050
  const opts = options || {};
27051
- const posix = utils.isWindows(options);
27051
+ const posix2 = utils.isWindows(options);
27052
27052
  const regex = isState ? picomatch.compileRe(glob2, options) : picomatch.makeRe(glob2, options, false, true);
27053
27053
  const state = regex.state;
27054
27054
  delete regex.state;
@@ -27058,8 +27058,8 @@ var require_picomatch = __commonJS({
27058
27058
  isIgnored = picomatch(opts.ignore, ignoreOpts, returnState);
27059
27059
  }
27060
27060
  const matcher = (input2, returnObject = false) => {
27061
- const { isMatch, match, output: output2 } = picomatch.test(input2, regex, options, { glob: glob2, posix });
27062
- const result = { glob: glob2, state, regex, posix, input: input2, output: output2, match, isMatch };
27061
+ const { isMatch, match, output: output2 } = picomatch.test(input2, regex, options, { glob: glob2, posix: posix2 });
27062
+ const result = { glob: glob2, state, regex, posix: posix2, input: input2, output: output2, match, isMatch };
27063
27063
  if (typeof opts.onResult === "function") {
27064
27064
  opts.onResult(result);
27065
27065
  }
@@ -27084,7 +27084,7 @@ var require_picomatch = __commonJS({
27084
27084
  }
27085
27085
  return matcher;
27086
27086
  };
27087
- picomatch.test = (input2, regex, options, { glob: glob2, posix } = {}) => {
27087
+ picomatch.test = (input2, regex, options, { glob: glob2, posix: posix2 } = {}) => {
27088
27088
  if (typeof input2 !== "string") {
27089
27089
  throw new TypeError("Expected input to be a string");
27090
27090
  }
@@ -27092,7 +27092,7 @@ var require_picomatch = __commonJS({
27092
27092
  return { isMatch: false, output: "" };
27093
27093
  }
27094
27094
  const opts = options || {};
27095
- const format = opts.format || (posix ? utils.toPosixSlashes : null);
27095
+ const format = opts.format || (posix2 ? utils.toPosixSlashes : null);
27096
27096
  let match = input2 === glob2;
27097
27097
  let output2 = match && format ? format(input2) : input2;
27098
27098
  if (match === false) {
@@ -27101,14 +27101,14 @@ var require_picomatch = __commonJS({
27101
27101
  }
27102
27102
  if (match === false || opts.capture === true) {
27103
27103
  if (opts.matchBase === true || opts.basename === true) {
27104
- match = picomatch.matchBase(input2, regex, options, posix);
27104
+ match = picomatch.matchBase(input2, regex, options, posix2);
27105
27105
  } else {
27106
27106
  match = regex.exec(output2);
27107
27107
  }
27108
27108
  }
27109
27109
  return { isMatch: Boolean(match), match, output: output2 };
27110
27110
  };
27111
- picomatch.matchBase = (input2, glob2, options, posix = utils.isWindows(options)) => {
27111
+ picomatch.matchBase = (input2, glob2, options, posix2 = utils.isWindows(options)) => {
27112
27112
  const regex = glob2 instanceof RegExp ? glob2 : picomatch.makeRe(glob2, options);
27113
27113
  return regex.test(path3.basename(input2));
27114
27114
  };
@@ -27299,9 +27299,9 @@ var require_micromatch = __commonJS({
27299
27299
  return [].concat(patterns).every((p) => picomatch(p, options)(str2));
27300
27300
  };
27301
27301
  micromatch.capture = (glob2, input2, options) => {
27302
- let posix = utils.isWindows(options);
27302
+ let posix2 = utils.isWindows(options);
27303
27303
  let regex = picomatch.makeRe(String(glob2), { ...options, capture: true });
27304
- let match = regex.exec(posix ? utils.toPosixSlashes(input2) : input2);
27304
+ let match = regex.exec(posix2 ? utils.toPosixSlashes(input2) : input2);
27305
27305
  if (match) {
27306
27306
  return match.slice(1).map((v) => v === void 0 ? "" : v);
27307
27307
  }
@@ -27481,15 +27481,15 @@ var require_pattern = __commonJS({
27481
27481
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
27482
27482
  function partitionAbsoluteAndRelative(patterns) {
27483
27483
  const absolute = [];
27484
- const relative10 = [];
27484
+ const relative11 = [];
27485
27485
  for (const pattern2 of patterns) {
27486
27486
  if (isAbsolute(pattern2)) {
27487
27487
  absolute.push(pattern2);
27488
27488
  } else {
27489
- relative10.push(pattern2);
27489
+ relative11.push(pattern2);
27490
27490
  }
27491
27491
  }
27492
- return [absolute, relative10];
27492
+ return [absolute, relative11];
27493
27493
  }
27494
27494
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
27495
27495
  function isAbsolute(pattern2) {
@@ -27920,11 +27920,11 @@ var require_out = __commonJS({
27920
27920
  async.read(path3, getSettings(optionsOrSettingsOrCallback), callback);
27921
27921
  }
27922
27922
  exports.stat = stat;
27923
- function statSync11(path3, optionsOrSettings) {
27923
+ function statSync12(path3, optionsOrSettings) {
27924
27924
  const settings = getSettings(optionsOrSettings);
27925
27925
  return sync.read(path3, settings);
27926
27926
  }
27927
- exports.statSync = statSync11;
27927
+ exports.statSync = statSync12;
27928
27928
  function getSettings(settingsOrOptions = {}) {
27929
27929
  if (settingsOrOptions instanceof settings_1.default) {
27930
27930
  return settingsOrOptions;
@@ -29804,19 +29804,19 @@ var require_out4 = __commonJS({
29804
29804
  return utils.path.convertPathToPattern(source);
29805
29805
  }
29806
29806
  FastGlob2.convertPathToPattern = convertPathToPattern;
29807
- let posix;
29808
- (function(posix2) {
29807
+ let posix2;
29808
+ (function(posix3) {
29809
29809
  function escapePath2(source) {
29810
29810
  assertPatternsInput(source);
29811
29811
  return utils.path.escapePosixPath(source);
29812
29812
  }
29813
- posix2.escapePath = escapePath2;
29813
+ posix3.escapePath = escapePath2;
29814
29814
  function convertPathToPattern2(source) {
29815
29815
  assertPatternsInput(source);
29816
29816
  return utils.path.convertPosixPathToPattern(source);
29817
29817
  }
29818
- posix2.convertPathToPattern = convertPathToPattern2;
29819
- })(posix = FastGlob2.posix || (FastGlob2.posix = {}));
29818
+ posix3.convertPathToPattern = convertPathToPattern2;
29819
+ })(posix2 = FastGlob2.posix || (FastGlob2.posix = {}));
29820
29820
  let win32;
29821
29821
  (function(win322) {
29822
29822
  function escapePath2(source) {
@@ -30776,13 +30776,14 @@ function fileOf(id) {
30776
30776
  if (kind === "sym") return rest2.split("#")[0];
30777
30777
  return void 0;
30778
30778
  }
30779
- var fileId, memoryId, symId, pkgId, personId, conceptId;
30779
+ var fileId, memoryId, docId, symId, pkgId, personId, conceptId;
30780
30780
  var init_ids = __esm({
30781
30781
  "src/graph/ids.ts"() {
30782
30782
  "use strict";
30783
30783
  init_esm_shims();
30784
30784
  fileId = (rel) => `file:${rel}`;
30785
30785
  memoryId = (name) => `memory:${name}`;
30786
+ docId = (name) => `doc:${name}`;
30786
30787
  symId = (rel, name) => `sym:${rel}#${name}`;
30787
30788
  pkgId = (name) => `pkg:${name}`;
30788
30789
  personId = (name) => `person:${name}`;
@@ -31152,9 +31153,10 @@ function impact(db, ref, opts = {}) {
31152
31153
  frontier = next;
31153
31154
  if (!frontier.length) break;
31154
31155
  }
31155
- for (const e of db.graphEdgesTo(start, ["mentions"])) {
31156
+ for (const e of db.graphEdgesTo(start, ["mentions", "documents"])) {
31156
31157
  const via = e.dst === root.id ? root.name : e.dst.replace(/^sym:[^#]*#/, "");
31157
- add(e.src, 0.5 * e.confidence, 1, `memory mentions ${via}`);
31158
+ const who = e.kind === "documents" ? "doc documents" : "memory mentions";
31159
+ add(e.src, 0.5 * e.confidence, 1, `${who} ${via}`);
31158
31160
  }
31159
31161
  if (opts.includeGit !== false) {
31160
31162
  const file3 = toFile(root.id);
@@ -35514,10 +35516,12 @@ __export(store_exports, {
35514
35516
  isValidName: () => isValidName,
35515
35517
  list: () => list,
35516
35518
  memoriesDir: () => memoriesDir,
35519
+ memoryLinks: () => memoryLinks,
35517
35520
  normalizeRelated: () => normalizeRelated,
35518
35521
  read: () => read,
35519
35522
  reconcile: () => reconcile,
35520
35523
  relOf: () => relOf,
35524
+ relatedTarget: () => relatedTarget,
35521
35525
  remove: () => remove,
35522
35526
  rename: () => rename,
35523
35527
  setMeta: () => setMeta,
@@ -35593,6 +35597,17 @@ function normalizeRelated(ids) {
35593
35597
  }
35594
35598
  return [...out];
35595
35599
  }
35600
+ function relatedTarget(id) {
35601
+ return id.startsWith("mem:") ? memoryId(id.slice(4)) : id;
35602
+ }
35603
+ function memoryLinks(body, self) {
35604
+ const out = /* @__PURE__ */ new Set();
35605
+ for (const m of body.matchAll(MEM_LINK_RE)) {
35606
+ const name = m[1].replace(/[.-]+$/, "");
35607
+ if (name && name !== self && isValidName(name)) out.add(name);
35608
+ }
35609
+ return [...out];
35610
+ }
35596
35611
  function writeMemoryFile(ctx, name, meta4, body) {
35597
35612
  const abs = absOf(ctx, name);
35598
35613
  mkdirSync7(dirname8(abs), { recursive: true });
@@ -35600,36 +35615,30 @@ function writeMemoryFile(ctx, name, meta4, body) {
35600
35615
  ${body.trim()}
35601
35616
  `));
35602
35617
  }
35603
- function syncGraph(db, name, rel, kind, tags, related, supersedes) {
35618
+ function syncGraph(db, name, rel, g) {
35619
+ const self = memoryId(name);
35604
35620
  db.graphDeleteForPath("curated", rel);
35605
35621
  db.graphUpsertNodes([
35606
35622
  {
35607
- id: memoryId(name),
35623
+ id: self,
35608
35624
  kind: "memory",
35609
35625
  name,
35610
35626
  path: rel,
35611
35627
  source: "curated",
35612
- meta: { kind, tags }
35628
+ meta: { kind: g.kind, tags: g.tags, ...g.status ? { status: g.status } : {} }
35613
35629
  }
35614
35630
  ]);
35615
- const edges = related.map((dst) => ({
35616
- src: memoryId(name),
35617
- dst,
35618
- kind: "mentions",
35619
- source: "curated",
35620
- confidence: 1,
35621
- weight: 1
35622
- }));
35623
- if (supersedes) {
35624
- edges.push({
35625
- src: memoryId(name),
35626
- dst: memoryId(supersedes),
35627
- kind: "supersedes",
35628
- source: "curated",
35629
- confidence: 1,
35630
- weight: 1
35631
- });
35632
- }
35631
+ const edges = [];
35632
+ const seen = /* @__PURE__ */ new Set();
35633
+ const push = (dst, kind) => {
35634
+ const key = `${kind}|${dst}`;
35635
+ if (dst === self || seen.has(key)) return;
35636
+ seen.add(key);
35637
+ edges.push({ src: self, dst, kind, source: "curated", confidence: 1, weight: 1 });
35638
+ };
35639
+ for (const id of g.related) push(relatedTarget(id), "mentions");
35640
+ for (const other of memoryLinks(g.body, name)) push(memoryId(other), "links");
35641
+ if (g.supersedes) push(memoryId(g.supersedes.replace(/^mem(?:ory)?:/, "")), "supersedes");
35633
35642
  if (edges.length) db.graphUpsertEdges(edges);
35634
35643
  }
35635
35644
  function forgetInDb(db, name, rel) {
@@ -35716,7 +35725,7 @@ tags: ${tags.join(", ")}` : ""}`,
35716
35725
  status,
35717
35726
  supersedes
35718
35727
  });
35719
- syncGraph(db, name, rel, kind, tags, related, supersedes);
35728
+ syncGraph(db, name, rel, { kind, tags, related, status, supersedes, body });
35720
35729
  return { changed: true, row: db.memoryGetByName(name) };
35721
35730
  }
35722
35731
  function setMeta(ctx, db, name, patch) {
@@ -35919,7 +35928,7 @@ function gitignoreHint(ctx) {
35919
35928
  const ignored = readFileSync16(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top || l === dir || l === `${top}/**` || l === `${dir}/**`);
35920
35929
  return ignored ? `note: .gitignore ignores ${dir}, so memories stay local to this machine (remove that line to share them with the team)` : void 0;
35921
35930
  }
35922
- var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, hinted;
35931
+ var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, hinted;
35923
35932
  var init_store = __esm({
35924
35933
  "src/memory/store.ts"() {
35925
35934
  "use strict";
@@ -35941,6 +35950,7 @@ var init_store = __esm({
35941
35950
  ];
35942
35951
  SEGMENT = "[a-z0-9][a-z0-9._-]*";
35943
35952
  NAME_RE = new RegExp(`^(?:${SEGMENT}/)?${SEGMENT}$`);
35953
+ MEM_LINK_RE = /(?<![A-Za-z0-9_./-])mem:([a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)?)/g;
35944
35954
  hinted = /* @__PURE__ */ new Set();
35945
35955
  }
35946
35956
  });
@@ -36626,7 +36636,7 @@ function architecture(ctx, db, aspects = ASPECTS) {
36626
36636
  for (const l of languages2.values()) langCounts[l] = (langCounts[l] ?? 0) + 1;
36627
36637
  const counts = db.graphCounts();
36628
36638
  const nodeCounts = db.graphNodeCounts();
36629
- const symbols = Object.entries(nodeCounts).filter(([k]) => !["file", "package", "person", "concept", "memory"].includes(k)).reduce((a, [, n]) => a + n, 0);
36639
+ const symbols = Object.entries(nodeCounts).filter(([k]) => !["file", "package", "person", "concept", "memory", "doc"].includes(k)).reduce((a, [, n]) => a + n, 0);
36630
36640
  const out = {
36631
36641
  project: {
36632
36642
  name: typeof pkg.name === "string" ? pkg.name : void 0,
@@ -39030,6 +39040,341 @@ var init_discover = __esm({
39030
39040
  }
39031
39041
  });
39032
39042
 
39043
+ // src/docs/category.ts
39044
+ function categoryInfo(id) {
39045
+ return BY_ID.get(id) ?? BY_ID.get("local");
39046
+ }
39047
+ function localCategory(s) {
39048
+ const title = s.title ?? "";
39049
+ for (const [re, cat] of TITLE_RULES) if (re.test(title)) return cat;
39050
+ for (const [re, cat] of NAME_RULES) if (re.test(s.name)) return cat;
39051
+ const path3 = s.path ?? "";
39052
+ if (/(^|\/)(adrs?|decisions)$/i.test(path3)) return "adr";
39053
+ const globs = s.glob ?? [];
39054
+ const hits = /* @__PURE__ */ new Map();
39055
+ for (const g of globs)
39056
+ for (const [re, cat] of GLOB_RULES)
39057
+ if (re.test(g)) {
39058
+ hits.set(cat, (hits.get(cat) ?? 0) + 1);
39059
+ break;
39060
+ }
39061
+ if (hits.size) return [...hits.entries()].sort((a, b) => b[1] - a[1])[0][0];
39062
+ if (/(^|\/)(docs?|documentation)$/i.test(path3)) return "guides";
39063
+ if (/README|CONTRIBUTING|CHANGELOG/i.test(path3)) return "readme";
39064
+ return "local";
39065
+ }
39066
+ function docCategory(s, entry) {
39067
+ if (s.kind === "path") return localCategory(s);
39068
+ if (entry) {
39069
+ const tier = entry.tier ?? "library";
39070
+ return tier === "service" ? "service" : tier === "infra" ? "infra" : "library";
39071
+ }
39072
+ const provider = s.provider ?? "";
39073
+ const i = provider.indexOf(":");
39074
+ const kind = i === -1 ? provider : provider.slice(0, i);
39075
+ switch (kind) {
39076
+ case "api":
39077
+ return "api";
39078
+ case "env":
39079
+ return "service";
39080
+ case "image":
39081
+ case "files":
39082
+ return "infra";
39083
+ case "npm":
39084
+ case "pypi":
39085
+ case "go":
39086
+ return "library";
39087
+ default:
39088
+ return "web";
39089
+ }
39090
+ }
39091
+ function categoryRank(id) {
39092
+ const i = DOC_CATEGORIES.findIndex((c) => c.id === id);
39093
+ return i === -1 ? DOC_CATEGORIES.length : i;
39094
+ }
39095
+ var DOC_CATEGORIES, BY_ID, GROUP_LABELS, TITLE_RULES, NAME_RULES, GLOB_RULES;
39096
+ var init_category = __esm({
39097
+ "src/docs/category.ts"() {
39098
+ "use strict";
39099
+ init_esm_shims();
39100
+ DOC_CATEGORIES = [
39101
+ {
39102
+ id: "readme",
39103
+ group: "project",
39104
+ label: "Readme & guides",
39105
+ blurb: "README, CONTRIBUTING, CHANGELOG and other top-level guides"
39106
+ },
39107
+ {
39108
+ id: "guides",
39109
+ group: "project",
39110
+ label: "Docs folders",
39111
+ blurb: "docs/ directories: handbooks, how-tos, references"
39112
+ },
39113
+ {
39114
+ id: "adr",
39115
+ group: "project",
39116
+ label: "Decision records",
39117
+ blurb: "architecture decision records"
39118
+ },
39119
+ {
39120
+ id: "agent-instructions",
39121
+ group: "project",
39122
+ label: "Agent instructions",
39123
+ blurb: "Copilot, Claude and Cursor instructions, agents and skills"
39124
+ },
39125
+ {
39126
+ id: "api-specs",
39127
+ group: "project",
39128
+ label: "API specs",
39129
+ blurb: "OpenAPI, Postman, GraphQL and protobuf definitions"
39130
+ },
39131
+ {
39132
+ id: "env-reference",
39133
+ group: "project",
39134
+ label: "Environment reference",
39135
+ blurb: ".env examples: every variable the project reads"
39136
+ },
39137
+ {
39138
+ id: "deploy-config",
39139
+ group: "project",
39140
+ label: "Deploy config",
39141
+ blurb: "Terraform, Kubernetes, Helm, Compose and CI workflows"
39142
+ },
39143
+ { id: "local", group: "project", label: "Other local files", blurb: "hand-registered paths" },
39144
+ {
39145
+ id: "service",
39146
+ group: "external",
39147
+ label: "Services",
39148
+ blurb: "third-party products and APIs the project integrates"
39149
+ },
39150
+ {
39151
+ id: "api",
39152
+ group: "external",
39153
+ label: "External APIs",
39154
+ blurb: "hosts the code calls, found by probing their docs"
39155
+ },
39156
+ {
39157
+ id: "infra",
39158
+ group: "external",
39159
+ label: "Infrastructure",
39160
+ blurb: "platforms the project runs on"
39161
+ },
39162
+ { id: "library", group: "external", label: "Libraries", blurb: "frameworks and packages" },
39163
+ { id: "web", group: "external", label: "Web pages", blurb: "hand-registered URLs" }
39164
+ ];
39165
+ BY_ID = new Map(DOC_CATEGORIES.map((c) => [c.id, c]));
39166
+ GROUP_LABELS = {
39167
+ project: "Project documentation",
39168
+ external: "External documentation"
39169
+ };
39170
+ TITLE_RULES = [
39171
+ [/^project readme/i, "readme"],
39172
+ [/^(docs|doc|documentation)\/ directory$/i, "guides"],
39173
+ [/decision records/i, "adr"],
39174
+ [/copilot|claude|cursor/i, "agent-instructions"],
39175
+ [/openapi|postman|graphql|protobuf/i, "api-specs"],
39176
+ [/environment variable/i, "env-reference"],
39177
+ [/terraform|kubernetes|helm|compose|workflow/i, "deploy-config"]
39178
+ ];
39179
+ NAME_RULES = [
39180
+ [/(^|-)readme$/, "readme"],
39181
+ [/(^|-)docs$/, "guides"],
39182
+ [/(^|-)adrs?$/, "adr"],
39183
+ [/(^|-)agent-instructions$/, "agent-instructions"],
39184
+ [/(^|-)api-specs$/, "api-specs"],
39185
+ [/(^|-)env-example$/, "env-reference"],
39186
+ [/(^|-)deploy-config$/, "deploy-config"]
39187
+ ];
39188
+ GLOB_RULES = [
39189
+ [/openapi|swagger|postman_collection|asyncapi|\.graphql$|\.proto$/i, "api-specs"],
39190
+ [/^\.env\./, "env-reference"],
39191
+ [
39192
+ /\.tf$|tfvars|^k8s\/|^helm\/|^charts\/|^manifests\/|compose.*\.ya?ml|\.github\/workflows/i,
39193
+ "deploy-config"
39194
+ ],
39195
+ [
39196
+ /copilot-instructions|\.github\/(instructions|agents|skills)|\.claude\/(agents|skills)|\.cursor\/rules|^(AGENTS|CLAUDE)\.md$/i,
39197
+ "agent-instructions"
39198
+ ],
39199
+ [/^(README|CONTRIBUTING|CHANGELOG|SECURITY)(\.md)?$/i, "readme"],
39200
+ [/^\*\*\/\*\.(md|mdx|rst|txt)$/i, "guides"]
39201
+ ];
39202
+ }
39203
+ });
39204
+
39205
+ // src/docs/graph.ts
39206
+ import { existsSync as existsSync21, statSync as statSync9 } from "fs";
39207
+ import { dirname as dirname10, posix, relative as relative8, sep as sep2 } from "path";
39208
+ function manifestRel(ctx) {
39209
+ return relative8(ctx.root, manifestPath(ctx)).split(sep2).join("/");
39210
+ }
39211
+ function packageCandidates(provider) {
39212
+ if (!provider) return [];
39213
+ const i = provider.indexOf(":");
39214
+ if (i === -1) return [];
39215
+ const kind = provider.slice(0, i);
39216
+ const name = provider.slice(i + 1).trim();
39217
+ if (!name) return [];
39218
+ if (kind === "npm" || kind === "go") return [pkgId(name)];
39219
+ if (kind === "pypi") {
39220
+ const lower = name.toLowerCase();
39221
+ return [.../* @__PURE__ */ new Set([pkgId(lower.replace(/-/g, "_")), pkgId(lower), pkgId(name)])];
39222
+ }
39223
+ return [];
39224
+ }
39225
+ function pathsIn(text3, pageRel, files) {
39226
+ const out = /* @__PURE__ */ new Set();
39227
+ for (const m of text3.matchAll(PATH_RE)) {
39228
+ const raw = m[1];
39229
+ const tries = [raw.replace(/^(\.\/)+/, "")];
39230
+ if (pageRel && raw.startsWith("."))
39231
+ tries.unshift(posix.normalize(posix.join(dirname10(pageRel), raw)));
39232
+ for (const t of tries) {
39233
+ if (files.has(t)) {
39234
+ out.add(t);
39235
+ break;
39236
+ }
39237
+ }
39238
+ }
39239
+ return [...out];
39240
+ }
39241
+ function pageRelOf(ctx, uri) {
39242
+ if (!uri?.startsWith("file://")) return void 0;
39243
+ const rel = relative8(ctx.root, uri.slice("file://".length)).split(sep2).join("/");
39244
+ return rel.startsWith("..") ? void 0 : rel;
39245
+ }
39246
+ function localEdges(ctx, db, src, files) {
39247
+ const id = docId(src.name);
39248
+ const hits = /* @__PURE__ */ new Map();
39249
+ for (const page of db.documentsBySource(docsTag(src.name), MAX_PAGES)) {
39250
+ const rel = pageRelOf(ctx, page.uri);
39251
+ if (rel && files.has(rel)) hits.set(rel, { via: "covers", count: 1 });
39252
+ const text3 = db.chunksOfDocument(page.id).map((c) => c.body).join("\n");
39253
+ for (const f of pathsIn(text3, rel, files)) {
39254
+ const h = hits.get(f);
39255
+ if (h) h.count++;
39256
+ else hits.set(f, { via: "mentions", count: 1 });
39257
+ }
39258
+ if (hits.size >= MAX_EDGES) break;
39259
+ }
39260
+ return [...hits].sort((a, b) => b[1].count - a[1].count).slice(0, MAX_EDGES).map(([rel, h]) => ({
39261
+ src: id,
39262
+ dst: `file:${rel}`,
39263
+ kind: DOCUMENTS_EDGE,
39264
+ source: "curated",
39265
+ confidence: h.via === "covers" ? 1 : 0.8,
39266
+ weight: Math.min(1, 0.5 + h.count / 10),
39267
+ meta: { via: h.via, count: h.count }
39268
+ }));
39269
+ }
39270
+ function packageEdges(src, packages) {
39271
+ const id = docId(src.name);
39272
+ const cands = packageCandidates(src.provider);
39273
+ if (!cands.length) return [];
39274
+ let targets = cands.filter((c) => packages.has(c));
39275
+ if (src.provider?.startsWith("go:"))
39276
+ targets = [...targets, ...[...packages].filter((p) => p.startsWith(`${cands[0]}/`))];
39277
+ if (!targets.length) targets = [cands[0]];
39278
+ return [...new Set(targets)].map((dst) => ({
39279
+ src: id,
39280
+ dst,
39281
+ kind: DOCUMENTS_EDGE,
39282
+ source: "curated",
39283
+ confidence: 1,
39284
+ weight: 1,
39285
+ meta: { via: "provider" }
39286
+ }));
39287
+ }
39288
+ function localDocPages(ctx, db) {
39289
+ const out = /* @__PURE__ */ new Map();
39290
+ for (const s of readManifest(ctx).sources) {
39291
+ if (s.kind !== "path") continue;
39292
+ for (const page of db.documentsBySource(docsTag(s.name), MAX_PAGES)) {
39293
+ const rel = pageRelOf(ctx, page.uri);
39294
+ if (!rel) continue;
39295
+ const names = out.get(rel) ?? [];
39296
+ if (!names.includes(s.name)) names.push(s.name);
39297
+ out.set(rel, names);
39298
+ }
39299
+ }
39300
+ return out;
39301
+ }
39302
+ function stampOf(ctx, db) {
39303
+ const file3 = manifestPath(ctx);
39304
+ const st = existsSync21(file3) ? statSync9(file3) : void 0;
39305
+ const counts = db.graphNodeCounts();
39306
+ const syncs = readManifest(ctx).sources.map((s) => syncState(db, s.name)?.at ?? 0).join(",");
39307
+ return `${st ? `${Math.floor(st.mtimeMs)}:${st.size}` : "none"}|${counts.file ?? 0}|${counts.package ?? 0}|${syncs}`;
39308
+ }
39309
+ function syncDocsGraph(ctx, db) {
39310
+ const rel = manifestRel(ctx);
39311
+ const m = readManifest(ctx);
39312
+ const catalog = mergedCatalog(ctx.config.docs.catalog);
39313
+ const files = new Set(
39314
+ db.graphFileNodes().filter((n) => n.source === "tree-sitter" && n.path).map((n) => n.path)
39315
+ );
39316
+ const packages = new Set(db.graphNodesByKind(["package"]).map((n) => n.id));
39317
+ const nodes = [];
39318
+ const edges = [];
39319
+ for (const s of m.sources) {
39320
+ const category = docCategory(
39321
+ s,
39322
+ s.provider ? catalogForProvider(s.provider, catalog) : void 0
39323
+ );
39324
+ nodes.push({
39325
+ id: docId(s.name),
39326
+ kind: "doc",
39327
+ name: s.name,
39328
+ path: rel,
39329
+ source: "curated",
39330
+ meta: {
39331
+ category,
39332
+ group: categoryInfo(category).group,
39333
+ sourceKind: s.kind,
39334
+ ...s.provider ? { provider: s.provider } : {},
39335
+ ...s.title ? { title: s.title } : {},
39336
+ ...s.tags?.length ? { tags: s.tags } : {},
39337
+ ...s.kind === "path" ? { target: s.path } : { url: s.urls?.[0] },
39338
+ documents: db.countDocuments(docsTag(s.name))
39339
+ }
39340
+ });
39341
+ edges.push(...packageEdges(s, packages));
39342
+ if (s.kind === "path") edges.push(...localEdges(ctx, db, s, files));
39343
+ }
39344
+ db.graphDeleteForPath("curated", rel, { keepFile: true });
39345
+ if (nodes.length) db.graphUpsertNodes(nodes);
39346
+ if (edges.length) db.graphUpsertEdges(edges);
39347
+ db.metaSet(STAMP_KEY, stampOf(ctx, db));
39348
+ return { sources: nodes.length, edges: edges.length };
39349
+ }
39350
+ function ensureDocsGraph(ctx, db) {
39351
+ if (db.metaGet(STAMP_KEY) === stampOf(ctx, db)) return false;
39352
+ syncDocsGraph(ctx, db);
39353
+ return true;
39354
+ }
39355
+ function syncDocsGraphQuietly(ctx, db) {
39356
+ try {
39357
+ syncDocsGraph(ctx, db);
39358
+ } catch {
39359
+ }
39360
+ }
39361
+ var DOCUMENTS_EDGE, MAX_PAGES, MAX_EDGES, PATH_RE, STAMP_KEY;
39362
+ var init_graph = __esm({
39363
+ "src/docs/graph.ts"() {
39364
+ "use strict";
39365
+ init_esm_shims();
39366
+ init_ids();
39367
+ init_catalog();
39368
+ init_category();
39369
+ init_manifest();
39370
+ DOCUMENTS_EDGE = "documents";
39371
+ MAX_PAGES = 500;
39372
+ MAX_EDGES = 400;
39373
+ PATH_RE = /(?:^|[\s(`'"[<])((?:\.{1,2}\/)*(?:[\w@.-]+\/)+[\w@.-]+\.[A-Za-z0-9]{1,8})(?=$|[\s)`'"\]>:#,;!?]|\.(?:\s|$))/gm;
39374
+ STAMP_KEY = "docs.graph.stamp";
39375
+ }
39376
+ });
39377
+
39033
39378
  // src/docs/resolve.ts
39034
39379
  async function getJson(url2, opts) {
39035
39380
  const f = opts.fetchImpl ?? fetch;
@@ -39914,6 +40259,7 @@ async function syncSources(ctx, db, names, opts = {}) {
39914
40259
  });
39915
40260
  }
39916
40261
  for (const src of wanted) out.push(await syncSource(ctx, db, src, opts));
40262
+ syncDocsGraphQuietly(ctx, db);
39917
40263
  return out;
39918
40264
  }
39919
40265
  function formatSync(results) {
@@ -39943,6 +40289,7 @@ var init_sync = __esm({
39943
40289
  init_queue();
39944
40290
  init_fetcher();
39945
40291
  init_indexer();
40292
+ init_graph();
39946
40293
  init_manifest();
39947
40294
  init_specs();
39948
40295
  SKIP_EXT2 = /\.(png|jpe?g|gif|svg|webp|ico|css|js|mjs|map|zip|gz|tgz|tar|pdf|woff2?|ttf|otf|eot|mp4|mp3|webm|xml|rss|atom|wasm)$/i;
@@ -40086,12 +40433,14 @@ async function addDocSources(ctx, db, inputs, opts = {}) {
40086
40433
  const names = [...out.added, ...out.updated];
40087
40434
  if (opts.sync !== false && names.length)
40088
40435
  out.sync = await syncSources(ctx, db, names, { force: opts.force });
40436
+ else if (names.length) syncDocsGraphQuietly(ctx, db);
40089
40437
  return out;
40090
40438
  }
40091
40439
  function removeDocSource(ctx, db, name) {
40092
40440
  const removed = removeSource(ctx, name);
40093
40441
  const documents = db.deleteDocumentsBySource(docsTag(name));
40094
40442
  clearSyncState(db, name);
40443
+ if (removed) syncDocsGraphQuietly(ctx, db);
40095
40444
  return { removed, documents };
40096
40445
  }
40097
40446
  function formatAdd(o) {
@@ -40125,6 +40474,7 @@ var init_add = __esm({
40125
40474
  init_esm_shims();
40126
40475
  init_catalog();
40127
40476
  init_discover();
40477
+ init_graph();
40128
40478
  init_manifest();
40129
40479
  init_resolve2();
40130
40480
  init_sync();
@@ -40141,168 +40491,6 @@ var init_add = __esm({
40141
40491
  }
40142
40492
  });
40143
40493
 
40144
- // src/docs/category.ts
40145
- function categoryInfo(id) {
40146
- return BY_ID.get(id) ?? BY_ID.get("local");
40147
- }
40148
- function localCategory(s) {
40149
- const title = s.title ?? "";
40150
- for (const [re, cat] of TITLE_RULES) if (re.test(title)) return cat;
40151
- for (const [re, cat] of NAME_RULES) if (re.test(s.name)) return cat;
40152
- const path3 = s.path ?? "";
40153
- if (/(^|\/)(adrs?|decisions)$/i.test(path3)) return "adr";
40154
- const globs = s.glob ?? [];
40155
- const hits = /* @__PURE__ */ new Map();
40156
- for (const g of globs)
40157
- for (const [re, cat] of GLOB_RULES)
40158
- if (re.test(g)) {
40159
- hits.set(cat, (hits.get(cat) ?? 0) + 1);
40160
- break;
40161
- }
40162
- if (hits.size) return [...hits.entries()].sort((a, b) => b[1] - a[1])[0][0];
40163
- if (/(^|\/)(docs?|documentation)$/i.test(path3)) return "guides";
40164
- if (/README|CONTRIBUTING|CHANGELOG/i.test(path3)) return "readme";
40165
- return "local";
40166
- }
40167
- function docCategory(s, entry) {
40168
- if (s.kind === "path") return localCategory(s);
40169
- if (entry) {
40170
- const tier = entry.tier ?? "library";
40171
- return tier === "service" ? "service" : tier === "infra" ? "infra" : "library";
40172
- }
40173
- const provider = s.provider ?? "";
40174
- const i = provider.indexOf(":");
40175
- const kind = i === -1 ? provider : provider.slice(0, i);
40176
- switch (kind) {
40177
- case "api":
40178
- return "api";
40179
- case "env":
40180
- return "service";
40181
- case "image":
40182
- case "files":
40183
- return "infra";
40184
- case "npm":
40185
- case "pypi":
40186
- case "go":
40187
- return "library";
40188
- default:
40189
- return "web";
40190
- }
40191
- }
40192
- function categoryRank(id) {
40193
- const i = DOC_CATEGORIES.findIndex((c) => c.id === id);
40194
- return i === -1 ? DOC_CATEGORIES.length : i;
40195
- }
40196
- var DOC_CATEGORIES, BY_ID, GROUP_LABELS, TITLE_RULES, NAME_RULES, GLOB_RULES;
40197
- var init_category = __esm({
40198
- "src/docs/category.ts"() {
40199
- "use strict";
40200
- init_esm_shims();
40201
- DOC_CATEGORIES = [
40202
- {
40203
- id: "readme",
40204
- group: "project",
40205
- label: "Readme & guides",
40206
- blurb: "README, CONTRIBUTING, CHANGELOG and other top-level guides"
40207
- },
40208
- {
40209
- id: "guides",
40210
- group: "project",
40211
- label: "Docs folders",
40212
- blurb: "docs/ directories: handbooks, how-tos, references"
40213
- },
40214
- {
40215
- id: "adr",
40216
- group: "project",
40217
- label: "Decision records",
40218
- blurb: "architecture decision records"
40219
- },
40220
- {
40221
- id: "agent-instructions",
40222
- group: "project",
40223
- label: "Agent instructions",
40224
- blurb: "Copilot, Claude and Cursor instructions, agents and skills"
40225
- },
40226
- {
40227
- id: "api-specs",
40228
- group: "project",
40229
- label: "API specs",
40230
- blurb: "OpenAPI, Postman, GraphQL and protobuf definitions"
40231
- },
40232
- {
40233
- id: "env-reference",
40234
- group: "project",
40235
- label: "Environment reference",
40236
- blurb: ".env examples: every variable the project reads"
40237
- },
40238
- {
40239
- id: "deploy-config",
40240
- group: "project",
40241
- label: "Deploy config",
40242
- blurb: "Terraform, Kubernetes, Helm, Compose and CI workflows"
40243
- },
40244
- { id: "local", group: "project", label: "Other local files", blurb: "hand-registered paths" },
40245
- {
40246
- id: "service",
40247
- group: "external",
40248
- label: "Services",
40249
- blurb: "third-party products and APIs the project integrates"
40250
- },
40251
- {
40252
- id: "api",
40253
- group: "external",
40254
- label: "External APIs",
40255
- blurb: "hosts the code calls, found by probing their docs"
40256
- },
40257
- {
40258
- id: "infra",
40259
- group: "external",
40260
- label: "Infrastructure",
40261
- blurb: "platforms the project runs on"
40262
- },
40263
- { id: "library", group: "external", label: "Libraries", blurb: "frameworks and packages" },
40264
- { id: "web", group: "external", label: "Web pages", blurb: "hand-registered URLs" }
40265
- ];
40266
- BY_ID = new Map(DOC_CATEGORIES.map((c) => [c.id, c]));
40267
- GROUP_LABELS = {
40268
- project: "Project documentation",
40269
- external: "External documentation"
40270
- };
40271
- TITLE_RULES = [
40272
- [/^project readme/i, "readme"],
40273
- [/^(docs|doc|documentation)\/ directory$/i, "guides"],
40274
- [/decision records/i, "adr"],
40275
- [/copilot|claude|cursor/i, "agent-instructions"],
40276
- [/openapi|postman|graphql|protobuf/i, "api-specs"],
40277
- [/environment variable/i, "env-reference"],
40278
- [/terraform|kubernetes|helm|compose|workflow/i, "deploy-config"]
40279
- ];
40280
- NAME_RULES = [
40281
- [/(^|-)readme$/, "readme"],
40282
- [/(^|-)docs$/, "guides"],
40283
- [/(^|-)adrs?$/, "adr"],
40284
- [/(^|-)agent-instructions$/, "agent-instructions"],
40285
- [/(^|-)api-specs$/, "api-specs"],
40286
- [/(^|-)env-example$/, "env-reference"],
40287
- [/(^|-)deploy-config$/, "deploy-config"]
40288
- ];
40289
- GLOB_RULES = [
40290
- [/openapi|swagger|postman_collection|asyncapi|\.graphql$|\.proto$/i, "api-specs"],
40291
- [/^\.env\./, "env-reference"],
40292
- [
40293
- /\.tf$|tfvars|^k8s\/|^helm\/|^charts\/|^manifests\/|compose.*\.ya?ml|\.github\/workflows/i,
40294
- "deploy-config"
40295
- ],
40296
- [
40297
- /copilot-instructions|\.github\/(instructions|agents|skills)|\.claude\/(agents|skills)|\.cursor\/rules|^(AGENTS|CLAUDE)\.md$/i,
40298
- "agent-instructions"
40299
- ],
40300
- [/^(README|CONTRIBUTING|CHANGELOG|SECURITY)(\.md)?$/i, "readme"],
40301
- [/^\*\*\/\*\.(md|mdx|rst|txt)$/i, "guides"]
40302
- ];
40303
- }
40304
- });
40305
-
40306
40494
  // src/tools/em_docs.ts
40307
40495
  var em_docs_exports = {};
40308
40496
  __export(em_docs_exports, {
@@ -41003,8 +41191,8 @@ var init_em_search = __esm({
41003
41191
  });
41004
41192
 
41005
41193
  // src/ui/api.ts
41006
- import { existsSync as existsSync21, readdirSync as readdirSync8 } from "fs";
41007
- import { join as join26, relative as relative8 } from "path";
41194
+ import { existsSync as existsSync22, readdirSync as readdirSync8 } from "fs";
41195
+ import { join as join26, relative as relative9 } from "path";
41008
41196
  function listProjects() {
41009
41197
  const dir = paths().projects;
41010
41198
  const out = [];
@@ -41037,7 +41225,7 @@ function statsPayload(ctx) {
41037
41225
  const sessionId = getCurrentSession(ctx);
41038
41226
  const g = globalDb();
41039
41227
  const onboardingDir = join26(memoriesDir(ctx), "onboarding");
41040
- const onboarding = existsSync21(onboardingDir) ? readdirSync8(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
41228
+ const onboarding = existsSync22(onboardingDir) ? readdirSync8(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
41041
41229
  return {
41042
41230
  project: ctx.root,
41043
41231
  projectId: ctx.id,
@@ -41229,7 +41417,7 @@ function docsPayload(ctx) {
41229
41417
  const known = new Set(m.sources.map((s) => docsTag(s.name)));
41230
41418
  const orphans2 = [...stats.values()].filter((s) => !known.has(s.source)).map((s) => ({ source: s.source, documents: s.documents, chunks: s.chunks }));
41231
41419
  return {
41232
- manifest: relative8(ctx.root, manifestPath(ctx)),
41420
+ manifest: relative9(ctx.root, manifestPath(ctx)),
41233
41421
  sources,
41234
41422
  orphans: orphans2,
41235
41423
  totals: {
@@ -41575,7 +41763,7 @@ var init_namepath = __esm({
41575
41763
  });
41576
41764
 
41577
41765
  // src/code/symbols.ts
41578
- import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
41766
+ import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
41579
41767
  import { join as join27 } from "path";
41580
41768
  function isExported2(n) {
41581
41769
  if (!n.meta) return false;
@@ -41612,8 +41800,8 @@ var init_symbols2 = __esm({
41612
41800
  });
41613
41801
 
41614
41802
  // src/ui/graph-api.ts
41615
- import { readFileSync as readFileSync21, realpathSync as realpathSync2, statSync as statSync9 } from "fs";
41616
- import { relative as relative9, resolve as resolve7 } from "path";
41803
+ import { readFileSync as readFileSync21, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
41804
+ import { relative as relative10, resolve as resolve7 } from "path";
41617
41805
  function metaOf2(row) {
41618
41806
  if (!row?.meta) return {};
41619
41807
  try {
@@ -41675,16 +41863,16 @@ function treePayload(ctx) {
41675
41863
  function filePayload(ctx, rel) {
41676
41864
  const clean = rel.replace(/^\.\//, "");
41677
41865
  const abs = resolve7(ctx.root, clean);
41678
- if (relative9(ctx.root, abs).startsWith("..")) throw new Error("path outside the project");
41866
+ if (relative10(ctx.root, abs).startsWith("..")) throw new Error("path outside the project");
41679
41867
  let real = abs;
41680
41868
  try {
41681
41869
  real = realpathSync2(abs);
41682
41870
  } catch {
41683
41871
  return { error: "not found", path: clean };
41684
41872
  }
41685
- if (relative9(realpathSync2(ctx.root), real).startsWith(".."))
41873
+ if (relative10(realpathSync2(ctx.root), real).startsWith(".."))
41686
41874
  throw new Error("path outside the project");
41687
- const st = statSync9(real);
41875
+ const st = statSync10(real);
41688
41876
  if (!st.isFile()) return { error: "not a file", path: clean };
41689
41877
  const db = getStorage(ctx);
41690
41878
  const row = db.fileIndexGet(clean);
@@ -43269,6 +43457,247 @@ var init_history_api = __esm({
43269
43457
  }
43270
43458
  });
43271
43459
 
43460
+ // src/ui/knowledge-api.ts
43461
+ function metaOf3(row) {
43462
+ if (!row?.meta) return {};
43463
+ try {
43464
+ return JSON.parse(row.meta);
43465
+ } catch {
43466
+ return {};
43467
+ }
43468
+ }
43469
+ function kindOfMissing(id) {
43470
+ const k = parseId(id).kind;
43471
+ return k === "pkg" ? "package" : k === "sym" ? "symbol" : k === "doc" ? "doc" : k;
43472
+ }
43473
+ function scopeOf2(raw) {
43474
+ return raw === "memory" || raw === "docs" ? raw : "all";
43475
+ }
43476
+ function knowledgePayload(ctx, opts = {}) {
43477
+ const db = getStorage(ctx);
43478
+ reconcile(ctx, db);
43479
+ try {
43480
+ ensureDocsGraph(ctx, db);
43481
+ } catch {
43482
+ }
43483
+ return buildKnowledge(ctx, db, opts);
43484
+ }
43485
+ function buildKnowledge(ctx, db, opts = {}) {
43486
+ const scope = opts.scope ?? "all";
43487
+ const limit = Math.max(10, Math.min(2e3, opts.limit ?? 300));
43488
+ const focus = opts.focus?.trim() || null;
43489
+ const memRows = new Map(
43490
+ db.memoryList({ limit: 1e5 }).filter((r) => r.name).map((r) => [r.name, r])
43491
+ );
43492
+ const memNodes = db.graphNodesByKind(["memory"]);
43493
+ const docNodes = db.graphNodesByKind(["doc"]);
43494
+ const rows = /* @__PURE__ */ new Map();
43495
+ for (const n of [...memNodes, ...docNodes]) rows.set(n.id, n);
43496
+ const hubsOf = (id) => {
43497
+ const n = rows.get(id);
43498
+ if (!n) return [];
43499
+ const m = metaOf3(n);
43500
+ const out = [];
43501
+ if (n.kind === "memory") {
43502
+ const row = memRows.get(n.name);
43503
+ const tags = row?.tags ? JSON.parse(row.tags) : m.tags ?? [];
43504
+ for (const t of tags) out.push({ id: tagId(t), kind: "tag", label: `#${t}` });
43505
+ if (n.name.includes("/")) {
43506
+ const topic = n.name.split("/")[0];
43507
+ out.push({ id: topicId(topic), kind: "topic", label: `${topic}/` });
43508
+ }
43509
+ } else if (n.kind === "doc") {
43510
+ for (const t of m.tags ?? [])
43511
+ out.push({ id: tagId(t), kind: "tag", label: `#${t}` });
43512
+ const cat = typeof m.category === "string" ? m.category : "local";
43513
+ out.push({ id: topicId(`docs/${cat}`), kind: "topic", label: categoryInfo(cat).label });
43514
+ }
43515
+ return out;
43516
+ };
43517
+ const scoped = [
43518
+ ...scope !== "docs" ? memNodes : [],
43519
+ ...scope !== "memory" ? docNodes : []
43520
+ ].map((n) => n.id);
43521
+ let seeds;
43522
+ if (!focus) seeds = scoped;
43523
+ else if (focus.startsWith("tag:") || focus.startsWith("topic:"))
43524
+ seeds = [...memNodes, ...docNodes].map((n) => n.id).filter((id) => hubsOf(id).some((h) => h.id === focus));
43525
+ else seeds = [focus];
43526
+ const edges = /* @__PURE__ */ new Map();
43527
+ const addEdge = (e) => {
43528
+ if (e.src === e.dst) return;
43529
+ const key = `${e.src}|${e.dst}|${e.kind}`;
43530
+ if (!edges.has(key)) edges.set(key, e);
43531
+ };
43532
+ const stored = (e) => addEdge({ src: e.src, dst: e.dst, kind: e.kind, weight: e.weight });
43533
+ const hop = (ids2) => {
43534
+ if (!ids2.length) return [];
43535
+ const found = /* @__PURE__ */ new Set();
43536
+ for (const e of db.graphEdgesFrom(ids2, ALL_KINDS)) {
43537
+ stored(e);
43538
+ found.add(e.dst);
43539
+ }
43540
+ for (const e of db.graphEdgesTo(ids2, ALL_KINDS)) {
43541
+ stored(e);
43542
+ found.add(e.src);
43543
+ }
43544
+ return [...found];
43545
+ };
43546
+ const first = hop(seeds);
43547
+ if (focus && !focus.startsWith("tag:") && !focus.startsWith("topic:")) {
43548
+ const fresh = first.filter((id) => !seeds.includes(id));
43549
+ hop(fresh);
43550
+ }
43551
+ const pages = localDocPages(ctx, db);
43552
+ const knowledgeIds = () => new Set([...edges.values()].flatMap((e) => [e.src, e.dst]).concat(seeds));
43553
+ if (pages.size) {
43554
+ for (const id of knowledgeIds()) {
43555
+ if (!id.startsWith("memory:")) continue;
43556
+ const row = memRows.get(id.slice("memory:".length));
43557
+ const related = row?.node_ids ? JSON.parse(row.node_ids) : [];
43558
+ for (const r of related) {
43559
+ const f = fileOf(r);
43560
+ for (const name of f && pages.get(f) || [])
43561
+ addEdge({ src: id, dst: docId(name), kind: "about", weight: 1, derived: true });
43562
+ }
43563
+ }
43564
+ const docsInView = [...knowledgeIds()].filter((id) => id.startsWith("doc:"));
43565
+ if (docsInView.length) {
43566
+ for (const [name, row] of memRows) {
43567
+ const related = row.node_ids ? JSON.parse(row.node_ids) : [];
43568
+ for (const r of related) {
43569
+ const f = fileOf(r);
43570
+ for (const doc of f && pages.get(f) || [])
43571
+ if (docsInView.includes(docId(doc)))
43572
+ addEdge({
43573
+ src: memoryId(name),
43574
+ dst: docId(doc),
43575
+ kind: "about",
43576
+ weight: 1,
43577
+ derived: true
43578
+ });
43579
+ }
43580
+ }
43581
+ }
43582
+ }
43583
+ const hubNodes = /* @__PURE__ */ new Map();
43584
+ for (const id of knowledgeIds()) {
43585
+ for (const h of hubsOf(id)) {
43586
+ if (!hubNodes.has(h.id))
43587
+ hubNodes.set(h.id, {
43588
+ id: h.id,
43589
+ label: h.label,
43590
+ kind: h.kind,
43591
+ path: null,
43592
+ line: null,
43593
+ group: "hub"
43594
+ });
43595
+ addEdge({
43596
+ src: id,
43597
+ dst: h.id,
43598
+ kind: h.kind === "tag" ? "tagged" : "in_topic",
43599
+ weight: 0.5,
43600
+ derived: true
43601
+ });
43602
+ }
43603
+ }
43604
+ if (focus && (focus.startsWith("tag:") || focus.startsWith("topic:")) && !hubNodes.has(focus))
43605
+ hubNodes.set(focus, {
43606
+ id: focus,
43607
+ label: focus.startsWith("tag:") ? `#${focus.slice(4)}` : `${focus.slice(6)}/`,
43608
+ kind: focus.startsWith("tag:") ? "tag" : "topic",
43609
+ path: null,
43610
+ line: null,
43611
+ group: "hub"
43612
+ });
43613
+ const ids = knowledgeIds();
43614
+ for (const h of hubNodes.keys()) ids.add(h);
43615
+ const graphRows = new Map(
43616
+ db.graphGetNodes([...ids].filter((id) => !hubNodes.has(id))).map((n) => [n.id, n])
43617
+ );
43618
+ const seedSet = new Set(seeds);
43619
+ if (focus) seedSet.add(focus);
43620
+ const nodes = [];
43621
+ for (const id of ids) {
43622
+ const hub = hubNodes.get(id);
43623
+ if (hub) {
43624
+ nodes.push({ ...hub, seed: seedSet.has(id) || void 0, root: id === focus });
43625
+ continue;
43626
+ }
43627
+ const row = graphRows.get(id);
43628
+ const base = vis(row, id, id === focus);
43629
+ const m = metaOf3(row);
43630
+ const node2 = {
43631
+ ...base,
43632
+ kind: row ? row.kind : kindOfMissing(id),
43633
+ group: "code",
43634
+ seed: seedSet.has(id) || void 0,
43635
+ missing: row ? void 0 : true
43636
+ };
43637
+ if (node2.kind === "memory") {
43638
+ const name = row?.name ?? id.slice("memory:".length);
43639
+ const mr = memRows.get(name);
43640
+ node2.group = "memory";
43641
+ node2.label = name;
43642
+ node2.sub = mr?.kind ?? m.kind;
43643
+ node2.status = mr?.status ?? m.status ?? (node2.sub === "decision" ? "accepted" : void 0);
43644
+ node2.tags = mr?.tags ? JSON.parse(mr.tags) : m.tags;
43645
+ node2.detail = name.includes("/") ? `${name.split("/")[0]}/` : void 0;
43646
+ } else if (node2.kind === "doc") {
43647
+ node2.group = "doc";
43648
+ node2.label = row?.name ?? id.slice("doc:".length);
43649
+ node2.sub = typeof m.category === "string" ? m.category : void 0;
43650
+ node2.detail = node2.sub ? categoryInfo(node2.sub).label : void 0;
43651
+ node2.provider = typeof m.provider === "string" ? m.provider : void 0;
43652
+ node2.tags = m.tags;
43653
+ }
43654
+ nodes.push(node2);
43655
+ }
43656
+ const deg = /* @__PURE__ */ new Map();
43657
+ for (const e of edges.values()) {
43658
+ deg.set(e.src, (deg.get(e.src) ?? 0) + 1);
43659
+ deg.set(e.dst, (deg.get(e.dst) ?? 0) + 1);
43660
+ }
43661
+ const updated = (n) => n.kind === "memory" ? memRows.get(n.label)?.updated_at ?? 0 : 0;
43662
+ const rank = (n) => n.root ? 0 : n.seed ? 1 : n.group === "memory" || n.group === "doc" ? 2 : n.group === "hub" ? 3 : 4;
43663
+ nodes.sort(
43664
+ (a, b) => rank(a) - rank(b) || updated(b) - updated(a) || (deg.get(b.id) ?? 0) - (deg.get(a.id) ?? 0) || a.id.localeCompare(b.id)
43665
+ );
43666
+ const total = nodes.length;
43667
+ const kept = nodes.slice(0, limit);
43668
+ const keep = new Set(kept.map((n) => n.id));
43669
+ const keptEdges = [...edges.values()].filter((e) => keep.has(e.src) && keep.has(e.dst));
43670
+ const counts = { nodes: {}, edges: {} };
43671
+ for (const n of kept) counts.nodes[n.kind] = (counts.nodes[n.kind] ?? 0) + 1;
43672
+ for (const e of keptEdges) counts.edges[e.kind] = (counts.edges[e.kind] ?? 0) + 1;
43673
+ return {
43674
+ scope,
43675
+ focus,
43676
+ nodes: kept,
43677
+ edges: keptEdges,
43678
+ truncated: total > kept.length,
43679
+ total,
43680
+ counts
43681
+ };
43682
+ }
43683
+ var KNOWLEDGE_EDGES, ALL_KINDS, tagId, topicId;
43684
+ var init_knowledge_api = __esm({
43685
+ "src/ui/knowledge-api.ts"() {
43686
+ "use strict";
43687
+ init_esm_shims();
43688
+ init_category();
43689
+ init_graph();
43690
+ init_ids();
43691
+ init_store();
43692
+ init_storage();
43693
+ init_graph_api();
43694
+ KNOWLEDGE_EDGES = ["mentions", "links", "supersedes", "documents"];
43695
+ ALL_KINDS = [...KNOWLEDGE_EDGES];
43696
+ tagId = (t) => `tag:${t}`;
43697
+ topicId = (t) => `topic:${t}`;
43698
+ }
43699
+ });
43700
+
43272
43701
  // src/ui/server.ts
43273
43702
  var server_exports = {};
43274
43703
  __export(server_exports, {
@@ -43276,7 +43705,7 @@ __export(server_exports, {
43276
43705
  uiDir: () => uiDir
43277
43706
  });
43278
43707
  import { spawn as spawn2 } from "child_process";
43279
- import { existsSync as existsSync23, readFileSync as readFileSync22, statSync as statSync10 } from "fs";
43708
+ import { existsSync as existsSync24, readFileSync as readFileSync22, statSync as statSync11 } from "fs";
43280
43709
  import { createServer } from "http";
43281
43710
  import { extname as extname3, join as join28, normalize as normalize6 } from "path";
43282
43711
  import { URL as URL2 } from "url";
@@ -43326,12 +43755,12 @@ function serveStatic(res, p) {
43326
43755
  const rel = p === "/" ? "index.html" : p.replace(/^\/+/, "");
43327
43756
  const file3 = normalize6(join28(dir, rel));
43328
43757
  if (!file3.startsWith(normalize6(dir))) return false;
43329
- if (!existsSync23(file3) || !statSync10(file3).isFile()) return false;
43758
+ if (!existsSync24(file3) || !statSync11(file3).isFile()) return false;
43330
43759
  const type = MIME[extname3(file3).toLowerCase()] ?? "application/octet-stream";
43331
43760
  if (rel === "index.html") {
43332
43761
  const stamp = (name) => {
43333
43762
  try {
43334
- return `/${name}?v=${Math.floor(statSync10(join28(dir, name)).mtimeMs)}`;
43763
+ return `/${name}?v=${Math.floor(statSync11(join28(dir, name)).mtimeMs)}`;
43335
43764
  } catch {
43336
43765
  return `/${name}`;
43337
43766
  }
@@ -43495,6 +43924,16 @@ async function serve(opts) {
43495
43924
  if (!provider) return json2(res, 400, { error: "provider required" });
43496
43925
  return json2(res, 200, await docsResolvePayload(ctx(), provider));
43497
43926
  }
43927
+ if (p === "/api/knowledge")
43928
+ return json2(
43929
+ res,
43930
+ 200,
43931
+ knowledgePayload(ctx(), {
43932
+ scope: scopeOf2(q.get("scope")),
43933
+ focus: str2("focus"),
43934
+ limit: num2("limit", 300)
43935
+ })
43936
+ );
43498
43937
  if (p === "/api/graph") {
43499
43938
  const mode = q.get("mode") ?? "impact";
43500
43939
  const node2 = q.get("node") ?? "";
@@ -43705,6 +44144,7 @@ var init_server = __esm({
43705
44144
  init_api2();
43706
44145
  init_graph_api();
43707
44146
  init_history_api();
44147
+ init_knowledge_api();
43708
44148
  MIME = {
43709
44149
  ".html": "text/html; charset=utf-8",
43710
44150
  ".js": "text/javascript; charset=utf-8",
@@ -43727,7 +44167,7 @@ var statusline_exports = {};
43727
44167
  __export(statusline_exports, {
43728
44168
  renderStatusline: () => renderStatusline
43729
44169
  });
43730
- import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
44170
+ import { existsSync as existsSync25, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
43731
44171
  function renderStatusline(raw) {
43732
44172
  let input2 = {};
43733
44173
  try {
@@ -43736,7 +44176,7 @@ function renderStatusline(raw) {
43736
44176
  }
43737
44177
  const cache3 = paths().statuslineCache;
43738
44178
  try {
43739
- if (existsSync24(cache3)) {
44179
+ if (existsSync25(cache3)) {
43740
44180
  const c = JSON.parse(readFileSync23(cache3, "utf8"));
43741
44181
  if (Date.now() - c.at < 5e3 && c.key === (input2.session_id ?? "")) return c.line;
43742
44182
  }