@@ -9,13 +9,21 @@ import {
99 MANAGED_SECTION_END ,
1010 ONBOARD_CODEX_LINE ,
1111 SKILLS ,
12+ buildSkillMarker ,
1213 pathFor ,
1314 renderForTarget ,
15+ renderOwnFileWithMarker ,
1416 TARGETS ,
1517 type AgentTarget ,
1618} from '../lib/agent-targets.js' ;
17- import type { AgentDeps , AgentFs , InstallResult , ListResult } from './agent.js' ;
18- import { AGENTS_MD_CODEX_BUDGET_BYTES , createAgentCommand , runInstall , runList } from './agent.js' ;
19+ import type { AgentDeps , AgentFs , InstallResult , ListResult , StatusResult } from './agent.js' ;
20+ import {
21+ AGENTS_MD_CODEX_BUDGET_BYTES ,
22+ createAgentCommand ,
23+ runInstall ,
24+ runList ,
25+ runStatus ,
26+ } from './agent.js' ;
1927
2028// ---------------------------------------------------------------------------
2129// In-memory AgentFs backed by a Map
@@ -2430,3 +2438,120 @@ describe('runInstall — SKILLS registry / DEFAULT_SKILLS contract', () => {
24302438 expect ( ONBOARD_CODEX_LINE ) . toContain ( '**First-time setup:**' ) ;
24312439 } ) ;
24322440} ) ;
2441+
2442+ // ---------------------------------------------------------------------------
2443+ // runStatus — `agent status` (issue #123)
2444+ // ---------------------------------------------------------------------------
2445+
2446+ describe ( 'runStatus — agent status (issue #123)' , ( ) => {
2447+ const statusOpts = {
2448+ profile : 'default' as const ,
2449+ output : 'json' as const ,
2450+ debug : false ,
2451+ dryRun : false ,
2452+ } ;
2453+
2454+ /** Run status against the given fs and return the printed rows. */
2455+ async function statusRows ( agentFs : AgentFs ) : Promise < { rows : StatusResult [ ] ; thrown : unknown } > {
2456+ const { capture, deps } = makeCapture ( ) ;
2457+ let thrown : unknown ;
2458+ try {
2459+ await runStatus ( statusOpts , { cwd : CWD , fs : agentFs , ...deps } ) ;
2460+ } catch ( err ) {
2461+ thrown = err ;
2462+ }
2463+ return { rows : JSON . parse ( capture . stdout . join ( '' ) ) as StatusResult [ ] , thrown } ;
2464+ }
2465+
2466+ it ( 'nothing installed: every row is absent and the command exits 0' , async ( ) => {
2467+ const { fs : agentFs } = makeMemFs ( ) ;
2468+ const { rows, thrown } = await statusRows ( agentFs ) ;
2469+ expect ( thrown ) . toBeUndefined ( ) ;
2470+ expect ( rows ) . toHaveLength ( Object . keys ( TARGETS ) . length * DEFAULT_SKILLS . length ) ;
2471+ expect ( rows . every ( row => row . state === 'absent' ) ) . toBe ( true ) ;
2472+ } ) ;
2473+
2474+ it ( 'fresh installs read ok (own-file and codex managed section), exit 0' , async ( ) => {
2475+ const { fs : agentFs } = makeMemFs ( ) ;
2476+ const { deps } = makeCapture ( ) ;
2477+ await runInstall (
2478+ {
2479+ profile : 'default' ,
2480+ output : 'text' ,
2481+ debug : false ,
2482+ dryRun : false ,
2483+ target : [ 'claude' , 'codex' ] ,
2484+ skills : [ ...DEFAULT_SKILLS ] ,
2485+ force : false ,
2486+ } ,
2487+ { cwd : CWD , fs : agentFs , ...deps } ,
2488+ ) ;
2489+
2490+ const { rows, thrown } = await statusRows ( agentFs ) ;
2491+ expect ( thrown ) . toBeUndefined ( ) ;
2492+ for ( const skill of DEFAULT_SKILLS ) {
2493+ expect ( rows . find ( r => r . target === 'claude' && r . skill === skill ) ?. state ) . toBe ( 'ok' ) ;
2494+ expect ( rows . find ( r => r . target === 'codex' && r . skill === skill ) ?. state ) . toBe ( 'ok' ) ;
2495+ expect ( rows . find ( r => r . target === 'cursor' && r . skill === skill ) ?. state ) . toBe ( 'absent' ) ;
2496+ }
2497+ } ) ;
2498+
2499+ it ( 'stale: a marker whose hash matches an OLDER body reads stale and exits 1' , async ( ) => {
2500+ const { fs : agentFs , seedFile } = makeMemFs ( ) ;
2501+ const oldBody = '# TestSprite Verification Loop\n\nold body from a previous CLI release\n' ;
2502+ seedFile (
2503+ path . resolve ( CWD , pathFor ( 'claude' , 'testsprite-verify' ) ) ,
2504+ renderOwnFileWithMarker (
2505+ 'claude' ,
2506+ 'testsprite-verify' ,
2507+ buildSkillMarker ( 'testsprite-verify' , oldBody ) ,
2508+ oldBody ,
2509+ ) ,
2510+ ) ;
2511+
2512+ const { rows, thrown } = await statusRows ( agentFs ) ;
2513+ expect ( rows . find ( r => r . target === 'claude' && r . skill === 'testsprite-verify' ) ?. state ) . toBe (
2514+ 'stale' ,
2515+ ) ;
2516+ expect ( thrown ) . toBeInstanceOf ( CLIError ) ;
2517+ expect ( ( thrown as CLIError ) . exitCode ) . toBe ( 1 ) ;
2518+ expect ( ( thrown as CLIError ) . message ) . toContain ( 'need attention' ) ;
2519+ } ) ;
2520+
2521+ it ( 'modified: current hash but edited bytes reads modified and exits 1' , async ( ) => {
2522+ const { fs : agentFs , seedFile } = makeMemFs ( ) ;
2523+ const canonical = renderForTarget ( 'claude' , 'testsprite-verify' ) . content ;
2524+ seedFile (
2525+ path . resolve ( CWD , pathFor ( 'claude' , 'testsprite-verify' ) ) ,
2526+ `${ canonical } \n<!-- my local tweak -->\n` ,
2527+ ) ;
2528+
2529+ const { rows, thrown } = await statusRows ( agentFs ) ;
2530+ expect ( rows . find ( r => r . target === 'claude' && r . skill === 'testsprite-verify' ) ?. state ) . toBe (
2531+ 'modified' ,
2532+ ) ;
2533+ expect ( ( thrown as CLIError ) . exitCode ) . toBe ( 1 ) ;
2534+ } ) ;
2535+
2536+ it ( 'unmarked: an artifact without a marker line reads unmarked and exits 1' , async ( ) => {
2537+ const { fs : agentFs , seedFile } = makeMemFs ( ) ;
2538+ seedFile (
2539+ path . resolve ( CWD , pathFor ( 'claude' , 'testsprite-verify' ) ) ,
2540+ '# hand-rolled skill file with no marker\n' ,
2541+ ) ;
2542+
2543+ const { rows, thrown } = await statusRows ( agentFs ) ;
2544+ expect ( rows . find ( r => r . target === 'claude' && r . skill === 'testsprite-verify' ) ?. state ) . toBe (
2545+ 'unmarked' ,
2546+ ) ;
2547+ expect ( ( thrown as CLIError ) . exitCode ) . toBe ( 1 ) ;
2548+ } ) ;
2549+
2550+ it ( 'rejects an explicit empty --dir (exit 5), matching the resolve-to-cwd hazard' , async ( ) => {
2551+ const { fs : agentFs } = makeMemFs ( ) ;
2552+ const { deps } = makeCapture ( ) ;
2553+ await expect (
2554+ runStatus ( { ...statusOpts , dir : ' ' } , { cwd : CWD , fs : agentFs , ...deps } ) ,
2555+ ) . rejects . toMatchObject ( { exitCode : 5 } ) ;
2556+ } ) ;
2557+ } ) ;
0 commit comments