fix: expose MDTODO safe stdin skill drift
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user