@softspark/ai-toolkit 3.0.1 → 3.1.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 (53) hide show
  1. package/AGENTS.md +13 -0
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +34 -20
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/app/skills/cpp-rules/SKILL.md +275 -0
  6. package/app/skills/csharp-rules/SKILL.md +282 -0
  7. package/app/skills/dart-rules/SKILL.md +299 -0
  8. package/app/skills/golang-rules/SKILL.md +262 -0
  9. package/app/skills/hook-creator/SKILL.md +7 -3
  10. package/app/skills/introspect/SKILL.md +1 -1
  11. package/app/skills/java-rules/SKILL.md +273 -0
  12. package/app/skills/kotlin-rules/SKILL.md +271 -0
  13. package/app/skills/medplum-rules/SKILL.md +271 -0
  14. package/app/skills/php-rules/SKILL.md +292 -0
  15. package/app/skills/python-rules/SKILL.md +257 -0
  16. package/app/skills/ruby-rules/SKILL.md +286 -0
  17. package/app/skills/rust-rules/SKILL.md +276 -0
  18. package/app/skills/swift-rules/SKILL.md +293 -0
  19. package/app/skills/typescript-rules/SKILL.md +249 -0
  20. package/benchmarks/ecosystem-doctor-snapshot.json +14 -14
  21. package/bin/ai-toolkit.js +5 -1
  22. package/kb/history/completed/deep-coverage-v3-20260423.md +3 -3
  23. package/kb/history/completed/ecosystem-deep-sweep-20260423.md +1 -1
  24. package/kb/procedures/release-preparation-sop.md +4 -4
  25. package/kb/procedures/release-verification-sop.md +11 -12
  26. package/kb/reference/architecture-overview.md +1 -1
  27. package/kb/reference/cli-reference.md +14 -3
  28. package/kb/reference/competitive-features-implementation.md +9 -9
  29. package/kb/reference/global-install-model.md +29 -6
  30. package/kb/reference/hooks-catalog.md +8 -2
  31. package/kb/reference/language-rules.md +54 -18
  32. package/kb/reference/mcp-editor-compatibility.md +4 -3
  33. package/kb/reference/mcp-templates.md +3 -2
  34. package/kb/reference/supported-tools-registry.md +10 -8
  35. package/kb/reference/windows-support.md +50 -0
  36. package/llms-full.txt +220 -72
  37. package/llms.txt +1 -0
  38. package/manifest.json +3 -3
  39. package/package.json +14 -3
  40. package/scripts/_common.py +21 -0
  41. package/scripts/check_deps.py +14 -0
  42. package/scripts/codex_skill_adapter.py +19 -3
  43. package/scripts/ecosystem_tools.json +7 -7
  44. package/scripts/generate_cline_rules.py +17 -8
  45. package/scripts/generate_codex_skills.py +33 -96
  46. package/scripts/generate_language_rules_skills.py +232 -0
  47. package/scripts/generate_roo_rules.py +11 -3
  48. package/scripts/install.py +6 -1
  49. package/scripts/install_steps/ai_tools.py +154 -51
  50. package/scripts/install_steps/install_state.py +14 -2
  51. package/scripts/mcp_editors.py +7 -0
  52. package/scripts/stats.py +126 -39
  53. package/scripts/validate.py +160 -4
package/llms.txt CHANGED
@@ -58,4 +58,5 @@
58
58
  - [Supported Tools Registry](kb/reference/supported-tools-registry.md)
59
59
  - [Config Sync](kb/reference/sync.md)
60
60
  - [Unique Features & Differentiators](kb/reference/unique-features.md)
61
+ - [Windows Support](kb/reference/windows-support.md)
61
62
  - [Troubleshooting](kb/troubleshooting/README.md)
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.0.1",
2
+ "version": "3.1.0",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
@@ -9,7 +9,7 @@
9
9
  "tags": ["core", "agents"]
10
10
  },
11
11
  "skills": {
12
- "description": "99 skills (32 task + 31 hybrid + 36 knowledge)",
12
+ "description": "112 skills (32 task + 31 hybrid + 49 knowledge)",
13
13
  "path": "app/skills",
14
14
  "target": ".claude/skills",
15
15
  "type": "symlink",
@@ -90,7 +90,7 @@
90
90
  "default": true
91
91
  },
92
92
  "skills": {
93
- "description": "99 skills (task, hybrid, knowledge)",
93
+ "description": "112 skills (task, hybrid, knowledge)",
94
94
  "default": true
95
95
  },
96
96
  "rules-common": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "3.0.1",
4
- "description": "AI coding toolkit: 99 skills, 44 agents, 12-editor write-through (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo, Aider, Augment, Antigravity, Codex, opencode), machine-enforced safety constitution, SARIF audit, signed npm provenance.",
3
+ "version": "3.1.0",
4
+ "description": "AI coding toolkit: 112 skills, 44 agents, 12-editor write-through (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo, Aider, Augment, Antigravity, Codex, opencode), machine-enforced safety constitution, SARIF audit, signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",
7
7
  "claude-code",
@@ -60,16 +60,27 @@
60
60
  "generate:roo": "python3 scripts/generate_roo_modes.py > .roomodes",
61
61
  "generate:aider": "python3 scripts/generate_aider_conf.py > .aider.conf.yml",
62
62
  "generate:codex-rules": "python3 scripts/generate_codex_rules.py .",
63
+ "generate:language-rules": "python3 scripts/generate_language_rules_skills.py",
63
64
  "generate:opencode-agents": "python3 scripts/generate_opencode_agents.py .",
64
65
  "generate:opencode-commands": "python3 scripts/generate_opencode_commands.py .",
65
- "generate:all": "npm run generate:agents && npm run generate:codex-rules && npm run generate:opencode-agents && npm run generate:opencode-commands && npm run generate:cursor && npm run generate:windsurf && npm run generate:copilot && npm run generate:gemini && npm run generate:cline && npm run generate:roo && npm run generate:aider && npm run generate:llms"
66
+ "generate:cursor-mdc": "python3 scripts/generate_cursor_mdc.py .",
67
+ "generate:windsurf-rules": "python3 scripts/generate_windsurf_rules.py .",
68
+ "generate:roo-rules": "python3 scripts/generate_roo_rules.py .",
69
+ "generate:augment-rules": "python3 scripts/generate_augment_rules.py .",
70
+ "generate:all": "npm run generate:language-rules && npm run generate:agents && npm run generate:codex-rules && npm run generate:opencode-agents && npm run generate:opencode-commands && npm run generate:cursor && npm run generate:cursor-mdc && npm run generate:windsurf && npm run generate:windsurf-rules && npm run generate:copilot && npm run generate:gemini && npm run generate:cline && npm run generate:roo && npm run generate:roo-rules && npm run generate:aider && npm run generate:augment-rules && npm run generate:llms"
66
71
  },
67
72
  "files": [
68
73
  "bin/",
69
74
  "app/",
75
+ "!app/**/__pycache__",
76
+ "!app/**/*.pyc",
70
77
  "scripts/",
78
+ "!scripts/**/__pycache__",
79
+ "!scripts/**/*.pyc",
71
80
  "kb/",
72
81
  "benchmarks/",
82
+ "!benchmarks/**/__pycache__",
83
+ "!benchmarks/**/*.pyc",
73
84
  "action.yml",
74
85
  "manifest.json",
75
86
  "README.md",
@@ -165,6 +165,27 @@ def detect_os() -> dict[str, str]:
165
165
  "install_cmd": install_cmd,
166
166
  }
167
167
 
168
+ if system == "windows":
169
+ managers = (
170
+ ("winget", "winget install"),
171
+ ("choco", "choco install -y"),
172
+ ("scoop", "scoop install"),
173
+ )
174
+ for manager, install_cmd in managers:
175
+ if shutil.which(manager):
176
+ return {
177
+ "os": "Windows",
178
+ "distro": platform.version(),
179
+ "pkg_manager": manager,
180
+ "install_cmd": install_cmd,
181
+ }
182
+ return {
183
+ "os": "Windows",
184
+ "distro": platform.version(),
185
+ "pkg_manager": "none",
186
+ "install_cmd": "",
187
+ }
188
+
168
189
  return {
169
190
  "os": system,
170
191
  "distro": "",
@@ -37,6 +37,9 @@ REQUIRED = [
37
37
  "pacman": "python3",
38
38
  "apk": "python3",
39
39
  "zypper": "python3",
40
+ "winget": "Python.Python.3",
41
+ "choco": "python",
42
+ "scoop": "python",
40
43
  },
41
44
  "reason": "All toolkit scripts run on Python 3 (stdlib only, no pip needed)",
42
45
  },
@@ -50,6 +53,9 @@ REQUIRED = [
50
53
  "pacman": "git",
51
54
  "apk": "git",
52
55
  "zypper": "git",
56
+ "winget": "Git.Git",
57
+ "choco": "git",
58
+ "scoop": "git",
53
59
  },
54
60
  "reason": "Version control — hooks, commits, PR workflows",
55
61
  },
@@ -64,6 +70,9 @@ REQUIRED = [
64
70
  "pacman": "nodejs",
65
71
  "apk": "nodejs",
66
72
  "zypper": "nodejs18",
73
+ "winget": "OpenJS.NodeJS",
74
+ "choco": "nodejs",
75
+ "scoop": "nodejs",
67
76
  },
68
77
  "reason": "CLI entry point (bin/ai-toolkit.js)",
69
78
  },
@@ -80,6 +89,9 @@ OPTIONAL = [
80
89
  "pacman": "sqlite",
81
90
  "apk": "sqlite",
82
91
  "zypper": "sqlite3",
92
+ "winget": "SQLite.SQLite",
93
+ "choco": "sqlite",
94
+ "scoop": "sqlite",
83
95
  },
84
96
  "reason": "Memory plugin pack (session persistence via SQLite + FTS5)",
85
97
  },
@@ -93,6 +105,8 @@ OPTIONAL = [
93
105
  "pacman": "bash-bats",
94
106
  "apk": "bats",
95
107
  "zypper": "bats",
108
+ "choco": "bats",
109
+ "scoop": "bats",
96
110
  },
97
111
  "reason": "Running toolkit test suite (npm test)",
98
112
  },
@@ -155,15 +155,31 @@ def sync_codex_skill(skill_dir: Path, skills_dst: Path) -> str:
155
155
 
156
156
  def cleanup_codex_skills(skills_dst: Path, skills_src: Path) -> None:
157
157
  """Remove broken symlinks and stale generated Codex skill wrappers."""
158
+ skills_src_resolved = skills_src.resolve()
158
159
  for item in skills_dst.iterdir():
159
160
  src = skills_src / item.name
160
- if item.is_symlink() and not item.exists():
161
- item.unlink()
162
- continue
161
+ if item.is_symlink():
162
+ if not item.exists():
163
+ item.unlink()
164
+ continue
165
+ target = item.resolve()
166
+ if src.is_dir() and target == src.resolve():
167
+ continue
168
+ if _is_relative_to(target, skills_src_resolved):
169
+ item.unlink()
170
+ continue
163
171
  if item.is_dir() and (item / ADAPTED_MARKER).is_file() and not src.is_dir():
164
172
  shutil.rmtree(item)
165
173
 
166
174
 
175
+ def _is_relative_to(path: Path, parent: Path) -> bool:
176
+ try:
177
+ path.relative_to(parent)
178
+ return True
179
+ except ValueError:
180
+ return False
181
+
182
+
167
183
  def _parse_frontmatter(frontmatter_text: str) -> list[tuple[str, str]]:
168
184
  entries: list[tuple[str, str]] = []
169
185
  for line in frontmatter_text.splitlines():
@@ -81,8 +81,7 @@
81
81
  "~/.cursor/mcp.json",
82
82
  ".cursor/skills/*/SKILL.md",
83
83
  ".cursor/agents/*.md",
84
- ".cursor/hooks.json",
85
- "~/.cursor/hooks.json"
84
+ ".cursor/hooks.json"
86
85
  ],
87
86
  "our_generators": [
88
87
  "scripts/generate_cursor_rules.py",
@@ -264,8 +263,8 @@
264
263
  ".roo/rules-{slug}/*.md",
265
264
  ".roo/mcp.json",
266
265
  "~/.roo/rules/",
267
- "~/.roo/settings/custom_modes.yaml",
268
- "~/.roo/settings/mcp_settings.json"
266
+ "~/.roo/custom_modes.yaml",
267
+ "mcp_settings.json"
269
268
  ],
270
269
  "our_generators": [
271
270
  "scripts/generate_roo_modes.py",
@@ -413,14 +412,15 @@
413
412
  "config_paths": [
414
413
  "AGENTS.md",
415
414
  ".agents/rules/*.md",
415
+ ".agents/skills/*/SKILL.md",
416
416
  ".codex/hooks.json",
417
- ".codex/skills/*/SKILL.md",
418
417
  "~/.codex/config.toml"
419
418
  ],
420
419
  "our_generators": [
421
420
  "scripts/generate_codex.py",
422
421
  "scripts/generate_codex_rules.py",
423
- "scripts/generate_codex_hooks.py"
422
+ "scripts/generate_codex_hooks.py",
423
+ "scripts/generate_codex_skills.py"
424
424
  ],
425
425
  "capability_markers": [
426
426
  "AGENTS.md",
@@ -437,7 +437,7 @@
437
437
  "hook handler: command",
438
438
  "hook handler: prompt",
439
439
  "hook handler: agent",
440
- ".codex/skills"
440
+ ".agents/skills"
441
441
  ],
442
442
  "version_probe": {
443
443
  "kind": "command",
@@ -79,13 +79,20 @@ def generate(target_dir: Path, *,
79
79
  rules_dir: Path | None = None,
80
80
  cleanup: bool = True,
81
81
  emit_workflows: bool = True,
82
- managed_scopes: tuple[str, ...] = (STANDARD_SCOPE,)) -> None:
83
- """Write ``.clinerules/*.md`` (and workflow) files to target_dir."""
82
+ managed_scopes: tuple[str, ...] = (STANDARD_SCOPE,),
83
+ output_root: Path | None = None) -> None:
84
+ """Write Cline rule files.
85
+
86
+ By default writes project-local ``target_dir/.clinerules/*.md``. When
87
+ ``output_root`` is provided, writes directly into that directory; this is
88
+ used for Cline's documented global rules directory.
89
+ """
84
90
  # Migrate: if .clinerules exists as a single file, remove it so the
85
91
  # directory can be created (Cline 3.7+ uses directory format).
86
- clinerules = target_dir / ".clinerules"
87
- if clinerules.is_file():
88
- clinerules.unlink()
92
+ if output_root is None:
93
+ clinerules = target_dir / ".clinerules"
94
+ if clinerules.is_file():
95
+ clinerules.unlink()
89
96
 
90
97
  rules: dict[str, callable] = dict(STANDARD_RULES)
91
98
  # Replace the testing rule with a conditional variant so it only
@@ -107,15 +114,17 @@ def generate(target_dir: Path, *,
107
114
 
108
115
  rules.update(build_registered_rules(rules_dir))
109
116
 
117
+ root = output_root.parent if output_root is not None else target_dir
118
+ subdir = output_root.name if output_root is not None else ".clinerules"
110
119
  write_rules(
111
- target_dir,
120
+ root,
112
121
  rules,
113
- ".clinerules",
122
+ subdir,
114
123
  cleanup=cleanup,
115
124
  managed_scopes=managed_scopes,
116
125
  )
117
126
 
118
- if emit_workflows:
127
+ if emit_workflows and output_root is None:
119
128
  _write_workflows(target_dir, cleanup=cleanup)
120
129
 
121
130
 
@@ -1,26 +1,23 @@
1
1
  #!/usr/bin/env python3
2
- """Mirror the full ai-toolkit skill catalogue into ``.codex/skills/``.
2
+ """Mirror the ai-toolkit skill catalogue into Codex ``.agents/skills/``.
3
3
 
4
- OpenAI Codex CLI supports the Agent Skills standard at
5
- ``.codex/skills/<skill-name>/SKILL.md`` with optional supporting files
6
- (scripts, references, assets) in the skill directory. Unlike the Augment
7
- and Gemini pointer pattern, Codex benefits from having the full skill
8
- content on disk, so this generator performs a **full mirror** of every
9
- skill in ``app/skills/`` into ``<target-dir>/.codex/skills/<name>/``.
4
+ OpenAI Codex CLI discovers Agent Skills from ``.agents/skills/`` in the
5
+ repository tree, plus user/admin/system skill locations. Unlike the Augment
6
+ and Gemini pointer pattern, Codex benefits from having the full skill catalog
7
+ on disk, so this generator syncs every skill in ``app/skills/`` into
8
+ ``<target-dir>/.agents/skills/<name>/``.
10
9
 
11
10
  The mirror is **opt-in**: ``enable_codex_skills=False`` is the default.
12
11
  Bucket 4 wires a ``--codex-skills`` CLI flag that toggles this on.
13
12
 
14
13
  Implementation:
15
- * Prefer symlinks from ``.codex/skills/<name>`` to the canonical
16
- ``app/skills/<name>`` directory (atomic and cheap).
17
- * Fall back to a recursive copy when symlinks are unavailable (Windows
18
- without developer mode, hostile filesystems, etc.).
14
+ * Native Codex-compatible skills are symlinked to canonical ``app/skills``.
15
+ * Skills that use Claude-only delegation tools are rendered as Codex
16
+ wrappers through ``codex_skill_adapter.sync_codex_skill``.
19
17
  * Skip ``_lib`` and any dotfile directories under ``app/skills/``.
20
- * Remove stale entries under ``.codex/skills/`` that no longer
21
- correspond to a source skill (cleanup on rerun).
22
- * Never touch user-authored entries in ``.codex/skills/`` that do not
23
- match a source skill name and are not our managed targets.
18
+ * Remove stale generated wrappers and broken managed symlinks.
19
+ * Preserve user-authored entries in ``.agents/skills/`` that are not managed
20
+ by ai-toolkit.
24
21
 
25
22
  Idempotent on rerun.
26
23
 
@@ -32,12 +29,11 @@ keeping the opt-in default enforced even when invoked directly.
32
29
  """
33
30
  from __future__ import annotations
34
31
 
35
- import os
36
- import shutil
37
32
  import sys
38
33
  from pathlib import Path
39
34
 
40
35
  sys.path.insert(0, str(Path(__file__).resolve().parent))
36
+ from codex_skill_adapter import cleanup_codex_skills, sync_codex_skill
41
37
  from emission import skills_dir
42
38
 
43
39
 
@@ -69,69 +65,11 @@ def _iter_source_skills() -> list[Path]:
69
65
  # Mirror operations
70
66
  # ---------------------------------------------------------------------------
71
67
 
72
- def _remove_existing(target: Path) -> None:
73
- """Remove an existing file, symlink, or directory at ``target``.
74
-
75
- Uses ``lstat`` so symlinks are unlinked without following them.
76
- """
77
- if not target.exists() and not target.is_symlink():
78
- return
79
- if target.is_symlink() or target.is_file():
80
- target.unlink()
81
- return
82
- shutil.rmtree(target)
83
-
84
-
85
- def _symlink_or_copy(source: Path, target: Path) -> str:
86
- """Create ``target`` as a symlink to ``source``; fall back to a copy.
87
-
88
- Returns ``"symlink"`` or ``"copy"`` indicating which strategy was used.
89
- """
90
- _remove_existing(target)
91
- target.parent.mkdir(parents=True, exist_ok=True)
92
- try:
93
- os.symlink(source, target, target_is_directory=True)
94
- return "symlink"
95
- except (OSError, NotImplementedError):
96
- shutil.copytree(source, target, symlinks=False)
97
- return "copy"
98
-
99
-
100
- def _cleanup_stale(codex_skills_dir: Path, live_names: set[str]) -> list[str]:
101
- """Remove managed entries under ``.codex/skills/`` that no longer map
102
- to a source skill. Returns the names removed.
103
-
104
- A managed entry is one whose directory name matches a historical
105
- source skill name pattern: it contains a ``SKILL.md`` either directly
106
- (copy) or via a symlink back into ``app/skills/``. User-authored
107
- entries that do not look managed are left alone.
108
- """
68
+ def _count_entries(codex_skills_dir: Path) -> int:
69
+ """Count top-level skill entries after sync."""
109
70
  if not codex_skills_dir.is_dir():
110
- return []
111
- removed: list[str] = []
112
- for entry in sorted(codex_skills_dir.iterdir()):
113
- if not entry.is_dir() and not entry.is_symlink():
114
- continue
115
- if entry.name in live_names:
116
- continue
117
- if entry.is_symlink():
118
- # Only remove symlinks that point inside our app/skills/ tree.
119
- try:
120
- resolved = entry.resolve()
121
- except OSError:
122
- continue
123
- try:
124
- resolved.relative_to(skills_dir.resolve())
125
- except ValueError:
126
- continue
127
- entry.unlink()
128
- removed.append(entry.name)
129
- continue
130
- # Copy mode: treat as managed only if a SKILL.md is present.
131
- if (entry / "SKILL.md").is_file():
132
- shutil.rmtree(entry)
133
- removed.append(entry.name)
134
- return removed
71
+ return 0
72
+ return sum(1 for entry in codex_skills_dir.iterdir() if entry.is_dir() or entry.is_symlink())
135
73
 
136
74
 
137
75
  # ---------------------------------------------------------------------------
@@ -139,10 +77,10 @@ def _cleanup_stale(codex_skills_dir: Path, live_names: set[str]) -> list[str]:
139
77
  # ---------------------------------------------------------------------------
140
78
 
141
79
  def generate(target_dir: Path, enable_codex_skills: bool = False) -> None:
142
- """Mirror ``app/skills/`` into ``<target_dir>/.codex/skills/``.
80
+ """Mirror ``app/skills/`` into ``<target_dir>/.agents/skills/``.
143
81
 
144
82
  Args:
145
- target_dir: Project root where ``.codex/skills/`` is written.
83
+ target_dir: Project root where ``.agents/skills/`` is written.
146
84
  enable_codex_skills: Opt-in flag. Defaults to ``False`` (no-op).
147
85
 
148
86
  Contract for Bucket 4 wiring::
@@ -156,31 +94,30 @@ def generate(target_dir: Path, enable_codex_skills: bool = False) -> None:
156
94
  if not enable_codex_skills:
157
95
  return
158
96
 
159
- codex_skills_dir = target_dir / ".codex" / "skills"
97
+ codex_skills_dir = target_dir / ".agents" / "skills"
160
98
  codex_skills_dir.mkdir(parents=True, exist_ok=True)
161
99
 
162
100
  sources = _iter_source_skills()
163
- live_names: set[str] = {s.name for s in sources}
164
101
 
165
- symlink_count = 0
166
- copy_count = 0
102
+ linked = 0
103
+ adapted = 0
104
+ skipped = 0
167
105
  for skill in sources:
168
- target = codex_skills_dir / skill.name
169
- mode = _symlink_or_copy(skill, target)
170
- if mode == "symlink":
171
- symlink_count += 1
106
+ mode = sync_codex_skill(skill, codex_skills_dir)
107
+ if mode == "linked":
108
+ linked += 1
109
+ elif mode == "adapted":
110
+ adapted += 1
172
111
  else:
173
- copy_count += 1
112
+ skipped += 1
174
113
 
175
- removed = _cleanup_stale(codex_skills_dir, live_names)
114
+ cleanup_codex_skills(codex_skills_dir, skills_dir)
176
115
 
177
- total = symlink_count + copy_count
178
116
  print(
179
- f" Codex skill mirror: {total} skills "
180
- f"({symlink_count} symlink, {copy_count} copy)"
117
+ f" Codex skill mirror: {_count_entries(codex_skills_dir)} skills "
118
+ f"to .agents/skills/ ({linked} linked, {adapted} adapted, "
119
+ f"{skipped} skipped)"
181
120
  )
182
- if removed:
183
- print(f" Codex skill mirror: removed {len(removed)} stale entries")
184
121
 
185
122
 
186
123
  def main() -> None: