@codyswann/lisa 4.64.2 → 4.64.4

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 (74) hide show
  1. package/all/create-only/.github/workflows/continuous-gates.yml +42 -2
  2. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  3. package/dist/core/lisa-owned-hash-ledger.js +24 -0
  4. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  5. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  6. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  7. package/dist/core/upstream-evidence-manifest.js +12 -10
  8. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  9. package/expo/create-only/stryker.conf.json +1 -0
  10. package/package.json +5 -5
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa/hooks/threshold-ratchet-compare.mjs +50 -8
  14. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +24 -0
  15. package/plugins/lisa-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/hooks/threshold-ratchet-compare.mjs +50 -8
  23. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +24 -0
  24. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cursor/hooks/threshold-ratchet-compare.mjs +50 -8
  26. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +24 -0
  27. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-agy/plugin.json +1 -1
  30. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  50. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails-agy/plugin.json +1 -1
  55. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  60. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  65. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/src/base/hooks/threshold-ratchet-compare.mjs +50 -8
  68. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +24 -0
  69. package/rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs +50 -8
  70. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
  71. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +297 -25
  72. package/typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs +50 -8
  73. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
  74. package/typescript/create-only/stryker.conf.json +1 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
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-rails",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
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-rails",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
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-rails",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
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": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, 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": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.64.2",
3
+ "version": "4.64.4",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -18,6 +18,7 @@ import {
18
18
  extractStrykerConstraints,
19
19
  extractStrykerMutate,
20
20
  familyFor,
21
+ globRoot,
21
22
  parseJson,
22
23
  } from "./threshold-ratchet-families.mjs";
23
24
 
@@ -154,15 +155,56 @@ function compareStryker(relPath, base, current) {
154
155
  );
155
156
  const baseMutate = extractStrykerMutate(base);
156
157
  const currentMutate = extractStrykerMutate(current);
158
+ // Roots the gate already covered, and roots this change adds. An exclusion
159
+ // scoped to a newly added root narrows territory that was not being mutated
160
+ // a moment ago, so it cannot shrink the gate — the change is a net widening.
161
+ //
162
+ // Without this, repairing an INERT `mutate` list is unreachable without a
163
+ // human-approved ratchet exception: the repair necessarily adds source roots
164
+ // AND the test-file exclusions that belong with them, and every one of those
165
+ // exclusions read as a weakening. Measured on two repositories whose real
166
+ // effect was 0 -> 69 and 0 -> 75 files mutated (CodySwannGT/lisa#4243).
167
+ const baseRoots = [...baseMutate.positives].map(globRoot);
168
+ /**
169
+ * Whether an exclusion might touch any baseline positive.
170
+ *
171
+ * Both containment directions matter: excluding `src` removes a baseline
172
+ * rooted at `src/existing`, too. An empty prefix (wildcards, braces, classes
173
+ * or extglobs at the root) might cover everything. We grant an exemption
174
+ * only for provably disjoint territory, never from a guessed glob overlap.
175
+ * @param {string} root A rooted glob prefix
176
+ * @returns {boolean} True when overlap cannot be ruled out
177
+ */
178
+ const overlapsBase = root =>
179
+ baseRoots.some(
180
+ base =>
181
+ root === "" ||
182
+ base === "" ||
183
+ root === base ||
184
+ root.startsWith(`${base}/`) ||
185
+ base.startsWith(`${root}/`)
186
+ );
187
+ const addedRoots = [...currentMutate.positives]
188
+ .filter(positive => !baseMutate.positives.has(positive))
189
+ .map(globRoot)
190
+ .filter(root => root !== "");
191
+ /**
192
+ * Whether a root lies inside territory THIS change is adding.
193
+ * @param {string} root A rooted glob prefix
194
+ * @returns {boolean} True when an added root equals or contains it
195
+ */
196
+ const underAdded = root =>
197
+ addedRoots.some(added => root === added || root.startsWith(`${added}/`));
157
198
  for (const negation of currentMutate.negations) {
158
- if (!baseMutate.negations.has(negation)) {
159
- findings.push({
160
- file: relPath,
161
- key: `mutate ${negation}`,
162
- type: TYPE_EXEMPTION_ADDED,
163
- message: `${relPath}: new mutation-testing exclusion "${negation}" — excluding files from a gate is a weakening.`,
164
- });
165
- }
199
+ if (baseMutate.negations.has(negation)) continue;
200
+ const root = globRoot(negation);
201
+ if (!overlapsBase(root) && underAdded(root)) continue;
202
+ findings.push({
203
+ file: relPath,
204
+ key: `mutate ${negation}`,
205
+ type: TYPE_EXEMPTION_ADDED,
206
+ message: `${relPath}: new mutation-testing exclusion "${negation}" — excluding files from a gate is a weakening.`,
207
+ });
166
208
  }
167
209
  for (const positive of baseMutate.positives) {
168
210
  if (!currentMutate.positives.has(positive)) {
@@ -308,6 +308,30 @@ export function extractStrykerMutate(conf) {
308
308
  return { positives, negations };
309
309
  }
310
310
 
311
+ /**
312
+ * The literal directory prefix a glob is rooted at, before any wildcard.
313
+ *
314
+ * `lib/**\/*.spec.ts` and `!lib/**\/*.d.ts` are both rooted at `lib`. This is
315
+ * what lets the ratchet tell territory a change ADDED from territory it
316
+ * already covered: an exclusion inside a newly added root cannot shrink a
317
+ * gate, because nothing there was being mutated a moment ago.
318
+ * @param {string} glob A stryker mutate entry, with or without a leading `!`
319
+ * @returns {string} The rooted prefix, or "" when the glob starts with a wildcard
320
+ */
321
+ export function globRoot(glob) {
322
+ const bare = glob.startsWith("!") ? glob.slice(1) : glob;
323
+ // Backslash escapes and parent traversal make textual containment unsafe.
324
+ // An unknown prefix overlaps everything rather than granting an exemption.
325
+ if (bare.includes("\\") || bare.split("/").includes("..")) return "";
326
+ const rooted = [];
327
+ for (const segment of bare.split("/")) {
328
+ if (segment === "." || segment === "") continue;
329
+ if (["*", "?", "{", "[", "("].some(meta => segment.includes(meta))) break;
330
+ rooted.push(segment);
331
+ }
332
+ return rooted.join("/");
333
+ }
334
+
311
335
  /**
312
336
  * Parse one k6 threshold expression (`p(95)<1000`, `rate>=0.99`, …) into a
313
337
  * ratchet constraint. Upper bounds (`<`, `<=`) may only decrease; lower
@@ -21,6 +21,7 @@ import {
21
21
  extractStrykerConstraints,
22
22
  extractStrykerMutate,
23
23
  familyFor,
24
+ globRoot,
24
25
  parseJson,
25
26
  } from "./threshold-ratchet-families.mjs";
26
27
 
@@ -157,15 +158,56 @@ function compareStryker(relPath, base, current) {
157
158
  );
158
159
  const baseMutate = extractStrykerMutate(base);
159
160
  const currentMutate = extractStrykerMutate(current);
161
+ // Roots the gate already covered, and roots this change adds. An exclusion
162
+ // scoped to a newly added root narrows territory that was not being mutated
163
+ // a moment ago, so it cannot shrink the gate — the change is a net widening.
164
+ //
165
+ // Without this, repairing an INERT `mutate` list is unreachable without a
166
+ // human-approved ratchet exception: the repair necessarily adds source roots
167
+ // AND the test-file exclusions that belong with them, and every one of those
168
+ // exclusions read as a weakening. Measured on two repositories whose real
169
+ // effect was 0 -> 69 and 0 -> 75 files mutated (CodySwannGT/lisa#4243).
170
+ const baseRoots = [...baseMutate.positives].map(globRoot);
171
+ /**
172
+ * Whether an exclusion might touch any baseline positive.
173
+ *
174
+ * Both containment directions matter: excluding `src` removes a baseline
175
+ * rooted at `src/existing`, too. An empty prefix (wildcards, braces, classes
176
+ * or extglobs at the root) might cover everything. We grant an exemption
177
+ * only for provably disjoint territory, never from a guessed glob overlap.
178
+ * @param {string} root A rooted glob prefix
179
+ * @returns {boolean} True when overlap cannot be ruled out
180
+ */
181
+ const overlapsBase = root =>
182
+ baseRoots.some(
183
+ base =>
184
+ root === "" ||
185
+ base === "" ||
186
+ root === base ||
187
+ root.startsWith(`${base}/`) ||
188
+ base.startsWith(`${root}/`)
189
+ );
190
+ const addedRoots = [...currentMutate.positives]
191
+ .filter(positive => !baseMutate.positives.has(positive))
192
+ .map(globRoot)
193
+ .filter(root => root !== "");
194
+ /**
195
+ * Whether a root lies inside territory THIS change is adding.
196
+ * @param {string} root A rooted glob prefix
197
+ * @returns {boolean} True when an added root equals or contains it
198
+ */
199
+ const underAdded = root =>
200
+ addedRoots.some(added => root === added || root.startsWith(`${added}/`));
160
201
  for (const negation of currentMutate.negations) {
161
- if (!baseMutate.negations.has(negation)) {
162
- findings.push({
163
- file: relPath,
164
- key: `mutate ${negation}`,
165
- type: TYPE_EXEMPTION_ADDED,
166
- message: `${relPath}: new mutation-testing exclusion "${negation}" — excluding files from a gate is a weakening.`,
167
- });
168
- }
202
+ if (baseMutate.negations.has(negation)) continue;
203
+ const root = globRoot(negation);
204
+ if (!overlapsBase(root) && underAdded(root)) continue;
205
+ findings.push({
206
+ file: relPath,
207
+ key: `mutate ${negation}`,
208
+ type: TYPE_EXEMPTION_ADDED,
209
+ message: `${relPath}: new mutation-testing exclusion "${negation}" — excluding files from a gate is a weakening.`,
210
+ });
169
211
  }
170
212
  for (const positive of baseMutate.positives) {
171
213
  if (!currentMutate.positives.has(positive)) {
@@ -311,6 +311,30 @@ export function extractStrykerMutate(conf) {
311
311
  return { positives, negations };
312
312
  }
313
313
 
314
+ /**
315
+ * The literal directory prefix a glob is rooted at, before any wildcard.
316
+ *
317
+ * `lib/**\/*.spec.ts` and `!lib/**\/*.d.ts` are both rooted at `lib`. This is
318
+ * what lets the ratchet tell territory a change ADDED from territory it
319
+ * already covered: an exclusion inside a newly added root cannot shrink a
320
+ * gate, because nothing there was being mutated a moment ago.
321
+ * @param {string} glob A stryker mutate entry, with or without a leading `!`
322
+ * @returns {string} The rooted prefix, or "" when the glob starts with a wildcard
323
+ */
324
+ export function globRoot(glob) {
325
+ const bare = glob.startsWith("!") ? glob.slice(1) : glob;
326
+ // Backslash escapes and parent traversal make textual containment unsafe.
327
+ // An unknown prefix overlaps everything rather than granting an exemption.
328
+ if (bare.includes("\\") || bare.split("/").includes("..")) return "";
329
+ const rooted = [];
330
+ for (const segment of bare.split("/")) {
331
+ if (segment === "." || segment === "") continue;
332
+ if (["*", "?", "{", "[", "("].some(meta => segment.includes(meta))) break;
333
+ rooted.push(segment);
334
+ }
335
+ return rooted.join("/");
336
+ }
337
+
314
338
  /**
315
339
  * Parse one k6 threshold expression (`p(95)<1000`, `rate>=0.99`, …) into a
316
340
  * ratchet constraint. Upper bounds (`<`, `<=`) may only decrease; lower