@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.
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +46 -0
- package/README.md +34 -20
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/skills/cpp-rules/SKILL.md +275 -0
- package/app/skills/csharp-rules/SKILL.md +282 -0
- package/app/skills/dart-rules/SKILL.md +299 -0
- package/app/skills/golang-rules/SKILL.md +262 -0
- package/app/skills/hook-creator/SKILL.md +7 -3
- package/app/skills/introspect/SKILL.md +1 -1
- package/app/skills/java-rules/SKILL.md +273 -0
- package/app/skills/kotlin-rules/SKILL.md +271 -0
- package/app/skills/medplum-rules/SKILL.md +271 -0
- package/app/skills/php-rules/SKILL.md +292 -0
- package/app/skills/python-rules/SKILL.md +257 -0
- package/app/skills/ruby-rules/SKILL.md +286 -0
- package/app/skills/rust-rules/SKILL.md +276 -0
- package/app/skills/swift-rules/SKILL.md +293 -0
- package/app/skills/typescript-rules/SKILL.md +249 -0
- package/benchmarks/ecosystem-doctor-snapshot.json +14 -14
- package/bin/ai-toolkit.js +5 -1
- package/kb/history/completed/deep-coverage-v3-20260423.md +3 -3
- package/kb/history/completed/ecosystem-deep-sweep-20260423.md +1 -1
- package/kb/procedures/release-preparation-sop.md +4 -4
- package/kb/procedures/release-verification-sop.md +11 -12
- package/kb/reference/architecture-overview.md +1 -1
- package/kb/reference/cli-reference.md +14 -3
- package/kb/reference/competitive-features-implementation.md +9 -9
- package/kb/reference/global-install-model.md +29 -6
- package/kb/reference/hooks-catalog.md +8 -2
- package/kb/reference/language-rules.md +54 -18
- package/kb/reference/mcp-editor-compatibility.md +4 -3
- package/kb/reference/mcp-templates.md +3 -2
- package/kb/reference/supported-tools-registry.md +10 -8
- package/kb/reference/windows-support.md +50 -0
- package/llms-full.txt +220 -72
- package/llms.txt +1 -0
- package/manifest.json +3 -3
- package/package.json +14 -3
- package/scripts/_common.py +21 -0
- package/scripts/check_deps.py +14 -0
- package/scripts/codex_skill_adapter.py +19 -3
- package/scripts/ecosystem_tools.json +7 -7
- package/scripts/generate_cline_rules.py +17 -8
- package/scripts/generate_codex_skills.py +33 -96
- package/scripts/generate_language_rules_skills.py +232 -0
- package/scripts/generate_roo_rules.py +11 -3
- package/scripts/install.py +6 -1
- package/scripts/install_steps/ai_tools.py +154 -51
- package/scripts/install_steps/install_state.py +14 -2
- package/scripts/mcp_editors.py +7 -0
- package/scripts/stats.py +126 -39
- 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
|
|
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": "
|
|
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": "
|
|
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
|
|
4
|
-
"description": "AI coding toolkit:
|
|
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:
|
|
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",
|
package/scripts/_common.py
CHANGED
|
@@ -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": "",
|
package/scripts/check_deps.py
CHANGED
|
@@ -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()
|
|
161
|
-
item.
|
|
162
|
-
|
|
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/
|
|
268
|
-
"
|
|
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
|
-
".
|
|
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,)
|
|
83
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
clinerules.
|
|
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
|
-
|
|
120
|
+
root,
|
|
112
121
|
rules,
|
|
113
|
-
|
|
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
|
|
2
|
+
"""Mirror the ai-toolkit skill catalogue into Codex ``.agents/skills/``.
|
|
3
3
|
|
|
4
|
-
OpenAI Codex CLI
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
73
|
-
"""
|
|
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
|
-
|
|
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>/.
|
|
80
|
+
"""Mirror ``app/skills/`` into ``<target_dir>/.agents/skills/``.
|
|
143
81
|
|
|
144
82
|
Args:
|
|
145
|
-
target_dir: Project root where ``.
|
|
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 / ".
|
|
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
|
-
|
|
166
|
-
|
|
102
|
+
linked = 0
|
|
103
|
+
adapted = 0
|
|
104
|
+
skipped = 0
|
|
167
105
|
for skill in sources:
|
|
168
|
-
|
|
169
|
-
mode
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
112
|
+
skipped += 1
|
|
174
113
|
|
|
175
|
-
|
|
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: {
|
|
180
|
-
f"({
|
|
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:
|