nexarch 0.13.7 → 0.13.8

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.
@@ -344,7 +344,8 @@ export function canonicalTargetKey(filePath) {
344
344
  * consent from the dry-run result before calling again with `dryRun: false`
345
345
  * to actually write — see the ADR-0112 note at the call site.
346
346
  */
347
- function injectAgentConfigs(registry, runtimeCodes, dryRun) {
347
+ /** Exported for regression coverage of the empty-return bug documented at its tail. */
348
+ export function injectAgentConfigs(registry, runtimeCodes, dryRun) {
348
349
  const templateByCode = new Map(registry.instructionTemplates.map((t) => [t.code, t]));
349
350
  const sortedTargets = [...registry.instructionTargets]
350
351
  .filter((target) => target.matchMode === "exact")
@@ -437,7 +438,17 @@ function injectAgentConfigs(registry, runtimeCodes, dryRun) {
437
438
  if (perRuntimeResults.length > 0)
438
439
  return [...existingResults, ...perRuntimeResults.filter((result, index, arr) => arr.findIndex((item) => canonicalTargetKey(item.path) === canonicalTargetKey(result.path)) === index)];
439
440
  }
440
- return [];
441
+ // Not `[]`. When every runtime's target was already collected into
442
+ // `existingMatches` — which is the steady state once a repo has been set up
443
+ // once — the per-runtime loop `continue`s on all of them and
444
+ // `perRuntimeResults` comes back empty. Returning [] here threw away the
445
+ // results already computed above, the caller read that as "no instruction
446
+ // files" and fell back to injectGenericAgentConfig, and from then on only
447
+ // the generic target (AGENTS.md) was ever seen. Paul's CLAUDE.md and
448
+ // .cursorrules were invisible to staleness checking and attestation refresh
449
+ // from the second setup run onward, which is how they kept an expired
450
+ // `instruction_injection` block while the handshake reported all green.
451
+ return existingResults;
441
452
  }
442
453
  function trustAttestationStaleness(path) {
443
454
  if (!existsSync(path))
@@ -507,6 +518,45 @@ export function instructionInjectionDetail(registrationOk, staleAttestationTarge
507
518
  return `updated ${agentConfigResults.length} instruction target file(s)`;
508
519
  return "no runtime instruction target matched this repository (non-fatal; create AGENTS.md/CLAUDE.md or configure a generic target)";
509
520
  }
521
+ /**
522
+ * Every instruction file the registry recognises that actually exists in this
523
+ * repo, deduplicated the way the rest of this module keys paths.
524
+ *
525
+ * This is the denominator for the coverage guard below. It is deliberately
526
+ * computed straight from the registry and the filesystem rather than from
527
+ * whatever the injection path happened to return, because the bug it guards
528
+ * against was the injection path silently returning fewer files than exist.
529
+ */
530
+ export function existingInstructionFilePaths(registry) {
531
+ const seen = new Set();
532
+ const paths = [];
533
+ for (const target of registry.instructionTargets) {
534
+ if (target.matchMode !== "exact")
535
+ continue;
536
+ const filePath = join(process.cwd(), target.filePathPattern);
537
+ const key = canonicalTargetKey(filePath);
538
+ if (seen.has(key))
539
+ continue;
540
+ seen.add(key);
541
+ if (existsSync(filePath))
542
+ paths.push(filePath);
543
+ }
544
+ return paths;
545
+ }
546
+ /**
547
+ * Instruction files that exist on disk but were never evaluated for staleness.
548
+ *
549
+ * "already current (no refresh needed)" is only honest about files somebody
550
+ * looked at. When the injection path under-reports its targets, the attestation
551
+ * check evaluates a subset and reports green for the whole repo — which is
552
+ * exactly how two of Paul's three files kept an expired, wrongly-scoped block
553
+ * through a clean setup run. A non-empty result here fails the check rather
554
+ * than letting it speak for files it never opened.
555
+ */
556
+ export function unevaluatedInstructionFiles(onDisk, evaluated) {
557
+ const evaluatedKeys = new Set(evaluated.map((result) => canonicalTargetKey(result.path)));
558
+ return onDisk.filter((path) => !evaluatedKeys.has(canonicalTargetKey(path)));
559
+ }
510
560
  /**
511
561
  * Summarises the per-target attestation writes into the check's ok/detail.
512
562
  *
@@ -1481,6 +1531,9 @@ export async function initAgent(args) {
1481
1531
  : identityCapture.detail,
1482
1532
  });
1483
1533
  const instructionsAlreadyConfigured = agentConfigResults.length > 0 && agentConfigResults.every((r) => r.status === "already_present");
1534
+ const uncoveredInstructionFiles = registration.ok
1535
+ ? unevaluatedInstructionFiles(existingInstructionFilePaths(registry), agentConfigResults)
1536
+ : [];
1484
1537
  const attestationSummary = summariseAttestationOutcomes(attestationOutcomes);
1485
1538
  /** Renewed means every attested target verified on disk, not that one mint succeeded. */
1486
1539
  const attestationRenewed = attestationOutcomes.length > 0 && attestationSummary.ok;
@@ -1492,23 +1545,26 @@ export async function initAgent(args) {
1492
1545
  checks.push({
1493
1546
  name: "agent.trust.attestation",
1494
1547
  ok: !registration.ok || (unsafeReattestationTargetCount === 0
1548
+ && uncoveredInstructionFiles.length === 0
1495
1549
  && attestationSummary.ok
1496
1550
  && ((staleAttestationTargetCount === 0 && !trustAttestationAttempted) || Boolean(trustAttestation?.ok))),
1497
1551
  detail: !registration.ok
1498
1552
  ? "skipped (registration failed)"
1499
- : unsafeReattestationTargetCount > 0
1500
- ? "managed section does not match the published template; re-run with --allow-instruction-write"
1501
- : staleAttestationTargetCount > 0 && !attestationWriteAllowed
1502
- ? `${mismatchedAttestationTargetCount > 0 ? "mismatched" : "stale"}; re-run with --reattest`
1503
- : instructionsAlreadyConfigured && !attestationWriteAllowed
1504
- ? "skipped (already configured)"
1505
- : !attestationWriteAllowed
1506
- ? "skipped (consent not granted)"
1507
- : !trustAttestationAttempted
1508
- ? agentConfigResults.length > 0
1509
- ? "already current (no refresh needed)"
1510
- : "skipped (no instruction target written)"
1511
- : attestationSummary.detail,
1553
+ : uncoveredInstructionFiles.length > 0
1554
+ ? `${uncoveredInstructionFiles.length} instruction file(s) on disk were never evaluated (${uncoveredInstructionFiles.map((path) => basename(path)).join(", ")}); their attestations are unverified`
1555
+ : unsafeReattestationTargetCount > 0
1556
+ ? "managed section does not match the published template; re-run with --allow-instruction-write"
1557
+ : staleAttestationTargetCount > 0 && !attestationWriteAllowed
1558
+ ? `${mismatchedAttestationTargetCount > 0 ? "mismatched" : "stale"}; re-run with --reattest`
1559
+ : instructionsAlreadyConfigured && !attestationWriteAllowed
1560
+ ? "skipped (already configured)"
1561
+ : !attestationWriteAllowed
1562
+ ? "skipped (consent not granted)"
1563
+ : !trustAttestationAttempted
1564
+ ? agentConfigResults.length > 0
1565
+ ? "already current (no refresh needed)"
1566
+ : "skipped (no instruction target written)"
1567
+ : attestationSummary.detail,
1512
1568
  });
1513
1569
  checks.push({
1514
1570
  name: "technology.components",
@@ -37,24 +37,24 @@ export async function policyAuditTemplate(args) {
37
37
  const defaultResult = (parseOptionValue(args, "--default-result") ?? "fail").toLowerCase();
38
38
  const selectedControlIds = new Set(parseMultiOptionValues(args, "--control-id"));
39
39
  if (parseFlag(args, "--help") || parseFlag(args, "-h")) {
40
- console.log(`
41
- Usage:
42
- nexarch policy-audit-template --entity <application:key> [options]
43
-
44
- Options:
45
- --entity <key> Required (e.g. application:bad-driving)
46
- --control-id <uuid> Optional repeatable filter (selected controls only)
47
- --default-result <value> pass|partial|fail (default: fail)
48
- --output, --out <path.json> Write findings array to file
49
- --json Print JSON to stdout
50
-
51
- Output shape is ready for:
52
- nexarch policy-audit-submit --findings-file <path.json>
53
-
54
- Each entry includes empty severity/effort/exploitability/confidence/fileRefs
55
- placeholders. Fill them in for every partial/fail finding -- they decide how
56
- findings get ranked and reported. Leave a field blank/empty rather than
57
- guess; policy-audit-submit drops anything that isn't a valid value.
40
+ console.log(`
41
+ Usage:
42
+ nexarch policy-audit-template --entity <application:key> [options]
43
+
44
+ Options:
45
+ --entity <key> Required (e.g. application:bad-driving)
46
+ --control-id <uuid> Optional repeatable filter (selected controls only)
47
+ --default-result <value> pass|partial|fail (default: fail)
48
+ --output, --out <path.json> Write findings array to file
49
+ --json Print JSON to stdout
50
+
51
+ Output shape is ready for:
52
+ nexarch policy-audit-submit --findings-file <path.json>
53
+
54
+ Each entry includes empty severity/effort/exploitability/confidence/fileRefs
55
+ placeholders. Fill them in for every partial/fail finding -- they decide how
56
+ findings get ranked and reported. Leave a field blank/empty rather than
57
+ guess; policy-audit-submit drops anything that isn't a valid value.
58
58
  `);
59
59
  return;
60
60
  }
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "nexarch",
3
- "version": "0.13.7",
4
- "description": "Your architecture workspace for AI delivery.",
5
- "keywords": [
6
- "nexarch",
7
- "mcp",
8
- "architecture",
9
- "ai"
10
- ],
11
- "license": "MIT",
12
- "author": "Nexarch <hello@nexarch.ai>",
13
- "homepage": "https://nexarch.ai",
14
- "engines": {
15
- "node": ">=18"
16
- },
17
- "type": "module",
18
- "bin": {
19
- "nexarch": "dist/index.js"
20
- },
21
- "files": [
22
- "dist"
23
- ],
24
- "scripts": {
25
- "build": "tsc",
26
- "prepublishOnly": "tsc",
27
- "dev": "tsx src/index.ts",
28
- "typecheck": "tsc --noEmit",
29
- "test": "tsx scripts/test-mcp-proxy-response.ts && tsx scripts/test-trust-reattest.ts && tsx scripts/test-similar-applications.ts && tsx scripts/test-iac-tool.ts && tsx scripts/test-ansible.ts && tsx scripts/test-reference-sightings.ts && tsx scripts/test-search-icons.ts && tsx scripts/test-paste-login.ts"
30
- },
31
- "devDependencies": {
32
- "@types/node": "^22",
33
- "tsx": "^4",
34
- "typescript": "^5"
35
- },
36
- "dependencies": {
37
- "yaml": "^2.9.0"
38
- }
39
- }
1
+ {
2
+ "name": "nexarch",
3
+ "version": "0.13.8",
4
+ "description": "Your architecture workspace for AI delivery.",
5
+ "keywords": [
6
+ "nexarch",
7
+ "mcp",
8
+ "architecture",
9
+ "ai"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "Nexarch <hello@nexarch.ai>",
13
+ "homepage": "https://nexarch.ai",
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "type": "module",
18
+ "bin": {
19
+ "nexarch": "dist/index.js"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepublishOnly": "tsc",
27
+ "dev": "tsx src/index.ts",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "tsx scripts/test-mcp-proxy-response.ts && tsx scripts/test-trust-reattest.ts && tsx scripts/test-similar-applications.ts && tsx scripts/test-iac-tool.ts && tsx scripts/test-ansible.ts && tsx scripts/test-reference-sightings.ts && tsx scripts/test-search-icons.ts && tsx scripts/test-paste-login.ts"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22",
33
+ "tsx": "^4",
34
+ "typescript": "^5"
35
+ },
36
+ "dependencies": {
37
+ "yaml": "^2.9.0"
38
+ }
39
+ }