claude-video-effects
51 video effects for Claude Code: drop in a talking-head clip, type /effects orbit. Face tracking, behind-your-head layers. By @vibha.in.progress
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
devAgent is a Claude Code plugin that runs development work through a fixed,
auditable issue workflow and keeps all of its state on disk — so you can switch
between issues, or hand one to a fresh session, without losing context. It
provides 58 slash commands driving a 24-step workflow (21 mandatory steps + 3 optional), works against
GitHub, GitLab, and JIRA issue trackers and GitHub and GitLab code forges (JIRA
is tracker-only; pair it with either forge), and layers capture + issue red-team,
revision, WBS, and status-report subsystems on top of the core loop.檢視完整 README ↗devAgent is a Claude Code plugin that runs development work through a fixed, auditable issue workflow and keeps all of its state on disk — so you can switch between issues, or hand one to a fresh session, without losing context. It provides 59 slash commands driving a 25-step workflow (22 mandatory steps + 3 optional), works against GitHub, GitLab, and JIRA issue trackers and GitHub and GitLab code forges (JIRA is tracker-only; pair it with either forge), and layers capture + issue red-team, revision, WBS, and status-report subsystems on top of the core loop.
See docs/specs/2026-05-19-devagent-plugin-design.md for the design spec and
docs/plans/ for the incremental build history. New to devAgent? Start with
the onboarding site at https://mtibbits.github.io/devagent/ (source:
docs-site/, beginning at docs-site/index.md).
devAgent is a Claude Code plugin. Add its marketplace, then install the plugin:
# 1. Add the marketplace (the devagent repo)
claude plugin marketplace add mtibbits/devagent
# 2. Install the plugin
claude plugin install devagent@devagent
The install reports two userConfig options not yet set (devdoc_root,
default_project); they only seed the /devagent:init interview and can be
left unset.
Recommended — superpowers. When the
superpowers
plugin is installed, devAgent's implement and review steps — and draft on
its inline (non-dispatched) path — use its skills. When it is absent, those
steps fall back to compact built-in paths and print a one-line install
nudge; devAgent itself always loads either way, and /devagent:doctor
warns when the plugin is missing or disabled (#541: recommended, never
hard-required; plugin dependencies do not auto-install, so these are
separate, optional commands):
# Not configured on a fresh install (measured on 2.1.260; not version-specific)
claude plugin marketplace add anthropics/claude-plugins-official
claude plugin install superpowers@claude-plugins-official
Installs from before #541: run claude plugin update devagent@devagent
once — the old manifest declared superpowers as a hard dependency, and a
cached copy of it keeps devAgent disabled until updated.
Updates. Releases are tagged semver versions (.claude-plugin/plugin.json
carries the version), so claude plugin update devagent@devagent picks up the
next release without an uninstall + reinstall. Merges to master between
releases do not reach an installed plugin; see
Versioning & releases.
Prerequisites. The workflow scripts need bash ≥ 4.4 (the resolver libs use
namerefs), python3 ≥ 3.11 (or 3.8–3.10 plus tomli), jq, and git. Where
run-suite.sh measures a tree that has an origin, it also needs coreutils timeout
on PATH and git ≥ 2.29, for its bounded freshness check (#660). A project with
analyze = "shellcheck" also needs shellcheck on PATH (step 13 stops without it);
devAgent is tested against shellcheck 0.9.0 or newer, and nothing checks the version
at run time — commands/analyze.md says where it is recorded. The backend and auth
scripts additionally call gh (GitHub), glab or curl (GitLab), or curl (JIRA),
as appropriate for your backend.
Supported platforms. devAgent is developed and tested on Linux, including
WSL on Windows; CI runs Linux only. macOS is currently untested: the scripts
assume GNU coreutils (stat -c, GNU sed -i, readlink -f). Native Git Bash on
Windows can run the plugin but is not a supported environment for the test suite
(see Running the test suite).
Claude Code version. Developed and verified against Claude Code 2.1.223;
earlier versions are untested. Workflow-script calls auto-approve: as of 2.1.223,
${CLAUDE_PLUGIN_ROOT} substitutes inside allowed-tools, and devAgent ships the
probe-verified quoted grant form
Bash(bash "${CLAUDE_PLUGIN_ROOT}/scripts/*"), Bash(bash "${CLAUDE_PLUGIN_ROOT}/scripts/*" *)
that matches the quoted script invocations the command bodies emit (#548 decision doc). Since #584 the same grant sits on every core-* skill whose body makes a script call, so an --auto chain does not stall on a step-internal call either (the four judgment-only core skills make none and carry no grant).
On older Claude Code (measured at 2.1.211 in #533, where the substitution does not fire;
older versions are untested but assumed the same, and the exact landing version between
2.1.211 and 2.1.223 is unmeasured, so intermediate versions may or may not prompt)
every workflow script call prompts; approve-and-remember there, or upgrade. The model can
occasionally retype a command in a form that misses the literal prefix match (e.g. a
different drive-letter case) — that falls back to a one-off prompt, never to a wider
grant. Never widen to bare Bash.
Every issue gets a checklist.md that tracks its progress through these steps.
Run them one at a time with /devagent:next (which advances to the next
unmarked step), or invoke any step command directly. Steps are numbered 0–24 in
execution order — 25 numbered steps, 22 of them mandatory; the optional steps
are off by default: 1 research and 3 spike opt in per issue via the
## Workflow flags block, and 19 mergetoall opts in per project by
configuring all_prs_branch. A normal issue's checklist reads top-to-bottom
with those rows pre-marked [-] (skipped):
0 pull · 1 research (optional, flagged) · 2 draft · 3 spike (optional, flagged) · 4 scope · 5 improve · 6 prune · 7 tighten8 branch · 9 implement · 10 quality · 11 document · 12 commit · 13 analyze14 draftmr · 15 review · 16 redmr · 17 preship · 18 ship19 mergetoall (optional, all_prs_branch) · 20 mergemr · 21 updatewbs · 22 impact · 23 lessonslearned · 24 cleanup20 mergemr merges the PR only when the project sets permissions.merge_mr = true, and only at the head the last passing preship verified; otherwise it
marks its row [?] until the PR is merged elsewhere. /devagent:next
re-checks a [?] mergemr row on every run and, while the PR is open, prints a
WAITING line and stops; nothing polls, and cleanup waits for the row
(/devagent:mergemr lists the escapes).
/devagent:comments fetches reviewer feedback, then /devagent:revise opens a
new revision pass that re-runs from draft — the revision's first pending step
(revise refuses until comments has run; it does not fetch them itself); /devagent:where and /devagent:catchup rehydrate an issue's state at
any point.
All commands live under the /devagent: namespace.
| Command | Purpose |
|---|---|
init | Interactive bootstrap of a new project under ~/.claude/devagent/ |
doctor | Validate config, state, paths, and template resolution |
auth | Manage PAT / SSH-key lifecycle for a project's tracker and forge backends |
template | Inspect resolved artifact templates (list / show) |
| Command | Purpose |
|---|---|
pull | Fetch an issue (origin or fork) and scaffold its workflow directory |
where / catchup | Show / one-screen-rehydrate the active issue's state |
status | Multi-project dashboard of active issues, STUCK, and parked |
history / grep | Chronological log / search across per-issue artifacts |
depends | Record or list issue dependencies |
park / resume / switch | Park, reactivate, or swap the active issue in one step |
use | Deliberately switch the active project (the one arg-driven pointer writer) |
sync | Async merge detection — fires on_merge for issues merged outside this session |
stuck / unstuck | Mark the current step stuck (with a reason) / clear it and resume |
| Command | Purpose |
|---|---|
capture | Draft a pre-issue or epic capture under <devdoc>/Captures/<slug>/ |
redissue | Run the issue red-team prompt against a capture draft |
scaffold | Bin an epic capture into child issue drafts |
file | File a capture draft as a tracker issue (origin or fork) |
crrf | Autonomous capture → red-team → revise → file for one discussed topic |
reap | Harvest follow-up candidates from completed work into a capture draft |
| Command | Purpose |
|---|---|
wbs | Work-breakdown-structure authoring and rendering (init / update / order / show) |
statusreport | Generate a per-project status report and advance the report pin |
Low-level building blocks the workflow commands use; you rarely call them
directly. checklist-init, checklist-mark, checklist-advance,
checklist-log, checklist-stuck, checklist-unstuck.
The 25 numbered step commands above (22 mandatory + the optional
1 research,3 spike, and19 mergetoallsteps), plusnext/revise/comments, together with the tables in this section, are the full set of 59 slash commands (56 commands + 3 user-invocable skills —next,capture,ship; #452).
Two sessions on DIFFERENT projects are safe as of #282 — pin each session via
"env": { "DEVAGENT_ACTIVE_PROJECT": "<project>" } in that directory's Claude
Code settings.local.json; the shared pointer is only written when actually
consulted. Same-project sessions are isolated per issue as of #240/#303 — each
issue's state lives under [context.<issue>], so keys can't launder between
issues; the only residual is the last-writer-wins pick of the shared
active_issue scalar, which a per-session DEVAGENT_ACTIVE_ISSUE pin avoids.
To switch the active project deliberately (rather than via the per-session env
pin), run /devagent:use <project> — the one arg-driven writer of the shared
pointer; hand-editing _active.toml is the fallback.
Details: skills/next/references/concurrency.md.
On a shared queue, a trust policy (policy.toml, #711) names the trusted
repo scopes, the accounts the worker runs as, and the accounts that may
approve, report or review. It lives at a fixed location outside anything the
agent writes — /etc/devagent/trust/ or /c/ProgramData/devagent/trust/
(system, wins) or $HOME/.config/devagent/trust/ (user, the baseline). In
scope, every entry point checks the worker's identity before it fetches or
writes, and the policy alone decides the permission gates. A refusal exits 9
(fix the host) or 10 (retry later). With no policy nothing changes.
/devagent:doctor reports the policy, its digest and which hardenings are met.
Spec §3.8 (policy) and §8.2 (gates under trust).
The auth subsystem manages personal access tokens (PATs) and SSH keypairs for each project's tracker and forge backends.
~/.claude/devagent/secrets/<project>.<backend>.pat (mode 600)~/.claude/devagent/secrets/<project>.ssh (symlink to ~/.ssh/<key>)v1 ships file-based storage only. A future --keyring mode will swap
in libsecret (Linux), Keychain (macOS), or wincred (Windows) without
changing the caller contract; see scripts/lib/secrets.sh for the
abstraction seam.
/devagent:auth <verb> [project] [backend] [args...] dispatches to
scripts/auth/<backend>.sh. See commands/auth.md.
Every backend script supports:
| Verb | Purpose |
|---|---|
| create | Interactive: open browser to PAT page, paste, validate, store |
| store | Non-interactive ingest: read token from a file |
| rotate | Atomic: create new, swap into place, destroy old |
| destroy | shred -u then unlink |
| status | Print presence, file mode/size/mtime, scopes when known (never the token) |
| exec | Set the env var (GH_TOKEN / GITLAB_TOKEN / JIRA_TOKEN) |
| and exec the trailing command. Token never on argv. |
For SSH (scripts/auth/ssh.sh), store and exec are not exposed
(SSH keys are presented to children via ssh-agent or ~/.ssh/config,
not env vars), but create, destroy, rotate, and status work
identically.
scripts/auth/custom.sh ships as a stub that exits 64 for every verb
except status. To implement a custom backend, copy that file and
fill in each verb following the contract above. The verb dispatch and
argument parsing are provided by scripts/lib/auth_common.sh; the
storage layer is scripts/lib/secrets.sh. Both are stable v1 APIs.
/devagent:doctor calls scripts/lib/doctor_auth.sh check <project> <backend>... and reports OK / WARN / SKIP / MISSING / ERROR per backend
plus the secrets-dir mode. The script never prints the token value
and always exits 0; doctor aggregates statuses across hooks.
tests/auth_security.bats pins these invariants:
exec never puts the token on argv (verified via ps -wwo args=)destroy leaves no on-disk traces of the tokenDo not write code that bypasses scripts/lib/secrets.sh for read or
write; direct file I/O against the secrets directory is a contract
violation and will break the future --keyring migration.
devAgent supports four issue backends out of the box: github,
gitlab, jira, and a custom stub. The custom backend is a
starting point for implementing a tracker that devAgent does not ship
(in-house Jira-likes, Redmine, Bugzilla, ServiceNow, etc.).
To add a new backend:
Copy scripts/issue/custom.sh to scripts/issue/<yourname>.sh and
replace each bc_die_not_implemented call with a real
implementation. Same for scripts/code/custom.sh if you also host
code.
Set backend = "<yourname>" under [project.<project>.issue_source]
(and optionally [project.<project>.code_source]) in
~/.claude/devagent/config.toml.
Confirm contract compliance by running the contract suite against
your backend. The harness is parametrized in
tests/backend-<name>.bats. Write a tests/backend-<yourname>.bats
modeled on tests/backend-gitlab.bats plus fixtures under
tests/fixtures/<yourname>/. Then:
LC_ALL=C.UTF-8 bats tests/backend-<yourname>.bats
(The locale pin matters for any direct bats invocation — see
Running the test suite.)
All tests must pass before the dispatcher (pull.sh, ship.sh,
etc.) will work reliably with your backend.
Every issue backend MUST implement fetch, create, transition, state
and comment-list, and MAY implement state-list, issue-list,
state-exists, state-class, events, comment-records and whoami
(spec §9.1):
| Verb | Args | Output / exit |
|---|---|---|
fetch | <repo> <num> | markdown to stdout |
create | <repo> <title> <body-file> [--label X]… | new num to stdout |
transition | <repo> <num> <semantic-stage> [--project <name>] | exit 0 landed, already there or no mapping; non-zero = did not land (the callers always append --project; a custom backend must accept it and may ignore it) |
state | <repo> <num> | backend state |
comment-list | <repo> <num> | markdown to stdout |
state-list | <repo> | <id>\t<class> lines (optional; read by wbs update; absent = exit 78, or 2 for an unknown verb) |
issue-list | <repo> | one JSON record per issue (optional; read by wbs init --from-tracker and --merge; absent = exit 78, or 2) |
state-exists | <repo> <state-name> | exit 0 present, 4 absent, else undetermined (optional; read by doctor; absent = exit 78, or 2) |
state-class | <repo> <num> | one <class>\t<reason> line (optional; read by mergemr for a dependency; absent = exit 78, or 2) |
events | <repo> <num>, or --kinds <repo> | one six-field record per change, or one <kind>\t<coverage> line per kind (optional; no caller yet; absent = exit 78, or 2; spec §9.3.1) |
comment-records | <repo> <num> | one six-field record per comment (optional; no caller yet; absent = exit 78, or 2) |
whoami | <repo> | the credential's account id on the repo's host (optional; no caller yet; absent = exit 78, or 2) |
Every code backend MUST implement push-branch, create-mr, mr-state,
mr-comments and merge-mr, and MAY implement issue-mrs, mr-checks and
mr-view (spec §9.2):
| Verb | Args | Output / exit |
|---|---|---|
push-branch | <remote> <branch> | exit 0 |
create-mr | <repo> <title> <body-file> <head> <base> [--draft] | MR URL to stdout |
mr-state | <mr-url> | `open |
mr-comments | <mr-url> | markdown to stdout |
merge-mr | ` [--method squash | merge |
issue-mrs | <repo> <id>... | issue/mr lines per id (optional; read by wbs update; absent = exit 78, or 2 for an unknown verb) |
mr-checks | <mr-url> | state, head and one line per CI check (optional; read by next and mergetoall, #782) |
mr-view | <mr-url> | the mr-checks lines plus the merge facts (optional; read by mergemr, #885) |
Exit-code conventions across all backends:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | generic failure |
| 2 | usage error (bad/missing args, unknown verb) — and only that: a transition with no stage mapping exits 0 with a warning |
| 3 | auth failure (HTTP 401/403); the github issue backend goes through gh, which reports 401/403 as its own exit 1, so it answers 1 |
| 4 | not found (HTTP 404); for transition, the mapped label or status is not on the tracker (devagent never creates it) |
| 5 | malformed, truncated or non-ending response (a 2xx page that is not the expected JSON, a list cut at a backend's limit, or a paging walk that does not end) |
| 78 | not implemented (EX_CONFIG; reserved for stubs) |
The markdown shapes produced by fetch, comment-list and mr-comments
are fixed across backends (spec §9.3) — downstream code is backend-agnostic.
mr-comments entries may add an optional · review: <STATE> or
· <path>:<line> suffix; the github backend includes review summaries and
inline code comments this way.
The canonical runner is scripts/run-suite.sh, which runs bats and pytest with
the hermetic environment baked in and writes the provenance artifact
<issue-dir>/analysis/<date>-suite-count.txt that preship-evidence.sh reads:
bash "$CLAUDE_PLUGIN_ROOT/scripts/run-suite.sh" <project> <Issue-N>
$CLAUDE_PLUGIN_ROOT is set inside a Claude Code session; in a plain shell use
the path to your clone. The runner needs <project> configured in devAgent and
the issue whose directory receives the artifact: pass it as <Issue-N>, or pin
DEVAGENT_ACTIVE_ISSUE for the session (the argument wins when both are set).
With neither it refuses with ISSUE UNSTATED and writes nothing, rather than take
the issue from shared state another session can move (#659). It also refuses,
writing nothing, a checkout that is not on a branch (DETACHED HEAD). It refuses a
checkout that does not contain its origin's tip of that branch, asked under a
per-call bound of DEVAGENT_FETCH_TIMEOUT seconds (default 30) (BEHIND ORIGIN). The
artifact records the branch: it measured and what origin said (upstream:)
(#660). It also records where it ran, in a platform: line: the OS (uname -s), the
kernel release (uname -r), the measured tree's filesystem type, and whether the
chmod probe found chmod to be a no-op there (#654). With only a
clone, run LC_ALL=C.UTF-8 bats tests/ and python3 -m pytest tests/ directly
— see CONTRIBUTING.md.
The suite has three environmental requirements. run-suite.sh enforces the first two
by refusing to write an artifact at all, rather than producing one it cannot stand
behind — the first only for a suite that references file modes, as this repo's does
(#600, below). The third it RECORDS, and preship-evidence.sh refuses on the recorded value.
A minimal container running these tests must provide all three. A fourth fact is
recorded but not required: the platform the suite ran on (platform:, #654), which
preship-evidence.sh refuses on only when the checking config declares
evidence_platforms (below).
A POSIX filesystem where chmod actually changes the mode.
tests/auth_security.bats and its siblings pin 0700/0600 modes on the secrets
store; on a mount where chmod is a no-op those tests can never pass, and the
run tells you nothing about your branch. run-suite.sh probes this
behaviourally. Where chmod is a no-op it then scans the suite source — tests/
recursively, plus a root conftest.py — for file-mode tokens (chmod, umask,
st_mode, os.access, stat -c, ls -l, PermissionError, 0o600-style literals
and similar). A hit refuses and names the first one; this repo's own suite always
hits, so everything below about WSL applies to it unchanged. For another project's
suite with no hit, run-suite.sh proceeds and records
file_modes: no-op; no test file references a file mode (scanned: tests/ + root conftest.py)
in the artifact, so a reader can tell that run from a file_modes: posix one. (posix
means the probe found chmod effective; the probe fails open on a directory it cannot
probe at all, so read it as "no no-op detected".) The scan is a proxy (#600): it cannot
see a mode dependency that lives only in the code under test or in test-support code
loaded from outside the scanned paths, and a suite that merely
chmod +xes a stub counts as a reference — over-refusing is the safe direction. A scan
that cannot run refuses. On native Windows a .venv/Scripts/ interpreter still needs
DEVAGENT_PYTEST_PYTHON (the pytest requirement below).
On Windows that means a WSL clone on a native Linux filesystem (ext4) — not a
checkout under /mnt/c, and not native Git Bash, whose default /etc/fstab
mounts are noacl. Give the WSL clone its own ~/.claude/devagent/config.toml
with a WSL source_dir; the devdoc tree can stay shared via /mnt/c. Git Bash
is fine for individual scripts (a preship check there attests the WSL tree,
below) — it is not a supported environment for the suite, and that is a
property of the mount, not a defect to repair. Run the suite in the WSL clone on the
issue's branch, never a detached SHA: git fetch origin && git checkout <branch>
the first time, and git merge --ff-only origin/<branch> after that.
A preship run from the Windows checkout cannot see the WSL tree that the
suite-count artifact's tree: line names, so preship-evidence.sh fails
TREE UNATTESTED until that tree is checked in WSL
(git -C <tree> rev-parse HEAD and git -C <tree> status --porcelain) and
the check re-runs with --attest-tree 'head=<sha> dirty=no path=<tree>'. The
preship verifier does both itself (#655). Likewise, such a cross-environment
artifact whose upstream: is (unreachable) or (unpushed) fails
UPSTREAM UNATTESTED until
git -C <tree> ls-remote origin refs/heads/<branch> is run there. The check then
re-runs with --attest-upstream 'head=<sha> upstream=<sha>|(unpushed)', and the
verifier does that too. In a single tree, (unreachable) needs the same step.
(unpushed), the normal state before ship pushes the branch, passes as
[upstream=unpushed] (#660). With origin persistently unreachable, preship cannot
pass, and neither can ship, which pushes to it.
Declaring which platforms count (optional, #654). preship-evidence.sh echoes
the artifact's platform: line at the end of its PASS line as [platform=…]:
os=Linux … fs=ext4 modes=posix from the WSL clone; fs=9p from a /mnt/c
checkout; fs=ntfs under Git Bash. A kernel release cannot tell the WSL clone from
a /mnt/c checkout, but the filesystem type can. To make evidence from anywhere
else fail instead of passing, the operator who RUNS preship declares the accepted
platforms in THEIR config.toml. In the sanctioned flow that is the Windows-side
config, not the WSL clone's:
[project.devagent]
evidence_platforms = ["os=Linux fs=ext4 modes=posix"]
An entry matches when each of its key=value pairs appears in the artifact's
platform: line. The keys are os, kernel, fs and modes. The example pins
modes=posix as well: an entry ignores the keys it does not name, so
os=Linux fs=ext4 alone would still accept an ext4 clone whose TMPDIR sits on a
mount where chmod is a no-op (its artifact says modes=no-op). modes=posix
means the chmod probe found no no-op. The probe fails open where chmod errors or
it cannot write, so posix is not proof that modes work.
The MR body carries the platform too. /devagent:draftmr copies the artifact's
platform: line into mr.md's ## Evidence block, and preship compares the two
byte for byte, declared or not. A mismatch fails and names the exact line, and so
does an Evidence platform: line with no artifact line behind it, or a second
platform: line. The stamper below replaces the template's placeholder, and refuses
an artifact that has no platform: line: left as it is, the placeholder fails
preship even when nothing is declared.
Writing the Evidence block (#894). The suite:, files: and platform: lines
are written by a script, never by hand:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/evidence-stamp.sh" <project> <Issue-N>
suite: is rebuilt from the newest suite-count artifact, files: is the
git diff --name-only <baseline_sha>..HEAD count, and platform: is the artifact's
own line. The stamper and preship build these lines from the same functions
(scripts/lib/evidence.sh), so they cannot disagree. The suite: line ends in the
full 40-character head SHA (the placeholders say <full head sha>): preship compares
it byte for byte, and a short SHA, the git log --oneline form, fails. The stamper
refuses, leaving mr.md unchanged, when there is no artifact, when the artifact's
head: is not HEAD (a commit came after the suite ran), when it records
dirty: yes, when its tree: is another local checkout, when it has no well-formed
platform: line, or when it records pytest: (error). Each refusal prints the
remedy: re-run run-suite.sh in the tree being shipped, then the stamper. An
artifact produced in the WSL clone and stamped from Windows needs
--attest-tree 'head=<full sha> dirty=no path=<tree>', exactly as preship does.
draftmr runs the stamper, and review and redmr re-run run-suite.sh and then the
stamper after any commit they make, so the block always names HEAD. Only those three
lines change, and each keeps its own line ending. A CRLF block keeps its CRs, but it
passes preship on GNU userlands only once #849 lands.
The Evidence block also carries the analyzer stamps (#677). /devagent:draftmr copies
every counted analyzer: line of the newest step-13 artifacts (the newest
analysis/<date>-<shellcheck|static|asan|ubsan|tsan>.txt per name) into the block
whole, after platform:. Counted means each column-0 analyzer: line of a shellcheck
or static artifact, and only line 2 of a sanitizer artifact, if it starts analyzer:.
Preship fails an Evidence analyzer: line that no counted line backs, and one that
stands twice. It warns, naming the line, about a counted line the block leaves out, and
that warning never changes the exit status. The platform: line describes the suite run
only; the analyzer's environment is not recorded. The warning reaches only the operator
(preship.md is private), so an MR reader cannot tell an omitted stamp from no stamped
artifact.
With an evidence_platforms declaration, the platform check fails:
PLATFORM UNATTESTED);platform: line;mr.md whose Evidence block lacks the artifact's platform: line, even when
the platform was acknowledged, and an mr.md with no Evidence block at all once a
suite-count artifact exists (with no artifact, the #149 warning stands).A malformed declaration stops the check before anything is decided. The
PLATFORM UNATTESTED failure prints three remedies:
--attest-platform '…' it prints.The third is the operator's decision, never the preship verifier's, and the PASS
line records it. Without a declaration no platform is refused: it is only echoed,
and an Evidence block without a platform: line is not checked. The line is a
claim the artifact makes: a hand-written artifact can carry any well-formed
platform: line.
A UTF-8 locale. Many @test names in this repo carry non-ASCII characters
(em dash, §, ⇒). bats encodes each name into a shell function name in a
child process, walking it one unit at a time; without a UTF-8 locale it walks
BYTES instead of characters.
What happens next is platform-dependent, and it is why the locale is pinned rather than left to the invoking shell:
0xe2 is classified as
[[:alnum:]] in the C locale, so it is copied into the function name RAW.
The resulting name is never defined, and bats prints unknown test name and
skips the test while still counting it in the 1..N plan — the run looks
complete and is quietly thinner.[[:alnum:]], so it is
hex-escaped consistently on both sides and the test still registers. The
silent skip does not occur there.run-suite.sh and CI both pin a UTF-8 locale, so neither depends on that
difference. If you invoke bats directly, pin it yourself:
LC_ALL=C.UTF-8 bats tests/some-file.bats
tests/locale-registration.bats fails loudly if the bats process running it
has no UTF-8 locale, so a bare bats tests/ reports the condition rather than
hiding it — and it asserts both platform branches above, so it is not vacuous
on either.
CI (.github/workflows/test.yml) runs the bats files as four shards, each with
bats --jobs 2 --no-parallelize-within-files: files in a shard run
concurrently, tests within a file serially. A test may therefore not depend on
another file's side effects, on the order files run in, or on a fixed path
outside its own $DEVAGENT_TMP; run-suite.sh is serial unless the project
opts in to suite_jobs > 1 ("Parallel bats" below), so a test that passes under
a serial run-suite.sh and fails in CI is usually sharing state across files.
A pytest-capable interpreter for a tree that has tests/test_*.py.
run-suite.sh prefers <tree>/.venv/bin/python when it exists and falls back to
ambient python3 — a Python project conventionally carries its interpreter inside the
tree, and measuring with the system one recorded 0 passed for a 51-test suite. If
neither can RUN pytest, the artifact records pytest: (error) rather than a zero count,
and preship-evidence.sh refuses it — including when the ## Evidence block is absent,
so the refusal cannot be sidestepped by deleting it. An unmeasured suite must not ship
as a green (#466).
A suite that RAN and had nothing to count is a different thing and stays shippable: all
tests skipped, or nothing collected, records a truthful pytest: 0 passed, 0 failed.
(error) means could not run, never ran and found nothing. Only the .venv/ spelling is searched, so for any other layout — venv/,
.venv/Scripts/, conda, uv, pyenv, or a linked worktree, where an untracked virtualenv
never travels — point DEVAGENT_PYTEST_PYTHON at the interpreter instead:
DEVAGENT_PYTEST_PYTHON=/path/to/python bash scripts/run-suite.sh <project> <Issue-N>
That names a working interpreter; it does not silence the (error) verdict, so the
fail-closed property is unweakened. born-red.sh resolves the interpreter the same way
(both go through scripts/lib/python-interp.sh) and refuses when it cannot run pytest
at all — an unmeasurable baseline is not a red one.
pandoc, for one file (optional). tests/build-docs-site.bats renders
docs-site/ and needs pandoc 3.x. Where pandoc is absent — including the
test suite workflow's runners — its rendering tests skip with a reason (the
input-refusal tests still run). pandoc is a contributor tool for this one file;
the plugin itself never calls it. The skipped tests are not unrun:
.github/workflows/publish-docs-site.yml installs pandoc, runs the file, and
fails if anything in it skips; docs/pages-deployment.md has the local build
command.
Parallel bats (optional). suite_jobs = N in [project.<name>] runs bats test
FILES N at a time (bats --jobs N --no-parallelize-within-files; the tests inside a
file still run in order), which cut this repo's bats leg from ~9 minutes to well under
half of that at four jobs. It needs GNU parallel on PATH (the parallel package —
Debian's moreutils parallel is a different program); with suite_jobs > 1 and no
GNU parallel, run-suite.sh dies loud naming the remedy rather than letting bats
report a truncated run — so a second clone of a project that has opted in (a WSL
clone with its own config.toml, say) needs the package too, or suite_jobs = 1 in
that clone's config. DEVAGENT_SUITE_JOBS=1 forces one serial run (the A/B seam; it
is never inherited by the suite itself), and the artifact's bats_jobs: line records
what actually ran. Opt a project in only after auditing its suite at file
granularity (shared HOME, /tmp, ports, git state — tests/README.md "Parallel
execution" has the rules for this repo); a test that fails only under
suite_jobs > 1 is a hermeticity defect in that test, never a reason to go back to
serial. Invoking bats directly:
LC_ALL=C.UTF-8 bats --jobs 4 --no-parallelize-within-files tests/
Every test file must be hermetic: one stray export DEVAGENT_ACTIVE_ISSUE=… in a
developer's shell must not change a verdict. tests/lib/hermetic-env.bash is the
single home for that — it unsets the #240 session pins and neutralizes a hostile
global gitconfig, TZ, and locale. How a file gets it depends on its shape:
A file that loads a setup layer (load 'lib/bats-helpers', load 'helpers/fixtures')
inherits the guard from that layer. Nothing to do.
A bare-setup file — one that loads nothing — must source it directly, at file
scope, in column 0:
. "${BATS_TEST_DIRNAME}/lib/hermetic-env.bash"
At file scope, not inside a test body: tests/lib_setup_layers.bats anchors its
check to column 0, because a line indented inside if false; then … fi would
otherwise read as guarded.
tests/lib_setup_layers.bats enforces both arms and fails naming the file and the
line to add. A file that genuinely must vary what the guard pins — today only
tests/locale-registration.bats, which varies LC_ALL on purpose — goes in that
file's HERMETIC_EXEMPT array as name|reason, with the reason machine-checked. The
list is capped at two: a third entry refuses, because a growing exemption list is a
backlog wearing the costume of a decision.
A test that shells out to the claude CLI takes a live dependency on the developer's
machine. tests/doctor-hermetic.bats enforces that any file invoking scripts/doctor.sh
loads tests/lib/doctor-harness.bash, which PATH-shadows claude with a deterministic
stub; loading the harness installs the stub, so the two cannot drift apart.
CI gates tests/*.bats against shellcheck SC2314 — "In bats, ! does
not cause a test failure", a vacuous negative assertion (! grep -q x foo)
that bats silently passes. The failure only shows up in GitHub Actions,
after a push.
To catch it in the dev loop instead, enable the tracked pre-push hook:
scripts/install-git-hooks.sh # sets core.hooksPath -> .githooks
.githooks/pre-push runs the same SC2314 check locally before every push
(SC2314-specific, like the CI gate — it ignores the other findings the gate
tolerates). It is opt-in: a fresh clone runs no hooks until you run the
installer. Requires shellcheck >= 0.9.0; it fails loudly (never silently
passes) if shellcheck is missing or too old.
git push --no-verifygit config --unset core.hooksPathCI (.github/workflows/shellcheck.yml) remains the authoritative, enforcing
gate; the hook is a faster local mirror, not a replacement.
Policy (re-taken at go-public 2026-09-18, #532): tagged semver releases.
.claude-plugin/plugin.json carries the release version; the marketplace
entry deliberately carries none — Claude Code uses the plugin.json value
without warning when both are set, so a second copy can only go stale. Until
1.0.0 the plugin was versioned by git commit SHA (the #532 branch (a) policy,
ratified 2026-07-20 while the repository was private); that decision named the
go-public flip as its revisit trigger, and its branch (b) is now in force. The
decision doc in devDoc Issue-532/decision-plugin-versioning.md records both
takes with the measured evidence.
What a version pins: a marketplace install resolves to the version string
and only sees an update when that string changes. Merging to master no
longer reaches installed users by itself — a release does. A marketplace added
from a local checkout (claude plugin marketplace add <path>) loads that
checkout in place and is not pinned.
Cutting a release, all on a clean master:
version in .claude-plugin/plugin.json. Patch for fixes; minor for
added commands, steps or options; major for a change that breaks an
existing project's config.toml, devdoc layout or command contract.## [Unreleased] entries in CHANGELOG.md under a new
## [<version>] — <date> heading; tests/test_plugin_versioning.py fails
until the section for the manifest version exists.claude plugin tag --push. It validates the
plugin, checks that plugin.json and the marketplace entry agree, refuses a
dirty tree or an existing tag, and pushes the annotated
devagent--v<version> tag — the name Claude Code's dependency resolver
reads when another plugin declares a version constraint on devAgent.gh release create devagent--v<version> --title "devagent <version>" --notes-file <section>.Guards: tests/test_plugin_versioning.py (CI) requires a semver version in
plugin.json, none in the marketplace entry, and a matching CHANGELOG section.
tests/plugin-validate.bats (local-only — CI has no claude CLI) runs
claude plugin validate --strict (rc=0 once the version landed, measured on
2.1.223; it was documented-red on the missing version before) and
claude plugin tag --dry-run.