@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.
- package/AGENTS.md +50 -0
- package/CHANGELOG.md +46 -0
- package/README.md +13 -13
- package/app/.claude-plugin/plugin.json +1 -1
- package/benchmarks/ecosystem-doctor-snapshot.json +76 -54
- package/bin/ai-toolkit.js +2 -5
- package/kb/procedures/maintenance-sop.md +9 -8
- package/kb/procedures/release-preparation-sop.md +24 -16
- package/kb/procedures/release-verification-sop.md +4 -6
- package/kb/reference/architecture-overview.md +5 -6
- 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 +40 -33
- package/llms-full.txt +89 -77
- package/manifest.json +1 -1
- package/package.json +2 -3
- package/scripts/ecosystem_tools.json +74 -20
- package/scripts/generate_antigravity.py +32 -21
- 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_cursor_mdc.py +4 -3
- package/scripts/generate_opencode_commands.py +9 -9
- package/scripts/generate_windsurf_hooks.py +6 -0
- package/scripts/generate_windsurf_rules.py +39 -24
- package/scripts/generate_windsurf_skills.py +14 -4
- package/scripts/install.py +1 -1
- package/scripts/install_steps/ai_tools.py +68 -30
- package/scripts/plugin.py +2 -2
- package/scripts/validate.py +1 -0
- 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).
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
|
|
@@ -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)
|
|
2
|
+
"""Generate ``.devin/rules/*.md`` and ``.windsurf/rules/*.md`` (and workflows)
|
|
3
|
+
for Devin Desktop (formerly Windsurf).
|
|
3
4
|
|
|
4
|
-
Windsurf
|
|
5
|
-
|
|
6
|
-
(
|
|
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,
|
|
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
|
-
|
|
14
|
-
|
|
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 —
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
167
|
+
if cleanup:
|
|
168
|
+
cleanup_stale(workflows_dir, set(STANDARD_WORKFLOWS.keys()))
|
|
155
169
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
187
|
+
"""Write ``rules/*.md`` and ``workflows/*.md`` to both config trees."""
|
|
174
188
|
rules = _build_rules(language_modules, rules_dir)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
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 =
|
|
21
|
+
skill_root: str | None = None) -> None:
|
|
14
22
|
if not emit_skill_pointer:
|
|
15
23
|
return
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
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:
|
|
@@ -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
|
-
|
|
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: .
|
|
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
|
|
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]] = {}
|
package/scripts/validate.py
CHANGED
|
@@ -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()
|