nexarch 0.1.25 → 0.1.29

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.25";
7
+ const CLI_VERSION = "0.1.29";
8
8
  const AGENT_ENTITY_TYPE = "agent";
9
9
  const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
10
10
  function parseFlag(args, flag) {
@@ -249,7 +249,6 @@ function scanProject(dir) {
249
249
  const relativePath = pkgPath.replace(dir + "/", "").replace("/package.json", "");
250
250
  const packageName = pkg.name ?? relativePath;
251
251
  const { entityType, subtype } = classifySubPackage(pkgPath, relativePath);
252
- const externalKey = `${entityType}:${slugify(packageName)}`;
253
252
  subPackages.push({
254
253
  name: packageName,
255
254
  relativePath,
@@ -257,7 +256,7 @@ function scanProject(dir) {
257
256
  depNames: deps,
258
257
  entityType,
259
258
  subtype,
260
- externalKey,
259
+ externalKey: "", // computed after scan once projectSlug is known
261
260
  });
262
261
  }
263
262
  for (const dep of deps)
@@ -282,14 +281,19 @@ function scanProject(dir) {
282
281
  return { projectName, packageJsonCount: pkgPaths.length, detectedNames: Array.from(names), rootDepNames, subPackages };
283
282
  }
284
283
  // ─── Relationship type selection ──────────────────────────────────────────────
285
- function pickRelationshipType(entityTypeCode) {
286
- switch (entityTypeCode) {
284
+ function pickRelationshipType(toEntityTypeCode, fromEntityTypeCode = "application") {
285
+ switch (toEntityTypeCode) {
287
286
  case "model":
288
287
  return "uses_model";
289
288
  case "platform":
290
289
  case "platform_component":
291
290
  case "skill":
292
291
  return "uses";
292
+ case "technology_component":
293
+ // tech→tech depends_on is not allowed by ontology; use "uses" instead
294
+ if (fromEntityTypeCode === "technology_component")
295
+ return "uses";
296
+ return "depends_on";
293
297
  default:
294
298
  return "depends_on";
295
299
  }
@@ -310,6 +314,15 @@ export async function initProject(args) {
310
314
  const displayName = nameOverride ?? projectName;
311
315
  const projectSlug = slugify(displayName);
312
316
  const projectExternalKey = `${entityTypeOverride}:${projectSlug}`;
317
+ // Compute sub-package external keys now that projectSlug is known.
318
+ // Unscoped names (no "/") get prefixed with the project slug to avoid ambiguous keys
319
+ // like "application:crawler" — they become "application:whatsontap-crawler" instead,
320
+ // matching what the enrichment agent would naturally choose.
321
+ for (const sp of subPackages) {
322
+ const nameSlug = slugify(sp.name);
323
+ const needsPrefix = !sp.name.includes("/") && !nameSlug.startsWith(projectSlug);
324
+ sp.externalKey = `${sp.entityType}:${needsPrefix ? `${projectSlug}-${nameSlug}` : nameSlug}`;
325
+ }
313
326
  if (!asJson) {
314
327
  console.log(` Project : ${displayName} (${entityTypeOverride})`);
315
328
  console.log(` Packages: ${packageJsonCount} package.json file(s)`);
@@ -454,7 +467,7 @@ export async function initProject(args) {
454
467
  const r = resolvedByInput.get(depName);
455
468
  if (!r?.canonicalExternalRef || !r.entityTypeCode)
456
469
  continue;
457
- addRel(pickRelationshipType(r.entityTypeCode), sp.externalKey, r.canonicalExternalRef);
470
+ addRel(pickRelationshipType(r.entityTypeCode, sp.entityType), sp.externalKey, r.canonicalExternalRef);
458
471
  }
459
472
  }
460
473
  if (!asJson)
@@ -468,64 +481,11 @@ export async function initProject(args) {
468
481
  const relsRaw = await callMcpTool("nexarch_upsert_relationships", { relationships, agentContext, policyContext }, mcpOpts);
469
482
  relsResult = parseToolText(relsRaw);
470
483
  }
471
- const output = {
472
- ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
473
- project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride },
474
- entities: entitiesResult.summary ?? {},
475
- relationships: relsResult?.summary ?? { requested: 0, succeeded: 0, failed: 0 },
476
- resolved: resolvedItems.length,
477
- unresolved: unresolvedItems.length,
478
- unresolvedSample: unresolvedItems.slice(0, 10).map((r) => r.input),
479
- entityErrors: entitiesResult.errors ?? [],
480
- relationshipErrors: relsResult?.errors ?? [],
481
- };
482
- if (asJson) {
483
- process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
484
- if (!output.ok)
485
- process.exitCode = 1;
486
- return;
487
- }
488
- console.log(`\nDone.`);
489
- console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${output.entities.failed ?? 0} failed`);
490
- console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
491
- if (unresolvedItems.length > 0) {
492
- console.log(` Candidates : ${unresolvedItems.length} added to reference candidates`);
493
- }
494
- if (output.entityErrors.length > 0) {
495
- console.log("\nEntity errors:");
496
- for (const err of output.entityErrors) {
497
- console.log(` ${err.externalKey}: ${err.error} — ${err.message}`);
498
- }
499
- }
500
- // ─── Enrichment task ────────────────────────────────────────────────────────
484
+ // Build structured enrichment task (included in JSON output and printed in human mode)
501
485
  const readmeHints = ["README.md", "README.mdx", "docs/README.md", "docs/index.md"]
502
486
  .filter((f) => existsSync(join(dir, f)));
503
- console.log(`
504
- ╔══════════════════════════════════════════════════════════════════╗
505
- ║ ENRICHMENT TASK — action required ║
506
- ╚══════════════════════════════════════════════════════════════════╝
507
-
508
- The mechanical scan has written a skeleton graph entry for this project.
509
- You now need to enrich it with meaningful content.
510
-
511
- PROJECT ENTITY : ${projectExternalKey}
512
- PROJECT DIR : ${dir}
513
- ${readmeHints.length > 0 ? `README FILES : ${readmeHints.join(", ")}` : "README FILES : (none found — check docs/)"}
514
-
515
- STEP 1 — Read the project README / docs and build your understanding of:
516
- • What does this project do? (business purpose, key features)
517
- • Who are the users?
518
- • What is the high-level architecture?
519
- • Any notable technical decisions or constraints?
520
-
521
- STEP 2 — Enrich the project entity. Run this command with the description you've written:
522
-
523
- npx nexarch update-entity \\
524
- --key "${projectExternalKey}" \\
525
- --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? ' \\\n --subtype "app_custom_built"' : ""} \\
526
- --name "<proper product name from README>" \\
527
- --description "<2–4 sentence summary of what it does and why>"
528
- ${subPackages.length > 0 ? `
487
+ function buildEnrichmentInstructions() {
488
+ const subPkgSection = subPackages.length === 0 ? "" : `
529
489
  STEP 3 — Classify and register each sub-package as its own entity.
530
490
  The scanner found ${subPackages.length} workspace package(s):
531
491
 
@@ -580,21 +540,143 @@ ${subPackages.map((sp) => ` • ${sp.name} (${sp.relativePath})`).join("\n")
580
540
  (FROM = ${projectExternalKey}, TO = the library)
581
541
 
582
542
  ⚠️ 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"
543
+ After registering each sub-app/library entity, wire its npm dependencies to IT.
544
+
545
+ RELATIONSHIP TYPE RULES:
546
+ application → technology_component : --type "depends_on"
547
+ technology_component → technology_component : --type "uses" (NOT depends_on)
548
+ any → platform / platform_component : --type "uses"
549
+ any → model : --type "uses_model"
588
550
 
589
551
  Pre-resolved dependencies per sub-package (wire these after registering each entity):
590
552
  ${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")}
599
- ` : ""}`);
553
+ const resolved = sp.depNames
554
+ .map((d) => resolvedByInput.get(d))
555
+ .filter((r) => !!r?.canonicalExternalRef);
556
+ if (resolved.length === 0)
557
+ return ` • ${sp.name}: (no pre-resolved deps — check package.json manually)`;
558
+ const lines = resolved.map((r) => {
559
+ const relType = pickRelationshipType(r.entityTypeCode, sp.entityType);
560
+ return ` --to "${r.canonicalExternalRef}" --type "${relType}" # ${r.canonicalName}`;
561
+ });
562
+ return ` • ${sp.name} (--from "${sp.externalKey}"):\n${lines.join("\n")}`;
563
+ }).join("\n\n")}
564
+ `;
565
+ const finalStep = subPackages.length > 0 ? "STEP 4" : "STEP 3";
566
+ const gapCheckSection = `
567
+ ${finalStep} — Identify architecturally significant components not auto-detected.
568
+
569
+ The mechanical scan only finds npm packages, env var names, and a few config files.
570
+ It systematically misses hosting platforms, managed infrastructure, SaaS integrations,
571
+ and external APIs that are only mentioned in README or deployment config.
572
+
573
+ Review what you've read and ask yourself: are there any of the following that are
574
+ clearly part of this system's architecture but weren't auto-detected?
575
+
576
+ • Hosting / deployment platforms (Vercel, Railway, Fly.io, AWS, GCP, Azure…)
577
+ • Managed data services (Neon, PlanetScale, Supabase, Upstash, Atlas…)
578
+ • Auth / identity providers (Clerk, Auth0, WorkOS, Okta…)
579
+ • External SaaS integrations (Stripe, Resend, SendGrid, Twilio, Sentry…)
580
+ • CDN / storage / queues (Cloudflare, S3, CloudFront, SQS, Redis…)
581
+ • CI/CD platforms (GitHub Actions, GitLab CI, CircleCI…)
582
+
583
+ ONLY register entries you are highly confident about — i.e. explicitly mentioned in
584
+ the README, a config file, or a deployment manifest. Do not guess.
585
+
586
+ For each one you identify:
587
+
588
+ 1. Resolve it to a canonical reference:
589
+ npx nexarch resolve-names --names "<platform name>"
590
+
591
+ 2. If resolved, wire the relationship from the relevant entity:
592
+ npx nexarch add-relationship \\
593
+ --from "${projectExternalKey}" \\
594
+ --to "<resolved-ref>" \\
595
+ --type "uses"
596
+
597
+ 3. If unresolved (not in the reference library), skip it for now — it will appear
598
+ as a reference candidate on the next scan once you register an alias for it.
599
+ `;
600
+ return `
601
+ ╔══════════════════════════════════════════════════════════════════╗
602
+ ║ ENRICHMENT TASK — action required ║
603
+ ╚══════════════════════════════════════════════════════════════════╝
604
+
605
+ The mechanical scan has written a skeleton graph entry for this project.
606
+ You now need to enrich it with meaningful content.
607
+
608
+ PROJECT ENTITY : ${projectExternalKey}
609
+ PROJECT DIR : ${dir}
610
+ ${readmeHints.length > 0 ? `README FILES : ${readmeHints.join(", ")}` : "README FILES : (none found — check docs/)"}
611
+
612
+ STEP 1 — Read the project README / docs and build your understanding of:
613
+ • What does this project do? (business purpose, key features)
614
+ • Who are the users?
615
+ • What is the high-level architecture?
616
+ • Any notable technical decisions or constraints?
617
+
618
+ STEP 2 — Enrich the project entity. Run this command with the description you've written:
619
+
620
+ npx nexarch update-entity \\
621
+ --key "${projectExternalKey}" \\
622
+ --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? " \\\n --subtype \"app_custom_built\"" : ""} \\
623
+ --name "<proper product name from README>" \\
624
+ --description "<2–4 sentence summary of what it does and why>"
625
+ ${subPkgSection}${gapCheckSection}`;
626
+ }
627
+ const enrichmentTask = {
628
+ instructions: buildEnrichmentInstructions(),
629
+ projectEntity: {
630
+ externalKey: projectExternalKey,
631
+ entityType: entityTypeOverride,
632
+ readmeFiles: readmeHints,
633
+ },
634
+ subPackages: subPackages.map((sp) => {
635
+ const resolvedDeps = sp.depNames
636
+ .map((d) => resolvedByInput.get(d))
637
+ .filter((r) => !!r?.canonicalExternalRef)
638
+ .map((r) => ({ canonicalExternalRef: r.canonicalExternalRef, canonicalName: r.canonicalName, entityTypeCode: r.entityTypeCode }));
639
+ return {
640
+ name: sp.name,
641
+ relativePath: sp.relativePath,
642
+ externalKey: sp.externalKey,
643
+ inferredEntityType: sp.entityType,
644
+ inferredSubtype: sp.subtype,
645
+ resolvedDeps,
646
+ unresolvedDeps: sp.depNames.filter((d) => !resolvedByInput.has(d)),
647
+ };
648
+ }),
649
+ };
650
+ const output = {
651
+ ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
652
+ project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride },
653
+ entities: entitiesResult.summary ?? {},
654
+ relationships: relsResult?.summary ?? { requested: 0, succeeded: 0, failed: 0 },
655
+ resolved: resolvedItems.length,
656
+ unresolved: unresolvedItems.length,
657
+ unresolvedSample: unresolvedItems.slice(0, 10).map((r) => r.input),
658
+ entityErrors: entitiesResult.errors ?? [],
659
+ relationshipErrors: relsResult?.errors ?? [],
660
+ enrichmentTask,
661
+ };
662
+ if (asJson) {
663
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
664
+ if (!output.ok)
665
+ process.exitCode = 1;
666
+ return;
667
+ }
668
+ console.log(`\nDone.`);
669
+ console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${output.entities.failed ?? 0} failed`);
670
+ console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
671
+ if (unresolvedItems.length > 0) {
672
+ console.log(` Candidates : ${unresolvedItems.length} added to reference candidates`);
673
+ }
674
+ if (output.entityErrors.length > 0) {
675
+ console.log("\nEntity errors:");
676
+ for (const err of output.entityErrors) {
677
+ console.log(` ${err.externalKey}: ${err.error} — ${err.message}`);
678
+ }
679
+ }
680
+ // ─── Enrichment task ────────────────────────────────────────────────────────
681
+ console.log(enrichmentTask.instructions);
600
682
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.1.25",
3
+ "version": "0.1.29",
4
4
  "description": "Connect AI coding tools to your Nexarch architecture workspace",
5
5
  "keywords": [
6
6
  "nexarch",