@softspark/ai-toolkit 4.5.1 → 4.7.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.
Files changed (35) hide show
  1. package/AGENTS.md +50 -0
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +13 -13
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/benchmarks/ecosystem-doctor-snapshot.json +76 -54
  6. package/bin/ai-toolkit.js +2 -5
  7. package/kb/procedures/maintenance-sop.md +9 -8
  8. package/kb/procedures/release-preparation-sop.md +24 -16
  9. package/kb/procedures/release-verification-sop.md +4 -6
  10. package/kb/reference/architecture-overview.md +5 -6
  11. package/kb/reference/cli-reference.md +2 -3
  12. package/kb/reference/global-install-model.md +3 -3
  13. package/kb/reference/opencode-compatibility.md +2 -2
  14. package/kb/reference/supported-tools-registry.md +40 -33
  15. package/llms-full.txt +89 -77
  16. package/manifest.json +1 -1
  17. package/package.json +2 -3
  18. package/scripts/ecosystem_tools.json +74 -20
  19. package/scripts/generate_antigravity.py +32 -21
  20. package/scripts/generate_augment_agents.py +9 -10
  21. package/scripts/generate_augment_commands.py +4 -17
  22. package/scripts/generate_augment_hooks.py +7 -5
  23. package/scripts/generate_codex.py +28 -0
  24. package/scripts/generate_codex_hooks.py +23 -1
  25. package/scripts/generate_cursor_agents.py +13 -29
  26. package/scripts/generate_cursor_mdc.py +4 -3
  27. package/scripts/generate_opencode_commands.py +9 -9
  28. package/scripts/generate_windsurf_hooks.py +6 -0
  29. package/scripts/generate_windsurf_rules.py +39 -24
  30. package/scripts/generate_windsurf_skills.py +14 -4
  31. package/scripts/install.py +1 -1
  32. package/scripts/install_steps/ai_tools.py +68 -30
  33. package/scripts/plugin.py +2 -2
  34. package/scripts/validate.py +1 -0
  35. package/scripts/generate_codex_rules.py +0 -52
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
  """Generate ``.cursor/rules/*.mdc`` files for Cursor IDE.
3
3
 
4
- Cursor reads rules from ``.cursor/rules/`` (since Cursor 0.45). Both ``.mdc``
5
- and plain ``.md`` extensions are accepted; we emit ``.mdc`` because only it
6
- carries the full YAML frontmatter used to control activation:
4
+ Cursor reads rules from ``.cursor/rules/`` (since Cursor 0.45). Only ``.mdc``
5
+ files are read — plain ``.md`` files in ``.cursor/rules/`` are ignored by the
6
+ rules system (per cursor.com/docs/context/rules). ``.mdc`` carries the YAML
7
+ frontmatter used to control activation:
7
8
 
8
9
  - ``alwaysApply: true`` — always in context
9
10
  - ``globs: ["**/*.ts"]`` — auto-attached for matching files
@@ -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
 
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env python3
2
2
  """Generate .windsurf/hooks.json for Windsurf Cascade.
3
3
 
4
+ DEPRECATED SURFACE: the Cascade agent is available only through 2026-07-01
5
+ (Devin Local is the default agent since the 2026-06-02 Devin Desktop rebrand),
6
+ and this hooks.json schema is Cascade-scoped. Migrate to the Devin CLI
7
+ lifecycle-hooks surface (docs.devin.ai/cli/extensibility/hooks/*) before that
8
+ date; tracked in the ecosystem registry status_note for windsurf.
9
+
4
10
  Writes `<target>/.windsurf/hooks.json`. Existing user hook entries are
5
11
  preserved; only entries tagged `_source: ai-toolkit` are replaced on
6
12
  regeneration. Windsurf merges system / user / workspace hooks at runtime, so
@@ -1,17 +1,25 @@
1
1
  #!/usr/bin/env python3
2
- """Generate ``.windsurf/rules/*.md`` (and workflows) for Windsurf IDE.
2
+ """Generate ``.devin/rules/*.md`` and ``.windsurf/rules/*.md`` (and workflows)
3
+ for Devin Desktop (formerly Windsurf).
3
4
 
4
- Windsurf reads directory-based rules from ``.windsurf/rules/*.md`` (since
5
- mid-2025). Each rule file supports YAML frontmatter with these fields
6
- (from docs.windsurf.com/windsurf/cascade/memories):
5
+ Windsurf rebranded to Devin Desktop on 2026-06-02. ``.devin/`` is now the
6
+ primary read+write workspace tree; ``.windsurf/`` is a legacy read-only
7
+ fallback that current builds still honor (docs.devin.ai/desktop/devin-desktop-faq:
8
+ "The application already supports .devin/ as the primary workspace directory
9
+ and falls back to .windsurf/ for backward compatibility"). We dual-emit the
10
+ same content to both trees so old Windsurf builds keep working during the
11
+ transition.
12
+
13
+ Each rule file supports YAML frontmatter with these fields
14
+ (from docs.devin.ai/desktop — memories docs):
7
15
 
8
16
  * ``trigger`` — activation mode: ``always_on`` | ``glob`` | ``model_decision``
9
- | ``manual``. When omitted, Windsurf defaults to manual-only.
17
+ | ``manual``. When omitted, the IDE defaults to manual-only.
10
18
  * ``globs`` — comma-separated glob patterns (only when ``trigger: glob``).
11
19
  * ``description`` — shown to the model when ``trigger: model_decision``.
12
20
 
13
- Windsurf also reads workflow markdown files from ``.windsurf/workflows/*.md``
14
- which users invoke via ``/<name>`` slash commands (Cascade). This generator
21
+ Workflow markdown files (invoked via ``/<name>`` slash commands) are emitted
22
+ to ``.devin/workflows/*.md`` and ``.windsurf/workflows/*.md``. This generator
15
23
  emits the same workflow catalogue used by Antigravity and Cline.
16
24
 
17
25
  The legacy ``.windsurfrules`` single-file format is still produced by
@@ -142,20 +150,26 @@ def _build_rules(language_modules: list[str] | None,
142
150
 
143
151
 
144
152
  # ---------------------------------------------------------------------------
145
- # Workflows — .windsurf/workflows/<name>.md invocable via /<name>
153
+ # Workflows — <tree>/workflows/<name>.md invocable via /<name>
146
154
  # ---------------------------------------------------------------------------
147
155
 
156
+ # .devin/ is primary since the 2026-06-02 Devin Desktop rebrand; .windsurf/
157
+ # is the legacy fallback still read by pre-rebrand builds.
158
+ CONFIG_TREES: tuple[str, ...] = (".devin", ".windsurf")
159
+
160
+
148
161
  def _write_workflows(target_dir: Path, *, cleanup: bool = True) -> None:
149
- """Write ``.windsurf/workflows/*.md`` files for Cascade slash commands."""
150
- workflows_dir = target_dir / ".windsurf" / "workflows"
151
- workflows_dir.mkdir(parents=True, exist_ok=True)
162
+ """Write ``workflows/*.md`` slash-command files to both config trees."""
163
+ for tree in CONFIG_TREES:
164
+ workflows_dir = target_dir / tree / "workflows"
165
+ workflows_dir.mkdir(parents=True, exist_ok=True)
152
166
 
153
- if cleanup:
154
- cleanup_stale(workflows_dir, set(STANDARD_WORKFLOWS.keys()))
167
+ if cleanup:
168
+ cleanup_stale(workflows_dir, set(STANDARD_WORKFLOWS.keys()))
155
169
 
156
- for filename, content_fn in STANDARD_WORKFLOWS.items():
157
- (workflows_dir / filename).write_text(content_fn(), encoding="utf-8")
158
- print(f" Generated: .windsurf/workflows/{filename}")
170
+ for filename, content_fn in STANDARD_WORKFLOWS.items():
171
+ (workflows_dir / filename).write_text(content_fn(), encoding="utf-8")
172
+ print(f" Generated: {tree}/workflows/{filename}")
159
173
 
160
174
 
161
175
  # ---------------------------------------------------------------------------
@@ -170,15 +184,16 @@ def generate(target_dir: Path, *,
170
184
  managed_scopes: tuple[str, ...] = (
171
185
  STANDARD_SCOPE, LANG_SCOPE, CUSTOM_SCOPE,
172
186
  )) -> None:
173
- """Write ``.windsurf/rules/*.md`` and ``.windsurf/workflows/*.md``."""
187
+ """Write ``rules/*.md`` and ``workflows/*.md`` to both config trees."""
174
188
  rules = _build_rules(language_modules, rules_dir)
175
- write_rules(
176
- target_dir,
177
- rules,
178
- ".windsurf/rules",
179
- cleanup=cleanup,
180
- managed_scopes=managed_scopes,
181
- )
189
+ for tree in CONFIG_TREES:
190
+ write_rules(
191
+ target_dir,
192
+ rules,
193
+ f"{tree}/rules",
194
+ cleanup=cleanup,
195
+ managed_scopes=managed_scopes,
196
+ )
182
197
 
183
198
  if emit_workflows:
184
199
  _write_workflows(target_dir, cleanup=cleanup)
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env python3
2
- """Generate a Windsurf skill pointer under ``.windsurf/skills/``."""
2
+ """Generate a Devin Desktop (formerly Windsurf) skill pointer.
3
+
4
+ Dual-emits under ``.devin/skills/`` (primary since the 2026-06-02 rebrand)
5
+ and ``.windsurf/skills/`` (legacy fallback). Pass an explicit ``skill_root``
6
+ to emit a single location instead (used for the ``~/.codeium/windsurf/``
7
+ global install path).
8
+ """
3
9
  from __future__ import annotations
4
10
 
5
11
  import sys
@@ -8,13 +14,17 @@ from pathlib import Path
8
14
  sys.path.insert(0, str(Path(__file__).resolve().parent))
9
15
  from skill_pointer import POINTER_SKILL_NAME, write_pointer_skill
10
16
 
17
+ DEFAULT_SKILL_ROOTS: tuple[str, ...] = (".devin/skills", ".windsurf/skills")
18
+
11
19
 
12
20
  def generate(target_dir: Path, *, emit_skill_pointer: bool = True,
13
- skill_root: str = ".windsurf/skills") -> None:
21
+ skill_root: str | None = None) -> None:
14
22
  if not emit_skill_pointer:
15
23
  return
16
- write_pointer_skill(target_dir, skill_root, "Windsurf")
17
- print(f" Generated: {skill_root}/{POINTER_SKILL_NAME}/SKILL.md")
24
+ roots = (skill_root,) if skill_root else DEFAULT_SKILL_ROOTS
25
+ for root in roots:
26
+ write_pointer_skill(target_dir, root, "Windsurf")
27
+ print(f" Generated: {root}/{POINTER_SKILL_NAME}/SKILL.md")
18
28
 
19
29
 
20
30
  def main() -> None:
@@ -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:
@@ -365,12 +381,19 @@ _EDITOR_MARKERS: dict[str, str] = {
365
381
  ".cursor/rules": "cursor",
366
382
  ".windsurfrules": "windsurf",
367
383
  ".windsurf/rules": "windsurf",
384
+ # .devin/ is the primary Devin Desktop tree since the 2026-06-02 rebrand;
385
+ # .windsurf/ markers stay to detect legacy installs.
386
+ ".devin/rules": "windsurf",
368
387
  ".clinerules": "cline",
369
388
  ".roomodes": "roo",
370
389
  ".roo/rules": "roo",
371
390
  ".aider.conf.yml": "aider",
372
391
  "CONVENTIONS.md": "aider",
373
392
  ".augment/rules": "augment",
393
+ # Antigravity 2.0 default is plural .agents/; .agents/workflows is the
394
+ # unambiguous marker (Codex also writes .agents/rules but no workflows).
395
+ # Singular .agent/rules is kept to detect legacy installs.
396
+ ".agents/workflows": "antigravity",
374
397
  ".agent/rules": "antigravity",
375
398
  ".agents/skills": "codex",
376
399
  ".codex": "codex",
@@ -391,11 +414,25 @@ def _detect_editors(cwd: Path) -> list[str]:
391
414
  found: set[str] = set()
392
415
  for marker, editor in _EDITOR_MARKERS.items():
393
416
  p = cwd / marker
394
- if p.exists():
395
- found.add(editor)
417
+ if not p.exists():
418
+ continue
419
+ if marker == ".agents/skills" and _is_pointer_only_skills_dir(p):
420
+ # The Antigravity CLI pointer skill also lives in .agents/skills/;
421
+ # only real (materialized) skills indicate a Codex install.
422
+ continue
423
+ found.add(editor)
396
424
  return sorted(found)
397
425
 
398
426
 
427
+ def _is_pointer_only_skills_dir(skills_dir: Path) -> bool:
428
+ """True when a skills dir holds only the ai-toolkit pointer skill."""
429
+ try:
430
+ entries = [e.name for e in skills_dir.iterdir() if not e.name.startswith(".")]
431
+ except OSError:
432
+ return False
433
+ return entries == ["ai-toolkit-skill-catalogue"]
434
+
435
+
399
436
  def _resolve_editors(editors_arg: str, cwd: Path) -> list[str]:
400
437
  """Resolve --editors argument to a list of editor names.
401
438
 
@@ -689,12 +726,12 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
689
726
  _EDITOR_DRY_RUN = {
690
727
  "copilot": " Would inject: .github/copilot-instructions.md",
691
728
  "cursor": " Would generate: .cursorrules + .cursor/rules/*.mdc",
692
- "windsurf": " Would generate: .windsurfrules + .windsurf/rules/*.md",
729
+ "windsurf": " Would generate: .windsurfrules + .devin/rules/*.md + .windsurf/rules/*.md",
693
730
  "cline": " Would generate: .clinerules/*.md",
694
731
  "roo": " Would generate: .roomodes + .roo/rules/*.md",
695
732
  "aider": " Would generate: .aider.conf.yml + CONVENTIONS.md",
696
733
  "augment": " Would generate: .augment/rules/ai-toolkit-*.md",
697
- "antigravity": " Would generate: .agent/rules/ + .agent/workflows/",
734
+ "antigravity": " Would generate: .agents/rules/ + .agents/workflows/",
698
735
  "gemini": " Would generate: GEMINI.md",
699
736
  "opencode": " Would generate: AGENTS.md + .opencode/{agents,commands,plugins}/ + opencode.json",
700
737
  }
@@ -711,7 +748,7 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
711
748
  if "cursor" in eds:
712
749
  print(" Would generate: .cursor/hooks.json + .cursor/agents/ + .cursor/skills/ (profile=full)")
713
750
  if "windsurf" in eds:
714
- print(" Would generate: .windsurf/hooks.json + .windsurf/skills/ (profile=full)")
751
+ print(" Would generate: .windsurf/hooks.json + .devin/skills/ + .windsurf/skills/ (profile=full)")
715
752
  if "cline" in eds:
716
753
  print(" Would generate: .cline/skills/ (profile=full)")
717
754
  if "augment" in eds:
@@ -899,6 +936,12 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
899
936
  add_copilot_dir = _profile in {"standard", "strict", "full"}
900
937
  add_gemini_hooks = _profile in {"standard", "strict", "full"}
901
938
  add_native_surfaces = _profile == "full"
939
+ # Skip the editor skill-catalogue pointer when real skills are already
940
+ # discoverable at .claude/skills (project or ~/.claude/skills global):
941
+ # Cursor/Cline/Augment read those natively and by default. Emit it only as
942
+ # the editor-only fallback when no Claude skills are present. Windsurf is
943
+ # excluded (its .claude scan is gated) and keeps its pointer unconditionally.
944
+ emit_pointer = not _claude_skills_discoverable(cwd)
902
945
 
903
946
  if "copilot" in eds:
904
947
  inject_with_rules(
@@ -925,7 +968,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
925
968
  if add_native_surfaces:
926
969
  _try_generator("generate_cursor_hooks", cwd)
927
970
  _try_generator("generate_cursor_agents", cwd)
928
- _try_generator("generate_cursor_skills", cwd)
971
+ _try_generator("generate_cursor_skills", cwd, emit_skill_pointer=emit_pointer)
929
972
 
930
973
  if "windsurf" in eds:
931
974
  inject_with_rules(
@@ -938,6 +981,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
938
981
  rules_dir=rules_dir)
939
982
  if add_native_surfaces:
940
983
  _try_generator("generate_windsurf_hooks", cwd)
984
+ # Windsurf pointer stays unconditional (its .claude scan is gated).
941
985
  _try_generator("generate_windsurf_skills", cwd)
942
986
 
943
987
  if "cline" in eds:
@@ -954,7 +998,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
954
998
  managed_scopes=("standard", "lang", "custom"),
955
999
  )
956
1000
  if add_native_surfaces:
957
- _try_generator("generate_cline_skills", cwd)
1001
+ _try_generator("generate_cline_skills", cwd, emit_skill_pointer=emit_pointer)
958
1002
 
959
1003
  if "roo" in eds:
960
1004
  roo_output = run_script("generate-roo-modes.sh", capture=True)
@@ -980,7 +1024,7 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
980
1024
  # Augment hooks live under $HOME/.augment/settings.json. The
981
1025
  # generator takes HOME (not cwd) per bucket-1 contract.
982
1026
  _try_generator("generate_augment_hooks", Path.home())
983
- _try_generator("generate_augment_skills", cwd)
1027
+ _try_generator("generate_augment_skills", cwd, emit_skill_pointer=emit_pointer)
984
1028
 
985
1029
  if "antigravity" in eds:
986
1030
  from generate_antigravity import generate as gen_antigravity
@@ -988,20 +1032,14 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
988
1032
  rules_dir=rules_dir)
989
1033
 
990
1034
  if "codex" in eds:
991
- # AGENTS.md — marker injection (like CLAUDE.md)
1035
+ # AGENTS.md — marker injection; universal coding rules are inlined into
1036
+ # AGENTS.md (Codex reads instructions only from AGENTS.md, not a
1037
+ # .agents/rules/ directory). Language rules reach Codex via .agents/skills/.
992
1038
  inject_with_rules(
993
1039
  "generate_codex.py",
994
1040
  cwd / "AGENTS.md",
995
1041
  rules_dir,
996
1042
  )
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
1043
  # .codex/hooks.json — Codex lifecycle hooks
1006
1044
  from generate_codex_hooks import generate as gen_codex_hooks
1007
1045
  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]] = {}
@@ -851,6 +851,7 @@ _README_PLATFORM_KEY = {
851
851
  "claude code": "claude",
852
852
  "cursor": "cursor",
853
853
  "windsurf": "windsurf",
854
+ "windsurf (devin desktop)": "windsurf",
854
855
  "gemini cli": "gemini",
855
856
  "github copilot": "copilot",
856
857
  "cline": "cline",
@@ -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()