newspaper_wedding/.aidlc/tools/aidlc-model-policy.ts
Andrew Ridgway bec1eaac87
Some checks failed
Test / test (push) Has been cancelled
first pass at the newspaper builder
2026-09-14 11:57:22 +10:00

824 lines
28 KiB
TypeScript

import {
existsSync,
readFileSync,
readdirSync,
writeFileSync,
} from "node:fs";
import { basename, join } from "node:path";
import {
kiroModelDefaults,
projectTier,
resolveTierCap,
type Harness as TierHarness,
type KiroEffort,
type Tier,
} from "./aidlc-tiers.ts";
export const MODEL_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
export type ModelEffort = (typeof MODEL_EFFORTS)[number];
export const MODEL_GROUPS = {
deciding: {
label: "Deciding",
tier: "judgment",
tradeoff:
"More effort gives design and implementation decisions more deliberation, with higher latency and cost.",
},
reviewing: {
label: "Reviewing",
tier: "balanced",
tradeoff:
"More effort gives review passes more deliberation, trading review time and cost for correctness.",
},
"writing-up": {
label: "Writing up",
tier: "templated",
tradeoff:
"More effort gives plans, pipelines, and runbooks more polish, with higher latency and cost.",
},
} as const;
export type ModelGroup = keyof typeof MODEL_GROUPS;
export type ModelHarness =
| "claude"
| "codex"
| "copilot"
| "cursor"
| "kiro"
| "kiro-ide"
| "opencode";
export type ModelPolicyLayer =
| "agent-exception"
| "group-dial"
| "shipped-tier-default"
| "session-inherit";
export type ModelGroupPolicy = {
effort: ModelEffort;
};
export type ModelAgentPolicy = {
effort?: ModelEffort;
model?: string;
};
export type ModelProfile = {
groups: Partial<Record<ModelGroup, ModelGroupPolicy>>;
};
export type ModelPolicyRecord = {
schemaVersion: 1;
preset?: string;
groups?: Partial<Record<ModelGroup, ModelGroupPolicy>>;
agents?: Record<string, ModelAgentPolicy>;
profiles?: Record<string, ModelProfile>;
};
export type EffectiveModelPolicy = {
agent: string;
group: ModelGroup;
layer: ModelPolicyLayer;
model?: string;
effort?: ModelEffort;
requestedModel?: string;
requestedEffort?: ModelEffort;
clampedEffort?: {
from: ModelEffort;
to: ModelEffort;
};
unexpressed: Array<"model" | "effort">;
};
const thoroughGroups = Object.freeze({
reviewing: Object.freeze({ effort: "xhigh" as const }),
});
const balancedGroups = Object.freeze({
reviewing: Object.freeze({ effort: "medium" as const }),
});
const minimalGroups = Object.freeze({
reviewing: Object.freeze({ effort: "medium" as const }),
"writing-up": Object.freeze({ effort: "low" as const }),
});
export const MODEL_PRESETS = Object.freeze({
thorough: Object.freeze({ groups: thoroughGroups }),
balanced: Object.freeze({ groups: balancedGroups }),
minimal: Object.freeze({ groups: minimalGroups }),
});
export type ModelPresetName = keyof typeof MODEL_PRESETS;
export const HARNESS_HONESTY = Object.freeze({
claude: Object.freeze({
model: true,
effort: true,
groupEffort: true,
message: "Claude Code can express model and effort policy on agent frontmatter.",
}),
codex: Object.freeze({
model: true,
effort: true,
groupEffort: true,
message:
"Codex can express model and effort policy; max effort clamps down to xhigh.",
}),
opencode: Object.freeze({
model: true,
effort: true,
groupEffort: true,
message:
"opencode can express model and variant policy; xhigh effort clamps down to high.",
}),
kiro: Object.freeze({
model: true,
effort: true,
groupEffort: false,
message:
"Kiro CLI cannot express group effort dials today; a per-agent model exception can carry effort through chat.modelDefaults.",
}),
"kiro-ide": Object.freeze({
model: false,
effort: false,
groupEffort: false,
message:
"Kiro IDE cannot express model or effort policy; its md-only agent surfaces carry no model keys, so set the chat model in the IDE (the kiro-ide-chat-model pending action tracks it).",
}),
cursor: Object.freeze({
model: false,
effort: false,
groupEffort: false,
message:
"Cursor cannot portably pin agent models or effort across plans; agents inherit the session.",
}),
copilot: Object.freeze({
model: false,
effort: false,
groupEffort: false,
message:
"GitHub Copilot cannot pin one portable agent model or effort across CLI and IDE; agents inherit the session.",
}),
});
type HarnessHonesty = (typeof HARNESS_HONESTY)[ModelHarness];
export type AgentTiers = Record<string, Tier>;
const POLICY_KEYS = new Set(["schemaVersion", "preset", "groups", "agents", "profiles"]);
const PROFILE_NAME = /^[a-z0-9][a-z0-9-]*$/;
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
export function isModelEffort(value: string): value is ModelEffort {
return (MODEL_EFFORTS as readonly string[]).includes(value);
}
export function isModelGroup(value: string): value is ModelGroup {
return Object.hasOwn(MODEL_GROUPS, value);
}
export function isModelPreset(value: string): value is ModelPresetName {
return Object.hasOwn(MODEL_PRESETS, value);
}
export function isModelHarness(value: string): value is ModelHarness {
return Object.hasOwn(HARNESS_HONESTY, value);
}
export function modelGroupForTier(tier: Tier): ModelGroup {
for (const [group, value] of Object.entries(MODEL_GROUPS)) {
if (value.tier === tier) return group as ModelGroup;
}
throw new Error(`tier ${JSON.stringify(tier)} has no public model group`);
}
export function modelAgentName(value: string): string {
const leaf = value.replaceAll("\\", "/").split("/").pop() ?? value;
return leaf
.replace(/^aidlc-/, "")
.replace(/-agent(?:\.(?:md|json|toml))?$/, "");
}
export function modelAgentStem(name: string): string {
return `aidlc-${name}-agent`;
}
function parseGroupPolicies(
value: unknown,
where: string,
): Partial<Record<ModelGroup, ModelGroupPolicy>> {
if (!isRecord(value)) throw new Error(`${where} must be an object`);
const groups: Partial<Record<ModelGroup, ModelGroupPolicy>> = {};
for (const [name, raw] of Object.entries(value)) {
if (!isModelGroup(name)) {
throw new Error(`${where} has unknown group ${JSON.stringify(name)}`);
}
if (!isRecord(raw) || Object.keys(raw).some((key) => key !== "effort")) {
throw new Error(`${where}.${name} must contain only effort`);
}
if (typeof raw.effort !== "string" || !isModelEffort(raw.effort)) {
throw new Error(
`${where}.${name}.effort must be one of ${MODEL_EFFORTS.join(", ")}`,
);
}
groups[name] = { effort: raw.effort };
}
return groups;
}
export function normalizeModelPolicy(value: unknown): ModelPolicyRecord | null {
if (value === undefined || value === null) return null;
if (!isRecord(value)) throw new Error("models policy must be an object");
if (value.schemaVersion !== 1) {
throw new Error(`models policy schemaVersion must be 1`);
}
const unknown = Object.keys(value).filter((key) => !POLICY_KEYS.has(key));
if (unknown.length > 0) {
throw new Error(`models policy has unknown key(s): ${unknown.join(", ")}`);
}
const out: ModelPolicyRecord = { schemaVersion: 1 };
if (value.preset !== undefined) {
if (typeof value.preset !== "string" || !isModelPreset(value.preset)) {
throw new Error(
`models policy preset must be one of ${Object.keys(MODEL_PRESETS).join(", ")}`,
);
}
out.preset = value.preset;
}
if (value.groups !== undefined) {
const groups = parseGroupPolicies(value.groups, "models.groups");
if (Object.keys(groups).length > 0) out.groups = groups;
}
if (value.agents !== undefined) {
if (!isRecord(value.agents)) throw new Error("models.agents must be an object");
const agents: Record<string, ModelAgentPolicy> = {};
for (const [name, raw] of Object.entries(value.agents)) {
if (!PROFILE_NAME.test(name)) {
throw new Error(`models.agents has invalid agent name ${JSON.stringify(name)}`);
}
if (
!isRecord(raw) ||
Object.keys(raw).some((key) => key !== "effort" && key !== "model")
) {
throw new Error(`${name} agent exception may contain only model and effort`);
}
const entry: ModelAgentPolicy = {};
if (raw.effort !== undefined) {
if (typeof raw.effort !== "string" || !isModelEffort(raw.effort)) {
throw new Error(
`models.agents.${name}.effort must be one of ${MODEL_EFFORTS.join(", ")}`,
);
}
entry.effort = raw.effort;
}
if (raw.model !== undefined) {
if (typeof raw.model !== "string" || raw.model.trim().length === 0) {
throw new Error(`models.agents.${name}.model must be a non-empty string`);
}
entry.model = raw.model.trim();
}
if (Object.keys(entry).length > 0) agents[name] = entry;
}
if (Object.keys(agents).length > 0) out.agents = agents;
}
if (value.profiles !== undefined) {
if (!isRecord(value.profiles)) {
throw new Error("models.profiles must be an object");
}
const profiles: Record<string, ModelProfile> = {};
for (const [name, raw] of Object.entries(value.profiles)) {
if (!PROFILE_NAME.test(name)) {
throw new Error(`models.profiles has invalid profile name ${JSON.stringify(name)}`);
}
if (
!isRecord(raw) ||
Object.keys(raw).some((key) => key !== "groups") ||
raw.groups === undefined
) {
throw new Error(`${name} profile must contain only groups`);
}
profiles[name] = {
groups: parseGroupPolicies(raw.groups, `models.profiles.${name}.groups`),
};
}
if (Object.keys(profiles).length > 0) out.profiles = profiles;
}
return out;
}
export function modelPolicyIsEmpty(policy: ModelPolicyRecord | null): boolean {
return policy === null ||
(
policy.preset === undefined &&
Object.keys(policy.groups ?? {}).length === 0 &&
Object.keys(policy.agents ?? {}).length === 0 &&
Object.keys(policy.profiles ?? {}).length === 0
);
}
export function activeModelGroups(
policy: ModelPolicyRecord | null,
): Partial<Record<ModelGroup, ModelGroupPolicy>> {
const groups: Partial<Record<ModelGroup, ModelGroupPolicy>> = {};
if (policy?.preset && isModelPreset(policy.preset)) {
Object.assign(groups, MODEL_PRESETS[policy.preset].groups);
}
Object.assign(groups, policy?.groups ?? {});
return groups;
}
export function profileGroups(
policy: ModelPolicyRecord | null,
name: string,
): Partial<Record<ModelGroup, ModelGroupPolicy>> {
if (isModelPreset(name)) return { ...MODEL_PRESETS[name].groups };
const profile = policy?.profiles?.[name];
if (!profile) {
throw new Error(
`unknown model preset or profile ${JSON.stringify(name)}; presets: ${
Object.keys(MODEL_PRESETS).join(", ")
}`,
);
}
return { ...profile.groups };
}
function tierHarness(harness: ModelHarness): TierHarness {
return harness === "kiro-ide" ? "kiro" : harness;
}
function projectedEffort(value: ReturnType<typeof projectTier>): ModelEffort | undefined {
if ("effort" in value && value.effort) return value.effort;
if ("variant" in value && value.variant) return value.variant;
return undefined;
}
function clampEffort(
effort: ModelEffort,
harness: ModelHarness,
): { effort: ModelEffort; clamped?: EffectiveModelPolicy["clampedEffort"] } {
if (harness === "codex" && effort === "max") {
return { effort: "xhigh", clamped: { from: "max", to: "xhigh" } };
}
if (harness === "opencode" && effort === "xhigh") {
return { effort: "high", clamped: { from: "xhigh", to: "high" } };
}
return { effort };
}
export function resolveModelPolicy(
policyValue: ModelPolicyRecord | null,
agentName: string,
agentTier: Tier,
harness: ModelHarness,
cap: Tier | null = null,
): EffectiveModelPolicy {
const policy = normalizeModelPolicy(policyValue);
const group = modelGroupForTier(agentTier);
const shipped = projectTier(agentTier, tierHarness(harness), cap);
const shippedModel = shipped.model ?? undefined;
const shippedEffort = projectedEffort(shipped);
const groupPolicy = activeModelGroups(policy)[group];
const agentPolicy = policy?.agents?.[agentName];
const requestedModel = agentPolicy?.model;
const requestedEffort = agentPolicy?.effort ?? groupPolicy?.effort;
const layer: ModelPolicyLayer = agentPolicy
? "agent-exception"
: groupPolicy
? "group-dial"
: shippedModel !== undefined || shippedEffort !== undefined
? "shipped-tier-default"
: "session-inherit";
const honesty = HARNESS_HONESTY[harness] as HarnessHonesty;
const unexpressed: Array<"model" | "effort"> = [];
let model = shippedModel;
let effort = shippedEffort;
let clampedEffort: EffectiveModelPolicy["clampedEffort"];
if (requestedModel !== undefined) {
if (honesty.model) model = requestedModel;
else unexpressed.push("model");
}
if (requestedEffort !== undefined) {
const kiroHasModel = harness === "kiro" && model !== undefined;
const supported = honesty.effort &&
(honesty.groupEffort || agentPolicy !== undefined || kiroHasModel) &&
(harness !== "kiro" || kiroHasModel);
if (supported) {
const clamped = clampEffort(requestedEffort, harness);
effort = clamped.effort;
clampedEffort = clamped.clamped;
} else {
unexpressed.push("effort");
}
}
return {
agent: agentName,
group,
layer,
...(model !== undefined ? { model } : {}),
...(effort !== undefined ? { effort } : {}),
...(requestedModel !== undefined ? { requestedModel } : {}),
...(requestedEffort !== undefined ? { requestedEffort } : {}),
...(clampedEffort ? { clampedEffort } : {}),
unexpressed,
};
}
export function readAgentTiers(harnessRoot: string): AgentTiers {
const path = join(harnessRoot, "tools", "data", "agent-tiers.json");
if (!existsSync(path)) throw new Error(`${path}: missing shipped agent tier data`);
const value = JSON.parse(readFileSync(path, "utf-8")) as unknown;
if (!isRecord(value)) throw new Error(`${path}: root must be an object`);
const out: AgentTiers = {};
for (const [name, tier] of Object.entries(value)) {
if (
!PROFILE_NAME.test(name) ||
(tier !== "judgment" && tier !== "balanced" && tier !== "templated")
) {
throw new Error(`${path}: invalid agent tier entry ${JSON.stringify(name)}`);
}
out[name] = tier;
}
return out;
}
export function serializeAgentTiers(tiers: AgentTiers): string {
return `${JSON.stringify(Object.fromEntries(Object.entries(tiers).sort()), null, 2)}\n`;
}
type MarkdownProjectionOptions = {
effortKey?: "effort" | "variant";
insertBeforeKeys?: readonly string[];
removeKeys?: readonly string[];
afterProjectionLines?: readonly string[];
};
function effectiveProjectionLines(
effective: Pick<EffectiveModelPolicy, "model" | "effort">,
effortKey: "effort" | "variant",
): string[] {
return [
...(effective.model !== undefined ? [`model: ${effective.model}`] : []),
...(effective.effort !== undefined ? [`${effortKey}: ${effective.effort}`] : []),
];
}
export function writeMarkdownAgentSurface(
content: string,
effective: Pick<EffectiveModelPolicy, "model" | "effort">,
options: MarkdownProjectionOptions = {},
): string {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
if (!match) throw new Error("agent markdown has no closed frontmatter block");
const effortKey = options.effortKey ?? "effort";
const removeKeys = new Set([
"tier",
"model",
"effort",
"variant",
...(options.removeKeys ?? []),
]);
const source = match[1].split(/\r?\n/);
const insertionKeys = new Set(options.insertBeforeKeys ?? []);
let insertAt = source.findIndex((line) => {
const key = /^([A-Za-z_][\w-]*):/.exec(line)?.[1];
return Boolean(key && (removeKeys.has(key) || insertionKeys.has(key)));
});
if (insertAt < 0) insertAt = source.length;
const kept = source.filter((line) => {
const key = /^([A-Za-z_][\w-]*):/.exec(line)?.[1];
return !key || !removeKeys.has(key);
});
const removedBefore = source.slice(0, insertAt).filter((line) => {
const key = /^([A-Za-z_][\w-]*):/.exec(line)?.[1];
return Boolean(key && removeKeys.has(key));
}).length;
const target = Math.max(0, insertAt - removedBefore);
const projected = [
...effectiveProjectionLines(effective, effortKey),
...(options.afterProjectionLines ?? []),
];
kept.splice(target, 0, ...projected);
return content.replace(match[0], () => `---\n${kept.join("\n")}\n---\n`);
}
export function writeCodexAgentSurface(
content: string,
effective: Pick<EffectiveModelPolicy, "model" | "effort">,
): string {
const hadNewline = content.endsWith("\n");
const source = content.replace(/\r\n/g, "\n").split("\n");
if (hadNewline) source.pop();
let insertAt = source.findIndex((line) =>
/^(?:model|model_reasoning_effort)\s*=/.test(line)
);
const kept = source.filter((line) =>
!/^(?:model|model_reasoning_effort)\s*=/.test(line)
);
if (insertAt < 0) {
insertAt = kept.findIndex((line) => /^description\s*=/.test(line));
insertAt = insertAt < 0 ? Math.min(2, kept.length) : insertAt + 1;
}
const lines = [
...(effective.model !== undefined ? [`model = ${JSON.stringify(effective.model)}`] : []),
...(effective.effort !== undefined
? [`model_reasoning_effort = ${JSON.stringify(effective.effort)}`]
: []),
];
kept.splice(insertAt, 0, ...lines);
return `${kept.join("\n")}${hadNewline ? "\n" : ""}`;
}
export function writeKiroAgentSurface(
content: string,
effective: Pick<EffectiveModelPolicy, "model">,
): string {
const parsed = JSON.parse(content) as Record<string, unknown>;
if (effective.model === undefined) delete parsed.model;
else parsed.model = effective.model;
return `${JSON.stringify(parsed, null, 2)}\n`;
}
export function writeKiroCliSurface(
content: string,
modelEfforts: readonly { model: string; effort: KiroEffort }[] = [],
cap: Tier | null = null,
): string {
const parsed = JSON.parse(content) as Record<string, unknown>;
const defaults = isRecord(parsed["chat.modelDefaults"])
? { ...parsed["chat.modelDefaults"] }
: {};
const effortOrder: readonly KiroEffort[] = ["low", "medium", "high", "xhigh", "max"];
for (const [model, effort] of Object.entries(kiroModelDefaults(cap, modelEfforts))) {
const rawEntry = defaults[model];
if (!isRecord(rawEntry)) {
defaults[model] = { output_config: { effort } };
continue;
}
const output = isRecord(rawEntry.output_config) ? rawEntry.output_config : {};
const current = typeof output.effort === "string" &&
(effortOrder as readonly string[]).includes(output.effort)
? output.effort as KiroEffort
: undefined;
if (!current || effortOrder.indexOf(effort) > effortOrder.indexOf(current)) {
defaults[model] = {
...rawEntry,
output_config: {
...output,
effort,
},
};
}
}
parsed["chat.modelDefaults"] = defaults;
return `${JSON.stringify(parsed, null, 2)}\n`;
}
export function applyModelPolicyToProjection(
projectionRoot: string,
harnessDir: string,
harness: ModelHarness,
policyValue?: ModelPolicyRecord | null,
capValue?: Tier | null,
): EffectiveModelPolicy[] {
const harnessRoot = join(projectionRoot, harnessDir);
const policy = policyValue ?? null;
const tiers = readAgentTiers(harnessRoot);
const cap = capValue === undefined
? resolveTierCap(join(projectionRoot, "aidlc", "spaces", "default", "memory"))
: capValue;
const effective = Object.entries(tiers).sort(([left], [right]) =>
left.localeCompare(right)
).map(([name, tier]) => resolveModelPolicy(policy, name, tier, harness, cap));
if (harness === "claude") {
for (const item of effective) {
const path = join(harnessRoot, "agents", `${modelAgentStem(item.agent)}.md`);
if (!existsSync(path)) throw new Error(`${path}: missing agent surface`);
writeFileSync(path, writeMarkdownAgentSurface(readFileSync(path, "utf-8"), item));
}
} else if (harness === "codex") {
for (const item of effective) {
const path = join(harnessRoot, "agents", `${modelAgentStem(item.agent)}.toml`);
if (!existsSync(path)) throw new Error(`${path}: missing agent surface`);
writeFileSync(path, writeCodexAgentSurface(readFileSync(path, "utf-8"), item));
}
} else if (harness === "opencode") {
for (const item of effective) {
const path = join(
projectionRoot,
".opencode",
"agents",
`${modelAgentStem(item.agent)}.md`,
);
if (!existsSync(path)) throw new Error(`${path}: missing agent surface`);
writeFileSync(
path,
writeMarkdownAgentSurface(readFileSync(path, "utf-8"), item, {
effortKey: "variant",
insertBeforeKeys: ["mode"],
}),
);
}
} else if (harness === "kiro") {
const modelEfforts: Array<{ model: string; effort: KiroEffort }> = [];
for (const item of effective) {
const path = join(harnessRoot, "agents", `${modelAgentStem(item.agent)}.json`);
if (!existsSync(path)) throw new Error(`${path}: missing agent surface`);
writeFileSync(path, writeKiroAgentSurface(readFileSync(path, "utf-8"), item));
if (item.model && item.effort) {
modelEfforts.push({ model: item.model, effort: item.effort });
}
}
const path = join(harnessRoot, "settings", "cli.json");
if (!existsSync(path)) throw new Error(`${path}: missing Kiro CLI settings`);
writeFileSync(
path,
writeKiroCliSurface(readFileSync(path, "utf-8"), modelEfforts, cap),
);
}
return effective;
}
function markdownSurfaceValues(
content: string,
effortKey: "effort" | "variant",
): { model?: string; effort?: ModelEffort } {
const fm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? "";
const model = /^model:\s*(\S+)\s*$/m.exec(fm)?.[1];
const effort = new RegExp(`^${effortKey}:\\s*(\\S+)\\s*$`, "m").exec(fm)?.[1];
return {
...(model ? { model } : {}),
...(effort && isModelEffort(effort) ? { effort } : {}),
};
}
function codexSurfaceValues(content: string): { model?: string; effort?: ModelEffort } {
const model = /^model\s*=\s*"([^"]+)"\s*$/m.exec(content)?.[1];
const effort = /^model_reasoning_effort\s*=\s*"([^"]+)"\s*$/m.exec(content)?.[1];
return {
...(model ? { model } : {}),
...(effort && isModelEffort(effort) ? { effort } : {}),
};
}
function valuesDiffer(
actual: { model?: string; effort?: ModelEffort },
expected: EffectiveModelPolicy,
): string[] {
const issues: string[] = [];
if (actual.model !== expected.model) {
issues.push(`model ${actual.model ?? "inherit"} != ${expected.model ?? "inherit"}`);
}
if (actual.effort !== expected.effort) {
issues.push(`effort ${actual.effort ?? "inherit"} != ${expected.effort ?? "inherit"}`);
}
return issues;
}
export function modelPolicySurfaceDrift(
projectDir: string,
harnessDir: string,
harness: ModelHarness,
policyValue: ModelPolicyRecord | null,
): string[] {
const harnessRoot = join(projectDir, harnessDir);
const policy = normalizeModelPolicy(policyValue);
const tiers = readAgentTiers(harnessRoot);
const cap = resolveTierCap(join(projectDir, "aidlc", "spaces", "default", "memory"));
const issues: string[] = [];
const expectedByAgent = Object.fromEntries(
Object.entries(tiers).map(([name, tier]) => [
name,
resolveModelPolicy(policy, name, tier, harness, cap),
]),
) as Record<string, EffectiveModelPolicy>;
const kiroCollapsed = harness === "kiro"
? kiroModelDefaults(
cap,
Object.values(expectedByAgent)
.filter((item): item is EffectiveModelPolicy & {
model: string;
effort: KiroEffort;
} => Boolean(item.model && item.effort))
.map((item) => ({ model: item.model, effort: item.effort })),
)
: {};
for (const [name] of Object.entries(tiers).sort(([left], [right]) =>
left.localeCompare(right)
)) {
const expected = expectedByAgent[name];
const surfaceExpected = harness === "kiro" && expected.model && kiroCollapsed[expected.model]
? { ...expected, effort: kiroCollapsed[expected.model] }
: expected;
for (const field of expected.unexpressed) {
issues.push(`${name}: ${field} policy is not expressible on ${harness}`);
}
let path = "";
let actual: { model?: string; effort?: ModelEffort } = {};
if (harness === "claude") {
path = join(harnessRoot, "agents", `${modelAgentStem(name)}.md`);
if (existsSync(path)) actual = markdownSurfaceValues(readFileSync(path, "utf-8"), "effort");
} else if (harness === "codex") {
path = join(harnessRoot, "agents", `${modelAgentStem(name)}.toml`);
if (existsSync(path)) actual = codexSurfaceValues(readFileSync(path, "utf-8"));
} else if (harness === "opencode") {
path = join(projectDir, ".opencode", "agents", `${modelAgentStem(name)}.md`);
if (existsSync(path)) actual = markdownSurfaceValues(readFileSync(path, "utf-8"), "variant");
} else if (harness === "kiro") {
path = join(harnessRoot, "agents", `${modelAgentStem(name)}.json`);
if (existsSync(path)) {
const parsed = JSON.parse(readFileSync(path, "utf-8")) as Record<string, unknown>;
actual.model = typeof parsed.model === "string" ? parsed.model : undefined;
if (surfaceExpected.model && surfaceExpected.effort) {
const cli = JSON.parse(
readFileSync(join(harnessRoot, "settings", "cli.json"), "utf-8"),
) as Record<string, unknown>;
const defaults = isRecord(cli["chat.modelDefaults"])
? cli["chat.modelDefaults"]
: {};
const rawEntry = defaults[surfaceExpected.model];
const entry: Record<string, unknown> = isRecord(rawEntry) ? rawEntry : {};
const output = isRecord(entry.output_config) ? entry.output_config : {};
actual.effort = typeof output.effort === "string" && isModelEffort(output.effort)
? output.effort
: undefined;
}
}
} else {
actual = {};
}
if (path && !existsSync(path)) {
issues.push(`${name}: missing surface ${path}`);
continue;
}
for (const issue of valuesDiffer(actual, surfaceExpected)) {
issues.push(`${name}: ${issue}`);
}
}
return [...new Set(issues)];
}
export function modelPolicyDoctorIssues(
harnessRoot: string,
harness: ModelHarness,
policyValue: ModelPolicyRecord | null,
): string[] {
const policy = normalizeModelPolicy(policyValue);
if (!policy) return [];
const tiers = readAgentTiers(harnessRoot);
const issues = Object.keys(policy.agents ?? {})
.filter((name) => !(name in tiers))
.map((name) => `orphaned agent exception: ${name}`);
const cap = resolveTierCap(join(harnessRoot, "..", "aidlc", "spaces", "default", "memory"));
for (const [name, tier] of Object.entries(tiers)) {
const effective = resolveModelPolicy(policy, name, tier, harness, cap);
for (const field of effective.unexpressed) {
issues.push(`${name}: ${field} policy is not expressible on ${harness}`);
}
}
return [...new Set(issues)].sort();
}
export function harnessHonestyNotes(
policy: ModelPolicyRecord | null,
tiers: AgentTiers,
harness: ModelHarness,
cap: Tier | null = null,
): string[] {
const effective = Object.entries(tiers).map(([name, tier]) =>
resolveModelPolicy(policy, name, tier, harness, cap)
);
const notes: string[] = [];
if (effective.some((item) => item.unexpressed.length > 0)) {
notes.push(HARNESS_HONESTY[harness].message);
}
for (const item of effective) {
if (item.clampedEffort) {
notes.push(
`${harness} clamps ${item.clampedEffort.from} effort down to ${item.clampedEffort.to}.`,
);
}
}
return [...new Set(notes)];
}
export function agentTiersFromAuthoredDirectory(agentsDir: string): AgentTiers {
const tiers: AgentTiers = {};
for (const file of readdirSync(agentsDir).filter((name) => name.endsWith("-agent.md")).sort()) {
const path = join(agentsDir, file);
const content = readFileSync(path, "utf-8");
const fm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
const tier = fm ? /^tier:\s*(\S+)\s*$/m.exec(fm)?.[1] : undefined;
if (tier !== "judgment" && tier !== "balanced" && tier !== "templated") {
throw new Error(`${path}: agent frontmatter has no valid tier line`);
}
tiers[modelAgentName(basename(file))] = tier;
}
return tiers;
}