agent-contracts 0.11.4 → 0.11.6
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 +86 -12
- package/dist/cli.js +227 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +34 -4
- package/dist/index.js +226 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,12 +221,13 @@ A **Workflow** defines a phase-level execution sequence:
|
|
|
221
221
|
* `entry_conditions`
|
|
222
222
|
* `trigger`
|
|
223
223
|
* `external_participants` — actors/participants outside the agent system (e.g., User, external advisory)
|
|
224
|
-
* ordered steps (
|
|
224
|
+
* ordered steps (delegate, gate, validation, decision)
|
|
225
225
|
|
|
226
226
|
Workflow steps support additional properties:
|
|
227
227
|
|
|
228
228
|
* `group` — consecutive steps with the same group are rendered as `par` (parallel) blocks in sequence diagrams
|
|
229
|
-
* `retry` (
|
|
229
|
+
* `retry` (delegate steps) — defines a conditional retry loop with `condition`, `fix_task`, and optional `revalidate_task`
|
|
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
|
|
230
231
|
|
|
231
232
|
### Guardrail
|
|
232
233
|
|
|
@@ -305,6 +306,9 @@ Design regressions become testable.
|
|
|
305
306
|
* **Software bindings** (DI) for tool-specific guardrail implementation (Cursor, Git, GitHub)
|
|
306
307
|
* **Guardrail generation** from DSL + policy + bindings via `generate guardrails`
|
|
307
308
|
* **Flexible file splitting** via `$ref` (replacement), `$refs` (import + deep-merge), and JSON Pointer `$ref` (in-document)
|
|
309
|
+
* **YAML safety linting** for reserved word collision detection across YAML 1.1/1.2
|
|
310
|
+
* **`x-extensions` declarations** for documenting project-specific custom extension fields
|
|
311
|
+
* **`resolve --expand-defaults`** to materialize all Zod schema defaults in output
|
|
308
312
|
* **JSON Schema for editor support and external tooling**
|
|
309
313
|
* **CI-friendly workflow checks**
|
|
310
314
|
|
|
@@ -342,6 +346,16 @@ guardrails: {}
|
|
|
342
346
|
guardrail_policies: {}
|
|
343
347
|
components:
|
|
344
348
|
schemas: {}
|
|
349
|
+
|
|
350
|
+
# Optional: declare project-specific x-* extensions
|
|
351
|
+
x-extensions:
|
|
352
|
+
x-flags:
|
|
353
|
+
type: array
|
|
354
|
+
items: string
|
|
355
|
+
description: "CLI flags for tool commands"
|
|
356
|
+
x-check-script:
|
|
357
|
+
type: string
|
|
358
|
+
description: "Path to hook check script"
|
|
345
359
|
````
|
|
346
360
|
|
|
347
361
|
This makes definitions easy to merge, extend, and reference by stable identifiers.
|
|
@@ -572,6 +586,23 @@ tasks:
|
|
|
572
586
|
`x-` prefixed custom properties work at any nesting level — including inside
|
|
573
587
|
`execution_steps`, `rules`, `workflow.steps`, and other nested objects.
|
|
574
588
|
|
|
589
|
+
### `x-extensions` declarations
|
|
590
|
+
|
|
591
|
+
Projects can optionally declare their custom `x-*` extension fields in the DSL using `x-extensions`. This makes extensions discoverable and self-documenting:
|
|
592
|
+
|
|
593
|
+
````yaml
|
|
594
|
+
x-extensions:
|
|
595
|
+
x-flags:
|
|
596
|
+
type: array
|
|
597
|
+
items: string
|
|
598
|
+
description: "CLI flags for tool commands"
|
|
599
|
+
x-check-script:
|
|
600
|
+
type: string
|
|
601
|
+
description: "Path to hook check script"
|
|
602
|
+
````
|
|
603
|
+
|
|
604
|
+
Each key must start with `x-` (validated at schema level). The `type`, `items`, and `description` fields describe the expected shape. This declaration is informational today; future versions may validate `x-*` field values against their declared schemas.
|
|
605
|
+
|
|
575
606
|
---
|
|
576
607
|
|
|
577
608
|
## Example: Artifact definition
|
|
@@ -602,21 +633,20 @@ workflow:
|
|
|
602
633
|
- User story or feature request received
|
|
603
634
|
trigger: "User invokes /speckit.specify or asks to create a feature spec."
|
|
604
635
|
steps:
|
|
605
|
-
- type:
|
|
606
|
-
handoff_kind: delegation
|
|
636
|
+
- type: delegate
|
|
607
637
|
task: specify-feature
|
|
608
638
|
from_agent: main-architect
|
|
609
639
|
- type: validation
|
|
610
|
-
|
|
640
|
+
validation: spec-semantic-review
|
|
611
641
|
- type: decision
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
- label: revise
|
|
617
|
-
retry_from_step: 0
|
|
642
|
+
routing_key: evidence-gate-verdict.verdict
|
|
643
|
+
branches:
|
|
644
|
+
PASS: [plan]
|
|
645
|
+
REVISE: [specify-feature]
|
|
618
646
|
````
|
|
619
647
|
|
|
648
|
+
Decision steps use `routing_key` to specify the field that determines branching. The legacy `on` field is still accepted but deprecated — see [YAML safety](#yaml-safety) below.
|
|
649
|
+
|
|
620
650
|
---
|
|
621
651
|
|
|
622
652
|
## Example: Handoff type definition
|
|
@@ -819,10 +849,19 @@ npx agent-contracts
|
|
|
819
849
|
The `[path]` argument defaults to `agent-contracts.yaml` in the current directory.
|
|
820
850
|
If `-c` / `--config` is specified, the DSL path from the config file is used.
|
|
821
851
|
|
|
852
|
+
#### `resolve` options
|
|
853
|
+
|
|
854
|
+
| Option | Description |
|
|
855
|
+
|--------|-------------|
|
|
856
|
+
| `--format <text\|json>` | Output format (default: `text`) |
|
|
857
|
+
| `--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
|
+
| `-c, --config <path>` | Path to `agent-contracts.config.yaml` |
|
|
859
|
+
|
|
822
860
|
### Common usage
|
|
823
861
|
|
|
824
862
|
````bash
|
|
825
863
|
agent-contracts resolve
|
|
864
|
+
agent-contracts resolve --expand-defaults --format json
|
|
826
865
|
agent-contracts validate
|
|
827
866
|
agent-contracts lint --strict
|
|
828
867
|
agent-contracts render -c agent-contracts.config.yaml
|
|
@@ -1153,6 +1192,34 @@ Checks:
|
|
|
1153
1192
|
|
|
1154
1193
|
Custom properties with `x-` prefix are allowed on any object in the DSL — top-level entities (agents, tasks, artifacts, …), nested objects (rules, execution steps, workflow steps, …), and the root DSL itself.
|
|
1155
1194
|
|
|
1195
|
+
### YAML safety
|
|
1196
|
+
|
|
1197
|
+
The DSL is expressed in YAML, which introduces risks from YAML 1.1's implicit type coercion. The `yaml-reserved-key-safety` lint rule warns when reserved words appear in positions that may be misinterpreted by non-1.2 parsers.
|
|
1198
|
+
|
|
1199
|
+
The most notable case is the `on` field in decision steps. In YAML 1.1, bare `on` as a mapping key is interpreted as boolean `true`. While `agent-contracts` uses a YAML 1.2 parser internally, DSL consumers (CI tools, editors, other parsers) may use YAML 1.1 parsers.
|
|
1200
|
+
|
|
1201
|
+
To address this:
|
|
1202
|
+
|
|
1203
|
+
* Decision steps now support `routing_key` as the preferred field name (replacing `on`)
|
|
1204
|
+
* The legacy `on` field is still accepted for backward compatibility but triggers a lint warning
|
|
1205
|
+
* Branch keys like `yes`, `no`, `true`, `false` also trigger warnings
|
|
1206
|
+
|
|
1207
|
+
````yaml
|
|
1208
|
+
# Preferred — safe across all YAML versions
|
|
1209
|
+
- type: decision
|
|
1210
|
+
routing_key: evidence-gate-verdict.verdict
|
|
1211
|
+
branches:
|
|
1212
|
+
PASS: [release]
|
|
1213
|
+
FAIL: [fix-violations]
|
|
1214
|
+
|
|
1215
|
+
# Deprecated — works but triggers yaml-reserved-key-safety warning
|
|
1216
|
+
- type: decision
|
|
1217
|
+
on: evidence-gate-verdict.verdict
|
|
1218
|
+
branches:
|
|
1219
|
+
PASS: [release]
|
|
1220
|
+
FAIL: [fix-violations]
|
|
1221
|
+
````
|
|
1222
|
+
|
|
1156
1223
|
### Reference integrity
|
|
1157
1224
|
|
|
1158
1225
|
Checks:
|
|
@@ -1167,15 +1234,22 @@ Checks:
|
|
|
1167
1234
|
Checks:
|
|
1168
1235
|
|
|
1169
1236
|
* bidirectional consistency
|
|
1170
|
-
* validation coverage
|
|
1237
|
+
* validation coverage — warns when artifacts lack validations or have empty `required_validations` (fails under `--strict`)
|
|
1238
|
+
* 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
|
|
1239
|
+
* task-output-validation completeness — checks that tasks producing artifacts (via `execution_steps.produces_artifact` or agent `can_write_artifacts`) cover those artifacts' `required_validations`
|
|
1171
1240
|
* workflow graph completeness
|
|
1172
1241
|
* merge integrity
|
|
1173
1242
|
* read-only write violations
|
|
1174
1243
|
* prerequisite readability
|
|
1175
1244
|
* artifact ownership — `produces_artifact`/`reads_artifact` in execution steps vs. artifact producers/editors/consumers
|
|
1176
1245
|
* tool commands — `commands[].reads`/`commands[].writes` reference valid artifacts and align with `output_artifacts`
|
|
1246
|
+
* 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
|
|
1177
1247
|
* naming/style issues through Spectral rules
|
|
1178
1248
|
|
|
1249
|
+
#### `--strict` mode
|
|
1250
|
+
|
|
1251
|
+
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
|
+
|
|
1179
1253
|
---
|
|
1180
1254
|
|
|
1181
1255
|
## Best used with runtime frameworks
|
package/dist/cli.js
CHANGED
|
@@ -50728,7 +50728,9 @@ var WorkflowValidationStepSchema = z11.object({
|
|
|
50728
50728
|
var WorkflowDecisionStepSchema = z11.object({
|
|
50729
50729
|
type: z11.literal("decision"),
|
|
50730
50730
|
description: z11.string().optional(),
|
|
50731
|
-
on
|
|
50731
|
+
/** @deprecated Use `routing_key` instead. `on` is kept for backward compatibility. */
|
|
50732
|
+
on: z11.string().optional(),
|
|
50733
|
+
routing_key: z11.string().optional(),
|
|
50732
50734
|
branches: z11.record(z11.string(), z11.array(z11.string())),
|
|
50733
50735
|
group: z11.string().optional()
|
|
50734
50736
|
}).passthrough();
|
|
@@ -50751,6 +50753,11 @@ var WorkflowSchema = z11.object({
|
|
|
50751
50753
|
var ComponentsSchema = z12.object({
|
|
50752
50754
|
schemas: z12.record(z12.string(), z12.record(z12.string(), z12.any())).default({})
|
|
50753
50755
|
}).passthrough();
|
|
50756
|
+
var XExtensionDeclSchema = z12.object({
|
|
50757
|
+
type: z12.string(),
|
|
50758
|
+
items: z12.string().optional(),
|
|
50759
|
+
description: z12.string().optional()
|
|
50760
|
+
});
|
|
50754
50761
|
var DslSchema = z12.object({
|
|
50755
50762
|
version: z12.literal(1),
|
|
50756
50763
|
extends: z12.string().optional(),
|
|
@@ -50765,7 +50772,8 @@ var DslSchema = z12.object({
|
|
|
50765
50772
|
policies: z12.record(z12.string(), PolicySchema).default({}),
|
|
50766
50773
|
guardrails: z12.record(z12.string(), GuardrailSchema).default({}),
|
|
50767
50774
|
guardrail_policies: z12.record(z12.string(), GuardrailPolicySchema).default({}),
|
|
50768
|
-
components: ComponentsSchema.default({ schemas: {} })
|
|
50775
|
+
components: ComponentsSchema.default({ schemas: {} }),
|
|
50776
|
+
"x-extensions": z12.record(z12.string(), XExtensionDeclSchema).optional()
|
|
50769
50777
|
}).passthrough();
|
|
50770
50778
|
|
|
50771
50779
|
// src/schema/json-schema-utils.ts
|
|
@@ -51620,6 +51628,13 @@ function substituteVars(data, vars) {
|
|
|
51620
51628
|
return walk(data, vars);
|
|
51621
51629
|
}
|
|
51622
51630
|
|
|
51631
|
+
// src/resolver/expand-defaults.ts
|
|
51632
|
+
function expandDefaults(data) {
|
|
51633
|
+
const result = DslSchema.safeParse(data);
|
|
51634
|
+
if (!result.success) return data;
|
|
51635
|
+
return JSON.parse(JSON.stringify(result.data));
|
|
51636
|
+
}
|
|
51637
|
+
|
|
51623
51638
|
// src/config/binding-merger.ts
|
|
51624
51639
|
function isRecord3(v2) {
|
|
51625
51640
|
return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
|
|
@@ -51760,12 +51775,15 @@ ${issues}`,
|
|
|
51760
51775
|
// src/cli/commands/resolve.ts
|
|
51761
51776
|
import { stringify } from "yaml";
|
|
51762
51777
|
var DIR_DEFAULT = "agent-contracts.yaml";
|
|
51763
|
-
var resolveCommand = new Command("resolve").description("Resolve DSL (load + merge extends) and output YAML").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").action(async (dir, opts) => {
|
|
51778
|
+
var resolveCommand = new Command("resolve").description("Resolve DSL (load + merge extends) and output YAML").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").option("--expand-defaults", "Expand all Zod default values in output", false).action(async (dir, opts) => {
|
|
51764
51779
|
try {
|
|
51765
51780
|
const config = await loadConfig(opts.config);
|
|
51766
51781
|
const dslPath = resolveDslPath(dir, DIR_DEFAULT, config);
|
|
51767
51782
|
const result = await resolve4(dslPath);
|
|
51768
|
-
|
|
51783
|
+
let data = config?.vars ? substituteVars(result.data, config.vars) : result.data;
|
|
51784
|
+
if (opts.expandDefaults) {
|
|
51785
|
+
data = expandDefaults(data);
|
|
51786
|
+
}
|
|
51769
51787
|
if (opts.format === "json") {
|
|
51770
51788
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
51771
51789
|
} else {
|
|
@@ -51844,6 +51862,47 @@ function checkCustomPropsRecursive(data, schema, path) {
|
|
|
51844
51862
|
}
|
|
51845
51863
|
return [];
|
|
51846
51864
|
}
|
|
51865
|
+
function checkXExtensionsKeys(data) {
|
|
51866
|
+
const extensions = data["x-extensions"];
|
|
51867
|
+
if (typeof extensions !== "object" || extensions === null) return [];
|
|
51868
|
+
const diagnostics = [];
|
|
51869
|
+
for (const key of Object.keys(extensions)) {
|
|
51870
|
+
if (!key.startsWith("x-")) {
|
|
51871
|
+
diagnostics.push({
|
|
51872
|
+
path: `x-extensions.${key}`,
|
|
51873
|
+
message: `Extension key "${key}" must start with "x-" prefix.`,
|
|
51874
|
+
code: "x-extension-key-prefix"
|
|
51875
|
+
});
|
|
51876
|
+
}
|
|
51877
|
+
}
|
|
51878
|
+
return diagnostics;
|
|
51879
|
+
}
|
|
51880
|
+
function checkDecisionStepRoutingKey(data) {
|
|
51881
|
+
const workflow = data["workflow"];
|
|
51882
|
+
if (typeof workflow !== "object" || workflow === null) return [];
|
|
51883
|
+
const diagnostics = [];
|
|
51884
|
+
for (const [wfKey, wf] of Object.entries(
|
|
51885
|
+
workflow
|
|
51886
|
+
)) {
|
|
51887
|
+
if (typeof wf !== "object" || wf === null) continue;
|
|
51888
|
+
const steps = wf["steps"];
|
|
51889
|
+
if (!Array.isArray(steps)) continue;
|
|
51890
|
+
for (let i3 = 0; i3 < steps.length; i3++) {
|
|
51891
|
+
const step = steps[i3];
|
|
51892
|
+
if (typeof step !== "object" || step === null) continue;
|
|
51893
|
+
const s2 = step;
|
|
51894
|
+
if (s2["type"] !== "decision") continue;
|
|
51895
|
+
if (s2["routing_key"] === void 0 && s2["on"] === void 0) {
|
|
51896
|
+
diagnostics.push({
|
|
51897
|
+
path: `workflow.${wfKey}.steps[${i3}]`,
|
|
51898
|
+
message: 'Decision step requires "routing_key" (or deprecated "on"). Prefer "routing_key".',
|
|
51899
|
+
code: "decision-missing-routing-key"
|
|
51900
|
+
});
|
|
51901
|
+
}
|
|
51902
|
+
}
|
|
51903
|
+
}
|
|
51904
|
+
return diagnostics;
|
|
51905
|
+
}
|
|
51847
51906
|
function validateSchema(data) {
|
|
51848
51907
|
const result = DslSchema.safeParse(data);
|
|
51849
51908
|
if (!result.success) {
|
|
@@ -51858,6 +51917,14 @@ function validateSchema(data) {
|
|
|
51858
51917
|
if (customPropDiagnostics.length > 0) {
|
|
51859
51918
|
return { success: false, diagnostics: customPropDiagnostics };
|
|
51860
51919
|
}
|
|
51920
|
+
const decisionDiagnostics = checkDecisionStepRoutingKey(data);
|
|
51921
|
+
if (decisionDiagnostics.length > 0) {
|
|
51922
|
+
return { success: false, diagnostics: decisionDiagnostics };
|
|
51923
|
+
}
|
|
51924
|
+
const xExtDiagnostics = checkXExtensionsKeys(data);
|
|
51925
|
+
if (xExtDiagnostics.length > 0) {
|
|
51926
|
+
return { success: false, diagnostics: xExtDiagnostics };
|
|
51927
|
+
}
|
|
51861
51928
|
return { success: true, data: result.data, diagnostics: [] };
|
|
51862
51929
|
}
|
|
51863
51930
|
|
|
@@ -52221,6 +52288,7 @@ var validationCoverageRule = {
|
|
|
52221
52288
|
}
|
|
52222
52289
|
validationsByArtifact.get(val.target_artifact).add(val.kind);
|
|
52223
52290
|
}
|
|
52291
|
+
const strictArtifactTypes = ["code", "config", "schema"];
|
|
52224
52292
|
for (const [artId, art] of Object.entries(dsl.artifacts)) {
|
|
52225
52293
|
const kinds = validationsByArtifact.get(artId);
|
|
52226
52294
|
if (!kinds || kinds.size === 0) {
|
|
@@ -52232,8 +52300,7 @@ var validationCoverageRule = {
|
|
|
52232
52300
|
});
|
|
52233
52301
|
continue;
|
|
52234
52302
|
}
|
|
52235
|
-
|
|
52236
|
-
if (mechanicalTypes.includes(art.type) && !kinds.has("mechanical") && !kinds.has("schema")) {
|
|
52303
|
+
if (strictArtifactTypes.includes(art.type) && !kinds.has("mechanical") && !kinds.has("schema")) {
|
|
52237
52304
|
diagnostics.push({
|
|
52238
52305
|
ruleId: "validation-coverage",
|
|
52239
52306
|
severity: "warning",
|
|
@@ -52242,6 +52309,16 @@ var validationCoverageRule = {
|
|
|
52242
52309
|
});
|
|
52243
52310
|
}
|
|
52244
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
|
+
}
|
|
52245
52322
|
const referencedValidations = /* @__PURE__ */ new Set();
|
|
52246
52323
|
for (const wf of Object.values(dsl.workflow)) {
|
|
52247
52324
|
for (const step of wf.steps) {
|
|
@@ -52583,6 +52660,146 @@ var guardrailPolicyCoverageRule = {
|
|
|
52583
52660
|
}
|
|
52584
52661
|
};
|
|
52585
52662
|
|
|
52663
|
+
// src/linter/rules/yaml-reserved-key-safety.ts
|
|
52664
|
+
var YAML_11_RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
52665
|
+
"on",
|
|
52666
|
+
"off",
|
|
52667
|
+
"yes",
|
|
52668
|
+
"no",
|
|
52669
|
+
"true",
|
|
52670
|
+
"false",
|
|
52671
|
+
"y",
|
|
52672
|
+
"n"
|
|
52673
|
+
]);
|
|
52674
|
+
var yamlReservedKeySafetyRule = {
|
|
52675
|
+
id: "yaml-reserved-key-safety",
|
|
52676
|
+
description: "Warns when YAML 1.1 reserved words are used as field values in positions where they may be misinterpreted by non-1.2 parsers",
|
|
52677
|
+
run(dsl) {
|
|
52678
|
+
const diagnostics = [];
|
|
52679
|
+
for (const [wfKey, wf] of Object.entries(dsl.workflow)) {
|
|
52680
|
+
for (let i3 = 0; i3 < wf.steps.length; i3++) {
|
|
52681
|
+
const step = wf.steps[i3];
|
|
52682
|
+
if (step.type !== "decision") continue;
|
|
52683
|
+
if (step.on !== void 0 && step.routing_key === void 0) {
|
|
52684
|
+
diagnostics.push({
|
|
52685
|
+
ruleId: "yaml-reserved-key-safety",
|
|
52686
|
+
severity: "warning",
|
|
52687
|
+
path: `workflow.${wfKey}.steps[${i3}].on`,
|
|
52688
|
+
message: 'Field name "on" is a YAML 1.1 reserved word and may be interpreted as boolean true by some parsers. Use "routing_key" instead.'
|
|
52689
|
+
});
|
|
52690
|
+
}
|
|
52691
|
+
const branchKeys = Object.keys(step.branches);
|
|
52692
|
+
for (const key of branchKeys) {
|
|
52693
|
+
if (YAML_11_RESERVED_KEYS.has(key.toLowerCase())) {
|
|
52694
|
+
diagnostics.push({
|
|
52695
|
+
ruleId: "yaml-reserved-key-safety",
|
|
52696
|
+
severity: "warning",
|
|
52697
|
+
path: `workflow.${wfKey}.steps[${i3}].branches.${key}`,
|
|
52698
|
+
message: `Branch key "${key}" is a YAML 1.1 reserved word and may be interpreted as a boolean by some parsers. Consider quoting or renaming.`
|
|
52699
|
+
});
|
|
52700
|
+
}
|
|
52701
|
+
}
|
|
52702
|
+
}
|
|
52703
|
+
}
|
|
52704
|
+
return diagnostics;
|
|
52705
|
+
}
|
|
52706
|
+
};
|
|
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
|
+
|
|
52586
52803
|
// src/linter/linter.ts
|
|
52587
52804
|
var builtinRules = [
|
|
52588
52805
|
validationCoverageRule,
|
|
@@ -52591,7 +52808,10 @@ var builtinRules = [
|
|
|
52591
52808
|
mergeIntegrityRule,
|
|
52592
52809
|
artifactOwnershipRule,
|
|
52593
52810
|
toolCommandsRule,
|
|
52594
|
-
guardrailPolicyCoverageRule
|
|
52811
|
+
guardrailPolicyCoverageRule,
|
|
52812
|
+
yamlReservedKeySafetyRule,
|
|
52813
|
+
artifactRequiredValidationWiringRule,
|
|
52814
|
+
taskOutputValidationCompletenessRule
|
|
52595
52815
|
];
|
|
52596
52816
|
function lint(dsl, rules = builtinRules) {
|
|
52597
52817
|
const diagnostics = [];
|