mosaic dispatch: codex review fixes (comms repo binding, q lookup, watch help)
ci/woodpecker/pr/ci Pipeline was successful

- comms: bind comms-send.sh to the SELECTED repository via
  FLEET_COMMS_REPO in the spawned env; without it, --comms-repo chose
  the executable but the tool still operated on the default checkout
  (codex on 9c8b6ebf). Spec asserts the stub observes the binding.
- q: Object.hasOwn subcommand lookup (reserved property names like
  toString must not leak through the record); spec arm added.
- watch: .helpOption(false) — the tool owns help too; --help now passes
  through to agent-watch.sh instead of commander's wrapper help
  (verified against dist).
This commit is contained in:
2026-08-28 17:50:39 -05:00
parent 9c8b6ebfe1
commit e858f1bc62
5 changed files with 22 additions and 4 deletions
+8 -2
View File
@@ -68,7 +68,7 @@ describe('registerCommsCommand routing', () => {
); );
writeFileSync( writeFileSync(
join(repo, 'tools', 'comms-send.sh'), join(repo, 'tools', 'comms-send.sh'),
`#!/usr/bin/env bash\nprintf '%s\\n' "$*" >> ${JSON.stringify(commsLog)}\nexit 5\n`, `#!/usr/bin/env bash\nprintf '%s\\n' "$*" >> ${JSON.stringify(commsLog)}\nprintf 'FLEET_COMMS_REPO=%s FLEET_COMMS_SITE=%s\\n' "$FLEET_COMMS_REPO" "$FLEET_COMMS_SITE" >> ${JSON.stringify(commsLog)}\nexit 5\n`,
); );
chmodSync(join(brain, 'tools', 'tmux', 'agent-send.sh'), 0o755); chmodSync(join(brain, 'tools', 'tmux', 'agent-send.sh'), 0o755);
chmodSync(join(repo, 'tools', 'comms-send.sh'), 0o755); chmodSync(join(repo, 'tools', 'comms-send.sh'), 0o755);
@@ -112,7 +112,13 @@ describe('registerCommsCommand routing', () => {
{ from: 'user' }, { from: 'user' },
); );
expect(process.exitCode).toBe(5); expect(process.exitCode).toBe(5);
expect(readFileSync(f.commsLog, 'utf8').trim()).toBe('-t usc/fred -c human -m hello there'); expect(readFileSync(f.commsLog, 'utf8').split('\n')[0]?.trim()).toBe(
'-t usc/fred -c human -m hello there',
);
// The sender must bind comms-send.sh to the SELECTED repo (codex 9c8b6ebf).
expect(readFileSync(f.commsLog, 'utf8')).toContain(
`FLEET_COMMS_REPO=${f.repo} FLEET_COMMS_SITE=usc`,
);
expect(existsSync(f.tmuxLog)).toBe(false); // same-host tool never invoked expect(existsSync(f.tmuxLog)).toBe(false); // same-host tool never invoked
}); });
+5 -1
View File
@@ -105,7 +105,11 @@ export function registerCommsCommand(program: Command): void {
process.exitCode = 2; // invocation defect: fixable by the caller process.exitCode = 2; // invocation defect: fixable by the caller
return; return;
} }
const env = { ...process.env, FLEET_COMMS_SITE: opts.site }; // comms-send.sh locates its working repo via FLEET_COMMS_REPO
// (default $HOME/src/fleet-comms); without this, --comms-repo
// would select the executable but not the repository it operates
// on (codex review of 9c8b6ebf).
const env = { ...process.env, FLEET_COMMS_SITE: opts.site, FLEET_COMMS_REPO: repo };
const result = spawnSync(tool, fleetCommsSendArgs(opts.site, target, message, opts), { const result = spawnSync(tool, fleetCommsSendArgs(opts.site, target, message, opts), {
stdio: 'inherit', stdio: 'inherit',
env, env,
+4
View File
@@ -57,6 +57,10 @@ describe('registerQCommand usage + dispatch', () => {
process.exitCode = undefined; process.exitCode = undefined;
await program.parseAsync(['q', 'bogus'], { from: 'user' }); await program.parseAsync(['q', 'bogus'], { from: 'user' });
expect(process.exitCode).toBe(2); expect(process.exitCode).toBe(2);
process.exitCode = undefined;
// Reserved property names must not leak through the record lookup.
await program.parseAsync(['q', 'toString'], { from: 'user' });
expect(process.exitCode).toBe(2);
}); });
it('execs the brain tool with pass-through args and exit code', async () => { it('execs the brain tool with pass-through args and exit code', async () => {
+1 -1
View File
@@ -44,7 +44,7 @@ export function registerQCommand(program: Command): void {
const home = mosaicHome ?? DEFAULT_MOSAIC_HOME; const home = mosaicHome ?? DEFAULT_MOSAIC_HOME;
const sub = args[0]; const sub = args[0];
if (!sub || !(sub in SUBCOMMANDS)) { if (!sub || !Object.hasOwn(SUBCOMMANDS, sub)) {
console.error('mosaic q: expected a subcommand:'); console.error('mosaic q: expected a subcommand:');
for (const [name, entry] of Object.entries(SUBCOMMANDS)) { for (const [name, entry] of Object.entries(SUBCOMMANDS)) {
console.error(` mosaic q ${name} ${entry.help}`); console.error(` mosaic q ${name} ${entry.help}`);
+4
View File
@@ -28,6 +28,10 @@ export function registerWatchCommand(program: Command): void {
// surface without the CLI needing passThroughOptions (which would // surface without the CLI needing passThroughOptions (which would
// force enablePositionalOptions fleet-wide on the root program). // force enablePositionalOptions fleet-wide on the root program).
.allowUnknownOption() .allowUnknownOption()
// The tool owns help too: without this, commander would intercept
// --help and answer with wrapper help instead of agent-watch's own
// (codex review of 18f3dd49).
.helpOption(false)
.argument('[args...]', 'args passed through to agent-watch.sh') .argument('[args...]', 'args passed through to agent-watch.sh')
.action(async (args: string[], _opts: unknown, command: Command) => { .action(async (args: string[], _opts: unknown, command: Command) => {
// --mosaic-home is not global in this CLI; walk parents for it and // --mosaic-home is not global in this CLI; walk parents for it and