agent-contracts 0.11.2 → 0.11.3
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 +39 -1
- package/dist/cli.js +70 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +72 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -297,7 +297,7 @@ Design regressions become testable.
|
|
|
297
297
|
* **Structured handoff definitions** with formal JSON Schema and `allOf` composition
|
|
298
298
|
* **Reusable schema components** via `components.schemas` and JSON Pointer `$ref`
|
|
299
299
|
* **Artifact ownership and lifecycle modeling**
|
|
300
|
-
* **Config-driven prompt rendering**
|
|
300
|
+
* **Config-driven prompt rendering** with `skip_empty` support for conditional file generation
|
|
301
301
|
* **Variable substitution** via `${vars.xxx}` in DSL values
|
|
302
302
|
* **Inheritance with merge operators via `extends`**
|
|
303
303
|
* **Guardrail definitions** for cross-cutting process constraints
|
|
@@ -865,6 +865,44 @@ This lets you generate static outputs for:
|
|
|
865
865
|
|
|
866
866
|
all from the same resolved DSL.
|
|
867
867
|
|
|
868
|
+
### Render target options
|
|
869
|
+
|
|
870
|
+
Each entry in `renders` supports these fields:
|
|
871
|
+
|
|
872
|
+
| Field | Type | Required | Description |
|
|
873
|
+
|-------|------|----------|-------------|
|
|
874
|
+
| `template` | string | yes | Path to Handlebars template |
|
|
875
|
+
| `context` | string | yes | Context type (see below) |
|
|
876
|
+
| `output` | string | yes | Output file path (supports `{<context>.id}` placeholder) |
|
|
877
|
+
| `include` | string[] | no | Only render these entity IDs (not with `system`) |
|
|
878
|
+
| `exclude` | string[] | no | Skip these entity IDs (not with `system`) |
|
|
879
|
+
| `skip_empty` | boolean | no | When `true`, if the rendered output is empty or whitespace-only, the file is **not written**. If the file already exists, it is **deleted**. |
|
|
880
|
+
|
|
881
|
+
#### `skip_empty` usage
|
|
882
|
+
|
|
883
|
+
`skip_empty` is useful when a single template applies to all entities of a context type, but only some entities produce meaningful output.
|
|
884
|
+
|
|
885
|
+
For example, when using `context: tool` to generate per-tool scripts, tools without an `x-script` property would produce empty files. With `skip_empty: true`, those files are simply not created:
|
|
886
|
+
|
|
887
|
+
````yaml
|
|
888
|
+
renders:
|
|
889
|
+
- template: ./templates/tool-script.sh.hbs
|
|
890
|
+
context: tool
|
|
891
|
+
output: ./output/scripts/{tool.id}.sh
|
|
892
|
+
skip_empty: true
|
|
893
|
+
````
|
|
894
|
+
|
|
895
|
+
````handlebars
|
|
896
|
+
{{!-- tool-script.sh.hbs --}}
|
|
897
|
+
{{#if tool.x-script}}
|
|
898
|
+
{{{tool.x-script}}}
|
|
899
|
+
{{/if}}
|
|
900
|
+
````
|
|
901
|
+
|
|
902
|
+
Tools with `x-script` get a generated script file; tools without it produce no file at all.
|
|
903
|
+
|
|
904
|
+
`skip_empty` also works with `render --check` (drift detection): when the expected output is empty, the check expects the file to **not exist** and reports drift if it does.
|
|
905
|
+
|
|
868
906
|
### Available context types
|
|
869
907
|
|
|
870
908
|
Each `context` type provides a different rendering scope:
|
package/dist/cli.js
CHANGED
|
@@ -50526,7 +50526,8 @@ var BindingOutputSchema = z3.object({
|
|
|
50526
50526
|
inline_template: z3.string().optional(),
|
|
50527
50527
|
mode: z3.enum(["write", "patch"]).default("write"),
|
|
50528
50528
|
group_by: z3.string().optional(),
|
|
50529
|
-
executable: z3.boolean().optional()
|
|
50529
|
+
executable: z3.boolean().optional(),
|
|
50530
|
+
skip_empty: z3.boolean().optional()
|
|
50530
50531
|
}).passthrough().refine(
|
|
50531
50532
|
(data) => !(data.template && data.inline_template),
|
|
50532
50533
|
{ message: "template and inline_template are mutually exclusive" }
|
|
@@ -50860,7 +50861,8 @@ var RenderTargetSchema = z14.object({
|
|
|
50860
50861
|
context: ContextTypeSchema,
|
|
50861
50862
|
output: z14.string(),
|
|
50862
50863
|
include: z14.array(z14.string()).optional(),
|
|
50863
|
-
exclude: z14.array(z14.string()).optional()
|
|
50864
|
+
exclude: z14.array(z14.string()).optional(),
|
|
50865
|
+
skip_empty: z14.boolean().optional()
|
|
50864
50866
|
}).refine(
|
|
50865
50867
|
(data) => !(data.include && data.exclude),
|
|
50866
50868
|
{ message: "include and exclude are mutually exclusive" }
|
|
@@ -53281,7 +53283,7 @@ var lintCommand = new Command3("lint").description("Run semantic lint rules on r
|
|
|
53281
53283
|
import { Command as Command4 } from "commander";
|
|
53282
53284
|
|
|
53283
53285
|
// src/renderer/renderer.ts
|
|
53284
|
-
import { readFile as readFile4, writeFile, mkdir } from "fs/promises";
|
|
53286
|
+
import { readFile as readFile4, writeFile, mkdir, unlink } from "fs/promises";
|
|
53285
53287
|
import { dirname as dirname5 } from "path";
|
|
53286
53288
|
import Handlebars from "handlebars";
|
|
53287
53289
|
|
|
@@ -54397,6 +54399,15 @@ function buildEntityContext(dsl, context, entityId) {
|
|
|
54397
54399
|
async function loadTemplate(templatePath) {
|
|
54398
54400
|
return readFile4(templatePath, "utf8");
|
|
54399
54401
|
}
|
|
54402
|
+
function isEffectivelyEmpty(output) {
|
|
54403
|
+
return output.trim().length === 0;
|
|
54404
|
+
}
|
|
54405
|
+
async function removeIfExists(filePath) {
|
|
54406
|
+
try {
|
|
54407
|
+
await unlink(filePath);
|
|
54408
|
+
} catch {
|
|
54409
|
+
}
|
|
54410
|
+
}
|
|
54400
54411
|
async function renderFromConfig(dsl, renderTargets) {
|
|
54401
54412
|
const outputFiles = [];
|
|
54402
54413
|
for (const target of renderTargets) {
|
|
@@ -54405,6 +54416,10 @@ async function renderFromConfig(dsl, renderTargets) {
|
|
|
54405
54416
|
if (target.context === "system") {
|
|
54406
54417
|
const ctx = buildSystemContext(dsl);
|
|
54407
54418
|
const output = compiled(ctx);
|
|
54419
|
+
if (target.skip_empty && isEffectivelyEmpty(output)) {
|
|
54420
|
+
await removeIfExists(target.output);
|
|
54421
|
+
continue;
|
|
54422
|
+
}
|
|
54408
54423
|
await mkdir(dirname5(target.output), { recursive: true });
|
|
54409
54424
|
await writeFile(target.output, output, "utf8");
|
|
54410
54425
|
outputFiles.push(target.output);
|
|
@@ -54416,6 +54431,10 @@ async function renderFromConfig(dsl, renderTargets) {
|
|
|
54416
54431
|
const ctx = buildEntityContext(dsl, target.context, entityId);
|
|
54417
54432
|
const output = compiled(ctx);
|
|
54418
54433
|
const outputPath = expandOutputPath(target.output, target.context, entityId);
|
|
54434
|
+
if (target.skip_empty && isEffectivelyEmpty(output)) {
|
|
54435
|
+
await removeIfExists(outputPath);
|
|
54436
|
+
continue;
|
|
54437
|
+
}
|
|
54419
54438
|
await mkdir(dirname5(outputPath), { recursive: true });
|
|
54420
54439
|
await writeFile(outputPath, output, "utf8");
|
|
54421
54440
|
outputFiles.push(outputPath);
|
|
@@ -54424,6 +54443,26 @@ async function renderFromConfig(dsl, renderTargets) {
|
|
|
54424
54443
|
}
|
|
54425
54444
|
return outputFiles;
|
|
54426
54445
|
}
|
|
54446
|
+
async function fileExists2(filePath) {
|
|
54447
|
+
try {
|
|
54448
|
+
await readFile4(filePath);
|
|
54449
|
+
return true;
|
|
54450
|
+
} catch {
|
|
54451
|
+
return false;
|
|
54452
|
+
}
|
|
54453
|
+
}
|
|
54454
|
+
function checkExpectedVsExisting(expected, outputPath, skipEmpty, diffs) {
|
|
54455
|
+
if (skipEmpty && isEffectivelyEmpty(expected)) {
|
|
54456
|
+
return fileExists2(outputPath).then((exists) => {
|
|
54457
|
+
if (exists) diffs.push(outputPath);
|
|
54458
|
+
});
|
|
54459
|
+
}
|
|
54460
|
+
return readFile4(outputPath, "utf8").then((existing) => {
|
|
54461
|
+
if (existing !== expected) diffs.push(outputPath);
|
|
54462
|
+
}).catch(() => {
|
|
54463
|
+
diffs.push(outputPath);
|
|
54464
|
+
});
|
|
54465
|
+
}
|
|
54427
54466
|
async function checkDriftFromConfig(dsl, renderTargets) {
|
|
54428
54467
|
const diffs = [];
|
|
54429
54468
|
for (const target of renderTargets) {
|
|
@@ -54432,12 +54471,7 @@ async function checkDriftFromConfig(dsl, renderTargets) {
|
|
|
54432
54471
|
if (target.context === "system") {
|
|
54433
54472
|
const ctx = buildSystemContext(dsl);
|
|
54434
54473
|
const expected = compiled(ctx);
|
|
54435
|
-
|
|
54436
|
-
const existing = await readFile4(target.output, "utf8");
|
|
54437
|
-
if (existing !== expected) diffs.push(target.output);
|
|
54438
|
-
} catch {
|
|
54439
|
-
diffs.push(target.output);
|
|
54440
|
-
}
|
|
54474
|
+
await checkExpectedVsExisting(expected, target.output, target.skip_empty, diffs);
|
|
54441
54475
|
} else {
|
|
54442
54476
|
const section = getDslSection(dsl, target.context);
|
|
54443
54477
|
const allIds = Object.keys(section);
|
|
@@ -54446,12 +54480,7 @@ async function checkDriftFromConfig(dsl, renderTargets) {
|
|
|
54446
54480
|
const ctx = buildEntityContext(dsl, target.context, entityId);
|
|
54447
54481
|
const expected = compiled(ctx);
|
|
54448
54482
|
const outputPath = expandOutputPath(target.output, target.context, entityId);
|
|
54449
|
-
|
|
54450
|
-
const existing = await readFile4(outputPath, "utf8");
|
|
54451
|
-
if (existing !== expected) diffs.push(outputPath);
|
|
54452
|
-
} catch {
|
|
54453
|
-
diffs.push(outputPath);
|
|
54454
|
-
}
|
|
54483
|
+
await checkExpectedVsExisting(expected, outputPath, target.skip_empty, diffs);
|
|
54455
54484
|
}
|
|
54456
54485
|
}
|
|
54457
54486
|
}
|
|
@@ -54609,7 +54638,7 @@ var checkCommand = new Command5("check").description("Run full pipeline: resolve
|
|
|
54609
54638
|
import { Command as Command6 } from "commander";
|
|
54610
54639
|
|
|
54611
54640
|
// src/guardrail-generator/generator.ts
|
|
54612
|
-
import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, chmod } from "fs/promises";
|
|
54641
|
+
import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink as unlink2 } from "fs/promises";
|
|
54613
54642
|
import { resolve as resolve6, dirname as dirname6 } from "path";
|
|
54614
54643
|
import Handlebars2 from "handlebars";
|
|
54615
54644
|
|
|
@@ -54783,6 +54812,7 @@ async function generateGuardrails(options) {
|
|
|
54783
54812
|
});
|
|
54784
54813
|
continue;
|
|
54785
54814
|
}
|
|
54815
|
+
const shouldSkipEmpty = outputDef.skip_empty === true;
|
|
54786
54816
|
if (outputDef.group_by) {
|
|
54787
54817
|
const groupField = outputDef.group_by;
|
|
54788
54818
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -54802,6 +54832,15 @@ async function generateGuardrails(options) {
|
|
|
54802
54832
|
const compiled = Handlebars2.compile(templateContent, { noEscape: true });
|
|
54803
54833
|
const output = compiled(groupCtx);
|
|
54804
54834
|
const groupTarget = resolve6(targetPath, groupKey);
|
|
54835
|
+
if (shouldSkipEmpty && output.trim().length === 0) {
|
|
54836
|
+
if (!dryRun) {
|
|
54837
|
+
try {
|
|
54838
|
+
await unlink2(groupTarget);
|
|
54839
|
+
} catch {
|
|
54840
|
+
}
|
|
54841
|
+
}
|
|
54842
|
+
continue;
|
|
54843
|
+
}
|
|
54805
54844
|
if (!dryRun) {
|
|
54806
54845
|
await mkdir2(dirname6(groupTarget), { recursive: true });
|
|
54807
54846
|
await writeFile2(groupTarget, output, "utf8");
|
|
@@ -54814,14 +54853,23 @@ async function generateGuardrails(options) {
|
|
|
54814
54853
|
} else {
|
|
54815
54854
|
const compiled = Handlebars2.compile(templateContent, { noEscape: true });
|
|
54816
54855
|
const output = compiled(ctx);
|
|
54817
|
-
if (
|
|
54818
|
-
|
|
54819
|
-
|
|
54820
|
-
|
|
54821
|
-
|
|
54856
|
+
if (shouldSkipEmpty && output.trim().length === 0) {
|
|
54857
|
+
if (!dryRun) {
|
|
54858
|
+
try {
|
|
54859
|
+
await unlink2(targetPath);
|
|
54860
|
+
} catch {
|
|
54861
|
+
}
|
|
54862
|
+
}
|
|
54863
|
+
} else {
|
|
54864
|
+
if (!dryRun) {
|
|
54865
|
+
await mkdir2(dirname6(targetPath), { recursive: true });
|
|
54866
|
+
await writeFile2(targetPath, output, "utf8");
|
|
54867
|
+
if (outputDef.executable) {
|
|
54868
|
+
await chmod(targetPath, 493);
|
|
54869
|
+
}
|
|
54822
54870
|
}
|
|
54871
|
+
outputFiles.push(targetPath);
|
|
54823
54872
|
}
|
|
54824
|
-
outputFiles.push(targetPath);
|
|
54825
54873
|
}
|
|
54826
54874
|
}
|
|
54827
54875
|
}
|