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.
- package/dist/commands/init-agent.js +1 -1
- package/dist/commands/init-project.js +157 -75
- package/package.json +1 -1
|
@@ -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.
|
|
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(
|
|
286
|
-
switch (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
}
|