@softspark/ai-toolkit 4.10.1 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +12 -13
- package/app/.claude-plugin/plugin.json +1 -1
- package/benchmarks/ecosystem-doctor-snapshot.json +18 -12
- package/bin/ai-toolkit.js +1 -1
- package/kb/procedures/maintenance-sop.md +5 -3
- package/kb/reference/architecture-overview.md +5 -4
- package/kb/reference/codex-cli-compatibility.md +15 -10
- package/kb/reference/extension-api.md +5 -5
- package/kb/reference/global-install-model.md +31 -11
- package/kb/reference/integrations.md +16 -17
- package/kb/reference/language-rules.md +35 -14
- package/kb/reference/supported-tools-registry.md +34 -25
- package/llms-full.txt +146 -89
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/ecosystem_tools.json +17 -6
- package/scripts/generate_antigravity.py +15 -0
- package/scripts/generate_codex_hooks.py +8 -5
- package/scripts/generate_copilot.py +10 -3
- package/scripts/generate_language_rules_skills.py +4 -2
- package/scripts/inject_hook_cli.py +16 -2
- package/scripts/install.py +5 -5
- package/scripts/install_steps/ai_tools.py +205 -36
- package/scripts/install_steps/install_state.py +9 -2
- package/scripts/install_steps/markers.py +132 -14
- package/scripts/plugin.py +31 -8
- package/scripts/remove_rule.py +15 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"description": "Authoritative registry of tools ai-toolkit integrates with. Consumed by scripts/ecosystem_doctor.py to detect upstream doc/version drift.",
|
|
4
|
-
"last_updated": "2026-06-
|
|
4
|
+
"last_updated": "2026-06-30",
|
|
5
5
|
"tools": [
|
|
6
6
|
{
|
|
7
7
|
"id": "claude-code",
|
|
@@ -226,7 +226,10 @@
|
|
|
226
226
|
".gemini/extensions/gemini-extension.json"
|
|
227
227
|
],
|
|
228
228
|
"our_generators": [
|
|
229
|
-
"scripts/generate_gemini.py"
|
|
229
|
+
"scripts/generate_gemini.py",
|
|
230
|
+
"scripts/generate_gemini_hooks.py",
|
|
231
|
+
"scripts/generate_gemini_commands.py",
|
|
232
|
+
"scripts/generate_gemini_skills.py"
|
|
230
233
|
],
|
|
231
234
|
"capability_markers": [
|
|
232
235
|
"GEMINI.md",
|
|
@@ -310,18 +313,23 @@
|
|
|
310
313
|
"kind": "editor",
|
|
311
314
|
"urls": {
|
|
312
315
|
"docs": "https://roocodeinc.github.io/Roo-Code",
|
|
313
|
-
"release_notes": "https://github.com/
|
|
316
|
+
"release_notes": "https://github.com/Zoo-Code-Org/Zoo-Code/releases",
|
|
314
317
|
"changelog": null
|
|
315
318
|
},
|
|
316
|
-
"status": "
|
|
317
|
-
"status_note": "Upstream RooCodeInc/Roo-Code
|
|
319
|
+
"status": "succeeded-by-fork",
|
|
320
|
+
"status_note": "Upstream RooCodeInc/Roo-Code archived/read-only 2026-05-15, frozen at v3.54.0; its releases feed is dead. Successor project by former Roo contributors: Zoo-Code-Org/Zoo-Code (marketplace ZooCodeOrganization.zoo-code, docs.zoocode.dev), active through v3.64.0 (2026-06-26) continuing Roo's version line. Config compat is total: same .roomodes (JSON 'still fully supported and won't be deprecated'), .roo/rules/, .roo/rules-{slug}/, .roo/skills/, .roo/commands/, ~/.roo/* paths — both generators remain valid. Watch Zoo GitHub releases as the authoritative feed (docs.zoocode.dev/update-notes is stale at v3.50.0). Skills (~/.roo/skills, ~/.agents/skills) shipped in v3.46.0/v3.47.2; ai-toolkit now installs them globally via ~/.agents/skills. Roo team pivoted to roomote.dev and recommended Cline.",
|
|
318
321
|
"config_paths": [
|
|
319
322
|
".roomodes",
|
|
320
323
|
".roo/rules/*.md",
|
|
321
324
|
".roo/rules-{slug}/*.md",
|
|
325
|
+
".roo/skills/*/SKILL.md",
|
|
326
|
+
"~/.roo/skills/*/SKILL.md",
|
|
327
|
+
".agents/skills/*/SKILL.md",
|
|
328
|
+
"~/.agents/skills/*",
|
|
329
|
+
".roo/commands/*.md",
|
|
330
|
+
"~/.roo/commands/*.md",
|
|
322
331
|
".roo/mcp.json",
|
|
323
332
|
"~/.roo/rules/",
|
|
324
|
-
"~/.roo/custom_modes.yaml",
|
|
325
333
|
"mcp_settings.json"
|
|
326
334
|
],
|
|
327
335
|
"our_generators": [
|
|
@@ -486,8 +494,11 @@
|
|
|
486
494
|
},
|
|
487
495
|
"config_paths": [
|
|
488
496
|
"AGENTS.md",
|
|
497
|
+
"~/.codex/AGENTS.md",
|
|
498
|
+
"~/.codex/AGENTS.override.md",
|
|
489
499
|
".agents/skills/*/SKILL.md",
|
|
490
500
|
".codex/hooks.json",
|
|
501
|
+
"~/.codex/hooks.json",
|
|
491
502
|
".codex/config.toml",
|
|
492
503
|
"~/.codex/config.toml"
|
|
493
504
|
],
|
|
@@ -99,6 +99,21 @@ def _write_skill_pointer(target_dir: Path) -> None:
|
|
|
99
99
|
# Main
|
|
100
100
|
# ---------------------------------------------------------------------------
|
|
101
101
|
|
|
102
|
+
def generate_global(target_dir: Path) -> None:
|
|
103
|
+
"""Write the skill pointer to Antigravity's documented HOME-scoped skill
|
|
104
|
+
dirs. ``~/.gemini/config/skills/`` is shared across all Antigravity products
|
|
105
|
+
(IDE, editor, CLI); ``~/.gemini/antigravity-cli/skills/`` is CLI-private.
|
|
106
|
+
Antigravity RULES have no documented global file surface, so only the skill
|
|
107
|
+
pointer is emitted globally (rules stay project-local).
|
|
108
|
+
"""
|
|
109
|
+
content = _pointer_skill_md()
|
|
110
|
+
for rel in (".gemini/config/skills", ".gemini/antigravity-cli/skills"):
|
|
111
|
+
skill_dir = target_dir / rel / POINTER_SKILL_NAME
|
|
112
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
(skill_dir / "SKILL.md").write_text(content, encoding="utf-8")
|
|
114
|
+
print(f" Generated: {rel}/{POINTER_SKILL_NAME}/SKILL.md")
|
|
115
|
+
|
|
116
|
+
|
|
102
117
|
def generate(target_dir: Path, *,
|
|
103
118
|
language_modules: list[str] | None = None,
|
|
104
119
|
rules_dir: Path | None = None,
|
|
@@ -8,11 +8,14 @@ Codex exposes 10 lifecycle events (PascalCase in config.toml / hooks.json):
|
|
|
8
8
|
``PreToolUse``, ``PostToolUse``, ``PermissionRequest``, ``PreCompact``,
|
|
9
9
|
``PostCompact``, ``SessionStart``, ``UserPromptSubmit``, ``SubagentStart``,
|
|
10
10
|
``SubagentStop``, ``Stop``. PreToolUse/PostToolUse only support the ``Bash``
|
|
11
|
-
matcher. We wire
|
|
12
|
-
hook scripts, mirroring the Claude Code mapping in ``app/hooks.json``.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
matcher. We wire 9 of the 10 events in ``CODEX_HOOKS`` below to the shared
|
|
12
|
+
toolkit hook scripts, mirroring the Claude Code mapping in ``app/hooks.json``.
|
|
13
|
+
``PostCompact`` is intentionally unwired (its only hook was the removed
|
|
14
|
+
environment-snapshot probe).
|
|
15
|
+
|
|
16
|
+
Handler types in Codex: ``command`` (what we emit). ``prompt`` and ``agent``
|
|
17
|
+
are parsed by Codex but not yet executed, so hand-authored handlers of those
|
|
18
|
+
types are inert. Reference: codex-rs/config/src/hook_config.rs.
|
|
16
19
|
|
|
17
20
|
Usage:
|
|
18
21
|
python3 scripts/generate_codex_hooks.py [target-dir]
|
|
@@ -228,17 +228,24 @@ def generate(target_dir: Path, *,
|
|
|
228
228
|
language_modules: list[str] | None = None,
|
|
229
229
|
rules_dir: Path | None = None,
|
|
230
230
|
emit_prompts: bool = True,
|
|
231
|
-
emit_instructions: bool = True
|
|
231
|
+
emit_instructions: bool = True,
|
|
232
|
+
config_root: Path | None = None) -> None:
|
|
232
233
|
"""Write Copilot path-specific instructions and prompt files.
|
|
233
234
|
|
|
235
|
+
By default writes to ``<target_dir>/.github/`` (project-local). Pass
|
|
236
|
+
``config_root=~/.copilot`` for the Copilot CLI user-level global layout,
|
|
237
|
+
where instructions land in ``~/.copilot/instructions/*.instructions.md``.
|
|
238
|
+
|
|
234
239
|
``.github/copilot-instructions.md`` is intentionally not written here —
|
|
235
240
|
the legacy ``main()`` entry point still emits it to stdout so existing
|
|
236
241
|
scripts (including ``ai-toolkit install``) keep working unchanged.
|
|
237
242
|
"""
|
|
238
243
|
github_dir = target_dir / ".github"
|
|
244
|
+
instr_root = config_root if config_root is not None else github_dir
|
|
245
|
+
instr_label = "~/.copilot/instructions" if config_root is not None else ".github/instructions"
|
|
239
246
|
|
|
240
247
|
if emit_instructions:
|
|
241
|
-
instr_dir =
|
|
248
|
+
instr_dir = instr_root / "instructions"
|
|
242
249
|
instr_dir.mkdir(parents=True, exist_ok=True)
|
|
243
250
|
|
|
244
251
|
instruction_files: dict[str, callable] = dict(_make_instruction_files())
|
|
@@ -269,7 +276,7 @@ def generate(target_dir: Path, *,
|
|
|
269
276
|
|
|
270
277
|
for name, content_fn in instruction_files.items():
|
|
271
278
|
(instr_dir / name).write_text(content_fn(), encoding="utf-8")
|
|
272
|
-
print(f" Generated:
|
|
279
|
+
print(f" Generated: {instr_label}/{name}")
|
|
273
280
|
|
|
274
281
|
if emit_prompts:
|
|
275
282
|
prompt_dir = github_dir / "prompts"
|
|
@@ -10,8 +10,10 @@ This is the proper progressive-disclosure replacement for the v1.3.8 pointer
|
|
|
10
10
|
block in ``.claude/CLAUDE.md``: instead of nudging Claude to Read absolute
|
|
11
11
|
nvm-pinned paths on demand, the rules ride on the Agent Skills mechanism.
|
|
12
12
|
|
|
13
|
-
Common rules (``app/rules/common/``)
|
|
14
|
-
|
|
13
|
+
Common rules (``app/rules/common/``) are installed as Claude Code
|
|
14
|
+
path-scoped ``.claude/rules/ai-toolkit-*.md`` files by ``install --local``.
|
|
15
|
+
This keeps ``CLAUDE.md`` below Claude's current size guidance while still
|
|
16
|
+
loading common guidance when project files are opened.
|
|
15
17
|
|
|
16
18
|
Idempotent: rerunning overwrites generated SKILL.md but leaves any other
|
|
17
19
|
files in the skill directory alone.
|
|
@@ -49,8 +49,22 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
49
49
|
# Protected source tag -- this CLI must never touch ai-toolkit's own entries.
|
|
50
50
|
PROTECTED_SOURCE = "ai-toolkit"
|
|
51
51
|
|
|
52
|
-
# Codex CLI
|
|
53
|
-
|
|
52
|
+
# Codex CLI's HookEventName enum defines 10 events; we propagate the 9 that
|
|
53
|
+
# generate_codex_hooks.py also wires (all except PostCompact, whose only hook
|
|
54
|
+
# was the removed environment-snapshot probe). Keeping this in sync with the
|
|
55
|
+
# generator prevents injected custom hooks from silently losing events Codex
|
|
56
|
+
# supports.
|
|
57
|
+
CODEX_EVENTS = {
|
|
58
|
+
"SessionStart",
|
|
59
|
+
"PreToolUse",
|
|
60
|
+
"PostToolUse",
|
|
61
|
+
"PermissionRequest",
|
|
62
|
+
"UserPromptSubmit",
|
|
63
|
+
"SubagentStart",
|
|
64
|
+
"SubagentStop",
|
|
65
|
+
"PreCompact",
|
|
66
|
+
"Stop",
|
|
67
|
+
}
|
|
54
68
|
|
|
55
69
|
|
|
56
70
|
# ---------------------------------------------------------------------------
|
package/scripts/install.py
CHANGED
|
@@ -9,7 +9,7 @@ Claude Code (~/.claude/):
|
|
|
9
9
|
- Per-file symlinks: agents/*.md, skills/*/ (merges with user files)
|
|
10
10
|
- Merged JSON: hooks.json (toolkit entries tagged with _source)
|
|
11
11
|
- Marker injection: constitution.md, ARCHITECTURE.md (preserves user content)
|
|
12
|
-
-
|
|
12
|
+
- User-level rules: rules/ai-toolkit-*.md plus compact CLAUDE.md index
|
|
13
13
|
|
|
14
14
|
Other tools (global config locations):
|
|
15
15
|
- Windsurf: ~/.codeium/windsurf/memories/global_rules.md + ~/.codeium/windsurf/skills/
|
|
@@ -18,10 +18,10 @@ Other tools (global config locations):
|
|
|
18
18
|
- Roo Code: ~/.roo/rules/
|
|
19
19
|
- Aider: ~/.aider.conf.yml (created only if absent)
|
|
20
20
|
- Augment: ~/.augment/rules/ai-toolkit.md
|
|
21
|
-
- Codex:
|
|
21
|
+
- Codex: ~/.codex/AGENTS.md, ~/.agents/skills/, ~/.codex/hooks.json
|
|
22
22
|
- opencode: ~/.config/opencode/
|
|
23
23
|
|
|
24
|
-
Registered rules (~/.softspark/ai-toolkit/rules/*.md) are also
|
|
24
|
+
Registered rules (~/.softspark/ai-toolkit/rules/*.md) are also synced into
|
|
25
25
|
all of the above. Add rules with: ai-toolkit add-rule <rule.md>
|
|
26
26
|
|
|
27
27
|
Usage:
|
|
@@ -412,7 +412,7 @@ def print_summary(local: bool = False) -> None:
|
|
|
412
412
|
else:
|
|
413
413
|
print()
|
|
414
414
|
print("Next steps:")
|
|
415
|
-
print(" 1.
|
|
415
|
+
print(" 1. Add hand-written global rules under ~/.claude/rules/ (avoid ai-toolkit-* prefix)")
|
|
416
416
|
print(" 2. Per project: ai-toolkit install --local --editors all")
|
|
417
417
|
print(" 3. To update: npm install -g @softspark/ai-toolkit@latest && ai-toolkit update")
|
|
418
418
|
print(" 4. To register rules from other tools: ai-toolkit add-rule <rule.md>")
|
|
@@ -750,7 +750,7 @@ def main() -> None:
|
|
|
750
750
|
global_eds = get_global_editors() or None
|
|
751
751
|
|
|
752
752
|
installed_eds = install_ai_tools(target_dir, rules_dir, dry_run,
|
|
753
|
-
editors=global_eds)
|
|
753
|
+
editors=global_eds, profile=profile)
|
|
754
754
|
install_persona(target_dir, persona, dry_run)
|
|
755
755
|
install_strict_git_hooks(profile, local, dry_run)
|
|
756
756
|
|
|
@@ -20,12 +20,16 @@ from injection import (
|
|
|
20
20
|
|
|
21
21
|
def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
22
22
|
dry_run: bool,
|
|
23
|
-
editors: list[str] | None = None
|
|
23
|
+
editors: list[str] | None = None,
|
|
24
|
+
profile: str = "standard") -> list[str]:
|
|
24
25
|
"""Install global editor configs.
|
|
25
26
|
|
|
26
27
|
Args:
|
|
27
28
|
editors: Explicit list of editors to install globally. If None,
|
|
28
29
|
uses DEFAULT_GLOBAL_EDITORS (empty = Claude only).
|
|
30
|
+
profile: Install profile. Gates each editor's optional native surfaces
|
|
31
|
+
the same way the local install does — hooks at
|
|
32
|
+
standard/strict/full, sub-agents/commands/skills at full.
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
List of editors that were actually installed (for state tracking).
|
|
@@ -43,6 +47,10 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
43
47
|
if not eds:
|
|
44
48
|
return []
|
|
45
49
|
|
|
50
|
+
_profile = profile if profile in {"minimal", "standard", "strict", "full"} else "standard"
|
|
51
|
+
add_hooks = _profile in {"standard", "strict", "full"}
|
|
52
|
+
add_native_surfaces = _profile == "full"
|
|
53
|
+
|
|
46
54
|
print()
|
|
47
55
|
print("## Other AI Tools (global)")
|
|
48
56
|
print()
|
|
@@ -57,6 +65,7 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
57
65
|
if dry_run:
|
|
58
66
|
print(" Would inject: ~/.codeium/windsurf/memories/global_rules.md")
|
|
59
67
|
print(" Would generate: ~/.codeium/windsurf/skills/ai-toolkit-skill-catalogue/SKILL.md")
|
|
68
|
+
print(" Would inject: ~/.config/devin/AGENTS.md (Devin CLI global rules)")
|
|
60
69
|
else:
|
|
61
70
|
_install_windsurf_global(target_dir, rules_dir)
|
|
62
71
|
installed.append("windsurf")
|
|
@@ -65,16 +74,37 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
65
74
|
gemini_file = target_dir / ".gemini" / "GEMINI.md"
|
|
66
75
|
if dry_run:
|
|
67
76
|
print(" Would inject: ~/.gemini/GEMINI.md")
|
|
77
|
+
if add_hooks:
|
|
78
|
+
print(" Would generate: ~/.gemini/settings.json (hooks)")
|
|
79
|
+
if add_native_surfaces:
|
|
80
|
+
print(" Would generate: ~/.gemini/commands/, ~/.gemini/skills/")
|
|
68
81
|
else:
|
|
69
82
|
inject_with_rules("generate-gemini.sh", gemini_file, rules_dir)
|
|
83
|
+
# Hooks (~/.gemini/settings.json), commands (~/.gemini/commands/),
|
|
84
|
+
# and the skills pointer (~/.gemini/skills/) are documented user-tier
|
|
85
|
+
# surfaces — install them globally, gated like the local install.
|
|
86
|
+
if add_hooks:
|
|
87
|
+
_try_generator("generate_gemini_hooks", target_dir)
|
|
88
|
+
if add_native_surfaces:
|
|
89
|
+
_try_generator("generate_gemini_commands", target_dir)
|
|
90
|
+
_try_generator("generate_gemini_skills", target_dir)
|
|
70
91
|
installed.append("gemini")
|
|
71
92
|
|
|
72
93
|
if "augment" in eds:
|
|
73
94
|
augment_file = target_dir / ".augment" / "rules" / "ai-toolkit.md"
|
|
74
95
|
if dry_run:
|
|
75
96
|
print(" Would inject: ~/.augment/rules/ai-toolkit.md")
|
|
97
|
+
if add_native_surfaces:
|
|
98
|
+
print(" Would generate: ~/.augment/agents/, ~/.augment/commands/, ~/.augment/settings.json (hooks)")
|
|
76
99
|
else:
|
|
77
100
|
inject_with_rules("generate-augment.sh", augment_file, rules_dir)
|
|
101
|
+
# Sub-agents, commands, and hooks all have documented user-tier
|
|
102
|
+
# surfaces under ~/.augment — a global-only Augment user otherwise
|
|
103
|
+
# gets no hooks/agents/commands. Gate them like the local install.
|
|
104
|
+
if add_native_surfaces:
|
|
105
|
+
_try_generator("generate_augment_agents", target_dir)
|
|
106
|
+
_try_generator("generate_augment_commands", target_dir)
|
|
107
|
+
_try_generator("generate_augment_hooks", target_dir)
|
|
78
108
|
installed.append("augment")
|
|
79
109
|
|
|
80
110
|
if "cline" in eds:
|
|
@@ -88,8 +118,15 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
88
118
|
if "roo" in eds:
|
|
89
119
|
if dry_run:
|
|
90
120
|
print(" Would generate: ~/.roo/rules/ai-toolkit-*.md")
|
|
121
|
+
if "codex" not in eds:
|
|
122
|
+
print(" Would generate: ~/.agents/skills/* (Roo native skill discovery)")
|
|
91
123
|
else:
|
|
92
124
|
_install_roo_global(target_dir, rules_dir)
|
|
125
|
+
# Roo/Zoo Code natively discover skills from ~/.agents/skills. Reuse
|
|
126
|
+
# the shared installer, but skip when codex is also selected — its
|
|
127
|
+
# branch populates the same directory — to avoid a duplicate pass.
|
|
128
|
+
if "codex" not in eds:
|
|
129
|
+
_install_codex_skills(target_dir)
|
|
93
130
|
installed.append("roo")
|
|
94
131
|
|
|
95
132
|
if "aider" in eds:
|
|
@@ -101,7 +138,7 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
101
138
|
|
|
102
139
|
if "codex" in eds:
|
|
103
140
|
if dry_run:
|
|
104
|
-
print(" Would inject:
|
|
141
|
+
print(" Would inject: ~/.codex/AGENTS.md, ~/.agents/skills/, ~/.codex/hooks.json")
|
|
105
142
|
else:
|
|
106
143
|
_install_codex_global(target_dir, rules_dir)
|
|
107
144
|
installed.append("codex")
|
|
@@ -114,28 +151,85 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
114
151
|
_install_opencode_global(target_dir, rules_dir)
|
|
115
152
|
installed.append("opencode")
|
|
116
153
|
|
|
154
|
+
if "cursor" in eds:
|
|
155
|
+
# Cursor RULES have no mergeable global file surface (Settings UI only),
|
|
156
|
+
# but ~/.cursor/hooks.json is a documented user-level hooks scope — so a
|
|
157
|
+
# global Cursor install activates the safety/quality hooks everywhere.
|
|
158
|
+
if dry_run:
|
|
159
|
+
if add_hooks:
|
|
160
|
+
print(" Would generate: ~/.cursor/hooks.json (global hooks)")
|
|
161
|
+
elif add_hooks:
|
|
162
|
+
_try_generator("generate_cursor_hooks", target_dir)
|
|
163
|
+
installed.append("cursor")
|
|
164
|
+
|
|
165
|
+
if "copilot" in eds:
|
|
166
|
+
# Copilot CLI reads user-level instructions from ~/.copilot/. RULES have
|
|
167
|
+
# a documented global surface here even though .github/ stays repo-only.
|
|
168
|
+
copilot_root = target_dir / ".copilot"
|
|
169
|
+
if dry_run:
|
|
170
|
+
print(" Would inject: ~/.copilot/copilot-instructions.md")
|
|
171
|
+
print(" Would generate: ~/.copilot/instructions/ai-toolkit-*.instructions.md")
|
|
172
|
+
else:
|
|
173
|
+
inject_with_rules(
|
|
174
|
+
"generate_copilot.py",
|
|
175
|
+
copilot_root / "copilot-instructions.md",
|
|
176
|
+
rules_dir,
|
|
177
|
+
)
|
|
178
|
+
_try_generator("generate_copilot", target_dir,
|
|
179
|
+
rules_dir=rules_dir, config_root=copilot_root,
|
|
180
|
+
emit_prompts=False)
|
|
181
|
+
installed.append("copilot")
|
|
182
|
+
|
|
183
|
+
if "antigravity" in eds:
|
|
184
|
+
# Antigravity RULES stay project-local, but the skill pointer has a
|
|
185
|
+
# documented global surface: ~/.gemini/config/skills (all Antigravity
|
|
186
|
+
# products) and ~/.gemini/antigravity-cli/skills (CLI-private).
|
|
187
|
+
if dry_run:
|
|
188
|
+
print(" Would generate: ~/.gemini/config/skills/, ~/.gemini/antigravity-cli/skills/ (skill pointer)")
|
|
189
|
+
else:
|
|
190
|
+
from generate_antigravity import generate_global as gen_antigravity_global
|
|
191
|
+
gen_antigravity_global(target_dir)
|
|
192
|
+
installed.append("antigravity")
|
|
193
|
+
|
|
117
194
|
print()
|
|
118
195
|
print(f" Available: {', '.join(GLOBAL_CAPABLE_EDITORS)}")
|
|
119
|
-
print(" Note: Cursor
|
|
196
|
+
print(" Note: Cursor and Antigravity RULES stay project-local (no mergeable "
|
|
197
|
+
"global file surface); their global installs cover hooks/skills only. "
|
|
198
|
+
"Use 'ai-toolkit install --local' for full per-project setup.")
|
|
120
199
|
|
|
121
200
|
return installed
|
|
122
201
|
|
|
123
202
|
|
|
124
203
|
def _install_codex_global(target_dir: Path, rules_dir: Path) -> None:
|
|
125
|
-
"""Install Codex at the global level (
|
|
204
|
+
"""Install Codex at the global level (~/.codex layer).
|
|
126
205
|
|
|
127
206
|
Creates:
|
|
128
|
-
-
|
|
129
|
-
Codex reads instructions
|
|
130
|
-
|
|
207
|
+
- ~/.codex/AGENTS.md (marker injection; universal coding rules inlined
|
|
208
|
+
here). Codex reads GLOBAL instructions from ``$CODEX_HOME/AGENTS.md``
|
|
209
|
+
(default ~/.codex/AGENTS.md), NOT ~/AGENTS.md — a home-root AGENTS.md
|
|
210
|
+
is only loaded in the degenerate case where a session's cwd is $HOME.
|
|
211
|
+
- ~/.agents/skills/* (skill symlinks; a documented Codex skill dir)
|
|
131
212
|
- ~/.codex/hooks.json (lifecycle hooks)
|
|
132
213
|
"""
|
|
133
214
|
inject_with_rules(
|
|
134
215
|
"generate_codex.py",
|
|
135
|
-
target_dir / "AGENTS.md",
|
|
216
|
+
target_dir / ".codex" / "AGENTS.md",
|
|
136
217
|
rules_dir,
|
|
137
218
|
)
|
|
138
219
|
|
|
220
|
+
# Migration: earlier versions wrote global instructions to ~/AGENTS.md,
|
|
221
|
+
# which Codex never loads as global instructions. Strip that stale toolkit
|
|
222
|
+
# section so upgraders are not left with dead, unread content.
|
|
223
|
+
if _strip_toolkit_sections(target_dir / "AGENTS.md"):
|
|
224
|
+
print(" Migrated: removed stale ai-toolkit section from ~/AGENTS.md")
|
|
225
|
+
|
|
226
|
+
override = target_dir / ".codex" / "AGENTS.override.md"
|
|
227
|
+
if override.is_file() and override.read_text(encoding="utf-8").strip():
|
|
228
|
+
print(
|
|
229
|
+
" Warning: ~/.codex/AGENTS.override.md exists and takes precedence "
|
|
230
|
+
"over ~/.codex/AGENTS.md — toolkit rules will be masked."
|
|
231
|
+
)
|
|
232
|
+
|
|
139
233
|
from generate_codex_hooks import generate as gen_codex_hooks
|
|
140
234
|
gen_codex_hooks(target_dir)
|
|
141
235
|
print(" Created: ~/.codex/hooks.json")
|
|
@@ -166,6 +260,13 @@ def _install_windsurf_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
166
260
|
windsurf_file = target_dir / ".codeium" / "windsurf" / "memories" / "global_rules.md"
|
|
167
261
|
inject_with_rules("generate-windsurf.sh", windsurf_file, rules_dir)
|
|
168
262
|
|
|
263
|
+
# Devin CLI reads global rules from ~/.config/devin/AGENTS.md, NOT the
|
|
264
|
+
# Desktop ~/.codeium/windsurf/memories/global_rules.md (that path is absent
|
|
265
|
+
# from read_config_from.windsurf). Cover editor-only Devin CLI installs
|
|
266
|
+
# where no global Claude install (~/.claude/CLAUDE.md compat read) exists.
|
|
267
|
+
devin_agents = target_dir / ".config" / "devin" / "AGENTS.md"
|
|
268
|
+
inject_with_rules("generate-windsurf.sh", devin_agents, rules_dir)
|
|
269
|
+
|
|
169
270
|
from generate_windsurf_skills import generate as gen_windsurf_skills
|
|
170
271
|
# Windsurf is excluded from the .claude/skills conditional: its native scan
|
|
171
272
|
# of .claude/skills is gated behind a Devin "Claude Code config reading"
|
|
@@ -352,6 +453,35 @@ def inject_with_rules(
|
|
|
352
453
|
print(f" Updated: {target_file}")
|
|
353
454
|
|
|
354
455
|
|
|
456
|
+
def _strip_toolkit_sections(target_file: Path) -> bool:
|
|
457
|
+
"""Remove ai-toolkit ``<!-- TOOLKIT:* -->`` marker sections from an existing
|
|
458
|
+
file, preserving any non-toolkit content. Returns True if the file changed.
|
|
459
|
+
|
|
460
|
+
Used to clean a stale ~/AGENTS.md after Codex global instructions moved to
|
|
461
|
+
~/.codex/AGENTS.md. If stripping leaves the file empty (it only ever held
|
|
462
|
+
toolkit content), the file is removed.
|
|
463
|
+
"""
|
|
464
|
+
if not target_file.is_file():
|
|
465
|
+
return False
|
|
466
|
+
import re
|
|
467
|
+
|
|
468
|
+
original = target_file.read_text(encoding="utf-8")
|
|
469
|
+
stripped = re.sub(
|
|
470
|
+
r"<!-- TOOLKIT:[^ ]+ START -->.*?<!-- TOOLKIT:[^ ]+ END -->\n?",
|
|
471
|
+
"",
|
|
472
|
+
original,
|
|
473
|
+
flags=re.DOTALL,
|
|
474
|
+
)
|
|
475
|
+
if stripped == original:
|
|
476
|
+
return False
|
|
477
|
+
stripped = stripped.lstrip("\n")
|
|
478
|
+
if stripped.strip():
|
|
479
|
+
target_file.write_text(stripped, encoding="utf-8")
|
|
480
|
+
else:
|
|
481
|
+
target_file.unlink()
|
|
482
|
+
return True
|
|
483
|
+
|
|
484
|
+
|
|
355
485
|
def _inject_text_section(target_file: Path, section: str, text: str) -> None:
|
|
356
486
|
"""Inject generated text into one marker section without touching others."""
|
|
357
487
|
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -661,17 +791,16 @@ def _apply_extends_config(cwd: Path, merged: dict) -> None:
|
|
|
661
791
|
|
|
662
792
|
|
|
663
793
|
def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
|
|
664
|
-
"""
|
|
794
|
+
"""Install Claude language-rule entrypoints for a project.
|
|
665
795
|
|
|
666
796
|
Per-language rules (``app/rules/<lang>/``) are NOT injected here -- they
|
|
667
797
|
ship as ``<lang>-rules`` knowledge skills under ``app/skills/`` and load
|
|
668
|
-
contextually via the Agent Skills progressive-disclosure mechanism.
|
|
669
|
-
keeps ``CLAUDE.md`` small while ensuring language-specific guidance still
|
|
670
|
-
reaches Claude when relevant.
|
|
798
|
+
contextually via the Agent Skills progressive-disclosure mechanism.
|
|
671
799
|
|
|
672
|
-
Common rules are
|
|
673
|
-
|
|
674
|
-
|
|
800
|
+
Common rules are written as Claude Code path-scoped rules under
|
|
801
|
+
``.claude/rules/``. Current Claude Code guidance targets under 200 lines
|
|
802
|
+
per ``CLAUDE.md`` file; path-scoped rules keep startup context smaller
|
|
803
|
+
while still loading the rule bodies when project files are opened.
|
|
675
804
|
"""
|
|
676
805
|
if not language_modules:
|
|
677
806
|
return
|
|
@@ -681,6 +810,8 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
681
810
|
if not common_dir.is_dir():
|
|
682
811
|
return
|
|
683
812
|
|
|
813
|
+
rule_files = _sync_claude_common_rules(cwd, common_dir)
|
|
814
|
+
|
|
684
815
|
# Detect requested per-language modules so we can name the linked skills
|
|
685
816
|
# in the marker block. The modules themselves are not inlined.
|
|
686
817
|
langs: list[str] = []
|
|
@@ -690,38 +821,30 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
690
821
|
if name != "common":
|
|
691
822
|
langs.append(name)
|
|
692
823
|
|
|
693
|
-
# Inline full content of every common rule file, stripping YAML
|
|
694
|
-
# frontmatter so the resulting block reads as plain Markdown.
|
|
695
|
-
inlined: list[str] = []
|
|
696
|
-
for f in sorted(common_dir.glob("*.md")):
|
|
697
|
-
body = f.read_text(encoding="utf-8")
|
|
698
|
-
if body.startswith("---"):
|
|
699
|
-
end = body.find("\n---", 3)
|
|
700
|
-
if end != -1:
|
|
701
|
-
body = body[end + 4:].lstrip("\n")
|
|
702
|
-
inlined.append(body.rstrip())
|
|
703
|
-
|
|
704
824
|
lines: list[str] = ["# Language Rules", ""]
|
|
705
825
|
lines.append(
|
|
706
|
-
"Common
|
|
707
|
-
"
|
|
708
|
-
"
|
|
709
|
-
|
|
710
|
-
|
|
826
|
+
"Common ai-toolkit rules live in `.claude/rules/ai-toolkit-*.md` "
|
|
827
|
+
"with Claude Code `paths` frontmatter so they load when project files "
|
|
828
|
+
"are opened instead of expanding this CLAUDE.md at session startup."
|
|
829
|
+
)
|
|
830
|
+
if rule_files:
|
|
831
|
+
lines.append("")
|
|
832
|
+
lines.append("Common rule files: " + ", ".join(f"`{p}`" for p in rule_files) + ".")
|
|
833
|
+
lines.append("")
|
|
834
|
+
lines.append(
|
|
835
|
+
"Language-specific rules live in `<lang>-rules` knowledge skills "
|
|
836
|
+
"(e.g. `python-rules`, `typescript-rules`) and load automatically "
|
|
837
|
+
"when their triggers match -- you do not need to Read them manually."
|
|
711
838
|
)
|
|
712
839
|
if langs:
|
|
713
840
|
skill_names = ", ".join(f"`{l}-rules`" for l in langs)
|
|
714
841
|
lines.append("")
|
|
715
842
|
lines.append(f"Detected languages: {skill_names}.")
|
|
716
|
-
lines.append("")
|
|
717
|
-
lines.append("---")
|
|
718
|
-
lines.append("")
|
|
719
|
-
lines.extend(inlined)
|
|
720
843
|
|
|
721
844
|
# Write to temp file, then inject as a single named section so reruns are
|
|
722
845
|
# idempotent (existing block is replaced, not duplicated).
|
|
723
846
|
import tempfile
|
|
724
|
-
combined = "\n
|
|
847
|
+
combined = "\n".join(lines).rstrip() + "\n"
|
|
725
848
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False,
|
|
726
849
|
encoding="utf-8") as tmp:
|
|
727
850
|
tmp.write(combined)
|
|
@@ -738,6 +861,51 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
738
861
|
tmp_path.unlink(missing_ok=True)
|
|
739
862
|
|
|
740
863
|
|
|
864
|
+
def _strip_rule_frontmatter(text: str) -> str:
|
|
865
|
+
if text.startswith("---"):
|
|
866
|
+
end = text.find("\n---", 3)
|
|
867
|
+
if end != -1:
|
|
868
|
+
return text[end + 4:].lstrip("\n")
|
|
869
|
+
return text
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _sync_claude_common_rules(cwd: Path, common_dir: Path) -> list[str]:
|
|
873
|
+
"""Write common ai-toolkit rules as Claude Code path-scoped rules.
|
|
874
|
+
|
|
875
|
+
Only ``ai-toolkit-*.md`` files are managed. User-authored files in
|
|
876
|
+
``.claude/rules/`` are preserved.
|
|
877
|
+
"""
|
|
878
|
+
rules_dir = cwd / ".claude" / "rules"
|
|
879
|
+
rules_dir.mkdir(parents=True, exist_ok=True)
|
|
880
|
+
|
|
881
|
+
source_files = sorted(common_dir.glob("*.md"))
|
|
882
|
+
expected = {f"ai-toolkit-{src.stem}.md" for src in source_files}
|
|
883
|
+
for stale in sorted(rules_dir.glob("ai-toolkit-*.md")):
|
|
884
|
+
if stale.name not in expected:
|
|
885
|
+
stale.unlink()
|
|
886
|
+
|
|
887
|
+
written: list[str] = []
|
|
888
|
+
for src in source_files:
|
|
889
|
+
body = _strip_rule_frontmatter(src.read_text(encoding="utf-8")).rstrip()
|
|
890
|
+
rel = Path(".claude") / "rules" / f"ai-toolkit-{src.stem}.md"
|
|
891
|
+
target = cwd / rel
|
|
892
|
+
target.write_text(
|
|
893
|
+
"\n".join([
|
|
894
|
+
"---",
|
|
895
|
+
"paths:",
|
|
896
|
+
' - "**/*"',
|
|
897
|
+
"---",
|
|
898
|
+
"",
|
|
899
|
+
body,
|
|
900
|
+
"",
|
|
901
|
+
]),
|
|
902
|
+
encoding="utf-8",
|
|
903
|
+
)
|
|
904
|
+
written.append(rel.as_posix())
|
|
905
|
+
|
|
906
|
+
return written
|
|
907
|
+
|
|
908
|
+
|
|
741
909
|
def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
|
|
742
910
|
profile: str = "standard",
|
|
743
911
|
codex_skills: bool = False) -> None:
|
|
@@ -750,6 +918,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
|
|
|
750
918
|
print(" Would create: CLAUDE.md (if missing)")
|
|
751
919
|
print(" Would create: .claude/settings.local.json (if missing)")
|
|
752
920
|
print(" Would inject: .claude/constitution.md")
|
|
921
|
+
print(" Would generate: .claude/rules/ai-toolkit-*.md")
|
|
753
922
|
|
|
754
923
|
add_copilot_dir = profile in {"standard", "strict", "full"}
|
|
755
924
|
add_gemini_hooks = profile in {"standard", "strict", "full"}
|
|
@@ -131,13 +131,20 @@ DEFAULT_GLOBAL_EDITORS: list[str] = []
|
|
|
131
131
|
|
|
132
132
|
# All editors that support global install (opt-in via --editors).
|
|
133
133
|
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
134
|
+
# Scope varies by editor's documented global file surfaces:
|
|
135
|
+
# - cursor: HOOKS only (~/.cursor/hooks.json). Cursor RULES stay project-local
|
|
136
|
+
# — their only global surface is the Settings UI, not a mergeable file.
|
|
137
|
+
# - copilot: user-level instructions (~/.copilot/, read by Copilot CLI).
|
|
138
|
+
# - antigravity: global skill pointer (~/.gemini/config/skills,
|
|
139
|
+
# ~/.gemini/antigravity-cli/skills). Rules stay project-local.
|
|
136
140
|
GLOBAL_CAPABLE_EDITORS = [
|
|
137
141
|
"aider",
|
|
142
|
+
"antigravity",
|
|
138
143
|
"augment",
|
|
139
144
|
"cline",
|
|
140
145
|
"codex",
|
|
146
|
+
"copilot",
|
|
147
|
+
"cursor",
|
|
141
148
|
"gemini",
|
|
142
149
|
"opencode",
|
|
143
150
|
"roo",
|