The cure for AI slop is a 1986 aircraft manual/kit

asd-ste100/install.py

8 KB·Raw on GitHub ↗

One command arms every layer: the skill, the output style, and the four hooks. Idempotent, reversible

#!/usr/bin/env python3
"""Installer for the asd-ste100 skill. One command arms every layer.
 
    python3 install.py              install or repair
    python3 install.py --dry-run    show the plan, write nothing
    python3 install.py --uninstall  remove what this script added
    python3 install.py --no-output-style   skip the output style
 
What it wires, for Claude Code:
 
1. ~/.claude/skills/asd-ste100        symlink to this directory
2. ~/.claude/output-styles/asd-ste100.md   symlink to output-style.md
3. ~/.claude/settings.json             four hook entries plus outputStyle
 
The four hooks:
 
- UserPromptSubmit  ste-inject.py   the rule card, every turn
- PostToolUse       ste-refresh.py  a short card every 12 tool calls, and a
                                    score report after a Write of an md file
- PreToolUse        ste-pregate.py  lints a commit message before it runs
- Stop              ste-gate.py     lints the final reply, three bands
 
The script is idempotent. A second run repairs the install. An entry that
points at an older copy of a hook (any command that names the same script
file) is replaced, not duplicated. The previous settings.json is saved next
to it as settings.json.ste-backup before the first change.
"""
import argparse
import json
import os
import sys
 
SRC = os.path.dirname(os.path.abspath(__file__))
HOME = os.path.expanduser("~")
CLAUDE = os.path.join(HOME, ".claude")
SETTINGS = os.path.join(CLAUDE, "settings.json")
STYLE_NAME = "asd-ste100"
 
HOOK_DIR = '"$HOME/.claude/skills/asd-ste100/hooks'
HOOKS = {
    "UserPromptSubmit": {
        "marker": "ste-inject",
        "entry": {"hooks": [{"type": "command",
                             "command": "python3 {}/ste-inject.py\"".format(HOOK_DIR),
                             "timeout": 10}]},
    },
    "PreToolUse": {
        "marker": "ste-pregate",
        "entry": {"matcher": "Bash|mcp__elliptic__.*",
                  "hooks": [{"type": "command",
                             "command": "python3 {}/ste-pregate.py\"".format(HOOK_DIR),
                             "timeout": 15}]},
    },
    "PostToolUse": {
        "marker": "ste-refresh",
        "entry": {"matcher": "*",
                  "hooks": [{"type": "command",
                             "command": "python3 {}/ste-refresh.py\"".format(HOOK_DIR),
                             "timeout": 15}]},
    },
    "Stop": {
        "marker": "ste-gate",
        "entry": {"hooks": [{"type": "command",
                             "command": "python3 {}/ste-gate.py\"".format(HOOK_DIR),
                             "timeout": 30,
                             "statusMessage": "Checking ASD-STE100"}]},
    },
}
MARKERS = tuple(spec["marker"] for spec in HOOKS.values())
 
 
def is_ste_hook(hook):
    cmd = hook.get("command", "") if isinstance(hook, dict) else ""
    return any(m in cmd for m in MARKERS)
 
 
def strip_ste(groups):
    """Remove every ste-* command hook from an event's entry list."""
    kept = []
    removed = 0
    for group in groups or []:
        inner = [h for h in group.get("hooks", []) if not is_ste_hook(h)]
        removed += len(group.get("hooks", [])) - len(inner)
        if inner:
            kept.append({**group, "hooks": inner})
    return kept, removed
 
 
def ensure_link(link, target, actions, dry):
    if os.path.exists(link) and os.path.realpath(link) == os.path.realpath(target):
        # Already in place. This also covers the skills CLI, which puts the
        # directory at the agent path itself - never link a path to itself.
        actions.append("ok       {} already points here".format(link))
        return
    if os.path.islink(link):
        actions.append("relink   {} (pointed elsewhere)".format(link))
        if not dry:
            os.remove(link)
    elif os.path.isdir(link) or os.path.isfile(link):
        backup = link + ".pre-ste-backup"
        actions.append("backup   {} -> {}".format(link, backup))
        if not dry:
            if os.path.exists(backup):
                actions.append("skip     backup exists, remove {} by hand".format(link))
                return
            os.rename(link, backup)
    else:
        actions.append("link     {} -> {}".format(link, target))
    if not dry:
        os.makedirs(os.path.dirname(link), exist_ok=True)
        if not os.path.islink(link):
            os.symlink(target, link)
 
 
def drop_link(link, actions, dry):
    if os.path.islink(link) and os.path.realpath(link).startswith(os.path.realpath(SRC)):
        actions.append("unlink   {}".format(link))
        if not dry:
            os.remove(link)
 
 
def main():
    parser = argparse.ArgumentParser(description="Install the asd-ste100 skill.")
    parser.add_argument("--dry-run", action="store_true")
    parser.add_argument("--uninstall", action="store_true")
    parser.add_argument("--no-output-style", action="store_true")
    args = parser.parse_args()
    dry = args.dry_run
    actions = []
 
    skill_link = os.path.join(CLAUDE, "skills", "asd-ste100")
    style_link = os.path.join(CLAUDE, "output-styles", STYLE_NAME + ".md")
 
    try:
        with open(SETTINGS, encoding="utf-8") as fh:
            settings = json.load(fh)
    except FileNotFoundError:
        settings = {}
    except json.JSONDecodeError:
        print("error: {} is not valid JSON. Fix it first.".format(SETTINGS))
        return 1
    hooks = settings.setdefault("hooks", {})
 
    if args.uninstall:
        drop_link(style_link, actions, dry)
        drop_link(skill_link, actions, dry)
        for event in HOOKS:
            kept, removed = strip_ste(hooks.get(event))
            if removed:
                actions.append("remove   {} ste hook from {}".format(removed, event))
            if kept:
                hooks[event] = kept
            else:
                hooks.pop(event, None)
        if settings.get("outputStyle") == STYLE_NAME:
            actions.append("remove   outputStyle")
            settings.pop("outputStyle")
    else:
        ensure_link(skill_link, SRC, actions, dry)
        if not args.no_output_style:
            ensure_link(style_link, os.path.join(SRC, "output-style.md"), actions, dry)
        for event, spec in HOOKS.items():
            kept, removed = strip_ste(hooks.get(event))
            if removed:
                actions.append("replace  {} older ste hook in {}".format(removed, event))
            else:
                actions.append("add      {} hook ({})".format(event, spec["marker"]))
            hooks[event] = kept + [spec["entry"]]
        if args.no_output_style:
            pass
        elif settings.get("outputStyle") in (None, STYLE_NAME, "ste-writing"):
            actions.append("set      outputStyle = {}".format(STYLE_NAME))
            settings["outputStyle"] = STYLE_NAME
        else:
            actions.append("keep     outputStyle = {} (yours; set it to {} by hand "
                           "if you want the style layer)".format(
                               settings["outputStyle"], STYLE_NAME))
 
    if not dry:
        os.makedirs(CLAUDE, exist_ok=True)
        backup = SETTINGS + ".ste-backup"
        if os.path.exists(SETTINGS) and not os.path.exists(backup):
            with open(SETTINGS, encoding="utf-8") as fh:
                data = fh.read()
            with open(backup, "w", encoding="utf-8") as fh:
                fh.write(data)
            actions.append("backup   settings.json -> settings.json.ste-backup")
        with open(SETTINGS, "w", encoding="utf-8") as fh:
            json.dump(settings, fh, indent=2)
            fh.write("\n")
 
    for line in actions:
        print(line)
    print("{}{}. Restart the Claude Code session to load the hooks.".format(
        "planned " if dry else "",
        "uninstalled" if args.uninstall else "installed"))
    return 0
 
 
if __name__ == "__main__":
    sys.exit(main())
 

← Back to the episode