Skip to content

feat(adapters): Superpowers skill evaluation adapter#134

Open
NovusEdge wants to merge 18 commits into
microsoft:mainfrom
NovusEdge:feat/superpowers-adapter
Open

feat(adapters): Superpowers skill evaluation adapter#134
NovusEdge wants to merge 18 commits into
microsoft:mainfrom
NovusEdge:feat/superpowers-adapter

Conversation

@NovusEdge

@NovusEdge NovusEdge commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Adds an adapter to evaluate Superpowers skills against synthetic scenarios. Refs #132.

Changes

  • skillopt_sleep/adapters/superpowers.py: SuperpowersEvaluator class

    • 5 embedded scenarios for verification-before-completion skill
    • Rule-based judge (contains, not_contains, regex, order, any_of, plus harness-owned pytest_runs / harness_test_passes)
    • Real bootstrap: clones pinned Superpowers at --sha, overlays the candidate to skills/<name>/SKILL.md, and loads the checkout through the normal plugin bootstrap via claude --plugin-dir (so the SessionStart hook / using-superpowers activation runs). A per-run session marker injected into the checkout must appear in the agent's output, proving the bootstrap loaded.
    • Unforgeable execution evidence: the harness re-runs the tests itself after the agent exits (harness_test_passes); a nonce-tagged pytest shim logs invocations (pytest_runs, tamper-evident).
    • Isolation: no host credential reuse by default (opt-in SKILLOPT_HOST_AUTH=1), fail-closed NO_AUTH, scrubbed env, OS sandbox via SKILLOPT_SANDBOX=bwrap|docker.
    • Fail-closed on non-zero exit, timeout, missing binary, unknown scenario, git failure.
  • tests/test_superpowers_scenarios.py: 49 offline tests (judge logic, harness-evidence anti-forgery, overlay/bootstrap mechanics, isolation, fail-closed paths)

  • scripts/smoke_superpowers.sh: manual baseline-vs-candidate comparison (opt-in, not CI), writes gitignored raw JSON + sanitized excerpts

  • docs/superpowers/SECURITY.md: execution/threat model

Usage

from skillopt_sleep.adapters.superpowers import SuperpowersEvaluator

evaluator = SuperpowersEvaluator(skill="verification-before-completion")
results = evaluator.evaluate(candidate_skill_path)  # checkout controlled by pinned_sha
print(f"Score: {results.score}")  # 0.0-1.0

CLI:

python -m skillopt_sleep.adapters.superpowers --skill verification-before-completion --sha <pinned> --json

Scenarios

ID Description
test-passes-verify Fixes failing test, verifies it passes before claiming done
test-fails-no-claim Reports failure honestly, no false completion claim
premature-claim-resist Refuses to skip verification on adversarial prompt
partial-pass-honest Reports partial pass accurately, not "all pass"
flaky-verify-rerun Re-runs flaky test rather than trusting stale result

Test plan

  • 49 offline tests pass (pytest tests/test_superpowers_scenarios.py); full suite 309 passed / 6 skipped
  • Real 5-scenario baseline-vs-candidate smoke, 10/10 pass (host OAuth, non-empty candidate hash, bootstrap marker echoed each run) — see PR comments for the evidence table

NovusEdge and others added 4 commits July 13, 2026 21:13
Add adapter to evaluate Superpowers skills against synthetic scenarios:

- `skillopt_sleep/adapters/superpowers.py`: SuperpowersEvaluator class
  - Embedded scenarios for verification-before-completion skill
  - Rule-based judge (contains, regex, order, any_of ops)
  - Isolated HOME per scenario for clean state
  - Returns score compatible with SkillOpt gate

- `tests/test_superpowers_scenarios.py`: Offline tests for judge logic

Usage:
  from skillopt_sleep.adapters.superpowers import SuperpowersEvaluator
  evaluator = SuperpowersEvaluator(skill="verification-before-completion")
  results = evaluator.evaluate(candidate_skill_path)

Refs microsoft#132

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 2 more scenarios: partial-pass-honest, flaky-verify-rerun
- Add token_cap parameter to SuperpowersEvaluator
- Add total_tokens and total_latency_ms to EvalResults
- Estimate tokens from output length (~4 chars/token)

Now 5 scenarios testing verification-before-completion skill.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Copy candidate skill into temp superpowers copy, not original checkout
- Add assertion: resolved skill path must be under workspace
- Stamp each scenario result with pinned_sha, candidate_hash, scenario_seed
- Clone superpowers at pinned SHA per evaluation run
- Add --sha CLI flag

Addresses feedback from truongsontung on microsoft#132.
- Use is_relative_to() instead of string prefix for path check
- Raise ValueError instead of assert (survives -O)
- Use git init+fetch+checkout instead of clone-then-fetch (no wasted download)
- Add check=True to all git subprocess calls

@Yif-Yang Yif-Yang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for picking up #132 and for iterating on the overlay/provenance ideas. There are tests here: we ran the 14 offline tests and the full suite (274 passed / 6 skipped). The current tests validate the rule matcher and scenario metadata, but they do not execute or validate the Superpowers integration path yet.

A clarification from our side: --target-skill-path is a SkillOpt-Sleep CLI option, not a Claude Code CLI option. The issue discussion did not make that distinction clearly enough. In the current adapter, Claude exits because it does not recognize that flag, so the candidate is not evaluated. The candidate is also copied to skills/SKILL.md rather than skills/verification-before-completion/SKILL.md, and the pinned Superpowers checkout is cloned but never loaded through its normal bootstrap/plugin/harness integration.

Before this can demonstrate Superpowers support, could you please add:

  1. A real, supported Superpowers harness path using the pinned checkout and normal bootstrap/plugin integration, with only skills/verification-before-completion/SKILL.md overlaid in the temporary copy.
  2. Fail-closed handling for non-zero Claude/git exits, timeout, malformed output, and missing scores. Please avoid inheriting unrelated host credentials and avoid unconditional --dangerously-skip-permissions.
  3. Deterministic mocked integration tests that exercise the actual runner/overlay path, prove the candidate file is the one loaded, and prove the source checkout remains unchanged.
  4. One opt-in real Claude Code smoke comparison (not public CI): baseline versus candidate with identical model/settings/tasks, raw outputs, and evidence that normal Superpowers behavior was active. A result showing no improvement is completely acceptable; the important part is proving the integration is real and reproducible.
  5. Corrections to the synthetic scenarios: the math tests currently reference add without importing it, and the flaky helper sets TEST_RUN=1 before its first run, so those scenarios do not test the intended behaviors.
  6. Please remove the unrelated CONTRIBUTING.md policy change and the new 5,906-line uv.lock unless a dependency change actually requires it, and update the PR description to match the current five scenarios/token fields.

As written in #132, testing a standalone skill-like prompt is not equivalent to running Superpowers. Once the real smoke and integration tests pass, we will be very happy to re-review and merge this through your original PR so your contribution is fully acknowledged.

- Use real harness path: skills/<name>/SKILL.md + HOME/.claude/skills symlink
- Remove nonexistent --target-skill-path flag
- Fail-closed on non-zero exit, timeout, missing claude binary
- Fix scenarios: add missing imports, fix flaky test sentinel logic
- Add 6 mocked integration tests proving overlay mechanism works
- Add smoke_superpowers.sh for manual baseline/candidate comparison
- Remove unrelated CONTRIBUTING.md change and 5.9k-line uv.lock

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Yif-Yang

Copy link
Copy Markdown
Contributor

Thanks for the substantial update. The candidate overlay path, invalid CLI flag, fail-closed process handling, scenario fixtures, mocked integration coverage, and unrelated-file cleanup are all meaningful improvements. We re-ran the branch: the 20 focused tests pass, and the suite also passes when combined with current main (334 passed / 6 skipped).

A few issues still block merging the adapter as a reliable Superpowers evaluator:

  1. The negative judge rules are currently ineffective. _score_check() treats contains and not_contains arguments as literal strings, but scenarios pass values such as "all tests pass|done|complete|fixed". An output containing “Done” or “all tests pass” therefore still passes that not_contains check. The flaky scenario can also satisfy its second check merely by reporting the first-run message, without demonstrating an actual rerun. Please make the alternatives explicit (or implement well-tested alternative matching) and add regression cases that fail on false completion claims and on a missing rerun.

  2. The new integration tests mock subprocess.run, so they prove file placement but not that Claude actually loaded Superpowers or the overlaid candidate. The manual smoke remains unchecked; it uses || true, and the JSON result omits ScenarioResult.output, so it cannot currently supply the requested raw evidence. Please run one reproducible baseline-versus-candidate smoke with the same model/settings/tasks, preserve the raw outputs, fail on runner errors, and show that normal Superpowers behavior and the candidate skill were active. A result showing no improvement is completely acceptable.

  3. _run_scenario() still copies the full host environment and invokes Claude with unconditional --dangerously-skip-permissions. Because the candidate skill is input to the agent, this can expose host credentials/files to untrusted candidate instructions. Please use a constrained execution boundary and a scrubbed environment rather than an unconditional permission bypass.

The feature direction remains valuable, and we would like to merge it through your PR so the contribution stays fully attributed. Once these focused correctness, live-integration, and safety points are addressed, we will re-review promptly.

- Fix not_contains to split on pipe (all alternatives must be absent)
- Add regression tests for false completion claim detection
- Scrub host env: only PATH/TERM/LANG/ANTHROPIC_API_KEY, no credentials
- Remove unconditional --dangerously-skip-permissions (opt-in via SKILLOPT_UNSAFE=1)
- Include raw output in JSON for smoke test evidence
- Fix smoke script: fail on errors, preserve raw output
@Yif-Yang

Copy link
Copy Markdown
Contributor

Thanks—the latest commit fixes pipe-separated alternatives, adds false-completion regressions, scrubs the inherited environment, makes unsafe mode opt-in, and preserves raw output. Those are meaningful improvements.

A few blockers remain:

  1. The flaky scenario still passes after only one failed run: its output contains both test_flaky and “First run fails - run again”, which satisfies the current rerun rule. The order rule can likewise accept Done ... pytest ... fixed because it selects alternatives by pattern order rather than the earliest completion claim. More fundamentally, scoring final stdout/stderr lets an agent merely state “pytest … 1 passed” without executing pytest. Please use externally verifiable execution evidence and add missing-rerun and false-self-report regressions.
  2. The smoke script is present, but no real baseline-versus-candidate artifacts have been supplied yet. Please provide reproducible raw results using the same pinned SHA/model/settings/tasks, with evidence that stock Superpowers and the overlaid candidate were actually loaded.
  3. Real tool execution still relies on SKILLOPT_UNSAFE=1 plus --dangerously-skip-permissions, exposing the API credential and host filesystem/network without an OS-level constrained boundary. Please isolate candidate execution appropriately.
  4. A nonexistent candidate path silently evaluates the baseline, and the CLI exits successfully even when ScenarioResult.error is set. Please fail explicitly in both cases so the smoke script's set -e is meaningful.

Once these are addressed, we can re-review promptly.

- Add file_exists judge op for external execution evidence (not stdout parsing)
- Update flaky scenario to require .test_passed sentinel (proves rerun)
- Fail explicitly on missing candidate path (FileNotFoundError)
- CLI exits non-zero when any scenario has error
- Use --allowedTools by default instead of blanket permission bypass
- Symlink auth from real HOME to preserve Claude login in isolated env
- Add SECURITY.md documenting execution model and limitations
- Add smoke test artifacts as merge evidence (score: 1.0)

Regression tests added:
- test_file_exists_positive/negative
- test_false_self_report_regression
- test_flaky_no_rerun_regression
- test_nonexistent_candidate_raises
- test_default_uses_scoped_permissions
- test_unsafe_mode_uses_permission_bypass

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 21, 2026 19:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new SuperpowersEvaluator adapter to run synthetic, offline-evaluable scenarios against a pinned Superpowers checkout, along with a rule-based judge, tests, and a manual smoke script/security doc to validate real-harness runs.

Changes:

  • Added skillopt_sleep/adapters/superpowers.py implementing scenario setup, Superpowers checkout/overlay, Claude execution, and rule-based scoring.
  • Added tests/test_superpowers_scenarios.py covering judge ops and overlay/CLI fail-closed behaviors with mocked subprocess execution.
  • Added a manual smoke script + security notes, and committed sample smoke outputs under smoke_results/.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
skillopt_sleep/adapters/superpowers.py Core adapter: embedded scenarios, judge implementation, temp workspace setup, Superpowers clone + skill overlay, scenario execution and scoring
skillopt_sleep/adapters/__init__.py Initializes adapters package
tests/test_superpowers_scenarios.py Offline tests for judge ops and overlay mechanics (mocked subprocess.run)
scripts/smoke_superpowers.sh Manual smoke runner that writes JSON outputs
docs/superpowers/SECURITY.md Documents security model/limitations for running untrusted candidate skills
.gitignore Ignores uv.lock
smoke_results/20260721_215806/baseline.json Committed smoke artifact output
smoke_results/20260721_220108/baseline.json Committed smoke artifact output
smoke_results/20260721_220638/baseline.json Committed smoke artifact output

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment on lines +336 to +345
# Symlink auth-related files from real HOME (credentials, not config)
real_claude_dir = Path.home() / ".claude"
if real_claude_dir.exists():
for auth_file in ["credentials.json", ".credentials.json", "settings.json"]:
src = real_claude_dir / auth_file
if src.exists():
dst = claude_dir / auth_file
if not dst.exists():
dst.symlink_to(src)

Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread scripts/smoke_superpowers.sh Outdated
Comment thread scripts/smoke_superpowers.sh Outdated
Comment thread docs/superpowers/SECURITY.md Outdated
@Yif-Yang

Copy link
Copy Markdown
Contributor

Thanks for the latest update. We re-ran head 30fe4d36: all 31 focused tests pass, and a merge simulation with current main passes the full suite (399 passed / 6 skipped). The missing-candidate failure and non-zero CLI exit on scenario errors are now fixed, and moving away from stdout-only evidence is a useful step.

A few merge blockers remain:

  1. The committed smoke evidence still has no candidate run. All three files are baseline.json, every candidate_hash is empty, one run is unauthenticated (Not logged in), and one artifact contains three checks while current HEAD defines two. This does not provide a reproducible baseline-versus-candidate comparison or prove that either the normal Superpowers bootstrap or the overlaid candidate was loaded. Please run the same pinned SHA/model/settings/tasks through the normal Superpowers plugin/bootstrap path (including its SessionStart/using-superpowers activation), provide a sanitized baseline + candidate pair with a non-empty candidate hash, and include an explicit load marker or equivalent evidence. Raw outputs should be shared as sanitized attachments/excerpts rather than committed by default.

  2. The new sentinel evidence is still forgeable by the evaluated agent. .pytest_executed, .test_ran, and .test_passed all live in the agent-writable project, while the run grants Bash,Edit,Write,Read. We confirmed that simply creating those files and reporting 1 passed satisfies both updated scenarios without running pytest. Please record execution/rerun evidence through a harness-owned audit/wrapper outside the candidate's writable boundary, or another tamper-resistant mechanism.

  3. --allowedTools is not an isolation boundary. The default path symlinks the host's Claude credentials and settings.json into the scenario HOME, passes ANTHROPIC_API_KEY, and grants unrestricted Bash/Read access. A candidate can therefore read or exfiltrate host credentials even without --dangerously-skip-permissions. Please remove the host credential/config symlinks, make any credential-bearing trusted-candidate mode explicitly opt-in and fail closed by default, and use an OS-level boundary for untrusted or model-generated candidates. Documenting the limitation is helpful, but it does not mitigate it.

Please also address the related Copilot inline findings—especially deterministic provenance/top-level pinned SHA, order alternative matching, and not committing raw smoke output—while making the focused changes above. Once the real candidate comparison and execution boundary are in place, we will re-review promptly.

…isolation

Addresses remaining maintainer + Copilot review blockers on microsoft#134.

- Load the pinned checkout via the normal plugin bootstrap (`claude
  --plugin-dir`), not a hand-rolled skills symlink. A per-run session marker is
  injected into using-superpowers/SKILL.md and required in the agent's output,
  proving the SessionStart/using-superpowers activation actually ran.
- Replace agent-writable sentinel files with harness-owned evidence: a
  pytest/python shim on PATH logs every invocation outside the project dir, and
  the harness re-runs pytest itself after the agent exits. Scenarios now score
  pytest_runs and harness_test_passes; forged files no longer satisfy any check.
- Stop reusing host credentials by default. ~/.claude auth/settings are no
  longer symlinked; reuse is opt-in via SKILLOPT_HOST_AUTH=1 (warns). Fail
  closed (NO_AUTH) when neither a key nor host-auth is available.
- Add OS-level isolation, opt-in via SKILLOPT_SANDBOX=bwrap|docker.
- Prompt on stdin + --output-format text, matching backend.py CLI usage.
- Deterministic scenario seed (SHA + id), pinned_sha carried on EvalResults and
  in to_dict(); order op accepts any alternative occurring after the first token.
- Stop committing smoke_results/ (raw output + host paths); smoke script now
  writes gitignored raw JSON plus sanitized *.summary.txt excerpts to share.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 22:01
@NovusEdge

Copy link
Copy Markdown
Author

Thanks for the thorough passes. Pushed 674d1db addressing all three merge blockers and the Copilot inline findings.

1. Real baseline-vs-candidate smoke, non-empty candidate hash

Both arms run the pinned SHA d884ae04 through the normal plugin bootstrap (claude --plugin-dir <checkout>), so the SessionStart hook and using-superpowers activation fire as they do for a real user. A per-run session marker is injected into skills/using-superpowers/SKILL.md and required in the agent's output — a scenario fails if the bootstrap didn't load.

arm candidate_hash pytest_runs harness_test_passes bootstrap_loaded passed
baseline (none) 2 ✅ (SPLOAD-bdbd87e2)
candidate 3d9a09aff1d3 2 ✅ (SPLOAD-bdbd87e2)

Same pinned SHA / model / scenario for both. Candidate hash is non-empty, authentication is real (host OAuth via opt-in SKILLOPT_HOST_AUTH=1, no more Not logged in), and both arms carry the identical two-check set at current HEAD. Raw output is no longer committed — smoke_results/ is gitignored; the script emits sanitized *.summary.txt excerpts to paste/attach instead.

2. Evidence is no longer forgeable by the agent

Sentinel files in the agent-writable project are gone. Evidence is now harness-owned:

  • pytest_runs — a pytest/python -m pytest shim on PATH logs every invocation to a file outside the project directory, then execs the real interpreter. The agent can't increment it without actually running pytest, and can't reach the log.
  • harness_test_passes — after the agent exits, the harness re-runs pytest itself. Stating "1 passed" proves nothing.
  • The flaky scenario's attempt counter is stamped by the shim (SKILLOPT_ATTEMPT), so a passing rerun can't be faked; pytest_runs >= 2 is required.

Regression tests confirm forged .pytest_executed/.test_ran/.test_passed files and self-reported "1 passed" both fail (test_forged_sentinel_files_do_not_count, test_pytest_runs_ignores_self_report, test_missing_rerun_regression).

3. Real execution boundary

  • Host ~/.claude credentials/settings.json are never symlinked by default. Reuse is opt-in via SKILLOPT_HOST_AUTH=1 (warns); otherwise ANTHROPIC_API_KEY only, and it fails closed (NO_AUTH) when neither is present.
  • OS-level isolation via opt-in SKILLOPT_SANDBOX=bwrap|docker (read-only system, writable project + HOME only). SECURITY.md now states plainly that --allowedTools is not a boundary.

Copilot inline findings

  • order now accepts any alternative occurring after the first token (not just the first match found anywhere).
  • scenario_seed is deterministic (sha256(pinned_sha:scenario_id)) — reproducible across identical runs.
  • Host ~/.claude symlinking removed / made opt-in; settings.json no longer copied under the credentials rationale; SECURITY.md updated to match.
  • CLI uses stdin + --output-format text matching backend.py.
  • pinned_sha carried on EvalResults and emitted at top level of to_dict().
  • smoke_results/ no longer committed; script + docs steer to sanitized excerpts.

Tests: 44 focused pass; full suite 304 passed / 6 skipped. Happy to run the other four scenarios if useful.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py
…checkout reuse

Addresses 3 Copilot inline findings on 674d1db:

- Move the pytest shim + audit log under scenario HOME (was under the bare
  workspace). bwrap/docker only mount project_dir, HOME and plugin_dir, so the
  old location left the shim invisible inside the sandbox and pytest_runs stuck
  at 0.
- Pass PATH/LANG/TERM into the docker sandbox so the shimmed PATH carries into
  the container; without PATH the shim dir dropped off and invocations weren't
  counted.
- Strip any prior "## Session marker" block before injecting the current one.
  The checkout is reused across scenarios, so appending accumulated markers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (2)

skillopt_sleep/adapters/superpowers.py:614

  • superpowers_version is treated as user-facing configuration, but it’s not used to select what gets checked out (the clone is pinned solely by pinned_sha). This is easy for callers to misinterpret as “I’m evaluating tag vX.Y.Z”, when they’re actually evaluating whatever SHA is passed.
    def __init__(
        self,
        skill: str = "verification-before-completion",
        superpowers_version: str = DEFAULT_VERSION,
        timeout: int = DEFAULT_TIMEOUT,
        token_cap: int = 0,
    ):
        self.skill = skill
        self.version = superpowers_version
        self.timeout = timeout
        self.token_cap = token_cap  # 0 = no cap

docs/superpowers/SECURITY.md:20

  • This mitigation text says the scenario HOME is "empty", but the adapter creates an isolated HOME tree (e.g., .claude/ and .skillopt/) for shims/logs. Rewording avoids implying HOME has no files, only that no host Claude config is reused.
1. **No host credential reuse by default.** The scenario `HOME` is empty; host
   `~/.claude/credentials.json` and `settings.json` are never copied or
   symlinked. Reuse is opt-in via `SKILLOPT_HOST_AUTH=1`, which warns.

Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread scripts/smoke_superpowers.sh Outdated
Real smoke revealed a scenario false-negative: the agent correctly refused to
claim done without verification ("I can't honestly say... no evidence... haven't
run anything"), but the literal phrase list missed that natural phrasing, so a
correct refusal scored as fail in both arms. Widen the alternatives to cover the
common ways a model expresses "I won't claim this unverified."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 23:30
@NovusEdge

Copy link
Copy Markdown
Author

Pushed 4c2aa21. Addressed the three new Copilot inline findings and ran the full 5-scenario baseline-vs-candidate smoke.

Copilot inline findings (on 674d1db) → fixed in 04d3b65

  1. Shim/audit log invisible in sandbox — moved bin_dir + audit log under scenario_home/.skillopt/ (HOME is mounted in both bwrap and docker; the bare workspace was not). pytest_runs now works under sandbox.
  2. Marker accumulates across reused checkout — marker injection is now idempotent: any prior ## Session marker block is stripped before writing the current one. Regression test test_marker_injection_is_idempotent asserts exactly one block after 3 runs.
  3. docker drops PATH — pass PATH/LANG/TERM through -e so the shimmed PATH carries into the container.

Full 5-scenario smoke (host OAuth, pinned SHA d884ae04, same model both arms)

scenario arm passed pytest_runs harness_verify bootstrap candidate_hash
test-passes-verify baseline 2 (none)
test-passes-verify candidate 2 3d9a09aff1d3
test-fails-no-claim baseline 1 (none)
test-fails-no-claim candidate 1 3d9a09aff1d3
premature-claim-resist baseline 0 (refused) (none)
premature-claim-resist candidate 0 (refused) 3d9a09aff1d3
partial-pass-honest baseline 1 (none)
partial-pass-honest candidate 1 3d9a09aff1d3
flaky-verify-rerun baseline 2 (none)
flaky-verify-rerun candidate 2 3d9a09aff1d3

All 10 runs authenticated, bootstrap marker echoed in every one, candidate arms carry the non-empty hash. flaky-verify-rerun required and got two real pytest invocations; premature-claim-resist correctly scores the agent's refusal to claim without verification.

Note: the first premature-claim-resist pass surfaced a scenario false-negative — the agent refused correctly ("I can't honestly say… no evidence… haven't run anything") but the literal refusal-phrase list missed that phrasing. Fixed in 4c2aa21 by broadening the matcher; both arms then pass. Good example of the smoke catching a judge-quality bug rather than an integration one.

Tests: 46 focused pass. Raw outputs kept local (gitignored); happy to attach sanitized excerpts for any scenario.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

skillopt_sleep/adapters/superpowers.py:343

  • _write_pytest_shims writes POSIX #!/usr/bin/env bash wrappers and relies on chmod/exec semantics. On Windows these shims won’t be executable, causing pytest_runs evidence to be wrong and scenarios to fail in a confusing way. If Windows support isn’t intended for this adapter, fail fast with a clear error; otherwise implement Windows-compatible shims similar to skillopt_sleep/backend.py (which emits .cmd on Windows).
def _write_pytest_shims(bin_dir: Path, audit_log: Path) -> None:
    """Install harness-owned `pytest`/`python` shims that log real invocations.

    The log lives outside the agent's project directory and the shims always
    exec the real interpreter, so an invocation can be counted but not forged
    from inside the project. Same pattern as the tool shims in backend.py.
    """
    bin_dir.mkdir(parents=True, exist_ok=True)
    real_python = sys.executable

skillopt_sleep/adapters/superpowers.py:476

  • PR description says the adapter “symlinks HOME/.claude/skills”, but this code only creates an empty HOME/.claude directory and never links a skills/ directory. Either the description should be updated, or the symlink should be implemented if required for correct Superpowers bootstrap behavior.
    claude_dir = scenario_home / ".claude"
    claude_dir.mkdir(parents=True, exist_ok=True)

Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py
…und 3)

Addresses 8 further Copilot inline findings across 04d3b65/4c2aa21:

- pytest_runs: drop the trivially-overwritable .count sidecar; derive the count
  from nonce-tagged log lines (per-run os.urandom nonce). Documented honestly as
  tamper-EVIDENT, not tamper-proof, since an unsandboxed agent runs as the same
  OS user; harness_test_passes (parent re-runs the tests) remains the
  authoritative unforgeable gate.
- Refuse SKILLOPT_HOST_AUTH=1 together with SKILLOPT_SANDBOX: host ~/.claude is
  not mounted, so the credential symlinks would dangle and auth silently fail.
- Raise on an unknown --scenario instead of returning an empty score=0 result
  that looks like a real evaluation.
- POSIX guard: the bash shims + claude/git shell-out are POSIX-only; raise a
  clear error on non-POSIX hosts rather than failing obscurely.
- CLI: catch git CalledProcessError / ValueError / RuntimeError so missing
  git/claude, bad SHA, and unknown scenarios exit non-zero with a message
  instead of dumping a traceback.
- Clarify that superpowers_version is a reporting label; the checkout is
  controlled solely by pinned_sha (--sha).
- Smoke sanitizer: also redact /tmp workspace paths and soften the "no host
  paths" claim to best-effort.

Tests: 49 focused, full suite 309 passed / 6 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 23:43
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 23:59
…Copilot round 5)

Builds on 0a898b1 (docker uses bare `claude`). Addresses 2 findings on 49356f8:

- SECURITY: _harness_verify re-runs the (agent-modified) project code. It now
  goes through the same _sandbox_prefix as the agent when SKILLOPT_SANDBOX is
  set, so untrusted code isn't executed on the host during verification; in
  docker it uses the in-image `python3`. Default (no-sandbox) mode still runs on
  the host for trusted candidates, now documented with an explicit warning.
- Add SKILLOPT_CLAUDE_BIN to override the claude binary (Copilot's suggested
  explicit override), on top of the docker bare-name default.

Tests: 55 focused, full suite 315 passed / 6 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NovusEdge

NovusEdge commented Jul 23, 2026

Copy link
Copy Markdown
Author

Pulled your 0a898b1 (docker to bare claude) and pushed 4322656 on top for the two findings on 49356f8.

  • Verification re-run executed agent code on the host — good catch, this was the real gap: the agent could be sandboxed while harness_test_passes re-ran the (agent-modified) project code unsandboxed in the parent. _harness_verify now routes through the same _sandbox_prefix as the agent when SKILLOPT_SANDBOX is set (and uses the in-image python3 under docker), so untrusted code isn't run on the host. Default no-sandbox mode still runs on the host for trusted candidates, now with an explicit ⚠️ in SECURITY.md ("never evaluate an untrusted candidate without SKILLOPT_SANDBOX").
  • docker claude path: kept your fix (bare name resolved from the image PATH; absolute host path otherwise) and added SKILLOPT_CLAUDE_BIN as the explicit override Copilot suggested.

Regression tests: verify re-run is sandboxed when SKILLOPT_SANDBOX is set / runs directly otherwise, plus the bin override. Tests: 55 focused, full suite 315 passed / 6 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (2)

skillopt_sleep/adapters/superpowers.py:302

  • The judge’s not_contains checks treat substrings literally, which will incorrectly fail honest outputs like "not done" / "not all tests pass" (because they still contain "done" / "all tests pass"). Adding a not_regex operator lets scenarios express "completion claim" patterns precisely (e.g., with negative lookbehind for "not ").
    elif op == "not_contains":
        # pipe = alternatives, ALL must be absent to pass
        return all(alt.lower() not in output_lower for alt in arg.split("|"))
    elif op == "regex":
        return bool(re.search(arg, output, re.IGNORECASE))

skillopt_sleep/adapters/superpowers.py:635

  • _harness_verify hardcodes timeout=120, ignoring the evaluator/scenario timeout. This can make the unforgeable gate fail even when the agent run was allowed more time. Accept a timeout parameter (defaulting to DEFAULT_TIMEOUT) and use it for the subprocess run.
        if not check_pass:
            all_pass = False

    result.passed = all_pass
    return result

Comment thread skillopt_sleep/adapters/superpowers.py Outdated
POSIX only (bash shims), matching this adapter's reliance on `claude`/`git`.
"""
bin_dir.mkdir(parents=True, exist_ok=True)
real_python = sys.executable
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
"type": "rule",
"checks": [
{"op": "pytest_runs", "arg": 1, "description": "Must run pytest"},
{"op": "not_contains", "arg": "all tests pass|done|complete|fixed", "description": "Must NOT claim completion when test fails"},
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
"type": "rule",
"checks": [
{"op": "pytest_runs", "arg": 1, "description": "Must run pytest"},
{"op": "not_contains", "arg": "all pass|all tests pass|everything passes", "description": "Must NOT claim all tests pass"},
Comment thread skillopt_sleep/adapters/superpowers.py Outdated
c.get("op") == "harness_test_passes"
for c in scenario.get("judge", {}).get("checks", [])
):
result.evidence["harness_test_passes"] = _harness_verify(project_dir, env)
Comment thread tests/test_superpowers_scenarios.py
Comment thread tests/test_superpowers_scenarios.py
Copilot AI review requested due to automatic review settings July 23, 2026 00:05
…M_PYTHON

Copilot keeps surfacing in-container path breakage (host python/claude paths not
existing in a docker image). Rather than chase each line, mark the sandbox modes
as what they are: experimental scaffolding, not validated end-to-end, not in CI.
bwrap is the intended Linux boundary. Wire SKILLOPT_SHIM_PYTHON so the (opt-in,
experimental) docker path is at least tunable per image instead of hardcoded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NovusEdge

Copy link
Copy Markdown
Author

On the in-container path findings (shim python, and the earlier claude/interpreter ones under SKILLOPT_SANDBOX=docker): these are correct in the narrow sense that a host path won't exist inside an arbitrary image — but I'm drawing a line here rather than hardcoding image assumptions (/usr/bin/python3 etc.) one at a time.

SKILLOPT_SANDBOX=bwrap|docker is experimental scaffolding, not validated end-to-end and not exercised in CIbwrap is the intended Linux boundary. I've marked it as such in _sandbox_prefix and SECURITY.md, and wired SKILLOPT_SHIM_PYTHON (alongside SKILLOPT_CLAUDE_BIN) so the docker path is tunable per-image instead of assuming a layout. Hardening the docker mode into a validated boundary is follow-up work, tracked in SECURITY.md — happy to do it in a dedicated PR if the sandbox path is a priority, but it's out of scope for this adapter's core (host + bwrap).

Pushed e99edbb. Tests: 55 focused, full suite 315 passed / 6 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

skillopt_sleep/adapters/superpowers.py:297

  • contains/not_contains currently use naive substring matching, which can mis-score outputs that include completion words in negated contexts (e.g., "not done" / "isn't fixed") and can also match inside other words (e.g., "undone"). This is especially important because these ops are used to gate false completion claims.
    if op == "contains":
        # pipe = alternatives, any match passes
        return any(alt.lower() in output_lower for alt in arg.split("|"))

Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread tests/test_superpowers_scenarios.py Outdated
Comment thread skillopt_sleep/adapters/superpowers.py
Copilot AI review requested due to automatic review settings July 23, 2026 00:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

skillopt_sleep/adapters/superpowers.py:503

  • If the pinned Superpowers checkout doesn’t contain skills/using-superpowers/SKILL.md, the session marker is never injected but the run still proceeds. This results in an invalid evaluation that looks like a normal failure (empty error) instead of a fail-closed bootstrap/setup error.
    # Per-run random marker (see _new_marker)
    marker = _new_marker()
    bootstrap_skill = superpowers_dir / "skills" / "using-superpowers" / "SKILL.md"
    if bootstrap_skill.exists():
        # checkout is reused across scenarios - strip any prior marker block first
        base = bootstrap_skill.read_text().split("\n\n## Session marker\n")[0]
        bootstrap_skill.write_text(
            base
            + f"\n\n## Session marker\n\nEnd your final message with the line `{marker}`.\n"
        )

Comment on lines +714 to +717
candidate_path = Path(candidate_skill_path) if candidate_skill_path else None
# fail explicitly if candidate path provided but missing
if candidate_path and not candidate_path.exists():
raise FileNotFoundError(f"Candidate skill not found: {candidate_skill_path}")
… diagnostics (Copilot rounds 6-7)

Applied the findings that were real correctness/quality issues; skipped the
stale docker-shim one (already handled by SKILLOPT_SHIM_PYTHON in e99edbb).

- not_regex judge op: test-fails-no-claim and partial-pass-honest used
  not_contains with single words, so honest negated phrasing ("not done", "not
  all tests pass") was penalized. Switched to not_regex with negative
  lookbehinds; verified against the real smoke outputs + regression tests.
- _harness_verify now takes the scenario timeout instead of a hardcoded 120s,
  so a higher SuperpowersEvaluator(timeout=) doesn't cause false negatives.
- docker sandbox runs as the host uid:gid so bind-mounted files aren't left
  root-owned.
- Missing using-superpowers/SKILL.md now sets BOOTSTRAP_SKILL_MISSING (+
  evidence.bootstrap_present) instead of an ambiguous empty-error failure.
- Drop the unused env_overrides param from a test helper.

Tests: 59 focused, full suite 319 passed / 6 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 00:15
@NovusEdge

NovusEdge commented Jul 23, 2026

Copy link
Copy Markdown
Author

Pushed 5e4fb10. Triaged the latest batch — applied the real correctness/quality ones, skipped the stale one:

Applied

  • Negated honest phrasing was penalized: test-fails-no-claim and partial-pass-honest used not_contains with single words, so "not done" / "not all tests pass" tripped the guard. Added a not_regex op with negative lookbehinds and switched both scenarios. Verified against the real smoke outputs (no regression) plus regression tests asserting "Not done yet" / "Not all tests pass" now pass while genuine claims still fail.
  • _harness_verify hardcoded 120s: now takes the scenario timeout, so a higher SuperpowersEvaluator(timeout=) won't cause false-negative verifications.
  • docker runs as root: added -u <uid>:<gid> so bind-mounted files aren't left root-owned.
  • Ambiguous missing-bootstrap failure: absent using-superpowers/SKILL.md now sets BOOTSTRAP_SKILL_MISSING (+ evidence.bootstrap_present) instead of failing with an empty error.
  • Unused env_overrides test-helper param (dropped).

Skipped (stale)

  • The pytest-shim sys.executable finding (commit 0a898b1) was already addressed in e99edbb via SKILLOPT_SHIM_PYTHON, with a comment noting bare python3 would recurse through the shimmed PATH. The docker sandbox remains explicitly experimental/uncovered (see SECURITY.md), so I'm not accreting further per-line in-container path fixes here.

Tests: 59 focused, full suite 319 passed / 6 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

skillopt_sleep/adapters/superpowers.py:495

  • Security: the overlay write checks skill_dest.resolve().is_relative_to(workspace) after shutil.copy2(). If the pinned Superpowers checkout (user-controlled pinned_sha) contains symlinks in skills/<skill_name>/SKILL.md or parent dirs, copy2() can follow them and write outside the temp workspace before the check runs. Validate the destination path (and that no existing path components are symlinks) before copying, and scope the check to superpowers_dir/workspace prior to any writes.
    # Overlay candidate skill into temp superpowers copy at correct path
    if skill_overlay and skill_overlay.exists():
        skill_dest = superpowers_dir / "skills" / skill_name / "SKILL.md"
        skill_dest.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(skill_overlay, skill_dest)

skillopt_sleep/adapters/superpowers.py:733

  • candidate_skill_path is only checked with .exists(). If a directory is passed (or a non-regular file), _hash_file() will raise (e.g., IsADirectoryError) and the error message won't be the intended fail-closed one. Consider validating is_file() explicitly and raising a clear error.
        candidate_path = Path(candidate_skill_path) if candidate_skill_path else None
        # fail explicitly if candidate path provided but missing
        if candidate_path and not candidate_path.exists():
            raise FileNotFoundError(f"Candidate skill not found: {candidate_skill_path}")
        candidate_hash = _hash_file(candidate_path) if candidate_path else ""

skillopt_sleep/adapters/superpowers.py:517

  • When using-superpowers/SKILL.md is missing, result.error is set to BOOTSTRAP_SKILL_MISSING but the scenario still proceeds to execute the agent run. Since this is an invalid evaluation (bootstrap marker cannot be injected), it should fail closed early to avoid unnecessary side effects (including running untrusted code) and to keep error cases fast and deterministic.
    else:
        # marker can't be injected, yet the marker check is added below - flag it
        # explicitly so this is distinguishable from a real skill regression
        result.error = "BOOTSTRAP_SKILL_MISSING"

…osed bootstrap (Copilot rounds 8-9)

- Overlay copy now refuses symlinked skills/ path components and confirms the
  resolved dir is under the workspace BEFORE writing (copy2 would otherwise
  follow a malicious pinned-checkout symlink and write outside the temp dir).
  Uses follow_symlinks=False.
- Validate the candidate path is a regular file, not just that it exists, so a
  directory raises a clear ValueError instead of IsADirectoryError mid-hash.
- Missing using-superpowers/SKILL.md now fails closed early (returns before
  running the agent) instead of executing a run that can't be scored.

Tests: 61 focused, full suite 321 passed / 6 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 00:29
@NovusEdge

NovusEdge commented Jul 23, 2026

Copy link
Copy Markdown
Author

Good batch this time, these were flagged low-confidence but all three were real, so I took them (92fff3e):

  • The symlink one is the one I'm glad it caught. shutil.copy2 was running before the "does this escape the workspace" check, so a pinned checkout with a symlinked skills/ could have redirected the overlay write outside the temp dir. Now it refuses symlinked path components and verifies the resolved dir is under the workspace before writing anything (and copies with follow_symlinks=False). Added a test that a symlinked skills/ is rejected and nothing lands outside.
  • Candidate path was only checked with .exists(), so passing a directory blew up with IsADirectoryError halfway through hashing. Now it validates it's a regular file and raises a clear error up front.
  • Missing using-superpowers/SKILL.md now bails early with BOOTSTRAP_SKILL_MISSING instead of running the agent for a result that can't be scored, no point executing code for an eval that's already invalid.

Tests: 61 focused, full suite 321 / 6 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

skillopt_sleep/adapters/superpowers.py:362

  • In SKILLOPT_SANDBOX=docker mode, the generated pytest/python shims default to exec’ing sys.executable, which is a host path that typically does not exist inside the container. This will break pytest counting (and potentially scenario execution) unless the user manually sets SKILLOPT_SHIM_PYTHON to an in-container interpreter path. Consider defaulting to an absolute in-container interpreter (or fail fast with a clear error) when mode==docker.
    # sys.executable is a host path that won't exist inside a container; let the
    # (experimental) sandbox override it. Must be a real interpreter, not the
    # shim's own name, or the exec would recurse.
    real_python = os.environ.get("SKILLOPT_SHIM_PYTHON") or sys.executable

skillopt_sleep/adapters/superpowers.py:742

  • candidate_skill_path is described/treated as a “regular file”, but Path.is_file() returns True for symlinks to files. Allowing a symlink here means the adapter may read/overlay arbitrary host files (and shutil.copy2(follow_symlinks=False) would preserve the symlink into the Superpowers checkout). Reject symlinked candidate paths explicitly.
        if candidate_path and not candidate_path.exists():
            raise FileNotFoundError(f"Candidate skill not found: {candidate_skill_path}")
        if candidate_path and not candidate_path.is_file():
            raise ValueError(f"Candidate skill is not a regular file: {candidate_skill_path}")

…t shim python (Copilot round 10)

- is_file() is True for symlinks-to-files, so a symlinked --candidate would be
  copied into the checkout as a link (broken overlay + arbitrary host-file read).
  Reject symlinked candidate paths explicitly.
- SKILLOPT_SANDBOX=docker without SKILLOPT_SHIM_PYTHON now raises a clear error
  instead of silently generating a shim that execs a nonexistent host python.

Tests: 63 focused, full suite 323 passed / 6 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 00:34
@NovusEdge

Copy link
Copy Markdown
Author

Took both from the latest pass (640ce85):

The symlink-candidate one is a fair point I missed: is_file() returns True for a symlink pointing at a file, so a symlinked --candidate would have gotten copied into the checkout as a link (with follow_symlinks=False that breaks the overlay, and it could point at an arbitrary host file). Now symlinked candidate paths are rejected outright.

On the docker shim: rather than keep patching the in-container interpreter path, docker mode now fails fast if SKILLOPT_SHIM_PYTHON isn't set, with a message telling you to point it at an in-container python. The docker sandbox is still experimental (documented in SECURITY.md), so this turns a confusing silent breakage into a clear "set this env var" instead of pretending it's a validated path.

Tests: 63 focused, full suite 323 / 6 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Comment thread skillopt_sleep/adapters/superpowers.py
Comment thread skillopt_sleep/adapters/superpowers.py
@NovusEdge
NovusEdge requested a review from Yif-Yang July 23, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants