@adia-ai/adia-ui-factory 0.8.56 → 0.8.57
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/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/agents/surface-qa-agent.md +1 -1
- package/commands/app-audit.md +4 -2
- package/commands/app-migration.md +1 -1
- package/commands/app-planning.md +3 -2
- package/commands/find-unused.md +3 -3
- package/commands/gen-ui-wiring.md +1 -1
- package/commands/project-scaffolding.md +3 -2
- package/commands/surface-qa.md +1 -1
- package/commands/theme-audit.md +2 -2
- package/hermes-mcp.yaml +1 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/prompts/app-audit.md +4 -2
- package/prompts/app-migration.md +1 -1
- package/prompts/app-planning.md +3 -2
- package/prompts/find-unused.md +3 -3
- package/prompts/gen-ui-wiring.md +1 -1
- package/prompts/project-scaffolding.md +3 -2
- package/prompts/surface-qa.md +1 -1
- package/prompts/theme-audit.md +2 -2
- package/references/authoring-components.md +1 -1
- package/references/composed-surface-rubric.md +1 -1
- package/references/shell-admin.md +1 -1
- package/scripts/adia-lint.mjs +193 -0
- package/scripts/lint-rules.generated.mjs +1700 -0
- package/skills/app-audit/references/gap-classes.md +1 -1
- package/skills/app-migration/SKILL.md +1 -1
- package/skills/app-planning/SKILL.md +5 -1
- package/skills/find-unused/SKILL.md +1 -1
- package/skills/find-unused/references/recon-and-audits.md +3 -3
- package/skills/pattern-catalog/references/pattern-index.md +66 -66
- package/skills/project-scaffolding/SKILL.md +6 -6
- package/skills/screen-composition/SKILL.md +6 -4
- package/skills/screen-composition/assets/figma-make/guidelines/components.md +5 -7
- package/skills/shell-selection/SKILL.md +1 -1
- package/skills/surface-qa/SKILL.md +1 -1
- package/skills/surface-qa/references/ci-recipe.md +2 -2
- package/skills/surface-qa/references/verification.md +4 -1
- package/skills/theme-audit/SKILL.md +2 -2
- package/hooks/hooks.json +0 -15
- package/scripts/adia-lint +0 -411
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-factory",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Author and verify apps built ON the adia-ui (@adia-ai) light-DOM web-component framework \u2014 orient, scaffold, compose, wire, verify, and migrate across SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
|
@@ -15,5 +15,6 @@
|
|
|
15
15
|
"web-components",
|
|
16
16
|
"app-authoring",
|
|
17
17
|
"consumer"
|
|
18
|
-
]
|
|
18
|
+
],
|
|
19
|
+
"hooks": "./adapters/claude/hooks.json"
|
|
19
20
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-factory",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Author and verify apps built ON the adia-ui (@adia-ai) light-DOM web-component framework — orient, scaffold, compose, wire, verify, and migrate across SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
package/.mcp.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog — adia-ui-kit-factory
|
|
2
2
|
|
|
3
|
+
## [0.8.57] — 2026-09-01
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Plugin passes `author-cross-harness-plugins`'s cross-harness validator, modulo one documented, permanent, irreconcilable exception (gh#2668).** `${CLAUDE_PLUGIN_ROOT}`/`$ARGUMENTS`/the `` !`command` `` auto-injection syntax removed from shared `app-migration`, `theme-audit`, `screen-composition`, `project-scaffolding`, `app-planning`, `surface-qa`, and `find-unused` SKILL.md bodies, all eight `commands/*.md` bodies, and their linked reference docs (`surface-qa/references/verification.md`, `find-unused/references/recon-and-audits.md`) — portable `<plugin-root>` prose / "named in the current request" phrasing throughout; `screen-composition`/`app-planning`'s Claude-only auto-injected context block became an explicit "run this yourself" instruction, since Codex has no equivalent auto-injection surface. `hooks/hooks.json` moved to `adapters/claude/hooks.json` (unchanged, `${CLAUDE_PLUGIN_ROOT}`-based); new root `hooks.json` mirrors the same event/matcher/action signature with a monorepo-root-relative command path for Codex's convention-discovered hook surface — an unverified-but-honest fallback documented in `HARNESS-NOTES.md`. **The one exception:** every skill's `disable-model-invocation:`/`user-invocable:` frontmatter — required, explicitly, by this repo's own blocking `check:skills` gate (`plugin-estate-v2.md`'s species-pinning charter) — collides head-on with the validator's opposite requirement on the exact same file, and Codex is hard-pinned to read root `skills/` directly (no adapter escape). The repo's own charter is not weakened; `npm run check:cross-harness-plugins` (`scripts/verify/check-cross-harness-plugins.mjs`, wrapping the vendored `scripts/verify/vendor/validate_cross_harness.py`) treats only this one exact finding shape as accepted (logged as INFO) and still fails on every other portability class. Full reasoning in `HARNESS-NOTES.md`.
|
|
7
|
+
|
|
8
|
+
### Docs
|
|
9
|
+
- **`skills/pattern-catalog/references/pattern-index.md` regenerated for `page-scroll`'s addition (gh#2639, ADR-0098 Fork 4).** `build:patterns-index`'s re-extraction picked up `admin-dashboard.contents.html`/`admin-shell.contents.html`'s tag-list changes under the page-chrome consolidation (`admin-page*` → `page-ui[band]`, `admin-scroll` → `page-scroll`).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **`adia-lint` rewritten as a thin Node CLI (`adia-lint.mjs`) consuming a shared generated lint-rule bank, retiring the Python script (gh#2638, LLD-0016, ADR-0099).** The consumer-app structural checks (the forge-shared rules plus SSR-DOUBLE-ROUTER, SSR-TOPLEVEL-IMPORT, HARDCODED-OPEN, SHELL-NESTING, SHELL-RESIZE, LLM-KEY-IN-CLIENT, GENUI-UNVALIDATED, GENUI-DOC-CONFLICT) now live as individually-fixtured rule modules under `scripts/lint/rules/` in the repo root, built into `scripts/lint/generated/rule-bank.mjs` and vendored byte-identically into this plugin, `adia-ui-forge`, and the gen-ui MCP's factory vendor tree (`npm run build:lint-rules`, freshness gated by `npm run check:lint-rules-fresh`). `hooks/hooks.json`'s PostToolUse command changes from `python3 ".../adia-lint" --hook` to `node ".../adia-lint.mjs" --hook`; the CLI contract (including `-h`/`--help`), exit codes, and PostToolUse never-block invariant are unchanged. `npm run check:lint-rules-selftest` proves finding-parity against the retired script's own `selftest()` fixtures. The gen-ui MCP factory server's `lint` tool (`audit_structure`) now spawns the Node CLI directly instead of shelling through `python3`.
|
|
13
|
+
|
|
14
|
+
### Maintenance
|
|
15
|
+
- **`.claude-plugin/plugin.json` version bump** — moves in lockstep with package.json (the `/plugin update` cache key).
|
|
16
|
+
- **`.codex-plugin/` touched in this release window** (1 file(s), e.g. `.codex-plugin/plugin.json`) — carried by the entries above.
|
|
17
|
+
- **`agents/` touched in this release window** (1 file(s), e.g. `agents/surface-qa-agent.md`) — carried by the entries above.
|
|
18
|
+
- **`prompts/` touched in this release window** (8 file(s), e.g. `prompts/app-audit.md`) — carried by the entries above.
|
|
19
|
+
|
|
3
20
|
## [0.8.56] — 2026-08-31
|
|
4
21
|
|
|
5
22
|
### Added
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ attributes vs. the shipped component contracts), and `adia-theme-audit.mjs`
|
|
|
111
111
|
|
|
112
112
|
## MCP
|
|
113
113
|
|
|
114
|
-
`adia-gen-ui` server pinned: `@adia-ai/mcp@0.8.
|
|
114
|
+
`adia-gen-ui` server pinned: `@adia-ai/mcp@0.8.57` (tool SoT:
|
|
115
115
|
`packages/gen-ui/mcp/TOOLS.md` — the `gen-ui` section; stability rule in
|
|
116
116
|
`references/contracts/`; pin lives in `.mcp.json` — `check:plugin-count-claims`
|
|
117
117
|
guards this README copy against it). The server is `adia-mcp gen-ui`,
|
|
@@ -39,7 +39,7 @@ a surface can pass the browser gate and still be a keyword-driven premature
|
|
|
39
39
|
render (D1) that never traced to a resolved plan. Every claim in the
|
|
40
40
|
proof cites its evidence (the probe JSON, the screenshot path, the element
|
|
41
41
|
inspected). The structure row's evidence comes from running
|
|
42
|
-
`
|
|
42
|
+
`node "${CLAUDE_PLUGIN_ROOT}/scripts/adia-lint.mjs"` over the surface's files via
|
|
43
43
|
Bash — the PostToolUse hook never fires for a seat that writes nothing. A
|
|
44
44
|
gate that cannot run (no dev server, Playwright missing) is reported
|
|
45
45
|
UNMEASURED with the reason, never silently skipped; a dispatch without a
|
package/commands/app-audit.md
CHANGED
|
@@ -6,9 +6,11 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Probe the project context.
|
|
9
|
+
Probe the project context named in the current request (a directory, if given).
|
|
10
10
|
|
|
11
|
-
Run `python3 "
|
|
11
|
+
Run `python3 "<plugin-root>/scripts/adia-info" [dir]` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT`
|
|
12
|
+
in Claude Code; the plugin's installed directory in Codex) with the directory named in the
|
|
13
|
+
current request, and present
|
|
12
14
|
the JSON as a short read: each field with the signal it derived from and
|
|
13
15
|
the decision it drives (screen-composition §Current project context carries the
|
|
14
16
|
field→decision table). An `installedVersion` that is null or mismatched vs
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Migrate this codebase.
|
|
9
|
+
Migrate this codebase to the target version named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`app-migration`**: read the shipped guide for the span
|
|
12
12
|
(`node_modules/@adia-ai/web-components/MIGRATION.md` — install/upgrade the
|
package/commands/app-planning.md
CHANGED
|
@@ -6,10 +6,11 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Orient in this repo.
|
|
9
|
+
Orient in this repo, answering the path or question named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`app-planning`** and produce the Orientation Record in the skill's
|
|
12
12
|
own contract shape (Rendering mode · Project shape · Shell · Task ·
|
|
13
13
|
Screen plan (start mode) · → Route · Verify target · Open questions), each
|
|
14
14
|
axis citing its signal. Self-check the record with
|
|
15
|
-
`python3 "
|
|
15
|
+
`python3 "<plugin-root>/scripts/record-lint" -` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in
|
|
16
|
+
Claude Code; the plugin's installed directory in Codex) before returning it.
|
package/commands/find-unused.md
CHANGED
|
@@ -6,12 +6,12 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Find what this app is not using.
|
|
9
|
+
Find what this app is not using, scoped to the target version named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`find-unused`**: enumerate every `@adia-ai/*` package each manifest
|
|
12
12
|
declares (never assume `web-components`), recon the three versions that differ
|
|
13
|
-
per package — declared range, installed, and the target: **the version in
|
|
14
|
-
|
|
13
|
+
per package — declared range, installed, and the target: **the version named in the current request
|
|
14
|
+
when one was given, registry latest only as the default**. Read
|
|
15
15
|
every `node_modules/@adia-ai/*/CHANGELOG.md` section across the
|
|
16
16
|
installed→target span, then run all four audits — inert opt-in layers, redundant local
|
|
17
17
|
workarounds, retired enum values sitting in persisted state, and shipped
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Build
|
|
9
|
+
Build the gen-UI experience or surface named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`gen-ui-wiring`** and run its generate → validate → render → iterate
|
|
12
12
|
loop; generated A2UI is validated as data before it is serialized, and the
|
|
@@ -6,10 +6,11 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Scaffold
|
|
9
|
+
Scaffold the adia-ui surface named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`project-scaffolding`** to classify the project shape and target, then use
|
|
12
|
-
|
|
12
|
+
`<plugin-root>/scripts/adia-scaffold` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in Claude
|
|
13
|
+
Code; the plugin's installed directory in Codex) for the mechanical skeleton. Shapes
|
|
13
14
|
the bin doesn't one-shot (rollup, shared-foundation) are composed per the
|
|
14
15
|
project-shapes reference. `inventory <app-root>` scores an existing app
|
|
15
16
|
against the structure rubric (4 mechanized gates with cited paths + the 2
|
package/commands/surface-qa.md
CHANGED
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Verify
|
|
9
|
+
Verify the composed surface named in the current request.
|
|
10
10
|
|
|
11
11
|
Dispatch the **surface-qa-agent** agent on the named surface/URL — the exit
|
|
12
12
|
gate runs ISOLATED from whatever context built the surface (generator ≠
|
package/commands/theme-audit.md
CHANGED
|
@@ -6,9 +6,9 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Audit
|
|
9
|
+
Audit the theme stylesheet named in the current request.
|
|
10
10
|
|
|
11
|
-
Run `node "
|
|
11
|
+
Run `node "<plugin-root>/scripts/adia-theme-audit.mjs" --theme <file>
|
|
12
12
|
--app <root>` (add `--json`/`--json-out <path>` for a structured report,
|
|
13
13
|
`--strict` to exit non-zero on any `high`-confidence finding, `--class
|
|
14
14
|
1,2,3,4` to narrow scope). No `@adia-ai/web-components` resolvable from
|
package/hermes-mcp.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/adia-ui-factory",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Author and verify apps built ON the adia-ui (@adia-ai) light-DOM web-component framework — orient, scaffold, compose, wire, verify, and migrate across SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adia-ui",
|
package/plugin.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
name: "adia-ui-kit-factory"
|
|
2
|
-
version: "0.8.
|
|
2
|
+
version: "0.8.57"
|
|
3
3
|
description: "Author and verify apps built ON the adia-ui (@adia-ai) light-DOM web-component framework — orient, scaffold, compose, wire, verify, and migrate across SPA and SSR rendering modes. Wires the a2ui MCP for catalog retrieval, UI generation, and validation."
|
|
4
4
|
manifest_version: 1
|
package/prompts/app-audit.md
CHANGED
|
@@ -3,9 +3,11 @@ description: "Probe this repo's adia-ui project context — declared vs installe
|
|
|
3
3
|
argument-hint: "[dir]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Probe the project context.
|
|
6
|
+
Probe the project context named in the current request (a directory, if given).
|
|
7
7
|
|
|
8
|
-
Run `python3 "
|
|
8
|
+
Run `python3 "<plugin-root>/scripts/adia-info" [dir]` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT`
|
|
9
|
+
in Claude Code; the plugin's installed directory in Codex) with the directory named in the
|
|
10
|
+
current request, and present
|
|
9
11
|
the JSON as a short read: each field with the signal it derived from and
|
|
10
12
|
the decision it drives (screen-composition §Current project context carries the
|
|
11
13
|
field→decision table). An `installedVersion` that is null or mismatched vs
|
package/prompts/app-migration.md
CHANGED
|
@@ -3,7 +3,7 @@ description: "Migrate consumer code across @adia-ai versions (or port a foreign
|
|
|
3
3
|
argument-hint: "[target version]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Migrate this codebase.
|
|
6
|
+
Migrate this codebase to the target version named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`app-migration`**: read the shipped guide for the span
|
|
9
9
|
(`node_modules/@adia-ai/web-components/MIGRATION.md` — install/upgrade the
|
package/prompts/app-planning.md
CHANGED
|
@@ -3,10 +3,11 @@ description: "Orient in an existing adia-ui app — classify rendering mode, pro
|
|
|
3
3
|
argument-hint: "[path or question]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Orient in this repo.
|
|
6
|
+
Orient in this repo, answering the path or question named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`app-planning`** and produce the Orientation Record in the skill's
|
|
9
9
|
own contract shape (Rendering mode · Project shape · Shell · Task ·
|
|
10
10
|
Screen plan (start mode) · → Route · Verify target · Open questions), each
|
|
11
11
|
axis citing its signal. Self-check the record with
|
|
12
|
-
`python3 "
|
|
12
|
+
`python3 "<plugin-root>/scripts/record-lint" -` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in
|
|
13
|
+
Claude Code; the plugin's installed directory in Codex) before returning it.
|
package/prompts/find-unused.md
CHANGED
|
@@ -3,12 +3,12 @@ description: "Find what a clean @adia-ai upgrade left unused — opt-in layers n
|
|
|
3
3
|
argument-hint: "[target version]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Find what this app is not using.
|
|
6
|
+
Find what this app is not using, scoped to the target version named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`find-unused`**: enumerate every `@adia-ai/*` package each manifest
|
|
9
9
|
declares (never assume `web-components`), recon the three versions that differ
|
|
10
|
-
per package — declared range, installed, and the target: **the version in
|
|
11
|
-
|
|
10
|
+
per package — declared range, installed, and the target: **the version named in the current request
|
|
11
|
+
when one was given, registry latest only as the default**. Read
|
|
12
12
|
every `node_modules/@adia-ai/*/CHANGELOG.md` section across the
|
|
13
13
|
installed→target span, then run all four audits — inert opt-in layers, redundant local
|
|
14
14
|
workarounds, retired enum values sitting in persisted state, and shipped
|
package/prompts/gen-ui-wiring.md
CHANGED
|
@@ -3,7 +3,7 @@ description: "Author a generative-UI experience — wire the a2ui runtime, gener
|
|
|
3
3
|
argument-hint: "[experience or surface]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Build
|
|
6
|
+
Build the gen-UI experience or surface named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`gen-ui-wiring`** and run its generate → validate → render → iterate
|
|
9
9
|
loop; generated A2UI is validated as data before it is serialized, and the
|
|
@@ -3,10 +3,11 @@ description: "Scaffold a new adia-ui app, page, or component skeleton (scripts/a
|
|
|
3
3
|
argument-hint: "[spa|ssr|page|component|inventory|selftest] [name or app-root]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Scaffold
|
|
6
|
+
Scaffold the adia-ui surface named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`project-scaffolding`** to classify the project shape and target, then use
|
|
9
|
-
|
|
9
|
+
`<plugin-root>/scripts/adia-scaffold` (`<plugin-root>` is `$CLAUDE_PLUGIN_ROOT` in Claude
|
|
10
|
+
Code; the plugin's installed directory in Codex) for the mechanical skeleton. Shapes
|
|
10
11
|
the bin doesn't one-shot (rollup, shared-foundation) are composed per the
|
|
11
12
|
project-shapes reference. `inventory <app-root>` scores an existing app
|
|
12
13
|
against the structure rubric (4 mechanized gates with cited paths + the 2
|
package/prompts/surface-qa.md
CHANGED
|
@@ -3,7 +3,7 @@ description: "Run the consumer-side exit gate on a composed surface — dispatch
|
|
|
3
3
|
argument-hint: "[url or surface] [key selectors]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Verify
|
|
6
|
+
Verify the composed surface named in the current request.
|
|
7
7
|
|
|
8
8
|
Dispatch the **surface-qa-agent** agent on the named surface/URL — the exit
|
|
9
9
|
gate runs ISOLATED from whatever context built the surface (generator ≠
|
package/prompts/theme-audit.md
CHANGED
|
@@ -3,9 +3,9 @@ description: "Classify a consumer app's theme.css against what the adia-ui frame
|
|
|
3
3
|
argument-hint: "[--theme <file>] [--app <root>]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Audit
|
|
6
|
+
Audit the theme stylesheet named in the current request.
|
|
7
7
|
|
|
8
|
-
Run `node "
|
|
8
|
+
Run `node "<plugin-root>/scripts/adia-theme-audit.mjs" --theme <file>
|
|
9
9
|
--app <root>` (add `--json`/`--json-out <path>` for a structured report,
|
|
10
10
|
`--strict` to exit non-zero on any `high`-confidence finding, `--class
|
|
11
11
|
1,2,3,4` to narrow scope). No `@adia-ai/web-components` resolvable from
|
|
@@ -109,4 +109,4 @@ The mechanizable smells the framework audits — these seed the phase-(c) hook a
|
|
|
109
109
|
| Hardcoded `open` on `<modal-ui>`/`<drawer-ui>` | drive via `.open = true` |
|
|
110
110
|
| `new URL('…', import.meta.url)` literal for a corpus path | hold the path in a variable first |
|
|
111
111
|
|
|
112
|
-
Run the plugin's `scripts/adia-lint` on changed files, the framework's `audit:*` gates when you have the repo, and `mcp__a2ui__check_anti_patterns` / `validate_schema` on generated markup.
|
|
112
|
+
Run the plugin's `scripts/adia-lint.mjs` on changed files, the framework's `audit:*` gates when you have the repo, and `mcp__a2ui__check_anti_patterns` / `validate_schema` on generated markup.
|
|
@@ -72,5 +72,5 @@ Verdict: <shippable | blocked: D#…>
|
|
|
72
72
|
|
|
73
73
|
_Provenance: the two-axis COMPOSE/REALIZE defect-quadrant model is adapted from a component-grading
|
|
74
74
|
rubric; the audit tool names and slot contracts here are the factory plugin's own (`adia-lint`,
|
|
75
|
-
`adia-probe`, the current primitive catalog). Verify a named audit rule against `scripts/adia-lint`'s
|
|
75
|
+
`adia-probe`, the current primitive catalog). Verify a named audit rule against `scripts/adia-lint.mjs`'s
|
|
76
76
|
actual rule set before citing it._
|
|
@@ -89,7 +89,7 @@ SPA mounts the full markup. SSR keeps the shell + chrome fixed and swaps only th
|
|
|
89
89
|
|
|
90
90
|
- **Piecemeal import** → `AdminSidebar`/`AdminCommand` unregistered; `.toggle()/.show()` undefined. Import the barrel.
|
|
91
91
|
- **Wrapping shell children in `<col-ui>`/`<row-ui>`** → breaks the grid (it reads tag selectors). Generics go _inside_ `admin-content`/`admin-page-body`.
|
|
92
|
-
- **Raw `<header>` vs `<header-ui>` inside `<admin-page-header>`**:
|
|
92
|
+
- **Raw `<header>` vs `<header-ui>` inside `<admin-page-header>`**: always use `<header-ui>`. The shell CSS targets `admin-page-header > :is(header, header-ui)` identically for geometry (css/admin-shell.templates.css:39), but only `<header-ui>` has the shadow DOM that routes `slot="heading"` / `slot="description"` / `slot="icon"` / `slot="action"` children — a raw `<header>` matches the CSS rhythm but silently drops every named slot.
|
|
93
93
|
- **`[resizable]` without a child `<div data-sidebar-resize>`** → no drag handle.
|
|
94
94
|
- **Hardcoded `[collapsed]`** → won't auto-clear on resize; use `.collapse()/.expand()`.
|
|
95
95
|
- **Multiple `<admin-page>` in one `<admin-scroll>`** → single-axis scroll breaks; one page per scroll.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* adia-lint.mjs — thin Node CLI consuming the vendored generated rule bank
|
|
4
|
+
* (lint-rules.generated.mjs, LLD-0016 §C5) for adia-ui CONSUMER apps: the
|
|
5
|
+
* same structural-smell checks adia-lint (Python) ran, now run 'shared' +
|
|
6
|
+
* 'consumer-only' scope rules — keeps the SSR/hardcoded-open/LLM-key/genui
|
|
7
|
+
* traps forge-lint drops. See ADR-0099 for why this replaced the Python
|
|
8
|
+
* script as the hooks.json entry point.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* adia-lint.mjs <file>... lint files; exit 1 if any error-severity finding, else 0
|
|
12
|
+
* adia-lint.mjs - lint stdin as a generic source file
|
|
13
|
+
* adia-lint.mjs --hook PostToolUse hook mode: read event JSON on stdin, lint the
|
|
14
|
+
* written source file, print advisory findings, ALWAYS exit 0
|
|
15
|
+
* adia-lint.mjs -h/--help print usage, exit 0
|
|
16
|
+
* adia-lint.mjs selftest run the inherited parity fixtures + the hook exit-0 invariant
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const { makeLinter, ext, LINT_EXT, RULES } = await import(join(__dirname, 'lint-rules.generated.mjs'));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Repo-root-relative parity fixtures are only reachable when this CLI is
|
|
27
|
+
* running from inside the monorepo checkout, not a packed/vendored install
|
|
28
|
+
* elsewhere — walk up from this file rather than a fixed `../..` depth,
|
|
29
|
+
* since this CLI shape is vendored to more than one directory depth
|
|
30
|
+
* (ADR-0099). A vendored copy outside the monorepo has no fixtures to find
|
|
31
|
+
* and `selftest` reports that plainly instead of crashing.
|
|
32
|
+
*/
|
|
33
|
+
function findParityFixtures() {
|
|
34
|
+
let dir = __dirname;
|
|
35
|
+
for (let i = 0; i < 8; i += 1) {
|
|
36
|
+
const candidate = join(dir, 'scripts/lint/rule-bank.selftest.mjs');
|
|
37
|
+
if (existsSync(candidate)) return candidate;
|
|
38
|
+
dir = join(dir, '..');
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { lintText } = makeLinter(RULES, { scopes: ['shared', 'consumer-only'] });
|
|
44
|
+
const USAGE = 'usage: adia-lint.mjs <file>... | adia-lint.mjs - | adia-lint.mjs --hook | adia-lint.mjs selftest';
|
|
45
|
+
|
|
46
|
+
function render(path, findings) {
|
|
47
|
+
const out = [`adia-lint: ${findings.length} structural smell(s) in ${path || '<stdin>'}`];
|
|
48
|
+
for (const f of findings) {
|
|
49
|
+
out.push(` [${f.name}] line ${f.line}: ${f.snippet}`);
|
|
50
|
+
out.push(` → ${f.why}`);
|
|
51
|
+
}
|
|
52
|
+
return out.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function lintPath(path) {
|
|
56
|
+
try {
|
|
57
|
+
return lintText(readFileSync(path, 'utf8'), path);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
return [{ name: 'READ-ERROR', line: 1, snippet: path.slice(0, 90), why: `failed to read file: ${err.message}` }];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readStdin() {
|
|
64
|
+
const chunks = [];
|
|
65
|
+
for await (const c of process.stdin) chunks.push(c);
|
|
66
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function hook() {
|
|
70
|
+
let event;
|
|
71
|
+
try {
|
|
72
|
+
event = JSON.parse(await readStdin());
|
|
73
|
+
} catch {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
const ti = event.tool_input || {};
|
|
77
|
+
const path = ti.file_path || '';
|
|
78
|
+
const segs = path.replace(/\\/g, '/').split('/');
|
|
79
|
+
if (segs.some((s) => ['node_modules', 'dist', 'build', '.next', 'coverage'].includes(s))) return 0;
|
|
80
|
+
if (!LINT_EXT.includes(ext(path))) return 0;
|
|
81
|
+
let text = ti.content;
|
|
82
|
+
if (text === undefined || text === null) {
|
|
83
|
+
if (!existsSync(path)) return 0;
|
|
84
|
+
try {
|
|
85
|
+
text = readFileSync(path, 'utf8');
|
|
86
|
+
} catch {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const findings = lintText(text, path);
|
|
91
|
+
if (findings.length) {
|
|
92
|
+
const context = render(path, findings)
|
|
93
|
+
+ '\n (adia-lint advisory — adia-ui authoring smells; the skills + a2ui MCP own the judgment)';
|
|
94
|
+
process.stdout.write(JSON.stringify({
|
|
95
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context },
|
|
96
|
+
}) + '\n');
|
|
97
|
+
}
|
|
98
|
+
return 0; // NEVER block
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function selftest() {
|
|
102
|
+
const fixturesPath = findParityFixtures();
|
|
103
|
+
if (!fixturesPath) {
|
|
104
|
+
console.log('selftest: SKIP — no monorepo checkout found (parity fixtures live in scripts/lint/, not vendored)');
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const { PARITY_CASES } = await import(fixturesPath);
|
|
108
|
+
let ok = true;
|
|
109
|
+
for (const { name, text, expected, scopes } of PARITY_CASES) {
|
|
110
|
+
if (scopes && !scopes.includes('consumer-only')) continue;
|
|
111
|
+
const got = new Set(lintText(text, name).map((f) => f.name));
|
|
112
|
+
const missing = [...expected].filter((e) => !got.has(e));
|
|
113
|
+
if (missing.length) {
|
|
114
|
+
ok = false;
|
|
115
|
+
console.error(`selftest: ${name} MISSING ${JSON.stringify(missing)} (got ${JSON.stringify([...got])})`);
|
|
116
|
+
}
|
|
117
|
+
if (expected.size === 0 && got.size) {
|
|
118
|
+
ok = false;
|
|
119
|
+
console.error(`selftest: ${name} expected clean, got ${JSON.stringify([...got])}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const savedWrite = process.stdout.write.bind(process.stdout);
|
|
123
|
+
let hookOut = '';
|
|
124
|
+
process.stdout.write = (chunk) => { hookOut += chunk; return true; };
|
|
125
|
+
const stdinMock = Buffer.from(JSON.stringify({ tool_input: { file_path: 'x.css', content: '@scope (x) {\n :scope { color:#f00; }\n}' } }));
|
|
126
|
+
const origStdin = process.stdin;
|
|
127
|
+
Object.defineProperty(process, 'stdin', { value: (async function* () { yield stdinMock; })(), configurable: true });
|
|
128
|
+
let rc;
|
|
129
|
+
try {
|
|
130
|
+
rc = await hook();
|
|
131
|
+
} finally {
|
|
132
|
+
process.stdout.write = savedWrite;
|
|
133
|
+
Object.defineProperty(process, 'stdin', { value: origStdin, configurable: true });
|
|
134
|
+
}
|
|
135
|
+
if (rc !== 0) {
|
|
136
|
+
ok = false;
|
|
137
|
+
console.error('selftest: --hook did not exit 0 on smelly input');
|
|
138
|
+
}
|
|
139
|
+
if (rc === 0 && hookOut && !hookOut.includes('"hookSpecificOutput"')) {
|
|
140
|
+
ok = false;
|
|
141
|
+
console.error('selftest: --hook stdout is not structured hookSpecificOutput JSON');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// REQ-05 (gh#1136): -h/--help exits 0 and prints usage.
|
|
145
|
+
for (const flag of ['-h', '--help']) {
|
|
146
|
+
const saved = console.log;
|
|
147
|
+
let helpOut = '';
|
|
148
|
+
console.log = (msg) => { helpOut += msg + '\n'; };
|
|
149
|
+
let rcHelp;
|
|
150
|
+
try {
|
|
151
|
+
rcHelp = await main([flag]);
|
|
152
|
+
} finally {
|
|
153
|
+
console.log = saved;
|
|
154
|
+
}
|
|
155
|
+
if (rcHelp !== 0) {
|
|
156
|
+
ok = false;
|
|
157
|
+
console.error(`selftest: ${flag} did not exit 0`);
|
|
158
|
+
}
|
|
159
|
+
if (!helpOut.startsWith('usage:')) {
|
|
160
|
+
ok = false;
|
|
161
|
+
console.error(`selftest: ${flag} did not print usage`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(ok ? 'selftest: PASS' : 'selftest: FAIL');
|
|
166
|
+
return ok ? 0 : 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function main(argv) {
|
|
170
|
+
if (argv[0] === 'selftest') return selftest();
|
|
171
|
+
if (argv.includes('--hook')) return hook();
|
|
172
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
173
|
+
console.log(USAGE);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
const args = argv.filter((a) => a === '-' || !a.startsWith('-'));
|
|
177
|
+
if (!args.length) {
|
|
178
|
+
console.error(USAGE);
|
|
179
|
+
return 2;
|
|
180
|
+
}
|
|
181
|
+
let total = 0;
|
|
182
|
+
for (let path of args) {
|
|
183
|
+
const findings = path === '-' ? lintText(await readStdin(), '<stdin>') : lintPath(path);
|
|
184
|
+
if (path === '-') path = '<stdin>';
|
|
185
|
+
if (findings.length) {
|
|
186
|
+
total += findings.length;
|
|
187
|
+
console.log(render(path, findings));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return total ? 1 : 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
process.exit(await main(process.argv.slice(2)));
|