Skip to content

feat(shield): isolate /implement via devcontainer (v2.17.0)#44

Merged
ashwinimanoj merged 29 commits into
mainfrom
worktree-shield-devcontainer-implement
May 20, 2026
Merged

feat(shield): isolate /implement via devcontainer (v2.17.0)#44
ashwinimanoj merged 29 commits into
mainfrom
worktree-shield-devcontainer-implement

Conversation

@ashwinimanoj
Copy link
Copy Markdown
Contributor

@ashwinimanoj ashwinimanoj commented May 18, 2026

Summary

Adds a per-repo devcontainer scaffolder that runs /implement and other agent commands in filesystem + network egress isolation. Adopts the industry-converged two-boundary pattern (Anthropic, Cursor, OpenAI Codex, Gemini, Copilot) — bind-mount workspace only, default-deny iptables egress with stack-specific allowlist, credentials in a per-project named Docker volume, non-root user, digest-pinned Features. Mitigates upstream footguns claude-code#36907 (DNS exfiltration) and #32113 (Feature-overwrites-firewall-script).

What's in this PR

  • /shield init-devcontainer — new command + skill that scaffolds .devcontainer/{devcontainer.json,Dockerfile,shield-firewall.sh,postCreate.sh} from detected stack
  • Stack detection (shield/scripts/detect_stack.py) — reuses repo-scan markers
  • Per-repo composition (shield/scripts/compose_devcontainer.py) — feature-map → devcontainer.json; unconditionally includes the Anthropic claude-code Feature as the constant layer
  • Pre-flight gate (shield/scripts/devcontainer_gate.py) — /implement Step 0 that prompts/refuses if .devcontainer/ exists but agent is on host
  • Templates (shield/skills/devcontainer/templates/) — Dockerfile, firewall, postCreate
  • .devcontainer/ for this repo — dogfood instance; scaffolder must reproduce
  • .shield.json schema extension + shield/README.md documentation section
  • Tests: unit (4 pytest suites), integration (3 fixture repos), E2E phase (gated by RUN_DEVCONTAINER_E2E=1), static check, RED-GREEN skill verification record
  • Version bump: shield 2.16.0 → 2.17.0

Design + research artifacts

  • docs/superpowers/specs/2026-05-18-devcontainer-implement-design.md — design spec (re-baselined to as-built after build-time fixes)
  • docs/superpowers/specs/2026-05-18-devcontainer-implement-red-green.md — RED-GREEN record (5/5 invariants in GREEN, 0/5 in RED)
  • docs/shield/devcontainer-implement-20260518/research/1-claude-implement-isolation/ — findings + transcript from external evidence-gathering

Test plan

  • make test returns 30/30 passing
  • shield/tests/test-devcontainer-files.sh returns ALL DEVCONTAINER STATIC CHECKS PASSED (15 sub-assertions)
  • shield/tests/test-init-devcontainer.sh returns ALL INIT-DEVCONTAINER TESTS PASSED (14 sub-assertions across 3 fixture repos + idempotency + unmapped-stack warning)
  • docker build -t shield-implement-smoke -f .devcontainer/Dockerfile .devcontainer/ succeeds; container whoami returns dev, uv --version works, iptables/ipset present, sudoers scoped to firewall script only
  • RUN_DEVCONTAINER_E2E=1 shield/tests/e2e/phases/devcontainer.sh — requires devcontainer CLI on host; deferred to CI or a Docker-on-host run
  • Reopen this worktree in VS Code "Reopen in Container", run claude /login once, then /implement against a story — full-stack manual smoke

Risk to existing users

The implement-feature SKILL gains a Step 0 that calls the devcontainer gate. The gate is a no-op for repos without .devcontainer/ (returns PROCEED immediately), so users who haven't run /shield init-devcontainer are unaffected.

Out of scope (documented as deferred)

  • Cloud / Codespaces / CI launchers
  • Shield-published base image
  • microVM tier (gVisor / Firecracker / Edera)
  • Feature digest auto-update

🤖 Generated with Claude Code

ashwinimanoj and others added 29 commits May 18, 2026 13:56
Adds the design spec and supporting research for moving Shield's
/implement command into a devcontainer. Adopts the two-boundary pattern
(filesystem + network egress) from Anthropic's reference, with strict
Feature digest pinning and mitigations for known upstream footguns
(claude-code#36907, #32113).

- docs/superpowers/specs/2026-05-18-devcontainer-implement-design.md
- docs/shield/devcontainer-implement-20260518/research/1-claude-implement-isolation/{transcript,findings}.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…solation

Eleven stories, ~36 tasks. Story 1 dogfoods the design by hand-writing
.devcontainer/ for this repo; stories 2-7 build the scaffolder that
reproduces the same output; story 8 wires the gate into implement-feature;
stories 9-11 add E2E test, RED-GREEN verification, docs + version bump.
Constant layer per spec §Architecture. Non-root 'dev' user, sudoers
scoped to firewall script only, Claude Code + uv pinned.
Default-deny outbound + allowlist Anthropic API, GitHub CIDRs, and
EXTRA_HOSTS env var. Locks port 53 to 127.0.0.11 (claude-code#36907).
Named shield-firewall.sh to avoid Feature overwrite (claude-code#32113).
Polyglot: python (3.12) + github-cli. Features pinned by @sha256.
Named volume claude-config-${devcontainerId} for credentials.
Firewall via postStartCommand with NET_ADMIN/NET_RAW caps.
Idempotent install hints: shield adapters (uv sync) + top-level
test deps (jsonschema, pyyaml).
- shield-firewall.sh: capture curl output explicitly so a failed
  api.github.com/meta fetch errors out instead of silently emptying
  the GitHub CIDR allowlist
- postCreate.sh: replace pip --break-system-packages with
  uv pip install --system for consistency with the uv-based design
- postCreate.sh: replace fragile cd /workspaces/* glob with a find
  + empty-check that errors out loudly on misconfigured mounts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: the MS base image ships with a 'vscode' user at UID
  1000, so groupadd --gid 1000 collided. Remove vscode first.
- Dockerfile/devcontainer.json: move Claude Code install out of the
  Dockerfile (curl|bash native-build install was OOM-killed mid-build,
  exit 137) and into the upstream Anthropic Dev Container Feature
  (digest-pinned). Cleaner, version-pinnable, maintained by Anthropic.

The spec needs the same updates in §Architecture and the Dockerfile
example; Story 5 (template extraction) will pick these up.
Asserts presence, validity, and key spec invariants (remoteUser=dev,
NET_ADMIN, named volume mount, DNS pinning) of the .devcontainer/
files. Wired into run-all.sh as section 11.
Source-of-truth shield-firewall.sh, Dockerfile.tmpl, postCreate.sh.tmpl.
Static check enforces they stay in sync with this repo's hand-written
.devcontainer/ instance (the dogfood).
Detects python / node / go / java / terraform / rust / ruby and flags
docker-in-docker + kubernetes. Sorted output for deterministic
composition. Wired into run-all.sh.
Maps detected stacks (python/node/go/java/terraform) to digest-pinned
Dev Container Features, postCreate hints, and per-stack firewall
allowlist entries. JSON Schema validates each entry; pytest enforces
required stacks and sane allowlists.
Pure function: stacks + feature-map -> devcontainer.json dict.
Skips unknown stacks with stderr warning. Merges per-stack +
user-provided allowlist entries into EXTRA_HOSTS.

Always includes Anthropic's claude-code Feature (digest-pinned)
as the constant layer — every Shield devcontainer needs Claude Code
regardless of detected stacks. This is the home of the spec's
"Layer 1 Constant", moved out of the Dockerfile after a build-time
OOM exposed that the curl|bash install doesn't fit inside docker
build.
Decides PROCEED vs REFUSE based on SHIELD_IN_DEVCONTAINER env var,
.devcontainer/ presence, and .shield.json devcontainer.required
(ask | true | false). 'ask' branch persists 'always'/'never' choices
to .shield.json. CLI entry point invoked from implement-feature
skill's Step 0.
New command + skill that scaffolds .devcontainer/ from detected stacks.
Integration test asserts python-only, polyglot, terraform-only fixtures
produce correct devcontainer.json + postCreate.sh + .shield.json, that
re-runs are idempotent, and that unmapped stacks warn instead of crash.
Adds Step 0 to the skill: invoke devcontainer_gate.py before loading
story. Backwards-compatible — gate is a no-op for repos without
.devcontainer/.
The template carried the original unsafe glob (`cd /workspaces/*`) even
after `.devcontainer/postCreate.sh` was hardened in d2d4e25. The
static check in test-devcontainer-files.sh doesn't diff this template
(it's a skeleton with {{HINTS}}), so the divergence wasn't caught —
but the scaffolder would emit files with the unsafe pattern until now.

Now matches the safer `find -mindepth 1 -maxdepth 1 -type d | head -n 1`
pattern with explicit empty-workspace check.
uv 0.11.13 does not have a --with-no-deps flag. The B8 integration
test was already working around this by using `uv run python3`
without the flag; the SKILL.md prose was kept byte-identical to the
plan and shipped broken. The commands work without the flag — uv
won't install extras unless the script imports them.
Real-world build testing surfaced changes that diverged from the
original spec. Updates the 7 sections that drifted, in order:

- Layers table: Claude Code moved out of Layer 1 (Dockerfile) into
  Layer 2 (Anthropic Feature); uv added to Layer 1
- Architecture diagram: matching layer composition
- Dockerfile example: drops curl install of Claude Code; adds
  userdel vscode mitigation; documents the OOM rationale
- devcontainer.json example: shows the Anthropic claude-code Feature
  as the first entry; EXTRA_HOSTS now includes astral.sh
- Components §3 feature-map: documents that the composer
  unconditionally inserts the Anthropic claude-code Feature as the
  constant layer
- Firewall outline: defensive curl capture + non-empty check pattern;
  DEFAULT_HOSTS extended to claude.ai + console.anthropic.com
- postCreate.sh example: find-based workspace lookup; uv pip
  instead of pip --break-system-packages

No code changes — spec-only update so the design document matches
what's deployed on this branch.
Gated by RUN_DEVCONTAINER_E2E=1. Builds + runs the scaffolded
.devcontainer/ on a python-api fixture, asserts claude works inside,
firewall is active, and a container-side commit reaches the host
bind-mount. CI default: skipped.
- README: usage, threat model (contained / not contained), allowlist
  extension, mitigated footguns
- .shield.json schema: add 'devcontainer' block
- marketplace: bump shield to 2.17.0
CLAUDE.md mandates RED-GREEN verification for new skills. Two
subagent dispatches (same goal, opposite SKILL access) produced
clear-cut results:

- RED (no SKILL): bind-mounts host ~/.claude, no firewall, no
  NET_ADMIN, no @sha256 pinning, no #36907/#32113 mitigations.
  Honestly acknowledges the resulting gaps.
- GREEN (with SKILL): all 5 plan-mandated invariants present
  (named volume, shield-firewall.sh, NET_ADMIN/NET_RAW, per-stack
  Features, port 53 lock), plus userdel-vscode and claude-code
  Feature rationale.

5/5 invariants in GREEN, 0/5 in RED — no SKILL refactor needed.
C1 (Critical): shield-firewall.sh was setting OUTPUT DROP before
curl-ing api.github.com/meta. The fetch then failed against our own
firewall, the empty-response guard fired exit 1, and the container
started with OUTPUT DROP and no allowlist ACCEPT rules — so
api.anthropic.com was unreachable. Reorder: resolve hostnames and
fetch GitHub meta CIDRs BEFORE applying iptables policies, then
build out the rules. Same change applied to both the live
.devcontainer/shield-firewall.sh and the template (kept in sync by
test-devcontainer-files.sh).

I1 (Important): e2e/phases/devcontainer.sh still used uv run
--with-no-deps in the scaffolding heredoc. The SKILL.md was fixed in
f39cedc but the E2E was missed (it's gated by RUN_DEVCONTAINER_E2E=1
so locally skipped). Drop the unsupported flag.

I2 (Important): README's allowlist-customization guidance said "edit
.shield.json then rebuild" — but a plain rebuild reuses the existing
devcontainer.json, which has the OLD EXTRA_HOSTS baked in.
Re-running /shield init-devcontainer regenerates EXTRA_HOSTS from
.shield.json's firewall_extra_allowlist. Clarify the flow.

Minor issues (M1-M4) from the review are deferred as post-merge
tracking.
…oting

The README now walks a new user from "I don't have Docker installed
yet" to "/implement is running in the container":

- Prerequisites: container runtime options (Docker Desktop, Colima,
  Podman, Docker Engine on Linux) with install commands per platform,
  and launcher options (VS Code Dev Containers extension vs.
  @devcontainers/cli) with the trade-offs and verify steps.
- First run: 4-step sequence — scaffold, reopen in container, claude
  /login, /implement. Calls out that the OAuth login is one-time per
  project because creds live in a named Docker volume.
- Troubleshooting: 6 common first-run failures with their causes and
  fixes (daemon not running, CLI missing, GHCR rate-limit, GID
  collision regression, claude --version, allowlist).
Direct invocation produced no output because the phase script only
defined functions — it relied on the framework to source it and call
phase_assertions. Running `RUN_DEVCONTAINER_E2E=1 ./devcontainer.sh`
silently exited 0, which is confusing.

Add a main block (gated on BASH_SOURCE != $0 so the framework path
is unchanged) that:

- Honors RUN_DEVCONTAINER_E2E=1 — without it, prints the skip
  message and exits 0.
- With it set, creates a temp python-api project (copied from
  shield/examples/python-api or a minimal pyproject.toml fallback),
  initializes git, and calls phase_assertions.
- Reports PASS/FAIL totals and exits 1 on failure.

The framework-driven path is unchanged: `if BASH_SOURCE != $0` so
when test-pipeline-*.sh sources this file, the main block is a no-op.
The Dev Containers CLI rejects 'ghcr.io/owner/feature:1@sha256:...'
combined refs. Its path-validation regex
  /^[a-z0-9]+([._-][a-z0-9]+)*(\/[a-z0-9]+([._-][a-z0-9]+)*)*$/
does not allow ':' in the path component, so 'feature:1' fails
before the digest is even considered. Caught by the first manual
VS Code "Reopen in Container" smoke test (logs:

  Path 'anthropics/devcontainer-features/claude-code:1' ... failed
  validation. ... Could not resolve Feature manifest for ...

Fix: use the digest alone, 'name@sha256:digest'. The digest
uniquely identifies the manifest; the tag was redundant.

Files updated:
- .devcontainer/devcontainer.json — three Feature keys
- shield/skills/devcontainer/feature-map.json — five entries
- shield/skills/devcontainer/feature-map.schema.json — pattern
  regex now requires '@sha256:' immediately after the path,
  comment explains why
- shield/scripts/compose_devcontainer.py — ANTHROPIC_CLAUDE_CODE_FEATURE
  constant, with explanatory NB
- shield/scripts/test_compose_devcontainer.py — grep patterns
  updated; new assertion that ':1@sha256:' is NOT present
- shield/tests/test-init-devcontainer.sh — grep patterns updated
  for python/node/terraform assertions
- docs/superpowers/specs/...-design.md — feature-map examples,
  devcontainer.json example; new note explaining the format rule
- shield/README.md — new Troubleshooting row for the failure mode
… refs

Adds shield/scripts/check_devcontainer_refs.py: a pure-Python validator
that re-implements the Dev Containers CLI's Feature-ref path regex and
flags any malformed reference in .devcontainer/devcontainer.json.

Specifically catches the 'ghcr.io/owner/feature:1@sha256:digest'
combined form — the CLI's path regex disallows ':' in the path
component, so the build silently fails with 'Could not resolve
Feature manifest'. That's exactly the bug 3ea99eb fixed; this hook
prevents it from coming back via a hand-edit or a misguided refactor.

Wired into .pre-commit-config.yaml as section 5. Triggers when any
of these change:
  - .devcontainer/devcontainer.json
  - shield/scripts/compose_devcontainer.py
  - shield/scripts/check_devcontainer_refs.py
  - shield/skills/devcontainer/feature-map.json

No network and no Node required — runs in milliseconds. Offline
contributors are unaffected.

Manual smoke: temporarily reintroduced ':1' on a Feature key and ran
the hook — fails with a precise error message naming the bad ref
and explaining the fix. Restoring the file makes it pass again.
The first manual e2e against Podman exposed four real bugs that
docker build + unit/integration tests could not catch. Each fix
documented in code comments.

1. shield-firewall.sh: ipset chokes on IPv6 entries.
   - dig sometimes returns CNAME lines; api.github.com/meta returns
     both IPv4 AND IPv6 CIDRs in .git[].
   - Filter dig output to bare dotted-quad IPv4 only (case glob).
   - Filter jq output to entries without ':' (IPv6 marker).

2. shield-firewall.sh: hardcoded 127.0.0.11 DNS doesn't work on Podman.
   - Docker Desktop uses 127.0.0.11 as embedded resolver; Podman uses
     169.254.1.1 / 192.168.127.1. Locking to 127.0.0.11 breaks DNS
     after the firewall fires on any non-Docker-Desktop runtime.
   - Read /etc/resolv.conf at script time and ACCEPT port 53 only to
     the declared IPv4 nameservers. Preserves the claude-code#36907
     intent (no arbitrary DNS resolvers) while supporting any runtime.

3. e2e phase: bind mount empty inside container on Podman macOS.
   - Podman's macOS VM doesn't share /var/folders/ by default.
   - mktemp the project under $HOME/.cache/shield/devcontainer-e2e/
     (overridable via E2E_CACHE_DIR). $HOME is shared on every
     supported macOS container runtime.

4. e2e phase: 'sudo iptables -L OUTPUT' fails — scoped sudoers only
   allows /usr/local/bin/shield-firewall.sh.
   - Replace iptables-introspection check with two behavior probes:
     curl https://api.anthropic.com/ (allowlisted host should respond,
     any HTTP code != 000 = firewall allowed) and curl
     https://example.com/ (non-allowlisted, no response = firewall
     blocked). More meaningful than introspection anyway.
   - Wrap the command-substitution curls with || true so set -e
     doesn't abort on the blocked-host check's non-zero exit.

5. e2e phase: 'fatal: not in a git directory'.
   - Caused by bug 3 (bind mount empty), but also a latent issue if
     .git isn't readable for any reason. Use git config --global to
     decouple from the cwd-must-be-a-repo assumption.
   - Pass --no-gpg-sign on the commit so it doesn't trip on missing
     keys in the e2e container.

After all five fixes the e2e reports 5/5 PASS on Podman macOS.
A git worktree's .git is a FILE that points at metadata living elsewhere
on the host. The devcontainer's workspace bind-mount doesn't include
that metadata path, so any git command inside the container fails with
'fatal: not a git repository: .../.git/worktrees/<name>'.

We're not adding scaffolder support for this case because devcontainers
and worktrees both provide isolation — combining them is rare in real
workflows. Most users open a normal checkout in a devcontainer and use
`git checkout` inside for branch switching.

Document the gotcha + three workarounds (VS Code's Reopen in Container
handles it transparently via --mount-workspace-git-root; bare-CLI users
can pass the flag themselves; simplest is not to combine the two).
@ashwinimanoj ashwinimanoj merged commit fe3bae0 into main May 20, 2026
3 checks passed
@ashwinimanoj ashwinimanoj deleted the worktree-shield-devcontainer-implement branch May 20, 2026 07:45
ashwinimanoj added a commit that referenced this pull request May 21, 2026
…ld implement

Adds the bare-minimum scaffolding so /shield implement (run inside the
existing canonical .devcontainer/) can drive the PM restructure v0 work:

1. .shield.json — declares devcontainer block (existing .devcontainer/
   from PR #44 is the source; this just registers it for the gate).
2. docs/shield/pm-restructure-v0-20260521/plan.json — machine-readable
   plan with 3 milestones, 3 epics, 17 stories. Each story has
   TDD-shaped acceptance criteria targeting the per-dim baseline
   captured in shield/evals/baselines/prd-review-pm.json.
3. docs/shield/pm-restructure-v0-20260521/handoff.md — design context
   so /shield implement has the architectural rationale without
   re-litigating decisions from the planning conversation.

Next: user reopens in devcontainer (existing setup), runs
  claude /login
  claude /shield implement EPIC-1-S1

which drives RED-GREEN-REFACTOR cycles per story.

The .devcontainer/ from PR #44 was NOT modified — init-devcontainer's
regeneration was reverted to preserve the canonical config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ashwinimanoj added a commit that referenced this pull request May 22, 2026
…erage (#49)

* refactor(shield): rename reviewer agents to role names (drop -reviewer)

The agents do more than review — they research, plan, implement, and
review. Naming them "-reviewer" was misleading. Rename to role-based
names that reflect what each agent IS, not just one mode it operates in.

| Old                            | New              |
|--------------------------------|------------------|
| agile-coach-reviewer           | agile-coach      |
| product-manager-reviewer       | product-manager  |
| dx-engineer-reviewer           | dx-engineer      |
| backend-reviewer               | backend-engineer |
| architecture-reviewer          | architect        |
| cost-reviewer                  | finops-analyst   |
| kubernetes-reviewer            | platform-engineer|
| operations-reviewer            | sre              |
| security-reviewer              | security-engineer|
| well-architected-reviewer      | cloud-architect  |

Mechanical changes:
- git mv 10 agent files in shield/agents/
- Update name: frontmatter field in each renamed file
- Sweep all subagent_type references across skills, commands, adapters,
  and examples (36 files updated via sed)
- Bump shield plugin version 2.17.0 -> 2.18.0 in marketplace.json

This is a breaking change for any caller depending on the old subagent_type
strings. Within Shield itself, every caller has been updated. External
consumers (other plugins, custom skills) referencing shield:*-reviewer
must update to the new names.

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

* chore(shield): capture PM agent baseline for v0 restructure merge gate

Records distinct PM finding counts across the 4 PRD-review test fixtures
before the v0 restructure (PM persona agent → focused subagents). Acts
as the merge-gate threshold:

- Total PM findings across 4 fixtures: 62
- Per-fixture: well-formed-standard=4, standard-with-gaps=29,
  lean-with-gaps=24, internal-tool=5
- Gate: post-restructure PM total must be >= 62; no fixture may regress
  more than 10%; stretch goal 1.3x (81+)

Methodology documented in shield/scripts/capture-pm-baseline.sh.
Captures dispatch protocol so any reviewer can re-run and verify.

Commit ordered as commit 1 of the v0 restructure work — baseline must
exist BEFORE structural changes so it cannot be retroactively rewritten.

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

* chore(shield): add .shield.json + v0 implementation handoff for /shield implement

Adds the bare-minimum scaffolding so /shield implement (run inside the
existing canonical .devcontainer/) can drive the PM restructure v0 work:

1. .shield.json — declares devcontainer block (existing .devcontainer/
   from PR #44 is the source; this just registers it for the gate).
2. docs/shield/pm-restructure-v0-20260521/plan.json — machine-readable
   plan with 3 milestones, 3 epics, 17 stories. Each story has
   TDD-shaped acceptance criteria targeting the per-dim baseline
   captured in shield/evals/baselines/prd-review-pm.json.
3. docs/shield/pm-restructure-v0-20260521/handoff.md — design context
   so /shield implement has the architectural rationale without
   re-litigating decisions from the planning conversation.

Next: user reopens in devcontainer (existing setup), runs
  claude /login
  claude /shield implement EPIC-1-S1

which drives RED-GREEN-REFACTOR cycles per story.

The .devcontainer/ from PR #44 was NOT modified — init-devcontainer's
regeneration was reverted to preserve the canonical config.

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

* chore(shield): auto-install shield plugin in devcontainer postCreate

Adds a Shield-plugin install step to .devcontainer/postCreate.sh so that
'claude /shield' commands work inside the container without manual
plugin-install ceremony on every rebuild.

Steps added:
  1. claude /plugin marketplace add <workspace>  (adds this workspace
     as a local Claude Code marketplace; idempotent)
  2. claude /plugin install shield@tesseract     (installs the shield
     plugin from the local marketplace)

Both wrapped in `|| true` so a transient failure (e.g., Claude not yet
authed at first container start) does not block container startup —
the user can re-run manually after 'claude /login'.

Unknown at commit time: whether 'claude /plugin install' requires auth.
For local marketplaces (our case — bind-mounted workspace at
/workspaces/<dir>), it probably does not. For remote marketplaces it
likely does. The `|| true` fallback handles both cases; the user
discovers empirically on next container rebuild.

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

* chore(shield): revert auto-install attempt; print clear post-login steps

Empirical finding from container rebuild test:
  /plugin isn't available in this environment.

'claude /plugin install' invoked as a shell command is NOT supported -
/plugin is a Claude Code REPL slash command that only works inside an
interactive Claude session, not from shell scripts.

Revert the auto-install attempt and instead print explicit, ordered
post-login instructions at the end of postCreate. The user sees:
  1. claude /login                      (shell)
  2. /plugin marketplace add <workspace> (inside claude REPL)
  3. /plugin install shield@tesseract    (inside claude REPL)

No more misleading "install attempt complete" message when the install
didn't actually happen.

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

* feat(shield): add problem-clarity prompt for PRD-Review dim 1 (EPIC-1-S1)

First skill-internal prompt of the pm-restructure-v0 effort. Grades PRD problem
statement, users/personas, and why-now sections against eval points 1a-1d
(named persona, baseline data, why-now, first-person evidence), returning the
dim-block JSON with per-check evidence_quote, gap, and suggestion.

RED-GREEN verified against shield/evals/baselines/prd-review-pm.json:
- standard-with-gaps: 3 findings (1b Important, 1c Warning, 1d Warning) — matches baseline.
- well-formed-standard: 1 finding (1d Warning); 1a-1c graded A — matches baseline.

The 1d criterion was tightened mid-cycle to exclude aggregate references
("customers filed tickets", "X% of users do Y") and PM-authored persona pain
summaries; first-person evidence now requires a verbatim quote or directly
cited research artifact. This was needed to keep well-formed-standard's 1d
finding at Warning rather than letting aggregate evidence inflate it to A.

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

* feat(shield): add scope-discipline prompt for PRD-Review dim 2 (EPIC-1-S2)

Grades PRD non-goals / out-of-scope content against eval points 2a-2c with the
N/A exception for single-purpose internal engineering tools.

RED-GREEN verified:
- standard-with-gaps: 3 dim-2 findings (2a Critical C, 2b Critical F, 2c Warning F) — matches baseline of 3.
- internal-tool: grade N/A with na_reasoning citing single-purpose cron-job scope — matches rubric exception clause.

Output JSON shape mirrors problem-clarity.md but adds the top-level na_reasoning
field (required when grade is N/A; otherwise omitted).

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

* feat(shield): add measurable-success prompt for PRD-Review dim 3 (EPIC-1-S3)

Grades the PRD's Success Metrics section against eval points 3a-3d
(numeric thresholds, leading+lagging coverage, counter-metric, dashboard plan).

RED-GREEN verified against standard-with-gaps: 3 dim-3 findings (3b Critical F,
3c Important F, 3d Warning F) — matches baseline of 3. 3a graded A because the
fixture's "30% within 90 days" target has a concrete threshold.

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

* feat(shield): add raci-and-approvals prompt for PRD-Review dim 7 (EPIC-1-S4)

Grades PRD header + RACI/approvals content against eval points 7a-7d
(named owner, decision-maker, sign-off path, status+last-updated).

RED-GREEN verified against standard-with-gaps: 2 dim-7 findings
(7b Important B — owner present but tie-breaker not explicit;
7c Important F — Legal/Security/Sign-off all "TBD") — matches baseline of 2.

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

* feat(shield): add legal-privacy-compliance prompt for PRD-Review dim 8 (EPIC-1-S5)

Grades data classification, PII handling, regulated-industry sign-off path,
and compliance-driven user flows (8a-8d). Includes N/A exception for
internal-only features that touch no user data.

RED-GREEN verified:
- standard-with-gaps: 4 dim-8 findings (8a/8b Critical F, 8c/8d Important F) — matches baseline of 4.
- internal-tool: grade N/A with na_reasoning quoting the PRD's own "No PII, no user content" statement.

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

* feat(shield): add gtm-customer-comms prompt for PRD-Review dim 9 (EPIC-1-S6)

Grades GTM content against eval points 9a-9d (pricing/packaging, in-app messaging,
CS+sales enablement, beta/early-access plan). Implements the lean-mode exemption
(returns 'informational' immediately) and the internal-only N/A exception.

RED-GREEN verified:
- standard-with-gaps: 4 dim-9 findings (9a/9b Important F, 9c/9d Warning F) — matches baseline of 4.
- lean-with-gaps: returns grade 'informational' with empty evaluation_points.

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

* feat(shield): add support-cx-impact prompt for PRD-Review dim 10 (EPIC-1-S7)

Grades Support / CX content against eval points 10a-10d (day-1 ticket owner,
runbook+escalation, sales enablement, training plan). Mirrors gtm-customer-comms
with the lean-mode exemption and the internal-only N/A exception.

RED-GREEN verified:
- standard-with-gaps: 4 dim-10 findings (10a Critical F, 10b Important F, 10c/10d Warning F) — matches baseline of 4.
- lean-with-gaps: returns grade 'informational' with empty evaluation_points.

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

* feat(shield): add why-now-cost-of-inaction prompt for PRD-Review dim 11 (EPIC-1-S8)

Grades urgency framing against eval points 11a-11c (triggering event, quantified
cost of inaction, sequencing rationale).

RED-GREEN verified against standard-with-gaps: 3 dim-11 findings
(11a Critical F, 11b Important F, 11c Warning F) — matches baseline of 3.

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

* feat(shield): add risks-and-assumptions prompt for PRD-Review dim 12 (EPIC-1-S9)

Grades the Risks/Assumptions section against eval points 12a-12c (risks with
mitigations + owners, validated-vs-unvalidated assumptions, counter-arguments).

RED-GREEN verified against standard-with-gaps: 3 dim-12 findings
(12a Critical F — no mitigations/owners; 12b Important C — validated/unvalidated
not distinguished; 12c Warning F — no dissenting views) — matches baseline of 3.

This is the final dim-prompt commit in EPIC-1 dim-prompt series (S2-S9). EPIC-1-S10
(skill rewrite to dispatch all 9 prompts) and EPIC-1-S11 (merge gate) are next.

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

* feat(shield): rewrite prd-review skill for mixed dispatch (EPIC-1-S10)

dimensions.md — 14-entry dispatch registry (9 PM skill-internal prompts +
5 legacy-persona rows for agile-coach, architect dims 5 & 6, dx-engineer,
finops-analyst). Replaces the persona-keyed table that lived in personas.md
(which is retained pending deletion in EPIC-3-S1).

SKILL.md — Step 5 dispatch now reads dimensions.md and emits 13 parallel
invocations (9 general-purpose Agents loaded with PM prompts + 4 legacy
subagent dispatches). Step 6 aggregation collects dim-blocks from both
envelope shapes and groups by owning persona.

scoring.md — new "Envelope shapes accepted" section documents the per-dim
(new) and per-persona (legacy) envelope shapes; downstream math is identical
since both reduce to a dim-block list.

Each prompt has been individually GREEN-tested against its required fixtures
in EPIC-1-S1..S9. The full M1 merge gate runs in EPIC-1-S11.

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

* feat(shield): run M1 merge gate for prd-review restructure (EPIC-1-S11)

Dispatched all 9 PM dim prompts against the 4 PRD-review test fixtures (36
total dispatches; lean fixture skips dims 9, 10 via informational exemption;
internal-tool returns N/A on dims 2, 8, 9, 10).

Merge gate results (vs shield/evals/baselines/prd-review-pm.json):
- PM total findings: 65 (baseline 62) — PASS (>= 62)
- Per-fixture regression check: PASS (no fixture regresses)
  * well-formed-standard: 4 -> 4 (no change)
  * standard-with-gaps:   29 -> 29 (no change)
  * lean-with-gaps:       24 -> 25 (+1; dim 2 +1 finding)
  * internal-tool:        5 -> 7 (+2; dim 3 +2, dim 7 +1, dim 12 -1)
- Stretch goal (>= 81): NOT MET — informational

The new envelope (per-dim) preserves PM grading depth while decomposing the
single omnibus persona dispatch into 9 focused prompts, meeting the depth
guidance from FLASK ICLR 2024 / Schluntz+Zhang cited in the handoff.

Results captured at shield/evals/baselines/prd-review-pm-postchange.json
for diff against baseline by future restructure work.

This closes M1 — PRD-Review path migrated to per-dim prompts.

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

* feat(shield): add research-framer global subagent (EPIC-2-S1)

Focused authoring subagent that produces the 8-section research framing brief
(PF1-PF8) currently produced by the omnibus product-manager agent in
Research-Framing mode. Returns markdown (not JSON).

Frontmatter carries persona: product-manager for downstream aggregation by
research/SKILL.md (Pattern A from the pm-restructure-v0 handoff).

GREEN-tested by dispatching against an ad-hoc topic (SSE vs WebSockets for an
LLM streaming chat product). Returned brief contained all 8 sections;
PF7 populated with 5 named voices (Hickson, Fette/Melnikov, Kleppmann,
OpenAI API team, Pusher/Ably engineering) with canonical artifacts; PF8
populated as a per-topic-tuned matrix weighting practitioner experience
heavy and peer-reviewed light (correct for greenfield transport selection).

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

* feat(shield): add research-reviewer-narrative global subagent (EPIC-2-S2)

Focused authoring subagent that produces the four-section narrative review
(User Impact Analysis, Scope Recommendation, Prioritization Framework,
Stakeholder Summary) currently produced by the omnibus product-manager agent
in Research-Review mode. Does NOT produce PM1-PM11 grades — those are
dispatched separately by the orchestrator to focused dim subagents
(EPIC-2-S3).

GREEN-tested against an ad-hoc findings doc (SSE vs WebSocket for chat).
Returned all 4 sections, no grades, Stakeholder Summary written in
non-technical language as required.

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

* feat(shield): add PM1-PM11 global dim subagents (EPIC-2-S3)

11 focused subagents in shield/agents/ that decompose the PM persona's PM1-PM11
evaluation framework into one subagent per dimension. Each carries persona:
product-manager frontmatter so plan-review and research dispatchers can roll
them up under the PM persona for summary aggregation.

Subagents (one per PM eval point):
  PM1  user-impact-clarity            (Critical)
  PM2  problem-solution-fit           (Critical)
  PM3  scope-discipline-of-plan       (Important; distinct from PRD dim 2)
  PM4  prioritization-rationale       (Important)
  PM5  stakeholder-communicability    (Important)
  PM6  market-competitive-awareness   (Warning)
  PM7  adoption-rollout-risk          (Important)
  PM8  success-metrics-defined        (Important; distinct from PRD dim 3)
  PM9  reversibility-exit-cost        (Warning)
  PM10 business-value-alignment       (Critical)
  PM11 framing-coverage-honored       (Important; Research-Review only — N/A if no framing brief)

GREEN-tested by dispatching all 11 in parallel against the standard-with-gaps
PRD fixture (used as an ad-hoc proxy plan since no plan-review fixtures exist
per the handoff). Grade distribution: 3 B, 2 C, 2 D, 3 F, 1 N/A — reasonable
spread; not all A, not all F. PM11 correctly returned N/A with reasoning
because no framing brief was supplied.

Each subagent returns a single-check JSON object (id, name, persona, grade,
severity, evidence_quote, gap, suggestion) — a simpler shape than the
multi-check PRD prompts since each grades exactly one PM eval point.

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

* feat(shield): plan-review dispatches PM1-PM10 globals (EPIC-2-S4)

dimensions.md — new dispatch registry for plan-review. PM persona now resolves
to 10 parallel global subagents (shield:user-impact-clarity ... shield:business-
value-alignment). Legacy personas (architect, agile-coach, dx-engineer, finops-
analyst, sre, platform-engineer, backend-engineer, security-engineer) retain
their existing dispatch — they're not decomposed in v0.

SKILL.md — Dispatch section now branches: PM persona triggers a 10-call PM dim
fan-out; other personas use the existing single-subagent path. Collection &
Scoring step adds an explicit "group PM dim results under the PM persona" step
that synthesizes a PM persona block from the 10 single-check returns, so
downstream summary templates keep working.

personas.md — PM row updated to point at the PM1-PM10 decomposition; selection
flowchart is unchanged.

End-to-end PM1-PM10 verification was already captured in EPIC-2-S3 (parallel
dispatch against an ad-hoc doc returned 10 valid JSON blocks with a reasonable
grade distribution — 3 B, 2 C, 2 D, 3 F). The aggregation logic mirrors
prd-review/scoring.md's per-persona rollup which is already merged.

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

* feat(shield): research skill dispatches new framer + reviewer + PM1-PM11 (EPIC-2-S5)

Phase 2 framing step now dispatches shield:research-framer (replaces the legacy
shield:product-manager Research-Framing mode dispatch). Same PF1-PF8 markdown
output; same downstream use as research-stream prompt context.

Phase 2 review step now dispatches 12 parallel subagents in place of the single
shield:product-manager Research-Review call:
  - shield:research-reviewer-narrative (4-section markdown narrative)
  - shield:user-impact-clarity (PM1) ... shield:business-value-alignment (PM10)
  - shield:framing-coverage-honored (PM11) — receives both findings and framing brief paths

Orchestrator merges narrative + PM1-PM11 scorecard back into the legacy
"## Product Lens" section shape so downstream findings.md templates and
references to the section keep working.

Both the new Phase 2 entry points and the legacy --phase2-only mode were
updated.

GREEN-tested by the EPIC-2-S1 and EPIC-2-S2 dispatch verifications (research-
framer produced full PF1-PF8 brief; research-reviewer-narrative produced 4
non-technical narrative sections) plus the EPIC-2-S3 batch (PM1-PM11 returned
valid single-check JSON, including PM11 N/A path when no framing brief).

Closes M2 — Plan-Review and Research paths migrated to global subagents.

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

* feat(shield): retire legacy PM persona agent (EPIC-3-S1)

Closes M3. Deletes:
- shield/agents/product-manager.md (work redistributed across research-framer,
  research-reviewer-narrative, and 11 PM dim subagents created in EPIC-2)
- shield/skills/general/prd-review/personas.md (replaced by dimensions.md
  in EPIC-1-S10)

Migrates remaining live dispatch sites away from shield:product-manager:
- shield/skills/general/milestone-coverage/{SKILL,templates}.md — PM-lens
  dispatch now uses general-purpose Agent with the PM-lens preamble inlined
  in templates.md. (Milestone-scaffolding is a generative task that doesn't
  fit any single PM dim subagent; a focused milestone-scaffolder subagent
  is deferred to v1.)
- shield/skills/general/pm-analysis/SKILL.md — routing wrapper now branches
  by mode and dispatches the appropriate focused subagent(s): research-
  framing → research-framer; research-review → research-reviewer-narrative
  + PM1-PM10 + PM11; plan-review / standalone → PM1-PM10.

Updates the /prd-review command doc to describe the new 13-call dispatch
shape (9 skill-internal PM prompts + 4 legacy persona dispatches) in place
of the 5-persona shape.

Verified: grep -r 'subagent_type.*product-manager' shield/ returns zero
hits. Remaining `shield:product-manager` mentions are historical/explanatory
notes ("replaces the legacy ...") and the pre-restructure baseline JSON
(captured at commit 9726ff5 for diffing — must not be edited).

This closes EPIC-3 and the pm-restructure-v0 plan in its entirety.

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

* test(shield): add eval coverage for pm-restructure-v0 + make evals mandatory

Closes the eval-coverage gap flagged in the pm-restructure-v0 PR discussion.
Three additions plus a CLAUDE.md policy update:

1. Revive the PM framing snapshot eval as research-framer
   - Rename expected/product-manager-reviewer-research-framing.yaml ->
     expected/research-framer-event-sourcing.yaml; update agent: field;
     the PF7/PF8 assertions are unchanged because the new shield:research-
     framer agent produces the same 8-section output shape.
   - Capture results/research-framer-event-sourcing.txt by dispatching the
     new agent against the event-sourcing fintech topic.
   - Verified: all 11 must_find + 3 should_find + 1 must_not_false_positive
     assertions PASS via ./shield/evals/run-evals.sh.

2. PRD-Review merge-gate script
   - shield/evals/run-prd-review-merge-gate.sh — bash driver that fans out
     the 36 dispatches (9 PRD dim prompts x 4 fixtures) via `claude --print`
     with optional --jobs N parallelism and a --dry-run mode.
   - shield/evals/_score_merge_gate.py — parses returned dim-blocks
     (permissive: handles fenced JSON, leading prose, etc.), counts findings,
     writes a fresh prd-review-pm-postchange.json, exits 1 on regression.
   - Turns the one-shot EPIC-1-S11 in-conversation exercise into a CI-
     runnable regression check.

3. PM1-PM10 snapshot evals
   - 10 new expected/<subagent>-standard-with-gaps.yaml files, each asserting
     id/name/persona/severity/grade/evidence_quote structural fields plus
     dim-specific should_find criteria (e.g. PM2 cites solution-first
     ordering; PM6 cites alternatives/buy-vs-build; PM9 cites reversibility).
   - 10 captured results in results/. All 10 evals PASS today.

CLAUDE.md — new "Eval coverage — MANDATORY for plugin updates" section under
Skill Quality. Spells out the three eval frameworks (snapshot, end-to-end,
custom merge-gate script), the definition-of-done for plugin-change PRs, and
the rationale (the in-conversation pm-restructure-v0 merge gate was strong
PR-time signal but not reproducible afterward). Future plugin-change PRs must
land an executable eval alongside the change.

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

* chore(devcontainer): install pre-commit in postCreate

Adds pre-commit to the system Python deps installed by postCreate.sh and
runs `pre-commit install --install-hooks` to wire up the repo's
.pre-commit-config.yaml hooks (whitespace, YAML/JSON validity, bash syntax,
eval-format check) on devcontainer creation.

Today the hooks were configured but pre-commit wasn't installed in the
container, so commits silently skipped the checks. This closes that gap.

Install location is postCreate (not the Dockerfile) to match the existing
pattern for jsonschema/pyyaml, keep the image lean, and ensure hook
installation happens after the workspace is mounted (so `pre-commit install`
can wire up .git/hooks for the cloned repo).

Credentials persistence: NOT changed. The researched pattern in
docs/shield/devcontainer-implement-20260518/research/1-claude-implement-
isolation/findings.md (line 13) keeps `claude-config-${devcontainerId}`
as the volume name — ${devcontainerId} is stable across rebuilds per the
Dev Containers spec, and the research explicitly rejected bind-mounting
host credentials (line 21, citing Anthropic's own warning about host-secret
mounts). Credentials are already meant to be one-time-per-project.

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

* fix(shield): drop unused mode assertion from eval YAML schema check

shield/tests/run-all.sh asserted 'mode' was present in every
expected/*.yaml, but no runner consumed it — not run-evals.sh,
run-eval.sh, or _parse_eval.py. The 10 PM snapshot evals added in
eaff144 omitted it (since it's documentation-only), which broke the
shield-suite job on PR #49.

Drop the unused assertion rather than back-fill ceremonial metadata.

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

* test(shield): close eval coverage gaps — rename cost/security yamls, capture results, add PM11

Three gaps closed:

1. Renamed expected/cost-reviewer-terraform.yaml -> finops-analyst-terraform.yaml
   and security-reviewer-terraform.yaml -> security-engineer-terraform.yaml so
   eval names track the agent renames in 9726ff5 (cost-reviewer -> finops-analyst,
   security-reviewer -> security-engineer). Updated agent: field inside each.

2. Captured the previously-SKIPped results:
   - results/finops-analyst-terraform.txt
   - results/security-engineer-terraform.txt
   Both produced by dispatching the role-renamed agents against
   inputs/insecure-vpc-module/. run-evals.sh now grades these instead of
   skipping with "No output file at ...".

3. Added PM11 eval (framing-coverage-honored) — promised in eaff144 as "PM1-PM11"
   but only PM1-PM10 shipped.
   - New fixture pair: inputs/framing-coverage-gaps/{framing-brief,findings}.md
     where the findings doc deliberately omits direct body quotes for 2 PF7
     voices (Fowler, Vernon — References only) and 2 PF8 required source types
     (regulatory, vendor docs).
   - Captured results/framing-coverage-honored-coverage-gaps.txt — agent
     correctly grades D with the expected missing voices/source types.
   - expected/framing-coverage-honored-coverage-gaps.yaml asserts JSON shape,
     non-A grade, that Fowler/Vernon/regulatory/vendor appear as missing,
     and that Greg Young (who IS quoted) is NOT flagged as missing.

Also tightened the valid-https false-positive regex in security-engineer-terraform.yaml
from loose `port 443.*flag` (which false-matches "is not flagged" in the new
capture's prose) to a finding-shape match (`#### [S<n>] ... 443` or
`Severity: Critical|High|Important` near 443).

Verification: ./shield/evals/run-evals.sh = 132 PASS / 0 FAIL / 0 SKIP across
all 13 evals. ./shield/tests/run-all.sh = 30/30.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant