rhachet-roles-ehmpathy 1.17.20 → 1.17.22

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.
@@ -0,0 +1,153 @@
1
+ # rule.forbid.remote-boundaries
2
+
3
+ ## .what
4
+
5
+ unit tests must not cross remote boundaries — if a test touches external resources, it is an integration test, not a unit test
6
+
7
+ ## .why
8
+
9
+ - **unit tests verify atomic logic** — pure functions with injected dependencies
10
+ - **remote boundaries introduce flake** — network timeouts, file locks, db state
11
+ - **remote boundaries introduce latency** — disk i/o, connection overhead, query time
12
+ - **remote boundaries hide in mocks** — mocks create false confidence; the mock behaves differently than reality
13
+ - **test classification matters** — `npm run test:unit` must be fast, deterministic, and parallelizable
14
+
15
+ ## .scope
16
+
17
+ applies to all files with `.test.ts` extension (unit tests)
18
+
19
+ does NOT apply to `.integration.test.ts` or `.acceptance.test.ts`
20
+
21
+ ## .remote boundaries
22
+
23
+ a remote boundary is any resource external to the process memory:
24
+
25
+ | boundary | examples | why it's remote |
26
+ | ---------- | ------------------------------------------------------------- | -------------------------------------------------- |
27
+ | filesystem | `fs.readFile`, `fs.writeFile`, `path.resolve` with real paths | disk i/o, file locks, path differences across os |
28
+ | database | `pg.query`, `mysql.execute`, dao calls | connection state, query latency, data dependencies |
29
+ | network | `fetch`, `axios`, http clients, sdk calls | latency, availability, rate limits |
30
+
31
+ ## .how
32
+
33
+ ### 👍 unit test — pure logic leaf
34
+
35
+ ```ts
36
+ // computeInvoiceTotal.test.ts
37
+ describe('computeInvoiceTotal', () => {
38
+ it('sums line items correctly', () => {
39
+ const result = computeInvoiceTotal({
40
+ lineItems: [
41
+ { amount: 100 },
42
+ { amount: 50 },
43
+ ],
44
+ });
45
+ expect(result).toEqual(150);
46
+ });
47
+ });
48
+ ```
49
+
50
+ ### 👍 unit test — dependency injection enables isolation
51
+
52
+ ```ts
53
+ // sendInvoice.test.ts
54
+ describe('sendInvoice', () => {
55
+ it('returns sent status when email succeeds', async () => {
56
+ // inject a fake, not a mock of a real service
57
+ const svcEmailDemo = {
58
+ send: async () => ({ success: true }),
59
+ };
60
+
61
+ const result = await sendInvoice(
62
+ { invoice: exampleInvoice }, // input stays pure as always
63
+ { svcEmail: svcEmailDemo }, // context has remote interfaces injected; makes it clear that _any_ shape that fits this contract is allowed
64
+ );
65
+
66
+ expect(result.sent).toEqual(true);
67
+ });
68
+ });
69
+ ```
70
+
71
+ ### 👎 bad — crosses filesystem boundary
72
+
73
+ ```ts
74
+ // ❌ this is an integration test, not a unit test
75
+ describe('loadConfig', () => {
76
+ it('reads config from disk', async () => {
77
+ const config = await loadConfig({ path: './config.json' }); // fs access
78
+ expect(config.apiKey).toBeDefined();
79
+ });
80
+ });
81
+ ```
82
+
83
+ ### 👎 bad — crosses database boundary
84
+
85
+ ```ts
86
+ // ❌ this is an integration test, not a unit test
87
+ describe('getUserById', () => {
88
+ it('fetches user from database', async () => {
89
+ const user = await getUserById({ id: '123' }, { dbConnection }); // db access
90
+ expect(user.name).toEqual('alice');
91
+ });
92
+ });
93
+ ```
94
+
95
+ ### 👎 bad — mocks hide the boundary (antipattern)
96
+
97
+ ```ts
98
+ // ❌ mocks are an antipattern — they create false confidence
99
+ jest.mock('fs');
100
+ const fs = require('fs');
101
+
102
+ describe('loadConfig', () => {
103
+ it('reads config', async () => {
104
+ fs.readFile.mockResolvedValue('{"key": "value"}');
105
+ const config = await loadConfig({ path: './config.json' });
106
+ expect(config.key).toEqual('value');
107
+ // 👎 this test passes but real fs.readFile behaves differently
108
+ // 👎 the mock doesn't validate encode, error handler, path resolution
109
+ });
110
+ });
111
+ ```
112
+
113
+ ## .what to do instead
114
+
115
+ | if your test needs... | then... |
116
+ | --------------------- | ------------------------------ |
117
+ | filesystem access | move to `.integration.test.ts` |
118
+ | database queries | move to `.integration.test.ts` |
119
+ | network calls | move to `.integration.test.ts` |
120
+
121
+ ## .the mock antipattern
122
+
123
+ mocks are forbidden because:
124
+
125
+ 1. **mocks lie** — they return what you tell them, not what the real dependency returns
126
+ 2. **mocks drift** — when the real dependency changes, the mock doesn't
127
+ 3. **mocks hide bugs** — the test passes but production fails
128
+ 4. **mocks test the mock** — you verify your mock setup, not your logic
129
+
130
+ ### instead of mocks, use:
131
+
132
+ - **fakes** — simplified implementations that behave like the real dependency
133
+ - **dependency injection** — pass the dependency as a parameter
134
+ - **integration tests** — test the real dependency in `.integration.test.ts`
135
+
136
+ ## .classification guide
137
+
138
+ | test characteristics | classification | file extension |
139
+ | -------------------------------------- | -------------- | ---------------------- |
140
+ | pure logic, no i/o, injected deps | unit | `.test.ts` |
141
+ | touches db, fs, network | integration | `.integration.test.ts` |
142
+ | blackbox via built artifact's contract | acceptance | `.acceptance.test.ts` |
143
+
144
+ ## .enforcement
145
+
146
+ unit test that crosses remote boundary = **BLOCKER**
147
+
148
+ mock usage in unit tests = **BLOCKER**
149
+
150
+ ---
151
+
152
+ > unit tests are fast, deterministic, and test logic in isolation.
153
+ > if it touches the outside world, it's not a unit test.
@@ -0,0 +1,49 @@
1
+ ### .rule = forbid-buzzwords
2
+
3
+ #### .what
4
+ buzzwords must be avoided — they obscure intent
5
+
6
+ #### .scope
7
+ - code: variable names, function names, type names, comments
8
+ - docs: markdown, briefs, prompts, commit messages
9
+ - comms: pr descriptions, reviews, discussions
10
+
11
+ #### .why
12
+ buzzwords have **large gravity** — they pull the mind toward them
13
+
14
+ this gravity causes **semantic diffusion**:
15
+ - the word feels meaningful, so thought stops
16
+ - precision erodes because the term is overloaded
17
+ - ambiguity creeps in because everyone maps it to their own interpretation
18
+ - communication degrades because we think we agree when we don't
19
+
20
+ it's a smell. flies buzz around smells. we don't like smells -> we dont like buzz.
21
+
22
+ #### .instead
23
+ think from the core. build the concept from the ground up.
24
+
25
+ find the most appropriate non-buzz word:
26
+ - what specifically do you mean?
27
+ - what is the precise action, object, or attribute?
28
+ - what word captures exactly this, with no surplus baggage?
29
+
30
+ precision > familiarity. clarity > trendiness.
31
+
32
+ #### .enforcement
33
+ buzzwords = **BLOCKER** on first encounter. if retry with justification, allow.
34
+
35
+ #### .the test
36
+ if you remove the buzzword and the sentence still makes sense — you didn't need it.
37
+ if you replace it with a concrete term and meaning is preserved — use the concrete term.
38
+ if you can't replace it with anything concrete — you don't know what you mean yet.
39
+
40
+ #### .note: context matters
41
+ some terms are buzzwords in one context but precise in another:
42
+ - `scalable` in marketing = buzz
43
+ - `scalable` in distributed systems with defined semantics = maybe ok (but still prefer specifics)
44
+
45
+ when in doubt, be more specific.
46
+
47
+ #### .see also
48
+ - `rule.prefer.lowercase` — no shouts
49
+ - `rule.require.ubiqlang` — consistent terminology
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "rhachet-roles-ehmpathy",
3
3
  "author": "ehmpathy",
4
4
  "description": "empathetic software construction roles and skills, via rhachet",
5
- "version": "1.17.20",
5
+ "version": "1.17.22",
6
6
  "repository": "ehmpathy/rhachet-roles-ehmpathy",
7
7
  "homepage": "https://github.com/ehmpathy/rhachet-roles-ehmpathy",
8
8
  "keywords": [
@@ -52,7 +52,7 @@
52
52
  "prepare:husky": "husky install && chmod ug+x .husky/*",
53
53
  "prepare": "if [ -e .git ] && [ -z $CI ]; then npm run prepare:husky && npm run prepare:rhachet; fi",
54
54
  "test:format:biome": "biome format",
55
- "prepare:rhachet": "rhachet init && rhachet roles link --role mechanic && rhachet roles init --role mechanic && rhachet roles link --role behaver && rhachet roles init --role behaver"
55
+ "prepare:rhachet": "rhachet init --roles behaver mechanic reviewer"
56
56
  },
57
57
  "dependencies": {
58
58
  "@ehmpathy/as-command": "1.0.3",
@@ -92,10 +92,10 @@
92
92
  "esbuild-register": "3.6.0",
93
93
  "husky": "8.0.3",
94
94
  "jest": "30.2.0",
95
- "rhachet": "1.22.0",
96
- "rhachet-roles-bhrain": "0.5.9",
97
- "rhachet-roles-bhuild": "0.6.4",
98
- "rhachet-roles-ehmpathy": "link:.",
95
+ "rhachet": "1.22.7",
96
+ "rhachet-roles-bhrain": "0.5.11",
97
+ "rhachet-roles-bhuild": "0.6.6",
98
+ "rhachet-roles-ehmpathy": "1.17.20",
99
99
  "test-fns": "1.6.0",
100
100
  "tsc-alias": "1.8.10",
101
101
  "tsx": "4.20.6",