Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions codev/projects/846-remove-codev-afx-codev-agent-f/status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
id: '846'
title: remove-codev-afx-codev-agent-f
protocol: air
phase: pr
plan_phases: []
current_plan_phase: null
gates:
pr:
status: pending
requested_at: '2026-05-24T19:22:30.304Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-05-24T19:11:38.472Z'
updated_at: '2026-05-24T19:22:30.305Z'
64 changes: 64 additions & 0 deletions codev/state/air-846_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# air-846 — Remove `codev afx` / `codev agent-farm` / `codev af` wrapped variants

## Context

Issue #846: kill the `codev`-wrapped invocation surface for agent-farm. `afx` standalone bin
stays as the only supported entrypoint. Trigger was PR #833 CMAP exposing that
`workspace-recover.ts` respawns via `spawn(process.execPath, [process.argv[1], 'spawn', ...])`
— which silently breaks when `process.argv[1]` is the `codev` entrypoint instead of `afx`.
Cheaper to remove the alternate surface than patch every spawn-child callsite.

## Plan

1. `packages/codev/src/cli.ts`
- Drop `.command('agent-farm').aliases(['afx', 'af'])` registration.
- Drop `args[0] === 'agent-farm'` early-dispatch in exported `run()`.
- Replace top-level argv branch (`agent-farm` / `afx` / `af`) with a non-zero exit
emitting "`codev <variant>` is no longer supported; use `afx <subcommand>` directly".
- Drop the now-unused `runAgentFarm` import.

2. `packages/codev/bin/afx.js` — currently delegates via `run(['agent-farm', ...args])`,
which depended on the removed early-dispatch. Switch to importing `runAgentFarm`
directly from `../dist/agent-farm/cli.js`. Architect's issue note ("Keep `runAgentFarm`
itself imported — it's still used by the standalone `afx` bin shim") is best honored
by making the bin shim import it directly rather than re-routing through cli.ts.

3. `packages/codev/bin/af.js` — standalone `af` bin (separate from the wrapped `codev af`)
already prints its own deprecation warning. Keep it working (issue doesn't ask to
remove this standalone) but switch to direct `runAgentFarm` call for the same reason.

4. Tests
- Extend `src/__tests__/cli/af.e2e.test.ts` (or sibling) with cases verifying that
`codev afx <anything>`, `codev agent-farm <anything>`, `codev af <anything>` exit
non-zero with the deprecation stderr.
- Existing `runAfx` E2E tests must still pass (path through afx.js bin unchanged from
a caller's perspective).

## Decisions

- **`af` standalone bin stays.** Issue scope is the codev-wrapped variants. The
standalone `af` bin is already deprecated and doesn't carry the
`process.argv[1]`-split fragility — it routes the same way as `afx` will after this
change. Removing it would expand scope.
- **No doc sweep changes needed.** Grep found zero live references to `codev afx` /
`codev agent-farm` / `codev af` outside historical specs/plans/reviews (which we
don't rewrite). Issue's "Docs sweep" bullet was a "spot-check suggests there are very
few but verify" — verified, there are none.

## Iter-2 (architect-expanded scope)

Architect overrode the `af`-stays decision: human wants `af` removed in the same PR.

Changes pushed in iter-2:
- Deleted `packages/codev/bin/af.js` (`git rm`).
- Removed `"af": "./bin/af.js"` from `packages/codev/package.json` bin map.
- Removed `af` from the cli.ts deprecation handler — `codev af` now falls through to
commander as an unknown command, consistent with `af` itself being a missing bin.
`codev afx` and `codev agent-farm` keep their helpful deprecation stderr because
those are the commonly-typed entrypoints that benefit from the gentle nudge.
- Updated `install.e2e.test.ts` to assert `AF_BIN` does NOT exist (was: asserts exists).
- Updated `af.e2e.test.ts` `codev af` case to assert commander's `unknown command` error.
- Removed the unused `runAf` helper from `cli/helpers.ts` (kept `AF_BIN` export for the
negative-existence assertion).

All checks still green: 3078 unit, 83 CLI e2e. PR will re-trigger the porch `pr` gate.
8 changes: 0 additions & 8 deletions packages/codev/bin/af.js

This file was deleted.

12 changes: 8 additions & 4 deletions packages/codev/bin/afx.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env node

// afx - Agent Farm CLI (standalone command)
// Routes to agent-farm command handler
import { run } from '../dist/cli.js';
// Issue #846: imports runAgentFarm directly. The `codev afx` / `codev agent-farm`
// wrapped variants were removed because they created a `process.argv[1]` invocation-style
// split that broke spawn(execPath, [argv[1], ...]) callers (e.g. workspace-recover.ts).
import { runAgentFarm } from '../dist/agent-farm/cli.js';

const args = process.argv.slice(2);
run(['agent-farm', ...args]);
runAgentFarm(process.argv.slice(2)).catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
1 change: 0 additions & 1 deletion packages/codev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"codev": "./bin/codev.js",
"porch": "./bin/porch.js",
"afx": "./bin/afx.js",
"af": "./bin/af.js",
"consult": "./bin/consult.js",
"team": "./bin/team.js",
"generate-image": "./bin/generate-image.js"
Expand Down
34 changes: 34 additions & 0 deletions packages/codev/src/__tests__/cli/af.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,40 @@ describe('afx command (CLI)', () => {
expect(output).toMatch(/Agent Farm|Tower|Status/i);
});

// === Issue #846: codev-wrapped variants removed ===

it('`codev afx` exits non-zero with deprecation stderr', () => {
const result = runCodev(['afx', 'status'], env.dir, env.env);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain('codev afx');
expect(result.stderr).toContain('no longer supported');
expect(result.stderr).toContain('afx status');
});

it('`codev agent-farm` exits non-zero with deprecation stderr', () => {
const result = runCodev(['agent-farm', 'spawn'], env.dir, env.env);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain('codev agent-farm');
expect(result.stderr).toContain('no longer supported');
expect(result.stderr).toContain('afx spawn');
});

it('`codev af` exits non-zero with unknown-command error (Issue #846)', () => {
// The standalone `af` bin was removed in this change, so `codev af` is
// intentionally NOT special-cased — it falls through to commander as an unknown
// command (consistent with `af` itself being a missing bin).
const result = runCodev(['af', 'help'], env.dir, env.env);
expect(result.status).not.toBe(0);
const output = result.stdout + result.stderr;
expect(output).toMatch(/unknown command|af/i);
});

it('`codev afx` with no subcommand still errors with a helpful hint', () => {
const result = runCodev(['afx'], env.dir, env.env);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain('afx <subcommand>');
});

it('status handles live architect state gracefully', () => {
runCodev(['init', 'test-project', '--yes'], env.dir, env.env);
const projectDir = join(env.dir, 'test-project');
Expand Down
10 changes: 2 additions & 8 deletions packages/codev/src/__tests__/cli/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export const CODEV_BIN = join(BIN_DIR, 'codev.js');
/** Path to the afx CLI entry point */
export const AFX_BIN = join(BIN_DIR, 'afx.js');

/** Path to the deprecated af CLI entry point */
/** Path where the deprecated `af` CLI entry point used to live (Issue #846 removed it).
* Retained only for the negative existence assertion in install.e2e.test.ts. */
export const AF_BIN = join(BIN_DIR, 'af.js');

/** Path to the consult CLI entry point */
Expand Down Expand Up @@ -125,13 +126,6 @@ export function runAfx(args: string[], cwd: string, env: NodeJS.ProcessEnv): Exe
return runCli(AFX_BIN, args, { cwd, env });
}

/**
* Run deprecated af CLI command.
*/
export function runAf(args: string[], cwd: string, env: NodeJS.ProcessEnv): ExecResult {
return runCli(AF_BIN, args, { cwd, env });
}

/**
* Run consult CLI command.
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/codev/src/__tests__/cli/install.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import {
setupCliEnv, teardownCliEnv, CliEnv,
runCodev, runAfx, runAf, runConsult,
runCodev, runAfx, runConsult,
CODEV_BIN, AFX_BIN, AF_BIN, CONSULT_BIN,
} from './helpers.js';

Expand All @@ -34,8 +34,8 @@ describe('package installation (CLI)', () => {
expect(existsSync(AFX_BIN)).toBe(true);
});

it('deprecated af binary exists', () => {
expect(existsSync(AF_BIN)).toBe(true);
it('deprecated af binary no longer exists (Issue #846)', () => {
expect(existsSync(AF_BIN)).toBe(false);
});

it('consult binary exists', () => {
Expand Down
54 changes: 22 additions & 32 deletions packages/codev/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { handleStats } from './commands/consult/stats.js';
import { cli as porchCli } from './commands/porch/index.js';
import { importCommand } from './commands/import.js';
import { generateImage } from './commands/generate-image.js';
import { runAgentFarm } from './agent-farm/cli.js';
import { version } from './version.js';
import { findWorkspaceRoot } from './agent-farm/utils/index.js';

Expand Down Expand Up @@ -350,16 +349,11 @@ teamCmd
}
});

// Agent-farm command (delegates to existing agent-farm CLI)
program
.command('agent-farm', { hidden: false })
.aliases(['afx', 'af'])
.description('Agent farm commands (start, spawn, status, etc.)')
.allowUnknownOption(true)
.action(async () => {
// This is handled specially - delegate to agent-farm
// The args after 'agent-farm' need to be passed through
});
// Note: `codev afx` / `codev agent-farm` are intentionally NOT registered here. Issue
// #846 removed the codev-wrapped invocation surface because it created a `process.argv[1]`
// invocation-style split that broke `spawn(process.execPath, [process.argv[1], ...])`
// callers (e.g. workspace-recover.ts). Use the standalone `afx` bin instead. The
// previously-deprecated `af` standalone bin was also removed in the same change.

// When invoked via standalone bin shim (e.g. `consult` not `codev consult`),
// strip the parent "codev" prefix from help usage lines
Expand All @@ -375,15 +369,10 @@ if (standaloneCmd) {

/**
* Run the CLI with given arguments
* Used by bin shims (afx.js, consult.js) to inject commands
* Used by bin shims (consult.js, team.js, generate-image.js) to inject commands.
* The afx/af bin shims invoke runAgentFarm directly and do not go through this entrypoint.
*/
export async function run(args: string[]): Promise<void> {
// Check if this is an agent-farm command
if (args[0] === 'agent-farm') {
await runAgentFarm(args.slice(1));
return;
}

// Prepend 'node' and 'codev' to make commander happy
const fullArgs = ['node', 'codev', ...args];
await program.parseAsync(fullArgs);
Expand All @@ -395,20 +384,21 @@ const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
process.argv[1]?.endsWith('/codev');

if (isMainModule) {
// Check for agent-farm subcommand before commander parses
const args = process.argv.slice(2);
if (args[0] === 'agent-farm' || args[0] === 'afx' || args[0] === 'af') {
if (args[0] === 'af') {
process.stderr.write('⚠ `codev af` is deprecated. Use `codev afx` or `afx` instead.\n');
}
runAgentFarm(args.slice(1)).catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
} else {
program.parseAsync(process.argv).catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
// Issue #846: `codev afx` and `codev agent-farm` are no longer supported (the
// wrapped agent-farm surface was removed). Emit a clear pointer at `afx` so
// callers don't get a bare commander "unknown command" error for these
// commonly-typed variants. `codev af` is intentionally NOT special-cased — the
// standalone `af` bin was also removed, so `codev af` falls through to commander
// as an unknown command (consistent with `af` itself being a missing bin).
if (args[0] === 'agent-farm' || args[0] === 'afx') {
const rest = args.slice(1).join(' ');
const hint = rest ? `afx ${rest}` : 'afx <subcommand>';
process.stderr.write(`\`codev ${args[0]}\` is no longer supported. Use \`${hint}\` directly.\n`);
process.exit(1);
}
program.parseAsync(process.argv).catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}
Loading