@datasynx/agentic-ai-cartography 2.7.0 → 2.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.
package/dist/index.cjs CHANGED
@@ -49,6 +49,7 @@ __export(src_exports, {
49
49
  DEFAULT_SERVER_NAME: () => DEFAULT_SERVER_NAME,
50
50
  DEFAULT_TENANT: () => DEFAULT_TENANT,
51
51
  DriftConfigSchema: () => DriftConfigSchema,
52
+ GraphStoreBackend: () => GraphStoreBackend,
52
53
  INGEST_SCHEMA_VERSION: () => INGEST_SCHEMA_VERSION,
53
54
  IngestEnvelopeSchema: () => IngestEnvelopeSchema,
54
55
  InvalidTenantError: () => InvalidTenantError,
@@ -220,6 +221,7 @@ __export(src_exports, {
220
221
  nodesToAssets: () => nodesToAssets,
221
222
  normalizeId: () => normalizeId,
222
223
  normalizeTenant: () => normalizeTenant,
224
+ openStoreBackend: () => openStoreBackend,
223
225
  orgKeyPath: () => orgKeyPath,
224
226
  osUser: () => osUser,
225
227
  parseApiArgs: () => parseApiArgs,
@@ -4653,6 +4655,148 @@ var SqliteStoreBackend = class {
4653
4655
  }
4654
4656
  };
4655
4657
 
4658
+ // src/store/graph.ts
4659
+ function toNum(v) {
4660
+ if (typeof v === "number") return v;
4661
+ if (v && typeof v === "object" && "toNumber" in v && typeof v.toNumber === "function") {
4662
+ return v.toNumber();
4663
+ }
4664
+ return Number(v ?? 0);
4665
+ }
4666
+ var GraphStoreBackend = class {
4667
+ constructor(driver) {
4668
+ this.driver = driver;
4669
+ }
4670
+ async run(cypher, params) {
4671
+ const session = this.driver.session();
4672
+ try {
4673
+ return await session.run(cypher, params);
4674
+ } finally {
4675
+ await session.close();
4676
+ }
4677
+ }
4678
+ async upsertNode(org, node, identity, contributor) {
4679
+ const res = await this.run(
4680
+ `MERGE (n:Node {org: $org, globalId: $globalId})
4681
+ ON CREATE SET n._created = true
4682
+ ON MATCH SET n._created = false
4683
+ SET n.id = $id, n.contentHash = $contentHash, n.type = $type, n.name = $name,
4684
+ n.domain = $domain, n.owner = $owner,
4685
+ n.confidence = CASE WHEN n.confidence IS NULL OR $confidence > n.confidence THEN $confidence ELSE n.confidence END
4686
+ MERGE (c:Contributor {org: $org, globalId: $globalId, machineId: $machineId})
4687
+ SET c.hostname = $hostname, c.user = $user, c.at = $at,
4688
+ c.confidence = CASE WHEN c.confidence IS NULL OR $contribConfidence > c.confidence THEN $contribConfidence ELSE c.confidence END
4689
+ RETURN n._created AS created`,
4690
+ {
4691
+ org,
4692
+ globalId: identity.globalId,
4693
+ contentHash: identity.contentHash,
4694
+ id: node.id,
4695
+ type: node.type,
4696
+ name: node.name,
4697
+ domain: node.domain ?? null,
4698
+ owner: node.owner ?? null,
4699
+ confidence: node.confidence,
4700
+ machineId: contributor.machineId,
4701
+ hostname: contributor.hostname,
4702
+ user: contributor.user,
4703
+ at: contributor.at,
4704
+ contribConfidence: contributor.confidence
4705
+ }
4706
+ );
4707
+ return res.records[0]?.get("created") === true ? "created" : "merged";
4708
+ }
4709
+ async insertEdge(org, edge) {
4710
+ await this.run(
4711
+ `MATCH (s:Node {org: $org, id: $source})
4712
+ MATCH (t:Node {org: $org, id: $target})
4713
+ MERGE (s)-[r:DEPENDS {relationship: $rel}]->(t)
4714
+ SET r.evidence = $evidence, r.confidence = $confidence`,
4715
+ { org, source: edge.sourceId, target: edge.targetId, rel: edge.relationship, evidence: edge.evidence, confidence: edge.confidence }
4716
+ );
4717
+ }
4718
+ async getSummary(org) {
4719
+ const totals = await this.run(
4720
+ `MATCH (n:Node {org: $org})
4721
+ OPTIONAL MATCH (n)-[r:DEPENDS]->(:Node {org: $org})
4722
+ RETURN count(DISTINCT n) AS nodes, count(r) AS edges`,
4723
+ { org }
4724
+ );
4725
+ const byType = await this.run(`MATCH (n:Node {org: $org}) RETURN n.type AS k, count(*) AS c`, { org });
4726
+ const byDomain = await this.run(`MATCH (n:Node {org: $org}) RETURN coalesce(n.domain, '(none)') AS k, count(*) AS c`, { org });
4727
+ const byRel = await this.run(`MATCH (:Node {org: $org})-[r:DEPENDS]->(:Node {org: $org}) RETURN r.relationship AS k, count(*) AS c`, { org });
4728
+ const top = await this.run(
4729
+ `MATCH (n:Node {org: $org})
4730
+ OPTIONAL MATCH (n)-[r:DEPENDS]-(:Node {org: $org})
4731
+ RETURN n.id AS id, n.name AS name, n.type AS type, count(r) AS degree
4732
+ ORDER BY degree DESC, id ASC LIMIT 10`,
4733
+ { org }
4734
+ );
4735
+ const contrib = await this.run(`MATCH (c:Contributor {org: $org}) RETURN count(DISTINCT c.machineId) AS contributors`, { org });
4736
+ const counts = (r) => {
4737
+ const out = {};
4738
+ for (const rec of r.records) out[String(rec.get("k"))] = toNum(rec.get("c"));
4739
+ return out;
4740
+ };
4741
+ return {
4742
+ org,
4743
+ totals: { nodes: toNum(totals.records[0]?.get("nodes")), edges: toNum(totals.records[0]?.get("edges")) },
4744
+ nodesByType: counts(byType),
4745
+ nodesByDomain: counts(byDomain),
4746
+ edgesByRelationship: counts(byRel),
4747
+ topConnected: top.records.map((rec) => ({
4748
+ id: String(rec.get("id")),
4749
+ name: String(rec.get("name")),
4750
+ type: String(rec.get("type")),
4751
+ degree: toNum(rec.get("degree"))
4752
+ })),
4753
+ contributors: toNum(contrib.records[0]?.get("contributors"))
4754
+ };
4755
+ }
4756
+ async getContributors(globalId2) {
4757
+ const res = await this.run(
4758
+ `MATCH (c:Contributor {globalId: $globalId})
4759
+ RETURN c.machineId AS machineId, c.hostname AS hostname, c.user AS user, c.org AS org, c.at AS at, c.confidence AS confidence`,
4760
+ { globalId: globalId2 }
4761
+ );
4762
+ return res.records.map((rec) => ({
4763
+ machineId: String(rec.get("machineId")),
4764
+ hostname: String(rec.get("hostname")),
4765
+ user: String(rec.get("user")),
4766
+ organization: rec.get("org") != null ? String(rec.get("org")) : void 0,
4767
+ at: String(rec.get("at")),
4768
+ confidence: toNum(rec.get("confidence"))
4769
+ }));
4770
+ }
4771
+ async close() {
4772
+ await this.driver.close();
4773
+ }
4774
+ };
4775
+
4776
+ // src/store/index.ts
4777
+ async function defaultNeo4jDriver(url, user, password) {
4778
+ const mod = await import("neo4j-driver");
4779
+ return mod.default.driver(url, mod.default.auth.basic(user, password));
4780
+ }
4781
+ async function openStoreBackend(db, opts = {}) {
4782
+ if (opts.backend === "graph" && opts.graphUrl) {
4783
+ try {
4784
+ const make = opts.driverFactory ?? defaultNeo4jDriver;
4785
+ const driver = await make(opts.graphUrl, opts.graphUser ?? "neo4j", opts.graphPassword ?? "");
4786
+ if (driver.verifyConnectivity) await driver.verifyConnectivity();
4787
+ logInfo("central store: graph backend active", { host: stripSensitive(opts.graphUrl) });
4788
+ return new GraphStoreBackend(driver);
4789
+ } catch (err) {
4790
+ logWarn("central store: graph backend unavailable \u2014 falling back to SQLite", {
4791
+ host: stripSensitive(opts.graphUrl),
4792
+ reason: err instanceof Error ? err.message : String(err)
4793
+ });
4794
+ return new SqliteStoreBackend(db);
4795
+ }
4796
+ }
4797
+ return new SqliteStoreBackend(db);
4798
+ }
4799
+
4656
4800
  // src/store/query.ts
4657
4801
  var NotFoundError = class extends Error {
4658
4802
  constructor(message) {
@@ -4967,9 +5111,9 @@ var IngestEnvelopeSchema = import_zod5.z.object({
4967
5111
  contributor: ContributorSchema.optional(),
4968
5112
  anonymizationLevel: import_zod5.z.enum(["none", "anonymized", "full"]).optional()
4969
5113
  });
4970
- function ingestEnvelope(store, envelope, opts = {}) {
5114
+ async function ingestEnvelope(store, envelope, opts = {}) {
4971
5115
  const anonMode = opts.anonMode ?? "reject";
4972
- const org = envelope.org ?? opts.defaultOrg ?? "local";
5116
+ const org = normalizeTenant(envelope.org ?? opts.defaultOrg);
4973
5117
  const level = envelope.anonymizationLevel ?? "anonymized";
4974
5118
  const at = (/* @__PURE__ */ new Date()).toISOString();
4975
5119
  const contributor = {
@@ -5020,7 +5164,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
5020
5164
  }
5021
5165
  const safe = check.node;
5022
5166
  const identity = computeIdentity(org, safe);
5023
- const outcome = store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
5167
+ const outcome = await store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });
5024
5168
  accepted += 1;
5025
5169
  if (outcome === "merged") merged += 1;
5026
5170
  acceptedNodeIds.add(safe.id);
@@ -5036,7 +5180,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
5036
5180
  if (acceptedNodeIds.size > 0 && (!acceptedNodeIds.has(edge.sourceId) || !acceptedNodeIds.has(edge.targetId))) {
5037
5181
  continue;
5038
5182
  }
5039
- store.insertEdge(org, edge);
5183
+ await store.insertEdge(org, edge);
5040
5184
  edges += 1;
5041
5185
  }
5042
5186
  logInfo("ingest", { org, accepted, merged, rejected, edges, violations, level, anonMode });
@@ -5046,7 +5190,7 @@ function ingestEnvelope(store, envelope, opts = {}) {
5046
5190
  // src/central/server.ts
5047
5191
  function createIngestHandler(store, opts = {}) {
5048
5192
  const quota = opts.quota;
5049
- return (body) => {
5193
+ return async (body) => {
5050
5194
  const parsed = IngestEnvelopeSchema.safeParse(body);
5051
5195
  if (!parsed.success) {
5052
5196
  const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
@@ -5062,7 +5206,7 @@ function createIngestHandler(store, opts = {}) {
5062
5206
  }
5063
5207
  }
5064
5208
  try {
5065
- const result = ingestEnvelope(store, parsed.data, opts);
5209
+ const result = await ingestEnvelope(store, parsed.data, opts);
5066
5210
  return { status: 200, body: result };
5067
5211
  } catch (err) {
5068
5212
  logWarn("ingest: failed", { error: err instanceof Error ? err.message : String(err) });
@@ -5991,7 +6135,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
5991
6135
 
5992
6136
  // src/mcp/server.ts
5993
6137
  var SERVER_NAME = "cartography";
5994
- var SERVER_VERSION = "2.7.0";
6138
+ var SERVER_VERSION = "2.8.0";
5995
6139
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
5996
6140
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
5997
6141
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -6066,9 +6210,10 @@ function createMcpServer(opts = {}) {
6066
6210
  "graph-summary",
6067
6211
  "cartography://graph/summary",
6068
6212
  { title: "Topology summary", description: "Low-token aggregate index of the whole landscape \u2014 read this first.", mimeType: "text/markdown" },
6069
- (uri) => {
6213
+ async (uri) => {
6070
6214
  if (org !== void 0) {
6071
- return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: summaryText(db.getOrgSummary(org)) }] };
6215
+ const s = opts.orgSummary ? await opts.orgSummary(org) : db.getOrgSummary(org);
6216
+ return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: summaryText(s) }] };
6072
6217
  }
6073
6218
  const sid = resolveSession();
6074
6219
  if (!sid) return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: "No discovery session found. Run discovery first." }] };
@@ -6143,8 +6288,8 @@ function createMcpServer(opts = {}) {
6143
6288
  server.registerTool(
6144
6289
  "get_summary",
6145
6290
  { title: "Get topology summary", description: "Low-token overview of the whole landscape (counts, types, domains, most-connected, anomalies).", inputSchema: {}, annotations: readOnly },
6146
- () => {
6147
- if (org !== void 0) return json(db.getOrgSummary(org));
6291
+ async () => {
6292
+ if (org !== void 0) return json(opts.orgSummary ? await opts.orgSummary(org) : db.getOrgSummary(org));
6148
6293
  const sid = resolveSession();
6149
6294
  if (!sid) return json({ error: "No discovery session found." });
6150
6295
  return json(db.getGraphSummary(sid));
@@ -6670,7 +6815,7 @@ async function runHttp(factory, opts = {}) {
6670
6815
  res.writeHead(413, { "content-type": "application/json" }).end('{"error":"payload too large"}');
6671
6816
  return;
6672
6817
  }
6673
- const out = onIngest(value);
6818
+ const out = await onIngest(value);
6674
6819
  res.writeHead(out.status, { "content-type": "application/json", ...out.headers ?? {} }).end(JSON.stringify(out.body));
6675
6820
  return;
6676
6821
  }
@@ -11927,6 +12072,7 @@ function checkClaudePrerequisites() {
11927
12072
  DEFAULT_SERVER_NAME,
11928
12073
  DEFAULT_TENANT,
11929
12074
  DriftConfigSchema,
12075
+ GraphStoreBackend,
11930
12076
  INGEST_SCHEMA_VERSION,
11931
12077
  IngestEnvelopeSchema,
11932
12078
  InvalidTenantError,
@@ -12098,6 +12244,7 @@ function checkClaudePrerequisites() {
12098
12244
  nodesToAssets,
12099
12245
  normalizeId,
12100
12246
  normalizeTenant,
12247
+ openStoreBackend,
12101
12248
  orgKeyPath,
12102
12249
  osUser,
12103
12250
  parseApiArgs,