@softspark/ai-toolkit 4.11.0 → 4.13.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 (46) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +33 -15
  3. package/app/.claude-plugin/plugin.json +11 -19
  4. package/app/ARCHITECTURE.md +6 -6
  5. package/app/claude-app/global-instructions.md +10 -0
  6. package/app/claude-app/hooks/hooks.json +284 -0
  7. package/app/claude-app/skills/ai-toolkit-rules/SKILL.md +359 -0
  8. package/app/hooks/config-desync-guard.sh +63 -23
  9. package/app/plugins/README.md +4 -1
  10. package/benchmarks/ecosystem-doctor-snapshot.json +91 -25
  11. package/bin/ai-toolkit.js +21 -3
  12. package/kb/planning/drop-cascade-hooks-after-sunset.md +13 -8
  13. package/kb/procedures/ecosystem-sync-sop.md +5 -5
  14. package/kb/procedures/maintenance-sop.md +42 -4
  15. package/kb/procedures/release-preparation-sop.md +5 -1
  16. package/kb/procedures/release-verification-sop.md +25 -9
  17. package/kb/reference/architecture-overview.md +7 -2
  18. package/kb/reference/claude-ecosystem-expansion-foundations.md +12 -3
  19. package/kb/reference/cli-reference.md +4 -2
  20. package/kb/reference/codex-cli-compatibility.md +15 -10
  21. package/kb/reference/global-install-model.md +37 -11
  22. package/kb/reference/hooks-catalog.md +2 -3
  23. package/kb/reference/plugin-pack-conventions.md +5 -5
  24. package/kb/reference/skills-catalog.md +2 -0
  25. package/kb/reference/supported-tools-registry.md +59 -33
  26. package/kb/reference/unique-features.md +3 -2
  27. package/llms-full.txt +236 -98
  28. package/manifest.json +8 -8
  29. package/package.json +4 -3
  30. package/scripts/claude_app.py +347 -0
  31. package/scripts/doctor.py +85 -4
  32. package/scripts/ecosystem_tools.json +45 -9
  33. package/scripts/generate_antigravity.py +15 -0
  34. package/scripts/generate_codex_hooks.py +8 -5
  35. package/scripts/generate_copilot.py +10 -3
  36. package/scripts/generate_devin_hooks.py +3 -4
  37. package/scripts/generate_windsurf_skills.py +5 -6
  38. package/scripts/inject_hook_cli.py +16 -2
  39. package/scripts/install.py +25 -23
  40. package/scripts/install_steps/ai_tools.py +234 -14
  41. package/scripts/install_steps/install_state.py +11 -2
  42. package/scripts/plugin.py +31 -8
  43. package/scripts/update_projects.py +12 -1
  44. package/scripts/validate.py +34 -2
  45. package/AGENTS.md +0 -790
  46. package/scripts/generate_windsurf_hooks.py +0 -152
package/scripts/plugin.py CHANGED
@@ -23,6 +23,7 @@ Actions:
23
23
  from __future__ import annotations
24
24
 
25
25
  import json
26
+ import re
26
27
  import shutil
27
28
  import sqlite3 as sqlite
28
29
  import subprocess
@@ -31,6 +32,7 @@ from pathlib import Path
31
32
 
32
33
  sys.path.insert(0, str(Path(__file__).resolve().parent))
33
34
  from _common import app_dir, inject_section, inject_rule, remove_rule_section
35
+ from injection import strip_section, trim_trailing_blanks
34
36
  from codex_skill_adapter import cleanup_codex_skills, sync_codex_skill
35
37
  from generate_codex_hooks import generate as generate_codex_hooks
36
38
  from install_steps.ai_tools import inject_with_rules
@@ -536,8 +538,9 @@ def _install_codex_extra_skills(pack: dict, pack_dir: Path) -> None:
536
538
  def _install_codex_base() -> None:
537
539
  _ensure_core_hook_scripts()
538
540
  # Universal coding rules are inlined into AGENTS.md by generate_codex.py;
539
- # Codex does not read a .agents/rules/ directory.
540
- inject_with_rules("generate_codex.py", CODEX_ROOT / "AGENTS.md", RULES_DIR)
541
+ # Codex does not read a .agents/rules/ directory. Global instructions must
542
+ # live at $CODEX_HOME/AGENTS.md (default ~/.codex/AGENTS.md), not ~/AGENTS.md.
543
+ inject_with_rules("generate_codex.py", CODEX_ROOT / ".codex" / "AGENTS.md", RULES_DIR)
541
544
  hooks_path = CODEX_ROOT / ".codex" / "hooks.json"
542
545
  existing = _load_json(hooks_path, {"hooks": {}})
543
546
  plugin_entries: dict[str, list[dict]] = {}
@@ -632,21 +635,41 @@ def _strip_codex_hooks(name: str) -> None:
632
635
 
633
636
 
634
637
  def _install_codex_rules(name: str, rule_specs: list[dict]) -> None:
635
- rules_dir = CODEX_ROOT / ".agents" / "rules"
636
- rules_dir.mkdir(parents=True, exist_ok=True)
638
+ # Codex reads instructions only from AGENTS.md, never from a .agents/rules/
639
+ # directory, so pack rules are marker-injected into ~/.codex/AGENTS.md (the
640
+ # documented global instruction file) instead of written as dead files.
641
+ agents_md = CODEX_ROOT / ".codex" / "AGENTS.md"
637
642
  for spec in rule_specs:
638
- dest = rules_dir / f"plugin-{name}-{spec['name']}.md"
639
- shutil.copy2(spec["source"], dest)
640
- print(f" Installed Codex rule: {dest.name}")
643
+ section = f"plugin-{name}-{spec['name']}"
644
+ inject_section(spec["source"], agents_md, section)
645
+ print(f" Injected Codex rule: {spec['name']} -> ~/.codex/AGENTS.md")
646
+ _clean_legacy_codex_rule_files(name)
641
647
 
642
648
 
643
649
  def _remove_codex_rules(name: str) -> None:
650
+ agents_md = CODEX_ROOT / ".codex" / "AGENTS.md"
651
+ if agents_md.is_file():
652
+ content = agents_md.read_text(encoding="utf-8")
653
+ changed = False
654
+ for match in re.findall(r"<!-- TOOLKIT:(plugin-" + re.escape(name) + r"-[^ ]+) START -->", content):
655
+ content = strip_section(content, match)
656
+ changed = True
657
+ if changed:
658
+ agents_md.write_text(trim_trailing_blanks(content) + "\n", encoding="utf-8")
659
+ print(f" Removed Codex rules for {name} from ~/.codex/AGENTS.md")
660
+ _clean_legacy_codex_rule_files(name)
661
+
662
+
663
+ def _clean_legacy_codex_rule_files(name: str) -> None:
664
+ """Remove dead ~/.agents/rules/plugin-<name>-*.md files written by earlier
665
+ versions. Codex never read them; Antigravity's .agents/rules/ is a separate,
666
+ project-local surface, so this only touches our own plugin-prefixed files."""
644
667
  rules_dir = CODEX_ROOT / ".agents" / "rules"
645
668
  if not rules_dir.is_dir():
646
669
  return
647
670
  for rule_file in sorted(rules_dir.glob(f"plugin-{name}-*.md")):
648
671
  rule_file.unlink()
649
- print(f" Removed Codex rule: {rule_file.name}")
672
+ print(f" Removed legacy Codex rule file: {rule_file.name}")
650
673
 
651
674
 
652
675
  def install_pack_codex(name: str, pack: dict, pack_dir: Path) -> bool:
@@ -28,8 +28,19 @@ def _update_project(project: dict[str, Any], install_script: str, extra_args: li
28
28
  # Pass saved editors from registry so update re-installs the same editors
29
29
  cmd_args = ["python3", install_script, "--local"] + extra_args
30
30
  project_editors = project.get("editors", [])
31
- if project_editors:
31
+ has_editor_override = any(
32
+ arg == "--editors" or arg.startswith("--editors=")
33
+ for arg in extra_args
34
+ )
35
+ if project_editors and not has_editor_override:
32
36
  cmd_args.extend(["--editors", ",".join(project_editors)])
37
+ has_profile_override = any(
38
+ arg == "--profile" or arg.startswith("--profile=")
39
+ for arg in extra_args
40
+ )
41
+ project_profile = project.get("profile", "")
42
+ if project_profile and not has_profile_override:
43
+ cmd_args.extend(["--profile", project_profile])
33
44
 
34
45
  try:
35
46
  proc = subprocess.run(
@@ -124,6 +124,10 @@ LANGUAGE_RULE_CATEGORIES = frozenset({
124
124
 
125
125
  PLANNED_ASSETS = [
126
126
  "app/.claude-plugin/plugin.json",
127
+ "app/claude-app/hooks/hooks.json",
128
+ "app/claude-app/skills/ai-toolkit-rules/SKILL.md",
129
+ "app/claude-app/global-instructions.md",
130
+ "scripts/claude_app.py",
127
131
  "scripts/doctor.py",
128
132
  "scripts/benchmark_ecosystem.py",
129
133
  "scripts/harvest_ecosystem.py",
@@ -561,9 +565,37 @@ def validate_planned_assets(tk_dir: Path, vr: ValidationResult) -> None:
561
565
  assert d["name"]
562
566
  assert d["version"]
563
567
  assert d["description"]
564
- print(" OK: plugin manifest JSON is valid")
568
+ assert isinstance(d.get("repository"), str)
569
+ ignored = {"npm", "install", "marketplace", "claude-code"} & set(d)
570
+ assert not ignored
571
+ for path_field in ("hooks", "skills"):
572
+ relative = str(d[path_field]).removeprefix("./")
573
+ assert (tk_dir / "app" / relative).exists()
574
+ print(" OK: plugin manifest matches the official schema surface")
565
575
  except (json.JSONDecodeError, KeyError, AssertionError):
566
- vr.error("Invalid plugin manifest JSON or missing required fields")
576
+ vr.error("Invalid plugin manifest JSON, official fields, or component paths")
577
+
578
+ try:
579
+ from claude_app import (
580
+ GLOBAL_INSTRUCTIONS_PATH,
581
+ PLUGIN_HOOKS_PATH,
582
+ PLUGIN_RULES_SKILL_PATH,
583
+ render_global_instructions,
584
+ render_plugin_hooks,
585
+ render_rules_skill,
586
+ )
587
+ generated = (
588
+ (PLUGIN_HOOKS_PATH, render_plugin_hooks()),
589
+ (PLUGIN_RULES_SKILL_PATH, render_rules_skill()),
590
+ (GLOBAL_INSTRUCTIONS_PATH, render_global_instructions()),
591
+ )
592
+ stale = [path for path, expected in generated if path.read_text(encoding="utf-8") != expected]
593
+ if stale:
594
+ vr.error("Stale Claude app assets; run: python3 scripts/claude_app.py sync")
595
+ else:
596
+ print(" OK: Claude app plugin assets match canonical rules/hooks")
597
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
598
+ vr.error(f"Invalid Claude app generated assets: {exc}")
567
599
 
568
600
  # Validate benchmark dashboard JSON
569
601
  benchmark_dashboard = tk_dir / "benchmarks" / "ecosystem-dashboard.json"