80 lines
4.6 KiB
Bash
Executable File
80 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Row 119 (#1613): role files for the chief of staff and the assistant.
|
||
# Each check must fail on a mutant. For each mutant this runs one named test file against the
|
||
# real source (control) and against the source with one exact-once text edit, in the given worktree,
|
||
# one test file at a time, and restores every source file on exit.
|
||
# Usage: mutants.sh <worktree> <outdir>
|
||
set -u
|
||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||
cd "$WT" || exit 2
|
||
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
|
||
declare -A SAVED
|
||
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
|
||
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
|
||
trap restore EXIT
|
||
edit() { # file old new: exact-once replace
|
||
save "$1"
|
||
node -e '
|
||
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
|
||
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
|
||
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
|
||
fs.writeFileSync(f, s.replace(a, () => b));
|
||
' "$1" "$2" "$3"
|
||
}
|
||
run() { # name testfile
|
||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||
}
|
||
mutant() { # name testfile file old new
|
||
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
|
||
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
|
||
run "$1" "$2"
|
||
cp "$BAK/$3" "$3"
|
||
}
|
||
BT=packages/business/tests; HT=packages/harness/tests; CT=packages/cli/tests; WT_=packages/webui/tests
|
||
COS=$BT/chief-of-staff.test.mjs; AST=$BT/assistant.test.mjs; ROLE=$BT/role.test.mjs; BUS=$BT/business.test.mjs
|
||
TOOLS=$HT/tools.test.mjs; BUNDLE=$HT/bundle.test.mjs; LAUNCH=$CT/launcher.test.mjs; READS=$WT_/reads.test.mjs
|
||
C=roles/chief-of-staff.json; P=roles/pm.json; A=roles/assistant.json; V=packages/business/src/business.mjs
|
||
for t in "$COS" "$AST" "$ROLE" "$BUS" "$TOOLS" "$BUNDLE" "$LAUNCH" "$READS"; do run "control-$(basename "$t" .test.mjs)" "$t"; done
|
||
|
||
# m1: the chief of staff loses role.launch (REQ-COS-1, decision 139)
|
||
for pair in "a $COS" "b $TOOLS" "c $BUNDLE" "d $LAUNCH" "e $READS" "f $BUS"; do
|
||
set -- $pair
|
||
mutant "m1$1-chief-drops-launch" "$2" "$C" '"withinRole": ["message.send", "role.launch"]' '"withinRole": ["message.send"]'
|
||
done
|
||
# m2: the PM regains role.launch (REQ-LAUNCH-1)
|
||
for pair in "a $COS" "b $ROLE" "c $TOOLS" "d $BUNDLE" "e $READS"; do
|
||
set -- $pair
|
||
mutant "m2$1-pm-regains-launch" "$2" "$P" '"task.close", "message.send"]' '"task.close", "message.send", "role.launch"]'
|
||
done
|
||
# m3: the assistant gains role.launch
|
||
mutant m3-assistant-launch "$AST" "$A" '"withinRole": ["message.send"]' '"withinRole": ["message.send", "role.launch"]'
|
||
# m4: the assistant gains a write tool
|
||
mutant m4-assistant-write "$AST" "$A" '"tools": ["read", "grep", "find", "ls"]' '"tools": ["read", "write", "grep", "find", "ls"]'
|
||
# m5: the assistant gains a cross-role action
|
||
mutant m5-assistant-crossrole "$AST" "$A" '"crossRole": []' '"crossRole": ["task.priority.change"]'
|
||
# m6: the assistant gains a credential
|
||
mutant m6-assistant-credential "$AST" "$A" '"credentials": []' '"credentials": [{"service": "gitea", "scopes": ["read:user"]}]'
|
||
# m7: the chief of staff gains a write tool
|
||
mutant m7-chief-write "$COS" "$C" '"tools": ["read", "grep", "find", "ls"]' '"tools": ["read", "write", "grep", "find", "ls"]'
|
||
# m8: the chief of staff gains a credential
|
||
mutant m8-chief-credential "$COS" "$C" '"credentials": []' '"credentials": [{"service": "gitea", "scopes": ["read:user"]}]'
|
||
# m9: the validator stops requiring role.launch of the launcher
|
||
for pair in "a $COS" "b $AST" "c $BUS"; do
|
||
set -- $pair
|
||
mutant "m9$1-validator-accepts-any-launcher" "$2" "$V" 'if (!launcher.authority.withinRole.includes("role.launch")) {' 'if (false) {'
|
||
done
|
||
# m10: the PM contract launches again
|
||
mutant m10-pm-contract-launches "$COS" roles/pm.md "You don't launch sessions." "Launch coders and reviewers when a task is ready."
|
||
# m11: the chief of staff contract talks to the user again
|
||
mutant m11-chief-contract-talks "$COS" roles/chief-of-staff.md "You never talk to the user." "You never talk to the user, but you are the main point of interaction."
|
||
# m12: the assistant contract directs agents again
|
||
mutant m12-assistant-contract-directs "$AST" roles/assistant.md "You don't direct" "You direct"
|
||
# m13: a shipped role file names a contract that isn't there (every shipped role loads)
|
||
for pair in "a $ROLE" "b $AST"; do
|
||
set -- $pair
|
||
mutant "m13$1-assistant-contract-missing" "$2" "$A" '"contract": "assistant.md"' '"contract": "missing.md"'
|
||
done
|
||
echo done
|