@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.
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env python3
2
- """Generate Antigravity IDE ``.agent/rules/``, ``.agent/workflows/``, and
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
- * ``.agent/rules/*.md`` — per-category rule files (this generator)
9
- * ``.agent/workflows/*.md`` — workflow templates (this generator)
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 files directly to target-dir/.agent/{rules,workflows,skills}/.
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 ``.agent/rules/``, ``.agent/workflows/``, and ``.agent/skills/``.
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, ".agent/rules")
105
- write_rules(target_dir, STANDARD_WORKFLOWS, ".agent/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) supports:
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: inherit`` is used unconditionally. ai-toolkit stores short aliases
21
- (``opus``/``sonnet``/``haiku``) that do not map to Augment's full model ids
22
- without assuming a provider, so we defer to the user's default model.
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
- # Augment recommends ``model: inherit`` for plugins that want to follow
83
- # the user's active model selection. ai-toolkit agents are authored with
84
- # short aliases that do not map to Augment's provider-qualified ids, so
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 markdown files
9
- with an optional YAML frontmatter header. The body of the file IS the prompt
10
- (no ``template:`` field, no TOML). Supported frontmatter fields:
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 stores hooks in `~/.augment/settings.json` (user scope) — there is no
5
- per-workspace hooks file. `/etc/augment/settings.json` exists for system-wide
6
- policy and we never write there. The generator accepts an optional target
7
- directory; when omitted it defaults to the user's `$HOME` and writes
8
- `~/.augment/settings.json`.
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 currently wire the subset defined in ``CODEX_HOOKS`` below.
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 (docs.cursor.com -> custom agents), agent files use YAML frontmatter:
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: <model-id> # optional, inherits user default if absent
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 omitted. Our agents store short aliases (``opus``, ``sonnet``,
20
- ``haiku``) that do not map to Cursor's provider-qualified model ids.
21
- Omitting the field makes Cursor fall back to the user's default model,
22
- which is the same behaviour our opencode generator uses.
23
- * ``tools`` is emitted as a YAML flow-list parsed verbatim from the source.
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
- # model intentionally omitted Cursor falls back to the user default.
77
- if color:
78
- lines.append(f"color: {color}")
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 use the required ``template`` frontmatter field.
9
- Files are prefixed ``ai-toolkit-`` for clean uninstall.
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 does NOT read "body" the prompt is entirely in the `template`
54
- # frontmatter field. We embed the SKILL.md body as the template using a
55
- # YAML block scalar (``|``) which preserves newlines without escaping.
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
 
@@ -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: ~/.cline/rules/ + ~/.cline/skills/
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: ~/.cline/rules/ai-toolkit-*.md")
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 with rules)
129
- - ~/.agents/rules/*.md (directory-based rules)
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
- rules_root = target_dir / ".cline" / "rules"
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: ~/.cline/rules/ai-toolkit-*.md")
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: .agent/rules/ + .agent/workflows/",
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 (like CLAUDE.md)
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()