nexarch 0.9.2 → 0.9.4

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.
@@ -59,12 +59,12 @@ async function promptForValue(promptText, required = false) {
59
59
  rl.close();
60
60
  }
61
61
  }
62
- async function confirmInstructionWrite() {
62
+ async function confirmInstructionWrite(promptText = "Allow nexarch init-agent to write/update AGENTS.md/CLAUDE.md registration instructions? [y/N]: ") {
63
63
  if (!process.stdin.isTTY || !process.stdout.isTTY)
64
64
  return false;
65
65
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
66
66
  try {
67
- const answer = (await rl.question("Allow nexarch init-agent to write/update AGENTS.md/CLAUDE.md registration instructions? [y/N]: ")).trim().toLowerCase();
67
+ const answer = (await rl.question(promptText)).trim().toLowerCase();
68
68
  return answer === "y" || answer === "yes";
69
69
  }
70
70
  finally {
@@ -1084,20 +1084,25 @@ export async function initAgent(args) {
1084
1084
  catch {
1085
1085
  // non-fatal
1086
1086
  }
1087
+ let existingInstructionTargets = injectAgentConfigs(registry);
1088
+ if (existingInstructionTargets.length === 0) {
1089
+ existingInstructionTargets = injectGenericAgentConfig(registry);
1090
+ }
1091
+ const alreadyConfigured = existingInstructionTargets.length > 0 && existingInstructionTargets.every((r) => r.status === "already_present");
1087
1092
  if (denyInstructionWriteFlag) {
1088
1093
  instructionsWriteAllowed = false;
1089
1094
  }
1090
1095
  else if (allowInstructionWriteFlag) {
1091
1096
  instructionsWriteAllowed = true;
1092
1097
  }
1098
+ else if (alreadyConfigured) {
1099
+ instructionsWriteAllowed = false;
1100
+ }
1093
1101
  else if (!asJson) {
1094
1102
  instructionsWriteAllowed = await confirmInstructionWrite();
1095
1103
  }
1096
1104
  if (instructionsWriteAllowed) {
1097
- agentConfigResults = injectAgentConfigs(registry);
1098
- if (agentConfigResults.length === 0) {
1099
- agentConfigResults = injectGenericAgentConfig(registry);
1100
- }
1105
+ agentConfigResults = existingInstructionTargets;
1101
1106
  if (agentConfigResults.length > 0) {
1102
1107
  trustAttestationAttempted = true;
1103
1108
  trustAttestation = await requestTrustAttestation(agentId);
@@ -1116,6 +1121,9 @@ export async function initAgent(args) {
1116
1121
  }
1117
1122
  }
1118
1123
  }
1124
+ else if (alreadyConfigured) {
1125
+ agentConfigResults = existingInstructionTargets;
1126
+ }
1119
1127
  }
1120
1128
  checks.push({
1121
1129
  name: "agent.registration",
@@ -1132,29 +1140,34 @@ export async function initAgent(args) {
1132
1140
  ? `awaiting identity input (${identityCapture.missingRequired.join(", ")})`
1133
1141
  : identityCapture.detail,
1134
1142
  });
1143
+ const instructionsAlreadyConfigured = agentConfigResults.length > 0 && agentConfigResults.every((r) => r.status === "already_present");
1135
1144
  checks.push({
1136
1145
  name: "agent.instructions.injection",
1137
1146
  ok: !registration.ok || !instructionsWriteAllowed || agentConfigResults.length > 0,
1138
1147
  detail: !registration.ok
1139
1148
  ? "skipped (registration failed)"
1140
- : !instructionsWriteAllowed
1141
- ? "skipped (consent not granted)"
1142
- : agentConfigResults.length > 0
1143
- ? `updated ${agentConfigResults.length} instruction target file(s)`
1144
- : "no runtime instruction target matched this repository (non-fatal; create AGENTS.md/CLAUDE.md or configure a generic target)",
1149
+ : instructionsAlreadyConfigured
1150
+ ? "already configured (no write needed)"
1151
+ : !instructionsWriteAllowed
1152
+ ? "skipped (consent not granted)"
1153
+ : agentConfigResults.length > 0
1154
+ ? `updated ${agentConfigResults.length} instruction target file(s)`
1155
+ : "no runtime instruction target matched this repository (non-fatal; create AGENTS.md/CLAUDE.md or configure a generic target)",
1145
1156
  });
1146
1157
  checks.push({
1147
1158
  name: "agent.trust.attestation",
1148
1159
  ok: !registration.ok || !instructionsWriteAllowed || !trustAttestationAttempted || Boolean(trustAttestation?.ok),
1149
1160
  detail: !registration.ok
1150
1161
  ? "skipped (registration failed)"
1151
- : !instructionsWriteAllowed
1152
- ? "skipped (consent not granted)"
1153
- : !trustAttestationAttempted
1154
- ? "skipped (no instruction target written)"
1155
- : trustAttestation?.ok
1156
- ? "minted and injected into instruction file(s)"
1157
- : `unavailable (${trustAttestation?.reason ?? "unknown"})`,
1162
+ : instructionsAlreadyConfigured && !instructionsWriteAllowed
1163
+ ? "skipped (already configured)"
1164
+ : !instructionsWriteAllowed
1165
+ ? "skipped (consent not granted)"
1166
+ : !trustAttestationAttempted
1167
+ ? "skipped (no instruction target written)"
1168
+ : trustAttestation?.ok
1169
+ ? "minted and injected into instruction file(s)"
1170
+ : `unavailable (${trustAttestation?.reason ?? "unknown"})`,
1158
1171
  });
1159
1172
  checks.push({
1160
1173
  name: "technology.components",
@@ -23,6 +23,19 @@ function parseToolText(result) {
23
23
  const text = result.content?.[0]?.text ?? "{}";
24
24
  return JSON.parse(text);
25
25
  }
26
+ function formatMs(ms) {
27
+ if (ms < 1000)
28
+ return `${ms}ms`;
29
+ return `${(ms / 1000).toFixed(2)}s`;
30
+ }
31
+ function chunkArray(items, size) {
32
+ if (size <= 0)
33
+ return [items];
34
+ const chunks = [];
35
+ for (let i = 0; i < items.length; i += size)
36
+ chunks.push(items.slice(i, i + size));
37
+ return chunks;
38
+ }
26
39
  function slugify(name) {
27
40
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
28
41
  }
@@ -951,6 +964,12 @@ async function promptApplicationChoice(matches, allApps, suggested) {
951
964
  export async function initProject(args) {
952
965
  const asJson = parseFlag(args, "--json");
953
966
  const dryRun = parseFlag(args, "--dry-run");
967
+ const commandStartedAt = Date.now();
968
+ const logProgress = (phase, details) => {
969
+ const elapsed = formatMs(Date.now() - commandStartedAt);
970
+ const line = details ? `[init-project +${elapsed}] ${phase} — ${details}` : `[init-project +${elapsed}] ${phase}`;
971
+ process.stderr.write(`${line}\n`);
972
+ };
954
973
  const dirArg = parseOptionValue(args, "--dir") ?? process.cwd();
955
974
  const dir = resolvePath(dirArg);
956
975
  const nameOverride = parseOptionValue(args, "--name");
@@ -962,11 +981,14 @@ export async function initProject(args) {
962
981
  const forceCreateApplication = parseFlag(args, "--create-application");
963
982
  const autoMapApplication = parseFlag(args, "--auto-map-application");
964
983
  const nonInteractive = parseFlag(args, "--non-interactive");
984
+ const upsertBatchSize = Number(parseOptionValue(args, "--batch-size") ?? "10") || 10;
965
985
  const creds = requireCredentials();
966
986
  const mcpOpts = { companyId: creds.companyId };
967
987
  if (!asJson)
968
988
  console.log(`Scanning ${dir}…`);
989
+ logProgress("scan.start", dir);
969
990
  const { projectName, packageJsonCount, detectedNames, rootDepNames, rootDepVersions, subPackages, detectedEcosystems } = scanProject(dir);
991
+ logProgress("scan.done", `packages=${packageJsonCount}, detectedNames=${detectedNames.length}, subPackages=${subPackages.length}`);
970
992
  const detectedRepo = detectSourceRepository(dir);
971
993
  const displayName = nameOverride ?? projectName;
972
994
  const projectSlug = slugify(displayName);
@@ -1004,12 +1026,15 @@ export async function initProject(args) {
1004
1026
  console.log("\nResolving against reference library…");
1005
1027
  const allResolveResults = [];
1006
1028
  const BATCH_SIZE = 200;
1029
+ logProgress("resolve.start", `count=${detectedNames.length}, batchSize=${BATCH_SIZE}`);
1007
1030
  for (let i = 0; i < detectedNames.length; i += BATCH_SIZE) {
1008
1031
  const batch = detectedNames.slice(i, i + BATCH_SIZE);
1032
+ logProgress("resolve.batch", `${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(detectedNames.length / BATCH_SIZE)} size=${batch.length}`);
1009
1033
  const raw = await callMcpTool("nexarch_resolve_reference", { names: batch, companyId: creds.companyId }, mcpOpts);
1010
1034
  const data = parseToolText(raw);
1011
1035
  allResolveResults.push(...data.results);
1012
1036
  }
1037
+ logProgress("resolve.done", `resolved=${allResolveResults.filter((r) => r.resolved).length}`);
1013
1038
  const resolvedItems = allResolveResults.filter((r) => r.resolved);
1014
1039
  const unresolvedItems = allResolveResults.filter((r) => !r.resolved);
1015
1040
  if (!asJson) {
@@ -1044,6 +1069,7 @@ export async function initProject(args) {
1044
1069
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
1045
1070
  return;
1046
1071
  }
1072
+ logProgress("preflight.onboarding.check");
1047
1073
  const onboardingRaw = await callMcpTool("nexarch_get_company_onboarding", {}, mcpOpts);
1048
1074
  const onboarding = parseToolText(onboardingRaw);
1049
1075
  if (onboarding.isComplete !== true) {
@@ -1056,6 +1082,7 @@ export async function initProject(args) {
1056
1082
  throw new Error(message);
1057
1083
  }
1058
1084
  // Policy bootstrap
1085
+ logProgress("preflight.policies.check");
1059
1086
  const policiesRaw = await callMcpTool("nexarch_get_applied_policies", {}, mcpOpts);
1060
1087
  const policies = parseToolText(policiesRaw);
1061
1088
  const policyBundleHash = policies.policyBundleHash ?? null;
@@ -1125,6 +1152,7 @@ export async function initProject(args) {
1125
1152
  }
1126
1153
  }
1127
1154
  }
1155
+ logProgress("application.target", projectExternalKey);
1128
1156
  const agentContext = {
1129
1157
  agentId: "nexarch-cli:init-project",
1130
1158
  agentRunId: `init-project-${Date.now()}`,
@@ -1293,14 +1321,45 @@ export async function initProject(args) {
1293
1321
  }
1294
1322
  if (!asJson)
1295
1323
  console.log(`\nWriting to graph…`);
1296
- // Upsert entities
1297
- const entitiesRaw = await callMcpTool("nexarch_upsert_entities", { entities, agentContext, policyContext }, mcpOpts);
1298
- const entitiesResult = parseToolText(entitiesRaw);
1299
- // Upsert relationships
1324
+ // Upsert entities (chunked for progressive feedback)
1325
+ const entityChunks = chunkArray(entities, Math.max(1, upsertBatchSize));
1326
+ const entitiesResult = {
1327
+ summary: { requested: 0, succeeded: 0, failed: 0 },
1328
+ errors: [],
1329
+ };
1330
+ logProgress("upsert.entities.start", `count=${entities.length}, batches=${entityChunks.length}, batchSize=${Math.max(1, upsertBatchSize)}`);
1331
+ for (let i = 0; i < entityChunks.length; i += 1) {
1332
+ const chunk = entityChunks[i];
1333
+ 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);
1335
+ const chunkResult = parseToolText(entitiesRaw);
1336
+ entitiesResult.summary.requested = Number(entitiesResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
1337
+ entitiesResult.summary.succeeded = Number(entitiesResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
1338
+ entitiesResult.summary.failed = Number(entitiesResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
1339
+ if (chunkResult.errors?.length)
1340
+ entitiesResult.errors.push(...chunkResult.errors);
1341
+ logProgress("upsert.entities.batch.done", `${i + 1}/${entityChunks.length} succeeded=${chunkResult.summary?.succeeded ?? 0}, failed=${chunkResult.summary?.failed ?? 0}`);
1342
+ }
1343
+ logProgress("upsert.entities.done", `succeeded=${entitiesResult.summary?.succeeded ?? 0}, failed=${entitiesResult.summary?.failed ?? 0}`);
1344
+ // Upsert relationships (chunked)
1300
1345
  let relsResult = null;
1301
1346
  if (relationships.length > 0) {
1302
- const relsRaw = await callMcpTool("nexarch_upsert_relationships", { relationships, agentContext, policyContext }, mcpOpts);
1303
- relsResult = parseToolText(relsRaw);
1347
+ const relationshipChunks = chunkArray(relationships, Math.max(1, upsertBatchSize));
1348
+ relsResult = { summary: { requested: 0, succeeded: 0, failed: 0 }, errors: [] };
1349
+ logProgress("upsert.relationships.start", `count=${relationships.length}, batches=${relationshipChunks.length}, batchSize=${Math.max(1, upsertBatchSize)}`);
1350
+ for (let i = 0; i < relationshipChunks.length; i += 1) {
1351
+ const chunk = relationshipChunks[i];
1352
+ 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);
1354
+ const chunkResult = parseToolText(relsRaw);
1355
+ relsResult.summary.requested = Number(relsResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
1356
+ relsResult.summary.succeeded = Number(relsResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
1357
+ relsResult.summary.failed = Number(relsResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
1358
+ if (chunkResult.errors?.length)
1359
+ relsResult.errors.push(...chunkResult.errors);
1360
+ logProgress("upsert.relationships.batch.done", `${i + 1}/${relationshipChunks.length} succeeded=${chunkResult.summary?.succeeded ?? 0}, failed=${chunkResult.summary?.failed ?? 0}`);
1361
+ }
1362
+ logProgress("upsert.relationships.done", `succeeded=${relsResult.summary?.succeeded ?? 0}, failed=${relsResult.summary?.failed ?? 0}`);
1304
1363
  }
1305
1364
  // Build structured enrichment task (included in JSON output and printed in human mode)
1306
1365
  const readmeHints = ["README.md", "README.mdx", "docs/README.md", "docs/index.md"]
@@ -1552,6 +1611,7 @@ ${subPkgSection}${adrSection}${gapCheckSection}`;
1552
1611
  enrichmentTask,
1553
1612
  };
1554
1613
  if (asJson) {
1614
+ logProgress("complete", `ok=${output.ok}`);
1555
1615
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
1556
1616
  if (!output.ok)
1557
1617
  process.exitCode = 1;
@@ -1571,4 +1631,5 @@ ${subPkgSection}${adrSection}${gapCheckSection}`;
1571
1631
  }
1572
1632
  // ─── Enrichment task ────────────────────────────────────────────────────────
1573
1633
  console.log(enrichmentTask.instructions);
1634
+ logProgress("complete", `ok=${output.ok}`);
1574
1635
  }
package/dist/index.js CHANGED
@@ -94,6 +94,7 @@ Usage:
94
94
  --create-application force new application entity
95
95
  --auto-map-application auto-map only when high confidence
96
96
  --non-interactive fail on ambiguous mapping
97
+ --batch-size <n> upsert batch size (default: 10)
97
98
  --dry-run preview without writing
98
99
  --json
99
100
  nexarch update-entity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",