@softspark/ai-toolkit 3.0.2 → 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 (41) hide show
  1. package/AGENTS.md +13 -0
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +30 -23
  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/java-rules/SKILL.md +273 -0
  10. package/app/skills/kotlin-rules/SKILL.md +271 -0
  11. package/app/skills/medplum-rules/SKILL.md +271 -0
  12. package/app/skills/php-rules/SKILL.md +292 -0
  13. package/app/skills/python-rules/SKILL.md +257 -0
  14. package/app/skills/ruby-rules/SKILL.md +286 -0
  15. package/app/skills/rust-rules/SKILL.md +276 -0
  16. package/app/skills/swift-rules/SKILL.md +293 -0
  17. package/app/skills/typescript-rules/SKILL.md +249 -0
  18. package/benchmarks/ecosystem-doctor-snapshot.json +14 -14
  19. package/kb/history/completed/deep-coverage-v3-20260423.md +3 -3
  20. package/kb/history/completed/ecosystem-deep-sweep-20260423.md +1 -1
  21. package/kb/procedures/release-preparation-sop.md +4 -4
  22. package/kb/procedures/release-verification-sop.md +11 -12
  23. package/kb/reference/architecture-overview.md +1 -1
  24. package/kb/reference/global-install-model.md +29 -6
  25. package/kb/reference/language-rules.md +54 -18
  26. package/kb/reference/mcp-editor-compatibility.md +4 -3
  27. package/kb/reference/mcp-templates.md +3 -2
  28. package/kb/reference/supported-tools-registry.md +10 -8
  29. package/llms-full.txt +133 -58
  30. package/manifest.json +3 -3
  31. package/package.json +10 -3
  32. package/scripts/codex_skill_adapter.py +19 -3
  33. package/scripts/ecosystem_tools.json +7 -7
  34. package/scripts/generate_cline_rules.py +17 -8
  35. package/scripts/generate_codex_skills.py +33 -96
  36. package/scripts/generate_language_rules_skills.py +232 -0
  37. package/scripts/generate_roo_rules.py +11 -3
  38. package/scripts/install.py +6 -1
  39. package/scripts/install_steps/ai_tools.py +154 -51
  40. package/scripts/install_steps/install_state.py +14 -2
  41. package/scripts/mcp_editors.py +7 -0
@@ -1,4 +1,4 @@
1
- """Install AI tool configs (Cursor, Windsurf, Gemini, Augment, Codex) and local project setup."""
1
+ """Install global and project-local AI tool configs."""
2
2
  from __future__ import annotations
3
3
 
4
4
  import shutil
@@ -53,14 +53,6 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
53
53
  # control Claude components like agents, hooks, rules). If an editor is
54
54
  # in the requested set, install it unconditionally.
55
55
 
56
- if "cursor" in eds:
57
- cursor_file = target_dir / ".cursor" / "rules"
58
- if dry_run:
59
- print(" Would inject: ~/.cursor/rules")
60
- else:
61
- inject_with_rules("generate-cursor-rules.sh", cursor_file, rules_dir)
62
- installed.append("cursor")
63
-
64
56
  if "windsurf" in eds:
65
57
  windsurf_file = target_dir / ".codeium" / "windsurf" / "memories" / "global_rules.md"
66
58
  if dry_run:
@@ -85,6 +77,27 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
85
77
  inject_with_rules("generate-augment.sh", augment_file, rules_dir)
86
78
  installed.append("augment")
87
79
 
80
+ if "cline" in eds:
81
+ if dry_run:
82
+ print(" Would generate: ~/Documents/Cline/Rules/ai-toolkit-*.md")
83
+ else:
84
+ _install_cline_global(target_dir, rules_dir)
85
+ installed.append("cline")
86
+
87
+ if "roo" in eds:
88
+ if dry_run:
89
+ print(" Would generate: ~/.roo/rules/ai-toolkit-*.md")
90
+ else:
91
+ _install_roo_global(target_dir, rules_dir)
92
+ installed.append("roo")
93
+
94
+ if "aider" in eds:
95
+ if dry_run:
96
+ print(" Would create if missing: ~/.aider.conf.yml + ~/.aider-ai-toolkit-CONVENTIONS.md")
97
+ else:
98
+ _install_aider_global(target_dir, rules_dir)
99
+ installed.append("aider")
100
+
88
101
  if "codex" in eds:
89
102
  if dry_run:
90
103
  print(" Would inject: ~/AGENTS.md, ~/.agents/, ~/.codex/hooks.json")
@@ -102,7 +115,7 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
102
115
 
103
116
  print()
104
117
  print(f" Available: {', '.join(GLOBAL_CAPABLE_EDITORS)}")
105
- print(" Note: Copilot, Cline, Roo Code, Aider, Antigravity have no global config -- use 'ai-toolkit install --local' per project")
118
+ print(" Note: Cursor, Copilot, and Antigravity rule installs are project-local; use 'ai-toolkit install --local' for those.")
106
119
 
107
120
  return installed
108
121
 
@@ -136,6 +149,77 @@ def _install_codex_global(target_dir: Path, rules_dir: Path) -> None:
136
149
  _install_codex_skills(target_dir)
137
150
 
138
151
 
152
+ def _install_cline_global(target_dir: Path, rules_dir: Path) -> None:
153
+ """Install Cline global rules in the documented user rules directory."""
154
+ from generate_cline_rules import generate as gen_cline_rules
155
+
156
+ rules_root = target_dir / "Documents" / "Cline" / "Rules"
157
+ gen_cline_rules(
158
+ target_dir,
159
+ rules_dir=rules_dir,
160
+ output_root=rules_root,
161
+ emit_workflows=False,
162
+ managed_scopes=("standard", "custom"),
163
+ )
164
+ print(" Created: ~/Documents/Cline/Rules/ai-toolkit-*.md")
165
+
166
+
167
+ def _install_roo_global(target_dir: Path, rules_dir: Path) -> None:
168
+ """Install Roo Code global rules in ~/.roo/rules."""
169
+ from generate_roo_rules import generate as gen_roo_rules
170
+
171
+ gen_roo_rules(target_dir, rules_dir=rules_dir, output_root=target_dir / ".roo" / "rules")
172
+ print(" Created: ~/.roo/rules/ai-toolkit-*.md")
173
+
174
+
175
+ def _install_aider_global(target_dir: Path, rules_dir: Path) -> None:
176
+ """Install an Aider global config only when it can be created safely.
177
+
178
+ Aider supports ~/.aider.conf.yml, but YAML merging without a parser risks
179
+ clobbering user settings. For existing files we leave the user's config
180
+ untouched and print an explicit next step.
181
+ """
182
+ conventions_file = target_dir / ".aider-ai-toolkit-CONVENTIONS.md"
183
+ inject_with_rules("generate_conventions.py", conventions_file, rules_dir)
184
+
185
+ config_file = target_dir / ".aider.conf.yml"
186
+ if config_file.exists():
187
+ print(" Kept: ~/.aider.conf.yml (already exists; not merging YAML automatically)")
188
+ print(f" Available: {conventions_file} for manual read: entry")
189
+ return
190
+
191
+ default_model = _aider_default_model()
192
+ config_file.write_text(
193
+ "\n".join([
194
+ "# Aider configuration generated by ai-toolkit",
195
+ "# Aider docs: https://aider.chat/docs/config/aider_conf.html",
196
+ "",
197
+ "architect: true",
198
+ "auto-accept-architect: true",
199
+ "",
200
+ "read:",
201
+ f' - "{conventions_file}"',
202
+ "",
203
+ f"model: {default_model}",
204
+ f"editor-model: {default_model}",
205
+ "",
206
+ 'commit-prompt: "Write a short, concise commit message following Conventional Commits (feat/fix/chore/docs/test/refactor)."',
207
+ "attribute-co-authored-by: false",
208
+ "attribute-commit-message-author: false",
209
+ "attribute-commit-message-committer: false",
210
+ "",
211
+ ]),
212
+ encoding="utf-8",
213
+ )
214
+ print(" Created: ~/.aider.conf.yml")
215
+
216
+
217
+ def _aider_default_model() -> str:
218
+ from _common import DEFAULT_CLAUDE_MODELS
219
+
220
+ return DEFAULT_CLAUDE_MODELS["sonnet"]
221
+
222
+
139
223
  def _install_opencode_global(target_dir: Path, rules_dir: Path) -> None:
140
224
  """Install opencode at the global level (~/.config/opencode/).
141
225
 
@@ -331,10 +415,10 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
331
415
  - ``full``: adds every native surface an editor can host (subagents,
332
416
  custom commands, hooks, skill-catalogue pointers).
333
417
 
334
- ``codex_skills`` (opt-in, off by default) additionally writes a full skill
335
- mirror under ``.codex/skills/``. Required even for ``--profile full``
336
- the mirror is intentionally gated behind an extra flag to keep the default
337
- footprint small.
418
+ ``codex_skills`` (opt-in, off by default) explicitly refreshes the full
419
+ Codex skill catalog under ``.agents/skills/``. ``--editors codex`` already
420
+ installs this catalog for normal local installs; the flag is kept as a
421
+ direct generator contract for scripts and dry-run verification.
338
422
 
339
423
  If ``merged_config`` is provided (from .softspark-toolkit.json extends resolution),
340
424
  additional rules and constitution amendments from the base config are injected.
@@ -491,53 +575,67 @@ def _apply_extends_config(cwd: Path, merged: dict) -> None:
491
575
 
492
576
 
493
577
  def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
494
- """Inject language-specific rule summary into project's .claude/CLAUDE.md.
578
+ """Inject ``app/rules/common/*.md`` content into project's ``.claude/CLAUDE.md``.
579
+
580
+ Per-language rules (``app/rules/<lang>/``) are NOT injected here -- they
581
+ ship as ``<lang>-rules`` knowledge skills under ``app/skills/`` and load
582
+ contextually via the Agent Skills progressive-disclosure mechanism. This
583
+ keeps ``CLAUDE.md`` small while ensuring language-specific guidance still
584
+ reaches Claude when relevant.
495
585
 
496
- Instead of injecting full rule content (hundreds of lines), injects a
497
- compact summary with the key rules per category. Full rules are available
498
- as knowledge skills that Claude auto-loads contextually.
586
+ Common rules are language-agnostic (security, git workflow, testing,
587
+ coding-style, performance) and stay inlined so they remain in scope for
588
+ every prompt.
499
589
  """
500
590
  if not language_modules:
501
591
  return
502
592
 
503
593
  rules_src = app_dir / "rules"
504
- if not rules_src.is_dir():
594
+ common_dir = rules_src / "common"
595
+ if not common_dir.is_dir():
505
596
  return
506
597
 
507
- # Detect language names
598
+ # Detect requested per-language modules so we can name the linked skills
599
+ # in the marker block. The modules themselves are not inlined.
508
600
  langs: list[str] = []
509
601
  for mod in language_modules:
510
602
  if mod.startswith("rules-"):
511
- langs.append(mod[6:])
603
+ name = mod[6:]
604
+ if name != "common":
605
+ langs.append(name)
606
+
607
+ # Inline full content of every common rule file, stripping YAML
608
+ # frontmatter so the resulting block reads as plain Markdown.
609
+ inlined: list[str] = []
610
+ for f in sorted(common_dir.glob("*.md")):
611
+ body = f.read_text(encoding="utf-8")
612
+ if body.startswith("---"):
613
+ end = body.find("\n---", 3)
614
+ if end != -1:
615
+ body = body[end + 4:].lstrip("\n")
616
+ inlined.append(body.rstrip())
512
617
 
513
- if not langs:
514
- return
515
-
516
- # Build a lightweight reference pointer — NOT the full rules content.
517
- # Full rules are available as knowledge skills (auto-loaded by Claude)
518
- # and as files Claude can Read on demand.
519
- toolkit_pkg = app_dir.parent
520
618
  lines: list[str] = ["# Language Rules", ""]
521
- lines.append(f"This project uses: **{', '.join(langs)}**")
619
+ lines.append(
620
+ "Common (language-agnostic) rules apply to every change in this "
621
+ "project. Language-specific rules live in `<lang>-rules` knowledge "
622
+ "skills (e.g. `python-rules`, `typescript-rules`) and load "
623
+ "automatically when their triggers match -- you do not need to "
624
+ "Read them manually."
625
+ )
626
+ if langs:
627
+ skill_names = ", ".join(f"`{l}-rules`" for l in langs)
628
+ lines.append("")
629
+ lines.append(f"Detected languages: {skill_names}.")
522
630
  lines.append("")
523
- lines.append("When writing or reviewing code, use the Glob and Read tools to read the rules:")
524
- # Resolve actual installed path for the rules
525
- rules_resolved = str(rules_src.resolve())
526
- all_dirs: list[str] = ["common"]
527
- for l in langs:
528
- if l not in all_dirs:
529
- all_dirs.append(l)
530
- for lang in all_dirs:
531
- lang_path = rules_src / lang
532
- if lang_path.is_dir():
533
- categories = ", ".join(f.stem for f in sorted(lang_path.glob("*.md")))
534
- lines.append(f"- `{lang_path.resolve()}/` ({categories})")
631
+ lines.append("---")
535
632
  lines.append("")
536
- lines.append("Read the relevant rule files before making code changes. Do NOT guess — read first.")
633
+ lines.extend(inlined)
537
634
 
538
- # Write summary to temp file, then inject as section
635
+ # Write to temp file, then inject as a single named section so reruns are
636
+ # idempotent (existing block is replaced, not duplicated).
539
637
  import tempfile
540
- combined = "\n".join(lines)
638
+ combined = "\n\n".join(lines).rstrip() + "\n"
541
639
  with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False,
542
640
  encoding="utf-8") as tmp:
543
641
  tmp.write(combined)
@@ -545,8 +643,11 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
545
643
 
546
644
  try:
547
645
  inject_section(tmp_path, cwd / ".claude" / "CLAUDE.md", "language-rules")
548
- lang_names = [l for l in langs if l != "common"]
549
- print(f" Injected: language rules summary (common + {', '.join(lang_names)})")
646
+ if langs:
647
+ print(f" Injected: common rules + {len(langs)} language skill(s) "
648
+ f"({', '.join(langs)})")
649
+ else:
650
+ print(" Injected: common rules")
550
651
  finally:
551
652
  tmp_path.unlink(missing_ok=True)
552
653
 
@@ -600,8 +701,10 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
600
701
  "$HOME/.augment/settings.json + .augment/skills/ (profile=full)")
601
702
  if "gemini" in eds:
602
703
  print(" Would generate: .gemini/commands/ + .gemini/skills/ (profile=full)")
603
- if "codex" in eds and codex_skills:
604
- print(" Would generate: .codex/skills/ full mirror (--codex-skills)")
704
+ if "codex" in eds:
705
+ print(" Would generate: .agents/skills/ Codex skills")
706
+ if codex_skills:
707
+ print(" Would refresh: .agents/skills/ via --codex-skills")
605
708
 
606
709
  if not eds:
607
710
  print(" No editors selected (use --editors <list> or --editors all)")
@@ -881,10 +984,10 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
881
984
  from generate_codex_hooks import generate as gen_codex_hooks
882
985
  gen_codex_hooks(cwd)
883
986
  print(" Created: .codex/hooks.json")
884
- # .agents/skills/ — filtered symlinks (Codex-compatible skills only)
987
+ # .agents/skills/ — Codex discovery path for repo-local skills
885
988
  _install_codex_skills(cwd)
886
- # .codex/skills/ full mirror, gated behind explicit --codex-skills flag
887
- # (does NOT activate automatically on --profile full — must opt in).
989
+ # --codex-skills explicitly re-runs the same Codex skill sync path.
990
+ # Codex upstream discovers skills from .agents/skills/, not .codex/skills/.
888
991
  if codex_skills:
889
992
  _try_generator("generate_codex_skills", cwd,
890
993
  enable_codex_skills=True)
@@ -88,8 +88,20 @@ def remove_mcp_template(name: str) -> None:
88
88
  # Default global install: Claude only — no other editors unless --editors is used
89
89
  DEFAULT_GLOBAL_EDITORS: list[str] = []
90
90
 
91
- # All editors that support global install (opt-in via --editors)
92
- GLOBAL_CAPABLE_EDITORS = ["augment", "codex", "cursor", "gemini", "opencode", "windsurf"]
91
+ # All editors that support global install (opt-in via --editors).
92
+ #
93
+ # Cursor intentionally stays out of this list: its documented global rules
94
+ # surface is the Settings UI, not a stable file path we can merge safely.
95
+ GLOBAL_CAPABLE_EDITORS = [
96
+ "aider",
97
+ "augment",
98
+ "cline",
99
+ "codex",
100
+ "gemini",
101
+ "opencode",
102
+ "roo",
103
+ "windsurf",
104
+ ]
93
105
 
94
106
 
95
107
  def get_global_editors() -> list[str]:
@@ -41,6 +41,13 @@ EDITOR_SPECS: dict[str, dict[str, str | None]] = {
41
41
  "format": "json",
42
42
  "doc_scope": "project + global",
43
43
  },
44
+ "roo": {
45
+ "label": "Roo Code",
46
+ "project_path": ".roo/mcp.json",
47
+ "global_path": None,
48
+ "format": "json",
49
+ "doc_scope": "project",
50
+ },
44
51
  "windsurf": {
45
52
  "label": "Windsurf",
46
53
  "project_path": None,