@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
|
@@ -133,11 +133,35 @@ Group the findings as:
|
|
|
133
133
|
`RISK`/`GAP` (need a local process — only viable if the cloud session can spawn them from the
|
|
134
134
|
repo). Flag interactively/OAuth-authed servers as `GAP` for headless auth — they cannot complete
|
|
135
135
|
a browser flow headless **regardless of transport** (an HTTP MCP like `linear-server` is still a
|
|
136
|
-
`GAP` because its *auth* is OAuth, not because of its transport).
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
`GAP` because its *auth* is OAuth, not because of its transport). Before finalizing any
|
|
137
|
+
OAuth/interactive/stdio MCP as a flat `GAP`, check for a documented headless substrate: a
|
|
138
|
+
static-token MCP header, a vendor CLI that can login from an env token, or a token-authenticated
|
|
139
|
+
REST API that the MCP wraps. If documented evidence exists, mark the MCP `headlessUsable: true`
|
|
140
|
+
in the inventory, mark the supporting secret `required: false` / `secret: true` unless the
|
|
141
|
+
integration is active, and set `replacedBy` to the concrete substrate rather than leaving it as
|
|
142
|
+
an impossible capability. When an OAuth MCP backs an active tracker/source, do not stop at the
|
|
143
|
+
`GAP` — cross-reference group 4a and point to the integration's token substrate as the headless
|
|
144
|
+
replacement (e.g. `linear-server` MCP -> `LINEAR_API_KEY` + Linear GraphQL). Note any user-scoped
|
|
145
|
+
MCP (`~/.claude.json`) as `GAP` — it never reaches the cloud; it must be moved into project
|
|
146
|
+
`.mcp.json`.
|
|
147
|
+
|
|
148
|
+
Use this data-driven hint table as seed evidence, and extend it only when you can cite a vendor
|
|
149
|
+
doc or committed access-skill reference:
|
|
150
|
+
|
|
151
|
+
| Match | Substrate | Env | Setup / wiring | Domains |
|
|
152
|
+
|---|---|---|---|---|
|
|
153
|
+
| `mcp.jam.dev`, `jam`, or Jam MCP entries | Jam CLI (`jam`) authenticated by PAT | `JAM_PAT` | install with `curl -fsSL https://native.jam.dev/install | bash`; export `~/.local/bin`; run `printf '%s' "$JAM_PAT" | jam auth login --token`; optionally `jam skills install` | `native.jam.dev`, `api.jam.dev` |
|
|
154
|
+
| `mcp/sonarqube`, `sonarqube`, or SonarCloud/SonarQube MCP entries | SonarCloud Web API | `SONAR_TOKEN` | use REST calls against `https://sonarcloud.io/api/`; if a host needs legacy token-as-basic-auth, put that in the access adapter | `sonarcloud.io` |
|
|
155
|
+
|
|
156
|
+
For PAT-bearer MCP substrates that really use the same MCP transport, include `mcpHeaders` in
|
|
157
|
+
the inventory with a snippet such as `headers: { "Authorization": "Bearer ${VAR}" }` so the
|
|
158
|
+
generator can print a commented `.mcp.json` example. Do **not** use that for Jam: Jam's preferred
|
|
159
|
+
headless substrate is the `jam` CLI, so the generator should emit CLI setup lines instead of an
|
|
160
|
+
`.mcp.json` header snippet.
|
|
161
|
+
|
|
162
|
+
When no documented substrate is known, keep the MCP as `headlessUsable: false` / `GAP`, and add
|
|
163
|
+
an action line like: `check the vendor for a documented PAT, API-key, CLI, or REST substrate`.
|
|
164
|
+
Never assert that no substrate exists unless the vendor documentation explicitly says so.
|
|
141
165
|
|
|
142
166
|
6. **Config scope & memory gaps** — identify reliance on user-scoped config that will not load
|
|
143
167
|
remotely: `~/.claude/CLAUDE.md`, user `enabledPlugins`, user skills/agents, user MCP. Most
|
|
@@ -250,10 +274,27 @@ so the generator can render acquisition comments into its template:
|
|
|
250
274
|
"headlessSubstrate": "gh CLI (token)",
|
|
251
275
|
"acquireUrl": "https://github.com/settings/personal-access-tokens",
|
|
252
276
|
"accessScope": "fine-grained PAT on target repo: Contents R/W, Issues R/W, Pull requests R/W, Metadata R; +Workflows R/W if editing .github/workflows; +Projects R/W if using ProjectV2"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "JAM_PAT", "required": false, "secret": true, "integration": "jam",
|
|
280
|
+
"reason": "optional non-tracker MCP recovery; Jam CLI can authenticate headlessly from a PAT",
|
|
281
|
+
"headlessSubstrate": "jam CLI (PAT)",
|
|
282
|
+
"acquireUrl": "https://jam.dev/docs/debug-a-jam/mcp/personal-access-tokens",
|
|
283
|
+
"accessScope": "Jam Personal Access Token for the workspace/team whose traces the routine needs to read",
|
|
284
|
+
"setupSnippet": "curl -fsSL https://native.jam.dev/install | bash; export PATH=\"$HOME/.local/bin:$PATH\"; printf '%s' \"$JAM_PAT\" | jam auth login --token; jam skills install"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"name": "SONAR_TOKEN", "required": false, "secret": true, "integration": "sonarcloud",
|
|
288
|
+
"reason": "optional non-tracker MCP recovery; SonarQube MCP wraps the token-authenticated SonarCloud Web API",
|
|
289
|
+
"headlessSubstrate": "SonarCloud Web API (token)",
|
|
290
|
+
"acquireUrl": "https://docs.sonarsource.com/sonarqube-cloud/managing-your-account/managing-tokens",
|
|
291
|
+
"accessScope": "token must be able to read the target SonarCloud organization/project quality gate, issues, hotspots, rules, and source snippets"
|
|
253
292
|
}
|
|
254
293
|
],
|
|
255
294
|
"mcp": [
|
|
256
|
-
{ "name": "linear-server", "transport": "http", "auth": "oauth", "headlessUsable": false, "replacedBy": "LINEAR_API_KEY + Linear GraphQL", "dormant": true }
|
|
295
|
+
{ "name": "linear-server", "transport": "http", "auth": "oauth", "headlessUsable": false, "replacedBy": "LINEAR_API_KEY + Linear GraphQL", "dormant": true },
|
|
296
|
+
{ "name": "jam", "transport": "http", "auth": "oauth", "headlessUsable": true, "replacedBy": "jam CLI + JAM_PAT", "dormant": true },
|
|
297
|
+
{ "name": "sonarqube", "transport": "stdio", "auth": "local-wrapper", "headlessUsable": true, "replacedBy": "SONAR_TOKEN + SonarCloud Web API", "dormant": true }
|
|
257
298
|
],
|
|
258
299
|
"gaps": [ "auto-memory not synced to cloud", "bun package fetch behind proxy", "OS keychain absent — env-var token form only" ],
|
|
259
300
|
"platform": {
|
|
@@ -305,3 +346,6 @@ so the generator can render acquisition comments into its template:
|
|
|
305
346
|
to environment editors; never imply the generated script can hide them.
|
|
306
347
|
- A browser-OAuth MCP (or interactively-authed CLI) backing an active integration is a `GAP`; the
|
|
307
348
|
remediation is its token substrate from the table, not "authenticate the MCP".
|
|
349
|
+
- For non-tracker MCPs, a documented CLI, PAT/API-key, or REST substrate converts the finding from
|
|
350
|
+
a flat `GAP` into an `OPTIONAL` headless recovery path; unknown vendors remain gaps with a
|
|
351
|
+
vendor-substrate follow-up, not invented credentials.
|
|
@@ -66,6 +66,27 @@ tracker/source, plus the host project's own package manager and tooling — not
|
|
|
66
66
|
When the entry is `GH_TOKEN`, add a comment from `platform.githubProxy` clarifying that the token
|
|
67
67
|
is for `gh` CLI commands against the project/Lisa repos, not for raw git clone/fetch/push or
|
|
68
68
|
sibling repos reachable through the routine's GitHub proxy.
|
|
69
|
+
For OPTIONAL non-tracker MCP recovery entries discovered by
|
|
70
|
+
`/lisa:analyze-claude-remote`, preserve the same names-only behavior:
|
|
71
|
+
include `JAM_PAT`, `SONAR_TOKEN`, or similar documented substrate env vars
|
|
72
|
+
only as optional secrets, with their acquire/scope comments when the analysis
|
|
73
|
+
supplied them. Never invent values or promote dormant substrates to required.
|
|
74
|
+
|
|
75
|
+
3a. **Emit substrate setup snippets.** When the inventory marks an MCP
|
|
76
|
+
`headlessUsable: true` through a documented substrate, render the matching
|
|
77
|
+
wiring guidance as commented, opt-in setup:
|
|
78
|
+
- CLI substrates: emit the detected install/login commands gated on the
|
|
79
|
+
optional env var. For Jam, this means `curl -fsSL https://native.jam.dev/install | bash`,
|
|
80
|
+
`export PATH="$HOME/.local/bin:$PATH"`, `printf '%s' "$JAM_PAT" | jam auth login --token`,
|
|
81
|
+
and `jam skills install`, all inside `[ -n "${JAM_PAT:-}" ] && ...` guards so missing
|
|
82
|
+
optional secrets do not fail the environment build.
|
|
83
|
+
- REST substitute substrates: do not install an MCP. Emit comments naming the
|
|
84
|
+
REST host and env var (for example `SONAR_TOKEN` with `https://sonarcloud.io/api/`) and
|
|
85
|
+
rely on the access skill or generated consumer to call the API.
|
|
86
|
+
- PAT-bearer MCP substrates: print a commented `.mcp.json` `headers` snippet from the
|
|
87
|
+
inventory's `mcpHeaders`. Use this only when the analysis explicitly says the same MCP
|
|
88
|
+
transport supports static-token auth. Do not print a Jam `.mcp.json` header snippet because
|
|
89
|
+
Jam's preferred headless substrate is its PAT-authenticated CLI.
|
|
69
90
|
|
|
70
91
|
4. **Emit the allowlist + gaps notice.** List any custom domains the setup or runtime reaches
|
|
71
92
|
(from `networkAccess.allowlistDomains`, falling back to legacy `allowlistDomains`) that the user
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jam-access
|
|
3
|
-
description: "Vendor-neutral access layer for Jam. Jam triage rules and skills MUST delegate through this skill rather than calling Jam MCP tools directly. Resolves Jam MCP first when available, then falls back to JAM_PAT
|
|
3
|
+
description: "Vendor-neutral access layer for Jam. Jam triage rules and skills MUST delegate through this skill rather than calling Jam MCP tools directly. Resolves Jam MCP first when available, then falls back to the JAM_PAT-authenticated Jam CLI for headless routines."
|
|
4
4
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,16 +24,16 @@ Return parsed JSON or a concise structured summary in a `<result>` block.
|
|
|
24
24
|
Probe in order:
|
|
25
25
|
|
|
26
26
|
1. Jam MCP, if the tool is available and authenticated.
|
|
27
|
-
2.
|
|
27
|
+
2. Jam CLI authenticated with `JAM_PAT`.
|
|
28
28
|
|
|
29
|
-
Jam documents
|
|
30
|
-
|
|
29
|
+
Jam documents a PAT-authenticated CLI that is cleaner for remote routines than
|
|
30
|
+
editing `.mcp.json` headers. The headless tier uses:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
curl -
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
curl -fsSL https://native.jam.dev/install | bash
|
|
34
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
35
|
+
printf '%s' "$JAM_PAT" | jam auth login --token
|
|
36
|
+
jam skills install
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
If neither tier works, fail with:
|
|
@@ -45,7 +45,8 @@ Error: no Jam access substrate available. Authenticate the Jam MCP or set JAM_PA
|
|
|
45
45
|
## Invariants
|
|
46
46
|
|
|
47
47
|
- Fallback is gated on `JAM_PAT`; do not retry Jam MCP failures blindly.
|
|
48
|
-
- Never commit a Jam PAT into `.mcp.json
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
- Never commit a Jam PAT into `.mcp.json` or any generated setup artifact.
|
|
49
|
+
- Headless Jam access requires `native.jam.dev` for the installer and
|
|
50
|
+
`api.jam.dev` for CLI/API calls in any custom remote network allowlist.
|
|
51
|
+
- If a requested operation is not yet mapped to the Jam CLI substrate, surface
|
|
51
52
|
that exact missing adapter instead of pretending the trace is unavailable.
|
|
@@ -10,6 +10,14 @@ description: |
|
|
|
10
10
|
|
|
11
11
|
This skill enforces Brad Frost's atomic design methodology adapted for React Native/Expo projects using Gluestack UI v3 + NativeWind v4. It provides clear guidelines for component categorization, directory structure, composition patterns, and testing strategies for each atomic level.
|
|
12
12
|
|
|
13
|
+
## Sealed Design System Guard
|
|
14
|
+
|
|
15
|
+
Before applying this skill, check whether the target project has a sealed or closed design system. Common markers include `.claude/rules/use-the-design-library.md`, `.claude/rules/figma-design-system.md`, design-system ESLint rules, or a project-owned `@/components/atoms` barrel paired with design-system docs/configuration.
|
|
16
|
+
|
|
17
|
+
If a sealed design system is present, defer to the project's design-library rules and skills instead, such as `use-the-design-library` and `add-design-library-item`. Keep the atomic hierarchy concepts only when they match the local seal. Do not redefine the atom library as `@/components/ui`, use Gluestack compound trees as canonical atoms, or recommend `className` and raw Gluestack tokens where the project seal forbids them.
|
|
18
|
+
|
|
19
|
+
For open Gluestack UI v3 + NativeWind v4 projects without those seal markers, continue with the guidance below.
|
|
20
|
+
|
|
13
21
|
## Core Principles
|
|
14
22
|
|
|
15
23
|
### The Atomic Hierarchy
|
package/plugins/lisa-expo/skills/atomic-design-gluestack/scripts/test_validate_atomic_structure.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for validate_atomic_structure.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_atomic_structure 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()
|
package/plugins/lisa-expo/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py
CHANGED
|
@@ -13,6 +13,7 @@ Usage:
|
|
|
13
13
|
If no path provided, validates the entire project from current directory.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import json
|
|
16
17
|
import os
|
|
17
18
|
import re
|
|
18
19
|
import sys
|
|
@@ -29,6 +30,109 @@ ATOMIC_LEVELS = {
|
|
|
29
30
|
"app": 4, # Expo Router pages
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
SEALED_DESIGN_SYSTEM_MARKERS = [
|
|
34
|
+
".claude/rules/use-the-design-library.md",
|
|
35
|
+
".claude/rules/figma-design-system.md",
|
|
36
|
+
"docs/design-system/tokens.md",
|
|
37
|
+
"docs/design-system-rfc.md",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
ATOM_BARREL_MARKERS = [
|
|
41
|
+
"components/atoms/index.ts",
|
|
42
|
+
"components/atoms/index.tsx",
|
|
43
|
+
"src/components/atoms/index.ts",
|
|
44
|
+
"src/components/atoms/index.tsx",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_project_roots(path: str) -> list[Path]:
|
|
49
|
+
"""Find candidate roots for marker detection, from nearest to farthest.
|
|
50
|
+
|
|
51
|
+
Returns all package boundaries (package.json or .git directories) found
|
|
52
|
+
when walking up from ``path``. In a monorepo this means both the inner
|
|
53
|
+
package root and the workspace root are returned, so seal markers stored at
|
|
54
|
+
the workspace level are not missed.
|
|
55
|
+
"""
|
|
56
|
+
current = Path(path).resolve()
|
|
57
|
+
if current.is_file():
|
|
58
|
+
current = current.parent
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
candidate
|
|
62
|
+
for candidate in [current, *current.parents]
|
|
63
|
+
if (candidate / "package.json").exists() or (candidate / ".git").exists()
|
|
64
|
+
] or [current]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def has_design_system_eslint_rule(project_root: Path) -> bool:
|
|
68
|
+
"""Detect project-owned design-system lint rules.
|
|
69
|
+
|
|
70
|
+
Only matches actual rule/plugin identifier syntax (``design-system/``) so
|
|
71
|
+
that comments, package names, or arbitrary strings containing
|
|
72
|
+
``design-system`` as a substring do not trigger a false positive.
|
|
73
|
+
"""
|
|
74
|
+
eslint_files = [
|
|
75
|
+
".eslintrc",
|
|
76
|
+
".eslintrc.js",
|
|
77
|
+
".eslintrc.cjs",
|
|
78
|
+
".eslintrc.yaml",
|
|
79
|
+
".eslintrc.yml",
|
|
80
|
+
".eslintrc.json",
|
|
81
|
+
"eslint.config.js",
|
|
82
|
+
"eslint.config.mjs",
|
|
83
|
+
"eslint.config.cjs",
|
|
84
|
+
"eslint.config.ts",
|
|
85
|
+
"eslint.config.mts",
|
|
86
|
+
"eslint.config.cts",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
for eslint_file in eslint_files:
|
|
90
|
+
path = project_root / eslint_file
|
|
91
|
+
if not path.exists() or not path.is_file():
|
|
92
|
+
continue
|
|
93
|
+
try:
|
|
94
|
+
content = path.read_text(encoding="utf-8")
|
|
95
|
+
except UnicodeDecodeError:
|
|
96
|
+
continue
|
|
97
|
+
if re.search(r"\bdesign-system/", content):
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
# Check package.json eslintConfig field explicitly so that dependency
|
|
101
|
+
# names (e.g. "design-system" in dependencies) don't cause false positives.
|
|
102
|
+
package_json_path = project_root / "package.json"
|
|
103
|
+
if package_json_path.exists() and package_json_path.is_file():
|
|
104
|
+
try:
|
|
105
|
+
pkg = json.loads(package_json_path.read_text(encoding="utf-8"))
|
|
106
|
+
eslint_config = pkg.get("eslintConfig")
|
|
107
|
+
if eslint_config is not None:
|
|
108
|
+
eslint_config_str = json.dumps(eslint_config)
|
|
109
|
+
if re.search(r"\bdesign-system/", eslint_config_str):
|
|
110
|
+
return True
|
|
111
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def has_sealed_design_system(path: str) -> bool:
|
|
118
|
+
"""Check whether this project has a closed local design-system seal."""
|
|
119
|
+
project_roots = find_project_roots(path)
|
|
120
|
+
has_seal_marker = any(
|
|
121
|
+
(root / marker).exists()
|
|
122
|
+
for root in project_roots
|
|
123
|
+
for marker in SEALED_DESIGN_SYSTEM_MARKERS
|
|
124
|
+
)
|
|
125
|
+
has_atom_barrel = any(
|
|
126
|
+
(root / marker).exists()
|
|
127
|
+
for root in project_roots
|
|
128
|
+
for marker in ATOM_BARREL_MARKERS
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if has_seal_marker or has_atom_barrel:
|
|
132
|
+
return True
|
|
133
|
+
return any(has_design_system_eslint_rule(root) for root in project_roots)
|
|
134
|
+
|
|
135
|
+
|
|
32
136
|
# Patterns to identify atomic level from file path
|
|
33
137
|
LEVEL_PATTERNS = [
|
|
34
138
|
(r"/components/atoms/", "atoms"),
|
|
@@ -303,6 +407,11 @@ def main():
|
|
|
303
407
|
print(f"Error: Path '{path}' does not exist")
|
|
304
408
|
sys.exit(1)
|
|
305
409
|
|
|
410
|
+
if has_sealed_design_system(path):
|
|
411
|
+
print("Sealed design system detected; deferring to the project's design-library rules.")
|
|
412
|
+
print("Skipping generic Gluestack atomic-design validation.")
|
|
413
|
+
sys.exit(0)
|
|
414
|
+
|
|
306
415
|
print(f"Validating atomic design structure in: {path}")
|
|
307
416
|
print("-" * 60)
|
|
308
417
|
|
|
@@ -7,6 +7,14 @@ description: This skill enforces Gluestack UI v3 and NativeWind v4 design patter
|
|
|
7
7
|
|
|
8
8
|
This skill enforces constrained, opinionated styling patterns that reduce decision fatigue, improve performance, enable consistent theming, and limit the solution space to canonical patterns.
|
|
9
9
|
|
|
10
|
+
## Sealed Design System Guard
|
|
11
|
+
|
|
12
|
+
Before applying this skill, check whether the target project has a sealed or closed design system. Common markers include `.claude/rules/use-the-design-library.md`, `.claude/rules/figma-design-system.md`, design-system ESLint rules, or a project-owned `@/components/atoms` barrel paired with design-system docs/configuration.
|
|
13
|
+
|
|
14
|
+
If a sealed design system is present, this skill does not apply. Defer to the project's design-library rules and skills instead, such as `use-the-design-library` and `add-design-library-item`. Do not instruct `@/components/ui` imports, Gluestack compound trees, raw shade tokens, margins, or `className` usage above the atom layer when the project seal forbids them.
|
|
15
|
+
|
|
16
|
+
For open Gluestack UI v3 + NativeWind v4 projects without those seal markers, continue with the guidance below.
|
|
17
|
+
|
|
10
18
|
## Core Principles
|
|
11
19
|
|
|
12
20
|
1. **Gluestack components over React Native primitives** - Gluestack wraps RN with theming, accessibility, and cross-platform consistency
|