nexarch 0.9.4 → 0.9.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.
@@ -964,12 +964,34 @@ async function promptApplicationChoice(matches, allApps, suggested) {
964
964
  export async function initProject(args) {
965
965
  const asJson = parseFlag(args, "--json");
966
966
  const dryRun = parseFlag(args, "--dry-run");
967
+ const profileEnabled = parseFlag(args, "--profile");
967
968
  const commandStartedAt = Date.now();
969
+ const profile = {
970
+ enabled: profileEnabled,
971
+ startedAt: new Date(commandStartedAt).toISOString(),
972
+ events: [],
973
+ mcpCalls: [],
974
+ upsertBatches: {
975
+ entities: [],
976
+ relationships: [],
977
+ },
978
+ };
968
979
  const logProgress = (phase, details) => {
969
- const elapsed = formatMs(Date.now() - commandStartedAt);
980
+ const atMs = Date.now() - commandStartedAt;
981
+ if (profileEnabled)
982
+ profile.events.push({ phase, atMs, details });
983
+ const elapsed = formatMs(atMs);
970
984
  const line = details ? `[init-project +${elapsed}] ${phase} — ${details}` : `[init-project +${elapsed}] ${phase}`;
971
985
  process.stderr.write(`${line}\n`);
972
986
  };
987
+ const callMcpProfiled = async (toolName, input, meta) => {
988
+ const startedAt = Date.now();
989
+ const result = await callMcpTool(toolName, input, mcpOpts);
990
+ if (profileEnabled) {
991
+ profile.mcpCalls.push({ tool: toolName, durationMs: Date.now() - startedAt, meta });
992
+ }
993
+ return result;
994
+ };
973
995
  const dirArg = parseOptionValue(args, "--dir") ?? process.cwd();
974
996
  const dir = resolvePath(dirArg);
975
997
  const nameOverride = parseOptionValue(args, "--name");
@@ -1030,7 +1052,7 @@ export async function initProject(args) {
1030
1052
  for (let i = 0; i < detectedNames.length; i += BATCH_SIZE) {
1031
1053
  const batch = detectedNames.slice(i, i + BATCH_SIZE);
1032
1054
  logProgress("resolve.batch", `${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(detectedNames.length / BATCH_SIZE)} size=${batch.length}`);
1033
- const raw = await callMcpTool("nexarch_resolve_reference", { names: batch, companyId: creds.companyId }, mcpOpts);
1055
+ const raw = await callMcpProfiled("nexarch_resolve_reference", { names: batch, companyId: creds.companyId }, { batchSize: batch.length });
1034
1056
  const data = parseToolText(raw);
1035
1057
  allResolveResults.push(...data.results);
1036
1058
  }
@@ -1070,7 +1092,7 @@ export async function initProject(args) {
1070
1092
  return;
1071
1093
  }
1072
1094
  logProgress("preflight.onboarding.check");
1073
- const onboardingRaw = await callMcpTool("nexarch_get_company_onboarding", {}, mcpOpts);
1095
+ const onboardingRaw = await callMcpProfiled("nexarch_get_company_onboarding", {});
1074
1096
  const onboarding = parseToolText(onboardingRaw);
1075
1097
  if (onboarding.isComplete !== true) {
1076
1098
  const message = "Company onboarding is incomplete. Complete onboarding before running init-project.";
@@ -1083,7 +1105,7 @@ export async function initProject(args) {
1083
1105
  }
1084
1106
  // Policy bootstrap
1085
1107
  logProgress("preflight.policies.check");
1086
- const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {}, mcpOpts);
1108
+ const policiesRaw = await callMcpProfiled("nexarch_get_applied_policies", {});
1087
1109
  const policies = parseToolText(policiesRaw);
1088
1110
  const policyBundleHash = policies.policyBundleHash ?? null;
1089
1111
  if (!policyBundleHash) {
@@ -1108,7 +1130,7 @@ export async function initProject(args) {
1108
1130
  console.log(`\nUsing --application-ref target: ${projectExternalKey}`);
1109
1131
  }
1110
1132
  else {
1111
- const appsRaw = await callMcpTool("nexarch_list_entities", { entityTypeCode: "application", status: "active", limit: 500, companyId: creds.companyId }, mcpOpts);
1133
+ const appsRaw = await callMcpProfiled("nexarch_list_entities", { entityTypeCode: "application", status: "active", limit: 500, companyId: creds.companyId }, { entityTypeCode: "application", limit: 500 });
1112
1134
  const appsData = parseToolText(appsRaw);
1113
1135
  const apps = (appsData.entities ?? []).filter((e) => (e.entityRef ?? e.externalKey));
1114
1136
  if (apps.length > 0) {
@@ -1125,18 +1147,40 @@ export async function initProject(args) {
1125
1147
  console.log(`\nAuto-mapped to existing application: ${suggested.name} (${projectExternalKey})`);
1126
1148
  }
1127
1149
  else if (!interactiveAllowed) {
1128
- if (highConfidence) {
1129
- projectExternalKey = suggested.entityRef;
1150
+ const message = "Application mapping requires explicit choice in non-interactive mode. Pass --application-ref <entityRef> or --create-application (or run interactively).";
1151
+ const existingApplications = apps.slice(0, 25).map((a) => ({
1152
+ entityRef: a.entityRef ?? a.externalKey,
1153
+ name: a.name,
1154
+ entityTypeCode: a.entityTypeCode,
1155
+ }));
1156
+ if (asJson) {
1157
+ process.stdout.write(`${JSON.stringify({
1158
+ ok: true,
1159
+ status: "input_required",
1160
+ code: "APPLICATION_MAPPING_REQUIRED",
1161
+ message,
1162
+ suggested: suggested ?? null,
1163
+ candidates: matches.slice(0, 10),
1164
+ existingApplications,
1165
+ requiredInput: {
1166
+ applicationRefOption: "--application-ref <entityRef>",
1167
+ createOption: "--create-application",
1168
+ },
1169
+ }, null, 2)}\n`);
1170
+ return;
1171
+ }
1172
+ console.log("\nInput required before continuing:");
1173
+ console.log(` ${message}`);
1174
+ if (suggested) {
1175
+ console.log(` Suggested: ${suggested.name} (${suggested.entityRef}) score=${suggested.score.toFixed(2)}`);
1130
1176
  }
1131
- else if (autoMapApplication) {
1132
- const message = "Ambiguous application mapping in non-interactive mode. Pass --application-ref or --create-application.";
1133
- if (asJson) {
1134
- process.stdout.write(`${JSON.stringify({ ok: false, error: "APPLICATION_MAPPING_AMBIGUOUS", message, candidates: matches.slice(0, 5) }, null, 2)}\n`);
1135
- process.exitCode = 1;
1136
- return;
1177
+ if (existingApplications.length > 0) {
1178
+ console.log(" Existing applications:");
1179
+ for (const app of existingApplications) {
1180
+ console.log(` - ${app.name} (${app.entityRef})`);
1137
1181
  }
1138
- throw new Error(message);
1139
1182
  }
1183
+ return;
1140
1184
  }
1141
1185
  else {
1142
1186
  const chosen = await promptApplicationChoice(matches, apps, highConfidence ? suggested : null);
@@ -1299,7 +1343,7 @@ export async function initProject(args) {
1299
1343
  // Company org is accountable_for the top-level project entity
1300
1344
  let orgExternalKey = `organisation:${normalizeToken(creds.companyId)}`;
1301
1345
  try {
1302
- const orgRaw = await callMcpTool("nexarch_list_entities", { entityTypeCode: "organisation", status: "active", limit: 1, companyId: creds.companyId }, mcpOpts);
1346
+ const orgRaw = await callMcpProfiled("nexarch_list_entities", { entityTypeCode: "organisation", status: "active", limit: 1, companyId: creds.companyId }, { entityTypeCode: "organisation", limit: 1 });
1303
1347
  const orgData = parseToolText(orgRaw);
1304
1348
  const org = (orgData.entities ?? [])[0];
1305
1349
  if (org?.entityRef || org?.externalKey)
@@ -1331,8 +1375,18 @@ export async function initProject(args) {
1331
1375
  for (let i = 0; i < entityChunks.length; i += 1) {
1332
1376
  const chunk = entityChunks[i];
1333
1377
  logProgress("upsert.entities.batch.start", `${i + 1}/${entityChunks.length} size=${chunk.length}`);
1334
- const entitiesRaw = await callMcpTool("nexarch_upsert_entities", { entities: chunk, agentContext, policyContext }, mcpOpts);
1378
+ const entityBatchStartedAt = Date.now();
1379
+ const entitiesRaw = await callMcpProfiled("nexarch_upsert_entities", { entities: chunk, agentContext, policyContext }, { batchIndex: i + 1, totalBatches: entityChunks.length, batchSize: chunk.length });
1335
1380
  const chunkResult = parseToolText(entitiesRaw);
1381
+ if (profileEnabled) {
1382
+ profile.upsertBatches.entities.push({
1383
+ index: i + 1,
1384
+ size: chunk.length,
1385
+ durationMs: Date.now() - entityBatchStartedAt,
1386
+ succeeded: Number(chunkResult.summary?.succeeded ?? 0),
1387
+ failed: Number(chunkResult.summary?.failed ?? 0),
1388
+ });
1389
+ }
1336
1390
  entitiesResult.summary.requested = Number(entitiesResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
1337
1391
  entitiesResult.summary.succeeded = Number(entitiesResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
1338
1392
  entitiesResult.summary.failed = Number(entitiesResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
@@ -1350,8 +1404,18 @@ export async function initProject(args) {
1350
1404
  for (let i = 0; i < relationshipChunks.length; i += 1) {
1351
1405
  const chunk = relationshipChunks[i];
1352
1406
  logProgress("upsert.relationships.batch.start", `${i + 1}/${relationshipChunks.length} size=${chunk.length}`);
1353
- const relsRaw = await callMcpTool("nexarch_upsert_relationships", { relationships: chunk, agentContext, policyContext }, mcpOpts);
1407
+ const relBatchStartedAt = Date.now();
1408
+ const relsRaw = await callMcpProfiled("nexarch_upsert_relationships", { relationships: chunk, agentContext, policyContext }, { batchIndex: i + 1, totalBatches: relationshipChunks.length, batchSize: chunk.length });
1354
1409
  const chunkResult = parseToolText(relsRaw);
1410
+ if (profileEnabled) {
1411
+ profile.upsertBatches.relationships.push({
1412
+ index: i + 1,
1413
+ size: chunk.length,
1414
+ durationMs: Date.now() - relBatchStartedAt,
1415
+ succeeded: Number(chunkResult.summary?.succeeded ?? 0),
1416
+ failed: Number(chunkResult.summary?.failed ?? 0),
1417
+ });
1418
+ }
1355
1419
  relsResult.summary.requested = Number(relsResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
1356
1420
  relsResult.summary.succeeded = Number(relsResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
1357
1421
  relsResult.summary.failed = Number(relsResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
@@ -1609,6 +1673,15 @@ ${subPkgSection}${adrSection}${gapCheckSection}`;
1609
1673
  entityErrors: entitiesResult.errors ?? [],
1610
1674
  relationshipErrors: relsResult?.errors ?? [],
1611
1675
  enrichmentTask,
1676
+ profile: profileEnabled
1677
+ ? {
1678
+ startedAt: profile.startedAt,
1679
+ totalDurationMs: Date.now() - commandStartedAt,
1680
+ events: profile.events,
1681
+ mcpCalls: profile.mcpCalls,
1682
+ upsertBatches: profile.upsertBatches,
1683
+ }
1684
+ : undefined,
1612
1685
  };
1613
1686
  if (asJson) {
1614
1687
  logProgress("complete", `ok=${output.ok}`);
package/dist/index.js CHANGED
@@ -95,6 +95,7 @@ Usage:
95
95
  --auto-map-application auto-map only when high confidence
96
96
  --non-interactive fail on ambiguous mapping
97
97
  --batch-size <n> upsert batch size (default: 10)
98
+ --profile include timing/profile data in JSON output
98
99
  --dry-run preview without writing
99
100
  --json
100
101
  nexarch update-entity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",