nexarch 0.9.21 → 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.
@@ -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
- // apps/* deployable application component under the root application.
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
- const name = relativePath.toLowerCase();
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
- // src/* deployable application; check scripts
479
- try {
480
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
481
- const scripts = Object.keys(pkg.scripts ?? {});
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);
@@ -1566,8 +1589,16 @@ export async function initProject(args) {
1566
1589
  pendingSteps.push({
1567
1590
  step: stepNum++,
1568
1591
  action: "classify_sub_packages",
1569
- instruction: `Update each sub-package in classifyPackages with a confirmed entity type, subtype, name, and description.`,
1570
- command: `nexarch update-entity --key "<subPackageExternalKey>" --entity-type "<entityType>" --subtype "<subtype>" --name "..." --description "..."`,
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
+ ],
1571
1602
  });
1572
1603
  }
1573
1604
  if (entityTypeOverride === "application") {
@@ -1576,8 +1607,8 @@ export async function initProject(args) {
1576
1607
  action: "discover_functions",
1577
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.`,
1578
1609
  commandTemplates: {
1579
- updateEntity: `nexarch update-entity --key "application_function:${projectSlug}-<function-slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`,
1580
- addRelationship: `nexarch add-relationship --from "application_function:${projectSlug}-<function-slug>" --to "${projectExternalKey}" --type part_of`,
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`,
1581
1612
  },
1582
1613
  });
1583
1614
  }
@@ -1596,8 +1627,8 @@ export async function initProject(args) {
1596
1627
  action: "register_decision_records",
1597
1628
  instruction: `Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register each as a decision_record entity.`,
1598
1629
  commandTemplates: {
1599
- updateEntity: `nexarch update-entity --key "decision_record:${projectSlug}-<adr-slug>" --entity-type decision_record --subtype decision_architecture --name "..." --attributes-json '{"decision":{"summary":"...","detail":"..."}}'`,
1600
- addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}-<adr-slug>" --to "${projectExternalKey}" --type decides`,
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`,
1601
1632
  },
1602
1633
  });
1603
1634
  return {
@@ -1624,6 +1655,7 @@ export async function initProject(args) {
1624
1655
  preWired: preWiredRelationshipKeys.has(relationshipKey),
1625
1656
  };
1626
1657
  });
1658
+ const structuralRel = structuralRelForSubPackage(sp, projectExternalKey);
1627
1659
  return {
1628
1660
  name: sp.name,
1629
1661
  relativePath: sp.relativePath,
@@ -1631,6 +1663,9 @@ export async function initProject(args) {
1631
1663
  heuristicEntityType: sp.entityType,
1632
1664
  heuristicSubtype: sp.subtype,
1633
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,
1634
1669
  resolvedDeps,
1635
1670
  unresolvedDeps: sp.depSpecs.map((d) => d.name).filter((d) => !resolvedByInput.has(d)),
1636
1671
  };
@@ -1686,6 +1721,10 @@ export async function initProject(args) {
1686
1721
  if (subPackages.length > 0) {
1687
1722
  lines.push("");
1688
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.`);
1689
1728
  for (const sp of subPackages) {
1690
1729
  const conf = subPackageConfidence(sp);
1691
1730
  lines.push(` ${sp.relativePath}`);
@@ -1695,7 +1734,12 @@ export async function initProject(args) {
1695
1734
  if (unresolvedDeps.length > 0) {
1696
1735
  lines.push(` unresolved deps (${unresolvedDeps.length}): ${unresolvedDeps.slice(0, 5).join(", ")}${unresolvedDeps.length > 5 ? ` … +${unresolvedDeps.length - 5} more` : ""}`);
1697
1736
  }
1698
- lines.push(` nexarch update-entity --key "${sp.externalKey}" --entity-type "${sp.entityType}" --subtype "${sp.subtype}" --name "..." --description "..."`);
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
+ }
1699
1743
  }
1700
1744
  }
1701
1745
  if (unresolvedItems.length > 0) {
@@ -1724,13 +1768,13 @@ export async function initProject(args) {
1724
1768
  lines.push(` integration_function (external connectivity), or data_function (data processing).`);
1725
1769
  lines.push(` Only register functions clearly evidenced by the codebase — do not invent them.`);
1726
1770
  lines.push(` For each one found:`);
1727
- lines.push(` nexarch update-entity --key "application_function:${projectExternalKey.split(":")[1] ?? "project"}-<function-slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`);
1728
- lines.push(` nexarch add-relationship --from "application_function:${projectExternalKey.split(":")[1] ?? "project"}-<function-slug>" --to "${projectExternalKey}" --type part_of`);
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`);
1729
1773
  }
1730
1774
  lines.push(` ${step++}. Scan the READMEs for platforms/SaaS not auto-detected (Vercel, Neon, Stripe, etc.).`);
1731
1775
  lines.push(` For each found: nexarch resolve-names --names "..." --json → nexarch update-entity → nexarch add-relationship`);
1732
1776
  lines.push(` ${step++}. Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register decision_record entities.`);
1733
- 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":"..."}}'`);
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":"..."}}'`);
1734
1778
  lines.push(` nexarch add-relationship --from "decision_record:..." --to "${projectExternalKey}" --type decides`);
1735
1779
  lines.push("");
1736
1780
  lines.push("RELATIONSHIP DIRECTION RULES (for sub-packages):");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",