@codyswann/lisa 3.47.4 → 3.47.6

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 (86) hide show
  1. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +270 -0
  2. package/all/copy-overwrite/scripts/lisa-gates.mjs +30 -0
  3. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +294 -34
  4. package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +126 -32
  5. package/all/copy-overwrite/scripts/lisa-work-item.mjs +83 -26
  6. package/dist/configs/eslint/expo.d.ts +50 -1
  7. package/dist/configs/eslint/expo.d.ts.map +1 -1
  8. package/dist/configs/eslint/expo.js +86 -5
  9. package/dist/configs/eslint/expo.js.map +1 -1
  10. package/dist/core/downstream-names.d.ts +83 -0
  11. package/dist/core/downstream-names.d.ts.map +1 -0
  12. package/dist/core/downstream-names.js +289 -0
  13. package/dist/core/downstream-names.js.map +1 -0
  14. package/dist/core/downstream-references.d.ts +21 -6
  15. package/dist/core/downstream-references.d.ts.map +1 -1
  16. package/dist/core/downstream-references.js +19 -16
  17. package/dist/core/downstream-references.js.map +1 -1
  18. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  19. package/dist/core/lisa-owned-hash-ledger.js +16 -0
  20. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  21. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  22. package/dist/core/upstream-evidence-manifest.js +23 -10
  23. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  24. package/dist/migrations/ensure-tsconfig-local-includes.d.ts +1 -1
  25. package/dist/migrations/ensure-tsconfig-local-includes.js +1 -1
  26. package/expo/copy-overwrite/eslint.config.ts +9 -0
  27. package/expo/copy-overwrite/eslint.expo.ts +92 -4
  28. package/package.json +5 -2
  29. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa/rules/reference/design-value-binding.md +4 -0
  32. package/plugins/lisa-agy/plugin.json +1 -1
  33. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  36. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-copilot/rules/reference/design-value-binding.md +4 -0
  40. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-cursor/rules/design-value-binding-reference.mdc +4 -0
  42. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-expo-agy/plugin.json +1 -1
  45. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  55. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  65. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails-agy/plugin.json +1 -1
  70. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  75. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  80. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/src/base/rules/reference/design-value-binding.md +4 -0
  83. package/scripts/lisa-mutation.mjs +13 -0
  84. package/typescript/copy-contents/.husky/pre-push +7 -1
  85. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +496 -122
  86. package/typescript/package-lisa/package.lisa.json +1 -0
@@ -9,7 +9,7 @@ import type { Migration, MigrationContext, MigrationResult } from "./migration.i
9
9
  *
10
10
  * Additionally, this migration preserves a project's narrower include/exclude from
11
11
  * the pre-strategy tsconfig.json when the stack template's include/exclude would
12
- * widen tsc scope. Evidence: thumbwar-backend's tsconfig.json pinned `include: ["src/**\/*"]`
12
+ * widen tsc scope. Evidence: acmeorgd-backend's tsconfig.json pinned `include: ["src/**\/*"]`
13
13
  * but after Lisa overwrote tsconfig.json the stack template's include was used,
14
14
  * pulling `vitest.*.ts` into typecheck scope.
15
15
  */
@@ -61,7 +61,7 @@ async function loadTemplateDefaults(lisaDir, type) {
61
61
  *
62
62
  * Additionally, this migration preserves a project's narrower include/exclude from
63
63
  * the pre-strategy tsconfig.json when the stack template's include/exclude would
64
- * widen tsc scope. Evidence: thumbwar-backend's tsconfig.json pinned `include: ["src/**\/*"]`
64
+ * widen tsc scope. Evidence: acmeorgd-backend's tsconfig.json pinned `include: ["src/**\/*"]`
65
65
  * but after Lisa overwrote tsconfig.json the stack template's include was used,
66
66
  * pulling `vitest.*.ts` into typecheck scope.
67
67
  */
@@ -24,6 +24,7 @@ import {
24
24
  defaultIgnores,
25
25
  defaultThresholds,
26
26
  getExpoConfig,
27
+ readTypedAxes,
27
28
  } from "@codyswann/lisa/eslint/expo";
28
29
 
29
30
  import ignoreConfig from "./eslint.ignore.config.json" with { type: "json" };
@@ -47,6 +48,13 @@ const localIgnores: string[] = existsSync(
47
48
  ? (require("./eslint.ignore.config.local.json").ignores ?? [])
48
49
  : [];
49
50
 
51
+ // Arm ui-standards/no-unbound-design-value from `design.tokens.axes` in
52
+ // .lisa.config.json. Declaring an axis is what arms the rule; declaring none
53
+ // leaves it silent. Read here, at config load, exactly like
54
+ // eslint.thresholds.json and eslint.ignore.config.json above — so there is no
55
+ // generation step and nothing that can go stale against the config (#2807).
56
+ const typedAxes = readTypedAxes(__dirname);
57
+
50
58
  // Type only the managed factory result, then trust project-owned additions at
51
59
  // their spread boundary. The double assertion is intentional: one custom-plugin
52
60
  // entry can be structurally different enough for a direct cast to raise TS2352,
@@ -66,6 +74,7 @@ const config: ReturnType<typeof getExpoConfig> = [
66
74
  ],
67
75
  thresholds: { ...defaultThresholds, ...thresholdsConfig },
68
76
  sourceRoot,
77
+ typedAxes,
69
78
  }),
70
79
  ...(localConfig as unknown as ReturnType<typeof getExpoConfig>),
71
80
  ];
@@ -24,7 +24,9 @@ import reactCompiler from "eslint-plugin-react-compiler";
24
24
  // @ts-expect-error -- eslint-plugin-react-perf lacks type declarations
25
25
  import reactPerf from "eslint-plugin-react-perf";
26
26
  import tailwind from "eslint-plugin-tailwindcss";
27
+ import { readFileSync } from "fs";
27
28
  import { createRequire } from "module";
29
+ import path from "path";
28
30
 
29
31
  // Import TypeScript config and utilities from the Lisa package
30
32
  // (eslint.typescript.ts is no longer deployed to downstream projects)
@@ -57,22 +59,105 @@ const componentStructure = require("@codyswann/eslint-plugin-component-structure
57
59
  const uiStandards = require("@codyswann/eslint-plugin-ui-standards");
58
60
  const expoConfig = require("eslint-config-expo/flat");
59
61
 
62
+ /**
63
+ * A project's `.lisa.config.json`, when it has one.
64
+ *
65
+ * Absence is not an error: a project that has never run `lisa apply` has no
66
+ * such file and declares no axes.
67
+ * @param file - Absolute path to the candidate `.lisa.config.json`.
68
+ * @returns Its text, or undefined when the project has no such file.
69
+ */
70
+ function readConfigText(file: string): string | undefined {
71
+ try {
72
+ return readFileSync(file, "utf8");
73
+ } catch {
74
+ return undefined;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Axes a project declares typed, read from its own `.lisa.config.json`.
80
+ *
81
+ * #2807: the design-value rule shipped installed, severity `"off"`, and arming
82
+ * it needed a severity AND a `typedAxes` option hand-written into
83
+ * `eslint.config.local.ts`. Nothing carried `design.tokens.axes` from
84
+ * `.lisa.config.json` to the rule, so a project could declare every axis,
85
+ * install the policy, pass every test, and enforce nothing.
86
+ *
87
+ * It is read HERE, at ESLint **config** load, rather than inside the rule at
88
+ * **rule** load. That distinction is what makes it unremarkable: the managed
89
+ * `eslint.config.ts` already reads `eslint.thresholds.json` and
90
+ * `eslint.ignore.config.json` from disk at exactly this moment, so this adds no
91
+ * new class of behaviour and gives ESLint's config cache nothing to fight. A
92
+ * rule reading project files on every `create()` would do both.
93
+ *
94
+ * It also needs no generation step, which is what the alternative — emitting
95
+ * rule options into a generated file during `lisa apply` — would have cost. A
96
+ * generated file can go stale against the config it was generated from; a value
97
+ * read at load cannot.
98
+ * @param projectRoot - Directory holding the project's `.lisa.config.json`.
99
+ * @returns The declared axes, or an empty list when none are declared.
100
+ * @throws {TypeError} When `design.tokens.axes` is present but is not a list of strings.
101
+ */
102
+ export function readTypedAxes(projectRoot: string): string[] {
103
+ const file = path.join(projectRoot, ".lisa.config.json");
104
+ const raw = readConfigText(file);
105
+ if (raw === undefined) return [];
106
+ const axes = (JSON.parse(raw) as { design?: { tokens?: { axes?: unknown } } })
107
+ .design?.tokens?.axes;
108
+ if (axes === undefined) return [];
109
+ if (!Array.isArray(axes) || axes.some(axis => typeof axis !== "string")) {
110
+ // Coercing a malformed declaration to "no axes" would disarm the rule
111
+ // silently — #2807 reproduced one layer down. Refuse loudly instead.
112
+ throw new TypeError(
113
+ `${file}: design.tokens.axes must be an array of axis names, e.g. ["color"].`
114
+ );
115
+ }
116
+ return axes as string[];
117
+ }
118
+
119
+ /**
120
+ * The `ui-standards/no-unbound-design-value` entry for a set of typed axes.
121
+ *
122
+ * Silence-by-default is deliberate and unchanged: an axis with no published
123
+ * variable collection behind it has no token to bind to, and a rule that fires
124
+ * on every project is a rule that gets switched off. What changes is that
125
+ * declaring an axis is now SUFFICIENT to arm it. Previously nothing was.
126
+ *
127
+ * An axis name outside the rule's fixed vocabulary is passed through rather
128
+ * than filtered out, so ESLint's own schema validation rejects it by name. A
129
+ * filter would turn a typo into silent non-enforcement, which is the defect
130
+ * this function exists to remove.
131
+ * @param typedAxes - Axes the project declares typed.
132
+ * @returns `"off"` when none are declared, an armed error entry otherwise.
133
+ */
134
+ export function getDesignValueBindingRule(
135
+ typedAxes: readonly string[]
136
+ ): import("eslint").Linter.RuleEntry {
137
+ return typedAxes.length > 0
138
+ ? ["error", { typedAxes: [...typedAxes] }]
139
+ : "off";
140
+ }
141
+
60
142
  /**
61
143
  * Creates the Expo ESLint configuration.
62
144
  * @param {object} options - Configuration options
63
145
  * @param {string} options.tsconfigRootDir - Root directory for tsconfig.json
64
146
  * @param {string[]} [options.ignorePatterns] - Patterns to ignore
65
147
  * @param {object} [options.thresholds] - Threshold overrides
148
+ * @param {string[]} [options.typedAxes] - Axes the project declares typed, mirroring `design.tokens.axes`. Empty leaves the design-value rule off.
66
149
  * @returns {Array} ESLint flat config array
67
150
  */
68
151
  export function getExpoConfig({
69
152
  tsconfigRootDir,
70
153
  ignorePatterns = defaultIgnores,
71
154
  thresholds = defaultThresholds,
155
+ typedAxes = [],
72
156
  }: {
73
157
  tsconfigRootDir: string;
74
158
  ignorePatterns?: string[];
75
159
  thresholds?: typeof defaultThresholds;
160
+ typedAxes?: readonly string[];
76
161
  }) {
77
162
  return [
78
163
  // Global ignores
@@ -125,10 +210,13 @@ export function getExpoConfig({
125
210
  "ui-standards/no-classname-outside-ui": "off",
126
211
  "ui-standards/no-direct-rn-imports": "off",
127
212
  // Regime-aware by declaration: silent until the project names the axes
128
- // it publishes design variables for. Activate per axis in
129
- // eslint.config.local.ts, mirroring `design.tokens.axes` in
130
- // .lisa.config.json e.g. ["error", { typedAxes: ["color"] }].
131
- "ui-standards/no-unbound-design-value": "off",
213
+ // it publishes design variables for. Declaring `design.tokens.axes` in
214
+ // .lisa.config.json is what arms it — the managed eslint.config.ts
215
+ // reads that key and hands it here (#2807). Declaring nothing leaves
216
+ // the rule off, which is the correct default and was previously the
217
+ // ONLY reachable state.
218
+ "ui-standards/no-unbound-design-value":
219
+ getDesignValueBindingRule(typedAxes),
132
220
 
133
221
  // Tailwind
134
222
  "tailwindcss/classnames-order": [
package/package.json CHANGED
@@ -3,13 +3,16 @@
3
3
  "build": "bun run build:dist && bun run build:plugins",
4
4
  "build:dist": "node scripts/clean-dist.mjs && tsc && node scripts/copy-codex-scripts.mjs && node scripts/copy-opencode-plugin-templates.mjs",
5
5
  "test:integration": "vitest run tests/integration",
6
+ "test:integration:push": "vitest run tests/integration --exclude='**/mutation-gate-bite.test.ts' --exclude='**/mutation-gate-diff-bite.test.ts'",
6
7
  "postinstall": "bash ./scripts/install-claude-plugins.sh || true; [ -d dist/configs ] || tsc || true",
7
8
  "pretest": "[ -d dist/configs ] || bun run build",
8
9
  "test": "vitest run",
9
10
  "test:unit": "vitest run --exclude='**/integration/**'",
10
11
  "test:cov": "vitest run --coverage",
12
+ "test:cov:unit": "vitest run --coverage --exclude='**/integration/**'",
11
13
  "pretest:mutation": "[ -d dist/configs ] || bun run build",
12
- "test:mutation": "stryker run",
14
+ "test:mutation": "node scripts/lisa-mutation.mjs",
15
+ "test:mutation:full": "stryker run",
13
16
  "lint": "oxlint --disable-nested-config && eslint . --quiet",
14
17
  "lint:fix": "oxlint --fix --disable-nested-config && eslint . --fix",
15
18
  "lint:slow": "eslint . --config eslint.slow.config.ts --quiet",
@@ -138,7 +141,7 @@
138
141
  "zod-validation-error": "^4.0.0"
139
142
  },
140
143
  "name": "@codyswann/lisa",
141
- "version": "3.47.4",
144
+ "version": "3.47.6",
142
145
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
143
146
  "main": "dist/index.js",
144
147
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
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",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -69,6 +69,10 @@ A frame-level read counts the chrome behind a modal and over-reports. One measur
69
69
 
70
70
  ESLint runs on source text with no network and no design-tool session, so `ui-standards/no-unbound-design-value` cannot read the map at all. It takes the typed axes as its `typedAxes` option, which mirrors `design.tokens.axes`, and reports nothing when that list is empty.
71
71
 
72
+ **How the option is armed (#2807).** The managed `eslint.config.ts` reads `design.tokens.axes` out of `.lisa.config.json` at ESLint **config** load — alongside `eslint.thresholds.json` and `eslint.ignore.config.json`, which it has always read there — and hands the list to the config factory, which emits `["error", { typedAxes }]` for a non-empty list and `"off"` for an empty one. **Declaring an axis is therefore sufficient to arm the rule; nothing has to be written into `eslint.config.local.ts`.** Before this the rule shipped severity `"off"` with no path from configuration to activation at all, so a project could declare every axis, install the policy, pass every test, and enforce nothing — a control installed, adopted, green and inert.
73
+
74
+ An axis name outside the six is passed straight through to ESLint's own schema validation, which rejects it by name. Filtering it out instead would turn a typo into silent non-enforcement, which is the same failure one layer down.
75
+
72
76
  The intake probe's regime is **observed**; the lint rule's is **declared**, and the declared list can go stale relative to the map. It is still the correct division — an over-firing lint rule is a disabled lint rule, and a rule that silently skipped an axis it could not verify would be worse than one that skips an axis nobody declared. Intake is the arm that sees the truth; lint is the arm that catches the same defect at authoring time on the axes the project has already committed to.
73
77
 
74
78
  ## A design source is optional
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -69,6 +69,10 @@ A frame-level read counts the chrome behind a modal and over-reports. One measur
69
69
 
70
70
  ESLint runs on source text with no network and no design-tool session, so `ui-standards/no-unbound-design-value` cannot read the map at all. It takes the typed axes as its `typedAxes` option, which mirrors `design.tokens.axes`, and reports nothing when that list is empty.
71
71
 
72
+ **How the option is armed (#2807).** The managed `eslint.config.ts` reads `design.tokens.axes` out of `.lisa.config.json` at ESLint **config** load — alongside `eslint.thresholds.json` and `eslint.ignore.config.json`, which it has always read there — and hands the list to the config factory, which emits `["error", { typedAxes }]` for a non-empty list and `"off"` for an empty one. **Declaring an axis is therefore sufficient to arm the rule; nothing has to be written into `eslint.config.local.ts`.** Before this the rule shipped severity `"off"` with no path from configuration to activation at all, so a project could declare every axis, install the policy, pass every test, and enforce nothing — a control installed, adopted, green and inert.
73
+
74
+ An axis name outside the six is passed straight through to ESLint's own schema validation, which rejects it by name. Filtering it out instead would turn a typo into silent non-enforcement, which is the same failure one layer down.
75
+
72
76
  The intake probe's regime is **observed**; the lint rule's is **declared**, and the declared list can go stale relative to the map. It is still the correct division — an over-firing lint rule is a disabled lint rule, and a rule that silently skipped an axis it could not verify would be worse than one that skips an axis nobody declared. Intake is the arm that sees the truth; lint is the arm that catches the same defect at authoring time on the axes the project has already committed to.
73
77
 
74
78
  ## A design source is optional
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -74,6 +74,10 @@ A frame-level read counts the chrome behind a modal and over-reports. One measur
74
74
 
75
75
  ESLint runs on source text with no network and no design-tool session, so `ui-standards/no-unbound-design-value` cannot read the map at all. It takes the typed axes as its `typedAxes` option, which mirrors `design.tokens.axes`, and reports nothing when that list is empty.
76
76
 
77
+ **How the option is armed (#2807).** The managed `eslint.config.ts` reads `design.tokens.axes` out of `.lisa.config.json` at ESLint **config** load — alongside `eslint.thresholds.json` and `eslint.ignore.config.json`, which it has always read there — and hands the list to the config factory, which emits `["error", { typedAxes }]` for a non-empty list and `"off"` for an empty one. **Declaring an axis is therefore sufficient to arm the rule; nothing has to be written into `eslint.config.local.ts`.** Before this the rule shipped severity `"off"` with no path from configuration to activation at all, so a project could declare every axis, install the policy, pass every test, and enforce nothing — a control installed, adopted, green and inert.
78
+
79
+ An axis name outside the six is passed straight through to ESLint's own schema validation, which rejects it by name. Filtering it out instead would turn a typo into silent non-enforcement, which is the same failure one layer down.
80
+
77
81
  The intake probe's regime is **observed**; the lint rule's is **declared**, and the declared list can go stale relative to the map. It is still the correct division — an over-firing lint rule is a disabled lint rule, and a rule that silently skipped an axis it could not verify would be worse than one that skips an axis nobody declared. Intake is the arm that sees the truth; lint is the arm that catches the same defect at authoring time on the axes the project has already committed to.
78
82
 
79
83
  ## A design source is optional
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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-openclaw",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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": "3.47.4",
3
+ "version": "3.47.6",
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"