@bompus/house-rules 0.0.0-stage → 0.13.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 (236) hide show
  1. package/CODE_OF_CONDUCT.md +83 -0
  2. package/CONTRIBUTING.md +54 -0
  3. package/LICENSE +21 -0
  4. package/README.md +378 -2
  5. package/THIRD_PARTY_NOTICES.md +188 -0
  6. package/compose.mjs +125 -0
  7. package/composition.mjs +361 -0
  8. package/config-view.mjs +209 -0
  9. package/config.mjs +381 -0
  10. package/docs/assets/banner-dark.png +0 -0
  11. package/docs/assets/banner-light.png +0 -0
  12. package/docs/assets/overview.png +0 -0
  13. package/docs/configuration.md +269 -0
  14. package/docs/skill-names.md +50 -0
  15. package/examples/person/house-rules.json +15 -0
  16. package/examples/person/rules/10-tooling.md +8 -0
  17. package/examples/person/rules/20-reporting.md +9 -0
  18. package/examples/project/AGENTS.md +18 -0
  19. package/package.json +42 -4
  20. package/rule-references.mjs +91 -0
  21. package/rules/core.md +340 -0
  22. package/rules/modifiers/backlog-outline.md +25 -0
  23. package/rules/modifiers/backlog-visual.md +16 -0
  24. package/rules/modifiers/coded-offers.md +70 -0
  25. package/rules/modifiers/cross-session-relay.md +23 -0
  26. package/rules/modifiers/effort-estimates.md +41 -0
  27. package/rules/modifiers/land-when-done.md +12 -0
  28. package/rules/modifiers/low-quota-handoff.md +20 -0
  29. package/rules/modifiers/model-default-questions.md +33 -0
  30. package/rules/modifiers/multi-agent.md +38 -0
  31. package/rules/modifiers/no-attribution.md +16 -0
  32. package/rules/modifiers/periodic-updates.md +25 -0
  33. package/rules/modifiers/plan-files.md +82 -0
  34. package/rules/modifiers/question-cards.md +48 -0
  35. package/rules/modifiers/release-batching.md +15 -0
  36. package/rules/modifiers/scratch-on-disk.md +15 -0
  37. package/rules/modifiers/shared-host-load.md +19 -0
  38. package/rules/modifiers/solo-operator.md +13 -0
  39. package/rules/modifiers/squash-landing.md +22 -0
  40. package/rules/modifiers/upstream-contributions.md +45 -0
  41. package/rules/modifiers/web-image-formats.md +35 -0
  42. package/rules/references/backlog-view.md +154 -0
  43. package/rules/references/github-actions.md +45 -0
  44. package/rules/references/landing.md +39 -0
  45. package/rules/references/multi-agent.md +51 -0
  46. package/rules/references/release-batching.md +66 -0
  47. package/rules/references/squash-landing.md +50 -0
  48. package/rules/references/thread-visuals.md +45 -0
  49. package/setup.mjs +445 -0
  50. package/skills/hr-agent-guidance-audit/LICENSE +21 -0
  51. package/skills/hr-agent-guidance-audit/SKILL.md +91 -0
  52. package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
  53. package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
  54. package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
  55. package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
  56. package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
  57. package/skills/hr-agent-guidance-refresh/SKILL.md +36 -0
  58. package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
  59. package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
  60. package/skills/hr-api-exposure-check/SKILL.md +159 -0
  61. package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
  62. package/skills/hr-audit-choices/SKILL.md +67 -0
  63. package/skills/hr-audit-choices/agents/openai.yaml +2 -0
  64. package/skills/hr-benchmarking/NOTICE.md +36 -0
  65. package/skills/hr-benchmarking/SKILL.md +279 -0
  66. package/skills/hr-benchmarking/agents/openai.yaml +2 -0
  67. package/skills/hr-benchmarking/references/javascript.md +130 -0
  68. package/skills/hr-benchmarking/references/tools.md +165 -0
  69. package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
  70. package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
  71. package/skills/hr-better-accessibility/LICENSE +22 -0
  72. package/skills/hr-better-accessibility/NOTICE.md +46 -0
  73. package/skills/hr-better-accessibility/SKILL.md +119 -0
  74. package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
  75. package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
  76. package/skills/hr-better-accessibility/forms.md +82 -0
  77. package/skills/hr-better-accessibility/hit-areas.md +98 -0
  78. package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
  79. package/skills/hr-better-accessibility/screen-readers.md +102 -0
  80. package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
  81. package/skills/hr-better-colors/LICENSE +21 -0
  82. package/skills/hr-better-colors/NOTICE.md +37 -0
  83. package/skills/hr-better-colors/SKILL.md +114 -0
  84. package/skills/hr-better-colors/agents/openai.yaml +3 -0
  85. package/skills/hr-better-colors/color-formats.md +90 -0
  86. package/skills/hr-better-colors/color-usage.md +140 -0
  87. package/skills/hr-better-colors/contrast.md +80 -0
  88. package/skills/hr-better-colors/palette-generation.md +104 -0
  89. package/skills/hr-better-colors/palette-structure.md +76 -0
  90. package/skills/hr-better-colors/token-naming.md +97 -0
  91. package/skills/hr-better-layout/LICENSE +22 -0
  92. package/skills/hr-better-layout/NOTICE.md +48 -0
  93. package/skills/hr-better-layout/SKILL.md +92 -0
  94. package/skills/hr-better-layout/agents/openai.yaml +3 -0
  95. package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
  96. package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
  97. package/skills/hr-better-typography/LICENSE +22 -0
  98. package/skills/hr-better-typography/NOTICE.md +46 -0
  99. package/skills/hr-better-typography/SKILL.md +172 -0
  100. package/skills/hr-better-typography/agents/openai.yaml +3 -0
  101. package/skills/hr-better-typography/choosing-fonts.md +69 -0
  102. package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
  103. package/skills/hr-better-typography/details-and-accessibility.md +119 -0
  104. package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
  105. package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
  106. package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
  107. package/skills/hr-better-writing/LICENSE +22 -0
  108. package/skills/hr-better-writing/NOTICE.md +46 -0
  109. package/skills/hr-better-writing/SKILL.md +122 -0
  110. package/skills/hr-better-writing/agents/openai.yaml +3 -0
  111. package/skills/hr-code-review/LICENSE +21 -0
  112. package/skills/hr-code-review/NOTICE.md +26 -0
  113. package/skills/hr-code-review/SKILL.md +163 -0
  114. package/skills/hr-code-review/agents/openai.yaml +3 -0
  115. package/skills/hr-code-review/references/acp-client.md +43 -0
  116. package/skills/hr-code-review/references/evidence.md +57 -0
  117. package/skills/hr-code-review/references/impact.md +103 -0
  118. package/skills/hr-code-review/references/panel-adjudication.md +58 -0
  119. package/skills/hr-code-review/references/panel-plan.md +61 -0
  120. package/skills/hr-code-review/references/panel-recommendations.json +105 -0
  121. package/skills/hr-code-review/references/panel-recommendations.md +46 -0
  122. package/skills/hr-code-review/references/panel-setup.md +54 -0
  123. package/skills/hr-code-review/references/reviewer-role.md +31 -0
  124. package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
  125. package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
  126. package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
  127. package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
  128. package/skills/hr-design-exploration/SKILL.md +105 -0
  129. package/skills/hr-design-exploration/agents/openai.yaml +4 -0
  130. package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
  131. package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
  132. package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
  133. package/skills/hr-diagnosing-bugs/references/reporting.md +97 -0
  134. package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
  135. package/skills/hr-explain-code/NOTICE.md +37 -0
  136. package/skills/hr-explain-code/SKILL.md +110 -0
  137. package/skills/hr-explain-code/agents/openai.yaml +5 -0
  138. package/skills/hr-explain-code/references/helper-brief.md +39 -0
  139. package/skills/hr-extract-shared-steps/SKILL.md +134 -0
  140. package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
  141. package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
  142. package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
  143. package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
  144. package/skills/hr-handoff/LICENSE +21 -0
  145. package/skills/hr-handoff/NOTICE.md +29 -0
  146. package/skills/hr-handoff/SKILL.md +114 -0
  147. package/skills/hr-handoff/agents/openai.yaml +5 -0
  148. package/skills/hr-handoff/assets/ledger.example.json +10 -0
  149. package/skills/hr-handoff/assets/receipt.example.json +76 -0
  150. package/skills/hr-handoff/assets/source.example.json +20 -0
  151. package/skills/hr-handoff/references/reconciliation.md +124 -0
  152. package/skills/hr-handoff/references/successor-thread.md +76 -0
  153. package/skills/hr-handoff/scripts/check-reconciliation.mjs +324 -0
  154. package/skills/hr-house-rules-setup/SKILL.md +114 -0
  155. package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
  156. package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
  157. package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
  158. package/skills/hr-lean-plan/SKILL.md +146 -0
  159. package/skills/hr-lean-plan/agents/openai.yaml +5 -0
  160. package/skills/hr-maintainability-review/SKILL.md +186 -0
  161. package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
  162. package/skills/hr-manual-qa/NOTICE.md +36 -0
  163. package/skills/hr-manual-qa/SKILL.md +37 -0
  164. package/skills/hr-manual-qa/agents/openai.yaml +3 -0
  165. package/skills/hr-navigation-retro/LICENSE +21 -0
  166. package/skills/hr-navigation-retro/NOTICE.md +5 -0
  167. package/skills/hr-navigation-retro/SKILL.md +86 -0
  168. package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
  169. package/skills/hr-navigation-retro/references/inputs.md +67 -0
  170. package/skills/hr-navigation-retro/scripts/retro.py +552 -0
  171. package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
  172. package/skills/hr-ordering-tests/SKILL.md +51 -0
  173. package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
  174. package/skills/hr-plain-prose/SKILL.md +264 -0
  175. package/skills/hr-plain-prose/agents/openai.yaml +3 -0
  176. package/skills/hr-plain-prose/scripts/check.mjs +331 -0
  177. package/skills/hr-pr-followup/LICENSE +21 -0
  178. package/skills/hr-pr-followup/NOTICE.md +9 -0
  179. package/skills/hr-pr-followup/SKILL.md +74 -0
  180. package/skills/hr-pr-followup/agents/openai.yaml +2 -0
  181. package/skills/hr-pr-followup/references/review-helpers.md +111 -0
  182. package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
  183. package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
  184. package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
  185. package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
  186. package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
  187. package/skills/hr-progress-report/SKILL.md +49 -0
  188. package/skills/hr-progress-report/agents/openai.yaml +5 -0
  189. package/skills/hr-read-reddit/SKILL.md +44 -0
  190. package/skills/hr-read-reddit/agents/openai.yaml +2 -0
  191. package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
  192. package/skills/hr-read-x-links/SKILL.md +42 -0
  193. package/skills/hr-read-x-links/agents/openai.yaml +2 -0
  194. package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
  195. package/skills/hr-research-shared-links/SKILL.md +103 -0
  196. package/skills/hr-split-to-prs/LICENSE +21 -0
  197. package/skills/hr-split-to-prs/NOTICE.md +9 -0
  198. package/skills/hr-split-to-prs/SKILL.md +61 -0
  199. package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
  200. package/skills/hr-sqlite/SKILL.md +174 -0
  201. package/skills/hr-sqlite/agents/openai.yaml +2 -0
  202. package/skills/hr-sqlite/references/evidence.md +94 -0
  203. package/skills/hr-sqlite/references/tool-decisions.md +39 -0
  204. package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
  205. package/skills/hr-stock-ui-audit/SKILL.md +132 -0
  206. package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
  207. package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
  208. package/skills/hr-tdd/LICENSE +21 -0
  209. package/skills/hr-tdd/NOTICE.md +10 -0
  210. package/skills/hr-tdd/SKILL.md +40 -0
  211. package/skills/hr-tdd/agents/openai.yaml +3 -0
  212. package/skills/hr-tdd/mocking.md +39 -0
  213. package/skills/hr-tdd/tests.md +77 -0
  214. package/skills/hr-test-audit/LICENSE +21 -0
  215. package/skills/hr-test-audit/NOTICE.md +67 -0
  216. package/skills/hr-test-audit/SKILL.md +146 -0
  217. package/skills/hr-test-audit/agents/openai.yaml +3 -0
  218. package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
  219. package/skills/hr-usage-report/SKILL.md +90 -0
  220. package/skills/hr-usage-report/agents/openai.yaml +2 -0
  221. package/skills/hr-usage-report/assets/example-report.json +125 -0
  222. package/skills/hr-usage-report/assets/report-template.html +377 -0
  223. package/skills/hr-usage-report/scripts/render-report.ts +86 -0
  224. package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
  225. package/skills/hr-what-next/SKILL.md +151 -0
  226. package/skills/hr-what-next/agents/openai.yaml +4 -0
  227. package/skills/hr-what-next/references/reporting.md +59 -0
  228. package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
  229. package/skills/hr-writing-for-agents/NOTICE.md +46 -0
  230. package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
  231. package/skills/hr-writing-for-agents/SKILL.md +108 -0
  232. package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
  233. package/skills/hr-writing-pr/NOTICE.md +48 -0
  234. package/skills/hr-writing-pr/SKILL.md +119 -0
  235. package/skills/hr-writing-pr/agents/openai.yaml +2 -0
  236. package/skills/hr-writing-pr/scripts/benefit-chart.py +200 -0
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only inspection of a SQLite database file and, optionally, the source
3
+ that opens it.
4
+
5
+ inspect-sqlite.py DATABASE [--source PATH ...] [--quick-check] [--json]
6
+
7
+ Reports what the file itself records (journal mode, page and freelist counts,
8
+ planner statistics, FTS tables, WAL size) plus the filesystem it sits on. It
9
+ never writes to the database. SQLite may create an empty `-shm` file beside a
10
+ WAL database that no process has open.
11
+
12
+ Connection settings such as busy_timeout, synchronous, foreign_keys and
13
+ cache_size are not stored in the file, so no database inspection can show what
14
+ an application sets. `--source` is a text scan: for each mention of
15
+ journal_mode it reports whether a busy_timeout mention comes earlier in the
16
+ same file. It does not establish run order or which connection each belongs
17
+ to. Treat those lines as places to read, not verdicts. A source path that cannot
18
+ be read is listed as not scanned and the exit status is 2.
19
+ """
20
+ import argparse
21
+ import json
22
+ import os
23
+ import pathlib
24
+ import re
25
+ import sqlite3
26
+ import sys
27
+
28
+ NETWORK_FILESYSTEMS = {"9p", "drvfs", "cifs", "smb3", "smbfs", "nfs", "nfs4", "fuse.sshfs", "virtiofs"}
29
+ SHADOW_SUFFIXES = ("_data", "_idx", "_content", "_docsize", "_config", "_segments", "_segdir", "_stat", "_node", "_parent", "_rowid")
30
+ SOURCE_SUFFIXES = {".py", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".rs", ".go", ".rb", ".java", ".kt", ".cs", ".c", ".cc", ".cpp", ".swift", ".php", ".sql"}
31
+
32
+
33
+ def mount_for(path):
34
+ """Return (mount point, filesystem type, options) for the longest mount prefix."""
35
+ try:
36
+ with open("/proc/self/mountinfo", encoding="utf-8", errors="replace") as handle:
37
+ lines = handle.read().splitlines()
38
+ except OSError:
39
+ return None
40
+ real = os.path.realpath(path)
41
+ best = None
42
+ for line in lines:
43
+ left, _, right = line.partition(" - ")
44
+ fields = left.split()
45
+ if len(fields) < 6 or len(right.split()) < 3:
46
+ continue
47
+ point = fields[4].replace("\\040", " ")
48
+ if real == point or real.startswith(point.rstrip("/") + "/") or point == "/":
49
+ if best is None or len(point) > len(best[0]):
50
+ kind, _, super_options = right.split()[:3]
51
+ best = (point, kind, ",".join([fields[5], super_options]))
52
+ return best
53
+
54
+
55
+ def scan_source(paths):
56
+ """For each file that mentions journal_mode, note where busy_timeout appears.
57
+
58
+ Returns (findings, errors). A path that cannot be read is an error, so a
59
+ failed scan never looks like a scan that found nothing.
60
+ """
61
+ findings = []
62
+ errors = []
63
+ files = []
64
+
65
+ def walk_error(error):
66
+ errors.append({"file": str(error.filename), "error": error.strerror or str(error)})
67
+
68
+ for entry in paths:
69
+ if os.path.isdir(entry):
70
+ for root, dirs, names in os.walk(entry, onerror=walk_error):
71
+ dirs[:] = [d for d in dirs if d not in {".git", "node_modules", ".venv", "target", "dist"}]
72
+ files.extend(
73
+ os.path.join(root, n) for n in names if os.path.splitext(n)[1] in SOURCE_SUFFIXES
74
+ )
75
+ else:
76
+ files.append(entry)
77
+ for file in sorted(files):
78
+ try:
79
+ with open(file, encoding="utf-8", errors="replace") as handle:
80
+ lines = handle.read().splitlines()
81
+ except OSError as error:
82
+ errors.append({"file": file, "error": error.strerror or str(error)})
83
+ continue
84
+ journals = [i + 1 for i, l in enumerate(lines) if re.search(r"journal_mode", l, re.I)]
85
+ timeouts = [i + 1 for i, l in enumerate(lines) if re.search(r"busy_timeout|busyTimeout|set_busy_timeout", l, re.I)]
86
+ for journal in journals:
87
+ earlier = [t for t in timeouts if t < journal]
88
+ if earlier:
89
+ status = "busy_timeout at line %d comes before journal_mode at line %d" % (earlier[-1], journal)
90
+ elif timeouts:
91
+ status = "journal_mode at line %d comes before busy_timeout at line %d" % (journal, timeouts[0])
92
+ else:
93
+ status = "journal_mode at line %d; no busy_timeout in this file" % journal
94
+ findings.append({"file": file, "line": journal, "status": status, "ok": bool(earlier)})
95
+ return findings, errors
96
+
97
+
98
+ def inspect(path, quick_check=False):
99
+ result = {"path": os.path.abspath(path)}
100
+ size = os.path.getsize(path)
101
+ result["fileBytes"] = size
102
+ uri = pathlib.Path(result["path"]).as_uri() + "?mode=ro"
103
+ connection = sqlite3.connect(uri, uri=True, timeout=2)
104
+ try:
105
+ connection.execute("PRAGMA busy_timeout = 2000")
106
+
107
+ def pragma(name):
108
+ row = connection.execute("PRAGMA " + name).fetchone()
109
+ return row[0] if row else None
110
+
111
+ result["library"] = {"version": sqlite3.sqlite_version}
112
+ options = [r[0] for r in connection.execute("PRAGMA compile_options")]
113
+ result["library"]["memstatusDisabled"] = "DEFAULT_MEMSTATUS=0" in options
114
+ result["library"]["threadsafe"] = next((o for o in options if o.startswith("THREADSAFE")), None)
115
+ result["journalMode"] = pragma("journal_mode")
116
+ result["pageSize"] = pragma("page_size")
117
+ result["pageCount"] = pragma("page_count")
118
+ result["freelistCount"] = pragma("freelist_count")
119
+ result["autoVacuum"] = pragma("auto_vacuum")
120
+ result["userVersion"] = pragma("user_version")
121
+ result["encoding"] = pragma("encoding")
122
+ names = connection.execute(
123
+ "SELECT name, sql FROM sqlite_master WHERE type = 'table' ORDER BY name"
124
+ ).fetchall()
125
+ tables = [n for n, _ in names if not n.startswith("sqlite_")]
126
+ virtual = {n: s for n, s in names if s and s.upper().startswith("CREATE VIRTUAL TABLE")}
127
+ shadow = {n for table in virtual for n in tables if n in {table + s for s in SHADOW_SUFFIXES}}
128
+ plain = [n for n in tables if n not in virtual and n not in shadow]
129
+ result["tables"] = len(plain)
130
+ result["indexes"] = connection.execute(
131
+ "SELECT count(*) FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%'"
132
+ ).fetchone()[0]
133
+ result["virtualTables"] = {
134
+ n: (re.search(r"USING\s+(\w+)", s, re.I) or [None, "unknown"])[1].lower()
135
+ for n, s in virtual.items()
136
+ }
137
+ has_stat = any(n == "sqlite_stat1" for n, _ in names)
138
+ analyzed = set()
139
+ if has_stat:
140
+ analyzed = {r[0] for r in connection.execute("SELECT DISTINCT tbl FROM sqlite_stat1")}
141
+ result["statistics"] = {
142
+ "sqliteStat1": has_stat,
143
+ "rows": connection.execute("SELECT count(*) FROM sqlite_stat1").fetchone()[0] if has_stat else 0,
144
+ "tablesWithoutStatistics": [n for n in plain if n not in analyzed],
145
+ }
146
+ if quick_check:
147
+ result["quickCheck"] = [r[0] for r in connection.execute("PRAGMA quick_check(10)")]
148
+ finally:
149
+ connection.close()
150
+ for suffix, key in (("-wal", "walBytes"), ("-shm", "shmBytes")):
151
+ side = result["path"] + suffix
152
+ result[key] = os.path.getsize(side) if os.path.exists(side) else None
153
+ mount = mount_for(result["path"])
154
+ if mount:
155
+ point, kind, options = mount
156
+ result["filesystem"] = {"mount": point, "type": kind, "options": options}
157
+ else:
158
+ result["filesystem"] = None
159
+ return result
160
+
161
+
162
+ def notes(result):
163
+ out = []
164
+ if result["journalMode"] != "wal":
165
+ out.append("journal_mode is %s, not wal; in a rollback journal a writer's commit waits for readers and blocks new ones while it commits" % result["journalMode"])
166
+ stats = result["statistics"]
167
+ if not stats["sqliteStat1"]:
168
+ out.append("no sqlite_stat1: the planner has no statistics; run PRAGMA optimize at a time you choose (the first run on a large file can take over a minute and hold the write lock)")
169
+ elif stats["tablesWithoutStatistics"]:
170
+ out.append("tables with no statistics rows: " + ", ".join(stats["tablesWithoutStatistics"][:8]))
171
+ pages = result["pageCount"] or 0
172
+ if pages and result["freelistCount"] and result["freelistCount"] / pages > 0.2:
173
+ out.append("more than 20%% of pages are free (%d of %d); the file is larger than its content" % (result["freelistCount"], pages))
174
+ if result["walBytes"] and result["walBytes"] > result["fileBytes"]:
175
+ out.append("the WAL file is larger than the database; look for a long-running reader or a blocked checkpoint")
176
+ fs = result["filesystem"]
177
+ if fs:
178
+ if fs["type"] in NETWORK_FILESYSTEMS:
179
+ out.append("filesystem %s: WAL needs shared memory on one host and does not work reliably here" % fs["type"])
180
+ if re.search(r"(^|,)discard(,|$)", fs["options"]):
181
+ out.append("mounted with online discard; this stalled fsync on one host (see the evidence), so check fsync latency")
182
+ if "quickCheck" in result and result["quickCheck"] != ["ok"]:
183
+ out.append("quick_check reported: " + "; ".join(result["quickCheck"][:3]))
184
+ return out
185
+
186
+
187
+ def render(result, source, source_errors=()):
188
+ lines = ["database: %s" % result["path"]]
189
+ lines.append("size: %d bytes; %s pages of %s bytes; %s free" % (
190
+ result["fileBytes"], result["pageCount"], result["pageSize"], result["freelistCount"]))
191
+ lines.append("journal_mode: %s; auto_vacuum: %s; user_version: %s; encoding: %s" % (
192
+ result["journalMode"], result["autoVacuum"], result["userVersion"], result["encoding"]))
193
+ lines.append("tables: %d; indexes: %d; virtual tables: %s" % (
194
+ result["tables"], result["indexes"],
195
+ ", ".join("%s (%s)" % kv for kv in result["virtualTables"].items()) or "none"))
196
+ stats = result["statistics"]
197
+ lines.append("statistics: sqlite_stat1 %s, %d rows" % ("present" if stats["sqliteStat1"] else "absent", stats["rows"]))
198
+ lines.append("wal file: %s; shm file: %s" % (result["walBytes"], result["shmBytes"]))
199
+ fs = result["filesystem"]
200
+ lines.append("filesystem: %s" % ("%s at %s (%s)" % (fs["type"], fs["mount"], fs["options"]) if fs else "not available on this platform"))
201
+ lines.append("inspector library: SQLite %s, %s" % (result["library"]["version"], result["library"]["threadsafe"]))
202
+ lines.append("not in the file: busy_timeout, synchronous, foreign_keys, cache_size, mmap_size, temp_store; read the code that opens the connection")
203
+ for note in notes(result):
204
+ lines.append("note: " + note)
205
+ if source is not None:
206
+ lines.append("source scan (journal_mode mentions): %d" % len(source))
207
+ for item in source:
208
+ lines.append(" %s: %s%s" % (item["file"], item["status"], "" if item["ok"] else " <- read this"))
209
+ for item in source_errors:
210
+ lines.append(" not scanned: %s (%s)" % (item["file"], item["error"]))
211
+ return "\n".join(lines)
212
+
213
+
214
+ def main(argv=None):
215
+ parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
216
+ parser.add_argument("database")
217
+ parser.add_argument("--source", nargs="+", metavar="PATH", help="files or directories to scan for busy_timeout order")
218
+ parser.add_argument("--quick-check", action="store_true", help="also run PRAGMA quick_check (reads the whole file)")
219
+ parser.add_argument("--json", action="store_true")
220
+ args = parser.parse_args(argv)
221
+ if not os.path.isfile(args.database):
222
+ print("error: not a file: %s" % args.database, file=sys.stderr)
223
+ return 2
224
+ try:
225
+ result = inspect(args.database, args.quick_check)
226
+ except (sqlite3.Error, OSError) as error:
227
+ print("error: %s" % error, file=sys.stderr)
228
+ return 2
229
+ source, source_errors = scan_source(args.source) if args.source else (None, [])
230
+ if args.json:
231
+ payload = dict(result)
232
+ payload["notes"] = notes(result)
233
+ if source is not None:
234
+ payload["source"] = source
235
+ payload["sourceErrors"] = source_errors
236
+ print(json.dumps(payload, indent=2, sort_keys=True))
237
+ else:
238
+ print(render(result, source, source_errors))
239
+ for item in source_errors:
240
+ print("error: could not scan %s: %s" % (item["file"], item["error"]), file=sys.stderr)
241
+ return 2 if source_errors else 0
242
+
243
+
244
+ if __name__ == "__main__":
245
+ sys.exit(main())
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: hr-stock-ui-audit
3
+ description: Find template-default styling nobody chose for this product (stock violet accents, purple-to-blue gradients, one stock font, emoji icons, glow and blur effects), triage each hit in context, and report or fix the real ones. Use when asked to audit or remove generic or AI-looking styling in existing UI. It does not set a visual direction.
4
+ ---
5
+
6
+ # Stock UI audit
7
+
8
+ Generated and starter-kit interfaces share a recognizable look: an indigo or
9
+ violet accent, a purple-to-blue hero gradient, Inter everywhere, frosted cards,
10
+ emoji in the nav. None of these is wrong on its own. The problem is a choice
11
+ that came with the template instead of from the product. This skill finds those
12
+ choices with a static scanner, then sorts them by judgment. The scanner
13
+ produces candidates; only triage produces findings.
14
+
15
+ This skill removes unchosen defaults. It does not invent a brand. If the
16
+ product has no visual direction yet, say so and ask for one rather than
17
+ replacing one default with another.
18
+
19
+ ## Before you scan
20
+
21
+ 1. Read the brief, the design tokens and the theme or component-library config.
22
+ Note every color, font and component the product asked for. Those stay,
23
+ even when the scanner flags them.
24
+ 2. Settle the mode from the request:
25
+ - **Review**: report findings only. Edit nothing.
26
+ - **Cleanup**: fix confirmed findings, and only inside the area the user
27
+ named (a page, a component folder, the whole app). Report anything you
28
+ notice outside it instead of fixing it.
29
+ 3. Pick the scan scope: the source folders that render UI, not the whole
30
+ repository.
31
+
32
+ ## Run the scanner
33
+
34
+ ```sh
35
+ bun <this skill's directory>/scripts/scan.mjs [--json] [--fail-on high|medium|low] <paths>...
36
+ ```
37
+
38
+ Node 22 or later works too. It walks directories, skips dependency, build and
39
+ coverage output and minified files, and reads `.html .css .scss .js .ts .jsx
40
+ .tsx .vue .svelte .astro`. It prints the number of files scanned. Exit 0 means
41
+ nothing reached the failing severity (high by default), 1 means something did,
42
+ 2 means bad usage or zero files scanned. A 2 is never a clean result. Pass
43
+ `--fail-on medium` when violet accents, a single stock font or emoji icons
44
+ should fail the run. Do not add the scanner as a CI gate unless asked.
45
+
46
+ | Rule | Severity | Catches |
47
+ | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `purple-blue-gradient` | high | gradients whose stops are all cool hues with at least one violet, in CSS or Tailwind `from-`/`to-` classes |
49
+ | `violet-accent` | medium | any hex, rgb, hsl, oklch or bare HSL token whose computed hue lands in the indigo-to-purple band, plus Tailwind indigo/violet/purple classes |
50
+ | `gradient-text` | medium | `background-clip: text` and `bg-clip-text` |
51
+ | `single-stock-font` | medium | one stock sans (Inter, Roboto, Poppins and similar) as the only named face across two or more declarations in the scanned set |
52
+ | `emoji-icon` | medium | emoji inside headings, buttons, links, nav items or `icon`/`label`/`title` fields |
53
+ | `neon-glow` | medium | zero-offset, wide-blur shadows in a saturated color, and Tailwind colored shadows |
54
+ | `frosted-glass` | low | `backdrop-filter: blur()` and `backdrop-blur` |
55
+ | `library-default-token` | low | untouched default primaries from Bootstrap, Material, MUI, Ant Design, Chakra, shadcn/ui and the Vite starter |
56
+ | `tracked-caps` | low | uppercase text with wide letter spacing |
57
+
58
+ The font rule looks across every file in one run, so scan the whole UI scope
59
+ together; scanning one file at a time hides it.
60
+
61
+ ## Triage every match
62
+
63
+ Open each flagged line and read enough around it to see what the element is for.
64
+ Give every match exactly one verdict:
65
+
66
+ - **Unchosen default**: nothing in the brief, tokens or history asks for it,
67
+ and it reads like a starter template. This is a finding.
68
+ - **Deliberate choice**: the brief, a token file, a brand guide or a commit
69
+ message shows someone picked it. Keep it and name the evidence.
70
+ - **False positive**: the match is not doing what the rule assumes, such as a
71
+ violet hue in a syntax-highlighting theme, a blur on a modal backdrop over
72
+ busy content, or an emoji inside user-generated sample data. Leave it and say
73
+ why.
74
+ - **Unresolved**: you cannot tell whether someone chose it. It is not a
75
+ finding and you change nothing. Report what is missing and the question
76
+ that would settle it.
77
+
78
+ ## Fixing what is real
79
+
80
+ - Fix the cause, not the line. Five violet hexes usually mean one missing
81
+ accent token; define the product's palette and type once and point the
82
+ components at it.
83
+ - Choose replacements that serve the content: contrast that holds, an accent
84
+ that marks the one action that matters, type that suits the reading load.
85
+ - Never nudge a value just to leave the scanner's range, such as shifting a hue
86
+ a few degrees or swapping a hex for an equivalent `oklch()`. If the color is
87
+ wrong, change it; if it is right, keep it and record why.
88
+ - Never change a brand color, a status color (success, warning, error) or a
89
+ focus indicator to make the scan pass. Those carry meaning; record them as
90
+ deliberate.
91
+ - Replace emoji icons with the project's existing icon set. Add a new icon
92
+ dependency only if the user agrees.
93
+
94
+ ## What the scanner cannot see
95
+
96
+ Check these by reading the rendered page, since no pattern match finds them:
97
+
98
+ - every section built from the same centered headline, subtitle and two buttons
99
+ - a three-card feature grid with an icon, title and line of text in each card
100
+ - a small badge or pill sitting above the main heading
101
+ - cards marked by a thick colored left border
102
+ - a stat banner of large numbers ("10k+ users", "99.9% uptime")
103
+ - numbered "1, 2, 3" step sections
104
+ - a dark theme forced on with no light option the brief asked for
105
+ - the same large corner radius and soft shadow on every surface
106
+ - spacing and type sizes that ignore the project's scale
107
+ - copy that could describe any product
108
+
109
+ ## Verify
110
+
111
+ 1. Rerun the scanner on the same scope and confirm each remaining match has a
112
+ recorded verdict.
113
+ 2. Look at the rendered UI at the sizes that matter (a narrow phone width and
114
+ a desktop width at least), in every theme the product ships.
115
+ 3. A clean scan says nothing about accessibility, layout or overall quality.
116
+ Check contrast and focus states on anything you changed.
117
+
118
+ If you could not render the UI, say the review was static only.
119
+
120
+ ## Report
121
+
122
+ Write the report in this order:
123
+
124
+ 1. Scope scanned, files scanned, mode, and the scanner's exit code.
125
+ 2. Findings fixed (or, in review mode, proposed), each with `file:line`, the
126
+ rule, and what replaced it.
127
+ 3. Matches kept as deliberate, each with its evidence.
128
+ 4. Unresolved matches, each with what is missing and the question that
129
+ settles it.
130
+ 5. False positives, each with one line on why.
131
+ 6. Issues the scanner cannot see, if you found any.
132
+ 7. How you verified: rerun result, sizes and themes viewed, or "static only".
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "HR: Stock UI audit"
3
+ short_description: "Audit or clean up unchosen template styling in UI code"