agent-contracts 0.11.5 → 0.11.7
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 +42 -1
- package/dist/cli.js +450 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +376 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,6 +309,7 @@ Design regressions become testable.
|
|
|
309
309
|
* **YAML safety linting** for reserved word collision detection across YAML 1.1/1.2
|
|
310
310
|
* **`x-extensions` declarations** for documenting project-specific custom extension fields
|
|
311
311
|
* **`resolve --expand-defaults`** to materialize all Zod schema defaults in output
|
|
312
|
+
* **DSL completeness scoring** with 7 dimensions, text/JSON output, and `--threshold` CI gate
|
|
312
313
|
* **JSON Schema for editor support and external tooling**
|
|
313
314
|
* **CI-friendly workflow checks**
|
|
314
315
|
|
|
@@ -843,6 +844,7 @@ npx agent-contracts
|
|
|
843
844
|
| `agent-contracts validate [path]` | Validate schema and references |
|
|
844
845
|
| `agent-contracts lint [path]` | Run semantic lint |
|
|
845
846
|
| `agent-contracts render` | Render outputs from config |
|
|
847
|
+
| `agent-contracts score [path]` | Calculate DSL completeness score |
|
|
846
848
|
| `agent-contracts generate guardrails` | Generate guardrail artifacts from bindings |
|
|
847
849
|
| `agent-contracts check` | Run resolve → validate → lint → render --check |
|
|
848
850
|
|
|
@@ -857,6 +859,26 @@ If `-c` / `--config` is specified, the DSL path from the config file is used.
|
|
|
857
859
|
| `--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
860
|
| `-c, --config <path>` | Path to `agent-contracts.config.yaml` |
|
|
859
861
|
|
|
862
|
+
#### `score` options
|
|
863
|
+
|
|
864
|
+
| Option | Description |
|
|
865
|
+
|--------|-------------|
|
|
866
|
+
| `--format <text\|json>` | Output format (default: `text`) |
|
|
867
|
+
| `--threshold <number>` | Minimum score; exit 1 if below (for CI gates) |
|
|
868
|
+
| `-c, --config <path>` | Path to `agent-contracts.config.yaml` |
|
|
869
|
+
|
|
870
|
+
The score command evaluates 7 dimensions:
|
|
871
|
+
|
|
872
|
+
| Dimension | What it measures | Weight |
|
|
873
|
+
|-----------|-----------------|--------|
|
|
874
|
+
| Artifact validation coverage | % of artifacts with non-empty `required_validations` | High |
|
|
875
|
+
| Task validation coverage | % of tasks with at least one entry in `validations` | High |
|
|
876
|
+
| Guardrail policy coverage | % of guardrails referenced by at least one policy rule | Medium |
|
|
877
|
+
| Workflow validation integration | % of blocking validations referenced in workflow steps or tasks | High |
|
|
878
|
+
| Schema completeness | % of optional fields filled (description, rationale, trigger, etc.) | Low |
|
|
879
|
+
| Cross-reference bidirectionality | % of agent↔artifact, agent↔tool refs that are reciprocated | Medium |
|
|
880
|
+
| Guardrail scope resolution | % of guardrail scope entries that resolve to existing entities | Medium |
|
|
881
|
+
|
|
860
882
|
### Common usage
|
|
861
883
|
|
|
862
884
|
````bash
|
|
@@ -864,6 +886,9 @@ agent-contracts resolve
|
|
|
864
886
|
agent-contracts resolve --expand-defaults --format json
|
|
865
887
|
agent-contracts validate
|
|
866
888
|
agent-contracts lint --strict
|
|
889
|
+
agent-contracts score
|
|
890
|
+
agent-contracts score -c agent-contracts.config.yaml --threshold 70
|
|
891
|
+
agent-contracts score --format json
|
|
867
892
|
agent-contracts render -c agent-contracts.config.yaml
|
|
868
893
|
agent-contracts render -c agent-contracts.config.yaml --check
|
|
869
894
|
agent-contracts check -c agent-contracts.config.yaml --strict
|
|
@@ -1234,7 +1259,9 @@ Checks:
|
|
|
1234
1259
|
Checks:
|
|
1235
1260
|
|
|
1236
1261
|
* bidirectional consistency
|
|
1237
|
-
* validation coverage
|
|
1262
|
+
* validation coverage — warns when artifacts lack validations or have empty `required_validations` (fails under `--strict`)
|
|
1263
|
+
* artifact-required-validation wiring — verifies every entry in `artifact.required_validations` exists, targets the correct artifact, and is referenced in a workflow step or task
|
|
1264
|
+
* task-output-validation completeness — checks that tasks producing artifacts (via `execution_steps.produces_artifact` or agent `can_write_artifacts`) cover those artifacts' `required_validations`
|
|
1238
1265
|
* workflow graph completeness
|
|
1239
1266
|
* merge integrity
|
|
1240
1267
|
* read-only write violations
|
|
@@ -1244,6 +1271,20 @@ Checks:
|
|
|
1244
1271
|
* 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
|
|
1245
1272
|
* naming/style issues through Spectral rules
|
|
1246
1273
|
|
|
1274
|
+
#### `--strict` mode
|
|
1275
|
+
|
|
1276
|
+
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`.
|
|
1277
|
+
|
|
1278
|
+
### Completeness scoring
|
|
1279
|
+
|
|
1280
|
+
`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.
|
|
1281
|
+
|
|
1282
|
+
Use `--threshold` in CI to enforce a minimum quality bar:
|
|
1283
|
+
|
|
1284
|
+
````bash
|
|
1285
|
+
agent-contracts score -c config.yaml --threshold 70
|
|
1286
|
+
````
|
|
1287
|
+
|
|
1247
1288
|
---
|
|
1248
1289
|
|
|
1249
1290
|
## 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";
|
|
@@ -52288,6 +52288,7 @@ var validationCoverageRule = {
|
|
|
52288
52288
|
}
|
|
52289
52289
|
validationsByArtifact.get(val.target_artifact).add(val.kind);
|
|
52290
52290
|
}
|
|
52291
|
+
const strictArtifactTypes = ["code", "config", "schema"];
|
|
52291
52292
|
for (const [artId, art] of Object.entries(dsl.artifacts)) {
|
|
52292
52293
|
const kinds = validationsByArtifact.get(artId);
|
|
52293
52294
|
if (!kinds || kinds.size === 0) {
|
|
@@ -52299,8 +52300,7 @@ var validationCoverageRule = {
|
|
|
52299
52300
|
});
|
|
52300
52301
|
continue;
|
|
52301
52302
|
}
|
|
52302
|
-
|
|
52303
|
-
if (mechanicalTypes.includes(art.type) && !kinds.has("mechanical") && !kinds.has("schema")) {
|
|
52303
|
+
if (strictArtifactTypes.includes(art.type) && !kinds.has("mechanical") && !kinds.has("schema")) {
|
|
52304
52304
|
diagnostics.push({
|
|
52305
52305
|
ruleId: "validation-coverage",
|
|
52306
52306
|
severity: "warning",
|
|
@@ -52309,6 +52309,16 @@ var validationCoverageRule = {
|
|
|
52309
52309
|
});
|
|
52310
52310
|
}
|
|
52311
52311
|
}
|
|
52312
|
+
for (const [artId, art] of Object.entries(dsl.artifacts)) {
|
|
52313
|
+
if (art.required_validations.length === 0) {
|
|
52314
|
+
diagnostics.push({
|
|
52315
|
+
ruleId: "validation-coverage",
|
|
52316
|
+
severity: "warning",
|
|
52317
|
+
path: `artifacts.${artId}`,
|
|
52318
|
+
message: `Artifact "${artId}" has empty required_validations`
|
|
52319
|
+
});
|
|
52320
|
+
}
|
|
52321
|
+
}
|
|
52312
52322
|
const referencedValidations = /* @__PURE__ */ new Set();
|
|
52313
52323
|
for (const wf of Object.values(dsl.workflow)) {
|
|
52314
52324
|
for (const step of wf.steps) {
|
|
@@ -52695,6 +52705,101 @@ var yamlReservedKeySafetyRule = {
|
|
|
52695
52705
|
}
|
|
52696
52706
|
};
|
|
52697
52707
|
|
|
52708
|
+
// src/linter/rules/artifact-required-validation-wiring.ts
|
|
52709
|
+
var artifactRequiredValidationWiringRule = {
|
|
52710
|
+
id: "artifact-required-validation-wiring",
|
|
52711
|
+
description: "Every validation listed in artifact.required_validations must exist, target the artifact, and be referenced in a workflow step or task.",
|
|
52712
|
+
run(dsl) {
|
|
52713
|
+
const diagnostics = [];
|
|
52714
|
+
const referencedValidations = /* @__PURE__ */ new Set();
|
|
52715
|
+
for (const wf of Object.values(dsl.workflow)) {
|
|
52716
|
+
for (const step of wf.steps) {
|
|
52717
|
+
if (step.type === "validation") {
|
|
52718
|
+
referencedValidations.add(step.validation);
|
|
52719
|
+
}
|
|
52720
|
+
}
|
|
52721
|
+
}
|
|
52722
|
+
for (const task of Object.values(dsl.tasks)) {
|
|
52723
|
+
for (const valId of task.validations) {
|
|
52724
|
+
referencedValidations.add(valId);
|
|
52725
|
+
}
|
|
52726
|
+
}
|
|
52727
|
+
for (const [artId, art] of Object.entries(dsl.artifacts)) {
|
|
52728
|
+
for (const reqValId of art.required_validations) {
|
|
52729
|
+
const val = dsl.validations[reqValId];
|
|
52730
|
+
if (!val) {
|
|
52731
|
+
diagnostics.push({
|
|
52732
|
+
ruleId: "artifact-required-validation-wiring",
|
|
52733
|
+
severity: "error",
|
|
52734
|
+
path: `artifacts.${artId}`,
|
|
52735
|
+
message: `required_validation "${reqValId}" does not exist in validations`
|
|
52736
|
+
});
|
|
52737
|
+
continue;
|
|
52738
|
+
}
|
|
52739
|
+
if (val.target_artifact !== artId) {
|
|
52740
|
+
diagnostics.push({
|
|
52741
|
+
ruleId: "artifact-required-validation-wiring",
|
|
52742
|
+
severity: "error",
|
|
52743
|
+
path: `artifacts.${artId}`,
|
|
52744
|
+
message: `required_validation "${reqValId}" has target_artifact "${val.target_artifact}" instead of "${artId}"`
|
|
52745
|
+
});
|
|
52746
|
+
}
|
|
52747
|
+
if (!referencedValidations.has(reqValId)) {
|
|
52748
|
+
diagnostics.push({
|
|
52749
|
+
ruleId: "artifact-required-validation-wiring",
|
|
52750
|
+
severity: "warning",
|
|
52751
|
+
path: `artifacts.${artId}`,
|
|
52752
|
+
message: `required_validation "${reqValId}" is defined but not referenced in any workflow step or task`
|
|
52753
|
+
});
|
|
52754
|
+
}
|
|
52755
|
+
}
|
|
52756
|
+
}
|
|
52757
|
+
return diagnostics;
|
|
52758
|
+
}
|
|
52759
|
+
};
|
|
52760
|
+
|
|
52761
|
+
// src/linter/rules/task-output-validation-completeness.ts
|
|
52762
|
+
var taskOutputValidationCompletenessRule = {
|
|
52763
|
+
id: "task-output-validation-completeness",
|
|
52764
|
+
description: "Tasks producing artifacts should cover those artifacts' required_validations in their validations list.",
|
|
52765
|
+
run(dsl) {
|
|
52766
|
+
const diagnostics = [];
|
|
52767
|
+
for (const [taskId, task] of Object.entries(dsl.tasks)) {
|
|
52768
|
+
const producedArtifacts = /* @__PURE__ */ new Set();
|
|
52769
|
+
if (task.execution_steps) {
|
|
52770
|
+
for (const step of task.execution_steps) {
|
|
52771
|
+
if (step.produces_artifact) {
|
|
52772
|
+
producedArtifacts.add(step.produces_artifact);
|
|
52773
|
+
}
|
|
52774
|
+
}
|
|
52775
|
+
}
|
|
52776
|
+
const agent = dsl.agents[task.target_agent];
|
|
52777
|
+
if (agent) {
|
|
52778
|
+
for (const artId of agent.can_write_artifacts) {
|
|
52779
|
+
producedArtifacts.add(artId);
|
|
52780
|
+
}
|
|
52781
|
+
}
|
|
52782
|
+
const taskValidations = new Set(task.validations);
|
|
52783
|
+
for (const artId of producedArtifacts) {
|
|
52784
|
+
const art = dsl.artifacts[artId];
|
|
52785
|
+
if (!art || art.required_validations.length === 0) continue;
|
|
52786
|
+
const missing = art.required_validations.filter(
|
|
52787
|
+
(v2) => !taskValidations.has(v2)
|
|
52788
|
+
);
|
|
52789
|
+
if (missing.length > 0) {
|
|
52790
|
+
diagnostics.push({
|
|
52791
|
+
ruleId: "task-output-validation-completeness",
|
|
52792
|
+
severity: "warning",
|
|
52793
|
+
path: `tasks.${taskId}`,
|
|
52794
|
+
message: `Task produces artifact "${artId}" which requires validations [${missing.join(", ")}] but task.validations does not include them`
|
|
52795
|
+
});
|
|
52796
|
+
}
|
|
52797
|
+
}
|
|
52798
|
+
}
|
|
52799
|
+
return diagnostics;
|
|
52800
|
+
}
|
|
52801
|
+
};
|
|
52802
|
+
|
|
52698
52803
|
// src/linter/linter.ts
|
|
52699
52804
|
var builtinRules = [
|
|
52700
52805
|
validationCoverageRule,
|
|
@@ -52704,7 +52809,9 @@ var builtinRules = [
|
|
|
52704
52809
|
artifactOwnershipRule,
|
|
52705
52810
|
toolCommandsRule,
|
|
52706
52811
|
guardrailPolicyCoverageRule,
|
|
52707
|
-
yamlReservedKeySafetyRule
|
|
52812
|
+
yamlReservedKeySafetyRule,
|
|
52813
|
+
artifactRequiredValidationWiringRule,
|
|
52814
|
+
taskOutputValidationCompletenessRule
|
|
52708
52815
|
];
|
|
52709
52816
|
function lint(dsl, rules = builtinRules) {
|
|
52710
52817
|
const diagnostics = [];
|
|
@@ -55076,10 +55183,347 @@ var generateGuardrailsCommand = new Command6("generate").description("Generate g
|
|
|
55076
55183
|
}
|
|
55077
55184
|
);
|
|
55078
55185
|
|
|
55186
|
+
// src/cli/commands/score.ts
|
|
55187
|
+
import { Command as Command7 } from "commander";
|
|
55188
|
+
|
|
55189
|
+
// src/scorer/scorer.ts
|
|
55190
|
+
function pct(n3, d2) {
|
|
55191
|
+
return d2 === 0 ? 100 : Math.round(n3 / d2 * 100);
|
|
55192
|
+
}
|
|
55193
|
+
function artifactValidationCoverage(dsl) {
|
|
55194
|
+
const entries = Object.entries(dsl.artifacts);
|
|
55195
|
+
const total = entries.length;
|
|
55196
|
+
const covered = entries.filter(
|
|
55197
|
+
([, a3]) => a3.required_validations.length > 0
|
|
55198
|
+
).length;
|
|
55199
|
+
const missing = entries.filter(([, a3]) => a3.required_validations.length === 0).map(([id]) => id);
|
|
55200
|
+
return {
|
|
55201
|
+
id: "artifact-validation-coverage",
|
|
55202
|
+
label: "Artifact validation coverage",
|
|
55203
|
+
score: covered,
|
|
55204
|
+
total,
|
|
55205
|
+
percent: pct(covered, total),
|
|
55206
|
+
weight: 3,
|
|
55207
|
+
recommendations: missing.length > 0 ? [`${missing.length} artifacts missing required_validations: ${missing.join(", ")}`] : []
|
|
55208
|
+
};
|
|
55209
|
+
}
|
|
55210
|
+
function taskValidationCoverage(dsl) {
|
|
55211
|
+
const entries = Object.entries(dsl.tasks);
|
|
55212
|
+
const total = entries.length;
|
|
55213
|
+
const covered = entries.filter(([, t3]) => t3.validations.length > 0).length;
|
|
55214
|
+
const missing = entries.filter(([, t3]) => t3.validations.length === 0).map(([id]) => id);
|
|
55215
|
+
return {
|
|
55216
|
+
id: "task-validation-coverage",
|
|
55217
|
+
label: "Task validation coverage",
|
|
55218
|
+
score: covered,
|
|
55219
|
+
total,
|
|
55220
|
+
percent: pct(covered, total),
|
|
55221
|
+
weight: 3,
|
|
55222
|
+
recommendations: missing.length > 0 ? [`${missing.length} tasks missing validations: ${missing.join(", ")}`] : []
|
|
55223
|
+
};
|
|
55224
|
+
}
|
|
55225
|
+
function guardrailPolicyCoverage(dsl) {
|
|
55226
|
+
const guardrailIds = Object.keys(dsl.guardrails);
|
|
55227
|
+
const total = guardrailIds.length;
|
|
55228
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
55229
|
+
for (const policy of Object.values(dsl.guardrail_policies)) {
|
|
55230
|
+
for (const rule of policy.rules) {
|
|
55231
|
+
referenced.add(rule.guardrail);
|
|
55232
|
+
}
|
|
55233
|
+
}
|
|
55234
|
+
const covered = guardrailIds.filter((id) => referenced.has(id)).length;
|
|
55235
|
+
const missing = guardrailIds.filter((id) => !referenced.has(id));
|
|
55236
|
+
return {
|
|
55237
|
+
id: "guardrail-policy-coverage",
|
|
55238
|
+
label: "Guardrail policy coverage",
|
|
55239
|
+
score: covered,
|
|
55240
|
+
total,
|
|
55241
|
+
percent: pct(covered, total),
|
|
55242
|
+
weight: 2,
|
|
55243
|
+
recommendations: missing.length > 0 ? [`${missing.length} guardrails not in any policy: ${missing.join(", ")}`] : []
|
|
55244
|
+
};
|
|
55245
|
+
}
|
|
55246
|
+
function workflowValidationIntegration(dsl) {
|
|
55247
|
+
const blockingValidations = Object.entries(dsl.validations).filter(
|
|
55248
|
+
([, v2]) => v2.blocking
|
|
55249
|
+
);
|
|
55250
|
+
const total = blockingValidations.length;
|
|
55251
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
55252
|
+
for (const wf of Object.values(dsl.workflow)) {
|
|
55253
|
+
for (const step of wf.steps) {
|
|
55254
|
+
if (step.type === "validation") {
|
|
55255
|
+
referenced.add(step.validation);
|
|
55256
|
+
}
|
|
55257
|
+
}
|
|
55258
|
+
}
|
|
55259
|
+
for (const task of Object.values(dsl.tasks)) {
|
|
55260
|
+
for (const valId of task.validations) {
|
|
55261
|
+
referenced.add(valId);
|
|
55262
|
+
}
|
|
55263
|
+
}
|
|
55264
|
+
const covered = blockingValidations.filter(
|
|
55265
|
+
([id]) => referenced.has(id)
|
|
55266
|
+
).length;
|
|
55267
|
+
const missing = blockingValidations.filter(([id]) => !referenced.has(id)).map(([id]) => id);
|
|
55268
|
+
return {
|
|
55269
|
+
id: "workflow-validation-integration",
|
|
55270
|
+
label: "Workflow validation integration",
|
|
55271
|
+
score: covered,
|
|
55272
|
+
total,
|
|
55273
|
+
percent: pct(covered, total),
|
|
55274
|
+
weight: 3,
|
|
55275
|
+
recommendations: missing.length > 0 ? [`${missing.length} blocking validations not wired: ${missing.join(", ")}`] : []
|
|
55276
|
+
};
|
|
55277
|
+
}
|
|
55278
|
+
var OPTIONAL_ENTITY_FIELDS = {
|
|
55279
|
+
agents: {
|
|
55280
|
+
section: "agents",
|
|
55281
|
+
fields: ["responsibilities", "constraints", "rules"]
|
|
55282
|
+
},
|
|
55283
|
+
tasks: {
|
|
55284
|
+
section: "tasks",
|
|
55285
|
+
fields: ["description", "responsibilities", "execution_steps", "completion_criteria"]
|
|
55286
|
+
},
|
|
55287
|
+
artifacts: {
|
|
55288
|
+
section: "artifacts",
|
|
55289
|
+
fields: ["description", "visibility"]
|
|
55290
|
+
},
|
|
55291
|
+
tools: {
|
|
55292
|
+
section: "tools",
|
|
55293
|
+
fields: ["description", "commands"]
|
|
55294
|
+
},
|
|
55295
|
+
workflow: {
|
|
55296
|
+
section: "workflow",
|
|
55297
|
+
fields: ["description", "trigger", "entry_conditions"]
|
|
55298
|
+
},
|
|
55299
|
+
guardrails: {
|
|
55300
|
+
section: "guardrails",
|
|
55301
|
+
fields: ["rationale", "tags"]
|
|
55302
|
+
}
|
|
55303
|
+
};
|
|
55304
|
+
function hasNonEmpty(obj, field) {
|
|
55305
|
+
const val = obj[field];
|
|
55306
|
+
if (val === void 0 || val === null) return false;
|
|
55307
|
+
if (typeof val === "string") return val.length > 0;
|
|
55308
|
+
if (Array.isArray(val)) return val.length > 0;
|
|
55309
|
+
return true;
|
|
55310
|
+
}
|
|
55311
|
+
function schemaCompleteness(dsl) {
|
|
55312
|
+
let totalSlots = 0;
|
|
55313
|
+
let filledSlots = 0;
|
|
55314
|
+
const lowSections = [];
|
|
55315
|
+
for (const [sectionKey, meta] of Object.entries(OPTIONAL_ENTITY_FIELDS)) {
|
|
55316
|
+
const entities = dsl[meta.section];
|
|
55317
|
+
if (!entities) continue;
|
|
55318
|
+
let sectionTotal = 0;
|
|
55319
|
+
let sectionFilled = 0;
|
|
55320
|
+
for (const entity of Object.values(entities)) {
|
|
55321
|
+
for (const field of meta.fields) {
|
|
55322
|
+
sectionTotal++;
|
|
55323
|
+
totalSlots++;
|
|
55324
|
+
if (hasNonEmpty(entity, field)) {
|
|
55325
|
+
sectionFilled++;
|
|
55326
|
+
filledSlots++;
|
|
55327
|
+
}
|
|
55328
|
+
}
|
|
55329
|
+
}
|
|
55330
|
+
if (sectionTotal > 0 && pct(sectionFilled, sectionTotal) < 50) {
|
|
55331
|
+
lowSections.push(sectionKey);
|
|
55332
|
+
}
|
|
55333
|
+
}
|
|
55334
|
+
return {
|
|
55335
|
+
id: "schema-completeness",
|
|
55336
|
+
label: "Schema completeness",
|
|
55337
|
+
score: filledSlots,
|
|
55338
|
+
total: totalSlots,
|
|
55339
|
+
percent: pct(filledSlots, totalSlots),
|
|
55340
|
+
weight: 1,
|
|
55341
|
+
recommendations: lowSections.length > 0 ? [`Low optional field coverage in: ${lowSections.join(", ")}`] : []
|
|
55342
|
+
};
|
|
55343
|
+
}
|
|
55344
|
+
function crossReferenceBidirectionality(dsl) {
|
|
55345
|
+
let totalChecks = 0;
|
|
55346
|
+
let passedChecks = 0;
|
|
55347
|
+
const issues = [];
|
|
55348
|
+
for (const [agentId, agent] of Object.entries(dsl.agents)) {
|
|
55349
|
+
for (const toolId of agent.can_execute_tools) {
|
|
55350
|
+
totalChecks++;
|
|
55351
|
+
const tool = dsl.tools[toolId];
|
|
55352
|
+
if (tool && tool.invokable_by.includes(agentId)) {
|
|
55353
|
+
passedChecks++;
|
|
55354
|
+
} else {
|
|
55355
|
+
issues.push(`agent ${agentId} \u2192 tool ${toolId}`);
|
|
55356
|
+
}
|
|
55357
|
+
}
|
|
55358
|
+
for (const artId of agent.can_write_artifacts) {
|
|
55359
|
+
totalChecks++;
|
|
55360
|
+
const art = dsl.artifacts[artId];
|
|
55361
|
+
if (art && (art.producers.includes(agentId) || art.editors.includes(agentId))) {
|
|
55362
|
+
passedChecks++;
|
|
55363
|
+
} else {
|
|
55364
|
+
issues.push(`agent ${agentId} \u2192 artifact ${artId} (write)`);
|
|
55365
|
+
}
|
|
55366
|
+
}
|
|
55367
|
+
for (const artId of agent.can_read_artifacts) {
|
|
55368
|
+
totalChecks++;
|
|
55369
|
+
const art = dsl.artifacts[artId];
|
|
55370
|
+
if (art && (art.consumers.includes(agentId) || art.producers.includes(agentId) || art.editors.includes(agentId))) {
|
|
55371
|
+
passedChecks++;
|
|
55372
|
+
} else {
|
|
55373
|
+
issues.push(`agent ${agentId} \u2192 artifact ${artId} (read)`);
|
|
55374
|
+
}
|
|
55375
|
+
}
|
|
55376
|
+
}
|
|
55377
|
+
for (const [toolId, tool] of Object.entries(dsl.tools)) {
|
|
55378
|
+
for (const agentId of tool.invokable_by) {
|
|
55379
|
+
totalChecks++;
|
|
55380
|
+
const agent = dsl.agents[agentId];
|
|
55381
|
+
if (agent && agent.can_execute_tools.includes(toolId)) {
|
|
55382
|
+
passedChecks++;
|
|
55383
|
+
} else {
|
|
55384
|
+
issues.push(`tool ${toolId} \u2192 agent ${agentId}`);
|
|
55385
|
+
}
|
|
55386
|
+
}
|
|
55387
|
+
}
|
|
55388
|
+
return {
|
|
55389
|
+
id: "cross-reference-bidirectionality",
|
|
55390
|
+
label: "Cross-reference bidirectionality",
|
|
55391
|
+
score: passedChecks,
|
|
55392
|
+
total: totalChecks,
|
|
55393
|
+
percent: pct(passedChecks, totalChecks),
|
|
55394
|
+
weight: 2,
|
|
55395
|
+
recommendations: issues.length > 0 ? [`${issues.length} unreciprocated cross-references: ${issues.slice(0, 5).join(", ")}${issues.length > 5 ? `, ... (${issues.length - 5} more)` : ""}`] : []
|
|
55396
|
+
};
|
|
55397
|
+
}
|
|
55398
|
+
function guardrailScopeResolution(dsl) {
|
|
55399
|
+
let totalRefs = 0;
|
|
55400
|
+
let resolvedRefs = 0;
|
|
55401
|
+
const unresolved = [];
|
|
55402
|
+
const sectionMap = {
|
|
55403
|
+
agents: dsl.agents,
|
|
55404
|
+
tasks: dsl.tasks,
|
|
55405
|
+
tools: dsl.tools,
|
|
55406
|
+
artifacts: dsl.artifacts,
|
|
55407
|
+
workflows: dsl.workflow
|
|
55408
|
+
};
|
|
55409
|
+
for (const [guardrailId, guardrail] of Object.entries(dsl.guardrails)) {
|
|
55410
|
+
const scope = guardrail.scope;
|
|
55411
|
+
for (const [scopeKey, entityIds] of Object.entries(scope)) {
|
|
55412
|
+
if (!Array.isArray(entityIds)) continue;
|
|
55413
|
+
const section = sectionMap[scopeKey];
|
|
55414
|
+
if (!section) continue;
|
|
55415
|
+
for (const entityId of entityIds) {
|
|
55416
|
+
totalRefs++;
|
|
55417
|
+
if (section[entityId]) {
|
|
55418
|
+
resolvedRefs++;
|
|
55419
|
+
} else {
|
|
55420
|
+
unresolved.push(`${guardrailId}.scope.${scopeKey}: ${entityId}`);
|
|
55421
|
+
}
|
|
55422
|
+
}
|
|
55423
|
+
}
|
|
55424
|
+
}
|
|
55425
|
+
return {
|
|
55426
|
+
id: "guardrail-scope-resolution",
|
|
55427
|
+
label: "Guardrail scope resolution",
|
|
55428
|
+
score: resolvedRefs,
|
|
55429
|
+
total: totalRefs,
|
|
55430
|
+
percent: pct(resolvedRefs, totalRefs),
|
|
55431
|
+
weight: 2,
|
|
55432
|
+
recommendations: unresolved.length > 0 ? [`${unresolved.length} unresolved scope refs: ${unresolved.join(", ")}`] : []
|
|
55433
|
+
};
|
|
55434
|
+
}
|
|
55435
|
+
function score(dsl) {
|
|
55436
|
+
const dimensions = [
|
|
55437
|
+
artifactValidationCoverage(dsl),
|
|
55438
|
+
taskValidationCoverage(dsl),
|
|
55439
|
+
guardrailPolicyCoverage(dsl),
|
|
55440
|
+
workflowValidationIntegration(dsl),
|
|
55441
|
+
schemaCompleteness(dsl),
|
|
55442
|
+
crossReferenceBidirectionality(dsl),
|
|
55443
|
+
guardrailScopeResolution(dsl)
|
|
55444
|
+
];
|
|
55445
|
+
const totalWeight = dimensions.reduce((s2, d2) => s2 + d2.weight, 0);
|
|
55446
|
+
const weightedSum = dimensions.reduce(
|
|
55447
|
+
(s2, d2) => s2 + d2.percent * d2.weight,
|
|
55448
|
+
0
|
|
55449
|
+
);
|
|
55450
|
+
const overall = totalWeight === 0 ? 100 : Math.round(weightedSum / totalWeight);
|
|
55451
|
+
return { overall, dimensions };
|
|
55452
|
+
}
|
|
55453
|
+
|
|
55454
|
+
// src/cli/commands/score.ts
|
|
55455
|
+
var DIR_DEFAULT4 = "agent-contracts.yaml";
|
|
55456
|
+
function formatText(result) {
|
|
55457
|
+
const lines = [];
|
|
55458
|
+
lines.push(`DSL Completeness Score: ${result.overall}/100`);
|
|
55459
|
+
lines.push("");
|
|
55460
|
+
for (const d2 of result.dimensions) {
|
|
55461
|
+
const detail = d2.total > 0 ? ` (${d2.score}/${d2.total} ${d2.id.split("-")[0]})` : "";
|
|
55462
|
+
lines.push(
|
|
55463
|
+
` ${d2.label.padEnd(40)} ${String(d2.percent).padStart(3)}%${detail}`
|
|
55464
|
+
);
|
|
55465
|
+
}
|
|
55466
|
+
const allRecs = result.dimensions.flatMap((d2) => d2.recommendations);
|
|
55467
|
+
if (allRecs.length > 0) {
|
|
55468
|
+
lines.push("");
|
|
55469
|
+
lines.push("Recommendations:");
|
|
55470
|
+
for (const rec of allRecs) {
|
|
55471
|
+
lines.push(` - ${rec}`);
|
|
55472
|
+
}
|
|
55473
|
+
}
|
|
55474
|
+
return lines.join("\n");
|
|
55475
|
+
}
|
|
55476
|
+
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(
|
|
55477
|
+
async (dir, opts) => {
|
|
55478
|
+
try {
|
|
55479
|
+
const config = await loadConfig(opts.config);
|
|
55480
|
+
const dslPath = resolveDslPath(dir, DIR_DEFAULT4, config);
|
|
55481
|
+
const resolved = await resolve4(dslPath);
|
|
55482
|
+
const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
|
|
55483
|
+
const schemaResult = validateSchema(data);
|
|
55484
|
+
if (!schemaResult.success) {
|
|
55485
|
+
const issues = schemaResult.diagnostics.map((d2) => ` ${d2.path}: ${d2.message}`).join("\n");
|
|
55486
|
+
process.stderr.write(`Schema validation failed:
|
|
55487
|
+
${issues}
|
|
55488
|
+
`);
|
|
55489
|
+
process.exit(1);
|
|
55490
|
+
}
|
|
55491
|
+
const result = score(schemaResult.data);
|
|
55492
|
+
if (opts.format === "json") {
|
|
55493
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
55494
|
+
} else {
|
|
55495
|
+
process.stdout.write(formatText(result) + "\n");
|
|
55496
|
+
}
|
|
55497
|
+
if (opts.threshold !== void 0) {
|
|
55498
|
+
const threshold = parseInt(opts.threshold, 10);
|
|
55499
|
+
if (isNaN(threshold)) {
|
|
55500
|
+
process.stderr.write(
|
|
55501
|
+
`Error: --threshold must be a number, got "${opts.threshold}"
|
|
55502
|
+
`
|
|
55503
|
+
);
|
|
55504
|
+
process.exit(1);
|
|
55505
|
+
}
|
|
55506
|
+
if (result.overall < threshold) {
|
|
55507
|
+
process.stderr.write(
|
|
55508
|
+
`Score ${result.overall} is below threshold ${threshold}
|
|
55509
|
+
`
|
|
55510
|
+
);
|
|
55511
|
+
process.exit(1);
|
|
55512
|
+
}
|
|
55513
|
+
}
|
|
55514
|
+
} catch (err) {
|
|
55515
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55516
|
+
process.stderr.write(`Error: ${msg}
|
|
55517
|
+
`);
|
|
55518
|
+
process.exit(1);
|
|
55519
|
+
}
|
|
55520
|
+
}
|
|
55521
|
+
);
|
|
55522
|
+
|
|
55079
55523
|
// src/cli/cli.ts
|
|
55080
55524
|
var __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
55081
55525
|
var pkg = JSON.parse(readFileSync(resolve7(__dirname, "../package.json"), "utf8"));
|
|
55082
|
-
var program = new
|
|
55526
|
+
var program = new Command8();
|
|
55083
55527
|
program.name("agent-contracts").description("Agent contracts tooling \u2014 validate, lint, render DSL files").version(pkg.version);
|
|
55084
55528
|
program.addCommand(resolveCommand);
|
|
55085
55529
|
program.addCommand(validateCommand);
|
|
@@ -55087,6 +55531,7 @@ program.addCommand(lintCommand);
|
|
|
55087
55531
|
program.addCommand(renderCommand);
|
|
55088
55532
|
program.addCommand(checkCommand);
|
|
55089
55533
|
program.addCommand(generateGuardrailsCommand);
|
|
55534
|
+
program.addCommand(scoreCommand);
|
|
55090
55535
|
program.parse();
|
|
55091
55536
|
/*! Bundled license information:
|
|
55092
55537
|
|