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.
- package/dist/commands/init-agent.js +31 -18
- package/dist/commands/init-project.js +67 -6
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -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(
|
|
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 =
|
|
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
|
-
:
|
|
1141
|
-
? "
|
|
1142
|
-
:
|
|
1143
|
-
?
|
|
1144
|
-
:
|
|
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 (
|
|
1153
|
-
: !
|
|
1154
|
-
? "skipped (
|
|
1155
|
-
:
|
|
1156
|
-
? "
|
|
1157
|
-
:
|
|
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
|
|
1298
|
-
const entitiesResult =
|
|
1299
|
-
|
|
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
|
|
1303
|
-
relsResult =
|
|
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
|