Skip to content
Open
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
1 change: 1 addition & 0 deletions .mise/tasks/lint/_default
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Run configured codebase convention lints"
#USAGE arg "[target]" default="." help="Path to the target repository"
#USAGE example "codebase lint /path/to/repo" header="Lint a repo"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint/bats-test-helper
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Flag direct invocation of .mise/tasks/* scripts from BATS tests (call the tool, not the script)"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:bats-test-helper ." header="Check for direct script invocation in BATS tests"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint/bats-test-task
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Enforce the canonical BATS test-task shape in .mise/tasks/test"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:bats-test-task ." header="Check BATS test task shape"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint/caller-pwd-contract
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Check shiv caller-cwd environment variable contract"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:caller-pwd-contract ." header="Check caller-pwd contract"

set -euo pipefail

Expand Down
2 changes: 2 additions & 0 deletions .mise/tasks/lint/github-actions
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#MISE description="Lint GitHub Actions workflows and create a KKL default workflow when missing"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE flag "--fix" help="Create a default GitHub Actions workflow when missing"
#USAGE example "codebase lint:github-actions ." header="Lint GitHub Actions workflows"
#USAGE example "codebase lint:github-actions . --fix" header="Create default workflow"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint/gum-table
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Detect manual table formatting that should use gum table"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:gum-table ." header="Check for manual table formatting"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/lint/mcr-scope
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Forbid MISE_CONFIG_ROOT in test/ and lib/ (use BATS_TEST_DIRNAME or BASH_SOURCE instead)"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:mcr-scope ." header="Check for MISE_CONFIG_ROOT misuse in test/lib"

set -euo pipefail

Expand Down
2 changes: 2 additions & 0 deletions .mise/tasks/lint/mise-settings
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#MISE description="Check that mise.toml has required settings"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE flag "--fix" help="Add missing settings to mise.toml"
#USAGE example "codebase lint:mise-settings ." header="Check mise.toml settings"
#USAGE example "codebase lint:mise-settings . --fix" header="Fix missing settings"

set -euo pipefail

Expand Down
96 changes: 96 additions & 0 deletions .mise/tasks/lint/mise-usage-examples
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
#MISE description="Enforce #USAGE example directives for public argument-bearing mise tasks"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:mise-usage-examples ." header="Check #USAGE example coverage"

set -euo pipefail

# shellcheck source=../../../lib/shell-files.sh
source "$MISE_CONFIG_ROOT/lib/shell-files.sh"

# Rationale: `mise run <task> --help` shows #USAGE example lines as usage
# examples. Tasks that declare #USAGE arg or #USAGE flag directives give
# users a parameter reference but no concrete invocation examples — making
# the --help output incomplete.
#
# The bats-test-task rule already enforces this for .mise/tasks/test;
# this rule generalises the check to all public tasks.
#
# Legitimate exceptions (e.g. internal-only tasks that happen to have
# #USAGE declarations for documentation) opt out via:
# mise.toml: codebase:ignore mise-usage-examples
# inline comment: # codebase:ignore mise-usage-examples -- reason
#
# Hidden tasks (#MISE hide=true) are always skipped — they aren't shown
# in --help anyway, so missing examples are harmless.

IFS=' ' read -ra TARGETS <<< "${usage_targets}"

if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "ERROR: at least one target is required" >&2
exit 1
fi

# Resolve relative paths against CALLER_PWD (see lib/shell-files.sh).
for i in "${!TARGETS[@]}"; do
TARGETS[$i]=$(resolve_target "${TARGETS[$i]}")
done

failures=0

for target in "${TARGETS[@]}"; do
if [[ ! -e "$target" ]]; then
echo "ERROR: target does not exist: $target" >&2
exit 1
fi

name=$(basename "$target")

# File-level ignore via mise.toml
toml="$target/mise.toml"
if [[ -f "$toml" ]] && rg -q 'codebase:ignore mise-usage-examples' "$toml"; then
echo "SKIP $name (codebase:ignore)"
continue
fi

tasks_dir="$target/.mise/tasks"
if [[ ! -d "$tasks_dir" ]]; then
echo "OK $name (no .mise/tasks — nothing to check)"
continue
fi

target_failures=0

while IFS= read -r -d '' task; do
task_rel="${task#"$tasks_dir/"}"
task_rel="${task_rel#/}"

# Skip hidden tasks — they aren't shown in --help
if rg -q '^#MISE hide=true' "$task"; then
continue
fi

# Skip tasks with no user-facing args/flags (no USAGE surface)
if ! rg -q '^#USAGE (arg|flag) ' "$task"; then
continue
fi

# Skip inline-ignored files
if rg -q 'codebase:ignore mise-usage-examples' "$task"; then
continue
fi

# Check for at least one #USAGE example directive
if ! rg -q '^#USAGE example ' "$task"; then
echo "FAIL $name $task_rel: missing #USAGE example directives (has args/flags but no examples)"
target_failures=$((target_failures + 1))
fi
done < <(find "$tasks_dir" -maxdepth 1 -type f -print0)

if [[ "$target_failures" -eq 0 ]]; then
echo "OK $name (.mise/tasks tasks all have #USAGE examples)"
fi
failures=$((failures + target_failures))
done

exit "$failures"
1 change: 1 addition & 0 deletions .mise/tasks/lint/shellcheck
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
#MISE description="Run shellcheck against shell files in a codebase"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:shellcheck ." header="Run shellcheck on repo"

set -euo pipefail

Expand Down
2 changes: 2 additions & 0 deletions .mise/tasks/migrate/task-pattern
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#MISE description="Migrate mise run calls to _task pattern (or reverse)"
#USAGE arg "<target>" help="Path to the codebase to migrate"
#USAGE flag "--reverse" help="Reverse: _task back to mise run"
#USAGE example "codebase migrate /path/to/repo" header="Migrate to _task pattern"
#USAGE example "codebase migrate /path/to/repo --reverse" header="Reverse migration to mise run"

set -euo pipefail

Expand Down
2 changes: 2 additions & 0 deletions .mise/tasks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#MISE description="Install or check codebase pre-commit hook"
#USAGE flag "--revert" help="Remove the codebase pre-commit hook"
#USAGE flag "--check" help="Check if hook is installed and current (no changes)"
#USAGE example "codebase pre-commit" header="Install the pre-commit hook"
#USAGE example "codebase pre-commit --check" header="Check hook installation"

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions .mise/tasks/scan
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#USAGE flag "-p --pattern <pattern>" required=#true help="ast-grep pattern to search for"
#USAGE flag "-l --lang <lang>" help="Language (default: bash)"
#USAGE flag "-e --exclude <excludes>" help="Glob patterns to exclude (space-separated)"
#USAGE example "codebase scan . -p 'some pattern' -l bash" header="Scan for AST patterns"

set -euo pipefail

Expand Down
116 changes: 116 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# AGENTS.md — codebase

Structural code analysis, linting & migrations for KnickKnackLabs repos.

---

## Commands

All commands are mise tasks in `.mise/tasks/`:

| Command | Description |
|---------|-------------|
| `mise run lint [target]` | Run configured codebase convention lints against a repo |
| `mise run scan <target> -p <pattern> [-l lang] [-e glob]` | Scan codebase for AST pattern matches via ast-grep |
| `mise run migrate <target>` | Migrate `mise run` calls to `_task` pattern (or reverse with `--reverse`) |
| `mise run pre-commit [--check] [--revert]` | Install/check/remove codebase pre-commit hook |
| `mise run test [args...]` | Run BATS test suite |

---

## Environment

| Key | Value |
|---|---|
| Repo | `$HOME/codebase` |
| Fork | `https://github.com/olavostauros/codebase` |
| Upstream | `https://github.com/KnickKnackLabs/codebase` |
| Tools | `bats`, `ast-grep`, `shellcheck`, `actionlint`, `ripgrep`, `fd` (managed via mise) |

---

## Usage

### Lint a repo

```bash
cd /path/to/target-repo
codebase lint
# or from anywhere:
mise run lint /path/to/target-repo
```

Lint rules are configured in the target repo's `mise.toml` under `[_.codebase]`:

```toml
[_.codebase]
lint = ["mise-settings", "gum-table", "shellcheck"]
```

### Available lint rules

| Rule | Description |
|------|-------------|
| `mise-settings` | Check that `mise.toml` has required settings (`quiet=true`, `task_output=interleave`) |
| `gum-table` | Detect manual table formatting using `column -t` or `printf %-Ns` that should use `gum table` |
| `shellcheck` | Run shellcheck against shell files in a codebase |
| `bats-test-task` | Enforce the canonical BATS test-task shape in `.mise/tasks/test` |
| `bats-test-helper` | Flag direct invocation of `.mise/tasks/*` scripts from BATS tests |
| `mcr-scope` | Forbid `MISE_CONFIG_ROOT` in `test/` and `lib/` |
| `caller-pwd-contract` | Check shiv caller-cwd environment variable contract |
| `github-actions` | Lint GitHub Actions workflows and create a KKL default workflow when missing |
| `or-true` | Classify risky unannotated `\|\| true` / `\|\| :` failure suppression |
| `mise-usage-examples` | Enforce `#USAGE example` directives for public argument-bearing `mise` tasks |

### Scan for patterns

```bash
codebase scan /path/to/repo -p "some pattern" -l bash
```

### Install pre-commit hook

```bash
cd /path/to/target-repo
codebase pre-commit
```

### Run tests

```bash
mise run test
mise run test lint/bats-test-task # specific suite
```

---

## Test structure

Tests live in `test/` organized by feature area:

| Directory | Tests |
|-----------|-------|
| `test/lib/` | Unit tests for shared lib functions |
| `test/lint/` | Lint rule tests (one subdir per rule) |
| `test/migrations/` | Migration task tests |
| `test/pre-commit/` | Pre-commit hook tests |
| `test/scan/` | Scan task tests (includes AST fixtures) |

---

## Shared libs

| File | Purpose |
|------|---------|
| `lib/codebase-config.sh` | Repo resolution, lint rule discovery from `mise.toml` |
| `lib/shell-files.sh` | File discovery, path resolution (`resolve_target`) |

---

## Conventions

- Bash-first (no Node/Python runtime dependencies)
- `test/test_helper.bash` provides common test bootstrapping
- CI runs on ubuntu-latest and macos-latest via GitHub Actions
- Lint rules are simple bash scripts that inspect repo structure
- Comments use single-line section headers (`# === Section title ===`), never multi-line ruler blocks
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#MISE description="Enable verbose output"
#USAGE flag "--verbose" help="Enable verbose output"
set -euo pipefail
echo "Verbose mode: ${usage_verbose:-off}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
#MISE description="Deploy the app"
#USAGE flag "--env <env>" help="Target environment"
#USAGE example "mise run deploy --env staging" header="Deploy to staging"
set -euo pipefail
echo "Deploying to ${usage_env:-production}..."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
#MISE description="Greet the user"
#USAGE arg "<name>" help="Name to greet"
#USAGE example "mise run greet Alice" header="Greet Alice"
set -euo pipefail
echo "Hello, ${usage_name}!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#MISE description="Deploy the app"
#USAGE flag "--env <env>" help="Target environment"
set -euo pipefail
echo "Deploying to ${usage_env:-production}..."
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#MISE description="Greet the user"
#USAGE arg "<name>" help="Name to greet"
set -euo pipefail
echo "Hello, ${usage_name}!"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
#MISE description="Internal tool — hidden from --help"
#MISE hide=true
#USAGE arg "<token>" help="Auth token"
set -euo pipefail
echo "Internal task running with token: ${usage_token}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
#MISE description="Greet the user"
#USAGE arg "<name>" help="Name to greet"
# codebase:ignore mise-usage-examples -- intentionally bare, this is an internal wrapper
set -euo pipefail
echo "Hello, ${usage_name}!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#MISE description="Deploy the app"
#USAGE flag "--env <env>" help="Target environment"
set -euo pipefail
echo "Deploying to ${usage_env:-production}..."
4 changes: 4 additions & 0 deletions test/lint/mise-usage-examples/fixtures/ignored-repo/mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[_.codebase]
lint = ["mise-settings"]

codebase:ignore mise-usage-examples
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#MISE description="Broken task — missing example"
#USAGE arg "<path>" help="Path to process"
set -euo pipefail
echo "Processing ${usage_path}..."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
#MISE description="Greet the user"
#USAGE arg "<name>" help="Name to greet"
#USAGE example "mise run greet Alice" header="Greet Alice"
set -euo pipefail
echo "Hello, ${usage_name}!"
Loading