@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.
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +378 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +125 -0
- package/composition.mjs +361 -0
- package/config-view.mjs +209 -0
- package/config.mjs +381 -0
- package/docs/assets/banner-dark.png +0 -0
- package/docs/assets/banner-light.png +0 -0
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +269 -0
- package/docs/skill-names.md +50 -0
- package/examples/person/house-rules.json +15 -0
- package/examples/person/rules/10-tooling.md +8 -0
- package/examples/person/rules/20-reporting.md +9 -0
- package/examples/project/AGENTS.md +18 -0
- package/package.json +42 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +340 -0
- package/rules/modifiers/backlog-outline.md +25 -0
- package/rules/modifiers/backlog-visual.md +16 -0
- package/rules/modifiers/coded-offers.md +70 -0
- package/rules/modifiers/cross-session-relay.md +23 -0
- package/rules/modifiers/effort-estimates.md +41 -0
- package/rules/modifiers/land-when-done.md +12 -0
- package/rules/modifiers/low-quota-handoff.md +20 -0
- package/rules/modifiers/model-default-questions.md +33 -0
- package/rules/modifiers/multi-agent.md +38 -0
- package/rules/modifiers/no-attribution.md +16 -0
- package/rules/modifiers/periodic-updates.md +25 -0
- package/rules/modifiers/plan-files.md +82 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +15 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +19 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/upstream-contributions.md +45 -0
- package/rules/modifiers/web-image-formats.md +35 -0
- package/rules/references/backlog-view.md +154 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +51 -0
- package/rules/references/release-batching.md +66 -0
- package/rules/references/squash-landing.md +50 -0
- package/rules/references/thread-visuals.md +45 -0
- package/setup.mjs +445 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +91 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +36 -0
- package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
- package/skills/hr-api-exposure-check/SKILL.md +159 -0
- package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
- package/skills/hr-audit-choices/SKILL.md +67 -0
- package/skills/hr-audit-choices/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/NOTICE.md +36 -0
- package/skills/hr-benchmarking/SKILL.md +279 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +130 -0
- package/skills/hr-benchmarking/references/tools.md +165 -0
- package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
- package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
- package/skills/hr-better-accessibility/LICENSE +22 -0
- package/skills/hr-better-accessibility/NOTICE.md +46 -0
- package/skills/hr-better-accessibility/SKILL.md +119 -0
- package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
- package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
- package/skills/hr-better-accessibility/forms.md +82 -0
- package/skills/hr-better-accessibility/hit-areas.md +98 -0
- package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
- package/skills/hr-better-accessibility/screen-readers.md +102 -0
- package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
- package/skills/hr-better-colors/LICENSE +21 -0
- package/skills/hr-better-colors/NOTICE.md +37 -0
- package/skills/hr-better-colors/SKILL.md +114 -0
- package/skills/hr-better-colors/agents/openai.yaml +3 -0
- package/skills/hr-better-colors/color-formats.md +90 -0
- package/skills/hr-better-colors/color-usage.md +140 -0
- package/skills/hr-better-colors/contrast.md +80 -0
- package/skills/hr-better-colors/palette-generation.md +104 -0
- package/skills/hr-better-colors/palette-structure.md +76 -0
- package/skills/hr-better-colors/token-naming.md +97 -0
- package/skills/hr-better-layout/LICENSE +22 -0
- package/skills/hr-better-layout/NOTICE.md +48 -0
- package/skills/hr-better-layout/SKILL.md +92 -0
- package/skills/hr-better-layout/agents/openai.yaml +3 -0
- package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
- package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
- package/skills/hr-better-typography/LICENSE +22 -0
- package/skills/hr-better-typography/NOTICE.md +46 -0
- package/skills/hr-better-typography/SKILL.md +172 -0
- package/skills/hr-better-typography/agents/openai.yaml +3 -0
- package/skills/hr-better-typography/choosing-fonts.md +69 -0
- package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
- package/skills/hr-better-typography/details-and-accessibility.md +119 -0
- package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
- package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
- package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
- package/skills/hr-better-writing/LICENSE +22 -0
- package/skills/hr-better-writing/NOTICE.md +46 -0
- package/skills/hr-better-writing/SKILL.md +122 -0
- package/skills/hr-better-writing/agents/openai.yaml +3 -0
- package/skills/hr-code-review/LICENSE +21 -0
- package/skills/hr-code-review/NOTICE.md +26 -0
- package/skills/hr-code-review/SKILL.md +163 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/acp-client.md +43 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-code-review/references/impact.md +103 -0
- package/skills/hr-code-review/references/panel-adjudication.md +58 -0
- package/skills/hr-code-review/references/panel-plan.md +61 -0
- package/skills/hr-code-review/references/panel-recommendations.json +105 -0
- package/skills/hr-code-review/references/panel-recommendations.md +46 -0
- package/skills/hr-code-review/references/panel-setup.md +54 -0
- package/skills/hr-code-review/references/reviewer-role.md +31 -0
- package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
- package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
- package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
- package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
- package/skills/hr-design-exploration/SKILL.md +105 -0
- package/skills/hr-design-exploration/agents/openai.yaml +4 -0
- package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
- package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
- package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/hr-diagnosing-bugs/references/reporting.md +97 -0
- package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
- package/skills/hr-explain-code/NOTICE.md +37 -0
- package/skills/hr-explain-code/SKILL.md +110 -0
- package/skills/hr-explain-code/agents/openai.yaml +5 -0
- package/skills/hr-explain-code/references/helper-brief.md +39 -0
- package/skills/hr-extract-shared-steps/SKILL.md +134 -0
- package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
- package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
- package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
- package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
- package/skills/hr-handoff/LICENSE +21 -0
- package/skills/hr-handoff/NOTICE.md +29 -0
- package/skills/hr-handoff/SKILL.md +114 -0
- package/skills/hr-handoff/agents/openai.yaml +5 -0
- package/skills/hr-handoff/assets/ledger.example.json +10 -0
- package/skills/hr-handoff/assets/receipt.example.json +76 -0
- package/skills/hr-handoff/assets/source.example.json +20 -0
- package/skills/hr-handoff/references/reconciliation.md +124 -0
- package/skills/hr-handoff/references/successor-thread.md +76 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +324 -0
- package/skills/hr-house-rules-setup/SKILL.md +114 -0
- package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
- package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
- package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
- package/skills/hr-lean-plan/SKILL.md +146 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +186 -0
- package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
- package/skills/hr-manual-qa/NOTICE.md +36 -0
- package/skills/hr-manual-qa/SKILL.md +37 -0
- package/skills/hr-manual-qa/agents/openai.yaml +3 -0
- package/skills/hr-navigation-retro/LICENSE +21 -0
- package/skills/hr-navigation-retro/NOTICE.md +5 -0
- package/skills/hr-navigation-retro/SKILL.md +86 -0
- package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
- package/skills/hr-navigation-retro/references/inputs.md +67 -0
- package/skills/hr-navigation-retro/scripts/retro.py +552 -0
- package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
- package/skills/hr-ordering-tests/SKILL.md +51 -0
- package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
- package/skills/hr-plain-prose/SKILL.md +264 -0
- package/skills/hr-plain-prose/agents/openai.yaml +3 -0
- package/skills/hr-plain-prose/scripts/check.mjs +331 -0
- package/skills/hr-pr-followup/LICENSE +21 -0
- package/skills/hr-pr-followup/NOTICE.md +9 -0
- package/skills/hr-pr-followup/SKILL.md +74 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +111 -0
- package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
- package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
- package/skills/hr-progress-report/SKILL.md +49 -0
- package/skills/hr-progress-report/agents/openai.yaml +5 -0
- package/skills/hr-read-reddit/SKILL.md +44 -0
- package/skills/hr-read-reddit/agents/openai.yaml +2 -0
- package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
- package/skills/hr-read-x-links/SKILL.md +42 -0
- package/skills/hr-read-x-links/agents/openai.yaml +2 -0
- package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
- package/skills/hr-research-shared-links/SKILL.md +103 -0
- package/skills/hr-split-to-prs/LICENSE +21 -0
- package/skills/hr-split-to-prs/NOTICE.md +9 -0
- package/skills/hr-split-to-prs/SKILL.md +61 -0
- package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/SKILL.md +174 -0
- package/skills/hr-sqlite/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/references/evidence.md +94 -0
- package/skills/hr-sqlite/references/tool-decisions.md +39 -0
- package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
- package/skills/hr-stock-ui-audit/SKILL.md +132 -0
- package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
- package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
- package/skills/hr-tdd/LICENSE +21 -0
- package/skills/hr-tdd/NOTICE.md +10 -0
- package/skills/hr-tdd/SKILL.md +40 -0
- package/skills/hr-tdd/agents/openai.yaml +3 -0
- package/skills/hr-tdd/mocking.md +39 -0
- package/skills/hr-tdd/tests.md +77 -0
- package/skills/hr-test-audit/LICENSE +21 -0
- package/skills/hr-test-audit/NOTICE.md +67 -0
- package/skills/hr-test-audit/SKILL.md +146 -0
- package/skills/hr-test-audit/agents/openai.yaml +3 -0
- package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
- package/skills/hr-usage-report/SKILL.md +90 -0
- package/skills/hr-usage-report/agents/openai.yaml +2 -0
- package/skills/hr-usage-report/assets/example-report.json +125 -0
- package/skills/hr-usage-report/assets/report-template.html +377 -0
- package/skills/hr-usage-report/scripts/render-report.ts +86 -0
- package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
- package/skills/hr-what-next/SKILL.md +151 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +59 -0
- package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
- package/skills/hr-writing-for-agents/NOTICE.md +46 -0
- package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
- package/skills/hr-writing-for-agents/SKILL.md +108 -0
- package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
- package/skills/hr-writing-pr/NOTICE.md +48 -0
- package/skills/hr-writing-pr/SKILL.md +119 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
- 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".
|