fix(maestro): resolve command wrappers with a shell-aware tokenizer (#199)

command_program/command_tool whitespace-split and only skipped whole
wrapper words, so quoted paths broke, sudo -u/env -i/nohup/time/npx
--yes/pnpm|yarn --silent dlx/bun x were rejected, and pnpm claude /
yarn claude (a package.json script, not `dlx claude`) resolved as a
Claude launch. Replace it with a tokenizer that honours quoting and
escaping without evaluating any expansion, and per-wrapper option
tables (env, sudo, command, nohup, time, npx, pnpm, yarn, bun) that
require dlx/x before looking through a package-manager wrapper.
Ambiguous shell grammar (pipelines, list operators, redirections,
substitution, unterminated quotes) is rejected outright rather than
guessed through; command_program still degrades to the naive first
word for display, but command_tool returns None.

Mirror the same resolver in web/src/toolIcon.ts and add
fixtures/command-resolution.json as a parity fixture both test
suites assert against.
This commit is contained in:
Miguel Palhas
2026-09-05 13:19:28 +01:00
parent e7086512e7
commit 370edfa763
5 changed files with 691 additions and 74 deletions
+15
View File
@@ -1,5 +1,12 @@
import { describe, expect, it } from "vitest";
import { splitCommand, toolIconSvg } from "./toolIcon.js";
import commandResolutionFixture from "../../fixtures/command-resolution.json";
interface CommandResolutionCase {
command: string;
program: string;
tool: string | null;
}
describe("tool icons on the command fragment (docs/SIDEBAR.md)", () => {
it("recognises the agents and shells sessions are spawned with", () => {
@@ -46,6 +53,14 @@ describe("tool icons on the command fragment (docs/SIDEBAR.md)", () => {
expect(splitCommand("").tool).toBeNull();
});
it("resolves every case in the shared command-resolution fixture (#199)", () => {
const cases = commandResolutionFixture.cases as CommandResolutionCase[];
expect(cases.length).toBeGreaterThan(0);
for (const { command, program, tool } of cases) {
expect(splitCommand(command), command).toEqual({ tool, program });
}
});
it("gives every recognised tool a labelled mark", () => {
for (const tool of ["claude", "codex", "opencode", "gemini", "aider", "cursor", "pi", "shell"]) {
const svg = toolIconSvg(tool);