Files
jason.woltje 4ff4f44c98 lead: commit researcher's untracked evidence (1406 files: queue-101 queue-105 queue-128 queue-134 queue-143 queue-148 queue-183 queue-228)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:14 -06:00

84 lines
6.0 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Row 128 (#1622): mosaic talks to the assistant in a named thread.
# Each behavior has a mutant that a named test file must fail. For each mutant this runs one 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> (MUTANT_DRY=1 only proves every edit matches exactly once;
# MUTANT_ONLY=<regex> runs just the matching names)
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
if [ -n "${MUTANT_DRY:-}" ]; then echo "$1: edit ok (dry)"; return; fi
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
if [ -n "${MUTANT_ONLY:-}" ] && ! [[ $1 =~ $MUTANT_ONLY ]]; then return; fi
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
run "$1" "$2"
cp "$BAK/$3" "$3"
}
TT=packages/cli/tests/thread-cli.test.mjs; PT=packages/cli/tests/project.test.mjs
CL=packages/cli/src/cli.mjs; PR=packages/cli/src/project.mjs; TH=packages/cli/src/thread.mjs
if [ -z "${MUTANT_DRY:-}" ] && [ -z "${MUTANT_ONLY:-}" ]; then for t in "$TT" "$PT"; do run "control-$(basename "$t" .test.mjs)" "$t"; done; fi
# project create / list
mutant p1-create-drops-repo "$TT" "$PR" "{ name, ...(parsed.values['--repo'] === undefined ? {} : { repo: parsed.values['--repo'] }) }" "{ name }"
mutant p2-create-prints-no-id "$TT" "$PR" 'created project ${name} id ${made.id}' 'created project ${name}'
mutant p3-list-blank-repo "$TT" "$PR" "\${p.repo ?? '-'}" "\${p.repo ?? ''}"
mutant p4-list-json-ignored "$TT" "$PR" "if (parsed.flags.has('--json')) return io.stdout.write" "if (false) return io.stdout.write"
mutant p5-create-arity "$PT" "$PR" "parsed.positional.length !== (sub === 'create' ? 1 : 0)" "parsed.positional.length !== (sub === 'create' ? parsed.positional.length : 0)"
mutant p6-init-check-to-bus "$PT" "$CL" '["create", "list"].includes(rest[0]) ? projectBusCommand' 'true ? projectBusCommand'
# the resolver
mutant r1-short-prefix-allowed "$TT" "$TH" 'if (ref.length < 8) throw new CliError(`thread reference' 'if (false) throw new CliError(`thread reference'
mutant r2-ambiguous-picks-first "$TT" "$TH" 'if (hits.length > 1) throw new CliError(`${ref} matches ${hits.length} threads; use more of the id, or <project>/<name>`, 2);' 'if (hits.length > 1) return { id: hits[0].id, label: labelOf(projects, hits[0]) };'
mutant r3-label-not-resolved "$TT" "$TH" ' ?? threads.find((t) => labelOf(projects, t) === ref);' ';'
mutant r4-full-id-not-passed-on "$TT" "$TH" 'if (UUID.test(ref)) return { id: ref, label: ref };' ''
mutant r5-prefix-not-resolved "$TT" "$TH" 'const hits = threads.filter((t) => t.id.startsWith(ref));' 'const hits = [];'
mutant r6-project-by-name-only "$TT" "$TH" 'projects.find((p) => p.id === ref) ?? projects.find((p) => p.name === ref)' 'projects.find((p) => p.name === ref)'
mutant r7-unknown-project-passed-on "$TT" "$TH" 'throw new CliError(`no project is named or numbered ${JSON.stringify(ref)}; see mosaic project list`, 2);' 'return { id: ref, name: ref };'
# thread verbs
mutant v1-create-sends-name-as-project "$TT" "$TH" 'call("thread.create", { project: project.id, name })' 'call("thread.create", { project: ref, name })'
mutant v2-open-writes-nothing "$TT" "$TH" ' await call("thread.open", { thread: thread.id });
' ''
mutant v3-read-one-page "$TT" "$TH" ' if (page.length < PAGE) return out;' ' return out;'
mutant v4-read-json-ignored "$TT" "$TH" 'if (parsed.flags.has("--json")) return json(io, messages);' ''
mutant v5-threads-project-ignored "$TT" "$TH" 'project ? { project: project.id } : {}' '{}'
mutant v6-threads-json-ignored "$TT" "$TH" 'if (parsed.flags.has("--json")) return json(io, threads);' ''
mutant v7-threads-one-extra-arg "$TT" "$TH" 'if (parsed.positional.length !== 0) throw new CliError(`threads takes no arguments' 'if (false) throw new CliError(`threads takes no arguments'
# talk --thread
mutant t1-send-not-to-thread "$TT" "$CL" 'thread ? "thread.send" : "message.send", thread ?' '"message.send", thread ?'
mutant t2-send-class "$TT" "$CL" '{ thread: thread.id, body, class: "REQUEST" }' '{ thread: thread.id, body, class: "INFO" }'
mutant t3-plain-talk-raw-code "$TT" "$CL" 'if (e.code === "thread-required-use-thread-send") {' 'if (false) {'
mutant t4-reply-filter-dropped "$TT" "$CL" 'if (!follow && m.in_reply_to !== sent.id) continue;' ''
mutant t5-thread-with-tester "$TT" "$CL" 'if (threadRef !== undefined && parsed.flags.has("--tester")) throw usage("--thread takes no --tester");' ''
mutant t6-thread-arity "$TT" "$CL" 'receiveOnly ? 0 : threadRef === undefined ? 2 : 1' 'receiveOnly ? 0 : 2'
# --follow in a thread
mutant f1-other-threads-printed "$TT" "$CL" 'if (members && !(members.seq(m.id) > (members.seq(sent?.id) ?? 0))) continue;' ''
mutant f2-earlier-thread-messages-printed "$TT" "$CL" '(members.seq(sent?.id) ?? 0)' '0'
mutant f3-thread-not-refreshed "$TT" "$CL" 'if (members && received.length > 0) await members.refresh();' ''
mutant f4-foreign-thread-received-first "$TT" "$CL" ' await members?.refresh();
' ''
echo done