@bompus/house-rules 0.0.0-stage → 0.11.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 (191) hide show
  1. package/CONTRIBUTING.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +347 -2
  4. package/THIRD_PARTY_NOTICES.md +188 -0
  5. package/compose.mjs +119 -0
  6. package/composition.mjs +353 -0
  7. package/config-view.mjs +194 -0
  8. package/config.mjs +320 -0
  9. package/docs/assets/banner-dark.png +0 -0
  10. package/docs/assets/banner-light.png +0 -0
  11. package/docs/assets/overview.png +0 -0
  12. package/docs/configuration.md +193 -0
  13. package/docs/npm-release.md +45 -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 +40 -4
  20. package/rule-references.mjs +91 -0
  21. package/rules/core.md +431 -0
  22. package/rules/modifiers/coded-offers.md +40 -0
  23. package/rules/modifiers/effort-estimates.md +41 -0
  24. package/rules/modifiers/land-when-done.md +12 -0
  25. package/rules/modifiers/low-quota-handoff.md +19 -0
  26. package/rules/modifiers/multi-agent.md +34 -0
  27. package/rules/modifiers/no-attribution.md +10 -0
  28. package/rules/modifiers/plan-files.md +50 -0
  29. package/rules/modifiers/question-cards.md +48 -0
  30. package/rules/modifiers/release-batching.md +13 -0
  31. package/rules/modifiers/scratch-on-disk.md +15 -0
  32. package/rules/modifiers/shared-host-load.md +48 -0
  33. package/rules/modifiers/solo-operator.md +13 -0
  34. package/rules/modifiers/squash-landing.md +22 -0
  35. package/rules/modifiers/swarmail.md +12 -0
  36. package/rules/references/github-actions.md +45 -0
  37. package/rules/references/landing.md +39 -0
  38. package/rules/references/multi-agent.md +47 -0
  39. package/rules/references/release-batching.md +43 -0
  40. package/rules/references/squash-landing.md +44 -0
  41. package/setup.mjs +375 -0
  42. package/skills/hr-agent-guidance-audit/LICENSE +21 -0
  43. package/skills/hr-agent-guidance-audit/SKILL.md +86 -0
  44. package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
  45. package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
  46. package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
  47. package/skills/hr-agent-guidance-refresh/SKILL.md +68 -0
  48. package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
  49. package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
  50. package/skills/hr-api-exposure-check/SKILL.md +159 -0
  51. package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
  52. package/skills/hr-audit-choices/SKILL.md +67 -0
  53. package/skills/hr-audit-choices/agents/openai.yaml +2 -0
  54. package/skills/hr-benchmarking/NOTICE.md +36 -0
  55. package/skills/hr-benchmarking/SKILL.md +316 -0
  56. package/skills/hr-benchmarking/agents/openai.yaml +2 -0
  57. package/skills/hr-benchmarking/references/javascript.md +17 -0
  58. package/skills/hr-benchmarking/references/priority-findings-20261006.md +115 -0
  59. package/skills/hr-benchmarking/references/tools.md +160 -0
  60. package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
  61. package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
  62. package/skills/hr-better-accessibility/LICENSE +22 -0
  63. package/skills/hr-better-accessibility/NOTICE.md +46 -0
  64. package/skills/hr-better-accessibility/SKILL.md +119 -0
  65. package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
  66. package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
  67. package/skills/hr-better-accessibility/forms.md +82 -0
  68. package/skills/hr-better-accessibility/hit-areas.md +98 -0
  69. package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
  70. package/skills/hr-better-accessibility/screen-readers.md +101 -0
  71. package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
  72. package/skills/hr-better-colors/LICENSE +21 -0
  73. package/skills/hr-better-colors/NOTICE.md +37 -0
  74. package/skills/hr-better-colors/SKILL.md +114 -0
  75. package/skills/hr-better-colors/agents/openai.yaml +3 -0
  76. package/skills/hr-better-colors/color-formats.md +90 -0
  77. package/skills/hr-better-colors/color-usage.md +140 -0
  78. package/skills/hr-better-colors/contrast.md +80 -0
  79. package/skills/hr-better-colors/palette-generation.md +104 -0
  80. package/skills/hr-better-colors/palette-structure.md +76 -0
  81. package/skills/hr-better-colors/token-naming.md +97 -0
  82. package/skills/hr-better-layout/LICENSE +22 -0
  83. package/skills/hr-better-layout/NOTICE.md +48 -0
  84. package/skills/hr-better-layout/SKILL.md +92 -0
  85. package/skills/hr-better-layout/agents/openai.yaml +3 -0
  86. package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
  87. package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
  88. package/skills/hr-better-typography/LICENSE +22 -0
  89. package/skills/hr-better-typography/NOTICE.md +46 -0
  90. package/skills/hr-better-typography/SKILL.md +172 -0
  91. package/skills/hr-better-typography/agents/openai.yaml +3 -0
  92. package/skills/hr-better-typography/choosing-fonts.md +69 -0
  93. package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
  94. package/skills/hr-better-typography/details-and-accessibility.md +119 -0
  95. package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
  96. package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
  97. package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
  98. package/skills/hr-better-writing/LICENSE +22 -0
  99. package/skills/hr-better-writing/NOTICE.md +46 -0
  100. package/skills/hr-better-writing/SKILL.md +122 -0
  101. package/skills/hr-better-writing/agents/openai.yaml +3 -0
  102. package/skills/hr-change-impact/SKILL.md +113 -0
  103. package/skills/hr-change-impact/agents/openai.yaml +5 -0
  104. package/skills/hr-code-review/LICENSE +21 -0
  105. package/skills/hr-code-review/NOTICE.md +26 -0
  106. package/skills/hr-code-review/SKILL.md +147 -0
  107. package/skills/hr-code-review/agents/openai.yaml +3 -0
  108. package/skills/hr-code-review/references/evidence.md +57 -0
  109. package/skills/hr-design-exploration/SKILL.md +82 -0
  110. package/skills/hr-design-exploration/agents/openai.yaml +4 -0
  111. package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
  112. package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
  113. package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
  114. package/skills/hr-diagnosing-bugs/references/reporting.md +72 -0
  115. package/skills/hr-explain-code/NOTICE.md +37 -0
  116. package/skills/hr-explain-code/SKILL.md +110 -0
  117. package/skills/hr-explain-code/agents/openai.yaml +5 -0
  118. package/skills/hr-explain-code/references/helper-brief.md +39 -0
  119. package/skills/hr-extract-shared-steps/SKILL.md +134 -0
  120. package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
  121. package/skills/hr-handoff/SKILL.md +91 -0
  122. package/skills/hr-handoff/agents/openai.yaml +5 -0
  123. package/skills/hr-handoff/assets/ledger.example.json +10 -0
  124. package/skills/hr-handoff/assets/receipt.example.json +76 -0
  125. package/skills/hr-handoff/assets/source.example.json +20 -0
  126. package/skills/hr-handoff/references/reconciliation.md +105 -0
  127. package/skills/hr-handoff/scripts/check-reconciliation.mjs +282 -0
  128. package/skills/hr-house-rules-setup/SKILL.md +100 -0
  129. package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
  130. package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
  131. package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
  132. package/skills/hr-lean-plan/SKILL.md +142 -0
  133. package/skills/hr-lean-plan/agents/openai.yaml +5 -0
  134. package/skills/hr-maintainability-review/SKILL.md +176 -0
  135. package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
  136. package/skills/hr-manual-qa/NOTICE.md +36 -0
  137. package/skills/hr-manual-qa/SKILL.md +37 -0
  138. package/skills/hr-manual-qa/agents/openai.yaml +3 -0
  139. package/skills/hr-navigation-retro/LICENSE +21 -0
  140. package/skills/hr-navigation-retro/NOTICE.md +5 -0
  141. package/skills/hr-navigation-retro/SKILL.md +86 -0
  142. package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
  143. package/skills/hr-navigation-retro/references/inputs.md +67 -0
  144. package/skills/hr-navigation-retro/scripts/retro.py +552 -0
  145. package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
  146. package/skills/hr-ordering-tests/SKILL.md +51 -0
  147. package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
  148. package/skills/hr-plain-prose/SKILL.md +261 -0
  149. package/skills/hr-plain-prose/agents/openai.yaml +3 -0
  150. package/skills/hr-plain-prose/scripts/check.mjs +331 -0
  151. package/skills/hr-pr-followup/LICENSE +21 -0
  152. package/skills/hr-pr-followup/NOTICE.md +9 -0
  153. package/skills/hr-pr-followup/SKILL.md +71 -0
  154. package/skills/hr-pr-followup/agents/openai.yaml +2 -0
  155. package/skills/hr-pr-followup/references/review-helpers.md +90 -0
  156. package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
  157. package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
  158. package/skills/hr-progress-report/SKILL.md +49 -0
  159. package/skills/hr-progress-report/agents/openai.yaml +5 -0
  160. package/skills/hr-read-reddit/SKILL.md +44 -0
  161. package/skills/hr-read-reddit/agents/openai.yaml +2 -0
  162. package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
  163. package/skills/hr-read-x-links/SKILL.md +42 -0
  164. package/skills/hr-read-x-links/agents/openai.yaml +2 -0
  165. package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
  166. package/skills/hr-split-to-prs/LICENSE +21 -0
  167. package/skills/hr-split-to-prs/NOTICE.md +9 -0
  168. package/skills/hr-split-to-prs/SKILL.md +61 -0
  169. package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
  170. package/skills/hr-stock-ui-audit/SKILL.md +132 -0
  171. package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
  172. package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
  173. package/skills/hr-tdd/LICENSE +21 -0
  174. package/skills/hr-tdd/NOTICE.md +10 -0
  175. package/skills/hr-tdd/SKILL.md +40 -0
  176. package/skills/hr-tdd/agents/openai.yaml +3 -0
  177. package/skills/hr-tdd/mocking.md +39 -0
  178. package/skills/hr-tdd/tests.md +77 -0
  179. package/skills/hr-test-audit/SKILL.md +146 -0
  180. package/skills/hr-test-audit/agents/openai.yaml +3 -0
  181. package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
  182. package/skills/hr-what-next/SKILL.md +61 -0
  183. package/skills/hr-what-next/agents/openai.yaml +4 -0
  184. package/skills/hr-what-next/references/reporting.md +68 -0
  185. package/skills/hr-writing-for-agents/NOTICE.md +46 -0
  186. package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
  187. package/skills/hr-writing-for-agents/SKILL.md +108 -0
  188. package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
  189. package/skills/hr-writing-pr/NOTICE.md +48 -0
  190. package/skills/hr-writing-pr/SKILL.md +104 -0
  191. package/skills/hr-writing-pr/agents/openai.yaml +2 -0
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env bun
2
+ // Read X/Twitter posts through the public fxtwitter API: full text, expanded
3
+ // links, quoted posts, long-form articles, and downloadable images.
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { parseArgs as parseCli } from "node:util";
7
+
8
+ const API = "https://api.fxtwitter.com";
9
+ // pbs.twimg.com answers 403 without a browser-like user agent.
10
+ const HEADERS = { "user-agent": "Mozilla/5.0" };
11
+ const HOSTS = /^(?:www\.|mobile\.)?(?:x|twitter|fixupx|fxtwitter|vxtwitter|fixvx)\.com$/;
12
+
13
+ // The parts of fxtwitter's response this script reads.
14
+ type Facet = { type?: string; original?: string; replacement?: string; indices: [number, number] };
15
+ type ArticleMedia = { media_id?: string; media_info?: { original_img_url?: string } };
16
+ type Block = { type?: string; text?: string; entityRanges?: { key?: number | string }[] };
17
+ type Entity = { type?: string; data?: { mediaItems?: { mediaId?: string }[] } };
18
+ export type Article = {
19
+ title?: string;
20
+ content?: { blocks?: Block[]; entityMap?: { key: number | string; value: Entity }[] };
21
+ cover_media?: ArticleMedia;
22
+ media_entities?: ArticleMedia[];
23
+ };
24
+ export type Tweet = {
25
+ id: string;
26
+ url?: string;
27
+ text?: string;
28
+ created_at?: string;
29
+ raw_text?: { text?: string; facets?: Facet[] };
30
+ author?: { screen_name?: string; name?: string };
31
+ replying_to?: string;
32
+ replying_to_status?: string;
33
+ media?: {
34
+ photos?: { url: string }[];
35
+ videos?: { url: string; thumbnail_url?: string; duration?: number }[];
36
+ };
37
+ article?: Article;
38
+ quote?: Tweet;
39
+ };
40
+
41
+ /** `{ user, id }` for a post URL on x.com or a known mirror; null otherwise. */
42
+ export function parseStatusUrl(input: string): { user: string; id: string } | null {
43
+ let url: URL;
44
+ try {
45
+ url = new URL(input);
46
+ } catch {
47
+ return null;
48
+ }
49
+ if (!HOSTS.test(url.hostname)) {
50
+ return null;
51
+ }
52
+ const match = /^\/([^/]+)\/status(?:es)?\/(\d+)/.exec(url.pathname);
53
+ return match ? { user: match[1]!, id: match[2]! } : null;
54
+ }
55
+
56
+ /** Post text with t.co links replaced by their targets. */
57
+ export function expandedText(tweet: Tweet): string {
58
+ const text = tweet.raw_text?.text ?? tweet.text ?? "";
59
+ const facets = (tweet.raw_text?.facets ?? [])
60
+ .filter((f) => f.type === "url" && f.original && f.replacement)
61
+ .sort((a, b) => b.indices[0] - a.indices[0]);
62
+ let out = text;
63
+ for (const f of facets) {
64
+ out = out.slice(0, f.indices[0]) + f.replacement + out.slice(f.indices[1]);
65
+ }
66
+ return out;
67
+ }
68
+
69
+ export type Photo = { url: string; id: string | null };
70
+
71
+ /**
72
+ * Article body as Markdown-ish text. An image block becomes `[image N]`, where N
73
+ * is that image's position in `mediaOf(tweet).photos` (and its downloaded file).
74
+ */
75
+ export function articleText(article: Article | undefined, photos: Photo[] = []): string {
76
+ const blocks = article?.content?.blocks ?? [];
77
+ const entities = new Map(
78
+ (article?.content?.entityMap ?? []).map((e) => [String(e.key), e.value]),
79
+ );
80
+ const lines: string[] = [];
81
+ let ordered = 0;
82
+ for (const block of blocks) {
83
+ const text = (block.text ?? "").trim();
84
+ if (block.type !== "ordered-list-item") {
85
+ ordered = 0;
86
+ }
87
+ if (block.type === "atomic") {
88
+ const entity = entities.get(String(block.entityRanges?.[0]?.key));
89
+ const mediaId = entity?.type === "MEDIA" ? entity.data?.mediaItems?.[0]?.mediaId : null;
90
+ if (mediaId) {
91
+ const n = photos.findIndex((p) => p.id === mediaId);
92
+ lines.push(n >= 0 ? `[image ${n + 1}]` : "[image]");
93
+ }
94
+ continue;
95
+ }
96
+ if (!text) {
97
+ continue;
98
+ }
99
+ if (block.type === "header-one") {
100
+ lines.push(`# ${text}`);
101
+ } else if (block.type === "header-two") {
102
+ lines.push(`## ${text}`);
103
+ } else if (block.type === "unordered-list-item") {
104
+ lines.push(`- ${text}`);
105
+ } else if (block.type === "ordered-list-item") {
106
+ lines.push(`${++ordered}. ${text}`);
107
+ } else if (block.type === "blockquote") {
108
+ lines.push(`> ${text}`);
109
+ } else {
110
+ lines.push(text);
111
+ }
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+
116
+ /** Every image and video a post carries, including article images. */
117
+ export function mediaOf(tweet: Tweet): {
118
+ photos: Photo[];
119
+ videos: { url: string; thumbnail?: string; duration?: number }[];
120
+ } {
121
+ const photos: Photo[] = (tweet.media?.photos ?? []).map((p) => ({ url: p.url, id: null }));
122
+ const article = tweet.article;
123
+ for (const m of [article?.cover_media, ...(article?.media_entities ?? [])]) {
124
+ if (m?.media_info?.original_img_url) {
125
+ photos.push({ url: m.media_info.original_img_url, id: m.media_id ?? null });
126
+ }
127
+ }
128
+ const videos = (tweet.media?.videos ?? []).map((v) => ({
129
+ url: v.url,
130
+ thumbnail: v.thumbnail_url,
131
+ duration: v.duration,
132
+ }));
133
+ return { photos, videos };
134
+ }
135
+
136
+ async function fetchPost(user: string, id: string): Promise<Tweet> {
137
+ const res = await fetch(`${API}/${user}/status/${id}`, { headers: HEADERS });
138
+ const body = (await res.json().catch(() => null)) as { tweet?: Tweet; message?: string } | null;
139
+ if (!res.ok || !body?.tweet) {
140
+ throw new Error(`${user}/status/${id}: HTTP ${res.status} ${body?.message ?? ""}`.trim());
141
+ }
142
+ return body.tweet;
143
+ }
144
+
145
+ async function download(url: string, path: string): Promise<string> {
146
+ const res = await fetch(url, { headers: HEADERS });
147
+ if (!res.ok) {
148
+ throw new Error(`HTTP ${res.status}`);
149
+ }
150
+ writeFileSync(path, Buffer.from(await res.arrayBuffer()));
151
+ return path;
152
+ }
153
+
154
+ async function render(tweet: Tweet, mediaDir: string | null, label: string): Promise<string> {
155
+ const out: string[] = [];
156
+ const a = tweet.author ?? {};
157
+ out.push(`### ${label}: @${a.screen_name} (${a.name ?? ""}) · ${tweet.created_at}`);
158
+ out.push(tweet.url ?? "");
159
+ if (tweet.replying_to) {
160
+ out.push(
161
+ `Reply to @${tweet.replying_to} (${tweet.replying_to_status}); earlier thread posts are not included.`,
162
+ );
163
+ }
164
+ out.push("", expandedText(tweet));
165
+ const { photos, videos } = mediaOf(tweet);
166
+ if (tweet.article) {
167
+ out.push("", `#### Article: ${tweet.article.title}`, "", articleText(tweet.article, photos));
168
+ }
169
+ for (const [i, { url }] of photos.entries()) {
170
+ if (!mediaDir) {
171
+ out.push(`image ${i + 1}: ${url}`);
172
+ continue;
173
+ }
174
+ const ext = /\.(\w+)(?:\?|$)/.exec(url)?.[1] ?? "jpg";
175
+ const path = join(mediaDir, `${tweet.id}-${i + 1}.${ext}`);
176
+ out.push(
177
+ `image ${i + 1}: ${await download(url, path).catch((e: unknown) => `${url} (download failed: ${e instanceof Error ? e.message : String(e)})`)}`,
178
+ );
179
+ }
180
+ for (const [i, v] of videos.entries()) {
181
+ out.push(
182
+ `video ${i + 1} (${v.duration ?? "?"}s, not readable as text): ${v.url} thumbnail: ${v.thumbnail}`,
183
+ );
184
+ }
185
+ return out.join("\n");
186
+ }
187
+
188
+ export function parseArgs(argv: string[]): {
189
+ mediaDir: string | null;
190
+ urls: string[];
191
+ error?: string;
192
+ } {
193
+ let parsed;
194
+ try {
195
+ parsed = parseCli({
196
+ args: argv,
197
+ allowPositionals: true,
198
+ options: { "media-dir": { type: "string" } },
199
+ });
200
+ } catch (error) {
201
+ return {
202
+ mediaDir: null,
203
+ urls: [],
204
+ error: error instanceof Error ? error.message : String(error),
205
+ };
206
+ }
207
+ const mediaDir = parsed.values["media-dir"] ?? null;
208
+ // A forgotten directory would otherwise take the first URL as its value.
209
+ if (mediaDir !== null && /^https?:/i.test(mediaDir)) {
210
+ return { mediaDir: null, urls: [], error: "--media-dir needs a directory value" };
211
+ }
212
+ return { mediaDir, urls: parsed.positionals };
213
+ }
214
+
215
+ async function main(argv: string[]): Promise<number> {
216
+ const { mediaDir, urls, error } = parseArgs(argv);
217
+ if (error || urls.length === 0) {
218
+ if (error) {
219
+ console.error(error);
220
+ }
221
+ console.error("usage: x-post.ts [--media-dir DIR] <x.com status URL>...");
222
+ return 2;
223
+ }
224
+ if (mediaDir) {
225
+ mkdirSync(mediaDir, { recursive: true });
226
+ }
227
+ let failed = 0;
228
+ for (const input of urls) {
229
+ const ref = parseStatusUrl(input);
230
+ if (!ref) {
231
+ console.log(`## ${input}\nnot an X post URL\n`);
232
+ failed++;
233
+ continue;
234
+ }
235
+ try {
236
+ const tweet = await fetchPost(ref.user, ref.id);
237
+ const parts = [await render(tweet, mediaDir, "Post")];
238
+ // The embedded quote omits its article and full media; fetch it directly.
239
+ if (tweet.quote) {
240
+ const embedded = tweet.quote;
241
+ let quote = embedded;
242
+ try {
243
+ quote = await fetchPost(embedded.author?.screen_name ?? "i", embedded.id);
244
+ } catch (error) {
245
+ parts.push(
246
+ `Quoted post fetch failed (${error instanceof Error ? error.message : String(error)}); showing the embedded copy, which can lack its article and full media.`,
247
+ );
248
+ }
249
+ parts.push(await render(quote, mediaDir, "Quoted post"));
250
+ }
251
+ console.log(`## ${input}\n${parts.join("\n\n")}\n`);
252
+ } catch (error) {
253
+ console.log(
254
+ `## ${input}\nfetch failed: ${error instanceof Error ? error.message : String(error)}\n`,
255
+ );
256
+ failed++;
257
+ }
258
+ }
259
+ return failed ? 1 : 0;
260
+ }
261
+
262
+ if (import.meta.main) {
263
+ process.exitCode = await main(process.argv.slice(2));
264
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Queen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ # Source notice
2
+
3
+ These instructions were written for house-rules. Their workflow purpose is
4
+ informed by Cursor's built-in split-to-prs skill, described in its public catalogue:
5
+ https://cursor.com/docs/skills
6
+
7
+ No Cursor instruction text is distributed in this bundle. The MIT licence in
8
+ LICENSE applies to the newly written house-rules material; it does not assert
9
+ a licence for Cursor's skill text.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: hr-split-to-prs
3
+ description: Split a branch, chat or set of changes into small reviewable PRs when the user asks to divide the work.
4
+ ---
5
+
6
+ # Split work into pull requests
7
+
8
+ Produce slices that can be reviewed and verified against their actual bases.
9
+ A request for a split proposal authorizes analysis. Execute a selected split
10
+ within the user's existing authorization; ask only about unresolved scope.
11
+ Repository guidance owns checks, submission, landing and cleanup.
12
+
13
+ ## Establish what belongs to the task
14
+
15
+ Identify the default branch and the checkout's committed, staged, unstaged and
16
+ untracked work. Inspect ownership guidance for affected paths. Record other
17
+ owners' changes separately and preserve them throughout the split.
18
+
19
+ Describe each slice by its resulting behavior, affected paths and acceptance
20
+ check. Divide independent concerns; keep changes together when separating them
21
+ would break either slice or require temporary machinery with no outside user.
22
+
23
+ For a real dependency, state which change must land first and why. Use a stack
24
+ only when a slice cannot work against the default branch. Compatibility stages
25
+ need an actual outside caller, released interface or stored-data constraint.
26
+ An API replacement with no such dependency can migrate its callers and remove
27
+ the old path together.
28
+
29
+ Present the proposed PR titles and dependencies before moving work unless the
30
+ user already selected that plan. Name any work left outside the split. A title
31
+ that describes only part of a slice calls for a better title or a different cut.
32
+
33
+ ## Preserve and partition
34
+
35
+ Before moving changes, establish a recovery point for all owned work. Committed
36
+ work needs a retained commit or ref. Dirty tracked files and untracked files
37
+ need a verified snapshot that preserves their contents. `git stash create`
38
+ can capture tracked changes without altering the tree; it omits untracked files.
39
+ Do not claim a backup is complete until those files are accounted for too.
40
+
41
+ Choose isolated checkouts when the starting checkout is shared. For each
42
+ selected slice, reconstruct only its owned changes on the intended base.
43
+ Stage explicit paths or hunks and inspect the staged diff before committing.
44
+ Do not discard the starting work or rewrite published history to obtain a clean
45
+ slice. If a path mixes concerns, split its hunks only when each result remains
46
+ correct; otherwise keep the dependent changes together.
47
+
48
+ Verify each slice against the base it will actually target, including any
49
+ required earlier slice. Run its relevant checks and every repository-required
50
+ gate. A passing combined branch does not prove each separated PR works.
51
+
52
+ Submit and land only within existing authorization through the repository's
53
+ required path. Follow its rules for stacked targets and branch retirement.
54
+ Preserve recovery material until cleanup is authorized and no owner needs it.
55
+
56
+ ## Account for the result
57
+
58
+ Report each PR or retained branch, its base and verification result. Reconcile
59
+ remaining working-tree files and commits against the original inventory.
60
+ Identify blockers, other owners' work and any recovery material still retained.
61
+ Use `hr-writing-pr` for PR text when that skill is available.
@@ -0,0 +1,2 @@
1
+ interface:
2
+ display_name: "HR: Split to PRs"
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: hr-stock-ui-audit
3
+ description: Find template-default styling in frontend code that 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 as the request asks. 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"