rhachet-roles-ehmpathy 1.15.21 → 1.15.23
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 +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/consistent.contracts/ref.package.as-command.[ref].md +1 -1
- 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.architecture/rule.require.domain-driven-design.md +7 -7
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.domain.objects/rule.forbid.undefined-attributes.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.domain.operations/define.domain-operation-core-variants.md +122 -0
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.forbid.positional-args.md.pt1.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.arrow-only.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.clear-contracts.md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.dependency-injection.md.pt1.md +16 -16
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.dependency-injection.md.pt2.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.input-context-pattern.md.pt1.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.input-context-pattern.md.pt2.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.named-args.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.procedures/rule.require.single-responsibility.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.repo.structure/rule.forbid.barrel-exports.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/evolvable.repo.structure/rule.require.directional-deps.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.errors/rule.forbid.error-hiding.md.pt1.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.errors/rule.prefer.helpful-error-wrap.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.errors/rule.require.fail-fast.md +11 -11
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.procedures/rule.require.idempotency.[seed].md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.procedures/rule.require.idempotent-procedures.md +5 -5
- 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.procedures/rule.require.pitofsuccess.md +5 -5
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.typedefs/define.bivariance-for-generics.[lesson].md +6 -6
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.typedefs/rule.forbid.as-cast.md +6 -6
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/pitofsuccess.typedefs/rule.require.shapefit.md +11 -11
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.comments/rule.require.what-why-headers.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.comments/rule.require.what-why-headers.v1.md +6 -6
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.narrative/rule.prefer.transformers-over-conditionals.[lesson].md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.prod/readable.narrative/rule.require.narrative-flow.md +3 -3
- package/dist/domain.roles/mechanic/briefs/practices/code.test/consistent.contracts/ref.package.test-fns.[ref].md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/howto.write-bdd.[lesson].md +9 -9
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/howto.write-bdd.[lesson].pt2.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.behavior/rule.require.given-when-then.md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.test/frames.caselist/rule.prefer.data-driven.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.test/lessons.howto/howto.diagnose.[lesson].md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/code.test/lessons.howto/howto.use.[lesson].md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.test/lessons.howto/howto.write.[lesson].md.pt2.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/code.test/lessons.howto/rule.require.snapshots.[lesson].md +2 -2
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/define.exec-vs-apply.md +9 -9
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.forbid.gerunds.md +84 -0
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.forbid.term-script.md.pt2.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.order.noun_adj.md.pt2.md +76 -0
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.treestruct.md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/lang.terms/rule.require.ubiqlang.md +8 -8
- package/dist/domain.roles/mechanic/briefs/practices/lang.tones/.readme.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/lang.tones/rule.prefer.lowercase.md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/work.flow/diagnose/howto.bisect.[lesson].md +4 -4
- package/dist/domain.roles/mechanic/briefs/practices/work.flow/diagnose/rule.forbid.trust-vscode-diagnostics.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/work.flow/refactor/rule.prefer.sedreplace-for-renames.md +10 -10
- package/dist/domain.roles/mechanic/briefs/practices/work.flow/refactor/rule.require.review-test-changes.md +1 -1
- package/dist/domain.roles/mechanic/briefs/practices/work.flow/tools/rule.require.externalized-knowledge.md +1 -1
- package/dist/domain.roles/mechanic/inits/init.claude.permissions.jsonc +5 -0
- package/package.json +4 -2
- package/readme.md +1 -1
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- ### **funcs:arrow-only**
|
|
15
15
|
- **rule**: all procedures must be declared as arrow functions
|
|
16
16
|
- `function` keyword is **disallowed**, even for exports
|
|
17
|
-
- object methods are allowed only inside class or
|
|
17
|
+
- object methods are allowed only inside class or api contracts
|
|
18
18
|
- always destructure `input` as first argument when applicable
|
|
19
19
|
- **example:**
|
|
20
20
|
```ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.tactic = arch:bounded-contexts
|
|
2
2
|
|
|
3
|
-
.what = enforce clear separation of concerns
|
|
3
|
+
.what = enforce clear separation of concerns via bounded contexts — each domain must own its logic, models, and procedures
|
|
4
4
|
|
|
5
5
|
.scope:
|
|
6
6
|
- applies to all modules, folders, stitched routes, domain objects, and services
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
.why:
|
|
10
10
|
- upholds the **single responsibility principle** at the system level:
|
|
11
|
-
- each context owns exactly one domain concern (e.g.
|
|
11
|
+
- each context owns exactly one domain concern (e.g. invoice, job, customer data)
|
|
12
12
|
- enforces **low-trust contracts** between contexts:
|
|
13
13
|
- no assumptions about internal structures or invariants of other domains
|
|
14
14
|
- all interaction must happen through **explicit, versionable contracts**
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
.positive:
|
|
41
41
|
- `job/JobQuote.ts` imports `CustomerPhoneUpdate` from `contracts/`, not from `customer/logic/`
|
|
42
42
|
- `invoice/` has its own `InvoiceDraft`, `InvoiceFinal`, and `generateInvoice.ts`
|
|
43
|
-
- `routes/submitJob.ts` orchestrates job + invoice
|
|
43
|
+
- `routes/submitJob.ts` orchestrates job + invoice via stitch, not via import of both
|
|
44
44
|
|
|
45
45
|
.negative:
|
|
46
46
|
- `job.ts` imports `from '../../invoice/utils.ts'` ⛔ reach-in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.tactic = arch:domain-driven-design
|
|
2
2
|
|
|
3
|
-
.what = model all business logic
|
|
3
|
+
.what = model all business logic via well-defined domain objects, procedures, and contracts — never loose bags of properties
|
|
4
4
|
|
|
5
5
|
.scope:
|
|
6
6
|
- applies to all business logic, application services, types, data flows, and stateful modules
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
- if a `Customer`, `Invoice`, or `Quote` already exists, reuse it — don’t make a new shape
|
|
29
29
|
- support **runtime validation** with schemas (Zod, Joi, Yup)
|
|
30
30
|
- all domain objects with external inputs must be validated on instantiation
|
|
31
|
-
- define `.unique` and `.updatable` fields for identity and change
|
|
32
|
-
- expose immutable behaviors
|
|
33
|
-
- procedures with more than 2 scalar arguments
|
|
31
|
+
- define `.unique` and `.updatable` fields for identity and change detection
|
|
32
|
+
- expose immutable behaviors via `.clone()` and `.build()` for safe updates
|
|
33
|
+
- procedures with more than 2 scalar arguments that represent **data** should be refactored to use a domain object
|
|
34
34
|
- control/options args are acceptable when clearly scoped (e.g. `{ dryRun: true }`)
|
|
35
35
|
|
|
36
36
|
.enforcement:
|
|
37
37
|
- code reviews must block untyped or loosely typed `{}` logic
|
|
38
38
|
- procedures with more than 2 scalar arguments should be refactored to use domain objects
|
|
39
39
|
- stitched routes and service layers must pass domain objects explicitly
|
|
40
|
-
- tests should validate domain behavior, not just
|
|
40
|
+
- tests should validate domain behavior, not just json shape
|
|
41
41
|
- imports from `domain-objects` are required for any core domain concept
|
|
42
42
|
|
|
43
43
|
.examples:
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
.negative:
|
|
53
53
|
- `{ name, email, address }` passed between modules without a domain object
|
|
54
54
|
- `function updateUser(name, email, phone)` instead of `updateCustomerInfo(Customer)`
|
|
55
|
-
- `type UserData = { ... }`
|
|
56
|
-
-
|
|
55
|
+
- `type UserData = { ... }` that duplicates `Customer` shape
|
|
56
|
+
- stored json blobs that replicate prior domain models
|
|
57
57
|
- `update()` method used on raw DTOs
|
|
58
58
|
|
|
59
59
|
.links:
|
|
@@ -4,7 +4,7 @@ never allow "undefined" attributes for domain objects UNLESS they are database-g
|
|
|
4
4
|
i.e.,
|
|
5
5
|
- ids, createdAt, updatedAt, etc - these are allowed to be undefined, since they're metadata, and only known on read
|
|
6
6
|
|
|
7
|
-
however,
|
|
7
|
+
however, all else should be explicitly knowable
|
|
8
8
|
- if we know that it is null, then set it as null
|
|
9
9
|
- however, we should be really clear as to WHY it can be null; if there is no good domain reason for null, forbid it
|
|
10
10
|
- otherwise, define the exact type
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
### .define = domain-operation-core-variants
|
|
2
|
+
|
|
3
|
+
#### .what
|
|
4
|
+
domain.operations contains two fundamental variants of leaf operations:
|
|
5
|
+
- **compute*** = deterministic computed work (pure functions, no external dependencies)
|
|
6
|
+
- **imagine*** = probabilistic creative work via `context.brain.repl.imagine({ role })`
|
|
7
|
+
|
|
8
|
+
#### .when
|
|
9
|
+
- most projects only have deterministic operations — no `brain.repl` invocations
|
|
10
|
+
- in these cases, the `compute*` prefix is **not required** (no ambiguity exists)
|
|
11
|
+
- enforcement becomes **mandatory** when a project contains both variants
|
|
12
|
+
- once `imagine*` operations exist in a codebase, `compute*` prefixes become required for clarity
|
|
13
|
+
|
|
14
|
+
#### .scope
|
|
15
|
+
- the `compute*`/`imagine*` prefix applies only to **leaf operations**
|
|
16
|
+
- leaf operations = the ones that actually perform the computation or imagination
|
|
17
|
+
- **composer operations** that orchestrate leaf operations use normal names (e.g., `getInvoice`, `setCustomerPhone`, `syncUserFromProvider`)
|
|
18
|
+
- the prefix signals *what kind of work is done*, not *what kind of work is called*
|
|
19
|
+
|
|
20
|
+
#### .why
|
|
21
|
+
when both variants exist in a codebase, the prefix:
|
|
22
|
+
- clarifies the nature of each leaf operation at a glance
|
|
23
|
+
- sets expectations for testability (deterministic vs probabilistic)
|
|
24
|
+
- signals dependency requirements (pure vs requires brain context)
|
|
25
|
+
- enables appropriate error strategies for each type
|
|
26
|
+
- guides code review focus (logic correctness vs prompt quality)
|
|
27
|
+
|
|
28
|
+
#### .how
|
|
29
|
+
|
|
30
|
+
##### compute* leaf operations
|
|
31
|
+
- prefix with `compute` (e.g., `computeInvoiceTotal`, `computeHashFromContent`)
|
|
32
|
+
- must be **pure functions** — same input always produces same output
|
|
33
|
+
- **no external dependencies** — no network, no database, no randomness
|
|
34
|
+
- trivially testable with simple input/output assertions
|
|
35
|
+
- can be safely memoized or cached
|
|
36
|
+
|
|
37
|
+
##### imagine* leaf operations
|
|
38
|
+
- prefix with `imagine` (e.g., `imagineChapterOutline`, `imagineCodeReview`)
|
|
39
|
+
- invoke `context.brain.repl.imagine({ role })` for LLM-powered generation
|
|
40
|
+
- **probabilistic** — same input may produce different valid outputs
|
|
41
|
+
- require snapshot tests or semantic assertions
|
|
42
|
+
- depend on `context.brain` provision
|
|
43
|
+
|
|
44
|
+
##### composer operations
|
|
45
|
+
- use normal verb prefixes (e.g., `get*`, `set*`, `sync*`, ...)
|
|
46
|
+
- orchestrate one or more leaf operations
|
|
47
|
+
- no special prefix required — their behavior is defined by what they compose
|
|
48
|
+
|
|
49
|
+
#### .examples
|
|
50
|
+
|
|
51
|
+
**✅ good — leaf operations with proper prefixes**
|
|
52
|
+
```ts
|
|
53
|
+
// computeInvoiceTotal.ts — leaf, deterministic
|
|
54
|
+
export const computeInvoiceTotal = (input: { lineItems: LineItem[] }) => {
|
|
55
|
+
return input.lineItems.reduce((sum, item) => sum + item.amount, 0);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// imagineChapterSummary.ts — leaf, probabilistic
|
|
59
|
+
export const imagineChapterSummary = async (
|
|
60
|
+
input: { chapter: string },
|
|
61
|
+
context: { brain: Brain },
|
|
62
|
+
) => {
|
|
63
|
+
return context.brain.repl.imagine({
|
|
64
|
+
role: 'summarizer',
|
|
65
|
+
prompt: `summarize this chapter: ${input.chapter}`,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**✅ good — composer operation with normal name**
|
|
71
|
+
```ts
|
|
72
|
+
// getInvoiceWithSummary.ts — composes both variants
|
|
73
|
+
export const getInvoiceWithSummary = async (
|
|
74
|
+
input: { invoiceId: string },
|
|
75
|
+
context: { brain: Brain; invoiceDao: InvoiceDao },
|
|
76
|
+
) => {
|
|
77
|
+
const invoice = await context.invoiceDao.findById(input.invoiceId);
|
|
78
|
+
const total = computeInvoiceTotal({ lineItems: invoice.lineItems });
|
|
79
|
+
const summary = await imagineChapterSummary({ chapter: invoice.notes }, context);
|
|
80
|
+
return { ...invoice, total, summary };
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**⛔ bad — prefix misleads**
|
|
85
|
+
```ts
|
|
86
|
+
// computeResponse.ts — "compute" but actually calls LLM
|
|
87
|
+
export const computeResponse = async (input, context) => {
|
|
88
|
+
return context.brain.repl.imagine({ ... }); // ⛔ prefix misleads
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**⛔ bad — ambiguous leaf name (only when both variants exist)**
|
|
93
|
+
```ts
|
|
94
|
+
// generateTotal.ts — unclear if deterministic or probabilistic
|
|
95
|
+
export const generateTotal = (...) => { ... }
|
|
96
|
+
|
|
97
|
+
// processChapter.ts — verb doesn't signal operation type
|
|
98
|
+
export const processChapter = (...) => { ... }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### .enforcement
|
|
102
|
+
- leaf operations that call `context.brain` must use `imagine*` prefix
|
|
103
|
+
- leaf operations prefixed with `compute*` must be pure and deterministic
|
|
104
|
+
- prefix mismatch vs implementation = **BLOCKER**
|
|
105
|
+
- ambiguous leaf names in a codebase with both variants = **BLOCKER**
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
#### .appendix: brain terminology
|
|
110
|
+
|
|
111
|
+
##### brain.atom
|
|
112
|
+
- the atomic unit of LLM interaction
|
|
113
|
+
- a single LLM api call (e.g., one completion request)
|
|
114
|
+
- stateless, no memory of prior calls
|
|
115
|
+
- e.g., **Claude api** = one `messages.create()` call
|
|
116
|
+
|
|
117
|
+
##### brain.repl
|
|
118
|
+
- **r**ead, **e**xecute, **p**rint, **l**oop
|
|
119
|
+
- orchestrates `brain.atom` calls within a tooluse loop
|
|
120
|
+
- enables multi-turn reason with tool execution between steps
|
|
121
|
+
- `brain.repl.imagine({ role })` = invoke the repl to imagine/generate via the specified role
|
|
122
|
+
- e.g., **Claude Code** = agentic loop that reads files, executes tools, prints results, loops until done
|
|
@@ -36,8 +36,8 @@ vs
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
- Self-
|
|
39
|
+
- Self-documented: You can see what each parameter is at the call site
|
|
40
40
|
- Order-independent: Arguments can be passed in any order
|
|
41
|
-
- Easier to maintain:
|
|
41
|
+
- Easier to maintain: New optional parameters won't break prior calls
|
|
42
42
|
- Validation: Each function validates required parameters
|
|
43
43
|
- Usage examples: Included in function headers
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
### .tactic = funcs:arrow-only
|
|
2
2
|
|
|
3
3
|
#### .what
|
|
4
|
-
enforce that all procedures are declared
|
|
4
|
+
enforce that all procedures are declared via arrow functions
|
|
5
5
|
disallow use of `function` keyword for any procedure
|
|
6
6
|
|
|
7
7
|
#### .where
|
|
8
8
|
- applies to all exported and internal functions across codebase
|
|
9
9
|
- required in modules, utilities, hooks, logic, and test declarations
|
|
10
|
-
- exempt only for class methods or dynamic `this`
|
|
10
|
+
- exempt only for class methods or dynamic `this` bind (rare)
|
|
11
11
|
|
|
12
12
|
#### .why
|
|
13
|
-
- enforces lexical `this`
|
|
13
|
+
- enforces lexical `this` bind for predictable behavior
|
|
14
14
|
- aligns code structure with uniform declaration style
|
|
15
15
|
- improves visual clarity and reduces syntactic noise
|
|
16
16
|
- simplifies use with higher-order functions, closures, and composability
|
|
@@ -20,7 +20,7 @@ disallow use of `function` keyword for any procedure
|
|
|
20
20
|
##### .rules
|
|
21
21
|
- use arrow syntax (`const fn = (input) => {}`) for all functions
|
|
22
22
|
- never use `function` keyword for standalone or inline functions
|
|
23
|
-
- use object methods only when tied to a `class` or
|
|
23
|
+
- use object methods only when tied to a `class` or api contract
|
|
24
24
|
- always destructure input as the first arg if applicable
|
|
25
25
|
|
|
26
26
|
##### .examples
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
|
|
2
2
|
.tactic = mech:contract-before-implementation
|
|
3
3
|
|
|
4
|
-
.what = declare the shape and expectations of behavior (the contract) before
|
|
4
|
+
.what = declare the shape and expectations of behavior (the contract) before the implementation is written
|
|
5
5
|
|
|
6
6
|
.why:
|
|
7
|
-
- forces clarity about what
|
|
8
|
-
- prevents premature optimization and over-
|
|
7
|
+
- forces clarity about what to build and why
|
|
8
|
+
- prevents premature optimization and over-engineer
|
|
9
9
|
- creates a shared agreement for both the builder and consumer
|
|
10
10
|
- enables parallel work (e.g., backend + frontend, or logic + UI)
|
|
11
11
|
|
|
12
12
|
.scope:
|
|
13
13
|
- applies to procedures, stitched flows, and any domain mechanisms
|
|
14
|
-
- applies at both macro (
|
|
14
|
+
- applies at both macro (api endpoint, service) and micro (function, component) levels
|
|
15
15
|
|
|
16
16
|
.how:
|
|
17
|
-
- always
|
|
17
|
+
- always start with a **procedure type** that defines:
|
|
18
18
|
- function signature or interface
|
|
19
19
|
- expected inputs and output shape
|
|
20
20
|
- error modes and invariants
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* .what = sends an invoice to the customer and marks it as sent
|
|
30
|
-
* .why = required for customer
|
|
30
|
+
* .why = required for customer bill compliance and automation
|
|
31
31
|
*/
|
|
32
32
|
export const sendInvoice = async ({
|
|
33
33
|
invoice,
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
// logic/sendInvoice.ts
|
|
44
44
|
|
|
45
45
|
export const sendInvoice = async (invoice, customer) => {
|
|
46
|
-
// suddenly starts
|
|
46
|
+
// suddenly starts to do 4 tasks and mutates stuff randomly
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
.enforcement:
|
|
50
50
|
- if a PR implements logic without any visible contract: **blocker**
|
|
51
51
|
- if types are vague or inferred (e.g. `any`, `unknown`): **blocker**
|
|
52
|
-
- if behavior differs from contract or
|
|
52
|
+
- if behavior differs from contract or a test is absent: **blocker**
|
|
53
53
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
## 🧠 `.brief.designer.dependency-injection`
|
|
2
2
|
|
|
3
3
|
### 🧠 topic: dependency injection
|
|
4
|
-
**why we pass in dependencies instead of
|
|
4
|
+
**why we pass in dependencies instead of hardcode them**
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
### 🎯 goal
|
|
9
9
|
build software that is **flexible, testable, and decoupled**,
|
|
10
|
-
|
|
10
|
+
via dependencies that are **explicit, swappable, and controlled from the outside**.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
### 🛠 what it is
|
|
15
15
|
|
|
16
|
-
> **dependency injection** =
|
|
17
|
-
> rather than
|
|
16
|
+
> **dependency injection** = to give a function, class, or module what it needs from the outside,
|
|
17
|
+
> rather than to let it construct or look up those tools internally.
|
|
18
18
|
|
|
19
|
-
for example, instead of
|
|
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
|
|
@@ -46,12 +46,12 @@ export const upsert = async (
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
- the function doesn’t reach into globals or construct its own tools
|
|
49
|
-
-
|
|
49
|
+
- all it needs is **passed in from the outside**, which makes it testable, predictable, and side-effect free
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
### 🔑 paved pattern: `(input, context)`
|
|
54
|
-
dependency injection is best done
|
|
54
|
+
dependency injection is best done via the **standard procedure contract**:
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
export const doThing = async (
|
|
@@ -62,7 +62,7 @@ export const doThing = async (
|
|
|
62
62
|
|
|
63
63
|
- all dependencies go in the `context` argument
|
|
64
64
|
- keeps `input` clean, and `context` flexible
|
|
65
|
-
- enables composition,
|
|
65
|
+
- enables composition, trace, and full test control
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
---
|
|
@@ -70,9 +70,9 @@ export const doThing = async (
|
|
|
70
70
|
### 🤝 why it matters
|
|
71
71
|
|
|
72
72
|
- **testability** — swap real dependencies with mocks/fakes
|
|
73
|
-
- **flexibility** — easily change implementations without
|
|
74
|
-
- **separation of concerns** — business logic doesn't manage its own
|
|
75
|
-
- **explicitness** — makes system
|
|
73
|
+
- **flexibility** — easily change implementations without core logic change
|
|
74
|
+
- **separation of concerns** — business logic doesn't manage its own plumb
|
|
75
|
+
- **explicitness** — makes system wires visible and intentional
|
|
76
76
|
- **reusability** — same logic can be reused with different behaviors
|
|
77
77
|
|
|
78
78
|
---
|
|
@@ -88,7 +88,7 @@ you keep **policies and tools separate from behaviors**.
|
|
|
88
88
|
|
|
89
89
|
### 🔍 mental model
|
|
90
90
|
|
|
91
|
-
> dependency injection is like **
|
|
91
|
+
> dependency injection is like to **plug in a tool** instead of to **build one every time**.
|
|
92
92
|
|
|
93
93
|
your procedures say:
|
|
94
94
|
> “give me what I need — I don’t care where it came from.”
|
|
@@ -100,7 +100,7 @@ your procedures say:
|
|
|
100
100
|
| without DI | with DI |
|
|
101
101
|
|----------------------------------------|--------------------------------------------|
|
|
102
102
|
| hardcoded database inside procedures | database passed in as via parameters |
|
|
103
|
-
| requires real
|
|
103
|
+
| requires real api in tests | can inject mock/fake api for tests |
|
|
104
104
|
| logic tied to concrete tools | logic works with any compatible tool |
|
|
105
105
|
| hard to switch infra/tools | swappable with config or environment setup |
|
|
106
106
|
|
|
@@ -110,9 +110,9 @@ your procedures say:
|
|
|
110
110
|
|
|
111
111
|
we inject dependencies to build systems that are easier to **test**, **scale**, and **evolve**.
|
|
112
112
|
|
|
113
|
-
> dependency injection is about
|
|
113
|
+
> dependency injection is about to build **pure, decoupled, and testable** systems.
|
|
114
114
|
> it eliminates shared state, surfaces side effects, and makes your logic portable and composable.
|
|
115
115
|
> it enables **modularity**, **testability**, and **control**,
|
|
116
|
-
>
|
|
116
|
+
> via **external dependencies** instead of hidden inside.
|
|
117
117
|
|
|
118
|
-
done right, it's **invisible
|
|
118
|
+
done right, it's **invisible when used**, and **invaluable for tests, scale, and evolution**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
todo: declare why dependency injection leads to a pit of success && why global shared state should be avoided
|
|
2
2
|
|
|
3
|
-
via dependency injection, you always know what
|
|
3
|
+
via dependency injection, you always know what references what. no side effects
|
|
4
4
|
|
|
5
5
|
persistance is the only global shared state that should exist - and even that should be managed cautiously (e.g., to avoid race conditions)
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ enforce hard requirement that all procedure args to follow the canonical pattern
|
|
|
8
8
|
- prevents positional argument confusion
|
|
9
9
|
- supports context injection without argument churn
|
|
10
10
|
- aligns with domain patterns: input = upstream data, context = runtime environment
|
|
11
|
-
- enables safe
|
|
11
|
+
- enables safe refactors and consistent documentation across codebase
|
|
12
12
|
|
|
13
13
|
#### .where
|
|
14
14
|
- applies to **all function definitions** (sync or async)
|
|
@@ -26,7 +26,7 @@ enforce hard requirement that all procedure args to follow the canonical pattern
|
|
|
26
26
|
- **hard requirement** — applies even to trivial utilities, pure transforms, and data extractors
|
|
27
27
|
|
|
28
28
|
- `input` does **not** need to be destructured at the function boundary; shape like `(input: { ... })` is fine
|
|
29
|
-
- `function` keyword is forbidden unless
|
|
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
|
|
@@ -17,22 +17,22 @@ export const doSomething = (input: { foo: string; bar: number }): void => {
|
|
|
17
17
|
## .why
|
|
18
18
|
this pattern maximizes **maintainability** and **readability** by:
|
|
19
19
|
|
|
20
|
-
1. **
|
|
20
|
+
1. **keep type info local**
|
|
21
21
|
- no need to jump to another file or scroll to find the interface/type definition.
|
|
22
22
|
- changes to argument structure are quick and visible in one place.
|
|
23
23
|
|
|
24
|
-
2. **
|
|
25
|
-
- variables from `input` are always referenced as `input.foo` or `input.bar`,
|
|
24
|
+
2. **reinforce origin clarity**
|
|
25
|
+
- variables from `input` are always referenced as `input.foo` or `input.bar`, which makes their provenance explicit.
|
|
26
26
|
- this avoids ambiguity in larger scopes, especially when a function mixes locals, constants, and context values.
|
|
27
27
|
|
|
28
|
-
3. **
|
|
28
|
+
3. **avoid premature fragmentation**
|
|
29
29
|
- not all shapes deserve a named type. we only extract to `type` or `interface` when the same shape is reused across multiple functions or modules.
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
33
|
## 📏 rules
|
|
34
34
|
- declare the first parameter as `(input: { ... })` with inline types by default.
|
|
35
|
-
-
|
|
35
|
+
- to destructure is allowed **inside** the function body, never in the parameter list.
|
|
36
36
|
- optional `context` parameter follows the same pattern `(context?: { ... })` and is always second.
|
|
37
37
|
- if the type is reused in 3+ places, extract to a named `type` but keep the `input` wrapper.
|
|
38
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
always used named arguments on inputs
|
|
2
2
|
|
|
3
|
-
they make it clear when
|
|
3
|
+
they make it clear when you read what the arguments are used for
|
|
4
4
|
|
|
5
|
-
they also make it possible to reorder arguments without
|
|
5
|
+
they also make it possible to reorder arguments without to break the contract (great for refactors, deprecations, renames, etc)
|
|
6
6
|
|
|
@@ -6,7 +6,7 @@ enforce that every code file and procedure has a singular, clearly-defined respo
|
|
|
6
6
|
#### .why
|
|
7
7
|
- makes code easier to locate, understand, and test
|
|
8
8
|
- ensures separation of concerns between logic, validation, and orchestration
|
|
9
|
-
- prevents files from
|
|
9
|
+
- prevents files from bloat with unrelated logic or creep concerns
|
|
10
10
|
|
|
11
11
|
#### .rules
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ enforce that every code file and procedure has a singular, clearly-defined respo
|
|
|
18
18
|
|
|
19
19
|
❌ forbidden
|
|
20
20
|
- more than one exported procedure per file
|
|
21
|
-
- co-
|
|
21
|
+
- co-located validation, parse, or orchestration logic alongside domain logic
|
|
22
22
|
- runtime type assertions or guards inside general-purpose logic
|
|
23
23
|
- dead code, TODOs, or unrelated comments in the file
|
|
24
24
|
- guards that apply redundant runtime typechecks (we trust typescript)
|
|
@@ -39,11 +39,11 @@ src/
|
|
|
39
39
|
- `contract/` may depend on `domain.objects/` and `domain.operations/`
|
|
40
40
|
- `access/` may depend on `domain.objects/` and `domain.operations/`
|
|
41
41
|
- `domain.operations/` may depend on `domain.objects/` or `infra/`
|
|
42
|
-
- `domain.objects/` must not depend on
|
|
43
|
-
- `infra/` must not depend on
|
|
42
|
+
- `domain.objects/` must not depend on any module outside its own layer
|
|
43
|
+
- `infra/` must not depend on any module outside its own layer
|
|
44
44
|
|
|
45
45
|
- stitched flows live in `domain.operations/` or `contract/commands/` and orchestrate downstream only
|
|
46
|
-
- never import upward across layers (e.g., `domain.objects/`
|
|
46
|
+
- never import upward across layers (e.g., `domain.objects/` that imports `access/`)
|
|
47
47
|
- shared types must follow the same directional rules
|
|
48
48
|
|
|
49
49
|
.enforcement
|
|
@@ -6,9 +6,9 @@ try/catch
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
the only cases where a trycatch is allowed is if the catch has an allowlist of the errors it catches and CAREFULLY handles those, while
|
|
9
|
+
the only cases where a trycatch is allowed is if the catch has an allowlist of the errors it catches and CAREFULLY handles those, while it throws the rest up
|
|
10
10
|
|
|
11
|
-
otherwise, its a failhide hazard, where real errors will be silently hidden,
|
|
11
|
+
otherwise, its a failhide hazard, where real errors will be silently hidden, which leads to defects or hours of pointless debug
|
|
12
12
|
|
|
13
13
|
never failhide
|
|
14
14
|
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
.tactic = flow:fail-fast
|
|
2
2
|
|
|
3
|
-
.what = enforce fail-fast logic
|
|
3
|
+
.what = enforce fail-fast logic via early exits and HelpfulError subclasses to reject invalid state or input immediately
|
|
4
4
|
|
|
5
5
|
.scope:
|
|
6
6
|
- required in all stitched logic, business procedures, and service flows
|
|
7
|
-
- applies to guard checks, validations, and
|
|
7
|
+
- applies to guard checks, validations, and branch paths
|
|
8
8
|
|
|
9
9
|
.why:
|
|
10
|
-
- improves readability
|
|
10
|
+
- improves readability via early collapse of failure paths
|
|
11
11
|
- eliminates nested branches and lets core logic shine
|
|
12
|
-
- increases safety
|
|
13
|
-
- makes bugs easier to debug with rich context
|
|
12
|
+
- increases safety via clear documentation and halt on invalid input
|
|
13
|
+
- makes bugs easier to debug with rich context via `helpful-errors`
|
|
14
14
|
|
|
15
15
|
.how:
|
|
16
16
|
- use **early returns or throws** for all guard clauses
|
|
17
|
-
- never use `if (...) else` or deep
|
|
17
|
+
- never use `if (...) else` or deep nested blocks to control flow
|
|
18
18
|
- prefer `UnexpectedCodePathError.throw(...)` for internal invariant violations
|
|
19
19
|
- these signal logic errors that “should never happen”
|
|
20
|
-
- prefer `BadRequestError.throw(...)`
|
|
20
|
+
- prefer `BadRequestError.throw(...)` to reject invalid input or user requests
|
|
21
21
|
- these signal invalid calls, not system failures
|
|
22
22
|
- use `.throw(...)` syntax for clean 1-liners inside nullish chains or short circuits
|
|
23
|
-
- include **context objects** in all thrown errors to aid
|
|
24
|
-
- validate error behavior in tests
|
|
23
|
+
- include **context objects** in all thrown errors to aid debug
|
|
24
|
+
- validate error behavior in tests via `getError(...)`
|
|
25
25
|
|
|
26
26
|
.code structure:
|
|
27
27
|
- treat each guard block as a **code paragraph**
|
|
28
|
-
- must be preceded by a comment
|
|
29
|
-
- blank line after each guard before
|
|
28
|
+
- must be preceded by a comment that summarizes *why* the guard exists
|
|
29
|
+
- blank line after each guard before main logic continues
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
// reject if user does not exist
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.tactic = proc:require-idempotency
|
|
2
2
|
|
|
3
|
-
.what = all procedures must be idempotent unless explicitly marked otherwise —
|
|
3
|
+
.what = all procedures must be idempotent unless explicitly marked otherwise — to call the same logic twice must not cause double effects
|
|
4
4
|
|
|
5
5
|
.scope:
|
|
6
6
|
- applies to all stitched procedures, exports, and service logic
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
.how:
|
|
15
15
|
- all procedures must **guard against re-entry**
|
|
16
|
-
- check whether the action has already occurred before
|
|
16
|
+
- check whether the action has already occurred before execute
|
|
17
17
|
- e.g., `if (invoice.status === 'FINAL') return invoice;`
|
|
18
|
-
- any **side-effect-
|
|
19
|
-
- check for
|
|
18
|
+
- any **side-effect-produce operation** must:
|
|
19
|
+
- check for a prior result (e.g. prior record, idempotency key, state flag)
|
|
20
20
|
- return early if already complete
|
|
21
21
|
- **external writes** (db, api, fs, queues) must be safe to call multiple times without duplication
|
|
22
22
|
- use upserts, conflict-ignore inserts, or external idempotency tokens
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- procedures with `set`, `push`, `post`, or other side-effect verbs must show idempotency check
|
|
30
30
|
- non-idempotent procedures must be annotated with a `.note`
|
|
31
31
|
- workflows and retry logic must assume re-entry and prove safety
|
|
32
|
-
- test cases must show repeat-
|
|
32
|
+
- test cases must show repeat-call has no additional effect
|
|
33
33
|
|
|
34
34
|
.examples:
|
|
35
35
|
|