nexarch 0.9.34 → 0.11.0

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.
@@ -0,0 +1,57 @@
1
+ import process from "process";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { callMcpTool } from "../lib/mcp.js";
4
+ function parseFlag(args, flag) {
5
+ return args.includes(flag);
6
+ }
7
+ function parseOptionValue(args, option) {
8
+ const idx = args.indexOf(option);
9
+ if (idx === -1)
10
+ return null;
11
+ const v = args[idx + 1];
12
+ if (!v || v.startsWith("--"))
13
+ return null;
14
+ return v;
15
+ }
16
+ function parseToolText(result) {
17
+ const text = result.content?.[0]?.text ?? "{}";
18
+ return JSON.parse(text);
19
+ }
20
+ export async function appliedPolicies(args) {
21
+ const asJson = parseFlag(args, "--json");
22
+ const showMarkdown = parseFlag(args, "--markdown");
23
+ if (parseFlag(args, "--help") || parseFlag(args, "-h")) {
24
+ console.log(`
25
+ Usage:
26
+ nexarch applied-policies [--pack <packCode>] [--markdown] [--json]
27
+
28
+ Options:
29
+ --pack <code> Filter to a specific policy pack code
30
+ --markdown Include full document markdown in human output
31
+ --json Print JSON response (always includes markdown)
32
+ `);
33
+ return;
34
+ }
35
+ const packCode = parseOptionValue(args, "--pack") ?? parseOptionValue(args, "--pack-code");
36
+ const creds = requireCredentials();
37
+ const raw = await callMcpTool("nexarch_get_applied_policies", { ...(packCode ? { packCode } : {}) }, { companyId: creds.companyId });
38
+ const result = parseToolText(raw);
39
+ if (asJson) {
40
+ process.stdout.write(JSON.stringify(result) + "\n");
41
+ return;
42
+ }
43
+ console.log(`Applied policies${packCode ? ` (pack: ${packCode})` : ""}`);
44
+ if (result.policyBundleHash)
45
+ console.log(`Bundle hash: ${result.policyBundleHash}`);
46
+ console.log(`Documents: ${result.policyCount ?? 0}`);
47
+ for (const pack of result.policies ?? []) {
48
+ console.log(`\n[${pack.packCode}] ${pack.packName} — v${pack.installedVersion}`);
49
+ for (const doc of pack.documents ?? []) {
50
+ console.log(` - ${doc.title} (${doc.code})`);
51
+ if (showMarkdown && doc.contentMarkdown) {
52
+ const indented = doc.contentMarkdown.split("\n").map((l) => ` ${l}`).join("\n");
53
+ console.log(indented);
54
+ }
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,56 @@
1
+ import process from "process";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { callMcpTool } from "../lib/mcp.js";
4
+ function parseFlag(args, flag) {
5
+ return args.includes(flag);
6
+ }
7
+ function parseToolText(result) {
8
+ const text = result.content?.[0]?.text ?? "{}";
9
+ return JSON.parse(text);
10
+ }
11
+ export async function governanceSummary(args) {
12
+ const asJson = parseFlag(args, "--json");
13
+ if (parseFlag(args, "--help") || parseFlag(args, "-h")) {
14
+ console.log(`
15
+ Usage:
16
+ nexarch governance-summary [--json]
17
+
18
+ Returns review queue counts, graph stats, and a per-application policy
19
+ audit rollup (latest run status, pass/partial/fail counts).
20
+ `);
21
+ return;
22
+ }
23
+ const creds = requireCredentials();
24
+ const raw = await callMcpTool("nexarch_get_governance_summary", { companyId: creds.companyId }, { companyId: creds.companyId });
25
+ const result = parseToolText(raw);
26
+ if (asJson) {
27
+ process.stdout.write(JSON.stringify(result) + "\n");
28
+ return;
29
+ }
30
+ console.log("Governance summary");
31
+ const review = result.reviewQueue ?? {};
32
+ console.log(` Review queue: ${review.pending_entities ?? 0} entities, ${review.pending_relationships ?? 0} relationships`);
33
+ console.log(` Graph: ${result.graphEntityCount ?? 0} entities, ${result.graphRelationshipCount ?? 0} relationships (${result.architectureFactCount ?? 0} facts)`);
34
+ console.log(` Canonical coverage: ${result.canonicalCoverageOverallPct ?? 0}% overall ` +
35
+ `(entities ${result.canonicalCoverageEntityPct ?? 0}%, relationships ${result.canonicalCoverageRelationshipPct ?? 0}%)`);
36
+ console.log(` Canonical registry size: ${result.canonicalRegistryCount ?? 0}`);
37
+ const audit = result.policyAudit;
38
+ if (audit) {
39
+ console.log("\nPolicy audit rollup");
40
+ console.log(` Applications: ${audit.totalApplications} (audited ${audit.appsAudited}, missing audit ${audit.appsWithoutAudit})`);
41
+ console.log(` With failures: ${audit.appsWithFailures}, with partials only: ${audit.appsWithPartials}, clean: ${audit.appsClean}`);
42
+ if (audit.applications.length > 0) {
43
+ console.log("\n Per application:");
44
+ for (const app of audit.applications) {
45
+ if (!app.latestRunId) {
46
+ console.log(` - ${app.applicationName} (${app.applicationEntityRef}): no audit run`);
47
+ continue;
48
+ }
49
+ console.log(` - ${app.applicationName} (${app.applicationEntityRef}): ${app.latestRunStatus} — ` +
50
+ `${app.passCount ?? 0} pass, ${app.partialCount ?? 0} partial, ${app.failCount ?? 0} fail` +
51
+ (app.totalRules ? ` of ${app.totalRules}` : "") +
52
+ (app.completedAt ? ` (completed ${app.completedAt})` : ""));
53
+ }
54
+ }
55
+ }
56
+ }
@@ -1490,7 +1490,8 @@ export async function initProject(args) {
1490
1490
  // Upsert entities (chunked for progressive feedback)
1491
1491
  const entityChunks = chunkArray(entities, Math.max(1, upsertBatchSize));
1492
1492
  const entitiesResult = {
1493
- summary: { requested: 0, succeeded: 0, failed: 0 },
1493
+ summary: { requested: 0, succeeded: 0, failed: 0, preserved: 0 },
1494
+ preserved: [],
1494
1495
  errors: [],
1495
1496
  };
1496
1497
  logProgress("upsert.entities.start", `count=${entities.length}, batches=${entityChunks.length}, batchSize=${Math.max(1, upsertBatchSize)}`);
@@ -1511,7 +1512,10 @@ export async function initProject(args) {
1511
1512
  }
1512
1513
  entitiesResult.summary.requested = Number(entitiesResult.summary.requested ?? 0) + Number(chunkResult.summary?.requested ?? chunk.length);
1513
1514
  entitiesResult.summary.succeeded = Number(entitiesResult.summary.succeeded ?? 0) + Number(chunkResult.summary?.succeeded ?? 0);
1515
+ entitiesResult.summary.preserved = Number(entitiesResult.summary.preserved ?? 0) + Number(chunkResult.summary?.preserved ?? 0);
1514
1516
  entitiesResult.summary.failed = Number(entitiesResult.summary.failed ?? 0) + Number(chunkResult.summary?.failed ?? 0);
1517
+ if (chunkResult.preserved?.length)
1518
+ entitiesResult.preserved.push(...chunkResult.preserved);
1515
1519
  if (chunkResult.errors?.length)
1516
1520
  entitiesResult.errors.push(...chunkResult.errors);
1517
1521
  logProgress("upsert.entities.batch.done", `${i + 1}/${entityChunks.length} succeeded=${chunkResult.summary?.succeeded ?? 0}, failed=${chunkResult.summary?.failed ?? 0}`);
@@ -1620,11 +1624,26 @@ export async function initProject(args) {
1620
1624
  addRelationship: `nexarch add-relationship --from "decision_record:${projectSlug}_<adr_slug>" --to "${projectExternalKey}" --type decides`,
1621
1625
  },
1622
1626
  });
1627
+ const preservedEntities = entitiesResult.preserved ?? [];
1628
+ const nothingWritten = (entitiesResult.summary?.succeeded ?? 0) === 0;
1629
+ const status = refreshMode && nothingWritten && preservedEntities.length > 0
1630
+ ? "review_enrichment"
1631
+ : "enrichment_required";
1623
1632
  return {
1624
- status: "enrichment_required",
1625
- note: refreshMode
1626
- ? "Execute ALL pendingSteps. In refresh mode, compare graphState vs scanState to identify gaps do not blindly remove relationships the scanner missed; the scanner cannot detect platforms, integrations, data assets, or application_functions."
1627
- : "Execute ALL pendingSteps before reporting the project as registered. Do not pause or ask for confirmation between steps.",
1633
+ status,
1634
+ note: status === "review_enrichment"
1635
+ ? "No new skeletons were written all entities were already enriched and preserved. Review the preservedEntities list and confirm scan values match. Update with nexarch update-entity only if something has genuinely changed."
1636
+ : refreshMode
1637
+ ? "Execute ALL pendingSteps. In refresh mode, compare graphState vs scanState to identify gaps — do not blindly remove relationships the scanner missed; the scanner cannot detect platforms, integrations, data assets, or application_functions."
1638
+ : "Execute ALL pendingSteps before reporting the project as registered. Do not pause or ask for confirmation between steps.",
1639
+ ...(refreshMode && preservedEntities.length > 0
1640
+ ? {
1641
+ preservedEntities: {
1642
+ note: "These entities already have enriched content and were NOT overwritten by the scan. Review the scan values — if anything has genuinely changed, update with nexarch update-entity.",
1643
+ entities: preservedEntities,
1644
+ },
1645
+ }
1646
+ : {}),
1628
1647
  projectEntity: {
1629
1648
  externalKey: projectExternalKey,
1630
1649
  entityType: entityTypeOverride,
@@ -1709,11 +1728,19 @@ export async function initProject(args) {
1709
1728
  // This is addressed to the *calling agent* (Claude Code, Codex, etc.) and tells it
1710
1729
  // to act inline in this same session — not to queue enrichment for later.
1711
1730
  function buildEnrichmentDirective() {
1731
+ const preservedEntities = entitiesResult.preserved ?? [];
1712
1732
  const lines = [];
1713
- lines.push("─── ENRICHMENT REQUIRED ─────────────────────────────────────────────────");
1714
- lines.push("init-project wrote a skeleton. Complete enrichment below — do not ask the");
1715
- lines.push("user for permission, do not summarise and pause, just execute the steps.");
1716
- lines.push("Do NOT report the project as registered until enrichment is done.");
1733
+ if (refreshMode) {
1734
+ lines.push("─── REFRESH REVIEW REQUIRED ──────────────────────────────────────────────");
1735
+ lines.push("update-project refreshed the scan. Review enriched entities and complete any");
1736
+ lines.push("new skeletons below do not ask the user for permission, just execute.");
1737
+ }
1738
+ else {
1739
+ lines.push("─── ENRICHMENT REQUIRED ─────────────────────────────────────────────────");
1740
+ lines.push("init-project wrote a skeleton. Complete enrichment below — do not ask the");
1741
+ lines.push("user for permission, do not summarise and pause, just execute the steps.");
1742
+ lines.push("Do NOT report the project as registered until enrichment is done.");
1743
+ }
1717
1744
  lines.push("");
1718
1745
  lines.push(`PROJECT : ${projectExternalKey}`);
1719
1746
  lines.push(`DIR : ${dir}`);
@@ -1759,6 +1786,19 @@ export async function initProject(args) {
1759
1786
  if (unresolvedItems.length > 20)
1760
1787
  lines.push(` … and ${unresolvedItems.length - 20} more`);
1761
1788
  }
1789
+ if (refreshMode && preservedEntities.length > 0) {
1790
+ lines.push("");
1791
+ lines.push(`ENRICHED (${preservedEntities.length} — already have descriptions; not overwritten by scan):`);
1792
+ lines.push(" Review each entry. If the scan detected a real change, run update-entity manually.");
1793
+ for (const p of preservedEntities) {
1794
+ lines.push(` ${p.externalKey ?? "?"}`);
1795
+ lines.push(` current : name="${p.currentValues?.name ?? ""}" subtype="${p.currentValues?.entitySubtypeCode ?? ""}"`);
1796
+ lines.push(` scan : name="${p.scanValues?.name ?? ""}" subtype="${p.scanValues?.entitySubtypeCode ?? ""}"`);
1797
+ if (p.currentValues?.name !== p.scanValues?.name || p.currentValues?.entitySubtypeCode !== p.scanValues?.entitySubtypeCode) {
1798
+ lines.push(` ⚠ values differ — review and update if the change is real`);
1799
+ }
1800
+ }
1801
+ }
1762
1802
  lines.push("");
1763
1803
  lines.push("THEN_RUN (execute these steps — do not pause or ask for confirmation):");
1764
1804
  let step = 1;
@@ -1808,9 +1848,10 @@ export async function initProject(args) {
1808
1848
  return lines.join("\n");
1809
1849
  }
1810
1850
  const enrichmentRequired = buildEnrichmentPayload();
1851
+ const outputStatus = enrichmentRequired.status;
1811
1852
  const output = {
1812
1853
  ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
1813
- status: "enrichment_required",
1854
+ status: outputStatus,
1814
1855
  mode: refreshMode ? "refresh" : "init",
1815
1856
  project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride, detectedEcosystems },
1816
1857
  entities: entitiesResult.summary ?? {},
@@ -1848,9 +1889,10 @@ export async function initProject(args) {
1848
1889
  }
1849
1890
  console.log(`\nDone.`);
1850
1891
  console.log(` Mode : ${refreshMode ? "refresh (update-project)" : "init"}`);
1851
- console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${output.entities.failed ?? 0} failed`);
1892
+ const preservedCount = output.entities.preserved ?? 0;
1893
+ console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${preservedCount > 0 ? `${preservedCount} preserved (enriched), ` : ""}${output.entities.failed ?? 0} failed`);
1852
1894
  console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
1853
- console.log(" Status : skeleton created; enrichment pending");
1895
+ console.log(` Status : ${output.status === "review_enrichment" ? "all enriched; review preserved entities" : "skeleton created; enrichment pending"}`);
1854
1896
  if (output.metrics.relationshipsSkippedAsDuplicate > 0) {
1855
1897
  console.log(` Deduped rels : ${output.metrics.relationshipsSkippedAsDuplicate} skipped as duplicates before upsert`);
1856
1898
  }
@@ -0,0 +1,103 @@
1
+ import process from "process";
2
+ import { requireCredentials } from "../lib/credentials.js";
3
+ import { callMcpTool } from "../lib/mcp.js";
4
+ function parseFlag(args, flag) {
5
+ return args.includes(flag);
6
+ }
7
+ function parseOptionValue(args, option) {
8
+ const idx = args.indexOf(option);
9
+ if (idx === -1)
10
+ return null;
11
+ const v = args[idx + 1];
12
+ if (!v || v.startsWith("--"))
13
+ return null;
14
+ return v;
15
+ }
16
+ function parseToolText(result) {
17
+ const text = result.content?.[0]?.text ?? "{}";
18
+ return JSON.parse(text);
19
+ }
20
+ export async function policyAuditResults(args) {
21
+ const asJson = parseFlag(args, "--json");
22
+ if (parseFlag(args, "--help") || parseFlag(args, "-h")) {
23
+ console.log(`
24
+ Usage:
25
+ nexarch policy-audit-results --entity <applicationEntityRef> [--limit <1-10>] [--json]
26
+
27
+ Options:
28
+ --entity <key> Required application reference (e.g. application:my-service).
29
+ Aliases: --entity-ref, --application-ref, --application-key
30
+ --limit <n> Number of most recent runs to return (default 1, max 10)
31
+ --json Print JSON response
32
+ `);
33
+ return;
34
+ }
35
+ const entity = parseOptionValue(args, "--entity") ??
36
+ parseOptionValue(args, "--entity-ref") ??
37
+ parseOptionValue(args, "--application-ref") ??
38
+ parseOptionValue(args, "--application-key");
39
+ if (!entity) {
40
+ console.error("error: --entity <applicationEntityRef> is required (e.g. application:my-service)");
41
+ process.exit(1);
42
+ }
43
+ const limitRaw = parseOptionValue(args, "--limit");
44
+ const limit = limitRaw ? Number.parseInt(limitRaw, 10) : null;
45
+ if (limitRaw && (!Number.isFinite(limit) || (limit ?? 0) < 1)) {
46
+ console.error("error: --limit must be a positive integer");
47
+ process.exit(1);
48
+ }
49
+ const creds = requireCredentials();
50
+ const raw = await callMcpTool("nexarch_get_policy_audit_results", {
51
+ applicationEntityRef: entity,
52
+ ...(limit ? { limit } : {}),
53
+ companyId: creds.companyId,
54
+ }, { companyId: creds.companyId });
55
+ const result = parseToolText(raw);
56
+ if (asJson) {
57
+ process.stdout.write(JSON.stringify(result) + "\n");
58
+ return;
59
+ }
60
+ if (result.ok === false) {
61
+ console.error(`Policy audit results failed${result.error ? `: ${result.error}` : ""}`);
62
+ if (result.hint)
63
+ console.error(result.hint);
64
+ process.exit(1);
65
+ }
66
+ const label = result.applicationName
67
+ ? `${result.applicationName} (${result.applicationEntityRef ?? entity})`
68
+ : (result.applicationEntityRef ?? entity);
69
+ console.log(`Policy audit results for ${label}`);
70
+ const runs = result.runs ?? [];
71
+ if (runs.length === 0) {
72
+ console.log("No audit runs found.");
73
+ if (result.hint)
74
+ console.log(result.hint);
75
+ return;
76
+ }
77
+ for (const run of runs) {
78
+ const summary = run.summary ?? {};
79
+ console.log(`\nRun ${run.runId} — ${run.status}`);
80
+ if (run.startedAt)
81
+ console.log(` Started: ${run.startedAt}`);
82
+ if (run.completedAt)
83
+ console.log(` Completed: ${run.completedAt}`);
84
+ if (summary && (summary.passCount !== undefined || summary.failCount !== undefined)) {
85
+ console.log(` Summary: ${summary.passCount ?? 0} pass, ${summary.partialCount ?? 0} partial, ${summary.failCount ?? 0} fail` +
86
+ (summary.totalRules !== undefined ? ` (of ${summary.totalRules})` : ""));
87
+ }
88
+ for (const control of run.controls ?? []) {
89
+ console.log(`\n - ${control.controlName} (${control.controlId})`);
90
+ for (const rule of control.rules ?? []) {
91
+ const level = rule.requirementLevel ? ` [${rule.requirementLevel}]` : "";
92
+ console.log(` • [${rule.result}] ${rule.ruleName}${level} (${rule.ruleId})`);
93
+ if (rule.rationale)
94
+ console.log(` ${rule.rationale}`);
95
+ if (Array.isArray(rule.missingRequirements) && rule.missingRequirements.length > 0) {
96
+ for (const m of rule.missingRequirements) {
97
+ console.log(` - missing: ${String(m)}`);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
package/dist/index.js CHANGED
@@ -21,6 +21,9 @@ import { commandClaim } from "./commands/command-claim.js";
21
21
  import { policyControls } from "./commands/policy-controls.js";
22
22
  import { policyAuditTemplate } from "./commands/policy-audit-template.js";
23
23
  import { policyAuditSubmit } from "./commands/policy-audit-submit.js";
24
+ import { policyAuditResults } from "./commands/policy-audit-results.js";
25
+ import { appliedPolicies } from "./commands/applied-policies.js";
26
+ import { governanceSummary } from "./commands/governance-summary.js";
24
27
  const [, , command, ...args] = process.argv;
25
28
  const commands = {
26
29
  login,
@@ -46,6 +49,9 @@ const commands = {
46
49
  "policy-controls": policyControls,
47
50
  "policy-audit-template": policyAuditTemplate,
48
51
  "policy-audit-submit": policyAuditSubmit,
52
+ "policy-audit-results": policyAuditResults,
53
+ "applied-policies": appliedPolicies,
54
+ "governance-summary": governanceSummary,
49
55
  };
50
56
  async function main() {
51
57
  if (command === "agent") {
@@ -206,6 +212,20 @@ Usage:
206
212
  --findings-json <json-array>
207
213
  --findings-file <path.json>
208
214
  --json
215
+ nexarch policy-audit-results
216
+ Retrieve stored results of previous policy audits for an application.
217
+ Options: --entity <applicationEntityRef> (required)
218
+ --limit <1-10> (default 1)
219
+ --json
220
+ nexarch applied-policies
221
+ List policy documents applied to this company account.
222
+ Options: --pack <packCode> filter to a specific pack
223
+ --markdown include full document markdown
224
+ --json
225
+ nexarch governance-summary
226
+ Print review queue, graph stats, and per-application policy
227
+ audit rollup (latest run status, pass/partial/fail counts).
228
+ Options: --json
209
229
  `);
210
230
  process.exit(command ? 1 : 0);
211
231
  }
package/dist/lib/mcp.js CHANGED
@@ -1,6 +1,21 @@
1
1
  import https from "https";
2
+ import { readFileSync } from "fs";
3
+ import { fileURLToPath } from "url";
4
+ import { dirname, join } from "path";
2
5
  import { requireCredentials } from "./credentials.js";
3
6
  const MCP_GATEWAY_URL = "https://mcp.nexarch.ai";
7
+ function readCliVersion() {
8
+ try {
9
+ const here = dirname(fileURLToPath(import.meta.url));
10
+ const pkgPath = join(here, "..", "..", "package.json");
11
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
12
+ return pkg.version ?? "0.0.0";
13
+ }
14
+ catch {
15
+ return "0.0.0";
16
+ }
17
+ }
18
+ const CLI_VERSION = readCliVersion();
4
19
  const REQUEST_TIMEOUT_MS = Number.parseInt(process.env.NEXARCH_MCP_TIMEOUT_MS ?? "90000", 10) || 90_000;
5
20
  const REQUEST_RETRIES = Math.max(0, Number.parseInt(process.env.NEXARCH_MCP_RETRIES ?? "2", 10) || 2);
6
21
  function sleep(ms) {
@@ -97,7 +112,7 @@ export async function mcpInitialize(options = {}) {
97
112
  return callMcpRpc("initialize", {
98
113
  protocolVersion: "2024-11-05",
99
114
  capabilities: {},
100
- clientInfo: { name: "nexarch-cli", version: "0.5.12" },
115
+ clientInfo: { name: "nexarch-cli", version: CLI_VERSION },
101
116
  }, options);
102
117
  }
103
118
  export async function mcpListTools(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.9.34",
3
+ "version": "0.11.0",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",