@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,205 @@
1
+ """Selected-file adapters for OpenCode exports, Cursor streams and ACP v1 logs.
2
+
3
+ No store discovery or recorded-command execution. Adapters emit the same events
4
+ as retro.py. ACP state is folded in a first pass so later input/name updates do
5
+ not change tool numbering or multiply a single call's results.
6
+ """
7
+ import json
8
+
9
+
10
+ def records(path):
11
+ with open(path, errors="replace") as source:
12
+ first = source.readline()
13
+ try:
14
+ record = json.loads(first)
15
+ except ValueError:
16
+ # Pretty-printed session exports are one JSON object, not JSONL.
17
+ if first.strip() == '{':
18
+ source.seek(0)
19
+ record = json.load(source)
20
+ if isinstance(record, dict):
21
+ yield record
22
+ return
23
+ record = None
24
+ if isinstance(record, dict):
25
+ yield record
26
+ for line in source:
27
+ try:
28
+ record = json.loads(line)
29
+ except ValueError:
30
+ continue
31
+ if isinstance(record, dict):
32
+ yield record
33
+
34
+
35
+ def text(value):
36
+ if isinstance(value, str):
37
+ return value
38
+ if isinstance(value, list):
39
+ return '\n'.join(text(v) for v in value)
40
+ if isinstance(value, dict):
41
+ for key in ('text', 'content', 'output'):
42
+ if key in value:
43
+ return text(value[key])
44
+ return json.dumps(value)
45
+ return '' if value is None else json.dumps(value)
46
+
47
+
48
+ def tool(name, value, kind=None, locations=None):
49
+ """Normalize documented names/inputs; unknown names remain ordinary tools."""
50
+ names = {'read': 'Read', 'read_file': 'Read', 'grep': 'Grep', 'glob': 'Glob',
51
+ 'list': 'LS', 'bash': 'exec_command', 'Bash': 'exec_command',
52
+ 'write': 'Write', 'edit': 'Edit'}
53
+ if not isinstance(name, str):
54
+ name = None
55
+ if not isinstance(kind, str):
56
+ kind = None
57
+ name = names.get(name, {'read': 'Read', 'search': 'Grep',
58
+ 'edit': 'Edit', 'execute': 'exec_command'}.get(kind, name or 'unknown'))
59
+ if name == 'Read' and not isinstance(value, (dict, str)):
60
+ value = {}
61
+ if not isinstance(value, dict):
62
+ value = value if isinstance(value, str) else json.dumps(value or {})
63
+ return name, value
64
+ if name == 'Read':
65
+ path = value.get('file_path') or value.get('filePath') or value.get('path')
66
+ if not path and isinstance(locations, list) and len(locations) == 1:
67
+ path = locations[0].get('path') if isinstance(locations[0], dict) else None
68
+ return name, path if isinstance(path, str) else ''
69
+ if name in ('Grep', 'Glob'):
70
+ return name, str(value.get('pattern') or json.dumps(value))
71
+ return name, json.dumps(value)
72
+
73
+
74
+ def opencode(record, timestamp):
75
+ info = record.get('info')
76
+ if not isinstance(info, dict) or not isinstance(record.get('messages'), list):
77
+ return
78
+ time = info.get('time') if isinstance(info.get('time'), dict) else {}
79
+ title = info.get('title') if isinstance(info.get('title'), str) else ''
80
+ directory = info.get('directory') if isinstance(info.get('directory'), str) else None
81
+ yield ('meta', directory, title, bool(info.get('parentID')), timestamp(time.get('created')))
82
+ for message in record['messages']:
83
+ if not isinstance(message, dict) or not isinstance(message.get('info'), dict):
84
+ continue
85
+ meta = message['info']
86
+ time = meta.get('time') if isinstance(meta.get('time'), dict) else {}
87
+ stamp = timestamp(time.get('created'))
88
+ path = meta.get('path')
89
+ if isinstance(path, dict) and isinstance(path.get('cwd'), str):
90
+ yield ('context', path['cwd'], stamp)
91
+ role = meta.get('role')
92
+ for part in message.get('parts') if isinstance(message.get('parts'), list) else []:
93
+ if not isinstance(part, dict):
94
+ continue
95
+ if part.get('type') == 'text' and role in ('user', 'assistant'):
96
+ yield (role, text(part.get('text')), stamp)
97
+ elif part.get('type') == 'tool' and role == 'assistant':
98
+ state = part.get('state')
99
+ if not isinstance(state, dict):
100
+ continue
101
+ times = state.get('time') if isinstance(state.get('time'), dict) else {}
102
+ name, arg = tool(part.get('tool'), state.get('input'))
103
+ call_id = part.get('callID') or part.get('id')
104
+ if not isinstance(call_id, str):
105
+ continue
106
+ yield ('call', name, arg, call_id, timestamp(times.get('start')) or stamp)
107
+ if state.get('status') in ('completed', 'error'):
108
+ yield ('result', call_id, text(state.get('output') or state.get('error')),
109
+ state['status'] == 'error', timestamp(times.get('end')) or stamp)
110
+
111
+
112
+ def acp_update(record):
113
+ params = record.get('params')
114
+ if isinstance(params, dict) and record.get('method') == 'session/update':
115
+ return params.get('sessionId', ''), params.get('update')
116
+ update = record.get('update')
117
+ if isinstance(update, dict):
118
+ return record.get('sessionId', ''), update
119
+ if 'sessionUpdate' in record:
120
+ return record.get('sessionId', ''), record
121
+ return None, None
122
+
123
+
124
+ def acp_calls(path):
125
+ calls = {}
126
+ for record in records(path):
127
+ session, update = acp_update(record)
128
+ if not isinstance(update, dict) or update.get('sessionUpdate') not in ('tool_call', 'tool_call_update'):
129
+ continue
130
+ call_id = update.get('toolCallId')
131
+ if not isinstance(call_id, str):
132
+ continue
133
+ if not isinstance(session, str):
134
+ continue
135
+ state = calls.setdefault((session, call_id), {})
136
+ for key, value in update.items():
137
+ if value is not None: # ACP v1 null/omission leaves the prior value.
138
+ state[key] = value
139
+ return calls
140
+
141
+
142
+ def acp(record, calls, seen, finished, timestamp):
143
+ session, update = acp_update(record)
144
+ if not isinstance(update, dict) or not isinstance(session, str):
145
+ return
146
+ stamp = timestamp(record.get('timestamp'))
147
+ kind = update.get('sessionUpdate')
148
+ if kind in ('user_message_chunk', 'agent_message_chunk'):
149
+ yield ('user_chunk' if kind.startswith('user') else 'assistant_chunk', text(update.get('content')), stamp)
150
+ elif kind in ('tool_call', 'tool_call_update'):
151
+ call_id = update.get('toolCallId')
152
+ if not isinstance(call_id, str):
153
+ return
154
+ key = (session, call_id)
155
+ state = calls[key]
156
+ if key not in seen:
157
+ seen.add(key)
158
+ name, arg = tool(state.get('name'), state.get('rawInput'), state.get('kind'), state.get('locations'))
159
+ yield ('call', name, arg, call_id, stamp)
160
+ if update.get('status') in ('completed', 'failed') and key not in finished:
161
+ finished.add(key)
162
+ output = state.get('rawOutput')
163
+ content = state.get('content')
164
+ if output is None and isinstance(content, list):
165
+ output = [item.get('content') for item in content if isinstance(item, dict)
166
+ and item.get('type') == 'content' and isinstance(item.get('content'), dict)
167
+ and item['content'].get('type') == 'text']
168
+ yield ('result', call_id, text(output),
169
+ update['status'] == 'failed', stamp)
170
+
171
+
172
+ def cursor(record, seen, timestamp):
173
+ stamp = timestamp(record.get('timestamp_ms')) or timestamp(record.get('timestamp'))
174
+ outer = record.get('tool_call')
175
+ if not isinstance(outer, dict):
176
+ return
177
+ name, args, result = 'unknown', {}, None
178
+ for key, canonical in (('readToolCall', 'Read'), ('writeToolCall', 'Write')):
179
+ value = outer.get(key)
180
+ if isinstance(value, dict):
181
+ name, args, result = canonical, value.get('args'), value.get('result')
182
+ break
183
+ else:
184
+ function = outer.get('function')
185
+ if isinstance(function, dict):
186
+ name, args = function.get('name'), function.get('arguments')
187
+ if isinstance(args, str):
188
+ try:
189
+ args = json.loads(args)
190
+ except ValueError:
191
+ pass
192
+ result = outer.get('result')
193
+ name, arg = tool(name, args)
194
+ call_id = record.get('call_id')
195
+ if not isinstance(call_id, str):
196
+ return
197
+ if ('call', call_id) not in seen:
198
+ seen.add(('call', call_id))
199
+ yield ('call', name, arg, call_id, stamp)
200
+ if record.get('subtype') == 'completed' and ('result', call_id) not in seen:
201
+ seen.add(('result', call_id))
202
+ error = isinstance(result, dict) and 'error' in result
203
+ if isinstance(result, dict):
204
+ result = result.get('error') if error else result.get('success', result)
205
+ yield ('result', call_id, text(result), error, stamp)
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: hr-ordering-tests
3
+ description: "Enumerate event orderings in ordinary unit tests. Use when a bug or change depends on the order of events between actors (tabs, workers, polling loops, storage writers, retries), or when code merges writes from more than one source. Not for pure functions of a single input."
4
+ ---
5
+
6
+ # Ordering tests
7
+
8
+ A hand-written test covers an ordering someone thought of. This technique
9
+ generates every ordering of a small set of events up to a fixed length and runs
10
+ each one through the real code, so it also finds the orderings nobody listed.
11
+ That is a model checker's job, done in the project's own test runner against
12
+ the implementation instead of a separate model that can drift from it.
13
+
14
+ ## Steps
15
+
16
+ 1. **Name the invariants.** State what must hold after any sequence, such as
17
+ "a completed record never reopens" or "the stored timestamp never goes
18
+ back". Done when each one is a yes/no check on observable state.
19
+ 2. **List the events.** Include each distinct write, message, restart or reset
20
+ an actor can produce, with the variants that take different code paths:
21
+ an older, equal and newer timestamp; a field left out; another identity
22
+ (session, tenant, league); two events delivered in one batch. Aim for 6 to
23
+ 10 events, and merge variants that exercise the same path.
24
+ 3. **Enumerate.** Walk every sequence up to length N depth-first. At each node,
25
+ restore the state its prefix left, apply one event through the real entry
26
+ point (listener, handler, reducer), then check every invariant. The cost is
27
+ events^N applications. N of 3 or 4 is usually enough, since ordering bugs
28
+ rarely need more than three events, and the test should finish within a
29
+ few seconds under full-suite load.
30
+ 4. **Report sequences.** Record each violation as the invariant plus the
31
+ sequence, such as `a completed record reopened after A → B → C`, and
32
+ assert the list is empty, so one run shows several failures.
33
+ 5. **Prove it discriminates.** Break one guard an invariant depends on,
34
+ confirm the test fails and names a sequence, then revert.
35
+ 6. **Act on what it finds.** Pin each sequence the search finds as one named
36
+ regression test next to the enumeration, its owning boundary, and fix it. When the bug is out
37
+ of the change's scope, record it as a follow-up and exclude that invariant
38
+ with the reason until the fix lands.
39
+
40
+ ## Keeping it deterministic
41
+
42
+ Drive asynchronous boundaries with fake timers, barriers or a synchronous
43
+ flush; never sleep or use real time. Clone event payloads per application so
44
+ one sequence cannot leak state into the next. For sequences too long to
45
+ enumerate, a seeded random walk over the same events is the fallback; print
46
+ the seed with the failing sequence.
47
+
48
+ ## Maintenance
49
+
50
+ When the code gains a new kind of event, add it to the list in the same
51
+ change. Reviewers check for this.
@@ -0,0 +1,2 @@
1
+ interface:
2
+ display_name: "HR: Ordering tests"
@@ -0,0 +1,261 @@
1
+ ---
2
+ name: hr-plain-prose
3
+ description: Revise text a person will read (commit messages, PR descriptions, docs, code comments, replies, the final report on a task) so it is plain, specific and free of AI tells, the habits that mark machine-written prose. Use when drafting or revising human-facing text, even a short message or reply, or when asked to "unslop" text or make it sound less generated.
4
+ ---
5
+
6
+ # Plain prose
7
+
8
+ Readers spot generated text by its habits, not by any one word: vague claims,
9
+ inflated vocabulary, the same sentence shapes over and over, decoration in
10
+ place of content. This skill removes those habits while keeping every fact the
11
+ author meant. Apply it to your own drafts before they ship, and to text the
12
+ user hands you. For pull request structure, use `hr-writing-pr`; for text an agent
13
+ reads, use `hr-writing-for-agents`.
14
+
15
+ ## Before editing
16
+
17
+ Mark what must survive unchanged: code, identifiers, commands, paths, version
18
+ numbers, error messages, quoted material, links and any technical claim. You
19
+ may rephrase around these, never inside them. Note the author's deliberate
20
+ choices too, such as a project's house terms, a required template or a
21
+ consistent "we". Those stay.
22
+
23
+ A passage the author has flagged to keep, by telling you so or with a
24
+ comment such as `plain-prose: keep`, stays exactly as written, whatever the
25
+ checks below say about it.
26
+
27
+ ## Say the specific thing
28
+
29
+ A sentence that could appear unchanged in any other project carries no
30
+ information. Replace it with the mechanism, the number, the file or the source,
31
+ or cut it.
32
+
33
+ - "Improves performance significantly" becomes "Cuts cold start from 2.1 s to
34
+ 0.8 s by caching the parsed config."
35
+ - "Various edge cases are handled" becomes "Empty input and a missing trailing
36
+ newline no longer crash the parser."
37
+
38
+ If the specific is not known, say what is unknown ("not measured", "untested on
39
+ Windows"). Never invent a figure, an example or a source to fill the gap. The
40
+ same goes for thresholds and norms. A judgment word such as "normal", "on
41
+ target", "reasonable" or "on track" needs the baseline it is measured against
42
+ ("RSS of 1.4 GB, under the 2 GB container limit"). If nothing defines the
43
+ expected value, write that it is undefined rather than passing judgment.
44
+
45
+ - **Unnamed authorities.** "Research suggests", "it is widely accepted" and
46
+ "most developers prefer" with no source behind them present an opinion as
47
+ consensus. Name the study, the survey or the person, or delete the
48
+ sentence.
49
+ - **Fake ranges.** "Handles everything from SQLite files to billing disputes"
50
+ joins two ends that sit on no shared scale, so nothing lies between them.
51
+ List the things it handles.
52
+
53
+ ## Choose plain words
54
+
55
+ - Prefer the short common word: "use" over "leverage", "help" over
56
+ "facilitate", "start" over "commence".
57
+ - Let "is" and "has" do their job. "The cache serves as a buffer" is "The cache
58
+ is a buffer."
59
+ - Drop intensifiers and padding: "very", "truly", "it is worth noting that",
60
+ "in order to", "at the end of the day".
61
+ - Keep one hedge where doubt is real and drop the rest. "This might possibly
62
+ help in some cases" becomes "This may help when the queue is full."
63
+ - An adverb attached to a weak verb signals that a stronger verb or a
64
+ measurement belongs there. "Loads really fast" becomes "Loads in 180 ms";
65
+ "sharply reduces errors" becomes "Cuts failed uploads from 4% to 0.3%".
66
+
67
+ The words below are cues to reread a sentence, not proof of anything. When
68
+ one turns up, check whether it says more than its plain replacement would, and
69
+ look harder where several cluster in one paragraph. The checker script under
70
+ "Check the result" reads this table and the one under "Name the actual
71
+ thing", so keep each row as `| term | replacement |`.
72
+
73
+ | Word | Write instead |
74
+ | --------------------- | ------------------------------------------ |
75
+ | delve into | look at, read, test |
76
+ | pivotal, crucial | say what breaks without it |
77
+ | robust | name the failure it survives |
78
+ | seamless | name the step that no longer fails, or cut |
79
+ | comprehensive | name what is covered |
80
+ | streamline | name the step removed |
81
+ | showcase | show |
82
+ | underscore | show, or cut |
83
+ | foster | help, build |
84
+ | elevate | improve, raise |
85
+ | tapestry | cut |
86
+ | realm | area, or cut |
87
+ | testament to | evidence of, or cut |
88
+ | moreover, furthermore | also, or cut |
89
+ | notably | cut |
90
+
91
+ ## Name the actual thing
92
+
93
+ Some nouns sound technical but describe a problem as terrain or a physical
94
+ force when an ordinary word for it is available. Name the actual thing.
95
+
96
+ | Stand-in | Plain word |
97
+ | ----------------- | -------------------------------------------- |
98
+ | landscape | the tools or vendors, listed |
99
+ | ecosystem | the packages, plugins or companies meant |
100
+ | friction | the extra step, prompt or wait |
101
+ | traction | users, downloads or revenue, with the number |
102
+ | momentum | what shipped, and when |
103
+ | headwinds | the specific problems |
104
+ | fault line | the boundary where the two designs disagree |
105
+ | center of gravity | the module or team that decides |
106
+ | bandwidth | time |
107
+ | lens | the criterion |
108
+ | pillars | parts, goals |
109
+ | erosion | the drop, with before and after figures |
110
+
111
+ Write figurative language literally too. That covers metaphors, figurative
112
+ verbs, code or documents described as if they had wishes, and slogan-like
113
+ one-liners.
114
+
115
+ - "The parser chokes on tabs" becomes "The parser rejects tab characters."
116
+ - "The README wants you to start with the setup section" becomes "Read the
117
+ setup section first."
118
+
119
+ ## Break the sentence habits
120
+
121
+ - **Reflexive threes.** Lists of three adjectives or three parallel clauses
122
+ appear because the rhythm feels finished, not because there are three things.
123
+ Keep the items that are true and distinct.
124
+ - **Contrast framing.** "Not only X, but also Y" and "This isn't about X, it's
125
+ about Y" set up a contrast nobody raised. State Y.
126
+ - **Tacked-on significance.** A trailing participle that announces importance
127
+ ("..., highlighting the need for better tests") adds a claim without support.
128
+ Cut it, or make it its own sentence with evidence.
129
+ - **Closing summaries.** A last paragraph that restates the ones above it, or an
130
+ "Overall, ..." line, adds length only. End on the last new fact.
131
+ - **Rotating synonyms.** Calling the same thing "the worker", "the process" and
132
+ "the job" in three sentences makes the reader wonder whether there are three.
133
+ Pick one name and keep it.
134
+
135
+ ## Fix the punctuation
136
+
137
+ - **Em dashes.** Leave them out of any prose a person reads. At the spot
138
+ where one would sit, end the sentence and start another, or use a comma. No
139
+ other mark may take its role, which rules out en dashes, spaced hyphens,
140
+ ellipses, colons inside a sentence and parenthetical asides. Do not contort
141
+ the sentence to avoid the missing dash either. Hyphenated words keep their
142
+ hyphens, and code, quotations and numeric ranges such as 10-20 keep their
143
+ dashes.
144
+ - **Colons.** Colons introduce a list, a sample or a block quotation. A
145
+ colon between two clauses in running prose usually sets up a reveal ("The
146
+ cause was simple: a stale cache."). Write the point directly ("A stale cache
147
+ caused the failure.").
148
+ - **Quotes.** Anything headed for source files, commit messages, a shell or a
149
+ Markdown file gets straight quotes and apostrophes. Typographic (curly)
150
+ quotes break shell commands and exact-match searches.
151
+
152
+ ## Strip the formatting habits
153
+
154
+ - Bold only what a skimming reader must not miss, at most a few times per page.
155
+ - Avoid bullets that open with a bold label and a colon and then repeat the
156
+ label in the sentence. Write the sentence, or use a heading. Opening a bullet
157
+ with a bolded name and a period, then giving information the name does not
158
+ already say, is fine.
159
+ - No emoji as decoration or as bullet markers.
160
+ - Use sentence case for headings unless the project's style says otherwise.
161
+ - Prefer a paragraph to a bullet list when the points depend on each other.
162
+
163
+ ## Remove chat leftovers
164
+
165
+ Text that leaves the conversation should not carry its manners. Delete
166
+ praise for the question ("Great question!"), offers of further help ("Let me
167
+ know if you'd like..."), announcements of what comes next ("Here's a breakdown
168
+ of..."), self-congratulation on a discovery ("Bingo!", "That did it!") and
169
+ self-reference ("As an AI..."). Start with the content.
170
+
171
+ ## Make it easy to read
172
+
173
+ - Keep spaces between prose words, counts and units, including agent messages.
174
+ Write "has 12 unfinished cases" and "64 MiB process peak", never
175
+ "has12unfinished" or "64MiBprocesspeak". Preserve literal identifiers and
176
+ filenames byte-for-byte in code spans. Run the checker on outgoing drafts;
177
+ correct its missing-space cues before sending. It cannot detect every joined word.
178
+ - One idea per sentence. Split a sentence that joins two claims with "and".
179
+ - Use active voice with a named actor: "The scheduler drops the job", not "The
180
+ job is dropped".
181
+ - Write replies as complete sentences about the actual work. Translate plan
182
+ labels and internal shorthand into words the user already knows. Explain a
183
+ needed technical term when first used. Keep exact identifiers where the
184
+ reader needs them to find or verify something.
185
+ - Cap sentence length. Keep a step the reader carries out near 20 words and a
186
+ descriptive sentence near 25. A longer one usually holds two ideas. Vary the
187
+ length below the cap.
188
+ - Write each step as a command with one action, and put its condition first:
189
+ "If the build fails, rerun it with `--verbose`." Two actions share a step
190
+ only when they happen at the same time.
191
+ - Before sending a reply, read it without the task notes. Then read only its
192
+ first and last lines. The opening should state the answer or result. The
193
+ ending should make any required next action or decision clear. When none
194
+ remains, end without inventing another task or repeating the answer.
195
+ Rewrite sentences that depend on unexplained labels, abbreviations or
196
+ knowledge of the agent's workflow. State permission limits plainly when
197
+ they affect the proposed action; routine updates do not need the unchanged list.
198
+ - In a report or a note to the user, make each heading or bold lead state the
199
+ takeaway ("Multi-turn ask is switched off"), not only the topic ("Prompt
200
+ caching and multi-turn ask"). Reference pages keep topic headings.
201
+
202
+ ## Do not overcorrect
203
+
204
+ - Judge by density. One "robust" or one list of three is fine; a paragraph built
205
+ from them is not. No single word proves the text was generated.
206
+ - Do not swap stiffness for forced casualness: no slang, jokes or "Honestly,"
207
+ openers the author did not write.
208
+ - Vary sentence length. Uniform short sentences are as mechanical as uniform
209
+ long ones.
210
+ - Let emphasis be uneven. A piece can hold one or two quiet, ordinary
211
+ sentences that make no point of their own. Do not fake this with misspellings,
212
+ padding or put-on informality.
213
+ - Editing out old habits can breed new, equally regular ones. Look for a short
214
+ moral at the close of each paragraph ("Small fixes add up."), a denial
215
+ followed at once by its correction ("The bug was not in the parser. It was
216
+ in the cache."), and openers that tease an answer ("So why did it fail?").
217
+ Once in a whole piece reads as voice; once per paragraph reads as a
218
+ template.
219
+ - When a stock phrase goes, delete it rather than swapping in another. A
220
+ replacement that keeps appearing across edits becomes a habit of its own.
221
+ - Leave text alone when it is already plain. A light edit beats a rewrite.
222
+
223
+ ## Check the result
224
+
225
+ Run `bun <this skill's directory>/scripts/check.mjs <file>` (Node 22+ also
226
+ works; with no file it reads standard input). It lists each of these with its
227
+ line and column, and exits 1 when it finds any:
228
+
229
+ - an em dash or other dash stand-in
230
+ - a colon between two clauses
231
+ - a curly quote or a filler phrase
232
+ - counts or measurement units joined to prose without spaces
233
+ - a word from the two tables above
234
+ - a sentence over 30 words
235
+
236
+ Each is a cue to reread, not a verdict, because the script cannot tell a
237
+ deliberate word from a habit. It skips code, URLs, block quotations and text
238
+ in straight double quotes, also when the quote wraps a line. It also skips
239
+ word-table rows like the ones above, a table cell holding only a dash and the
240
+ paragraph after a `plain-prose: keep` comment. A colon that ends the label
241
+ opening a list item is not counted. It does not compare the revision with the
242
+ original; the steps below do.
243
+
244
+ Compare the revision against the original, sentence by sentence:
245
+
246
+ 1. Every fact, number, condition and caveat from the original is still there,
247
+ with the same meaning and strength.
248
+ 2. Every item you marked in "Before editing" is byte-for-byte unchanged.
249
+ 3. Nothing new was claimed that the original or the evidence does not support.
250
+ 4. Read it once start to finish. If a sentence still sounds like it could
251
+ belong to any project, fix it or cut it.
252
+ 5. Read the opening sentence of every paragraph and nothing else, top to
253
+ bottom. When that sequence reads like a neat abstract of the whole text,
254
+ the paragraphs follow a template. Combine some, change their order or begin
255
+ a section with something other than its summary, without changing what it
256
+ says. Specs, runbooks and reference pages are meant to be skimmed this way,
257
+ so skip the step for them.
258
+
259
+ When revising someone else's text, return the edited version and, if any
260
+ meaning was unclear, list the spots where you had to guess rather than guess
261
+ silently.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "HR: Plain prose"
3
+ short_description: "Make human-facing text plain and specific"