sage: row 64 packet, build notes, both gates, mutants and the landing manifest; BUILD.md item 5 corrected for Darkwing N1 (mos-0863 D5, #1557)
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# Row 64: a managed session gets its role's skills as prompt text on both harnesses
|
||||
|
||||
Author and gate owner: Sage (mos-0855). Reviewers Darkwing and Filbert. Sage, 2026-10-19.
|
||||
Brief `docs/plans/2026-10-11_managed-session-config.md`, anchor "Managed sessions get their role's skills on
|
||||
both harnesses", blob 21ae25a3. Issue #1557. Rulings mos-0857 (reviewer checks), mos-0862 (mutant coverage).
|
||||
|
||||
Base 087d202bc. Candidate c6d7d538f, one commit, built in `/mnt/storage/scratch/sage-r64/wt` and gated in a
|
||||
clean detached worktree at `/mnt/storage/scratch/sage-r64/cand`. The commit is in the canonical object store,
|
||||
so `git show c6d7d538f` works from the canonical checkout. `candidate.diff` is that output.
|
||||
`candidate-manifest.sha256` lists the 28 changed paths.
|
||||
|
||||
## The choice (`probe.md`)
|
||||
|
||||
The probe ran Claude Code 2.1.303 and read the pinned pi docs. On Claude Code a `--plugin-dir` skill works only
|
||||
with the `Skill` tool on, and with it on, Claude Code's own bundled skills come along under names that belong to
|
||||
the Claude Code version. On pi, `--skill` puts a description in the prompt and the model reads SKILL.md with a
|
||||
file tool, which the gate refuses because the bundle is outside the workspace.
|
||||
|
||||
So the skill text goes into `prompt.md` on both harnesses, rendered from the bundle copies. Pi keeps
|
||||
`--no-skills`. Claude Code keeps `--disable-slash-commands` and `--restricted`. Neither harness discovers a skill,
|
||||
no new tool passes the gate, and the manifest's digests cover what the model reads.
|
||||
|
||||
## What changed
|
||||
|
||||
- `roles/*.json`: an optional `skills` list. Names match `/^[A-Za-z0-9._-]+$/`, with "." and ".." refused, no
|
||||
repeats, none on version 1 role files (`role.mjs`). `resolve.mjs` carries the list on the record.
|
||||
- First lists: coder ms-unslop, ms-sdlc, ms-file-read, ms-file-write. Reviewer ms-unslop, ms-sdlc,
|
||||
ms-file-read. Cto ms-unslop, ms-sdlc, ms-archify, ms-grill-me. Pm ms-unslop, ms-goal, ms-communications. The
|
||||
chief of staff lists none.
|
||||
- `bundle.mjs` reads each skill from `<dataRoot>/skills-enabled/<name>`, copies it to `<bundle>/skills/<name>`
|
||||
and renders every file, in sorted path order, between `<skill-file skill="..." path="...">` and
|
||||
`</skill-file>` lines under a "## Skills" heading. A role with no list gets no heading, no skills directory and
|
||||
an empty manifest list. The manifest records each skill's path and per-file sha256.
|
||||
- Refusals: a listed skill that isn't enabled is skill-not-enabled and names `scripts/skill.sh activate`. A
|
||||
symlink anywhere in the skill (the skill directory, SKILL.md, a sub-file) or a non-file entry, a file name
|
||||
outside SKILL_PATH, non-UTF-8 bytes, or a file holding the end marker is skill-refused. `lstatSync` throughout,
|
||||
so a symlink is refused, not followed.
|
||||
- SKILL_BYTES is 64 KiB across all of a role's skills together. The claude adapter passes `prompt.md` as one
|
||||
`--system-prompt` argument and Linux caps one argument at 128 KiB (MAX_ARG_STRLEN), so the cap leaves room for
|
||||
the contract and the rest of the prompt. Bytes per role by `wc -c` over the repo copies: coder 16853,
|
||||
reviewer 16252, cto 35888, pm 29080. Each fits (skills.test.mjs, the SKILL_BYTES test).
|
||||
- `launcher.mjs:254` checks `readSkills(resolved.skills, skillsDir)` after the route and contract checks and
|
||||
before the harness-off and binary checks, so the refusal comes before any run exists and doesn't depend on the
|
||||
harness. skill-not-enabled stays skill-not-enabled, anything else becomes skill-refused.
|
||||
- `adapters/README.md` and `packages/harness/README.md` describe the section and the flags. The other test
|
||||
edits give the fixtures a `skills-enabled` directory.
|
||||
|
||||
## Tests the brief names, each on both harnesses
|
||||
|
||||
| Brief test | Where |
|
||||
|---|---|
|
||||
| exactly the contracted skills | skills.test.mjs:45, four roles x pi and claude-code, prompt vs bundle copy vs source, byte for byte |
|
||||
| a contracted skill that isn't enabled refuses before any spawn | skills.test.mjs:109 (build, both harnesses), launcher.test.mjs row 64 pi test and the row 64 Claude Code test (launch, no run made) |
|
||||
| an enabled but unlisted skill is absent | skills.test.mjs:92, both harnesses |
|
||||
| no list: Pi runs with --no-skills, Claude Code gets none | skills.test.mjs:79 (build, both harnesses) and :170 (chief through each real adapter) |
|
||||
|
||||
skills.test.mjs:170 runs each real `adapter.sh` against a stand-in CLI that records argv. For the reviewer and
|
||||
the chief on each harness it checks:
|
||||
|
||||
- one `--system-prompt`, equal to `prompt.md` less trailing newlines
|
||||
- every marked file equal to its bundle copy
|
||||
- no skill path or marker anywhere else in argv
|
||||
- `--no-skills` and no `--skill` on pi, `--disable-slash-commands` and `--restricted` on Claude Code
|
||||
|
||||
The four-way loop and the Claude Code launcher test were added after gate run 1 started, when I read the brief's
|
||||
"each on both harnesses" against the tests. I stopped run 1 (bus and business had passed). Run 2 is on the
|
||||
amended commit.
|
||||
|
||||
## Mutants (`mutants.py` and `mutants-run2.txt` here, run 2 on c6d7d538f, logs in `/mnt/storage/scratch/sage-r64/mutants/`)
|
||||
|
||||
| Mutant | Change | Caught by |
|
||||
|---|---|---|
|
||||
| M1 | drop the launcher precheck | launcher.test.mjs, both row 64 tests |
|
||||
| M2 | map skill-not-enabled to skill-refused | launcher.test.mjs, both row 64 tests |
|
||||
| M3 | no SKILL_PATH check | skills.test.mjs |
|
||||
| M4 | no SKILL_BYTES check | skills.test.mjs |
|
||||
| M5 | no end-marker check | skills.test.mjs |
|
||||
| M6 | the walk follows symlinks (statSync) | skills.test.mjs |
|
||||
| M7 | the found check follows symlinks | skills.test.mjs |
|
||||
| M8 | no newline before the end marker | skills.test.mjs |
|
||||
| M9 | role names allow "." and ".." | role.test.mjs |
|
||||
| M10 | resolve drops the skills list | skills.test.mjs (resolve.test.mjs passes) |
|
||||
| M11 | Claude Code without --disable-slash-commands | skills.test.mjs |
|
||||
| M12 | Claude Code without --restricted | skills.test.mjs |
|
||||
| M13 | pi without --no-skills | skills.test.mjs |
|
||||
|
||||
13 of 13 killed. M10 is caught only by skills.test.mjs. Mos ruled that enough (mos-0862): a mutant counts as
|
||||
killed if any gate test catches it. M6 and M7 add `statSync` to the import so they fail on behavior, not on a
|
||||
ReferenceError. The files were restored byte for byte (`restored True`) and the orphan sweep found nothing.
|
||||
|
||||
## Base drift
|
||||
|
||||
`packages/harness/README.md` and `packages/harness/tests/bundle.test.mjs` changed on refactor in row 134
|
||||
(3a600cf5b), in different hunks from this row. The rebase onto 087d202bc was clean, and the gate runs on that
|
||||
base.
|
||||
|
||||
## Gate
|
||||
|
||||
`/mnt/storage/scratch/sage-r64/gate.sh` from the template, on the clean worktree at c6d7d538f, dirty 0, with
|
||||
the manifest checked before any suite. Run 2: GATE_PASSED (`gate-run2.txt` here, logs in `/mnt/storage/scratch/sage-r64/out/`). 15 package suites,
|
||||
2384 pass and 0 fail by an awk sum over the log (harness 133, cli 349, business 149, plugins 111). All 10
|
||||
script suites passed. The compose network was removed, and no container from the gate is left.
|
||||
|
||||
Run 1, on the earlier commit ece9fb5a3, was stopped by hand after bus and business passed (`gate-run1.txt`,
|
||||
`out-stopped-run1/`). It didn't fail. I stopped it to add the both-harness tests above.
|
||||
|
||||
## For the reviewers (mos-0857)
|
||||
|
||||
1. The rendered `prompt.md` matches the bundle copies byte for byte: skills.test.mjs:45 for the build and :170
|
||||
for what each adapter actually passes.
|
||||
2. `--no-skills`, `--disable-slash-commands` and `--restricted` are still on: `adapters/pi/adapter.sh`,
|
||||
`adapters/claude/adapter.sh`, and skills.test.mjs:170 with M11-M13.
|
||||
3. `candidate-manifest.sha256` against `git show c6d7d538f`.
|
||||
4. The 64 KiB cap against the one-argument limit, and that every repo role fits.
|
||||
5. That the launcher precheck comes before any run. (Round 1 correction, Darkwing N1: the code also puts it
|
||||
before the harness-specific checks, but no test pins that order. The brief asks for "before any spawn",
|
||||
which the launcher tests check.)
|
||||
|
||||
Landing waits for Mos's go (mos-0856, mos-0861 A2): activate the skills in the live data root, then start one
|
||||
managed coder session per harness and attach the manifest.
|
||||
|
||||
## Round 1 follow-ups
|
||||
|
||||
- F1 (Filbert, #1557 comment 29218, doc-only): `packages/harness/README.md:55-58` says a symlinked skill, a bad
|
||||
file name, non-UTF-8 bytes, the end marker and an over-cap list refuse `skill-not-enabled`. The code gives
|
||||
them `bundle-refused`, which the launcher maps to `skill-refused`. No test reads that README (grep of
|
||||
packages/*/tests and scripts/test-*.sh), so under decision 137 it doesn't block. It gets its own doc commit
|
||||
at landing, after the reviewed candidate, so the approved manifest stays as reviewed.
|
||||
- Darkwing (#1557 comment 29219) N1: covered by the item 5 correction above. N2: a leading UTF-8 BOM is stripped
|
||||
by the decoder, so the copy and digest would be 3 bytes off (`ignoreBOM: true` fixes it). N3: EACCES or
|
||||
ENOTDIR inside a skill dir comes out as launch-failed or a raw throw, not skill-refused, though the launch
|
||||
still refuses. N2 and N3 are follow-up issue #1672. N4: the cli and webui fixture edits are mechanical.
|
||||
N5: past this row, coder, reviewer, cto and pm launches refuse until all eight listed skills are active in
|
||||
the data root. Live has only ms-communications. Any re-pin past row 64 activates them first (sage-1064 R4).
|
||||
- Landing prep (sage-1065): cherry-pick 9f0039711 of c6d7d538f on 1a01f63ea, range-diff `=`, patch-id
|
||||
7e1948e2f2e9 equal. F1 doc commit 6b6567b8b. `landing-manifest.sha256` is the same 28 paths at 6b6567b8b; 27
|
||||
match the candidate manifest and the README differs by F1 only. Nothing goes onto refactor before Mos's go.
|
||||
|
||||
## Landing (mos-0863)
|
||||
|
||||
Mos gave the go for steps 1, 2, 3 and 5 in mos-0863. Step 4, the live activation, is now a window step
|
||||
(D3, `agents/sage/work/window-plan/carry-past-row64.md`). N2 and N3 are #1672, with its own row after 173 (D4).
|
||||
D5: the item 5 correction above is the N1 text fix, in this packet commit.
|
||||
|
||||
The landing gate ran `gate-land.sh` on a clean worktree at 6b6567b8b, with `landing-manifest.sha256` checked
|
||||
before any suite (`gate-land-run1.txt` here, logs in `/mnt/storage/scratch/sage-r64/out-land/`). 9f0039711 and
|
||||
6b6567b8b go onto refactor by fast-forward from 1a01f63ea, so the landed shas are the gated ones.
|
||||
@@ -0,0 +1,28 @@
|
||||
f4a4bc8bebf42c9cd897c4d7b3fea9635fff7c177fd2b881a4ab35d0492e4a3c adapters/claude/adapter.sh
|
||||
19f73115a5768cd67eef24d904706d5df1ec5c5d8108c92cba6968fa4f56bc88 adapters/pi/adapter.sh
|
||||
cb05dbe506967957c152d417046b0902fd407a58936af87f0100199c0d389235 adapters/README.md
|
||||
2318a58523c9a0f93ea2c4e8439b761f8190480ee5ccb6f93466c878750b5ad4 packages/business/src/resolve.mjs
|
||||
f24f27b05c3e02fa909c543e331b38ec6385892629ac0b731b52bf3a56d6a2cd packages/business/src/role.mjs
|
||||
f5cf086b161112f88ab930f5b557ff7f832ebbd143b4e5291a295d4e60db31de packages/business/tests/resolve.test.mjs
|
||||
609efd32e5e325d518c09f48765f587efb5d2ff794c656c09e3002bd1a70f543 packages/business/tests/role.test.mjs
|
||||
0573985439a072378b49541136ec4d2091db1f14a9fa7137bbf7229872d8614b packages/cli/src/launcher.mjs
|
||||
0e5f58ec412bf484714afd016e821deaad6228012c03bb0a709bd728a503f841 packages/cli/tests/channel-fixture.mjs
|
||||
ec4bbbc98adab6561205e5ec8e2f2495f1f55224da53e567c7a72e36b5410709 packages/cli/tests/git-socket.test.mjs
|
||||
e5c0aa0242ee92c474a7795074354123efafa8de497fcfeacfc87c8ca31efa32 packages/cli/tests/helpers.mjs
|
||||
4ac87c566046746e1d91957b6451a562a2b07187eae3ab6958edf26ed73ad33f packages/cli/tests/instances.test.mjs
|
||||
aad8187d8464b6fe88d8993948158fff7ac9ab7d07b4e670f32866d8ff41e85f packages/cli/tests/launcher.test.mjs
|
||||
776c180a09ed9791b09d06f5f28f18d2e500c5e2b5ad19f93042a19787b5853f packages/cli/tests/thread-launch.test.mjs
|
||||
fce6c078205462f63a60cfe80c68047b93bf201c99f77814085c920add08eb6c packages/harness/README.md
|
||||
46d8b7159077683e4028fdc389bab1c5d4634a0bdb4e2889c58524838346cd05 packages/harness/src/bundle.mjs
|
||||
d492c9a8059cc0cbc4e48d7ab5e053c0693d8a8bb99f900d4650db1b23de9838 packages/harness/tests/bundle.test.mjs
|
||||
e81b3e0b1deb22d15680bc6dade4edea070f2d0abbfa972883dfe9fa2e5c62a0 packages/harness/tests/claude-session.test.mjs
|
||||
8d1ae8daec5f89452c78fd64756b5c51210187b2ab17a76caffec2e0101da697 packages/harness/tests/helpers.mjs
|
||||
0aad8e9bbd910d848b5e60a8b0009c50a803b908b533332fc440e64ccdfeebb4 packages/harness/tests/pi-session.test.mjs
|
||||
d98f9e4283afb1c2dbc655710bde594ba8cbf65e5a15f2d2d6e170144afcab3c packages/harness/tests/skills.test.mjs
|
||||
8583a53a1c16e8a570fe3f1e015fe23556bd89ba5d2baa88e3d1392dcf8e8234 packages/webui/tests/instances.test.mjs
|
||||
0b4f0fba3f928f018f8b2cc7319e88ba3e46dfa9a40bc18b66f8c8d0fb13fbcd packages/webui/tests/routes-browser.test.mjs
|
||||
8c3558e1bf640fc86a737100a0840b44cf003cb49057c6c2e5d5a6b90466aec1 packages/webui/tests/routes.test.mjs
|
||||
13713c4e64ade3b1b1eedabed75f5f76fe65e73f6df336b771d1e34881d111f3 roles/coder.json
|
||||
60b6482a29a578234b4e5bf0d4c9a123bd3a65a65dc6e488f474546cf4456bb2 roles/cto.json
|
||||
1af1cb70512163eb5c39ae942496a4cc309b0b1d47b9cb48253ac788862f0e9f roles/pm.json
|
||||
4d2b2ab1208aced3a82d7de1ace879574fae2930ac1bc684f76e1d16e58cec8f roles/reviewer.json
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
manifest OK
|
||||
base 6b6567b8b dirty 0 DOCKER_HOST=unset
|
||||
pkg-bus rc=0 ℹ pass 428 ℹ fail 0 | ℹ duration_ms 2910.720699
|
||||
pkg-business rc=0 ℹ pass 149 ℹ fail 0 | ℹ duration_ms 1911.055343
|
||||
pkg-cli rc=0 ℹ pass 349 ℹ fail 0 | ℹ duration_ms 124237.078818
|
||||
pkg-control-board rc=0 ℹ pass 124 ℹ fail 0 | ℹ duration_ms 592.19684
|
||||
pkg-conversation rc=0 ℹ pass 182 ℹ fail 0 | ℹ duration_ms 33517.979983
|
||||
pkg-discord rc=0 ℹ pass 183 ℹ fail 0 | ℹ duration_ms 2609.094649
|
||||
pkg-harness rc=0 ℹ pass 133 ℹ fail 0 | ℹ duration_ms 11385.31588
|
||||
pkg-ledger rc=0 ℹ pass 89 ℹ fail 0 | ℹ duration_ms 12841.758249
|
||||
pkg-mosaic rc=0 ℹ pass 69 ℹ fail 0 | ℹ duration_ms 2087.375348
|
||||
pkg-plugins rc=0 ℹ pass 111 ℹ fail 0 | ℹ duration_ms 250.708711
|
||||
pkg-project rc=0 ℹ pass 37 ℹ fail 0 | ℹ duration_ms 1039.534021
|
||||
pkg-runs rc=0 ℹ pass 43 ℹ fail 0 | ℹ duration_ms 725.337132
|
||||
pkg-seat rc=0 ℹ pass 151 ℹ fail 0 | ℹ duration_ms 5814.69479
|
||||
pkg-tasks rc=0 ℹ pass 97 ℹ fail 0 | ℹ duration_ms 1149.422432
|
||||
pkg-webui rc=0 ℹ pass 239 ℹ fail 0 | ℹ duration_ms 57192.798712
|
||||
script-auth rc=0 | selftest: 15 passed, 0 failed
|
||||
script-conductor rc=0 | selftest: 17 passed, 0 failed
|
||||
script-config rc=0 | selftest: 24 passed, 0 failed
|
||||
script-discord rc=0 | discord suite: 78 passed, 0 failed
|
||||
script-extension-package rc=0 | extension package selftest: 18 passed, 0 failed
|
||||
script-foundation rc=0 | selftest: 44 passed, 0 failed
|
||||
script-land-work rc=0 | land-work selftests: 86 passed, 0 failed
|
||||
script-queue rc=0 ℹ pass 156 ℹ fail 0 | queue suite: 28 passed, 0 failed
|
||||
script-release rc=0 | selftest: 14 passed, 0 failed
|
||||
script-task rc=0 | selftest: 98 passed, 0 failed
|
||||
GATE_PASSED
|
||||
removed network sage-r64_default
|
||||
@@ -0,0 +1,29 @@
|
||||
manifest OK
|
||||
base c6d7d538f dirty 0 DOCKER_HOST=unset
|
||||
pkg-bus rc=0 ℹ pass 428 ℹ fail 0 | ℹ duration_ms 2962.041151
|
||||
pkg-business rc=0 ℹ pass 149 ℹ fail 0 | ℹ duration_ms 1896.955166
|
||||
pkg-cli rc=0 ℹ pass 349 ℹ fail 0 | ℹ duration_ms 118551.279573
|
||||
pkg-control-board rc=0 ℹ pass 124 ℹ fail 0 | ℹ duration_ms 578.067759
|
||||
pkg-conversation rc=0 ℹ pass 182 ℹ fail 0 | ℹ duration_ms 32999.614386
|
||||
pkg-discord rc=0 ℹ pass 183 ℹ fail 0 | ℹ duration_ms 2725.161912
|
||||
pkg-harness rc=0 ℹ pass 133 ℹ fail 0 | ℹ duration_ms 11394.510251
|
||||
pkg-ledger rc=0 ℹ pass 89 ℹ fail 0 | ℹ duration_ms 12782.512329
|
||||
pkg-mosaic rc=0 ℹ pass 69 ℹ fail 0 | ℹ duration_ms 2033.740426
|
||||
pkg-plugins rc=0 ℹ pass 111 ℹ fail 0 | ℹ duration_ms 244.365573
|
||||
pkg-project rc=0 ℹ pass 37 ℹ fail 0 | ℹ duration_ms 1031.796824
|
||||
pkg-runs rc=0 ℹ pass 43 ℹ fail 0 | ℹ duration_ms 722.039369
|
||||
pkg-seat rc=0 ℹ pass 151 ℹ fail 0 | ℹ duration_ms 5809.458583
|
||||
pkg-tasks rc=0 ℹ pass 97 ℹ fail 0 | ℹ duration_ms 1123.624373
|
||||
pkg-webui rc=0 ℹ pass 239 ℹ fail 0 | ℹ duration_ms 56273.900823
|
||||
script-auth rc=0 | selftest: 15 passed, 0 failed
|
||||
script-conductor rc=0 | selftest: 17 passed, 0 failed
|
||||
script-config rc=0 | selftest: 24 passed, 0 failed
|
||||
script-discord rc=0 | discord suite: 78 passed, 0 failed
|
||||
script-extension-package rc=0 | extension package selftest: 18 passed, 0 failed
|
||||
script-foundation rc=0 | selftest: 44 passed, 0 failed
|
||||
script-land-work rc=0 | land-work selftests: 86 passed, 0 failed
|
||||
script-queue rc=0 ℹ pass 156 ℹ fail 0 | queue suite: 28 passed, 0 failed
|
||||
script-release rc=0 | selftest: 14 passed, 0 failed
|
||||
script-task rc=0 | selftest: 98 passed, 0 failed
|
||||
GATE_PASSED
|
||||
removed network sage-r64_default
|
||||
@@ -0,0 +1,28 @@
|
||||
f4a4bc8bebf42c9cd897c4d7b3fea9635fff7c177fd2b881a4ab35d0492e4a3c adapters/claude/adapter.sh
|
||||
19f73115a5768cd67eef24d904706d5df1ec5c5d8108c92cba6968fa4f56bc88 adapters/pi/adapter.sh
|
||||
cb05dbe506967957c152d417046b0902fd407a58936af87f0100199c0d389235 adapters/README.md
|
||||
2318a58523c9a0f93ea2c4e8439b761f8190480ee5ccb6f93466c878750b5ad4 packages/business/src/resolve.mjs
|
||||
f24f27b05c3e02fa909c543e331b38ec6385892629ac0b731b52bf3a56d6a2cd packages/business/src/role.mjs
|
||||
f5cf086b161112f88ab930f5b557ff7f832ebbd143b4e5291a295d4e60db31de packages/business/tests/resolve.test.mjs
|
||||
609efd32e5e325d518c09f48765f587efb5d2ff794c656c09e3002bd1a70f543 packages/business/tests/role.test.mjs
|
||||
0573985439a072378b49541136ec4d2091db1f14a9fa7137bbf7229872d8614b packages/cli/src/launcher.mjs
|
||||
0e5f58ec412bf484714afd016e821deaad6228012c03bb0a709bd728a503f841 packages/cli/tests/channel-fixture.mjs
|
||||
ec4bbbc98adab6561205e5ec8e2f2495f1f55224da53e567c7a72e36b5410709 packages/cli/tests/git-socket.test.mjs
|
||||
e5c0aa0242ee92c474a7795074354123efafa8de497fcfeacfc87c8ca31efa32 packages/cli/tests/helpers.mjs
|
||||
4ac87c566046746e1d91957b6451a562a2b07187eae3ab6958edf26ed73ad33f packages/cli/tests/instances.test.mjs
|
||||
aad8187d8464b6fe88d8993948158fff7ac9ab7d07b4e670f32866d8ff41e85f packages/cli/tests/launcher.test.mjs
|
||||
776c180a09ed9791b09d06f5f28f18d2e500c5e2b5ad19f93042a19787b5853f packages/cli/tests/thread-launch.test.mjs
|
||||
a52268a146a121a9c479571672537912325001d0389a4a22a2d3c682ce283046 packages/harness/README.md
|
||||
46d8b7159077683e4028fdc389bab1c5d4634a0bdb4e2889c58524838346cd05 packages/harness/src/bundle.mjs
|
||||
d492c9a8059cc0cbc4e48d7ab5e053c0693d8a8bb99f900d4650db1b23de9838 packages/harness/tests/bundle.test.mjs
|
||||
e81b3e0b1deb22d15680bc6dade4edea070f2d0abbfa972883dfe9fa2e5c62a0 packages/harness/tests/claude-session.test.mjs
|
||||
8d1ae8daec5f89452c78fd64756b5c51210187b2ab17a76caffec2e0101da697 packages/harness/tests/helpers.mjs
|
||||
0aad8e9bbd910d848b5e60a8b0009c50a803b908b533332fc440e64ccdfeebb4 packages/harness/tests/pi-session.test.mjs
|
||||
d98f9e4283afb1c2dbc655710bde594ba8cbf65e5a15f2d2d6e170144afcab3c packages/harness/tests/skills.test.mjs
|
||||
8583a53a1c16e8a570fe3f1e015fe23556bd89ba5d2baa88e3d1392dcf8e8234 packages/webui/tests/instances.test.mjs
|
||||
0b4f0fba3f928f018f8b2cc7319e88ba3e46dfa9a40bc18b66f8c8d0fb13fbcd packages/webui/tests/routes-browser.test.mjs
|
||||
8c3558e1bf640fc86a737100a0840b44cf003cb49057c6c2e5d5a6b90466aec1 packages/webui/tests/routes.test.mjs
|
||||
13713c4e64ade3b1b1eedabed75f5f76fe65e73f6df336b771d1e34881d111f3 roles/coder.json
|
||||
60b6482a29a578234b4e5bf0d4c9a123bd3a65a65dc6e488f474546cf4456bb2 roles/cto.json
|
||||
1af1cb70512163eb5c39ae942496a4cc309b0b1d47b9cb48253ac788862f0e9f roles/pm.json
|
||||
4d2b2ab1208aced3a82d7de1ace879574fae2930ac1bc684f76e1d16e58cec8f roles/reviewer.json
|
||||
@@ -0,0 +1,14 @@
|
||||
M1-no-launcher-precheck KILLED | launcher.test.mjs rc=1 ✖ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain direc
|
||||
M2-not-enabled-mapped-refused KILLED | launcher.test.mjs rc=1 ✖ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain direc
|
||||
M3-no-path-check KILLED | skills.test.mjs rc=1 ✖ a skill file that can't go into the prompt refuses: a bad name, non-UTF-8 bytes, or the end marker
|
||||
M4-no-size-check KILLED | skills.test.mjs rc=1 ✖ a role's skills over SKILL_BYTES together refuse, naming the skill that crossed it (0.949446ms)
|
||||
M5-no-end-marker-check KILLED | skills.test.mjs rc=1 ✖ a skill file that can't go into the prompt refuses: a bad name, non-UTF-8 bytes, or the end marker
|
||||
M6-walk-follows-symlinks KILLED | skills.test.mjs rc=1 ✖ a skill directory that isn't plain refuses: a symlinked skill, SKILL.md or sub-file, or a non-file
|
||||
M7-found-follows-symlinks KILLED | skills.test.mjs rc=1 ✖ a skill directory that isn't plain refuses: a symlinked skill, SKILL.md or sub-file, or a non-file
|
||||
M8-no-newline-before-end KILLED | skills.test.mjs rc=1 ✖ each repo role's prompt carries exactly its listed skills, each file as the bundle copy and the so
|
||||
M9-role-allows-dot KILLED | role.test.mjs rc=1 ✖ row 64 skills: optional, distinct skill directory names, none on version 1 (0.778394ms)
|
||||
M10-resolve-drops-skills KILLED | resolve.test.mjs rc=0 ; skills.test.mjs rc=1 ✖ each repo role's prompt carries exactly its listed skills, each file as the bundle copy and the so
|
||||
M11-claude-slash-on KILLED | skills.test.mjs rc=1 ✖ each adapter passes the skills only as --system-prompt text, with skill discovery off (14.743943ms
|
||||
M12-claude-unrestricted KILLED | skills.test.mjs rc=1 ✖ each adapter passes the skills only as --system-prompt text, with skill discovery off (14.675833ms
|
||||
M13-pi-skills-on KILLED | skills.test.mjs rc=1 ✖ each adapter passes the skills only as --system-prompt text, with skill discovery off (8.408382ms)
|
||||
restored True
|
||||
@@ -0,0 +1,59 @@
|
||||
import os, signal, subprocess, sys, shutil
|
||||
W='/mnt/storage/scratch/sage-r64/wt'
|
||||
OUT='/mnt/storage/scratch/sage-r64/mutants'
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
BUNDLE='packages/harness/src/bundle.mjs'; LAUNCH='packages/cli/src/launcher.mjs'
|
||||
ROLE='packages/business/src/role.mjs'; RESOLVE='packages/business/src/resolve.mjs'
|
||||
CLAUDE='adapters/claude/adapter.sh'; PI='adapters/pi/adapter.sh'
|
||||
SKT=('packages/harness','tests/skills.test.mjs')
|
||||
LT=('packages/cli','tests/launcher.test.mjs')
|
||||
RT=('packages/business','tests/role.test.mjs')
|
||||
RST=('packages/business','tests/resolve.test.mjs')
|
||||
M=[
|
||||
('M1-no-launcher-precheck', LAUNCH, ' readSkills(resolved.skills, skillsDir);\n', '', [LT]),
|
||||
('M2-not-enabled-mapped-refused', LAUNCH, 'e.code === "skill-not-enabled" ? "skill-not-enabled" : "skill-refused"', '"skill-refused"', [LT]),
|
||||
('M3-no-path-check', BUNDLE, ' if (!SKILL_PATH.test(entry)) throw', ' if (false) throw', [SKT]),
|
||||
('M4-no-size-check', BUNDLE, ' if (size.bytes > SKILL_BYTES) throw', ' if (false) throw', [SKT]),
|
||||
('M5-no-end-marker-check', BUNDLE, ' if (text.includes(SKILL_END)) throw', ' if (false) throw', [SKT]),
|
||||
('M6-walk-follows-symlinks', BUNDLE, ' const stat = lstatSync(path);', ' const stat = statSync(path);', [SKT]),
|
||||
('M7-found-follows-symlinks', BUNDLE, ' try { return lstatSync(path); }', ' try { return statSync(path); }', [SKT]),
|
||||
('M8-no-newline-before-end', BUNDLE, '\\n${f.text}\\n${SKILL_END}', '\\n${f.text}${SKILL_END}', [SKT]),
|
||||
('M9-role-allows-dot', ROLE, ' || name === "." || name === ".."', '', [RT]),
|
||||
('M10-resolve-drops-skills', RESOLVE, ' skills: [...definition.skills],', ' skills: [],', [RST, SKT]),
|
||||
('M11-claude-slash-on', CLAUDE, ' --disable-slash-commands \\\n', '', [SKT]),
|
||||
('M12-claude-unrestricted', CLAUDE, ' --restricted \\\n', '', [SKT]),
|
||||
('M13-pi-skills-on', PI, 'SKILLS_FLAG="--no-skills"', 'SKILLS_FLAG=""', [SKT]),
|
||||
]
|
||||
files=sorted({m[1] for m in M})
|
||||
BK={}
|
||||
for f in files:
|
||||
b=os.path.join(OUT, f.replace('/','__')+'.orig'); shutil.copyfile(os.path.join(W,f), b)
|
||||
if open(b,'rb').read()!=open(os.path.join(W,f),'rb').read(): sys.exit('backup failed '+f)
|
||||
BK[f]=b
|
||||
try:
|
||||
for name,f,o,n,tests in M:
|
||||
orig=open(BK[f]).read()
|
||||
assert orig.count(o)==1, name
|
||||
mut=orig.replace(o,n)
|
||||
if 'statSync(path)' in n and 'lstatSync' not in n:
|
||||
imp='import { lstatSync, mkdirSync,'
|
||||
assert mut.count(imp)==1, name+' import'
|
||||
mut=mut.replace(imp,'import { lstatSync, statSync, mkdirSync,')
|
||||
open(os.path.join(W,f),'w').write(mut)
|
||||
results=[]
|
||||
for pkg,test in tests:
|
||||
lp=os.path.join(OUT,f'{name}--{os.path.basename(test)}.log'); log=open(lp,'w')
|
||||
p=subprocess.Popen(['node','--test',test],cwd=os.path.join(W,pkg),stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
|
||||
try: rc=p.wait(timeout=300)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(p.pid,signal.SIGKILL); p.wait(); rc='timeout'
|
||||
log.close()
|
||||
txt=open(lp).read()
|
||||
fails=[l for l in txt.splitlines() if l.startswith('✖ ') and 'failing tests' not in l]
|
||||
results.append((os.path.basename(test),rc,fails[0][:100] if fails else ''))
|
||||
killed=any(r[1]!=0 for r in results)
|
||||
print(name,'KILLED' if killed else 'SURVIVED','|',' ; '.join(f'{t} rc={rc} {fl}' for t,rc,fl in results),flush=True)
|
||||
shutil.copyfile(BK[f],os.path.join(W,f))
|
||||
finally:
|
||||
for f in files: shutil.copyfile(BK[f],os.path.join(W,f))
|
||||
print('restored', all(open(BK[f],'rb').read()==open(os.path.join(W,f),'rb').read() for f in files), flush=True)
|
||||
Reference in New Issue
Block a user