@tyroneross/build-loop 0.30.3
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/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,476 @@
|
|
|
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);
|