Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/scripts/setup-bats.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
BATS_DIR="${REPO_ROOT}/.bats"

BATS_VERSION="1.11.0"
BATS_VERSION="1.14.0"
BATS_SUPPORT_VERSION="0.3.0"
BATS_ASSERT_VERSION="2.1.0"
BATS_ASSERT_VERSION="2.2.4"

echo "Installing BATS testing framework..."

Expand Down Expand Up @@ -46,4 +46,4 @@ echo "Run all tests with:"
echo " ${BATS_DIR}/bats-core/bin/bats plugin-tests/**/*.bats"
echo ""
echo "Or run specific plugin tests:"
echo " ${BATS_DIR}/bats-core/bin/bats plugin-tests/guardrails/native-tools-enforcer/*.bats"
echo " ${BATS_DIR}/bats-core/bin/bats plugin-tests/native-tools-enforcer/*.bats"
19 changes: 15 additions & 4 deletions plugin-tests/software-writer/inject_extension.bats
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,21 @@ Run the project's dead-code target only."
}

# bats test_tags=failure
@test "fails when extension file exists but is not readable" {
@test "fails when the extension path exists but is not a regular file" {
local extension="${CLAUDE_PROJECT_DIR}/.claude/extensions/software-writer/writing-tests.md"
mkdir -p "$extension"
run_post_tool_use "software-writer:writing-tests"
assert_failure 1
assert_output --partial "extension file exists but is not readable"
}

# bats test_tags=failure
@test "fails when the extension file exists but has no read permission" {
(( EUID != 0 )) || skip "root reads files regardless of mode"
make_extension "writing-tests" "## Post-Step-2"
chmod 000 "${CLAUDE_PROJECT_DIR}/.claude/extensions/software-writer/writing-tests.md"
local extension="${CLAUDE_PROJECT_DIR}/.claude/extensions/software-writer/writing-tests.md"
chmod 000 "$extension"
run_post_tool_use "software-writer:writing-tests"
chmod 644 "${CLAUDE_PROJECT_DIR}/.claude/extensions/software-writer/writing-tests.md"
assert_failure
assert_failure 1
assert_output --partial "extension file exists but is not readable"
}
19 changes: 15 additions & 4 deletions plugin-tests/work-orchestrator/inject_extension.bats
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,21 @@ List the project gates before stating the strategy."
}

# bats test_tags=failure
@test "fails when the extension file exists but is not readable" {
@test "fails when the extension path exists but is not a regular file" {
local extension="${CLAUDE_PROJECT_DIR}/${EXTENSION_REL}"
mkdir -p "$extension"
run_post_tool_use "$SKILL"
assert_failure 1
assert_output --partial "extension file exists but is not readable"
}

# bats test_tags=failure
@test "fails when the extension file exists but has no read permission" {
(( EUID != 0 )) || skip "root reads files regardless of mode"
make_extension "## Post-Dispatch"
chmod 000 "${CLAUDE_PROJECT_DIR}/${EXTENSION_REL}"
local extension="${CLAUDE_PROJECT_DIR}/${EXTENSION_REL}"
chmod 000 "$extension"
run_post_tool_use "$SKILL"
chmod 644 "${CLAUDE_PROJECT_DIR}/${EXTENSION_REL}"
assert_failure
assert_failure 1
assert_output --partial "extension file exists but is not readable"
}
25 changes: 25 additions & 0 deletions plugins/software-writer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## [Unreleased]

### Added

- `writing-code` Write-the-line step: when an edit makes a function fail where it used to return a value, search its callers for a fallback (`|| x='{}'`, `?? default`) that was unreachable before and now runs.
- `writing-code` bash footgun entries: restoring shell options by replaying the saved state (with errexit re-asserted from `$-`, since the `$(set +o)` capture can drop it), `local` with dependent assignments on one line, `read -a` reading the first line only, redirection order with `2>`, passing data into `jq` and `awk` with `--arg` and `ENVIRON` instead of interpolating it, octal reading of numbers with leading zeros, and `jq` `// empty` bindings with exact-string comparison.
- `writing-tests` `stacks/bash/bats.md` covers the error-path idiom (`run -<N>`, `run !`, `bats_require_minimum_version`, the BW01 and BW02 warnings, and the blind spot of `refute`, `run !`, and a bare `assert_failure` when the command is missing), `--separate-stderr` with stderr assertions, the `refute_*` helpers, a loop versus one test per case, the abort on a duplicate test name, helpers and temporary directories (`load`, the `BATS_*_TMPDIR` scopes, `setup_file` exports), the prerequisites and opt-outs for `--jobs`, the EXIT-trap hazard, root ignoring permission bits, test doubles (function override, `export -f`, `PATH` shim), and a table of non-deterministic calls.
- `writing-tests` framework references for Go (`stdlib-testing`, `testify`, `ginkgo-gomega`), PHP (`phpunit`, `pest`), Python (`pytest`, `unittest`), and TypeScript (`jest`, `mocha`, `node-test`, `vitest`) each name the framework's helper idiom and its per-test temporary-directory mechanism. `mocha` (12.x with Chai 6.x), `node-test` (Node 26), `unittest` (Python 3.15), and `stdlib-testing` (Go 1.27) state the version they were written against.
- `writing-code` stack references name what a named exception or a `|| true`-style construct still hides: Python exception subclasses and try bodies wider than the call that can raise; Go `_ =` and an unchecked `Close` on a written file; PHP `json_decode` returning null for both invalid input and a JSON `null` (use `JSON_THROW_ON_ERROR`), `@` suppressing the warning but not the failure, and `foreach (null)` warning and continuing; TypeScript empty and default-returning catch-alls. The comments reference says an empty catch block's comment names what it swallows.

### Changed

- `writing-code` bash footgun entry for Bash 4 features gains a leading rule to check bash's NEWS for the minor version, plus `declare -g` and `local -` as further examples.
- `writing-tests` `stacks/bash/bats.md` is written against bats-core 1.14.x and bats-assert 2.2.x, and states that from 1.14.0 `run` honors a `set -e` the function turns on itself.
- `writing-code` Step 6 applies the exemption markers first, then checks each comment written, edited, or kept on the lines being written or edited against the code and runs any claim the comment makes about the language, the interpreter, or a tool in an isolated probe, never by running the code under edit. A run counts only on the version or platform the claim names. A claim a run refutes is corrected, limited to the refuted part; a claim that cannot be run here stays unchanged and is reported to the user as unverified. A comment is never deleted for a claim, and still goes on to classification; when classification deletes a comment this step corrected, the report to the user names the deletion and its bucket. The step heading is now "Check and classify each comment".
- `writing-tests` framework references move to Vitest 5.x, Pest 5.x on PHPUnit 13, and pytest 9.2.x. The pytest reference now lists fixtures of every scope above function, not only class scope, among the deprecated instance-method fixtures.
- The `bats-referenced-by-name` template, copied into `writing-tests` and `sweeping-dead-code`, lists `bats_test_function --description '<name>' -- <function>` as a by-name reference to a function.

### Fixed

- `writing-code` bash footgun entry for command substitution under `set -e` is corrected for `set -o posix`.
- `writing-code` bash footgun entry for `|| true`: use it only when every non-zero status is an answer; otherwise branch on the exact status; when a failure shares its status with an answer, avoid the input that fails or check the condition another way.
- `writing-tests` `stacks/bash/bats.md` corrects what `run` does inside a function that does not enable `set -e` itself (errexit is suppressed unless `--errexit` is set, so `run -0 f` can pass where a direct `f` fails), the claim about `< <(...)` (a failing one registers no tests and the run can still exit 0), and the `|| code=$?` capture, which it limits to functions that propagate their own failures. It states the exact `--jobs` prerequisites and that `bats_require_minimum_version` may come before the first flagged `run` at file level, in `setup`, or in the test body.
- `writing-tests` Jest reference corrects the mock-reset advice: `jest.restoreAllMocks()` restores `jest.spyOn` spies and `jest.replaceProperty` replacements and leaves `jest.fn()` implementations in place, `jest.resetAllMocks()` replaces every `jest.fn()` implementation with one that returns `undefined` (so each test or `beforeEach` sets the implementations it needs), and the `resetMocks` and `restoreMocks` flags run the same calls before every test.

## [2.4.0] - 2026-10-12

### Added
Expand Down
2 changes: 1 addition & 1 deletion plugins/software-writer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Without project-specific additions, the skills run on universal defaults: stack

**Triggers:** writing or editing implementation code — any change that adds, modifies, or removes logic, signatures, or comments on the lines being written, including cleaning up the comments on code being edited. Test files and their fixtures belong to `writing-tests`, except deleting a test whose only subject a `writing-code` edit deleted.

**Behavior:** consults the stack's doc tool before any uncertain API call, gates new package dependencies on registry verification, routes wrapped domains through in-repo primitives, refuses unrecognized or invalid input with a message before any fallback runs (logging then falling back only where the fallback is documented), validates arguments before the first side effect, calls an existing helper instead of writing a second copy, enforces explicit dependency entry and a non-test caller for every new export, deletes symbols an edit leaves without a non-test reference (searching the stack's indirect-reference forms, repeating until nothing more is orphaned), scans each line against the stack's footgun catalog, and classifies every comment into a two-tier policy (API doc comments as contract, implementation comments as point-of-use why) after checking it against the code it describes.
**Behavior:** consults the stack's doc tool before any uncertain API call, gates new package dependencies on registry verification, routes wrapped domains through in-repo primitives, refuses unrecognized or invalid input with a message before any fallback runs (logging then falling back only where the fallback is documented), validates arguments before the first side effect, calls an existing helper instead of writing a second copy, searches callers for a fallback that starts running once an edit makes a function fail, enforces explicit dependency entry and a non-test caller for every new export, deletes symbols an edit leaves without a non-test reference (searching the stack's indirect-reference forms, repeating until nothing more is orphaned), scans each line against the stack's footgun catalog, and classifies every comment written, edited, or kept on those lines into a two-tier policy (API doc comments as contract, implementation comments as point-of-use why) after checking it against the code it describes and running any claim it makes about the language, interpreter, or tool in an isolated probe (a run corrects only the part it refutes; a claim that cannot be run here stays and is reported as unverified).

### Writing Tests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ A plain-name search for a caller finds none of these; deleting one of them requi

- `load 'name'` sources a file relative to the test file's directory, trying `name.bash` before `name`, and `bats_load_library 'name'` sources `name` or `name/load.bash` from a `BATS_LIB_PATH` directory; neither names the extension or the functions the file defines.
- `setup`, `teardown`, `setup_file`, and `teardown_file` in a test file, and `setup_suite` and `teardown_suite` in a `setup_suite.bash` beside the first test file (or the file `--setup-suite-file` names), run with no call naming them.
- `run <function>` passes a function to `run` by name, `run bash -c "source '<file>'; <function>"` names a function and a script inside a string, and a script under test is usually reached through a path built from `$BATS_TEST_DIRNAME`; all are test-only references.
- `run <function>` and `bats_test_function --description '<name>' -- <function>` pass a function by name, `run bash -c "source '<file>'; <function>"` names a function and a script inside a string, and a script under test is usually reached through a path built from `$BATS_TEST_DIRNAME`; all are test-only references.
- `bats --filter <regex>` in CI, a Makefile, or a `package.json` script selects tests by matching their names; renaming or deleting a test can leave a filter that matches nothing.
Loading
Loading