@codyswann/lisa 2.178.1 → 2.178.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/rules/reference/integration-access-layer.md +1 -1
- package/plugins/lisa/skills/analyze-claude-remote/SKILL.md +50 -6
- package/plugins/lisa/skills/generate-claude-remote-build-script/SKILL.md +21 -0
- package/plugins/lisa/skills/jam-access/SKILL.md +12 -11
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/analyze-claude-remote/SKILL.md +50 -6
- package/plugins/lisa-agy/skills/generate-claude-remote-build-script/SKILL.md +21 -0
- package/plugins/lisa-agy/skills/jam-access/SKILL.md +12 -11
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/reference/integration-access-layer.md +1 -1
- package/plugins/lisa-copilot/skills/analyze-claude-remote/SKILL.md +50 -6
- package/plugins/lisa-copilot/skills/generate-claude-remote-build-script/SKILL.md +21 -0
- package/plugins/lisa-copilot/skills/jam-access/SKILL.md +12 -11
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/integration-access-layer-reference.mdc +1 -1
- package/plugins/lisa-cursor/skills/analyze-claude-remote/SKILL.md +50 -6
- package/plugins/lisa-cursor/skills/generate-claude-remote-build-script/SKILL.md +21 -0
- package/plugins/lisa-cursor/skills/jam-access/SKILL.md +12 -11
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/skills/atomic-design-gluestack/SKILL.md +8 -0
- package/plugins/lisa-expo/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +109 -0
- package/plugins/lisa-expo/skills/gluestack-nativewind/SKILL.md +8 -0
- package/plugins/lisa-expo/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo/skills/gluestack-nativewind/scripts/validate_styling.py +109 -0
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-agy/skills/atomic-design-gluestack/SKILL.md +8 -0
- package/plugins/lisa-expo-agy/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-agy/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +109 -0
- package/plugins/lisa-expo-agy/skills/gluestack-nativewind/SKILL.md +8 -0
- package/plugins/lisa-expo-agy/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-agy/skills/gluestack-nativewind/scripts/validate_styling.py +109 -0
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/skills/atomic-design-gluestack/SKILL.md +8 -0
- package/plugins/lisa-expo-copilot/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-copilot/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +109 -0
- package/plugins/lisa-expo-copilot/skills/gluestack-nativewind/SKILL.md +8 -0
- package/plugins/lisa-expo-copilot/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-copilot/skills/gluestack-nativewind/scripts/validate_styling.py +109 -0
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/skills/atomic-design-gluestack/SKILL.md +8 -0
- package/plugins/lisa-expo-cursor/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/lisa-expo-cursor/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +109 -0
- package/plugins/lisa-expo-cursor/skills/gluestack-nativewind/SKILL.md +8 -0
- package/plugins/lisa-expo-cursor/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/lisa-expo-cursor/skills/gluestack-nativewind/scripts/validate_styling.py +109 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/reference/integration-access-layer.md +1 -1
- package/plugins/src/base/skills/analyze-claude-remote/SKILL.md +50 -6
- package/plugins/src/base/skills/generate-claude-remote-build-script/SKILL.md +21 -0
- package/plugins/src/base/skills/jam-access/SKILL.md +12 -11
- package/plugins/src/expo/skills/atomic-design-gluestack/SKILL.md +8 -0
- package/plugins/src/expo/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py +240 -0
- package/plugins/src/expo/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +109 -0
- package/plugins/src/expo/skills/gluestack-nativewind/SKILL.md +8 -0
- package/plugins/src/expo/skills/gluestack-nativewind/scripts/test_validate_styling.py +240 -0
- package/plugins/src/expo/skills/gluestack-nativewind/scripts/validate_styling.py +109 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for validate_styling.py seal detection helpers.
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
- find_project_roots: should return ALL ancestor roots (not just first)
|
|
6
|
+
- has_design_system_eslint_rule: should only match actual rule/plugin syntax
|
|
7
|
+
- has_design_system_eslint_rule: should check .eslintrc.yaml/.yml, eslint.config.mts/.cts
|
|
8
|
+
- has_design_system_eslint_rule: should parse package.json eslintConfig field
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Allow importing the module under test
|
|
18
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
19
|
+
from validate_styling import (
|
|
20
|
+
find_project_roots,
|
|
21
|
+
has_design_system_eslint_rule,
|
|
22
|
+
has_sealed_design_system,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestFindProjectRoots(unittest.TestCase):
|
|
27
|
+
"""find_project_roots should return ALL package boundaries, not just the nearest."""
|
|
28
|
+
|
|
29
|
+
def test_returns_all_ancestor_roots_in_monorepo(self) -> None:
|
|
30
|
+
"""In a monorepo, both the inner package root and the workspace root are returned."""
|
|
31
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
32
|
+
workspace = Path(tmp) / "workspace"
|
|
33
|
+
inner = workspace / "apps" / "mobile"
|
|
34
|
+
inner.mkdir(parents=True)
|
|
35
|
+
|
|
36
|
+
# workspace root: has .git
|
|
37
|
+
(workspace / ".git").mkdir()
|
|
38
|
+
# inner package: has package.json
|
|
39
|
+
(inner / "package.json").write_text("{}", encoding="utf-8")
|
|
40
|
+
|
|
41
|
+
source_file = inner / "src" / "components"
|
|
42
|
+
source_file.mkdir(parents=True)
|
|
43
|
+
|
|
44
|
+
roots = find_project_roots(str(source_file))
|
|
45
|
+
# Both the inner package root and the workspace root should be returned.
|
|
46
|
+
# Use .resolve() to handle symlinked temp dirs (e.g. /var → /private/var on macOS).
|
|
47
|
+
self.assertIn(inner.resolve(), roots)
|
|
48
|
+
self.assertIn(workspace.resolve(), roots)
|
|
49
|
+
|
|
50
|
+
def test_returns_single_root_for_simple_project(self) -> None:
|
|
51
|
+
"""A flat project returns its single root."""
|
|
52
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
53
|
+
project = Path(tmp) / "project"
|
|
54
|
+
project.mkdir()
|
|
55
|
+
(project / "package.json").write_text("{}", encoding="utf-8")
|
|
56
|
+
(project / ".git").mkdir()
|
|
57
|
+
|
|
58
|
+
roots = find_project_roots(str(project / "src"))
|
|
59
|
+
self.assertEqual(len(roots), 1)
|
|
60
|
+
# Use .resolve() to handle symlinked temp dirs (e.g. /var → /private/var on macOS).
|
|
61
|
+
self.assertEqual(roots[0], project.resolve())
|
|
62
|
+
|
|
63
|
+
def test_falls_back_to_current_directory_when_no_root_found(self) -> None:
|
|
64
|
+
"""When no package.json or .git exists, returns the starting path."""
|
|
65
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
66
|
+
orphan = Path(tmp) / "orphan" / "dir"
|
|
67
|
+
orphan.mkdir(parents=True)
|
|
68
|
+
roots = find_project_roots(str(orphan))
|
|
69
|
+
self.assertEqual(len(roots), 1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestHasDesignSystemEslintRule(unittest.TestCase):
|
|
73
|
+
"""has_design_system_eslint_rule should match actual rule/plugin identifiers only."""
|
|
74
|
+
|
|
75
|
+
def test_ignores_comment_containing_design_system_substring(self) -> None:
|
|
76
|
+
"""A comment that mentions 'design-system' without a slash must not trigger the seal."""
|
|
77
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
78
|
+
root = Path(tmp)
|
|
79
|
+
eslint = root / ".eslintrc.json"
|
|
80
|
+
eslint.write_text(
|
|
81
|
+
'{"rules": {}, "// note": "do not use design-system here"}',
|
|
82
|
+
encoding="utf-8",
|
|
83
|
+
)
|
|
84
|
+
self.assertFalse(
|
|
85
|
+
has_design_system_eslint_rule(root),
|
|
86
|
+
msg="Plain 'design-system' substring in a comment must not match",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def test_matches_design_system_slash_rule_syntax(self) -> None:
|
|
90
|
+
"""An ESLint rule like 'design-system/no-raw-colors' should trigger the seal."""
|
|
91
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
92
|
+
root = Path(tmp)
|
|
93
|
+
eslint = root / ".eslintrc.json"
|
|
94
|
+
eslint.write_text(
|
|
95
|
+
'{"rules": {"design-system/no-raw-colors": "error"}}',
|
|
96
|
+
encoding="utf-8",
|
|
97
|
+
)
|
|
98
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
99
|
+
|
|
100
|
+
def test_checks_eslintrc_yaml(self) -> None:
|
|
101
|
+
""".eslintrc.yaml is a valid ESLint config and must be checked."""
|
|
102
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
103
|
+
root = Path(tmp)
|
|
104
|
+
yaml_config = root / ".eslintrc.yaml"
|
|
105
|
+
yaml_config.write_text(
|
|
106
|
+
'rules:\n design-system/no-raw-colors: error\n',
|
|
107
|
+
encoding="utf-8",
|
|
108
|
+
)
|
|
109
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
110
|
+
|
|
111
|
+
def test_checks_eslintrc_yml(self) -> None:
|
|
112
|
+
""".eslintrc.yml is a valid ESLint config and must be checked."""
|
|
113
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
114
|
+
root = Path(tmp)
|
|
115
|
+
yml_config = root / ".eslintrc.yml"
|
|
116
|
+
yml_config.write_text(
|
|
117
|
+
'rules:\n design-system/no-raw-colors: error\n',
|
|
118
|
+
encoding="utf-8",
|
|
119
|
+
)
|
|
120
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
121
|
+
|
|
122
|
+
def test_checks_eslint_config_mts(self) -> None:
|
|
123
|
+
"""eslint.config.mts is a valid ESLint flat-config entry point and must be checked."""
|
|
124
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
125
|
+
root = Path(tmp)
|
|
126
|
+
mts_config = root / "eslint.config.mts"
|
|
127
|
+
mts_config.write_text(
|
|
128
|
+
'export default [{"rules": {"design-system/no-raw-colors": "error"}}];',
|
|
129
|
+
encoding="utf-8",
|
|
130
|
+
)
|
|
131
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
132
|
+
|
|
133
|
+
def test_checks_eslint_config_cts(self) -> None:
|
|
134
|
+
"""eslint.config.cts is a valid ESLint flat-config entry point and must be checked."""
|
|
135
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
136
|
+
root = Path(tmp)
|
|
137
|
+
cts_config = root / "eslint.config.cts"
|
|
138
|
+
cts_config.write_text(
|
|
139
|
+
'module.exports = [{"rules": {"design-system/no-raw-colors": "error"}}];',
|
|
140
|
+
encoding="utf-8",
|
|
141
|
+
)
|
|
142
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
143
|
+
|
|
144
|
+
def test_checks_package_json_eslintconfig_field(self) -> None:
|
|
145
|
+
"""The eslintConfig key in package.json must be checked for design-system rules."""
|
|
146
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
147
|
+
root = Path(tmp)
|
|
148
|
+
pkg = {
|
|
149
|
+
"name": "my-app",
|
|
150
|
+
"eslintConfig": {
|
|
151
|
+
"rules": {"design-system/no-raw-colors": "error"}
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
(root / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
|
|
155
|
+
self.assertTrue(has_design_system_eslint_rule(root))
|
|
156
|
+
|
|
157
|
+
def test_package_json_non_eslintconfig_does_not_trigger(self) -> None:
|
|
158
|
+
"""A 'design-system' substring in package.json outside eslintConfig must not match."""
|
|
159
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
160
|
+
root = Path(tmp)
|
|
161
|
+
pkg = {
|
|
162
|
+
"name": "my-app",
|
|
163
|
+
"dependencies": {"design-system": "^1.0.0"},
|
|
164
|
+
}
|
|
165
|
+
(root / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
|
|
166
|
+
self.assertFalse(
|
|
167
|
+
has_design_system_eslint_rule(root),
|
|
168
|
+
msg="'design-system' in dependencies must not trigger the ESLint seal check",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestHasSealedDesignSystem(unittest.TestCase):
|
|
173
|
+
"""has_sealed_design_system seal-detection logic."""
|
|
174
|
+
|
|
175
|
+
def test_seal_marker_alone_is_sufficient(self) -> None:
|
|
176
|
+
"""A seal marker without an atom barrel or .claude/rules directory seals the project."""
|
|
177
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
178
|
+
project = Path(tmp) / "project"
|
|
179
|
+
project.mkdir()
|
|
180
|
+
(project / ".git").mkdir()
|
|
181
|
+
docs = project / "docs"
|
|
182
|
+
docs.mkdir()
|
|
183
|
+
(docs / "design-system-rfc.md").write_text("", encoding="utf-8")
|
|
184
|
+
# Deliberately NO atom barrel and NO .claude/rules directory.
|
|
185
|
+
|
|
186
|
+
self.assertTrue(
|
|
187
|
+
has_sealed_design_system(str(project)),
|
|
188
|
+
msg="A single seal marker must be sufficient to detect a sealed design system",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def test_atom_barrel_alone_is_sufficient(self) -> None:
|
|
192
|
+
"""A project exposing only an atoms barrel is treated as having a sealed design system."""
|
|
193
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
194
|
+
project = Path(tmp) / "project"
|
|
195
|
+
project.mkdir()
|
|
196
|
+
(project / ".git").mkdir()
|
|
197
|
+
atoms = project / "src" / "components" / "atoms"
|
|
198
|
+
atoms.mkdir(parents=True)
|
|
199
|
+
(atoms / "index.ts").write_text("", encoding="utf-8")
|
|
200
|
+
# Deliberately NO seal marker and NO .claude/rules directory.
|
|
201
|
+
|
|
202
|
+
self.assertTrue(
|
|
203
|
+
has_sealed_design_system(str(project)),
|
|
204
|
+
msg="An atoms barrel alone must be sufficient to detect a sealed design system",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestHasSealedDesignSystemMonorepo(unittest.TestCase):
|
|
209
|
+
"""has_sealed_design_system should find seal markers in ancestor (workspace) roots."""
|
|
210
|
+
|
|
211
|
+
def test_finds_seal_marker_at_workspace_root_not_inner_package(self) -> None:
|
|
212
|
+
"""Seal markers placed at the workspace root must be respected for inner packages."""
|
|
213
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
214
|
+
workspace = Path(tmp) / "workspace"
|
|
215
|
+
inner = workspace / "apps" / "mobile"
|
|
216
|
+
inner.mkdir(parents=True)
|
|
217
|
+
|
|
218
|
+
# workspace root: .git + seal marker
|
|
219
|
+
(workspace / ".git").mkdir()
|
|
220
|
+
rules_dir = workspace / ".claude" / "rules"
|
|
221
|
+
rules_dir.mkdir(parents=True)
|
|
222
|
+
(rules_dir / "use-the-design-library.md").write_text("", encoding="utf-8")
|
|
223
|
+
|
|
224
|
+
# inner package: package.json + atom barrel
|
|
225
|
+
(inner / "package.json").write_text("{}", encoding="utf-8")
|
|
226
|
+
atoms = inner / "components" / "atoms"
|
|
227
|
+
atoms.mkdir(parents=True)
|
|
228
|
+
(atoms / "index.ts").write_text("", encoding="utf-8")
|
|
229
|
+
|
|
230
|
+
target_dir = inner / "src" / "components" / "atoms"
|
|
231
|
+
target_dir.mkdir(parents=True)
|
|
232
|
+
|
|
233
|
+
self.assertTrue(
|
|
234
|
+
has_sealed_design_system(str(target_dir)),
|
|
235
|
+
msg="Seal marker at workspace root must be detected even for nested packages",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
unittest.main()
|
|
@@ -19,6 +19,7 @@ Arguments:
|
|
|
19
19
|
Can be a file or directory.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import json
|
|
22
23
|
import os
|
|
23
24
|
import re
|
|
24
25
|
import sys
|
|
@@ -99,6 +100,109 @@ SKIP_PATTERNS = [
|
|
|
99
100
|
]
|
|
100
101
|
|
|
101
102
|
|
|
103
|
+
SEALED_DESIGN_SYSTEM_MARKERS = [
|
|
104
|
+
".claude/rules/use-the-design-library.md",
|
|
105
|
+
".claude/rules/figma-design-system.md",
|
|
106
|
+
"docs/design-system/tokens.md",
|
|
107
|
+
"docs/design-system-rfc.md",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
ATOM_BARREL_MARKERS = [
|
|
111
|
+
"components/atoms/index.ts",
|
|
112
|
+
"components/atoms/index.tsx",
|
|
113
|
+
"src/components/atoms/index.ts",
|
|
114
|
+
"src/components/atoms/index.tsx",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def find_project_roots(path: str) -> list[Path]:
|
|
119
|
+
"""Find candidate roots for marker detection, from nearest to farthest.
|
|
120
|
+
|
|
121
|
+
Returns all package boundaries (package.json or .git directories) found
|
|
122
|
+
when walking up from ``path``. In a monorepo this means both the inner
|
|
123
|
+
package root and the workspace root are returned, so seal markers stored at
|
|
124
|
+
the workspace level are not missed.
|
|
125
|
+
"""
|
|
126
|
+
current = Path(path).resolve()
|
|
127
|
+
if current.is_file():
|
|
128
|
+
current = current.parent
|
|
129
|
+
|
|
130
|
+
return [
|
|
131
|
+
candidate
|
|
132
|
+
for candidate in [current, *current.parents]
|
|
133
|
+
if (candidate / "package.json").exists() or (candidate / ".git").exists()
|
|
134
|
+
] or [current]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def has_design_system_eslint_rule(project_root: Path) -> bool:
|
|
138
|
+
"""Detect project-owned design-system lint rules.
|
|
139
|
+
|
|
140
|
+
Only matches actual rule/plugin identifier syntax (``design-system/``) so
|
|
141
|
+
that comments, package names, or arbitrary strings containing
|
|
142
|
+
``design-system`` as a substring do not trigger a false positive.
|
|
143
|
+
"""
|
|
144
|
+
eslint_files = [
|
|
145
|
+
".eslintrc",
|
|
146
|
+
".eslintrc.js",
|
|
147
|
+
".eslintrc.cjs",
|
|
148
|
+
".eslintrc.yaml",
|
|
149
|
+
".eslintrc.yml",
|
|
150
|
+
".eslintrc.json",
|
|
151
|
+
"eslint.config.js",
|
|
152
|
+
"eslint.config.mjs",
|
|
153
|
+
"eslint.config.cjs",
|
|
154
|
+
"eslint.config.ts",
|
|
155
|
+
"eslint.config.mts",
|
|
156
|
+
"eslint.config.cts",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
for eslint_file in eslint_files:
|
|
160
|
+
path = project_root / eslint_file
|
|
161
|
+
if not path.exists() or not path.is_file():
|
|
162
|
+
continue
|
|
163
|
+
try:
|
|
164
|
+
content = path.read_text(encoding="utf-8")
|
|
165
|
+
except UnicodeDecodeError:
|
|
166
|
+
continue
|
|
167
|
+
if re.search(r"\bdesign-system/", content):
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Check package.json eslintConfig field explicitly so that dependency
|
|
171
|
+
# names (e.g. "design-system" in dependencies) don't cause false positives.
|
|
172
|
+
package_json_path = project_root / "package.json"
|
|
173
|
+
if package_json_path.exists() and package_json_path.is_file():
|
|
174
|
+
try:
|
|
175
|
+
pkg = json.loads(package_json_path.read_text(encoding="utf-8"))
|
|
176
|
+
eslint_config = pkg.get("eslintConfig")
|
|
177
|
+
if eslint_config is not None:
|
|
178
|
+
eslint_config_str = json.dumps(eslint_config)
|
|
179
|
+
if re.search(r"\bdesign-system/", eslint_config_str):
|
|
180
|
+
return True
|
|
181
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def has_sealed_design_system(path: str) -> bool:
|
|
188
|
+
"""Check whether this project has a closed local design-system seal."""
|
|
189
|
+
project_roots = find_project_roots(path)
|
|
190
|
+
has_seal_marker = any(
|
|
191
|
+
(root / marker).exists()
|
|
192
|
+
for root in project_roots
|
|
193
|
+
for marker in SEALED_DESIGN_SYSTEM_MARKERS
|
|
194
|
+
)
|
|
195
|
+
has_atom_barrel = any(
|
|
196
|
+
(root / marker).exists()
|
|
197
|
+
for root in project_roots
|
|
198
|
+
for marker in ATOM_BARREL_MARKERS
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if has_seal_marker or has_atom_barrel:
|
|
202
|
+
return True
|
|
203
|
+
return any(has_design_system_eslint_rule(root) for root in project_roots)
|
|
204
|
+
|
|
205
|
+
|
|
102
206
|
def should_skip_file(file_path: str) -> bool:
|
|
103
207
|
"""Check if file should be skipped."""
|
|
104
208
|
for pattern in SKIP_PATTERNS:
|
|
@@ -270,6 +374,11 @@ def main():
|
|
|
270
374
|
print(f"❌ Path not found: {path}")
|
|
271
375
|
sys.exit(1)
|
|
272
376
|
|
|
377
|
+
if has_sealed_design_system(path):
|
|
378
|
+
print("Sealed design system detected; deferring to the project's design-library rules.")
|
|
379
|
+
print("Skipping generic Gluestack NativeWind validation.")
|
|
380
|
+
sys.exit(0)
|
|
381
|
+
|
|
273
382
|
print(f"🔍 Validating styling patterns in: {path}\n")
|
|
274
383
|
|
|
275
384
|
# Validate
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.178.
|
|
3
|
+
"version": "2.178.3",
|
|
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": "2.178.
|
|
3
|
+
"version": "2.178.3",
|
|
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": "2.178.
|
|
3
|
+
"version": "2.178.3",
|
|
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": "2.178.
|
|
3
|
+
"version": "2.178.3",
|
|
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": "2.178.
|
|
3
|
+
"version": "2.178.3",
|
|
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"
|