self-evolve-framework 1.1.0 → 1.2.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.
Files changed (220) hide show
  1. package/package.json +1 -1
  2. package/template/skills/impeccable/SKILL.md +174 -47
  3. package/template/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
  4. package/template/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
  5. package/template/skills/impeccable/agents/openai.yaml +4 -0
  6. package/template/skills/impeccable/reference/adapt.md +311 -0
  7. package/template/skills/impeccable/reference/animate.md +201 -0
  8. package/template/skills/impeccable/reference/audit.md +133 -0
  9. package/template/skills/impeccable/reference/bolder.md +113 -0
  10. package/template/skills/impeccable/reference/brand.md +108 -0
  11. package/template/skills/impeccable/reference/clarify.md +288 -0
  12. package/template/skills/impeccable/reference/codex.md +105 -0
  13. package/template/skills/impeccable/reference/colorize.md +257 -0
  14. package/template/skills/impeccable/reference/craft.md +123 -0
  15. package/template/skills/impeccable/reference/critique.md +790 -0
  16. package/template/skills/impeccable/reference/delight.md +302 -0
  17. package/template/skills/impeccable/reference/distill.md +111 -0
  18. package/template/skills/impeccable/reference/document.md +429 -0
  19. package/template/skills/impeccable/reference/extract.md +69 -0
  20. package/template/skills/impeccable/reference/harden.md +347 -0
  21. package/template/skills/impeccable/reference/hooks.md +90 -0
  22. package/template/skills/impeccable/reference/init.md +172 -0
  23. package/template/skills/impeccable/reference/interaction-design.md +189 -0
  24. package/template/skills/impeccable/reference/layout.md +161 -0
  25. package/template/skills/impeccable/reference/live.md +720 -0
  26. package/template/skills/impeccable/reference/onboard.md +234 -0
  27. package/template/skills/impeccable/reference/optimize.md +258 -0
  28. package/template/skills/impeccable/reference/overdrive.md +130 -0
  29. package/template/skills/impeccable/reference/polish.md +241 -0
  30. package/template/skills/impeccable/reference/product.md +60 -0
  31. package/template/skills/impeccable/reference/quieter.md +99 -0
  32. package/template/skills/impeccable/reference/shape.md +165 -0
  33. package/template/skills/impeccable/reference/typeset.md +279 -0
  34. package/template/skills/impeccable/scripts/command-metadata.json +94 -0
  35. package/template/skills/impeccable/scripts/context-signals.mjs +225 -0
  36. package/template/skills/impeccable/scripts/context.mjs +961 -0
  37. package/template/skills/impeccable/scripts/critique-storage.mjs +242 -0
  38. package/template/skills/impeccable/scripts/detect-csp.mjs +198 -0
  39. package/template/skills/impeccable/scripts/detect.mjs +21 -0
  40. package/template/skills/impeccable/scripts/detector/browser/injected/index.mjs +1937 -0
  41. package/template/skills/impeccable/scripts/detector/cli/main.mjs +290 -0
  42. package/template/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  43. package/template/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +5138 -0
  44. package/template/skills/impeccable/scripts/detector/detect-antipatterns.mjs +50 -0
  45. package/template/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +277 -0
  46. package/template/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +568 -0
  47. package/template/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +1015 -0
  48. package/template/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +234 -0
  49. package/template/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  50. package/template/skills/impeccable/scripts/detector/findings.mjs +12 -0
  51. package/template/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
  52. package/template/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  53. package/template/skills/impeccable/scripts/detector/registry/antipatterns.mjs +448 -0
  54. package/template/skills/impeccable/scripts/detector/rules/checks.mjs +2671 -0
  55. package/template/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  56. package/template/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
  57. package/template/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  58. package/template/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  59. package/template/skills/impeccable/scripts/hook-admin.mjs +661 -0
  60. package/template/skills/impeccable/scripts/hook-before-edit.mjs +476 -0
  61. package/template/skills/impeccable/scripts/hook-lib.mjs +1632 -0
  62. package/template/skills/impeccable/scripts/hook.mjs +61 -0
  63. package/template/skills/impeccable/scripts/lib/design-parser.mjs +842 -0
  64. package/template/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  65. package/template/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  66. package/template/skills/impeccable/scripts/lib/is-generated.mjs +69 -0
  67. package/template/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  68. package/template/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  69. package/template/skills/impeccable/scripts/live/completion.mjs +19 -0
  70. package/template/skills/impeccable/scripts/live/event-validation.mjs +137 -0
  71. package/template/skills/impeccable/scripts/live/insert-ui.mjs +458 -0
  72. package/template/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  73. package/template/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  74. package/template/skills/impeccable/scripts/live/manual-edits-buffer.mjs +152 -0
  75. package/template/skills/impeccable/scripts/live/session-store.mjs +289 -0
  76. package/template/skills/impeccable/scripts/live/svelte-component.mjs +826 -0
  77. package/template/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  78. package/template/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  79. package/template/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  80. package/template/skills/impeccable/scripts/live-accept.mjs +812 -0
  81. package/template/skills/impeccable/scripts/live-browser-dom.js +146 -0
  82. package/template/skills/impeccable/scripts/live-browser-session.js +123 -0
  83. package/template/skills/impeccable/scripts/live-browser.js +11173 -0
  84. package/template/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
  85. package/template/skills/impeccable/scripts/live-complete.mjs +75 -0
  86. package/template/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  87. package/template/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  88. package/template/skills/impeccable/scripts/live-inject.mjs +583 -0
  89. package/template/skills/impeccable/scripts/live-insert.mjs +272 -0
  90. package/template/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
  91. package/template/skills/impeccable/scripts/live-poll.mjs +384 -0
  92. package/template/skills/impeccable/scripts/live-resume.mjs +94 -0
  93. package/template/skills/impeccable/scripts/live-server.mjs +1135 -0
  94. package/template/skills/impeccable/scripts/live-status.mjs +61 -0
  95. package/template/skills/impeccable/scripts/live-target.mjs +30 -0
  96. package/template/skills/impeccable/scripts/live-wrap.mjs +894 -0
  97. package/template/skills/impeccable/scripts/live.mjs +297 -0
  98. package/template/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  99. package/template/skills/impeccable/scripts/palette.mjs +633 -0
  100. package/template/skills/impeccable/scripts/pin.mjs +214 -0
  101. package/template/rules/CodeGraph.mdc +0 -23
  102. package/template/rules/Svelte_5.mdc +0 -167
  103. package/template/rules/Svelte_Flow.mdc +0 -176
  104. package/template/rules/Tailwind_CSS_v4.mdc +0 -187
  105. package/template/rules/Tauri.mdc +0 -145
  106. package/template/rules/app-error-pattern.mdc +0 -65
  107. package/template/rules/invoke-safe-pattern.mdc +0 -53
  108. package/template/rules/js.mdc +0 -10
  109. package/template/rules/powershell.mdc +0 -9
  110. package/template/rules//346/227/245/345/277/227.mdc +0 -15
  111. package/template/rules//350/257/267/346/261/202.mdc +0 -49
  112. package/template/skills/caveman/SKILL.md +0 -49
  113. package/template/skills/check/SKILL.md +0 -393
  114. package/template/skills/check/agents/reviewer-architecture.md +0 -39
  115. package/template/skills/check/agents/reviewer-security.md +0 -39
  116. package/template/skills/check/references/persona-catalog.md +0 -56
  117. package/template/skills/check/references/project-context.md +0 -120
  118. package/template/skills/check/references/public-reply.md +0 -14
  119. package/template/skills/check/scripts/audit_signals.py +0 -666
  120. package/template/skills/check/scripts/run-tests.sh +0 -19
  121. package/template/skills/design/SKILL.md +0 -173
  122. package/template/skills/design/references/design-aesthetic-quality.md +0 -67
  123. package/template/skills/design/references/design-data-viz.md +0 -34
  124. package/template/skills/design/references/design-reference.md +0 -295
  125. package/template/skills/design/references/design-tokens.md +0 -45
  126. package/template/skills/design/references/design-traps.md +0 -43
  127. package/template/skills/design-an-interface/SKILL.md +0 -94
  128. package/template/skills/diagnose/SKILL.md +0 -117
  129. package/template/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
  130. package/template/skills/edit-article/SKILL.md +0 -14
  131. package/template/skills/git-guardrails-claude-code/SKILL.md +0 -95
  132. package/template/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +0 -25
  133. package/template/skills/grill-me/SKILL.md +0 -10
  134. package/template/skills/grill-with-docs/ADR-FORMAT.md +0 -47
  135. package/template/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -60
  136. package/template/skills/grill-with-docs/SKILL.md +0 -88
  137. package/template/skills/handoff/SKILL.md +0 -15
  138. package/template/skills/health/SKILL.md +0 -260
  139. package/template/skills/health/agents/inspector-context.md +0 -119
  140. package/template/skills/health/agents/inspector-control.md +0 -84
  141. package/template/skills/health/agents/inspector-maintainability.md +0 -55
  142. package/template/skills/health/scripts/check-agent-context.sh +0 -5
  143. package/template/skills/health/scripts/check-doc-refs.sh +0 -8
  144. package/template/skills/health/scripts/check-maintainability.sh +0 -8
  145. package/template/skills/health/scripts/check-verifier-output.sh +0 -5
  146. package/template/skills/health/scripts/check_agent_context.py +0 -444
  147. package/template/skills/health/scripts/check_doc_refs.py +0 -110
  148. package/template/skills/health/scripts/check_maintainability.py +0 -635
  149. package/template/skills/health/scripts/check_verifier_output.py +0 -116
  150. package/template/skills/health/scripts/collect-data.sh +0 -751
  151. package/template/skills/hunt/SKILL.md +0 -232
  152. package/template/skills/hunt/references/failure-patterns.md +0 -138
  153. package/template/skills/hunt/references/ime-unicode.md +0 -58
  154. package/template/skills/hunt/references/logging-techniques.md +0 -72
  155. package/template/skills/hunt/references/rendering-debug.md +0 -34
  156. package/template/skills/improve-codebase-architecture/DEEPENING.md +0 -37
  157. package/template/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  158. package/template/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
  159. package/template/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
  160. package/template/skills/improve-codebase-architecture/SKILL.md +0 -81
  161. package/template/skills/learn/SKILL.md +0 -140
  162. package/template/skills/migrate-to-shoehorn/SKILL.md +0 -118
  163. package/template/skills/obsidian-vault/SKILL.md +0 -59
  164. package/template/skills/prototype/LOGIC.md +0 -79
  165. package/template/skills/prototype/SKILL.md +0 -30
  166. package/template/skills/prototype/UI.md +0 -112
  167. package/template/skills/qa/SKILL.md +0 -130
  168. package/template/skills/read/SKILL.md +0 -141
  169. package/template/skills/read/references/read-methods.md +0 -129
  170. package/template/skills/read/scripts/fetch.sh +0 -106
  171. package/template/skills/read/scripts/fetch_feishu.py +0 -251
  172. package/template/skills/read/scripts/fetch_local.py +0 -218
  173. package/template/skills/read/scripts/fetch_weixin.py +0 -107
  174. package/template/skills/request-refactor-plan/SKILL.md +0 -68
  175. package/template/skills/review/SKILL.md +0 -78
  176. package/template/skills/rust-auto-fix/SKILL.md +0 -94
  177. package/template/skills/scaffold-exercises/SKILL.md +0 -106
  178. package/template/skills/sdd-dev/SKILL.md +0 -114
  179. package/template/skills/setup-matt-pocock-skills/SKILL.md +0 -121
  180. package/template/skills/setup-matt-pocock-skills/domain.md +0 -51
  181. package/template/skills/setup-matt-pocock-skills/issue-tracker-github.md +0 -22
  182. package/template/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +0 -23
  183. package/template/skills/setup-matt-pocock-skills/issue-tracker-local.md +0 -19
  184. package/template/skills/setup-matt-pocock-skills/triage-labels.md +0 -15
  185. package/template/skills/setup-pre-commit/SKILL.md +0 -91
  186. package/template/skills/svelte-warnings-fix/SKILL.md +0 -94
  187. package/template/skills/tauri-nsis-installer-icon/SKILL.md +0 -92
  188. package/template/skills/tauri-nsis-installer-icon/references/tauri-nsis-schema.md +0 -71
  189. package/template/skills/tb/SKILL.md +0 -62
  190. package/template/skills/tdd/SKILL.md +0 -109
  191. package/template/skills/tdd/deep-modules.md +0 -33
  192. package/template/skills/tdd/interface-design.md +0 -31
  193. package/template/skills/tdd/mocking.md +0 -59
  194. package/template/skills/tdd/refactoring.md +0 -10
  195. package/template/skills/tdd/tests.md +0 -61
  196. package/template/skills/teach/GLOSSARY-FORMAT.md +0 -35
  197. package/template/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  198. package/template/skills/teach/MISSION-FORMAT.md +0 -31
  199. package/template/skills/teach/RESOURCES-FORMAT.md +0 -32
  200. package/template/skills/teach/SKILL.md +0 -91
  201. package/template/skills/think/SKILL.md +0 -184
  202. package/template/skills/to-issues/SKILL.md +0 -83
  203. package/template/skills/to-prd/SKILL.md +0 -74
  204. package/template/skills/triage/AGENT-BRIEF.md +0 -168
  205. package/template/skills/triage/OUT-OF-SCOPE.md +0 -101
  206. package/template/skills/triage/SKILL.md +0 -103
  207. package/template/skills/ubiquitous-language/SKILL.md +0 -93
  208. package/template/skills/ver/SKILL.md +0 -62
  209. package/template/skills/write/SKILL.md +0 -209
  210. package/template/skills/write/references/write-en.md +0 -199
  211. package/template/skills/write/references/write-product-localization.md +0 -43
  212. package/template/skills/write/references/write-zh-bilingual.md +0 -59
  213. package/template/skills/write/references/write-zh-prose.md +0 -50
  214. package/template/skills/write/references/write-zh-release-notes.md +0 -40
  215. package/template/skills/write/references/write-zh.md +0 -721
  216. package/template/skills/write-a-skill/SKILL.md +0 -117
  217. package/template/skills/writing-beats/SKILL.md +0 -52
  218. package/template/skills/writing-fragments/SKILL.md +0 -75
  219. package/template/skills/writing-shape/SKILL.md +0 -64
  220. package/template/skills/zoom-out/SKILL.md +0 -7
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Anti-Pattern Detector for Impeccable
5
+ * Copyright (c) 2026 Paul Bakaus
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ *
8
+ * Public API facade. Runtime engines live under cli/engine/engines/.
9
+ */
10
+
11
+ import { detectCli } from './cli/main.mjs';
12
+
13
+ export { ANTIPATTERNS, RULE_ENGINE_SUPPORT, getAntipattern, getRulesForCategory, getRuleEngineSupport } from './registry/antipatterns.mjs';
14
+ export { SAFE_TAGS, BORDER_SAFE_TAGS, OVERUSED_FONTS, GENERIC_FONTS, KNOWN_SERIF_FONTS } from './shared/constants.mjs';
15
+ export { isNeutralColor, parseRgb, relativeLuminance, contrastRatio, parseGradientColors, hasChroma, getHue, colorToHex } from './shared/color.mjs';
16
+ export { isFullPage } from './shared/page.mjs';
17
+ export {
18
+ checkElementBorders,
19
+ checkElementMotion,
20
+ checkElementGlow,
21
+ checkPageTypography,
22
+ checkPageLayout,
23
+ checkHtmlPatterns,
24
+ } from './rules/checks.mjs';
25
+ export { createDetectorProfile, summarizeDetectorProfile } from './profile/profiler.mjs';
26
+ export {
27
+ parseFrontmatter as parseDesignFrontmatter,
28
+ normalizeDesignSystem,
29
+ loadDesignSystemForCwd,
30
+ checkSourceDesignSystem,
31
+ collectStaticDesignSystemFindings,
32
+ } from './design-system.mjs';
33
+ export { detectHtml } from './engines/static-html/detect-html.mjs';
34
+ export { detectUrl, createBrowserDetector } from './engines/browser/detect-url.mjs';
35
+ export { detectText, extractStyleBlocks, extractCSSinJS } from './engines/regex/detect-text.mjs';
36
+ export {
37
+ walkDir,
38
+ SCANNABLE_EXTENSIONS,
39
+ SKIP_DIRS,
40
+ buildImportGraph,
41
+ resolveImport,
42
+ detectFrameworkConfig,
43
+ isPortListening,
44
+ FRAMEWORK_CONFIGS,
45
+ } from './node/file-system.mjs';
46
+ export { formatFindings, detectCli } from './cli/main.mjs';
47
+
48
+ const isMainModule = process.argv[1]?.endsWith('detect-antipatterns.mjs') ||
49
+ process.argv[1]?.endsWith('detect-antipatterns.mjs/');
50
+ if (isMainModule) detectCli();
@@ -0,0 +1,277 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { finding } from '../../findings.mjs';
6
+ import { filterByProviders } from '../../registry/antipatterns.mjs';
7
+ import { profileFindingsAsync, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
8
+ import { captureVisualContrastCandidate } from '../visual/screenshot-contrast.mjs';
9
+
10
+ function serializeDesignSystemForBrowser(designSystem) {
11
+ if (!designSystem?.present) return null;
12
+ return {
13
+ present: true,
14
+ hasFonts: designSystem.hasFonts === true,
15
+ allowedFonts: Array.from(designSystem.allowedFonts || []),
16
+ hasColors: designSystem.hasColors === true,
17
+ allowedColors: Array.from(designSystem.allowedColorKeys?.values?.() || [])
18
+ .map(entry => entry?.color)
19
+ .filter(color => color && Number.isFinite(color.r) && Number.isFinite(color.g) && Number.isFinite(color.b))
20
+ .map(color => ({ r: color.r, g: color.g, b: color.b })),
21
+ hasRadii: designSystem.hasRadii === true,
22
+ allowedRadii: (designSystem.allowedRadii || [])
23
+ .map(entry => Number(entry?.px))
24
+ .filter(px => Number.isFinite(px)),
25
+ hasPillRadius: designSystem.hasPillRadius === true,
26
+ };
27
+ }
28
+
29
+ async function runVisualContrastFallback(page, serializedGroups, options, profile, target) {
30
+ if (options?.visualContrast === false) return [];
31
+ const maxCandidates = Number.isFinite(options?.visualContrastMaxCandidates)
32
+ ? options.visualContrastMaxCandidates
33
+ : 12;
34
+ const scrollOffscreen = options?.visualContrastScrollOffscreen !== false;
35
+ const existingLowContrastSelectors = new Set(
36
+ serializedGroups
37
+ .filter(group => group.findings?.some(f => f.type === 'low-contrast'))
38
+ .map(group => group.selector)
39
+ .filter(Boolean)
40
+ );
41
+
42
+ let browserAnalyses = [];
43
+ const findings = [];
44
+ if (options?.visualContrastBrowser !== false) {
45
+ const browserFindings = await profileFindingsAsync(profile, {
46
+ engine: 'browser',
47
+ phase: 'visual-contrast',
48
+ ruleId: 'browser-fallback',
49
+ target,
50
+ }, async () => {
51
+ browserAnalyses = await page.evaluate(async ({ maxCandidates, scrollOffscreen }) => {
52
+ if (typeof window.impeccableAnalyzeVisualContrast !== 'function') return [];
53
+ return window.impeccableAnalyzeVisualContrast({ maxCandidates, scrollOffscreen });
54
+ }, { maxCandidates, scrollOffscreen });
55
+ return browserAnalyses
56
+ .filter(result => result.finding && !existingLowContrastSelectors.has(result.selector))
57
+ .map(result => result.finding);
58
+ });
59
+ findings.push(...browserFindings);
60
+ }
61
+
62
+ let candidates = browserAnalyses.length > 0 ? browserAnalyses : [];
63
+ if (candidates.length === 0) {
64
+ candidates = await profileStepAsync(profile, {
65
+ engine: 'browser',
66
+ phase: 'visual-contrast',
67
+ ruleId: 'collect-candidates',
68
+ target,
69
+ }, () => page.evaluate(({ maxCandidates }) => {
70
+ if (typeof window.impeccableCollectVisualContrastCandidates !== 'function') return [];
71
+ return window.impeccableCollectVisualContrastCandidates({ maxCandidates });
72
+ }, { maxCandidates }));
73
+ }
74
+
75
+ const viewport = options?.viewport || { width: 1280, height: 800 };
76
+ const browserResolvedSelectors = new Set(
77
+ browserAnalyses
78
+ .filter(result => result.status === 'fail' || result.status === 'pass')
79
+ .map(result => result.selector)
80
+ .filter(Boolean)
81
+ );
82
+ const filtered = candidates.filter(candidate =>
83
+ !existingLowContrastSelectors.has(candidate.selector) &&
84
+ !browserResolvedSelectors.has(candidate.selector)
85
+ );
86
+ if (options?.visualContrastPixel === false) return findings;
87
+ for (const candidate of filtered) {
88
+ const result = await profileFindingsAsync(profile, {
89
+ engine: 'browser',
90
+ phase: 'visual-contrast',
91
+ ruleId: 'pixel-diff',
92
+ target,
93
+ }, async () => {
94
+ const finding = await captureVisualContrastCandidate(page, candidate, viewport);
95
+ return finding ? [finding] : [];
96
+ });
97
+ findings.push(...result);
98
+ }
99
+ return findings;
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Puppeteer detection (for URLs)
104
+ // ---------------------------------------------------------------------------
105
+
106
+ async function detectUrl(url, options = {}) {
107
+ const profile = options?.profile;
108
+ const waitUntil = options?.waitUntil || 'networkidle0';
109
+ const settleMs = Number.isFinite(options?.settleMs) ? options.settleMs : 0;
110
+ const viewport = options?.viewport || { width: 1280, height: 800 };
111
+ const externalBrowser = options?.browser || null;
112
+ let puppeteer;
113
+ if (!externalBrowser) {
114
+ try {
115
+ puppeteer = await profileStepAsync(profile, {
116
+ engine: 'browser',
117
+ phase: 'setup',
118
+ ruleId: 'import-puppeteer',
119
+ target: url,
120
+ }, () => import('puppeteer'));
121
+ } catch {
122
+ throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');
123
+ }
124
+ }
125
+
126
+ // Read the browser detection script — reuse it instead of reimplementing
127
+ const browserScriptPath = path.resolve(
128
+ path.dirname(fileURLToPath(import.meta.url)),
129
+ '..',
130
+ '..',
131
+ 'detect-antipatterns-browser.js'
132
+ );
133
+ let browserScript;
134
+ try {
135
+ browserScript = profileStep(profile, {
136
+ engine: 'browser',
137
+ phase: 'setup',
138
+ ruleId: 'read-browser-script',
139
+ target: url,
140
+ }, () => fs.readFileSync(browserScriptPath, 'utf-8'));
141
+ } catch {
142
+ throw new Error(`Browser script not found at ${browserScriptPath}`);
143
+ }
144
+
145
+ // CI runners (GitHub Actions Ubuntu) block unprivileged user namespaces, so
146
+ // Chrome can't initialize its sandbox there. Disable the sandbox only when
147
+ // running in CI; local users keep the default hardened launch.
148
+ const launchArgs = process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [];
149
+ const browser = externalBrowser || await profileStepAsync(profile, {
150
+ engine: 'browser',
151
+ phase: 'load',
152
+ ruleId: 'launch-browser',
153
+ target: url,
154
+ }, () => puppeteer.default.launch({ headless: true, args: launchArgs }));
155
+ const page = await profileStepAsync(profile, {
156
+ engine: 'browser',
157
+ phase: 'load',
158
+ ruleId: 'new-page',
159
+ target: url,
160
+ }, () => browser.newPage());
161
+ let results = [];
162
+ try {
163
+ await profileStepAsync(profile, {
164
+ engine: 'browser',
165
+ phase: 'load',
166
+ ruleId: 'set-viewport',
167
+ target: url,
168
+ }, () => page.setViewport(viewport));
169
+ await profileStepAsync(profile, {
170
+ engine: 'browser',
171
+ phase: 'load',
172
+ ruleId: `goto:${waitUntil}`,
173
+ target: url,
174
+ }, () => page.goto(url, { waitUntil, timeout: 30000 }));
175
+ if (settleMs > 0) {
176
+ await profileStepAsync(profile, {
177
+ engine: 'browser',
178
+ phase: 'load',
179
+ ruleId: 'settle',
180
+ target: url,
181
+ }, () => new Promise(resolve => setTimeout(resolve, settleMs)));
182
+ }
183
+
184
+ // Inject the browser detection script and collect results
185
+ const browserDesignSystem = serializeDesignSystemForBrowser(options?.designSystem);
186
+ await profileStepAsync(profile, {
187
+ engine: 'browser',
188
+ phase: 'scan',
189
+ ruleId: 'configure-pure-detect',
190
+ target: url,
191
+ }, () => page.evaluate((designSystem) => {
192
+ window.__IMPECCABLE_CONFIG__ = {
193
+ ...(window.__IMPECCABLE_CONFIG__ || {}),
194
+ autoScan: false,
195
+ ...(designSystem ? { designSystem } : {}),
196
+ };
197
+ }, browserDesignSystem));
198
+ await profileStepAsync(profile, {
199
+ engine: 'browser',
200
+ phase: 'scan',
201
+ ruleId: 'inject-browser-script',
202
+ target: url,
203
+ }, () => page.evaluate(browserScript));
204
+ let serializedGroups = [];
205
+ results = await profileFindingsAsync(profile, {
206
+ engine: 'browser',
207
+ phase: 'scan',
208
+ ruleId: 'browser-scan',
209
+ target: url,
210
+ }, async () => {
211
+ serializedGroups = await page.evaluate(() => {
212
+ if (!window.impeccableDetect) return [];
213
+ return window.impeccableDetect({ decorate: false, serialize: true });
214
+ });
215
+ return serializedGroups.flatMap(({ findings }) =>
216
+ findings.map(f => ({ id: f.type, snippet: f.detail, ignoreValue: f.ignoreValue || '' }))
217
+ );
218
+ });
219
+ const visualFindings = await runVisualContrastFallback(page, serializedGroups, options, profile, url);
220
+ results.push(...visualFindings);
221
+ } finally {
222
+ await profileStepAsync(profile, {
223
+ engine: 'browser',
224
+ phase: 'load',
225
+ ruleId: 'close-page',
226
+ target: url,
227
+ }, () => page.close().catch(() => {}));
228
+ if (!externalBrowser) {
229
+ await profileStepAsync(profile, {
230
+ engine: 'browser',
231
+ phase: 'load',
232
+ ruleId: 'close-browser',
233
+ target: url,
234
+ }, () => browser.close());
235
+ }
236
+ }
237
+ return filterByProviders(results.map(f => {
238
+ const item = finding(f.id, url, f.snippet);
239
+ if (f.ignoreValue) item.ignoreValue = f.ignoreValue;
240
+ return item;
241
+ }), options.providers);
242
+ }
243
+
244
+ async function createBrowserDetector(options = {}) {
245
+ let puppeteer;
246
+ try {
247
+ puppeteer = await import('puppeteer');
248
+ } catch {
249
+ throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');
250
+ }
251
+ const launchArgs = options.launchArgs || (process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []);
252
+ const browser = options.browser || await puppeteer.default.launch({
253
+ headless: options.headless ?? true,
254
+ args: launchArgs,
255
+ });
256
+ const ownsBrowser = !options.browser;
257
+ const defaults = {
258
+ waitUntil: options.waitUntil || 'load',
259
+ settleMs: Number.isFinite(options.settleMs) ? options.settleMs : 100,
260
+ viewport: options.viewport || { width: 1280, height: 800 },
261
+ };
262
+ return {
263
+ browser,
264
+ async detectUrl(url, scanOptions = {}) {
265
+ return detectUrl(url, {
266
+ ...defaults,
267
+ ...scanOptions,
268
+ browser,
269
+ });
270
+ },
271
+ async close() {
272
+ if (ownsBrowser) await browser.close().catch(() => {});
273
+ },
274
+ };
275
+ }
276
+
277
+ export { runVisualContrastFallback, detectUrl, createBrowserDetector };