nexarch 0.1.23 → 0.1.25

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.
@@ -4,7 +4,7 @@ import { join } from "path";
4
4
  import process from "process";
5
5
  import { requireCredentials } from "../lib/credentials.js";
6
6
  import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
7
- const CLI_VERSION = "0.1.23";
7
+ const CLI_VERSION = "0.1.25";
8
8
  const AGENT_ENTITY_TYPE = "agent";
9
9
  const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
10
10
  function parseFlag(args, flag) {
@@ -115,6 +115,34 @@ function readRootPackage(pkgPath) {
115
115
  return {};
116
116
  }
117
117
  }
118
+ // Guess entity type + subtype for a sub-package based on its path and package.json scripts.
119
+ function classifySubPackage(pkgPath, relativePath) {
120
+ const topDir = relativePath.split("/")[0] ?? "";
121
+ // packages/* → shared library/component
122
+ if (topDir === "packages") {
123
+ const name = relativePath.split("/")[1] ?? "";
124
+ if (name.includes("ui") || name.includes("components"))
125
+ return { entityType: "technology_component", subtype: "tech_framework" };
126
+ if (name.includes("types") || name.includes("shared"))
127
+ return { entityType: "technology_component", subtype: "tech_library" };
128
+ return { entityType: "technology_component", subtype: "tech_library" };
129
+ }
130
+ // apps/* or src/* → deployable application; check scripts
131
+ try {
132
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
133
+ const scripts = Object.keys(pkg.scripts ?? {});
134
+ const hasServer = scripts.some((s) => ["start", "dev", "serve", "build"].includes(s));
135
+ if (hasServer) {
136
+ const name = relativePath.toLowerCase();
137
+ if (name.includes("crawl") || name.includes("worker") || name.includes("job") || name.includes("etl")) {
138
+ return { entityType: "application", subtype: "app_integration_service" };
139
+ }
140
+ return { entityType: "application", subtype: "app_custom_built" };
141
+ }
142
+ }
143
+ catch { }
144
+ return { entityType: "technology_component", subtype: "tech_library" };
145
+ }
118
146
  // Resolve workspace glob patterns (e.g. "apps/*", "packages/*") to actual directories.
119
147
  // Handles the common case of a single wildcard — no full glob library needed.
120
148
  function resolveWorkspaceDirs(rootDir, workspaces) {
@@ -205,24 +233,34 @@ function scanProject(dir) {
205
233
  const names = new Set();
206
234
  let projectName = basename(dir);
207
235
  const subPackages = [];
236
+ let rootDepNames = new Set();
208
237
  const pkgPaths = findPackageJsonPaths(dir);
209
238
  const rootPkgPath = join(dir, "package.json");
210
239
  for (const pkgPath of pkgPaths) {
211
240
  const pkg = readRootPackage(pkgPath);
241
+ const deps = collectPackageDeps(pkgPath);
212
242
  if (pkgPath === rootPkgPath) {
213
243
  if (pkg.name)
214
244
  projectName = pkg.name;
245
+ rootDepNames = new Set(deps);
215
246
  }
216
247
  else {
217
248
  // Record as a sub-package for enrichment task
218
249
  const relativePath = pkgPath.replace(dir + "/", "").replace("/package.json", "");
250
+ const packageName = pkg.name ?? relativePath;
251
+ const { entityType, subtype } = classifySubPackage(pkgPath, relativePath);
252
+ const externalKey = `${entityType}:${slugify(packageName)}`;
219
253
  subPackages.push({
220
- name: pkg.name ?? relativePath,
254
+ name: packageName,
221
255
  relativePath,
222
256
  packageJsonPath: pkgPath,
257
+ depNames: deps,
258
+ entityType,
259
+ subtype,
260
+ externalKey,
223
261
  });
224
262
  }
225
- for (const dep of collectPackageDeps(pkgPath))
263
+ for (const dep of deps)
226
264
  names.add(dep);
227
265
  }
228
266
  // .env files at root
@@ -241,7 +279,7 @@ function scanProject(dir) {
241
279
  names.add(name);
242
280
  for (const name of detectFromFilesystem(dir))
243
281
  names.add(name);
244
- return { projectName, packageJsonCount: pkgPaths.length, detectedNames: Array.from(names), subPackages };
282
+ return { projectName, packageJsonCount: pkgPaths.length, detectedNames: Array.from(names), rootDepNames, subPackages };
245
283
  }
246
284
  // ─── Relationship type selection ──────────────────────────────────────────────
247
285
  function pickRelationshipType(entityTypeCode) {
@@ -268,7 +306,7 @@ export async function initProject(args) {
268
306
  const mcpOpts = { companyId: creds.companyId };
269
307
  if (!asJson)
270
308
  console.log(`Scanning ${dir}…`);
271
- const { projectName, packageJsonCount, detectedNames, subPackages } = scanProject(dir);
309
+ const { projectName, packageJsonCount, detectedNames, rootDepNames, subPackages } = scanProject(dir);
272
310
  const displayName = nameOverride ?? projectName;
273
311
  const projectSlug = slugify(displayName);
274
312
  const projectExternalKey = `${entityTypeOverride}:${projectSlug}`;
@@ -358,23 +396,66 @@ export async function initProject(args) {
358
396
  confidence: 0.95,
359
397
  });
360
398
  }
361
- // Build relationships from project entity to each resolved dependency
399
+ // Auto-create stub entities for each sub-package
400
+ const seenSubKeys = new Set();
401
+ for (const sp of subPackages) {
402
+ if (seenSubKeys.has(sp.externalKey))
403
+ continue;
404
+ seenSubKeys.add(sp.externalKey);
405
+ entities.push({
406
+ externalKey: sp.externalKey,
407
+ entityTypeCode: sp.entityType,
408
+ entitySubtypeCode: sp.subtype,
409
+ name: sp.name,
410
+ confidence: 0.8,
411
+ attributes: { source_dir: `${dir}/${sp.relativePath}`, scanned_at: nowIso },
412
+ });
413
+ }
414
+ // Build a lookup from input name to resolved result for enrichment task and sub-app wiring
415
+ const resolvedByInput = new Map();
416
+ for (const r of resolvedItems) {
417
+ if (r.canonicalExternalRef)
418
+ resolvedByInput.set(r.input, r);
419
+ if (r.normalised && r.canonicalExternalRef)
420
+ resolvedByInput.set(r.normalised, r);
421
+ }
422
+ // Build relationships:
423
+ // - Root-level deps → wired to the top-level project entity
424
+ // - Sub-package deps → wired to each sub-package entity
425
+ // - Sub-packages that are apps → part_of the top-level project
362
426
  const relationships = [];
363
427
  const seenRelPairs = new Set();
428
+ function addRel(type, from, to, confidence = 0.9) {
429
+ const key = `${type}::${from}::${to}`;
430
+ if (seenRelPairs.has(key))
431
+ return;
432
+ seenRelPairs.add(key);
433
+ relationships.push({ relationshipTypeCode: type, fromEntityExternalKey: from, toEntityExternalKey: to, confidence });
434
+ }
435
+ // Root-level deps → top-level project
364
436
  for (const r of resolvedItems) {
365
437
  if (!r.canonicalExternalRef || !r.entityTypeCode)
366
438
  continue;
367
- const relType = pickRelationshipType(r.entityTypeCode);
368
- const pairKey = `${relType}::${projectExternalKey}::${r.canonicalExternalRef}`;
369
- if (seenRelPairs.has(pairKey))
439
+ if (!rootDepNames.has(r.input) && !rootDepNames.has(r.normalised))
370
440
  continue;
371
- seenRelPairs.add(pairKey);
372
- relationships.push({
373
- relationshipTypeCode: relType,
374
- fromEntityExternalKey: projectExternalKey,
375
- toEntityExternalKey: r.canonicalExternalRef,
376
- confidence: 0.9,
377
- });
441
+ addRel(pickRelationshipType(r.entityTypeCode), projectExternalKey, r.canonicalExternalRef);
442
+ }
443
+ // Sub-package relationships
444
+ for (const sp of subPackages) {
445
+ if (!sp.externalKey || seenSubKeys.has(`rel:${sp.externalKey}`))
446
+ continue;
447
+ seenSubKeys.add(`rel:${sp.externalKey}`);
448
+ // Apps are part_of the top-level project
449
+ if (sp.entityType === "application") {
450
+ addRel("part_of", sp.externalKey, projectExternalKey);
451
+ }
452
+ // Wire each sub-package's resolved deps to itself
453
+ for (const depName of sp.depNames) {
454
+ const r = resolvedByInput.get(depName);
455
+ if (!r?.canonicalExternalRef || !r.entityTypeCode)
456
+ continue;
457
+ addRel(pickRelationshipType(r.entityTypeCode), sp.externalKey, r.canonicalExternalRef);
458
+ }
378
459
  }
379
460
  if (!asJson)
380
461
  console.log(`\nWriting to graph…`);
@@ -497,5 +578,23 @@ ${subPackages.map((sp) => ` • ${sp.name} (${sp.relativePath})`).join("\n")
497
578
  --to "<library-key>" \\
498
579
  --type "depends_on"
499
580
  (FROM = ${projectExternalKey}, TO = the library)
581
+
582
+ ⚠️ WIRE DEPENDENCIES TO THE SUB-APP THAT DECLARES THEM, NOT TO THE PARENT.
583
+ After registering each sub-app/library entity, wire its npm dependencies to IT:
584
+ npx nexarch add-relationship \\
585
+ --from "<sub-app-or-library-key>" \\
586
+ --to "<resolved-tech-ref>" \\
587
+ --type "depends_on"
588
+
589
+ Pre-resolved dependencies per sub-package (wire these after registering each entity):
590
+ ${subPackages.map((sp) => {
591
+ const resolved = sp.depNames
592
+ .map((d) => resolvedByInput.get(d))
593
+ .filter((r) => !!r?.canonicalExternalRef);
594
+ if (resolved.length === 0)
595
+ return ` • ${sp.name}: (no pre-resolved deps — check package.json manually)`;
596
+ const lines = resolved.map((r) => ` --to "${r.canonicalExternalRef}" # ${r.canonicalName}`);
597
+ return ` • ${sp.name} (<sub-app-key> depends_on):\n${lines.join("\n")}`;
598
+ }).join("\n\n")}
500
599
  ` : ""}`);
501
600
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",