agent-contracts 0.14.1 → 0.15.0

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/dist/cli.js CHANGED
@@ -50427,7 +50427,7 @@ var require_dist13 = __commonJS({
50427
50427
  // src/cli/cli.ts
50428
50428
  import { readFileSync } from "fs";
50429
50429
  import { fileURLToPath } from "url";
50430
- import { resolve as resolve7, dirname as dirname7 } from "path";
50430
+ import { resolve as resolve7, dirname as dirname8 } from "path";
50431
50431
  import { Command as Command8 } from "commander";
50432
50432
 
50433
50433
  // src/cli/commands/resolve.ts
@@ -50961,13 +50961,51 @@ var RenderTargetSchema = z15.object({
50961
50961
  },
50962
50962
  { message: "include/exclude cannot be used with context: system" }
50963
50963
  );
50964
+ var TeamConfigSchema = z15.object({
50965
+ dsl: z15.string().optional(),
50966
+ bindings: z15.array(z15.string()).default([]),
50967
+ vars: z15.record(z15.string(), z15.string()).optional(),
50968
+ paths: z15.record(z15.string(), z15.string()).optional(),
50969
+ active_guardrail_policy: z15.string().optional(),
50970
+ interface_output: z15.string().optional()
50971
+ });
50964
50972
  var AgentContractsConfigSchema = z15.object({
50965
- dsl: z15.string(),
50973
+ dsl: z15.string().optional(),
50966
50974
  vars: z15.record(z15.string(), z15.string()).optional(),
50967
50975
  renders: z15.array(RenderTargetSchema).default([]),
50968
50976
  bindings: z15.array(z15.string()).default([]),
50969
50977
  active_guardrail_policy: z15.string().optional(),
50970
- paths: z15.record(z15.string(), z15.string()).optional()
50978
+ paths: z15.record(z15.string(), z15.string()).optional(),
50979
+ teams: z15.record(z15.string(), TeamConfigSchema).optional()
50980
+ }).superRefine((data, ctx) => {
50981
+ if (data.dsl !== void 0 && data.teams !== void 0) {
50982
+ ctx.addIssue({
50983
+ code: z15.ZodIssueCode.custom,
50984
+ message: "dsl and teams are mutually exclusive",
50985
+ path: ["teams"]
50986
+ });
50987
+ return;
50988
+ }
50989
+ if (data.dsl === void 0 && data.teams === void 0) {
50990
+ ctx.addIssue({
50991
+ code: z15.ZodIssueCode.custom,
50992
+ message: "Either dsl or teams must be specified",
50993
+ path: []
50994
+ });
50995
+ return;
50996
+ }
50997
+ if (data.teams) {
50998
+ for (const [key, team] of Object.entries(data.teams)) {
50999
+ if (key === "_defaults") continue;
51000
+ if (team.dsl === void 0) {
51001
+ ctx.addIssue({
51002
+ code: z15.ZodIssueCode.custom,
51003
+ message: `Team "${key}" must specify dsl`,
51004
+ path: ["teams", key, "dsl"]
51005
+ });
51006
+ }
51007
+ }
51008
+ }
50971
51009
  });
50972
51010
 
50973
51011
  // src/config/loader.ts
@@ -50988,6 +51026,27 @@ async function fileExists(filePath) {
50988
51026
  return false;
50989
51027
  }
50990
51028
  }
51029
+ function resolveTeamConfigs(teams, configDir) {
51030
+ const defaults = teams._defaults;
51031
+ const result = {};
51032
+ for (const [key, team] of Object.entries(teams)) {
51033
+ if (key === "_defaults") continue;
51034
+ const mergedBindings = [...defaults?.bindings ?? [], ...team.bindings].map(
51035
+ (b3) => resolve(configDir, b3)
51036
+ );
51037
+ const mergedVars = defaults?.vars || team.vars ? { ...defaults?.vars ?? {}, ...team.vars ?? {} } : void 0;
51038
+ const mergedPaths = defaults?.paths || team.paths ? { ...defaults?.paths ?? {}, ...team.paths ?? {} } : void 0;
51039
+ result[key] = {
51040
+ dsl: resolve(configDir, team.dsl),
51041
+ bindings: mergedBindings,
51042
+ vars: mergedVars,
51043
+ activeGuardrailPolicy: team.active_guardrail_policy ?? defaults?.active_guardrail_policy,
51044
+ paths: mergedPaths,
51045
+ interfaceOutput: team.interface_output ? resolve(configDir, team.interface_output) : void 0
51046
+ };
51047
+ }
51048
+ return result;
51049
+ }
50991
51050
  async function loadConfig(configPath) {
50992
51051
  const isExplicit = configPath !== void 0;
50993
51052
  const targetPath = resolve(configPath ?? DEFAULT_CONFIG_NAME);
@@ -51030,14 +51089,27 @@ ${issues}`,
51030
51089
  }
51031
51090
  const configDir = dirname(targetPath);
51032
51091
  const config = result.data;
51092
+ const renders = config.renders.map((r3) => ({
51093
+ ...r3,
51094
+ template: resolve(configDir, r3.template),
51095
+ output: resolve(configDir, r3.output)
51096
+ }));
51097
+ if (config.teams) {
51098
+ return {
51099
+ dsl: "",
51100
+ vars: void 0,
51101
+ renders,
51102
+ configDir,
51103
+ bindings: [],
51104
+ activeGuardrailPolicy: void 0,
51105
+ paths: void 0,
51106
+ teams: resolveTeamConfigs(config.teams, configDir)
51107
+ };
51108
+ }
51033
51109
  return {
51034
51110
  dsl: resolve(configDir, config.dsl),
51035
51111
  vars: config.vars,
51036
- renders: config.renders.map((r3) => ({
51037
- ...r3,
51038
- template: resolve(configDir, r3.template),
51039
- output: resolve(configDir, r3.output)
51040
- })),
51112
+ renders,
51041
51113
  configDir,
51042
51114
  bindings: (config.bindings ?? []).map((b3) => resolve(configDir, b3)),
51043
51115
  activeGuardrailPolicy: config.active_guardrail_policy,
@@ -51859,28 +51931,79 @@ ${issues}`,
51859
51931
 
51860
51932
  // src/cli/commands/resolve.ts
51861
51933
  import { stringify } from "yaml";
51862
- var DIR_DEFAULT = "agent-contracts.yaml";
51863
- 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) => {
51864
- try {
51865
- const config = await loadConfig(opts.config);
51866
- const dslPath = resolveDslPath(dir, DIR_DEFAULT, config);
51867
- const result = await resolve4(dslPath);
51868
- let data = config?.vars ? substituteVars(result.data, config.vars) : result.data;
51869
- if (opts.expandDefaults) {
51870
- data = expandDefaults(data);
51871
- }
51872
- if (opts.format === "json") {
51873
- process.stdout.write(JSON.stringify(data, null, 2) + "\n");
51874
- } else {
51875
- process.stdout.write(stringify(data));
51934
+
51935
+ // src/cli/multi-team.ts
51936
+ function isMultiTeamConfig(config) {
51937
+ return config.teams !== void 0 && Object.keys(config.teams).length > 0;
51938
+ }
51939
+ function getTeamEntries(config, teamFilter) {
51940
+ if (!config.teams) return [];
51941
+ const entries = Object.entries(config.teams);
51942
+ if (teamFilter) {
51943
+ const found = entries.filter(([k2]) => k2 === teamFilter);
51944
+ if (found.length === 0) {
51945
+ throw new Error(
51946
+ `Team "${teamFilter}" not found. Available teams: ${entries.map(([k2]) => k2).join(", ")}`
51947
+ );
51876
51948
  }
51877
- } catch (err) {
51878
- const msg = err instanceof Error ? err.message : String(err);
51879
- process.stderr.write(`Error: ${msg}
51949
+ return found;
51950
+ }
51951
+ return entries;
51952
+ }
51953
+
51954
+ // src/cli/commands/resolve.ts
51955
+ var DIR_DEFAULT = "agent-contracts.yaml";
51956
+ 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("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json)", "text").option("--expand-defaults", "Expand all Zod default values in output", false).action(
51957
+ async (dir, opts) => {
51958
+ try {
51959
+ const config = await loadConfig(opts.config);
51960
+ if (config !== null && isMultiTeamConfig(config)) {
51961
+ const teamEntries = getTeamEntries(config, opts.team);
51962
+ if (opts.format === "json") {
51963
+ const out = {};
51964
+ for (const [teamId, teamConfig] of teamEntries) {
51965
+ const result2 = await resolve4(teamConfig.dsl);
51966
+ let data2 = teamConfig.vars ? substituteVars(result2.data, teamConfig.vars) : result2.data;
51967
+ if (opts.expandDefaults) {
51968
+ data2 = expandDefaults(data2);
51969
+ }
51970
+ out[teamId] = data2;
51971
+ }
51972
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
51973
+ } else {
51974
+ for (const [teamId, teamConfig] of teamEntries) {
51975
+ process.stdout.write(`
51976
+ --- Team: ${teamId} ---
51977
+ `);
51978
+ const result2 = await resolve4(teamConfig.dsl);
51979
+ let data2 = teamConfig.vars ? substituteVars(result2.data, teamConfig.vars) : result2.data;
51980
+ if (opts.expandDefaults) {
51981
+ data2 = expandDefaults(data2);
51982
+ }
51983
+ process.stdout.write(stringify(data2));
51984
+ }
51985
+ }
51986
+ return;
51987
+ }
51988
+ const dslPath = resolveDslPath(dir, DIR_DEFAULT, config);
51989
+ const result = await resolve4(dslPath);
51990
+ let data = config?.vars ? substituteVars(result.data, config.vars) : result.data;
51991
+ if (opts.expandDefaults) {
51992
+ data = expandDefaults(data);
51993
+ }
51994
+ if (opts.format === "json") {
51995
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
51996
+ } else {
51997
+ process.stdout.write(stringify(data));
51998
+ }
51999
+ } catch (err) {
52000
+ const msg = err instanceof Error ? err.message : String(err);
52001
+ process.stderr.write(`Error: ${msg}
51880
52002
  `);
51881
- process.exit(1);
52003
+ process.exit(1);
52004
+ }
51882
52005
  }
51883
- });
52006
+ );
51884
52007
 
51885
52008
  // src/cli/commands/validate.ts
51886
52009
  import { Command as Command2 } from "commander";
@@ -52960,44 +53083,88 @@ function formatDiagnostics(diagnostics, options) {
52960
53083
 
52961
53084
  // src/cli/commands/validate.ts
52962
53085
  var DIR_DEFAULT2 = "agent-contracts.yaml";
52963
- var validateCommand = new Command2("validate").description("Validate DSL against schema and check references").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT2).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(async (dir, opts) => {
52964
- try {
52965
- const config = await loadConfig(opts.config);
52966
- const dslPath = resolveDslPath(dir, DIR_DEFAULT2, config);
52967
- const resolved = await resolve4(dslPath);
52968
- const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
52969
- const schemaResult = validateSchema(data);
52970
- const schemaWarnings = schemaResult.diagnostics.filter(
52971
- (d2) => d2.severity === "warning"
52972
- );
52973
- if (!schemaResult.success) {
52974
- const output = formatDiagnostics(schemaResult.diagnostics, opts);
52975
- if (output) process.stderr.write(output + "\n");
52976
- process.exit(1);
52977
- }
52978
- const refDiags = checkReferences(schemaResult.data);
52979
- const handoffDiags = validateHandoffSchemas(schemaResult.data);
52980
- const allDiags = [...refDiags, ...handoffDiags, ...schemaWarnings];
52981
- const hasWarnings = allDiags.some(
52982
- (d2) => "severity" in d2 && d2.severity === "warning"
52983
- );
52984
- if (allDiags.length > 0) {
52985
- const output = formatDiagnostics(allDiags, opts);
52986
- if (output) process.stderr.write(output + "\n");
52987
- if (refDiags.length > 0 || handoffDiags.length > 0 || opts.strict && hasWarnings) {
53086
+ var validateCommand = new Command2("validate").description("Validate DSL against schema and check references").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT2).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(
53087
+ async (dir, opts) => {
53088
+ try {
53089
+ const config = await loadConfig(opts.config);
53090
+ if (config !== null && isMultiTeamConfig(config)) {
53091
+ const teamEntries = getTeamEntries(config, opts.team);
53092
+ let hasErrors = false;
53093
+ let allTeamsFullyClean = true;
53094
+ for (const [teamId, teamConfig] of teamEntries) {
53095
+ if (!opts.quiet) process.stdout.write(`
53096
+ --- Team: ${teamId} ---
53097
+ `);
53098
+ const resolved2 = await resolve4(teamConfig.dsl);
53099
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
53100
+ const schemaResult2 = validateSchema(data2);
53101
+ const schemaWarnings2 = schemaResult2.diagnostics.filter(
53102
+ (d2) => d2.severity === "warning"
53103
+ );
53104
+ if (!schemaResult2.success) {
53105
+ const output = formatDiagnostics(schemaResult2.diagnostics, opts);
53106
+ if (output) process.stderr.write(output + "\n");
53107
+ hasErrors = true;
53108
+ allTeamsFullyClean = false;
53109
+ continue;
53110
+ }
53111
+ const refDiags2 = checkReferences(schemaResult2.data);
53112
+ const handoffDiags2 = validateHandoffSchemas(schemaResult2.data);
53113
+ const allDiags2 = [...refDiags2, ...handoffDiags2, ...schemaWarnings2];
53114
+ const hasWarnings2 = allDiags2.some(
53115
+ (d2) => "severity" in d2 && d2.severity === "warning"
53116
+ );
53117
+ if (allDiags2.length > 0) {
53118
+ allTeamsFullyClean = false;
53119
+ const output = formatDiagnostics(allDiags2, opts);
53120
+ if (output) process.stderr.write(output + "\n");
53121
+ if (refDiags2.length > 0 || handoffDiags2.length > 0 || opts.strict && hasWarnings2) {
53122
+ hasErrors = true;
53123
+ }
53124
+ }
53125
+ }
53126
+ if (hasErrors) process.exit(1);
53127
+ if (!opts.quiet && allTeamsFullyClean) {
53128
+ process.stdout.write("Validation passed.\n");
53129
+ }
53130
+ return;
53131
+ }
53132
+ const dslPath = resolveDslPath(dir, DIR_DEFAULT2, config);
53133
+ const resolved = await resolve4(dslPath);
53134
+ const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
53135
+ const schemaResult = validateSchema(data);
53136
+ const schemaWarnings = schemaResult.diagnostics.filter(
53137
+ (d2) => d2.severity === "warning"
53138
+ );
53139
+ if (!schemaResult.success) {
53140
+ const output = formatDiagnostics(schemaResult.diagnostics, opts);
53141
+ if (output) process.stderr.write(output + "\n");
52988
53142
  process.exit(1);
52989
53143
  }
52990
- }
52991
- if (!opts.quiet && allDiags.length === 0) {
52992
- process.stdout.write("Validation passed.\n");
52993
- }
52994
- } catch (err) {
52995
- const msg = err instanceof Error ? err.message : String(err);
52996
- process.stderr.write(`Error: ${msg}
53144
+ const refDiags = checkReferences(schemaResult.data);
53145
+ const handoffDiags = validateHandoffSchemas(schemaResult.data);
53146
+ const allDiags = [...refDiags, ...handoffDiags, ...schemaWarnings];
53147
+ const hasWarnings = allDiags.some(
53148
+ (d2) => "severity" in d2 && d2.severity === "warning"
53149
+ );
53150
+ if (allDiags.length > 0) {
53151
+ const output = formatDiagnostics(allDiags, opts);
53152
+ if (output) process.stderr.write(output + "\n");
53153
+ if (refDiags.length > 0 || handoffDiags.length > 0 || opts.strict && hasWarnings) {
53154
+ process.exit(1);
53155
+ }
53156
+ }
53157
+ if (!opts.quiet && allDiags.length === 0) {
53158
+ process.stdout.write("Validation passed.\n");
53159
+ }
53160
+ } catch (err) {
53161
+ const msg = err instanceof Error ? err.message : String(err);
53162
+ process.stderr.write(`Error: ${msg}
52997
53163
  `);
52998
- process.exit(1);
53164
+ process.exit(1);
53165
+ }
52999
53166
  }
53000
- });
53167
+ );
53001
53168
 
53002
53169
  // src/cli/commands/lint.ts
53003
53170
  import { Command as Command3 } from "commander";
@@ -54395,10 +54562,63 @@ async function spectralLint(dslObject) {
54395
54562
 
54396
54563
  // src/cli/commands/lint.ts
54397
54564
  var DIR_DEFAULT3 = "agent-contracts.yaml";
54398
- var lintCommand = new Command3("lint").description("Run semantic lint rules on resolved DSL").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT3).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(
54565
+ var lintCommand = new Command3("lint").description("Run semantic lint rules on resolved DSL").argument("[dir]", "Path to agent-contracts.yaml", DIR_DEFAULT3).option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(
54399
54566
  async (dir, opts) => {
54400
54567
  try {
54401
54568
  const config = await loadConfig(opts.config);
54569
+ if (config !== null && isMultiTeamConfig(config)) {
54570
+ const teamEntries = getTeamEntries(config, opts.team);
54571
+ let hasErrors = false;
54572
+ let allClean = true;
54573
+ for (const [teamId, teamConfig] of teamEntries) {
54574
+ if (!opts.quiet) process.stdout.write(`
54575
+ --- Team: ${teamId} ---
54576
+ `);
54577
+ const resolved2 = await resolve4(teamConfig.dsl);
54578
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
54579
+ const schemaResult2 = validateSchema(data2);
54580
+ const schemaWarnings2 = schemaResult2.diagnostics.filter(
54581
+ (d2) => d2.severity === "warning"
54582
+ );
54583
+ if (!schemaResult2.success) {
54584
+ const output = formatDiagnostics(schemaResult2.diagnostics, {
54585
+ format: opts.format,
54586
+ quiet: opts.quiet
54587
+ });
54588
+ if (output) process.stderr.write(output + "\n");
54589
+ hasErrors = true;
54590
+ allClean = false;
54591
+ continue;
54592
+ }
54593
+ const tsDiagnostics2 = lint(schemaResult2.data);
54594
+ const spectralDiagnostics2 = await spectralLint(
54595
+ schemaResult2.data
54596
+ );
54597
+ const diagnostics2 = [
54598
+ ...tsDiagnostics2,
54599
+ ...spectralDiagnostics2,
54600
+ ...schemaWarnings2
54601
+ ];
54602
+ if (diagnostics2.length > 0) {
54603
+ allClean = false;
54604
+ const output = formatDiagnostics(diagnostics2, {
54605
+ format: opts.format,
54606
+ quiet: opts.quiet
54607
+ });
54608
+ if (output) process.stderr.write(output + "\n");
54609
+ const teamHasErrors = diagnostics2.some((d2) => d2.severity === "error");
54610
+ const teamHasWarnings = diagnostics2.some((d2) => d2.severity === "warning");
54611
+ if (teamHasErrors || opts.strict && teamHasWarnings) {
54612
+ hasErrors = true;
54613
+ }
54614
+ }
54615
+ }
54616
+ if (hasErrors) process.exit(1);
54617
+ if (!opts.quiet && allClean) {
54618
+ process.stdout.write("Lint passed.\n");
54619
+ }
54620
+ return;
54621
+ }
54402
54622
  const dslPath = resolveDslPath(dir, DIR_DEFAULT3, config);
54403
54623
  const resolved = await resolve4(dslPath);
54404
54624
  const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
@@ -55832,7 +56052,7 @@ async function checkDriftFromConfig(dsl, renderTargets, options) {
55832
56052
  }
55833
56053
 
55834
56054
  // src/cli/commands/render.ts
55835
- var renderCommand = new Command4("render").description("Render resolved DSL with Handlebars templates (requires config)").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--check", "Check for drift without writing files", false).option("--quiet", "Suppress output on success", false).action(
56055
+ var renderCommand = new Command4("render").description("Render resolved DSL with Handlebars templates (requires config)").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--check", "Check for drift without writing files", false).option("--quiet", "Suppress output on success", false).action(
55836
56056
  async (opts) => {
55837
56057
  try {
55838
56058
  const config = await loadConfig(opts.config);
@@ -55842,6 +56062,68 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
55842
56062
  );
55843
56063
  process.exit(1);
55844
56064
  }
56065
+ if (isMultiTeamConfig(config)) {
56066
+ const teamEntries = getTeamEntries(config, opts.team);
56067
+ for (const [teamId, teamConfig] of teamEntries) {
56068
+ if (!opts.quiet) process.stdout.write(`
56069
+ --- Team: ${teamId} ---
56070
+ `);
56071
+ const resolved2 = await resolve4(teamConfig.dsl);
56072
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
56073
+ const schemaResult2 = validateSchema(data2);
56074
+ if (!schemaResult2.success) {
56075
+ process.stderr.write(
56076
+ `Schema validation failed for team ${teamId}. Run 'agent-contracts validate' for details.
56077
+ `
56078
+ );
56079
+ process.exit(1);
56080
+ }
56081
+ let renderOptions2;
56082
+ if (teamConfig.bindings.length > 0) {
56083
+ const loadedBindings = await loadBindings(teamConfig.bindings);
56084
+ renderOptions2 = {
56085
+ loadedBindings,
56086
+ activeGuardrailPolicy: teamConfig.activeGuardrailPolicy
56087
+ };
56088
+ }
56089
+ if (opts.check) {
56090
+ const drift = await checkDriftFromConfig(
56091
+ schemaResult2.data,
56092
+ config.renders,
56093
+ renderOptions2
56094
+ );
56095
+ if (drift.hasDrift) {
56096
+ process.stderr.write(
56097
+ `Drift detected for team ${teamId} in the following files:
56098
+ `
56099
+ );
56100
+ for (const f2 of drift.diffs) {
56101
+ process.stderr.write(` ${f2}
56102
+ `);
56103
+ }
56104
+ process.exit(1);
56105
+ }
56106
+ if (!opts.quiet) {
56107
+ process.stdout.write("No drift detected.\n");
56108
+ }
56109
+ } else {
56110
+ const files = await renderFromConfig(
56111
+ schemaResult2.data,
56112
+ config.renders,
56113
+ renderOptions2
56114
+ );
56115
+ if (!opts.quiet) {
56116
+ process.stdout.write(`Rendered ${files.length} file(s):
56117
+ `);
56118
+ for (const f2 of files) {
56119
+ process.stdout.write(` ${f2}
56120
+ `);
56121
+ }
56122
+ }
56123
+ }
56124
+ }
56125
+ return;
56126
+ }
55845
56127
  const resolved = await resolve4(config.dsl);
55846
56128
  const data = config.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
55847
56129
  const schemaResult = validateSchema(data);
@@ -55907,7 +56189,9 @@ var renderCommand = new Command4("render").description("Render resolved DSL with
55907
56189
 
55908
56190
  // src/cli/commands/check.ts
55909
56191
  import { Command as Command5 } from "commander";
55910
- var checkCommand = new Command5("check").description("Run full pipeline: resolve \u2192 validate \u2192 lint \u2192 render --check").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(
56192
+ import { access as access2 } from "fs/promises";
56193
+ import { resolve as pathResolve, dirname as dirname6 } from "path";
56194
+ var checkCommand = new Command5("check").description("Run full pipeline: resolve \u2192 validate \u2192 lint \u2192 render --check").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json)", "text").option("--quiet", "Suppress output on success", false).option("--strict", "Treat warnings as errors", false).action(
55911
56195
  async (opts) => {
55912
56196
  let hasErrors = false;
55913
56197
  try {
@@ -55918,6 +56202,111 @@ var checkCommand = new Command5("check").description("Run full pipeline: resolve
55918
56202
  );
55919
56203
  process.exit(1);
55920
56204
  }
56205
+ if (isMultiTeamConfig(config)) {
56206
+ const teamEntries = getTeamEntries(config, opts.team);
56207
+ for (const [teamId, teamConfig] of teamEntries) {
56208
+ if (!opts.quiet) process.stderr.write(`
56209
+ --- Team: ${teamId} ---
56210
+ `);
56211
+ const resolved2 = await resolve4(teamConfig.dsl);
56212
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
56213
+ const schemaResult2 = validateSchema(data2);
56214
+ const schemaWarnings2 = schemaResult2.diagnostics.filter(
56215
+ (d2) => d2.severity === "warning"
56216
+ );
56217
+ if (!schemaResult2.success) {
56218
+ const output = formatDiagnostics(schemaResult2.diagnostics, {
56219
+ format: opts.format,
56220
+ quiet: opts.quiet
56221
+ });
56222
+ if (output) process.stderr.write(output + "\n");
56223
+ hasErrors = true;
56224
+ continue;
56225
+ }
56226
+ const refDiags2 = checkReferences(schemaResult2.data);
56227
+ const handoffDiags2 = validateHandoffSchemas(schemaResult2.data);
56228
+ const allRefDiags2 = [...refDiags2, ...handoffDiags2, ...schemaWarnings2];
56229
+ if (allRefDiags2.length > 0) {
56230
+ const output = formatDiagnostics(allRefDiags2, {
56231
+ format: opts.format,
56232
+ quiet: opts.quiet
56233
+ });
56234
+ if (output) process.stderr.write(output + "\n");
56235
+ hasErrors = true;
56236
+ }
56237
+ const tsLintDiags2 = lint(schemaResult2.data);
56238
+ const spectralDiags2 = await spectralLint(
56239
+ schemaResult2.data
56240
+ );
56241
+ const lintDiags2 = [...tsLintDiags2, ...spectralDiags2];
56242
+ if (lintDiags2.length > 0) {
56243
+ const output = formatDiagnostics(lintDiags2, {
56244
+ format: opts.format,
56245
+ quiet: opts.quiet
56246
+ });
56247
+ if (output) process.stderr.write(output + "\n");
56248
+ if (lintDiags2.some((d2) => d2.severity === "error")) {
56249
+ hasErrors = true;
56250
+ }
56251
+ if (opts.strict && lintDiags2.some((d2) => d2.severity === "warning")) {
56252
+ hasErrors = true;
56253
+ }
56254
+ }
56255
+ let renderOptions2;
56256
+ if (teamConfig.bindings.length > 0) {
56257
+ const loadedBindings = await loadBindings(teamConfig.bindings);
56258
+ renderOptions2 = {
56259
+ loadedBindings,
56260
+ activeGuardrailPolicy: teamConfig.activeGuardrailPolicy
56261
+ };
56262
+ }
56263
+ const drift2 = await checkDriftFromConfig(
56264
+ schemaResult2.data,
56265
+ config.renders,
56266
+ renderOptions2
56267
+ );
56268
+ if (drift2.hasDrift) {
56269
+ process.stderr.write(`Drift detected for team ${teamId} in:
56270
+ `);
56271
+ for (const f2 of drift2.diffs) {
56272
+ process.stderr.write(` ${f2}
56273
+ `);
56274
+ }
56275
+ hasErrors = true;
56276
+ }
56277
+ }
56278
+ if (!hasErrors) {
56279
+ for (const [teamId, teamConfig] of teamEntries) {
56280
+ const resolved2 = await resolve4(teamConfig.dsl);
56281
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
56282
+ const schemaResult2 = validateSchema(data2);
56283
+ if (!schemaResult2.success || !schemaResult2.data) continue;
56284
+ const dsl = schemaResult2.data;
56285
+ const imports = dsl.imports;
56286
+ if (!imports) continue;
56287
+ for (const [importName, importDef] of Object.entries(imports)) {
56288
+ const interfacePathRel = importDef?.interface;
56289
+ if (typeof interfacePathRel !== "string") continue;
56290
+ const interfacePath = pathResolve(
56291
+ dirname6(teamConfig.dsl),
56292
+ interfacePathRel
56293
+ );
56294
+ try {
56295
+ await access2(interfacePath);
56296
+ } catch {
56297
+ process.stderr.write(
56298
+ `Cross-team error: Team "${teamId}" imports "${importName}" but interface file not found: ${interfacePath}
56299
+ `
56300
+ );
56301
+ hasErrors = true;
56302
+ }
56303
+ }
56304
+ }
56305
+ }
56306
+ if (hasErrors) process.exit(1);
56307
+ if (!opts.quiet) process.stdout.write("All checks passed.\n");
56308
+ return;
56309
+ }
55921
56310
  const resolved = await resolve4(config.dsl);
55922
56311
  const data = config.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
55923
56312
  const schemaResult = validateSchema(data);
@@ -56005,7 +56394,7 @@ import { Command as Command6 } from "commander";
56005
56394
 
56006
56395
  // src/guardrail-generator/generator.ts
56007
56396
  import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink as unlink2, copyFile } from "fs/promises";
56008
- import { resolve as resolve6, dirname as dirname6 } from "path";
56397
+ import { resolve as resolve6, dirname as dirname7 } from "path";
56009
56398
  import Handlebars2 from "handlebars";
56010
56399
  import YAML from "yaml";
56011
56400
 
@@ -56232,7 +56621,7 @@ async function generateGuardrails(options) {
56232
56621
  const sourcePath = resolve6(config.configDir, outputDef.source);
56233
56622
  if (!dryRun) {
56234
56623
  try {
56235
- await mkdir2(dirname6(targetPath), { recursive: true });
56624
+ await mkdir2(dirname7(targetPath), { recursive: true });
56236
56625
  await copyFile(sourcePath, targetPath);
56237
56626
  if (outputDef.executable) {
56238
56627
  await chmod(targetPath, 493);
@@ -56316,7 +56705,7 @@ async function generateGuardrails(options) {
56316
56705
  }
56317
56706
  const output = isPatch && !dryRun ? await applyPatch(groupTarget, rendered, outputDef) : rendered;
56318
56707
  if (!dryRun) {
56319
- await mkdir2(dirname6(groupTarget), { recursive: true });
56708
+ await mkdir2(dirname7(groupTarget), { recursive: true });
56320
56709
  await writeFile2(groupTarget, output, "utf8");
56321
56710
  if (outputDef.executable) {
56322
56711
  await chmod(groupTarget, 493);
@@ -56337,7 +56726,7 @@ async function generateGuardrails(options) {
56337
56726
  } else {
56338
56727
  const output = isPatch && !dryRun ? await applyPatch(targetPath, rendered, outputDef) : rendered;
56339
56728
  if (!dryRun) {
56340
- await mkdir2(dirname6(targetPath), { recursive: true });
56729
+ await mkdir2(dirname7(targetPath), { recursive: true });
56341
56730
  await writeFile2(targetPath, output, "utf8");
56342
56731
  if (outputDef.executable) {
56343
56732
  await chmod(targetPath, 493);
@@ -56390,7 +56779,7 @@ async function generateGuardrails(options) {
56390
56779
  continue;
56391
56780
  }
56392
56781
  if (!dryRun) {
56393
- await mkdir2(dirname6(outputPath), { recursive: true });
56782
+ await mkdir2(dirname7(outputPath), { recursive: true });
56394
56783
  await writeFile2(outputPath, rendered, "utf8");
56395
56784
  if (renderTarget.executable) {
56396
56785
  await chmod(outputPath, 493);
@@ -56420,7 +56809,7 @@ async function generateGuardrails(options) {
56420
56809
  continue;
56421
56810
  }
56422
56811
  if (!dryRun) {
56423
- await mkdir2(dirname6(outputPath), { recursive: true });
56812
+ await mkdir2(dirname7(outputPath), { recursive: true });
56424
56813
  await writeFile2(outputPath, rendered, "utf8");
56425
56814
  if (renderTarget.executable) {
56426
56815
  await chmod(outputPath, 493);
@@ -56441,7 +56830,18 @@ function resolveBindingRenderOutputPath(output, paths) {
56441
56830
  }
56442
56831
 
56443
56832
  // src/cli/commands/generate-guardrails.ts
56444
- var generateGuardrailsCommand = new Command6("generate").description("Generate guardrail runtime artifacts from DSL, policies, and bindings").argument("[type]", "Type of artifacts to generate", "guardrails").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--binding <name...>", "Filter to specific software binding(s)").option("--dry-run", "Print what would be generated without writing files", false).option("--quiet", "Suppress output on success", false).action(
56833
+ function resolvedConfigForTeam(workspace, teamConfig) {
56834
+ return {
56835
+ dsl: "",
56836
+ vars: teamConfig.vars,
56837
+ renders: workspace.renders,
56838
+ configDir: workspace.configDir,
56839
+ bindings: teamConfig.bindings,
56840
+ activeGuardrailPolicy: teamConfig.activeGuardrailPolicy,
56841
+ paths: teamConfig.paths
56842
+ };
56843
+ }
56844
+ var generateGuardrailsCommand = new Command6("generate").description("Generate guardrail runtime artifacts from DSL, policies, and bindings").argument("[type]", "Type of artifacts to generate", "guardrails").option("-c, --config <path>", "Path to agent-contracts.config.yaml").option("--team <id>", "Limit to one team (multi-team config only)").option("--binding <name...>", "Filter to specific software binding(s)").option("--dry-run", "Print what would be generated without writing files", false).option("--quiet", "Suppress output on success", false).action(
56445
56845
  async (type, opts) => {
56446
56846
  if (type !== "guardrails") {
56447
56847
  process.stderr.write(`Unknown generate type: ${type}
@@ -56456,6 +56856,73 @@ var generateGuardrailsCommand = new Command6("generate").description("Generate g
56456
56856
  );
56457
56857
  process.exit(1);
56458
56858
  }
56859
+ if (isMultiTeamConfig(config)) {
56860
+ const teamEntries = getTeamEntries(config, opts.team);
56861
+ let exitWithError = false;
56862
+ for (const [teamId, teamConfig] of teamEntries) {
56863
+ if (!opts.quiet) process.stdout.write(`
56864
+ --- Team: ${teamId} ---
56865
+ `);
56866
+ const resolved2 = await resolve4(teamConfig.dsl);
56867
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
56868
+ const schemaResult2 = validateSchema(data2);
56869
+ if (!schemaResult2.success) {
56870
+ process.stderr.write(
56871
+ `Schema validation failed for team ${teamId}. Run 'agent-contracts validate' for details.
56872
+ `
56873
+ );
56874
+ exitWithError = true;
56875
+ continue;
56876
+ }
56877
+ const loadedBindings2 = await loadBindings(teamConfig.bindings);
56878
+ const result2 = await generateGuardrails({
56879
+ dsl: schemaResult2.data,
56880
+ config: resolvedConfigForTeam(config, teamConfig),
56881
+ loadedBindings: loadedBindings2,
56882
+ filterBindings: opts.binding,
56883
+ dryRun: opts.dryRun
56884
+ });
56885
+ const errors2 = result2.diagnostics.filter((d2) => d2.severity === "error");
56886
+ const warnings2 = result2.diagnostics.filter((d2) => d2.severity === "warning");
56887
+ const infos2 = result2.diagnostics.filter((d2) => d2.severity === "info");
56888
+ if (errors2.length > 0) {
56889
+ process.stderr.write(`Errors (team ${teamId}):
56890
+ `);
56891
+ for (const d2 of errors2) {
56892
+ process.stderr.write(` error [${d2.path}] ${d2.message}
56893
+ `);
56894
+ }
56895
+ exitWithError = true;
56896
+ }
56897
+ if (warnings2.length > 0 && !opts.quiet) {
56898
+ process.stderr.write(`Warnings (team ${teamId}):
56899
+ `);
56900
+ for (const d2 of warnings2) {
56901
+ process.stderr.write(` warning [${d2.path}] ${d2.message}
56902
+ `);
56903
+ }
56904
+ }
56905
+ if (infos2.length > 0 && !opts.quiet) {
56906
+ for (const d2 of infos2) {
56907
+ process.stderr.write(` info [${d2.path}] ${d2.message}
56908
+ `);
56909
+ }
56910
+ }
56911
+ if (!opts.quiet) {
56912
+ const action = opts.dryRun ? "Would generate" : "Generated";
56913
+ process.stdout.write(
56914
+ `${action} ${result2.outputFiles.length} file(s):
56915
+ `
56916
+ );
56917
+ for (const f2 of result2.outputFiles) {
56918
+ process.stdout.write(` ${f2}
56919
+ `);
56920
+ }
56921
+ }
56922
+ }
56923
+ if (exitWithError) process.exit(1);
56924
+ return;
56925
+ }
56459
56926
  const resolved = await resolve4(config.dsl);
56460
56927
  const data = config.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
56461
56928
  const schemaResult = validateSchema(data);
@@ -56861,10 +57328,56 @@ function formatText(result) {
56861
57328
  }
56862
57329
  return lines.join("\n");
56863
57330
  }
56864
- 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(
57331
+ 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("--team <id>", "Limit to one team (multi-team config only)").option("--format <format>", "Output format (text|json)", "text").option("--threshold <number>", "Minimum score (exit 1 if below)", void 0).action(
56865
57332
  async (dir, opts) => {
56866
57333
  try {
56867
57334
  const config = await loadConfig(opts.config);
57335
+ if (config !== null && isMultiTeamConfig(config)) {
57336
+ const teamEntries = getTeamEntries(config, opts.team);
57337
+ let thresholdNum;
57338
+ if (opts.threshold !== void 0) {
57339
+ thresholdNum = parseInt(opts.threshold, 10);
57340
+ if (isNaN(thresholdNum)) {
57341
+ process.stderr.write(
57342
+ `Error: --threshold must be a number, got "${opts.threshold}"
57343
+ `
57344
+ );
57345
+ process.exit(1);
57346
+ }
57347
+ }
57348
+ let hasErrors = false;
57349
+ for (const [teamId, teamConfig] of teamEntries) {
57350
+ process.stdout.write(`
57351
+ --- Team: ${teamId} ---
57352
+ `);
57353
+ const resolved2 = await resolve4(teamConfig.dsl);
57354
+ const data2 = teamConfig.vars ? substituteVars(resolved2.data, teamConfig.vars) : resolved2.data;
57355
+ const schemaResult2 = validateSchema(data2);
57356
+ if (!schemaResult2.success) {
57357
+ const issues = schemaResult2.diagnostics.map((d2) => ` ${d2.path}: ${d2.message}`).join("\n");
57358
+ process.stderr.write(`Schema validation failed:
57359
+ ${issues}
57360
+ `);
57361
+ hasErrors = true;
57362
+ continue;
57363
+ }
57364
+ const result2 = score(schemaResult2.data);
57365
+ if (opts.format === "json") {
57366
+ process.stdout.write(JSON.stringify(result2, null, 2) + "\n");
57367
+ } else {
57368
+ process.stdout.write(formatText(result2) + "\n");
57369
+ }
57370
+ if (thresholdNum !== void 0 && result2.overall < thresholdNum) {
57371
+ process.stderr.write(
57372
+ `Score ${result2.overall} is below threshold ${thresholdNum}
57373
+ `
57374
+ );
57375
+ hasErrors = true;
57376
+ }
57377
+ }
57378
+ if (hasErrors) process.exit(1);
57379
+ return;
57380
+ }
56868
57381
  const dslPath = resolveDslPath(dir, DIR_DEFAULT4, config);
56869
57382
  const resolved = await resolve4(dslPath);
56870
57383
  const data = config?.vars ? substituteVars(resolved.data, config.vars) : resolved.data;
@@ -56909,7 +57422,7 @@ ${issues}
56909
57422
  );
56910
57423
 
56911
57424
  // src/cli/cli.ts
56912
- var __dirname = dirname7(fileURLToPath(import.meta.url));
57425
+ var __dirname = dirname8(fileURLToPath(import.meta.url));
56913
57426
  var pkg = JSON.parse(readFileSync(resolve7(__dirname, "../package.json"), "utf8"));
56914
57427
  var program = new Command8();
56915
57428
  program.name("agent-contracts").description("Agent contracts tooling \u2014 validate, lint, render DSL files").version(pkg.version);