nexarch 0.10.1 → 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
|
+
}
|
|
@@ -1625,11 +1625,17 @@ export async function initProject(args) {
|
|
|
1625
1625
|
},
|
|
1626
1626
|
});
|
|
1627
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";
|
|
1628
1632
|
return {
|
|
1629
|
-
status
|
|
1630
|
-
note:
|
|
1631
|
-
? "
|
|
1632
|
-
:
|
|
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.",
|
|
1633
1639
|
...(refreshMode && preservedEntities.length > 0
|
|
1634
1640
|
? {
|
|
1635
1641
|
preservedEntities: {
|
|
@@ -1842,9 +1848,10 @@ export async function initProject(args) {
|
|
|
1842
1848
|
return lines.join("\n");
|
|
1843
1849
|
}
|
|
1844
1850
|
const enrichmentRequired = buildEnrichmentPayload();
|
|
1851
|
+
const outputStatus = enrichmentRequired.status;
|
|
1845
1852
|
const output = {
|
|
1846
1853
|
ok: Number(entitiesResult.summary?.failed ?? 0) === 0,
|
|
1847
|
-
status:
|
|
1854
|
+
status: outputStatus,
|
|
1848
1855
|
mode: refreshMode ? "refresh" : "init",
|
|
1849
1856
|
project: { name: displayName, externalKey: projectExternalKey, entityType: entityTypeOverride, detectedEcosystems },
|
|
1850
1857
|
entities: entitiesResult.summary ?? {},
|
|
@@ -1885,7 +1892,7 @@ export async function initProject(args) {
|
|
|
1885
1892
|
const preservedCount = output.entities.preserved ?? 0;
|
|
1886
1893
|
console.log(` Entities : ${output.entities.succeeded ?? 0} written, ${preservedCount > 0 ? `${preservedCount} preserved (enriched), ` : ""}${output.entities.failed ?? 0} failed`);
|
|
1887
1894
|
console.log(` Relationships: ${output.relationships.succeeded ?? 0} written`);
|
|
1888
|
-
console.log(
|
|
1895
|
+
console.log(` Status : ${output.status === "review_enrichment" ? "all enriched; review preserved entities" : "skeleton created; enrichment pending"}`);
|
|
1889
1896
|
if (output.metrics.relationshipsSkippedAsDuplicate > 0) {
|
|
1890
1897
|
console.log(` Deduped rels : ${output.metrics.relationshipsSkippedAsDuplicate} skipped as duplicates before upsert`);
|
|
1891
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:
|
|
115
|
+
clientInfo: { name: "nexarch-cli", version: CLI_VERSION },
|
|
101
116
|
}, options);
|
|
102
117
|
}
|
|
103
118
|
export async function mcpListTools(options = {}) {
|