fix: show skill proposal support files on inspect

This commit is contained in:
Shakker
2026-05-30 13:52:51 +01:00
committed by Shakker
parent bc6d570659
commit 3ea82adf97
10 changed files with 93 additions and 2 deletions

View File

@@ -104,6 +104,12 @@ describe("SkillsProposalInspectResultSchema", () => {
],
},
content: "# Weather Helper\n",
supportFiles: [
{
path: "references/weather.md",
content: "Use current weather before recommendations.\n",
},
],
};
expect(Value.Check(SkillsProposalInspectResultSchema, result)).toBe(true);

View File

@@ -628,6 +628,7 @@ export const SkillsProposalInspectResultSchema = Type.Object(
{
record: SkillProposalRecordSchema,
content: Type.String(),
supportFiles: Type.Optional(Type.Array(SkillProposalSupportFileInputSchema, { maxItems: 64 })),
},
{ additionalProperties: false },
);

View File

@@ -158,6 +158,21 @@ describe("skill_research tool", () => {
expect((inspected.details as { proposalContent: string }).proposalContent).toContain(
"Check weather, alerts, and timing.",
);
expect((inspected.content[0] as { text: string }).text).toContain(
"--- references/weather.md ---",
);
expect(
(
inspected.details as {
supportFiles: Array<{ path: string; content: string }>;
}
).supportFiles,
).toEqual([
{
path: "references/weather.md",
content: "Use weather API details and current alerts.\n",
},
]);
const revisedByName = await tool.execute("call-5", {
action: "revise",

View File

@@ -287,6 +287,9 @@ function proposalResult(
scanState: proposal.record.scan.state,
proposedVersion: proposal.record.proposedVersion,
...(options.includeContent ? { proposalContent: proposal.content } : {}),
...(options.includeContent && proposal.supportFiles
? { supportFiles: proposal.supportFiles }
: {}),
},
};
}
@@ -310,10 +313,15 @@ async function readProposalForInspect(
}
return proposal;
}
return await resolvePendingSkillProposal({
const resolved = await resolvePendingSkillProposal({
name: readStringParam(params, "name", { required: true }),
workspaceDir,
});
const proposal = await inspectSkillProposal(resolved.record.id, { workspaceDir });
if (!proposal) {
throw new ToolInputError(`Skill proposal not found: ${resolved.record.id}`);
}
return proposal;
}
function readProposalStatusParam(params: Record<string, unknown>): SkillProposalStatus | undefined {
@@ -398,6 +406,14 @@ function formatProposalList(proposals: readonly SkillProposalManifestEntry[]): s
}
function formatProposalInspect(proposal: SkillProposalReadResult): string {
const supportFiles =
proposal.supportFiles && proposal.supportFiles.length > 0
? [
"",
"Support files:",
...proposal.supportFiles.flatMap((file) => ["", `--- ${file.path} ---`, file.content]),
]
: [];
return [
`Proposal: ${proposal.record.id}`,
`Status: ${proposal.record.status}`,
@@ -407,6 +423,7 @@ function formatProposalInspect(proposal: SkillProposalReadResult): string {
`Scan: ${proposal.record.scan.state}`,
"",
proposal.content,
...supportFiles,
].join("\n");
}

View File

@@ -192,6 +192,14 @@ function formatSkillProposalList(manifest: SkillProposalManifest): string {
function formatSkillProposalInspect(read: SkillProposalReadResult): string {
const { record } = read;
const supportFiles =
read.supportFiles && read.supportFiles.length > 0
? [
"",
"Support files:",
...read.supportFiles.flatMap((file) => ["", `--- ${file.path} ---`, file.content]),
]
: [];
return [
`ID: ${record.id}`,
`Status: ${record.status}`,
@@ -202,6 +210,7 @@ function formatSkillProposalInspect(read: SkillProposalReadResult): string {
record.statusReason ? `Reason: ${record.statusReason}` : undefined,
"",
read.content,
...supportFiles,
]
.filter((line) => line !== undefined)
.join("\n");

View File

@@ -139,6 +139,8 @@ describe("skills workshop cli", () => {
await runCommand(["skills", "workshop", "inspect", proposalId!]);
expect(mocks.runtimeStdout.at(-1)).toContain("status: proposal");
expect(mocks.runtimeStdout.at(-1)).toContain("--- references/weather.md ---");
expect(mocks.runtimeStdout.at(-1)).toContain("Use current conditions before recommendations.");
const revisedPath = path.join(mocks.workspaceDir, "revised-proposal.md");
await fs.writeFile(

View File

@@ -118,6 +118,18 @@ describe("skills proposal gateway handlers", () => {
});
expect(inspect.ok).toBe(true);
expect((inspect.response as { content: string }).content).toContain("status: proposal");
expect(
(
inspect.response as {
supportFiles?: Array<{ path: string; content: string }>;
}
).supportFiles,
).toEqual([
{
path: "references/weather.md",
content: "Use current weather before recommendations.\n",
},
]);
const revise = await callHandler("skills.proposals.revise", {
proposalId: created.record.id,

View File

@@ -71,6 +71,18 @@ describe("skill workshop proposals", () => {
"references/weather-api.md",
"scripts/check-weather.js",
]);
await expect(inspectSkillProposal(proposal.record.id)).resolves.toMatchObject({
supportFiles: [
{
path: "references/weather-api.md",
content: "# Weather API\n\nUse the current weather endpoint.\n",
},
{
path: "scripts/check-weather.js",
content: "export function parseWeather(value) { return value; }\n",
},
],
});
expect(proposal.record.target.skillFile).toBe(
path.join(workspaceDir, "skills", "weather-helper", "SKILL.md"),
);

View File

@@ -169,7 +169,7 @@ export async function inspectSkillProposal(
if (options.workspaceDir && !isProposalInWorkspace(read.record, options.workspaceDir)) {
return null;
}
return read;
return await hydrateProposalSupportFiles(read);
}
export async function resolvePendingSkillProposal(input: {
@@ -804,6 +804,22 @@ async function readRequiredProposal(
return read;
}
async function hydrateProposalSupportFiles(
read: SkillProposalReadResult,
): Promise<SkillProposalReadResult> {
const supportFiles = await readProposalSupportFiles(read.record);
if (supportFiles.length === 0) {
return read;
}
return {
...read,
supportFiles: supportFiles.map((file) => ({
path: file.path,
content: file.content,
})),
};
}
function isProposalInWorkspace(record: SkillProposalRecord, workspaceDir: string): boolean {
try {
assertInsideWorkspace(workspaceDir, record.target.skillFile, "skill file");

View File

@@ -141,6 +141,7 @@ export type SkillProposalActionInput = {
export type SkillProposalReadResult = {
record: SkillProposalRecord;
content: string;
supportFiles?: SkillProposalSupportFileInput[];
};
export type SkillProposalApplyResult = {