@codyswann/lisa 4.8.0 → 4.8.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.
- package/all/copy-overwrite/scripts/lisa-gates.mjs +72 -0
- package/dist/codex/hooks-installer.d.ts +24 -0
- package/dist/codex/hooks-installer.d.ts.map +1 -1
- package/dist/codex/hooks-installer.js +46 -7
- package/dist/codex/hooks-installer.js.map +1 -1
- package/dist/codex/scripts/block-migration-edits.sh +75 -3
- package/dist/codex/scripts/block-suppress-directives.sh +72 -2
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +2 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +15 -4
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-lighthouse-collect-options.d.ts +3 -1
- package/dist/migrations/ensure-lighthouse-collect-options.d.ts.map +1 -1
- package/dist/migrations/ensure-lighthouse-collect-options.js +12 -4
- package/dist/migrations/ensure-lighthouse-collect-options.js.map +1 -1
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts +51 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.d.ts.map +1 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js +122 -0
- package/dist/migrations/ensure-nightly-e2e-workflow-pins.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -1
- package/expo/create-only/lighthouserc.js +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/hooks/block-migration-edits.sh +79 -10
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/hooks/block-migration-edits.sh +79 -10
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/hooks/block-migration-edits.sh +79 -10
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/hooks/block-suppress-directives.sh +71 -3
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/hooks/block-suppress-directives.sh +71 -3
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/hooks/block-suppress-directives.sh +71 -3
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/nestjs/hooks/block-migration-edits.sh +79 -10
- package/plugins/src/typescript/hooks/block-suppress-directives.sh +71 -3
|
@@ -13,13 +13,42 @@
|
|
|
13
13
|
# and identifiers that merely mention these tokens are not flagged.
|
|
14
14
|
# Exit code 2 blocks the tool call and surfaces stderr to Claude.
|
|
15
15
|
# Reference: https://docs.claude.com/en/docs/claude-code/hooks
|
|
16
|
+
#
|
|
17
|
+
# WHAT A REFUSAL HOOK OWES THAT AN ON-EDIT HOOK DOES NOT. The five PostToolUse
|
|
18
|
+
# scripts may bail out when they cannot run: the write has already happened and
|
|
19
|
+
# a skipped lint is a missed report. This one decides whether the write happens
|
|
20
|
+
# at all, so a bail-out is a PERMIT — it lets through exactly the text it exists
|
|
21
|
+
# to stop, while reporting success. Every exit below is therefore one of three
|
|
22
|
+
# things and never a fourth: out of scope (nothing to judge), judged by the
|
|
23
|
+
# project's declared task, or judged by the built-in. "Could not judge" refuses.
|
|
16
24
|
|
|
17
25
|
JSON_INPUT=$(cat)
|
|
18
26
|
|
|
19
27
|
# Project rule (host rules, .agents/rules/): never parse JSON in shell with
|
|
20
|
-
# grep/sed/cut/awk — always use jq.
|
|
21
|
-
#
|
|
22
|
-
|
|
28
|
+
# grep/sed/cut/awk — always use jq.
|
|
29
|
+
#
|
|
30
|
+
# FAIL CLOSED, reversed from the `exit 0` this shipped with. Without jq the
|
|
31
|
+
# payload cannot be read at all: not the path, not the file type, not the new
|
|
32
|
+
# text. Permitting there is the one branch that let a suppression through while
|
|
33
|
+
# the hook reported success, and it could not even say which write it had waved
|
|
34
|
+
# past. The refusal is deliberately unscoped for the same reason — with no
|
|
35
|
+
# parse there is no file type to scope it to. This is the same shape the
|
|
36
|
+
# sibling on-edit hook already ships for a missing `oxlint`: fail loudly rather
|
|
37
|
+
# than silently skip.
|
|
38
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
39
|
+
cat >&2 <<'MSG'
|
|
40
|
+
❌ Blocked: block-suppress-directives cannot inspect this write.
|
|
41
|
+
|
|
42
|
+
`jq` is not installed, so the tool payload cannot be parsed — this hook cannot
|
|
43
|
+
tell whether the write adds an error-suppression directive. It refuses rather
|
|
44
|
+
than permitting an edit it was unable to check.
|
|
45
|
+
|
|
46
|
+
Install jq (`brew install jq`, `apt-get install jq`), or declare the
|
|
47
|
+
`suppression-residue` gate at `pre-tool` in .lisa.config.json to have your own
|
|
48
|
+
check decide instead.
|
|
49
|
+
MSG
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
23
52
|
|
|
24
53
|
FILE_PATH=$(printf '%s' "$JSON_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
25
54
|
[ -n "$FILE_PATH" ] || exit 0
|
|
@@ -31,6 +60,45 @@ case "${FILE_PATH##*.}" in
|
|
|
31
60
|
*) exit 0 ;;
|
|
32
61
|
esac
|
|
33
62
|
|
|
63
|
+
# Where this script lives, resolved BEFORE any `cd`. `$0` is the path the
|
|
64
|
+
# harness invoked, and the façade helper ships beside it.
|
|
65
|
+
LISA_HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Gate façade. The project's declaration decides BEFORE the built-in judges
|
|
69
|
+
# anything. Full contract, and why an undeclared project sees no change at all,
|
|
70
|
+
# in lisa-edit-gate.sh beside this file.
|
|
71
|
+
#
|
|
72
|
+
# THE WORKING DIRECTORY IS PART OF THE WIRING HERE, which is why this hook was
|
|
73
|
+
# left out of the change that wired the five on-edit ones. Those hooks already
|
|
74
|
+
# `cd "$CLAUDE_PROJECT_DIR"` to run a tool against the tree; this one acts on
|
|
75
|
+
# the payload and never establishes a directory at all. The helper resolves
|
|
76
|
+
# .lisa.config.json and the gate registry RELATIVE to the current directory, so
|
|
77
|
+
# without the `cd` it would read whatever directory the harness happened to
|
|
78
|
+
# launch in and report "nothing declared" for a project that had declared.
|
|
79
|
+
#
|
|
80
|
+
# The `cd` is safe for the built-in below because the built-in reads only the
|
|
81
|
+
# payload and the absolute path inside it — nothing it does depends on the
|
|
82
|
+
# working directory. That is what keeps an undeclared project on exactly the
|
|
83
|
+
# command, and exactly the exit status, it had before.
|
|
84
|
+
#
|
|
85
|
+
# ONE PROPERTY, so there is no all-or-nothing question here: this hook proves
|
|
86
|
+
# `suppression-residue` and nothing else.
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] &&
|
|
89
|
+
[ -f "$LISA_HOOK_DIR/lisa-edit-gate.sh" ] &&
|
|
90
|
+
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null; then
|
|
91
|
+
# shellcheck source=/dev/null
|
|
92
|
+
. "$LISA_HOOK_DIR/lisa-edit-gate.sh"
|
|
93
|
+
if LISA_GATE_COMMANDS="$(lisa_edit_gate_tasks pre-tool suppression-residue)"; then
|
|
94
|
+
# The declared task's own exit status decides the write: non-zero refuses,
|
|
95
|
+
# exactly as the built-in's match does. `lisa_edit_gate_run` exits 2 on the
|
|
96
|
+
# first failure, which is this surface's refusal status.
|
|
97
|
+
lisa_edit_gate_run "$FILE_PATH" "$LISA_GATE_COMMANDS"
|
|
98
|
+
exit $?
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
34
102
|
# Resolve the new text per tool shape:
|
|
35
103
|
# Write -> tool_input.content
|
|
36
104
|
# MultiEdit -> tool_input.edits[].new_string
|
|
@@ -13,13 +13,42 @@
|
|
|
13
13
|
# and identifiers that merely mention these tokens are not flagged.
|
|
14
14
|
# Exit code 2 blocks the tool call and surfaces stderr to Claude.
|
|
15
15
|
# Reference: https://docs.claude.com/en/docs/claude-code/hooks
|
|
16
|
+
#
|
|
17
|
+
# WHAT A REFUSAL HOOK OWES THAT AN ON-EDIT HOOK DOES NOT. The five PostToolUse
|
|
18
|
+
# scripts may bail out when they cannot run: the write has already happened and
|
|
19
|
+
# a skipped lint is a missed report. This one decides whether the write happens
|
|
20
|
+
# at all, so a bail-out is a PERMIT — it lets through exactly the text it exists
|
|
21
|
+
# to stop, while reporting success. Every exit below is therefore one of three
|
|
22
|
+
# things and never a fourth: out of scope (nothing to judge), judged by the
|
|
23
|
+
# project's declared task, or judged by the built-in. "Could not judge" refuses.
|
|
16
24
|
|
|
17
25
|
JSON_INPUT=$(cat)
|
|
18
26
|
|
|
19
27
|
# Project rule (host rules, .agents/rules/): never parse JSON in shell with
|
|
20
|
-
# grep/sed/cut/awk — always use jq.
|
|
21
|
-
#
|
|
22
|
-
|
|
28
|
+
# grep/sed/cut/awk — always use jq.
|
|
29
|
+
#
|
|
30
|
+
# FAIL CLOSED, reversed from the `exit 0` this shipped with. Without jq the
|
|
31
|
+
# payload cannot be read at all: not the path, not the file type, not the new
|
|
32
|
+
# text. Permitting there is the one branch that let a suppression through while
|
|
33
|
+
# the hook reported success, and it could not even say which write it had waved
|
|
34
|
+
# past. The refusal is deliberately unscoped for the same reason — with no
|
|
35
|
+
# parse there is no file type to scope it to. This is the same shape the
|
|
36
|
+
# sibling on-edit hook already ships for a missing `oxlint`: fail loudly rather
|
|
37
|
+
# than silently skip.
|
|
38
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
39
|
+
cat >&2 <<'MSG'
|
|
40
|
+
❌ Blocked: block-suppress-directives cannot inspect this write.
|
|
41
|
+
|
|
42
|
+
`jq` is not installed, so the tool payload cannot be parsed — this hook cannot
|
|
43
|
+
tell whether the write adds an error-suppression directive. It refuses rather
|
|
44
|
+
than permitting an edit it was unable to check.
|
|
45
|
+
|
|
46
|
+
Install jq (`brew install jq`, `apt-get install jq`), or declare the
|
|
47
|
+
`suppression-residue` gate at `pre-tool` in .lisa.config.json to have your own
|
|
48
|
+
check decide instead.
|
|
49
|
+
MSG
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
23
52
|
|
|
24
53
|
FILE_PATH=$(printf '%s' "$JSON_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
25
54
|
[ -n "$FILE_PATH" ] || exit 0
|
|
@@ -31,6 +60,45 @@ case "${FILE_PATH##*.}" in
|
|
|
31
60
|
*) exit 0 ;;
|
|
32
61
|
esac
|
|
33
62
|
|
|
63
|
+
# Where this script lives, resolved BEFORE any `cd`. `$0` is the path the
|
|
64
|
+
# harness invoked, and the façade helper ships beside it.
|
|
65
|
+
LISA_HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Gate façade. The project's declaration decides BEFORE the built-in judges
|
|
69
|
+
# anything. Full contract, and why an undeclared project sees no change at all,
|
|
70
|
+
# in lisa-edit-gate.sh beside this file.
|
|
71
|
+
#
|
|
72
|
+
# THE WORKING DIRECTORY IS PART OF THE WIRING HERE, which is why this hook was
|
|
73
|
+
# left out of the change that wired the five on-edit ones. Those hooks already
|
|
74
|
+
# `cd "$CLAUDE_PROJECT_DIR"` to run a tool against the tree; this one acts on
|
|
75
|
+
# the payload and never establishes a directory at all. The helper resolves
|
|
76
|
+
# .lisa.config.json and the gate registry RELATIVE to the current directory, so
|
|
77
|
+
# without the `cd` it would read whatever directory the harness happened to
|
|
78
|
+
# launch in and report "nothing declared" for a project that had declared.
|
|
79
|
+
#
|
|
80
|
+
# The `cd` is safe for the built-in below because the built-in reads only the
|
|
81
|
+
# payload and the absolute path inside it — nothing it does depends on the
|
|
82
|
+
# working directory. That is what keeps an undeclared project on exactly the
|
|
83
|
+
# command, and exactly the exit status, it had before.
|
|
84
|
+
#
|
|
85
|
+
# ONE PROPERTY, so there is no all-or-nothing question here: this hook proves
|
|
86
|
+
# `suppression-residue` and nothing else.
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] &&
|
|
89
|
+
[ -f "$LISA_HOOK_DIR/lisa-edit-gate.sh" ] &&
|
|
90
|
+
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null; then
|
|
91
|
+
# shellcheck source=/dev/null
|
|
92
|
+
. "$LISA_HOOK_DIR/lisa-edit-gate.sh"
|
|
93
|
+
if LISA_GATE_COMMANDS="$(lisa_edit_gate_tasks pre-tool suppression-residue)"; then
|
|
94
|
+
# The declared task's own exit status decides the write: non-zero refuses,
|
|
95
|
+
# exactly as the built-in's match does. `lisa_edit_gate_run` exits 2 on the
|
|
96
|
+
# first failure, which is this surface's refusal status.
|
|
97
|
+
lisa_edit_gate_run "$FILE_PATH" "$LISA_GATE_COMMANDS"
|
|
98
|
+
exit $?
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
34
102
|
# Resolve the new text per tool shape:
|
|
35
103
|
# Write -> tool_input.content
|
|
36
104
|
# MultiEdit -> tool_input.edits[].new_string
|
|
@@ -8,23 +8,96 @@
|
|
|
8
8
|
# break the schema/migration contract.
|
|
9
9
|
# Reference: https://docs.claude.com/en/docs/claude-code/hooks
|
|
10
10
|
# Exit code 2 blocks the tool call and surfaces stderr to Claude.
|
|
11
|
+
#
|
|
12
|
+
# WHAT A REFUSAL HOOK OWES THAT AN ON-EDIT HOOK DOES NOT. The five PostToolUse
|
|
13
|
+
# scripts may bail out when they cannot run: the write has already happened and
|
|
14
|
+
# a skipped lint is a missed report. This one decides whether the write happens
|
|
15
|
+
# at all, so a bail-out is a PERMIT — it lets through exactly the edit it exists
|
|
16
|
+
# to stop, while reporting success. Every exit below is therefore one of three
|
|
17
|
+
# things and never a fourth: out of scope (nothing to judge), judged by the
|
|
18
|
+
# project's declared task, or judged by the built-in. "Could not judge" refuses.
|
|
11
19
|
|
|
12
20
|
JSON_INPUT=$(cat)
|
|
13
21
|
|
|
22
|
+
# Project rule (host rules, .agents/rules/): never parse JSON in shell with
|
|
23
|
+
# grep/sed/cut/awk — always use jq.
|
|
24
|
+
#
|
|
25
|
+
# FAIL CLOSED, reversed from the `exit 0` this shipped with. Without jq the
|
|
26
|
+
# payload cannot be read at all, so the hook cannot tell a migration edit from
|
|
27
|
+
# any other write — and it said so on stderr while permitting the edit anyway,
|
|
28
|
+
# which is a guard announcing that it did not check and then waving the write
|
|
29
|
+
# through. The refusal is unscoped for the same reason: with no parse there is
|
|
30
|
+
# no path to scope it to.
|
|
14
31
|
if ! command -v jq >/dev/null 2>&1; then
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
cat >&2 <<'MSG'
|
|
33
|
+
❌ Blocked: block-migration-edits cannot inspect this write.
|
|
34
|
+
|
|
35
|
+
`jq` is not installed, so the tool payload cannot be parsed — this hook cannot
|
|
36
|
+
tell whether the write targets a TypeORM migration. It refuses rather than
|
|
37
|
+
permitting an edit it was unable to check.
|
|
38
|
+
|
|
39
|
+
Install jq (`brew install jq`, `apt-get install jq`), or declare the
|
|
40
|
+
`migration-provenance` gate at `pre-tool` in .lisa.config.json to have your own
|
|
41
|
+
check decide instead.
|
|
42
|
+
MSG
|
|
43
|
+
exit 2
|
|
17
44
|
fi
|
|
18
45
|
|
|
19
|
-
FILE_PATH=$(
|
|
46
|
+
FILE_PATH=$(printf '%s' "$JSON_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
20
47
|
|
|
21
48
|
if [ -z "$FILE_PATH" ]; then
|
|
22
49
|
exit 0
|
|
23
50
|
fi
|
|
24
51
|
|
|
52
|
+
# Scope. Outside a migration path this hook does nothing at all, so there is
|
|
53
|
+
# nothing here for a declaration to take over — the same reason the sibling
|
|
54
|
+
# on-edit hook filters by file type and source directory BEFORE it consults.
|
|
25
55
|
case "$FILE_PATH" in
|
|
26
|
-
*/migrations/*.ts
|
|
27
|
-
|
|
56
|
+
*/migrations/*.ts | */migrations/*.js) ;;
|
|
57
|
+
*) exit 0 ;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
# Where this script lives, resolved BEFORE any `cd`. `$0` is the path the
|
|
61
|
+
# harness invoked, and the façade helper ships beside it.
|
|
62
|
+
LISA_HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Gate façade. The project's declaration decides BEFORE the built-in refuses.
|
|
66
|
+
# Full contract, and why an undeclared project sees no change at all, in
|
|
67
|
+
# lisa-edit-gate.sh beside this file.
|
|
68
|
+
#
|
|
69
|
+
# THE WORKING DIRECTORY IS PART OF THE WIRING HERE, which is why this hook was
|
|
70
|
+
# left out of the change that wired the five on-edit ones. Those hooks already
|
|
71
|
+
# `cd "$CLAUDE_PROJECT_DIR"` to run a tool against the tree; this one acts on
|
|
72
|
+
# the payload and never establishes a directory at all. The helper resolves
|
|
73
|
+
# .lisa.config.json and the gate registry RELATIVE to the current directory, so
|
|
74
|
+
# without the `cd` it would read whatever directory the harness happened to
|
|
75
|
+
# launch in and report "nothing declared" for a project that had declared.
|
|
76
|
+
#
|
|
77
|
+
# The `cd` is safe for the built-in below because the built-in reads only the
|
|
78
|
+
# path out of the payload — nothing it does depends on the working directory.
|
|
79
|
+
# That is what keeps an undeclared project on exactly the command, and exactly
|
|
80
|
+
# the exit status, it had before.
|
|
81
|
+
#
|
|
82
|
+
# ONE PROPERTY, so there is no all-or-nothing question here: this hook proves
|
|
83
|
+
# `migration-provenance` and nothing else.
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] &&
|
|
86
|
+
[ -f "$LISA_HOOK_DIR/lisa-edit-gate.sh" ] &&
|
|
87
|
+
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null; then
|
|
88
|
+
# shellcheck source=/dev/null
|
|
89
|
+
. "$LISA_HOOK_DIR/lisa-edit-gate.sh"
|
|
90
|
+
if LISA_GATE_COMMANDS="$(lisa_edit_gate_tasks pre-tool migration-provenance)"; then
|
|
91
|
+
# The declared task's own exit status decides the write: non-zero refuses,
|
|
92
|
+
# exactly as the built-in's outright refusal does. A project that declares
|
|
93
|
+
# this gate is saying its own check knows which migrations came from the
|
|
94
|
+
# model, which is the judgement the built-in makes by refusing them all.
|
|
95
|
+
lisa_edit_gate_run "$FILE_PATH" "$LISA_GATE_COMMANDS"
|
|
96
|
+
exit $?
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
cat >&2 <<EOF
|
|
28
101
|
❌ Blocked: Direct edits to TypeORM migration files are not allowed.
|
|
29
102
|
|
|
30
103
|
File: $FILE_PATH
|
|
@@ -53,8 +126,4 @@ If you believe this edit is an out-of-band migration:
|
|
|
53
126
|
Do NOT silently hand-write a migration. See the nestjs-rules skill
|
|
54
127
|
for the full rationale.
|
|
55
128
|
EOF
|
|
56
|
-
|
|
57
|
-
;;
|
|
58
|
-
esac
|
|
59
|
-
|
|
60
|
-
exit 0
|
|
129
|
+
exit 2
|
|
@@ -13,13 +13,42 @@
|
|
|
13
13
|
# and identifiers that merely mention these tokens are not flagged.
|
|
14
14
|
# Exit code 2 blocks the tool call and surfaces stderr to Claude.
|
|
15
15
|
# Reference: https://docs.claude.com/en/docs/claude-code/hooks
|
|
16
|
+
#
|
|
17
|
+
# WHAT A REFUSAL HOOK OWES THAT AN ON-EDIT HOOK DOES NOT. The five PostToolUse
|
|
18
|
+
# scripts may bail out when they cannot run: the write has already happened and
|
|
19
|
+
# a skipped lint is a missed report. This one decides whether the write happens
|
|
20
|
+
# at all, so a bail-out is a PERMIT — it lets through exactly the text it exists
|
|
21
|
+
# to stop, while reporting success. Every exit below is therefore one of three
|
|
22
|
+
# things and never a fourth: out of scope (nothing to judge), judged by the
|
|
23
|
+
# project's declared task, or judged by the built-in. "Could not judge" refuses.
|
|
16
24
|
|
|
17
25
|
JSON_INPUT=$(cat)
|
|
18
26
|
|
|
19
27
|
# Project rule (host rules, .agents/rules/): never parse JSON in shell with
|
|
20
|
-
# grep/sed/cut/awk — always use jq.
|
|
21
|
-
#
|
|
22
|
-
|
|
28
|
+
# grep/sed/cut/awk — always use jq.
|
|
29
|
+
#
|
|
30
|
+
# FAIL CLOSED, reversed from the `exit 0` this shipped with. Without jq the
|
|
31
|
+
# payload cannot be read at all: not the path, not the file type, not the new
|
|
32
|
+
# text. Permitting there is the one branch that let a suppression through while
|
|
33
|
+
# the hook reported success, and it could not even say which write it had waved
|
|
34
|
+
# past. The refusal is deliberately unscoped for the same reason — with no
|
|
35
|
+
# parse there is no file type to scope it to. This is the same shape the
|
|
36
|
+
# sibling on-edit hook already ships for a missing `oxlint`: fail loudly rather
|
|
37
|
+
# than silently skip.
|
|
38
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
39
|
+
cat >&2 <<'MSG'
|
|
40
|
+
❌ Blocked: block-suppress-directives cannot inspect this write.
|
|
41
|
+
|
|
42
|
+
`jq` is not installed, so the tool payload cannot be parsed — this hook cannot
|
|
43
|
+
tell whether the write adds an error-suppression directive. It refuses rather
|
|
44
|
+
than permitting an edit it was unable to check.
|
|
45
|
+
|
|
46
|
+
Install jq (`brew install jq`, `apt-get install jq`), or declare the
|
|
47
|
+
`suppression-residue` gate at `pre-tool` in .lisa.config.json to have your own
|
|
48
|
+
check decide instead.
|
|
49
|
+
MSG
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
23
52
|
|
|
24
53
|
FILE_PATH=$(printf '%s' "$JSON_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
25
54
|
[ -n "$FILE_PATH" ] || exit 0
|
|
@@ -31,6 +60,45 @@ case "${FILE_PATH##*.}" in
|
|
|
31
60
|
*) exit 0 ;;
|
|
32
61
|
esac
|
|
33
62
|
|
|
63
|
+
# Where this script lives, resolved BEFORE any `cd`. `$0` is the path the
|
|
64
|
+
# harness invoked, and the façade helper ships beside it.
|
|
65
|
+
LISA_HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Gate façade. The project's declaration decides BEFORE the built-in judges
|
|
69
|
+
# anything. Full contract, and why an undeclared project sees no change at all,
|
|
70
|
+
# in lisa-edit-gate.sh beside this file.
|
|
71
|
+
#
|
|
72
|
+
# THE WORKING DIRECTORY IS PART OF THE WIRING HERE, which is why this hook was
|
|
73
|
+
# left out of the change that wired the five on-edit ones. Those hooks already
|
|
74
|
+
# `cd "$CLAUDE_PROJECT_DIR"` to run a tool against the tree; this one acts on
|
|
75
|
+
# the payload and never establishes a directory at all. The helper resolves
|
|
76
|
+
# .lisa.config.json and the gate registry RELATIVE to the current directory, so
|
|
77
|
+
# without the `cd` it would read whatever directory the harness happened to
|
|
78
|
+
# launch in and report "nothing declared" for a project that had declared.
|
|
79
|
+
#
|
|
80
|
+
# The `cd` is safe for the built-in below because the built-in reads only the
|
|
81
|
+
# payload and the absolute path inside it — nothing it does depends on the
|
|
82
|
+
# working directory. That is what keeps an undeclared project on exactly the
|
|
83
|
+
# command, and exactly the exit status, it had before.
|
|
84
|
+
#
|
|
85
|
+
# ONE PROPERTY, so there is no all-or-nothing question here: this hook proves
|
|
86
|
+
# `suppression-residue` and nothing else.
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] &&
|
|
89
|
+
[ -f "$LISA_HOOK_DIR/lisa-edit-gate.sh" ] &&
|
|
90
|
+
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null; then
|
|
91
|
+
# shellcheck source=/dev/null
|
|
92
|
+
. "$LISA_HOOK_DIR/lisa-edit-gate.sh"
|
|
93
|
+
if LISA_GATE_COMMANDS="$(lisa_edit_gate_tasks pre-tool suppression-residue)"; then
|
|
94
|
+
# The declared task's own exit status decides the write: non-zero refuses,
|
|
95
|
+
# exactly as the built-in's match does. `lisa_edit_gate_run` exits 2 on the
|
|
96
|
+
# first failure, which is this surface's refusal status.
|
|
97
|
+
lisa_edit_gate_run "$FILE_PATH" "$LISA_GATE_COMMANDS"
|
|
98
|
+
exit $?
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
34
102
|
# Resolve the new text per tool shape:
|
|
35
103
|
# Write -> tool_input.content
|
|
36
104
|
# MultiEdit -> tool_input.edits[].new_string
|