Team consensus from Claude, Codex, and Gemini (2025-12-19).
Rule: Commands must be pure functions that receive a Context object.
Reason: Enables dependency injection for testing (mock Tmux, FileSystem, etc.).
Constraint:
- Never use
process.exit()inside a command → usectx.exit() - Never use
console.log()inside a command → usectx.ui.*
// ✅ Good
export async function cmdTalk(ctx: Context, target: string, message: string): Promise<void> {
const { ui, tmux, exit } = ctx;
// ...
ui.success('Message sent');
}
// ❌ Bad
export async function cmdTalk(ctx: Context, target: string, message: string): Promise<void> {
console.log('Message sent'); // Don't use console directly
process.exit(0); // Don't use process.exit
}- Prefix with
cmd(e.g.,cmdTalk,cmdList,cmdConfig)
- Use kebab-case:
talk.ts,config.ts,fs.ts - Test files:
<name>.test.ts(colocated with source)
- Group related logic in subdirectories:
src/commands/ - Prefer function-based modules over classes
- ESM-style with explicit
.jsextensions (tsx-compatible)
import { loadConfig } from './config.js';
import type { Context } from './types.js';Defaults → Global Config → Local Config → CLI Flags
CLI flags always win.
- Always use
ctx.pathsobject - Never hardcode
~/.tmux-teamor./tmux-team.json - XDG compliance via
config.tslogic
// ✅ Good
const stateFile = ctx.paths.stateFile;
// ❌ Bad
const stateFile = path.join(os.homedir(), '.tmux-team', 'state.json');Every command must support --json flag:
- If
flags.jsonis true, output single valid JSON object to stdout - Errors also go as JSON (with
errorfield) - No mixing stdout/stderr in JSON mode
if (flags.json) {
ui.json({ status: 'success', data: result });
} else {
ui.success('Operation completed');
}- Use
isTTYchecks for spinners, progress indicators, colors - Ensure clean output when piped to other tools
Always use ExitCodes registry:
| Code | Name | Meaning |
|---|---|---|
| 0 | SUCCESS |
Command completed |
| 1 | ERROR |
General error |
| 2 | CONFIG_MISSING |
Required config missing |
| 3 | PANE_NOT_FOUND |
Tmux pane not found |
| 4 | TIMEOUT |
Wait timed out |
| 5 | CONFLICT |
GitHub state differs |
Default to seconds (no suffix needed):
--delay 5→ 5 seconds--timeout 60→ 60 seconds--delay 500ms→ 500 milliseconds (suffix supported)
Normalize in CLI parsing; avoid internal ms unless explicitly noted.
- Prefer synchronous FS operations in CLI paths for simplicity
- Use
fs.writeFileSyncfor atomic-like config/state updates
[SYSTEM: <preamble>]
<message>
- Blank line separates preamble from message
- Use
buildMessage()helper for consistent formatting
{tmux-team-end:<nonce>}
- 4-character hex nonce
- Appended in
[IMPORTANT: ...]instruction block
- Gemini: Remove
!from messages (TTY rendering issue)
Use consistent separator pattern:
// ─────────────────────────────────────────────────────────────
// Section Title
// ─────────────────────────────────────────────────────────────- Validate early, fail fast
- Use
ctx.exit(ExitCodes.*)for expected errors - Let unexpected errors bubble up to top-level handler
- Use
typeimports for types-only:import type { Context } from './types.js' - Explicit return types on exported functions
- Colocate tests:
foo.test.tsnext tofoo.ts - Use vitest with
describe/it/expect
describe('functionName', () => {
it('does expected behavior', () => {
// Arrange
// Act
// Assert
});
});- Mock
ctxfor command tests - Mock filesystem for storage tests
- Use
vi.mock()for module mocks
- 2025-12-19: Initial conventions from Phase 1-4 implementation review