@softspark/ai-toolkit 4.5.1 → 4.6.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/AGENTS.md +50 -0
- package/CHANGELOG.md +28 -0
- package/README.md +10 -10
- package/app/.claude-plugin/plugin.json +1 -1
- package/benchmarks/ecosystem-doctor-snapshot.json +67 -54
- package/bin/ai-toolkit.js +2 -5
- package/kb/procedures/release-preparation-sop.md +24 -16
- package/kb/procedures/release-verification-sop.md +3 -5
- package/kb/reference/architecture-overview.md +2 -3
- package/kb/reference/cli-reference.md +2 -3
- package/kb/reference/global-install-model.md +3 -3
- package/kb/reference/opencode-compatibility.md +2 -2
- package/kb/reference/supported-tools-registry.md +20 -18
- package/llms-full.txt +56 -50
- package/manifest.json +1 -1
- package/package.json +2 -3
- package/scripts/ecosystem_tools.json +18 -15
- package/scripts/generate_antigravity.py +11 -7
- package/scripts/generate_augment_agents.py +9 -10
- package/scripts/generate_augment_commands.py +4 -17
- package/scripts/generate_augment_hooks.py +7 -5
- package/scripts/generate_codex.py +28 -0
- package/scripts/generate_codex_hooks.py +23 -1
- package/scripts/generate_cursor_agents.py +13 -29
- package/scripts/generate_opencode_commands.py +9 -9
- package/scripts/install.py +1 -1
- package/scripts/install_steps/ai_tools.py +47 -26
- package/scripts/plugin.py +2 -2
- package/scripts/generate_codex_rules.py +0 -52
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Generate Antigravity IDE ``.
|
|
2
|
+
"""Generate Antigravity IDE ``.agents/rules/``, ``.agents/workflows/``, and
|
|
3
3
|
skill pointer files.
|
|
4
4
|
|
|
5
|
+
Antigravity 2.0 reads rules/workflows from the plural ``.agents/`` directory by
|
|
6
|
+
default; singular ``.agent/`` is still accepted as a backward-compatible
|
|
7
|
+
fallback (Antigravity checks plural first, then singular). We emit plural.
|
|
5
8
|
Antigravity reads rules from:
|
|
6
9
|
* ``GEMINI.md`` (highest priority) — generated by ``generate_gemini.py``
|
|
7
10
|
* ``AGENTS.md`` (cross-tool) — generated by ``generate_agents_md.py``
|
|
8
|
-
* ``.
|
|
9
|
-
* ``.
|
|
11
|
+
* ``.agents/rules/*.md`` — per-category rule files (this generator)
|
|
12
|
+
* ``.agents/workflows/*.md`` — workflow templates (this generator)
|
|
10
13
|
|
|
11
14
|
Antigravity also supports the Agent Skills standard via ``.agent/skills/
|
|
12
15
|
<skill-name>/SKILL.md`` (the changelog entry "Agent Skills now available in
|
|
@@ -19,7 +22,8 @@ that teaches Antigravity to look up the real catalogue in
|
|
|
19
22
|
Usage:
|
|
20
23
|
python3 scripts/generate_antigravity.py [target-dir]
|
|
21
24
|
|
|
22
|
-
Writes
|
|
25
|
+
Writes rules/workflows to target-dir/.agents/{rules,workflows}/ and the skill
|
|
26
|
+
pointer to target-dir/.agent/skills/ (skills location is unchanged upstream).
|
|
23
27
|
"""
|
|
24
28
|
from __future__ import annotations
|
|
25
29
|
|
|
@@ -93,7 +97,7 @@ def generate(target_dir: Path, *,
|
|
|
93
97
|
language_modules: list[str] | None = None,
|
|
94
98
|
rules_dir: Path | None = None,
|
|
95
99
|
emit_skill_pointer: bool = True) -> None:
|
|
96
|
-
"""Write ``.
|
|
100
|
+
"""Write ``.agents/rules/``, ``.agents/workflows/``, and ``.agent/skills/``.
|
|
97
101
|
|
|
98
102
|
``emit_skill_pointer`` controls whether the pointer SKILL.md is written.
|
|
99
103
|
Set to ``False`` if you manage ``.agent/skills/`` yourself.
|
|
@@ -101,8 +105,8 @@ def generate(target_dir: Path, *,
|
|
|
101
105
|
rules = dict(STANDARD_RULES)
|
|
102
106
|
rules.update(build_language_rules(language_modules))
|
|
103
107
|
rules.update(build_registered_rules(rules_dir))
|
|
104
|
-
write_rules(target_dir, rules, ".
|
|
105
|
-
write_rules(target_dir, STANDARD_WORKFLOWS, ".
|
|
108
|
+
write_rules(target_dir, rules, ".agents/rules")
|
|
109
|
+
write_rules(target_dir, STANDARD_WORKFLOWS, ".agents/workflows")
|
|
106
110
|
if emit_skill_pointer:
|
|
107
111
|
_write_skill_pointer(target_dir)
|
|
108
112
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"""Generate ``.augment/agents/ai-toolkit-*.md`` files for Augment Code.
|
|
3
3
|
|
|
4
4
|
Each ai-toolkit agent in ``app/agents/`` is mirrored as an Augment native
|
|
5
|
-
subagent. Augment's subagent frontmatter (per docs.augmentcode.com)
|
|
5
|
+
subagent. Augment's subagent frontmatter (per docs.augmentcode.com/cli/subagents)
|
|
6
|
+
documents: name, description, color, model, tools, disabled_tools.
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
name: <slug>
|
|
9
10
|
description: "<single-line description>"
|
|
10
|
-
model: inherit # or explicit model id
|
|
11
11
|
color: <color-name> # optional UI hint
|
|
12
12
|
tools: [Read, Write, ...]
|
|
13
13
|
disabled_tools: []
|
|
@@ -17,9 +17,10 @@ subagent. Augment's subagent frontmatter (per docs.augmentcode.com) supports:
|
|
|
17
17
|
|
|
18
18
|
Design choices:
|
|
19
19
|
|
|
20
|
-
* ``model
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
* ``model`` is omitted. The docs state "If not specified, the CLI default model
|
|
21
|
+
is used" and do not document ``inherit`` as a value. ai-toolkit stores short
|
|
22
|
+
aliases (``opus``/``sonnet``/``haiku``) that do not map to Augment's full
|
|
23
|
+
model ids, so we omit the field to defer to the CLI default.
|
|
23
24
|
* ``tools`` are passed through verbatim from the source file, normalized into
|
|
24
25
|
YAML flow-list form (``[Read, Write, ...]``) so Augment parses them as a
|
|
25
26
|
native list.
|
|
@@ -79,11 +80,9 @@ def _render_augment_agent(agent_file: Path) -> str:
|
|
|
79
80
|
lines: list[str] = ["---"]
|
|
80
81
|
lines.append(f"name: {name}")
|
|
81
82
|
lines.append(f'description: "{safe_desc}"')
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# we deliberately emit ``inherit`` instead of translating.
|
|
86
|
-
lines.append("model: inherit")
|
|
83
|
+
# `model` is omitted: Augment's docs say the CLI default model is used when
|
|
84
|
+
# absent, and `inherit` is not a documented value. Our short aliases do not
|
|
85
|
+
# map to Augment's provider-qualified ids, so we defer to the CLI default.
|
|
87
86
|
if color:
|
|
88
87
|
lines.append(f"color: {color}")
|
|
89
88
|
if tools:
|
|
@@ -5,13 +5,13 @@ User-invocable skills become Augment custom slash commands. Knowledge skills
|
|
|
5
5
|
(``user-invocable: false``) are excluded — they load automatically via
|
|
6
6
|
AGENTS.md/rules context instead of ``/`` invocation.
|
|
7
7
|
|
|
8
|
-
Per docs.augmentcode.com, Augment custom commands are plain
|
|
9
|
-
with an optional YAML frontmatter header. The body of the file IS
|
|
10
|
-
(no ``template:`` field, no TOML).
|
|
8
|
+
Per docs.augmentcode.com/cli/custom-commands, Augment custom commands are plain
|
|
9
|
+
markdown files with an optional YAML frontmatter header. The body of the file IS
|
|
10
|
+
the prompt (no ``template:`` field, no TOML). The documented frontmatter fields
|
|
11
|
+
are ``description``, ``argument-hint``, and ``model`` (no ``agent`` field):
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
description: "<short one-liner shown in the palette>"
|
|
14
|
-
agent: <optional agent slug to route into>
|
|
15
15
|
argument-hint: "<optional hint shown after the command name>"
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -44,20 +44,9 @@ def _skill_body(skill_file: Path) -> str:
|
|
|
44
44
|
return parts[2].lstrip("\n") if len(parts) >= 3 else text
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def _map_agent_name(value: str) -> str:
|
|
48
|
-
"""Map ai-toolkit agent names to our prefixed Augment subagent slugs."""
|
|
49
|
-
value = value.strip()
|
|
50
|
-
if not value:
|
|
51
|
-
return value
|
|
52
|
-
if value.startswith(COMMAND_PREFIX):
|
|
53
|
-
return value
|
|
54
|
-
return f"{COMMAND_PREFIX}{value}"
|
|
55
|
-
|
|
56
|
-
|
|
57
47
|
def _render_augment_command(skill_file: Path) -> str:
|
|
58
48
|
"""Render a single Augment command .md file from a user-invocable skill."""
|
|
59
49
|
description = frontmatter_field(skill_file, "description")
|
|
60
|
-
agent_field = frontmatter_field(skill_file, "agent")
|
|
61
50
|
argument_hint = frontmatter_field(skill_file, "argument-hint")
|
|
62
51
|
body = _skill_body(skill_file).rstrip()
|
|
63
52
|
|
|
@@ -65,8 +54,6 @@ def _render_augment_command(skill_file: Path) -> str:
|
|
|
65
54
|
if description:
|
|
66
55
|
safe_desc = description.replace('"', "'")
|
|
67
56
|
lines.append(f'description: "{safe_desc}"')
|
|
68
|
-
if agent_field:
|
|
69
|
-
lines.append(f"agent: {_map_agent_name(agent_field)}")
|
|
70
57
|
if argument_hint:
|
|
71
58
|
safe_hint = argument_hint.replace('"', "'")
|
|
72
59
|
lines.append(f'argument-hint: "{safe_hint}"')
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Generate Augment settings.json hooks block.
|
|
3
3
|
|
|
4
|
-
Augment
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Augment reads hooks from several settings.json layers (per
|
|
5
|
+
docs.augmentcode.com/cli/hooks): `<workspace>/.augment/settings.local.json`,
|
|
6
|
+
`<workspace>/.augment/settings.json`, `~/.augment/settings.json` (user scope),
|
|
7
|
+
and `/etc/augment/settings.json` (system policy). This generator writes the
|
|
8
|
+
user-scope file: it accepts an optional target directory and defaults to the
|
|
9
|
+
user's `$HOME`, writing `~/.augment/settings.json`. Project-local hook emission
|
|
10
|
+
for `--local` installs is a possible future extension.
|
|
9
11
|
|
|
10
12
|
Augment hook events (per docs.augmentcode.com/cli/hooks.md):
|
|
11
13
|
PreToolUse, PostToolUse, SessionStart, SessionEnd, Stop
|
|
@@ -13,6 +13,12 @@ from pathlib import Path
|
|
|
13
13
|
|
|
14
14
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
15
15
|
from codex_skill_adapter import codex_skill_description
|
|
16
|
+
from dir_rules_shared import (
|
|
17
|
+
rule_code_style,
|
|
18
|
+
rule_output_mode,
|
|
19
|
+
rule_security,
|
|
20
|
+
rule_testing,
|
|
21
|
+
)
|
|
16
22
|
from emission import (
|
|
17
23
|
agents_dir,
|
|
18
24
|
skills_dir,
|
|
@@ -54,6 +60,23 @@ def _emit_skills() -> str:
|
|
|
54
60
|
return "\n".join(lines)
|
|
55
61
|
|
|
56
62
|
|
|
63
|
+
def _emit_coding_rules() -> str:
|
|
64
|
+
"""Inline the universal coding-rule bodies so Codex receives them.
|
|
65
|
+
|
|
66
|
+
Codex reads project instructions only from AGENTS.md (and AGENTS.override.md);
|
|
67
|
+
it does not read a ``.agents/rules/`` directory. Workflow + quality standards
|
|
68
|
+
are already emitted above, so this adds code-style, testing, security, and
|
|
69
|
+
output-mode under a single ``## Coding Rules`` section (H1 demoted to H3).
|
|
70
|
+
"""
|
|
71
|
+
sections: list[str] = []
|
|
72
|
+
for rule_fn in (rule_code_style, rule_testing, rule_security, rule_output_mode):
|
|
73
|
+
body = rule_fn().rstrip()
|
|
74
|
+
if body.startswith("# "):
|
|
75
|
+
body = "### " + body[2:]
|
|
76
|
+
sections.append(body)
|
|
77
|
+
return "## Coding Rules\n\n" + "\n\n".join(sections)
|
|
78
|
+
|
|
79
|
+
|
|
57
80
|
def main() -> None:
|
|
58
81
|
print_toolkit_start()
|
|
59
82
|
|
|
@@ -86,6 +109,11 @@ def main() -> None:
|
|
|
86
109
|
print()
|
|
87
110
|
print(generate_workflow_guidelines())
|
|
88
111
|
|
|
112
|
+
# Universal coding rules — Codex reads instructions only from AGENTS.md,
|
|
113
|
+
# so inline them here (previously emitted to the unread .agents/rules/).
|
|
114
|
+
print()
|
|
115
|
+
print(_emit_coding_rules())
|
|
116
|
+
|
|
89
117
|
print_toolkit_end()
|
|
90
118
|
|
|
91
119
|
# Registered custom rules from ~/.softspark/ai-toolkit/rules/
|
|
@@ -8,7 +8,8 @@ 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
|
|
11
|
+
matcher. We wire all 10 events in ``CODEX_HOOKS`` below to the shared toolkit
|
|
12
|
+
hook scripts, mirroring the Claude Code mapping in ``app/hooks.json``.
|
|
12
13
|
|
|
13
14
|
Handler types in Codex: ``command`` (what we emit), ``prompt``, and ``agent``.
|
|
14
15
|
Reference: codex-rs/config/src/hook_config.rs.
|
|
@@ -39,6 +40,12 @@ CODEX_HOOKS: dict[str, list[tuple[str, str]]] = {
|
|
|
39
40
|
("Bash", "commit-quality.sh"),
|
|
40
41
|
("Bash", "revert-guard.sh"),
|
|
41
42
|
],
|
|
43
|
+
"PostToolUse": [
|
|
44
|
+
# Fires after Bash/apply_patch/MCP tool output. Capture governance
|
|
45
|
+
# signals and detect repetition loops (mirrors app/hooks.json).
|
|
46
|
+
("Bash", "governance-capture.sh"),
|
|
47
|
+
("Bash", "loop-guard.sh"),
|
|
48
|
+
],
|
|
42
49
|
"PermissionRequest": [
|
|
43
50
|
# Fires when Codex asks the user to approve a tool call. Our guard
|
|
44
51
|
# reviews the tool input and can veto destructive patterns before the
|
|
@@ -49,6 +56,21 @@ CODEX_HOOKS: dict[str, list[tuple[str, str]]] = {
|
|
|
49
56
|
("", "user-prompt-submit.sh"),
|
|
50
57
|
("", "track-usage.sh"),
|
|
51
58
|
],
|
|
59
|
+
"SubagentStart": [
|
|
60
|
+
("", "subagent-start.sh"),
|
|
61
|
+
],
|
|
62
|
+
"SubagentStop": [
|
|
63
|
+
("", "subagent-stop.sh"),
|
|
64
|
+
],
|
|
65
|
+
"PreCompact": [
|
|
66
|
+
# Capture session memory before Codex compacts the conversation.
|
|
67
|
+
("", "pre-compact.sh"),
|
|
68
|
+
("", "pre-compact-save.sh"),
|
|
69
|
+
],
|
|
70
|
+
"PostCompact": [
|
|
71
|
+
# Re-establish working context after compaction.
|
|
72
|
+
("", "session-context.sh"),
|
|
73
|
+
],
|
|
52
74
|
"Stop": [
|
|
53
75
|
("", "quality-check.sh"),
|
|
54
76
|
("", "save-session.sh"),
|
|
@@ -2,25 +2,24 @@
|
|
|
2
2
|
"""Generate ``.cursor/agents/ai-toolkit-*.md`` files for Cursor IDE.
|
|
3
3
|
|
|
4
4
|
Each ai-toolkit agent is mirrored as a Cursor custom agent. Per Cursor's
|
|
5
|
-
docs (
|
|
5
|
+
subagent docs (cursor.com/docs/subagents) the frontmatter schema documents
|
|
6
|
+
only these fields:
|
|
6
7
|
|
|
7
8
|
---
|
|
8
|
-
name: <slug>
|
|
9
|
-
description: "<one-line summary shown in the picker>"
|
|
10
|
-
model:
|
|
11
|
-
color: <color-name> # optional UI hint
|
|
12
|
-
tools: [Read, Write, ...] # list of allowed tool names
|
|
9
|
+
name: <slug> # lowercase letters and hyphens
|
|
10
|
+
description: "<one-line summary shown in the Task picker>"
|
|
11
|
+
model: inherit # `inherit` (default) or a specific model id
|
|
13
12
|
---
|
|
14
13
|
|
|
15
14
|
<system prompt body>
|
|
16
15
|
|
|
17
16
|
Design choices:
|
|
18
17
|
|
|
19
|
-
* ``model`` is
|
|
20
|
-
``haiku``) that do not map to
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
* ``model: inherit`` is emitted — the documented default value. Our agents
|
|
19
|
+
store short aliases (``opus``/``sonnet``/``haiku``) that do not map to
|
|
20
|
+
Cursor's provider-qualified model ids, so we use the literal ``inherit``.
|
|
21
|
+
* ``tools`` and ``color`` are NOT emitted — the current Cursor schema documents
|
|
22
|
+
only name/description/model/readonly/is_background (tools/color were dropped).
|
|
24
23
|
* Files are prefixed ``ai-toolkit-`` so install/uninstall can identify ours.
|
|
25
24
|
* Regeneration removes stale ``ai-toolkit-*.md`` files whose source agent
|
|
26
25
|
no longer exists, but leaves user-authored agents untouched.
|
|
@@ -53,34 +52,19 @@ def _agent_body(agent_file: Path) -> str:
|
|
|
53
52
|
return parts[2].lstrip("\n")
|
|
54
53
|
|
|
55
54
|
|
|
56
|
-
def _parse_tools(tools_raw: str) -> list[str]:
|
|
57
|
-
"""Parse the comma-separated ``tools:`` frontmatter value into a list."""
|
|
58
|
-
if not tools_raw:
|
|
59
|
-
return []
|
|
60
|
-
return [t.strip() for t in tools_raw.split(",") if t.strip()]
|
|
61
|
-
|
|
62
|
-
|
|
63
55
|
def _render_cursor_agent(agent_file: Path) -> str:
|
|
64
56
|
"""Render a single Cursor custom agent .md file."""
|
|
65
57
|
name = frontmatter_field(agent_file, "name")
|
|
66
58
|
description = frontmatter_field(agent_file, "description")
|
|
67
|
-
color = frontmatter_field(agent_file, "color")
|
|
68
|
-
tools_raw = frontmatter_field(agent_file, "tools")
|
|
69
|
-
tools = _parse_tools(tools_raw)
|
|
70
59
|
|
|
71
60
|
safe_desc = description.replace('"', "'")
|
|
72
61
|
|
|
73
62
|
lines: list[str] = ["---"]
|
|
74
63
|
lines.append(f"name: {name}")
|
|
75
64
|
lines.append(f'description: "{safe_desc}"')
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if tools:
|
|
80
|
-
tools_flow = ", ".join(tools)
|
|
81
|
-
lines.append(f"tools: [{tools_flow}]")
|
|
82
|
-
else:
|
|
83
|
-
lines.append("tools: []")
|
|
65
|
+
# `inherit` is the documented default model value; our short aliases do not
|
|
66
|
+
# map to Cursor's provider-qualified model ids.
|
|
67
|
+
lines.append("model: inherit")
|
|
84
68
|
lines.append("---")
|
|
85
69
|
lines.append("")
|
|
86
70
|
body = _agent_body(agent_file).rstrip()
|
|
@@ -5,8 +5,10 @@ User-invocable skills become opencode slash commands. Knowledge skills
|
|
|
5
5
|
(``user-invocable: false``) are excluded — they load automatically via
|
|
6
6
|
AGENTS.md context instead of `/` invocation.
|
|
7
7
|
|
|
8
|
-
Generated commands
|
|
9
|
-
|
|
8
|
+
Generated commands put the prompt in the markdown BODY. opencode reads the
|
|
9
|
+
body as the prompt template; the ``template`` frontmatter field is JSON-config
|
|
10
|
+
only and is ignored in .md command files. Files are prefixed ``ai-toolkit-``
|
|
11
|
+
for clean uninstall.
|
|
10
12
|
|
|
11
13
|
opencode commands: https://opencode.ai/docs/commands/
|
|
12
14
|
"""
|
|
@@ -47,12 +49,11 @@ def _skill_body(skill_file: Path) -> str:
|
|
|
47
49
|
|
|
48
50
|
def _render_opencode_command(skill_file: Path) -> str:
|
|
49
51
|
"""Render a single opencode command .md file from a user-invocable skill."""
|
|
50
|
-
name = frontmatter_field(skill_file, "name")
|
|
51
52
|
description = frontmatter_field(skill_file, "description")
|
|
52
53
|
agent_field = frontmatter_field(skill_file, "agent")
|
|
53
|
-
# opencode
|
|
54
|
-
# frontmatter
|
|
55
|
-
#
|
|
54
|
+
# opencode reads the command prompt from the markdown BODY of the file.
|
|
55
|
+
# The frontmatter only defines command properties; the `template` field is
|
|
56
|
+
# JSON-config-only and is ignored in .md command files.
|
|
56
57
|
body = _skill_body(skill_file).rstrip()
|
|
57
58
|
|
|
58
59
|
lines: list[str] = ["---"]
|
|
@@ -62,11 +63,10 @@ def _render_opencode_command(skill_file: Path) -> str:
|
|
|
62
63
|
if agent_field:
|
|
63
64
|
# opencode accepts an `agent` frontmatter field pointing at a subagent
|
|
64
65
|
lines.append(f"agent: {_map_agent_name(agent_field)}")
|
|
65
|
-
lines.append("template: |")
|
|
66
|
-
for tpl_line in body.splitlines() or [""]:
|
|
67
|
-
lines.append(f" {tpl_line}" if tpl_line else " ")
|
|
68
66
|
lines.append("---")
|
|
69
67
|
lines.append("")
|
|
68
|
+
lines.append(body)
|
|
69
|
+
lines.append("")
|
|
70
70
|
return "\n".join(lines)
|
|
71
71
|
|
|
72
72
|
|
package/scripts/install.py
CHANGED
|
@@ -14,7 +14,7 @@ Claude Code (~/.claude/):
|
|
|
14
14
|
Other tools (global config locations):
|
|
15
15
|
- Windsurf: ~/.codeium/windsurf/memories/global_rules.md + ~/.codeium/windsurf/skills/
|
|
16
16
|
- Gemini: ~/.gemini/GEMINI.md
|
|
17
|
-
- Cline:
|
|
17
|
+
- Cline: ~/Documents/Cline/Rules/ + ~/.cline/skills/
|
|
18
18
|
- Roo Code: ~/.roo/rules/
|
|
19
19
|
- Aider: ~/.aider.conf.yml (created only if absent)
|
|
20
20
|
- Augment: ~/.augment/rules/ai-toolkit.md
|
|
@@ -79,7 +79,7 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
|
|
|
79
79
|
|
|
80
80
|
if "cline" in eds:
|
|
81
81
|
if dry_run:
|
|
82
|
-
print(" Would generate:
|
|
82
|
+
print(" Would generate: ~/Documents/Cline/Rules/ai-toolkit-*.md")
|
|
83
83
|
print(" Would generate: ~/.cline/skills/ai-toolkit-skill-catalogue/SKILL.md")
|
|
84
84
|
else:
|
|
85
85
|
_install_cline_global(target_dir, rules_dir)
|
|
@@ -125,8 +125,8 @@ def _install_codex_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
125
125
|
"""Install Codex at the global level (~/ layer).
|
|
126
126
|
|
|
127
127
|
Creates:
|
|
128
|
-
- ~/AGENTS.md (marker injection
|
|
129
|
-
|
|
128
|
+
- ~/AGENTS.md (marker injection; universal coding rules inlined here —
|
|
129
|
+
Codex reads instructions only from AGENTS.md, not .agents/rules/)
|
|
130
130
|
- ~/.agents/skills/* (skill symlinks)
|
|
131
131
|
- ~/.codex/hooks.json (lifecycle hooks)
|
|
132
132
|
"""
|
|
@@ -136,13 +136,6 @@ def _install_codex_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
136
136
|
rules_dir,
|
|
137
137
|
)
|
|
138
138
|
|
|
139
|
-
from generate_codex_rules import generate as gen_codex_rules
|
|
140
|
-
gen_codex_rules(
|
|
141
|
-
target_dir,
|
|
142
|
-
rules_dir=rules_dir,
|
|
143
|
-
managed_scopes=("standard", "custom"),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
139
|
from generate_codex_hooks import generate as gen_codex_hooks
|
|
147
140
|
gen_codex_hooks(target_dir)
|
|
148
141
|
print(" Created: ~/.codex/hooks.json")
|
|
@@ -150,12 +143,33 @@ def _install_codex_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
150
143
|
_install_codex_skills(target_dir)
|
|
151
144
|
|
|
152
145
|
|
|
146
|
+
def _claude_skills_discoverable(project_dir: Path | None = None) -> bool:
|
|
147
|
+
"""True when real ai-toolkit skills already exist at a ``.claude/skills``
|
|
148
|
+
directory the editor scans natively (project-local or global
|
|
149
|
+
``~/.claude/skills``), making an editor skill-catalogue pointer redundant.
|
|
150
|
+
|
|
151
|
+
Cursor, Cline and Augment discover ``SKILL.md`` from ``.claude/skills`` and
|
|
152
|
+
``~/.claude/skills`` by default, so when those are populated the pointer adds
|
|
153
|
+
nothing. (Windsurf/Devin gates that scan behind a setting and is excluded
|
|
154
|
+
from the conditional by its callers.) Returns ``False`` for editor-only
|
|
155
|
+
installs (no Claude skills present), where the pointer is the only
|
|
156
|
+
skill-discovery surface.
|
|
157
|
+
"""
|
|
158
|
+
candidates = [Path.home() / ".claude" / "skills"]
|
|
159
|
+
if project_dir is not None:
|
|
160
|
+
candidates.insert(0, project_dir / ".claude" / "skills")
|
|
161
|
+
return any(d.is_dir() and any(d.glob("*/SKILL.md")) for d in candidates)
|
|
162
|
+
|
|
163
|
+
|
|
153
164
|
def _install_windsurf_global(target_dir: Path, rules_dir: Path) -> None:
|
|
154
165
|
"""Install Windsurf global rules plus an Agent Skills catalogue pointer."""
|
|
155
166
|
windsurf_file = target_dir / ".codeium" / "windsurf" / "memories" / "global_rules.md"
|
|
156
167
|
inject_with_rules("generate-windsurf.sh", windsurf_file, rules_dir)
|
|
157
168
|
|
|
158
169
|
from generate_windsurf_skills import generate as gen_windsurf_skills
|
|
170
|
+
# Windsurf is excluded from the .claude/skills conditional: its native scan
|
|
171
|
+
# of .claude/skills is gated behind a Devin "Claude Code config reading"
|
|
172
|
+
# setting, so we always emit the pointer as a reliable fallback.
|
|
159
173
|
gen_windsurf_skills(
|
|
160
174
|
target_dir,
|
|
161
175
|
skill_root=".codeium/windsurf/skills",
|
|
@@ -166,7 +180,9 @@ def _install_cline_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
166
180
|
"""Install Cline global rules in the documented ~/.cline directory."""
|
|
167
181
|
from generate_cline_rules import generate as gen_cline_rules
|
|
168
182
|
|
|
169
|
-
|
|
183
|
+
# Cline reads GLOBAL rules from ~/Documents/Cline/Rules/ (docs.cline.bot);
|
|
184
|
+
# ~/.cline/rules/ is not a Cline-read path.
|
|
185
|
+
rules_root = target_dir / "Documents" / "Cline" / "Rules"
|
|
170
186
|
gen_cline_rules(
|
|
171
187
|
target_dir,
|
|
172
188
|
rules_dir=rules_dir,
|
|
@@ -174,10 +190,10 @@ def _install_cline_global(target_dir: Path, rules_dir: Path) -> None:
|
|
|
174
190
|
emit_workflows=False,
|
|
175
191
|
managed_scopes=("standard", "custom"),
|
|
176
192
|
)
|
|
177
|
-
print(" Created:
|
|
193
|
+
print(" Created: ~/Documents/Cline/Rules/ai-toolkit-*.md")
|
|
178
194
|
|
|
179
195
|
from generate_cline_skills import generate as gen_cline_skills
|
|
180
|
-
gen_cline_skills(target_dir)
|
|
196
|
+
gen_cline_skills(target_dir, emit_skill_pointer=not _claude_skills_discoverable(target_dir))
|
|
181
197
|
|
|
182
198
|
|
|
183
199
|
def _install_roo_global(target_dir: Path, rules_dir: Path) -> None:
|
|
@@ -371,6 +387,10 @@ _EDITOR_MARKERS: dict[str, str] = {
|
|
|
371
387
|
".aider.conf.yml": "aider",
|
|
372
388
|
"CONVENTIONS.md": "aider",
|
|
373
389
|
".augment/rules": "augment",
|
|
390
|
+
# Antigravity 2.0 default is plural .agents/; .agents/workflows is the
|
|
391
|
+
# unambiguous marker (Codex also writes .agents/rules but no workflows).
|
|
392
|
+
# Singular .agent/rules is kept to detect legacy installs.
|
|
393
|
+
".agents/workflows": "antigravity",
|
|
374
394
|
".agent/rules": "antigravity",
|
|
375
395
|
".agents/skills": "codex",
|
|
376
396
|
".codex": "codex",
|
|
@@ -694,7 +714,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
|
|
|
694
714
|
"roo": " Would generate: .roomodes + .roo/rules/*.md",
|
|
695
715
|
"aider": " Would generate: .aider.conf.yml + CONVENTIONS.md",
|
|
696
716
|
"augment": " Would generate: .augment/rules/ai-toolkit-*.md",
|
|
697
|
-
"antigravity": " Would generate: .
|
|
717
|
+
"antigravity": " Would generate: .agents/rules/ + .agents/workflows/",
|
|
698
718
|
"gemini": " Would generate: GEMINI.md",
|
|
699
719
|
"opencode": " Would generate: AGENTS.md + .opencode/{agents,commands,plugins}/ + opencode.json",
|
|
700
720
|
}
|
|
@@ -899,6 +919,12 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
899
919
|
add_copilot_dir = _profile in {"standard", "strict", "full"}
|
|
900
920
|
add_gemini_hooks = _profile in {"standard", "strict", "full"}
|
|
901
921
|
add_native_surfaces = _profile == "full"
|
|
922
|
+
# Skip the editor skill-catalogue pointer when real skills are already
|
|
923
|
+
# discoverable at .claude/skills (project or ~/.claude/skills global):
|
|
924
|
+
# Cursor/Cline/Augment read those natively and by default. Emit it only as
|
|
925
|
+
# the editor-only fallback when no Claude skills are present. Windsurf is
|
|
926
|
+
# excluded (its .claude scan is gated) and keeps its pointer unconditionally.
|
|
927
|
+
emit_pointer = not _claude_skills_discoverable(cwd)
|
|
902
928
|
|
|
903
929
|
if "copilot" in eds:
|
|
904
930
|
inject_with_rules(
|
|
@@ -925,7 +951,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
925
951
|
if add_native_surfaces:
|
|
926
952
|
_try_generator("generate_cursor_hooks", cwd)
|
|
927
953
|
_try_generator("generate_cursor_agents", cwd)
|
|
928
|
-
_try_generator("generate_cursor_skills", cwd)
|
|
954
|
+
_try_generator("generate_cursor_skills", cwd, emit_skill_pointer=emit_pointer)
|
|
929
955
|
|
|
930
956
|
if "windsurf" in eds:
|
|
931
957
|
inject_with_rules(
|
|
@@ -938,6 +964,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
938
964
|
rules_dir=rules_dir)
|
|
939
965
|
if add_native_surfaces:
|
|
940
966
|
_try_generator("generate_windsurf_hooks", cwd)
|
|
967
|
+
# Windsurf pointer stays unconditional (its .claude scan is gated).
|
|
941
968
|
_try_generator("generate_windsurf_skills", cwd)
|
|
942
969
|
|
|
943
970
|
if "cline" in eds:
|
|
@@ -954,7 +981,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
954
981
|
managed_scopes=("standard", "lang", "custom"),
|
|
955
982
|
)
|
|
956
983
|
if add_native_surfaces:
|
|
957
|
-
_try_generator("generate_cline_skills", cwd)
|
|
984
|
+
_try_generator("generate_cline_skills", cwd, emit_skill_pointer=emit_pointer)
|
|
958
985
|
|
|
959
986
|
if "roo" in eds:
|
|
960
987
|
roo_output = run_script("generate-roo-modes.sh", capture=True)
|
|
@@ -980,7 +1007,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
980
1007
|
# Augment hooks live under $HOME/.augment/settings.json. The
|
|
981
1008
|
# generator takes HOME (not cwd) per bucket-1 contract.
|
|
982
1009
|
_try_generator("generate_augment_hooks", Path.home())
|
|
983
|
-
_try_generator("generate_augment_skills", cwd)
|
|
1010
|
+
_try_generator("generate_augment_skills", cwd, emit_skill_pointer=emit_pointer)
|
|
984
1011
|
|
|
985
1012
|
if "antigravity" in eds:
|
|
986
1013
|
from generate_antigravity import generate as gen_antigravity
|
|
@@ -988,20 +1015,14 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
|
|
|
988
1015
|
rules_dir=rules_dir)
|
|
989
1016
|
|
|
990
1017
|
if "codex" in eds:
|
|
991
|
-
# AGENTS.md — marker injection
|
|
1018
|
+
# AGENTS.md — marker injection; universal coding rules are inlined into
|
|
1019
|
+
# AGENTS.md (Codex reads instructions only from AGENTS.md, not a
|
|
1020
|
+
# .agents/rules/ directory). Language rules reach Codex via .agents/skills/.
|
|
992
1021
|
inject_with_rules(
|
|
993
1022
|
"generate_codex.py",
|
|
994
1023
|
cwd / "AGENTS.md",
|
|
995
1024
|
rules_dir,
|
|
996
1025
|
)
|
|
997
|
-
# .agents/rules/ — directory-based rules
|
|
998
|
-
from generate_codex_rules import generate as gen_codex_rules
|
|
999
|
-
gen_codex_rules(
|
|
1000
|
-
cwd,
|
|
1001
|
-
language_modules=language_modules,
|
|
1002
|
-
rules_dir=rules_dir,
|
|
1003
|
-
managed_scopes=("standard", "lang", "custom"),
|
|
1004
|
-
)
|
|
1005
1026
|
# .codex/hooks.json — Codex lifecycle hooks
|
|
1006
1027
|
from generate_codex_hooks import generate as gen_codex_hooks
|
|
1007
1028
|
gen_codex_hooks(cwd)
|
package/scripts/plugin.py
CHANGED
|
@@ -33,7 +33,6 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
33
33
|
from _common import app_dir, inject_section, inject_rule, remove_rule_section
|
|
34
34
|
from codex_skill_adapter import cleanup_codex_skills, sync_codex_skill
|
|
35
35
|
from generate_codex_hooks import generate as generate_codex_hooks
|
|
36
|
-
from generate_codex_rules import generate as generate_codex_rules
|
|
37
36
|
from install_steps.ai_tools import inject_with_rules
|
|
38
37
|
from paths import HOOKS_DIR as _HOOKS_DIR
|
|
39
38
|
from paths import RULES_DIR, TOOLKIT_DATA_DIR
|
|
@@ -536,8 +535,9 @@ def _install_codex_extra_skills(pack: dict, pack_dir: Path) -> None:
|
|
|
536
535
|
|
|
537
536
|
def _install_codex_base() -> None:
|
|
538
537
|
_ensure_core_hook_scripts()
|
|
538
|
+
# Universal coding rules are inlined into AGENTS.md by generate_codex.py;
|
|
539
|
+
# Codex does not read a .agents/rules/ directory.
|
|
539
540
|
inject_with_rules("generate_codex.py", CODEX_ROOT / "AGENTS.md", RULES_DIR)
|
|
540
|
-
generate_codex_rules(CODEX_ROOT, rules_dir=RULES_DIR)
|
|
541
541
|
hooks_path = CODEX_ROOT / ".codex" / "hooks.json"
|
|
542
542
|
existing = _load_json(hooks_path, {"hooks": {}})
|
|
543
543
|
plugin_entries: dict[str, list[dict]] = {}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Generate Codex CLI .agents/rules/ files.
|
|
3
|
-
|
|
4
|
-
Codex discovers rules in .agents/rules/ at the project root.
|
|
5
|
-
This generator follows the same pattern as generate_antigravity.py.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python3 scripts/generate_codex_rules.py [target-dir]
|
|
9
|
-
|
|
10
|
-
Writes files directly to target-dir/.agents/rules/.
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import sys
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
18
|
-
from dir_rules_shared import (
|
|
19
|
-
STANDARD_RULES,
|
|
20
|
-
STANDARD_SCOPE,
|
|
21
|
-
build_language_rules,
|
|
22
|
-
build_registered_rules,
|
|
23
|
-
write_rules,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def generate(target_dir: Path, *,
|
|
28
|
-
language_modules: list[str] | None = None,
|
|
29
|
-
rules_dir: Path | None = None,
|
|
30
|
-
cleanup: bool = True,
|
|
31
|
-
managed_scopes: tuple[str, ...] = (STANDARD_SCOPE,)) -> None:
|
|
32
|
-
"""Write .agents/rules/ files to target_dir."""
|
|
33
|
-
rules = dict(STANDARD_RULES)
|
|
34
|
-
rules.update(build_language_rules(language_modules))
|
|
35
|
-
rules.update(build_registered_rules(rules_dir))
|
|
36
|
-
write_rules(
|
|
37
|
-
target_dir,
|
|
38
|
-
rules,
|
|
39
|
-
".agents/rules",
|
|
40
|
-
cleanup=cleanup,
|
|
41
|
-
managed_scopes=managed_scopes,
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def main() -> None:
|
|
46
|
-
target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
|
|
47
|
-
from paths import RULES_DIR
|
|
48
|
-
generate(target, rules_dir=RULES_DIR)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if __name__ == "__main__":
|
|
52
|
-
main()
|