@softspark/ai-toolkit 4.4.1 → 4.5.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/CHANGELOG.md +44 -0
- package/README.md +28 -28
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/ARCHITECTURE.md +1 -1
- package/app/hooks/_profile-check.sh +17 -0
- package/app/hooks/guard-destructive.sh +19 -1
- package/app/hooks/loop-guard.sh +59 -0
- package/app/hooks/session-start.sh +4 -2
- package/app/hooks.json +10 -0
- package/app/plugins/memory-pack/README.md +2 -2
- package/app/plugins/memory-pack/scripts/strip_private.py +31 -4
- package/app/skills/hook-creator/SKILL.md +1 -0
- package/app/skills/instinct-review/SKILL.md +10 -10
- package/app/skills/skill-audit/SKILL.md +10 -0
- package/app/skills/verification-before-completion/SKILL.md +19 -0
- package/benchmarks/ecosystem-doctor-snapshot.json +27 -15
- package/kb/reference/hooks-catalog.md +22 -3
- package/kb/reference/mcp-editor-compatibility.md +1 -1
- package/kb/reference/supported-tools-registry.md +3 -3
- package/llms-full.txt +68 -17
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/audit_skills.py +82 -2
- package/scripts/doctor.py +51 -0
- package/scripts/ecosystem_tools.json +9 -1
- package/scripts/generate_codex_hooks.py +5 -4
- package/scripts/validate.py +84 -0
package/scripts/doctor.py
CHANGED
|
@@ -60,6 +60,7 @@ EXPECTED_HOOKS = [
|
|
|
60
60
|
"config-desync-guard.sh",
|
|
61
61
|
"instructions-audit.sh",
|
|
62
62
|
"post-tool-use.sh",
|
|
63
|
+
"loop-guard.sh",
|
|
63
64
|
"quality-check.sh",
|
|
64
65
|
"quality-gate.sh",
|
|
65
66
|
"revert-guard.sh",
|
|
@@ -549,6 +550,55 @@ def check_url_hooks(dr: DiagResult, fix_mode: bool) -> None:
|
|
|
549
550
|
print(f" Could not re-fetch: {exc}")
|
|
550
551
|
|
|
551
552
|
|
|
553
|
+
# ---------------------------------------------------------------------------
|
|
554
|
+
# Check 10: Language Rules Drift (project-local)
|
|
555
|
+
# ---------------------------------------------------------------------------
|
|
556
|
+
|
|
557
|
+
def check_language_drift(dr: DiagResult) -> None:
|
|
558
|
+
"""Warn when the current project gained a language but its rules were not injected.
|
|
559
|
+
|
|
560
|
+
Runs only inside a local-install project (cwd has .claude/CLAUDE.md with the
|
|
561
|
+
language-rules block). A new Cargo.toml / go.mod that appears after install
|
|
562
|
+
otherwise leaves the matching <lang>-rules unlinked with zero signal.
|
|
563
|
+
"""
|
|
564
|
+
print()
|
|
565
|
+
print("## 10. Language Rules Drift")
|
|
566
|
+
|
|
567
|
+
claude_md = Path.cwd() / ".claude" / "CLAUDE.md"
|
|
568
|
+
if not claude_md.is_file():
|
|
569
|
+
dr.skip("not a local-install project (no .claude/CLAUDE.md)")
|
|
570
|
+
return
|
|
571
|
+
try:
|
|
572
|
+
content = claude_md.read_text(encoding="utf-8")
|
|
573
|
+
except OSError:
|
|
574
|
+
dr.skip("could not read .claude/CLAUDE.md")
|
|
575
|
+
return
|
|
576
|
+
if "TOOLKIT:language-rules" not in content:
|
|
577
|
+
dr.skip("no language-rules block in .claude/CLAUDE.md")
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
from install_steps.detect_language import detect_languages
|
|
582
|
+
except Exception:
|
|
583
|
+
dr.skip("language detection unavailable")
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
missing = []
|
|
587
|
+
for module in detect_languages(Path.cwd(), toolkit_dir):
|
|
588
|
+
if not module.startswith("rules-"):
|
|
589
|
+
continue
|
|
590
|
+
skill = f"{module[len('rules-'):]}-rules"
|
|
591
|
+
if skill not in content:
|
|
592
|
+
missing.append(skill)
|
|
593
|
+
|
|
594
|
+
if not missing:
|
|
595
|
+
dr.ok("project language rules in sync")
|
|
596
|
+
return
|
|
597
|
+
for skill in missing:
|
|
598
|
+
lang = skill[: -len("-rules")]
|
|
599
|
+
dr.warn(f"{lang} detected but {skill} not injected — run: ai-toolkit install --local --lang {lang}")
|
|
600
|
+
|
|
601
|
+
|
|
552
602
|
# ---------------------------------------------------------------------------
|
|
553
603
|
# Main
|
|
554
604
|
# ---------------------------------------------------------------------------
|
|
@@ -571,6 +621,7 @@ def main() -> None:
|
|
|
571
621
|
check_benchmark_freshness(dr)
|
|
572
622
|
check_stale_rules(dr, fix_mode)
|
|
573
623
|
check_url_hooks(dr, fix_mode)
|
|
624
|
+
check_language_drift(dr)
|
|
574
625
|
|
|
575
626
|
# Summary
|
|
576
627
|
print("========================")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"description": "Authoritative registry of tools ai-toolkit integrates with. Consumed by scripts/ecosystem_doctor.py to detect upstream doc/version drift.",
|
|
4
|
-
"last_updated": "2026-05-
|
|
4
|
+
"last_updated": "2026-05-30",
|
|
5
5
|
"tools": [
|
|
6
6
|
{
|
|
7
7
|
"id": "claude-code",
|
|
@@ -24,12 +24,16 @@
|
|
|
24
24
|
"capability_markers": [
|
|
25
25
|
"PreToolUse",
|
|
26
26
|
"PostToolUse",
|
|
27
|
+
"PostToolUseFailure",
|
|
28
|
+
"PostToolBatch",
|
|
27
29
|
"SessionStart",
|
|
28
30
|
"SessionEnd",
|
|
29
31
|
"SubagentStart",
|
|
30
32
|
"SubagentStop",
|
|
31
33
|
"UserPromptSubmit",
|
|
34
|
+
"UserPromptExpansion",
|
|
32
35
|
"Notification",
|
|
36
|
+
"MessageDisplay",
|
|
33
37
|
"Stop",
|
|
34
38
|
"StopFailure",
|
|
35
39
|
"PreCompact",
|
|
@@ -452,6 +456,10 @@
|
|
|
452
456
|
"hook event: UserPromptSubmit",
|
|
453
457
|
"hook event: Stop",
|
|
454
458
|
"hook event: PermissionRequest",
|
|
459
|
+
"hook event: PreCompact",
|
|
460
|
+
"hook event: PostCompact",
|
|
461
|
+
"hook event: SubagentStart",
|
|
462
|
+
"hook event: SubagentStop",
|
|
455
463
|
"hook handler: command",
|
|
456
464
|
"hook handler: prompt",
|
|
457
465
|
"hook handler: agent",
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
Maps compatible ai-toolkit hooks to Codex lifecycle events.
|
|
5
5
|
Hook scripts are shared with Claude Code (stored in ~/.softspark/ai-toolkit/hooks/).
|
|
6
6
|
|
|
7
|
-
Codex
|
|
8
|
-
``PreToolUse``, ``PostToolUse``, ``PermissionRequest``, ``
|
|
9
|
-
``
|
|
10
|
-
the ``Bash``
|
|
7
|
+
Codex exposes 10 lifecycle events (PascalCase in config.toml / hooks.json):
|
|
8
|
+
``PreToolUse``, ``PostToolUse``, ``PermissionRequest``, ``PreCompact``,
|
|
9
|
+
``PostCompact``, ``SessionStart``, ``UserPromptSubmit``, ``SubagentStart``,
|
|
10
|
+
``SubagentStop``, ``Stop``. PreToolUse/PostToolUse only support the ``Bash``
|
|
11
|
+
matcher. We currently wire the subset defined in ``CODEX_HOOKS`` below.
|
|
11
12
|
|
|
12
13
|
Handler types in Codex: ``command`` (what we emit), ``prompt``, and ``agent``.
|
|
13
14
|
Reference: codex-rs/config/src/hook_config.rs.
|
package/scripts/validate.py
CHANGED
|
@@ -785,6 +785,9 @@ def validate_metadata_contracts(
|
|
|
785
785
|
# Cross-validate versions: package.json vs manifest.json vs plugin.json
|
|
786
786
|
_validate_version_sync(tk_dir, vr)
|
|
787
787
|
|
|
788
|
+
# Cross-validate the README platform matrix's Hooks column against reality
|
|
789
|
+
_validate_editor_hooks_honesty(tk_dir, vr)
|
|
790
|
+
|
|
788
791
|
print()
|
|
789
792
|
return actual_tests
|
|
790
793
|
|
|
@@ -836,6 +839,87 @@ def _validate_version_sync(tk_dir: Path, vr: ValidationResult) -> None:
|
|
|
836
839
|
)
|
|
837
840
|
|
|
838
841
|
|
|
842
|
+
# Editors that receive lifecycle hook enforcement without a generate_*_hooks.py
|
|
843
|
+
# generator: Claude is native (app/hooks), opencode bridges via its plugin
|
|
844
|
+
# (generate_opencode_plugin.py). Every other hook-enabled editor is derived
|
|
845
|
+
# from the generate_<editor>_hooks.py set so this check stays honest as
|
|
846
|
+
# generators come and go.
|
|
847
|
+
_NATIVE_HOOK_EDITORS = {"claude", "opencode"}
|
|
848
|
+
|
|
849
|
+
# README platform label (lowercased) -> canonical editor key.
|
|
850
|
+
_README_PLATFORM_KEY = {
|
|
851
|
+
"claude code": "claude",
|
|
852
|
+
"cursor": "cursor",
|
|
853
|
+
"windsurf": "windsurf",
|
|
854
|
+
"gemini cli": "gemini",
|
|
855
|
+
"github copilot": "copilot",
|
|
856
|
+
"cline": "cline",
|
|
857
|
+
"roo code": "roo",
|
|
858
|
+
"aider": "aider",
|
|
859
|
+
"augment": "augment",
|
|
860
|
+
"google antigravity": "antigravity",
|
|
861
|
+
"codex cli": "codex",
|
|
862
|
+
"opencode": "opencode",
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _validate_editor_hooks_honesty(tk_dir: Path, vr: ValidationResult) -> None:
|
|
867
|
+
"""Ensure the README platform matrix's Hooks column matches reality.
|
|
868
|
+
|
|
869
|
+
Guards against the overclaim that every editor gets hook enforcement when
|
|
870
|
+
only a subset does — the machine-enforced constitution needs generated
|
|
871
|
+
hooks, and rules-only editors receive guidance text without blocking hooks.
|
|
872
|
+
"""
|
|
873
|
+
scripts_dir = tk_dir / "scripts"
|
|
874
|
+
readme = tk_dir / "README.md"
|
|
875
|
+
if not scripts_dir.is_dir() or not readme.is_file():
|
|
876
|
+
return # installed copy without source — nothing to cross-check
|
|
877
|
+
|
|
878
|
+
# Actual hook-enabled editors: native set + generate_<editor>_hooks.py stems.
|
|
879
|
+
actual = set(_NATIVE_HOOK_EDITORS)
|
|
880
|
+
for gen in scripts_dir.glob("generate_*_hooks.py"):
|
|
881
|
+
stem = gen.name[len("generate_"):-len("_hooks.py")]
|
|
882
|
+
actual.add(stem)
|
|
883
|
+
|
|
884
|
+
content = readme.read_text(encoding="utf-8")
|
|
885
|
+
if "| Hooks |" not in content:
|
|
886
|
+
return # matrix has no Hooks column to validate
|
|
887
|
+
|
|
888
|
+
claimed: set[str] = set()
|
|
889
|
+
parsed_any = False
|
|
890
|
+
for line in content.splitlines():
|
|
891
|
+
if not line.startswith("|"):
|
|
892
|
+
continue
|
|
893
|
+
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
|
894
|
+
if len(cells) < 4:
|
|
895
|
+
continue
|
|
896
|
+
key = _README_PLATFORM_KEY.get(cells[0].lower())
|
|
897
|
+
if key is None:
|
|
898
|
+
continue # header, separator, or unknown row
|
|
899
|
+
parsed_any = True
|
|
900
|
+
if "✅" in cells[2]: # ✅
|
|
901
|
+
claimed.add(key)
|
|
902
|
+
|
|
903
|
+
if not parsed_any:
|
|
904
|
+
return
|
|
905
|
+
|
|
906
|
+
missing = actual - claimed # has hooks but README omits the ✅
|
|
907
|
+
overclaimed = claimed - actual # README claims ✅ but no generator exists
|
|
908
|
+
if missing:
|
|
909
|
+
vr.error(
|
|
910
|
+
"README Hooks column understates enforcement for: "
|
|
911
|
+
+ ", ".join(sorted(missing))
|
|
912
|
+
)
|
|
913
|
+
if overclaimed:
|
|
914
|
+
vr.error(
|
|
915
|
+
"README Hooks column overclaims enforcement for: "
|
|
916
|
+
+ ", ".join(sorted(overclaimed))
|
|
917
|
+
+ " (no hook generator or native bridge found)"
|
|
918
|
+
)
|
|
919
|
+
if not missing and not overclaimed:
|
|
920
|
+
print(f" OK: editor hooks honesty ({len(claimed)} hook-enabled editors)")
|
|
921
|
+
|
|
922
|
+
|
|
839
923
|
ROMAN_NUMERALS = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
|
|
840
924
|
|
|
841
925
|
|