@@ -2,8 +2,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
22import { mkdirSync , writeFileSync , readFileSync , rmSync } from 'node:fs'
33import { join , basename } from 'node:path'
44import { tmpdir } from 'node:os'
5- import { execSync } from 'node:child_process'
5+ import { execFileSync , execSync } from 'node:child_process'
66import {
7+ buildTakeoverRunOptions ,
78 detectLanguages ,
89 detectPackageManagers ,
910 detectFrameworks ,
@@ -595,18 +596,15 @@ describe('materializeTakeoverProject', () => {
595596 touch ( 'README.md' , '# Local Test' )
596597 touch ( 'package.json' , JSON . stringify ( { name : 'local-test' } ) )
597598 execSync ( 'git init -b main' , { cwd : tmpDir , stdio : 'pipe' } )
598- execSync ( 'git add -A && git commit -m "init"' , {
599- cwd : tmpDir ,
600- stdio : 'pipe' ,
601- env : {
602- ...process . env ,
603- GIT_AUTHOR_NAME : 'test' ,
604- GIT_AUTHOR_EMAIL : 'test@example.com' ,
605- GIT_COMMITTER_NAME : 'test' ,
606- GIT_COMMITTER_EMAIL : 'test@example.com' ,
607- } ,
608- shell : '/bin/bash' ,
609- } )
599+ const gitEnv = {
600+ ...process . env ,
601+ GIT_AUTHOR_NAME : 'test' ,
602+ GIT_AUTHOR_EMAIL : 'test@example.com' ,
603+ GIT_COMMITTER_NAME : 'test' ,
604+ GIT_COMMITTER_EMAIL : 'test@example.com' ,
605+ }
606+ execFileSync ( 'git' , [ 'add' , '-A' ] , { cwd : tmpDir , stdio : 'pipe' , env : gitEnv } )
607+ execFileSync ( 'git' , [ 'commit' , '-m' , 'init' ] , { cwd : tmpDir , stdio : 'pipe' , env : gitEnv } )
610608
611609 const profile = scanProject ( tmpDir )
612610 const generated = generateAgentConfig ( profile , undefined , 'codex' )
@@ -615,13 +613,15 @@ describe('materializeTakeoverProject', () => {
615613 const skill = readFileSync ( join ( overlayDir , 'skills' , 'takeover-context' , 'SKILL.md' ) , 'utf-8' )
616614 const agentGuide = readFileSync ( join ( overlayDir , 'agents' , 'ceo' , 'AGENT.md' ) , 'utf-8' )
617615 const config = JSON . parse ( readFileSync ( join ( overlayDir , 'agents.json' ) , 'utf-8' ) ) as { gitRoot : string }
616+ const normalizedSkill = skill . replace ( / \\ / g, '/' )
617+ const normalizedAgentGuide = agentGuide . replace ( / \\ / g, '/' )
618618
619- expect ( skill ) . toContain ( join ( tmpDir , '.wanman' , 'worktree' ) . replace ( / \\ / g, '/' ) )
619+ expect ( normalizedSkill ) . toContain ( join ( tmpDir , '.wanman' , 'worktree' ) . replace ( / \\ / g, '/' ) )
620620 expect ( skill ) . toContain ( 'initiative list' )
621621 expect ( skill ) . toContain ( 'capsule create' )
622622 expect ( skill ) . toContain ( 'wanman initiative list' )
623623 expect ( skill ) . not . toContain ( 'packages/cli/dist/index.js' )
624- expect ( agentGuide ) . toContain ( join ( tmpDir , '.wanman' , 'skills' , 'takeover-context' , 'SKILL.md' ) . replace ( / \\ / g, '/' ) )
624+ expect ( normalizedAgentGuide ) . toContain ( join ( tmpDir , '.wanman' , 'skills' , 'takeover-context' , 'SKILL.md' ) . replace ( / \\ / g, '/' ) )
625625 expect ( agentGuide ) . toContain ( 'Create at least 3 tasks in the first CEO cycle' )
626626 expect ( agentGuide ) . toContain ( 'visible backlog creation is mandatory' )
627627 expect ( agentGuide ) . toContain ( 'capsule create' )
@@ -747,6 +747,64 @@ describe('planLocalDynamicClone', () => {
747747} )
748748
749749describe ( 'takeoverCommand' , ( ) => {
750+ it ( 'keeps explicit finite loop counts for takeover runs' , ( ) => {
751+ const opts = buildTakeoverRunOptions ( 'Goal' , [ '--loops' , '2' ] )
752+
753+ expect ( opts . infinite ) . toBe ( false )
754+ expect ( opts . loops ) . toBe ( 2 )
755+ } )
756+
757+ it ( 'defaults takeover runs to infinite mode when no run mode is supplied' , ( ) => {
758+ const opts = buildTakeoverRunOptions ( 'Goal' , [ '--poll' , '5' ] )
759+
760+ expect ( opts . infinite ) . toBe ( true )
761+ expect ( opts . loops ) . toBe ( Infinity )
762+ expect ( opts . pollInterval ) . toBe ( 5 )
763+ } )
764+
765+ it ( 'keeps explicit infinite mode for takeover runs' , ( ) => {
766+ const opts = buildTakeoverRunOptions ( 'Goal' , [ '--infinite' , '--poll' , '5' ] )
767+
768+ expect ( opts . infinite ) . toBe ( true )
769+ expect ( opts . loops ) . toBe ( Infinity )
770+ expect ( opts . pollInterval ) . toBe ( 5 )
771+ } )
772+
773+ it ( 'prints help when invoked without a project path' , async ( ) => {
774+ const logs : string [ ] = [ ]
775+ const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( message = '' ) => {
776+ logs . push ( String ( message ) )
777+ } )
778+
779+ try {
780+ await takeoverCommand ( [ ] )
781+ } finally {
782+ logSpy . mockRestore ( )
783+ }
784+
785+ expect ( logs . join ( '\n' ) ) . toContain ( 'wanman takeover - Take over an existing git repo' )
786+ expect ( logs . join ( '\n' ) ) . toContain ( '--loops <n>' )
787+ } )
788+
789+ it ( 'fails fast when the takeover path does not exist' , async ( ) => {
790+ const errors : string [ ] = [ ]
791+ const errorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( message = '' ) => {
792+ errors . push ( String ( message ) )
793+ } )
794+ const exitSpy = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ( code ?: number ) => {
795+ throw new Error ( `process.exit:${ code ?? 0 } ` )
796+ } ) as never )
797+
798+ try {
799+ await expect ( takeoverCommand ( [ join ( tmpDir , 'missing-project' ) ] ) ) . rejects . toThrow ( 'process.exit:1' )
800+ } finally {
801+ errorSpy . mockRestore ( )
802+ exitSpy . mockRestore ( )
803+ }
804+
805+ expect ( errors . join ( '\n' ) ) . toContain ( `Error: path does not exist: ${ join ( tmpDir , 'missing-project' ) } ` )
806+ } )
807+
750808 it ( 'runs a local dry-run takeover and writes tier-based local overlay config' , async ( ) => {
751809 execSync ( 'git init' , { cwd : tmpDir , stdio : 'ignore' } )
752810 execSync ( 'git config user.email test@example.com' , { cwd : tmpDir , stdio : 'ignore' } )
0 commit comments