diff --git a/.changeset/fix-skill-rule-extra-args.md b/.changeset/fix-skill-rule-extra-args.md new file mode 100644 index 0000000..0644d45 --- /dev/null +++ b/.changeset/fix-skill-rule-extra-args.md @@ -0,0 +1,5 @@ +--- +"@stainless-code/codemap": patch +--- + +Reject unexpected arguments on `codemap skill` and `codemap rule` instead of silently printing bundled content. diff --git a/src/cli/cmd-skill.test.ts b/src/cli/cmd-skill.test.ts index 3b1be58..09497c3 100644 --- a/src/cli/cmd-skill.test.ts +++ b/src/cli/cmd-skill.test.ts @@ -8,6 +8,38 @@ import { checkConsumerPointers, EXPECTED_POINTER_VERSION, } from "../application/agent-content"; +import { parseAgentContentRest } from "./cmd-skill"; + +describe("parseAgentContentRest", () => { + it("returns help on --help / -h", () => { + expect(parseAgentContentRest(["skill", "--help"]).kind).toBe("help"); + expect(parseAgentContentRest(["rule", "-h"]).kind).toBe("help"); + }); + + it("runs with no extra arguments", () => { + expect(parseAgentContentRest(["skill"])).toEqual({ + kind: "run", + verb: "skill", + }); + expect(parseAgentContentRest(["rule"])).toEqual({ + kind: "run", + verb: "rule", + }); + }); + + it("errors on unexpected flags or positionals", () => { + const skillJson = parseAgentContentRest(["skill", "--json"]); + expect(skillJson.kind).toBe("error"); + if (skillJson.kind === "error") { + expect(skillJson.message).toContain("unexpected argument"); + } + const ruleExtra = parseAgentContentRest(["rule", "extra"]); + expect(ruleExtra.kind).toBe("error"); + if (ruleExtra.kind === "error") { + expect(ruleExtra.message).toContain("unexpected argument"); + } + }); +}); describe("agent content fetch surfaces", () => { it("assembles the skill from section files", () => { diff --git a/src/cli/cmd-skill.ts b/src/cli/cmd-skill.ts index 5644b53..7768843 100644 --- a/src/cli/cmd-skill.ts +++ b/src/cli/cmd-skill.ts @@ -13,6 +13,37 @@ import type { AgentContentKind } from "../application/agent-content"; */ export type { AgentContentKind }; +export type AgentContentRest = + | { kind: "help"; verb: AgentContentKind } + | { kind: "run"; verb: AgentContentKind } + | { kind: "error"; message: string }; + +/** Parse `codemap skill` / `codemap rule` argv after bootstrap strips global flags. */ +export function parseAgentContentRest(rest: string[]): AgentContentRest { + const verb = rest[0]; + if (verb !== "skill" && verb !== "rule") { + throw new Error( + `parseAgentContentRest: expected first token skill|rule, got ${String(verb)}`, + ); + } + const args = rest.slice(1); + if (args.length === 0) return { kind: "run", verb }; + if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) { + return { kind: "help", verb }; + } + const bad = args.find((a) => a !== "--help" && a !== "-h"); + if (bad !== undefined) { + return { + kind: "error", + message: `codemap ${verb}: unexpected argument "${bad}". Run \`codemap ${verb} --help\` for usage.`, + }; + } + return { + kind: "error", + message: `codemap ${verb}: unexpected extra arguments. Run \`codemap ${verb} --help\` for usage.`, + }; +} + export function printAgentContentCmdHelp(kind: AgentContentKind): void { const verb = kind; const source = `templates/agent-content/${kind}/*.md (assembled)`; diff --git a/src/cli/main.ts b/src/cli/main.ts index 4c73fa5..8681c87 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -98,14 +98,21 @@ Copies bundled agent templates into .agents/ under the project root. } if (rest[0] === "skill" || rest[0] === "rule") { - const kind = rest[0]; - const { printAgentContentCmdHelp, runAgentContentCmd } = - await import("./cmd-skill.js"); - if (rest.includes("--help") || rest.includes("-h")) { - printAgentContentCmdHelp(kind); + const { + parseAgentContentRest, + printAgentContentCmdHelp, + runAgentContentCmd, + } = await import("./cmd-skill.js"); + const parsed = parseAgentContentRest(rest); + if (parsed.kind === "help") { + printAgentContentCmdHelp(parsed.verb); return; } - runAgentContentCmd(kind); + if (parsed.kind === "error") { + console.error(parsed.message); + process.exit(1); + } + runAgentContentCmd(parsed.verb); return; }