@codyswann/lisa 2.8.10 → 2.9.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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +37 -1
- package/plugins/lisa/hooks/enforce-team-first.sh +169 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/.claude-plugin/plugin.json +20 -1
- package/plugins/src/base/hooks/enforce-team-first.sh +169 -0
package/package.json
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"lodash": ">=4.18.1"
|
|
80
80
|
},
|
|
81
81
|
"name": "@codyswann/lisa",
|
|
82
|
-
"version": "2.
|
|
82
|
+
"version": "2.9.0",
|
|
83
83
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
84
84
|
"main": "dist/index.js",
|
|
85
85
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
"command": "command -v entire >/dev/null 2>&1 && entire hooks claude-code user-prompt-submit || true"
|
|
16
16
|
}
|
|
17
17
|
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"matcher": "",
|
|
21
|
+
"hooks": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
18
27
|
}
|
|
19
28
|
],
|
|
20
29
|
"PostToolUse": [
|
|
@@ -35,6 +44,15 @@
|
|
|
35
44
|
"command": "command -v entire >/dev/null 2>&1 && entire hooks claude-code post-todo || true"
|
|
36
45
|
}
|
|
37
46
|
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"matcher": "TeamCreate",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
38
56
|
}
|
|
39
57
|
],
|
|
40
58
|
"PreToolUse": [
|
|
@@ -55,6 +73,15 @@
|
|
|
55
73
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-no-verify.sh"
|
|
56
74
|
}
|
|
57
75
|
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"matcher": "",
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
58
85
|
}
|
|
59
86
|
],
|
|
60
87
|
"Stop": [
|
|
@@ -133,6 +160,15 @@
|
|
|
133
160
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/inject-flow-context.sh"
|
|
134
161
|
}
|
|
135
162
|
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"matcher": "",
|
|
166
|
+
"hooks": [
|
|
167
|
+
{
|
|
168
|
+
"type": "command",
|
|
169
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh"
|
|
170
|
+
}
|
|
171
|
+
]
|
|
136
172
|
}
|
|
137
173
|
],
|
|
138
174
|
"SessionEnd": [
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Enforces team-first orchestration for lifecycle skills.
|
|
3
|
+
#
|
|
4
|
+
# Triggered on four hook events:
|
|
5
|
+
# - UserPromptSubmit : detects /lisa:research|plan|implement|intake in the
|
|
6
|
+
# raw prompt and arms enforcement for the session
|
|
7
|
+
# - PreToolUse : detects the same skills via a `Skill` tool call,
|
|
8
|
+
# arms enforcement, and blocks bypass tool calls
|
|
9
|
+
# until ToolSearch+TeamCreate have fired
|
|
10
|
+
# - PostToolUse : on a successful TeamCreate, marks the session as
|
|
11
|
+
# team-created (lifts enforcement)
|
|
12
|
+
# - SubagentStart : marks the new subagent session as a teammate so
|
|
13
|
+
# it is exempt — teammates inherit the lead's team
|
|
14
|
+
# and must never call TeamCreate (double-create
|
|
15
|
+
# is rejected by the harness)
|
|
16
|
+
#
|
|
17
|
+
# Per-session state lives under "$STATE_DIR" as flag files keyed by
|
|
18
|
+
# session_id. Stale state (>24h) is cleaned on each invocation.
|
|
19
|
+
#
|
|
20
|
+
# Fail-open: any unexpected jq parse failure or missing field exits 0
|
|
21
|
+
# rather than blocking. A broken hook must never brick a session.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
26
|
+
if [ -z "$INPUT" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
HOOK_EVENT=$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)
|
|
31
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true)
|
|
32
|
+
|
|
33
|
+
if [ -z "$SESSION_ID" ]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
STATE_DIR="${TMPDIR:-/tmp}/lisa-team-enforce"
|
|
38
|
+
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
39
|
+
|
|
40
|
+
SUBAGENT_FLAG="${STATE_DIR}/${SESSION_ID}.subagent"
|
|
41
|
+
SKILL_FLAG="${STATE_DIR}/${SESSION_ID}.skill"
|
|
42
|
+
TEAM_FLAG="${STATE_DIR}/${SESSION_ID}.team"
|
|
43
|
+
|
|
44
|
+
# Best-effort cleanup of stale state files. Errors are ignored.
|
|
45
|
+
find "$STATE_DIR" -maxdepth 1 -type f -mmin +1440 -delete 2>/dev/null || true
|
|
46
|
+
|
|
47
|
+
is_lifecycle_skill() {
|
|
48
|
+
case "$1" in
|
|
49
|
+
lisa:research|lisa:plan|lisa:implement|lisa:intake) return 0 ;;
|
|
50
|
+
*) return 1 ;;
|
|
51
|
+
esac
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case "$HOOK_EVENT" in
|
|
55
|
+
SubagentStart)
|
|
56
|
+
touch "$SUBAGENT_FLAG" 2>/dev/null || true
|
|
57
|
+
exit 0
|
|
58
|
+
;;
|
|
59
|
+
|
|
60
|
+
UserPromptSubmit)
|
|
61
|
+
PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
|
|
62
|
+
if [ -n "$PROMPT" ]; then
|
|
63
|
+
# Match a slash command at the start of the prompt (allow optional whitespace).
|
|
64
|
+
LEADING=$(printf '%s' "$PROMPT" | sed -n '1p' | sed -E 's/^[[:space:]]*//')
|
|
65
|
+
case "$LEADING" in
|
|
66
|
+
/lisa:research*|/lisa:plan*|/lisa:implement*|/lisa:intake*)
|
|
67
|
+
# Strip leading slash and any args after the first whitespace.
|
|
68
|
+
SKILL_NAME=$(printf '%s' "$LEADING" | sed -E 's|^/||; s/[[:space:]].*$//')
|
|
69
|
+
printf '%s\n' "$SKILL_NAME" >"$SKILL_FLAG" 2>/dev/null || true
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
fi
|
|
73
|
+
exit 0
|
|
74
|
+
;;
|
|
75
|
+
|
|
76
|
+
PostToolUse)
|
|
77
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)
|
|
78
|
+
if [ "$TOOL_NAME" = "TeamCreate" ]; then
|
|
79
|
+
ERROR=$(printf '%s' "$INPUT" | jq -r '.tool_response.error // empty' 2>/dev/null || true)
|
|
80
|
+
IS_ERROR=$(printf '%s' "$INPUT" | jq -r '.tool_response.is_error // empty' 2>/dev/null || true)
|
|
81
|
+
if [ -z "$ERROR" ] && [ "$IS_ERROR" != "true" ]; then
|
|
82
|
+
touch "$TEAM_FLAG" 2>/dev/null || true
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
exit 0
|
|
86
|
+
;;
|
|
87
|
+
|
|
88
|
+
PreToolUse)
|
|
89
|
+
: # fall through
|
|
90
|
+
;;
|
|
91
|
+
|
|
92
|
+
*)
|
|
93
|
+
exit 0
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
|
|
97
|
+
# --- PreToolUse enforcement path ---
|
|
98
|
+
|
|
99
|
+
# Teammates inherit the team; never enforce on subagent sessions.
|
|
100
|
+
if [ -f "$SUBAGENT_FLAG" ]; then
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)
|
|
105
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
106
|
+
exit 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Detect lifecycle skill invocation via the Skill tool.
|
|
110
|
+
if [ "$TOOL_NAME" = "Skill" ]; then
|
|
111
|
+
SKILL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_input.skill // empty' 2>/dev/null || true)
|
|
112
|
+
if is_lifecycle_skill "$SKILL_NAME"; then
|
|
113
|
+
printf '%s\n' "$SKILL_NAME" >"$SKILL_FLAG" 2>/dev/null || true
|
|
114
|
+
# The skill load itself is allowed; enforcement begins on the next call.
|
|
115
|
+
exit 0
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# No lifecycle skill armed — nothing to enforce.
|
|
120
|
+
if [ ! -f "$SKILL_FLAG" ]; then
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Team has been created — enforcement complete.
|
|
125
|
+
if [ -f "$TEAM_FLAG" ]; then
|
|
126
|
+
exit 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# These two are the path forward; never block them.
|
|
130
|
+
case "$TOOL_NAME" in
|
|
131
|
+
ToolSearch|TeamCreate)
|
|
132
|
+
exit 0
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
|
|
136
|
+
# Determine whether this tool is a bypass path. Anything not in this set
|
|
137
|
+
# is allowed (e.g. TodoWrite, AskUserQuestion, ExitPlanMode) so we don't
|
|
138
|
+
# over-block.
|
|
139
|
+
should_block=0
|
|
140
|
+
case "$TOOL_NAME" in
|
|
141
|
+
Task|TaskCreate|TaskGet|TaskList|TaskOutput|TaskStop|TaskUpdate)
|
|
142
|
+
should_block=1 ;;
|
|
143
|
+
Skill|Read|Write|Edit|MultiEdit|NotebookEdit|Bash|Grep|Glob|WebSearch|WebFetch)
|
|
144
|
+
should_block=1 ;;
|
|
145
|
+
mcp__*)
|
|
146
|
+
should_block=1 ;;
|
|
147
|
+
esac
|
|
148
|
+
|
|
149
|
+
if [ "$should_block" -eq 0 ]; then
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
ACTIVE_SKILL=$(cat "$SKILL_FLAG" 2>/dev/null || echo "lisa:???")
|
|
154
|
+
cat >&2 <<EOF
|
|
155
|
+
Blocked: this session invoked /${ACTIVE_SKILL}, which is an agent-team flow.
|
|
156
|
+
Before any other tool call, you must:
|
|
157
|
+
|
|
158
|
+
1. ToolSearch with query: "select:TeamCreate" (load the deferred schema)
|
|
159
|
+
2. TeamCreate (actually create the team)
|
|
160
|
+
|
|
161
|
+
The current attempt to call \`${TOOL_NAME}\` is a team-bypass path. Reading
|
|
162
|
+
the ticket, exploring the code, fetching context — those are tasks for the
|
|
163
|
+
team you are about to create, not for the lead session before the team
|
|
164
|
+
exists.
|
|
165
|
+
|
|
166
|
+
Re-read the orchestration preamble in /${ACTIVE_SKILL} and start with
|
|
167
|
+
ToolSearch.
|
|
168
|
+
EOF
|
|
169
|
+
exit 2
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
"command": "command -v entire >/dev/null 2>&1 && entire hooks claude-code user-prompt-submit || true"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"matcher": "",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh" }
|
|
21
|
+
]
|
|
16
22
|
}
|
|
17
23
|
],
|
|
18
24
|
"PostToolUse": [
|
|
@@ -27,6 +33,12 @@
|
|
|
27
33
|
"hooks": [
|
|
28
34
|
{ "type": "command", "command": "command -v entire >/dev/null 2>&1 && entire hooks claude-code post-todo || true" }
|
|
29
35
|
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"matcher": "TeamCreate",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh" }
|
|
41
|
+
]
|
|
30
42
|
}
|
|
31
43
|
],
|
|
32
44
|
"PreToolUse": [
|
|
@@ -41,6 +53,12 @@
|
|
|
41
53
|
"hooks": [
|
|
42
54
|
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-no-verify.sh" }
|
|
43
55
|
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"matcher": "",
|
|
59
|
+
"hooks": [
|
|
60
|
+
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh" }
|
|
61
|
+
]
|
|
44
62
|
}
|
|
45
63
|
],
|
|
46
64
|
"Stop": [
|
|
@@ -55,7 +73,8 @@
|
|
|
55
73
|
],
|
|
56
74
|
"SubagentStart": [
|
|
57
75
|
{ "matcher": "", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/inject-rules.sh" }] },
|
|
58
|
-
{ "matcher": "", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/inject-flow-context.sh" }] }
|
|
76
|
+
{ "matcher": "", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/inject-flow-context.sh" }] },
|
|
77
|
+
{ "matcher": "", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-team-first.sh" }] }
|
|
59
78
|
],
|
|
60
79
|
"SessionEnd": [
|
|
61
80
|
{ "matcher": "", "hooks": [{ "type": "command", "command": "command -v entire >/dev/null 2>&1 && entire hooks claude-code session-end || true" }] }
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Enforces team-first orchestration for lifecycle skills.
|
|
3
|
+
#
|
|
4
|
+
# Triggered on four hook events:
|
|
5
|
+
# - UserPromptSubmit : detects /lisa:research|plan|implement|intake in the
|
|
6
|
+
# raw prompt and arms enforcement for the session
|
|
7
|
+
# - PreToolUse : detects the same skills via a `Skill` tool call,
|
|
8
|
+
# arms enforcement, and blocks bypass tool calls
|
|
9
|
+
# until ToolSearch+TeamCreate have fired
|
|
10
|
+
# - PostToolUse : on a successful TeamCreate, marks the session as
|
|
11
|
+
# team-created (lifts enforcement)
|
|
12
|
+
# - SubagentStart : marks the new subagent session as a teammate so
|
|
13
|
+
# it is exempt — teammates inherit the lead's team
|
|
14
|
+
# and must never call TeamCreate (double-create
|
|
15
|
+
# is rejected by the harness)
|
|
16
|
+
#
|
|
17
|
+
# Per-session state lives under "$STATE_DIR" as flag files keyed by
|
|
18
|
+
# session_id. Stale state (>24h) is cleaned on each invocation.
|
|
19
|
+
#
|
|
20
|
+
# Fail-open: any unexpected jq parse failure or missing field exits 0
|
|
21
|
+
# rather than blocking. A broken hook must never brick a session.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
26
|
+
if [ -z "$INPUT" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
HOOK_EVENT=$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)
|
|
31
|
+
SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true)
|
|
32
|
+
|
|
33
|
+
if [ -z "$SESSION_ID" ]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
STATE_DIR="${TMPDIR:-/tmp}/lisa-team-enforce"
|
|
38
|
+
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
39
|
+
|
|
40
|
+
SUBAGENT_FLAG="${STATE_DIR}/${SESSION_ID}.subagent"
|
|
41
|
+
SKILL_FLAG="${STATE_DIR}/${SESSION_ID}.skill"
|
|
42
|
+
TEAM_FLAG="${STATE_DIR}/${SESSION_ID}.team"
|
|
43
|
+
|
|
44
|
+
# Best-effort cleanup of stale state files. Errors are ignored.
|
|
45
|
+
find "$STATE_DIR" -maxdepth 1 -type f -mmin +1440 -delete 2>/dev/null || true
|
|
46
|
+
|
|
47
|
+
is_lifecycle_skill() {
|
|
48
|
+
case "$1" in
|
|
49
|
+
lisa:research|lisa:plan|lisa:implement|lisa:intake) return 0 ;;
|
|
50
|
+
*) return 1 ;;
|
|
51
|
+
esac
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case "$HOOK_EVENT" in
|
|
55
|
+
SubagentStart)
|
|
56
|
+
touch "$SUBAGENT_FLAG" 2>/dev/null || true
|
|
57
|
+
exit 0
|
|
58
|
+
;;
|
|
59
|
+
|
|
60
|
+
UserPromptSubmit)
|
|
61
|
+
PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
|
|
62
|
+
if [ -n "$PROMPT" ]; then
|
|
63
|
+
# Match a slash command at the start of the prompt (allow optional whitespace).
|
|
64
|
+
LEADING=$(printf '%s' "$PROMPT" | sed -n '1p' | sed -E 's/^[[:space:]]*//')
|
|
65
|
+
case "$LEADING" in
|
|
66
|
+
/lisa:research*|/lisa:plan*|/lisa:implement*|/lisa:intake*)
|
|
67
|
+
# Strip leading slash and any args after the first whitespace.
|
|
68
|
+
SKILL_NAME=$(printf '%s' "$LEADING" | sed -E 's|^/||; s/[[:space:]].*$//')
|
|
69
|
+
printf '%s\n' "$SKILL_NAME" >"$SKILL_FLAG" 2>/dev/null || true
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
fi
|
|
73
|
+
exit 0
|
|
74
|
+
;;
|
|
75
|
+
|
|
76
|
+
PostToolUse)
|
|
77
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)
|
|
78
|
+
if [ "$TOOL_NAME" = "TeamCreate" ]; then
|
|
79
|
+
ERROR=$(printf '%s' "$INPUT" | jq -r '.tool_response.error // empty' 2>/dev/null || true)
|
|
80
|
+
IS_ERROR=$(printf '%s' "$INPUT" | jq -r '.tool_response.is_error // empty' 2>/dev/null || true)
|
|
81
|
+
if [ -z "$ERROR" ] && [ "$IS_ERROR" != "true" ]; then
|
|
82
|
+
touch "$TEAM_FLAG" 2>/dev/null || true
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
exit 0
|
|
86
|
+
;;
|
|
87
|
+
|
|
88
|
+
PreToolUse)
|
|
89
|
+
: # fall through
|
|
90
|
+
;;
|
|
91
|
+
|
|
92
|
+
*)
|
|
93
|
+
exit 0
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
|
|
97
|
+
# --- PreToolUse enforcement path ---
|
|
98
|
+
|
|
99
|
+
# Teammates inherit the team; never enforce on subagent sessions.
|
|
100
|
+
if [ -f "$SUBAGENT_FLAG" ]; then
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)
|
|
105
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
106
|
+
exit 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Detect lifecycle skill invocation via the Skill tool.
|
|
110
|
+
if [ "$TOOL_NAME" = "Skill" ]; then
|
|
111
|
+
SKILL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_input.skill // empty' 2>/dev/null || true)
|
|
112
|
+
if is_lifecycle_skill "$SKILL_NAME"; then
|
|
113
|
+
printf '%s\n' "$SKILL_NAME" >"$SKILL_FLAG" 2>/dev/null || true
|
|
114
|
+
# The skill load itself is allowed; enforcement begins on the next call.
|
|
115
|
+
exit 0
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# No lifecycle skill armed — nothing to enforce.
|
|
120
|
+
if [ ! -f "$SKILL_FLAG" ]; then
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Team has been created — enforcement complete.
|
|
125
|
+
if [ -f "$TEAM_FLAG" ]; then
|
|
126
|
+
exit 0
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# These two are the path forward; never block them.
|
|
130
|
+
case "$TOOL_NAME" in
|
|
131
|
+
ToolSearch|TeamCreate)
|
|
132
|
+
exit 0
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
|
|
136
|
+
# Determine whether this tool is a bypass path. Anything not in this set
|
|
137
|
+
# is allowed (e.g. TodoWrite, AskUserQuestion, ExitPlanMode) so we don't
|
|
138
|
+
# over-block.
|
|
139
|
+
should_block=0
|
|
140
|
+
case "$TOOL_NAME" in
|
|
141
|
+
Task|TaskCreate|TaskGet|TaskList|TaskOutput|TaskStop|TaskUpdate)
|
|
142
|
+
should_block=1 ;;
|
|
143
|
+
Skill|Read|Write|Edit|MultiEdit|NotebookEdit|Bash|Grep|Glob|WebSearch|WebFetch)
|
|
144
|
+
should_block=1 ;;
|
|
145
|
+
mcp__*)
|
|
146
|
+
should_block=1 ;;
|
|
147
|
+
esac
|
|
148
|
+
|
|
149
|
+
if [ "$should_block" -eq 0 ]; then
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
ACTIVE_SKILL=$(cat "$SKILL_FLAG" 2>/dev/null || echo "lisa:???")
|
|
154
|
+
cat >&2 <<EOF
|
|
155
|
+
Blocked: this session invoked /${ACTIVE_SKILL}, which is an agent-team flow.
|
|
156
|
+
Before any other tool call, you must:
|
|
157
|
+
|
|
158
|
+
1. ToolSearch with query: "select:TeamCreate" (load the deferred schema)
|
|
159
|
+
2. TeamCreate (actually create the team)
|
|
160
|
+
|
|
161
|
+
The current attempt to call \`${TOOL_NAME}\` is a team-bypass path. Reading
|
|
162
|
+
the ticket, exploring the code, fetching context — those are tasks for the
|
|
163
|
+
team you are about to create, not for the lead session before the team
|
|
164
|
+
exists.
|
|
165
|
+
|
|
166
|
+
Re-read the orchestration preamble in /${ACTIVE_SKILL} and start with
|
|
167
|
+
ToolSearch.
|
|
168
|
+
EOF
|
|
169
|
+
exit 2
|