/*
Unit tests for argv covering parseKernelArgv (legacy heuristic parser); resolveCommandFlags (schema-driven parser); KERNEL_UNIVERSAL_FLAGS.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- Fixtures stay inline and minimal so each case states its own setup.
- RFC-1311: Compass header sweep — declares the argv suite's contract for test-file scaffold coverage.
*/
// @ai-invariant fixtures must not couple to kernel internals beyond the public surface under test
import { test, expect, describe } from "vitest";
import { parseKernelArgv, resolveCommandFlags, KERNEL_UNIVERSAL_FLAGS } from "../argv.ts";
import type { KernelCommandDefinition } from "@warpgogol/werkstatt-shared/kernel";
const mockDefinition: KernelCommandDefinition = {
name: "test.cmd",
description: "test command",
scope: "workspace",
flags: {
id: { kind: "string", required: true, description: "The ID" },
tag: { kind: "string[]", description: "Tags" },
verbose: { kind: "boolean", description: "Verbose" },
},
execute: async () => ({ exitCode: 0, ok: true, summary: "ok" }),
};
describe("parseKernelArgv (legacy heuristic parser)", () => {
test("parses boolean flag without consuming next token", () => {
const { flags, diagnostics } = parseKernelArgv(["--dry-run", "alpha"]);
expect(flags["dry-run"]).toBe(true);
expect(diagnostics).toHaveLength(1);
expect(diagnostics[0]!.ruleId).toBe("KERNEL-ARG-01");
});
test("parses string flag by consuming next token", () => {
const { flags, diagnostics } = parseKernelArgv(["--site", "my-site"]);
expect(flags["site"]).toBe("my-site");
expect(diagnostics).toHaveLength(0);
});
test("parses inline value with =", () => {
const { flags } = parseKernelArgv(["--site=my-site"]);
expect(flags["site"]).toBe("my-site");
});
test("inline value containing = is preserved verbatim", () => {
const { flags } = parseKernelArgv(["--site=my-site=extra"]);
expect(flags["site"]).toBe("my-site=extra");
});
test("collects repeated string flags into array", () => {
const { flags } = parseKernelArgv(["--tag", "a", "--tag", "b"]);
expect(flags["tag"]).toEqual(["a", "b"]);
});
test("emits diagnostic for positional argument", () => {
const { diagnostics } = parseKernelArgv(["positional"]);
expect(diagnostics).toHaveLength(1);
expect(diagnostics[0]!.ruleId).toBe("KERNEL-ARG-01");
});
test("passthrough after -- emits diagnostics for all subsequent tokens", () => {
const { diagnostics } = parseKernelArgv(["--", "foo", "bar"]);
expect(diagnostics).toHaveLength(2);
});
test("boolean flag set includes dry-run, force, json, quiet, verbose", () => {
const { flags } = parseKernelArgv(["--dry-run", "--force", "--json", "--quiet", "--verbose"]);
expect(flags["dry-run"]).toBe(true);
expect(flags["force"]).toBe(true);
expect(flags["json"]).toBe(true);
expect(flags["quiet"]).toBe(true);
expect(flags["verbose"]).toBe(true);
});
test("--output aliases to --json without consuming the next token", () => {
const { flags, diagnostics } = parseKernelArgv(["--output"]);
expect(flags["json"]).toBe(true);
expect(flags["output"]).toBeUndefined();
expect(diagnostics).toHaveLength(0);
});
});
describe("resolveCommandFlags (schema-driven parser)", () => {
test("resolves boolean flag from schema", () => {
const { flags, diagnostics } = resolveCommandFlags(["--verbose"], mockDefinition);
expect(flags["verbose"]).toBe(true);
// KERNEL-FLAG-03 for missing required --id is expected
expect(diagnostics.filter((d) => d.ruleId !== "KERNEL-FLAG-03")).toHaveLength(0);
});
test("resolves string flag by consuming next token", () => {
const { flags, diagnostics } = resolveCommandFlags(["--id", "abc"], mockDefinition);
expect(flags["id"]).toBe("abc");
expect(diagnostics).toHaveLength(0);
});
test("resolves inline value with =", () => {
const { flags } = resolveCommandFlags(["--id=abc"], mockDefinition);
expect(flags["id"]).toBe("abc");
});
test("emits KERNEL-FLAG-01 for unknown flag", () => {
const { diagnostics } = resolveCommandFlags(["--bogus"], mockDefinition);
const flag01 = diagnostics.filter((d) => d.ruleId === "KERNEL-FLAG-01");
expect(flag01).toHaveLength(1);
});
test("--output aliases to --json in the schema-driven parser", () => {
const { flags, diagnostics } = resolveCommandFlags(["--output"], mockDefinition);
expect(flags["json"]).toBe(true);
expect(flags["output"]).toBeUndefined();
expect(diagnostics.filter((d) => d.ruleId !== "KERNEL-FLAG-03")).toHaveLength(0);
});
test("emits KERNEL-FLAG-02 when string flag has no value", () => {
const { diagnostics } = resolveCommandFlags(["--id"], mockDefinition);
const flag02 = diagnostics.filter((d) => d.ruleId === "KERNEL-FLAG-02");
expect(flag02).toHaveLength(1);
});
test("emits KERNEL-FLAG-02 when a single-value string flag is repeated", () => {
const { diagnostics } = resolveCommandFlags(["--id", "a", "--id", "b"], mockDefinition);
const flag02 = diagnostics.filter((d) => d.ruleId === "KERNEL-FLAG-02");
expect(flag02).toHaveLength(1);
expect(flag02[0]!.message).toContain("single value");
});
test("repeated string[] flag still collects into an array without diagnostics", () => {
const { flags, diagnostics } = resolveCommandFlags(
["--id", "x", "--tag", "a", "--tag", "b"],
mockDefinition,
);
expect(flags["tag"]).toEqual(["a", "b"]);
expect(diagnostics.filter((d) => d.ruleId === "KERNEL-FLAG-02")).toHaveLength(0);
});
test("inline value containing = is preserved verbatim", () => {
const { flags } = resolveCommandFlags(["--id=abc=def"], mockDefinition);
expect(flags["id"]).toBe("abc=def");
});
test("emits KERNEL-FLAG-03 for missing required flag", () => {
const { diagnostics } = resolveCommandFlags(["--verbose"], mockDefinition);
const flag03 = diagnostics.find((d) => d.ruleId === "KERNEL-FLAG-03");
expect(flag03).toBeDefined();
expect(flag03!.message).toContain("--id");
});
test("applies default value for unspecified flag with default", () => {
const def: KernelCommandDefinition = {
name: "test.cmd",
description: "test",
scope: "workspace",
flags: {
level: { kind: "string", default: "info", description: "Level" },
},
execute: async () => ({ exitCode: 0, ok: true, summary: "ok" }),
};
const { flags } = resolveCommandFlags([], def);
expect(flags["level"]).toBe("info");
});
test("accepts universal flags (site, json, dry-run, etc.)", () => {
const { flags, diagnostics } = resolveCommandFlags(
["--site", "foo", "--json", "--dry-run"],
mockDefinition,
);
expect(diagnostics.filter((d) => d.ruleId === "KERNEL-FLAG-01")).toHaveLength(0);
expect(flags["site"]).toBe("foo");
expect(flags["json"]).toBe(true);
expect(flags["dry-run"]).toBe(true);
});
test("collects string[] flag from repeated tokens", () => {
const { flags } = resolveCommandFlags(["--tag", "a", "--tag", "b"], mockDefinition);
expect(flags["tag"]).toEqual(["a", "b"]);
});
test("string[] flag consumes consecutive bare tokens as a value list", () => {
const { flags, diagnostics } = resolveCommandFlags(
["--id", "x", "--tag", "a", "b", "c"],
mockDefinition,
);
expect(flags["tag"]).toEqual(["a", "b", "c"]);
expect(diagnostics).toHaveLength(0);
});
test("string[] greedy consumption stops at the next --flag", () => {
const { flags, diagnostics } = resolveCommandFlags(
["--id", "x", "--tag", "a", "b", "--verbose"],
mockDefinition,
);
expect(flags["tag"]).toEqual(["a", "b"]);
expect(flags["verbose"]).toBe(true);
expect(diagnostics).toHaveLength(0);
});
test("string[] flag with no value still emits KERNEL-FLAG-02", () => {
const { diagnostics } = resolveCommandFlags(
["--id", "x", "--tag", "--verbose"],
mockDefinition,
);
expect(diagnostics.some((d) => d.ruleId === "KERNEL-FLAG-02")).toBe(true);
});
test("single string flag does NOT consume extra bare tokens — they stay KERNEL-ARG-01", () => {
const { flags, diagnostics } = resolveCommandFlags(["--id", "a", "b"], mockDefinition);
expect(flags["id"]).toBe("a");
expect(diagnostics).toHaveLength(1);
expect(diagnostics[0]!.ruleId).toBe("KERNEL-ARG-01");
});
test("boolean flag with inline false value resolves to false", () => {
const { flags } = resolveCommandFlags(["--verbose=false"], mockDefinition);
expect(flags["verbose"]).toBe(false);
});
});
describe("KERNEL_UNIVERSAL_FLAGS", () => {
test("includes site, all, json, quiet, verbose, root, dry-run, force, help", () => {
const keys = Object.keys(KERNEL_UNIVERSAL_FLAGS);
expect(keys).toContain("site");
expect(keys).toContain("all");
expect(keys).toContain("json");
expect(keys).toContain("quiet");
expect(keys).toContain("verbose");
expect(keys).toContain("root");
expect(keys).toContain("dry-run");
expect(keys).toContain("force");
expect(keys).toContain("help");
});
test("site is string kind", () => {
expect(KERNEL_UNIVERSAL_FLAGS["site"]!.kind).toBe("string");
});
test("dry-run is boolean kind", () => {
expect(KERNEL_UNIVERSAL_FLAGS["dry-run"]!.kind).toBe("boolean");
});
});