ctrl defines the rules.
shft executes them.
Every developer using Claude Code or Copilot hits the same walls. Context degrades mid-task. Instructions drift between machines. Secrets leak into the conversation. Irrelevant rules load for every project. One repo fixes all four — and hardens AFK Docker runs with per-iteration, short-lived credentials instead of long-lived token reuse.
Context degrades mid-task. The agent repeats itself, compaction loses nuance, quality drops. A fresh conversation has no idea where things left off.
Instructions drift between your laptop and VPS. Different machines run different rules. There's no single source of truth.
Secrets leak into agent context. API keys, tokens, passwords visible inside the conversation — or worse, logged somewhere they shouldn't be.
Irrelevant rules load for every project regardless of stack. Next.js rules on a PHP project. PHP rules in a Python repo. Noise drowning signal.
When context gets high, the agent writes its plan to working/.
A
fresh conversation continues exactly where the old one left off — no repetition, no lost nuance.
detect-context.sh scans your working directory. A Next.js
project
loads Next.js rules. Nothing from PHP. Nothing from Python. Only what matches.
Three tiers: config agents can see, credentials scoped to a child
process, and AFK iteration tokens (short-lived GitHub App installation tokens) minted per
loop. run-with-secrets.sh
injects
API keys into a child process — they vanish on exit. Deny rules block env,
printenv, and cat secrets/*.
Clone to ~/dotfiles on your laptop, your VPS, anywhere.
git pull updates every machine simultaneously. Bootstrap is idempotent — safe to
re-run
anytime.
/grill-me/write-a-prd/architect/prd-to-issues/do-workshft/do-work/grill-me/write-a-prd/prd-to-issues/architectskill-scaffolderexploreresearchcodebase-auditimprove-architecture/tddsystematic-debuggingatomic-commits/review/documentsanity-best-practicescompliance-auditstress-test⚡ How auto-invoke works: Every skill's description is loaded into the agent's system prompt at session start. Skills marked ⚡ have broad descriptions that match common task patterns — the agent loads them automatically when it detects a matching situation. No slash command needed. They act as passive quality guardrails: same rigorous process every time, without you remembering to ask.
+
Add private skills: skills/_local/your-skill/SKILL.md —
auto-discovered, gitignored.
Each subagent runs in an isolated context window with its own system prompt and tool restrictions. Exploration stays out of your main conversation.
Four behavioral rules in global.instructions.md, addressing the most expensive AI failure
modes: building wrong things,
overengineering, drive-by refactoring, and vague success criteria — derived from
Andrej Karpathy's observations on LLM coding
pitfalls
and adapted here for this workflow to bias toward correctness over speed.
Surface confusion before acting. Present multiple interpretations. Name assumptions explicitly. Push back when simpler exists. Clarifying questions come before implementation, not after a wrong turn.
Minimum code that solves the problem. No speculative features. No abstractions for single-use code. If 200 lines could be 50, rewrite it. Prefer straightforward solutions a senior engineer would call obvious and maintainable.
Touch only what you must. Match existing style exactly. Don't refactor what isn't broken. Every changed line traces to the user's request. Mention adjacent issues, but don't fold unrelated cleanup into the same diff.
Define success criteria. Loop until verified. Transform imperative tasks into verifiable goals with acceptance criteria at each step. Convert vague asks into outcomes that can be tested and proven.
Three tiers. Config lives in shell, run-with-secrets.sh injects credentials into a child
process only, and AFK loops use AFK iteration tokens (short-lived GitHub App installation tokens)
minted per loop.
Deny rules block any attempt to read
secrets.
| File | In Shell? | Agent-Visible? | Contains |
|---|---|---|---|
.env.agent |
YES | YES | Usernames, hosts, IDs |
.env.secrets |
NO | NO | API keys, tokens, passwords |
AFK iteration token |
NO | NO | Minted per loop, expires ~1h |
env, printenv, cat secrets/*, and
echo $*KEY* at the agent level. Agents can't accidentally inherit what they can't see.
Claude Code hooks fire on lifecycle events — before tools run, when the agent stops. Shell scripts receive JSON on stdin, return exit codes. No model judgment required.
tsc --noEmit on TypeScript projects. Blocks the agent
from
stopping until types pass.hooks/ receive JSON on stdin, check conditions, return exit code 0 (allow)
or 2 (block). Bootstrap symlinks hooks/ → ~/.claude/hooks/ and merges
configuration into ~/.claude/settings.json.
The HUD is a real-time HUD that shows which rules loaded, which skills fired, which projects are being touched, and whether compliance checks pass or fail. It runs locally and updates instantly via WebSocket.
ws://localhost:7822. Falls back to HTTP
polling. Single-file frontend — no build step.ctrl hud (or bash ~/dotfiles/bin/start-hud.sh) — then visit
localhost:7823. Hooks emit events automatically. No
configuration required.
Not a framework. A bash loop that runs Claude against your GitHub issues backlog — sandboxed in Docker for AFK mode, direct on host for HITL. Exits when the backlog is empty.
jq and srt on PATH.
ctrl checkctrl check --afkctrl bootstrapctrl syncctrl sync-settingsctrl statusctrl contextctrl hudctrl hud stopctrl hud statusctrl hud restartctrl hud logs [-f]ctrl hud eventsctrl hud violationsctrl hud statectrl hud clearctrl hud openctrl hud urlctrl hud --fgctrl new-clientctrl migratectrl uninstallctrl verify-tokenctrl retroctrl digestctrl costctrl ask "question"ctrl learnctrl versionctrl helpshft runshft afk [n]shft afk --forceshft statusshft stopshft log [-f]shft issuesshft nextshft done [n]shft contextshft planshft plan editshft plan clearshft validateshft mintshft promptshft versionshft helpYou shouldn't adopt anything that touches your global config without knowing exactly what it does, what it writes, and how to get out. Here's everything.
Dotfiles are configuration files in your home directory — named with a leading dot
(.bashrc, .gitconfig) so they're hidden by default. Developers
store them in a git repo and symlink them into place, so their entire environment syncs
across every machine with a git pull.
Claude Code follows the same convention: it looks for CLAUDE.md and
.claude/ in your home directory for global instructions, skills, and agents
that apply to every project. That makes ~/dotfiles the natural home — bootstrap
symlinks everything into place so your agent gets the same context on your laptop, VPS, or a
fresh machine.
Yes. Run the uninstall command:
Or directly:
bash ~/dotfiles/bin/uninstall.sh
It removes only the symlinks and shell integration that bootstrap
created — nothing in your project repos, nothing else in ~/dotfiles. If you had
dotfiles there before installing ctrl+shft, they're untouched.
No. Since you fork before cloning, your fork is your source of truth. Stop pulling upstream
whenever you want. The tool keeps working exactly as-is — no upstream dependency, no
breaking changes you didn't ask for. And if you want out entirely,
ctrl uninstall
cleans it up in one command.
No. Everything installs into your home directory — ~/dotfiles,
~/.claude/, ~/.bashrc. No system-level writes, no elevated
permissions required.
CLAUDE.md — will this overwrite it?Depends where it lives. A CLAUDE.md in a project repo is untouched —
project-level config always takes precedence over global. If you already have
~/.claude/CLAUDE.md, bootstrap replaces it with a symlink (printing a
message). Back it up first and fold your content into CLAUDE.base.md.
Use --adopt to merge your existing config.
Full footprint from bootstrap:
uninstall.sh reverses all of it.
A PAT works for HITL mode. AFK mode structurally blocks the PAT path — the validator enforces this at startup, so it's not just discouraged, it's unavailable.
The reason: static credentials create long-lived compromise windows across runs. Instead, a three-tier model keeps secrets out of agent context entirely:
run-with-secrets.sh — never exported to shell
It runs inside a Docker sandbox with the minimum GitHub App permissions you configure: Contents, Issues, and Pull Requests (read/write). It cannot access other repos, org settings, or anything outside that scope.
Deny rules block it from reading secrets or env vars — even ones technically in scope.
env, printenv, cat secrets/*, and
echo $*KEY* are all blocked at the agent level.
Run ctrl context in any directory — it prints the active contexts
and which rule files are loaded. Narrow-scope skills are invoked explicitly.
Skills marked ⚡ auto-invoke (like systematic-debugging and compliance-audit)
are loaded automatically when the agent detects a matching pattern.
Yes. detect-context.sh only loads rules matching your current stack — it doesn't
clobber existing config. If you already have a CLAUDE.md in a project repo,
that takes precedence. It stays out of your way.
Currently optimized for Claude Code — that's where CLAUDE.md, skills, and agents
are natively picked up. The rules and principles are plain markdown, so nothing stops you
referencing them in your own API prompts, but the automatic loading and context detection
are Claude Code features.
HITL mode works on any Claude plan. AFK mode — autonomous loops running potentially hundreds of turns overnight — is designed for Claude Max where usage limits won't interrupt a run mid-task. Pro limits will likely cut a long AFK session short.
For normal HITL use, minimal — skills only load when invoked, not on every prompt. For AFK loops, burn scales with backlog size and task complexity. A well-scoped issue on a mid-size codebase typically runs 20–50k tokens. A backlog of 10 issues could be 200–500k tokens overnight.
Monitor your first few runs to baseline before going wide.
Your personal config lives in gitignored files: secrets/.env.agent,
secrets/.env.secrets, skills/_local/, and
instructions/_local/. None are tracked, so git pull upstream main
never touches them.
git pull?
Symlinks survive pulls — your Claude setup keeps working. The main thing that goes stale is
CLAUDE.md if CLAUDE.base.md changed upstream. Bootstrap
regenerates it. It's idempotent, so re-running is always safe.
"Read X" confirms a rule was loaded into context — not that it was followed. The
compliance-audit skill closes the gap: after each task it reviews the diff
against every active rule, logs pass/fail/warn to working/compliance-log.md,
and the HUD surfaces compliance rates across sessions in real time.
Run /stress-test for adversarial verification before deploying in a team
environment. Current compliance on well-formed tasks: ~85–90%, with known failure modes
documented and actively tracked.
# Fork at github.com/arndvs/ctrlshft/fork, then: git clone https://github.com/YOUR_USERNAME/ctrlshft.git ~/dotfiles
bash ~/dotfiles/bin/bootstrap.sh # after install: ctrl bootstrap
~/.claude/settings.json.
Creates secrets from templates. Wires shell integration. Installs Python deps into
secrets/.venv (including PyJWT for AFK GitHub App token
minting).
github.com/settings/apps/new.https://github.com/<you>/dotfiles).github.com/settings/installations/<id>.$EDITOR ~/dotfiles/secrets/.env.agent # non-sensitive config $EDITOR ~/dotfiles/secrets/.env.secrets # process-scoped credentials # Required for AFK credential rotation: GITHUB_APP_ID=... GITHUB_APP_INSTALLATION_ID=... GITHUB_APP_PRIVATE_KEY_B64=...
base64 -w 0 ~/Downloads/your-app-key.pem) and place all three values in
secrets/.env.secrets.
bash ~/dotfiles/bin/validate-env.sh --afk bash ~/dotfiles/bin/verify-github-app-token.sh # after shell reload: ctrl check --afk && ctrl verify-token
mint_success=yes,
expires_at=..., token_len=40.
python3 opens Microsoft
Store, AFK scripts use secrets/.venv Python first. Re-run bootstrap.sh
if venv is missing.PyJWT or
requests, re-run bash ~/dotfiles/bin/bootstrap.sh.
GITHUB_APP_PRIVATE_KEY_B64, then rerun mint
verification.bash ~/dotfiles/bin/sync-settings.sh # after shell reload: ctrl sync-settings
source ~/.bashrc # bash source ~/.zshrc # zsh