@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,220 @@
1
+ // Portable panel preferences and a pure resolver. See ../references/panel-plan.md.
2
+ // Exact model/provider bindings come from a user-owned file; nothing here calls a model.
3
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
4
+ const alias = /^[a-z][a-z0-9-]{0,63}$/;
5
+
6
+ function keys(value, required, optional, path) {
7
+ if (!object(value)) throw new Error(`${path} must be an object`);
8
+ for (const key of required)
9
+ if (!Object.hasOwn(value, key)) throw new Error(`${path}.${key} is required`);
10
+ for (const key of Object.keys(value))
11
+ if (![...required, ...optional].includes(key)) throw new Error(`unknown ${path}.${key}`);
12
+ }
13
+
14
+ export function validatePanelPreferences(value) {
15
+ keys(value, ["version", "roles", "policy"], ["profile"], "panel");
16
+ if (value.version !== 1) throw new Error("panel.version must be 1");
17
+ if (value.profile !== undefined && !["quick", "balanced", "deep"].includes(value.profile))
18
+ throw new Error("panel.profile must be quick, balanced or deep");
19
+ if (!Array.isArray(value.roles) || value.roles.length === 0)
20
+ throw new Error("panel.roles must be a non-empty array");
21
+ const ids = new Set();
22
+ for (const [index, role] of value.roles.entries()) {
23
+ const path = `panel.roles[${index}]`;
24
+ keys(role, ["id", "candidates"], [], path);
25
+ if (typeof role.id !== "string" || !alias.test(role.id))
26
+ throw new Error(`${path}.id must be a lowercase alias (letters, digits and hyphens)`);
27
+ if (ids.has(role.id)) throw new Error(`duplicate panel role "${role.id}"`);
28
+ ids.add(role.id);
29
+ if (
30
+ !Array.isArray(role.candidates) ||
31
+ role.candidates.length === 0 ||
32
+ role.candidates.some((candidate) => typeof candidate !== "string" || !alias.test(candidate))
33
+ )
34
+ throw new Error(`${path}.candidates must be a non-empty array of lowercase aliases`);
35
+ if (new Set(role.candidates).size !== role.candidates.length)
36
+ throw new Error(`${path}.candidates contains duplicates`);
37
+ }
38
+ keys(
39
+ value.policy,
40
+ ["fallback", "roundLimit", "meteredRoutes"],
41
+ ["distinctFamilies", "excludeAuthorFamily", "allowReducedPanel"],
42
+ "panel.policy",
43
+ );
44
+ for (const key of ["distinctFamilies", "excludeAuthorFamily", "allowReducedPanel"])
45
+ if (value.policy[key] !== undefined && typeof value.policy[key] !== "boolean")
46
+ throw new Error(`panel.policy.${key} must be boolean`);
47
+ if (!["none", "approved-only"].includes(value.policy.fallback))
48
+ throw new Error("panel.policy.fallback must be none or approved-only");
49
+ if (!Number.isSafeInteger(value.policy.roundLimit) || value.policy.roundLimit < 1)
50
+ throw new Error("panel.policy.roundLimit must be a positive safe integer");
51
+ if (!["included-only", "explicit-approval-required"].includes(value.policy.meteredRoutes))
52
+ throw new Error(
53
+ "panel.policy.meteredRoutes must be included-only or explicit-approval-required",
54
+ );
55
+ }
56
+
57
+ export function changePanelPreferences(config, value) {
58
+ validatePanelPreferences(value);
59
+ return { ...structuredClone(config), panel: structuredClone(value) };
60
+ }
61
+
62
+ const familyName = /^[a-z][a-z0-9-]{0,63}$/;
63
+
64
+ export function validatePanelBindings(value) {
65
+ keys(value, ["version", "aliases"], [], "bindings");
66
+ if (value.version !== 1) throw new Error("bindings.version must be 1");
67
+ if (!object(value.aliases)) throw new Error("bindings.aliases must be an object");
68
+ for (const [name, binding] of Object.entries(value.aliases)) {
69
+ const path = `bindings.aliases.${name}`;
70
+ if (!alias.test(name))
71
+ throw new Error(`${path}: alias must be lowercase letters, digits and hyphens`);
72
+ keys(binding, ["launch"], ["families", "metered"], path);
73
+ if (!object(binding.launch) || Object.keys(binding.launch).length === 0)
74
+ throw new Error(`${path}.launch must be a non-empty object`);
75
+ if (
76
+ binding.families !== undefined &&
77
+ (!Array.isArray(binding.families) ||
78
+ binding.families.length === 0 ||
79
+ binding.families.some((family) => typeof family !== "string" || !familyName.test(family)) ||
80
+ new Set(binding.families).size !== binding.families.length)
81
+ )
82
+ throw new Error(`${path}.families must be a non-empty array of distinct lowercase names`);
83
+ if (binding.metered !== undefined && typeof binding.metered !== "boolean")
84
+ throw new Error(`${path}.metered must be boolean`);
85
+ }
86
+ }
87
+
88
+ // Project and task layers may only tighten the user's policy.
89
+ function combinePolicy(layers) {
90
+ const policies = layers.map((layer) => layer.policy);
91
+ const [user] = policies;
92
+ return {
93
+ fallback: policies.some((p) => p.fallback === "none") ? "none" : "approved-only",
94
+ roundLimit: Math.min(...policies.map((p) => p.roundLimit)),
95
+ meteredRoutes: policies.some((p) => p.meteredRoutes === "included-only")
96
+ ? "included-only"
97
+ : "explicit-approval-required",
98
+ distinctFamilies: policies.some((p) => p.distinctFamilies === true),
99
+ excludeAuthorFamily: policies.some((p) => p.excludeAuthorFamily === true),
100
+ allowReducedPanel:
101
+ user.allowReducedPanel === true && policies.every((p) => p.allowReducedPanel !== false),
102
+ };
103
+ }
104
+
105
+ // A later layer replaces a role's whole ordered candidate list; new role ids append.
106
+ function combineRoles(layers) {
107
+ const roles = new Map();
108
+ for (const layer of layers)
109
+ for (const role of layer.roles) roles.set(role.id, [...role.candidates]);
110
+ return [...roles].map(([id, candidates]) => ({ id, candidates }));
111
+ }
112
+
113
+ function assign(roles, index, taken, chosen, distinct) {
114
+ if (index === roles.length) return chosen;
115
+ for (const option of roles[index].options) {
116
+ if (distinct && option.families.some((family) => taken.has(family))) continue;
117
+ const next = assign(
118
+ roles,
119
+ index + 1,
120
+ new Set([...taken, ...option.families]),
121
+ [...chosen, { role: roles[index].id, ...option }],
122
+ distinct,
123
+ );
124
+ if (next) return next;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * request: { user, project?, task?, bindings, authorFamilies?, approvals?: { project?, metered? } }
131
+ * Returns { ok, seats, reduced, rejected, blockers, roundLimit, policy }; never launches a seat.
132
+ */
133
+ export function resolvePanel(request) {
134
+ const { user, project, task, bindings, authorFamilies = [], approvals = {} } = request;
135
+ validatePanelPreferences(user);
136
+ validatePanelBindings(bindings);
137
+ const blockers = [];
138
+ const layers = [user];
139
+ for (const [name, layer] of [
140
+ ["project", project],
141
+ ["task", task],
142
+ ]) {
143
+ if (layer === undefined) continue;
144
+ validatePanelPreferences(layer);
145
+ if (name === "project" && approvals.project !== true)
146
+ blockers.push({ message: "project panel preferences are not approved" });
147
+ else layers.push(layer);
148
+ }
149
+ const policy = combinePolicy(layers);
150
+ const strictFamilies = policy.distinctFamilies || policy.excludeAuthorFamily;
151
+ if (policy.excludeAuthorFamily && authorFamilies.length === 0)
152
+ blockers.push({ message: "author family is unknown, so it cannot be excluded" });
153
+ const metered = new Set(approvals.metered ?? []);
154
+ const rejected = [];
155
+ const reduced = [];
156
+ const roles = [];
157
+ for (const role of combineRoles(layers)) {
158
+ const options = [];
159
+ const candidates = policy.fallback === "none" ? role.candidates.slice(0, 1) : role.candidates;
160
+ for (const name of candidates) {
161
+ const binding = Object.hasOwn(bindings.aliases, name) ? bindings.aliases[name] : undefined;
162
+ const reject = (reason) => rejected.push({ role: role.id, alias: name, reason });
163
+ if (binding === undefined) reject("no binding for this alias");
164
+ else if (strictFamilies && binding.families === undefined) reject("model family is unknown");
165
+ else if (
166
+ policy.excludeAuthorFamily &&
167
+ binding.families.some((f) => authorFamilies.includes(f))
168
+ )
169
+ reject("shares a model family with the author");
170
+ else if (binding.metered === true && policy.meteredRoutes === "included-only")
171
+ reject("metered route is not allowed by an included-only policy");
172
+ else if (binding.metered === true && !metered.has(name))
173
+ reject("metered route needs explicit approval");
174
+ else
175
+ options.push({
176
+ alias: name,
177
+ families: binding.families ?? [],
178
+ metered: binding.metered === true,
179
+ launch: binding.launch,
180
+ });
181
+ }
182
+ if (options.length > 0) roles.push({ id: role.id, options });
183
+ else if (policy.allowReducedPanel) reduced.push(role.id);
184
+ else blockers.push({ role: role.id, message: `no eligible route for role "${role.id}"` });
185
+ }
186
+ let seats = [];
187
+ if (blockers.length === 0) {
188
+ if (roles.length === 0) blockers.push({ message: "no role has an eligible route" });
189
+ else {
190
+ seats = assign(roles, 0, new Set(), [], policy.distinctFamilies);
191
+ if (seats === null) {
192
+ seats = [];
193
+ blockers.push({ message: "no assignment gives every seat a distinct model family" });
194
+ }
195
+ }
196
+ }
197
+ return {
198
+ ok: blockers.length === 0,
199
+ seats,
200
+ reduced,
201
+ rejected,
202
+ blockers,
203
+ roundLimit: policy.roundLimit,
204
+ policy,
205
+ };
206
+ }
207
+
208
+ export function formatPanelPlan(plan) {
209
+ const lines = [plan.ok ? "Panel plan (no model has been called):" : "Panel blocked:"];
210
+ for (const seat of plan.seats) {
211
+ const families = seat.families.length > 0 ? seat.families.join("+") : "unknown";
212
+ lines.push(` ${seat.role}: ${seat.alias} (${families}${seat.metered ? ", metered" : ""})`);
213
+ }
214
+ for (const role of plan.reduced) lines.push(` ${role}: dropped, no eligible route`);
215
+ for (const blocker of plan.blockers) lines.push(` blocker: ${blocker.message}`);
216
+ for (const item of plan.rejected)
217
+ lines.push(` skipped ${item.alias} for ${item.role}: ${item.reason}`);
218
+ lines.push(` rounds: ${plan.roundLimit}`);
219
+ return lines.join("\n");
220
+ }
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: hr-design-exploration
3
+ description: "Explore visual design or redesign alternatives through four ranked directions, top-two feedback and refinement toward one chosen design, or compare variants of one component in place. Use when asked to propose or compare UI design directions or component variants; ordinary UI fixes and implementing a chosen design do not need it."
4
+ ---
5
+
6
+ # Design exploration
7
+
8
+ Use the user's requested scope and constraints. The default is four initial
9
+ directions, then rounds containing the two selected designs and one variation
10
+ of each. A requested count applies to the initial comparison unless the user
11
+ also specifies refinement rounds. Refinement defaults to four options. Before
12
+ a differently sized refinement round, agree which selected designs to retain
13
+ and how many variations to show. The user's chosen method takes precedence.
14
+
15
+ ## Establish the comparison
16
+
17
+ Read the brief and current design. Identify the intended users, primary task,
18
+ content, required interactions, brand constraints and target viewports. Use
19
+ existing answers; ask only for missing information that would change the
20
+ alternatives. State the goal used to rank recommendations.
21
+
22
+ Keep the same real content, states and viewport sizes across previews. Hold
23
+ accessibility and required behavior constant. If content is unavailable, label
24
+ placeholder content and the assumptions it introduces.
25
+
26
+ ## Show the initial directions
27
+
28
+ Create four meaningfully different approaches by default to the same goal. Differences
29
+ should affect hierarchy, layout, navigation, interaction or density. Color
30
+ swaps alone do not make distinct directions. Stay within the authorized
31
+ prototype scope; keep the current implementation available for comparison.
32
+
33
+ Show inspectable previews at comparable sizes, using the host's available
34
+ preview or image tools. Include interaction states when they distinguish the
35
+ directions. If rendering is unavailable, label the output as concepts and name
36
+ what remains unverified. Never claim an unseen design was visually checked.
37
+
38
+ Give each direction a stable ID and a short descriptive name. Present the directions
39
+ in recommendation order, strongest first. Mark the strongest choices
40
+ "(Recommended)" and explain why they fit the stated goal. Recommendations are
41
+ judgments; do not invent usability results, performance gains or scores.
42
+
43
+ Beside the previews, use a short comparison table with each direction's ID,
44
+ main difference, benefit and tradeoff. Keep IDs stable when rankings change.
45
+ Ask the user to select their top two and give feedback on each, including what
46
+ to retain and what to change. Follow the host's question format. Wait for the
47
+ answer before narrowing or generating the next round.
48
+
49
+ ## One component
50
+
51
+ When the scope is a single component rather than a screen or flow, build three
52
+ variants by default. When the authorized prototype scope includes the page
53
+ where the component renders, put them behind a switcher there, so the user
54
+ compares them where the component actually lives; otherwise compare them
55
+ within the scope you have. Keep the switcher and variants out of production
56
+ code paths, and remove them once a variant is chosen.
57
+
58
+ Pick one design axis and give each variant a different position on it:
59
+ structure (grouping, order, what collapses), density (spacing, hit areas),
60
+ emphasis (where strong color and weight go), type (scale and weight contrast)
61
+ or voice (labels and amount of copy). Let secondary choices follow from that
62
+ position. Variants that differ on several axes at once cannot show which
63
+ change made the difference. Name each variant for its direction, not "Option
64
+ A". Before ranking, name the accessibility checks every variant must pass, at
65
+ least keyboard reach, visible focus, accessible names for controls, no
66
+ clipping at the narrowest supported width and no meaning carried by color
67
+ alone, plus `hr-better-accessibility`'s checks when it is available. Check
68
+ each variant against the same list and drop any direction that fails one.
69
+
70
+ Rank and refine the variants as described below.
71
+
72
+ ## Refine the selected two
73
+
74
+ Record the chosen IDs and feedback in the task's durable design notes. Keep
75
+ eliminated directions there so later rounds do not revive rejected ideas by
76
+ accident. If the user chooses one as final, move to final selection below.
77
+
78
+ By default, show four options again:
79
+
80
+ 1. The first selected design, unchanged as a comparison.
81
+ 2. The second selected design, unchanged as a comparison.
82
+ 3. One variation of the first design responding to its feedback.
83
+ 4. One variation of the second design responding to its feedback.
84
+
85
+ Give variations new IDs and name their parent IDs. Explain what changed and
86
+ which feedback it addresses. Keep the inherited strengths visible and the
87
+ variation distinct enough to assess. Rerank the round against the goal and
88
+ latest feedback, mark recommended choices, and ask for the next top two with
89
+ feedback on each.
90
+
91
+ Repeat this round only after a new selection. Do not add new unrelated
92
+ concepts unless the user asks to reopen exploration. If feedback leaves no
93
+ meaningful variation to test, offer final selection or ask which unresolved
94
+ tradeoff needs another round instead of inventing cosmetic differences.
95
+
96
+ ## Final selection
97
+
98
+ An explicit final choice ends exploration; no minimum round count is required.
99
+ Record the chosen ID, accepted refinements, rejected directions and remaining
100
+ uncertainties. Summarize the final design's layout, interactions and relevant
101
+ responsive states so implementation can follow it.
102
+
103
+ Choosing two for refinement authorizes that comparison, not promotion of a
104
+ winner. Apply the final design within existing implementation authorization;
105
+ when implementation or landing has not been authorized, offer that next step.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "HR: Design exploration"
3
+ short_description: "Compare design directions or component variants and refine"
4
+ default_prompt: "Use $hr-design-exploration to show four ranked visual directions, or three variants on one design axis for a single component, and refine my top two toward a final design."
@@ -0,0 +1,45 @@
1
+ # Source notices
2
+
3
+ This notice accompanies the hr-diagnosing-bugs bundle when copied on its own.
4
+
5
+ ## mattpocock/skills
6
+
7
+ Source: https://github.com/mattpocock/skills (compared at revision `d81f3a1`)
8
+
9
+ - `skills/hr-diagnosing-bugs/`: written for this repository after
10
+ `skills/engineering/diagnosing-bugs/`, which it shares a purpose with.
11
+
12
+ Copyright (c) 2026 Matt Pocock
13
+
14
+ ## cursor/plugins (pstack)
15
+
16
+ Source: https://github.com/cursor/plugins (compared at revision `e43c7ee`)
17
+
18
+ - `skills/hr-diagnosing-bugs/SKILL.md`: § Question the premise is adapted from
19
+ `pstack/skills/principle-attack-the-premise/`.
20
+
21
+ Copyright (c) 2026 Lauren Tan
22
+
23
+ ## house-rules and MIT permission notice
24
+
25
+ MIT License
26
+
27
+ Copyright (c) 2026 Aaron Queen
28
+
29
+ Permission is hereby granted, free of charge, to any person obtaining a copy
30
+ of this software and associated documentation files (the "Software"), to deal
31
+ in the Software without restriction, including without limitation the rights
32
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33
+ copies of the Software, and to permit persons to whom the Software is
34
+ furnished to do so, subject to the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be included in all
37
+ copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: hr-diagnosing-bugs
3
+ description: Investigate unknown-cause or intermittent bugs and performance regressions systematically. Evident fixes only on explicit diagnosis requests; exclude change reviews.
4
+ ---
5
+
6
+ # Diagnosing bugs
7
+
8
+ When filing an upstream bug, follow [Reporting a bug](references/reporting.md)
9
+ for reduction, submission and fix verification.
10
+
11
+ Follow project guidance for source paths, domain safeguards, tests and deployment.
12
+ Read relevant domain documents when they help explain the affected behavior.
13
+ Choose the next probe from the evidence; these techniques are not mandatory phases.
14
+
15
+ ## Choose a model when needed
16
+
17
+ If the project names a benchmark-backed order for bug-finding models and
18
+ efforts, use it. Primary, secondary and tertiary choices can change with benchmark
19
+ results; read the current selection rather than carrying names from an old run.
20
+ If the primary is unavailable through quota, outage or an unsupported route,
21
+ use the next available eligible choice within authorized billing limits only
22
+ when changing provider, model or effort is authorized. When the low-quota
23
+ handoff rule applies, use the `hr-handoff` skill and stop, even if a model change
24
+ was authorized. Preserve review independence and record the model, effort and fallback
25
+ reason.
26
+ If no eligible model is available, report the blocker.
27
+
28
+ ## Establish the symptom
29
+
30
+ Identify expected and observed behavior and seek a repeatable signal for the
31
+ reported failure. Source inspection and initial hypotheses can help construct it.
32
+ Prefer an existing test, command or captured input before building a harness.
33
+ Minimize the scenario when doing so helps distinguish causes or create a useful
34
+ regression test; an exhaustively minimal reproduction is not a prerequisite.
35
+
36
+ For intermittent failures, record attempts and failures under comparable
37
+ conditions. Increase the reproduction rate when practical within host load and
38
+ environment safeguards; do not impose a fixed retry count or success threshold.
39
+ For performance regressions, establish a measured baseline before changing code.
40
+
41
+ If reproduction is unavailable, continue useful source and artifact analysis,
42
+ label hypotheses as unverified, and report what would confirm them. Ask for
43
+ missing access or evidence only when it blocks further progress. Production
44
+ instrumentation still requires the applicable authorization.
45
+
46
+ ## Distinguish causes
47
+
48
+ State a falsifiable prediction for the likely cause and test it. Consider
49
+ alternatives when evidence is ambiguous or a probe fails; there is no hypothesis
50
+ quota. Use targeted inspection, a debugger, logs, replay, bisection or differential
51
+ testing according to the uncertainty each resolves. Keep probes attributable by
52
+ changing one relevant variable at a time where practical.
53
+
54
+ When the cause depends on event order between actors, control the relevant
55
+ boundaries in an implementation test. Pause a response or write, interrupt the
56
+ process, then resume or restart it and assert the visible result. Use barriers
57
+ or explicit signals instead of sleeps to force the failing order. When the
58
+ failing order is unknown, the `hr-ordering-tests` skill enumerates the orderings
59
+ through the real code and names the sequence that breaks.
60
+
61
+ Keep secrets out of commands and shared artifacts; use environment variables
62
+ for credentials and redact captured headers or logs before displaying them.
63
+ Tag temporary instrumentation so it can be found and removed. When a human must
64
+ perform a reproduction step, give clear steps and the expected observation.
65
+
66
+ ## Bound the loop
67
+
68
+ When retrying a probe or fix, cap attempts per hypothesis, stop on
69
+ no-progress (repeated identical calls, oscillation, unchanged error class),
70
+ and enforce time/token/cost ceilings outside the agent so a stuck loop
71
+ terminates instead of retrying.
72
+
73
+ ## Question the premise
74
+
75
+ When two fixes that rest on the same assumption have failed the same check,
76
+ stop fixing and test the assumption. Write it down as the one sentence every
77
+ failed fix took for granted. Before a third fix, gather evidence that would
78
+ show the sentence false, such as a count per input, actor or run, and keep it
79
+ as a script that can be rerun. If the evidence contradicts the sentence, find
80
+ what made it false and fix that, instead of compensating for it on every run.
81
+ If the evidence supports the sentence, keep it as a record and look for the
82
+ cause elsewhere.
83
+
84
+ ## Fix and verify
85
+
86
+ When the confirmed cause admits more than one viable fix, such as a targeted
87
+ workaround versus a root-cause correction, present the fixes as an offer (the house rules' § Offers) with
88
+ rough relative effort before changing code. Mark the recommendation and name
89
+ the right fix when it differs, with the reason: scope, risk, blast radius or
90
+ existing debt. Skip this when the evidence supports a single obvious fix or
91
+ the user already prescribed the remedy.
92
+
93
+ Make the smallest fix supported by the evidence. Use a regression assertion that
94
+ exercises the actual failure when a suitable seam exists and the change warrants
95
+ it under project guidance. It must fail on the pre-fix code for the intended
96
+ reason and pass after the fix, as `hr-test-audit` requires. Test-first ordering is optional
97
+ unless requested or required by the project.
98
+
99
+ Recheck the original symptom, including the original scenario if a minimized
100
+ case was used. For intermittent or performance issues, compare measurements and
101
+ report their limits. If verification is blocked or coverage lacks a suitable
102
+ seam, state the gap rather than claiming the fix is proven or expanding the
103
+ architecture without authorization.
104
+
105
+ Remove temporary instrumentation and throwaway artifacts. Report the cause,
106
+ change, checks actually run and remaining uncertainty. Recommend preventive work
107
+ only when the evidence supports a concrete benefit; follow project completion
108
+ and scope boundaries.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "HR: Diagnosing Bugs"
3
+ short_description: "Diagnose hard bugs and regressions"
@@ -0,0 +1,97 @@
1
+ # Reporting a bug
2
+
3
+ Before filing, apply the submission search in your loaded agent guidance's "Repository work" section.
4
+ If that section is unavailable, locate the local house-rules checkout and read
5
+ `rules/core.md` there. Report an unavailable local source before submitting.
6
+ Inspect labels, linked changes and timeline actions as well as comments.
7
+ For the same problem and scope, add new evidence to a matching open issue.
8
+ When a closed report still reproduces and remains materially unchanged,
9
+ explain the failed fix or changed conditions and request reopening. If reopening
10
+ is unavailable, file one linked follow-up. For material changes, use the
11
+ replacement workflow below.
12
+
13
+ Reduce the failure to the smallest self-contained reproduction you can verify.
14
+ Remove unrelated code, dependencies, data and setup. Keep reducing until each
15
+ remaining part is needed to reproduce the failure. For a library failure,
16
+ trace the failing path and isolate its underlying runtime API or language
17
+ operation. Use source inspection, profiling or controlled substitutions to
18
+ choose the next reduction; a slow dependency does not establish its cause.
19
+
20
+ Verify both the final reduction and the original scenario with exact commands
21
+ and pinned versions. Preserve the inputs, output checks and relevant allocation,
22
+ retention, concurrency or lifecycle behavior. A similar standalone failure is
23
+ a contributor observation until evidence connects it to the original symptom.
24
+ For performance or memory bugs, compare equivalent work under the applicable
25
+ benchmark protocol. State units, repetitions, variability and measured controls;
26
+ put the focused case before a larger comparison matrix.
27
+
28
+ Include a cause-and-reduction note in the report: the isolated operation,
29
+ evidence connecting it to the original failure, and remaining uncertainty.
30
+ Test the proposed cause by changing or removing the suspected factor while
31
+ preserving the other relevant conditions. Distinguish a reproduced operation,
32
+ a supported causal explanation and a confirmed internal defect. Do not infer
33
+ an engine defect merely because one runtime is slower on a library workload.
34
+
35
+ Lead with one symptom, the reproduction, expected and actual text results,
36
+ and the versions and environment needed to reproduce it. Check the current
37
+ stable release; identify development builds separately when tested. A verified
38
+ small reproduction can be reported while its internal cause remains unknown.
39
+
40
+ Add an image or chart to the report only as the "Visuals in issues, discussions
41
+ and comments" section of your loaded guidance describes; locate it in `rules/core.md`
42
+ when it is not loaded.
43
+
44
+ Show direct reproduction commands. Omit incidental scheduling, resource-control
45
+ and observer wrappers from issue and pull request examples. When a control is
46
+ required to reproduce the defect, include it and explain why. Keep exact executed
47
+ commands in measurement records; disclose material conditions beside performance
48
+ results without presenting local controls as prerequisites or changing historical
49
+ conditions. This reporting choice is checked by the author, not a syntax gate.
50
+
51
+ When reduction is blocked, record the attempted isolations and why the remaining
52
+ parts are necessary before asking whether to submit the broader case.
53
+
54
+ Read the repository's contribution guidance and matching issue template,
55
+ including organization defaults. Complete required fields and checklists.
56
+ Submit a YAML issue form (`.github/ISSUE_TEMPLATE/*.yml`) through the form
57
+ itself. `gh issue create` cannot submit one, so the form's labels, dropdowns
58
+ and checklist are lost, and copied headings alone do not preserve form
59
+ metadata. When the host has a browser signed in to the user's GitHub account,
60
+ open `https://github.com/<owner>/<repo>/issues/new?template=<file>.yml`, fill
61
+ every field and submit it there. Use the CLI only when the repository has no
62
+ issue form or only a Markdown template. Verify the published body and labels.
63
+ With no signed-in browser, prepare the report and ask for access or user
64
+ submission; do not fall back to `gh issue create` and drop the form's labels.
65
+
66
+ Every issue offers the pull request, whatever the repository's triage timing.
67
+ Waiting for a maintainer's verdict decides when you open the pull request, not
68
+ whether the issue says one is available. State in the issue's fix or workaround
69
+ field (or its closing lines, when the form has none) what exists and offer the
70
+ pull request: "None applied. I can open a pull request for this once triage
71
+ confirms the direction. <branch exists / not written yet>; <the approach or
72
+ options I would measure>." When a branch or pull request already exists, link it
73
+ instead.
74
+
75
+ Before showing a draft to the user, run
76
+ `node scripts/check-pr-offer.mjs <draft.md>` from this skill; it fails when that
77
+ text does not offer a pull request or link a pull request or branch. It is a
78
+ text match: it skips a sentence that puts a negation before "pull request" but
79
+ cannot judge meaning, so confirm the offer is genuine yourself. Read the offer back from the
80
+ form's fields before submitting.
81
+
82
+ When an issue needs major or material changes, replace it with a new issue
83
+ in any repository. Compare the proposed revision with the published report.
84
+ A revision is major or material if it changes the reported problem, scope,
85
+ reproduction mechanism or main conclusion. Carry forward relevant evidence and unresolved
86
+ questions, and link the old and new reports both ways. Submit and verify the
87
+ replacement before closing the old report as superseded, not fixed. Routine
88
+ corrections and evidence that leave the report materially unchanged stay in place.
89
+
90
+ Keep one active report per problem after replacement. Follow up with new
91
+ evidence or a specific unanswered question. An absent bot comment is not
92
+ evidence that nothing happened. Do not duplicate a report or close and reopen it merely to seek
93
+ priority. Small examples help investigation; they do not guarantee action or a fix deadline.
94
+
95
+ When a fix build is available, test the same reproduction and report the
96
+ revision and before/after result. Distinguish triage, an open fix, a merged fix
97
+ and a released fix. Verify the affected behavior before calling it resolved.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Fails when an issue draft neither offers a pull request nor links one.
3
+ // usage: check-pr-offer.mjs <draft.md> (or the draft on stdin)
4
+ // The text checked is the "Fix applied or workaround" field when the draft has one, else the
5
+ // draft's last lines. Run it before showing a draft to the user and again on the filed body.
6
+ import { readFileSync } from "node:fs";
7
+
8
+ const TAIL_LINES = 8;
9
+ const FIELD = /^(?:#{1,6}\s*|\*\*)?fix applied or workaround\b/i;
10
+ const HEADING = /^#{1,6}\s/;
11
+ const OFFER = /pull request|github\.com\/[^\s/]+\/[^\s/]+\/(?:pull\/\d+|tree\/\S+)/i;
12
+ // A sentence that denies the offer ("I cannot open a pull request") does not count.
13
+ const DENIAL = /\b(?:cannot|can't|can not|won't|will not|unable|never|no|not)\b[^.]*pull request/i;
14
+
15
+ // The field's text: from its heading or label to the next heading, or null when the draft has none.
16
+ export function offerText(draft) {
17
+ const lines = draft.split("\n");
18
+ const start = lines.findIndex((l) => FIELD.test(l.trim()));
19
+ if (start >= 0) {
20
+ const end = lines.findIndex((l, i) => i > start && HEADING.test(l));
21
+ return lines.slice(start, end < 0 ? undefined : end).join("\n");
22
+ }
23
+ return lines
24
+ .filter((l) => l.trim())
25
+ .slice(-TAIL_LINES)
26
+ .join("\n");
27
+ }
28
+
29
+ export const offersPullRequest = (draft) =>
30
+ OFFER.test(
31
+ offerText(draft)
32
+ .split(/(?<=[.!?])\s+|\n/)
33
+ .filter((sentence) => !DENIAL.test(sentence))
34
+ .join("\n"),
35
+ );
36
+
37
+ if (import.meta.main ?? import.meta.url === `file://${process.argv[1]}`) {
38
+ const draft = readFileSync(process.argv[2] ?? 0, "utf8");
39
+ if (offersPullRequest(draft)) {
40
+ process.exit(0);
41
+ }
42
+ console.error(
43
+ 'FAIL: the "Fix applied or workaround" text (or the draft\'s closing lines) neither says "pull request" nor links one.\n' +
44
+ 'Add the offer: "None applied. I can open a pull request for this once triage confirms the direction. <branch exists / not written yet>; <the approach I would measure>." or link the existing branch or pull request.',
45
+ );
46
+ process.exit(1);
47
+ }