feat(hooks): add ticket-gate Crush hook — enforce ticket-first work
Mechanically enforces the Agent Authority policy: blocks all modifying
operations (bash, edit, write, multiedit) until an active ticket is set
via: echo '#NNN' > .crush/active-ticket
Exempts read-only tools, ticket management (redmine-cli/discourse-cli),
repo hygiene (check-rules, shellcheck), and monitoring commands.
Also: un-ignore .crush/crush.json so the hook config is tracked in git.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!crush.json
|
||||
!.gitignore
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "^(bash|edit|write|multiedit|lsp_replace_symbol|lsp_rename)$",
|
||||
"command": "./hooks/ticket-gate.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -1,5 +1,9 @@
|
||||
# Crush internal state
|
||||
.crush/
|
||||
# Crush internal state (track crush.json config, ignore session data)
|
||||
.crush/crush.db
|
||||
.crush/crush.db-*
|
||||
.crush/logs/
|
||||
.crush/memory/
|
||||
.crush/active-ticket
|
||||
|
||||
# OS/editor
|
||||
.DS_Store
|
||||
|
||||
@@ -130,6 +130,11 @@ for a full audit or `--fast` for pre-commit speed. Bypass with `--no-verify`
|
||||
- **Redmine is the system of record for all work.**
|
||||
- **NEVER close a ticket without explicit user permission.** Suggest a
|
||||
close when clearly scoped/delivered; if ambiguous, don't suggest.
|
||||
- **Ticket-first enforcement (mechanically enforced).** Before starting
|
||||
any work, set the active ticket: `echo '#NNN' > .crush/active-ticket`.
|
||||
The Crush hook (`hooks/ticket-gate.sh`) blocks modifying operations
|
||||
until this file exists. If no ticket exists, CREATE ONE FIRST via
|
||||
redmine-cli, then set it. Clear when done: `> .crush/active-ticket`.
|
||||
- **WORKING.md** is the only in-repo task tracker — a scratchpad for the current
|
||||
session. The pre-commit hook blocks commits while any task remains unchecked.
|
||||
- Clear WORKING.md before responding to the user.
|
||||
|
||||
+2
-7
@@ -7,10 +7,5 @@ A commit is blocked while any task below remains unchecked.
|
||||
|
||||
(all done — session complete)
|
||||
|
||||
- [x] Build VM MAC→IP mapping from ARP table + Proxmox data
|
||||
- [x] Generate dhcpd.conf host reservations (18 production VMs, current IPs)
|
||||
- [x] Deploy reservations to netinfra-01 (syntax check passed, DHCP reloaded)
|
||||
- [x] Sync to netinfra-02 (manual trigger, confirmed)
|
||||
- [x] Add PTR records in Technitium (18 PTR-only records, verified)
|
||||
- [x] Pull updated dhcpd.conf into repo
|
||||
- [x] Create #421 (inotify DHCP sync, someday/maybe)
|
||||
- [x] Add ticket-gate Crush hook (blocks work without active ticket)
|
||||
- [x] Document active-ticket workflow in AGENTS.md + meta template
|
||||
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# hooks/ticket-gate.sh — enforce ticket-first work policy
|
||||
#
|
||||
# Blocks modifying operations until an active ticket is established.
|
||||
# The agent sets the active ticket via: echo '#NNN' > .crush/active-ticket
|
||||
# And clears it when done: > .crush/active-ticket
|
||||
#
|
||||
# Exempts read-only and management commands (so you can create tickets,
|
||||
# run audits, check status, etc.).
|
||||
set -euo pipefail
|
||||
|
||||
TICKET_FILE="${CRUSH_PROJECT_DIR}/.crush/active-ticket"
|
||||
TOOL="${CRUSH_TOOL_NAME:-}"
|
||||
CMD="${CRUSH_TOOL_INPUT_COMMAND:-}"
|
||||
|
||||
# Read-only tools — always allowed
|
||||
case "$TOOL" in
|
||||
view|ls|grep|glob|agent|sourcegraph|fetch|agentic_fetch|download|lsp_diagnostics|lsp_symbols|lsp_definition|lsp_references|lsp_call_hierarchy|crush_info|crush_logs|question|todos)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# For bash tool: exempt read-only and management commands
|
||||
if [ "$TOOL" = "bash" ]; then
|
||||
# Ticket/doc/dns management — always allowed
|
||||
case "$CMD" in
|
||||
*"redmine-cli"*|*"discourse-cli"*|*"dns-cli"*|*"technitium"*) exit 0 ;;
|
||||
esac
|
||||
# Read-only git
|
||||
case "$CMD" in
|
||||
*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*) exit 0 ;;
|
||||
esac
|
||||
# Repo hygiene scripts
|
||||
case "$CMD" in
|
||||
*"check-rules"*|*"setup-hooks"*|*"shellcheck"*|*"run-tests"*) exit 0 ;;
|
||||
esac
|
||||
# Monitoring/probe commands
|
||||
case "$CMD" in
|
||||
*"tailscale status"*|*"access-matrix"*) exit 0 ;;
|
||||
esac
|
||||
# Setting/clearing the active ticket
|
||||
case "$CMD" in
|
||||
*active-ticket*) exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# For edit/write: exempt policy/hook files (these ARE the policy)
|
||||
FILE_PATH="${CRUSH_TOOL_INPUT_FILE_PATH:-}"
|
||||
case "$FILE_PATH" in
|
||||
*/AGENTS.md|*/check-rules.sh|*/crush.json|*/hooks/*)
|
||||
if [ "$TOOL" = "write" ] || [ "$TOOL" = "edit" ] || [ "$TOOL" = "multiedit" ]; then
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for active ticket
|
||||
if [ -f "$TICKET_FILE" ] && [ -s "$TICKET_FILE" ]; then
|
||||
TICKET=$(cat "$TICKET_FILE")
|
||||
printf '{"context":"Active ticket: %s"}\n' "$TICKET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# No active ticket — block
|
||||
cat >&2 <<'MSG'
|
||||
TICKET GATE: No active ticket set.
|
||||
|
||||
This project requires ticket-governed work (AGENTS.md Agent Authority).
|
||||
Before modifying systems or code, set the active ticket:
|
||||
|
||||
echo '#NNN' > .crush/active-ticket
|
||||
|
||||
If no ticket exists yet, create one first (redmine-cli create), then set it.
|
||||
Clear the ticket when work is complete:
|
||||
|
||||
> .crush/active-ticket
|
||||
MSG
|
||||
exit 2
|
||||
Reference in New Issue
Block a user