@codyswann/lisa 2.145.2 → 2.146.1

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 (73) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa-agy/plugin.json +1 -1
  5. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  8. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  9. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa-expo-agy/plugin.json +1 -1
  15. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  20. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  25. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  30. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-rails-agy/plugin.json +1 -1
  35. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  40. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-wiki/scripts/_wiki-lib.mjs +19 -0
  45. package/plugins/lisa-wiki/scripts/ingest-git.mjs +10 -9
  46. package/plugins/lisa-wiki/scripts/ingest-memory.mjs +14 -9
  47. package/plugins/lisa-wiki/scripts/ingest-roles.mjs +13 -3
  48. package/plugins/lisa-wiki/scripts/ingest_slack_channel.py +39 -7
  49. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  50. package/plugins/lisa-wiki-agy/scripts/_wiki-lib.mjs +19 -0
  51. package/plugins/lisa-wiki-agy/scripts/ingest-git.mjs +10 -9
  52. package/plugins/lisa-wiki-agy/scripts/ingest-memory.mjs +14 -9
  53. package/plugins/lisa-wiki-agy/scripts/ingest-roles.mjs +13 -3
  54. package/plugins/lisa-wiki-agy/scripts/ingest_slack_channel.py +39 -7
  55. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-wiki-copilot/scripts/_wiki-lib.mjs +19 -0
  57. package/plugins/lisa-wiki-copilot/scripts/ingest-git.mjs +10 -9
  58. package/plugins/lisa-wiki-copilot/scripts/ingest-memory.mjs +14 -9
  59. package/plugins/lisa-wiki-copilot/scripts/ingest-roles.mjs +13 -3
  60. package/plugins/lisa-wiki-copilot/scripts/ingest_slack_channel.py +39 -7
  61. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki-cursor/scripts/_wiki-lib.mjs +19 -0
  63. package/plugins/lisa-wiki-cursor/scripts/ingest-git.mjs +10 -9
  64. package/plugins/lisa-wiki-cursor/scripts/ingest-memory.mjs +14 -9
  65. package/plugins/lisa-wiki-cursor/scripts/ingest-roles.mjs +13 -3
  66. package/plugins/lisa-wiki-cursor/scripts/ingest_slack_channel.py +39 -7
  67. package/plugins/src/wiki/scripts/_wiki-lib.mjs +19 -0
  68. package/plugins/src/wiki/scripts/ingest-git.mjs +10 -9
  69. package/plugins/src/wiki/scripts/ingest-memory.mjs +14 -9
  70. package/plugins/src/wiki/scripts/ingest-roles.mjs +13 -3
  71. package/plugins/src/wiki/scripts/ingest_slack_channel.py +39 -7
  72. package/scripts/install-claude-plugins.sh +31 -0
  73. package/all/copy-overwrite/.safety-net.json +0 -25
@@ -22,10 +22,37 @@ from typing import Any
22
22
 
23
23
 
24
24
  TOKEN_PATTERNS = [
25
- re.compile(r"xox[pbar]-[A-Za-z0-9-]+"),
26
- re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{20,}"),
27
- re.compile(r"AKIA[0-9A-Z]{16}"),
28
- re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.S),
25
+ (re.compile(r"xox[pbar]-[A-Za-z0-9-]+"), "[REDACTED:OAUTH_TOKEN]"),
26
+ (
27
+ re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{20,}"),
28
+ "[REDACTED:OAUTH_TOKEN]",
29
+ ),
30
+ (re.compile(r"AKIA[0-9A-Z]{16}"), "[REDACTED:API_KEY]"),
31
+ (
32
+ re.compile(
33
+ r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----",
34
+ re.S,
35
+ ),
36
+ "[REDACTED:PRIVATE_KEY]",
37
+ ),
38
+ (
39
+ re.compile(r"\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b"),
40
+ "[REDACTED:SSN]",
41
+ ),
42
+ (
43
+ re.compile(
44
+ r"\b(?:password|passwd|pwd)\s*[:=]\s*(['\"]?)([^\s'\",;]{8,})\1",
45
+ re.I,
46
+ ),
47
+ "[REDACTED:PASSWORD]",
48
+ ),
49
+ (
50
+ re.compile(
51
+ r"\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret)\s*[:=]\s*(['\"]?)([A-Za-z0-9._-]{20,})\1",
52
+ re.I,
53
+ ),
54
+ "[REDACTED:API_KEY]",
55
+ ),
29
56
  ]
30
57
 
31
58
 
@@ -49,8 +76,13 @@ def ts_from_input(value: str | None) -> str | None:
49
76
 
50
77
  def redact(text: str) -> str:
51
78
  out = text
52
- for pattern in TOKEN_PATTERNS:
53
- out = pattern.sub("[REDACTED]", out)
79
+ for pattern, replacement in TOKEN_PATTERNS:
80
+ out = pattern.sub(
81
+ lambda match: match.group(0).replace(match.group(2), replacement)
82
+ if len(match.groups()) >= 2
83
+ else replacement,
84
+ out,
85
+ )
54
86
  return out
55
87
 
56
88
 
@@ -289,7 +321,7 @@ def main() -> int:
289
321
  for reply in replies:
290
322
  lines.extend(render_message(reply))
291
323
 
292
- source_path.write_text("\n".join(lines), encoding="utf-8")
324
+ source_path.write_text(redact("\n".join(lines)), encoding="utf-8")
293
325
 
294
326
  latest_ts = previous_state.get("latest_message_ts")
295
327
  if messages:
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
+ import { sanitizeWikiSourceText } from "./wiki-safety.mjs";
8
9
 
9
10
  /** Read and parse a JSON file, or return undefined if missing/invalid. */
10
11
  export function readJsonSafe(file) {
@@ -59,6 +60,24 @@ export function walkFiles(dir, { ext } = {}) {
59
60
  return out.sort();
60
61
  }
61
62
 
63
+ /**
64
+ * Sanitize a wiki source note immediately before it is persisted.
65
+ *
66
+ * Connectors should keep fetched raw material in memory or temporary locations
67
+ * outside the repo, render the source note, then call this helper at the final
68
+ * `wiki/sources/**` write boundary. The return value contains safe finding
69
+ * metadata only; callers can include it in handoff metadata when useful.
70
+ */
71
+ export function writeSanitizedSourceNote(file, rawText, sourceMetadata = {}) {
72
+ const result = sanitizeWikiSourceText(rawText, {
73
+ ...sourceMetadata,
74
+ path: sourceMetadata.path ?? file,
75
+ });
76
+ fs.mkdirSync(path.dirname(file), { recursive: true });
77
+ fs.writeFileSync(file, result.text);
78
+ return result;
79
+ }
80
+
62
81
  /**
63
82
  * Minimal frontmatter detector. Returns whether a leading `--- ... ---` block
64
83
  * exists and the top-level keys it declares (enough to check required fields;
@@ -15,7 +15,7 @@
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  import { execFileSync } from "node:child_process";
18
- import { readJsonSafe, SECRET_PATTERNS } from "./_wiki-lib.mjs";
18
+ import { readJsonSafe, writeSanitizedSourceNote } from "./_wiki-lib.mjs";
19
19
 
20
20
  const argv = process.argv.slice(2);
21
21
  const opt = (n, d) => {
@@ -53,12 +53,6 @@ const commitExists = c => {
53
53
  return false;
54
54
  }
55
55
  };
56
- const redact = t =>
57
- SECRET_PATTERNS.reduce(
58
- (acc, { re }) => acc.replace(new RegExp(re, "g"), "[REDACTED]"),
59
- t
60
- );
61
-
62
56
  if (
63
57
  !fs.existsSync(path.join(repo, ".git")) &&
64
58
  !tryGit(["rev-parse", "--is-inside-work-tree"])
@@ -165,8 +159,11 @@ ${
165
159
  }
166
160
  `;
167
161
 
168
- fs.mkdirSync(sourceDir, { recursive: true });
169
- fs.writeFileSync(notePath, redact(note));
162
+ const safety = writeSanitizedSourceNote(notePath, note, {
163
+ sourceId: path.relative(process.cwd(), notePath),
164
+ sourceSystem: "git",
165
+ project: slug,
166
+ });
170
167
 
171
168
  const meta = {
172
169
  connector: "git",
@@ -174,6 +171,10 @@ const meta = {
174
171
  ranAt: new Date().toISOString(),
175
172
  proposedCursor: { lastCommit: head, lastPr },
176
173
  sourceNotes: [path.relative(process.cwd(), notePath)],
174
+ safety: {
175
+ reviewRequired: safety.reviewRequired,
176
+ findings: safety.findings,
177
+ },
177
178
  };
178
179
  if (emitMeta) {
179
180
  fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
@@ -15,7 +15,11 @@
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  import os from "node:os";
18
- import { loadConfig, walkFiles, SECRET_PATTERNS } from "./_wiki-lib.mjs";
18
+ import {
19
+ loadConfig,
20
+ walkFiles,
21
+ writeSanitizedSourceNote,
22
+ } from "./_wiki-lib.mjs";
19
23
 
20
24
  const argv = process.argv.slice(2);
21
25
  const opt = (n, d) => {
@@ -79,15 +83,10 @@ if (!(under(claudeMem) || under(projectCodexMem) || allowedRoots.some(under))) {
79
83
  );
80
84
  }
81
85
 
82
- const redact = t =>
83
- SECRET_PATTERNS.reduce(
84
- (acc, { re }) => acc.replace(new RegExp(re, "g"), "[REDACTED]"),
85
- t
86
- );
87
86
  const mdFiles = walkFiles(resolvedMem, { ext: ".md" });
88
87
  const date = new Date().toISOString().slice(0, 10);
89
88
  const entries = mdFiles.map(f => {
90
- const body = redact(fs.readFileSync(f, "utf8")).trim();
89
+ const body = fs.readFileSync(f, "utf8").trim();
91
90
  return `### ${path.basename(f)}\n\n${body}`;
92
91
  });
93
92
 
@@ -110,8 +109,10 @@ sensitivity: internal
110
109
  ${entries.join("\n\n") || "_(no memory files)_"}
111
110
  `;
112
111
 
113
- fs.mkdirSync(sourceDir, { recursive: true });
114
- fs.writeFileSync(notePath, note);
112
+ const safety = writeSanitizedSourceNote(notePath, note, {
113
+ sourceId: path.relative(process.cwd(), notePath),
114
+ sourceSystem: "memory",
115
+ });
115
116
 
116
117
  const meta = {
117
118
  connector: "memory",
@@ -119,6 +120,10 @@ const meta = {
119
120
  ranAt: new Date().toISOString(),
120
121
  proposedCursor: { files: mdFiles.length, lastIngest: date },
121
122
  sourceNotes: [path.relative(process.cwd(), notePath)],
123
+ safety: {
124
+ reviewRequired: safety.reviewRequired,
125
+ findings: safety.findings,
126
+ },
122
127
  };
123
128
  if (emitMeta) {
124
129
  fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
@@ -10,7 +10,11 @@
10
10
  */
11
11
  import fs from "node:fs";
12
12
  import path from "node:path";
13
- import { loadConfig, walkFiles } from "./_wiki-lib.mjs";
13
+ import {
14
+ loadConfig,
15
+ walkFiles,
16
+ writeSanitizedSourceNote,
17
+ } from "./_wiki-lib.mjs";
14
18
 
15
19
  const argv = process.argv.slice(2);
16
20
  const opt = (n, d) => {
@@ -61,8 +65,10 @@ ${rosterRows}
61
65
  ${staffPages.length ? staffPages.map(p => `- \`${path.relative(wikiRoot, p)}\``).join("\n") : "_(none)_"}
62
66
  `;
63
67
 
64
- fs.mkdirSync(sourceDir, { recursive: true });
65
- fs.writeFileSync(notePath, note);
68
+ const safety = writeSanitizedSourceNote(notePath, note, {
69
+ sourceId: path.relative(process.cwd(), notePath),
70
+ sourceSystem: "roles",
71
+ });
66
72
 
67
73
  const meta = {
68
74
  connector: "roles",
@@ -74,6 +80,10 @@ const meta = {
74
80
  lastIngest: date,
75
81
  },
76
82
  sourceNotes: [path.relative(process.cwd(), notePath)],
83
+ safety: {
84
+ reviewRequired: safety.reviewRequired,
85
+ findings: safety.findings,
86
+ },
77
87
  };
78
88
  if (emitMeta) {
79
89
  fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
@@ -22,10 +22,37 @@ from typing import Any
22
22
 
23
23
 
24
24
  TOKEN_PATTERNS = [
25
- re.compile(r"xox[pbar]-[A-Za-z0-9-]+"),
26
- re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{20,}"),
27
- re.compile(r"AKIA[0-9A-Z]{16}"),
28
- re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.S),
25
+ (re.compile(r"xox[pbar]-[A-Za-z0-9-]+"), "[REDACTED:OAUTH_TOKEN]"),
26
+ (
27
+ re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{20,}"),
28
+ "[REDACTED:OAUTH_TOKEN]",
29
+ ),
30
+ (re.compile(r"AKIA[0-9A-Z]{16}"), "[REDACTED:API_KEY]"),
31
+ (
32
+ re.compile(
33
+ r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----",
34
+ re.S,
35
+ ),
36
+ "[REDACTED:PRIVATE_KEY]",
37
+ ),
38
+ (
39
+ re.compile(r"\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b"),
40
+ "[REDACTED:SSN]",
41
+ ),
42
+ (
43
+ re.compile(
44
+ r"\b(?:password|passwd|pwd)\s*[:=]\s*(['\"]?)([^\s'\",;]{8,})\1",
45
+ re.I,
46
+ ),
47
+ "[REDACTED:PASSWORD]",
48
+ ),
49
+ (
50
+ re.compile(
51
+ r"\b(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret)\s*[:=]\s*(['\"]?)([A-Za-z0-9._-]{20,})\1",
52
+ re.I,
53
+ ),
54
+ "[REDACTED:API_KEY]",
55
+ ),
29
56
  ]
30
57
 
31
58
 
@@ -49,8 +76,13 @@ def ts_from_input(value: str | None) -> str | None:
49
76
 
50
77
  def redact(text: str) -> str:
51
78
  out = text
52
- for pattern in TOKEN_PATTERNS:
53
- out = pattern.sub("[REDACTED]", out)
79
+ for pattern, replacement in TOKEN_PATTERNS:
80
+ out = pattern.sub(
81
+ lambda match: match.group(0).replace(match.group(2), replacement)
82
+ if len(match.groups()) >= 2
83
+ else replacement,
84
+ out,
85
+ )
54
86
  return out
55
87
 
56
88
 
@@ -289,7 +321,7 @@ def main() -> int:
289
321
  for reply in replies:
290
322
  lines.extend(render_message(reply))
291
323
 
292
- source_path.write_text("\n".join(lines), encoding="utf-8")
324
+ source_path.write_text(redact("\n".join(lines)), encoding="utf-8")
293
325
 
294
326
  latest_ts = previous_state.get("latest_message_ts")
295
327
  if messages:
@@ -112,6 +112,37 @@ if changed:
112
112
  PYEOF
113
113
  fi
114
114
 
115
+ # Remove the legacy cc-safety-net inline rules file (self-heal existing projects).
116
+ #
117
+ # Lisa historically shipped a project-root `.safety-net.json` (the cc-safety-net
118
+ # <=0.9.0 inline-rules format) via all/copy-overwrite/. cc-safety-net 1.0.1
119
+ # dropped that format entirely: its PreToolUse Bash guard now treats a
120
+ # project-level `.safety-net.json` as a "legacy rules config location" and FAILS
121
+ # CLOSED — denying EVERY Bash command (even `echo`/`ls`) with "legacy rules
122
+ # config location is no longer used; ask the user to run `npx -y cc-safety-net
123
+ # rule migrate`" — while `rule migrate` cannot convert it (it only looks for a
124
+ # global ~/.cc-safety-net/config.json). The result bricks the agent, and on an
125
+ # unattended/scheduled run there is no human to intervene.
126
+ #
127
+ # 1.0.1 runs fine on its built-in rules with no config file, and Lisa's own
128
+ # block-no-verify.sh + parity-safety-net.sh hooks already enforce --no-verify and
129
+ # destructive-command guards across every agent, so the file is now dead weight.
130
+ # Lisa no longer ships it (removed from all/copy-overwrite/), but copy-overwrite
131
+ # never deletes, so already-provisioned projects keep a stale copy. Remove it
132
+ # here — but ONLY the Lisa-shipped file (identified by its marker rule name), so a
133
+ # project's own hand-authored `.safety-net.json` is never touched.
134
+ LEGACY_SAFETY_NET="$PROJECT_ROOT/.safety-net.json"
135
+ if [ -f "$LEGACY_SAFETY_NET" ] && command -v jq >/dev/null 2>&1; then
136
+ if jq -e '
137
+ (.rules | type == "array")
138
+ and ([.rules[]?.name] | index("block-git-commit-no-verify") != null)
139
+ and ([.rules[]?.name] | index("block-git-push-no-verify") != null)
140
+ ' "$LEGACY_SAFETY_NET" >/dev/null 2>&1; then
141
+ rm -f "$LEGACY_SAFETY_NET" \
142
+ && echo "Removed legacy .safety-net.json (incompatible with cc-safety-net >=1.0.0; using built-in + Lisa-native guards)."
143
+ fi
144
+ fi
145
+
115
146
  # Install plugins only when claude CLI is available
116
147
  if ! command -v claude &>/dev/null; then exit 0; fi
117
148
 
@@ -1,25 +0,0 @@
1
- {
2
- "version": 1,
3
- "rules": [
4
- {
5
- "name": "block-git-commit-no-verify",
6
- "command": "git",
7
- "subcommand": "commit",
8
- "block_args": ["--no-verify", "-n"],
9
- "reason": "--no-verify is not allowed. Fix the commit to pass all checks."
10
- },
11
- {
12
- "name": "block-git-stash",
13
- "command": "git",
14
- "subcommand": "stash",
15
- "reason": "Stashing changes is not allowed. Please commit or discard your changes before stashing. If a commit hook is preventing the commit, either fix whatever is preventing the commit or fail out and let the human know why."
16
- },
17
- {
18
- "name": "block-git-push-no-verify",
19
- "command": "git",
20
- "subcommand": "push",
21
- "block_args": ["--no-verify"],
22
- "reason": "--no-verify is not allowed. Fix the push to pass all checks."
23
- }
24
- ]
25
- }