nexarch 0.12.4 → 0.12.5
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 +207 -89
- package/package.json +35 -35
|
@@ -1162,20 +1162,32 @@ export async function initProject(args) {
|
|
|
1162
1162
|
const displayName = nameOverride ?? projectName;
|
|
1163
1163
|
const projectSlug = slugify(displayName);
|
|
1164
1164
|
let projectExternalKey = `${entityTypeOverride}:${projectSlug}`;
|
|
1165
|
+
// ADR 8a (docs/strategy/adr-ontology-reset-v1.md): the repository is modelled
|
|
1166
|
+
// as a `project` entity — evidence, not architecture. Monorepos no longer get
|
|
1167
|
+
// a synthetic root application; deployable packages become applications
|
|
1168
|
+
// sourced_from the project. projectExternalKey remains the APPLICATION key,
|
|
1169
|
+
// used only when the repo is itself a single deployable.
|
|
1170
|
+
const projectConstruct = entityTypeOverride === "application";
|
|
1171
|
+
const isMonorepo = subPackages.length > 0;
|
|
1172
|
+
const projectDirName = nameOverride ?? basename(dir);
|
|
1173
|
+
const projectDirSlug = slugify(projectDirName);
|
|
1174
|
+
const projectEntityKey = `project:${projectDirSlug}`;
|
|
1165
1175
|
// Compute sub-package external keys now that projectSlug is known.
|
|
1166
1176
|
// Unscoped names (no "/") get prefixed with the project slug to avoid ambiguous keys
|
|
1167
1177
|
// like "application:crawler" — they become "application:whatsontap-crawler" instead,
|
|
1168
1178
|
// matching what the enrichment agent would naturally choose.
|
|
1179
|
+
// Keys derive from the directory basename, not package.json name: paths are
|
|
1180
|
+
// stable, unique within a repo, and human-meaningful, while npm names can
|
|
1181
|
+
// collide with the project itself (this repo's CLI package is literally named
|
|
1182
|
+
// "nexarch"). For existing layouts this reproduces the pre-8a keys
|
|
1183
|
+
// byte-for-byte (application:nexarch_web), keeping re-runs idempotent.
|
|
1184
|
+
const seenSubSlugs = new Set();
|
|
1169
1185
|
for (const sp of subPackages) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const relSlug = slugify(sp.relativePath.replace(/\//g, "_"));
|
|
1176
|
-
keySlug = relSlug ? `${projectSlug}_${relSlug}` : `${projectSlug}_${nameSlug}_sub`;
|
|
1177
|
-
}
|
|
1178
|
-
sp.externalKey = `${sp.entityType}:${keySlug}`;
|
|
1186
|
+
let dirSlug = slugify(basename(sp.relativePath));
|
|
1187
|
+
if (seenSubSlugs.has(dirSlug))
|
|
1188
|
+
dirSlug = slugify(sp.relativePath.replace(/\//g, "_"));
|
|
1189
|
+
seenSubSlugs.add(dirSlug);
|
|
1190
|
+
sp.externalKey = `${sp.entityType}:${projectDirSlug}_${dirSlug}`;
|
|
1179
1191
|
}
|
|
1180
1192
|
if (!asJson) {
|
|
1181
1193
|
console.log(` Project : ${displayName} (${entityTypeOverride})`);
|
|
@@ -1214,9 +1226,10 @@ export async function initProject(args) {
|
|
|
1214
1226
|
const output = {
|
|
1215
1227
|
dryRun: true,
|
|
1216
1228
|
project: {
|
|
1217
|
-
name: displayName,
|
|
1218
|
-
externalKey: projectExternalKey,
|
|
1219
|
-
entityType: entityTypeOverride,
|
|
1229
|
+
name: projectConstruct ? projectDirName : displayName,
|
|
1230
|
+
externalKey: projectConstruct ? projectEntityKey : projectExternalKey,
|
|
1231
|
+
entityType: projectConstruct ? "project" : entityTypeOverride,
|
|
1232
|
+
...(projectConstruct ? { subtype: isMonorepo ? "project_monorepo" : "project_repository" } : {}),
|
|
1220
1233
|
sourceRepository: detectedRepo
|
|
1221
1234
|
? {
|
|
1222
1235
|
ref: detectedRepo.rawRef,
|
|
@@ -1270,7 +1283,10 @@ export async function initProject(args) {
|
|
|
1270
1283
|
const repoUrl = repoUrlOverride ?? detectedRepo?.url ?? null;
|
|
1271
1284
|
const sourceVcsType = detectedRepo?.vcsType ?? "unknown";
|
|
1272
1285
|
const sourceProvider = detectedRepo?.provider ?? "unknown";
|
|
1273
|
-
if (
|
|
1286
|
+
if (projectConstruct && isMonorepo && (applicationRefOverride || forceCreateApplication) && !asJson) {
|
|
1287
|
+
console.log("\nNote: monorepo detected — no root application is created (ADR 8a); --application-ref/--create-application apply only to single-package repositories.");
|
|
1288
|
+
}
|
|
1289
|
+
if (entityTypeOverride === "application" && !forceCreateApplication && !isMonorepo) {
|
|
1274
1290
|
if (applicationRefOverride) {
|
|
1275
1291
|
projectExternalKey = applicationRefOverride;
|
|
1276
1292
|
if (!asJson)
|
|
@@ -1343,7 +1359,7 @@ export async function initProject(args) {
|
|
|
1343
1359
|
}
|
|
1344
1360
|
}
|
|
1345
1361
|
}
|
|
1346
|
-
logProgress("application.target", projectExternalKey);
|
|
1362
|
+
logProgress(projectConstruct ? "project.target" : "application.target", projectConstruct ? (isMonorepo ? projectEntityKey : `${projectEntityKey} + ${projectExternalKey}`) : projectExternalKey);
|
|
1347
1363
|
// In refresh mode, snapshot the current graph state for this project before writing,
|
|
1348
1364
|
// so we can diff what changed and surface stale relationships to the agent.
|
|
1349
1365
|
let currentOutgoingRels = [];
|
|
@@ -1376,26 +1392,46 @@ export async function initProject(args) {
|
|
|
1376
1392
|
: undefined;
|
|
1377
1393
|
// Build entity list — project entity + all resolved reference entities
|
|
1378
1394
|
const entities = [];
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1395
|
+
const scanProvenanceAttributes = {
|
|
1396
|
+
source_dir: dir,
|
|
1397
|
+
scanned_at: nowIso,
|
|
1398
|
+
package_json_count: packageJsonCount,
|
|
1399
|
+
...(repoUrl ? { repository_url: repoUrl, source_repository_url: repoUrl } : {}),
|
|
1400
|
+
repository_ref: repoRef,
|
|
1401
|
+
source_repository_ref: repoRef,
|
|
1402
|
+
source_vcs_type: sourceVcsType,
|
|
1403
|
+
source_provider: sourceProvider,
|
|
1404
|
+
...(detectedRepo?.canonicalRepoRef ? { source_repository_component_ref: detectedRepo.canonicalRepoRef } : {}),
|
|
1405
|
+
};
|
|
1406
|
+
// The repository as evidence: a `project` entity (ADR 8a). It arrives active —
|
|
1407
|
+
// a repository is a fact needing no review — while the applications it sources
|
|
1408
|
+
// arrive proposed (ADR 8b, enforced at the gateway).
|
|
1409
|
+
if (projectConstruct) {
|
|
1410
|
+
entities.push({
|
|
1411
|
+
externalKey: projectEntityKey,
|
|
1412
|
+
entityTypeCode: "project",
|
|
1413
|
+
entitySubtypeCode: isMonorepo ? "project_monorepo" : "project_repository",
|
|
1414
|
+
name: projectDirName,
|
|
1415
|
+
confidence: 1,
|
|
1416
|
+
attributes: {
|
|
1417
|
+
...scanProvenanceAttributes,
|
|
1418
|
+
...(projectName !== projectDirName ? { npm_package_name: projectName } : {}),
|
|
1419
|
+
},
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
// The root application exists only when the repo IS a single deployable
|
|
1423
|
+
// (or in legacy non-application mode). A monorepo's container is the project.
|
|
1424
|
+
if (!projectConstruct || !isMonorepo) {
|
|
1425
|
+
const projectSubtype = entityTypeOverride === "application" ? "app_custom_built" : undefined;
|
|
1426
|
+
entities.push({
|
|
1427
|
+
externalKey: projectExternalKey,
|
|
1428
|
+
entityTypeCode: entityTypeOverride,
|
|
1429
|
+
...(projectSubtype ? { entitySubtypeCode: projectSubtype } : {}),
|
|
1430
|
+
name: displayName,
|
|
1431
|
+
confidence: 1,
|
|
1432
|
+
attributes: scanProvenanceAttributes,
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1399
1435
|
// Resolved reference entities (deduplicated by canonical external ref)
|
|
1400
1436
|
const seenRefs = new Set();
|
|
1401
1437
|
for (const r of resolvedItems) {
|
|
@@ -1412,7 +1448,9 @@ export async function initProject(args) {
|
|
|
1412
1448
|
});
|
|
1413
1449
|
}
|
|
1414
1450
|
// Ensure source repository component is represented when we can infer one.
|
|
1415
|
-
|
|
1451
|
+
// Under the project construct the project entity IS the repository, so the
|
|
1452
|
+
// separate technology_component stand-in is no longer created.
|
|
1453
|
+
if (!projectConstruct && detectedRepo?.canonicalRepoRef && !seenRefs.has(detectedRepo.canonicalRepoRef)) {
|
|
1416
1454
|
seenRefs.add(detectedRepo.canonicalRepoRef);
|
|
1417
1455
|
entities.push({
|
|
1418
1456
|
externalKey: detectedRepo.canonicalRepoRef,
|
|
@@ -1439,13 +1477,18 @@ export async function initProject(args) {
|
|
|
1439
1477
|
if (seenSubKeys.has(sp.externalKey))
|
|
1440
1478
|
continue;
|
|
1441
1479
|
seenSubKeys.add(sp.externalKey);
|
|
1480
|
+
const dirDisplayName = basename(sp.relativePath);
|
|
1442
1481
|
entities.push({
|
|
1443
1482
|
externalKey: sp.externalKey,
|
|
1444
1483
|
entityTypeCode: sp.entityType,
|
|
1445
1484
|
entitySubtypeCode: sp.subtype,
|
|
1446
|
-
name:
|
|
1485
|
+
name: dirDisplayName,
|
|
1447
1486
|
confidence: 0.8,
|
|
1448
|
-
attributes: {
|
|
1487
|
+
attributes: {
|
|
1488
|
+
source_dir: `${dir}/${sp.relativePath}`,
|
|
1489
|
+
scanned_at: nowIso,
|
|
1490
|
+
...(sp.name && sp.name !== dirDisplayName ? { npm_package_name: sp.name } : {}),
|
|
1491
|
+
},
|
|
1449
1492
|
});
|
|
1450
1493
|
}
|
|
1451
1494
|
// Build a lookup from input name to resolved result for enrichment task and sub-app wiring
|
|
@@ -1487,8 +1530,13 @@ export async function initProject(args) {
|
|
|
1487
1530
|
seenRelPairs.add(key);
|
|
1488
1531
|
relationships.push({ relationshipTypeCode: type, fromEntityExternalKey: from, toEntityExternalKey: to, confidence, attributes });
|
|
1489
1532
|
}
|
|
1490
|
-
// Root-level deps →
|
|
1491
|
-
|
|
1533
|
+
// Root-level deps → the root application (single-package/legacy only). A
|
|
1534
|
+
// project carries provenance, not dependencies — the ontology forbids
|
|
1535
|
+
// depends_on FROM project by design.
|
|
1536
|
+
if (projectConstruct && isMonorepo && rootDepNames.size > 0 && !asJson) {
|
|
1537
|
+
console.log(` Note: ${rootDepNames.size} root-level manifest dep(s) recorded as entities; not wired to the project (shared tooling).`);
|
|
1538
|
+
}
|
|
1539
|
+
for (const r of (projectConstruct && isMonorepo ? [] : resolvedItems)) {
|
|
1492
1540
|
if (!r.canonicalExternalRef || !r.entityTypeCode)
|
|
1493
1541
|
continue;
|
|
1494
1542
|
if (!rootDepNames.has(r.input) && !rootDepNames.has(r.normalised))
|
|
@@ -1505,11 +1553,16 @@ export async function initProject(args) {
|
|
|
1505
1553
|
if (!sp.externalKey || seenSubKeys.has(`rel:${sp.externalKey}`))
|
|
1506
1554
|
continue;
|
|
1507
1555
|
seenSubKeys.add(`rel:${sp.externalKey}`);
|
|
1508
|
-
//
|
|
1509
|
-
//
|
|
1510
|
-
//
|
|
1511
|
-
//
|
|
1512
|
-
if (
|
|
1556
|
+
// ADR 8a: provenance, not containment. Every application-like package is
|
|
1557
|
+
// sourced_from the project; the composes/part_of wiring to a synthetic root
|
|
1558
|
+
// is gone. Genuine product composition is a human assertion made through
|
|
1559
|
+
// the proposal flow, never inferred from directory layout.
|
|
1560
|
+
if (projectConstruct) {
|
|
1561
|
+
if (isApplicationLikeEntityType(sp.entityType)) {
|
|
1562
|
+
addRel("sourced_from", sp.externalKey, projectEntityKey, 1);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
else if (sp.entityType === "application_component") {
|
|
1513
1566
|
addRel("part_of", sp.externalKey, projectExternalKey);
|
|
1514
1567
|
}
|
|
1515
1568
|
else if (sp.entityType === "application") {
|
|
@@ -1550,15 +1603,21 @@ export async function initProject(args) {
|
|
|
1550
1603
|
catch {
|
|
1551
1604
|
// keep fallback
|
|
1552
1605
|
}
|
|
1553
|
-
|
|
1606
|
+
if (!projectConstruct || !isMonorepo) {
|
|
1607
|
+
addRel("accountable_for", orgExternalKey, projectExternalKey, 1);
|
|
1608
|
+
}
|
|
1554
1609
|
// Also accountable_for any sub-package applications
|
|
1555
1610
|
for (const sp of subPackages) {
|
|
1556
1611
|
if (sp.externalKey && isApplicationLikeEntityType(sp.entityType)) {
|
|
1557
1612
|
addRel("accountable_for", orgExternalKey, sp.externalKey, 1);
|
|
1558
1613
|
}
|
|
1559
1614
|
}
|
|
1560
|
-
//
|
|
1561
|
-
if (
|
|
1615
|
+
// Single-package repos: the one application is sourced_from the project.
|
|
1616
|
+
if (projectConstruct && !isMonorepo) {
|
|
1617
|
+
addRel("sourced_from", projectExternalKey, projectEntityKey, 1);
|
|
1618
|
+
}
|
|
1619
|
+
// Legacy only: the project entity now carries repository provenance itself.
|
|
1620
|
+
if (!projectConstruct && detectedRepo?.canonicalRepoRef) {
|
|
1562
1621
|
addRel("depends_on", projectExternalKey, detectedRepo.canonicalRepoRef, 0.95);
|
|
1563
1622
|
}
|
|
1564
1623
|
// In refresh mode the raw graph state (fetched above) is emitted alongside scan results
|
|
@@ -1650,29 +1709,50 @@ export async function initProject(args) {
|
|
|
1650
1709
|
// so agents using --json receive the same mandatory instructions as text-mode agents.
|
|
1651
1710
|
const pendingSteps = [];
|
|
1652
1711
|
let stepNum = 1;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1712
|
+
if (projectConstruct) {
|
|
1713
|
+
pendingSteps.push({
|
|
1714
|
+
step: stepNum++,
|
|
1715
|
+
action: "describe_project",
|
|
1716
|
+
instruction: `Give the project (repository) entity a short description of what lives in this repo.`,
|
|
1717
|
+
command: `nexarch update-entity --key "${projectEntityKey}" --entity-type "project" --name "${projectDirName}" --description "..."`,
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
if (!projectConstruct || !isMonorepo) {
|
|
1721
|
+
pendingSteps.push({
|
|
1722
|
+
step: stepNum++,
|
|
1723
|
+
action: "enrich_entity",
|
|
1724
|
+
instruction: `Enrich the application entity with a meaningful name, description, subtype, and icon.`,
|
|
1725
|
+
command: `nexarch update-entity --key "${projectExternalKey}" --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? ' --subtype "<subtype>" --icon "<lucide-icon>"' : ""} --name "..." --description "..."`,
|
|
1726
|
+
...(entityTypeOverride === "application"
|
|
1727
|
+
? { notes: [APPLICATION_SUBTYPE_HINT, "Choose app_custom_built as the default if none of the others clearly apply."] }
|
|
1728
|
+
: {}),
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1662
1731
|
if (subPackages.length > 0) {
|
|
1663
1732
|
pendingSteps.push({
|
|
1664
1733
|
step: stepNum++,
|
|
1665
1734
|
action: "classify_sub_packages",
|
|
1666
|
-
instruction:
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1735
|
+
instruction: projectConstruct
|
|
1736
|
+
? `For each sub-package in classifyPackages, run update-entity to confirm type/subtype/name/description. The sourced_from relationship to the project is wired automatically — no structural add-relationship is needed. 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) and the old key's sourced_from should be retired.`
|
|
1737
|
+
: `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.`,
|
|
1738
|
+
commandTemplates: projectConstruct
|
|
1739
|
+
? {
|
|
1740
|
+
updateEntity: `nexarch update-entity --key "<subPackageExternalKey>" --entity-type "<entityType>" --subtype "<subtype>" --name "..." --description "..."`,
|
|
1741
|
+
}
|
|
1742
|
+
: {
|
|
1743
|
+
updateEntity: `nexarch update-entity --key "<subPackageExternalKey>" --entity-type "<entityType>" --subtype "<subtype>" --name "..." --description "..."`,
|
|
1744
|
+
wireRelationship: `nexarch add-relationship --from "<from>" --to "<to>" --type <composes|part_of> (see structuralRelationship on each classifyPackages entry)`,
|
|
1745
|
+
},
|
|
1746
|
+
notes: projectConstruct
|
|
1747
|
+
? [
|
|
1748
|
+
`Every package is already sourced_from ${projectEntityKey} — provenance is automatic.`,
|
|
1749
|
+
"New applications arrive as proposed and wait for human activation in the workspace — do not describe them as fully registered.",
|
|
1750
|
+
]
|
|
1751
|
+
: [
|
|
1752
|
+
"application sub-packages: parent composes sub-app → add-relationship --from parent --to sub-app --type composes",
|
|
1753
|
+
"application_component sub-packages: component part_of parent → add-relationship --from sub-pkg --to parent --type part_of",
|
|
1754
|
+
"Do NOT wire the relationship before the entity exists at the correct key.",
|
|
1755
|
+
],
|
|
1676
1756
|
});
|
|
1677
1757
|
}
|
|
1678
1758
|
if (entityTypeOverride === "application") {
|
|
@@ -1682,7 +1762,7 @@ export async function initProject(args) {
|
|
|
1682
1762
|
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.`,
|
|
1683
1763
|
commandTemplates: {
|
|
1684
1764
|
updateEntity: `nexarch update-entity --key "application_function:${projectSlug}_<function_slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`,
|
|
1685
|
-
addRelationship: `nexarch add-relationship --from "application_function:${projectSlug}_<function_slug>" --to "${projectExternalKey}" --type part_of`,
|
|
1765
|
+
addRelationship: `nexarch add-relationship --from "application_function:${projectSlug}_<function_slug>" --to ${projectConstruct && isMonorepo ? '"<owning application key from classifyPackages>"' : `"${projectExternalKey}"`} --type part_of`,
|
|
1686
1766
|
},
|
|
1687
1767
|
});
|
|
1688
1768
|
}
|
|
@@ -1693,7 +1773,7 @@ export async function initProject(args) {
|
|
|
1693
1773
|
commandTemplates: {
|
|
1694
1774
|
resolve: `nexarch resolve-names --names "..." --json`,
|
|
1695
1775
|
updateEntity: `nexarch update-entity --key "<canonicalExternalRef>" --entity-type "<entityType>" --name "..."`,
|
|
1696
|
-
addRelationship: `nexarch add-relationship --from "${projectExternalKey}" --to "<canonicalExternalRef>" --type depends_on`,
|
|
1776
|
+
addRelationship: `nexarch add-relationship --from ${projectConstruct && isMonorepo ? '"<the application that uses it>"' : `"${projectExternalKey}"`} --to "<canonicalExternalRef>" --type depends_on`,
|
|
1697
1777
|
},
|
|
1698
1778
|
});
|
|
1699
1779
|
pendingSteps.push({
|
|
@@ -1702,7 +1782,7 @@ export async function initProject(args) {
|
|
|
1702
1782
|
instruction: `Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register each as a decision_record entity.`,
|
|
1703
1783
|
commandTemplates: {
|
|
1704
1784
|
updateEntity: `nexarch update-entity --key "decision_record:${projectSlug}_<adr_slug>" --entity-type decision_record --subtype decision_architecture --name "..." --attributes-json '{"decision":{"summary":"...","detail":"..."}}'`,
|
|
1705
|
-
addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}_<adr_slug>" --to "${projectExternalKey}" --type decides`,
|
|
1785
|
+
addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}_<adr_slug>" --to ${projectConstruct && isMonorepo ? '"<the relevant application key>"' : `"${projectExternalKey}"`} --type decides`,
|
|
1706
1786
|
},
|
|
1707
1787
|
});
|
|
1708
1788
|
const preservedEntities = entitiesResult.preserved ?? [];
|
|
@@ -1725,10 +1805,17 @@ export async function initProject(args) {
|
|
|
1725
1805
|
},
|
|
1726
1806
|
}
|
|
1727
1807
|
: {}),
|
|
1728
|
-
projectEntity:
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1808
|
+
projectEntity: projectConstruct
|
|
1809
|
+
? {
|
|
1810
|
+
externalKey: projectEntityKey,
|
|
1811
|
+
entityType: "project",
|
|
1812
|
+
subtype: isMonorepo ? "project_monorepo" : "project_repository",
|
|
1813
|
+
...(isMonorepo ? {} : { applicationExternalKey: projectExternalKey }),
|
|
1814
|
+
}
|
|
1815
|
+
: {
|
|
1816
|
+
externalKey: projectExternalKey,
|
|
1817
|
+
entityType: entityTypeOverride,
|
|
1818
|
+
},
|
|
1732
1819
|
pendingSteps,
|
|
1733
1820
|
readFiles: readmeHints,
|
|
1734
1821
|
...(refreshMode
|
|
@@ -1832,7 +1919,7 @@ export async function initProject(args) {
|
|
|
1832
1919
|
lines.push("Until they are done, this project is registered as a skeleton only.");
|
|
1833
1920
|
}
|
|
1834
1921
|
lines.push("");
|
|
1835
|
-
lines.push(`PROJECT : ${projectExternalKey}`);
|
|
1922
|
+
lines.push(`PROJECT : ${projectConstruct ? `${projectEntityKey} (${isMonorepo ? "monorepo" : "repository"})` : projectExternalKey}`);
|
|
1836
1923
|
lines.push(`DIR : ${dir}`);
|
|
1837
1924
|
if (readmeHints.length > 0) {
|
|
1838
1925
|
lines.push("");
|
|
@@ -1861,10 +1948,15 @@ export async function initProject(args) {
|
|
|
1861
1948
|
lines.push(` unresolved deps (${unresolvedDeps.length}): ${unresolvedDeps.slice(0, 5).join(", ")}${unresolvedDeps.length > 5 ? ` … +${unresolvedDeps.length - 5} more` : ""}`);
|
|
1862
1949
|
}
|
|
1863
1950
|
lines.push(` 1) nexarch update-entity --key "${sp.externalKey}" --entity-type "${sp.entityType}" --subtype "${sp.subtype}" --name "..." --description "..."`);
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1951
|
+
if (projectConstruct) {
|
|
1952
|
+
lines.push(` (sourced_from ${projectEntityKey} is wired automatically — no structural relationship needed)`);
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
const rel = structuralRelForSubPackage(sp, projectExternalKey);
|
|
1956
|
+
if (rel) {
|
|
1957
|
+
lines.push(` 2) nexarch add-relationship --from "${rel.from}" --to "${rel.to}" --type ${rel.type}`);
|
|
1958
|
+
lines.push(` (adjust key prefix if you changed the entity type above)`);
|
|
1959
|
+
}
|
|
1868
1960
|
}
|
|
1869
1961
|
}
|
|
1870
1962
|
}
|
|
@@ -1892,10 +1984,16 @@ export async function initProject(args) {
|
|
|
1892
1984
|
lines.push("");
|
|
1893
1985
|
lines.push("REMAINING STEPS:");
|
|
1894
1986
|
let step = 1;
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
lines.push(`
|
|
1898
|
-
|
|
1987
|
+
if (projectConstruct) {
|
|
1988
|
+
lines.push(` ${step++}. nexarch update-entity --key "${projectEntityKey}" --entity-type "project" --name "${projectDirName}" --description "..."`);
|
|
1989
|
+
lines.push(` (short description of what lives in this repository)`);
|
|
1990
|
+
}
|
|
1991
|
+
if (!projectConstruct || !isMonorepo) {
|
|
1992
|
+
lines.push(` ${step++}. nexarch update-entity --key "${projectExternalKey}" --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? ' --subtype "<subtype>" --icon "<lucide-icon>"' : ""} --name "..." --description "..."`);
|
|
1993
|
+
if (entityTypeOverride === "application") {
|
|
1994
|
+
lines.push(` ${APPLICATION_SUBTYPE_HINT}`);
|
|
1995
|
+
lines.push(` (choose app_custom_built as the default if none of the others clearly apply)`);
|
|
1996
|
+
}
|
|
1899
1997
|
}
|
|
1900
1998
|
if (subPackages.length > 0) {
|
|
1901
1999
|
lines.push(` ${step++}. Update each sub-package listed under CLASSIFY_THESE above.`);
|
|
@@ -1907,19 +2005,28 @@ export async function initProject(args) {
|
|
|
1907
2005
|
lines.push(` integration_function (external connectivity), or data_function (data processing).`);
|
|
1908
2006
|
lines.push(` Only register functions clearly evidenced by the codebase — do not invent them.`);
|
|
1909
2007
|
lines.push(` For each one found:`);
|
|
2008
|
+
const fnOwnerTarget = projectConstruct && isMonorepo ? '"<owning application key from CLASSIFY_THESE>"' : `"${projectExternalKey}"`;
|
|
1910
2009
|
lines.push(` nexarch update-entity --key "application_function:${projectExternalKey.split(":")[1] ?? "project"}_<function_slug>" --entity-type application_function --subtype core_function --name "..." --description "..."`);
|
|
1911
|
-
lines.push(` nexarch add-relationship --from "application_function:${projectExternalKey.split(":")[1] ?? "project"}_<function_slug>" --to
|
|
2010
|
+
lines.push(` nexarch add-relationship --from "application_function:${projectExternalKey.split(":")[1] ?? "project"}_<function_slug>" --to ${fnOwnerTarget} --type part_of`);
|
|
1912
2011
|
}
|
|
1913
2012
|
lines.push(` ${step++}. Scan the READMEs for platforms/SaaS not auto-detected (Vercel, Neon, Stripe, etc.).`);
|
|
1914
2013
|
lines.push(` For each found: nexarch resolve-names --names "..." --json → nexarch update-entity → nexarch add-relationship`);
|
|
1915
2014
|
lines.push(` ${step++}. Look for ADRs (docs/adr/, decisions/, ADR-*.md) and register decision_record entities.`);
|
|
1916
2015
|
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":"..."}}'`);
|
|
1917
|
-
lines.push(` nexarch add-relationship --from "decision_record:..." --to "${projectExternalKey}" --type decides`);
|
|
2016
|
+
lines.push(` nexarch add-relationship --from "decision_record:..." --to ${projectConstruct && isMonorepo ? '"<the relevant application key>"' : `"${projectExternalKey}"`} --type decides`);
|
|
1918
2017
|
lines.push("");
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2018
|
+
if (projectConstruct) {
|
|
2019
|
+
lines.push("RELATIONSHIP RULES:");
|
|
2020
|
+
lines.push(` Every package is sourced_from ${projectEntityKey} — wired automatically (provenance, not containment).`);
|
|
2021
|
+
lines.push(" Manifest deps are pre-wired per package; do not re-add unless a key changed.");
|
|
2022
|
+
lines.push(" Genuine product composition (one product spanning these apps) is a human judgement — propose it, never infer it from directory layout.");
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
lines.push("RELATIONSHIP DIRECTION RULES (for sub-packages):");
|
|
2026
|
+
lines.push(" apps/* (deployable components) → parent composes → sub-application");
|
|
2027
|
+
lines.push(" packages/* (shared libraries) → parent depends_on → library");
|
|
2028
|
+
lines.push(" Deps wired from manifests are already pre-wired; do not re-add unless key changed.");
|
|
2029
|
+
}
|
|
1923
2030
|
if (refreshMode && (currentOutgoingRels.length > 0 || currentPartOfRels.length > 0)) {
|
|
1924
2031
|
lines.push("");
|
|
1925
2032
|
lines.push(`GRAPH_STATE (${currentOutgoingRels.length + currentPartOfRels.length} current relationships in Nexarch — compare against what the scanner detected above):`);
|
|
@@ -1943,7 +2050,18 @@ export async function initProject(args) {
|
|
|
1943
2050
|
ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
|
|
1944
2051
|
status: outputStatus,
|
|
1945
2052
|
mode: refreshMode ? "refresh" : "init",
|
|
1946
|
-
project:
|
|
2053
|
+
project: projectConstruct
|
|
2054
|
+
? {
|
|
2055
|
+
name: projectDirName,
|
|
2056
|
+
externalKey: projectEntityKey,
|
|
2057
|
+
entityType: "project",
|
|
2058
|
+
subtype: isMonorepo ? "project_monorepo" : "project_repository",
|
|
2059
|
+
detectedEcosystems,
|
|
2060
|
+
...(isMonorepo
|
|
2061
|
+
? { applications: subPackages.filter((sp) => sp.entityType === "application").map((sp) => sp.externalKey) }
|
|
2062
|
+
: { applicationExternalKey: projectExternalKey }),
|
|
2063
|
+
}
|
|
2064
|
+
: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride, detectedEcosystems },
|
|
1947
2065
|
entities: entitiesResult.summary ?? {},
|
|
1948
2066
|
relationships: relsResult?.summary ?? { requested: 0, succeeded: 0, failed: 0 },
|
|
1949
2067
|
metrics: {
|
package/package.json
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nexarch",
|
|
3
|
-
"version": "0.12.
|
|
4
|
-
"description": "Your architecture workspace for AI delivery.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"nexarch",
|
|
7
|
-
"mcp",
|
|
8
|
-
"architecture",
|
|
9
|
-
"ai"
|
|
10
|
-
],
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"author": "Nexarch <hello@nexarch.ai>",
|
|
13
|
-
"homepage": "https://nexarch.ai",
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=18"
|
|
16
|
-
},
|
|
17
|
-
"type": "module",
|
|
18
|
-
"bin": {
|
|
19
|
-
"nexarch": "dist/index.js"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist"
|
|
23
|
-
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsc",
|
|
26
|
-
"prepublishOnly": "tsc",
|
|
27
|
-
"dev": "tsx src/index.ts",
|
|
28
|
-
"typecheck": "tsc --noEmit"
|
|
29
|
-
},
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"@types/node": "^22",
|
|
32
|
-
"tsx": "^4",
|
|
33
|
-
"typescript": "^5"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "nexarch",
|
|
3
|
+
"version": "0.12.5",
|
|
4
|
+
"description": "Your architecture workspace for AI delivery.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nexarch",
|
|
7
|
+
"mcp",
|
|
8
|
+
"architecture",
|
|
9
|
+
"ai"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Nexarch <hello@nexarch.ai>",
|
|
13
|
+
"homepage": "https://nexarch.ai",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"bin": {
|
|
19
|
+
"nexarch": "dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublishOnly": "tsc",
|
|
27
|
+
"dev": "tsx src/index.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22",
|
|
32
|
+
"tsx": "^4",
|
|
33
|
+
"typescript": "^5"
|
|
34
|
+
}
|
|
35
|
+
}
|