fix: expose MDTODO safe stdin skill drift

This commit is contained in:
Codex
2026-07-13 10:25:50 +02:00
parent f5e5f937b1
commit cfcade4ec4
3 changed files with 183 additions and 0 deletions
@@ -81,6 +81,14 @@ export interface SkillAvailabilityReport {
sourceSampledSkillNames: string[];
targetSampledSkillNames: string[];
};
capabilities: {
mdtodoSafeTitleStdin: MdtodoSafeTitleStdinCapability;
};
warnings: string[];
next: {
inspect: string;
safeTitleExample: string;
};
repairHint: string | null;
error: string | null;
valuesPrinted: false;
@@ -164,6 +172,10 @@ export interface SkillSyncPreflightReport {
sourceSampledSkillNames: string[];
targetSampledSkillNames: string[];
};
capabilities: {
mdtodoSafeTitleStdin: MdtodoSafeTitleStdinCapability;
};
warnings: string[];
missing: {
sourceSkills: string[];
targetSkills: string[];
@@ -185,10 +197,30 @@ export interface SkillSyncPreflightReport {
health: string;
runtimePreflight: string;
contractTest: string;
safeTitleExample: string;
};
valuesPrinted: false;
}
export interface MdtodoSafeTitleStdinProbe {
path: string;
exists: boolean;
commands: Record<"add" | "add-sub" | "update", boolean>;
ready: boolean;
identity: string | null;
}
export interface MdtodoSafeTitleStdinCapability {
name: "mdtodo-safe-title-stdin";
nonBlocking: true;
ready: boolean;
freshness: "ready" | "source-stale" | "target-stale" | "missing";
source: MdtodoSafeTitleStdinProbe;
target: MdtodoSafeTitleStdinProbe;
warning: string | null;
next: string;
}
export const defaultRequiredSkills = ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"];
export const defaultSource = "/home/ubuntu/.agents/skills";
export const expectedTarget = "/root/.agents/skills";
@@ -303,6 +335,64 @@ function skillSetVersion(path: string, readable: boolean): SkillSyncPathReport["
}
}
function mdtodoCliPath(skillsPath: string): string {
return resolve(skillsPath, "mdtodo-edit", "scripts", "mdtodo-edit-cli.py");
}
function probeMdtodoSafeTitleStdin(skillsPath: string): MdtodoSafeTitleStdinProbe {
const path = mdtodoCliPath(skillsPath);
const exists = existsSync(path);
const commands = { add: false, "add-sub": false, update: false };
if (exists) {
for (const command of Object.keys(commands) as Array<keyof typeof commands>) {
const result = spawnSync("python3", [path, command, "--help"], { encoding: "utf8", timeout: 2000 });
commands[command] = result.status === 0 && `${result.stdout ?? ""}\n${result.stderr ?? ""}`.includes("--stdin");
}
}
const ready = Object.values(commands).every(Boolean);
let identity: string | null = null;
if (exists) {
try {
identity = createHash("sha256").update(readFileSync(path)).digest("hex").slice(0, 16);
} catch {
identity = null;
}
}
return { path, exists, commands, ready, identity };
}
export function collectMdtodoSafeTitleStdinCapability(sourcePath: string, targetPath: string): MdtodoSafeTitleStdinCapability {
const source = probeMdtodoSafeTitleStdin(sourcePath);
const target = probeMdtodoSafeTitleStdin(targetPath);
const freshness = target.ready
? "ready"
: source.ready
? "target-stale"
: source.exists || target.exists
? "source-stale"
: "missing";
const warning = freshness === "ready"
? null
: freshness === "target-stale"
? "MDTODO safe title stdin is available in the approved source but missing from the runner target projection."
: "MDTODO safe title stdin is missing from the approved source; update the managed agent_skills source before refreshing projections.";
return {
name: "mdtodo-safe-title-stdin",
nonBlocking: true,
ready: target.ready,
freshness,
source,
target,
warning,
next: "bun scripts/cli.ts codex skills-sync --dry-run --full",
};
}
export function renderMdtodoSafeTitleCommand(file: string, command: "add" | "add-sub" | "update", taskId?: string): string {
const positional = command === "add" ? shellQuote(file) : `${shellQuote(file)} ${shellQuote(taskId ?? "<taskId>")}`;
return `python3 "$HOME/.agents/skills/mdtodo-edit/scripts/mdtodo-edit-cli.py" ${command} ${positional} --stdin <<'EOF'\n<title>\nEOF`;
}
function accessProbe(path: string, mode: number, operation: string): { ok: boolean; failure: { path: string; operation: string; error: string } | null } {
try {
accessSync(path, mode);
@@ -450,6 +540,7 @@ export function collectSkillAvailability(options: SkillAvailabilityOptions): Ski
: targetSymlinkToSource ? "target-symlink" : "target";
const resolvedPath = target;
const selectedReport = targetProbe;
const mdtodoSafeTitleStdin = collectMdtodoSafeTitleStdinCapability(source, target);
const runnerUsable = !forbiddenPathConfigured && targetReady && !forbiddenPathExists;
const contractOk = runnerUsable;
const blocker = forbiddenPathConfigured
@@ -539,6 +630,12 @@ export function collectSkillAvailability(options: SkillAvailabilityOptions): Ski
sourceSampledSkillNames: sourceProbe.version.sampledSkillNames,
targetSampledSkillNames: targetProbe.version.sampledSkillNames,
},
capabilities: { mdtodoSafeTitleStdin },
warnings: mdtodoSafeTitleStdin.warning === null ? [] : [mdtodoSafeTitleStdin.warning],
next: {
inspect: mdtodoSafeTitleStdin.next,
safeTitleExample: renderMdtodoSafeTitleCommand("docs/MDTODO/example.md", "add"),
},
repairHint: contractOk
? null
: sourceHasRequiredSkills
@@ -561,6 +658,7 @@ export function collectSkillSyncPreflight(options: SkillSyncPreflightOptions = {
const forbiddenPathConfigured = forbiddenSourceConfigured || forbiddenTargetConfigured;
const forbiddenPathExists = forbiddenTargets.some((path) => existsSync(path));
const permissionFailures = [...source.permissionFailures, ...target.permissionFailures];
const mdtodoSafeTitleStdin = collectMdtodoSafeTitleStdinCapability(sourcePath, targetPath);
const blocker = forbiddenPathConfigured
? "forbidden-skills-path-configured"
: !source.report.approved
@@ -623,6 +721,8 @@ export function collectSkillSyncPreflight(options: SkillSyncPreflightOptions = {
sourceSampledSkillNames: source.report.version.sampledSkillNames,
targetSampledSkillNames: target.report.version.sampledSkillNames,
},
capabilities: { mdtodoSafeTitleStdin },
warnings: mdtodoSafeTitleStdin.warning === null ? [] : [mdtodoSafeTitleStdin.warning],
missing: {
sourceSkills: source.report.missingSkills,
targetSkills: target.report.missingSkills,
@@ -659,6 +759,7 @@ export function collectSkillSyncPreflight(options: SkillSyncPreflightOptions = {
health: "bun scripts/cli.ts microservice health code-queue",
runtimePreflight: "bun scripts/cli.ts codex pr-preflight --remote",
contractTest: "bun scripts/code-queue-runner-skills-contract-test.ts",
safeTitleExample: renderMdtodoSafeTitleCommand("docs/MDTODO/example.md", "add"),
},
valuesPrinted: false,
};