@tyroneross/build-loop 0.36.0 → 0.43.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 (270) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor/rules/build-loop-surface.mdc +12 -11
  6. package/.cursor/rules/skill-index.mdc +33 -0
  7. package/AGENTS.md +213 -34
  8. package/README.md +99 -31
  9. package/agents/advisor.md +4 -4
  10. package/agents/alignment-checker.md +2 -2
  11. package/agents/architecture-scout.md +4 -4
  12. package/agents/build-orchestrator.md +38 -36
  13. package/agents/database-assessor.md +11 -5
  14. package/agents/design-contract-specialist.md +8 -8
  15. package/agents/fact-checker.md +13 -3
  16. package/agents/fix-critique.md +2 -2
  17. package/agents/independent-auditor.md +60 -7
  18. package/agents/leak-scanner.md +82 -0
  19. package/agents/overfitting-reviewer.md +2 -2
  20. package/agents/plan-critic.md +1 -1
  21. package/agents/promotion-reviewer.md +5 -5
  22. package/agents/retrospective-synthesizer.md +138 -35
  23. package/agents/scope-auditor.md +82 -11
  24. package/agents/security-reviewer.md +56 -2
  25. package/agents/self-improvement-architect.md +17 -3
  26. package/agents/transcript-pattern-miner.md +5 -5
  27. package/agents/ui-validator.md +1 -1
  28. package/bin/build-loop-debugger.js +143 -0
  29. package/bin/build-loop-install.js +1 -4
  30. package/bin/build-loop-load-probe.js +345 -0
  31. package/codex-skills/build-loop/SKILL.md +28 -6
  32. package/commands/feedback.md +37 -0
  33. package/dist/src/interactive-verifier.d.ts +1 -14
  34. package/dist/src/interactive-verifier.d.ts.map +1 -1
  35. package/dist/src/interactive-verifier.js +6 -113
  36. package/dist/src/interactive-verifier.js.map +1 -1
  37. package/dist/src/quality.d.ts +5 -0
  38. package/dist/src/quality.d.ts.map +1 -0
  39. package/dist/src/quality.js +81 -0
  40. package/dist/src/quality.js.map +1 -0
  41. package/dist/src/storage.d.ts.map +1 -1
  42. package/dist/src/storage.js +37 -3
  43. package/dist/src/storage.js.map +1 -1
  44. package/docs/agent-surface-policy.md +35 -31
  45. package/docs/memory-setup.md +19 -0
  46. package/hooks/git/pre-push +65 -4
  47. package/hooks/hooks.json +95 -38
  48. package/hooks/pre-commit +20 -1
  49. package/hooks/pre-edit-rally-point.sh +10 -3
  50. package/hooks/session-start-codex-hook-trust.sh +30 -0
  51. package/hooks/session-start-git-hooks.sh +3 -1
  52. package/hooks/session-start-rally-point.sh +52 -4
  53. package/hooks/session-start-worktree-gc.sh +47 -94
  54. package/hooks/stop-transcript-sweep.sh +173 -0
  55. package/hooks/test_closeout.sh +14 -2
  56. package/package.json +8 -7
  57. package/scripts/README.md +1 -1
  58. package/scripts/_paths.py +65 -0
  59. package/scripts/groundwork_exchange.py +1012 -0
  60. package/scripts/install_memory.py +33 -1
  61. package/scripts/lessons_index/ingest.py +13 -2
  62. package/scripts/lessons_index/query.py +36 -13
  63. package/scripts/memory_context/__init__.py +108 -14
  64. package/scripts/memory_graph/__init__.py +5 -1
  65. package/scripts/project_resolver.py +42 -36
  66. package/scripts/sync_plugin_cache.py +37 -2
  67. package/skills/agent-rally-point/SKILL.md +46 -0
  68. package/skills/api-registry-bridge/SKILL.md +1 -1
  69. package/skills/architecture/dead/SKILL.md +1 -1
  70. package/skills/architecture/impact/SKILL.md +1 -1
  71. package/skills/architecture/review/SKILL.md +1 -1
  72. package/skills/architecture/rules/SKILL.md +3 -3
  73. package/skills/architecture/scan/SKILL.md +1 -1
  74. package/skills/architecture/trace/SKILL.md +1 -1
  75. package/skills/attribution-standard/SKILL.md +6 -6
  76. package/skills/auto-decision-capture/SKILL.md +31 -2
  77. package/skills/auto-finding-capture/SKILL.md +28 -1
  78. package/skills/build-loop/SKILL.md +131 -23
  79. package/skills/build-loop/fallbacks.md +16 -21
  80. package/skills/build-loop/phases/ui-validation.md +2 -2
  81. package/skills/build-loop/references/advisor-dispatch-ladder.md +1 -1
  82. package/skills/build-loop/references/apple-native-planning.md +1 -1
  83. package/skills/build-loop/references/autonomous-and-per-commit-modes.md +11 -5
  84. package/skills/build-loop/references/autonomy-dashboard.md +115 -0
  85. package/skills/build-loop/references/capability-routing.md +24 -2
  86. package/skills/build-loop/references/coordination.md +24 -6
  87. package/skills/build-loop/references/experiment-results-template.md +15 -3
  88. package/skills/build-loop/references/leadership.md +1 -1
  89. package/skills/build-loop/references/memory.md +14 -3
  90. package/skills/build-loop/references/modular-systems-pack.md +8 -0
  91. package/skills/build-loop/references/output-style.md +86 -0
  92. package/skills/build-loop/references/phase-1-assess.md +102 -2
  93. package/skills/build-loop/references/phase-2-plan.md +9 -1
  94. package/skills/build-loop/references/phase-3-execute.md +5 -2
  95. package/skills/build-loop/references/phase-4-review.md +85 -8
  96. package/skills/build-loop/references/phase-5-iterate.md +76 -8
  97. package/skills/build-loop/references/phase-6-learn.md +10 -17
  98. package/skills/build-loop/references/privileged-request-broker.md +254 -0
  99. package/skills/build-loop/references/resource-aware-execution.md +183 -0
  100. package/skills/build-loop/references/self-recursive-dev.md +2 -2
  101. package/skills/build-loop/references/status-output-format.md +207 -0
  102. package/skills/build-loop/references/verify-dispatch.md +56 -2
  103. package/skills/building-with-deepagents/SKILL.md +1 -1
  104. package/skills/claim-scope/SKILL.md +185 -0
  105. package/skills/color-engine/SKILL.md +103 -0
  106. package/skills/color-engine/_core.py +464 -0
  107. package/skills/color-engine/color_engine.py +175 -0
  108. package/skills/cost-rca/SKILL.md +61 -0
  109. package/skills/data-plane-worktrees/SKILL.md +139 -0
  110. package/skills/data-plane-worktrees/agents/openai.yaml +4 -0
  111. package/skills/database-practice/SKILL.md +200 -0
  112. package/skills/database-practice/references/diagnostic-queries.sql +126 -0
  113. package/skills/database-practice/references/vector-and-graph-tuning.md +208 -0
  114. package/skills/database-practice/scripts/db_table_map.py +1244 -0
  115. package/skills/database-practice/scripts/test_db_table_map.py +514 -0
  116. package/skills/debug-loop/SKILL.md +36 -6
  117. package/skills/debugging-memory/SKILL.md +32 -430
  118. package/skills/debugging-memory/references/pattern-extraction.md +4 -4
  119. package/skills/debugging-memory/references/search.md +32 -120
  120. package/skills/debugging-memory/references/store.md +32 -126
  121. package/skills/debugging-memory/references/subagent-integration.md +1 -1
  122. package/skills/decision-queue/SKILL.md +251 -0
  123. package/skills/decision-queue/assets/template.html +1242 -0
  124. package/skills/decision-queue/references/example-large-queue-batching.md +164 -0
  125. package/skills/decision-queue/scripts/regen_template_constants.py +160 -0
  126. package/skills/defenseclaw-bridge/SKILL.md +2 -2
  127. package/skills/defenseclaw-bridge/references/dc-config-mapping.md +2 -9
  128. package/skills/drain-proposals/SKILL.md +53 -0
  129. package/skills/focused-loop-builder/SKILL.md +31 -0
  130. package/skills/focused-loop-builder/references/spec-format.md +27 -0
  131. package/skills/handoff/SKILL.md +169 -8
  132. package/skills/ibr-bridge/SKILL.md +4 -1
  133. package/skills/knowledge/SKILL.md +26 -14
  134. package/skills/knowledge/references/review-mode.md +2 -3
  135. package/skills/knowledge/templates/madr-minimal.md +1 -1
  136. package/skills/mcp-builder/SKILL.md +1 -1
  137. package/skills/model-bakeoff/SKILL.md +48 -10
  138. package/skills/model-tiering/SKILL.md +92 -31
  139. package/skills/native-ax-driver/SKILL.md +38 -5
  140. package/skills/native-ax-driver/scripts/native_driver.py +278 -22
  141. package/skills/native-ax-driver/scripts/test_native_driver.py +227 -0
  142. package/skills/optimize/SKILL.md +1 -1
  143. package/skills/plugin-builder/SKILL.md +48 -1
  144. package/skills/plugin-builder/references/build-loop-phase-guidance.md +3 -4
  145. package/skills/plugin-builder/references/distribution.md +13 -2
  146. package/skills/plugin-builder/references/plugin-hygiene-lessons.md +2 -2
  147. package/skills/plugin-tests/SKILL.md +2 -2
  148. package/skills/recursive-retrospective/SKILL.md +1 -1
  149. package/skills/repo-closeout/SKILL.md +17 -0
  150. package/skills/repo-closeout/agents/openai.yaml +4 -0
  151. package/skills/repo-maintenance/SKILL.md +179 -0
  152. package/skills/repo-maintenance/agents/openai.yaml +4 -0
  153. package/skills/repo-maintenance/references/pre-public-hygiene.md +134 -0
  154. package/skills/repo-maintenance/references/repository-taxonomy.md +161 -0
  155. package/skills/repo-maintenance/references/safety-protocol.md +106 -0
  156. package/skills/repo-maintenance/references/stack-profiles.md +138 -0
  157. package/skills/repo-maintenance/scripts/audit_repo_maintenance.py +1198 -0
  158. package/skills/repo-maintenance/scripts/test_audit_repo_maintenance.py +506 -0
  159. package/skills/repository-intelligence/SKILL.md +189 -0
  160. package/skills/repository-intelligence/agents/openai.yaml +4 -0
  161. package/skills/repository-intelligence/references/assessment-rubric.md +88 -0
  162. package/skills/repository-intelligence/scripts/repository_inventory.py +347 -0
  163. package/skills/research/SKILL.md +12 -2
  164. package/skills/root-cause-analysis/SKILL.md +1 -1
  165. package/skills/runtime-parity-verification/SKILL.md +36 -1
  166. package/skills/security-methodology/SKILL.md +23 -10
  167. package/skills/security-methodology/references/agentic-handoff-templates.md +220 -0
  168. package/skills/security-methodology/references/cross-source-matrix.md +1 -1
  169. package/skills/security-methodology/references/owasp-agentic-top-10.md +1 -1
  170. package/skills/security-scan/SKILL.md +55 -15
  171. package/skills/self-improve/SKILL.md +70 -50
  172. package/skills/silent-assumptions/SKILL.md +341 -0
  173. package/skills/silent-assumptions/references/elicitation-detectors.md +342 -0
  174. package/skills/spec-writing/SKILL.md +128 -24
  175. package/skills/spec-writing/scripts/check_checklist.py +114 -15
  176. package/skills/ui-design/SKILL.md +6 -4
  177. package/skills/ui-design/references/color-engine.md +132 -0
  178. package/skills/ui-design/references/design-preferences-from-owned-apps.md +8 -8
  179. package/skills/ui-design/references/ui-guidance-sources.md +1 -1
  180. package/skills/ui-design/references/universal-design-principles.alt.md +2 -2
  181. package/plugin-artifacts/codex/.codex-plugin/plugin.json +0 -41
  182. package/plugin-artifacts/codex/AGENTS.md +0 -560
  183. package/plugin-artifacts/codex/BUILD-ARTIFACT.md +0 -5
  184. package/plugin-artifacts/codex/LICENSE +0 -202
  185. package/plugin-artifacts/codex/README.md +0 -313
  186. package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
  187. package/plugin-artifacts/codex/docs/agent-surface-policy.md +0 -63
  188. package/plugin-artifacts/codex/references/advisor-dispatch-ladder.md +0 -62
  189. package/plugin-artifacts/codex/references/agent-role-taxonomy.md +0 -135
  190. package/plugin-artifacts/codex/references/autonomous-and-per-commit-modes.md +0 -161
  191. package/plugin-artifacts/codex/references/autonomy-config.md +0 -231
  192. package/plugin-artifacts/codex/references/backlog-system.md +0 -285
  193. package/plugin-artifacts/codex/references/capability-routing.md +0 -231
  194. package/plugin-artifacts/codex/references/codex-subagents.md +0 -106
  195. package/plugin-artifacts/codex/references/coordination-file-template.md +0 -181
  196. package/plugin-artifacts/codex/references/coordination-rules.md +0 -552
  197. package/plugin-artifacts/codex/references/dogfood-reload-checkpoint.md +0 -112
  198. package/plugin-artifacts/codex/references/halt-and-ask-protocol.md +0 -102
  199. package/plugin-artifacts/codex/references/implementer-envelope-schema.md +0 -302
  200. package/plugin-artifacts/codex/references/intent-capability-pack.md +0 -257
  201. package/plugin-artifacts/codex/references/intent-exploration-prompts.md +0 -96
  202. package/plugin-artifacts/codex/references/leadership.md +0 -72
  203. package/plugin-artifacts/codex/references/memory-systems.md +0 -261
  204. package/plugin-artifacts/codex/references/memory.md +0 -313
  205. package/plugin-artifacts/codex/references/model-tier-mapping.md +0 -296
  206. package/plugin-artifacts/codex/references/modular-systems-pack.md +0 -96
  207. package/plugin-artifacts/codex/references/phase-1-assess.md +0 -249
  208. package/plugin-artifacts/codex/references/phase-2-plan.md +0 -86
  209. package/plugin-artifacts/codex/references/phase-3-execute.md +0 -49
  210. package/plugin-artifacts/codex/references/phase-4-review.md +0 -341
  211. package/plugin-artifacts/codex/references/phase-5-iterate.md +0 -72
  212. package/plugin-artifacts/codex/references/phase-6-learn.md +0 -58
  213. package/plugin-artifacts/codex/references/recent-design-structures.md +0 -274
  214. package/plugin-artifacts/codex/references/research-trigger-policy.md +0 -140
  215. package/plugin-artifacts/codex/references/runtime-smoke-triggers.md +0 -42
  216. package/plugin-artifacts/codex/references/self-review.md +0 -234
  217. package/plugin-artifacts/codex/references/single-writer-commit-protocol.md +0 -90
  218. package/plugin-artifacts/codex/references/task-capture-policy.md +0 -68
  219. package/plugin-artifacts/codex/references/ui-io-contract.md +0 -116
  220. package/plugin-artifacts/codex/references/ui-spotcheck-protocol.md +0 -65
  221. package/plugin-artifacts/codex/references/verify-dispatch.md +0 -85
  222. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +0 -381
  223. package/plugin-artifacts/codex/skills/build-loop/detect-plugins.mjs +0 -82
  224. package/plugin-artifacts/codex/skills/build-loop/eval-guide.md +0 -65
  225. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +0 -549
  226. package/plugin-artifacts/codex/skills/build-loop/phases/fact-check.md +0 -42
  227. package/plugin-artifacts/codex/skills/build-loop/phases/ui-validation.md +0 -267
  228. package/plugin-artifacts/codex/skills/build-loop/references/advisor-dispatch-ladder.md +0 -62
  229. package/plugin-artifacts/codex/skills/build-loop/references/apple-native-planning.md +0 -439
  230. package/plugin-artifacts/codex/skills/build-loop/references/autonomous-and-per-commit-modes.md +0 -161
  231. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +0 -231
  232. package/plugin-artifacts/codex/skills/build-loop/references/codex-subagents.md +0 -106
  233. package/plugin-artifacts/codex/skills/build-loop/references/coordination.md +0 -161
  234. package/plugin-artifacts/codex/skills/build-loop/references/correction-aware-capture.md +0 -177
  235. package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +0 -101
  236. package/plugin-artifacts/codex/skills/build-loop/references/independent-auditor.md +0 -72
  237. package/plugin-artifacts/codex/skills/build-loop/references/intent-capability-pack.md +0 -257
  238. package/plugin-artifacts/codex/skills/build-loop/references/intent-exploration-prompts.md +0 -96
  239. package/plugin-artifacts/codex/skills/build-loop/references/leadership.md +0 -72
  240. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +0 -313
  241. package/plugin-artifacts/codex/skills/build-loop/references/modular-systems-pack.md +0 -96
  242. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +0 -222
  243. package/plugin-artifacts/codex/skills/build-loop/references/pay-it-forward-arch.md +0 -98
  244. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +0 -249
  245. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +0 -86
  246. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +0 -49
  247. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +0 -341
  248. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +0 -72
  249. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +0 -58
  250. package/plugin-artifacts/codex/skills/build-loop/references/recent-design-structures.md +0 -274
  251. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/ASSESSMENT.md +0 -85
  252. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +0 -149
  253. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +0 -32
  254. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +0 -48
  255. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +0 -60
  256. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +0 -51
  257. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +0 -71
  258. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +0 -52
  259. package/plugin-artifacts/codex/skills/build-loop/references/refactor-history/trace-comparison.md +0 -202
  260. package/plugin-artifacts/codex/skills/build-loop/references/self-recursive-dev.md +0 -77
  261. package/plugin-artifacts/codex/skills/build-loop/references/self-review.md +0 -234
  262. package/plugin-artifacts/codex/skills/build-loop/references/ui-io-contract.md +0 -116
  263. package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +0 -85
  264. package/plugin-artifacts/codex/skills/build-loop/scanners/audit-design-rules.mjs +0 -476
  265. package/plugin-artifacts/codex/skills/build-loop/scanners/require-visual-evidence.mjs +0 -239
  266. package/plugin-artifacts/codex/skills/build-loop/templates/backlog-item.md +0 -35
  267. package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +0 -100
  268. package/plugin-artifacts/codex/skills/build-loop/templates/ui-subagent-prompt.md +0 -179
  269. package/plugin-artifacts/codex/skills/build-loop/templates/ux-fix-plan.md +0 -40
  270. package/scripts/build_codex_plugin_artifact.py +0 -321
@@ -1,476 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
3
- // SPDX-License-Identifier: Apache-2.0
4
- /**
5
- * build-loop Phase 7, Gate C: Design Rule Scanner
6
- *
7
- * Zero-dependency, single-file static scanner for UI design-rule violations.
8
- * Mirrors audit-hardcoded-secrets.mjs architecture (Node >= 18, stdlib only).
9
- *
10
- * Pattern library covers:
11
- * - SwiftUI / iOS: status pills, animation accessibility, raw UIColor RGB,
12
- * literal font sizes, literal corner radii, icon-only accessibility labels
13
- * - React / Web: Tailwind pill patterns, missing motion-reduce, raw hex colors,
14
- * arbitrary text size values
15
- *
16
- * Exit codes:
17
- * 0 = clean
18
- * 1 = warnings only
19
- * 2 = must-fix findings present
20
- *
21
- * Usage:
22
- * node audit-design-rules.mjs # scan cwd, auto-detect platform
23
- * node audit-design-rules.mjs --root=/path/to/project
24
- * node audit-design-rules.mjs --platform=swiftui
25
- * node audit-design-rules.mjs --platform=react
26
- * node audit-design-rules.mjs --targets=Views,Components
27
- * node audit-design-rules.mjs --exts=.swift
28
- * node audit-design-rules.mjs --json
29
- */
30
-
31
- import fs from 'fs';
32
- import path from 'path';
33
-
34
- // =============================================================================
35
- // ARG PARSING
36
- // =============================================================================
37
-
38
- function parseArgs(argv) {
39
- const out = {
40
- targets: null,
41
- exts: null,
42
- root: process.cwd(),
43
- platform: null,
44
- json: false,
45
- };
46
- for (const arg of argv.slice(2)) {
47
- if (arg.startsWith('--targets=')) {
48
- out.targets = arg.slice('--targets='.length).split(',').map((s) => s.trim()).filter(Boolean);
49
- } else if (arg.startsWith('--exts=')) {
50
- out.exts = arg.slice('--exts='.length).split(',').map((s) => s.trim()).filter(Boolean);
51
- } else if (arg.startsWith('--root=')) {
52
- out.root = path.resolve(arg.slice('--root='.length));
53
- } else if (arg.startsWith('--platform=')) {
54
- out.platform = arg.slice('--platform='.length).trim().toLowerCase();
55
- } else if (arg === '--json') {
56
- out.json = true;
57
- }
58
- }
59
- return out;
60
- }
61
-
62
- const args = parseArgs(process.argv);
63
- const root = args.root;
64
-
65
- // =============================================================================
66
- // PATTERN LIBRARIES
67
- // =============================================================================
68
-
69
- /**
70
- * Rule shape:
71
- * id string unique rule identifier
72
- * severity 'must-fix' | 'warn' | 'info'
73
- * description string human message
74
- * pattern RegExp match in (comment-stripped) file content
75
- * contextRequired RegExp? the match is skipped unless this regex also
76
- * matches within ±contextWindow chars of the match
77
- * contextWindow number? characters each side to check (default 300)
78
- * fileMustContain RegExp? whole-file guard (before comment stripping)
79
- * invertFileCheck boolean? if true, rule fires when file does NOT contain
80
- * fileMustContain (i.e. it's the absence check)
81
- * pathExclude RegExp? skip this rule entirely if the file path matches
82
- */
83
-
84
- const SWIFTUI_RULES = [
85
- {
86
- // Fires when .background(...) immediately followed (possibly with
87
- // intervening whitespace/newline) by .clipShape(Capsule()) and the
88
- // surrounding context contains a chip/badge/status/trend/score indicator.
89
- // The background argument itself must contain a color expression
90
- // (not just Theme.surfacePrimary which is fine for layouts).
91
- // We look for .opacity( or a status/score color variable as the tell
92
- // that this is a status pill, not just a card container.
93
- id: 'status-pill-background',
94
- severity: 'must-fix',
95
- description: 'Status indicators must use text color only, not background fills (CLAUDE.md "Signal" rule)',
96
- // Matches .background(<anything with opacity or color>).clipShape(Capsule())
97
- // allowing whitespace between the two modifiers.
98
- pattern: /\.background\([^)]*(?:\.opacity\([^)]+\)|[Cc]olor[^)]*)\)[\s\S]{0,60}?\.clipShape\(Capsule\(\)\)/,
99
- contextRequired: /(Chip|chip|Badge|badge|Status|status|Trend|trend|Score|score|Rating|rating|Label|label|Tag|tag|filterChip|dimChip|paceRating|trendColor)/,
100
- contextWindow: 400,
101
- },
102
- {
103
- // .repeatForever( in a file that does NOT have accessibilityReduceMotion
104
- id: 'animation-without-reducemotion',
105
- severity: 'must-fix',
106
- description: 'Continuous animations must respect accessibilityReduceMotion',
107
- pattern: /\.repeatForever\(/,
108
- fileMustContain: /accessibilityReduceMotion/,
109
- invertFileCheck: true,
110
- },
111
- {
112
- // Raw UIColor(red:green:blue:) literals outside Theme/
113
- id: 'uicolor-rgb-outside-theme',
114
- severity: 'must-fix',
115
- description: 'Use Theme tokens, not raw UIColor(red:green:blue:) literals',
116
- pattern: /UIColor\(red:\s*[\d.]+,\s*green:\s*[\d.]+,\s*blue:\s*[\d.]+/,
117
- pathExclude: /\/Theme\//,
118
- },
119
- {
120
- // Matches Text(...)\n? .font(.system(size: N)) where the .font is NOT
121
- // chained on Image. We use a positive contextRequired to require "Text("
122
- // in the preceding 80 chars AND a negative contextRequired-style guard
123
- // (_preContextRequired) that we handle in the scanner loop.
124
- id: 'system-font-size-body-copy',
125
- severity: 'warn',
126
- description: 'Body copy should use Theme.fontBody/fontCaption tokens for Dynamic Type support',
127
- // Only capture the simple .font(.system(size: N)) form (no weight/design)
128
- // — weight-bearing calls like .system(size:weight:design:) are intentional
129
- pattern: /\.font\(\.system\(size:\s*\d+\)\)/,
130
- contextRequired: /Text\(/,
131
- contextWindow: 120,
132
- _preContextOnly: true, // custom flag: contextRequired must match BEFORE the offset
133
- pathExclude: /\/Theme\/|Chip|chip|Badge|badge|Tabular|Numeric/,
134
- },
135
- {
136
- id: 'literal-corner-radius',
137
- severity: 'warn',
138
- description: 'Use Theme.cornerSmall/Medium/Large/XL tokens, not bare numeric corner radii',
139
- pattern: /(\.cornerRadius\(\s*\d+\s*\)|RoundedRectangle\(cornerRadius:\s*\d+\s*\))/,
140
- pathExclude: /\/Theme\//,
141
- },
142
- {
143
- // withAnimation(.easeInOut(duration:) or similar in file without reduceMotion guard
144
- id: 'animation-duration-without-reducemotion',
145
- severity: 'warn',
146
- description: 'Animations with duration should be gated on accessibilityReduceMotion',
147
- pattern: /(withAnimation\(\.\w+\(duration:|\.easeInOut\(duration:)/,
148
- fileMustContain: /accessibilityReduceMotion/,
149
- invertFileCheck: true,
150
- },
151
- {
152
- // Icon-only Image(systemName:) without an .accessibilityLabel within 300 chars
153
- id: 'sf-symbol-without-label',
154
- severity: 'warn',
155
- description: 'Icon-only Image(systemName:) should have explicit accessibilityLabel',
156
- // Negative lookahead is not reliable across multiline — use contextRequired inversion below
157
- pattern: /Image\(systemName:\s*"[^"]+"\)/,
158
- // We invert this: skip the finding IF accessibilityLabel appears nearby
159
- contextRequired: /^(?![\s\S]*accessibilityLabel)/, // placeholder — handled in code
160
- contextWindow: 300,
161
- pathExclude: /Tests/,
162
- _accessibilityCheck: true, // custom flag handled in scanner loop
163
- },
164
- ];
165
-
166
- const WEB_RULES = [
167
- {
168
- id: 'status-pill-tailwind',
169
- severity: 'must-fix',
170
- description: 'Status indicators must use text color only, not background fills',
171
- pattern: /className="[^"]*\bbg-\w+-\d{3}\b[^"]*\brounded-(full|2xl|xl)\b[^"]*"/,
172
- contextRequired: /(chip|badge|status|trend|tag)/i,
173
- contextWindow: 300,
174
- },
175
- {
176
- id: 'animation-without-motion-reduce',
177
- severity: 'must-fix',
178
- description: 'Continuous animations must have motion-reduce: variant',
179
- pattern: /\b(animate-pulse|animate-spin|animate-bounce|animate-ping)\b(?![^"]*motion-reduce)/,
180
- },
181
- {
182
- id: 'hex-color-outside-theme',
183
- severity: 'warn',
184
- description: 'Use theme tokens, not raw hex color literals',
185
- pattern: /[:'"]\s*#[0-9a-fA-F]{3,6}\s*[;'"]?/,
186
- pathExclude: /tailwind\.config|theme|tokens|\.css$|\.scss$/,
187
- },
188
- {
189
- id: 'arbitrary-text-size',
190
- severity: 'warn',
191
- description: 'Use theme text size tokens, not arbitrary px values',
192
- pattern: /\btext-\[\d+px\]/,
193
- },
194
- ];
195
-
196
- // =============================================================================
197
- // PLATFORM CONFIG
198
- // =============================================================================
199
-
200
- const PLATFORM_CONFIG = {
201
- swiftui: {
202
- rules: SWIFTUI_RULES,
203
- defaultTargets: ['Views', 'Components', 'View.swift'],
204
- defaultExts: ['.swift'],
205
- },
206
- react: {
207
- rules: WEB_RULES,
208
- defaultTargets: ['src', 'components', 'app', 'pages'],
209
- defaultExts: ['.tsx', '.jsx', '.ts', '.js', '.html', '.vue'],
210
- },
211
- web: {
212
- rules: WEB_RULES,
213
- defaultTargets: ['src', 'components', 'app', 'pages'],
214
- defaultExts: ['.tsx', '.jsx', '.ts', '.js', '.html', '.vue'],
215
- },
216
- };
217
-
218
- // Dirs always skipped during walk
219
- const SKIP_DIRS = new Set([
220
- 'node_modules', '.build', 'DerivedData', '__tests__', '.git',
221
- '.turbo', '.vercel', 'out', '.cache', 'dist', 'build', 'coverage',
222
- ]);
223
-
224
- const SKIP_PATH_FRAGMENTS = ['/Tests/', '/test/', '/__tests__/'];
225
-
226
- // =============================================================================
227
- // PLATFORM AUTO-DETECTION
228
- // =============================================================================
229
-
230
- function* walkForDetect(dir, depth = 0) {
231
- if (depth > 4) return;
232
- let entries;
233
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
234
- for (const e of entries) {
235
- if (SKIP_DIRS.has(e.name)) continue;
236
- const full = path.join(dir, e.name);
237
- if (e.isDirectory()) yield* walkForDetect(full, depth + 1);
238
- else yield full;
239
- }
240
- }
241
-
242
- function detectPlatform(scanRoot, targets) {
243
- const checkDirs = targets
244
- ? targets.map((t) => path.join(scanRoot, t))
245
- : [scanRoot];
246
-
247
- let hasSwift = false;
248
- let hasTsxJsx = false;
249
-
250
- outer: for (const dir of checkDirs) {
251
- if (!fs.existsSync(dir)) continue;
252
- const stat = fs.statSync(dir);
253
- const files = stat.isDirectory() ? [...walkForDetect(dir)] : [dir];
254
- for (const f of files) {
255
- const ext = path.extname(f);
256
- if (ext === '.swift') { hasSwift = true; break outer; }
257
- if (ext === '.tsx' || ext === '.jsx') hasTsxJsx = true;
258
- }
259
- }
260
- if (hasSwift) return 'swiftui';
261
- if (hasTsxJsx) return 'react';
262
- return 'web';
263
- }
264
-
265
- // =============================================================================
266
- // FILE WALKER
267
- // =============================================================================
268
-
269
- function* walk(dir) {
270
- let entries;
271
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
272
- for (const entry of entries) {
273
- if (entry.name.startsWith('.') && SKIP_DIRS.has(entry.name.slice(1))) continue;
274
- if (SKIP_DIRS.has(entry.name)) continue;
275
- const fullPath = path.join(dir, entry.name);
276
- if (entry.isDirectory()) yield* walk(fullPath);
277
- else yield fullPath;
278
- }
279
- }
280
-
281
- // =============================================================================
282
- // COMMENT STRIPPING (Swift and JS/TS)
283
- // For design rules we strip comments — commented-out code is not rendered UI.
284
- // =============================================================================
285
-
286
- function stripComments(source, ext) {
287
- if (ext === '.swift' || ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx' || ext === '.mjs') {
288
- return source
289
- .replace(/\/\*[\s\S]*?\*\//g, (m) => ' '.repeat(m.length)) // block comments → spaces (preserve offsets)
290
- .replace(/(^|\s)\/\/.*$/gm, (m, p1) => p1 + ' '.repeat(m.length - p1.length)); // line comments → spaces
291
- }
292
- if (ext === '.html' || ext === '.vue') {
293
- return source.replace(/<!--[\s\S]*?-->/g, (m) => ' '.repeat(m.length));
294
- }
295
- return source;
296
- }
297
-
298
- // =============================================================================
299
- // OFFSET → LINE NUMBER
300
- // =============================================================================
301
-
302
- function lineNumberAt(source, offset) {
303
- let line = 1;
304
- for (let i = 0; i < offset && i < source.length; i++) {
305
- if (source[i] === '\n') line++;
306
- }
307
- return line;
308
- }
309
-
310
- // =============================================================================
311
- // SNIPPET: single line of context around match
312
- // =============================================================================
313
-
314
- function snippetAt(source, offset) {
315
- const start = source.lastIndexOf('\n', offset) + 1;
316
- const end = source.indexOf('\n', offset);
317
- return source.slice(start, end === -1 ? undefined : end).trim();
318
- }
319
-
320
- // =============================================================================
321
- // MAIN SCAN
322
- // =============================================================================
323
-
324
- const platform = args.platform ?? detectPlatform(root, args.targets);
325
- const config = PLATFORM_CONFIG[platform] ?? PLATFORM_CONFIG.swiftui;
326
- const rules = config.rules;
327
- const exts = new Set(args.exts ?? config.defaultExts);
328
- const scanTargets = args.targets ?? config.defaultTargets;
329
-
330
- // Collect files
331
- const filesToScan = [];
332
-
333
- for (const target of scanTargets) {
334
- // Target can be a path fragment (substring match) or a resolvable path
335
- const absoluteTarget = path.isAbsolute(target) ? target : path.join(root, target);
336
-
337
- if (fs.existsSync(absoluteTarget)) {
338
- const stat = fs.statSync(absoluteTarget);
339
- const candidates = stat.isDirectory() ? [...walk(absoluteTarget)] : [absoluteTarget];
340
- for (const f of candidates) {
341
- if (exts.has(path.extname(f)) && !filesToScan.includes(f)) {
342
- // Apply global path exclusions
343
- const skip = SKIP_PATH_FRAGMENTS.some((frag) => f.includes(frag));
344
- if (!skip) filesToScan.push(f);
345
- }
346
- }
347
- } else {
348
- // Treat as substring: walk root and include files whose paths contain this target
349
- for (const f of walk(root)) {
350
- if (!exts.has(path.extname(f))) continue;
351
- if (!f.includes(target)) continue;
352
- if (filesToScan.includes(f)) continue;
353
- const skip = SKIP_PATH_FRAGMENTS.some((frag) => f.includes(frag));
354
- if (!skip) filesToScan.push(f);
355
- }
356
- }
357
- }
358
-
359
- const findings = [];
360
-
361
- for (const filePath of filesToScan) {
362
- const ext = path.extname(filePath);
363
- let source;
364
- try { source = fs.readFileSync(filePath, 'utf8'); } catch { continue; }
365
-
366
- const stripped = stripComments(source, ext);
367
-
368
- for (const rule of rules) {
369
- // pathExclude check
370
- if (rule.pathExclude && rule.pathExclude.test(filePath)) continue;
371
-
372
- // fileMustContain / invertFileCheck — whole-file guard (use original source)
373
- if (rule.fileMustContain) {
374
- const fileHas = rule.fileMustContain.test(source);
375
- if (rule.invertFileCheck) {
376
- // Fire only when file does NOT contain the guard — skip file if it does
377
- if (fileHas) continue;
378
- } else {
379
- if (!fileHas) continue;
380
- }
381
- }
382
-
383
- // Reset regex state
384
- rule.pattern.lastIndex = 0;
385
-
386
- let match;
387
- const re = new RegExp(rule.pattern.source, rule.pattern.flags.includes('g') ? rule.pattern.flags : rule.pattern.flags + 'g');
388
-
389
- while ((match = re.exec(stripped)) !== null) {
390
- const matchOffset = match.index;
391
- const cw = rule.contextWindow ?? 300;
392
- const contextStart = Math.max(0, matchOffset - cw);
393
- const contextEnd = Math.min(stripped.length, matchOffset + match[0].length + cw);
394
- const context = stripped.slice(contextStart, contextEnd);
395
-
396
- // Special accessibility check: skip if accessibilityLabel appears nearby
397
- if (rule._accessibilityCheck) {
398
- if (/accessibilityLabel/.test(context)) continue;
399
- }
400
-
401
- // contextRequired check (skip if nearby context doesn't match)
402
- if (rule.contextRequired && !rule._accessibilityCheck) {
403
- if (rule._preContextOnly) {
404
- // Only check the content BEFORE the match (preceding contextWindow chars)
405
- const preContext = stripped.slice(contextStart, matchOffset);
406
- if (!rule.contextRequired.test(preContext)) continue;
407
- } else {
408
- if (!rule.contextRequired.test(context)) continue;
409
- }
410
- }
411
-
412
- const lineNum = lineNumberAt(source, matchOffset);
413
- const snippet = snippetAt(source, matchOffset);
414
- const relPath = path.relative(root, filePath);
415
-
416
- findings.push({
417
- rule: rule.id,
418
- severity: rule.severity,
419
- file: relPath,
420
- line: lineNum,
421
- snippet: snippet.length > 120 ? snippet.slice(0, 120) + '...' : snippet,
422
- description: rule.description,
423
- });
424
-
425
- // For rules without 'g' flag in original, only match once per file
426
- if (!rule.pattern.flags.includes('g')) break;
427
- }
428
- }
429
- }
430
-
431
- // =============================================================================
432
- // OUTPUT
433
- // =============================================================================
434
-
435
- const summary = {
436
- mustFix: findings.filter((f) => f.severity === 'must-fix').length,
437
- warn: findings.filter((f) => f.severity === 'warn').length,
438
- info: findings.filter((f) => f.severity === 'info').length,
439
- };
440
-
441
- const exitCode = summary.mustFix > 0 ? 2 : summary.warn > 0 ? 1 : 0;
442
-
443
- if (args.json) {
444
- const output = {
445
- platform,
446
- rulesEvaluated: rules.length,
447
- filesScanned: filesToScan.length,
448
- findings,
449
- summary,
450
- exit: exitCode,
451
- };
452
- console.log(JSON.stringify(output, null, 2));
453
- process.exit(exitCode);
454
- }
455
-
456
- // Text mode
457
- console.log(`audit-design-rules: ${platform} pack, ${rules.length} rules`);
458
- console.log(`files scanned: ${filesToScan.length}`);
459
- console.log('');
460
-
461
- function printGroup(severity, label) {
462
- const group = findings.filter((f) => f.severity === severity);
463
- console.log(`${label}: ${group.length}`);
464
- for (const f of group) {
465
- console.log(` ${f.file}:${f.line} [${f.rule}] ${f.description}`);
466
- console.log(` → ${f.snippet}`);
467
- }
468
- if (group.length) console.log('');
469
- }
470
-
471
- printGroup('must-fix', 'must-fix');
472
- printGroup('warn', 'warn');
473
- printGroup('info', 'info');
474
-
475
- console.log(`Exit: ${exitCode}`);
476
- process.exit(exitCode);