nexarch 0.9.20 → 0.9.23
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-project.js +75 -37
- package/package.json +1 -1
|
@@ -444,7 +444,7 @@ function readRootPackage(pkgPath) {
|
|
|
444
444
|
// Guess entity type + subtype for a sub-package based on its path and package.json scripts.
|
|
445
445
|
function classifySubPackage(pkgPath, relativePath) {
|
|
446
446
|
const topDir = relativePath.split("/")[0] ?? "";
|
|
447
|
-
// packages/* → shared library/component
|
|
447
|
+
// packages/* → shared library/component (no server, not independently deployable)
|
|
448
448
|
if (topDir === "packages") {
|
|
449
449
|
const name = relativePath.split("/")[1] ?? "";
|
|
450
450
|
if (name.includes("ui") || name.includes("components"))
|
|
@@ -453,9 +453,31 @@ function classifySubPackage(pkgPath, relativePath) {
|
|
|
453
453
|
return { entityType: "technology_component", subtype: "tech_library" };
|
|
454
454
|
return { entityType: "technology_component", subtype: "tech_library" };
|
|
455
455
|
}
|
|
456
|
-
//
|
|
456
|
+
// Read package.json once for script + bin signals used across all paths below.
|
|
457
|
+
let scripts = [];
|
|
458
|
+
let hasBin = false;
|
|
459
|
+
try {
|
|
460
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
461
|
+
scripts = Object.keys(pkg.scripts ?? {});
|
|
462
|
+
hasBin = pkg.bin !== undefined && pkg.bin !== null;
|
|
463
|
+
}
|
|
464
|
+
catch { }
|
|
465
|
+
const name = relativePath.toLowerCase();
|
|
466
|
+
const hasServerScript = scripts.some((s) => ["start", "dev", "serve"].includes(s));
|
|
467
|
+
const hasBuildScript = scripts.some((s) => s === "build");
|
|
468
|
+
// A bin entry means it is a CLI application regardless of path.
|
|
469
|
+
if (hasBin)
|
|
470
|
+
return { entityType: "application", subtype: "app_cli" };
|
|
471
|
+
// apps/* — treat as full application when it has its own server/dev script
|
|
472
|
+
// (independently deployable); fall back to component classification otherwise.
|
|
457
473
|
if (topDir === "apps") {
|
|
458
|
-
|
|
474
|
+
if (hasServerScript) {
|
|
475
|
+
if (name.includes("worker") || name.includes("job") || name.includes("etl") || name.includes("crawl")) {
|
|
476
|
+
return { entityType: "application", subtype: "app_integration_service" };
|
|
477
|
+
}
|
|
478
|
+
return { entityType: "application", subtype: "app_custom_built" };
|
|
479
|
+
}
|
|
480
|
+
// No server script → treat as a component of the parent application.
|
|
459
481
|
if (name.includes("worker"))
|
|
460
482
|
return { entityType: "application_component", subtype: "app_comp_worker" };
|
|
461
483
|
if (name.includes("job"))
|
|
@@ -475,20 +497,13 @@ function classifySubPackage(pkgPath, relativePath) {
|
|
|
475
497
|
return { entityType: "application_component", subtype: "app_comp_api" };
|
|
476
498
|
return { entityType: "application_component", subtype: "app_comp_api" };
|
|
477
499
|
}
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const hasServer = scripts.some((s) => ["start", "dev", "serve", "build"].includes(s));
|
|
483
|
-
if (hasServer) {
|
|
484
|
-
const name = relativePath.toLowerCase();
|
|
485
|
-
if (name.includes("crawl") || name.includes("worker") || name.includes("job") || name.includes("etl")) {
|
|
486
|
-
return { entityType: "application", subtype: "app_integration_service" };
|
|
487
|
-
}
|
|
488
|
-
return { entityType: "application", subtype: "app_custom_built" };
|
|
500
|
+
// Root-level or other paths — use script signals to detect standalone applications.
|
|
501
|
+
if (hasServerScript || hasBuildScript) {
|
|
502
|
+
if (name.includes("crawl") || name.includes("worker") || name.includes("job") || name.includes("etl")) {
|
|
503
|
+
return { entityType: "application", subtype: "app_integration_service" };
|
|
489
504
|
}
|
|
505
|
+
return { entityType: "application", subtype: "app_custom_built" };
|
|
490
506
|
}
|
|
491
|
-
catch { }
|
|
492
507
|
return { entityType: "technology_component", subtype: "tech_library" };
|
|
493
508
|
}
|
|
494
509
|
// Resolve workspace glob patterns (e.g. "apps/*", "packages/*") to actual directories.
|
|
@@ -794,6 +809,14 @@ function scanEcosystems(dir) {
|
|
|
794
809
|
function isApplicationLikeEntityType(entityType) {
|
|
795
810
|
return entityType === "application" || entityType === "application_component";
|
|
796
811
|
}
|
|
812
|
+
/** Returns the structural relationship that should be wired between the root project and this sub-package. */
|
|
813
|
+
function structuralRelForSubPackage(sp, projectExternalKey) {
|
|
814
|
+
if (sp.entityType === "application")
|
|
815
|
+
return { type: "composes", from: projectExternalKey, to: sp.externalKey };
|
|
816
|
+
if (sp.entityType === "application_component")
|
|
817
|
+
return { type: "part_of", from: sp.externalKey, to: projectExternalKey };
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
797
820
|
function scanProject(dir) {
|
|
798
821
|
const names = new Set();
|
|
799
822
|
let projectName = basename(dir);
|
|
@@ -1367,8 +1390,8 @@ export async function initProject(args) {
|
|
|
1367
1390
|
// Build relationships:
|
|
1368
1391
|
// - Root-level deps → wired to the top-level project entity
|
|
1369
1392
|
// - Sub-package deps → wired to each sub-package entity
|
|
1370
|
-
// - Sub-packages that are apps →
|
|
1371
|
-
// - Sub-package deps that resolve to another sub-package →
|
|
1393
|
+
// - Sub-packages that are apps → root composes sub-app
|
|
1394
|
+
// - Sub-package deps that resolve to another sub-package → depends_on
|
|
1372
1395
|
const relationships = [];
|
|
1373
1396
|
const seenRelPairs = new Set();
|
|
1374
1397
|
let relationshipAddAttempts = 0;
|
|
@@ -1403,19 +1426,13 @@ export async function initProject(args) {
|
|
|
1403
1426
|
seenSubKeys.add(`rel:${sp.externalKey}`);
|
|
1404
1427
|
// Wire structural relationship to the top-level project entity:
|
|
1405
1428
|
// - application_component → part_of → root (component within a single deployable app)
|
|
1406
|
-
// - application → root
|
|
1407
|
-
//
|
|
1408
|
-
// application root: sub-app part_of root (treat as a bounded sub-application)
|
|
1429
|
+
// - application → root composes sub-app (ontology only allows application on part_of FROM
|
|
1430
|
+
// for application_component/application_function, not application itself)
|
|
1409
1431
|
if (sp.entityType === "application_component") {
|
|
1410
1432
|
addRel("part_of", sp.externalKey, projectExternalKey);
|
|
1411
1433
|
}
|
|
1412
1434
|
else if (sp.entityType === "application") {
|
|
1413
|
-
|
|
1414
|
-
addRel("composes", projectExternalKey, sp.externalKey);
|
|
1415
|
-
}
|
|
1416
|
-
else {
|
|
1417
|
-
addRel("part_of", sp.externalKey, projectExternalKey);
|
|
1418
|
-
}
|
|
1435
|
+
addRel("composes", projectExternalKey, sp.externalKey);
|
|
1419
1436
|
}
|
|
1420
1437
|
// Wire each sub-package's resolved deps to itself
|
|
1421
1438
|
for (const dep of sp.depSpecs) {
|
|
@@ -1572,8 +1589,16 @@ export async function initProject(args) {
|
|
|
1572
1589
|
pendingSteps.push({
|
|
1573
1590
|
step: stepNum++,
|
|
1574
1591
|
action: "classify_sub_packages",
|
|
1575
|
-
instruction: `
|
|
1576
|
-
|
|
1592
|
+
instruction: `For each sub-package in classifyPackages: (1) run update-entity to confirm type/subtype/name/description, then (2) immediately run add-relationship to wire the structural relationship. The external key includes the entity type as a prefix — if you change the entity type, the key changes (e.g. application_component:foo → application:foo). Always run update-entity before add-relationship for each package.`,
|
|
1593
|
+
commandTemplates: {
|
|
1594
|
+
updateEntity: `nexarch update-entity --key "<subPackageExternalKey>" --entity-type "<entityType>" --subtype "<subtype>" --name "..." --description "..."`,
|
|
1595
|
+
wireRelationship: `nexarch add-relationship --from "<from>" --to "<to>" --type <composes|part_of> (see structuralRelationship on each classifyPackages entry)`,
|
|
1596
|
+
},
|
|
1597
|
+
notes: [
|
|
1598
|
+
"application sub-packages: parent composes sub-app → add-relationship --from parent --to sub-app --type composes",
|
|
1599
|
+
"application_component sub-packages: component part_of parent → add-relationship --from sub-pkg --to parent --type part_of",
|
|
1600
|
+
"Do NOT wire the relationship before the entity exists at the correct key.",
|
|
1601
|
+
],
|
|
1577
1602
|
});
|
|
1578
1603
|
}
|
|
1579
1604
|
if (entityTypeOverride === "application") {
|
|
@@ -1582,8 +1607,8 @@ export async function initProject(args) {
|
|
|
1582
1607
|
action: "discover_functions",
|
|
1583
1608
|
instruction: `Review the codebase to identify discrete application functions (what the application does). Examine named modules, route layout, service boundaries, and any architecture documentation. Register functions as application_function entities with subtype core_function (primary business function), supporting_function (auxiliary/enablement), integration_function (external connectivity), or data_function (data processing). Only register functions clearly evidenced by the codebase — do not invent them.`,
|
|
1584
1609
|
commandTemplates: {
|
|
1585
|
-
updateEntity: `nexarch update-entity --key "application_function:${projectSlug}
|
|
1586
|
-
addRelationship: `nexarch add-relationship --from "application_function:${projectSlug}
|
|
1610
|
+
updateEntity: `nexarch update-entity --key "application_function:${projectSlug}_<function_slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`,
|
|
1611
|
+
addRelationship: `nexarch add-relationship --from "application_function:${projectSlug}_<function_slug>" --to "${projectExternalKey}" --type part_of`,
|
|
1587
1612
|
},
|
|
1588
1613
|
});
|
|
1589
1614
|
}
|
|
@@ -1602,8 +1627,8 @@ export async function initProject(args) {
|
|
|
1602
1627
|
action: "register_decision_records",
|
|
1603
1628
|
instruction: `Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register each as a decision_record entity.`,
|
|
1604
1629
|
commandTemplates: {
|
|
1605
|
-
updateEntity: `nexarch update-entity --key "decision_record:${projectSlug}
|
|
1606
|
-
addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}
|
|
1630
|
+
updateEntity: `nexarch update-entity --key "decision_record:${projectSlug}_<adr_slug>" --entity-type decision_record --subtype decision_architecture --name "..." --attributes-json '{"decision":{"summary":"...","detail":"..."}}'`,
|
|
1631
|
+
addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}_<adr_slug>" --to "${projectExternalKey}" --type decides`,
|
|
1607
1632
|
},
|
|
1608
1633
|
});
|
|
1609
1634
|
return {
|
|
@@ -1630,6 +1655,7 @@ export async function initProject(args) {
|
|
|
1630
1655
|
preWired: preWiredRelationshipKeys.has(relationshipKey),
|
|
1631
1656
|
};
|
|
1632
1657
|
});
|
|
1658
|
+
const structuralRel = structuralRelForSubPackage(sp, projectExternalKey);
|
|
1633
1659
|
return {
|
|
1634
1660
|
name: sp.name,
|
|
1635
1661
|
relativePath: sp.relativePath,
|
|
@@ -1637,6 +1663,9 @@ export async function initProject(args) {
|
|
|
1637
1663
|
heuristicEntityType: sp.entityType,
|
|
1638
1664
|
heuristicSubtype: sp.subtype,
|
|
1639
1665
|
confidence: subPackageConfidence(sp),
|
|
1666
|
+
structuralRelationship: structuralRel
|
|
1667
|
+
? { ...structuralRel, note: "Wire AFTER update-entity. If entity type changes, update the key prefix in from/to before running add-relationship." }
|
|
1668
|
+
: null,
|
|
1640
1669
|
resolvedDeps,
|
|
1641
1670
|
unresolvedDeps: sp.depSpecs.map((d) => d.name).filter((d) => !resolvedByInput.has(d)),
|
|
1642
1671
|
};
|
|
@@ -1692,6 +1721,10 @@ export async function initProject(args) {
|
|
|
1692
1721
|
if (subPackages.length > 0) {
|
|
1693
1722
|
lines.push("");
|
|
1694
1723
|
lines.push(`CLASSIFY_THESE (${subPackages.length} sub-package(s) — read each, confirm type, then update):`);
|
|
1724
|
+
lines.push(` NOTE: The external key includes the entity type as a prefix (e.g. application:foo).`);
|
|
1725
|
+
lines.push(` If you change the entity type, the key changes too — update-entity will create an entity`);
|
|
1726
|
+
lines.push(` at the new key. Always wire the structural relationship AFTER the update-entity call,`);
|
|
1727
|
+
lines.push(` using the confirmed key. Do not wire relationships before the entity exists.`);
|
|
1695
1728
|
for (const sp of subPackages) {
|
|
1696
1729
|
const conf = subPackageConfidence(sp);
|
|
1697
1730
|
lines.push(` ${sp.relativePath}`);
|
|
@@ -1701,7 +1734,12 @@ export async function initProject(args) {
|
|
|
1701
1734
|
if (unresolvedDeps.length > 0) {
|
|
1702
1735
|
lines.push(` unresolved deps (${unresolvedDeps.length}): ${unresolvedDeps.slice(0, 5).join(", ")}${unresolvedDeps.length > 5 ? ` … +${unresolvedDeps.length - 5} more` : ""}`);
|
|
1703
1736
|
}
|
|
1704
|
-
lines.push(`
|
|
1737
|
+
lines.push(` 1) nexarch update-entity --key "${sp.externalKey}" --entity-type "${sp.entityType}" --subtype "${sp.subtype}" --name "..." --description "..."`);
|
|
1738
|
+
const rel = structuralRelForSubPackage(sp, projectExternalKey);
|
|
1739
|
+
if (rel) {
|
|
1740
|
+
lines.push(` 2) nexarch add-relationship --from "${rel.from}" --to "${rel.to}" --type ${rel.type}`);
|
|
1741
|
+
lines.push(` (adjust key prefix if you changed the entity type above)`);
|
|
1742
|
+
}
|
|
1705
1743
|
}
|
|
1706
1744
|
}
|
|
1707
1745
|
if (unresolvedItems.length > 0) {
|
|
@@ -1730,17 +1768,17 @@ export async function initProject(args) {
|
|
|
1730
1768
|
lines.push(` integration_function (external connectivity), or data_function (data processing).`);
|
|
1731
1769
|
lines.push(` Only register functions clearly evidenced by the codebase — do not invent them.`);
|
|
1732
1770
|
lines.push(` For each one found:`);
|
|
1733
|
-
lines.push(` nexarch update-entity --key "application_function:${projectExternalKey.split(":")[1] ?? "project"}
|
|
1734
|
-
lines.push(` nexarch add-relationship --from "application_function:${projectExternalKey.split(":")[1] ?? "project"}
|
|
1771
|
+
lines.push(` nexarch update-entity --key "application_function:${projectExternalKey.split(":")[1] ?? "project"}_<function_slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`);
|
|
1772
|
+
lines.push(` nexarch add-relationship --from "application_function:${projectExternalKey.split(":")[1] ?? "project"}_<function_slug>" --to "${projectExternalKey}" --type part_of`);
|
|
1735
1773
|
}
|
|
1736
1774
|
lines.push(` ${step++}. Scan the READMEs for platforms/SaaS not auto-detected (Vercel, Neon, Stripe, etc.).`);
|
|
1737
1775
|
lines.push(` For each found: nexarch resolve-names --names "..." --json → nexarch update-entity → nexarch add-relationship`);
|
|
1738
1776
|
lines.push(` ${step++}. Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register decision_record entities.`);
|
|
1739
|
-
lines.push(` nexarch update-entity --key "decision_record:${projectExternalKey.split(":")[1] ?? "project"}
|
|
1777
|
+
lines.push(` nexarch update-entity --key "decision_record:${projectExternalKey.split(":")[1] ?? "project"}_<adr_slug>" --entity-type decision_record --subtype decision_architecture --name "..." --attributes-json '{"decision":{"summary":"...","detail":"..."}}'`);
|
|
1740
1778
|
lines.push(` nexarch add-relationship --from "decision_record:..." --to "${projectExternalKey}" --type decides`);
|
|
1741
1779
|
lines.push("");
|
|
1742
1780
|
lines.push("RELATIONSHIP DIRECTION RULES (for sub-packages):");
|
|
1743
|
-
lines.push(" apps/* (deployable components) →
|
|
1781
|
+
lines.push(" apps/* (deployable components) → parent composes → sub-application");
|
|
1744
1782
|
lines.push(" packages/* (shared libraries) → parent depends_on → library");
|
|
1745
1783
|
lines.push(" Deps wired from manifests are already pre-wired; do not re-add unless key changed.");
|
|
1746
1784
|
if (graphDiff) {
|