quiver-cli 1.3.0 → 1.3.1
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/template/.agents/skills/agent-browser/SKILL.md +1 -0
- package/template/.agents/skills/apps/skybridge/SKILL.md +2 -0
- package/template/.agents/skills/data/prisma-cli/SKILL.md +25 -7
- package/template/.agents/skills/data/prisma-cli/references/agent-safety.md +27 -0
- package/template/.agents/skills/data/prisma-cli/references/complete.md +22 -0
- package/template/.agents/skills/data/prisma-cli/references/db-push.md +2 -0
- package/template/.agents/skills/data/prisma-cli/references/init.md +3 -0
- package/template/.agents/skills/data/prisma-cli/references/mcp.md +2 -1
- package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +1 -1
- package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +2 -0
- package/template/.agents/skills/data/prisma-client-api/SKILL.md +1 -1
- package/template/.agents/skills/data/prisma-client-api/references/constructor.md +13 -0
- package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +4 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +5 -5
- package/template/.agents/skills/design/impeccable/reference/android.md +6 -0
- package/template/.agents/skills/design/impeccable/reference/animate.md +3 -0
- package/template/.agents/skills/design/impeccable/reference/bolder.md +3 -1
- package/template/.agents/skills/design/impeccable/reference/craft-floor.md +2 -0
- package/template/.agents/skills/design/impeccable/reference/critique.md +23 -5
- package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +15 -68
- package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +13 -12
- package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/doctor.md +1 -0
- package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/hooks.md +17 -11
- package/template/.agents/skills/design/impeccable/reference/init.md +9 -3
- package/template/.agents/skills/design/impeccable/reference/ios.md +6 -0
- package/template/.agents/skills/design/impeccable/reference/new-work.md +69 -29
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/polish.md +13 -5
- package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/routing.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/visualize.md +21 -22
- package/template/.agents/skills/design/impeccable/scripts/build-phase.mjs +1022 -0
- package/template/.agents/skills/design/impeccable/scripts/comp-diff.mjs +391 -0
- package/template/.agents/skills/design/impeccable/scripts/comp-spec.mjs +513 -0
- package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +297 -41
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +10 -19
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +124 -9
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +279 -19
- package/template/.agents/skills/design/impeccable/scripts/data/font-index-failures.json +121 -0
- package/template/.agents/skills/design/impeccable/scripts/data/font-index.json +1 -0
- package/template/.agents/skills/design/impeccable/scripts/detect.mjs +9 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +192 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +10 -16
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +339 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +1482 -722
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +1 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +64 -2
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +580 -29
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +62 -7
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +31 -7
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +23 -22
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +18 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +471 -370
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +474 -2
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +17 -2
- package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +14 -21
- package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +81 -48
- package/template/.agents/skills/design/impeccable/scripts/font-match.mjs +457 -0
- package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +219 -12
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +93 -15
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +41 -19
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +507 -117
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +10 -9
- package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +40 -1
- package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +120 -82
- package/template/.agents/skills/design/impeccable/scripts/lib/font-fingerprint.mjs +564 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/font-index.mjs +130 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/hero-checks.mjs +246 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/image-metrics.mjs +306 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +41 -59
- package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +5 -2
- package/template/.agents/skills/design/impeccable/scripts/lib/live-path-globs.mjs +37 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/open-system-browser.mjs +26 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/png.mjs +281 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/raster.mjs +194 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +26 -19
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +31 -3
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +93 -17
- package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +9 -11
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +31 -2
- package/template/.agents/skills/design/impeccable/scripts/live/project-ignores.mjs +139 -0
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +10 -2
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +26 -2
- package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +15 -27
- package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +4 -25
- package/template/.agents/skills/design/impeccable/scripts/live/ui-surfaces.mjs +75 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +21 -37
- package/template/.agents/skills/design/impeccable/scripts/live-browser-ignores.js +242 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +62 -23
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +68 -112
- package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +132 -15
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +2 -42
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +5 -4
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +56 -19
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +12 -37
- package/template/.agents/skills/design/impeccable/scripts/pin.mjs +8 -5
- package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +1006 -155
- package/template/.agents/skills/design/shadcn/rules/chat.md +26 -0
- package/template/.agents/skills/hono/SKILL.md +17 -3
- package/template/.agents/skills/integrations/langfuse/SKILL.md +8 -4
- package/template/.agents/skills/integrations/langfuse/references/cli.md +1 -1
- package/template/.agents/skills/integrations/langfuse/references/create-dataset.md +35 -0
- package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +9 -10
- package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +8 -6
- package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +34 -0
- package/template/.agents/skills/integrations/langfuse/references/setting-up-evals.md +65 -0
- package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +20 -40
- package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -30
- package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +3 -1
- package/template/.agents/skills/supabase/CHANGELOG.md +7 -0
- package/template/.agents/skills/supabase/SKILL.md +5 -1
- package/template/.agents/skills/writing/humanizer/LICENSE +21 -0
- package/template/.agents/skills/writing/humanizer/README.md +209 -0
- package/template/.agents/skills/writing/humanizer/SKILL.md +173 -338
- package/template/.agents/upstreams.json +21 -22
- package/template/.agents/skills/design/impeccable/scripts/live/ui-core.mjs +0 -180
|
@@ -226,6 +226,10 @@ const STATIC_INHERITED_PROPS = new Set([
|
|
|
226
226
|
'color', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'fontVariant',
|
|
227
227
|
'lineHeight', 'letterSpacing', 'textTransform', 'textAlign', 'hyphens',
|
|
228
228
|
'webkitHyphens',
|
|
229
|
+
// visibility inherits in real CSS, and the invisible-at-rest contrast skip
|
|
230
|
+
// relies on descendants of a hidden container computing as hidden. A child
|
|
231
|
+
// that declares `visibility: visible` still overrides the inherited value.
|
|
232
|
+
'visibility',
|
|
229
233
|
]);
|
|
230
234
|
|
|
231
235
|
const STATIC_DEFAULT_STYLE = {
|
|
@@ -278,6 +282,7 @@ const STATIC_DEFAULT_STYLE = {
|
|
|
278
282
|
marginLeft: '0px',
|
|
279
283
|
position: 'static',
|
|
280
284
|
visibility: 'visible',
|
|
285
|
+
opacity: '1',
|
|
281
286
|
top: 'auto',
|
|
282
287
|
right: 'auto',
|
|
283
288
|
bottom: 'auto',
|
|
@@ -334,6 +339,7 @@ const STATIC_PROP_MAP = {
|
|
|
334
339
|
'margin-left': 'marginLeft',
|
|
335
340
|
'position': 'position',
|
|
336
341
|
'visibility': 'visibility',
|
|
342
|
+
'opacity': 'opacity',
|
|
337
343
|
'top': 'top',
|
|
338
344
|
'right': 'right',
|
|
339
345
|
'bottom': 'bottom',
|
|
@@ -829,10 +835,11 @@ class StaticElement {
|
|
|
829
835
|
}
|
|
830
836
|
}
|
|
831
837
|
closest(selector) {
|
|
838
|
+
const matcher = this._doc.matcherFor(selector);
|
|
832
839
|
let cur = this.node;
|
|
833
840
|
while (cur && cur.type === 'tag') {
|
|
834
841
|
try {
|
|
835
|
-
if (
|
|
842
|
+
if (matcher(cur)) return this._doc.wrap(cur);
|
|
836
843
|
} catch {
|
|
837
844
|
return null;
|
|
838
845
|
}
|
|
@@ -856,9 +863,10 @@ class StaticDocument {
|
|
|
856
863
|
this.root = root;
|
|
857
864
|
this.selectAll = modules.selectAll;
|
|
858
865
|
this.selectOne = modules.selectOne;
|
|
859
|
-
this.
|
|
866
|
+
this.compile = modules.compile;
|
|
860
867
|
this.domutils = modules.domutils;
|
|
861
868
|
this._wrappers = new WeakMap();
|
|
869
|
+
this._compiledSelectors = new Map();
|
|
862
870
|
this._styleMap = new WeakMap();
|
|
863
871
|
this._hoverStyleMap = new WeakMap();
|
|
864
872
|
this._accentDashPseudo = new WeakSet();
|
|
@@ -876,6 +884,20 @@ class StaticDocument {
|
|
|
876
884
|
}
|
|
877
885
|
return wrapped;
|
|
878
886
|
}
|
|
887
|
+
matcherFor(selector) {
|
|
888
|
+
let matcher = this._compiledSelectors.get(selector);
|
|
889
|
+
if (!matcher) {
|
|
890
|
+
try {
|
|
891
|
+
matcher = this.compile(selector);
|
|
892
|
+
} catch (err) {
|
|
893
|
+
// Cache the failure as a rethrower so a bad selector still reaches
|
|
894
|
+
// closest()'s catch on every call, first and repeat alike.
|
|
895
|
+
matcher = () => { throw err; };
|
|
896
|
+
}
|
|
897
|
+
this._compiledSelectors.set(selector, matcher);
|
|
898
|
+
}
|
|
899
|
+
return matcher;
|
|
900
|
+
}
|
|
879
901
|
querySelectorAll(selector) {
|
|
880
902
|
try {
|
|
881
903
|
return this.selectAll(selector, this.root.children || []).map(node => this.wrap(node));
|
|
@@ -942,8 +964,34 @@ function buildStaticWindow(staticDoc) {
|
|
|
942
964
|
};
|
|
943
965
|
}
|
|
944
966
|
|
|
967
|
+
function resolveLinkedCssPath(fileDir, href) {
|
|
968
|
+
const stripped = href.split(/[?#]/)[0];
|
|
969
|
+
const rootRelative = stripped.startsWith('/') && !stripped.startsWith('//');
|
|
970
|
+
if (!rootRelative) return path.resolve(fileDir, stripped);
|
|
971
|
+
// Drop "." and reject ".." so /../outside.css cannot walk out of dir.
|
|
972
|
+
const segments = stripped.replace(/^\/+/, '').split(/[/\\]/).filter(p => p && p !== '.');
|
|
973
|
+
if (segments.some(p => p === '..')) return path.join(fileDir, segments.filter(p => p !== '..').join(path.sep));
|
|
974
|
+
const rel = segments.join(path.sep);
|
|
975
|
+
let dir = fileDir;
|
|
976
|
+
for (;;) {
|
|
977
|
+
const parent = path.dirname(dir);
|
|
978
|
+
if (parent === dir) break; // never use the filesystem root as document root
|
|
979
|
+
try {
|
|
980
|
+
const candidate = path.join(dir, rel);
|
|
981
|
+
if (fs.statSync(candidate).isFile()) return candidate;
|
|
982
|
+
} catch { /* missing or unreadable candidate */ }
|
|
983
|
+
// Stop at the project root so a coincidental ~/static/app.css cannot win.
|
|
984
|
+
try {
|
|
985
|
+
if (fs.existsSync(path.join(dir, 'package.json')) || fs.existsSync(path.join(dir, '.git'))) break;
|
|
986
|
+
} catch { /* unreadable marker */ }
|
|
987
|
+
dir = parent;
|
|
988
|
+
}
|
|
989
|
+
return path.join(fileDir, rel);
|
|
990
|
+
}
|
|
991
|
+
|
|
945
992
|
function collectStaticCssText(root, fileDir, profile, filePath, modules) {
|
|
946
993
|
const styleTexts = [];
|
|
994
|
+
const warnedMissingStylesheets = new Set();
|
|
947
995
|
for (const styleEl of modules.selectAll('style', root.children || [])) {
|
|
948
996
|
styleTexts.push(modules.domutils.textContent(styleEl));
|
|
949
997
|
}
|
|
@@ -952,10 +1000,10 @@ function collectStaticCssText(root, fileDir, profile, filePath, modules) {
|
|
|
952
1000
|
const rel = link.attribs?.rel || '';
|
|
953
1001
|
const href = link.attribs?.href || '';
|
|
954
1002
|
if (!/\bstylesheet\b/i.test(rel) || !href || /^(https?:)?\/\//i.test(href)) continue;
|
|
955
|
-
// Cache-busting
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
const cssPath =
|
|
1003
|
+
// Cache-busting (styles.css?v=3) and root-relative (/static/app.css) hrefs
|
|
1004
|
+
// must not resolve as OS-absolute paths; otherwise the whole stylesheet is
|
|
1005
|
+
// invisible to every element-level check.
|
|
1006
|
+
const cssPath = resolveLinkedCssPath(fileDir, href);
|
|
959
1007
|
try {
|
|
960
1008
|
const css = profileStep(profile, {
|
|
961
1009
|
engine: 'static-html',
|
|
@@ -965,7 +1013,14 @@ function collectStaticCssText(root, fileDir, profile, filePath, modules) {
|
|
|
965
1013
|
detail: href,
|
|
966
1014
|
}, () => fs.readFileSync(cssPath, 'utf-8'));
|
|
967
1015
|
styleTexts.push(css);
|
|
968
|
-
} catch {
|
|
1016
|
+
} catch {
|
|
1017
|
+
if (!warnedMissingStylesheets.has(cssPath)) {
|
|
1018
|
+
warnedMissingStylesheets.add(cssPath);
|
|
1019
|
+
process.stderr.write(
|
|
1020
|
+
`impeccable detect: could not read linked stylesheet ${href} (resolved to ${cssPath}); color and custom-property rules will be incomplete\n`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
969
1024
|
}
|
|
970
1025
|
return styleTexts.join('\n');
|
|
971
1026
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { OVERUSED_FONTS, primaryFontFace } from '../../shared/constants.mjs';
|
|
5
5
|
import {
|
|
6
6
|
checkSourceDesignSystem,
|
|
7
7
|
collectStaticDesignSystemFindings,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
checkCreamPalette,
|
|
29
29
|
checkHtmlPatterns,
|
|
30
30
|
checkKickerAboveHeadingFromDoc,
|
|
31
|
+
scopedIgnoreActive,
|
|
31
32
|
checkNumberedSectionLabelsFromDoc,
|
|
32
33
|
checkPageLayout,
|
|
33
34
|
checkPageQualityFromDoc,
|
|
@@ -50,9 +51,7 @@ function checkStaticPageTypography(document, window) {
|
|
|
50
51
|
for (const el of document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, dd, blockquote, figcaption, a, button, label, span, div')) {
|
|
51
52
|
const hasText = el.childNodes.some(n => n.nodeType === 3 && n.textContent.trim().length > 0);
|
|
52
53
|
if (!hasText) continue;
|
|
53
|
-
const
|
|
54
|
-
const stack = ff.split(',').map(f => f.trim().replace(/^['"]|['"]$/g, '').toLowerCase());
|
|
55
|
-
const primary = stack.find(f => f && !GENERIC_FONTS.has(f));
|
|
54
|
+
const primary = primaryFontFace(window.getComputedStyle(el).fontFamily);
|
|
56
55
|
if (!primary) continue;
|
|
57
56
|
fonts.add(primary);
|
|
58
57
|
if (OVERUSED_FONTS.has(primary)) overusedFound.add(primary);
|
|
@@ -133,15 +132,26 @@ async function detectHtml(filePath, options = {}) {
|
|
|
133
132
|
parseDocument: htmlparser2.parseDocument,
|
|
134
133
|
selectAll: cssSelect.selectAll,
|
|
135
134
|
selectOne: cssSelect.selectOne,
|
|
136
|
-
|
|
135
|
+
compile: cssSelect.compile,
|
|
137
136
|
csstree,
|
|
138
137
|
domutils,
|
|
139
138
|
};
|
|
140
139
|
});
|
|
141
|
-
} catch {
|
|
142
|
-
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (!globalThis.__impeccableStaticHtmlWarned) {
|
|
142
|
+
globalThis.__impeccableStaticHtmlWarned = true;
|
|
143
|
+
|
|
144
|
+
process.stderr.write(
|
|
145
|
+
'impeccable detect: DEGRADED - HTML parser modules unavailable ' +
|
|
146
|
+
'(htmlparser2, css-select, css-tree, domutils).\n' +
|
|
147
|
+
'Falling back to regex matching. Custom properties, selector matching and computed ' +
|
|
148
|
+
'contrast are NOT evaluated; findings are an undercount, not a clean bill of health.\n'
|
|
149
|
+
);
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
return detectText(html, filePath, options);
|
|
153
|
+
}
|
|
154
|
+
|
|
145
155
|
const resolvedPath = path.resolve(filePath);
|
|
146
156
|
const fileDir = path.dirname(resolvedPath);
|
|
147
157
|
const root = profileStep(profile, {
|
|
@@ -171,6 +181,9 @@ async function detectHtml(filePath, options = {}) {
|
|
|
171
181
|
const tag = el.tagName.toLowerCase();
|
|
172
182
|
const style = window.getComputedStyle(el);
|
|
173
183
|
for (const f of runElementCheck(rule.id, () => rule.run(el, tag, style, window, customPropMap))) {
|
|
184
|
+
// Element-scoped waivers: a data-impeccable-ignore ancestor suppresses
|
|
185
|
+
// matching findings for its subtree, same as the browser walk.
|
|
186
|
+
if (scopedIgnoreActive(el, f.id)) continue;
|
|
174
187
|
findings.push(finding(f.id, filePath, f.snippet));
|
|
175
188
|
}
|
|
176
189
|
}
|
|
@@ -238,6 +251,17 @@ async function detectHtml(filePath, options = {}) {
|
|
|
238
251
|
for (const f of runPageCheck('html-patterns', () => checkHtmlPatterns(html, patternCorpora).filter(item =>
|
|
239
252
|
item.id !== 'bounce-easing' && item.id !== 'layout-transition'
|
|
240
253
|
))) {
|
|
254
|
+
// Selector-backed page findings honor scoped waivers here too, matching
|
|
255
|
+
// the browser pass: resolve the selector and drop the finding when an
|
|
256
|
+
// ignoring ancestor covers a match. Unlike the browser, an unmatched
|
|
257
|
+
// selector keeps the finding — static scans see partial documents.
|
|
258
|
+
if (f.selector) {
|
|
259
|
+
let matches = null;
|
|
260
|
+
try {
|
|
261
|
+
matches = document.querySelectorAll(String(f.selector).replace(/::?[a-zA-Z-]+(\([^)]*\))?/g, '').trim());
|
|
262
|
+
} catch { matches = null; }
|
|
263
|
+
if (matches && matches.length > 0 && [...matches].every(el => scopedIgnoreActive(el, f.id))) continue;
|
|
264
|
+
}
|
|
241
265
|
const item = finding(f.id, filePath, f.snippet);
|
|
242
266
|
// Position-aware severity promotion: checks may attach a per-finding
|
|
243
267
|
// severity (e.g. a pulsing dot inside a header/nav landmark) that
|
|
@@ -26,11 +26,26 @@ const HIDDEN_SOURCE_DIRS = new Set(['.vitepress', '.vuepress', '.storybook']);
|
|
|
26
26
|
const SCANNABLE_EXTENSIONS = new Set([
|
|
27
27
|
'.html', '.htm', '.css', '.scss', '.sass', '.less',
|
|
28
28
|
'.jsx', '.tsx', '.js', '.ts',
|
|
29
|
-
'.vue', '.svelte', '.astro',
|
|
29
|
+
'.vue', '.svelte', '.astro', '.blade.php',
|
|
30
30
|
]);
|
|
31
31
|
|
|
32
32
|
const HTML_EXTENSIONS = new Set(['.html', '.htm']);
|
|
33
33
|
|
|
34
|
+
function hasScannableExtension(filename) {
|
|
35
|
+
const lower = filename.toLowerCase();
|
|
36
|
+
if (SCANNABLE_EXTENSIONS.has(path.extname(lower))) return true;
|
|
37
|
+
for (const ext of SCANNABLE_EXTENSIONS) {
|
|
38
|
+
if (ext.indexOf('.', 1) !== -1 && lower.endsWith(ext)) return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const IMPORT_SPECIFIER_PATTERNS = [
|
|
44
|
+
/import\s+(?:[\s\S]*?from\s+)?['"]([^'"]+)['"]/g,
|
|
45
|
+
/@import\s+(?:url\(\s*)?['"]?([^'");\s]+)['"]?\s*\)?/g,
|
|
46
|
+
/@(?:use|forward)\s+['"]([^'"]+)['"]/g,
|
|
47
|
+
];
|
|
48
|
+
|
|
34
49
|
function walkDir(dir) {
|
|
35
50
|
const files = [];
|
|
36
51
|
let entries;
|
|
@@ -40,7 +55,7 @@ function walkDir(dir) {
|
|
|
40
55
|
if (entry.isDirectory() && entry.name.startsWith('.') && !HIDDEN_SOURCE_DIRS.has(entry.name)) continue;
|
|
41
56
|
const full = path.join(dir, entry.name);
|
|
42
57
|
if (entry.isDirectory()) files.push(...walkDir(full));
|
|
43
|
-
else if (
|
|
58
|
+
else if (hasScannableExtension(entry.name)) files.push(full);
|
|
44
59
|
}
|
|
45
60
|
return files;
|
|
46
61
|
}
|
|
@@ -75,26 +90,11 @@ function buildImportGraph(files) {
|
|
|
75
90
|
const dir = path.dirname(file);
|
|
76
91
|
const imports = new Set();
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (resolved) imports.add(resolved);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// CSS @import
|
|
87
|
-
const cssRe = /@import\s+(?:url\(\s*)?['"]?([^'");\s]+)['"]?\s*\)?/g;
|
|
88
|
-
while ((m = cssRe.exec(content)) !== null) {
|
|
89
|
-
const resolved = resolveImport(m[1], dir, fileSet);
|
|
90
|
-
if (resolved) imports.add(resolved);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// SCSS @use / @forward
|
|
94
|
-
const scssRe = /@(?:use|forward)\s+['"]([^'"]+)['"]/g;
|
|
95
|
-
while ((m = scssRe.exec(content)) !== null) {
|
|
96
|
-
const resolved = resolveImport(m[1], dir, fileSet);
|
|
97
|
-
if (resolved) imports.add(resolved);
|
|
93
|
+
for (const pattern of IMPORT_SPECIFIER_PATTERNS) {
|
|
94
|
+
for (const match of content.matchAll(pattern)) {
|
|
95
|
+
const resolved = resolveImport(match[1], dir, fileSet);
|
|
96
|
+
if (resolved) imports.add(resolved);
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
graph.set(file, imports);
|
|
@@ -203,6 +203,7 @@ export {
|
|
|
203
203
|
SKIP_DIRS,
|
|
204
204
|
SCANNABLE_EXTENSIONS,
|
|
205
205
|
HTML_EXTENSIONS,
|
|
206
|
+
hasScannableExtension,
|
|
206
207
|
walkDir,
|
|
207
208
|
resolveImport,
|
|
208
209
|
buildImportGraph,
|
package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs
CHANGED
|
@@ -121,6 +121,24 @@ const ANTIPATTERNS = [
|
|
|
121
121
|
'A large inline SVG that builds a pictorial scene from a pile of primitive shapes reads as placeholder clip art, not illustration. Icons, logos, and data graphics are fine at their scale; a hero-sized visual deserves real artwork, a photograph, or a deliberately drawn graphic.',
|
|
122
122
|
skillSection: 'Imagery',
|
|
123
123
|
},
|
|
124
|
+
{
|
|
125
|
+
id: 'organic-clip-path',
|
|
126
|
+
category: 'quality',
|
|
127
|
+
name: 'Organic contour drawn as clip-path',
|
|
128
|
+
description:
|
|
129
|
+
'A clip-path polygon with many arbitrary vertices, or a curved clip-path path(), is CSS approximating a torn edge, blob, or silhouette. It reads as the cheap version of the effect and is usually a produced or photographic material replaced with code. Derive an alpha matte from the real image, or ship the shape as a cut-out raster; keep clip-path for geometry (cut corners, diagonals, hexagons).',
|
|
130
|
+
skillSection: 'Imagery',
|
|
131
|
+
skillGuideline: 'geometric masks standing in for organic contours',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'buried-raster',
|
|
135
|
+
category: 'quality',
|
|
136
|
+
name: 'Raster buried under a wash or opacity',
|
|
137
|
+
description:
|
|
138
|
+
'A background image under a near-opaque gradient wash, or a raster on an element at near-zero opacity, never reaches the screen: the page shows the wash, and the produced texture or photo ships as a compliance token. Let the material show (a tint under 0.9 alpha, a blend mode, an opacity you can see) or remove the file.',
|
|
139
|
+
skillSection: 'Imagery',
|
|
140
|
+
skillGuideline: 'a produced material must survive to the screen',
|
|
141
|
+
},
|
|
124
142
|
{
|
|
125
143
|
id: 'dark-glow',
|
|
126
144
|
category: 'slop',
|