coding-tools 0.8.6

Declarative, agent-friendly CLI tools behind one 'ct' command: search, view, verifiable edits, and framed command tests.
Documentation
{
  "name": "ct-steer",
  "description": "Steer ad-hoc shell to the ct tool that serves it, and install the steering hook. ct-steer recognises a fixed set of high-confidence shell idioms a suite tool serves better — `find … | xargs grep` / `find -exec grep` and `grep -r`/`rg`/`ag` (ct search), `find … -name` (ct search), `sed -i`/`perl -i` (ct edit), `head`/`tail`/`sed -n 'A,Bp'` on a file and `python -c`/`node -e`/`perl -e`/`ruby -e`/`jq` reading a file (ct view / ct search), `grep -c` (ct search --summary), `ls -R`/`tree` and `wc`/`wc -l` over files incl. `cat FILES | wc` (ct tree), `for`/`while` per-item loops (ct each), sleep-bearing `for`/`while`/`until` poll/wait loops (ct await), and `&&`/`||` chains whose every segment is itself ct-serviceable (ct and / ct or). A multi-line scriptlet is classified line by line (ignoring cd/assignments/echo/comments): when every meaningful step is already ct or ct-advisable and not all are yet ct, the whole thing folds into one shell-less `ct and A ::: B ::: C` chain; when some steps are opaque, the ct equivalents are advised individually. Run as a Claude Code PreToolUse hook, it steers the agent to the ct equivalent instead of running the raw command. The matcher is deliberately conservative: it only fires on those idioms, never re-steers a command that already invokes ct, and the hook is fail-open (anything it does not recognise, or any malformed input, is allowed silently). Beyond Bash, the hook can also gate the harness's own Grep/Glob/Read tools — Grep/Glob steer to ct search, Read to ct view (images/PDFs/notebooks pass through) — enabled per tool at install time. Subcommands: `hook` is the runtime hook — it reads a PreToolUse tool-call envelope on stdin and prints a decision JSON (`permissionDecision` deny/ask, or an `additionalContext` warn) on a match, nothing on a miss, always exiting 0 so it never fails the call on its own account; `--mode deny|ask|warn` (default deny) picks the action. `install`/`uninstall` merge or remove the PreToolUse steering hook in a Claude Code settings file (`--scope project|local|user`, default project → .claude/settings.json / .claude/settings.local.json / ~/.claude/settings.json; `--tools Bash,Grep,Glob,Read` (default Bash) chooses which tools to gate, one matcher entry each); the merge is idempotent and preserves the rest of the file — including comments and layout — because it edits through ct-patch's byte-range splices rather than reserialising, with `--dry-run` to show the resulting file and `--print` to emit just the snippet for manual paste. `check` classifies a command string and prints what the hook would decide, exiting 0 when the command is allowed and 1 when it would be steered. Invoke as `ct steer …` or `ct-steer …`.",
  "input_schema": {
    "type": "object",
    "properties": {
      "json": {
        "type": "boolean",
        "description": "Emit a structured JSON result instead of text where applicable (the install/uninstall outcome, or the check decision)."
      },
      "quiet": {
        "type": "boolean",
        "description": "Suppress informational output (exit status still reports)."
      },
      "timeout": {
        "type": "number",
        "description": "Abort with exit 2 if the run exceeds SECS seconds (fractional allowed)."
      },
      "heartbeat": {
        "type": "number",
        "description": "Print a liveness pulse every SECS seconds (fractional allowed) while running."
      },
      "heartbeat-emit": {
        "type": "string",
        "description": "Heartbeat line template. Tokens: {ELAPSED} {TOOL}. Default: \"[{ELAPSED}s]\"."
      },
      "heartbeat-to": {
        "type": "string",
        "enum": [
          "stderr",
          "stdout"
        ],
        "description": "Stream heartbeat pulses are written to. Default: stderr."
      }
    }
  },
  "commands": [
    {
      "name": "hook",
      "description": "Runtime PreToolUse hook: read a tool-call envelope on stdin, print a deny/ask/warn decision on a match (nothing on a miss). Handles Bash plus the harness Grep/Glob/Read tools. Always exits 0. --mode deny|ask|warn (default deny). --nudge-pipelines adds a warn-only (never deny) nudge against ANY shell pipeline the specific rules did not steer, prompting harder use of ct. Tool-call logging is ON by default: it appends one JSONL record per call — including the silent allows, the raw material for spotting un-steered idioms — to .ct/tclog/<yyyy-mm-dd>.jsonl under the nearest .ct (created if absent), and keeps a .ct/.gitignore '*log' rule so the logs stay out of git. Each record has event ('pre'), tool, command, cwd, session_id, decision, rule_id, ct_tool, ts_ms. --log-dir DIR (or CT_STEER_LOG) redirects the logs; --no-log disables them. Best-effort and fail-open."
    },
    {
      "name": "post",
      "description": "Runtime PostToolUse recorder: read a PostToolUse envelope on stdin and append a record of the call as it actually executed (event 'post', with tool, command, ct (whether the executed command used ct), cwd, session_id, ts_ms) to the same daily .ct/tclog log. Enables measuring whether steer guidance was followed — correlate a 'pre' steer decision with the follow-up 'post' call by session_id and time. Only observes; always exits 0. Shares --log-dir/--no-log with the hook. Wired by `install --measure`."
    },
    {
      "name": "install",
      "description": "Merge the PreToolUse steering hook into a Claude Code settings file. --scope project|local|user (default project), --mode deny|ask|warn, --tools Bash,Grep,Glob,Read (default Bash; one matcher per tool), --all-tools (a single '*' matcher, supersedes --tools; for full-coverage logging), --nudge-pipelines (bake the warn-only pipeline nudge into the hook), --measure (also install a PostToolUse '*' recorder running `ct steer post`, to measure whether guidance was followed), --log-dir DIR (bake a log-directory override; logging is on by default), --no-log (bake in disabling the default tool-call logging), --dry-run (show the file), --print (emit just the snippet). Before a real write, a preflight verifies the `ct` that will fire the hook can parse the armed command (subcommand + baked flags) — a hook the resolving `ct` rejects would clap-error and block tool calls; install REFUSES (exit non-zero, with a recovery hint) if that check fails. --force skips the preflight; --pin bakes THIS binary's absolute path into the hook (instead of resolving `ct` on PATH) so version skew or a missing `ct` can't break it. If a hook ever starts erroring, `ct steer uninstall` (which uses only stable syntax) clears it. Idempotent."
    },
    {
      "name": "uninstall",
      "description": "Remove the steering hook from a Claude Code settings file, pruning emptied entries. --scope project|local|user, --dry-run."
    },
    {
      "name": "check",
      "description": "Classify a COMMAND string and print what the hook would decide. Exit 0 when allowed, 1 when it would be steered. --mode affects the printed label; --json prints the decision JSON."
    }
  ],
  "examples": [
    {"cmd": "ct steer install --all-tools --mode warn", "why": "Log every tool call to .ct/tclog and non-intrusively steer recognized idioms."},
    {"cmd": "ct steer check 'grep -r TODO src'", "why": "Ask what the hook would do with a command (exit 1 means it would steer to ct)."}
  ]
}