@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 CHANGED
@@ -79,7 +79,7 @@
79
79
  "lodash": ">=4.18.1"
80
80
  },
81
81
  "name": "@codyswann/lisa",
82
- "version": "2.8.10",
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.8.10",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.8.10",
3
+ "version": "2.9.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.8.10",
3
+ "version": "2.9.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.8.10",
3
+ "version": "2.9.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.8.10",
3
+ "version": "2.9.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.8.10",
3
+ "version": "2.9.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -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