@tenkara/afm-linux-arm64 0.0.0-stage → 4.0.0-alpha.11
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/bin/afm +4 -0
- package/content/.coupling-baseline.tsv +1 -0
- package/content/.pending-commands.txt +2 -0
- package/content/CHANGELOG-archive.md +806 -0
- package/content/CHANGELOG.md +4330 -0
- package/content/PRINCIPLES.md +119 -0
- package/content/README-light.md +158 -0
- package/content/README.md +150 -0
- package/content/code-style/_any/file-size.md +62 -0
- package/content/code-style/_any/imports.md +68 -0
- package/content/code-style/_any/naming.md +72 -0
- package/content/code-style/_any/test-placement.md +57 -0
- package/content/code-style/nextjs-app-router/imports.md +50 -0
- package/content/code-style/nextjs-app-router/naming.md +50 -0
- package/content/code-style/prisma-postgres/error-boundary.md +55 -0
- package/content/code-style/prisma-postgres/imports.md +61 -0
- package/content/code-style/shadcn-ui/naming.md +48 -0
- package/content/code-style/stripe-billing/naming.md +76 -0
- package/content/code-style/trigger-dev-v4/imports.md +58 -0
- package/content/code-style/trigger-dev-v4/naming.md +49 -0
- package/content/code-style/trigger-dev-v4/test-placement.md +61 -0
- package/content/code-style/trpc-v11/error-boundary.md +51 -0
- package/content/code-style/trpc-v11/file-size.md +44 -0
- package/content/code-style/trpc-v11/naming.md +60 -0
- package/content/code-style/trpc-v11/test-placement.md +52 -0
- package/content/core/ach.md +260 -0
- package/content/core/afm.md +514 -0
- package/content/core/gotchas.md +18 -0
- package/content/core/prd.md +141 -0
- package/content/core/protocol.md +158 -0
- package/content/core/ust.md +65 -0
- package/content/guardrails/README.md +55 -0
- package/content/guardrails/dsl-com-ordem-obrigatoria.md +47 -0
- package/content/guardrails/exaustividade-de-union.md +48 -0
- package/content/guardrails/fronteira-de-import.md +57 -0
- package/content/hooks/README.md +70 -0
- package/content/interview/bootstrap.md +417 -0
- package/content/interview/promotion.md +115 -0
- package/content/interview/refactor.md +379 -0
- package/content/interview/triggers.md +107 -0
- package/content/modules/nextjs-app-router/ach.patch.md +44 -0
- package/content/modules/nextjs-app-router/gotchas.seed.md +78 -0
- package/content/modules/nextjs-app-router/rules.patch.md +35 -0
- package/content/modules/prisma-postgres/ach.patch.md +65 -0
- package/content/modules/prisma-postgres/rules.patch.md +28 -0
- package/content/modules/shadcn-ui/ach.patch.md +42 -0
- package/content/modules/stripe-billing/ach.patch.md +46 -0
- package/content/modules/stripe-billing/gotchas.seed.md +73 -0
- package/content/modules/stripe-billing/rules.patch.md +30 -0
- package/content/modules/trigger-dev-v4/ach.patch.md +64 -0
- package/content/modules/trigger-dev-v4/gotchas.seed.md +53 -0
- package/content/modules/trigger-dev-v4/rules.patch.md +35 -0
- package/content/modules/trpc-v11/ach.patch.md +56 -0
- package/content/modules/trpc-v11/rules.patch.md +34 -0
- package/content/modules/vireya-ui/ach.patch.md +85 -0
- package/content/modules/vireya-ui/gotchas.seed.md +98 -0
- package/content/modules/vireya-ui/rules.patch.md +36 -0
- package/content/ops/adr.md +54 -0
- package/content/ops/analyze.md +130 -0
- package/content/ops/clarify.md +86 -0
- package/content/ops/deliver.md +209 -0
- package/content/ops/diagnose.md +143 -0
- package/content/ops/evolve.md +366 -0
- package/content/ops/export-template.md +97 -0
- package/content/ops/generalize.md +117 -0
- package/content/ops/gotcha.md +46 -0
- package/content/ops/obligations.md +145 -0
- package/content/ops/plan.md +61 -0
- package/content/ops/policy.md +61 -0
- package/content/ops/promote.md +72 -0
- package/content/ops/protocol.md +51 -0
- package/content/ops/reconcile.md +44 -0
- package/content/ops/reflect.md +182 -0
- package/content/ops/remediate.md +143 -0
- package/content/ops/restructure.md +142 -0
- package/content/ops/rule.md +170 -0
- package/content/ops/specify.md +62 -0
- package/content/ops/tasks.md +67 -0
- package/content/ops/update.md +252 -0
- package/content/ops/verify.md +67 -0
- package/content/patterns/adapter.md +46 -0
- package/content/patterns/facade.md +47 -0
- package/content/patterns/factory.md +46 -0
- package/content/patterns/null-object.md +55 -0
- package/content/patterns/result-vs-exception.md +52 -0
- package/content/patterns/strategy.md +45 -0
- package/content/patterns/template-method-vs-composition.md +50 -0
- package/content/questions/adr.questions.yaml +14 -0
- package/content/questions/analyze.questions.yaml +12 -0
- package/content/questions/bootstrap-mapping.questions.yaml +14 -0
- package/content/questions/bootstrap-rules.questions.yaml +10 -0
- package/content/questions/bootstrap-stack.questions.yaml +23 -0
- package/content/questions/clarify.questions.yaml +10 -0
- package/content/questions/code-style.questions.yaml +12 -0
- package/content/questions/confirm.questions.yaml +12 -0
- package/content/questions/deliver.questions.yaml +23 -0
- package/content/questions/evolve.questions.yaml +15 -0
- package/content/questions/export-template-apply.questions.yaml +11 -0
- package/content/questions/export-template.questions.yaml +23 -0
- package/content/questions/gates.questions.yaml +27 -0
- package/content/questions/generalize.questions.yaml +13 -0
- package/content/questions/gotcha.questions.yaml +11 -0
- package/content/questions/init-template.questions.yaml +10 -0
- package/content/questions/init.questions.yaml +41 -0
- package/content/questions/obligations.questions.yaml +17 -0
- package/content/questions/plan.questions.yaml +14 -0
- package/content/questions/refactor.questions.yaml +23 -0
- package/content/questions/reflect.questions.yaml +13 -0
- package/content/questions/remediate.questions.yaml +27 -0
- package/content/questions/restructure.questions.yaml +15 -0
- package/content/questions/rf-mapping.questions.yaml +12 -0
- package/content/questions/rule-scope.questions.yaml +12 -0
- package/content/questions/rule.questions.yaml +12 -0
- package/content/questions/specify.questions.yaml +23 -0
- package/content/questions/update-artifacts.questions.yaml +10 -0
- package/content/questions/update-strings.questions.yaml +14 -0
- package/content/questions/update.questions.yaml +10 -0
- package/content/questions/verify.questions.yaml +9 -0
- package/content/roles/executor.md +18 -0
- package/content/roles/explorer.md +22 -0
- package/content/roles/reviewer.md +23 -0
- package/content/rubrics/enum-vs-union-vs-branded.md +40 -0
- package/content/rubrics/episodic-vs-semantic-boundary.md +37 -0
- package/content/rubrics/error-classification.md +98 -0
- package/content/rubrics/failure-classification.md +111 -0
- package/content/rubrics/negative-filters.md +38 -0
- package/content/rubrics/solid-triggers.md +119 -0
- package/content/rubrics/template-vs-streaming-precedence.md +34 -0
- package/content/rubrics/validation-boundary.md +94 -0
- package/content/rubrics/when-to-create-dsl.md +65 -0
- package/content/rubrics/when-to-create-lib.md +49 -0
- package/content/rubrics/when-to-create-module.md +39 -0
- package/content/rubrics/when-to-evolve-methodology.md +62 -0
- package/content/skills/adr.md +42 -0
- package/content/skills/analyze.md +28 -0
- package/content/skills/clarify.md +28 -0
- package/content/skills/deliver.md +26 -0
- package/content/skills/diagnose.md +42 -0
- package/content/skills/evolve.md +43 -0
- package/content/skills/export-template.md +37 -0
- package/content/skills/generalize.md +31 -0
- package/content/skills/gotcha.md +40 -0
- package/content/skills/init.md +77 -0
- package/content/skills/obligations.md +27 -0
- package/content/skills/plan.md +28 -0
- package/content/skills/policy.md +25 -0
- package/content/skills/promote.md +39 -0
- package/content/skills/protocol.md +41 -0
- package/content/skills/reconcile.md +45 -0
- package/content/skills/refactor.md +79 -0
- package/content/skills/reflect.md +43 -0
- package/content/skills/remediate.md +33 -0
- package/content/skills/restructure.md +46 -0
- package/content/skills/rule.md +43 -0
- package/content/skills/specify.md +28 -0
- package/content/skills/tasks.md +24 -0
- package/content/skills/update.md +58 -0
- package/content/skills/verify.md +29 -0
- package/content/templates/adr.template.md +51 -0
- package/content/templates/evolution-report.template.md +132 -0
- package/content/templates/feature-plan.template.md +84 -0
- package/content/templates/feature-research.template.md +56 -0
- package/content/templates/feature-spec.template.md +86 -0
- package/content/templates/feature-tasks.induced.template.md +91 -0
- package/content/templates/feature-tasks.template.md +82 -0
- package/content/templates/focus.template.md +30 -0
- package/content/templates/gotcha.template.md +31 -0
- package/content/templates/guardrail-runner.template.md +97 -0
- package/content/templates/guardrail.template.md +63 -0
- package/content/templates/learning.template.md +40 -0
- package/content/templates/procedure.template.md +47 -0
- package/content/templates/protocols/p1.md +29 -0
- package/content/templates/protocols/p2.md +28 -0
- package/content/templates/protocols/p3.md +47 -0
- package/content/templates/research.template.md +50 -0
- package/content/templates/session-handoff.template.md +58 -0
- package/content/templates/us.template.md +39 -0
- package/content/tools/gate/core-rules/PLUGIN-13-secrets.sh +36 -0
- package/content/tools/gate/core-rules/PLUGIN-21-code-language.sh +27 -0
- package/content/tools/gate/core-rules/PLUGIN-35-gate-complete.sh +19 -0
- package/content/tools/gate/core-rules/PLUGIN-36-absolute-path.sh +38 -0
- package/content/tools/gate/core-rules/PLUGIN-37-prose-language.sh +56 -0
- package/content/tools/gate/core-rules/PLUGIN-38-parser-marker.sh +82 -0
- package/content/tools/gate/core-rules/PLUGIN-39-frontmatter-ops.sh +325 -0
- package/content/tools/gate/core-rules/PLUGIN-40-dead-reference.sh +74 -0
- package/content/tools/gate/core-rules/README.md +55 -0
- package/content/tools/gate/guardrails.sh +740 -0
- package/content/tools/gate/lib/ratchet.sh +119 -0
- package/content/tools/gate/lib/scope.sh +85 -0
- package/content/tools/gate/ratchet.seed.tsv +1 -0
- package/content/tools/gate/rules/README.md +50 -0
- package/package.json +19 -5
- package/plugin/agy/hooks.json +29 -0
- package/plugin/agy/mcp_config.json +7 -0
- package/plugin/agy/plugin.json +6 -0
- package/plugin/agy/skills/afm-adr/SKILL.md +48 -0
- package/plugin/agy/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/agy/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/agy/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/agy/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/agy/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/agy/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/agy/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/agy/skills/afm-init/SKILL.md +81 -0
- package/plugin/agy/skills/afm-obligations/SKILL.md +31 -0
- package/plugin/agy/skills/afm-plan/SKILL.md +34 -0
- package/plugin/agy/skills/afm-policy/SKILL.md +29 -0
- package/plugin/agy/skills/afm-promote/SKILL.md +43 -0
- package/plugin/agy/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/agy/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/agy/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/agy/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/agy/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/agy/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/agy/skills/afm-rule/SKILL.md +49 -0
- package/plugin/agy/skills/afm-specify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/agy/skills/afm-update/SKILL.md +64 -0
- package/plugin/agy/skills/afm-verify/SKILL.md +35 -0
- package/plugin/claude-code/.claude-plugin/marketplace.json +15 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +9 -0
- package/plugin/claude-code/.mcp.json +7 -0
- package/plugin/claude-code/agents/afm-executor.md +18 -0
- package/plugin/claude-code/agents/afm-explorer.md +20 -0
- package/plugin/claude-code/agents/afm-reviewer.md +21 -0
- package/plugin/claude-code/hooks/hooks.json +85 -0
- package/plugin/claude-code/skills/adr/SKILL.md +48 -0
- package/plugin/claude-code/skills/analyze/SKILL.md +34 -0
- package/plugin/claude-code/skills/clarify/SKILL.md +34 -0
- package/plugin/claude-code/skills/deliver/SKILL.md +30 -0
- package/plugin/claude-code/skills/diagnose/SKILL.md +46 -0
- package/plugin/claude-code/skills/evolve/SKILL.md +49 -0
- package/plugin/claude-code/skills/export-template/SKILL.md +43 -0
- package/plugin/claude-code/skills/generalize/SKILL.md +37 -0
- package/plugin/claude-code/skills/gotcha/SKILL.md +46 -0
- package/plugin/claude-code/skills/init/SKILL.md +81 -0
- package/plugin/claude-code/skills/obligations/SKILL.md +31 -0
- package/plugin/claude-code/skills/plan/SKILL.md +34 -0
- package/plugin/claude-code/skills/policy/SKILL.md +29 -0
- package/plugin/claude-code/skills/promote/SKILL.md +43 -0
- package/plugin/claude-code/skills/protocol/SKILL.md +45 -0
- package/plugin/claude-code/skills/reconcile/SKILL.md +49 -0
- package/plugin/claude-code/skills/refactor/SKILL.md +83 -0
- package/plugin/claude-code/skills/reflect/SKILL.md +49 -0
- package/plugin/claude-code/skills/remediate/SKILL.md +39 -0
- package/plugin/claude-code/skills/restructure/SKILL.md +52 -0
- package/plugin/claude-code/skills/rule/SKILL.md +49 -0
- package/plugin/claude-code/skills/specify/SKILL.md +34 -0
- package/plugin/claude-code/skills/tasks/SKILL.md +28 -0
- package/plugin/claude-code/skills/update/SKILL.md +64 -0
- package/plugin/claude-code/skills/verify/SKILL.md +35 -0
- package/plugin/opencode/afm/index.js +245 -0
- package/plugin/opencode/afm/tui.js +21 -0
- package/plugin/opencode/agents/afm-executor.md +39 -0
- package/plugin/opencode/agents/afm-explorer.md +92 -0
- package/plugin/opencode/agents/afm-reviewer.md +93 -0
- package/plugin/opencode/skills/afm-adr/SKILL.md +48 -0
- package/plugin/opencode/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/opencode/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/opencode/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-init/SKILL.md +81 -0
- package/plugin/opencode/skills/afm-obligations/SKILL.md +31 -0
- package/plugin/opencode/skills/afm-plan/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-policy/SKILL.md +29 -0
- package/plugin/opencode/skills/afm-promote/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/opencode/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/opencode/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/opencode/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/opencode/skills/afm-rule/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-specify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/opencode/skills/afm-update/SKILL.md +64 -0
- package/plugin/opencode/skills/afm-verify/SKILL.md +35 -0
- package/README.md +0 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Decides the shape of an export name — prefix or suffix, and action-first or entity-first.'
|
|
4
|
+
load: cold
|
|
5
|
+
concern: naming
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Code style — Export naming
|
|
9
|
+
|
|
10
|
+
## The decision
|
|
11
|
+
|
|
12
|
+
This is not about which word to use — it is about the SHAPE the decision takes. Two independent
|
|
13
|
+
axes decide the shape of an exported name, and a codebase needs an answer on each:
|
|
14
|
+
|
|
15
|
+
1. **Where does the layer tag sit** — as a prefix (`domain_createUser`), as a suffix
|
|
16
|
+
(`createUserDomain`), or nowhere in the name at all, left to the folder the file lives in?
|
|
17
|
+
2. **What comes first inside the name** — the action (`createUser`) or the entity
|
|
18
|
+
(`userCreate`)?
|
|
19
|
+
|
|
20
|
+
## Axis 1 — Prefix, suffix, or no tag
|
|
21
|
+
|
|
22
|
+
- **Prefix** (`domain_<action><Entity>`): sorted alphabetically, every export in a layer clusters
|
|
23
|
+
together — `grep '^export const domain_'` finds the whole layer's public surface in one
|
|
24
|
+
command, independent of file location. **Cost:** the identifier now carries information the
|
|
25
|
+
file's own path already carries, and the two can drift — a file that moves to a different layer
|
|
26
|
+
keeps a prefix that no longer describes it until someone renames the export too.
|
|
27
|
+
- **Suffix** (`<action><Entity>Domain`): reads as a noun phrase — "createUserDomain" parses as
|
|
28
|
+
"the Domain [operation] for creating a User" the way English adjectives-after-noun sometimes
|
|
29
|
+
read more naturally in code completion, where the base name (`createUser`) still autocompletes
|
|
30
|
+
first, and the layer qualifier appears as a refinement. **Cost:** same drift risk as a prefix,
|
|
31
|
+
and alphabetical clustering by layer is lost — sorting now clusters by action or entity instead.
|
|
32
|
+
- **No tag, layer signaled by path alone**: the shortest name, and the file's directory is the
|
|
33
|
+
single source of truth for its layer, so there is nothing in the identifier that can drift out
|
|
34
|
+
of sync with reality. **Cost:** nothing greppable-by-name marks the boundary. Enforcing "domain
|
|
35
|
+
code never imports the transport layer" now needs a path-based rule instead of a name-based one
|
|
36
|
+
— which is not necessarily worse, but it is a different mechanism, and re-exports through a
|
|
37
|
+
barrel file can make the path itself ambiguous.
|
|
38
|
+
|
|
39
|
+
## Axis 2 — Action-first or entity-first
|
|
40
|
+
|
|
41
|
+
- **Action-first** (`createUser`, `deleteUser`, `createOrder`): matches how the code reads at the
|
|
42
|
+
call site — a verb phrase. Typing the action ("create…") surfaces every creator across every
|
|
43
|
+
entity, which is how "what CRUD operations exist" gets scanned.
|
|
44
|
+
- **Entity-first** (`userCreate`, `userDelete`, `orderCreate`): typing the entity ("user…")
|
|
45
|
+
surfaces every operation on that one entity, which is how "what can I do to a User" gets
|
|
46
|
+
scanned. This is the axis that matters more when most work is "go deep on one entity" rather
|
|
47
|
+
than "go wide across one action."
|
|
48
|
+
|
|
49
|
+
Neither axis has a universally right answer — the right answer is whichever one matches how this
|
|
50
|
+
team actually searches its own codebase more often, which the measurement below settles instead
|
|
51
|
+
of guessing.
|
|
52
|
+
|
|
53
|
+
## How to measure which shape this codebase already follows
|
|
54
|
+
|
|
55
|
+
Look at the exports that already exist before proposing a shape:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
grep -rhoE '^export (async )?function [a-zA-Z_]+' src \
|
|
59
|
+
| sed 's/.* //' | sort | uniq -c | sort -rn | head -20
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Read the output for a dominant prefix (`^[a-z]+_`), a dominant suffix, or neither. If a majority
|
|
63
|
+
already follows one shape, propose codifying it — do not introduce a third shape into a codebase
|
|
64
|
+
that has already, even informally, converged on one.
|
|
65
|
+
|
|
66
|
+
## Consequence of mixing both
|
|
67
|
+
|
|
68
|
+
A codebase with `domain_createUser` next to `createOrderDomain` next to `handleWebhook` (no tag at
|
|
69
|
+
all) cannot be greped for "every domain export" by any single pattern — the search has to know,
|
|
70
|
+
file by file, which shape that file happened to use. The mechanical boundary check that a
|
|
71
|
+
consistent shape would give for free (one `grep`, one hard rule) degrades into a manual read of
|
|
72
|
+
every file, which is the exact cost a naming convention exists to avoid.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Decides where a test file lives relative to the source file it tests.'
|
|
4
|
+
load: cold
|
|
5
|
+
concern: test-placement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Code style — Test placement
|
|
9
|
+
|
|
10
|
+
## The decision
|
|
11
|
+
|
|
12
|
+
A test file needs a fixed address relative to the source it tests. Two shapes answer that, and a
|
|
13
|
+
codebase drifts the moment both are in use at once.
|
|
14
|
+
|
|
15
|
+
## Option A — Neighbor file
|
|
16
|
+
|
|
17
|
+
`<name>.test.ts` sits in the same directory as `<name>.ts`.
|
|
18
|
+
|
|
19
|
+
- **For:** the test is one file-tree hop from the code, so opening one strongly suggests the
|
|
20
|
+
other. Deleting or moving the source and forgetting the test is rare — the two sit side by side
|
|
21
|
+
in every directory listing and every diff.
|
|
22
|
+
- **Against:** a directory that holds ten source files now holds twenty entries. A quick `ls` of
|
|
23
|
+
"what does this module do" is diluted by an equal count of test files.
|
|
24
|
+
|
|
25
|
+
## Option B — A `__tests__/` directory
|
|
26
|
+
|
|
27
|
+
Tests for a module collect under a sibling `__tests__/` folder instead of sitting next to the
|
|
28
|
+
source.
|
|
29
|
+
|
|
30
|
+
- **For:** the source directory stays a pure listing of what the module does. Tooling that treats
|
|
31
|
+
"test" as a separate concern (a coverage report, an editor's file-tree filter) has one folder to
|
|
32
|
+
point at instead of a naming pattern to match.
|
|
33
|
+
- **Against:** the link between a source file and its test is now a path convention, not
|
|
34
|
+
proximity. Renaming `foo.ts` to `bar.ts` and forgetting `__tests__/foo.test.ts` produces an
|
|
35
|
+
orphaned test that nothing flags — the file still runs, just against code that no longer exists
|
|
36
|
+
under that name.
|
|
37
|
+
|
|
38
|
+
## How to measure which one this codebase already follows
|
|
39
|
+
|
|
40
|
+
Count both shapes before proposing either one — the codebase has usually already chosen:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
find src -name "*.test.*" -o -name "*.spec.*" | grep -v '__tests__' | wc -l # neighbor count
|
|
44
|
+
find src -type d -name "__tests__" | wc -l # __tests__ dirs present
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A clear majority on one side settles it. Propose codifying what already dominates, not the
|
|
48
|
+
option either of you prefers in the abstract — a rule that fights the existing grain gets
|
|
49
|
+
routed around at the first deadline.
|
|
50
|
+
|
|
51
|
+
## Consequence of mixing both
|
|
52
|
+
|
|
53
|
+
A codebase with both shapes present can't grep its way to "did this file get a test" — the search
|
|
54
|
+
has to check two locations, and a script that checks only one silently under-reports coverage.
|
|
55
|
+
Worse, a reviewer scanning for the test of a changed file checks the neighbor first, finds
|
|
56
|
+
nothing, and assumes untested when the test is one level away in `__tests__/`. Pick one, and let
|
|
57
|
+
the measurement above decide which one costs less to enforce.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes a rule that a route handler stays thin and delegates logic to a server/ layer, instead of embedding business rules in the App Router route file.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: nextjs-app-router
|
|
6
|
+
concern: imports
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("next")' package.json >/dev/null 2>&1; else grep -q '"next"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Next.js route handlers delegate to server/
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Next.js. Next.js has no opinion on whether a
|
|
14
|
+
> `route.ts` file contains business logic or calls out to another layer — this is a
|
|
15
|
+
> layer-direction choice, the same kind covered generally in `code-style/_any/imports.md`.
|
|
16
|
+
> Materialize it into `docs/ach.md` and `docs/afm.md` §3 only if the operator accepts it. Nothing
|
|
17
|
+
> here is `(mandatory)`.
|
|
18
|
+
|
|
19
|
+
## The convention
|
|
20
|
+
|
|
21
|
+
- A route handler under `src/app/api/.../route.ts` validates input, calls into a `server/` layer,
|
|
22
|
+
and returns the response. It contains no inline business rule.
|
|
23
|
+
- The `server/` layer (or whatever a project already calls its business/domain code) owns the
|
|
24
|
+
actual logic; the route file is a thin adapter between the HTTP boundary and that layer.
|
|
25
|
+
|
|
26
|
+
## Why this works
|
|
27
|
+
|
|
28
|
+
A route handler that only adapts input to a call and a result to a response stays small enough to
|
|
29
|
+
read in one pass, and the same business logic becomes reachable from a second entry point (a cron
|
|
30
|
+
job, a CLI, another route) without duplicating it. Keeping the rule qualitative — "delegate, don't
|
|
31
|
+
embed" — rather than a line-count threshold avoids the trap the line-count version of this rule
|
|
32
|
+
fell into: a fixed number with no measurement behind it. A project that wants a mechanical
|
|
33
|
+
backstop for file size can add one from `code-style/_any/file-size.md` instead, with a threshold
|
|
34
|
+
it actually measured against its own files.
|
|
35
|
+
|
|
36
|
+
## The trade-off
|
|
37
|
+
|
|
38
|
+
"Delegate to server/" assumes a project already has, or wants, a server/domain layer distinct from
|
|
39
|
+
the route file — a small project with three routes and no other consumer of that logic gets pure
|
|
40
|
+
indirection for no reuse benefit. And the rule itself is not mechanically checkable: nothing greps
|
|
41
|
+
"this route file contains a business rule" reliably, so it stays a review-time principle unless a
|
|
42
|
+
project also has file-size or complexity tooling that catches a route file growing past what
|
|
43
|
+
adapting alone should need.
|
|
44
|
+
|
|
45
|
+
## If the operator accepts this
|
|
46
|
+
|
|
47
|
+
Materialize it into `docs/ach.md` §6 as a §1.3 principle (review, not a grep trigger) — there is
|
|
48
|
+
no mechanical check for "no inline business rule." A project that also wants a numeric backstop
|
|
49
|
+
should adopt `code-style/_any/file-size.md` separately, with a threshold measured against its own
|
|
50
|
+
route files, not an imported constant.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes rooting the App Router at src/app/, a src/components/ location for UI primitives, and a naming convention for route groups.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: nextjs-app-router
|
|
6
|
+
concern: naming
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("next")' package.json >/dev/null 2>&1; else grep -q '"next"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Next.js App Router folder naming
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Next.js. Next.js accepts the App Router rooted at
|
|
14
|
+
> `app/` **or** `src/app/` — either works with no config. Where UI primitives live and how a route
|
|
15
|
+
> group is named are house choices on top of that, not a framework requirement. Materialize any
|
|
16
|
+
> part of this into `docs/ach.md` and `docs/afm.md` §3 only if the operator accepts it — item by
|
|
17
|
+
> item, at bootstrap. Nothing here is `(mandatory)`.
|
|
18
|
+
|
|
19
|
+
## The convention
|
|
20
|
+
|
|
21
|
+
- Root the App Router at `src/app/`, not the repo-root `app/` — keeps every source file under one
|
|
22
|
+
`src/` tree, alongside `src/lib/`, `src/server/`, and the like.
|
|
23
|
+
- Put UI primitives (shadcn-style components, feature components) in `src/components/`. No direct
|
|
24
|
+
fetch — data arrives through the project's transport client, never a component-level `fetch`.
|
|
25
|
+
- Group authenticated routes under a named route group, for example `(protected)/`, and
|
|
26
|
+
unauthenticated ones under a plain folder, for example `auth/` — so the URL path and the auth
|
|
27
|
+
boundary read the same way in the file tree.
|
|
28
|
+
|
|
29
|
+
## Why this works
|
|
30
|
+
|
|
31
|
+
`src/` as a single root means a contributor never has to ask "is this file at the repo root or
|
|
32
|
+
under `src/`" — there is one answer, and tooling (a `tsconfig.json` `baseUrl`, an ESLint
|
|
33
|
+
`import/no-relative-parent-imports` rule) can assume it. A named route group for the auth boundary
|
|
34
|
+
makes the split visible in a file listing, before opening any file.
|
|
35
|
+
|
|
36
|
+
## The trade-off
|
|
37
|
+
|
|
38
|
+
Next.js's own default is `app/` at the repo root, with no `src/` at all — a project that starts
|
|
39
|
+
there and later adds a `src/` tree for everything else pays a one-time move, and any doc or
|
|
40
|
+
tutorial written against the plain-`app/` default needs a mental translation. Route-group naming
|
|
41
|
+
is invisible to the URL (Next.js strips the parens), so it only pays off if the team actually
|
|
42
|
+
reads folder names during review; a team that navigates by IDE search alone gets no benefit from
|
|
43
|
+
the convention and only the extra folder nesting.
|
|
44
|
+
|
|
45
|
+
## If the operator accepts this
|
|
46
|
+
|
|
47
|
+
Materialize the tree into `docs/ach.md` §2 and the route-group convention into §6. There is no
|
|
48
|
+
mechanical check for "is this the right root" — Next.js accepts either — so this stays a §1.3
|
|
49
|
+
principle, not a §3 hard rule, unless the project also adds a lint rule that fails on the
|
|
50
|
+
unwanted root.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes a soft-delete convention — a deletedAt column, a filter-by-default query rule, and a scheduled hard-delete job — as one boundary a query must respect, not a Prisma requirement.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: prisma-postgres
|
|
6
|
+
concern: error-boundary
|
|
7
|
+
detect: |
|
|
8
|
+
test -f prisma/schema.prisma
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Prisma + Postgres soft-delete boundary
|
|
12
|
+
|
|
13
|
+
> Soft delete is a **design choice**, not a Prisma fact — Prisma has no opinion on whether a row
|
|
14
|
+
> ever leaves the table. This is a proposal for one way to implement it, framed as a boundary a
|
|
15
|
+
> query must respect: a soft-deleted row must not cross from storage into application code unless
|
|
16
|
+
> the caller explicitly asked for it. Materialize it into `docs/ach.md` and `docs/afm.md` §3 only
|
|
17
|
+
> if the operator accepts it. Nothing here is `(mandatory)`.
|
|
18
|
+
|
|
19
|
+
## The convention
|
|
20
|
+
|
|
21
|
+
- Add `deletedAt: DateTime?` to a model that needs soft delete.
|
|
22
|
+
- Mark the retention SLA with a schema comment next to the column, for example
|
|
23
|
+
`// hard-delete after 180d`.
|
|
24
|
+
- Every query against a soft-deletable model filters `deletedAt: null` by default. An admin or
|
|
25
|
+
audit path may opt out explicitly — never silently.
|
|
26
|
+
- A scheduled job performs the actual hard delete once the SLA elapses, separate from the request
|
|
27
|
+
path that sets `deletedAt`.
|
|
28
|
+
|
|
29
|
+
## Why this works
|
|
30
|
+
|
|
31
|
+
Without a default filter, a soft-deleted row is indistinguishable from a live one to every query
|
|
32
|
+
that does not remember to exclude it — and that memory is exactly what a hard rule replaces. Two
|
|
33
|
+
grep-able boundaries make it mechanical: which models declare `deletedAt` at all, and whether
|
|
34
|
+
every read against them filters it.
|
|
35
|
+
|
|
36
|
+
## The trade-off
|
|
37
|
+
|
|
38
|
+
A default filter on every query is easy to forget for a *new* soft-deletable model — the check
|
|
39
|
+
below only fires once a model already has `deletedAt`, so nothing enforces adding the column in
|
|
40
|
+
the first place. It also adds a `WHERE` clause and often a partial index to every query against
|
|
41
|
+
that model, for a feature many projects never use (a hard delete plus an audit-log row is simpler
|
|
42
|
+
when no product requirement asks for undo or a retention window). A project without that
|
|
43
|
+
requirement should skip this entirely rather than add the column speculatively.
|
|
44
|
+
|
|
45
|
+
## If the operator accepts this
|
|
46
|
+
|
|
47
|
+
Materialize the convention into `docs/ach.md` §6 and add a hard rule to `docs/afm.md` §3, verified
|
|
48
|
+
by:
|
|
49
|
+
```sh
|
|
50
|
+
grep -rn "deletedAt" {{src-path}}/server/modules/
|
|
51
|
+
```
|
|
52
|
+
Every match is a query against a soft-deletable model; each one either filters `deletedAt: null`
|
|
53
|
+
or is an admin/audit path that says so in a comment. List the models that actually declare
|
|
54
|
+
`deletedAt` first (`grep -n "deletedAt" prisma/schema.prisma` or the project's schema files) —
|
|
55
|
+
the check only means something once at least one model has the column.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes a schema-file split, a generated-client output path, and three layer-direction import rules for a Prisma + Postgres project.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: prisma-postgres
|
|
6
|
+
concern: imports
|
|
7
|
+
detect: |
|
|
8
|
+
test -f prisma/schema.prisma
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Prisma + Postgres schema layout and import direction
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Prisma. Prisma itself only requires one
|
|
14
|
+
> `schema.prisma` file that a `generator client` block points at some output path — where split
|
|
15
|
+
> files live, what the output folder is called, and which layer may import the generated client
|
|
16
|
+
> directly are all house choices. Materialize any part of this into `docs/ach.md` and
|
|
17
|
+
> `docs/afm.md` §3 only if the operator accepts it — item by item, at bootstrap. Nothing here is
|
|
18
|
+
> `(mandatory)`.
|
|
19
|
+
|
|
20
|
+
## The convention
|
|
21
|
+
|
|
22
|
+
**Schema layout**
|
|
23
|
+
- Split the schema by domain: `prisma/schemas/<module>.prisma`, one file per domain, merged by a
|
|
24
|
+
`prisma.config.ts` (Prisma 5.15+ multi-file schema).
|
|
25
|
+
- Output the generated client to `.prisma/` at the repo root (gitignored). Reference it with a
|
|
26
|
+
relative import: `../../.prisma/client`, `../../.prisma/enums`.
|
|
27
|
+
|
|
28
|
+
**Import direction**
|
|
29
|
+
- `{{src-path}}/server/modules/<f>/domain/*` may import types from `.prisma/enums`. It does not
|
|
30
|
+
import the Prisma client directly — it receives `db` through `ctx`.
|
|
31
|
+
- `{{src-path}}/lib/*` never imports `.prisma/*` — `lib/` is publishable outside this project, and
|
|
32
|
+
a generated-client import would tie it back to this schema.
|
|
33
|
+
- `{{src-path}}/trigger/*` may import `.prisma/*` through a context-constructor helper, if the
|
|
34
|
+
project has one.
|
|
35
|
+
|
|
36
|
+
## Why this works
|
|
37
|
+
|
|
38
|
+
A fixed schema-split location and a single generated-client path give every contributor the same
|
|
39
|
+
two answers ("where do I add a model", "where does the client come from") without asking. The
|
|
40
|
+
import-direction rules turn "domain code should not couple to Prisma's client type, and `lib/`
|
|
41
|
+
should stay portable" from a review-time reminder into something a hard rule can check by path.
|
|
42
|
+
|
|
43
|
+
## The trade-off
|
|
44
|
+
|
|
45
|
+
None of this is required by Prisma itself. A project with a single-schema file, a different
|
|
46
|
+
output path (Prisma's own default is `node_modules/.prisma/client`), or no `lib/`/`trigger/`
|
|
47
|
+
split at all gets nothing from importing this layout — the value is internal consistency, not a
|
|
48
|
+
Prisma constraint. The `db`-through-`ctx` rule also asks every domain function to accept a `ctx`
|
|
49
|
+
argument even where it feels like ceremony for a one-line query; a smaller project may prefer the
|
|
50
|
+
simplicity of importing the client directly and accept the harder-to-mock trade-off instead.
|
|
51
|
+
|
|
52
|
+
## If the operator accepts this
|
|
53
|
+
|
|
54
|
+
Materialize the layout into `docs/ach.md` §2 (the folder tree) and the import rules into
|
|
55
|
+
`docs/afm.md` §3, verified per rule by:
|
|
56
|
+
```sh
|
|
57
|
+
grep -rnE "from.*prisma/client|new PrismaClient" {{src-path}}/server/modules/*/domain/
|
|
58
|
+
grep -rn "\.prisma/" {{src-path}}/lib/
|
|
59
|
+
```
|
|
60
|
+
Both must return 0 for the rule to hold; fill `{{src-path}}` with the project's actual root
|
|
61
|
+
before pasting either grep — an unresolved placeholder matches nothing and passes vacuously.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes separate directories for copied shadcn primitives and for feature components that compose them.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: shadcn-ui
|
|
6
|
+
concern: naming
|
|
7
|
+
detect: |
|
|
8
|
+
test -f components.json
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — shadcn primitive vs. feature component layout
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about shadcn. Materialize it into `docs/ach.md` only if the
|
|
14
|
+
> operator accepts it — item by item, at bootstrap. Nothing here is `(mandatory)`.
|
|
15
|
+
|
|
16
|
+
## The convention
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
components/
|
|
21
|
+
ui/ # shadcn primitives — CLI-copied, then edited in place
|
|
22
|
+
<feature>/ # components that compose primitives with feature logic
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`components/ui/` holds only what `npx shadcn add` copied in, plus your in-place edits to it.
|
|
26
|
+
Anything that combines a primitive with data, feature logic, or app-specific composition goes in
|
|
27
|
+
its own `components/<feature>/` directory instead.
|
|
28
|
+
|
|
29
|
+
## Why this works
|
|
30
|
+
|
|
31
|
+
Keeping the copied primitives in one directory makes it obvious, at a glance, which files are
|
|
32
|
+
"vendored" shadcn code you have edited versus app code written from scratch — useful the day you
|
|
33
|
+
want to diff a primitive against an upstream shadcn update.
|
|
34
|
+
|
|
35
|
+
## The trade-off
|
|
36
|
+
|
|
37
|
+
A project with only a handful of components total does not need the split yet — one flat
|
|
38
|
+
`components/` directory is simpler until the primitive/feature distinction starts to matter. A
|
|
39
|
+
design system that already dictates its own component directory convention (the `vireya-ui`
|
|
40
|
+
module, for one) should not have this layered on top of it — the two conventions are for
|
|
41
|
+
different stacks and are not meant to combine.
|
|
42
|
+
|
|
43
|
+
## If the operator accepts this
|
|
44
|
+
|
|
45
|
+
Materialize the directory split into `docs/ach.md`, under the UI primitive component type. This
|
|
46
|
+
convention has no mechanical check of its own — `components.json`'s `aliases.ui` path is the
|
|
47
|
+
only part shadcn itself enforces, and nothing distinguishes a "feature" component from a
|
|
48
|
+
misplaced primitive except review.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes a file layout and naming scheme for a Stripe billing module: one way to organize it, not a Stripe requirement.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: stripe-billing
|
|
6
|
+
concern: naming
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("stripe")' package.json >/dev/null 2>&1; else grep -q '"stripe"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Stripe billing naming and layout
|
|
12
|
+
|
|
13
|
+
> Seed for `docs/code-style/stripe-billing/naming.md`. A proposal, materialized only if the
|
|
14
|
+
> operator accepts it during the interview. Stripe imposes none of this; it is one way to
|
|
15
|
+
> organize a multi-plan billing module, offered so a project does not have to invent the layout
|
|
16
|
+
> from zero.
|
|
17
|
+
|
|
18
|
+
## Proposal: an SDK-wrapper directory with a prefixed export name
|
|
19
|
+
|
|
20
|
+
- Put every direct call into the Stripe SDK behind a thin wrapper, in its own directory (for
|
|
21
|
+
example `src/lib/stripe/`), so a reader can tell at a glance which functions cross the network
|
|
22
|
+
and which are local logic.
|
|
23
|
+
- Name every exported wrapper function with the same lowercase prefix (for example
|
|
24
|
+
`stripe_createCustomer`, `stripe_verifyWebhook`), so a call site reads as "this touches Stripe"
|
|
25
|
+
without opening the import.
|
|
26
|
+
- Export one client-getter function (for example `stripe_getClient()`) instead of a module-level
|
|
27
|
+
singleton instance. A getter is easier to mock in a test and easier to reconfigure per request
|
|
28
|
+
(multi-tenant API keys, test mode) than a value captured at import time.
|
|
29
|
+
|
|
30
|
+
**Trade-off.** The prefix is redundant once the directory name already says "stripe" — a project
|
|
31
|
+
that values shorter names over grep-ability may skip it and rely on the import path alone. The
|
|
32
|
+
getter-over-singleton choice costs one extra function call at every use site; a project with a
|
|
33
|
+
single, static API key and no test doubles to satisfy may prefer the plain singleton.
|
|
34
|
+
|
|
35
|
+
## Proposal: a dedicated billing domain with a capability matrix
|
|
36
|
+
|
|
37
|
+
For a subscription product with more than one paid plan, keep the mapping from plan to
|
|
38
|
+
capabilities in one place instead of scattering `if (plan === 'pro')` checks through the
|
|
39
|
+
codebase:
|
|
40
|
+
|
|
41
|
+
- A **plan-to-price config**: one constant per paid plan (lookup key, amount, interval), plus a
|
|
42
|
+
map from the app's own plan identifier to that config, and the reverse lookup (a Stripe Price
|
|
43
|
+
lookup key back to the app's plan identifier) that a webhook handler needs.
|
|
44
|
+
- A **capability matrix**: one file that is the single source of truth for which capability each
|
|
45
|
+
plan unlocks — a table or map from plan to a set of boolean flags, one flag per gated feature.
|
|
46
|
+
- A **billing-state resolver**: a pure function from the current subscription (status, plan) to
|
|
47
|
+
the resolved capability flags. A subscription in good standing (active, trialing, or a
|
|
48
|
+
grace-period past-due) carries its plan's capabilities; a suspended subscription (unpaid,
|
|
49
|
+
incomplete, paused) drops to the free tier's capabilities even while the stored plan label
|
|
50
|
+
still shows what the customer was nominally on, so an account page can say "you were on the
|
|
51
|
+
Pro plan" instead of silently forgetting it.
|
|
52
|
+
- A **webhook dispatcher** that resolves the plan from the event's Price lookup key and calls the
|
|
53
|
+
resolver.
|
|
54
|
+
|
|
55
|
+
**Trade-off.** This earns the indirection once there are two or more paid plans with different
|
|
56
|
+
capabilities — below that, a single `isPaid: boolean` flag is simpler, and the matrix is YAGNI.
|
|
57
|
+
The four-file split also assumes a `domain/` layer already exists in this codebase's module
|
|
58
|
+
convention; a project without one should collapse it to fewer files rather than manufacture the
|
|
59
|
+
layer just to hold this pattern.
|
|
60
|
+
|
|
61
|
+
## Proposal: gate every capability check through one accessor shape, server-side
|
|
62
|
+
|
|
63
|
+
Expose the resolved capability flags on the request context under one dot-path (for example
|
|
64
|
+
`ctx.user.billing.canX`), and have every procedure that performs a gated action check that flag
|
|
65
|
+
before it runs — never only in the frontend, since a client-side check is UX, not a boundary an
|
|
66
|
+
attacker cannot cross.
|
|
67
|
+
|
|
68
|
+
*Suggested verification, once a project adopts this shape (point the glob at where this
|
|
69
|
+
project's procedures actually live):* `grep -rnE "billing\.can" <procedures-dir>` should show
|
|
70
|
+
every gated procedure checking the flag before it acts.
|
|
71
|
+
|
|
72
|
+
**Trade-off.** A single dot-path accessor is convenient once many procedures gate on it, but it
|
|
73
|
+
couples every caller to that exact context shape. A project that already has its own
|
|
74
|
+
authorization layer (a policy object, a decorator, a middleware) should express the same
|
|
75
|
+
"check server-side, never trust the client" principle through its own shape instead of adopting
|
|
76
|
+
this one.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes an import boundary for task files and a shared context helper for their DB access.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
concern: imports
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — task import boundary and context helper
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Trigger.dev. Materialize it into `docs/ach.md` and
|
|
14
|
+
> `docs/afm.md` §3 only if the operator accepts it — item by item, at bootstrap. Nothing here is
|
|
15
|
+
> `(mandatory)`.
|
|
16
|
+
|
|
17
|
+
## The convention
|
|
18
|
+
|
|
19
|
+
A task file may import `lib/`, `domain/`, and the ORM client, but not the request-layer
|
|
20
|
+
procedures (tRPC routers, REST handlers, and so on) — a task is triggered, not called, so
|
|
21
|
+
importing a procedure the same way an HTTP handler would defeats the boundary the task runs
|
|
22
|
+
behind.
|
|
23
|
+
|
|
24
|
+
To avoid every task constructing its own DB client by hand, route the ones that need one through
|
|
25
|
+
a single helper that wraps the ORM singleton, for example:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// src/lib/trigger.ts
|
|
29
|
+
export function createTriggerContext() {
|
|
30
|
+
return { db: prismaClient /* , ...whatever else every task needs */ };
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Why this works
|
|
35
|
+
|
|
36
|
+
One place constructs the client, so pool sizing and connection reuse are decided once instead of
|
|
37
|
+
once per task file. It also gives every task the same starting shape, which lowers the cost of
|
|
38
|
+
reading the next one.
|
|
39
|
+
|
|
40
|
+
## The trade-off
|
|
41
|
+
|
|
42
|
+
A single task, or a set of tasks with no DB access at all, does not need a shared helper — a
|
|
43
|
+
direct import is simpler, and the helper would be a speculative abstraction (YAGNI). A project
|
|
44
|
+
that already has a context-building convention for its request layer gains nothing from a second,
|
|
45
|
+
task-only version — reuse the existing one instead. The import boundary itself is also only as
|
|
46
|
+
good as the discipline behind it: nothing in Trigger.dev enforces it, so a project that adopts it
|
|
47
|
+
needs a mechanical check, not just a convention nobody reads.
|
|
48
|
+
|
|
49
|
+
## If the operator accepts this
|
|
50
|
+
|
|
51
|
+
- Materialize the import boundary and the context-helper pattern into `docs/ach.md`, under the
|
|
52
|
+
Task component type.
|
|
53
|
+
- Add a hard rule to `docs/afm.md` §3, verified by (point the glob at where this project's tasks
|
|
54
|
+
actually live):
|
|
55
|
+
```sh
|
|
56
|
+
grep -rnE "prismaClient|new PrismaClient" {{src-path}}/trigger/
|
|
57
|
+
```
|
|
58
|
+
Zero results required, except the file that **defines** the context helper.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes a directory tree and file-naming pattern for Trigger.dev v4 tasks.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
concern: naming
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Trigger.dev v4 task layout
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Trigger.dev. Materialize it into `docs/ach.md` only if
|
|
14
|
+
> the operator accepts it — item by item, at bootstrap. Nothing here is `(mandatory)`.
|
|
15
|
+
|
|
16
|
+
## The convention
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
trigger/ # Trigger.dev v4 tasks
|
|
21
|
+
cron-<X>.ts # cron / schedules
|
|
22
|
+
<stage>-<entity>.ts # subtasks
|
|
23
|
+
start-<entity>-workflow.ts # orchestrators
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
One directory, one file per task, and the file name states the task's role (`cron-`,
|
|
27
|
+
`start-…-workflow`, or a bare `<stage>-<entity>`) before you open it.
|
|
28
|
+
|
|
29
|
+
## Why this works
|
|
30
|
+
|
|
31
|
+
Grepping `src/trigger/` lists every task the project runs, in one place, sorted by what kind
|
|
32
|
+
each one is — no need to open a file to learn whether it is a cron, an orchestrator, or a
|
|
33
|
+
subtask.
|
|
34
|
+
|
|
35
|
+
## The trade-off
|
|
36
|
+
|
|
37
|
+
A monorepo that already splits tasks per package has its own layout, and forcing this tree onto
|
|
38
|
+
it adds a migration with no payoff. A project with only one or two tasks does not need a
|
|
39
|
+
dedicated subtree yet — inline them where they are triggered from, and adopt this once a third
|
|
40
|
+
task shows up (Rule of Three). The prefixes themselves are also redundant with the `id` passed to
|
|
41
|
+
`tasks.task(...)`, which already states the same thing at the call site.
|
|
42
|
+
|
|
43
|
+
## If the operator accepts this
|
|
44
|
+
|
|
45
|
+
Materialize the tree and the three file-name prefixes into `docs/ach.md`, under the Task
|
|
46
|
+
component type. This convention has no mechanical check of its own beyond the directory existing
|
|
47
|
+
— naming a task file correctly is check-able only by parsing each file's exported task `id`
|
|
48
|
+
against its filename, which duplicates the kebab-case convention already stated as fact in
|
|
49
|
+
`docs/ach.md`. Do not add a redundant hard rule for the prefixes alone.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: code-style
|
|
3
|
+
description: 'Proposes co-locating an idempotency test with each task, and an atomic-upsert pattern for the state it guards.'
|
|
4
|
+
load: cold
|
|
5
|
+
stack: trigger-dev-v4
|
|
6
|
+
concern: test-placement
|
|
7
|
+
detect: |
|
|
8
|
+
if command -v jq >/dev/null 2>&1; then jq -e '(.dependencies//{})+(.devDependencies//{})+(.peerDependencies//{})|has("@trigger.dev/sdk")' package.json >/dev/null 2>&1; else grep -q '"@trigger.dev/sdk"' package.json 2>/dev/null; fi
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code style — Trigger.dev v4 idempotency test placement
|
|
12
|
+
|
|
13
|
+
> This is a **proposal**, not a fact about Trigger.dev. Materialize it into `docs/ach.md` and
|
|
14
|
+
> `docs/afm.md` §3 only if the operator accepts it — item by item, at bootstrap. Nothing here is
|
|
15
|
+
> `(mandatory)`.
|
|
16
|
+
|
|
17
|
+
## The convention
|
|
18
|
+
|
|
19
|
+
**Neighbor test.** Every task a rerun could touch twice (a webhook retry, a `triggerAndWait`
|
|
20
|
+
failure that reruns its parent, a manual replay from the dashboard) gets a test co-located with
|
|
21
|
+
it — `<task>.test.ts` in the same directory as `<task>.ts` — asserting that a second run does not
|
|
22
|
+
corrupt state: an early return on a terminal state, or a no-op on an already-applied write.
|
|
23
|
+
|
|
24
|
+
**One way to make the write itself safe:** an atomic upsert guarded by the field that tracks the
|
|
25
|
+
run:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
await db.thing.updateMany({
|
|
29
|
+
where: { id, runId: null },
|
|
30
|
+
data: { runId },
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`runId` is written once; a rerun with the same `runId` finds no row where it is still `null`, so
|
|
35
|
+
it updates nothing.
|
|
36
|
+
|
|
37
|
+
## Why this works
|
|
38
|
+
|
|
39
|
+
A task that silently double-applies its write on a retry is a production incident, not a style
|
|
40
|
+
nit — Trigger.dev retries by design, so idempotency is the one thing every task must get right.
|
|
41
|
+
Testing it next to the task keeps the two in sync when either one changes.
|
|
42
|
+
|
|
43
|
+
## The trade-off
|
|
44
|
+
|
|
45
|
+
A task with no side effect a rerun could double-apply (a pure fan-out, a read-only report) does
|
|
46
|
+
not need this test — write one only where a second run has something to corrupt. A project whose
|
|
47
|
+
test layout is centralized instead of co-located should keep the idempotency test there too — the
|
|
48
|
+
co-location is the suggestion, the idempotency test itself is not. The `updateMany`/`runId`
|
|
49
|
+
pattern also assumes a DB row already exists to guard; a task whose idempotency depends on an
|
|
50
|
+
external system (an upstream API that is itself idempotent by request key) needs a different
|
|
51
|
+
guard entirely.
|
|
52
|
+
|
|
53
|
+
## If the operator accepts this
|
|
54
|
+
|
|
55
|
+
- Materialize the neighbor-test convention into `docs/ach.md`, under the Task component type.
|
|
56
|
+
- Add a hard rule to `docs/afm.md` §3, verified by (point the glob at where this project's tasks
|
|
57
|
+
actually live):
|
|
58
|
+
```sh
|
|
59
|
+
grep -rLE "rerun|retry|already.*processed" {{src-path}}/trigger/*.test.ts
|
|
60
|
+
```
|
|
61
|
+
Lists tasks with no idempotency test — review the list, it is not a pass/fail count.
|