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 +61 -0
- package/dist/cli.js +405 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +331 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|