Skip to content

feat(cli): install Claude orchestration rules globally (closes #25)#26

Merged
kLOsk merged 1 commit intomainfrom
feat/global-claude-rules
Apr 28, 2026
Merged

feat(cli): install Claude orchestration rules globally (closes #25)#26
kLOsk merged 1 commit intomainfrom
feat/global-claude-rules

Conversation

@kLOsk
Copy link
Copy Markdown
Owner

@kLOsk kLOsk commented Apr 28, 2026

Summary

Closes #25. Adds adloop install-rules, update-rules, and uninstall-rules subcommands that manage AdLoop's orchestration rules + slash commands at the user level (~/.claude/), so Claude Code sessions outside this repo inherit the same safety patterns, GAQL reference, and 43-tool guidance that Cursor users get for free via workspace rules.

The adloop init wizard now also detects Claude installations and offers a one-shot install at the end of setup.

Why

Currently, the orchestration rules and slash commands only work when:

  • Cursor is open inside this repo (workspace rules pick up .cursor/rules/).
  • Claude Code is launched from this repo (it auto-loads CLAUDE.md + .claude/).

Users running AdLoop in Claude Code from any other project or in Claude Desktop / claude.ai get raw tool access but none of the orchestration intelligence — no safety rules, no GAQL reference, no orchestration patterns. This was surfaced by @sg-modlab in #23 and the underlying context-bloat trade-off was raised by @luison in #18.

What's new

Subcommands

Command Behaviour
adloop install-rules Detect Claude installations, write a managed block to ~/.claude/CLAUDE.md, copy slash commands to ~/.claude/commands/ (prefixed adloop-*).
adloop install-rules --lazy Use lazy mode (see below).
adloop install-rules --no-commands Skip slash-command install.
adloop update-rules Refresh an existing managed block. Preserves the existing mode unless --inline/--lazy is passed.
adloop uninstall-rules Remove the managed block and adloop-* commands. User content is never touched.

Two install modes

  • inline (default) — full rules embedded between sentinel comments in ~/.claude/CLAUDE.md. Reliable; ~10K tokens loaded every Claude Code session.
  • lazy (--lazy) — short directive in CLAUDE.md pointing at ~/.claude/rules/adloop.md. The LLM reads the full file only when AdLoop tools are in scope. Cheaper baseline cost.

Idempotency

A sentinel-bracketed block is the canonical write surface:

<!-- adloop:rules:start v0.6.5 -->
... managed content ...
<!-- adloop:rules:end -->

Re-running install-rules replaces the block in place (no duplication). The version is part of the start sentinel so update-rules can detect drift across upgrades and overwrite stale blocks. Uninstall surgically strips the block and removes the adloop-* slash commands — anything outside the sentinels (the user's own CLAUDE.md content, their own slash commands) is preserved exactly.

Claude Desktop / claude.ai

Has no programmatic rules location. The installer detects it and prints copy-paste instructions (with the full rules content stripped of frontmatter, scoped between BEGIN/END markers) for the user to paste into Project settings → Custom instructions on claude.ai. Re-paste after every update-rules.

Architecture

  • src/adloop/rules/{adloop.md, commands/*.md} — bundled with the wheel. uv_build picks them up automatically (verified by building locally and unzipping the resulting wheel).

  • scripts/sync-rules.py now writes three targets from the canonical .cursor/rules/adloop.mdc:

    • .claude/rules/adloop.md (in-repo Claude Code)
    • src/adloop/rules/adloop.md (bundled with the wheel)
    • src/adloop/rules/commands/*.md (mirrored from .claude/commands/)

    Single source of truth is preserved.

  • src/adloop/rules_install.py — pure module with detect_clients, install_rules, update_rules, uninstall_rules. Frontmatter is stripped for inline mode (CLAUDE.md is itself a rules file and shouldn't contain nested frontmatter) but preserved for the lazy-mode sibling file at ~/.claude/rules/adloop.md.

  • CLI is wired through src/adloop/__init__.py:main and src/adloop/cli.py:run_rules_command. The init wizard prompts at the end if any Claude client is detected.

Tests

26 new in tests/test_rules_install.py:

  • Detection — Claude Code via dir, Claude Desktop via macOS path, no false positives on bare home.
  • Install (inline) — block + sentinels + frontmatter stripping + user-content preservation.
  • Install (lazy) — small directive (<2KB block) + sibling rules file with frontmatter intact.
  • Idempotency — repeat install produces exactly one start sentinel; reports 'updated' second time.
  • Update — preserves existing mode by default, switches mode when explicit flag passed.
  • Uninstall — removes block, removes lazy rules file, removes only adloop-* commands (user commands untouched), removes empty CLAUDE.md (but preserves it if user content remains), safe no-op when nothing installed.
  • Sentinel handling — replaces blocks written by older AdLoop versions cleanly.
  • Bundled-content access_read_bundled_rules and _list_bundled_commands work via importlib.resources.
  • CLI smokerun_rules_command install/uninstall happy paths + unknown-subcommand error.
  • Edge cases — install creates ~/.claude/ if missing, no detected clients returns empty.

Full suite: 184 passed (158 baseline + 26 new).

End-to-end smoke verification (locally, against $TMPHOME)

Verified manually before pushing:

  • ✓ inline install onto an existing CLAUDE.md preserves user content above the block
  • ✓ second install reports 'updated' and produces exactly one sentinel start
  • --lazy install writes 5-line directive in CLAUDE.md + 580-line rules file at ~/.claude/rules/adloop.md
  • update-rules without flags preserves lazy mode
  • update-rules --inline switches to full inline content (block grew from 5 to 585 lines)
  • ✓ uninstall removes block + lazy rules file + 6 namespaced commands, leaves user content intact

Test plan

  • uv run pytest — 184 pass
  • Local install/uninstall against temp HOME (see above)
  • Wheel build includes src/adloop/rules/ and src/adloop/rules/commands/
  • Reviewer: confirm adloop install-rules against your real ~/.claude/ doesn't surprise you (use adloop uninstall-rules to clean up if needed — guaranteed safe by the sentinel design)

Out of scope (good follow-ups)

  • Auto-uninstall when adloop is removed via pipx — pip uninstall hooks are unreliable; documented as a manual step instead.
  • VSCode / other IDE clients — handle when those gain native rules support.
  • Slash command name collisions — adloop-* namespace is collision-free with current command names but doesn't prevent users from later authoring an adloop-foo.md of their own. Could add a check in install if it becomes an issue.

Acceptance criteria (from #25)

  • adloop init detects Claude installations and offers global rules install
  • Idempotent install/update via sentinel block
  • adloop install-rules, update-rules, uninstall-rules subcommands (note: as standalone subcommands rather than init --update-rules flags — matches the rest of the CLI surface better)
  • Documentation in README explaining what gets installed where, and how to remove it
  • No drift: global installs always derive from the in-repo canonical rules file via the extended sync-rules.py

Adds `adloop install-rules`, `update-rules`, and `uninstall-rules`
subcommands that manage AdLoop's orchestration rules + slash commands at
the user level (~/.claude/), so Claude Code sessions outside this repo
inherit the same safety patterns, GAQL reference, and 43-tool guidance
that Cursor users get for free via workspace rules.

The init wizard now detects Claude installations and offers to install
the rules at the end of setup.

Two install modes:

- inline (default): full rules embedded in ~/.claude/CLAUDE.md between
  sentinel comments. Reliable; ~10K tokens loaded every Claude Code
  session.
- lazy (--lazy): small directive in CLAUDE.md pointing at
  ~/.claude/rules/adloop.md. Cheaper baseline cost; the LLM reads the
  full rules only when AdLoop tools are in scope.

Idempotency is handled by sentinel comments
`<!-- adloop:rules:start vX.Y.Z --> ... <!-- adloop:rules:end -->`
that include the AdLoop version, so update-rules can detect drift
across versions and replace stale blocks cleanly. uninstall-rules only
touches the managed block and adloop-* prefixed slash commands —
user-authored content is never modified.

Claude Desktop has no programmatic rules location, so it returns
manual paste-into-claude.ai instructions instead.

Architecture:

- src/adloop/rules/{adloop.md, commands/*.md} — bundled with the wheel
  via uv_build's default file inclusion (verified by building locally).
- scripts/sync-rules.py now writes three targets: in-repo Cursor format
  source -> .claude/rules/adloop.md (in-repo Claude Code) +
  src/adloop/rules/adloop.md (bundled) + .claude/commands/ ->
  src/adloop/rules/commands/. Single source of truth maintained.
- src/adloop/rules_install.py — pure module with detect_clients,
  install_rules, update_rules, uninstall_rules. Frontmatter is stripped
  for inline mode (CLAUDE.md is itself a rules file) but preserved for
  the lazy-mode sibling file.

Tests: 26 new in tests/test_rules_install.py covering detection,
inline/lazy install, idempotency, frontmatter handling, mode preservation
on update, version-mismatched block replacement, namespaced-command
isolation during uninstall, and CLI entry-point smoke tests. Full suite:
184 passed.
@kLOsk kLOsk merged commit 3d8fd4b into main Apr 28, 2026
3 checks passed
@kLOsk kLOsk deleted the feat/global-claude-rules branch April 28, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Install Claude rules globally during adloop init

1 participant