@codyswann/lisa 2.329.0 → 2.330.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.
Files changed (81) hide show
  1. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +4 -2
  2. package/all/copy-overwrite/scripts/lisa-hooks/block-instruction-file-edits.sh +142 -0
  3. package/all/merge/.claude/settings.json +9 -0
  4. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  5. package/dist/core/upstream-evidence-manifest.js +21 -6
  6. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  7. package/dist/opencode/hooks-installer.d.ts.map +1 -1
  8. package/dist/opencode/hooks-installer.js +1 -41
  9. package/dist/opencode/hooks-installer.js.map +1 -1
  10. package/dist/opencode/plugin-catalog.d.ts +25 -0
  11. package/dist/opencode/plugin-catalog.d.ts.map +1 -0
  12. package/dist/opencode/plugin-catalog.js +47 -0
  13. package/dist/opencode/plugin-catalog.js.map +1 -0
  14. package/dist/opencode/plugin-templates/lisa-block-instruction-file-edits.ts +72 -0
  15. package/package.json +1 -1
  16. package/plugins/lisa/.claude-plugin/plugin.json +14 -1
  17. package/plugins/lisa/.codex-plugin/hooks.json +13 -0
  18. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa/hooks/block-instruction-file-edits.agy.sh +58 -0
  20. package/plugins/lisa/hooks/block-instruction-file-edits.sh +142 -0
  21. package/plugins/lisa-agy/hooks/block-instruction-file-edits.agy.sh +58 -0
  22. package/plugins/lisa-agy/hooks/block-instruction-file-edits.sh +142 -0
  23. package/plugins/lisa-agy/hooks.json +13 -0
  24. package/plugins/lisa-agy/plugin.json +1 -1
  25. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  28. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-copilot/.claude-plugin/plugin.json +14 -1
  31. package/plugins/lisa-copilot/hooks/block-instruction-file-edits.sh +142 -0
  32. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-cursor/hooks/block-instruction-file-edits.sh +142 -0
  34. package/plugins/lisa-cursor/hooks/hooks.json +8 -0
  35. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo-agy/plugin.json +1 -1
  38. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  48. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  53. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  58. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails-agy/plugin.json +1 -1
  63. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  68. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  73. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/src/base/.claude-plugin/plugin.json +13 -0
  76. package/plugins/src/base/hooks/block-instruction-file-edits.agy.sh +58 -0
  77. package/plugins/src/base/hooks/block-instruction-file-edits.sh +142 -0
  78. package/scripts/build-plugins.sh +2 -1
  79. package/scripts/generate-agy-plugin-artifacts.mjs +11 -0
  80. package/scripts/lib/per-agent-hook-filter.mjs +10 -0
  81. package/scripts/lisa-enforcement-fallback.sh +4 -2
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook: refuse agent writes to the session-instruction files.
3
+ #
4
+ # `AGENTS.md`, `CLAUDE.md`, and `.github/copilot-instructions.md` are the files
5
+ # every agent loads at session start. They are human-authored and curated on
6
+ # purpose — Lisa's own contract says so in two places:
7
+ # - plugins/src/base/rules/reference/project-learnings.md
8
+ # - plugins/src/base/skills/lisa-debrief-apply/SKILL.md ("CLAUDE.md is
9
+ # human-authored ... apply never writes to any of the three")
10
+ #
11
+ # Nothing enforced it, so agents appended their own findings anyway: one fleet
12
+ # repo reached 949 lines of ticket-keyed trap dumps in `AGENTS.md` while its
13
+ # learnings ledger held 22. Every one of those lines is charged to the context
14
+ # of every later session in that project, forever. Prose did not hold; this is
15
+ # the executable control that does.
16
+ #
17
+ # Blocked signatures:
18
+ # 1. Write / Edit / MultiEdit / NotebookEdit whose target basename is one of
19
+ # the instruction files;
20
+ # 2. Bash output redirection (`>`, `>>`), `tee`, or `sed -i` aimed at one.
21
+ # Reads (`cat AGENTS.md`, `rg pattern AGENTS.md`) never fire.
22
+ #
23
+ # Exemptions (allowed):
24
+ # - `LISA_ALLOW_INSTRUCTION_FILE_WRITE` set — the operator's explicit override,
25
+ # named in the refusal so a human who really wants the edit can take it;
26
+ # - payloads carrying a `<!-- LISA_` marker: Lisa's own marker-bounded regions
27
+ # (the agy project-learnings bridge, cross-pollinate's rule section) replace
28
+ # in place on re-run and cannot grow the file;
29
+ # - paths under `node_modules/` or `dist/` — vendored copies, not the host's.
30
+ set -euo pipefail
31
+
32
+ input="$(cat)"
33
+
34
+ command -v jq >/dev/null 2>&1 || exit 0
35
+
36
+ # The operator's override. Checked before anything else so it is always the
37
+ # cheapest way out of a refusal a human deliberately wants to overrule.
38
+ if [ -n "${LISA_ALLOW_INSTRUCTION_FILE_WRITE:-}" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
43
+ [ -n "$tool_name" ] || exit 0
44
+
45
+ # Lisa's own bounded bridges write marked regions. A payload carrying a marker
46
+ # is replacing a delimited block, not appending prose, so it cannot be the
47
+ # unbounded growth this guard exists to stop.
48
+ if printf '%s' "$input" | grep -q '<!-- LISA_'; then
49
+ exit 0
50
+ fi
51
+
52
+ # Basename match, case-insensitively: `agents.md` and `AGENTS.md` are the same
53
+ # file on the macOS checkouts this fleet runs on.
54
+ is_instruction_file() {
55
+ local candidate="$1"
56
+ case "$candidate" in
57
+ */node_modules/* | node_modules/* | */dist/* | dist/*) return 1 ;;
58
+ esac
59
+ local base="${candidate##*/}"
60
+ case "$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')" in
61
+ agents.md | claude.md | copilot-instructions.md) return 0 ;;
62
+ esac
63
+ return 1
64
+ }
65
+
66
+ refuse() {
67
+ local target="$1"
68
+ cat >&2 <<EOF
69
+ BLOCKED: refusing to write \`$target\`.
70
+
71
+ WHY: this is a session-instruction file, not a place to record what you just
72
+ learned. Every line in it is loaded into the context of every agent, in every
73
+ future session, in this project, forever. It is human-curated on purpose. An
74
+ agent appending its own findings is how these files grow into hundreds of lines
75
+ of stale, ticket-specific trivia that every later session pays for and half of
76
+ which was only ever true once.
77
+
78
+ WHERE IT GOES INSTEAD — take the first one that fits:
79
+
80
+ 1. Every agent genuinely needs this in every session, and only in this project.
81
+ That is a project rule — but do not write it yourself. Capture it with
82
+ \`/lisa:persist-learning\` so it lands in the learnings ledger with provenance
83
+ and a confidence score. The gardener (\`/lisa:learnings:audit\`) then proposes
84
+ promotion into \`.claude/rules/PROJECT_RULES.md\` as a human-gated ticket.
85
+
86
+ 2. It changes how an existing skill should behave. Edit that skill's SKILL.md.
87
+ Knowledge belongs next to the procedure it modifies, not in a global preamble.
88
+
89
+ 3. It is true beyond this project. Propose it upstream to Lisa via
90
+ \`/lisa:cross-pollinate\`, or open an issue on CodySwannGT/lisa. A local copy of
91
+ a general rule silently drifts the moment upstream changes.
92
+
93
+ 4. None of the above — it is background someone may want to look up, not a
94
+ standing instruction. Write it into the project documentation (\`wiki/\` or
95
+ \`docs/\`).
96
+
97
+ If a human explicitly asked for this edit, they can re-run with
98
+ \`LISA_ALLOW_INSTRUCTION_FILE_WRITE=1\` set, which bypasses this guard.
99
+ EOF
100
+ exit 2
101
+ }
102
+
103
+ case "$tool_name" in
104
+ Write | Edit | MultiEdit | NotebookEdit | Update)
105
+ # `file_path` covers Write/Edit; `edits[].file_path` covers the MultiEdit
106
+ # shapes that carry a path per edit; `notebook_path` covers NotebookEdit.
107
+ paths="$(printf '%s' "$input" | jq -r '
108
+ [ .tool_input.file_path?,
109
+ .tool_input.path?,
110
+ .tool_input.notebook_path?,
111
+ (.tool_input.edits? // [] | .[].file_path?)
112
+ ] | map(select(. != null and . != "")) | .[]' 2>/dev/null || true)"
113
+ while IFS= read -r candidate; do
114
+ [ -n "$candidate" ] || continue
115
+ if is_instruction_file "$candidate"; then
116
+ refuse "$candidate"
117
+ fi
118
+ done <<EOF
119
+ $paths
120
+ EOF
121
+ ;;
122
+ Bash)
123
+ command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
124
+ [ -n "$command_str" ] || exit 0
125
+ # Fast path: no instruction filename mentioned means nothing to classify.
126
+ if ! printf '%s' "$command_str" |
127
+ grep -Eqi '(agents|claude|copilot-instructions)\.md'; then
128
+ exit 0
129
+ fi
130
+ # Write signatures only. A bare mention (`cat AGENTS.md`, `rg x AGENTS.md`)
131
+ # is a read and must stay allowed — the filename has to appear as the target
132
+ # of a redirection, a tee, or an in-place sed.
133
+ write_target='[^ |;&]*(agents|claude|copilot-instructions)\.md'
134
+ if printf '%s' "$command_str" |
135
+ grep -Eqi ">>?[[:space:]]*['\"]?$write_target|tee([[:space:]]+-[a-z]+)*[[:space:]]+['\"]?$write_target|sed[[:space:]]+[^|;&]*-i[^|;&]*$write_target"; then
136
+ refuse "$(printf '%s' "$command_str" |
137
+ grep -Eoi "$write_target" | head -1)"
138
+ fi
139
+ ;;
140
+ esac
141
+
142
+ exit 0
@@ -37,5 +37,18 @@
37
37
  ]
38
38
  }
39
39
  ]
40
+ },
41
+ "lisa-block-instruction-file-edits": {
42
+ "PreToolUse": [
43
+ {
44
+ "matcher": "run_command",
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "bash \"$HOME/.gemini/config/plugins/lisa/hooks/block-instruction-file-edits.agy.sh\""
49
+ }
50
+ ]
51
+ }
52
+ ]
40
53
  }
41
54
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-cdk",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-cdk",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-cdk",
3
- "version": "2.329.0",
3
+ "version": "2.330.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",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -52,6 +52,19 @@
52
52
  {
53
53
  "type": "command",
54
54
  "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-shell-json-parsing.sh"
55
+ },
56
+ {
57
+ "type": "command",
58
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-instruction-file-edits.sh"
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "matcher": "Write|Edit|MultiEdit",
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-instruction-file-edits.sh"
55
68
  }
56
69
  ]
57
70
  },
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook: refuse agent writes to the session-instruction files.
3
+ #
4
+ # `AGENTS.md`, `CLAUDE.md`, and `.github/copilot-instructions.md` are the files
5
+ # every agent loads at session start. They are human-authored and curated on
6
+ # purpose — Lisa's own contract says so in two places:
7
+ # - plugins/src/base/rules/reference/project-learnings.md
8
+ # - plugins/src/base/skills/lisa-debrief-apply/SKILL.md ("CLAUDE.md is
9
+ # human-authored ... apply never writes to any of the three")
10
+ #
11
+ # Nothing enforced it, so agents appended their own findings anyway: one fleet
12
+ # repo reached 949 lines of ticket-keyed trap dumps in `AGENTS.md` while its
13
+ # learnings ledger held 22. Every one of those lines is charged to the context
14
+ # of every later session in that project, forever. Prose did not hold; this is
15
+ # the executable control that does.
16
+ #
17
+ # Blocked signatures:
18
+ # 1. Write / Edit / MultiEdit / NotebookEdit whose target basename is one of
19
+ # the instruction files;
20
+ # 2. Bash output redirection (`>`, `>>`), `tee`, or `sed -i` aimed at one.
21
+ # Reads (`cat AGENTS.md`, `rg pattern AGENTS.md`) never fire.
22
+ #
23
+ # Exemptions (allowed):
24
+ # - `LISA_ALLOW_INSTRUCTION_FILE_WRITE` set — the operator's explicit override,
25
+ # named in the refusal so a human who really wants the edit can take it;
26
+ # - payloads carrying a `<!-- LISA_` marker: Lisa's own marker-bounded regions
27
+ # (the agy project-learnings bridge, cross-pollinate's rule section) replace
28
+ # in place on re-run and cannot grow the file;
29
+ # - paths under `node_modules/` or `dist/` — vendored copies, not the host's.
30
+ set -euo pipefail
31
+
32
+ input="$(cat)"
33
+
34
+ command -v jq >/dev/null 2>&1 || exit 0
35
+
36
+ # The operator's override. Checked before anything else so it is always the
37
+ # cheapest way out of a refusal a human deliberately wants to overrule.
38
+ if [ -n "${LISA_ALLOW_INSTRUCTION_FILE_WRITE:-}" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
43
+ [ -n "$tool_name" ] || exit 0
44
+
45
+ # Lisa's own bounded bridges write marked regions. A payload carrying a marker
46
+ # is replacing a delimited block, not appending prose, so it cannot be the
47
+ # unbounded growth this guard exists to stop.
48
+ if printf '%s' "$input" | grep -q '<!-- LISA_'; then
49
+ exit 0
50
+ fi
51
+
52
+ # Basename match, case-insensitively: `agents.md` and `AGENTS.md` are the same
53
+ # file on the macOS checkouts this fleet runs on.
54
+ is_instruction_file() {
55
+ local candidate="$1"
56
+ case "$candidate" in
57
+ */node_modules/* | node_modules/* | */dist/* | dist/*) return 1 ;;
58
+ esac
59
+ local base="${candidate##*/}"
60
+ case "$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')" in
61
+ agents.md | claude.md | copilot-instructions.md) return 0 ;;
62
+ esac
63
+ return 1
64
+ }
65
+
66
+ refuse() {
67
+ local target="$1"
68
+ cat >&2 <<EOF
69
+ BLOCKED: refusing to write \`$target\`.
70
+
71
+ WHY: this is a session-instruction file, not a place to record what you just
72
+ learned. Every line in it is loaded into the context of every agent, in every
73
+ future session, in this project, forever. It is human-curated on purpose. An
74
+ agent appending its own findings is how these files grow into hundreds of lines
75
+ of stale, ticket-specific trivia that every later session pays for and half of
76
+ which was only ever true once.
77
+
78
+ WHERE IT GOES INSTEAD — take the first one that fits:
79
+
80
+ 1. Every agent genuinely needs this in every session, and only in this project.
81
+ That is a project rule — but do not write it yourself. Capture it with
82
+ \`/lisa:persist-learning\` so it lands in the learnings ledger with provenance
83
+ and a confidence score. The gardener (\`/lisa:learnings:audit\`) then proposes
84
+ promotion into \`.claude/rules/PROJECT_RULES.md\` as a human-gated ticket.
85
+
86
+ 2. It changes how an existing skill should behave. Edit that skill's SKILL.md.
87
+ Knowledge belongs next to the procedure it modifies, not in a global preamble.
88
+
89
+ 3. It is true beyond this project. Propose it upstream to Lisa via
90
+ \`/lisa:cross-pollinate\`, or open an issue on CodySwannGT/lisa. A local copy of
91
+ a general rule silently drifts the moment upstream changes.
92
+
93
+ 4. None of the above — it is background someone may want to look up, not a
94
+ standing instruction. Write it into the project documentation (\`wiki/\` or
95
+ \`docs/\`).
96
+
97
+ If a human explicitly asked for this edit, they can re-run with
98
+ \`LISA_ALLOW_INSTRUCTION_FILE_WRITE=1\` set, which bypasses this guard.
99
+ EOF
100
+ exit 2
101
+ }
102
+
103
+ case "$tool_name" in
104
+ Write | Edit | MultiEdit | NotebookEdit | Update)
105
+ # `file_path` covers Write/Edit; `edits[].file_path` covers the MultiEdit
106
+ # shapes that carry a path per edit; `notebook_path` covers NotebookEdit.
107
+ paths="$(printf '%s' "$input" | jq -r '
108
+ [ .tool_input.file_path?,
109
+ .tool_input.path?,
110
+ .tool_input.notebook_path?,
111
+ (.tool_input.edits? // [] | .[].file_path?)
112
+ ] | map(select(. != null and . != "")) | .[]' 2>/dev/null || true)"
113
+ while IFS= read -r candidate; do
114
+ [ -n "$candidate" ] || continue
115
+ if is_instruction_file "$candidate"; then
116
+ refuse "$candidate"
117
+ fi
118
+ done <<EOF
119
+ $paths
120
+ EOF
121
+ ;;
122
+ Bash)
123
+ command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
124
+ [ -n "$command_str" ] || exit 0
125
+ # Fast path: no instruction filename mentioned means nothing to classify.
126
+ if ! printf '%s' "$command_str" |
127
+ grep -Eqi '(agents|claude|copilot-instructions)\.md'; then
128
+ exit 0
129
+ fi
130
+ # Write signatures only. A bare mention (`cat AGENTS.md`, `rg x AGENTS.md`)
131
+ # is a read and must stay allowed — the filename has to appear as the target
132
+ # of a redirection, a tee, or an in-place sed.
133
+ write_target='[^ |;&]*(agents|claude|copilot-instructions)\.md'
134
+ if printf '%s' "$command_str" |
135
+ grep -Eqi ">>?[[:space:]]*['\"]?$write_target|tee([[:space:]]+-[a-z]+)*[[:space:]]+['\"]?$write_target|sed[[:space:]]+[^|;&]*-i[^|;&]*$write_target"; then
136
+ refuse "$(printf '%s' "$command_str" |
137
+ grep -Eoi "$write_target" | head -1)"
138
+ fi
139
+ ;;
140
+ esac
141
+
142
+ exit 0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook: refuse agent writes to the session-instruction files.
3
+ #
4
+ # `AGENTS.md`, `CLAUDE.md`, and `.github/copilot-instructions.md` are the files
5
+ # every agent loads at session start. They are human-authored and curated on
6
+ # purpose — Lisa's own contract says so in two places:
7
+ # - plugins/src/base/rules/reference/project-learnings.md
8
+ # - plugins/src/base/skills/lisa-debrief-apply/SKILL.md ("CLAUDE.md is
9
+ # human-authored ... apply never writes to any of the three")
10
+ #
11
+ # Nothing enforced it, so agents appended their own findings anyway: one fleet
12
+ # repo reached 949 lines of ticket-keyed trap dumps in `AGENTS.md` while its
13
+ # learnings ledger held 22. Every one of those lines is charged to the context
14
+ # of every later session in that project, forever. Prose did not hold; this is
15
+ # the executable control that does.
16
+ #
17
+ # Blocked signatures:
18
+ # 1. Write / Edit / MultiEdit / NotebookEdit whose target basename is one of
19
+ # the instruction files;
20
+ # 2. Bash output redirection (`>`, `>>`), `tee`, or `sed -i` aimed at one.
21
+ # Reads (`cat AGENTS.md`, `rg pattern AGENTS.md`) never fire.
22
+ #
23
+ # Exemptions (allowed):
24
+ # - `LISA_ALLOW_INSTRUCTION_FILE_WRITE` set — the operator's explicit override,
25
+ # named in the refusal so a human who really wants the edit can take it;
26
+ # - payloads carrying a `<!-- LISA_` marker: Lisa's own marker-bounded regions
27
+ # (the agy project-learnings bridge, cross-pollinate's rule section) replace
28
+ # in place on re-run and cannot grow the file;
29
+ # - paths under `node_modules/` or `dist/` — vendored copies, not the host's.
30
+ set -euo pipefail
31
+
32
+ input="$(cat)"
33
+
34
+ command -v jq >/dev/null 2>&1 || exit 0
35
+
36
+ # The operator's override. Checked before anything else so it is always the
37
+ # cheapest way out of a refusal a human deliberately wants to overrule.
38
+ if [ -n "${LISA_ALLOW_INSTRUCTION_FILE_WRITE:-}" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
43
+ [ -n "$tool_name" ] || exit 0
44
+
45
+ # Lisa's own bounded bridges write marked regions. A payload carrying a marker
46
+ # is replacing a delimited block, not appending prose, so it cannot be the
47
+ # unbounded growth this guard exists to stop.
48
+ if printf '%s' "$input" | grep -q '<!-- LISA_'; then
49
+ exit 0
50
+ fi
51
+
52
+ # Basename match, case-insensitively: `agents.md` and `AGENTS.md` are the same
53
+ # file on the macOS checkouts this fleet runs on.
54
+ is_instruction_file() {
55
+ local candidate="$1"
56
+ case "$candidate" in
57
+ */node_modules/* | node_modules/* | */dist/* | dist/*) return 1 ;;
58
+ esac
59
+ local base="${candidate##*/}"
60
+ case "$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')" in
61
+ agents.md | claude.md | copilot-instructions.md) return 0 ;;
62
+ esac
63
+ return 1
64
+ }
65
+
66
+ refuse() {
67
+ local target="$1"
68
+ cat >&2 <<EOF
69
+ BLOCKED: refusing to write \`$target\`.
70
+
71
+ WHY: this is a session-instruction file, not a place to record what you just
72
+ learned. Every line in it is loaded into the context of every agent, in every
73
+ future session, in this project, forever. It is human-curated on purpose. An
74
+ agent appending its own findings is how these files grow into hundreds of lines
75
+ of stale, ticket-specific trivia that every later session pays for and half of
76
+ which was only ever true once.
77
+
78
+ WHERE IT GOES INSTEAD — take the first one that fits:
79
+
80
+ 1. Every agent genuinely needs this in every session, and only in this project.
81
+ That is a project rule — but do not write it yourself. Capture it with
82
+ \`/lisa:persist-learning\` so it lands in the learnings ledger with provenance
83
+ and a confidence score. The gardener (\`/lisa:learnings:audit\`) then proposes
84
+ promotion into \`.claude/rules/PROJECT_RULES.md\` as a human-gated ticket.
85
+
86
+ 2. It changes how an existing skill should behave. Edit that skill's SKILL.md.
87
+ Knowledge belongs next to the procedure it modifies, not in a global preamble.
88
+
89
+ 3. It is true beyond this project. Propose it upstream to Lisa via
90
+ \`/lisa:cross-pollinate\`, or open an issue on CodySwannGT/lisa. A local copy of
91
+ a general rule silently drifts the moment upstream changes.
92
+
93
+ 4. None of the above — it is background someone may want to look up, not a
94
+ standing instruction. Write it into the project documentation (\`wiki/\` or
95
+ \`docs/\`).
96
+
97
+ If a human explicitly asked for this edit, they can re-run with
98
+ \`LISA_ALLOW_INSTRUCTION_FILE_WRITE=1\` set, which bypasses this guard.
99
+ EOF
100
+ exit 2
101
+ }
102
+
103
+ case "$tool_name" in
104
+ Write | Edit | MultiEdit | NotebookEdit | Update)
105
+ # `file_path` covers Write/Edit; `edits[].file_path` covers the MultiEdit
106
+ # shapes that carry a path per edit; `notebook_path` covers NotebookEdit.
107
+ paths="$(printf '%s' "$input" | jq -r '
108
+ [ .tool_input.file_path?,
109
+ .tool_input.path?,
110
+ .tool_input.notebook_path?,
111
+ (.tool_input.edits? // [] | .[].file_path?)
112
+ ] | map(select(. != null and . != "")) | .[]' 2>/dev/null || true)"
113
+ while IFS= read -r candidate; do
114
+ [ -n "$candidate" ] || continue
115
+ if is_instruction_file "$candidate"; then
116
+ refuse "$candidate"
117
+ fi
118
+ done <<EOF
119
+ $paths
120
+ EOF
121
+ ;;
122
+ Bash)
123
+ command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
124
+ [ -n "$command_str" ] || exit 0
125
+ # Fast path: no instruction filename mentioned means nothing to classify.
126
+ if ! printf '%s' "$command_str" |
127
+ grep -Eqi '(agents|claude|copilot-instructions)\.md'; then
128
+ exit 0
129
+ fi
130
+ # Write signatures only. A bare mention (`cat AGENTS.md`, `rg x AGENTS.md`)
131
+ # is a read and must stay allowed — the filename has to appear as the target
132
+ # of a redirection, a tee, or an in-place sed.
133
+ write_target='[^ |;&]*(agents|claude|copilot-instructions)\.md'
134
+ if printf '%s' "$command_str" |
135
+ grep -Eqi ">>?[[:space:]]*['\"]?$write_target|tee([[:space:]]+-[a-z]+)*[[:space:]]+['\"]?$write_target|sed[[:space:]]+[^|;&]*-i[^|;&]*$write_target"; then
136
+ refuse "$(printf '%s' "$command_str" |
137
+ grep -Eoi "$write_target" | head -1)"
138
+ fi
139
+ ;;
140
+ esac
141
+
142
+ exit 0
@@ -29,6 +29,14 @@
29
29
  "command": "${CURSOR_PLUGIN_ROOT}/hooks/block-shell-json-parsing.sh",
30
30
  "matcher": "Bash"
31
31
  },
32
+ {
33
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/block-instruction-file-edits.sh",
34
+ "matcher": "Bash"
35
+ },
36
+ {
37
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/block-instruction-file-edits.sh",
38
+ "matcher": "Write|Edit|MultiEdit"
39
+ },
32
40
  {
33
41
  "command": "${CURSOR_PLUGIN_ROOT}/hooks/enforce-verification-gate.sh"
34
42
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-expo",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Expo and 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-expo",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-expo",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-expo",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-harper-fabric",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.329.0",
3
+ "version": "2.330.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-nestjs",
3
- "version": "2.329.0",
3
+ "version": "2.330.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"