nexarch 0.8.24 → 0.8.27

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.
@@ -4,7 +4,6 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
4
  import { basename, join, relative, resolve as resolvePath } from "node:path";
5
5
  import { requireCredentials } from "../lib/credentials.js";
6
6
  import { callMcpTool } from "../lib/mcp.js";
7
- import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
8
7
  import { buildVersionAttributes } from "../lib/version-normalization.js";
9
8
  // ─── Helpers ─────────────────────────────────────────────────────────────────
10
9
  function parseFlag(args, flag) {
@@ -23,9 +22,6 @@ function parseToolText(result) {
23
22
  const text = result.content?.[0]?.text ?? "{}";
24
23
  return JSON.parse(text);
25
24
  }
26
- function renderTemplate(template, values) {
27
- return template.replace(/{{\s*([A-Z0-9_]+)\s*}}/g, (_match, key) => values[key] ?? "");
28
- }
29
25
  function slugify(name) {
30
26
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
31
27
  }
@@ -1161,22 +1157,6 @@ export async function initProject(args) {
1161
1157
  // Build structured enrichment task (included in JSON output and printed in human mode)
1162
1158
  const readmeHints = ["README.md", "README.mdx", "docs/README.md", "docs/index.md"]
1163
1159
  .filter((f) => existsSync(join(dir, f)));
1164
- const INIT_PROJECT_TEMPLATE_CODE = "nexarch_init_project_enrichment_v1";
1165
- let enrichmentTemplateBody = null;
1166
- let enrichmentTemplateSource = "fallback";
1167
- let enrichmentTemplateVersion = null;
1168
- try {
1169
- const registry = await fetchAgentRegistryOrThrow();
1170
- const template = registry.instructionTemplates.find((t) => t.code === INIT_PROJECT_TEMPLATE_CODE);
1171
- if (template?.body?.trim()) {
1172
- enrichmentTemplateBody = template.body;
1173
- enrichmentTemplateSource = "registry";
1174
- enrichmentTemplateVersion = registry.release.version;
1175
- }
1176
- }
1177
- catch {
1178
- // non-fatal: init-project can still run with fallback guidance.
1179
- }
1180
1160
  function buildEnrichmentInstructions() {
1181
1161
  const ecosystemLabel = detectedEcosystems.length > 0
1182
1162
  ? detectedEcosystems.join(", ")
@@ -1377,26 +1357,13 @@ STEP 2 — Enrich the project entity. Run this command with the description you'
1377
1357
  --name "<proper product name from README>" \\
1378
1358
  --description "<2–4 sentence summary of what it does and why>"
1379
1359
  ${subPkgSection}${adrSection}${gapCheckSection}`;
1380
- if (!enrichmentTemplateBody)
1381
- return fallbackTemplate;
1382
- return renderTemplate(enrichmentTemplateBody, {
1383
- PROJECT_ENTITY_KEY: projectExternalKey,
1384
- PROJECT_DIR: dir,
1385
- ECOSYSTEM_LABEL: ecosystemLabel,
1386
- MANIFEST_HINT: manifestHint,
1387
- README_HINTS: readmeHints.length > 0 ? readmeHints.join(", ") : "(none found — check docs/)",
1388
- ENTITY_TYPE: entityTypeOverride,
1389
- ENTITY_BASE_COMMAND: `npx nexarch update-entity \\\n --key "${projectExternalKey}" \\\n --entity-type "${entityTypeOverride}"${entityTypeOverride === "application" ? " \\\n --subtype \"app_custom_built\" \\\n --icon \"<curated icon>\"" : ""} \\\n --name "<proper product name from README>" \\\n --description "<2–4 sentence summary of what it does and why>"`,
1390
- STEP_SUBPACKAGES: subPkgSection,
1391
- STEP_ADR: adrSection,
1392
- STEP_GAP_CHECK: gapCheckSection,
1393
- });
1360
+ return fallbackTemplate;
1394
1361
  }
1395
1362
  const enrichmentTask = {
1396
1363
  template: {
1397
- code: INIT_PROJECT_TEMPLATE_CODE,
1398
- source: enrichmentTemplateSource,
1399
- registryVersion: enrichmentTemplateVersion,
1364
+ code: "builtin:init-project-enrichment",
1365
+ source: "builtin",
1366
+ registryVersion: null,
1400
1367
  },
1401
1368
  instructions: buildEnrichmentInstructions(),
1402
1369
  iconHints: {
package/dist/lib/mcp.js CHANGED
@@ -1,8 +1,18 @@
1
1
  import https from "https";
2
2
  import { requireCredentials } from "./credentials.js";
3
3
  const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
4
- const REQUEST_TIMEOUT_MS = 90 * 1000;
5
- async function callMcpRpc(method, params = {}, options = {}) {
4
+ const REQUEST_TIMEOUT_MS = Number.parseInt(process.env.NEXARCH_MCP_TIMEOUT_MS ?? "90000", 10) || 90_000;
5
+ const REQUEST_RETRIES = Math.max(0, Number.parseInt(process.env.NEXARCH_MCP_RETRIES ?? "2", 10) || 2);
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ function isRetryableNetworkError(error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ const code = error && typeof error === "object" && "code" in error ? String(error.code ?? "") : "";
12
+ return /timed out|etimedout|econnreset|eai_again|socket hang up|request timeout/i.test(message)
13
+ || ["ETIMEDOUT", "ECONNRESET", "EAI_AGAIN"].includes(code);
14
+ }
15
+ async function requestOnce(method, params, options) {
6
16
  const creds = requireCredentials();
7
17
  const companyId = options.companyId ?? creds.companyId;
8
18
  const body = JSON.stringify({
@@ -54,13 +64,32 @@ async function callMcpRpc(method, params = {}, options = {}) {
54
64
  });
55
65
  });
56
66
  req.on("timeout", () => {
57
- req.destroy(new Error("Request timed out. Check your connection and try again."));
67
+ req.destroy(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS}ms. Check your connection and try again.`));
58
68
  });
59
69
  req.on("error", reject);
60
70
  req.write(body);
61
71
  req.end();
62
72
  });
63
73
  }
74
+ async function callMcpRpc(method, params = {}, options = {}) {
75
+ let lastError = null;
76
+ for (let attempt = 0; attempt <= REQUEST_RETRIES; attempt += 1) {
77
+ try {
78
+ return await requestOnce(method, params, options);
79
+ }
80
+ catch (error) {
81
+ lastError = error;
82
+ const isRetryable = isRetryableNetworkError(error);
83
+ const isLastAttempt = attempt >= REQUEST_RETRIES;
84
+ if (!isRetryable || isLastAttempt)
85
+ break;
86
+ // 300ms, 900ms, 2100ms...
87
+ const backoffMs = 300 * (2 ** attempt) + (attempt * 300);
88
+ await sleep(backoffMs);
89
+ }
90
+ }
91
+ throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "MCP call failed"));
92
+ }
64
93
  export async function callMcpTool(toolName, toolArgs = {}, options = {}) {
65
94
  return callMcpRpc("tools/call", { name: toolName, arguments: toolArgs }, options);
66
95
  }
@@ -101,7 +130,7 @@ export async function forwardToGateway(token, companyId, body) {
101
130
  });
102
131
  });
103
132
  req.on("timeout", () => {
104
- req.destroy(new Error("Request timed out."));
133
+ req.destroy(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS}ms.`));
105
134
  });
106
135
  req.on("error", reject);
107
136
  req.write(body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.8.24",
3
+ "version": "0.8.27",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",