agent-contracts 0.11.6 → 0.11.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.
package/README.md CHANGED
@@ -229,6 +229,31 @@ Workflow steps support additional properties:
229
229
  * `retry` (delegate steps) — defines a conditional retry loop with `condition`, `fix_task`, and optional `revalidate_task`
230
230
  * `routing_key` (decision steps) — the field that determines branch selection. The legacy field `on` is still accepted but deprecated due to YAML 1.1 reserved word collision
231
231
 
232
+ ### Validation
233
+
234
+ A **Validation** defines a verification step for an artifact:
235
+
236
+ * `target_artifact` — the artifact being verified
237
+ * `kind` — the type of verification (see below)
238
+ * `executor_type` — `tool` (automated) or `agent` (agent-driven)
239
+ * `executor` — the tool or agent that runs the validation
240
+ * `blocking` — whether the validation must pass before proceeding
241
+ * `produces_evidence` — optional artifact produced as evidence
242
+
243
+ #### Validation kinds
244
+
245
+ | Kind | Purpose | Example |
246
+ |------|---------|---------|
247
+ | `schema` | Structural schema check | JSON Schema validation, OpenAPI lint, SQL syntax |
248
+ | `mechanical` | Automated tool check | CLI linters, diff checks, coverage reports |
249
+ | `semantic` | Meaning-level review | Agent-based review of spec intent, plan coherence |
250
+ | `approval` | Human/agent sign-off gate | Architect approval before implementation |
251
+ | `provenance` | Source derivation verification | Confirm generated artifact derives from its canonical source (e.g., manifest from API contracts) |
252
+ | `traceability` | Cross-artifact link completeness | Verify every spec requirement reaches contracts, tests, and code |
253
+ | `fidelity` | Semantic faithfulness to source | Confirm tests actually verify spec intent, not just structural compliance |
254
+
255
+ `schema` and `mechanical` are best suited for automated checks via tools. `semantic`, `fidelity`, and `approval` are typically agent-driven. `provenance` and `traceability` can be either tool or agent-based depending on the verification complexity.
256
+
232
257
  ### Guardrail
233
258
 
234
259
  A **Guardrail** declares a cross-cutting constraint:
@@ -309,6 +334,7 @@ Design regressions become testable.
309
334
  * **YAML safety linting** for reserved word collision detection across YAML 1.1/1.2
310
335
  * **`x-extensions` declarations** for documenting project-specific custom extension fields
311
336
  * **`resolve --expand-defaults`** to materialize all Zod schema defaults in output
337
+ * **DSL completeness scoring** with 7 dimensions, text/JSON output, and `--threshold` CI gate
312
338
  * **JSON Schema for editor support and external tooling**
313
339
  * **CI-friendly workflow checks**
314
340
 
@@ -843,6 +869,7 @@ npx agent-contracts
843
869
  | `agent-contracts validate [path]` | Validate schema and references |
844
870
  | `agent-contracts lint [path]` | Run semantic lint |
845
871
  | `agent-contracts render` | Render outputs from config |
872
+ | `agent-contracts score [path]` | Calculate DSL completeness score |
846
873
  | `agent-contracts generate guardrails` | Generate guardrail artifacts from bindings |
847
874
  | `agent-contracts check` | Run resolve → validate → lint → render --check |
848
875
 
@@ -857,6 +884,26 @@ If `-c` / `--config` is specified, the DSL path from the config file is used.
857
884
  | `--expand-defaults` | Expand all Zod default values in output. Fields like `required_validations: []`, `tags: []`, and `can_read_artifacts: []` are written explicitly instead of being silently applied by schema defaults. |
858
885
  | `-c, --config <path>` | Path to `agent-contracts.config.yaml` |
859
886
 
887
+ #### `score` options
888
+
889
+ | Option | Description |
890
+ |--------|-------------|
891
+ | `--format <text\|json>` | Output format (default: `text`) |
892
+ | `--threshold <number>` | Minimum score; exit 1 if below (for CI gates) |
893
+ | `-c, --config <path>` | Path to `agent-contracts.config.yaml` |
894
+
895
+ The score command evaluates 7 dimensions:
896
+
897
+ | Dimension | What it measures | Weight |
898
+ |-----------|-----------------|--------|
899
+ | Artifact validation coverage | % of artifacts with non-empty `required_validations` | High |
900
+ | Task validation coverage | % of tasks with at least one entry in `validations` | High |
901
+ | Guardrail policy coverage | % of guardrails referenced by at least one policy rule | Medium |
902
+ | Workflow validation integration | % of blocking validations referenced in workflow steps or tasks | High |
903
+ | Schema completeness | % of optional fields filled (description, rationale, trigger, etc.) | Low |
904
+ | Cross-reference bidirectionality | % of agent↔artifact, agent↔tool refs that are reciprocated | Medium |
905
+ | Guardrail scope resolution | % of guardrail scope entries that resolve to existing entities | Medium |
906
+
860
907
  ### Common usage
861
908
 
862
909
  ````bash
@@ -864,6 +911,9 @@ agent-contracts resolve
864
911
  agent-contracts resolve --expand-defaults --format json
865
912
  agent-contracts validate
866
913
  agent-contracts lint --strict
914
+ agent-contracts score
915
+ agent-contracts score -c agent-contracts.config.yaml --threshold 70
916
+ agent-contracts score --format json
867
917
  agent-contracts render -c agent-contracts.config.yaml
868
918
  agent-contracts render -c agent-contracts.config.yaml --check
869
919
  agent-contracts check -c agent-contracts.config.yaml --strict
@@ -1243,6 +1293,7 @@ Checks:
1243
1293
  * prerequisite readability
1244
1294
  * artifact ownership — `produces_artifact`/`reads_artifact` in execution steps vs. artifact producers/editors/consumers
1245
1295
  * tool commands — `commands[].reads`/`commands[].writes` reference valid artifacts and align with `output_artifacts`
1296
+ * semantic validation phase coverage — warns when `semantic` or `fidelity` validations only appear in late workflow phases (e.g., audit) but not earlier phases (e.g., specify, plan)
1246
1297
  * YAML safety — warns when YAML 1.1 reserved words (`on`, `yes`, `no`, `true`, `false`, etc.) are used in positions where they may be misinterpreted by non-1.2 parsers
1247
1298
  * naming/style issues through Spectral rules
1248
1299
 
@@ -1250,6 +1301,16 @@ Checks:
1250
1301
 
1251
1302
  When `--strict` is passed to `lint` or `check`, warnings are treated as failures (exit code 1). This is particularly relevant for artifact-centric validation rules — empty `required_validations`, orphaned validation wiring, and incomplete task coverage are all warnings that become blocking under `--strict`.
1252
1303
 
1304
+ ### Completeness scoring
1305
+
1306
+ `agent-contracts score` provides a quantitative assessment of the DSL's completeness. While `validate` checks structural correctness (pass/fail) and `lint` checks semantic quality (warnings/errors), `score` produces a **numeric metric** (0–100) covering validation coverage, schema completeness, cross-reference consistency, and more.
1307
+
1308
+ Use `--threshold` in CI to enforce a minimum quality bar:
1309
+
1310
+ ````bash
1311
+ agent-contracts score -c config.yaml --threshold 70
1312
+ ````
1313
+
1253
1314
  ---
1254
1315
 
1255
1316
  ## Best used with runtime frameworks
package/dist/cli.js CHANGED
@@ -50428,7 +50428,7 @@ var require_dist13 = __commonJS({
50428
50428
  import { readFileSync } from "fs";
50429
50429
  import { fileURLToPath } from "url";
50430
50430
  import { resolve as resolve7, dirname as dirname7 } from "path";
50431
- import { Command as Command7 } from "commander";
50431
+ import { Command as Command8 } from "commander";
50432
50432
 
50433
50433
  // src/cli/commands/resolve.ts
50434
50434
  import { Command } from "commander";
@@ -50676,7 +50676,7 @@ var ToolSchema = z9.object({
50676
50676
  import { z as z10 } from "zod";
50677
50677
  var ValidationSchema = z10.object({
50678
50678
  target_artifact: z10.string(),
50679
- kind: z10.enum(["schema", "mechanical", "semantic", "approval"]),
50679
+ kind: z10.enum(["schema", "mechanical", "semantic", "approval", "provenance", "traceability", "fidelity"]),
50680
50680
  executor_type: z10.enum(["tool", "agent"]),
50681
50681
  executor: z10.string(),
50682
50682
  blocking: z10.boolean(),
@@ -52800,6 +52800,68 @@ var taskOutputValidationCompletenessRule = {
52800
52800
  }
52801
52801
  };
52802
52802
 
52803
+ // src/linter/rules/semantic-validation-phase-coverage.ts
52804
+ var SEMANTIC_KINDS = /* @__PURE__ */ new Set(["semantic", "fidelity"]);
52805
+ var semanticValidationPhaseCoverageRule = {
52806
+ id: "semantic-validation-phase-coverage",
52807
+ description: "Semantic and fidelity validations should appear in early workflow phases, not only in late phases.",
52808
+ run(dsl) {
52809
+ const diagnostics = [];
52810
+ const semanticValIds = /* @__PURE__ */ new Set();
52811
+ for (const [valId, val] of Object.entries(dsl.validations)) {
52812
+ if (SEMANTIC_KINDS.has(val.kind)) {
52813
+ semanticValIds.add(valId);
52814
+ }
52815
+ }
52816
+ if (semanticValIds.size === 0) {
52817
+ return diagnostics;
52818
+ }
52819
+ const phaseOrder = dsl.system.default_workflow_order;
52820
+ if (phaseOrder.length < 2) {
52821
+ return diagnostics;
52822
+ }
52823
+ const phaseValidations = /* @__PURE__ */ new Map();
52824
+ for (const phase of phaseOrder) {
52825
+ phaseValidations.set(phase, /* @__PURE__ */ new Set());
52826
+ }
52827
+ for (const [phase, wf] of Object.entries(dsl.workflow)) {
52828
+ if (!phaseValidations.has(phase)) continue;
52829
+ for (const step of wf.steps) {
52830
+ if (step.type === "validation" && semanticValIds.has(step.validation)) {
52831
+ phaseValidations.get(phase).add(step.validation);
52832
+ }
52833
+ }
52834
+ }
52835
+ for (const task of Object.values(dsl.tasks)) {
52836
+ const phase = task.workflow;
52837
+ if (!phaseValidations.has(phase)) continue;
52838
+ for (const valId of task.validations) {
52839
+ if (semanticValIds.has(valId)) {
52840
+ phaseValidations.get(phase).add(valId);
52841
+ }
52842
+ }
52843
+ }
52844
+ const earlyBoundary = Math.ceil(phaseOrder.length / 2);
52845
+ const earlyPhases = phaseOrder.slice(0, earlyBoundary);
52846
+ const latePhases = phaseOrder.slice(earlyBoundary);
52847
+ const earlyHasAny = earlyPhases.some(
52848
+ (p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
52849
+ );
52850
+ const lateWithSemantic = latePhases.filter(
52851
+ (p2) => (phaseValidations.get(p2)?.size ?? 0) > 0
52852
+ );
52853
+ if (!earlyHasAny && lateWithSemantic.length > 0) {
52854
+ diagnostics.push({
52855
+ ruleId: "semantic-validation-phase-coverage",
52856
+ severity: "warning",
52857
+ path: "validations",
52858
+ message: `Semantic validations are only referenced in workflow phases [${lateWithSemantic.join(", ")}]. Consider adding semantic review to earlier phases [${earlyPhases.join(", ")}] to catch issues before implementation.`
52859
+ });
52860
+ }
52861
+ return diagnostics;
52862
+ }
52863
+ };
52864
+
52803
52865
  // src/linter/linter.ts
52804
52866
  var builtinRules = [
52805
52867
  validationCoverageRule,
@@ -52811,7 +52873,8 @@ var builtinRules = [
52811
52873
  guardrailPolicyCoverageRule,
52812
52874
  yamlReservedKeySafetyRule,
52813
52875
  artifactRequiredValidationWiringRule,
52814
- taskOutputValidationCompletenessRule
52876
+ taskOutputValidationCompletenessRule,
52877
+ semanticValidationPhaseCoverageRule
52815
52878
  ];
52816
52879
  function lint(dsl, rules = builtinRules) {
52817
52880
  const diagnostics = [];
@@ -55183,10 +55246,347 @@ var generateGuardrailsCommand = new Command6("generate").description("Generate g
55183
55246
  }
55184
55247
  );
55185
55248
 
55249
+ // src/cli/commands/score.ts
55250
+ import { Command as Command7 } from "commander";
55251
+
55252
+ // src/scorer/scorer.ts
55253
+ function pct(n3, d2) {
55254
+ return d2 === 0 ? 100 : Math.round(n3 / d2 * 100);
55255
+ }
55256
+ function artifactValidationCoverage(dsl) {
55257
+ const entries = Object.entries(dsl.artifacts);
55258
+ const total = entries.length;
55259
+ const covered = entries.filter(
55260
+ ([, a3]) => a3.required_validations.length > 0
55261
+ ).length;
55262
+ const missing = entries.filter(([, a3]) => a3.required_validations.length === 0).map(([id]) => id);
55263
+ return {
55264
+ id: "artifact-validation-coverage",
55265
+ label: "Artifact validation coverage",
55266
+ score: covered,
55267
+ total,
55268
+ percent: pct(covered, total),
55269
+ weight: 3,
55270
+ recommendations: missing.length > 0 ? [`${missing.length} artifacts missing required_validations: ${missing.join(", ")}`] : []
55271
+ };
55272
+ }
55273
+ function taskValidationCoverage(dsl) {
55274
+ const entries = Object.entries(dsl.tasks);
55275
+ const total = entries.length;
55276
+ const covered = entries.filter(([, t3]) => t3.validations.length > 0).length;
55277
+ const missing = entries.filter(([, t3]) => t3.validations.length === 0).map(([id]) => id);
55278
+ return {
55279
+ id: "task-validation-coverage",
55280
+ label: "Task validation coverage",
55281
+ score: covered,
55282
+ total,
55283
+ percent: pct(covered, total),
55284
+ weight: 3,
55285
+ recommendations: missing.length > 0 ? [`${missing.length} tasks missing validations: ${missing.join(", ")}`] : []
55286
+ };
55287
+ }
55288
+ function guardrailPolicyCoverage(dsl) {
55289
+ const guardrailIds = Object.keys(dsl.guardrails);
55290
+ const total = guardrailIds.length;
55291
+ const referenced = /* @__PURE__ */ new Set();
55292
+ for (const policy of Object.values(dsl.guardrail_policies)) {
55293
+ for (const rule of policy.rules) {
55294
+ referenced.add(rule.guardrail);
55295
+ }
55296
+ }
55297
+ const covered = guardrailIds.filter((id) => referenced.has(id)).length;
55298
+ const missing = guardrailIds.filter((id) => !referenced.has(id));
55299
+ return {
55300
+ id: "guardrail-policy-coverage",
55301
+ label: "Guardrail policy coverage",
55302
+ score: covered,
55303
+ total,
55304
+ percent: pct(covered, total),
55305
+ weight: 2,
55306
+ recommendations: missing.length > 0 ? [`${missing.length} guardrails not in any policy: ${missing.join(", ")}`] : []
55307
+ };
55308
+ }
55309
+ function workflowValidationIntegration(dsl) {
55310
+ const blockingValidations = Object.entries(dsl.validations).filter(
55311
+ ([, v2]) => v2.blocking
55312
+ );
55313
+ const total = blockingValidations.length;
55314
+ const referenced = /* @__PURE__ */ new Set();
55315
+ for (const wf of Object.values(dsl.workflow)) {
55316
+ for (const step of wf.steps) {
55317
+ if (step.type === "validation") {
55318
+ referenced.add(step.validation);
55319
+ }
55320
+ }
55321
+ }
55322
+ for (const task of Object.values(dsl.tasks)) {
55323
+ for (const valId of task.validations) {
55324
+ referenced.add(valId);
55325
+ }
55326
+ }
55327
+ const covered = blockingValidations.filter(
55328
+ ([id]) => referenced.has(id)
55329
+ ).length;
55330
+ const missing = blockingValidations.filter(([id]) => !referenced.has(id)).map(([id]) => id);
55331
+ return {
55332
+ id: "workflow-validation-integration",
55333
+ label: "Workflow validation integration",
55334
+ score: covered,
55335
+ total,
55336
+ percent: pct(covered, total),
55337
+ weight: 3,
55338
+ recommendations: missing.length > 0 ? [`${missing.length} blocking validations not wired: ${missing.join(", ")}`] : []
55339
+ };
55340
+ }
55341
+ var OPTIONAL_ENTITY_FIELDS = {
55342
+ agents: {
55343
+ section: "agents",
55344
+ fields: ["responsibilities", "constraints", "rules"]
55345
+ },
55346
+ tasks: {
55347
+ section: "tasks",
55348
+ fields: ["description", "responsibilities", "execution_steps", "completion_criteria"]
55349
+ },
55350
+ artifacts: {
55351
+ section: "artifacts",
55352
+ fields: ["description", "visibility"]
55353
+ },
55354
+ tools: {
55355
+ section: "tools",
55356
+ fields: ["description", "commands"]
55357
+ },
55358
+ workflow: {
55359
+ section: "workflow",
55360
+ fields: ["description", "trigger", "entry_conditions"]
55361
+ },
55362
+ guardrails: {
55363
+ section: "guardrails",
55364
+ fields: ["rationale", "tags"]
55365
+ }
55366
+ };
55367
+ function hasNonEmpty(obj, field) {
55368
+ const val = obj[field];
55369
+ if (val === void 0 || val === null) return false;
55370
+ if (typeof val === "string") return val.length > 0;
55371
+ if (Array.isArray(val)) return val.length > 0;
55372
+ return true;
55373
+ }
55374
+ function schemaCompleteness(dsl) {
55375
+ let totalSlots = 0;
55376
+ let filledSlots = 0;
55377
+ const lowSections = [];
55378
+ for (const [sectionKey, meta] of Object.entries(OPTIONAL_ENTITY_FIELDS)) {
55379
+ const entities = dsl[meta.section];
55380
+ if (!entities) continue;
55381
+ let sectionTotal = 0;
55382
+ let sectionFilled = 0;
55383
+ for (const entity of Object.values(entities)) {
55384
+ for (const field of meta.fields) {
55385
+ sectionTotal++;
55386
+ totalSlots++;
55387
+ if (hasNonEmpty(entity, field)) {
55388
+ sectionFilled++;
55389
+ filledSlots++;
55390
+ }
55391
+ }
55392
+ }
55393
+ if (sectionTotal > 0 && pct(sectionFilled, sectionTotal) < 50) {
55394
+ lowSections.push(sectionKey);
55395
+ }
55396
+ }
55397
+ return {
55398
+ id: "schema-completeness",
55399
+ label: "Schema completeness",
55400
+ score: filledSlots,
55401
+ total: totalSlots,
55402
+ percent: pct(filledSlots, totalSlots),
55403
+ weight: 1,
55404
+ recommendations: lowSections.length > 0 ? [`Low optional field coverage in: ${lowSections.join(", ")}`] : []
55405
+ };
55406
+ }
55407
+ function crossReferenceBidirectionality(dsl) {
55408
+ let totalChecks = 0;
55409
+ let passedChecks = 0;
55410
+ const issues = [];
55411
+ for (const [agentId, agent] of Object.entries(dsl.agents)) {
55412
+ for (const toolId of agent.can_execute_tools) {
55413
+ totalChecks++;
55414
+ const tool = dsl.tools[toolId];
55415
+ if (tool && tool.invokable_by.includes(agentId)) {
55416
+ passedChecks++;
55417
+ } else {
55418
+ issues.push(`agent ${agentId} \u2192 tool ${toolId}`);
55419
+ }
55420
+ }
55421
+ for (const artId of agent.can_write_artifacts) {
55422
+ totalChecks++;
55423
+ const art = dsl.artifacts[artId];
55424
+ if (art && (art.producers.includes(agentId) || art.editors.includes(agentId))) {
55425
+ passedChecks++;
55426
+ } else {
55427
+ issues.push(`agent ${agentId} \u2192 artifact ${artId} (write)`);
55428
+ }
55429
+ }
55430
+ for (const artId of agent.can_read_artifacts) {
55431
+ totalChecks++;
55432
+ const art = dsl.artifacts[artId];
55433
+ if (art && (art.consumers.includes(agentId) || art.producers.includes(agentId) || art.editors.includes(agentId))) {
55434
+ passedChecks++;
55435
+ } else {
55436
+ issues.push(`agent ${agentId} \u2192 artifact ${artId} (read)`);
55437
+ }
55438
+ }
55439
+ }
55440
+ for (const [toolId, tool] of Object.entries(dsl.tools)) {
55441
+ for (const agentId of tool.invokable_by) {
55442
+ totalChecks++;
55443
+ const agent = dsl.agents[agentId];
55444
+ if (agent && agent.can_execute_tools.includes(toolId)) {
55445
+ passedChecks++;
55446
+ } else {
55447
+ issues.push(`tool ${toolId} \u2192 agent ${agentId}`);
55448
+ }
55449
+ }
55450
+ }
55451
+ return {
55452
+ id: "cross-reference-bidirectionality",
55453
+ label: "Cross-reference bidirectionality",
55454
+ score: passedChecks,
55455
+ total: totalChecks,
55456
+ percent: pct(passedChecks, totalChecks),
55457
+ weight: 2,
55458
+ recommendations: issues.length > 0 ? [`${issues.length} unreciprocated cross-references: ${issues.slice(0, 5).join(", ")}${issues.length > 5 ? `, ... (${issues.length - 5} more)` : ""}`] : []
55459
+ };
55460
+ }
55461
+ function guardrailScopeResolution(dsl) {
55462
+ let totalRefs = 0;
55463
+ let resolvedRefs = 0;
55464
+ const unresolved = [];
55465
+ const sectionMap = {
55466
+ agents: dsl.agents,
55467
+ tasks: dsl.tasks,
55468
+ tools: dsl.tools,
55469
+ artifacts: dsl.artifacts,
55470
+ workflows: dsl.workflow
55471
+ };
55472
+ for (const [guardrailId, guardrail] of Object.entries(dsl.guardrails)) {
55473
+ const scope = guardrail.scope;
55474
+ for (const [scopeKey, entityIds] of Object.entries(scope)) {
55475
+ if (!Array.isArray(entityIds)) continue;
55476
+ const section = sectionMap[scopeKey];
55477
+ if (!section) continue;
55478
+ for (const entityId of entityIds) {
55479
+ totalRefs++;
55480
+ if (section[entityId]) {
55481
+ resolvedRefs++;
55482
+ } else {
55483
+ unresolved.push(`${guardrailId}.scope.${scopeKey}: ${entityId}`);
55484
+ }
55485
+ }
55486
+ }
55487
+ }
55488
+ return {
55489
+ id: "guardrail-scope-resolution",
55490
+ label: "Guardrail scope resolution",
55491
+ score: resolvedRefs,
55492
+ total: totalRefs,
55493
+ percent: pct(resolvedRefs, totalRefs),
55494
+ weight: 2,
55495
+ recommendations: unresolved.length > 0 ? [`${unresolved.length} unresolved scope refs: ${unresolved.join(", ")}`] : []
55496
+ };
55497
+ }
55498
+ function score(dsl) {
55499
+ const dimensions = [
55500
+ artifactValidationCoverage(dsl),
55501
+ taskValidationCoverage(dsl),
55502
+ guardrailPolicyCoverage(dsl),
55503
+ workflowValidationIntegration(dsl),
55504
+ schemaCompleteness(dsl),
55505
+ crossReferenceBidirectionality(dsl),
55506
+ guardrailScopeResolution(dsl)
55507
+ ];
55508
+ const totalWeight = dimensions.reduce((s2, d2) => s2 + d2.weight, 0);
55509
+ const weightedSum = dimensions.reduce(
55510
+ (s2, d2) => s2 + d2.percent * d2.weight,
55511
+ 0
55512
+ );
55513
+ const overall = totalWeight === 0 ? 100 : Math.round(weightedSum / totalWeight);
55514
+ return { overall, dimensions };
55515
+ }
55516
+
55517
+ // src/cli/commands/score.ts
55518
+ var DIR_DEFAULT4 = "agent-contracts.yaml";
55519
+ function formatText(result) {
55520
+ const lines = [];
55521
+ lines.push(`DSL Completeness Score: ${result.overall}/100`);
55522
+ lines.push("");
55523
+ for (const d2 of result.dimensions) {
55524
+ const detail = d2.total > 0 ? ` (${d2.score}/${d2.total} ${d2.id.split("-")[0]})` : "";
55525
+ lines.push(
55526
+ ` ${d2.label.padEnd(40)} ${String(d2.percent).padStart(3)}%${detail}`
55527
+ );
55528
+ }
55529
+ const allRecs = result.dimensions.flatMap((d2) => d2.recommendations);
55530
+ if (allRecs.length > 0) {
55531
+ lines.push("");
55532
+ lines.push("Recommendations:");
55533
+ for (const rec of allRecs) {
55534
+ lines.push(` - ${rec}`);
55535
+ }
55536
+ }
55537
+ return lines.join("\n");
55538
+ }
55539
+ var scoreCommand = new Command7("score").description("Calculate DSL completeness score").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT4).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").option("--threshold <number>", "Minimum score (exit 1 if below)", void 0).action(
55540
+ async (dir, opts) => {
55541
+ try {
55542
+ const config = await loadConfig(opts.config);
55543
+ const dslPath = resolveDslPath(dir, DIR_DEFAULT4, config);
55544
+ const resolved = await resolve4(dslPath);
55545
+ const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
55546
+ const schemaResult = validateSchema(data);
55547
+ if (!schemaResult.success) {
55548
+ const issues = schemaResult.diagnostics.map((d2) => ` ${d2.path}: ${d2.message}`).join("\n");
55549
+ process.stderr.write(`Schema validation failed:
55550
+ ${issues}
55551
+ `);
55552
+ process.exit(1);
55553
+ }
55554
+ const result = score(schemaResult.data);
55555
+ if (opts.format === "json") {
55556
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
55557
+ } else {
55558
+ process.stdout.write(formatText(result) + "\n");
55559
+ }
55560
+ if (opts.threshold !== void 0) {
55561
+ const threshold = parseInt(opts.threshold, 10);
55562
+ if (isNaN(threshold)) {
55563
+ process.stderr.write(
55564
+ `Error: --threshold must be a number, got "${opts.threshold}"
55565
+ `
55566
+ );
55567
+ process.exit(1);
55568
+ }
55569
+ if (result.overall < threshold) {
55570
+ process.stderr.write(
55571
+ `Score ${result.overall} is below threshold ${threshold}
55572
+ `
55573
+ );
55574
+ process.exit(1);
55575
+ }
55576
+ }
55577
+ } catch (err) {
55578
+ const msg = err instanceof Error ? err.message : String(err);
55579
+ process.stderr.write(`Error: ${msg}
55580
+ `);
55581
+ process.exit(1);
55582
+ }
55583
+ }
55584
+ );
55585
+
55186
55586
  // src/cli/cli.ts
55187
55587
  var __dirname = dirname7(fileURLToPath(import.meta.url));
55188
55588
  var pkg = JSON.parse(readFileSync(resolve7(__dirname, "../package.json"), "utf8"));
55189
- var program = new Command7();
55589
+ var program = new Command8();
55190
55590
  program.name("agent-contracts").description("Agent contracts tooling \u2014 validate, lint, render DSL files").version(pkg.version);
55191
55591
  program.addCommand(resolveCommand);
55192
55592
  program.addCommand(validateCommand);
@@ -55194,6 +55594,7 @@ program.addCommand(lintCommand);
55194
55594
  program.addCommand(renderCommand);
55195
55595
  program.addCommand(checkCommand);
55196
55596
  program.addCommand(generateGuardrailsCommand);
55597
+ program.addCommand(scoreCommand);
55197
55598
  program.parse();
55198
55599
  /*! Bundled license information:
55199
55600