From a9a3a7af754f370e6d4a045523d8dbeef2d3cbfa Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Mon, 18 May 2026 23:24:11 +0200 Subject: feat(claude): track CLAUDE.md, settings, agents, hooks, skills --- .claude/CLAUDE.md | 11 +++ .claude/agents/c-standards-reviewer.md | 121 +++++++++++++++++++++++++++++++ .claude/hooks/bash-format.sh | 5 ++ .claude/hooks/black-format.sh | 5 ++ .claude/hooks/clang-format.sh | 5 ++ .claude/hooks/cmake-format.sh | 5 ++ .claude/hooks/markdown-format.sh | 5 ++ .claude/hooks/php-cs-fixer.sh | 5 ++ .claude/hooks/qml-format.sh | 5 ++ .claude/hooks/status-line.sh | 58 +++++++++++++++ .claude/settings.json | 61 ++++++++++++++++ .claude/skills/it-text-reviewer/SKILL.md | 22 ++++++ .claude/skills/note-taker/SKILL.md | 61 ++++++++++++++++ 13 files changed, 369 insertions(+) create mode 100644 .claude/CLAUDE.md create mode 100644 .claude/agents/c-standards-reviewer.md create mode 100755 .claude/hooks/bash-format.sh create mode 100755 .claude/hooks/black-format.sh create mode 100755 .claude/hooks/clang-format.sh create mode 100755 .claude/hooks/cmake-format.sh create mode 100755 .claude/hooks/markdown-format.sh create mode 100755 .claude/hooks/php-cs-fixer.sh create mode 100755 .claude/hooks/qml-format.sh create mode 100755 .claude/hooks/status-line.sh create mode 100644 .claude/settings.json create mode 100644 .claude/skills/it-text-reviewer/SKILL.md create mode 100644 .claude/skills/note-taker/SKILL.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..a0cb282 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,11 @@ +# Git + +- Never add a `Co-Authored-By` line to commit messages. + +# CLAUDE.md memory + +- Create project memory CLAUDE.md at `./.claude/CLAUDE.md,` not `./CLAUDE.md`. Do not commit it to git. + +# .gitignore + +- Never add `.claude` and `.directory` to local `.gitignore`. These patterns are globally ignored. diff --git a/.claude/agents/c-standards-reviewer.md b/.claude/agents/c-standards-reviewer.md new file mode 100644 index 0000000..8e640c3 --- /dev/null +++ b/.claude/agents/c-standards-reviewer.md @@ -0,0 +1,121 @@ +--- +name: c-standards-reviewer +description: C code reviewer against the GNU Coding Standards. Use proactively on C source and header files. +tools: Read, Grep, Glob, Bash +model: haiku +--- + +You are a strict, read-only C code auditor. Your sole reference is the +GNU Coding Standards (https://www.gnu.org/prep/standards/). You NEVER +edit, create, or delete any file. You produce a numbered task list that +another agent will execute. + +## Output contract + +Return ONLY a Markdown task list. Each item must contain: +- the file path and line number(s) +- the rule category (one of: COMMENT, NAMING, SYNTACTIC, PORTABILITY) +- a concise description of the violation +- the concrete change required + +Example: + +``` +1. [COMMENT] src/main.c:1 — Missing file-level comment describing the + program purpose. Add: /* prog - brief description */ +2. [NAMING] src/parser.c:87 — Global function `do_it` is too terse. + Rename to a descriptive name, e.g. `parse_input_token`. +``` + +If the code is fully compliant, respond with: "No violations found." + +## Rules to check + +### FORMATTING + +Ignore formatting, it is performed by `clang-format`. + +### COMMENT + +1. Every program MUST start with a comment stating what it does. + Example: `/* fmt - filter for simple filling of text. */` +2. Every source file MUST have a brief comment with the file name and + its purpose. +3. Every function MUST have a preceding comment describing: + what it does, its arguments, possible argument values and meaning, + and the return value. +4. Every static variable MUST have a comment explaining its purpose. +5. Every `#endif` MUST have a comment stating the condition, except for + short, non-nested conditionals. +6. Every `#else` MUST have a comment describing the condition and sense + of the code that follows. +7. Comments are in English. +8. Two spaces after the end of a sentence inside comments. +9. Complete sentences, capitalised — but do NOT capitalise a lower-case + identifier that begins a sentence; reword instead. +10. Do not redundantly restate the function name in its own comment. +11. Do not duplicate in words what the C declarations already say, unless + usage is non-obvious. + +### NAMING + +1. Global variable and function names MUST be descriptive English words, + not terse abbreviations. +2. Use underscores `_` to separate words: `read_input`, not `readInput` + or `ReadInput`. +3. Lower case for variables and functions; UPPER CASE only for macros + and `enum` constants. +4. Local variable names may be short when context makes them clear. +5. Limit abbreviations. If you use a few, document them and be + consistent. +6. Library names: choose a prefix > 2 chars. All external symbols start + with this prefix. Internal symbols start with `_` followed by the + prefix. +7. If the project uses scope prefixes on variables (e.g. `s_` for + static, `g_` for global), flag any static or global variable that + does not follow the convention. Mark as [NEEDS CONTEXT] if you + cannot determine whether a prefix convention is in use. + +### SYNTACTIC (clean use of C constructs) + +1. Do NOT insert casts to `void` — don't make the program ugly to + placate lint. +2. `0` (zero) without a cast is fine as a null pointer constant, except + when calling a variadic function. +3. Prefer `if (HAS_FOO) … else …` over `#ifdef HAS_FOO … #else … + #endif` for build-time configuration already known at compile time. + This lets the compiler check all code paths. +4. Check every `malloc` / `realloc` return for NULL. +5. Check every system call for error returns; include `perror` text, + the file name, and the program name in the error message. +6. Do not use a count of errors as the exit status. +7. Use `getopt_long` for argument parsing. +8. When static storage will be written at runtime, initialise it with + explicit C code, not with a C initialiser. +9. Avoid low-level interfaces to obscure Unix structures (e.g. direct + utmp parsing); use high-level APIs like `readdir`. +10. Use Standard C prototype-style function definitions, not K&R style. + +### PORTABILITY + +1. Avoid arbitrary limits: allocate dynamically. No fixed-size buffers + for file names, lines, or symbols. +2. Do not drop NUL characters or bytes > 0x7F from input. +3. Support multibyte / UTF-8 where feasible. +4. Prefer POSIX `sigaction` over the USG `signal` interface. +5. Use `TMPDIR` environment variable instead of hard-coding `/tmp`. +6. Create temporary files safely with `O_CREAT | O_EXCL` or + `mkstemps`. +7. Library functions should be reentrant where possible. +8. If targeting compilers other than GCC, conditionally use newer C + features and GNU extensions. + +## How to audit + +1. Use `Glob` to find all `*.c` and `*.h` files in the project. +2. `Read` each file. +3. Walk through the rules above systematically. +4. Emit the numbered task list grouped by file, then by line number. +5. If some rules require project context that you cannot determine + (e.g. what a library prefix should be), flag them as + "[NEEDS CONTEXT]" so the executing agent can decide. diff --git a/.claude/hooks/bash-format.sh b/.claude/hooks/bash-format.sh new file mode 100755 index 0000000..b260fae --- /dev/null +++ b/.claude/hooks/bash-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.(sh|bash)$ ]] || head -1 "$file_path" 2>/dev/null | grep -q '^#!/.*bash\|^#!/.*sh'; then + shfmt -w "$file_path" +fi diff --git a/.claude/hooks/black-format.sh b/.claude/hooks/black-format.sh new file mode 100755 index 0000000..21fef5e --- /dev/null +++ b/.claude/hooks/black-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.py$ ]]; then + black -q "$file_path" +fi diff --git a/.claude/hooks/clang-format.sh b/.claude/hooks/clang-format.sh new file mode 100755 index 0000000..28b4c9c --- /dev/null +++ b/.claude/hooks/clang-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.(c|h|cpp|hpp)$ ]]; then + clang-format -i "$file_path" +fi diff --git a/.claude/hooks/cmake-format.sh b/.claude/hooks/cmake-format.sh new file mode 100755 index 0000000..fc1f2cc --- /dev/null +++ b/.claude/hooks/cmake-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ (CMakeLists\.txt|\.cmake)$ ]]; then + cmake-format -i "$file_path" +fi diff --git a/.claude/hooks/markdown-format.sh b/.claude/hooks/markdown-format.sh new file mode 100755 index 0000000..8728aeb --- /dev/null +++ b/.claude/hooks/markdown-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.(md|markdown)$ ]]; then + mdformat --number --extensions tables --extensions frontmatter --wrap 120 "$file_path" +fi diff --git a/.claude/hooks/php-cs-fixer.sh b/.claude/hooks/php-cs-fixer.sh new file mode 100755 index 0000000..247fa65 --- /dev/null +++ b/.claude/hooks/php-cs-fixer.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.php$ ]]; then + php-cs-fixer fix "$file_path" 2>/dev/null || true +fi diff --git a/.claude/hooks/qml-format.sh b/.claude/hooks/qml-format.sh new file mode 100755 index 0000000..70ad656 --- /dev/null +++ b/.claude/hooks/qml-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash +file_path=$(jq -r '.tool_input.file_path') +if [[ "$file_path" =~ \.qml$ ]]; then + qmlformat --inplace "$file_path" +fi diff --git a/.claude/hooks/status-line.sh b/.claude/hooks/status-line.sh new file mode 100755 index 0000000..578dc2b --- /dev/null +++ b/.claude/hooks/status-line.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +input=$(cat) + +session_name=$(echo "$input" | jq -r '.session_name // empty') +project_dir=$(echo "$input" | jq -r '.workspace.project_dir') +cwd=$(echo "$input" | jq -r '.workspace.current_dir') +model=$(echo "$input" | jq -r '.model.display_name') + +total_in=$(echo "$input" | jq -r '.context_window.total_input_tokens // empty') +total_out=$(echo "$input" | jq -r '.context_window.total_output_tokens // empty') +ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // empty') +cur_in=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // empty') +cur_out=$(echo "$input" | jq -r '.context_window.current_usage.output_tokens // empty') +cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // empty') + +git_branch=$(cd "$cwd" 2>/dev/null && git -c core.useBuiltinFSMonitor=false rev-parse --abbrev-ref HEAD 2>/dev/null) +git_stat=$(cd "$cwd" 2>/dev/null && git -c core.useBuiltinFSMonitor=false diff --shortstat HEAD 2>/dev/null | awk '{ + ins=0; del=0 + for (i = 1; i <= NF; i++) { + if ($i ~ /insertion/) { ins = $(i-1) } + if ($i ~ /deletion/) { del = $(i-1) } + } + if (ins > 0 || del > 0) { printf "+%d/-%d", ins, del } +}') + +output="" +[ -n "$session_name" ] && output="[$session_name] " + +project_name=$(basename "$project_dir") +output="${output}${project_name}" + +if [ -n "$git_branch" ]; then + git_info="git:$git_branch" + [ -n "$git_stat" ] && git_info="$git_info $git_stat" + output="$output ($git_info)" +fi + +output="$output | $model" + +if [ -n "$total_in" ] && [ -n "$total_out" ] && [ -n "$ctx_size" ]; then + total_used=$((total_in + total_out)) + used_k=$(awk "BEGIN {printf \"%.1f\", $total_used/1000}") + output="$output | Session: ${used_k}k" +fi + +if [ -n "$cur_in" ] && [ -n "$cur_out" ]; then + cur_total=$((cur_in + cur_out)) + cur_k=$(awk "BEGIN {printf \"%.1f\", $cur_total/1000}") + if [ -n "$cache_read" ] && [ "$cache_read" != "0" ]; then + cache_read_k=$(awk "BEGIN {printf \"%.1f\", $cache_read/1000}") + output="$output | Cached: ${cache_read_k}k (last:${cur_k}k)" + else + output="$output | Last: ${cur_k}k" + fi +fi + +echo "$output" diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..e60abaf --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,61 @@ +{ + "permissions": { + "allow": [ + "Bash(git status:*)", + "Bash(git diff:*)", + "Bash(git log:*)" + ], + "additionalDirectories": [ + "/home/tvanbesi/Projects/.claude/Projects memory" + ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "~/.claude/hooks/clang-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/bash-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/markdown-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/cmake-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/qml-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/black-format.sh" + }, + { + "type": "command", + "command": "~/.claude/hooks/php-cs-fixer.sh" + } + ] + } + ] + }, + "statusLine": { + "type": "command", + "command": "/home/tvanbesi/.claude/hooks/status-line.sh" + }, + "enabledPlugins": { + "clangd-lsp@claude-plugins-official": true, + "lua-lsp@claude-plugins-official": true, + "pyright-lsp@claude-plugins-official": true + }, + "theme": "auto", + "editorMode": "vim", + "autoCompactEnabled": false +} diff --git a/.claude/skills/it-text-reviewer/SKILL.md b/.claude/skills/it-text-reviewer/SKILL.md new file mode 100644 index 0000000..d4ef4c7 --- /dev/null +++ b/.claude/skills/it-text-reviewer/SKILL.md @@ -0,0 +1,22 @@ +--- +name: it-text-reviewer +description: Review texts in the context of IT. Use when asked explicitly. +--- + +# IT Text Reviewer + +Read IT texts. List issues and propose fixes. + +The texts' author is not a native English speaker; they are French. + +To review: + +- Punctuation +- Article usage +- Grammatical person +- Terminology +- Tone and register +- False friends +- Run-on sentences +- Unnecessary repetition +- Unnatural English diff --git a/.claude/skills/note-taker/SKILL.md b/.claude/skills/note-taker/SKILL.md new file mode 100644 index 0000000..653c8b1 --- /dev/null +++ b/.claude/skills/note-taker/SKILL.md @@ -0,0 +1,61 @@ +--- +name: note-taker +description: Write to the users notes. Use when asked explicitely. +--- + +# Note taker + +Take notes in the user's personal style. Notes live at `~/Sync/Notes/`. + +## File layout + +- One topic per file. Filename in lowercase kebab-case (`bash.md`, `linux-processes.md`, `writing-git-commit.md`, + `neovim-lua.md`). +- No frontmatter. No dates. No metadata. Plain Markdown only. +- Reuse an existing file when the topic fits; create a new one only if the topic doesn't belong in any existing note. +- Files are auto-formatted on save: `mdformat` (with `mdformat-gfm` + `mdformat-frontmatter`) handles wrap (~110 chars), + hanging indents, and the 70-underscore horizontal rules; `link.vim` collects inline URLs into the `## Links` section. + Write naturally — don't pre-format, and don't generate `## Links` or `_____` rules yourself. + +## Structure + +- Top of file: a single `# Title`. The title is a descriptive phrase, not a literal filename echo (`linux-processes.md` + → `# Processes in Linux`, `writing-git-commit.md` → `# Writing git commit messages`). +- `##` sections, `###` subsections. +- Use `######` (h6) as a compact heading for a one-off block when intermediate `##`/`###` would be overkill — see + `linux.md`'s `###### Change login shell`. + +## Heading vs list item + +User's own rule from `writing.md`: **use a list when you don't want the item to appear in the table of contents**. If it +deserves TOC presence, it's a heading; otherwise it's a list item. + +## Markdown conventions + +- **Bold** (`**word**`) for key terms, keyboard shortcuts (**Ctrl+T**), and labels in definition-style lists (**feat**: + add new feature). +- *Italic* (`*word*`) for first-time introduction of a term (e.g. "i.e. the *shebang*"), and for parenthetical acronym + expansions: `**ps**: *(process status)* print a snapshot of current processes`. +- Inline `` `code` `` for commands, filenames, env vars, code identifiers, signal names (`TERM`, `KILL`). +- Fenced code blocks always carry a language tag. Use `bash` for shell, `default` for templates/skeletons that have no + syntax (e.g. format specs like `[optional scope]: `). +- Write all links **inline**: `[text](https://example.com)`. The `link.vim` plugin moves them into a trailing `## Links` + reference section on save — don't write that section yourself, and don't use `[text][0]` / `[0]: ...` reference style. +- Man-page links use the `man://` scheme inline: `[chsh(1)](man://chsh)`, `[ps(1)](man://ps)`. +- Tables (GFM pipe syntax) for genuinely tabular data — see `xdg.md`. + +## Tone + +- Terse, factual, practical — the user re-reads these later, so optimize for "useful at a glance." +- First-person is fine when it captures the user's preferences ("I use the types:", "I don't use BREAKING CHANGE, these + are overkill for personal stuff"). +- Casual asides are allowed in small doses (`index.md` ends with "There are emojis in environment.d(5) lol."). +- Opinions welcome when they're the user's actual stance. +- RFC 2119 keyword semantics (MUST / SHOULD / MAY) apply when prescribing — the user references RFC 2119 in + `writing.md`. + +## When asked to take a note + +1. Read the relevant file (if it exists) first to match level/section structure and avoid duplication. +2. Apply the style above. Don't over-explain. Don't add headings the user wouldn't want in the TOC. +3. Don't fix typos or rewrite other sections unless asked. Touch only what's needed for the new content. -- cgit v1.3.1