Install
openclaw skills install @chpomob/adversarial-specAdversarial specification writer. Takes a brief (from grill-me or user) and produces a structured spec.md with YAML frontmatter, requirements, acceptance criteria, and target files. Git-aware pipeline: each run on its own branch, squash-merge on approval.
openclaw skills install @chpomob/adversarial-specBrief → structured specification. Two-role adversarial pipeline that transforms a vague idea (from grill-me or direct input) into a formal spec.md with YAML frontmatter, requirements, acceptance criteria, and target file descriptions.
Requires the adversarial-common sibling repo (shared engine). One-line install:
curl -fsSL https://raw.githubusercontent.com/chpomob/adversarial-spec/main/scripts/install.sh | bash
or, from an existing checkout:
bash scripts/install.sh
Both place adversarial-spec and adversarial-common side by side under ~/.hermes/skills (override the target with $1 or $HERMES_HOME).
PHASE 0 ──→ GIT SETUP (branch, stash, init)
PHASE 1 ──→ WRITE (spec-writer reads brief, writes spec.md)
PHASE 2 ──→ CHALLENGE (spec-challenger critiques for gaps/contradictions)
PHASE 3 ──→ REVISE (spec-writer amends spec.md per findings)
PHASE 4 ──→ VERIFY (spec-challenger checks findings resolved)
MERGE ──→ squash-merge (APPROVED) or [REJECTED] commit
python3 scripts/adversarial_spec.py \
--brief <file> # brief file (default: stdin)
--dev-cmd <cmd> # default: pi --provider zai --model glm-5.2
--review-cmd <cmd> # default: pi --provider deepseek --model deepseek-v4-pro
--workdir <dir> # default: .
--max-loops <N> # default: 2
--feature <name> # default: from brief filename
--timeout <N> # default: 600
--out <dir> # default: .adversarial-spec
--provider-config <path> # external provider config (default: ~/.config/adversarial/providers.yaml)
--no-merge
Run these BEFORE writing the brief, especially when contributing to an upstream project:
feat/, fix/, docs/), commit message format (Conventional Commits), PR template fields, and test requirements. Incorporate these into the spec's acceptance criteria.upstream/main (not your fork's main): git checkout upstream/main -b feat/my-feature. For work on your own fork, use your fork's main. The parent branch determines what the pipeline's squash-merge targets.adversarial-code-loop (partial-merge-gap-fill) before writing the brief. The spec should target gaps, not re-implement what's already merged.--workdir is a live install.spec.md with YAML frontmatter:
---
name: "feature-name"
version: "1.0"
author: "adversarial-spec"
status: "draft"
targets:
- file: path/to/file.rs
description: "What changes in this file"
---
# Feature title
## Problem
What problem does this solve?
## Requirements
- Bullet list of functional requirements
## Acceptance criteria
1. Each requirement has at least one testable criterion
Loaded from adversarial-common/personas/:
| Code | Meaning |
|---|---|
| 0 | APPROVED — spec squash-merged |
| 1 | Infrastructure failure |
| 2 | Usage error |
| 3 | REJECT |
| 5 | CONTEXT_BLOCKED — CI/preflight context gate rejected the brief |
All pipeline-internal text (spec, commit messages, findings, JSON) is English. User-facing conversation stays in the conversation's language. Write the brief in English unless the user explicitly instructs otherwise. A spec written in French or other non-English languages will break later pipeline stages (plan, code loop) that expect English section headings and identifiers.
Provider-mode precedence is --provider-config (or the
ADVERSARIAL_PROVIDER_CONFIG/implicit default registry) > explicit role flags >
hardcoded fallback commands. A selected registry supplies the writer and challenger
chains and enables quota-aware selection:
python3 scripts/adversarial_spec.py \
--brief brief.md \
--provider-config ~/.config/adversarial/providers.yaml
The provider config is a YAML file where each role lists commands in preference order.
The pipeline checks real-time quota before each phase and picks the first available command.
Within registry mode, a non-empty --dev-cmd or --review-cmd is an explicit,
quota-bypassing override for that role. Without a registry, each role resolves its
command from the explicit flag, then ASPEC_DEV_CMD/ASPEC_REVIEW_CMD, then these
hardcoded fallbacks:
pi --provider zai --model glm-5.2pi --provider deepseek --model deepseek-v4-proThis means:
spec.md from the phase working directory.
It includes only bounded metadata such as the branch-point SHA and output
schema; the specification itself remains on disk. Embedding contradicts the
adversarial design principle that context lives on disk / in git.spec.md from
the current working directory. Inspect the cumulative change from the
branch-point SHA. Output ONLY valid JSON."scripts/phases/phase_challenge.py previously
concatenated the full spec text into the prompt (f"--- spec.md ---\n{spec_text}")
despite the SKILL.md saying not to. Patched to an under-1KB instruction with
no embedding; its size is independent of the spec length. The model reads
spec.md from disk via --cwd. See adversarial-plan pitfall about CHALLENGE
prompt reduction for the sibling fix.--cwd points to the
workdir so the model can read the files. Without --cwd, the tmux session
starts in the wrong directory and the model cannot find plan.md/spec.md.--yolo. The --dangerously-skip-permissions
behaviour is the DEFAULT (set danger=True in the wrapper, no flag needed). Passing
--yolo causes argparse exit code 2. Just omit it: --model sonnet --timeout 900
is sufficient. Validated on the v1 wrapper at /home/chpo/claude-tmux-wrapper/claude-tmux.py.cmd: field in
target file descriptions should describe the change ("add YAML config loader",
"expose ProviderConfig dataclass"), not the tool that makes it.R1: or R1- (colon or hyphen after the ID). R1 (P0) — or R1 — with em dash will NOT match — shows "Requirements section has no identifiable requirement ids". Always write requirements as - R1: description or - R1 - description. The em dash — is not in the regex lookahead set.AC1 (R1): at the start of a bullet line. No space between ) and :. Wrong: - AC1 (R1) : text. Right: - AC1 (R1): text.A full adversarial pre-publication review surfaced 20 findings (1 blocker, 8 major, 6 minor, 5 nit). Status below is reconciled against current code and tests; items marked "fixed" carry a test pointer, the rest remain accurate as open issues.
pipeline_base.setup_git records the stash id onto the shared state the instant git stash push succeeds, before branch creation even runs, so a later failure within the same setup_git call still leaves restore_git able to pop it back onto the parent branch. See tests/test_p14_integration.py::test_setup_git_partial_failure_leaves_recoverable_state.EXIT_INFRA and rewrites the persisted final.json verdict to INFRA, so a merge failure can never read as APPROVED. See tests/test_orchestrator.py::test_finish_merge_failure_returns_infra_and_records_error and ::test_verdict_not_approved_on_merge_failure.phase_spec._REQUIRED_SCALARS now requires name, version, and author; a non-empty targets list (each entry needing file/description) and full requirement/acceptance-criteria coverage are enforced too. See tests/test_phases.py::test_validate_spec_missing_name and ::test_validate_spec_requires_all_frontmatter_fields.commit_all still stages everything (git add -A), not just spec.md; a prompt-injected writer could modify source files. Still open.max_loops re-running the same contradictory REJECT-with-all-settled findings — is unfixed and has no test coverage. Still open.pipeline_base.phase_failure only logs to ISSUES.md; it never writes or clears final.json, so a stale final.json from a prior run survives a phase crash untouched. Still open.--out paths can be committed (B8) — fixed. _ensure_out_gitignored anchors a normalized .gitignore entry at the nearest tracked ancestor of --out before the first commit — covering relative paths, ./-prefixed paths, paths outside workdir but inside the enclosing repo, and paths that resolve to the repo root itself (e.g. --out ., where the naive ./ pattern would ignore nothing). See tests/test_orchestrator.py::test_custom_out_dir_is_gitignored, ::test_relative_out_with_dot_prefix_is_gitignored, ::test_outside_workdir_but_inside_repo_is_gitignored, ::test_custom_out_dot_dir_is_gitignored, ::test_custom_out_abs_path_inside_repo_is_gitignored, and ::test_custom_out_abs_repo_root_outside_workdir_is_gitignored._retrospective/ISSUES.md's header now correctly states that pipeline failures go to <out_dir>/ISSUES.md, not this file.