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
25 changes: 25 additions & 0 deletions .claude/skills/wait/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: wait
description: Pause execution for a requested number of minutes by sleeping one-minute increments to avoid exceeding shell timeouts.
user_invocable: true
triggers:
- /wait
- wait
---

# Wait Skill

Use this skill whenever the user asks the assistant to pause or wait for a few minutes during a task. Instead of issuing a single long `sleep` command (which often hits the 2-minute shell timeout), run one-minute sleeps repeatedly for the requested duration.

## Workflow

1. **Determine the wait time.** Parse the user’s request for a duration expressed in minutes. If the request is vague, ask a clarifying question (e.g., “How many minutes should I wait?”) before running commands.
2. **Enforce sane limits.** If the user requests a very large number of minutes, warn them and offer to break the wait into smaller chunks or confirm before proceeding.
3. **Execute sequential Bash sleeps.** For each of the requested N minutes, issue a separate `bash` tool call with `sleep 60`. Before each call, report the upcoming iteration as `executing sleep: i/N: bash sleep 60` so observers know how many sleeps will run. Avoid bundling the sleeps into a single script; the goal is to keep every sleeping command under the 2-minute timeout.

4. **Report completion.** Once the loop finishes, notify the user that the wait is over and resume the primary task.

## Error handling

- If the shell command fails (e.g., `sleep` unavailable), report the failure and stop waiting.
- If the user changes their mind mid-wait, cancel the remaining iterations and explain how much time was actually spent waiting.
2 changes: 2 additions & 0 deletions .github/workflows/linter_require_ruff.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ jobs:
run: make tech_debt
- name: Run duplicate code check
run: make duplicate_code
- name: Run AI writing check
run: uv run python scripts/check_ai_writing.py
- name: Run import-linter
run: make import_lint
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ repos:
language: system
pass_filenames: false
always_run: true
- id: ai-writing-check
name: AI writing check
entry: uv run python scripts/check_ai_writing.py
language: system
pass_filenames: false
always_run: true
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Structure as: `init()` → `continue(id)` → `cleanup(id)`
## Git Workflow
- **Protected Branch**: `main` is protected. Do not push directly to `main`. Use PRs.
- **Merge Strategy**: Squash and merge.
- **Never force push**: Do not use `git push --force` or `--force-with-lease`. If you hit a git issue, stop and ask the user for guidance.

## Deprecated

Expand Down
92 changes: 92 additions & 0 deletions scripts/check_ai_writing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

import pathlib
from collections.abc import Iterable, Sequence

REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
EM_DASH = chr(0x2014)
SKIP_DIRS = {
".git",
".venv",
".uv_cache",
".uv-cache",
".uv_tools",
".uv-tools",
".cache",
".pytest_cache",
"__pycache__",
"node_modules",
".next",
}
SKIP_SUFFIXES = {
".png",
".jpg",
".jpeg",
".gif",
".webp",
".ico",
".mp4",
".mov",
".mp3",
".woff",
".woff2",
".ttf",
".otf",
".eot",
".pdf",
".zip",
".tar",
".gz",
".bz2",
".7z",
".ckpt",
".bin",
".pyc",
".pyo",
".db",
}


def iter_text_files(root: pathlib.Path) -> Iterable[pathlib.Path]:
for path in root.rglob("*"):
if not path.is_file():
continue
rel = path.relative_to(root)
if any(part in SKIP_DIRS for part in rel.parts):
continue
Comment on lines +51 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iter_text_files() skips any file whose relative path parts contain a name in SKIP_DIRS. This will also skip user/source directories that happen to share a name with these entries (e.g., a package named cache/, node_modules/, etc.), not just the intended hidden/tooling dirs at repo root. If the intent is to skip only top-level tool directories, this will produce false negatives where em dashes in those directories are never checked.

Also appears in the same function at scripts/check_ai_writing.py:55.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/check_ai_writing.py
Line: 51:56

Comment:
`iter_text_files()` skips any file whose *relative path parts* contain a name in `SKIP_DIRS`. This will also skip user/source directories that happen to share a name with these entries (e.g., a package named `cache/`, `node_modules/`, etc.), not just the intended hidden/tooling dirs at repo root. If the intent is to skip only top-level tool directories, this will produce false negatives where em dashes in those directories are never checked.

Also appears in the same function at `scripts/check_ai_writing.py:55`.

How can I resolve this? If you propose a fix, please make it concise.

if path.suffix.lower() in SKIP_SUFFIXES:
continue
yield path


def find_em_dashes(path: pathlib.Path) -> Sequence[tuple[int, str]]:
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except OSError:
return []
lines: list[tuple[int, str]] = []
for lineno, line in enumerate(text.splitlines(), start=1):
if EM_DASH in line:
lines.append((lineno, line))
return lines


def main() -> int:
violations: list[tuple[pathlib.Path, int, str]] = []
for path in iter_text_files(REPO_ROOT):
for lineno, line in find_em_dashes(path):
violations.append((path.relative_to(REPO_ROOT), lineno, line.strip()))
if violations:
print(
f"AI writing check failed: {EM_DASH!r} (em dash) detected in the repository"
)
for rel_path, lineno, snippet in violations:
print(f"{rel_path}:{lineno}: {snippet}")
print("Please remove the em dash or explain why it is acceptable.")
return 1
print("AI writing check passed (no em dash found).")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading