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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run AI tooling tests
# test-canonical-skills.sh is excluded: it fails on a false positive in
# sprint-planning/scripts/test_board_scripts.py (a comment, not a path).
run: |
ai/tests/test-skill-spec.sh
ai/tests/test-canonical-skills.sh
ai/tests/test-plain-writing-contract.sh
ai/tests/test-ai-installers.sh
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ This repo ships a fair amount of tooling: shared AI skills and subagents, shell

### AI skills

Skills live in [`ai/skills/`](ai/skills). The installer symlinks the same directories into `~/.claude/skills/` and `~/.agents/skills/`, so both platforms use one canonical source. Each skill is a self-contained directory with a `SKILL.md` and any supporting scripts.
Skills live in [`ai/skills/`](ai/skills). The installer symlinks the same directories into `~/.claude/skills/` and `~/.agents/skills/`, so both platforms use one canonical source, minus the Codex exclusions in `ai/codex/excluded-skills.txt`. [`ai/codex/skills/`](ai/codex/skills) holds the few skills only Codex gets, because Claude already bundles its own under the same name. Each skill is a self-contained directory with a `SKILL.md` and any supporting scripts.

| Skill | What it does |
| ------- | ------------ |
Expand All @@ -64,6 +64,7 @@ Skills live in [`ai/skills/`](ai/skills). The installer symlinks the same direct
| [`quarterly-planning`](ai/skills/quarterly-planning) | Draft quarterly goals for a PostHog team, walking the HOGS framework from issues and strategy docs. |
| [`resolve-conflicts`](ai/skills/resolve-conflicts) | Resolve git conflicts with mergiraf structural merging, lock file handling, stacked PR dedup. |
| [`review-fix-cycle`](ai/skills/review-fix-cycle) | One review, fix, simplify, clean comments, commit iteration. |
| [`simplify`](ai/codex/skills/simplify) | Simplify recently changed code for clarity and maintainability without changing behavior. Codex only; Claude bundles its own. |
| [`sprint-planning`](ai/skills/sprint-planning) | Bi-weekly sprint planning updates for the Feature Flags Platform team. |
| [`squash`](ai/skills/squash) | Squash each contributor's run of contiguous commits on the branch into one, preserving authorship. |
| [`standup`](ai/skills/standup) | Generate standup notes from your recent GitHub PR activity. |
Expand All @@ -82,7 +83,7 @@ Subagents live in [`ai/agents/`](ai/agents). Claude uses the Markdown definition
| Agent | When to use it |
| ------- | -------------- |
| [`bug-root-cause-analyzer`](ai/agents/bug-root-cause-analyzer.md) | Failing tests, intermittent bugs, or environment-specific defects that need a systematic investigation. |
| [`code-reviewer`](ai/agents/code-reviewer.md) | Pre-commit correctness, security, and guideline review (use `/simplify` for readability). |
| [`code-reviewer`](ai/agents/code-reviewer.md) | Pre-commit correctness, security, and guideline review (use the `simplify` skill for readability). |
| [`implementation-planner`](ai/agents/implementation-planner.md) | Break down complex features into staged technical plans before writing code. |
| [`investigator`](ai/agents/investigator.md) | Investigate a single operational hypothesis using Grafana, Prometheus, Loki, and PostHog data. Spawn in parallel for multi-hypothesis incident reviews. |
| [`note-taker`](ai/agents/note-taker.md) | Preserve non-obvious technical discoveries after a long exploration session. |
Expand Down
2 changes: 1 addition & 1 deletion ai/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ If arriving from an approved Plan Mode plan, invoke the `go` skill with `--plan-
2. `unit-test-writer` writes tests first (red)
3. Implement minimal code to pass (green)
4. Refactor with tests passing
5. Run `/simplify` to review changed code, then `comment-cleanup` over the result
5. Run the `simplify` skill to review changed code, then `comment-cleanup` over the result
6. `code-reviewer` before committing

After 2 failed attempts, stop and use `bug-root-cause-analyzer`. Don't keep pushing a broken approach.
Expand Down
3 changes: 2 additions & 1 deletion ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ The installers preserve regular files and unmanaged symlinks in the destination
## Shared sources

- `AGENTS.md` contains global instructions and is linked as `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`.
- `skills/` is linked into both `~/.claude/skills/` and `~/.agents/skills/`. A skill refers to its own scripts relative to its directory so it resolves under either agent; an absolute `~/.dotfiles/…` path means it reaches outside itself, to a repo binary or another skill. Codex-only exclusions live in `codex/excluded-skills.txt`; they cover configuration workflows, orchestrators that still depend on Claude-only slash commands or external Claude skills, and skills whose `allowed-tools` fence matters because they read untrusted input, since Codex has no per-skill tool scoping.
- `skills/` is linked into both `~/.claude/skills/` and `~/.agents/skills/`. A skill refers to its own scripts relative to its directory so it resolves under either agent; an absolute `~/.dotfiles/…` path means it reaches outside itself, to a repo binary or another skill. Codex exclusions live in `codex/excluded-skills.txt`; they cover configuration workflows, orchestrators that still depend on Claude-only slash commands or external Claude skills, and skills whose `allowed-tools` fence matters because they read untrusted input, since Codex has no per-skill tool scoping.
- `codex/skills/` is linked into `~/.agents/skills/` only. It holds skills that exist because Claude bundles its own workflow under that name, so shipping ours to Claude would override the bundled one. Both roots land in the same destination directory, so a name may appear in only one of them.
- `agents/` contains the canonical Markdown agent definitions. Claude consumes them directly and `bin/render-codex-agents.py` converts them to Codex TOML.
- `mcp-servers.sh` defines the MCP inventory once while each installer uses its platform's registration command.

Expand Down
6 changes: 3 additions & 3 deletions ai/agents/code-reviewer.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: code-reviewer
description: "Reviews code for bugs, logic errors, security vulnerabilities, and project guideline violations. Does not cover readability or refactoring (use /simplify for that). Examples: before committing changes, after implementing a new feature, or when you want a correctness check."
description: "Reviews code for bugs, logic errors, security vulnerabilities, and project guideline violations. Does not cover readability or refactoring (use the simplify skill for that). Examples: before committing changes, after implementing a new feature, or when you want a correctness check."
model: opus
color: red
---

You are a senior code reviewer focused on correctness and safety. Catch bugs, security issues, and project guideline violations — not refactoring or style improvements (`/simplify` handles those).
You are a senior code reviewer focused on correctness and safety. Catch bugs, security issues, and project guideline violations — not refactoring or style improvements (the `simplify` skill handles those).

## Before You Review

Expand All @@ -28,7 +28,7 @@ You are a senior code reviewer focused on correctness and safety. Catch bugs, se

## Out of Scope

Do not flag readability, naming aesthetics, redundant code, structural refactoring, comment quality, or stylistic preferences — use `/simplify` for those.
Do not flag readability, naming aesthetics, redundant code, structural refactoring, comment quality, or stylistic preferences — use the `simplify` skill for those.

## Confidence Scoring

Expand Down
33 changes: 33 additions & 0 deletions ai/codex/skills/simplify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: simplify
description: Simplify recently changed code for clarity, consistency, and maintainability while preserving behavior. Use after implementation or when asked to simplify or refactor code without changing what it does.
model: opus
metadata:
execution-tier: deep
---

# Simplify

Improve recently changed code without changing its observable behavior.

## Scope

Use the files or code the user names. Otherwise, inspect the working tree and branch diff and focus on code changed for the current task. Preserve unrelated user changes and avoid broad cleanup outside that scope.

If there is no changed code and the user did not name a target, ask what code to simplify.

## Review and edit

Read the applicable repository instructions and nearby code before editing. Apply changes that materially improve clarity, consistency, or maintainability, including:

- Reduce unnecessary nesting, indirection, duplication, and special cases.
- Remove abstractions or helpers that do not earn their complexity.
- Replace names and control flow that obscure what the code does.

Prefer explicit, readable code over compressed expressions. Do not optimize for fewer lines, introduce speculative abstractions, expand the task, or change public behavior.

Apply safe improvements directly. Leave ambiguous or behavior-changing opportunities untouched and report them to the user.

## Verify

Run the most relevant formatter, linter, and focused tests for the changed code. Finish with a concise summary of material simplifications, verification performed, and anything left unchanged because it requires user judgment.
18 changes: 18 additions & 0 deletions ai/helpers/excluded-skills.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh

# Shared parser for the per-platform excluded-skills lists.
#
# The Codex installer and the skill tests read the lists through this one function. A
# looser matcher in any one of them would call a skill excluded that the installer
# still ships, and the mismatch surfaces as an unrelated assertion about a missing
# symlink.

# is_excluded_skill SKILL_NAME EXCLUSIONS_FILE
#
# Succeeds when SKILL_NAME occupies a whole line of EXCLUSIONS_FILE. Blank lines and
# lines whose first non-space character is `#` are skipped. A file that does not
# exist excludes nothing.
is_excluded_skill() {
[ -f "$2" ] || return 1
grep -Ev '^[[:space:]]*(#|$)' "$2" | grep -Fxq "$1"
}
30 changes: 30 additions & 0 deletions ai/helpers/managed-links.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,33 @@ install_managed_link() {
ln -s "$source_path" "$destination"
fi
}

# remove_managed_link DESTINATION MANAGED_PREFIX [MANAGED_PREFIX...]
#
# Delete DESTINATION when it is a symlink this repo owns, meaning its target starts
# with one of the MANAGED_PREFIX values. Prefixes are matched the same way
# install_managed_link matches them, so the two agree on what "ours" means.
#
# Returns 0 when DESTINATION is gone, whether it was removed or was never there, and
# 1 when something this repo does not own still occupies it, so callers can warn
# about what they left behind. Callers under `set -e` must invoke this in an `if` or
# `||` context; a bare call aborts on the first destination it cannot own.
remove_managed_link() {
local destination="$1"
shift
local link_target prefix
if [ -L "$destination" ]; then
link_target=$(readlink "$destination")
for prefix in "$@"; do
case "$link_target" in
"$prefix"*)
rm -f "$destination"
return 0
;;
esac
done
fi
# A dangling symlink fails -e, so both tests are needed to spot a survivor.
[ -e "$destination" ] || [ -L "$destination" ] || return 0
return 1
}
26 changes: 7 additions & 19 deletions ai/install-claude.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ uninstall_claude_config() {
# Remove CLAUDE.md symlink
if [ "$INSTALL_CLAUDE_MD" = "true" ]; then
if [ -L ~/.claude/CLAUDE.md ]; then
case "$(readlink ~/.claude/CLAUDE.md)" in
"$ZSH"/ai/AGENTS.md|"$ZSH"/ai/CLAUDE.md)
rm -f ~/.claude/CLAUDE.md
success "Removed CLAUDE.md symlink"
;;
esac
if remove_managed_link ~/.claude/CLAUDE.md "$ZSH/ai/AGENTS.md" "$ZSH/ai/CLAUDE.md"; then
success "Removed CLAUDE.md symlink"
fi
elif [ -f ~/.claude/CLAUDE.md ]; then
warning "~/.claude/CLAUDE.md is a regular file, not a symlink - skipping"
fi
Expand All @@ -29,11 +26,7 @@ uninstall_claude_config() {
if [ "$INSTALL_AGENTS" = "true" ]; then
if [ -d ~/.claude/agents ]; then
for agent in ~/.claude/agents/*.*; do
if [ -L "$agent" ]; then
case "$(readlink "$agent")" in
"$ZSH"/ai/agents/*) rm -f "$agent" ;;
esac
fi
remove_managed_link "$agent" "$ZSH/ai/agents/"
done
success "Removed agent symlinks"
fi
Expand All @@ -42,15 +35,10 @@ uninstall_claude_config() {
# Remove skill symlinks and contexts
if [ "$INSTALL_SKILLS" = "true" ]; then
if [ -d ~/.claude/skills ]; then
# The glob must not be restricted to */, or a broken symlink left behind
# by a renamed or deleted skill fails -d and survives the sweep.
for skill in ~/.claude/skills/*; do
# Remove any skill symlink, including broken ones left behind by
# a renamed or deleted skill. A dangling symlink fails -d/-e, so
# the glob must not be restricted to */ and the test must be -L.
if [ -L "$skill" ]; then
case "$(readlink "$skill")" in
"$ZSH"/ai/skills/*) rm -f "$skill" ;;
esac
fi
remove_managed_link "$skill" "$ZSH/ai/skills/"
done
success "Removed skill symlinks"
fi
Expand Down
46 changes: 22 additions & 24 deletions ai/install-codex.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
set -eu

DOTFILES_ROOT="$HOME/.dotfiles"
CODEX_EXCLUSIONS="$DOTFILES_ROOT/ai/codex/excluded-skills.txt"

# shellcheck source=/dev/null
. "$DOTFILES_ROOT/ai/helpers/output.sh"
# shellcheck source=/dev/null
. "$DOTFILES_ROOT/ai/helpers/managed-links.sh"
# shellcheck source=/dev/null
. "$DOTFILES_ROOT/ai/helpers/excluded-skills.sh"
# shellcheck source=/dev/null
. "$DOTFILES_ROOT/ai/mcp-servers.sh"

UNINSTALL=false
Expand Down Expand Up @@ -78,18 +81,10 @@ while [ $# -gt 0 ]; do
shift
done

is_excluded_skill() {
skill_name="$1"
grep -Ev '^[[:space:]]*(#|$)' "$DOTFILES_ROOT/ai/codex/excluded-skills.txt" | grep -Fxq "$skill_name"
}

remove_managed_skill_links() {
[ -d "$HOME/.agents/skills" ] || return 0
for link in "$HOME"/.agents/skills/*; do
[ -L "$link" ] || continue
case "$(readlink "$link")" in
"$DOTFILES_ROOT"/ai/skills/*) rm -f "$link" ;;
esac
remove_managed_link "$link" "$DOTFILES_ROOT/ai/skills/" "$DOTFILES_ROOT/ai/codex/skills/" || true
done
}

Expand All @@ -103,9 +98,7 @@ remove_managed_agents() {
for agent in "$HOME"/.codex/agents/*.toml; do
[ -e "$agent" ] || [ -L "$agent" ] || continue
if [ -L "$agent" ]; then
case "$(readlink "$agent")" in
"$HOME"/.codex/.dotfiles-agents/* | "$HOME"/.codex/agents/.dotfiles/*) rm -f "$agent" ;;
esac
remove_managed_link "$agent" "$HOME/.codex/.dotfiles-agents/" "$HOME/.codex/agents/.dotfiles/" || true
elif [ "$(head -n 1 "$agent")" = "$MANAGED_AGENT_HEADER" ]; then
rm -f "$agent"
fi
Expand Down Expand Up @@ -144,19 +137,24 @@ if [ "$INSTALL_SKILLS" = "true" ]; then
# excluded-skills.txt, stops being linked on an existing install.
remove_managed_skill_links
shadowed_skills=""
for skill_dir in "$DOTFILES_ROOT"/ai/skills/*/; do
[ -d "$skill_dir" ] || continue
skill_name=$(basename "$skill_dir")
destination="$HOME/.agents/skills/$skill_name"
if is_excluded_skill "$skill_name"; then
# remove_managed_skill_links only removes symlinks, so an excluded skill that
# predates the exclusion survives as a real directory and Codex keeps loading it.
if [ -e "$destination" ] || [ -L "$destination" ]; then
warning "$skill_name is excluded from Codex but $destination still exists; remove it by hand"
# ai/codex/skills/ holds skills only Codex gets, because Claude bundles its own
# under the same name. Both roots land in one destination directory, so a name may
# appear in only one of them.
for skills_root in "$DOTFILES_ROOT/ai/skills" "$DOTFILES_ROOT/ai/codex/skills"; do
for skill_dir in "$skills_root"/*/; do
[ -d "$skill_dir" ] || continue
skill_name=$(basename "$skill_dir")
destination="$HOME/.agents/skills/$skill_name"
if is_excluded_skill "$skill_name" "$CODEX_EXCLUSIONS"; then
# remove_managed_skill_links only removes symlinks, so an excluded skill that
# predates the exclusion survives as a real directory and Codex keeps loading it.
if [ -e "$destination" ] || [ -L "$destination" ]; then
warning "$skill_name is excluded from Codex but $destination still exists; remove it by hand"
fi
elif ! install_managed_link "$skill_dir" "$destination" "$skills_root/"; then
shadowed_skills="$shadowed_skills $skill_name"
fi
elif ! install_managed_link "$skill_dir" "$destination" "$DOTFILES_ROOT/ai/skills/"; then
shadowed_skills="$shadowed_skills $skill_name"
fi
done
done
if [ -n "$shadowed_skills" ]; then
error "Not linked, a directory already occupies the destination:$shadowed_skills"
Expand Down
Loading
Loading