agent-contracts 0.19.3 → 0.19.5

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/cli-contract.yaml CHANGED
@@ -3,7 +3,7 @@ cliContracts: 0.1.0
3
3
 
4
4
  info:
5
5
  title: agent-contracts CLI
6
- version: 0.19.2
6
+ version: 0.19.4
7
7
  description: >-
8
8
  Declarative YAML DSL toolkit for defining, validating, and rendering
9
9
  multi-agent development workflows. Provides static validation,
@@ -625,44 +625,81 @@ commandSets:
625
625
  schema:
626
626
  type: string
627
627
 
628
+ - name: fail-on
629
+ description: >-
630
+ Minimum finding severity that causes exit 10
631
+ (info|warning|error|critical).
632
+ valueName: level
633
+ schema:
634
+ type: string
635
+ enum: [info, warning, error, critical]
636
+ default: critical
637
+
638
+ - name: output
639
+ aliases: [o]
640
+ description: Write result to a file instead of stdout.
641
+ valueName: file
642
+ schema:
643
+ type: string
644
+ file:
645
+ mode: write
646
+ encoding: utf-8
647
+
648
+ - name: report-format
649
+ description: >-
650
+ Alias for --format. When both are specified, --report-format
651
+ takes precedence.
652
+ valueName: fmt
653
+ schema:
654
+ type: string
655
+ enum: [text, json, markdown]
656
+ default: text
657
+
628
658
  exits:
629
659
  '0':
630
- description: No critical findings detected.
660
+ description: Audit succeeded. No findings at or above --fail-on threshold.
631
661
  stdout:
632
662
  format: '{options.format}'
633
- schemaNote: >-
634
- Audit findings are always emitted on stdout as the primary
635
- output. Format depends on --format option (text, json, or
636
- markdown).
663
+ schema:
664
+ $ref: './dsl_base/components.yaml#/schemas/agent-audit-result'
637
665
 
638
666
  '1':
639
- description: Critical findings detected.
667
+ description: Unexpected error (invalid input, config error, or internal failure).
640
668
  stderr:
641
669
  format: text
642
- schemaNote: >-
643
- Summary diagnostic on stderr. Structured findings are
644
- emitted on stdout in the requested --format.
670
+
671
+ '10':
672
+ description: Findings at or above --fail-on severity threshold detected.
645
673
  stdout:
646
674
  format: '{options.format}'
675
+ schema:
676
+ $ref: './dsl_base/components.yaml#/schemas/agent-audit-result'
677
+ stderr:
678
+ format: text
647
679
  schemaNote: >-
648
- Full findings output. Exit 1 indicates at least one
649
- critical-severity finding exists; the findings themselves
650
- are valid structured data, not error messages.
680
+ Summary diagnostic on stderr. Structured findings are
681
+ emitted on stdout in the requested format.
651
682
 
652
- '2':
653
- description: Invalid input or configuration error.
683
+ '11':
684
+ description: >-
685
+ Runtime dependency missing (agent-contracts-runtime not installed).
654
686
  stderr:
655
687
  format: text
656
688
 
657
- '3':
658
- description: LLM adapter error (API failure, runtime not installed).
689
+ '12':
690
+ description: LLM provider or adapter error (API failure, auth error).
659
691
  stderr:
660
692
  format: text
661
693
 
662
694
  x-agent:
663
695
  riskLevel: medium
664
696
  requiresConfirmation: false
665
- idempotent: true
697
+ idempotent: false
698
+ idempotentNote: >-
699
+ Safe to repeat (no persistent side effects beyond network calls),
700
+ but LLM inference is non-deterministic — identical inputs may
701
+ produce different findings, severity assignments, and
702
+ recommendation text across runs.
666
703
  sideEffects:
667
704
  - network
668
705
  sideEffectNote: >-
@@ -671,7 +708,7 @@ commandSets:
671
708
  cost and sends DSL content to the LLM provider.
672
709
  safeDryRunOption: dry-run
673
710
  expectedDurationMs: 120000
674
- retryableExitCodes: [3]
711
+ retryableExitCodes: [12]
675
712
  recommendedBeforeUse:
676
713
  - Ensure agent-contracts.config.yaml exists with render targets.
677
714
  - Run validate first to confirm DSL is valid.
package/dist/cli.js CHANGED
@@ -49565,18 +49565,27 @@ async function resolveRefsDeep(data, baseDir, resolving, rootDoc) {
49565
49565
  }
49566
49566
  resolving.add(refValue);
49567
49567
  const target = resolveJsonPointer(rootDoc, refValue);
49568
- const resolved2 = await resolveRefsDeep(
49568
+ const resolved = await resolveRefsDeep(
49569
49569
  deepClone(target),
49570
49570
  baseDir,
49571
49571
  resolving,
49572
49572
  rootDoc
49573
49573
  );
49574
49574
  resolving.delete(refValue);
49575
- return resolved2;
49575
+ return resolved;
49576
49576
  }
49577
- const refTarget = resolve2(baseDir, refValue);
49577
+ const hashIdx = refValue.indexOf("#");
49578
+ const filePart = hashIdx >= 0 ? refValue.slice(0, hashIdx) : refValue;
49579
+ const fragment = hashIdx >= 0 ? refValue.slice(hashIdx) : null;
49580
+ const refTarget = resolve2(baseDir, filePart);
49578
49581
  const s2 = await fsStat(refTarget).catch(() => null);
49579
49582
  if (s2?.isDirectory()) {
49583
+ if (fragment) {
49584
+ throw new DslLoadError(
49585
+ `Cannot use JSON Pointer fragment with directory $ref: ${refValue}`,
49586
+ refTarget
49587
+ );
49588
+ }
49580
49589
  if (resolving.has(refTarget)) {
49581
49590
  throw new DslLoadError(
49582
49591
  `Circular $ref detected: ${refTarget}`,
@@ -49599,14 +49608,24 @@ async function resolveRefsDeep(data, baseDir, resolving, rootDoc) {
49599
49608
  }
49600
49609
  resolving.add(refTarget);
49601
49610
  const content = await readYaml(refTarget);
49602
- const resolved = await resolveRefsDeep(
49611
+ const fileRoot = fragment ? content : rootDoc;
49612
+ let fileData = await resolveRefsDeep(
49603
49613
  content,
49604
49614
  dirname2(refTarget),
49605
49615
  resolving,
49606
- rootDoc
49616
+ fileRoot
49607
49617
  );
49608
49618
  resolving.delete(refTarget);
49609
- return resolved;
49619
+ if (fragment && fragment.startsWith("#/")) {
49620
+ if (!isRecord(fileData)) {
49621
+ throw new DslLoadError(
49622
+ `Cannot resolve fragment "${fragment}" in ${refTarget}: file content is not an object`,
49623
+ refTarget
49624
+ );
49625
+ }
49626
+ fileData = resolveJsonPointer(fileData, fragment);
49627
+ }
49628
+ return fileData;
49610
49629
  }
49611
49630
  let obj = data;
49612
49631
  if (hasRefs(obj)) {
@@ -55898,6 +55917,7 @@ ${issues}
55898
55917
  );
55899
55918
 
55900
55919
  // src/cli/commands/audit.ts
55920
+ import { writeFileSync as writeFileSync2 } from "fs";
55901
55921
  import { Command as Command8 } from "commander";
55902
55922
 
55903
55923
  // src/auditor/context-builder.ts
@@ -56632,16 +56652,22 @@ function formatAuditResults(results, format2) {
56632
56652
  }
56633
56653
  return results.map((r3) => formatAuditResult(r3, format2)).join("\n\n");
56634
56654
  }
56635
- function computeExitCode(results) {
56655
+ function computeExitCode(results, failOn) {
56636
56656
  for (const r3 of results) {
56637
56657
  if (r3.status === "error") {
56638
- if (r3.errorMessage?.includes("not installed") || r3.errorMessage?.includes("adapter")) return 3;
56639
- return 2;
56658
+ if (r3.errorMessage?.includes("not installed")) return 11;
56659
+ if (r3.errorMessage?.includes("adapter") || r3.errorMessage?.includes("API")) return 12;
56660
+ return 1;
56640
56661
  }
56641
56662
  }
56663
+ const severityRank = { info: 0, warning: 1, error: 2, critical: 3 };
56664
+ const threshold = severityRank[failOn ?? "critical"] ?? 3;
56642
56665
  for (const r3 of results) {
56643
- if (r3.data?.critical_gaps && r3.data.critical_gaps.some((g2) => g2.severity === "critical")) {
56644
- return 1;
56666
+ if (r3.data?.critical_gaps) {
56667
+ for (const gap of r3.data.critical_gaps) {
56668
+ const rank = severityRank[gap.severity ?? ""] ?? 0;
56669
+ if (rank >= threshold) return 10;
56670
+ }
56645
56671
  }
56646
56672
  }
56647
56673
  return 0;
@@ -56659,13 +56685,13 @@ function parseAuditConfig(config) {
56659
56685
  cache_dir: audit.cache_dir
56660
56686
  };
56661
56687
  }
56662
- async function runAuditForDsl(dslPath, vars, configObj, auditType, opts) {
56688
+ async function runAuditForDsl(dslPath, vars, configObj, auditType, opts, failOn) {
56663
56689
  const resolved = await resolve4(dslPath);
56664
56690
  const data = vars ? substituteVars(resolved.data, vars) : resolved.data;
56665
56691
  const schemaResult = validateSchema(data);
56666
56692
  if (!schemaResult.success) {
56667
56693
  process.stderr.write("Schema validation failed. Run 'agent-contracts validate' first.\n");
56668
- return 2;
56694
+ return { exitCode: 1, output: "" };
56669
56695
  }
56670
56696
  const auditConfig = parseAuditConfig(
56671
56697
  configObj.audit
@@ -56678,17 +56704,18 @@ async function runAuditForDsl(dslPath, vars, configObj, auditType, opts) {
56678
56704
  scope: opts.scope,
56679
56705
  dryRun: opts.dryRun
56680
56706
  });
56707
+ let output2;
56681
56708
  if (opts.dryRun) {
56682
- for (const r3 of results) {
56683
- process.stdout.write(`
56709
+ output2 = results.map(
56710
+ (r3) => `
56684
56711
  --- Audit prompt: ${r3.auditType} ---
56685
- `);
56686
- process.stdout.write(r3.prompt + "\n");
56687
- }
56712
+ ${r3.prompt}
56713
+ `
56714
+ ).join("");
56688
56715
  } else {
56689
- process.stdout.write(formatAuditResults(results, opts.format) + "\n");
56716
+ output2 = formatAuditResults(results, opts.format) + "\n";
56690
56717
  }
56691
- return computeExitCode(results);
56718
+ return { exitCode: computeExitCode(results, failOn), output: output2 };
56692
56719
  }
56693
56720
  const result = await runAudit(schemaResult.data, configObj, auditConfig, {
56694
56721
  auditType,
@@ -56696,67 +56723,78 @@ async function runAuditForDsl(dslPath, vars, configObj, auditType, opts) {
56696
56723
  scope: opts.scope,
56697
56724
  dryRun: opts.dryRun
56698
56725
  });
56726
+ let output;
56699
56727
  if (opts.dryRun) {
56700
- process.stdout.write(result.prompt + "\n");
56728
+ output = result.prompt + "\n";
56701
56729
  } else {
56702
- process.stdout.write(formatAuditResult(result, opts.format) + "\n");
56730
+ output = formatAuditResult(result, opts.format) + "\n";
56703
56731
  }
56704
- return computeExitCode([result]);
56732
+ return { exitCode: computeExitCode([result], failOn), output };
56705
56733
  }
56706
- var auditCommand = new Command8("audit").description("Run LLM-based semantic audit on DSL definitions and generated outputs").argument("[type]", `Audit type: ${AUDIT_TYPES.join(", ")}`, "all").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json|markdown)", "text").option("--scope <entities>", "Limit audit scope (e.g. agents:architect,implementer)").option("--dry-run", "Output the audit prompt without calling LLM", false).option("--adapter <name>", "SDK adapter to use (default from config)").option("--model <name>", "LLM model override").action(
56734
+ var auditCommand = new Command8("audit").description("Run LLM-based semantic audit on DSL definitions and generated outputs").argument("[type]", `Audit type: ${AUDIT_TYPES.join(", ")}`, "all").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json|markdown)", "text").option("--scope <entities>", "Limit audit scope (e.g. agents:architect,implementer)").option("--dry-run", "Output the audit prompt without calling LLM", false).option("--adapter <name>", "SDK adapter to use (default from config)").option("--model <name>", "LLM model override").option("--fail-on <level>", "Minimum severity that causes exit 10 (info|warning|error|critical)", "critical").option("-o, --output <file>", "Write result to a file instead of stdout").option("--report-format <fmt>", "Alias for --format (for cli-contracts standard conformance)").action(
56707
56735
  async (type, opts) => {
56708
56736
  if (!AUDIT_TYPES.includes(type)) {
56709
56737
  process.stderr.write(
56710
56738
  `Unknown audit type: "${type}". Use one of: ${AUDIT_TYPES.join(", ")}
56711
56739
  `
56712
56740
  );
56713
- process.exit(2);
56741
+ process.exit(1);
56714
56742
  }
56715
- const format2 = opts.format;
56743
+ const format2 = opts.reportFormat ?? opts.format;
56716
56744
  try {
56717
56745
  const config = await loadConfig(opts.config);
56718
56746
  if (!config) {
56719
56747
  process.stderr.write(
56720
56748
  "Error: agent-contracts.config.yaml not found. Use --config to specify path.\n"
56721
56749
  );
56722
- process.exit(2);
56750
+ process.exit(1);
56723
56751
  }
56724
56752
  if (isMultiTeamConfig(config)) {
56725
56753
  const teamEntries = getTeamEntries(config, opts.team);
56726
56754
  let maxExit = 0;
56755
+ const allOutput = [];
56727
56756
  for (const [teamId, teamConfig] of teamEntries) {
56728
56757
  process.stderr.write(`
56729
56758
  --- Team: ${teamId} ---
56730
56759
  `);
56731
- const exitCode2 = await runAuditForDsl(
56760
+ const result2 = await runAuditForDsl(
56732
56761
  teamConfig.dsl,
56733
56762
  teamConfig.vars,
56734
56763
  config,
56735
56764
  type,
56736
- { format: format2, scope: opts.scope, dryRun: opts.dryRun, adapter: opts.adapter, model: opts.model }
56765
+ { format: format2, scope: opts.scope, dryRun: opts.dryRun, adapter: opts.adapter, model: opts.model },
56766
+ opts.failOn
56737
56767
  );
56738
- if (exitCode2 > maxExit) maxExit = exitCode2;
56768
+ process.stdout.write(result2.output);
56769
+ allOutput.push(result2.output);
56770
+ if (result2.exitCode > maxExit) maxExit = result2.exitCode;
56739
56771
  }
56772
+ if (opts.output) writeFileSync2(opts.output, allOutput.join(""), "utf8");
56740
56773
  process.exit(maxExit);
56741
56774
  }
56742
- const exitCode = await runAuditForDsl(
56775
+ const result = await runAuditForDsl(
56743
56776
  config.dsl,
56744
56777
  config.vars,
56745
56778
  config,
56746
56779
  type,
56747
- { format: format2, scope: opts.scope, dryRun: opts.dryRun, adapter: opts.adapter, model: opts.model }
56780
+ { format: format2, scope: opts.scope, dryRun: opts.dryRun, adapter: opts.adapter, model: opts.model },
56781
+ opts.failOn
56748
56782
  );
56749
- process.exit(exitCode);
56783
+ process.stdout.write(result.output);
56784
+ if (opts.output) writeFileSync2(opts.output, result.output, "utf8");
56785
+ process.exit(result.exitCode);
56750
56786
  } catch (err) {
56751
56787
  if (err instanceof ConfigLoadError) {
56752
56788
  process.stderr.write(`Config error: ${err.message}
56753
56789
  `);
56754
- process.exit(2);
56790
+ process.exit(1);
56755
56791
  }
56756
56792
  const msg = err instanceof Error ? err.message : String(err);
56757
56793
  process.stderr.write(`Error: ${msg}
56758
56794
  `);
56759
- process.exit(err instanceof Error && msg.includes("not installed") ? 3 : 2);
56795
+ if (msg.includes("not installed")) process.exit(11);
56796
+ if (msg.includes("adapter") || msg.includes("API")) process.exit(12);
56797
+ process.exit(1);
56760
56798
  }
56761
56799
  }
56762
56800
  );