@softspark/ai-toolkit 4.29.1 → 4.30.2

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 (42) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +44 -18
  3. package/app/.claude-plugin/plugin.json +1 -1
  4. package/app/ARCHITECTURE.md +2 -2
  5. package/app/mcp-templates/README.md +7 -2
  6. package/app/mcp-templates/rag-mcp-legal.json +11 -0
  7. package/app/mcp-templates/rag-mcp.json +11 -0
  8. package/app/surface.json +1 -0
  9. package/benchmarks/ecosystem-doctor-snapshot.json +29 -17
  10. package/bin/ai-toolkit.js +8 -0
  11. package/kb/history/completed/dsh-integration-plan-superseded.md +322 -0
  12. package/kb/history/completed/dsh-native-install-target-plan.md +331 -0
  13. package/kb/procedures/ecosystem-sync-sop.md +7 -5
  14. package/kb/procedures/maintenance-sop.md +1 -1
  15. package/kb/procedures/release-verification-sop.md +35 -5
  16. package/kb/reference/architecture-overview.md +24 -5
  17. package/kb/reference/cli-reference.md +1 -1
  18. package/kb/reference/dsh-compatibility.md +183 -0
  19. package/kb/reference/manifest-install.md +112 -5
  20. package/kb/reference/mcp-templates.md +11 -4
  21. package/kb/reference/plugin-pack-conventions.md +35 -18
  22. package/kb/reference/supported-tools-registry.md +30 -6
  23. package/llms-full.txt +1110 -50
  24. package/llms.txt +3 -0
  25. package/manifest.json +2 -2
  26. package/package.json +2 -2
  27. package/scripts/codex_skill_adapter.py +673 -34
  28. package/scripts/config_resolver.py +80 -14
  29. package/scripts/doctor.py +98 -20
  30. package/scripts/ecosystem_tools.json +51 -1
  31. package/scripts/generate_codex_skills.py +22 -20
  32. package/scripts/install.py +30 -13
  33. package/scripts/install_steps/ai_tools.py +104 -34
  34. package/scripts/install_steps/dsh.py +5063 -0
  35. package/scripts/install_steps/install_state.py +1645 -57
  36. package/scripts/mcp_editors.py +5 -2
  37. package/scripts/plugin.py +2495 -163
  38. package/scripts/plugin_mcp.py +279 -0
  39. package/scripts/plugin_rules.py +389 -0
  40. package/scripts/plugin_schema.py +139 -23
  41. package/scripts/uninstall.py +47 -4
  42. package/scripts/validate.py +421 -0
@@ -5,6 +5,7 @@
5
5
  """Install global and project-local AI tool configs."""
6
6
  from __future__ import annotations
7
7
 
8
+ from dataclasses import dataclass
8
9
  import os
9
10
  import shutil
10
11
  import subprocess
@@ -13,8 +14,10 @@ from pathlib import Path
13
14
  from _common import app_dir, inject_section, toolkit_dir
14
15
  from codex_skill_adapter import (
15
16
  cleanup_codex_skills,
16
- prepare_codex_skills_dir,
17
+ managed_skill_surface_transaction,
18
+ skill_surface_owners,
17
19
  sync_codex_skill,
20
+ sync_dsh_skill,
18
21
  unmanaged_codex_skill_names,
19
22
  )
20
23
  from mcp_editors import sync_project_mcp_to_editors
@@ -173,7 +176,7 @@ def install_ai_tools(target_dir: Path, rules_dir: Path,
173
176
  if "codex" in eds:
174
177
  if dry_run:
175
178
  print(" Would inject: $CODEX_HOME/AGENTS.md, $CODEX_HOME/agents/, "
176
- "$CODEX_HOME/hooks.json")
179
+ "$CODEX_HOME/hooks.json + $CODEX_HOME/ai-toolkit-hooks/")
177
180
  print(" Would generate: ~/.agents/skills/ (shared Codex skill discovery)")
178
181
  else:
179
182
  _install_codex_global(target_dir, rules_dir)
@@ -751,11 +754,40 @@ def run_script(script_name: str, *args: str, capture: bool = False) -> str:
751
754
  return result.stdout if capture else ""
752
755
 
753
756
 
754
- # All known editor identifiers for --editors flag
757
+ @dataclass(frozen=True, slots=True)
758
+ class EditorCapability:
759
+ """Selection policy for one ``--editors`` target."""
760
+
761
+ include_in_all: bool = True
762
+ requires_local: bool = False
763
+
764
+
765
+ EDITOR_CAPABILITIES = {
766
+ "copilot": EditorCapability(),
767
+ "cursor": EditorCapability(),
768
+ "windsurf": EditorCapability(),
769
+ "cline": EditorCapability(),
770
+ "roo": EditorCapability(),
771
+ "aider": EditorCapability(),
772
+ "augment": EditorCapability(),
773
+ "antigravity": EditorCapability(),
774
+ "codex": EditorCapability(),
775
+ "gemini": EditorCapability(),
776
+ "opencode": EditorCapability(),
777
+ "dsh": EditorCapability(include_in_all=False, requires_local=True),
778
+ }
779
+
780
+ SELECTABLE_EDITORS = list(EDITOR_CAPABILITIES)
755
781
  ALL_EDITORS = [
756
- "copilot", "cursor", "windsurf", "cline", "roo",
757
- "aider", "augment", "antigravity", "codex", "gemini", "opencode",
782
+ editor
783
+ for editor, capability in EDITOR_CAPABILITIES.items()
784
+ if capability.include_in_all
758
785
  ]
786
+ LOCAL_ONLY_EDITORS = {
787
+ editor
788
+ for editor, capability in EDITOR_CAPABILITIES.items()
789
+ if capability.requires_local
790
+ }
759
791
 
760
792
  # Map of project files/dirs → editor names for auto-detection
761
793
  _EDITOR_MARKERS: dict[str, str] = {
@@ -812,6 +844,10 @@ def _detect_editors(cwd: Path) -> list[str]:
812
844
  # The canonical workspace Antigravity pointer also lives in
813
845
  # .agents/skills/; only materialized skills indicate Codex.
814
846
  continue
847
+ if marker == ".agents/skills":
848
+ owners = skill_surface_owners(p)
849
+ if owners is not None and "codex" not in owners:
850
+ continue
815
851
  found.add(editor)
816
852
  return sorted(found)
817
853
 
@@ -1224,11 +1260,18 @@ def _install_local_dry_run(reset: bool, editors: list[str] | None = None,
1224
1260
  if "opencode" in eds:
1225
1261
  print(" Would generate: .opencode/skills/ (profile=full)")
1226
1262
  if "codex" in eds:
1263
+ # Hooks are not profile-gated: the live path runs gen_codex_hooks
1264
+ # unconditionally whenever codex is selected, so the preview must say so
1265
+ # at every profile. Announcing the asset directory as well as the
1266
+ # manifest matters here — the hook scripts are what a user reviews and
1267
+ # trusts with /hooks before Codex will run them.
1268
+ print(" Would generate: .codex/hooks.json + .codex/hooks/ (hook scripts)")
1227
1269
  print(" Would generate: .codex/agents/ native agents")
1228
1270
  print(" Would generate: .agents/skills/ Codex skills")
1229
1271
  if codex_skills:
1230
1272
  print(" Would refresh: .agents/skills/ via --codex-skills")
1231
-
1273
+ if "dsh" in eds:
1274
+ print(" Would generate: .agents/skills/ DSH-compatible managed skills")
1232
1275
  if not eds:
1233
1276
  print(" No editors selected (use --editors <list> or --editors all)")
1234
1277
 
@@ -1324,42 +1367,53 @@ def _create_local_settings(cwd: Path, reset: bool) -> None:
1324
1367
  print(" Kept: .claude/settings.local.json (already exists)")
1325
1368
 
1326
1369
 
1327
- def _install_codex_skills(cwd: Path) -> None:
1328
- """Install all skills to `.agents/skills/` for Codex.
1370
+ def _install_agent_skills(cwd: Path, *, target: str) -> None:
1371
+ """Install all skills to the managed ``.agents/skills`` surface.
1329
1372
 
1330
- Native Codex-compatible skills are symlinked directly. Skills that rely on
1331
- Claude-only orchestration primitives are rendered into generated wrappers
1332
- with Codex-native delegation guidance.
1373
+ Codex-only installs retain Codex-specific wrappers. DSH-only and shared
1374
+ Codex/DSH installs use portable wrappers that retain DSH invocation fields.
1333
1375
  """
1376
+ if target not in {"codex", "dsh", "shared"}:
1377
+ raise ValueError(f"Unsupported agent skill target: {target}")
1334
1378
  skills_src = app_dir / "skills"
1335
1379
  if not skills_src.is_dir():
1336
1380
  return
1337
1381
 
1338
- skills_dst = prepare_codex_skills_dir(cwd)
1339
- user_names = unmanaged_codex_skill_names(skills_dst, skills_src)
1340
-
1341
1382
  linked = 0
1342
1383
  adapted = 0
1343
1384
  skipped = 0
1344
- for skill_dir in sorted(skills_src.iterdir()):
1345
- if not skill_dir.is_dir() or skill_dir.name.startswith("_"):
1346
- continue
1347
- skill_md = skill_dir / "SKILL.md"
1348
- if not skill_md.is_file():
1349
- continue
1350
- if skill_dir.name in user_names:
1351
- skipped += 1
1352
- continue
1353
-
1354
- mode = sync_codex_skill(skill_dir, skills_dst)
1355
- if mode == "linked":
1356
- linked += 1
1357
- elif mode == "adapted":
1358
- adapted += 1
1359
- else:
1360
- skipped += 1
1385
+ with managed_skill_surface_transaction(cwd, skills_src) as transaction:
1386
+ skills_dst = transaction.skills_dst
1387
+ user_names = unmanaged_codex_skill_names(skills_dst, skills_src)
1388
+
1389
+ for skill_dir in sorted(skills_src.iterdir()):
1390
+ if not skill_dir.is_dir() or skill_dir.name.startswith("_"):
1391
+ continue
1392
+ skill_md = skill_dir / "SKILL.md"
1393
+ if not skill_md.is_file():
1394
+ continue
1395
+ if skill_dir.name in user_names:
1396
+ skipped += 1
1397
+ continue
1398
+
1399
+ if target == "codex":
1400
+ mode = sync_codex_skill(skill_dir, skills_dst)
1401
+ else:
1402
+ mode = sync_dsh_skill(
1403
+ skill_dir,
1404
+ skills_dst,
1405
+ shared=target == "shared",
1406
+ )
1407
+ if mode == "linked":
1408
+ linked += 1
1409
+ elif mode == "adapted":
1410
+ adapted += 1
1411
+ else:
1412
+ skipped += 1
1361
1413
 
1362
- cleanup_codex_skills(skills_dst, skills_src, user_names)
1414
+ cleanup_codex_skills(skills_dst, skills_src, user_names)
1415
+ owners = {"codex", "dsh"} if target == "shared" else {target}
1416
+ transaction.commit(owners)
1363
1417
 
1364
1418
  print(
1365
1419
  f" Installed: {linked + adapted} skills to .agents/skills/"
@@ -1367,6 +1421,16 @@ def _install_codex_skills(cwd: Path) -> None:
1367
1421
  )
1368
1422
 
1369
1423
 
1424
+ def _install_codex_skills(cwd: Path) -> None:
1425
+ """Install the Codex-specific managed skill surface."""
1426
+ _install_agent_skills(cwd, target="codex")
1427
+
1428
+
1429
+ def _install_dsh_skills(cwd: Path, *, shared: bool = False) -> None:
1430
+ """Install DSH-portable skills, optionally shared with Codex."""
1431
+ _install_agent_skills(cwd, target="shared" if shared else "dsh")
1432
+
1433
+
1370
1434
  def _install_codex_agents(cwd: Path, *, config_root: Path | None = None) -> None:
1371
1435
  """Generate native Codex custom-agent TOML files."""
1372
1436
  from generate_codex_agents import generate as gen_codex_agents
@@ -1575,13 +1639,19 @@ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path,
1575
1639
  print(" Created: .codex/hooks.json")
1576
1640
  # .codex/agents/ -- native Codex custom-agent definitions
1577
1641
  _install_codex_agents(cwd)
1578
- # .agents/skills/ — Codex discovery path for repo-local skills
1642
+ if {"codex", "dsh"}.issubset(eds):
1643
+ # The clients share one discovery directory. A joint install therefore
1644
+ # converges on one portable rendering instead of depending on list order.
1645
+ _install_dsh_skills(cwd, shared=True)
1646
+ elif "codex" in eds:
1579
1647
  _install_codex_skills(cwd)
1580
1648
  # --codex-skills explicitly re-runs the same Codex skill sync path.
1581
- # Codex upstream discovers skills from .agents/skills/, not .codex/skills/.
1582
1649
  if codex_skills:
1583
1650
  _try_generator("generate_codex_skills", cwd,
1584
1651
  enable_codex_skills=True)
1652
+ elif "dsh" in eds:
1653
+ # DSH receives only the shared skill surface, without Codex config.
1654
+ _install_dsh_skills(cwd)
1585
1655
 
1586
1656
  if "gemini" in eds:
1587
1657
  # GEMINI.md — marker injection from the shared generator output