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
48 changes: 45 additions & 3 deletions src/cli/fleet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ describe('fleet CLI parsing', () => {
})
})

it('defaults factory start to live mode', () => {
expect(parseFleetCommand(['start'])).toEqual({
kind: 'factory',
action: 'start',
mode: 'live',
})
})

it('rejects the removed nested factory namespace', () => {
expect(() => parseFleetCommand(['factory', 'run-once'])).toThrow(/Unknown factory command: factory/)
})
Expand Down Expand Up @@ -158,7 +166,8 @@ describe('fleet CLI runtime', () => {
expect(errors.text()).toBe('')
expect(output.text()).toContain('usage: factory <command> [options]')
expect(output.text()).toContain('run-once')
expect(output.text()).toContain('start --mode live')
expect(output.text()).toContain('start')
expect(output.text()).toContain('default: ./factory.config.json')
expect(output.text()).toContain('fleet <command>')
expect(output.text()).not.toContain('usage: fleet')
})
Expand Down Expand Up @@ -599,8 +608,6 @@ describe('fleet CLI runtime', () => {

const code = await runFleetCli([
'start',
'--mode',
'live',
'--config',
configPath,
], {
Expand Down Expand Up @@ -672,6 +679,41 @@ describe('fleet CLI runtime', () => {
}
})

it('uses ./factory.config.json by default for factory commands', async () => {
const root = await mkdtemp(join(tmpdir(), 'fleet-cli-default-config-'))
const previousCwd = process.cwd()
try {
await writeConfig(root)
process.chdir(root)
const factory = {
start: vi.fn(async () => {}),
stop: vi.fn(async () => {}),
runLoop: vi.fn(async () => []),
runOnce: vi.fn(),
status: vi.fn(),
triageIssue: vi.fn(),
dispatch: vi.fn(),
on: vi.fn(),
dispose: vi.fn(),
} as unknown as Factory
const code = await runFleetCli(['start'], {
fleet: new FakeFleetClient(),
mount: new FakeMountClient(),
createFactory: vi.fn(() => factory),
ensureLocalMount: vi.fn(async () => {}),
waitForStopSignal: vi.fn(async () => undefined),
stdout: buffer(),
stderr: buffer(),
})

expect(code).toBe(0)
expect(factory.start).toHaveBeenCalledWith({ mode: 'live' })
} finally {
process.chdir(previousCwd)
await rm(root, { recursive: true, force: true })
}
})

it('factory start exits cleanly on SIGTERM after the signal handler stops the factory once', async () => {
const root = await mkdtemp(join(tmpdir(), 'fleet-cli-start-sigterm-'))
try {
Expand Down
12 changes: 6 additions & 6 deletions src/cli/fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ function evaluateFactoryCanary(
}
}

function parseFactoryStartFlags(args: Array<string | undefined>): { mode?: 'live' } {
let mode: 'live' | undefined
function parseFactoryStartFlags(args: Array<string | undefined>): { mode: 'live' } {
let mode: 'live' = 'live'
const flags = args.filter((arg): arg is string => Boolean(arg))
for (let index = 0; index < flags.length; index += 1) {
const flag = flags[index]
Expand All @@ -461,8 +461,8 @@ function parseFactoryStartFlags(args: Array<string | undefined>): { mode?: 'live
}

async function loadConfig(path?: string): Promise<LoadedConfig> {
if (!path) throw new Error('factory commands require --config <path>')
const raw = JSON.parse(await readFile(path, 'utf8')) as unknown
const configPath = path ?? resolve(process.cwd(), 'factory.config.json')
const raw = JSON.parse(await readFile(configPath, 'utf8')) as unknown
const record = asRecord(raw)
return {
config: FactoryConfigSchema.parse(record.factoryConfig ?? record),
Expand Down Expand Up @@ -736,7 +736,7 @@ function helpText(): string {

Commands:
run-once Run one discovery -> triage -> dispatch cycle
start --mode live Run the live factory daemon
start Run the live factory daemon
status Print current factory status as JSON
loop Run the bounded loop configured in factory.config.json
loop-status Print heartbeat/liveness status for the loop
Expand All @@ -749,7 +749,7 @@ Commands:
fleet <command> Low-level fleet commands: spawn, roster, release

Options:
--config <path> Factory config JSON path
--config <path> Factory config JSON path (default: ./factory.config.json)
--dry-run Discover and triage without writes or agent spawns
--backend <backend> Fleet backend: internal or relay
-h, --help Show this help
Expand Down
18 changes: 9 additions & 9 deletions src/orchestrator/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1999,7 +1999,7 @@ describe('FactoryLoop', () => {
const fleet = new FakeFleetClient()
const factory = createFactory(config(), { mount, fleet, triage: new StaticTriage() })

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })

expect(fleet.spawns).toEqual([])
expect(factory.status().inFlight).toEqual([])
Expand Down Expand Up @@ -3296,7 +3296,7 @@ describe('FactoryLoop', () => {
const fleet = new FakeFleetClient()
const factory = createFactory(config(), { mount, fleet, triage: new StaticTriage() })

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })

expect(fleet.spawns.map((spawn) => spawn.name)).toEqual(['ar-11-impl-pear', 'ar-11-review'])
expect(factory.status().inFlight.map((issue) => issue.key)).toEqual(['AR-11'])
Expand Down Expand Up @@ -3647,7 +3647,7 @@ describe('FactoryLoop', () => {
},
})

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })
expect(mount.activeSubscriptions).toBe(1)
await expect(factory.stop()).resolves.toBeUndefined()

Expand Down Expand Up @@ -3727,7 +3727,7 @@ describe('FactoryLoop', () => {
}
})

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })

expect(fleet.spawns.map((spawn) => spawn.name)).toEqual(['ar-15-impl-pear', 'ar-15-review'])
expect(factory.status().inFlight.map((issue) => issue.key)).toEqual(['AR-15'])
Expand All @@ -3741,7 +3741,7 @@ describe('FactoryLoop', () => {
const fleet = new FakeFleetClient()
const factory = createFactory(config(), { mount, fleet, triage: new StaticTriage() })

await Promise.all([factory.start(), factory.start()])
await Promise.all([factory.start({ mode: 'backfill-and-subscribe' }), factory.start({ mode: 'backfill-and-subscribe' })])

expect(mount.subscribeCount).toBe(1)
expect(fleet.spawns.map((spawn) => spawn.name)).toEqual(['ar-12-impl-pear', 'ar-12-review'])
Expand All @@ -3753,7 +3753,7 @@ describe('FactoryLoop', () => {
const fleet = new FakeFleetClient()
const factory = createFactory(config(), { mount, fleet, triage: new StaticTriage() })

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })
mount.emit(changeEvent(issuePath(17), 'event-duplicate-1'))
mount.emit(changeEvent(issuePath(17), 'event-duplicate-2'))
await flush()
Expand Down Expand Up @@ -4869,7 +4869,7 @@ describe('FactoryLoop', () => {
const factory = createFactory(config(), { mount, fleet, triage: new StaticTriage() })
const errors: unknown[] = []
factory.on('error', (payload) => errors.push(payload))
await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })

const decision = await factory.triageIssue(parseLinearIssue(issuePath(8), issueFile(8)))
await factory.dispatch(decision)
Expand Down Expand Up @@ -5413,7 +5413,7 @@ describe('FactoryLoop', () => {
},
})

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })
expect(factory.status().inFlight.map((issue) => issue.key)).toEqual(['AR-243'])

await vi.advanceTimersByTimeAsync(0)
Expand Down Expand Up @@ -7346,7 +7346,7 @@ describe('FactoryLoop', () => {
stateStore: state,
})

await factory.start()
await factory.start({ mode: 'backfill-and-subscribe' })

expect(factory.status().counters.slackWatchersRearmed).toBeUndefined()
expect(slack.roots).toEqual([])
Expand Down
2 changes: 1 addition & 1 deletion src/orchestrator/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ export class FactoryLoop implements Factory {

this.#wireFleetEvents()

if (opts.mode === 'live') {
if ((opts.mode ?? 'live') === 'live') {
this.#started = true
try {
await this.#startLiveSubscription(opts.liveSubscription)
Expand Down