shadscan-vue 0.0.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Every entry below is generated by release-please from conventional commits. The
9
+ `0.0.0` bootstrap publish exists only so npm can attach a trusted publisher to
10
+ the package; it is deprecated once the first real release lands.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vinayak Kulkarni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # shadscan-vue
2
+
3
+ > Find the UI fundamentals your shadcn-vue app forgot.
4
+
5
+ Deterministic checks. Evidence. Agent-ready fixes. Static audit — no AI required.
6
+
7
+ `shadscan-vue` audits **shadcn-vue** (Vue 3 + Vite) and **shadcn-nuxt** (Nuxt 4)
8
+ applications for the fundamentals that are easy to skip and expensive to miss:
9
+ theme wiring, command menus, route and error states, accessible controls, form
10
+ feedback, metadata, and mobile behavior.
11
+
12
+ ```bash
13
+ npx shadscan-vue
14
+ ```
15
+
16
+ It never starts your app, never edits files, never calls a model, and never
17
+ uploads your source. Every finding cites a file and a line.
18
+
19
+ ## What you get
20
+
21
+ ```
22
+ shadscan-vue v0.1.0
23
+ my-app · nuxt · pnpm
24
+
25
+ Score 66/100 · Grade D
26
+
27
+ Foundation ████████████████░░░░ 84
28
+ Interaction ██████████░░░░░░░░░░ 52
29
+ States ████████████████░░░░ 84
30
+ Accessibility ████████████░░░░░░░░ 61
31
+ Forms and Data Entry ███░░░░░░░░░░░░░░░░░ 18
32
+ Production Polish ████████████████░░░░ 78
33
+
34
+ Findings
35
+
36
+ FAIL images-have-alt (accessibility, error)
37
+ <img> is missing alternative text.
38
+ app/pages/index.vue:23
39
+ fix: Add an alt attribute describing the image, or bind :alt to dynamic content.
40
+ ```
41
+
42
+ ## Scoring
43
+
44
+ 52 rules across six weighted categories:
45
+
46
+ | Category | Weight |
47
+ | -------------------- | -----: |
48
+ | Foundation | 20 |
49
+ | Interaction | 20 |
50
+ | States | 20 |
51
+ | Accessibility | 20 |
52
+ | Forms and Data Entry | 10 |
53
+ | Production Polish | 10 |
54
+
55
+ Findings fall into four buckets. **Fixes** are provable failures that cost
56
+ points. **Advisories** are lower-confidence observations that never subtract
57
+ points. **Passes** are verified. **Not applicable** rules leave the score
58
+ untouched — a project without forms is not penalised for form rules.
59
+
60
+ Grades: A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 60, otherwise F.
61
+
62
+ ## Usage
63
+
64
+ ```bash
65
+ shadscan-vue [path] # audit a project (defaults to .)
66
+ shadscan-vue --json # machine-readable report
67
+ shadscan-vue --prompt # paste-ready prompt for an AI agent
68
+ shadscan-vue --category accessibility # run one category
69
+ shadscan-vue --fail-under 70 # exit 1 below a threshold
70
+ shadscan-vue rules # print the rule catalog
71
+ shadscan-vue setup --pre-commit # install a git pre-commit hook
72
+ ```
73
+
74
+ Exit codes are `0` (completed) and `1` (threshold not met, or an error).
75
+
76
+ ### Continuous integration
77
+
78
+ ```yaml
79
+ - run: npx shadscan-vue --fail-under 70 --no-interactive
80
+ ```
81
+
82
+ `--fail-under` also fails when the score is unassessed or when source coverage
83
+ was partial, so a silently-skipped scan cannot pass a gate.
84
+
85
+ ### Agent handoff
86
+
87
+ `--prompt` emits a remediation brief with the machine-readable report embedded
88
+ in a delimited block, marked as untrusted data rather than instructions.
89
+
90
+ ## Supported projects
91
+
92
+ | Adapter | Detected by |
93
+ | ------------- | ----------------------------- |
94
+ | `nuxt` | a `nuxt` dependency |
95
+ | `vite-vue` | `vue` and `vite` dependencies |
96
+ | `generic-vue` | a `vue` dependency |
97
+
98
+ shadcn components are recognised through `components.json` aliases, through the
99
+ `components/ui` convention, and through `shadcn-nuxt` auto-imports.
100
+
101
+ ## Credits
102
+
103
+ shadscan-vue is a Vue and Nuxt port of [shadscan](https://github.com/TheOrcDev/shadscan)
104
+ by [TheOrcDev](https://github.com/TheOrcDev), built with the original author's
105
+ permission. The rule taxonomy, scoring model, and product concept originate
106
+ there. See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
107
+
108
+ ## License
109
+
110
+ [MIT](./LICENSE.md) © Vinayak Kulkarni. Portions derived from shadscan, MIT © 2026 TheOrcDev.
@@ -0,0 +1,33 @@
1
+ # Third-Party Notices
2
+
3
+ ## shadscan
4
+
5
+ shadscan-vue is a Vue/Nuxt port of [shadscan](https://github.com/TheOrcDev/shadscan)
6
+ by [TheOrcDev](https://github.com/TheOrcDev), created with the original author's
7
+ permission. The rule taxonomy, scoring model, CLI contract, and product concept
8
+ originate from shadscan. Portions of this software are derived from or
9
+ substantially based on shadscan, which is licensed under the MIT License:
10
+
11
+ ```
12
+ MIT License
13
+
14
+ Copyright (c) 2026 TheOrcDev
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../dist/index.js';
3
+
4
+ const code = await run(process.argv);
5
+ process.exit(code);
@@ -0,0 +1,309 @@
1
+ import { SFCDescriptor, SFCParseResult } from "@vue/compiler-sfc";
2
+ import { RootNode } from "@vue/compiler-dom";
3
+ import ts from "typescript";
4
+ //#region src/cli.d.ts
5
+ declare const run: (argv: readonly string[]) => Promise<number>;
6
+ //#endregion
7
+ //#region src/discovery.d.ts
8
+ type AdapterId = 'nuxt' | 'vite-vue' | 'generic-vue';
9
+ type PackageManagerId = 'pnpm' | 'yarn' | 'npm' | 'bun' | 'unknown';
10
+ interface ShadcnAliases {
11
+ utils?: string;
12
+ components?: string;
13
+ ui?: string;
14
+ lib?: string;
15
+ composables?: string;
16
+ }
17
+ interface ShadcnDiscovery {
18
+ /** components.json exists and parsed cleanly. */
19
+ configPresent: boolean;
20
+ confidence: 'high' | 'low';
21
+ style?: string;
22
+ tailwindCss?: string;
23
+ aliases: ShadcnAliases;
24
+ /** Resolved alias for ui components (aliases.ui or `${aliases.components}/ui`). */
25
+ uiAlias?: string;
26
+ /** shadcn-nuxt module dependency detected. */
27
+ nuxtModule: boolean;
28
+ /** Statically-read shadcn-nuxt component prefix, when determinable. */
29
+ nuxtPrefix?: string;
30
+ }
31
+ interface ProjectDiscovery {
32
+ /** Absolute real path of the scanned project root. */
33
+ rootDir: string;
34
+ packageName: string;
35
+ packageManager: PackageManagerId;
36
+ adapter: AdapterId;
37
+ /** Merged dependencies + devDependencies from package.json. */
38
+ dependencies: Record<string, string>;
39
+ shadcn: ShadcnDiscovery;
40
+ warnings: string[];
41
+ }
42
+ declare const discoverProject: (inputPath: string) => Promise<ProjectDiscovery>;
43
+ //#endregion
44
+ //#region src/source-requirements.d.ts
45
+ type CoverageStatus = 'complete' | 'partial';
46
+ interface SourceCoverage {
47
+ status: CoverageStatus;
48
+ fileCount: number;
49
+ totalBytes: number;
50
+ warnings: string[];
51
+ }
52
+ //#endregion
53
+ //#region src/rules/source-files.d.ts
54
+ type SourceKind = 'vue' | 'ts' | 'js' | 'html';
55
+ interface SourceFile {
56
+ /** Absolute path. */
57
+ path: string;
58
+ /** Project-root-relative posix path. */
59
+ relPath: string;
60
+ kind: SourceKind;
61
+ text: string;
62
+ }
63
+ interface StyleFile {
64
+ path: string;
65
+ relPath: string;
66
+ text: string;
67
+ }
68
+ interface CollectedSources {
69
+ files: SourceFile[];
70
+ styles: StyleFile[];
71
+ coverage: SourceCoverage;
72
+ }
73
+ //#endregion
74
+ //#region src/parse/sfc.d.ts
75
+ interface ParsedSfc {
76
+ descriptor: SFCDescriptor;
77
+ templateAst?: RootNode;
78
+ errors: SFCParseResult['errors'];
79
+ }
80
+ //#endregion
81
+ //#region src/parse/typescript.d.ts
82
+ interface ScriptImport {
83
+ moduleSpecifier: string;
84
+ /** Imported binding names (local names after `as` renames use original name). */
85
+ named: string[];
86
+ defaultName?: string;
87
+ namespaceName?: string;
88
+ line: number;
89
+ }
90
+ //#endregion
91
+ //#region src/parse/project-files.d.ts
92
+ interface ParsedFile {
93
+ relPath: string;
94
+ path: string;
95
+ kind: SourceFile['kind'];
96
+ text: string;
97
+ /** Present for .vue files that parsed. */
98
+ sfc?: ParsedSfc;
99
+ /** TS AST of .ts/.js files, or of the SFC's script/scriptSetup content. */
100
+ scriptAst?: ts.SourceFile;
101
+ imports: ScriptImport[];
102
+ parseError?: string;
103
+ }
104
+ //#endregion
105
+ //#region src/rules/rule-result.d.ts
106
+ interface Evidence {
107
+ path: string;
108
+ line?: number;
109
+ }
110
+ interface Finding {
111
+ message: string;
112
+ evidence: Evidence[];
113
+ remediation?: string;
114
+ }
115
+ type RuleStatus = 'pass' | 'fail' | 'advisory' | 'not-applicable';
116
+ interface RuleResult {
117
+ status: RuleStatus;
118
+ findings: Finding[];
119
+ }
120
+ declare const pass: () => RuleResult;
121
+ declare const fail: (findings: Finding[]) => RuleResult;
122
+ declare const advisory: (findings: Finding[]) => RuleResult;
123
+ declare const notApplicable: () => RuleResult;
124
+ //#endregion
125
+ //#region src/audit.d.ts
126
+ type AuditCategoryId = 'foundation' | 'interaction' | 'states' | 'accessibility' | 'forms' | 'production-polish';
127
+ type Severity = 'error' | 'warning' | 'info';
128
+ type Confidence = 'high' | 'medium' | 'low';
129
+ interface CategoryDefinition {
130
+ id: AuditCategoryId;
131
+ title: string;
132
+ weight: number;
133
+ }
134
+ declare const CATEGORIES: readonly CategoryDefinition[];
135
+ interface AuditContext {
136
+ discovery: ProjectDiscovery;
137
+ sources: () => Promise<ParsedFile[]>;
138
+ styles: () => Promise<StyleFile[]>;
139
+ /** Reserved for the Vue component render graph (later wave). */
140
+ componentRenderGraph: () => never;
141
+ helpers: {
142
+ /**
143
+ * True when a module specifier resolves to the project's shadcn ui
144
+ * directory (`.../ui/<module>` or the configured ui alias).
145
+ */
146
+ isShadcnUiImport: (moduleSpecifier: string) => boolean;
147
+ };
148
+ result: {
149
+ pass: typeof pass;
150
+ fail: typeof fail;
151
+ advisory: typeof advisory;
152
+ notApplicable: typeof notApplicable;
153
+ };
154
+ }
155
+ interface AuditRule {
156
+ id: string;
157
+ title: string;
158
+ description: string;
159
+ category: AuditCategoryId;
160
+ severity: Severity;
161
+ confidence: Confidence;
162
+ maxScore: number;
163
+ adapters: readonly AdapterId[];
164
+ run: (context: AuditContext) => Promise<RuleResult> | RuleResult;
165
+ }
166
+ interface RuleOutcome {
167
+ rule: AuditRule;
168
+ status: RuleStatus;
169
+ findings: Finding[];
170
+ /** Points awarded (0 for fail; maxScore for pass/advisory). */
171
+ score: number;
172
+ impactsScore: boolean;
173
+ }
174
+ interface CategoryScore {
175
+ id: AuditCategoryId;
176
+ title: string;
177
+ weight: number;
178
+ /** 0-100 within the category, or undefined when no applicable scored rules. */
179
+ score?: number;
180
+ rawScore: number;
181
+ rawMax: number;
182
+ ruleCount: number;
183
+ }
184
+ interface AuditReport {
185
+ score?: number;
186
+ grade?: string;
187
+ categories: CategoryScore[];
188
+ outcomes: RuleOutcome[];
189
+ warnings: string[];
190
+ durationMs: number;
191
+ }
192
+ declare const gradeFor: (score: number) => string;
193
+ interface RunAuditOptions {
194
+ category?: AuditCategoryId;
195
+ }
196
+ declare const runAudit: (discovery: ProjectDiscovery, rules: readonly AuditRule[], options?: RunAuditOptions) => Promise<AuditReport & {
197
+ collected: CollectedSources;
198
+ }>;
199
+ //#endregion
200
+ //#region src/scan.d.ts
201
+ declare const RULESET_VERSION = "0.1.0";
202
+ interface ScanResult {
203
+ discovery: ProjectDiscovery;
204
+ report: AuditReport & {
205
+ collected: CollectedSources;
206
+ };
207
+ }
208
+ interface ScanOptions {
209
+ category?: AuditCategoryId;
210
+ }
211
+ declare const scanProject: (inputPath: string, options?: ScanOptions) => Promise<ScanResult>;
212
+ //#endregion
213
+ //#region src/rules/index.d.ts
214
+ /**
215
+ * Canonical ordered rule registry. Order is part of the deterministic output
216
+ * contract: rules run and render in registry order.
217
+ */
218
+ declare const defaultRules: readonly AuditRule[];
219
+ //#endregion
220
+ //#region src/render-json.d.ts
221
+ declare const JSON_SCHEMA_VERSION = 1;
222
+ interface JsonReport {
223
+ schemaVersion: number;
224
+ engineVersion: string;
225
+ rulesetVersion: string;
226
+ score: number | null;
227
+ maxScore: number;
228
+ grade: string | null;
229
+ framework: {
230
+ adapter: string;
231
+ packageName: string;
232
+ };
233
+ packageManager: string;
234
+ shadcn: {
235
+ configPresent: boolean;
236
+ confidence: string;
237
+ style?: string;
238
+ uiAlias?: string;
239
+ nuxtModule: boolean;
240
+ };
241
+ coverage: {
242
+ status: string;
243
+ fileCount: number;
244
+ warnings: string[];
245
+ };
246
+ categories: {
247
+ id: string;
248
+ title: string;
249
+ weight: number;
250
+ score: number | null;
251
+ ruleCount: number;
252
+ }[];
253
+ findings: {
254
+ id: string;
255
+ title: string;
256
+ category: string;
257
+ severity: string;
258
+ confidence: string;
259
+ status: string;
260
+ score: number;
261
+ maxScore: number;
262
+ impactsScore: boolean;
263
+ message: string | null;
264
+ evidence: {
265
+ path: string;
266
+ line?: number;
267
+ }[];
268
+ remediation: string | null;
269
+ }[];
270
+ warnings: string[];
271
+ durationMs: number;
272
+ }
273
+ declare const buildJsonReport: (result: ScanResult, engineVersion: string) => JsonReport;
274
+ declare const renderJson: (result: ScanResult, engineVersion: string) => string;
275
+ //#endregion
276
+ //#region src/terminal-capabilities.d.ts
277
+ /**
278
+ * Terminal capability resolution. Rich rendering (color, unicode) only on a
279
+ * capable interactive TTY; deterministic plain ASCII when piped or in CI.
280
+ */
281
+ interface TerminalCapabilities {
282
+ isTTY: boolean;
283
+ isCI: boolean;
284
+ color: boolean;
285
+ unicode: boolean;
286
+ }
287
+ //#endregion
288
+ //#region src/render-human.d.ts
289
+ declare const renderHuman: (result: ScanResult, engineVersion: string, caps?: TerminalCapabilities, roast?: boolean) => string;
290
+ //#endregion
291
+ //#region src/render-agent-prompt.d.ts
292
+ declare const PROMPT_VERSION = 1;
293
+ declare const renderAgentPrompt: (result: ScanResult, engineVersion: string) => string;
294
+ //#endregion
295
+ //#region src/cli-error.d.ts
296
+ /**
297
+ * Stable, typed CLI failures. Every expected failure path throws a `CliError`
298
+ * with a machine-readable `code`; anything else is an internal error.
299
+ */
300
+ type CliErrorCode = 'invalid-path' | 'not-a-project' | 'invalid-package-json' | 'unsupported-project' | 'invalid-flag' | 'conflicting-flags' | 'threshold-not-met' | 'internal-error';
301
+ declare class CliError extends Error {
302
+ readonly code: CliErrorCode;
303
+ readonly exitCode: number;
304
+ constructor(code: CliErrorCode, message: string, exitCode?: number);
305
+ }
306
+ declare const isCliError: (error: unknown) => error is CliError;
307
+ //#endregion
308
+ export { type AdapterId, type AuditCategoryId, type AuditContext, type AuditReport, type AuditRule, CATEGORIES, type CategoryScore, CliError, type CliErrorCode, type Confidence, type Evidence, type Finding, JSON_SCHEMA_VERSION, PROMPT_VERSION, type PackageManagerId, type ProjectDiscovery, RULESET_VERSION, type RuleOutcome, type RuleResult, type RuleStatus, type ScanOptions, type ScanResult, type Severity, type ShadcnDiscovery, advisory, buildJsonReport, defaultRules, discoverProject, fail, gradeFor, isCliError, notApplicable, pass, renderAgentPrompt, renderHuman, renderJson, run, runAudit, scanProject };
309
+ //# sourceMappingURL=index.d.ts.map