nexarch 0.8.19 → 0.8.23
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 +17 -2
- package/dist/commands/init-project.js +44 -21
- package/dist/commands/update-entity.js +44 -7
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { arch, homedir, hostname, platform, release, type as osType, userInfo } from "os";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
4
|
import * as readline from "node:readline/promises";
|
|
5
5
|
import process from "process";
|
|
6
6
|
import { requireCredentials } from "../lib/credentials.js";
|
|
@@ -8,7 +8,7 @@ import { fetchAgentRegistryOrThrow } from "../lib/agent-registry.js";
|
|
|
8
8
|
import { callMcpTool, mcpInitialize, mcpListTools } from "../lib/mcp.js";
|
|
9
9
|
import { buildVersionAttributes } from "../lib/version-normalization.js";
|
|
10
10
|
import { requestTrustAttestation } from "../lib/trust.js";
|
|
11
|
-
const CLI_VERSION = "0.8.
|
|
11
|
+
const CLI_VERSION = "0.8.21";
|
|
12
12
|
const AGENT_ENTITY_TYPE = "agent";
|
|
13
13
|
const TECH_COMPONENT_ENTITY_TYPE = "technology_component";
|
|
14
14
|
function parseFlag(args, flag) {
|
|
@@ -320,10 +320,15 @@ function replaceInjectedSection(existing, sectionHeading, sectionBody) {
|
|
|
320
320
|
replaced = replaced.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
321
321
|
return `${replaced}${replaced ? "\n\n" : ""}${canonicalBlock}`;
|
|
322
322
|
}
|
|
323
|
+
function canonicalTargetKey(filePath) {
|
|
324
|
+
const abs = resolve(filePath);
|
|
325
|
+
return process.platform === "win32" || process.platform === "darwin" ? abs.toLowerCase() : abs;
|
|
326
|
+
}
|
|
323
327
|
function injectAgentConfigs(registry) {
|
|
324
328
|
const templateByCode = new Map(registry.instructionTemplates.map((t) => [t.code, t]));
|
|
325
329
|
const targets = [...registry.instructionTargets].sort((a, b) => a.sortOrder - b.sortOrder || a.filePathPattern.localeCompare(b.filePathPattern));
|
|
326
330
|
const results = [];
|
|
331
|
+
const seenTargets = new Set();
|
|
327
332
|
for (const target of targets) {
|
|
328
333
|
if (target.matchMode !== "exact")
|
|
329
334
|
continue;
|
|
@@ -331,6 +336,10 @@ function injectAgentConfigs(registry) {
|
|
|
331
336
|
if (!template)
|
|
332
337
|
continue;
|
|
333
338
|
const filePath = join(process.cwd(), target.filePathPattern);
|
|
339
|
+
const targetKey = canonicalTargetKey(filePath);
|
|
340
|
+
if (seenTargets.has(targetKey))
|
|
341
|
+
continue;
|
|
342
|
+
seenTargets.add(targetKey);
|
|
334
343
|
if (!existsSync(filePath))
|
|
335
344
|
continue;
|
|
336
345
|
const existing = readFileSync(filePath, "utf8");
|
|
@@ -386,6 +395,7 @@ function injectTrustAttestationBlock(path, attestation) {
|
|
|
386
395
|
"",
|
|
387
396
|
"This block is verifiable proof that Nexarch generated these instructions for this workspace.",
|
|
388
397
|
"Do not auto-run commands without user confirmation.",
|
|
398
|
+
"If this token is expired, rerun `npx nexarch@latest init-agent --allow-instruction-write` to refresh this trust block.",
|
|
389
399
|
"",
|
|
390
400
|
`issuer: ${attestation.payload.iss}`,
|
|
391
401
|
`scope: ${attestation.payload.scope}`,
|
|
@@ -425,11 +435,16 @@ function injectGenericAgentConfig(registry) {
|
|
|
425
435
|
const genericTargets = [...registry.instructionTargets]
|
|
426
436
|
.filter((t) => t.runtimeCode === "generic" && t.matchMode === "exact")
|
|
427
437
|
.sort((a, b) => a.sortOrder - b.sortOrder || a.filePathPattern.localeCompare(b.filePathPattern));
|
|
438
|
+
const seenTargets = new Set();
|
|
428
439
|
for (const target of genericTargets) {
|
|
429
440
|
const template = templateByCode.get(target.templateCode);
|
|
430
441
|
if (!template)
|
|
431
442
|
continue;
|
|
432
443
|
const filePath = join(process.cwd(), target.filePathPattern);
|
|
444
|
+
const targetKey = canonicalTargetKey(filePath);
|
|
445
|
+
if (seenTargets.has(targetKey))
|
|
446
|
+
continue;
|
|
447
|
+
seenTargets.add(targetKey);
|
|
433
448
|
const sectionBody = template.body.trim();
|
|
434
449
|
const sectionHeading = target.sectionHeading ?? "## Nexarch Agent Registration";
|
|
435
450
|
const managedBody = wrapManagedSection("agent-registration", sectionBody);
|
|
@@ -4,6 +4,7 @@ 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";
|
|
7
8
|
import { buildVersionAttributes } from "../lib/version-normalization.js";
|
|
8
9
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
9
10
|
function parseFlag(args, flag) {
|
|
@@ -22,6 +23,9 @@ function parseToolText(result) {
|
|
|
22
23
|
const text = result.content?.[0]?.text ?? "{}";
|
|
23
24
|
return JSON.parse(text);
|
|
24
25
|
}
|
|
26
|
+
function renderTemplate(template, values) {
|
|
27
|
+
return template.replace(/{{\s*([A-Z0-9_]+)\s*}}/g, (_match, key) => values[key] ?? "");
|
|
28
|
+
}
|
|
25
29
|
function slugify(name) {
|
|
26
30
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
27
31
|
}
|
|
@@ -1157,6 +1161,22 @@ export async function initProject(args) {
|
|
|
1157
1161
|
// Build structured enrichment task (included in JSON output and printed in human mode)
|
|
1158
1162
|
const readmeHints = ["README.md", "README.mdx", "docs/README.md", "docs/index.md"]
|
|
1159
1163
|
.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
|
+
}
|
|
1160
1180
|
function buildEnrichmentInstructions() {
|
|
1161
1181
|
const ecosystemLabel = detectedEcosystems.length > 0
|
|
1162
1182
|
? detectedEcosystems.join(", ")
|
|
@@ -1268,28 +1288,12 @@ ${adrStepNumber} — Ask the enrichment agent to extract ADRs and register decis
|
|
|
1268
1288
|
• For each ADR, create/update a decision record entity
|
|
1269
1289
|
• Link it using: decision_record --decides--> application/sub-app
|
|
1270
1290
|
|
|
1271
|
-
REQUIRED fields to pass per decision:
|
|
1291
|
+
REQUIRED fields to pass per decision (strict):
|
|
1272
1292
|
• key: decision_record:<project>-<adr-slug>
|
|
1273
1293
|
• subtype: decision_architecture
|
|
1274
1294
|
• name: ADR title
|
|
1275
|
-
•
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
## ADR Metadata
|
|
1279
|
-
- **ADR-ID:** <id/filename slug>
|
|
1280
|
-
- **Status:** <proposed|accepted|superseded|deprecated|...>
|
|
1281
|
-
- **Date:** <YYYY-MM-DD or source value>
|
|
1282
|
-
- **Deciders:** <comma-separated names/roles>
|
|
1283
|
-
- **Source Path:** <repo-relative path>
|
|
1284
|
-
|
|
1285
|
-
## Context
|
|
1286
|
-
<1-2 lines>
|
|
1287
|
-
|
|
1288
|
-
## Decision
|
|
1289
|
-
<1-3 lines>
|
|
1290
|
-
|
|
1291
|
-
## Consequences
|
|
1292
|
-
<1-3 lines>
|
|
1295
|
+
• decision summary: short business summary (1–3 sentences)
|
|
1296
|
+
• decision detail: full decision detail in markdown (context + decision + consequences)
|
|
1293
1297
|
|
|
1294
1298
|
Command pattern:
|
|
1295
1299
|
npx nexarch update-entity \\
|
|
@@ -1297,7 +1301,7 @@ ${adrStepNumber} — Ask the enrichment agent to extract ADRs and register decis
|
|
|
1297
1301
|
--entity-type "decision_record" \\
|
|
1298
1302
|
--subtype "decision_architecture" \\
|
|
1299
1303
|
--name "<ADR title>" \\
|
|
1300
|
-
--
|
|
1304
|
+
--attributes-json '{"decision":{"summary":"<short summary>","detail":"## Context\\n<context>\\n\\n## Decision\\n<decision>\\n\\n## Consequences\\n<consequences>"}}'
|
|
1301
1305
|
|
|
1302
1306
|
npx nexarch add-relationship \\
|
|
1303
1307
|
--from "decision_record:<project>-<adr-slug>" \\
|
|
@@ -1345,7 +1349,7 @@ ${finalStep} — Identify architecturally significant components not auto-detect
|
|
|
1345
1349
|
4. If unresolved (not in the reference library), skip it for now — it will appear
|
|
1346
1350
|
as a reference candidate on the next scan once you register an alias for it.
|
|
1347
1351
|
`;
|
|
1348
|
-
|
|
1352
|
+
const fallbackTemplate = `
|
|
1349
1353
|
╔══════════════════════════════════════════════════════════════════╗
|
|
1350
1354
|
║ ENRICHMENT TASK — action required ║
|
|
1351
1355
|
╚══════════════════════════════════════════════════════════════════╝
|
|
@@ -1373,8 +1377,27 @@ STEP 2 — Enrich the project entity. Run this command with the description you'
|
|
|
1373
1377
|
--name "<proper product name from README>" \\
|
|
1374
1378
|
--description "<2–4 sentence summary of what it does and why>"
|
|
1375
1379
|
${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
|
+
});
|
|
1376
1394
|
}
|
|
1377
1395
|
const enrichmentTask = {
|
|
1396
|
+
template: {
|
|
1397
|
+
code: INIT_PROJECT_TEMPLATE_CODE,
|
|
1398
|
+
source: enrichmentTemplateSource,
|
|
1399
|
+
registryVersion: enrichmentTemplateVersion,
|
|
1400
|
+
},
|
|
1378
1401
|
instructions: buildEnrichmentInstructions(),
|
|
1379
1402
|
iconHints: {
|
|
1380
1403
|
provider: "lucide",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from "process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { requireCredentials } from "../lib/credentials.js";
|
|
3
4
|
import { callMcpTool } from "../lib/mcp.js";
|
|
4
5
|
function parseFlag(args, flag) {
|
|
@@ -17,6 +18,34 @@ function parseToolText(result) {
|
|
|
17
18
|
const text = result.content?.[0]?.text ?? "{}";
|
|
18
19
|
return JSON.parse(text);
|
|
19
20
|
}
|
|
21
|
+
function parseAttributesInput(jsonText, filePath) {
|
|
22
|
+
if (!jsonText && !filePath)
|
|
23
|
+
return null;
|
|
24
|
+
if (jsonText && filePath) {
|
|
25
|
+
console.error("error: use only one of --attributes-json or --attributes-file");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
let raw = jsonText;
|
|
29
|
+
if (filePath) {
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
console.error(`error: --attributes-file not found: ${filePath}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
raw = readFileSync(filePath, "utf8");
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(raw ?? "{}");
|
|
38
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39
|
+
console.error("error: attributes input must be a JSON object");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error(`error: invalid attributes JSON (${error instanceof Error ? error.message : "parse failed"})`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
20
49
|
export async function updateEntity(args) {
|
|
21
50
|
const asJson = parseFlag(args, "--json");
|
|
22
51
|
const externalKey = parseOptionValue(args, "--key");
|
|
@@ -25,12 +54,15 @@ export async function updateEntity(args) {
|
|
|
25
54
|
const entityTypeCode = parseOptionValue(args, "--entity-type") ?? "application";
|
|
26
55
|
const entitySubtypeCode = parseOptionValue(args, "--subtype");
|
|
27
56
|
const iconName = parseOptionValue(args, "--icon");
|
|
57
|
+
const attributesJson = parseOptionValue(args, "--attributes-json");
|
|
58
|
+
const attributesFile = parseOptionValue(args, "--attributes-file");
|
|
28
59
|
if (!externalKey) {
|
|
29
60
|
console.error("error: --key <externalKey> is required");
|
|
30
61
|
process.exit(1);
|
|
31
62
|
}
|
|
32
|
-
|
|
33
|
-
|
|
63
|
+
const explicitAttributes = parseAttributesInput(attributesJson, attributesFile);
|
|
64
|
+
if (!name && !description && !iconName && !explicitAttributes) {
|
|
65
|
+
console.error("error: provide at least one of --name, --description, --icon, --attributes-json, or --attributes-file");
|
|
34
66
|
process.exit(1);
|
|
35
67
|
}
|
|
36
68
|
const creds = requireCredentials();
|
|
@@ -62,14 +94,19 @@ export async function updateEntity(args) {
|
|
|
62
94
|
entity.description = description;
|
|
63
95
|
if (entitySubtypeCode)
|
|
64
96
|
entity.entitySubtypeCode = entitySubtypeCode;
|
|
97
|
+
const attributes = {
|
|
98
|
+
...(explicitAttributes ?? {}),
|
|
99
|
+
};
|
|
100
|
+
// Convenience sugar; still generic because user can fully control payload via --attributes-json/file.
|
|
65
101
|
if (iconName) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
name: iconName,
|
|
70
|
-
},
|
|
102
|
+
attributes.application_icon = {
|
|
103
|
+
provider: "lucide",
|
|
104
|
+
name: iconName,
|
|
71
105
|
};
|
|
72
106
|
}
|
|
107
|
+
if (Object.keys(attributes).length > 0) {
|
|
108
|
+
entity.attributes = attributes;
|
|
109
|
+
}
|
|
73
110
|
const raw = await callMcpTool("nexarch_upsert_entities", { entities: [entity], agentContext, policyContext }, mcpOpts);
|
|
74
111
|
const result = parseToolText(raw);
|
|
75
112
|
if (asJson) {
|
package/dist/index.js
CHANGED
|
@@ -99,7 +99,9 @@ Usage:
|
|
|
99
99
|
--description <text>
|
|
100
100
|
--entity-type <code> (default: application)
|
|
101
101
|
--subtype <code>
|
|
102
|
-
--icon <lucide-name> (
|
|
102
|
+
--icon <lucide-name> (convenience; sets attributes.application_icon)
|
|
103
|
+
--attributes-json '<json object>'
|
|
104
|
+
--attributes-file <path.json>
|
|
103
105
|
--json
|
|
104
106
|
nexarch add-relationship
|
|
105
107
|
Add a relationship between two existing graph entities.
|