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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); docs: stop instructing shfmt -w, and scope make sa correctly by Chemaclass · Pull Request #863 · TypedDevs/bashunit · GitHub
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
33 changes: 28 additions & 5 deletions .claude/CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,11 +27,14 @@ See `.claude/rules/bash-style.md` for complete compatibility guide (auto-loaded
Every change must pass:
```bash
make sa # ShellCheck static analysis
make lint # EditorConfig linting
make lint # EditorConfig linting (the formatting authority — 2-space indent)
./bashunit tests/ # All tests passing
shfmt -w . # Code formatting
```

**Never run `shfmt -w`.** shfmt and `.editorconfig` disagree on this repo: 146
tracked files are already shfmt-dirty on a clean `main`, and satisfying shfmt
breaks `make lint`. See "Formatting" below.

## Architecture

```
Expand DownExpand Up@@ -61,8 +64,28 @@ bashunit/
./bashunit --parallel tests/ # Parallel execution
./bashunit tests/unit/ # Run unit tests only
make sa # ShellCheck static analysis
make lint # EditorConfig checker
shfmt -w . # Format all shell files
make lint # EditorConfig checker (formatting authority)
```

## Formatting

Match the surrounding 2-space style by hand and verify with `make lint`. There is
no `shfmt` target, and **`shfmt -w .` must not be run** — it fights `.editorconfig`
two ways:

- `.editorconfig` sets `indent_size = unset` for `tests/acceptance/**.sh` and
`src/console_header.sh`; shfmt reads `unset` as "use my default", which is tabs,
while `[*]` sets `indent_style = space`. Any new file under `tests/acceptance/`
therefore shows up shfmt-dirty — that is expected, not a defect.
- shfmt collapses `\`-continued strings onto one line, which would push
`src/state.sh`'s record writer past `max_line_length = 120`.

To check you introduced no new drift, compare the dirty-file *list* against main
rather than requiring it to be empty:

```bash
git ls-files -z '*.sh' bashunit | xargs -0 shfmt -d 2>/dev/null \
| grep '^--- ' | sed 's|--- ||; s|\.orig.*||' | sort
```

## Test Patterns
Expand DownExpand Up@@ -137,7 +160,7 @@ Rules auto-load based on file paths being edited (via `paths:` frontmatter in ea
- All tests green for the **right reason**
- `make sa` passes (ShellCheck)
- `make lint` passes (EditorConfig)
- Code formatted (`shfmt -w .`)
- Code formatted to 2-space indent by hand (verified by `make lint`, never `shfmt -w`)
- Bash 3.0+ compatible
- Parallel tests passing (`./bashunit --parallel tests/`)
- CHANGELOG.md updated (if user-facing changes)
Expand Down
4 changes: 3 additions & 1 deletion .claude/rules/bash-style.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,9 @@ bashunit must work on **Bash 3.0+** (macOS default). These features are **prohib

## Coding Conventions

- **2 spaces** indent, no tabs — enforced by `shfmt -w .`
- **2 spaces** indent, no tabs — enforced by `make lint` (EditorConfig). Do **not**
run `shfmt -w`: it wants tabs for `tests/acceptance/**` and collapses
`\`-continued strings past the 120-char limit, breaking `make lint`
- **120 chars** max line length (soft)
- Follow [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)
- Always quote variables unless explicit word splitting is needed
Expand Down
5 changes: 2 additions & 3 deletions .claude/rules/tdd-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,15 +59,14 @@ Test: (none yet)

1. Improve readability, naming, extract duplication — **no behavior changes**
2. Run tests after each change
3. Run quality checks: `make sa && make lint && shfmt -w .`
3. Run quality checks: `make sa && make lint` (never `shfmt -w` — it breaks `make lint`)

## Quality Gate (Before Commit)

```bash
./bashunit tests/ # All tests
./bashunit --parallel tests/ # Parallel (isolation check)
make sa && make lint # Static analysis + linting
shfmt -w . # Formatting
make sa && make lint # Static analysis + linting (lint is the format gate)
```

## Definition of Done
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-assertion/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,7 @@ For each test in inventory, follow RED -> GREEN -> REFACTOR:
- Source new file in `src/bashunit.sh` if created
- `export -f` the assertion function
- Run full test suite: `./bashunit tests/`
- Quality checks: `make sa && make lint && shfmt -w .`
- Quality checks: `make sa && make lint`

### 5. Documentation

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/gh-issue/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,7 +83,7 @@ Fetch a GitHub issue, create branch, implement following TDD, and open a PR.

12. **Quality checks** after each refactor:
```bash
make sa && make lint && shfmt -w .
make sa && make lint
```

### Phase 4: Ship
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pre-release/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ All must pass. Run 3-5 times to catch flaky tests.
```bash
make sa # ShellCheck — zero warnings
make lint # EditorConfig — clean
shfmt -l . # Check formatting (don't modify)
# NB: no shfmt gate — it conflicts with .editorconfig here; make lint is the authority
```

### 4. Documentation
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/review/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ Review a GitHub PR. When no number is given, review the PR for the current branc
- Bash 3.0+ compatibility (no `declare -A`, `[[ ]]`, `${var,,}`, negative indexing, `&>>`)
- Tests exist and follow TDD; both success and failure paths covered
- Naming, namespacing (`bashunit::*` / `_private`), and dynamic-scope safety
- ShellCheck cleanliness and `shfmt` formatting
- ShellCheck cleanliness and 2-space indentation (`make lint`, not `shfmt`)
- CHANGELOG.md updated for user-facing changes

4. **Report findings** grouped by severity (blocker / suggestion / nit), each as `path:line — problem. fix.`. No praise, no scope creep. State plainly if the PR looks good.
2 changes: 1 addition & 1 deletion .claude/skills/tdd-cycle/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@ Check for `.tasks/YYYY-MM-DD-*.md`. If missing, create one before proceeding.

1. Improve readability, naming, extract duplication — no behavior changes
2. Run tests after each change
3. Quality checks: `make sa && make lint && shfmt -w .`
3. Quality checks: `make sa && make lint`
4. Full suite: `./bashunit tests/`
5. Update task file with refactoring notes

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,11 +94,21 @@ test/parallel: $(TEST_SCRIPTS)
# SHELLCHECK_OPTS for local/CI parity. One file per invocation (like CI's
# action) because shellcheck 0.11.0 can crash on multi-file batches with -x;
# -P 4 keeps the wall time reasonable.
#
# The file list comes from git, not `find`, for two reasons: `find .` descends
# into .claude/worktrees/ (linked worktrees hold whole copies of the tree, so
# the target lints the repo N+1 times and appears to hang), and its "*.sh" glob
# never sees the extensionless entrypoint or the bin/ scripts. `find` remains
# the fallback outside a git checkout.
sa:
ifndef STATIC_ANALYSIS_CHECKER
@printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1
else
@find . -name "*.sh" -not -path "./local/*" -print0 \
@{ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
git ls-files -z "*.sh" bashunit bin/pre-commit bin/create-pr; \
else \
find . -name "*.sh" -not -path "./local/*" -not -path "./.claude/worktrees/*" -print0; \
fi; } \
| xargs -0 -n 1 -P 4 shellcheck -xC -e SC1091 -e SC2155 -e SC2016 \
&& printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!"
endif
Expand Down
Loading