nexarch 0.1.24 → 0.1.28

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.24";
7
+ const CLI_VERSION = "0.1.28";
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) {
@@ -219,11 +247,17 @@ function scanProject(dir) {
219
247
  else {
220
248
  // Record as a sub-package for enrichment task
221
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)}`;
222
253
  subPackages.push({
223
- name: pkg.name ?? relativePath,
254
+ name: packageName,
224
255
  relativePath,
225
256
  packageJsonPath: pkgPath,
226
257
  depNames: deps,
258
+ entityType,
259
+ subtype,
260
+ externalKey,
227
261
  });
228
262
  }
229
263
  for (const dep of deps)
@@ -248,14 +282,19 @@ function scanProject(dir) {
248
282
  return { projectName, packageJsonCount: pkgPaths.length, detectedNames: Array.from(names), rootDepNames, subPackages };
249
283
  }
250
284
  // ─── Relationship type selection ──────────────────────────────────────────────
251
- function pickRelationshipType(entityTypeCode) {
252
- switch (entityTypeCode) {
285
+ function pickRelationshipType(toEntityTypeCode, fromEntityTypeCode = "application") {
286
+ switch (toEntityTypeCode) {
253
287
  case "model":
254
288
  return "uses_model";
255
289
  case "platform":
256
290
  case "platform_component":
257
291
  case "skill":
258
292
  return "uses";
293
+ case "technology_component":
294
+ // tech→tech depends_on is not allowed by ontology; use "uses" instead
295
+ if (fromEntityTypeCode === "technology_component")
296
+ return "uses";
297
+ return "depends_on";
259
298
  default:
260
299
  return "depends_on";
261
300
  }
@@ -362,7 +401,22 @@ export async function initProject(args) {
362
401
  confidence: 0.95,
363
402
  });
364
403
  }
365
- // Build a lookup from input name to resolved result for enrichment task use
404
+ // Auto-create stub entities for each sub-package
405
+ const seenSubKeys = new Set();
406
+ for (const sp of subPackages) {
407
+ if (seenSubKeys.has(sp.externalKey))
408
+ continue;
409
+ seenSubKeys.add(sp.externalKey);
410
+ entities.push({
411
+ externalKey: sp.externalKey,
412
+ entityTypeCode: sp.entityType,
413
+ entitySubtypeCode: sp.subtype,
414
+ name: sp.name,
415
+ confidence: 0.8,
416
+ attributes: { source_dir: `${dir}/${sp.relativePath}`, scanned_at: nowIso },
417
+ });
418
+ }
419
+ // Build a lookup from input name to resolved result for enrichment task and sub-app wiring
366
420
  const resolvedByInput = new Map();
367
421
  for (const r of resolvedItems) {
368
422
  if (r.canonicalExternalRef)
@@ -370,27 +424,43 @@ export async function initProject(args) {
370
424
  if (r.normalised && r.canonicalExternalRef)
371
425
  resolvedByInput.set(r.normalised, r);
372
426
  }
373
- // Build relationships — only wire root-level deps to the project entity.
374
- // Sub-package deps are wired during enrichment (the agent does it per sub-app).
427
+ // Build relationships:
428
+ // - Root-level deps wired to the top-level project entity
429
+ // - Sub-package deps → wired to each sub-package entity
430
+ // - Sub-packages that are apps → part_of the top-level project
375
431
  const relationships = [];
376
432
  const seenRelPairs = new Set();
433
+ function addRel(type, from, to, confidence = 0.9) {
434
+ const key = `${type}::${from}::${to}`;
435
+ if (seenRelPairs.has(key))
436
+ return;
437
+ seenRelPairs.add(key);
438
+ relationships.push({ relationshipTypeCode: type, fromEntityExternalKey: from, toEntityExternalKey: to, confidence });
439
+ }
440
+ // Root-level deps → top-level project
377
441
  for (const r of resolvedItems) {
378
442
  if (!r.canonicalExternalRef || !r.entityTypeCode)
379
443
  continue;
380
- // Skip deps that don't belong to the root package — only wire root-level deps here
381
444
  if (!rootDepNames.has(r.input) && !rootDepNames.has(r.normalised))
382
445
  continue;
383
- const relType = pickRelationshipType(r.entityTypeCode);
384
- const pairKey = `${relType}::${projectExternalKey}::${r.canonicalExternalRef}`;
385
- if (seenRelPairs.has(pairKey))
446
+ addRel(pickRelationshipType(r.entityTypeCode), projectExternalKey, r.canonicalExternalRef);
447
+ }
448
+ // Sub-package relationships
449
+ for (const sp of subPackages) {
450
+ if (!sp.externalKey || seenSubKeys.has(`rel:${sp.externalKey}`))
386
451
  continue;
387
- seenRelPairs.add(pairKey);
388
- relationships.push({
389
- relationshipTypeCode: relType,
390
- fromEntityExternalKey: projectExternalKey,
391
- toEntityExternalKey: r.canonicalExternalRef,
392
- confidence: 0.9,
393
- });
452
+ seenSubKeys.add(`rel:${sp.externalKey}`);
453
+ // Apps are part_of the top-level project
454
+ if (sp.entityType === "application") {
455
+ addRel("part_of", sp.externalKey, projectExternalKey);
456
+ }
457
+ // Wire each sub-package's resolved deps to itself
458
+ for (const depName of sp.depNames) {
459
+ const r = resolvedByInput.get(depName);
460
+ if (!r?.canonicalExternalRef || !r.entityTypeCode)
461
+ continue;
462
+ addRel(pickRelationshipType(r.entityTypeCode, sp.entityType), sp.externalKey, r.canonicalExternalRef);
463
+ }
394
464
  }
395
465
  if (!asJson)
396
466
  console.log(`\nWriting to graph…`);
@@ -403,64 +473,11 @@ export async function initProject(args) {
403
473
  const relsRaw = await callMcpTool("nexarch_upsert_relationships", { relationships, agentContext, policyContext }, mcpOpts);
404
474
  relsResult = parseToolText(relsRaw);
405
475
  }
406
- const output = {
407
- ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
408
- project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride },
409
- entities: entitiesResult.summary ?? {},
410
- relationships: relsResult?.summary ?? { requested: 0, succeeded: 0, failed: 0 },
411
- resolved: resolvedItems.length,
412
- unresolved: unresolvedItems.length,
413
- unresolvedSample: unresolvedItems.slice(0, 10).map((r) => r.input),
414
- entityErrors: entitiesResult.errors ?? [],
415
- relationshipErrors: relsResult?.errors ?? [],
416
- };
417
- if (asJson) {
418
- process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
419
- if (!output.ok)
420
- process.exitCode = 1;
421
- return;
422
- }
423
- console.log(`\nDone.`);
424
- console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${output.entities.failed ?? 0} failed`);
425
- console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
426
- if (unresolvedItems.length > 0) {
427
- console.log(` Candidates : ${unresolvedItems.length} added to reference candidates`);
428
- }
429
- if (output.entityErrors.length > 0) {
430
- console.log("\nEntity errors:");
431
- for (const err of output.entityErrors) {
432
- console.log(` ${err.externalKey}: ${err.error} — ${err.message}`);
433
- }
434
- }
435
- // ─── Enrichment task ────────────────────────────────────────────────────────
476
+ // Build structured enrichment task (included in JSON output and printed in human mode)
436
477
  const readmeHints = ["README.md", "README.mdx", "docs/README.md", "docs/index.md"]
437
478
  .filter((f) => existsSync(join(dir, f)));
438
- console.log(`
439
- ╔══════════════════════════════════════════════════════════════════╗
440
- ║ ENRICHMENT TASK — action required ║
441
- ╚══════════════════════════════════════════════════════════════════╝
442
-
443
- The mechanical scan has written a skeleton graph entry for this project.
444
- You now need to enrich it with meaningful content.
445
-
446
- PROJECT ENTITY : ${projectExternalKey}
447
- PROJECT DIR : ${dir}
448
- ${readmeHints.length > 0 ? `README FILES : ${readmeHints.join(", ")}` : "README FILES : (none found — check docs/)"}
449
-
450
- STEP 1 — Read the project README / docs and build your understanding of:
451
- • What does this project do? (business purpose, key features)
452
- • Who are the users?
453
- • What is the high-level architecture?
454
- • Any notable technical decisions or constraints?
455
-
456
- STEP 2 — Enrich the project entity. Run this command with the description you've written:
457
-
458
- npx nexarch update-entity \\
459
- --key "${projectExternalKey}" \\
460
- --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? ' \\\n --subtype "app_custom_built"' : ""} \\
461
- --name "<proper product name from README>" \\
462
- --description "<2–4 sentence summary of what it does and why>"
463
- ${subPackages.length > 0 ? `
479
+ function buildEnrichmentInstructions() {
480
+ const subPkgSection = subPackages.length === 0 ? "" : `
464
481
  STEP 3 — Classify and register each sub-package as its own entity.
465
482
  The scanner found ${subPackages.length} workspace package(s):
466
483
 
@@ -523,13 +540,95 @@ ${subPackages.map((sp) => ` • ${sp.name} (${sp.relativePath})`).join("\n")
523
540
 
524
541
  Pre-resolved dependencies per sub-package (wire these after registering each entity):
525
542
  ${subPackages.map((sp) => {
526
- const resolved = sp.depNames
527
- .map((d) => resolvedByInput.get(d))
528
- .filter((r) => !!r?.canonicalExternalRef);
529
- if (resolved.length === 0)
530
- return ` • ${sp.name}: (no pre-resolved deps — check package.json manually)`;
531
- const lines = resolved.map((r) => ` --to "${r.canonicalExternalRef}" # ${r.canonicalName}`);
532
- return ` • ${sp.name} (<sub-app-key> depends_on):\n${lines.join("\n")}`;
533
- }).join("\n\n")}
534
- ` : ""}`);
543
+ const resolved = sp.depNames
544
+ .map((d) => resolvedByInput.get(d))
545
+ .filter((r) => !!r?.canonicalExternalRef);
546
+ if (resolved.length === 0)
547
+ return ` • ${sp.name}: (no pre-resolved deps — check package.json manually)`;
548
+ const lines = resolved.map((r) => ` --to "${r.canonicalExternalRef}" # ${r.canonicalName}`);
549
+ return ` • ${sp.name} (<sub-app-key> depends_on):\n${lines.join("\n")}`;
550
+ }).join("\n\n")}
551
+ `;
552
+ return `
553
+ ╔══════════════════════════════════════════════════════════════════╗
554
+ ║ ENRICHMENT TASK — action required ║
555
+ ╚══════════════════════════════════════════════════════════════════╝
556
+
557
+ The mechanical scan has written a skeleton graph entry for this project.
558
+ You now need to enrich it with meaningful content.
559
+
560
+ PROJECT ENTITY : ${projectExternalKey}
561
+ PROJECT DIR : ${dir}
562
+ ${readmeHints.length > 0 ? `README FILES : ${readmeHints.join(", ")}` : "README FILES : (none found — check docs/)"}
563
+
564
+ STEP 1 — Read the project README / docs and build your understanding of:
565
+ • What does this project do? (business purpose, key features)
566
+ • Who are the users?
567
+ • What is the high-level architecture?
568
+ • Any notable technical decisions or constraints?
569
+
570
+ STEP 2 — Enrich the project entity. Run this command with the description you've written:
571
+
572
+ npx nexarch update-entity \\
573
+ --key "${projectExternalKey}" \\
574
+ --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? " \\\n --subtype \"app_custom_built\"" : ""} \\
575
+ --name "<proper product name from README>" \\
576
+ --description "<2–4 sentence summary of what it does and why>"
577
+ ${subPkgSection}`;
578
+ }
579
+ const enrichmentTask = {
580
+ instructions: buildEnrichmentInstructions(),
581
+ projectEntity: {
582
+ externalKey: projectExternalKey,
583
+ entityType: entityTypeOverride,
584
+ readmeFiles: readmeHints,
585
+ },
586
+ subPackages: subPackages.map((sp) => {
587
+ const resolvedDeps = sp.depNames
588
+ .map((d) => resolvedByInput.get(d))
589
+ .filter((r) => !!r?.canonicalExternalRef)
590
+ .map((r) => ({ canonicalExternalRef: r.canonicalExternalRef, canonicalName: r.canonicalName, entityTypeCode: r.entityTypeCode }));
591
+ return {
592
+ name: sp.name,
593
+ relativePath: sp.relativePath,
594
+ externalKey: sp.externalKey,
595
+ inferredEntityType: sp.entityType,
596
+ inferredSubtype: sp.subtype,
597
+ resolvedDeps,
598
+ unresolvedDeps: sp.depNames.filter((d) => !resolvedByInput.has(d)),
599
+ };
600
+ }),
601
+ };
602
+ const output = {
603
+ ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
604
+ project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride },
605
+ entities: entitiesResult.summary ?? {},
606
+ relationships: relsResult?.summary ?? { requested: 0, succeeded: 0, failed: 0 },
607
+ resolved: resolvedItems.length,
608
+ unresolved: unresolvedItems.length,
609
+ unresolvedSample: unresolvedItems.slice(0, 10).map((r) => r.input),
610
+ entityErrors: entitiesResult.errors ?? [],
611
+ relationshipErrors: relsResult?.errors ?? [],
612
+ enrichmentTask,
613
+ };
614
+ if (asJson) {
615
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
616
+ if (!output.ok)
617
+ process.exitCode = 1;
618
+ return;
619
+ }
620
+ console.log(`\nDone.`);
621
+ console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${output.entities.failed ?? 0} failed`);
622
+ console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
623
+ if (unresolvedItems.length > 0) {
624
+ console.log(` Candidates : ${unresolvedItems.length} added to reference candidates`);
625
+ }
626
+ if (output.entityErrors.length > 0) {
627
+ console.log("\nEntity errors:");
628
+ for (const err of output.entityErrors) {
629
+ console.log(` ${err.externalKey}: ${err.error} — ${err.message}`);
630
+ }
631
+ }
632
+ // ─── Enrichment task ────────────────────────────────────────────────────────
633
+ console.log(enrichmentTask.instructions);
535
634
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.24",
3
+ "version": "0.1.28",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",