rhachet-roles-ehmpathy 1.17.17 → 1.17.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domain.roles/mechanic/briefs/.archive/_mech.compressed.md +4 -4
- package/dist/domain.roles/mechanic/briefs/.archive/patterns.directional-dependencies.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.architecture/rule.require.bounded-contexts.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.domain.objects/ref.package.domain-objects.[ref].md +13 -13
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.domain.operations/define.domain-operation-core-variants.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.forbid.io-as-domain-objects.md +7 -7
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.forbid.io-as-interfaces.md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.arrow-only.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.clear-contracts.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.dependency-injection.md.pt1.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.hook-wrapper-pattern.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.input-context-pattern.md.pt1.md +7 -7
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.input-context-pattern.md.pt2.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.single-responsibility.md +9 -9
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.repo.structure/rule.require.directional-deps.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.procedures/rule.require.idempotent-procedures.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.procedures/rule.require.immutable-vars.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.typedefs/define.bivariance-for-generics.[lesson].md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.typedefs/rule.forbid.as-cast.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.comments/rule.require.what-why-headers.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.comments/rule.require.what-why-headers.v1.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/howto.write-bdd.[lesson].md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/howto.write-bdd.[lesson].pt2.md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/rule.require.given-when-then.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.forbid.gerunds.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.order.noun_adj.md.pt2.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.treestruct.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/lang.tones/rule.prefer.lowercase.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/lang.tones/rule.require.term-human.md +2 -2
- package/package.json +3 -3
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
- inline callbacks may be exempt if anonymous + tightly scoped
|
|
6
6
|
- **example:**
|
|
7
7
|
```ts
|
|
8
|
-
export const doWork = async (input, context) => { ... } //
|
|
9
|
-
export function legacyFunc(a, b) {} //
|
|
8
|
+
export const doWork = async (input, context) => { ... } // 👍
|
|
9
|
+
export function legacyFunc(a, b) {} // 👎
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
---
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
- always destructure `input` as first argument when applicable
|
|
19
19
|
- **example:**
|
|
20
20
|
```ts
|
|
21
|
-
const getName = ({ name }) => name; //
|
|
22
|
-
export function getName(name) { return name } //
|
|
21
|
+
const getName = ({ name }) => name; // 👍
|
|
22
|
+
export function getName(name) { return name } // 👎
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
---
|
|
@@ -56,7 +56,7 @@ src/
|
|
|
56
56
|
|
|
57
57
|
.examples
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
👍 positive
|
|
60
60
|
\`\`\`ts
|
|
61
61
|
// contract/endpoints/sendInvoice.ts
|
|
62
62
|
import { generateInvoice } from '@/domain.operations/generateInvoice';
|
|
@@ -69,14 +69,14 @@ import { Job } from '@/domain.objects/Job';
|
|
|
69
69
|
import { LineItem } from '@/domain.objects/LineItem';
|
|
70
70
|
\`\`\`
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
👎 negative
|
|
73
73
|
\`\`\`ts
|
|
74
74
|
// domain.objects/Customer.ts
|
|
75
|
-
import { customerDao } from '@/access/daos/customerDao'; //
|
|
75
|
+
import { customerDao } from '@/access/daos/customerDao'; // 👎 illegal upward import
|
|
76
76
|
|
|
77
77
|
// domain.operations/InvoiceOps.ts
|
|
78
|
-
import { runFlow } from '@/contract/commands'; //
|
|
78
|
+
import { runFlow } from '@/contract/commands'; // 👎 direction violation
|
|
79
79
|
|
|
80
80
|
// access/svcs/sdkWrapper.ts
|
|
81
|
-
import { dispatchFlow } from '@/contract/'; //
|
|
81
|
+
import { dispatchFlow } from '@/contract/'; // 👎 bottom-up reference
|
|
82
82
|
\`\`\`
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
- `routes/submitJob.ts` orchestrates job + invoice via stitch, not via import of both
|
|
44
44
|
|
|
45
45
|
.negative:
|
|
46
|
-
- `job.ts` imports `from '../../invoice/utils.ts'`
|
|
47
|
-
- `invoiceService.ts` directly updates `customer.email`
|
|
48
|
-
- `customer.ts` imports `JobQuote` and infers state
|
|
46
|
+
- `job.ts` imports `from '../../invoice/utils.ts'` 👎 reach-in
|
|
47
|
+
- `invoiceService.ts` directly updates `customer.email` 👎 cross-context mutation
|
|
48
|
+
- `customer.ts` imports `JobQuote` and infers state 👎 ownership violation
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
|
|
@@ -298,16 +298,16 @@ class SeaTurtle extends DomainEntity<SeaTurtle> implements SeaTurtle {
|
|
|
298
298
|
public static unique = ['seawaterSecurityNumber'] as const;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
//
|
|
301
|
+
// 👍 valid
|
|
302
302
|
const primaryRef: RefByPrimary<typeof SeaTurtle> = { uuid: 'beefbeef...' };
|
|
303
303
|
|
|
304
|
-
//
|
|
304
|
+
// 👎 invalid - must be a string
|
|
305
305
|
const wrongType: RefByPrimary<typeof SeaTurtle> = { uuid: 8335 };
|
|
306
306
|
|
|
307
|
-
//
|
|
307
|
+
// 👎 invalid - wrong key
|
|
308
308
|
const wrongKey: RefByPrimary<typeof SeaTurtle> = { guid: 'beefbeef...' };
|
|
309
309
|
|
|
310
|
-
//
|
|
310
|
+
// 👎 invalid - missing primary key
|
|
311
311
|
const missing: RefByPrimary<typeof SeaTurtle> = {};
|
|
312
312
|
```
|
|
313
313
|
|
|
@@ -328,16 +328,16 @@ class SeaTurtle extends DomainEntity<SeaTurtle> implements SeaTurtle {
|
|
|
328
328
|
public static unique = ['seawaterSecurityNumber'] as const;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
//
|
|
331
|
+
// 👍 valid
|
|
332
332
|
const uniqueRef: RefByUnique<typeof SeaTurtle> = { seawaterSecurityNumber: 'ABC-999' };
|
|
333
333
|
|
|
334
|
-
//
|
|
334
|
+
// 👎 invalid - wrong type
|
|
335
335
|
const wrongType: RefByUnique<typeof SeaTurtle> = { seawaterSecurityNumber: 999 };
|
|
336
336
|
|
|
337
|
-
//
|
|
337
|
+
// 👎 invalid - wrong key
|
|
338
338
|
const wrongKey: RefByUnique<typeof SeaTurtle> = { saltwaterSecurityNumber: 'ABC-999' };
|
|
339
339
|
|
|
340
|
-
//
|
|
340
|
+
// 👎 invalid - empty object
|
|
341
341
|
const empty: RefByUnique<typeof SeaTurtle> = {};
|
|
342
342
|
```
|
|
343
343
|
|
|
@@ -359,22 +359,22 @@ class EarthWorm extends DomainEntity<EarthWorm> implements EarthWorm {
|
|
|
359
359
|
public static unique = ['soilSecurityNumber', 'wormSegmentNumber'] as const;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
//
|
|
362
|
+
// 👍 primary
|
|
363
363
|
const byPrimary: Ref<typeof EarthWorm> = { uuid: 'beefbeef...' };
|
|
364
364
|
|
|
365
|
-
//
|
|
365
|
+
// 👍 unique
|
|
366
366
|
const byUnique: Ref<typeof EarthWorm> = {
|
|
367
367
|
soilSecurityNumber: 'SOIL-001',
|
|
368
368
|
wormSegmentNumber: 'SEG-42',
|
|
369
369
|
};
|
|
370
370
|
|
|
371
|
-
//
|
|
371
|
+
// 👎 invalid - missed part of unique key
|
|
372
372
|
const incompleteUnique: Ref<typeof EarthWorm> = { soilSecurityNumber: 'SOIL-001' };
|
|
373
373
|
|
|
374
|
-
//
|
|
374
|
+
// 👎 invalid - not related to either key
|
|
375
375
|
const wrongKey: Ref<typeof EarthWorm> = { guid: 'beefbeef...' };
|
|
376
376
|
|
|
377
|
-
//
|
|
377
|
+
// 👎 invalid - empty object
|
|
378
378
|
const empty: Ref<typeof EarthWorm> = {};
|
|
379
379
|
```
|
|
380
380
|
|
|
@@ -48,7 +48,7 @@ when both variants exist in a codebase, the prefix:
|
|
|
48
48
|
|
|
49
49
|
#### .examples
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**👍 good — leaf operations with proper prefixes**
|
|
52
52
|
```ts
|
|
53
53
|
// computeInvoiceTotal.ts — leaf, deterministic
|
|
54
54
|
export const computeInvoiceTotal = (input: { lineItems: LineItem[] }) => {
|
|
@@ -67,7 +67,7 @@ export const imagineChapterSummary = async (
|
|
|
67
67
|
};
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
**👍 good — composer operation with normal name**
|
|
71
71
|
```ts
|
|
72
72
|
// getInvoiceWithSummary.ts — composes both variants
|
|
73
73
|
export const getInvoiceWithSummary = async (
|
|
@@ -81,15 +81,15 @@ export const getInvoiceWithSummary = async (
|
|
|
81
81
|
};
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
**👎 bad — prefix misleads**
|
|
85
85
|
```ts
|
|
86
86
|
// computeResponse.ts — "compute" but actually calls LLM
|
|
87
87
|
export const computeResponse = async (input, context) => {
|
|
88
|
-
return context.brain.repl.imagine({ ... }); //
|
|
88
|
+
return context.brain.repl.imagine({ ... }); // 👎 prefix misleads
|
|
89
89
|
};
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
**👎 bad — ambiguous leaf name (only when both variants exist)**
|
|
93
93
|
```ts
|
|
94
94
|
// generateTotal.ts — unclear if deterministic or probabilistic
|
|
95
95
|
export const generateTotal = (...) => { ... }
|
|
@@ -30,12 +30,12 @@ forbid domain objects for procedure inputs and outputs; declare them inline on t
|
|
|
30
30
|
|
|
31
31
|
#### .how
|
|
32
32
|
|
|
33
|
-
#####
|
|
33
|
+
##### 👍 required
|
|
34
34
|
- declare input types inline: `(input: { invoice: Invoice; customer: Customer })`
|
|
35
35
|
- declare return types inline: `): Promise<{ success: boolean; invoice: Invoice }>`
|
|
36
36
|
- use domain objects as **properties** within inline types, not as the type itself
|
|
37
37
|
|
|
38
|
-
#####
|
|
38
|
+
##### 👎 forbidden
|
|
39
39
|
- `class SendInvoiceInput extends DomainLiteral<...>`
|
|
40
40
|
- `interface GenerateReportOutput { ... }` in a separate file
|
|
41
41
|
- `type SyncCustomerArgs = { ... }` outside the procedure file
|
|
@@ -47,7 +47,7 @@ forbid domain objects for procedure inputs and outputs; declare them inline on t
|
|
|
47
47
|
|
|
48
48
|
#### .examples
|
|
49
49
|
|
|
50
|
-
#####
|
|
50
|
+
##### 👍 positive
|
|
51
51
|
```ts
|
|
52
52
|
/**
|
|
53
53
|
* .what = sends an invoice to the customer
|
|
@@ -74,9 +74,9 @@ export const generateMonthlyReport = async (
|
|
|
74
74
|
};
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
#####
|
|
77
|
+
##### 👎 negative
|
|
78
78
|
```ts
|
|
79
|
-
//
|
|
79
|
+
// 👎 input as domain object — SendInvoiceInput is not a domain concept
|
|
80
80
|
interface SendInvoiceInput {
|
|
81
81
|
invoice: Invoice;
|
|
82
82
|
customer: Customer;
|
|
@@ -88,7 +88,7 @@ export const sendInvoice = async (input: SendInvoiceInput, context: Context) =>
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
|
-
//
|
|
91
|
+
// 👎 output as separate type — pollutes type namespace with transient shapes
|
|
92
92
|
type GenerateReportResult = {
|
|
93
93
|
report: Report;
|
|
94
94
|
generatedAt: string;
|
|
@@ -98,7 +98,7 @@ export const generateMonthlyReport = async (input: {...}): Promise<GenerateRepor
|
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
|
-
//
|
|
101
|
+
// 👎 args file pattern — fragments the contract across files
|
|
102
102
|
// file: generateReport.args.ts
|
|
103
103
|
export interface GenerateReportArgs { ... }
|
|
104
104
|
|
|
@@ -32,12 +32,12 @@ forbid separate `interface` or `type` declarations for procedure inputs and outp
|
|
|
32
32
|
|
|
33
33
|
#### .how
|
|
34
34
|
|
|
35
|
-
#####
|
|
35
|
+
##### 👍 required
|
|
36
36
|
- declare input types inline on the procedure signature
|
|
37
37
|
- declare return types inline on the procedure signature
|
|
38
38
|
- use domain objects as **properties** within inline types when appropriate
|
|
39
39
|
|
|
40
|
-
#####
|
|
40
|
+
##### 👎 forbidden
|
|
41
41
|
- `interface DoThingInput { ... }` declarations
|
|
42
42
|
- `interface DoThingOutput { ... }` declarations
|
|
43
43
|
- `type DoThingArgs = { ... }` declarations
|
|
@@ -51,7 +51,7 @@ forbid separate `interface` or `type` declarations for procedure inputs and outp
|
|
|
51
51
|
|
|
52
52
|
#### .examples
|
|
53
53
|
|
|
54
|
-
#####
|
|
54
|
+
##### 👍 positive
|
|
55
55
|
```ts
|
|
56
56
|
/**
|
|
57
57
|
* .what = syncs customer phone from external provider
|
|
@@ -77,15 +77,15 @@ export const calculateInvoiceTotal = (
|
|
|
77
77
|
};
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
#####
|
|
80
|
+
##### 👎 negative
|
|
81
81
|
```ts
|
|
82
|
-
//
|
|
82
|
+
// 👎 separate interface for input
|
|
83
83
|
interface SyncCustomerPhoneInput {
|
|
84
84
|
customerId: string;
|
|
85
85
|
provider: 'whodis' | 'twilio';
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// 👎 separate interface for output
|
|
89
89
|
interface SyncCustomerPhoneOutput {
|
|
90
90
|
updated: boolean;
|
|
91
91
|
phoneBefore: string | null;
|
|
@@ -99,7 +99,7 @@ export const syncCustomerPhone = async (
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
|
-
//
|
|
102
|
+
// 👎 type aliases for single-use shapes
|
|
103
103
|
type CalculateTotalArgs = {
|
|
104
104
|
lineItems: LineItem[];
|
|
105
105
|
taxRate: number;
|
|
@@ -115,7 +115,7 @@ export const calculateInvoiceTotal = (input: CalculateTotalArgs): CalculateTotal
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
```ts
|
|
118
|
-
//
|
|
118
|
+
// 👎 file fragmentation pattern
|
|
119
119
|
// file: syncCustomerPhone.input.ts
|
|
120
120
|
export interface SyncCustomerPhoneInput { ... }
|
|
121
121
|
|
|
@@ -37,11 +37,11 @@ const getName = (input) => input.name;
|
|
|
37
37
|
|
|
38
38
|
###### .negative
|
|
39
39
|
```ts
|
|
40
|
-
function setCustomerPhone({ customer, phone }) { //
|
|
40
|
+
function setCustomerPhone({ customer, phone }) { // 👎 function keyword
|
|
41
41
|
return { ...customer, phone };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function doWork() { //
|
|
44
|
+
export function doWork() { // 👎 export with function
|
|
45
45
|
...
|
|
46
46
|
}
|
|
47
47
|
```
|
|
@@ -19,7 +19,7 @@ via dependencies that are **explicit, swappable, and controlled from the outside
|
|
|
19
19
|
for example, instead of a function that reaches for its own dependencies:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
//
|
|
22
|
+
// 👎 tightly coupled and side-effect prone
|
|
23
23
|
import { log } from '@/utils/logger';
|
|
24
24
|
import { getDatabaseConnection } from '@/utils/database';
|
|
25
25
|
|
|
@@ -33,7 +33,7 @@ export const upsert = async ({ cost }: { cost: JobLeadCost }) => {
|
|
|
33
33
|
you inject those dependencies via the standard `(input, context)` pattern:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
//
|
|
36
|
+
// 👍 dependency injection via context
|
|
37
37
|
export const upsert = async (
|
|
38
38
|
{ cost }: { cost: JobLeadCost },
|
|
39
39
|
context: { dbConnection: DatabaseConnection, log: LogMethods }, // note how the .context pattern cleanly separates inputs from dependencies
|
|
@@ -24,7 +24,7 @@ the alternative — inline wrapping at the function declaration — causes the e
|
|
|
24
24
|
|
|
25
25
|
#### .examples
|
|
26
26
|
|
|
27
|
-
#####
|
|
27
|
+
##### 👍 good — wrapper pattern
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
/**
|
|
@@ -43,7 +43,7 @@ const _sendInvoice = async (
|
|
|
43
43
|
export const sendInvoice = withLogTrail(_sendInvoice);
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
#####
|
|
46
|
+
##### 👍 good — multiple hooks composed
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
49
|
const _processPayment = async (
|
|
@@ -59,10 +59,10 @@ export const processPayment = withLogTrail(
|
|
|
59
59
|
);
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
#####
|
|
62
|
+
##### 👎 bad — inline decoration
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
|
-
//
|
|
65
|
+
// 👎 adding/removing the wrapper shifts the entire function body
|
|
66
66
|
export const sendInvoice = withLogTrail(async (
|
|
67
67
|
input: { invoice: Invoice },
|
|
68
68
|
context: { log: LogMethods },
|
|
@@ -18,7 +18,7 @@ enforce hard requirement that all procedure args to follow the canonical pattern
|
|
|
18
18
|
|
|
19
19
|
#### .how
|
|
20
20
|
|
|
21
|
-
#####
|
|
21
|
+
##### 👍 required
|
|
22
22
|
- every function must accept exactly:
|
|
23
23
|
- one `input` arg — a destructurable object
|
|
24
24
|
- optional second `context` arg — also a destructurable object
|
|
@@ -28,7 +28,7 @@ enforce hard requirement that all procedure args to follow the canonical pattern
|
|
|
28
28
|
- `input` does **not** need to be destructured at the function boundary; shape like `(input: { ... })` is fine
|
|
29
29
|
- `function` keyword is forbidden unless to implement class methods (see `.tactic:funcs:arrow-only`)
|
|
30
30
|
|
|
31
|
-
#####
|
|
31
|
+
##### 👎 forbidden
|
|
32
32
|
- more than 2 positional args
|
|
33
33
|
- non-destructurable inputs
|
|
34
34
|
- context blended into input
|
|
@@ -39,7 +39,7 @@ enforce hard requirement that all procedure args to follow the canonical pattern
|
|
|
39
39
|
|
|
40
40
|
### .examples
|
|
41
41
|
|
|
42
|
-
#####
|
|
42
|
+
##### 👍 positive
|
|
43
43
|
```ts
|
|
44
44
|
// standard function
|
|
45
45
|
export const genRoute = async (input: { slug: string }, context?: { traceId?: string }) => { ... }
|
|
@@ -51,11 +51,11 @@ const updateUser = ({ userId }: { userId: string }, context: { userDao: UserDao
|
|
|
51
51
|
expect(hasChanges({ before, after })).toBe(true);
|
|
52
52
|
```ts
|
|
53
53
|
|
|
54
|
-
#####
|
|
54
|
+
##### 👎 negative
|
|
55
55
|
```ts
|
|
56
|
-
export function doThing(a, b, c) {} //
|
|
56
|
+
export function doThing(a, b, c) {} // 👎 positional args & function keyword
|
|
57
57
|
|
|
58
|
-
handleRequest(input, options, env) //
|
|
58
|
+
handleRequest(input, options, env) // 👎 more than two args
|
|
59
59
|
|
|
60
|
-
export const getTotal = (invoice) => ... //
|
|
60
|
+
export const getTotal = (invoice) => ... // 👎 input not typed
|
|
61
61
|
```
|
|
@@ -38,7 +38,7 @@ this pattern maximizes **maintainability** and **readability** by:
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## 👍 example
|
|
42
42
|
|
|
43
43
|
\`\`\`ts
|
|
44
44
|
export const sendEmail = (
|
|
@@ -52,12 +52,12 @@ export const sendEmail = (
|
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## 👎 anti-pattern
|
|
56
56
|
|
|
57
57
|
\`\`\`ts
|
|
58
|
-
//
|
|
58
|
+
// 👎 loses "input" provenance
|
|
59
59
|
export const sendEmail = ({ to, subject, body }: { to: string; subject: string; body: string }) => {};
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// 👎 type drift — must look up the type definition elsewhere for basic usage
|
|
62
62
|
export const sendEmail = (input: EmailInput) => {};
|
|
63
63
|
\`\`\`
|
|
@@ -10,13 +10,13 @@ enforce that every code file and procedure has a singular, clearly-defined respo
|
|
|
10
10
|
|
|
11
11
|
#### .rules
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
👍 required
|
|
14
14
|
- every file must export **exactly one** named procedure
|
|
15
15
|
- filename must match the exported procedure
|
|
16
|
-
- all logic in the file must directly support the procedure
|
|
16
|
+
- all logic in the file must directly support the procedure's **domain intent**
|
|
17
17
|
- runtime typechecks are only allowed **if** the file's sole purpose is validation
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
👎 forbidden
|
|
20
20
|
- more than one exported procedure per file
|
|
21
21
|
- co-located validation, parse, or orchestration logic alongside domain logic
|
|
22
22
|
- runtime type assertions or guards inside general-purpose logic
|
|
@@ -26,7 +26,7 @@ enforce that every code file and procedure has a singular, clearly-defined respo
|
|
|
26
26
|
|
|
27
27
|
#### .examples
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
**👍 good**
|
|
30
30
|
```ts
|
|
31
31
|
// getCustomerInvoices.ts
|
|
32
32
|
/**
|
|
@@ -40,25 +40,25 @@ export const getCustomerInvoices = ({ customerId }: { customerId: string }) => {
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
**👎 bad**
|
|
44
44
|
```ts
|
|
45
45
|
// customerUtils.ts
|
|
46
46
|
// utility file that contains many things
|
|
47
47
|
|
|
48
48
|
export const getCustomerInvoices = (...) => { ... }
|
|
49
49
|
|
|
50
|
-
export const validateCustomer = (...) => { ... } //
|
|
50
|
+
export const validateCustomer = (...) => { ... } // 👎 multiple responsibilities
|
|
51
51
|
|
|
52
|
-
// random comment about edge cases —
|
|
52
|
+
// random comment about edge cases — 👎 unrelated noise
|
|
53
53
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
**👎 bad**
|
|
58
58
|
```ts
|
|
59
59
|
// getCustomerInvoices.ts
|
|
60
60
|
export const getCustomerInvoices = ({ customerId }: { customerId: string }) => {
|
|
61
|
-
if (typeof customerId !== 'string') //
|
|
61
|
+
if (typeof customerId !== 'string') // 👎 redundant runtime check
|
|
62
62
|
throw new Error('bad id');
|
|
63
63
|
|
|
64
64
|
return invoiceDao.findMany({ customerId });
|
|
@@ -56,7 +56,7 @@ src/
|
|
|
56
56
|
|
|
57
57
|
.examples
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
👍 positive
|
|
60
60
|
\`\`\`ts
|
|
61
61
|
// contract/endpoints/sendInvoice.ts
|
|
62
62
|
import { generateInvoice } from '@/domain.operations/generateInvoice';
|
|
@@ -69,14 +69,14 @@ import { Job } from '@/domain.objects/Job';
|
|
|
69
69
|
import { LineItem } from '@/domain.objects/LineItem';
|
|
70
70
|
\`\`\`
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
👎 negative
|
|
73
73
|
\`\`\`ts
|
|
74
74
|
// domain.objects/Customer.ts
|
|
75
|
-
import { customerDao } from '@/access/daos/customerDao'; //
|
|
75
|
+
import { customerDao } from '@/access/daos/customerDao'; // 👎 illegal upward import
|
|
76
76
|
|
|
77
77
|
// domain.operations/InvoiceOps.ts
|
|
78
|
-
import { runFlow } from '@/contract/commands'; //
|
|
78
|
+
import { runFlow } from '@/contract/commands'; // 👎 direction violation
|
|
79
79
|
|
|
80
80
|
// access/svcs/sdkWrapper.ts
|
|
81
|
-
import { dispatchFlow } from '@/contract/'; //
|
|
81
|
+
import { dispatchFlow } from '@/contract/'; // 👎 bottom-up reference
|
|
82
82
|
\`\`\`
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
|
|
55
55
|
.negative:
|
|
56
56
|
```ts
|
|
57
|
-
await sendEmail(input); //
|
|
57
|
+
await sendEmail(input); // 👎 no check for previous send
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
|
-
const saved = await insertLog({ ... }) //
|
|
61
|
+
const saved = await insertLog({ ... }) // 👎 may insert duplicate on retry
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
|
|
41
41
|
.negative:
|
|
42
42
|
- `let count = 0; count++`
|
|
43
|
-
- `input.customer.name = 'bob'` //
|
|
44
|
-
- `config.debug = false` //
|
|
45
|
-
- `arr.push(1)` //
|
|
43
|
+
- `input.customer.name = 'bob'` // 👎 input mutation
|
|
44
|
+
- `config.debug = false` // 👎 shared singleton mutation
|
|
45
|
+
- `arr.push(1)` // 👎 in-place array mutation
|
|
46
46
|
|
|
47
47
|
.links:
|
|
48
48
|
- see also: `arch:immutable-core`, `args:input-context`, `domain-objects.withImmute`
|
|
@@ -41,7 +41,7 @@ const specificDao: DeclastructDao<typeof MyResource> = { ... };
|
|
|
41
41
|
// generic dao shape where RefByPrimary<any> resolves to {}
|
|
42
42
|
type GenericDao = DeclastructDao<any>;
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// 👎 fails: {uuid: string} is not assignable to {}
|
|
45
45
|
const generic: GenericDao = specificDao;
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -59,7 +59,7 @@ interface DeclastructDao<TResourceClass> {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// 👍 works: bivariance allows assignment in either direction
|
|
63
63
|
const generic: GenericDao = specificDao;
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
|
|
52
52
|
.negative:
|
|
53
53
|
```ts
|
|
54
|
-
//
|
|
54
|
+
// 👎 undocumented cast
|
|
55
55
|
const user = data as User;
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// 👎 cast to silence error
|
|
58
58
|
const result = badFunction() as ExpectedType;
|
|
59
59
|
|
|
60
|
-
//
|
|
60
|
+
// 👎 any-cast escape hatch
|
|
61
61
|
const x = (y as any) as SomeType;
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -16,11 +16,11 @@ require oneliner summaries of .what and why comments to precede every code parag
|
|
|
16
16
|
|
|
17
17
|
comments are a hard requirement — they must follow precise structure and length:
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
👍 required
|
|
20
20
|
- /** .what, .why */ block above all named procedures
|
|
21
21
|
- // one-liner before every logical paragraph of code
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
👎 forbidden
|
|
24
24
|
- absent .what or .why above a procedure
|
|
25
25
|
- multiline // paragraph comments
|
|
26
26
|
- vague, redundant, or “code-shaped” comments
|
|
@@ -79,14 +79,14 @@ export const proposeCode = async ({ threads }) => {
|
|
|
79
79
|
|
|
80
80
|
forbidden, negative examples
|
|
81
81
|
```ts
|
|
82
|
-
// absent .what/.why above export
|
|
82
|
+
// absent .what/.why above export 👎 blocker
|
|
83
83
|
export const doStuff = () => { ... }
|
|
84
84
|
|
|
85
|
-
// vague comment
|
|
85
|
+
// vague comment 👎 no intent
|
|
86
86
|
// run flow
|
|
87
87
|
const r = run();
|
|
88
88
|
|
|
89
|
-
// multiline paragraph comment
|
|
89
|
+
// multiline paragraph comment 👎 must extract into procedure
|
|
90
90
|
// handle logic for retries because retries are complicated
|
|
91
91
|
// and they sometimes need to be skipped on failure
|
|
92
92
|
const result = retry(input);
|
|
@@ -48,7 +48,7 @@ const invoice = await getInvoiceById(invoiceId);
|
|
|
48
48
|
|
|
49
49
|
#### .examples
|
|
50
50
|
|
|
51
|
-
#####
|
|
51
|
+
##### 👍 positive
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
54
|
/**
|
|
@@ -64,13 +64,13 @@ export const proposeCode = async ({ threads }) => {
|
|
|
64
64
|
};
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
#####
|
|
67
|
+
##### 👎 negative
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
|
-
// run flow
|
|
70
|
+
// run flow 👎 unclear
|
|
71
71
|
const r = await run();
|
|
72
72
|
|
|
73
|
-
return r.artifact; //
|
|
73
|
+
return r.artifact; // 👎 what is this? why return this?
|
|
74
74
|
|
|
75
75
|
// very long comment that describes why this block exists
|
|
76
76
|
// and what it does and why it's written in this particular way
|
|
@@ -57,7 +57,7 @@ Instead of `let` + `beforeAll` + `afterAll`:
|
|
|
57
57
|
|
|
58
58
|
e.g.,
|
|
59
59
|
```typescript
|
|
60
|
-
//
|
|
60
|
+
// 👎 Don't do this
|
|
61
61
|
let dbConnection: DatabaseConnection;
|
|
62
62
|
beforeAll(async () => {
|
|
63
63
|
dbConnection = await getDatabaseConnection();
|
|
@@ -66,7 +66,7 @@ afterAll(async () => {
|
|
|
66
66
|
await dbConnection.end();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// 👍 Do this
|
|
70
70
|
const dbConnection = useBeforeAll(() => getDatabaseConnection());
|
|
71
71
|
afterAll(async () => dbConnection.end());
|
|
72
72
|
```
|
|
@@ -102,7 +102,7 @@ given('[case2] second scenario', () => {
|
|
|
102
102
|
Each `then` block should test a single behavioral assertion. This makes test failures more precise and test names more descriptive:
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
//
|
|
105
|
+
// 👎 Don't do this - multiple assertions in one then
|
|
106
106
|
when('[t0] command executed in PLAN mode', () => {
|
|
107
107
|
then('decision is UPDATE and doer remains unchanged', async () => {
|
|
108
108
|
const result = await command({ mode: 'PLAN' });
|
|
@@ -114,7 +114,7 @@ when('[t0] command executed in PLAN mode', () => {
|
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
//
|
|
117
|
+
// 👍 Do this - separate then blocks for each behavioral assertion
|
|
118
118
|
when('[t0] command executed in PLAN mode', () => {
|
|
119
119
|
then('decision is "UPDATE"', async () => {
|
|
120
120
|
const result = await command({ mode: 'PLAN' });
|
|
@@ -74,12 +74,12 @@ given('[case1] prose-author example repo', () => {
|
|
|
74
74
|
don't split related scenarios across multiple `given` blocks:
|
|
75
75
|
|
|
76
76
|
```ts
|
|
77
|
-
//
|
|
77
|
+
// 👎 bad - fragmented
|
|
78
78
|
given('[case8] prose-author rule enumeration', () => { ... });
|
|
79
79
|
given('[case9] prose-author chapter enumeration', () => { ... });
|
|
80
80
|
given('[case10] prose-author review works', () => { ... });
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// 👍 good - consolidated
|
|
83
83
|
given('[case8] prose-author example repo', () => {
|
|
84
84
|
when('[t0] before any changes', () => {
|
|
85
85
|
then('rules glob matches', ...);
|
|
@@ -93,24 +93,24 @@ given('[case8] prose-author example repo', () => {
|
|
|
93
93
|
### when describes state/time, not action
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
|
-
//
|
|
96
|
+
// 👎 bad - describes action
|
|
97
97
|
when('[t0] assets are checked', () => { ... });
|
|
98
98
|
|
|
99
|
-
//
|
|
99
|
+
// 👍 good - describes state/time
|
|
100
100
|
when('[t0] before any changes', () => { ... });
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
### use afterEach for cleanup
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
|
-
//
|
|
106
|
+
// 👎 bad - inline cleanup
|
|
107
107
|
then('creates output file', async () => {
|
|
108
108
|
const result = await doThing();
|
|
109
109
|
await fs.rm(outputPath); // cleanup inside then
|
|
110
110
|
expect(result).toBeDefined();
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
//
|
|
113
|
+
// 👍 good - afterEach cleanup
|
|
114
114
|
when('[t1] operation runs', () => {
|
|
115
115
|
const outputPath = path.join(os.tmpdir(), 'output.md');
|
|
116
116
|
afterEach(async () => fs.rm(outputPath, { force: true }));
|
|
@@ -125,13 +125,13 @@ when('[t1] operation runs', () => {
|
|
|
125
125
|
### preconditions shouldn't expect errors
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
|
-
//
|
|
128
|
+
// 👎 bad - precondition expects error then checks it's not a validation error
|
|
129
129
|
then('does not throw validation errors', async () => {
|
|
130
130
|
const error = await getError(doThing());
|
|
131
131
|
expect(error.message).not.toContain('validation');
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
//
|
|
134
|
+
// 👍 good - precondition checks assets directly
|
|
135
135
|
then('rules glob matches 2 files', async () => {
|
|
136
136
|
const files = await enumFiles({ glob: 'rules/*.md' });
|
|
137
137
|
expect(files).toHaveLength(2);
|
|
@@ -56,7 +56,7 @@ given('a mechanic with ask, claim, and coderefs', () => {
|
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
```ts
|
|
59
|
-
when('executed', async () => { //
|
|
59
|
+
when('executed', async () => { // 👎 async not allowed in when()
|
|
60
60
|
const result = await doSomething();
|
|
61
61
|
});
|
|
62
62
|
```
|
|
@@ -40,7 +40,7 @@ gerund usage = **BLOCKER**
|
|
|
40
40
|
|
|
41
41
|
#### .alternatives
|
|
42
42
|
|
|
43
|
-
|
|
|
43
|
+
| 👎 gerund | 👍 alternative | .why |
|
|
44
44
|
|-----------|----------------|------|
|
|
45
45
|
| `existing` | `found`, `current`, `prior` | state, not action |
|
|
46
46
|
| `processing` | `process`, `processor`, `processed` | verb, agent, or result |
|
|
@@ -56,7 +56,7 @@ gerund usage = **BLOCKER**
|
|
|
56
56
|
|
|
57
57
|
#### .examples
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
**👎 bad**
|
|
60
60
|
```ts
|
|
61
61
|
const existingUser = await findUser(); // gerund
|
|
62
62
|
const processingQueue = []; // gerund
|
|
@@ -64,7 +64,7 @@ const handlingErrors = true; // gerund
|
|
|
64
64
|
// we are currently loading the data // gerund in comment
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
**👍 good**
|
|
68
68
|
```ts
|
|
69
69
|
const userFound = await findUser(); // past participle = result
|
|
70
70
|
const processQueue = []; // noun
|
package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.order.noun_adj.md.pt2.md
CHANGED
|
@@ -19,7 +19,7 @@ always use `[noun][state/adjective]` order for variable and property names
|
|
|
19
19
|
#### .categories
|
|
20
20
|
|
|
21
21
|
##### temporal qualifiers
|
|
22
|
-
|
|
|
22
|
+
| 👎 bad | 👍 good |
|
|
23
23
|
|--------|---------|
|
|
24
24
|
| `previousValue` | `valuePrevious` |
|
|
25
25
|
| `currentOwner` | `ownerCurrent` |
|
|
@@ -28,7 +28,7 @@ always use `[noun][state/adjective]` order for variable and property names
|
|
|
28
28
|
| `afterState` | `stateAfter` |
|
|
29
29
|
|
|
30
30
|
##### state qualifiers (past participles)
|
|
31
|
-
|
|
|
31
|
+
| 👎 bad | 👍 good |
|
|
32
32
|
|--------|---------|
|
|
33
33
|
| `existingUser` | `userFound` |
|
|
34
34
|
| `foundRecord` | `recordFound` |
|
|
@@ -38,7 +38,7 @@ always use `[noun][state/adjective]` order for variable and property names
|
|
|
38
38
|
| `matchingResult` | `resultMatched` |
|
|
39
39
|
|
|
40
40
|
##### descriptive qualifiers
|
|
41
|
-
|
|
|
41
|
+
| 👎 bad | 👍 good |
|
|
42
42
|
|--------|---------|
|
|
43
43
|
| `validInput` | `inputValid` |
|
|
44
44
|
| `emptyList` | `listEmpty` |
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
- `invoice` // base noun with implicit sense
|
|
42
42
|
|
|
43
43
|
.negative:
|
|
44
|
-
- `stepImagineGenFromTemplate` //
|
|
45
|
-
- `customerSet` //
|
|
46
|
-
- `FoundContent` //
|
|
47
|
-
- `submitQuoteJob` //
|
|
48
|
-
- `update()` //
|
|
44
|
+
- `stepImagineGenFromTemplate` // 👎 wrong order — verb must come first
|
|
45
|
+
- `customerSet` // 👎 vague mechanism — better as `setCustomer` or `setCustomerPhone`
|
|
46
|
+
- `FoundContent` // 👎 reversed word order and PascalCase — should be `contentFound`
|
|
47
|
+
- `submitQuoteJob` // 👎 noun hierarchy flipped — should be `submitJobQuote`
|
|
48
|
+
- `update()` // 👎 unscoped verb — must include noun domain (e.g., `updateInvoice`)
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
- `// pass into StitchStepImagine to generate`
|
|
30
30
|
- `// openai prompt requires flattened string`
|
|
31
31
|
.negative:
|
|
32
|
-
- `// Returns a new invoice` //
|
|
33
|
-
- `// This is handled in GitRepo` //
|
|
34
|
-
- `// Submit the job for approval` //
|
|
35
|
-
- `// Customer must exist first` //
|
|
36
|
-
- `// GitHub Repository` //
|
|
32
|
+
- `// Returns a new invoice` // 👎 capitalized sentence start
|
|
33
|
+
- `// This is handled in GitRepo` // 👎 capitalized sentence + verb
|
|
34
|
+
- `// Submit the job for approval` // 👎 capitalized imperative
|
|
35
|
+
- `// Customer must exist first` // 👎 capitalized domain noun
|
|
36
|
+
- `// GitHub Repository` // 👎 capitalized generic noun
|
|
@@ -19,14 +19,14 @@ the mechanic always refers to the person they work with as "the human", never "t
|
|
|
19
19
|
|
|
20
20
|
## .examples
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### 👍 good
|
|
23
23
|
|
|
24
24
|
- "the human asked for..."
|
|
25
25
|
- "waiting for the human to confirm"
|
|
26
26
|
- "the human's request"
|
|
27
27
|
- "let me check with the human"
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### 👎 bad
|
|
30
30
|
|
|
31
31
|
- "the user asked for..."
|
|
32
32
|
- "waiting for the user to confirm"
|
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.
|
|
5
|
+
"version": "1.17.18",
|
|
6
6
|
"repository": "ehmpathy/rhachet-roles-ehmpathy",
|
|
7
7
|
"homepage": "https://github.com/ehmpathy/rhachet-roles-ehmpathy",
|
|
8
8
|
"keywords": [
|
|
@@ -89,9 +89,9 @@
|
|
|
89
89
|
"esbuild-register": "3.6.0",
|
|
90
90
|
"husky": "8.0.3",
|
|
91
91
|
"jest": "30.2.0",
|
|
92
|
-
"rhachet": "1.
|
|
92
|
+
"rhachet": "1.21.5",
|
|
93
93
|
"rhachet-roles-bhrain": "0.5.9",
|
|
94
|
-
"rhachet-roles-bhuild": "0.
|
|
94
|
+
"rhachet-roles-bhuild": "0.6.4",
|
|
95
95
|
"rhachet-roles-ehmpathy": "link:.",
|
|
96
96
|
"test-fns": "1.6.0",
|
|
97
97
|
"tsc-alias": "1.8.10",
|