agent-contracts 0.11.8 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -998,7 +998,7 @@ Each `context` type provides a different rendering scope:
998
998
 
999
999
  | Context | Scope | Output | Key variables |
1000
1000
  |---------|-------|--------|---------------|
1001
- | `system` | Single file | `output` as-is | `system`, `dsl` |
1001
+ | `system` | Single file | `output` as-is | `system`, `dsl`, `guardrailEnforcement`\*, `bindings`\* |
1002
1002
  | `agent` | Per agent | `{agent.id}` in output path | `agent`, `receivableTasks`, `delegatableTasks`, `relatedArtifacts`, `relatedTools`, `relatedHandoffTypes`, `mergedBehavior`, `dsl` |
1003
1003
  | `task` | Per task | `{task.id}` in output path | `task`, `targetAgent`, `dsl` |
1004
1004
  | `artifact` | Per artifact | `{artifact.id}` in output path | `artifact`, `relatedTools`, `relatedValidations`, `producerAgents`, `consumerAgents`, `editorAgents`, `createdInWorkflows`, `dsl` |
@@ -1027,6 +1027,18 @@ Each `context` type provides a different rendering scope:
1027
1027
  * `producerAgents` / `consumerAgents` / `editorAgents` — resolved agent records
1028
1028
  * `createdInWorkflows` — workflow phases where this artifact is written
1029
1029
 
1030
+ **`system` context** includes binding-aware guardrail enforcement data when `bindings` and `active_guardrail_policy` are configured:
1031
+
1032
+ * `guardrailEnforcement` — array of enforcement entries, each with `guardrail_id`, `description`, `severity`, `action`, scoped entities (`scoped_agents`, `scoped_tasks`, `scoped_workflows`, `scoped_tools`, `scoped_artifacts`), `allow_override`, `override_requires`, `trigger` (from binding matcher type), and `escalation`
1033
+ * `bindings` — array of loaded `SoftwareBinding` objects
1034
+
1035
+ These fields are only populated when the config specifies `bindings` and `active_guardrail_policy`. Existing templates that do not reference these fields are unaffected.
1036
+
1037
+ **Matrix helpers** are available in `context: system` templates:
1038
+
1039
+ * `guardrailCoverageMatrix` — generates a Guardrail Coverage Matrix table (guardrail × severity × action × scoped entities × trigger × override × escalation)
1040
+ * `taskGuardrailMatrix` — generates a Task × Guardrail cross-reference table showing which action applies to each task
1041
+
1030
1042
  **`tool` context** resolves agent and artifact references:
1031
1043
 
1032
1044
  * `invokableAgents` — agents listed in `invokable_by`
package/dist/cli.js CHANGED
@@ -2243,7 +2243,7 @@ var require_lodash = __commonJS({
2243
2243
  } else {
2244
2244
  newValue = [];
2245
2245
  }
2246
- } else if (isPlainObject(srcValue) || isArguments(srcValue)) {
2246
+ } else if (isPlainObject2(srcValue) || isArguments(srcValue)) {
2247
2247
  newValue = objValue;
2248
2248
  if (isArguments(objValue)) {
2249
2249
  newValue = toPlainObject(objValue);
@@ -3207,7 +3207,7 @@ var require_lodash = __commonJS({
3207
3207
  return objValue;
3208
3208
  }
3209
3209
  function customOmitClone(value) {
3210
- return isPlainObject(value) ? undefined2 : value;
3210
+ return isPlainObject2(value) ? undefined2 : value;
3211
3211
  }
3212
3212
  function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
3213
3213
  var isPartial = bitmask & COMPARE_PARTIAL_FLAG, arrLength = array.length, othLength = other.length;
@@ -4708,7 +4708,7 @@ var require_lodash = __commonJS({
4708
4708
  var isBuffer = nativeIsBuffer || stubFalse;
4709
4709
  var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
4710
4710
  function isElement(value) {
4711
- return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
4711
+ return isObjectLike(value) && value.nodeType === 1 && !isPlainObject2(value);
4712
4712
  }
4713
4713
  function isEmpty(value) {
4714
4714
  if (value == null) {
@@ -4744,7 +4744,7 @@ var require_lodash = __commonJS({
4744
4744
  return false;
4745
4745
  }
4746
4746
  var tag = baseGetTag(value);
4747
- return tag == errorTag || tag == domExcTag || typeof value.message == "string" && typeof value.name == "string" && !isPlainObject(value);
4747
+ return tag == errorTag || tag == domExcTag || typeof value.message == "string" && typeof value.name == "string" && !isPlainObject2(value);
4748
4748
  }
4749
4749
  function isFinite2(value) {
4750
4750
  return typeof value == "number" && nativeIsFinite(value);
@@ -4795,7 +4795,7 @@ var require_lodash = __commonJS({
4795
4795
  function isNumber(value) {
4796
4796
  return typeof value == "number" || isObjectLike(value) && baseGetTag(value) == numberTag;
4797
4797
  }
4798
- function isPlainObject(value) {
4798
+ function isPlainObject2(value) {
4799
4799
  if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
4800
4800
  return false;
4801
4801
  }
@@ -5866,7 +5866,7 @@ var require_lodash = __commonJS({
5866
5866
  lodash.isNumber = isNumber;
5867
5867
  lodash.isObject = isObject2;
5868
5868
  lodash.isObjectLike = isObjectLike;
5869
- lodash.isPlainObject = isPlainObject;
5869
+ lodash.isPlainObject = isPlainObject2;
5870
5870
  lodash.isRegExp = isRegExp;
5871
5871
  lodash.isSafeInteger = isSafeInteger;
5872
5872
  lodash.isSet = isSet;
@@ -17506,10 +17506,10 @@ var require_immer_cjs_development = __commonJS({
17506
17506
  function isDraftable(value) {
17507
17507
  var _value$constructor;
17508
17508
  if (!value) return false;
17509
- return isPlainObject(value) || Array.isArray(value) || !!value[DRAFTABLE] || !!((_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor[DRAFTABLE]) || isMap(value) || isSet(value);
17509
+ return isPlainObject2(value) || Array.isArray(value) || !!value[DRAFTABLE] || !!((_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor[DRAFTABLE]) || isMap(value) || isSet(value);
17510
17510
  }
17511
17511
  var objectCtorString = /* @__PURE__ */ Object.prototype.constructor.toString();
17512
- function isPlainObject(value) {
17512
+ function isPlainObject2(value) {
17513
17513
  if (!value || typeof value !== "object") return false;
17514
17514
  var proto = Object.getPrototypeOf(value);
17515
17515
  if (proto === null) {
@@ -40192,10 +40192,10 @@ var require_object_inspect = __commonJS({
40192
40192
  }
40193
40193
  if (!isDate(obj) && !isRegExp(obj)) {
40194
40194
  var ys = arrObjKeys(obj, inspect);
40195
- var isPlainObject = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object;
40195
+ var isPlainObject2 = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object;
40196
40196
  var protoTag = obj instanceof Object ? "" : "null prototype";
40197
- var stringTag = !isPlainObject && toStringTag2 && Object(obj) === obj && toStringTag2 in obj ? $slice.call(toStr(obj), 8, -1) : protoTag ? "Object" : "";
40198
- var constructorTag = isPlainObject || typeof obj.constructor !== "function" ? "" : obj.constructor.name ? obj.constructor.name + " " : "";
40197
+ var stringTag = !isPlainObject2 && toStringTag2 && Object(obj) === obj && toStringTag2 in obj ? $slice.call(toStr(obj), 8, -1) : protoTag ? "Object" : "";
40198
+ var constructorTag = isPlainObject2 || typeof obj.constructor !== "function" ? "" : obj.constructor.name ? obj.constructor.name + " " : "";
40199
40199
  var tag = constructorTag + (stringTag || protoTag ? "[" + $join.call($concat.call([], stringTag || [], protoTag || []), ": ") + "] " : "");
40200
40200
  if (ys.length === 0) {
40201
40201
  return tag + "{}";
@@ -44530,7 +44530,7 @@ var require_lodash3 = __commonJS({
44530
44530
  } else {
44531
44531
  newValue = [];
44532
44532
  }
44533
- } else if (isPlainObject(srcValue) || isArguments(srcValue)) {
44533
+ } else if (isPlainObject2(srcValue) || isArguments(srcValue)) {
44534
44534
  newValue = objValue;
44535
44535
  if (isArguments(objValue)) {
44536
44536
  newValue = toPlainObject(objValue);
@@ -45501,7 +45501,7 @@ var require_lodash3 = __commonJS({
45501
45501
  return objValue;
45502
45502
  }
45503
45503
  function customOmitClone(value) {
45504
- return isPlainObject(value) ? undefined2 : value;
45504
+ return isPlainObject2(value) ? undefined2 : value;
45505
45505
  }
45506
45506
  function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
45507
45507
  var isPartial = bitmask & COMPARE_PARTIAL_FLAG, arrLength = array.length, othLength = other.length;
@@ -47002,7 +47002,7 @@ var require_lodash3 = __commonJS({
47002
47002
  var isBuffer = nativeIsBuffer || stubFalse;
47003
47003
  var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
47004
47004
  function isElement(value) {
47005
- return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
47005
+ return isObjectLike(value) && value.nodeType === 1 && !isPlainObject2(value);
47006
47006
  }
47007
47007
  function isEmpty(value) {
47008
47008
  if (value == null) {
@@ -47038,7 +47038,7 @@ var require_lodash3 = __commonJS({
47038
47038
  return false;
47039
47039
  }
47040
47040
  var tag = baseGetTag(value);
47041
- return tag == errorTag || tag == domExcTag || typeof value.message == "string" && typeof value.name == "string" && !isPlainObject(value);
47041
+ return tag == errorTag || tag == domExcTag || typeof value.message == "string" && typeof value.name == "string" && !isPlainObject2(value);
47042
47042
  }
47043
47043
  function isFinite2(value) {
47044
47044
  return typeof value == "number" && nativeIsFinite(value);
@@ -47089,7 +47089,7 @@ var require_lodash3 = __commonJS({
47089
47089
  function isNumber(value) {
47090
47090
  return typeof value == "number" || isObjectLike(value) && baseGetTag(value) == numberTag;
47091
47091
  }
47092
- function isPlainObject(value) {
47092
+ function isPlainObject2(value) {
47093
47093
  if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
47094
47094
  return false;
47095
47095
  }
@@ -48155,7 +48155,7 @@ var require_lodash3 = __commonJS({
48155
48155
  lodash.isNumber = isNumber;
48156
48156
  lodash.isObject = isObject2;
48157
48157
  lodash.isObjectLike = isObjectLike;
48158
- lodash.isPlainObject = isPlainObject;
48158
+ lodash.isPlainObject = isPlainObject2;
48159
48159
  lodash.isRegExp = isRegExp;
48160
48160
  lodash.isSafeInteger = isSafeInteger;
48161
48161
  lodash.isSet = isSet;
@@ -50527,13 +50527,20 @@ var BindingOutputSchema = z3.object({
50527
50527
  target: z3.string(),
50528
50528
  template: z3.string().optional(),
50529
50529
  inline_template: z3.string().optional(),
50530
+ source: z3.string().optional(),
50530
50531
  mode: z3.enum(["write", "patch"]).default("write"),
50532
+ format: z3.enum(["json", "yaml", "text"]).optional(),
50533
+ patch_strategy: z3.enum(["deep_merge", "append"]).optional(),
50534
+ array_merge_key: z3.string().optional(),
50531
50535
  group_by: z3.string().optional(),
50532
50536
  executable: z3.boolean().optional(),
50533
50537
  skip_empty: z3.boolean().optional()
50534
50538
  }).passthrough().refine(
50535
- (data) => !(data.template && data.inline_template),
50536
- { message: "template and inline_template are mutually exclusive" }
50539
+ (data) => {
50540
+ const count = [data.template, data.inline_template, data.source].filter(Boolean).length;
50541
+ return count === 1 || count === 0;
50542
+ },
50543
+ { message: "Only one of template, inline_template, or source may be specified" }
50537
50544
  );
50538
50545
  var ReportingSchema = z3.object({
50539
50546
  commands: z3.record(z3.string(), z3.string()),
@@ -53574,8 +53581,52 @@ import { dirname as dirname5 } from "path";
53574
53581
  import Handlebars from "handlebars";
53575
53582
 
53576
53583
  // src/renderer/context.ts
53577
- function buildSystemContext(dsl) {
53578
- return { system: dsl.system, dsl };
53584
+ function buildSystemContext(dsl, options) {
53585
+ const ctx = { system: dsl.system, dsl };
53586
+ if (options?.loadedBindings && options.loadedBindings.length > 0) {
53587
+ ctx.bindings = options.loadedBindings.map((lb) => lb.binding);
53588
+ const policyName = options.activeGuardrailPolicy;
53589
+ const policy = policyName ? dsl.guardrail_policies[policyName] : void 0;
53590
+ if (policy) {
53591
+ ctx.guardrailEnforcement = buildGuardrailEnforcement(dsl, policy, options.loadedBindings);
53592
+ }
53593
+ }
53594
+ return ctx;
53595
+ }
53596
+ function buildGuardrailEnforcement(dsl, policy, loadedBindings) {
53597
+ const entries = [];
53598
+ const bindingTriggers = /* @__PURE__ */ new Map();
53599
+ for (const lb of loadedBindings) {
53600
+ const impl = lb.binding.guardrail_impl ?? {};
53601
+ for (const [guardrailId, gi] of Object.entries(impl)) {
53602
+ for (const check of gi.checks) {
53603
+ if (check.matcher) {
53604
+ bindingTriggers.set(guardrailId, check.matcher.type);
53605
+ }
53606
+ }
53607
+ }
53608
+ }
53609
+ for (const rule of policy.rules) {
53610
+ const guardrail = dsl.guardrails[rule.guardrail];
53611
+ if (!guardrail) continue;
53612
+ const scope = guardrail.scope ?? {};
53613
+ entries.push({
53614
+ guardrail_id: rule.guardrail,
53615
+ description: guardrail.description,
53616
+ severity: rule.severity,
53617
+ action: rule.action,
53618
+ scoped_agents: scope.agents ?? [],
53619
+ scoped_tasks: scope.tasks ?? [],
53620
+ scoped_workflows: scope.workflows ?? [],
53621
+ scoped_tools: scope.tools ?? [],
53622
+ scoped_artifacts: scope.artifacts ?? [],
53623
+ allow_override: rule.allow_override,
53624
+ override_requires: rule.override_requires ?? [],
53625
+ trigger: bindingTriggers.get(rule.guardrail) ?? null,
53626
+ escalation: rule.escalation ? { target: rule.escalation.target, condition: rule.escalation.condition } : null
53627
+ });
53628
+ }
53629
+ return entries;
53579
53630
  }
53580
53631
  function buildTaskContext(dsl, taskId) {
53581
53632
  const taskDef = dsl.tasks[taskId];
@@ -54630,6 +54681,50 @@ Handlebars.registerHelper(
54630
54681
  return generateOverviewFlowchart(dsl);
54631
54682
  }
54632
54683
  );
54684
+ Handlebars.registerHelper(
54685
+ "guardrailCoverageMatrix",
54686
+ function() {
54687
+ const enforcement = this["guardrailEnforcement"];
54688
+ if (!enforcement || enforcement.length === 0) return "";
54689
+ const header = "| Guardrail | Severity | Action | Agents | Tasks | Workflows | Tools | Artifacts | Trigger | Override | Escalation |";
54690
+ const divider = "|-----------|----------|--------|--------|-------|-----------|-------|-----------|---------|----------|------------|";
54691
+ const rows = enforcement.map((e3) => {
54692
+ const agents = e3["scoped_agents"]?.join(", ") || "\u2014";
54693
+ const tasks = e3["scoped_tasks"]?.join(", ") || "\u2014";
54694
+ const workflows = e3["scoped_workflows"]?.join(", ") || "\u2014";
54695
+ const tools = e3["scoped_tools"]?.join(", ") || "\u2014";
54696
+ const artifacts = e3["scoped_artifacts"]?.join(", ") || "\u2014";
54697
+ const trigger = e3["trigger"] || "\u2014";
54698
+ const override = e3["allow_override"] ? "yes" : "no";
54699
+ const esc = e3["escalation"];
54700
+ const escalation = esc ? esc["target"] : "\u2014";
54701
+ return `| ${e3["guardrail_id"]} | ${e3["severity"]} | ${e3["action"]} | ${agents} | ${tasks} | ${workflows} | ${tools} | ${artifacts} | ${trigger} | ${override} | ${escalation} |`;
54702
+ });
54703
+ return [header, divider, ...rows].join("\n");
54704
+ }
54705
+ );
54706
+ Handlebars.registerHelper(
54707
+ "taskGuardrailMatrix",
54708
+ function() {
54709
+ const dsl = this["dsl"];
54710
+ const enforcement = this["guardrailEnforcement"];
54711
+ if (!dsl || !enforcement || enforcement.length === 0) return "";
54712
+ const taskIds = Object.keys(dsl.tasks);
54713
+ if (taskIds.length === 0) return "";
54714
+ const guardrailIds = enforcement.map((e3) => e3["guardrail_id"]);
54715
+ const header = `| Task | ${guardrailIds.join(" | ")} |`;
54716
+ const divider = `|------|${guardrailIds.map(() => "------").join("|")}|`;
54717
+ const rows = taskIds.map((taskId) => {
54718
+ const cells = enforcement.map((e3) => {
54719
+ const scopedTasks = e3["scoped_tasks"];
54720
+ if (scopedTasks.length > 0 && !scopedTasks.includes(taskId)) return "n/a";
54721
+ return e3["action"];
54722
+ });
54723
+ return `| ${taskId} | ${cells.join(" | ")} |`;
54724
+ });
54725
+ return [header, divider, ...rows].join("\n");
54726
+ }
54727
+ );
54633
54728
  function getDslSection(dsl, context) {
54634
54729
  const sectionMap = {
54635
54730
  agent: dsl.agents,
@@ -54694,13 +54789,13 @@ async function removeIfExists(filePath) {
54694
54789
  } catch {
54695
54790
  }
54696
54791
  }
54697
- async function renderFromConfig(dsl, renderTargets) {
54792
+ async function renderFromConfig(dsl, renderTargets, options) {
54698
54793
  const outputFiles = [];
54699
54794
  for (const target of renderTargets) {
54700
54795
  const templateContent = await loadTemplate(target.template);
54701
54796
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54702
54797
  if (target.context === "system") {
54703
- const ctx = buildSystemContext(dsl);
54798
+ const ctx = buildSystemContext(dsl, options);
54704
54799
  const output = compiled(ctx);
54705
54800
  if (target.skip_empty && isEffectivelyEmpty(output)) {
54706
54801
  await removeIfExists(target.output);
@@ -54749,13 +54844,13 @@ function checkExpectedVsExisting(expected, outputPath, skipEmpty, diffs) {
54749
54844
  diffs.push(outputPath);
54750
54845
  });
54751
54846
  }
54752
- async function checkDriftFromConfig(dsl, renderTargets) {
54847
+ async function checkDriftFromConfig(dsl, renderTargets, options) {
54753
54848
  const diffs = [];
54754
54849
  for (const target of renderTargets) {
54755
54850
  const templateContent = await loadTemplate(target.template);
54756
54851
  const compiled = Handlebars.compile(templateContent, { noEscape: false });
54757
54852
  if (target.context === "system") {
54758
- const ctx = buildSystemContext(dsl);
54853
+ const ctx = buildSystemContext(dsl, options);
54759
54854
  const expected = compiled(ctx);
54760
54855
  await checkExpectedVsExisting(expected, target.output, target.skip_empty, diffs);
54761
54856
  } else {
@@ -54793,10 +54888,19 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54793
54888
  );
54794
54889
  process.exit(1);
54795
54890
  }
54891
+ let renderOptions;
54892
+ if (config.bindings.length > 0) {
54893
+ const loadedBindings = await loadBindings(config.bindings);
54894
+ renderOptions = {
54895
+ loadedBindings,
54896
+ activeGuardrailPolicy: config.activeGuardrailPolicy
54897
+ };
54898
+ }
54796
54899
  if (opts.check) {
54797
54900
  const drift = await checkDriftFromConfig(
54798
54901
  schemaResult.data,
54799
- config.renders
54902
+ config.renders,
54903
+ renderOptions
54800
54904
  );
54801
54905
  if (drift.hasDrift) {
54802
54906
  process.stderr.write("Drift detected in the following files:\n");
@@ -54812,7 +54916,8 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
54812
54916
  } else {
54813
54917
  const files = await renderFromConfig(
54814
54918
  schemaResult.data,
54815
- config.renders
54919
+ config.renders,
54920
+ renderOptions
54816
54921
  );
54817
54922
  if (!opts.quiet) {
54818
54923
  process.stdout.write(`Rendered ${files.length} file(s):
@@ -54924,9 +55029,10 @@ var checkCommand = new Command5("check").description("Run full pipeline: resolve
54924
55029
  import { Command as Command6 } from "commander";
54925
55030
 
54926
55031
  // src/guardrail-generator/generator.ts
54927
- import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink as unlink2 } from "fs/promises";
55032
+ import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink as unlink2, copyFile } from "fs/promises";
54928
55033
  import { resolve as resolve6, dirname as dirname6 } from "path";
54929
55034
  import Handlebars2 from "handlebars";
55035
+ import YAML from "yaml";
54930
55036
 
54931
55037
  // src/guardrail-generator/resolve-checks.ts
54932
55038
  function resolveChecks(dsl, binding, policy) {
@@ -54998,6 +55104,81 @@ Handlebars2.registerHelper(
54998
55104
  return new Handlebars2.SafeString(result);
54999
55105
  }
55000
55106
  );
55107
+ function isPlainObject(val) {
55108
+ return typeof val === "object" && val !== null && !Array.isArray(val);
55109
+ }
55110
+ function deepMergeArrays(existing, incoming, mergeKey) {
55111
+ if (!mergeKey) return [...existing, ...incoming];
55112
+ const merged = [...existing];
55113
+ for (const item of incoming) {
55114
+ if (isPlainObject(item) && mergeKey in item) {
55115
+ const idx = merged.findIndex(
55116
+ (e3) => isPlainObject(e3) && e3[mergeKey] === item[mergeKey]
55117
+ );
55118
+ if (idx >= 0) {
55119
+ merged[idx] = item;
55120
+ } else {
55121
+ merged.push(item);
55122
+ }
55123
+ } else {
55124
+ merged.push(item);
55125
+ }
55126
+ }
55127
+ return merged;
55128
+ }
55129
+ function deepMerge(existing, incoming, arrayMergeKey) {
55130
+ if (Array.isArray(existing) && Array.isArray(incoming)) {
55131
+ return deepMergeArrays(existing, incoming, arrayMergeKey);
55132
+ }
55133
+ if (isPlainObject(existing) && isPlainObject(incoming)) {
55134
+ const result = { ...existing };
55135
+ for (const [key, val] of Object.entries(incoming)) {
55136
+ result[key] = key in result ? deepMerge(result[key], val, arrayMergeKey) : val;
55137
+ }
55138
+ return result;
55139
+ }
55140
+ return incoming;
55141
+ }
55142
+ function parseContent(raw, format2) {
55143
+ if (format2 === "json") return JSON.parse(raw);
55144
+ if (format2 === "yaml") return YAML.parse(raw);
55145
+ throw new Error(`Unsupported format for patch parsing: ${format2}`);
55146
+ }
55147
+ function serializeContent(data, format2) {
55148
+ if (format2 === "json") return JSON.stringify(data, null, 2) + "\n";
55149
+ if (format2 === "yaml") return YAML.stringify(data);
55150
+ throw new Error(`Unsupported format for patch serialization: ${format2}`);
55151
+ }
55152
+ async function applyPatch(targetPath, patchContent, outputDef) {
55153
+ const format2 = outputDef.format ?? "json";
55154
+ if (format2 === "text") {
55155
+ let existing = "";
55156
+ try {
55157
+ existing = await readFile5(targetPath, "utf8");
55158
+ } catch {
55159
+ }
55160
+ return existing + patchContent;
55161
+ }
55162
+ const patchData = parseContent(patchContent, format2);
55163
+ let existingData;
55164
+ try {
55165
+ const existingRaw = await readFile5(targetPath, "utf8");
55166
+ existingData = parseContent(existingRaw, format2);
55167
+ } catch {
55168
+ return serializeContent(patchData, format2);
55169
+ }
55170
+ const strategy = outputDef.patch_strategy ?? "deep_merge";
55171
+ if (strategy === "append" && Array.isArray(existingData)) {
55172
+ const merged2 = deepMergeArrays(
55173
+ existingData,
55174
+ Array.isArray(patchData) ? patchData : [patchData],
55175
+ outputDef.array_merge_key
55176
+ );
55177
+ return serializeContent(merged2, format2);
55178
+ }
55179
+ const merged = deepMerge(existingData, patchData, outputDef.array_merge_key);
55180
+ return serializeContent(merged, format2);
55181
+ }
55001
55182
  async function generateGuardrails(options) {
55002
55183
  const { dsl, config, loadedBindings, filterBindings, dryRun } = options;
55003
55184
  const outputFiles = [];
@@ -55067,6 +55248,31 @@ async function generateGuardrails(options) {
55067
55248
  continue;
55068
55249
  }
55069
55250
  const targetPath = resolve6(config.configDir, pathResult.resolved);
55251
+ if (outputDef.source) {
55252
+ const sourcePath = resolve6(config.configDir, outputDef.source);
55253
+ if (!dryRun) {
55254
+ try {
55255
+ await mkdir2(dirname6(targetPath), { recursive: true });
55256
+ await copyFile(sourcePath, targetPath);
55257
+ if (outputDef.executable) {
55258
+ await chmod(targetPath, 493);
55259
+ }
55260
+ } catch (err) {
55261
+ const code = err.code;
55262
+ if (code === "ENOENT") {
55263
+ diagnostics.push({
55264
+ path: `binding.${binding.software}.outputs.${outputId}`,
55265
+ message: `Source file not found: ${sourcePath}`,
55266
+ severity: "error"
55267
+ });
55268
+ continue;
55269
+ }
55270
+ throw err;
55271
+ }
55272
+ }
55273
+ outputFiles.push(targetPath);
55274
+ continue;
55275
+ }
55070
55276
  let templateContent;
55071
55277
  if (outputDef.inline_template) {
55072
55278
  templateContent = outputDef.inline_template;
@@ -55093,12 +55299,13 @@ async function generateGuardrails(options) {
55093
55299
  } else {
55094
55300
  diagnostics.push({
55095
55301
  path: `binding.${binding.software}.outputs.${outputId}`,
55096
- message: "Output has neither template nor inline_template",
55302
+ message: "Output has neither template, inline_template, nor source",
55097
55303
  severity: "error"
55098
55304
  });
55099
55305
  continue;
55100
55306
  }
55101
55307
  const shouldSkipEmpty = outputDef.skip_empty === true;
55308
+ const isPatch = outputDef.mode === "patch";
55102
55309
  if (outputDef.group_by) {
55103
55310
  const groupField = outputDef.group_by;
55104
55311
  const groups = /* @__PURE__ */ new Map();
@@ -55116,9 +55323,9 @@ async function generateGuardrails(options) {
55116
55323
  current_group: groupKey
55117
55324
  };
55118
55325
  const compiled = Handlebars2.compile(templateContent, { noEscape: true });
55119
- const output = compiled(groupCtx);
55326
+ const rendered = compiled(groupCtx);
55120
55327
  const groupTarget = resolve6(targetPath, groupKey);
55121
- if (shouldSkipEmpty && output.trim().length === 0) {
55328
+ if (shouldSkipEmpty && rendered.trim().length === 0) {
55122
55329
  if (!dryRun) {
55123
55330
  try {
55124
55331
  await unlink2(groupTarget);
@@ -55127,6 +55334,7 @@ async function generateGuardrails(options) {
55127
55334
  }
55128
55335
  continue;
55129
55336
  }
55337
+ const output = isPatch && !dryRun ? await applyPatch(groupTarget, rendered, outputDef) : rendered;
55130
55338
  if (!dryRun) {
55131
55339
  await mkdir2(dirname6(groupTarget), { recursive: true });
55132
55340
  await writeFile2(groupTarget, output, "utf8");
@@ -55138,8 +55346,8 @@ async function generateGuardrails(options) {
55138
55346
  }
55139
55347
  } else {
55140
55348
  const compiled = Handlebars2.compile(templateContent, { noEscape: true });
55141
- const output = compiled(ctx);
55142
- if (shouldSkipEmpty && output.trim().length === 0) {
55349
+ const rendered = compiled(ctx);
55350
+ if (shouldSkipEmpty && rendered.trim().length === 0) {
55143
55351
  if (!dryRun) {
55144
55352
  try {
55145
55353
  await unlink2(targetPath);
@@ -55147,6 +55355,7 @@ async function generateGuardrails(options) {
55147
55355
  }
55148
55356
  }
55149
55357
  } else {
55358
+ const output = isPatch && !dryRun ? await applyPatch(targetPath, rendered, outputDef) : rendered;
55150
55359
  if (!dryRun) {
55151
55360
  await mkdir2(dirname6(targetPath), { recursive: true });
55152
55361
  await writeFile2(targetPath, output, "utf8");