@@ -6,6 +6,7 @@ use anyhow::{bail, Context};
66const FEATURES_JSON : & str = "features.json" ;
77const PROGRESS_MD : & str = "rexos-progress.md" ;
88const INIT_SH : & str = "init.sh" ;
9+ const INIT_PS1 : & str = "init.ps1" ;
910const REXOS_DIR : & str = ".rexos" ;
1011const SESSION_ID_FILE : & str = "session_id" ;
1112
@@ -16,8 +17,9 @@ pub fn init_workspace(workspace_dir: &Path) -> anyhow::Result<()> {
1617 let features_path = workspace_dir. join ( FEATURES_JSON ) ;
1718 let progress_path = workspace_dir. join ( PROGRESS_MD ) ;
1819 let init_sh_path = workspace_dir. join ( INIT_SH ) ;
20+ let init_ps1_path = workspace_dir. join ( INIT_PS1 ) ;
1921
20- if features_path. exists ( ) || progress_path. exists ( ) || init_sh_path. exists ( ) {
22+ if features_path. exists ( ) || progress_path. exists ( ) || init_sh_path. exists ( ) || init_ps1_path . exists ( ) {
2123 bail ! ( "workspace already initialized" ) ;
2224 }
2325
@@ -49,6 +51,15 @@ echo "[rexos] init.sh: customize this script for your project"
4951 )
5052 . with_context ( || format ! ( "write {}" , init_sh_path. display( ) ) ) ?;
5153
54+ std:: fs:: write (
55+ & init_ps1_path,
56+ r#"$ErrorActionPreference = "Stop"
57+
58+ Write-Output "[rexos] init.ps1: customize this script for your project"
59+ "# ,
60+ )
61+ . with_context ( || format ! ( "write {}" , init_ps1_path. display( ) ) ) ?;
62+
5263 #[ cfg( unix) ]
5364 {
5465 use std:: os:: unix:: fs:: PermissionsExt ;
@@ -58,7 +69,7 @@ echo "[rexos] init.sh: customize this script for your project"
5869 }
5970
6071 ensure_git_repo ( workspace_dir) ?;
61- git ( workspace_dir, [ "add" , FEATURES_JSON , PROGRESS_MD , INIT_SH ] ) ?;
72+ git ( workspace_dir, [ "add" , FEATURES_JSON , PROGRESS_MD , INIT_SH , INIT_PS1 ] ) ?;
6273 git_with_identity (
6374 workspace_dir,
6475 [
@@ -118,7 +129,7 @@ pub async fn bootstrap_with_prompt(
118129 )
119130 . await ?;
120131
121- run_init_sh ( workspace_dir) ?;
132+ run_init_script ( workspace_dir) ?;
122133 commit_checkpoint_if_dirty ( workspace_dir, "chore: rexos harness bootstrap" ) ?;
123134 Ok ( ( ) )
124135}
@@ -146,7 +157,7 @@ pub async fn run_harness(
146157 )
147158 . await ?;
148159
149- match run_init_sh_capture ( workspace_dir) {
160+ match run_init_script_capture ( workspace_dir) {
150161 Ok ( _) => {
151162 commit_checkpoint_if_dirty ( workspace_dir, "chore: rexos harness checkpoint" ) ?;
152163 return Ok ( out) ;
@@ -170,8 +181,12 @@ pub fn preflight(workspace_dir: &Path) -> anyhow::Result<()> {
170181 let features_path = workspace_dir. join ( FEATURES_JSON ) ;
171182 let progress_path = workspace_dir. join ( PROGRESS_MD ) ;
172183 let init_sh_path = workspace_dir. join ( INIT_SH ) ;
184+ let init_ps1_path = workspace_dir. join ( INIT_PS1 ) ;
173185
174- if !features_path. exists ( ) || !progress_path. exists ( ) || !init_sh_path. exists ( ) {
186+ if !features_path. exists ( )
187+ || !progress_path. exists ( )
188+ || ( !init_sh_path. exists ( ) && !init_ps1_path. exists ( ) )
189+ {
175190 bail ! (
176191 "workspace not initialized; run `rexos harness init {}`" ,
177192 workspace_dir. display( )
@@ -212,14 +227,7 @@ pub fn preflight(workspace_dir: &Path) -> anyhow::Result<()> {
212227 println ! ( "[rexos] features.json: could not parse (continuing)" ) ;
213228 }
214229
215- let status = Command :: new ( "bash" )
216- . arg ( INIT_SH )
217- . current_dir ( workspace_dir)
218- . status ( )
219- . with_context ( || format ! ( "run {}" , init_sh_path. display( ) ) ) ?;
220- if !status. success ( ) {
221- bail ! ( "init.sh failed" ) ;
222- }
230+ run_init_script ( workspace_dir) ?;
223231
224232 Ok ( ( ) )
225233}
@@ -336,7 +344,7 @@ Your job:
336344Rules:
337345- Work only inside the workspace directory.
338346- Prefer tools (`fs_read`, `fs_write`, `shell`) to inspect and change files.
339- - After edits, run `./init.sh` and ensure it succeeds.
347+ - After edits, run the workspace init script ( `./init.sh`, or `./init.ps1` on Windows) and ensure it succeeds.
340348- Commit your changes to git with a descriptive message.
341349"#
342350}
@@ -348,39 +356,101 @@ Rules:
348356- Work only inside the workspace directory.
349357- Make small, incremental progress (one feature at a time).
350358- Prefer using tools (`fs_read`, `fs_write`, `shell`) to inspect and change files.
351- - If you change code, run the workspace's ` init.sh` (smoke checks) and fix any failures.
359+ - If you change code, run the workspace init script (smoke checks) and fix any failures.
352360- Append a short summary to `rexos-progress.md`.
353361- Commit meaningful progress to git with a descriptive message.
354362"#
355363}
356364
357- fn run_init_sh ( workspace_dir : & Path ) -> anyhow:: Result < ( ) > {
358- let init_sh_path = workspace_dir. join ( INIT_SH ) ;
359- let status = Command :: new ( "bash" )
360- . arg ( INIT_SH )
361- . current_dir ( workspace_dir)
362- . status ( )
363- . with_context ( || format ! ( "run {}" , init_sh_path. display( ) ) ) ?;
364- if !status. success ( ) {
365- bail ! ( "init.sh failed" ) ;
365+ #[ derive( Debug , Clone , Copy ) ]
366+ enum InitScript {
367+ Bash ,
368+ PowerShell ,
369+ }
370+
371+ fn select_init_script ( workspace_dir : & Path ) -> anyhow:: Result < InitScript > {
372+ let sh_exists = workspace_dir. join ( INIT_SH ) . exists ( ) ;
373+ let ps1_exists = workspace_dir. join ( INIT_PS1 ) . exists ( ) ;
374+
375+ if cfg ! ( windows) {
376+ if ps1_exists {
377+ return Ok ( InitScript :: PowerShell ) ;
378+ }
379+ if sh_exists {
380+ return Ok ( InitScript :: Bash ) ;
381+ }
382+ } else if sh_exists {
383+ return Ok ( InitScript :: Bash ) ;
366384 }
367- Ok ( ( ) )
385+
386+ if ps1_exists && !sh_exists {
387+ bail ! ( "init.ps1 exists but init.sh is missing" ) ;
388+ }
389+
390+ bail ! ( "no init script found (expected init.sh and/or init.ps1)" ) ;
368391}
369392
370- fn run_init_sh_capture ( workspace_dir : & Path ) -> anyhow:: Result < String > {
371- let init_sh_path = workspace_dir. join ( INIT_SH ) ;
372- let output = Command :: new ( "bash" )
373- . arg ( INIT_SH )
374- . current_dir ( workspace_dir)
375- . output ( )
376- . with_context ( || format ! ( "run {}" , init_sh_path. display( ) ) ) ?;
393+ fn run_init_script ( workspace_dir : & Path ) -> anyhow:: Result < ( ) > {
394+ match select_init_script ( workspace_dir) ? {
395+ InitScript :: Bash => {
396+ let status = Command :: new ( "bash" )
397+ . arg ( INIT_SH )
398+ . current_dir ( workspace_dir)
399+ . status ( )
400+ . with_context ( || format ! ( "run {}" , workspace_dir. join( INIT_SH ) . display( ) ) ) ?;
401+ if !status. success ( ) {
402+ bail ! ( "init.sh failed" ) ;
403+ }
404+ Ok ( ( ) )
405+ }
406+ InitScript :: PowerShell => {
407+ let status = Command :: new ( "powershell" )
408+ . args ( [
409+ "-NoProfile" ,
410+ "-NonInteractive" ,
411+ "-ExecutionPolicy" ,
412+ "Bypass" ,
413+ "-File" ,
414+ INIT_PS1 ,
415+ ] )
416+ . current_dir ( workspace_dir)
417+ . status ( )
418+ . with_context ( || format ! ( "run {}" , workspace_dir. join( INIT_PS1 ) . display( ) ) ) ?;
419+ if !status. success ( ) {
420+ bail ! ( "init.ps1 failed" ) ;
421+ }
422+ Ok ( ( ) )
423+ }
424+ }
425+ }
426+
427+ fn run_init_script_capture ( workspace_dir : & Path ) -> anyhow:: Result < String > {
428+ let output = match select_init_script ( workspace_dir) ? {
429+ InitScript :: Bash => Command :: new ( "bash" )
430+ . arg ( INIT_SH )
431+ . current_dir ( workspace_dir)
432+ . output ( )
433+ . with_context ( || format ! ( "run {}" , workspace_dir. join( INIT_SH ) . display( ) ) ) ?,
434+ InitScript :: PowerShell => Command :: new ( "powershell" )
435+ . args ( [
436+ "-NoProfile" ,
437+ "-NonInteractive" ,
438+ "-ExecutionPolicy" ,
439+ "Bypass" ,
440+ "-File" ,
441+ INIT_PS1 ,
442+ ] )
443+ . current_dir ( workspace_dir)
444+ . output ( )
445+ . with_context ( || format ! ( "run {}" , workspace_dir. join( INIT_PS1 ) . display( ) ) ) ?,
446+ } ;
377447
378448 let mut combined = String :: new ( ) ;
379449 combined. push_str ( & String :: from_utf8_lossy ( & output. stdout ) ) ;
380450 combined. push_str ( & String :: from_utf8_lossy ( & output. stderr ) ) ;
381451
382452 if !output. status . success ( ) {
383- bail ! ( "init.sh failed: {}" , combined. trim( ) ) ;
453+ bail ! ( "init failed: {}" , combined. trim( ) ) ;
384454 }
385455
386456 Ok ( combined)
0 commit comments