char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
1c0a335
main
1import { assertEquals , assertRejects } from "@std/assert" ; 2import { mkdir , readFile , writeFile } from "node:fs/promises" ; 3import { join } from "node:path" ; 4import { localEnvironment } from "@berry/api/environment.ts" ; 5import { testExtension } from "@berry/api/testing.ts" ; 6import code from "./mod.ts" ; 7import { onPath , tempDir } from "@berry/util" ; 8 9const jjAvailable = onPath ( "jj" ); 10 11async function jj ( cwd :string , ...args :string []) :Promise < string > { 12const result = await new Deno . Command ( "jj" , { 13args :[ "--no-pager" , "--color=never" , ...args ], 14 cwd, 15stdin :"null" , 16stdout :"piped" , 17stderr :"piped" , 18}). output (); 19if ( ! result . success ) throw new Error ( new TextDecoder (). decode ( result . stderr )); 20return new TextDecoder (). decode ( result . stdout ). trim (); 21} 22 23/** The commit that `dir`'s working copy sits on. */ 24const base = ( dir :string ) => 25jj ( dir , "--ignore-working-copy" , "log" , "-r" , "@-" , "--no-graph" , "-T" , 'commit_id ++ "\n"' ); 26 27async function project ( 28run :( root :string , repo :string , workspaces :string ) => Promise < void >, 29) { 30await usingrootDir = await tempDir ({ prefix :"workspace-test-" }); 31const root = rootDir . path ; 32const saved = new Map ( 33[ "BERRY_WORKSPACES_DIR" , "JJ_CONFIG" ]. map (( key ) => [ key , Deno . env . get ( key )]), 34); 35try { 36const config = join ( root , "config.toml" ); 37await writeFile ( config , '[user]\nname = "Test"\nemail = "test@example.com"\n' ); 38Deno . env . set ( "JJ_CONFIG" , config ); 39const workspaces = join ( root , "workspaces" ); 40Deno . env . set ( "BERRY_WORKSPACES_DIR" , workspaces ); 41const repo = join ( root , "repo with spaces" ); 42await jj ( root , "git" , "init" , repo ); 43await writeFile ( join ( repo , "baseline.txt" ), "shared base\n" ); 44await jj ( repo , "new" ); 45await run ( root , repo , workspaces ); 46} finally { 47for ( const [ key , value ] of saved ) { 48if ( value === undefined ) Deno . env . delete ( key ); 49else Deno . env . set ( key , value ); 50} 51} 52} 53 54Deno . test ({ 55name : 56"workspace skips empty ancestors and stays independent when the source staging commit changes" , 57ignore :! jjAvailable , 58fn :() => 59project ( async ( _root , repo ) => { 60const before = await base ( repo ); 61await jj ( repo , "describe" , "--message" , "empty staging" ); 62await jj ( repo , "new" , "--message" , "another empty stage" ); 63const ext = testExtension ( code , { env :localEnvironment ( repo ) }); 64const target = await ext . command ( "workspace" ) as string ; 65assertEquals ( await base ( target ), before ); 66 67await writeFile ( join ( repo , "baseline.txt" ), "new source work\n" ); 68await jj ( repo , "status" ); 69await jj ( target , "status" ); 70assertEquals ( await readFile ( join ( target , "baseline.txt" ), "utf8" ), "shared base\n" ); 71assertEquals ( await base ( target ), before ); 72}), 73}); 74 75Deno . test ({ 76name :"workspace uses only the nearest nonempty ancestors of an empty merge" , 77ignore :! jjAvailable , 78fn :() => 79project ( async ( _root , repo ) => { 80await jj ( repo , "new" , "@-" , "--message" , "left" ); 81await writeFile ( join ( repo , "left.txt" ), "left\n" ); 82const left = await jj ( repo , "log" , "-r" , "@" , "--no-graph" , "-T" , "commit_id" ); 83await jj ( repo , "new" , "@-" , "--message" , "right" ); 84await writeFile ( join ( repo , "right.txt" ), "right\n" ); 85const right = await jj ( repo , "log" , "-r" , "@" , "--no-graph" , "-T" , "commit_id" ); 86await jj ( repo , "new" , left , right ); 87const ext = testExtension ( code , { env :localEnvironment ( repo ) }); 88const target = await ext . command ( "workspace" ) as string ; 89const parents = await base ( target ); 90assertEquals ( parents . split ( "\n" ). sort (), [ left , right ]. sort ()); 91assertEquals ( await readFile ( join ( target , "left.txt" ), "utf8" ), "left\n" ); 92assertEquals ( await readFile ( join ( target , "right.txt" ), "utf8" ), "right\n" ); 93}), 94}); 95 96Deno . test ({ 97name :"workspace refuses a repository with no nonempty history before creating a destination" , 98ignore :! jjAvailable , 99fn :() => 100project ( async ( root , _repo , workspaces ) => { 101const empty = join ( root , "empty" ); 102await jj ( root , "git" , "init" , empty ); 103const ext = testExtension ( code , { env :localEnvironment ( empty ) }); 104await assertRejects ( 105() => ext . command ( "workspace" ), 106Error , 107"no nonempty ancestor other than root" , 108); 109assertEquals ( ext . env . cwd , empty ); 110await assertRejects (() => Deno . stat ( workspaces ), Deno . errors . NotFound ); 111}), 112}); 113 114Deno . test ({ 115name :"workspace reuses its registered directory without resetting unfinished work" , 116ignore :! jjAvailable , 117fn :() => 118project ( async ( root , repo ) => { 119const ext = testExtension ( code , { env :localEnvironment ( repo ) }); 120const target = await ext . command ( "workspace" ) as string ; 121await writeFile ( join ( target , "unfinished.txt" ), "keep me\n" ); 122assertEquals ( await ext . command ( "workspace" ), target ); 123ext . env . cwd = repo ; 124await writeFile ( join ( repo , "later.txt" ), "not part of the task\n" ); 125Deno . env . set ( "BERRY_WORKSPACES_DIR" , join ( root , "different-location" )); 126assertEquals ( await ext . command ( "workspace" ), target ); 127assertEquals ( await readFile ( join ( target , "unfinished.txt" ), "utf8" ), "keep me\n" ); 128await assertRejects (() => readFile ( join ( target , "later.txt" ))); 129}), 130}); 131 132Deno . test ({ 133name : 134"workspace identifies the shared repo from another workspace even after the default is renamed" , 135ignore :! jjAvailable , 136fn :() => 137project ( async ( _root , repo , workspaces ) => { 138await jj ( repo , "workspace" , "rename" , "main" ); 139const first = testExtension ( code , { tid :"first" , env :localEnvironment ( repo ) }); 140const firstPath = await first . command ( "workspace" ) as string ; 141const second = testExtension ( code , { 142tid :"second" , 143env :localEnvironment ( firstPath ), 144}); 145const secondPath = join ( workspaces , "repo with spaces" , "second" ); 146assertEquals ( await second . command ( "workspace" ), secondPath ); 147await writeFile ( join ( firstPath , "private.txt" ), "first only\n" ); 148await assertRejects (() => readFile ( join ( secondPath , "private.txt" ))); 149assertEquals ( 150( await jj ( repo , "--ignore-working-copy" , "workspace" , "list" , "-T" , 'name ++ "\n"' )) 151. split ( "\n" ). sort (), 152[ "first" , "main" , "second" ], 153); 154}), 155}); 156 157Deno . test ({ 158name :"workspace refuses an occupied destination and leaves both cwd and existing files alone" , 159ignore :! jjAvailable , 160fn :() => 161project ( async ( _root , repo , workspaces ) => { 162const tid = "occupied" ; 163const target = join ( workspaces , "repo with spaces" , tid ); 164await mkdir ( target , { recursive :true }); 165await writeFile ( join ( target , "keep.txt" ), "do not overwrite\n" ); 166const ext = testExtension ( code , { tid, env :localEnvironment ( repo ) }); 167await assertRejects (() => ext . command ( "workspace" )); 168assertEquals ( ext . env . cwd , repo ); 169assertEquals ( await readFile ( join ( target , "keep.txt" ), "utf8" ), "do not overwrite\n" ); 170assertEquals ( 171await jj ( repo , "--ignore-working-copy" , "workspace" , "list" , "-T" , "name" ), 172"default" , 173); 174}), 175}); 176 177Deno . test ({ 178name : 179"workspace refuses non-jj directories, unexpected arguments, and unsafe conversation names without creating files or moving cwd" , 180ignore :! jjAvailable , 181fn :() => 182project ( async ( root , repo , workspaces ) => { 183const ext = testExtension ( code , { env :localEnvironment ( root ) }); 184await assertRejects (() => ext . command ( "workspace" ), Error , "No command /workspace" ); 185assertEquals ( ext . env . cwd , root ); 186ext . env . cwd = repo ; 187await assertRejects (() => ext . command ( "workspace" , "extra" ), Error , "Usage: /workspace" ); 188assertEquals ( ext . env . cwd , repo ); 189const unsafe = testExtension ( code , { 190tid :"../outside" , 191env :localEnvironment ( repo ), 192}); 193await assertRejects (() => unsafe . command ( "workspace" ), Error , "Invalid conversation ID" ); 194assertEquals ( unsafe . env . cwd , repo ); 195await assertRejects (() => Deno . stat ( workspaces ), Deno . errors . NotFound ); 196}), 197});