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.
- package/dist/commands/init-agent.js +1 -1
- package/dist/commands/init-project.js +181 -82
- 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.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:
|
|
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(
|
|
252
|
-
switch (
|
|
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
|
-
//
|
|
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
|
|
374
|
-
//
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
}
|