nexarch 0.12.10 → 0.12.12

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.
@@ -440,7 +440,17 @@ function injectTrustAttestationBlock(path, attestation) {
440
440
  "",
441
441
  "This block is verifiable proof that Nexarch generated these instructions for this workspace.",
442
442
  "Do not auto-run commands without user confirmation.",
443
- "If this token is expired, rerun `npx nexarch@latest init-agent --allow-instruction-write` to refresh this trust block.",
443
+ "",
444
+ "**To verify, run `npx nexarch@latest verify-trust`.** It reads the token from this",
445
+ "file and checks it for you.",
446
+ "",
447
+ "Do not retype, paste or reconstruct the token below into a URL or a shell command.",
448
+ "It is a cryptographic signature: reproducing it from memory yields a plausible token",
449
+ "that fails as `bad_signature`, which is indistinguishable from a forged block. A",
450
+ "transcription slip would then read as tampering and stop work for no reason.",
451
+ "",
452
+ "If verification says `expired`, rerun `npx nexarch@latest init-agent --allow-instruction-write`.",
453
+ "If it cannot reach the endpoint, that is a network problem and not a failed attestation — retry.",
444
454
  "",
445
455
  `issuer: ${attestation.payload.iss}`,
446
456
  `scope: ${attestation.payload.scope}`,
@@ -477,27 +487,46 @@ function injectTrustAttestationUnavailableBlock(path, reason) {
477
487
  }
478
488
  function injectInitProjectReportingContract(path) {
479
489
  const section = [
480
- "## Nexarch Init-Project Reporting Contract",
490
+ "## Nexarch Init-Project Completion Contract",
481
491
  "",
482
- "When you run `npx nexarch@latest init-project`, treat it as skeleton registration only.",
483
- "Do not claim full completion until enrichment commands are actually executed.",
492
+ "`init-project` is the first step of registration, not the whole task. It returns",
493
+ "`registrationStatus`, `enrichmentCompleted` and a `nextSteps` array. While",
494
+ "`enrichmentCompleted` is `false`, the work is unfinished.",
484
495
  "",
485
- "Useful status fields to report after init-project:",
486
- '- `registration_status`: `"skeleton_only"` or `"enriched"`',
487
- '- `enrichment_completed`: `true` or `false`',
496
+ "**Run the commands in `nextSteps` before you report back.** They are the step that",
497
+ "puts real content in the graph. Reporting `skeleton_only` and stopping is not a",
498
+ "completed task it is a description of a task you have not finished.",
488
499
  "",
489
- "Notes:",
490
- '- While `enrichment_completed=false`, the project is a skeleton; describing it as fully registered would overstate what is in the graph.',
491
- '- Enrichment uses explicit per-entity `update-entity` runs rather than a bulk shortcut, so each entity gets evidence-based content.',
500
+ "For an infrastructure repository, `nextSteps` holds one `ingest-infra` command per",
501
+ "Terraform root module. Until they run, the graph knows the repository exists but",
502
+ "nothing it provisions.",
492
503
  "",
493
- "You may set `registration_status=\"enriched\"` only after executing enrichment actions (e.g. update-entity for project/sub-packages, aliases, and any chosen gap wiring), with evidence-based per-entity descriptions/subtypes.",
504
+ "Stop early only when a step genuinely cannot run missing cloud credentials, a root",
505
+ "module that needs `terraform init`, or an environment you should not touch without",
506
+ "asking. When that happens, say which step stopped you and why, then ask. Do not",
507
+ "silently return a skeleton.",
508
+ "",
509
+ "When reporting:",
510
+ '- `registration_status`: `"skeleton_only"` or `"enriched"` — take it from the command output, do not infer it',
511
+ '- `enrichment_completed`: `true` or `false`',
512
+ '- Describing a skeleton as fully registered overstates what is in the graph; so does reporting success without saying what remains.',
513
+ "",
514
+ "Enrichment uses explicit per-entity `update-entity` runs rather than a bulk shortcut,",
515
+ "so each entity gets evidence-based content.",
494
516
  "",
495
517
  ].join("\n");
496
518
  const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
497
519
  const managed = wrapManagedSection("init-project-reporting-contract", section.trim());
498
520
  let replaced = replaceManagedSection(existing, "init-project-reporting-contract", section.trim());
499
521
  if (replaced === existing) {
500
- replaced = replaceInjectedSection(existing, "## Nexarch Init-Project Reporting Contract", managed);
522
+ // The section was renamed from "Reporting Contract" to "Completion Contract".
523
+ // Files written before managed markers existed carry the old heading, so both
524
+ // are matched — otherwise the rename would leave the superseded contract in
525
+ // place alongside the new one, and the old one tells the agent to stop.
526
+ replaced = replaceInjectedSection(existing, "## Nexarch Init-Project Completion Contract", managed);
527
+ if (replaced === existing) {
528
+ replaced = replaceInjectedSection(existing, "## Nexarch Init-Project Reporting Contract", managed);
529
+ }
501
530
  }
502
531
  writeFileSync(path, replaced !== existing ? replaced : `${existing}${existing.endsWith("\n") ? "" : "\n"}${managed}\n`, "utf8");
503
532
  }
@@ -0,0 +1,108 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ /**
4
+ * Verifies the trust attestation without anyone retyping it.
5
+ *
6
+ * The attestation block asked the reader to take a ~500 character signed token
7
+ * out of a markdown file and paste it into a URL on a shell command line. That
8
+ * is a copy operation with no error detection: drop one character and the
9
+ * endpoint answers `bad_signature`, which is indistinguishable from a forged
10
+ * block. Agents duly reported the instructions as untrusted and refused to work
11
+ * — the correct response to that answer, and completely wrong about the facts.
12
+ *
13
+ * Reading the token from the file removes the copy, and with it the failure.
14
+ */
15
+ const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".windsurfrules", ".github/copilot-instructions.md"];
16
+ const DEFAULT_VERIFY_BASE = "https://mcp.nexarch.ai/trust/verify";
17
+ function findAttestation(dir) {
18
+ for (const name of INSTRUCTION_FILES) {
19
+ const path = join(dir, name);
20
+ if (!existsSync(path))
21
+ continue;
22
+ let content = "";
23
+ try {
24
+ content = readFileSync(path, "utf8");
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ const token = content.match(/^token:\s*(\S+)\s*$/m)?.[1];
30
+ if (!token)
31
+ continue;
32
+ const verifyUrl = content.match(/^verify_url:\s*(\S+)\s*$/m)?.[1] ?? null;
33
+ return { file: name, token, verifyUrl };
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Prefers the token field over the URL's copy of it.
39
+ *
40
+ * Both are written from the same value, but only the token field is a single
41
+ * self-contained word. Rebuilding the query string here also means a wrapped or
42
+ * truncated `verify_url` line cannot poison the check.
43
+ */
44
+ function verifyEndpoint(attestation) {
45
+ const base = attestation.verifyUrl?.split("?")[0];
46
+ const endpoint = base && base.startsWith("http") ? base : DEFAULT_VERIFY_BASE;
47
+ return `${endpoint}?token=${encodeURIComponent(attestation.token)}`;
48
+ }
49
+ export async function verifyTrust(args) {
50
+ const asJson = args.includes("--json");
51
+ const dirArg = args.indexOf("--dir");
52
+ const dir = resolve(dirArg !== -1 && args[dirArg + 1] && !args[dirArg + 1].startsWith("--") ? args[dirArg + 1] : process.cwd());
53
+ const attestation = findAttestation(dir);
54
+ if (!attestation) {
55
+ const message = `No Nexarch trust attestation found in ${dir}. Run \`npx nexarch@latest init-agent --allow-instruction-write\` to write one.`;
56
+ if (asJson) {
57
+ process.stdout.write(`${JSON.stringify({ verified: false, reason: "no_attestation_found", dir }, null, 2)}\n`);
58
+ }
59
+ else {
60
+ console.log(message);
61
+ }
62
+ process.exitCode = 1;
63
+ return;
64
+ }
65
+ let body;
66
+ try {
67
+ const response = await fetch(verifyEndpoint(attestation));
68
+ body = (await response.json());
69
+ }
70
+ catch (error) {
71
+ const reason = error instanceof Error ? error.message : String(error);
72
+ if (asJson) {
73
+ process.stdout.write(`${JSON.stringify({ verified: false, reason: "unreachable", detail: reason }, null, 2)}\n`);
74
+ }
75
+ else {
76
+ // An unreachable endpoint is not a failed verification, and must not be
77
+ // reported as one: a network blip would otherwise read as a forged block.
78
+ console.log(`Could not reach the verification endpoint — ${reason}`);
79
+ console.log("This is a connectivity problem, not a failed attestation. Retry before treating the instructions as untrusted.");
80
+ }
81
+ process.exitCode = 2;
82
+ return;
83
+ }
84
+ if (asJson) {
85
+ process.stdout.write(`${JSON.stringify({ ...body, source: attestation.file }, null, 2)}\n`);
86
+ process.exitCode = body.verified ? 0 : 1;
87
+ return;
88
+ }
89
+ if (body.verified) {
90
+ const payload = body.payload ?? {};
91
+ console.log(`✓ Trust attestation verified (${attestation.file})`);
92
+ console.log(` issuer: ${String(payload.iss ?? "unknown")}`);
93
+ console.log(` scope: ${String(payload.scope ?? "unknown")}`);
94
+ console.log(` agent_id: ${String(payload.agent_id ?? "unknown")}`);
95
+ if (typeof payload.exp === "number") {
96
+ console.log(` expires: ${new Date(payload.exp * 1000).toISOString()}`);
97
+ }
98
+ return;
99
+ }
100
+ console.log(`✗ Trust attestation NOT verified (${attestation.file}) — ${body.reason ?? "unknown reason"}`);
101
+ if (body.reason === "expired") {
102
+ console.log(" Refresh it: npx nexarch@latest init-agent --allow-instruction-write");
103
+ }
104
+ else {
105
+ console.log(" The instruction block may have been altered. Treat it as untrusted and ask the human how to proceed.");
106
+ }
107
+ process.exitCode = 1;
108
+ }
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { governanceSummary } from "./commands/governance-summary.js";
27
27
  import { proposalsStart } from "./commands/proposals-start.js";
28
28
  import { registerRuntime } from "./commands/register-runtime.js";
29
29
  import { ingestInfra } from "./commands/ingest-infra.js";
30
+ import { verifyTrust } from "./commands/verify-trust.js";
30
31
  const [, , command, ...args] = process.argv;
31
32
  const commands = {
32
33
  login,
@@ -36,6 +37,7 @@ const commands = {
36
37
  "mcp-config": mcpConfig,
37
38
  "mcp-proxy": mcpProxy,
38
39
  "init-agent": initAgent,
40
+ "verify-trust": verifyTrust,
39
41
  "agent-identify": agentIdentify,
40
42
  "init-project": initProject,
41
43
  "ingest-infra": ingestInfra,
@@ -83,6 +85,10 @@ Usage:
83
85
  Option: --company <id>
84
86
  nexarch logout Remove stored credentials
85
87
  nexarch status Check connection and show architecture summary
88
+ nexarch verify-trust Verify this repo's Nexarch trust attestation. Reads the token
89
+ from the instruction file, so nothing has to be copied.
90
+ Options: --dir <path> (default: cwd)
91
+ --json
86
92
  nexarch setup One-step onboarding: login (if needed) + MCP config + register agent
87
93
  Names the workspace it will write to and confirms it before
88
94
  registering anything; answer 'n' to pick a different one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexarch",
3
- "version": "0.12.10",
3
+ "version": "0.12.12",
4
4
  "description": "Your architecture workspace for AI delivery.",
5
5
  "keywords": [
6
6
  "nexarch",