nexarch 0.5.7 → 0.5.8

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.
@@ -5,7 +5,8 @@ import process from "process";
5
5
  import { requireCredentials } from "../lib/credentials.js";
6
6
  import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
7
7
  import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
8
- const CLI_VERSION = "0.5.7";
8
+ import { buildVersionAttributes } from "../lib/version-normalization.js";
9
+ const CLI_VERSION = "0.5.8";
9
10
  const AGENT_ENTITY_TYPE = "agent";
10
11
  const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
11
12
  function parseFlag(args, flag) {
@@ -516,7 +517,7 @@ export async function initAgent(args) {
516
517
  attributes: {
517
518
  kind: "runtime",
518
519
  runtime: "nodejs",
519
- version: runtime.nodeVersion,
520
+ ...buildVersionAttributes(runtime.nodeVersion, "runtime_probe"),
520
521
  },
521
522
  },
522
523
  ],
@@ -582,7 +583,12 @@ export async function initAgent(args) {
582
583
  fromEntityExternalKey: agentExternalKey,
583
584
  toEntityExternalKey: nodeExternalKey,
584
585
  confidence: 0.95,
585
- attributes: { source: "nexarch-cli-init-agent", kind: "runtime", createdAt: nowIso },
586
+ attributes: {
587
+ source: "nexarch-cli-init-agent",
588
+ kind: "runtime",
589
+ createdAt: nowIso,
590
+ ...buildVersionAttributes(runtime.nodeVersion, "runtime_probe"),
591
+ },
586
592
  },
587
593
  // Host runs on its OS — gives the host entity its own OS relationship
588
594
  {
@@ -5,6 +5,7 @@ import { basename, join, relative, resolve as resolvePath } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import { requireCredentials } from "../lib/credentials.js";
7
7
  import { callMcpTool } from "../lib/mcp.js";
8
+ import { buildVersionAttributes } from "../lib/version-normalization.js";
8
9
  // ─── Helpers ─────────────────────────────────────────────────────────────────
9
10
  function parseFlag(args, flag) {
10
11
  return args.includes(flag);
@@ -290,10 +291,15 @@ function collectPackageDeps(pkgPath) {
290
291
  try {
291
292
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
292
293
  // devDependencies are build/test/type tooling — no architectural value
293
- return Object.keys({
294
+ const merged = {
294
295
  ...pkg.dependencies,
295
296
  ...pkg.peerDependencies,
296
- });
297
+ };
298
+ return Object.entries(merged).map(([name, versionRaw]) => ({
299
+ name,
300
+ versionRaw: typeof versionRaw === "string" ? versionRaw : null,
301
+ source: "package_json",
302
+ }));
297
303
  }
298
304
  catch {
299
305
  return [];
@@ -618,6 +624,7 @@ function scanProject(dir) {
618
624
  let projectName = basename(dir);
619
625
  const subPackages = [];
620
626
  let rootDepNames = new Set();
627
+ const rootDepVersions = new Map();
621
628
  const detectedEcosystems = [];
622
629
  // ── npm / Node.js ──────────────────────────────────────────────────────────
623
630
  const pkgPaths = findPackageJsonPaths(dir);
@@ -630,7 +637,9 @@ function scanProject(dir) {
630
637
  if (pkgPath === rootPkgPath) {
631
638
  if (pkg.name)
632
639
  projectName = pkg.name;
633
- rootDepNames = new Set(deps);
640
+ rootDepNames = new Set(deps.map((d) => d.name));
641
+ for (const dep of deps)
642
+ rootDepVersions.set(dep.name, dep);
634
643
  }
635
644
  else {
636
645
  const relativePath = relative(dir, pkgPath)
@@ -642,14 +651,14 @@ function scanProject(dir) {
642
651
  name: packageName,
643
652
  relativePath,
644
653
  packageJsonPath: pkgPath,
645
- depNames: deps,
654
+ depSpecs: deps,
646
655
  entityType,
647
656
  subtype,
648
657
  externalKey: "",
649
658
  });
650
659
  }
651
660
  for (const dep of deps)
652
- names.add(dep);
661
+ names.add(dep.name);
653
662
  }
654
663
  }
655
664
  // ── Other ecosystems (Python, Go, Ruby, Rust) ──────────────────────────────
@@ -660,8 +669,12 @@ function scanProject(dir) {
660
669
  if (!detectedEcosystems.includes(e))
661
670
  detectedEcosystems.push(e);
662
671
  // Root deps for non-npm ecosystems — treat all detected names as root-level
663
- for (const n of ecosystemNames)
672
+ for (const n of ecosystemNames) {
664
673
  rootDepNames.add(n);
674
+ if (!rootDepVersions.has(n)) {
675
+ rootDepVersions.set(n, { name: n, versionRaw: null, source: `${e}_manifest` });
676
+ }
677
+ }
665
678
  }
666
679
  // Try to infer project name from Python pyproject.toml if no package.json name
667
680
  if (!detectedEcosystems.includes("nodejs") && existsSync(join(dir, "pyproject.toml"))) {
@@ -704,6 +717,7 @@ function scanProject(dir) {
704
717
  packageJsonCount: pkgPaths.length,
705
718
  detectedNames: Array.from(names),
706
719
  rootDepNames,
720
+ rootDepVersions,
707
721
  subPackages,
708
722
  detectedEcosystems,
709
723
  };
@@ -740,7 +754,7 @@ export async function initProject(args) {
740
754
  const mcpOpts = { companyId: creds.companyId };
741
755
  if (!asJson)
742
756
  console.log(`Scanning ${dir}…`);
743
- const { projectName, packageJsonCount, detectedNames, rootDepNames, subPackages, detectedEcosystems } = scanProject(dir);
757
+ const { projectName, packageJsonCount, detectedNames, rootDepNames, rootDepVersions, subPackages, detectedEcosystems } = scanProject(dir);
744
758
  const detectedRepo = detectSourceRepository(dir);
745
759
  const displayName = nameOverride ?? projectName;
746
760
  const projectSlug = slugify(displayName);
@@ -944,12 +958,12 @@ export async function initProject(args) {
944
958
  // - Sub-packages that are apps → part_of the top-level project
945
959
  const relationships = [];
946
960
  const seenRelPairs = new Set();
947
- function addRel(type, from, to, confidence = 0.9) {
961
+ function addRel(type, from, to, confidence = 0.9, attributes) {
948
962
  const key = `${type}::${from}::${to}`;
949
963
  if (seenRelPairs.has(key))
950
964
  return;
951
965
  seenRelPairs.add(key);
952
- relationships.push({ relationshipTypeCode: type, fromEntityExternalKey: from, toEntityExternalKey: to, confidence });
966
+ relationships.push({ relationshipTypeCode: type, fromEntityExternalKey: from, toEntityExternalKey: to, confidence, attributes });
953
967
  }
954
968
  // Root-level deps → top-level project
955
969
  for (const r of resolvedItems) {
@@ -957,7 +971,12 @@ export async function initProject(args) {
957
971
  continue;
958
972
  if (!rootDepNames.has(r.input) && !rootDepNames.has(r.normalised))
959
973
  continue;
960
- addRel(pickRelationshipType(r.entityTypeCode), projectExternalKey, r.canonicalExternalRef);
974
+ const depSpec = rootDepVersions.get(r.input) ?? rootDepVersions.get(r.normalised);
975
+ addRel(pickRelationshipType(r.entityTypeCode), projectExternalKey, r.canonicalExternalRef, 0.9, {
976
+ source: depSpec?.source ?? "manifest_scan",
977
+ detected_at: nowIso,
978
+ ...buildVersionAttributes(depSpec?.versionRaw ?? null, depSpec?.source ?? "manifest_scan"),
979
+ });
961
980
  }
962
981
  // Sub-package relationships
963
982
  for (const sp of subPackages) {
@@ -969,11 +988,15 @@ export async function initProject(args) {
969
988
  addRel("part_of", sp.externalKey, projectExternalKey);
970
989
  }
971
990
  // Wire each sub-package's resolved deps to itself
972
- for (const depName of sp.depNames) {
973
- const r = resolvedByInput.get(depName);
991
+ for (const dep of sp.depSpecs) {
992
+ const r = resolvedByInput.get(dep.name);
974
993
  if (!r?.canonicalExternalRef || !r.entityTypeCode)
975
994
  continue;
976
- addRel(pickRelationshipType(r.entityTypeCode, sp.entityType), sp.externalKey, r.canonicalExternalRef);
995
+ addRel(pickRelationshipType(r.entityTypeCode, sp.entityType), sp.externalKey, r.canonicalExternalRef, 0.9, {
996
+ source: dep.source,
997
+ detected_at: nowIso,
998
+ ...buildVersionAttributes(dep.versionRaw, dep.source),
999
+ });
977
1000
  }
978
1001
  }
979
1002
  // Company org is accountable_for the top-level project entity
@@ -1089,8 +1112,8 @@ ${subPackages.map((sp) => ` • ${sp.name} (${sp.relativePath})`).join("\n")
1089
1112
 
1090
1113
  Pre-resolved dependencies per sub-package (wire these after registering each entity):
1091
1114
  ${subPackages.map((sp) => {
1092
- const resolved = sp.depNames
1093
- .map((d) => resolvedByInput.get(d))
1115
+ const resolved = sp.depSpecs
1116
+ .map((d) => resolvedByInput.get(d.name))
1094
1117
  .filter((r) => !!r?.canonicalExternalRef);
1095
1118
  if (resolved.length === 0)
1096
1119
  return ` • ${sp.name}: (no pre-resolved deps — check package.json manually)`;
@@ -1184,8 +1207,8 @@ ${subPkgSection}${gapCheckSection}`;
1184
1207
  readmeFiles: readmeHints,
1185
1208
  },
1186
1209
  subPackages: subPackages.map((sp) => {
1187
- const resolvedDeps = sp.depNames
1188
- .map((d) => resolvedByInput.get(d))
1210
+ const resolvedDeps = sp.depSpecs
1211
+ .map((d) => resolvedByInput.get(d.name))
1189
1212
  .filter((r) => !!r?.canonicalExternalRef)
1190
1213
  .map((r) => ({ canonicalExternalRef: r.canonicalExternalRef, canonicalName: r.canonicalName, entityTypeCode: r.entityTypeCode }));
1191
1214
  return {
@@ -1195,7 +1218,7 @@ ${subPkgSection}${gapCheckSection}`;
1195
1218
  inferredEntityType: sp.entityType,
1196
1219
  inferredSubtype: sp.subtype,
1197
1220
  resolvedDeps,
1198
- unresolvedDeps: sp.depNames.filter((d) => !resolvedByInput.has(d)),
1221
+ unresolvedDeps: sp.depSpecs.map((d) => d.name).filter((d) => !resolvedByInput.has(d)),
1199
1222
  };
1200
1223
  }),
1201
1224
  };
package/dist/lib/mcp.js CHANGED
@@ -68,7 +68,7 @@ export async function mcpInitialize(options = {}) {
68
68
  return callMcpRpc("initialize", {
69
69
  protocolVersion: "2024-11-05",
70
70
  capabilities: {},
71
- clientInfo: { name: "nexarch-cli", version: "0.5.7" },
71
+ clientInfo: { name: "nexarch-cli", version: "0.5.8" },
72
72
  }, options);
73
73
  }
74
74
  export async function mcpListTools(options = {}) {
@@ -0,0 +1,35 @@
1
+ export function normalizeVersion(raw) {
2
+ const value = typeof raw === "string" ? raw.trim() : "";
3
+ if (!value) {
4
+ return {
5
+ versionRaw: null,
6
+ versionNormalized: null,
7
+ versionMajor: null,
8
+ versionMinor: null,
9
+ isExact: false,
10
+ };
11
+ }
12
+ const withoutV = value.replace(/^v/i, "");
13
+ const exactMatch = withoutV.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-+][\w.-]+)?$/);
14
+ const majorMinorPatchMatch = withoutV.match(/(\d+)\.(\d+)(?:\.(\d+))?/);
15
+ const major = exactMatch?.[1] ?? majorMinorPatchMatch?.[1] ?? null;
16
+ const minor = exactMatch?.[2] ?? majorMinorPatchMatch?.[2] ?? null;
17
+ return {
18
+ versionRaw: value,
19
+ versionNormalized: exactMatch ? withoutV : (majorMinorPatchMatch ? majorMinorPatchMatch[0] : null),
20
+ versionMajor: major ? Number.parseInt(major, 10) : null,
21
+ versionMinor: minor ? Number.parseInt(minor, 10) : null,
22
+ isExact: Boolean(exactMatch),
23
+ };
24
+ }
25
+ export function buildVersionAttributes(raw, source) {
26
+ const normalized = normalizeVersion(raw);
27
+ return {
28
+ version_raw: normalized.versionRaw,
29
+ version_normalized: normalized.versionNormalized,
30
+ version_major: normalized.versionMajor,
31
+ version_minor: normalized.versionMinor,
32
+ version_is_exact: normalized.isExact,
33
+ version_source: source,
34
+ };
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",