jackherd 0.0.1 → 1.0.1
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/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/NOTICE +38 -0
- package/README.md +1122 -3
- package/assets/branding/README.md +62 -0
- package/assets/branding/jackherd-icon-256.png +0 -0
- package/assets/branding/jackherd-icon-512.png +0 -0
- package/assets/branding/jackherd-icon-64.png +0 -0
- package/assets/branding/jackherd-icon-small-32.png +0 -0
- package/assets/branding/jackherd-icon-small-64.png +0 -0
- package/assets/branding/jackherd-icon-small.svg +1 -0
- package/assets/branding/jackherd-icon.svg +1 -0
- package/assets/branding/jackherd-logo-1024.png +0 -0
- package/assets/branding/jackherd-logo-512.png +0 -0
- package/assets/branding/jackherd-logo-dark-1024.png +0 -0
- package/assets/branding/jackherd-logo-dark-512.png +0 -0
- package/assets/branding/jackherd-logo-dark.svg +1 -0
- package/assets/branding/jackherd-logo-mark-1024.png +0 -0
- package/assets/branding/jackherd-logo-mark-512.png +0 -0
- package/assets/branding/jackherd-logo-mark.svg +1 -0
- package/assets/branding/jackherd-logo.svg +1 -0
- package/dist/chunk-R6MGHCD7.js +93 -0
- package/dist/config.d.ts +167 -0
- package/dist/config.js +401 -0
- package/dist/plan-format.d.ts +325 -0
- package/dist/plan-format.js +636 -0
- package/docs/lanes.mmd +9 -0
- package/docs/lanes.svg +1 -0
- package/docs/skills.mmd +10 -0
- package/docs/skills.svg +1 -0
- package/docs/workflow.svg +172 -0
- package/herdr-plugin/herdr-plugin.toml +22 -0
- package/herdr-plugin/open-plans.ts +43 -0
- package/herdr-plugin/restore-plans.ts +107 -0
- package/install.sh +157 -0
- package/kit/hooks/hooks/git-guard/decision.ts +438 -0
- package/kit/hooks/hooks/git-guard/index.ts +38 -0
- package/kit/hooks/hooks/git-guard/shell.ts +407 -0
- package/kit/hooks/hooks/watch-guard/background.ts +36 -0
- package/kit/hooks/hooks/watch-guard/decision.ts +53 -0
- package/kit/hooks/hooks/watch-guard/index.ts +11 -0
- package/kit/hooks/hooks/watch-guard/shell-input.ts +32 -0
- package/kit/hooks/hooks/watch-guard/starts.ts +154 -0
- package/kit/hooks/hooks/watch-guard/text-fallback.ts +40 -0
- package/kit/hooks/hooks.json +30 -0
- package/kit/hooks/statusline.sh +72 -0
- package/kit/review/agents/devops.md +11 -0
- package/kit/review/agents/public-content.md +39 -0
- package/kit/review/agents/security-audit.md +11 -0
- package/kit/review/agents/uncle-bob.md +11 -0
- package/kit/review/skills/devops/SKILL.md +50 -0
- package/kit/review/skills/diagnose/SKILL.md +140 -0
- package/kit/review/skills/diagnose/references/feedback-loops.md +59 -0
- package/kit/review/skills/diagnose/scripts/hitl-loop.template.sh +71 -0
- package/kit/review/skills/pre-commit/SKILL.md +100 -0
- package/kit/review/skills/review/SKILL.md +43 -0
- package/kit/review/skills/security-audit/SKILL.md +65 -0
- package/kit/review/skills/uncle-bob/SKILL.md +43 -0
- package/kit/rules/rules/kit/arc42.md +89 -0
- package/kit/rules/rules/kit/architecture-files.md +87 -0
- package/kit/rules/rules/kit/code-style.md +159 -0
- package/kit/rules/rules/kit/pull-requests.md +59 -0
- package/kit/rules/rules/kit/testing.md +91 -0
- package/kit/rules/rules/kit/writing-for-agents.md +110 -0
- package/kit/workflow/agents/explore.md +25 -0
- package/kit/workflow/agents/implement.md +16 -0
- package/kit/workflow/agents/plan.md +13 -0
- package/kit/workflow/agents/spec-review.md +96 -0
- package/kit/workflow/skills/follow/SKILL.md +48 -0
- package/kit/workflow/skills/grill/SKILL.md +60 -0
- package/kit/workflow/skills/implement/SKILL.md +107 -0
- package/kit/workflow/skills/orchestrate/SKILL.md +565 -0
- package/kit/workflow/skills/orchestrate/handover/decision.ts +247 -0
- package/kit/workflow/skills/orchestrate/handover/index.ts +171 -0
- package/kit/workflow/skills/plan/SKILL.md +118 -0
- package/kit/workflow/skills/plan/references/template-English.md +92 -0
- package/kit/workflow/skills/plan/references/template-German.md +92 -0
- package/kit/workflow/skills/start/SKILL.md +47 -0
- package/package.json +63 -5
- package/schemas/jackherd.schema.json +601 -0
- package/src/check/changes.ts +45 -0
- package/src/check/command-line.ts +12 -0
- package/src/check/command.ts +136 -0
- package/src/check/deferral-arrows.ts +41 -0
- package/src/check/execute.ts +29 -0
- package/src/check/glob.ts +15 -0
- package/src/check/plan-structure-check.ts +135 -0
- package/src/check/plan.ts +106 -0
- package/src/check/public-content-check.ts +32 -0
- package/src/check/public-content-facts.ts +177 -0
- package/src/check/public-content.ts +216 -0
- package/src/check/report.ts +55 -0
- package/src/check/result.ts +42 -0
- package/src/check/roots.ts +36 -0
- package/src/check/routing.ts +22 -0
- package/src/check/schema.ts +83 -0
- package/src/check/settings.ts +44 -0
- package/src/claude/paths.ts +14 -0
- package/src/cli.ts +46 -0
- package/src/config/command.ts +45 -0
- package/src/config/detect.ts +196 -0
- package/src/config/index.ts +2 -0
- package/src/config/json-schema.ts +5 -0
- package/src/config/legacy-names.ts +56 -0
- package/src/config/link-kinds.ts +25 -0
- package/src/config/location.ts +46 -0
- package/src/config/read-config.ts +42 -0
- package/src/config/repository-facts.ts +19 -0
- package/src/config/repository-values.ts +46 -0
- package/src/config/schema.ts +302 -0
- package/src/context/command.ts +39 -0
- package/src/context/coordinator-name.ts +16 -0
- package/src/context/fill.ts +69 -0
- package/src/context/index.ts +10 -0
- package/src/context/level.ts +22 -0
- package/src/context/options.ts +29 -0
- package/src/context/report.ts +42 -0
- package/src/context/session-log.ts +91 -0
- package/src/plan-format/drift.ts +69 -0
- package/src/plan-format/header.ts +27 -0
- package/src/plan-format/index.ts +9 -0
- package/src/plan-format/lanes.ts +128 -0
- package/src/plan-format/markdown.ts +98 -0
- package/src/plan-format/phase-tally.ts +55 -0
- package/src/plan-format/phases.ts +224 -0
- package/src/plan-format/plan-structure.ts +150 -0
- package/src/plan-format/progress.ts +202 -0
- package/src/plan-format/sprint-document.ts +69 -0
- package/src/plan-format/terminal-text.ts +28 -0
- package/src/plans/board-choice.ts +60 -0
- package/src/plans/board-pick.ts +72 -0
- package/src/plans/board-rows.ts +62 -0
- package/src/plans/command.ts +60 -0
- package/src/plans/ensure-panes.ts +71 -0
- package/src/plans/error-text.ts +5 -0
- package/src/plans/fetch.ts +96 -0
- package/src/plans/git.ts +145 -0
- package/src/plans/github.ts +35 -0
- package/src/plans/lane-discovery.ts +34 -0
- package/src/plans/lane-plans.ts +80 -0
- package/src/plans/layout.ts +881 -0
- package/src/plans/link-chips.ts +74 -0
- package/src/plans/live.ts +336 -0
- package/src/plans/markdown-html.ts +140 -0
- package/src/plans/options.ts +15 -0
- package/src/plans/phase-source.ts +71 -0
- package/src/plans/plan-chips.ts +75 -0
- package/src/plans/plan-panes.ts +206 -0
- package/src/plans/planned.ts +117 -0
- package/src/plans/pull-requests.ts +107 -0
- package/src/plans/restart-decision.ts +42 -0
- package/src/plans/selection.ts +352 -0
- package/src/plans/settings.ts +68 -0
- package/src/plans/single-flight.ts +25 -0
- package/src/plans/snapshot.ts +300 -0
- package/src/plans/sprint-export.ts +103 -0
- package/src/plans/sprint-html.ts +301 -0
- package/src/plans/sprint-page.ts +287 -0
- package/src/plans/supervisor.ts +124 -0
- package/src/plans/symbols.ts +54 -0
- package/src/plans/texts.ts +103 -0
- package/src/plans/unlisted-lanes.ts +45 -0
- package/src/plans/view.ts +68 -0
- package/src/plans/worked-phases.ts +53 -0
- package/src/sync/checksum.ts +13 -0
- package/src/sync/choice.ts +14 -0
- package/src/sync/command-context.ts +113 -0
- package/src/sync/command.ts +94 -0
- package/src/sync/error-message.ts +1 -0
- package/src/sync/gitignore.ts +45 -0
- package/src/sync/global-kit.ts +176 -0
- package/src/sync/herdr-plugin.ts +175 -0
- package/src/sync/if-changed.ts +109 -0
- package/src/sync/init-command.ts +239 -0
- package/src/sync/init.ts +136 -0
- package/src/sync/kit-hooks.ts +18 -0
- package/src/sync/kit-path.ts +77 -0
- package/src/sync/kit-source.ts +169 -0
- package/src/sync/kit.ts +9 -0
- package/src/sync/legacy-files.ts +116 -0
- package/src/sync/manifest.ts +44 -0
- package/src/sync/next-steps.ts +42 -0
- package/src/sync/plan-file.ts +154 -0
- package/src/sync/plan-hooks.ts +165 -0
- package/src/sync/project-fs.ts +132 -0
- package/src/sync/render.ts +60 -0
- package/src/sync/report.ts +96 -0
- package/src/sync/settings-hooks.ts +90 -0
- package/src/sync/settings-migration.ts +94 -0
- package/src/sync/settings-offer.ts +168 -0
- package/src/sync/setup-questions.ts +70 -0
- package/src/sync/setup.ts +52 -0
- package/src/sync/sync-plan.ts +120 -0
- package/src/sync/sync-result.ts +9 -0
- package/src/sync/sync.ts +307 -0
- package/src/sync/tools.ts +52 -0
- package/src/terminal/herdr.ts +91 -0
- package/src/terminal/index.ts +13 -0
- package/src/terminal/none.ts +7 -0
- package/src/terminal/open.ts +39 -0
- package/src/terminal/text.ts +159 -0
- package/src/terminal/types.ts +30 -0
- package/src/terminal/width.ts +90 -0
- package/src/update/command.ts +65 -0
- package/src/update/package-manager.ts +56 -0
- package/src/update/update.ts +105 -0
- package/src/version/installed-version.ts +32 -0
- package/src/version/release.ts +22 -0
- package/src/version/skill-diff.ts +63 -0
- package/src/version/started-kit.ts +14 -0
- package/src/vocabulary/index.ts +132 -0
- package/src/watch/base-tip.ts +18 -0
- package/src/watch/command.ts +919 -0
- package/src/watch/coordinator-fill.ts +76 -0
- package/src/watch/feedback-read.ts +73 -0
- package/src/watch/feedback.ts +308 -0
- package/src/watch/gh.ts +22 -0
- package/src/watch/github-read.ts +175 -0
- package/src/watch/herdr.ts +11 -0
- package/src/watch/json-guards.ts +9 -0
- package/src/watch/kit-update.ts +18 -0
- package/src/watch/lane-fill.ts +36 -0
- package/src/watch/lane-message.ts +99 -0
- package/src/watch/lane-progress.ts +44 -0
- package/src/watch/lane-report-file.ts +53 -0
- package/src/watch/lane-report.ts +270 -0
- package/src/watch/lane-search.ts +41 -0
- package/src/watch/lane-sources.ts +46 -0
- package/src/watch/lane-watch.ts +121 -0
- package/src/watch/monitor-tool.ts +19 -0
- package/src/watch/options.ts +79 -0
- package/src/watch/plan-badges-read.ts +30 -0
- package/src/watch/plan-badges.ts +66 -0
- package/src/watch/plan-views.ts +59 -0
- package/src/watch/preview-read.ts +60 -0
- package/src/watch/previews.ts +234 -0
- package/src/watch/pull-requests.ts +117 -0
- package/src/watch/single-report.ts +15 -0
- package/src/watch/skill-change.ts +61 -0
- package/src/watch/source-dry-run-command.ts +35 -0
- package/src/watch/source-dry-run.ts +124 -0
- package/src/watch/source-fields.ts +9 -0
- package/src/watch/source-path.ts +40 -0
- package/src/watch/source-run.ts +90 -0
- package/src/watch/sources.ts +344 -0
- package/src/watch/stream-output.ts +14 -0
- package/src/watch/texts.ts +76 -0
- package/src/watch/watch-events.ts +399 -0
- package/src/watch/watcher-lock.ts +145 -0
- package/src/watch/workflow-runs.ts +96 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Human-in-the-loop diagnosis loop. Last resort for Phase 1 of /diagnose, when the
|
|
4
|
+
# trigger cannot be driven unattended: a device, a physical button, a native build.
|
|
5
|
+
#
|
|
6
|
+
# Copy it next to the investigation, fill in the four blocks, run it, and paste the
|
|
7
|
+
# captured output back. The point is that a human pressing a button still produces a
|
|
8
|
+
# structured, repeatable observation instead of a chat message.
|
|
9
|
+
#
|
|
10
|
+
# Credentials come from the environment; export them before running and redact
|
|
11
|
+
# anything you paste back.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
readonly RUNS="${RUNS:-5}"
|
|
16
|
+
readonly OUTPUT_DIR="${OUTPUT_DIR:-$(mktemp -d -t diagnose.XXXXXX)}"
|
|
17
|
+
|
|
18
|
+
# The exact symptom this loop asserts. One sentence, in the user's words.
|
|
19
|
+
readonly SYMPTOM='<the symptom, as the user described it>'
|
|
20
|
+
|
|
21
|
+
# What the human does each round. Precise enough that two people do the same thing.
|
|
22
|
+
readonly HUMAN_STEP='<the action, and what to watch for how long>'
|
|
23
|
+
|
|
24
|
+
# Machine-side state captured BEFORE the human acts — evidence a restart would destroy.
|
|
25
|
+
# The probes keep `|| true`: a failing probe is the signal, not an error.
|
|
26
|
+
capture_before() {
|
|
27
|
+
ping -c 2 -- "${TARGET_HOST}" || true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Machine-side state captured AFTER the human acts. This is what goes red or green.
|
|
31
|
+
capture_after() {
|
|
32
|
+
ping -c 2 -- "${TARGET_HOST}" || true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if [[ -z "${TARGET_HOST:-}" ]]; then
|
|
36
|
+
echo 'TARGET_HOST is not set — export it before running (the probes need a target).' >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
mkdir -p -- "${OUTPUT_DIR}"
|
|
41
|
+
|
|
42
|
+
for run in $(seq 1 "${RUNS}"); do
|
|
43
|
+
log="${OUTPUT_DIR}/run-${run}.log"
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
echo "=== run ${run}/${RUNS} ==="
|
|
47
|
+
echo "symptom: ${SYMPTOM}"
|
|
48
|
+
echo "--- before ---"
|
|
49
|
+
capture_before
|
|
50
|
+
} >>"${log}" 2>&1
|
|
51
|
+
|
|
52
|
+
printf '\n[run %s/%s] %s\n' "${run}" "${RUNS}" "${HUMAN_STEP}"
|
|
53
|
+
printf 'Did the symptom occur? [y/n/q] '
|
|
54
|
+
read -r verdict </dev/tty
|
|
55
|
+
|
|
56
|
+
case "${verdict}" in
|
|
57
|
+
q) break ;;
|
|
58
|
+
y) echo "verdict: RED" >>"${log}" ;;
|
|
59
|
+
*) echo "verdict: green" >>"${log}" ;;
|
|
60
|
+
esac
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
echo "--- after ---"
|
|
64
|
+
capture_after
|
|
65
|
+
} >>"${log}" 2>&1
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
echo
|
|
69
|
+
echo "Captured ${OUTPUT_DIR}"
|
|
70
|
+
grep -h '^verdict:' "${OUTPUT_DIR}"/run-*.log | sort | uniq -c
|
|
71
|
+
echo 'Reproduction rate below ~50% is not yet debuggable — raise it before Phase 2.'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pre-commit
|
|
3
|
+
description: 'Pre-Commit Orchestrator — detects changed files, routes to the right reviewers, suggests commit message.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If `.claude/project/pre-commit.md` exists, read it first. It extends this skill for this project (extra judgement checks, post-deploy reminders) and wins where the two disagree.
|
|
7
|
+
|
|
8
|
+
You are the **Pre-Commit Orchestrator**. You measure what a command can measure, route the change to the reviewers that judge the rest, and ask the user for the commit.
|
|
9
|
+
|
|
10
|
+
## Step 1: Run the deterministic checks
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bunx jackherd check
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One command answers everything a command can answer, and **its output is the evidence for steps 1, 3 and 4** — the changed files (including untracked ones), the affected package roots, which reviewers the changed paths call for, which files a generator owns, staged build artifacts, lint over the changed files, types and tests per package root, and the project's own checks. All of it is configured in the `check` block of `.jackherd/config.json`.
|
|
17
|
+
|
|
18
|
+
**Quote its results; restate nothing from your own reading of the diff.** A verdict about type errors, passing tests or lint that was not measured is a guess, and the failure mode is a confident false PASS. Where the command says `❌`, the summary says FIX FIRST.
|
|
19
|
+
|
|
20
|
+
`--skip-slow` drops types and tests. Use it only when the user asks for a quick pass, and say so in the summary.
|
|
21
|
+
|
|
22
|
+
## Step 2: Judgement Review
|
|
23
|
+
|
|
24
|
+
Run `/review` for what the command cannot decide: dead exports, naming, function size, the design review. The measured points are already settled above.
|
|
25
|
+
|
|
26
|
+
## Step 3: Specialized Reviewers
|
|
27
|
+
|
|
28
|
+
**Step 1 printed the reviewer list** (`Reviewers: …`, from `check.reviewerRoutes`) — dispatch exactly those, each as a subagent via the `Agent` tool with its `subagent_type`:
|
|
29
|
+
|
|
30
|
+
- Pass in the `prompt`: the `git diff` of the relevant files and the file paths.
|
|
31
|
+
- Start all of them **in parallel**, in a single message, and wait for every result before step 4.
|
|
32
|
+
|
|
33
|
+
`security-audit` has a judgement half: add it when the diff *does* something security-relevant in a path no route lists — auth middleware, API key handling, a schema at an HTTP endpoint, a raw database query, an access policy.
|
|
34
|
+
|
|
35
|
+
**`spec-review` starts in the same message.** It reviews a different axis, not different files, so it applies regardless of which paths changed. Pass it the full `git diff`, the commit list (`git log <base>..HEAD --oneline`, base `{{kit.baseBranch}}`), the base ref and the branch name.
|
|
36
|
+
|
|
37
|
+
## Step 4: Generated Files
|
|
38
|
+
|
|
39
|
+
Step 1 lists the changed files a generator owns and names the generator. A regenerated file lands in the diff the same way as a hand-edited one, so the judgement is whether this one came from the generator. Ask when it looks hand-edited.
|
|
40
|
+
|
|
41
|
+
## Step 5: Spec Alignment
|
|
42
|
+
|
|
43
|
+
Take the `spec-review` result. Steps 2 and 3 ask whether the code is written well (**Standards**); this one asks whether it does what was asked (**Spec**). Report the two side by side, each under its own heading, so neither masks the other.
|
|
44
|
+
|
|
45
|
+
## Step 6: arc42 Check
|
|
46
|
+
|
|
47
|
+
Apply the filter in `.claude/rules/kit/arc42.md` to the changed files. On a hit, **ask the user concretely**. No hit for bug fixes, styling, small UI changes, or refactorings without decision character.
|
|
48
|
+
|
|
49
|
+
## Step 7: Architecture Check
|
|
50
|
+
|
|
51
|
+
For changes in a service, a domain package or a data layer, apply the filter in `.claude/rules/kit/architecture-files.md`. On a hit, **ask the user concretely** whether the unit's `ARCHITECTURE.md` needs an update.
|
|
52
|
+
|
|
53
|
+
## Output Format
|
|
54
|
+
|
|
55
|
+
Write the summary in {{kit.responseLanguage}}:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
## Pre-Commit Review
|
|
59
|
+
|
|
60
|
+
Changed files: [file list]
|
|
61
|
+
Active reviewers: Review + Spec + [routed reviewers]
|
|
62
|
+
|
|
63
|
+
### Deterministic checks: PASS / FAIL
|
|
64
|
+
[the result lines of `jh check`, with the failing detail. Numbers, not impressions]
|
|
65
|
+
|
|
66
|
+
### Code Quality: PASS / FIX FIRST
|
|
67
|
+
### [reviewer name]: PASS / FAIL
|
|
68
|
+
[findings, one paragraph per reviewer]
|
|
69
|
+
|
|
70
|
+
### Generated Files: ✅ / ❌
|
|
71
|
+
### Spec: PASS / PASS WITH NOTES / FAIL / N/A
|
|
72
|
+
### arc42: ✅ checked / ⚠️ update recommended (question follows)
|
|
73
|
+
### Architecture (ARCHITECTURE.md): ✅ checked / ⚠️ update recommended (question follows) / N/A
|
|
74
|
+
|
|
75
|
+
### Verdict: COMMIT / FIX FIRST
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
On FIX FIRST, apply what a tool fixes on its own — formatting (`{{kit.command.format}}` or the formatter on the file), lint auto-fixes — then **re-run `bunx jackherd check` and quote the new result.** A fix is verified by the command that found the problem.
|
|
79
|
+
|
|
80
|
+
A second review round after fixes dispatches only the reviewers whose findings were applied, plus the reviewers of any path the fixes newly touched (the reviewer list of the new `check` run); `jh check` itself always runs in full.
|
|
81
|
+
|
|
82
|
+
Architecture problems, missing tests, domain logic errors and **every Spec finding** are reported for the user to decide: whether a requirement was dropped on purpose or the scope grew deliberately is the user's call.
|
|
83
|
+
|
|
84
|
+
## Commit
|
|
85
|
+
|
|
86
|
+
On COMMIT, propose a Conventional Commit message (`type(scope): description`, imperative, subject at most 72 characters; the body explains cause and consequence in prose).
|
|
87
|
+
|
|
88
|
+
Ask the commit question with the `AskUserQuestion` tool, as a clickable choice in {{kit.responseLanguage}}: commit with this message / adjust the message / cancel.
|
|
89
|
+
|
|
90
|
+
On confirmation, write the message to a file in your scratchpad with the `Write` tool, stage **the files step 1 listed** by name, and commit from the file:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git add -- "<path from step 1>" "<path from step 1>"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
git commit -F "<message file>"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each is one Bash call: a chained command cannot be approved as one (Claude Code docs, page "Configure permissions", section "Compound commands"). Staging by name keeps unreviewed files out of the commit. A message file reaches git untouched; passed through the shell (`-m "…"` or a heredoc), the backticks and `$(…)` a commit body quotes could run as commands. Let the repository's own commit hooks run.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: 'Code Quality Review — the judgement checks over changed files that no command decides. Use after any code change before committing.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If `.claude/project/review.md` exists, read it first. It extends this skill for this project and wins where the two disagree.
|
|
7
|
+
|
|
8
|
+
You are the **Reviewer** — you judge what a command cannot. Architecture and design are `/uncle-bob`'s job.
|
|
9
|
+
|
|
10
|
+
## Measurements first
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bunx jackherd check
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Types, lint, unused imports, staged build artifacts and failing tests are decidable, and this command decides them. **Quote its output; the verdict on those points is the command's.** A verdict you could not measure is a guess, and the guess that costs is the confident false PASS. When invoked from `/pre-commit` the command has already run — take that output instead of running it twice.
|
|
17
|
+
|
|
18
|
+
## Checks that need judgement
|
|
19
|
+
|
|
20
|
+
1. **Dead code**: unused exports, unreachable branches, commented-out leftovers
|
|
21
|
+
2. **Naming**: does the name reveal intent, or does it need a comment to explain what it holds
|
|
22
|
+
3. **Function size**: anything past ~20 lines, or a description that needs an "and"
|
|
23
|
+
4. **Duplication**: more than three lines of identical logic in two places
|
|
24
|
+
5. **Design review**: the checklist in `.claude/rules/kit/code-style.md` → Design Review
|
|
25
|
+
|
|
26
|
+
## Output Format
|
|
27
|
+
|
|
28
|
+
In {{kit.responseLanguage}}:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
## Code Quality Review
|
|
32
|
+
|
|
33
|
+
Measured (jh check): PASS / FAIL
|
|
34
|
+
[the failing lines, verbatim]
|
|
35
|
+
|
|
36
|
+
1. Dead code: ✅ / ❌
|
|
37
|
+
2. Naming: ✅ / ❌
|
|
38
|
+
3. Function size: ✅ / ❌
|
|
39
|
+
4. Duplication: ✅ / ❌
|
|
40
|
+
5. Design review: ✅ / ❌
|
|
41
|
+
|
|
42
|
+
Verdict: PASS / FIX FIRST
|
|
43
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-audit
|
|
3
|
+
description: 'Use when a change touches auth, tenant isolation, access policies, API keys, secrets, user input, database queries, message payloads or infrastructure: OWASP Top 10, secrets exposure, authz, input validation, injection, path traversal.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If `.claude/project/security-audit.md` exists, read it first. It adds this project's security checks (its database's access policies, its tenant model, its known incidents) and wins where the two disagree.
|
|
7
|
+
|
|
8
|
+
You are a Security Engineer: paranoid, systematic, every finding carries its evidence.
|
|
9
|
+
|
|
10
|
+
You receive a `git diff` or a list of changed files. Read the relevant files and review through these lenses.
|
|
11
|
+
|
|
12
|
+
> **Why the name.** This skill is `security-audit`, not `security-review`, because Claude Code ships a
|
|
13
|
+
> built-in skill of that name. A project skill sharing it would leave which one `/security-review`
|
|
14
|
+
> reaches undecided. Review gates name `/security-audit`.
|
|
15
|
+
|
|
16
|
+
## Checks
|
|
17
|
+
|
|
18
|
+
### Secrets & Credentials
|
|
19
|
+
|
|
20
|
+
1. No hardcoded secrets: API keys, passwords, tokens live in environment variables or the secret store.
|
|
21
|
+
2. No secret logging: credentials stay out of logs, error messages and terminal output.
|
|
22
|
+
3. Secret files stay out of git: `.env`, `.env.local`, `*.pem`, `*.key`.
|
|
23
|
+
|
|
24
|
+
### Authentication & Authorization
|
|
25
|
+
|
|
26
|
+
4. Access policies: every table with user data is protected by the database's access policies (row-level security where the database has it).
|
|
27
|
+
5. Tenant isolation: in a multi-tenant system, every query and every message is scoped by tenant. No cross-tenant reads or writes.
|
|
28
|
+
6. API key validation: new endpoints sit behind the auth middleware.
|
|
29
|
+
7. Token validation: tokens are verified (signature, expiry, issuer), never only decoded.
|
|
30
|
+
8. Policy cost: a policy compares a claim from the token against a column; a per-row lookup into the user table is unsargable and can take a database down under load.
|
|
31
|
+
|
|
32
|
+
### Input Validation
|
|
33
|
+
|
|
34
|
+
9. Schemas at system boundaries: every external input (HTTP body, message payload, webhook) is validated with a schema.
|
|
35
|
+
10. SQL injection: queries are parameterized; user input never reaches query text through string interpolation.
|
|
36
|
+
11. Path traversal: a user- or config-controlled file path is normalised and checked to stay inside its base directory, symlinks included.
|
|
37
|
+
12. XSS: user input never reaches HTML unescaped.
|
|
38
|
+
|
|
39
|
+
### Write Paths
|
|
40
|
+
|
|
41
|
+
13. No `upsert` on a generated natural key: a conflict target the caller does not own turns a replayed identifier into a write into someone else's row. Create and update are separate statements, and an update is scoped by tenant as well as by key (`.claude/rules/kit/code-style.md` → Database Writes).
|
|
42
|
+
14. Generated identifiers carry entropy, not a clock.
|
|
43
|
+
|
|
44
|
+
### Dependencies & Supply Chain
|
|
45
|
+
|
|
46
|
+
15. New dependencies: check for known CVEs. Is it actually needed?
|
|
47
|
+
16. Excessive permissions: new packages with native binaries, network calls at install, or `postinstall` scripts.
|
|
48
|
+
|
|
49
|
+
### Messaging
|
|
50
|
+
|
|
51
|
+
17. Message handlers validate the payload before processing.
|
|
52
|
+
|
|
53
|
+
## Your Output Format
|
|
54
|
+
|
|
55
|
+
In {{kit.responseLanguage}}. For each finding:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
[SEVERITY] Category — File:Line
|
|
59
|
+
Description of the issue
|
|
60
|
+
→ Recommended fix
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Severities: CRITICAL, HIGH, MEDIUM, LOW, INFO
|
|
64
|
+
|
|
65
|
+
At the end a summary: findings per severity, overall verdict (**PASS** / **PASS WITH NOTES** / **FAIL**).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uncle-bob
|
|
3
|
+
description: 'Clean Architecture Review — SOLID, Dependency Direction, Small Functions. Use for backend and service code review.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If `.claude/project/uncle-bob.md` exists, read it first. It adds this project's architecture checks (event patterns, service boundaries, where domain types live) and wins where the two disagree.
|
|
7
|
+
|
|
8
|
+
You are **Uncle Bob** — Clean Architecture, SOLID, small functions.
|
|
9
|
+
|
|
10
|
+
## Checks
|
|
11
|
+
|
|
12
|
+
1. **Single Responsibility**: Every function/module does exactly one thing. Does the description need an "and"? Then split it. In a diff this shows up as *divergent change* — one file edited for several unrelated reasons. → split so each module changes for one reason.
|
|
13
|
+
2. **DRY**: No copy-pasted logic. More than 3 identical lines in two places → shared helper.
|
|
14
|
+
3. **Dead Code**: Unused exports, unreachable branches, commented-out code → remove them.
|
|
15
|
+
4. **Naming**: Names reveal intent. If a comment is needed to explain what a variable holds → rename it.
|
|
16
|
+
5. **Small Functions**: Functions > ~20 lines are a smell. Extract sub-steps into named helpers.
|
|
17
|
+
6. **Dependency Direction**: Pure utilities never import side-effectful modules. Arrows point inward.
|
|
18
|
+
7. **No Flag Arguments**: Booleans that switch behavior → split into two functions.
|
|
19
|
+
8. **Error Surface**: Only handle errors that are meaningfully recoverable. Let the rest propagate.
|
|
20
|
+
9. **Data Clumps**: The same few fields or parameters keep travelling together — a type wanting to be born. → bundle them into one type and pass that.
|
|
21
|
+
10. **Primitive Obsession**: A `string`, `number` or raw record standing in for a domain concept. → give the concept its own small type, usually a schema with an inferred type.
|
|
22
|
+
11. **Shotgun Surgery**: One logical change forces scattered edits across many files in the diff. → gather what changes together into one module. The inverse of divergent change in check 1.
|
|
23
|
+
12. **Speculative Generality**: Abstraction, parameters, slots or hooks added for a need nothing in the diff has. → delete it and inline back until a real second caller shows up.
|
|
24
|
+
|
|
25
|
+
Checks 9 to 12 are **labelled heuristics, never hard violations** — report them as "possible Data Clump", with the call left to the author. Skip anything the linter already enforces, and skip anything a documented rule in `.claude/rules/` explicitly endorses: the rules win over this list.
|
|
26
|
+
|
|
27
|
+
### Additional Checks
|
|
28
|
+
|
|
29
|
+
- **Code Order**: Types → pure functions → higher-order functions (`.claude/rules/kit/code-style.md`).
|
|
30
|
+
- **Framework as adapter**: domain logic lives in framework-free modules (`.claude/rules/kit/code-style.md`).
|
|
31
|
+
|
|
32
|
+
## Your Output Format
|
|
33
|
+
|
|
34
|
+
In {{kit.responseLanguage}}. For each violation:
|
|
35
|
+
|
|
36
|
+
- **Rule** (which of the 12 points)
|
|
37
|
+
- **File + line**
|
|
38
|
+
- **Problem** (one sentence)
|
|
39
|
+
- **Fix** (concrete proposal)
|
|
40
|
+
|
|
41
|
+
At the end: overall verdict (PASS / PASS WITH NOTES / FAIL).
|
|
42
|
+
|
|
43
|
+
If everything is clean, just say: "Clean. Ship it."
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "docs/architecture/**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# arc42 Architecture Documentation
|
|
7
|
+
|
|
8
|
+
Single source of truth for the architecture documentation in `docs/architecture/`. Skills (`/plan`, `/implement`, `/pre-commit`) reference this file for the arc42 filter.
|
|
9
|
+
|
|
10
|
+
Reference model: [arc42.org/overview](https://arc42.org/overview). arc42 defines 12 sections; maintain a **subset** — only what delivers real value. Empty sections stay out.
|
|
11
|
+
|
|
12
|
+
> **Output language: {{kit.planLanguage}}.** These instructions are English; the documents in
|
|
13
|
+
> `docs/architecture/` are written in {{kit.planLanguage}}, like plans and ADRs.
|
|
14
|
+
|
|
15
|
+
## Core Principle: derive from the code
|
|
16
|
+
|
|
17
|
+
Architecture documentation is derived **after** the implementation from the finished code. Exception: an ADR accompanying a plan records a decision as soon as it is made — before implementation, with `Status: Proposed`.
|
|
18
|
+
|
|
19
|
+
**Planned ≠ built.** Document only built code as real. Mark building blocks, systems and flows that are not yet implemented explicitly as planned, with a link to the plan in `{{kit.planDirectory}}/`, and ADRs with `Status: Proposed`. A reader must be able to tell what exists and what is the target picture without looking at the code.
|
|
20
|
+
|
|
21
|
+
## Section Mapping
|
|
22
|
+
|
|
23
|
+
| File | arc42 section | Content |
|
|
24
|
+
| ---- | ------------- | ------- |
|
|
25
|
+
| `01-context.md` | 3 — Context & scope | External systems, interfaces, direction, protocol |
|
|
26
|
+
| `05-building-blocks.md` | 5 — Building block view | Static decomposition into levels (whitebox) |
|
|
27
|
+
| `06-runtime.md` | 6 — Runtime view | Significant flows as sequence/state diagrams |
|
|
28
|
+
| `08-concepts.md` | 8 — Cross-cutting concepts | Reusable patterns (auth, events, tracing, data access) |
|
|
29
|
+
| `09-decisions.md` | 9 — ADRs | Architecture decisions, numbered chronologically |
|
|
30
|
+
|
|
31
|
+
Create further sections (deployment, solution strategy, quality, risks, glossary) only when a plan shows real value for them.
|
|
32
|
+
|
|
33
|
+
## arc42 Filter — which change hits which section?
|
|
34
|
+
|
|
35
|
+
On at least one hit: **ask the user concretely** (see ask workflow).
|
|
36
|
+
|
|
37
|
+
| Criterion | Section | Update when… |
|
|
38
|
+
| --------- | ------- | ------------ |
|
|
39
|
+
| New external system, API, hardware, service | `01-context.md` | An integration with a new system is implemented |
|
|
40
|
+
| New app / service / package | `05-building-blocks.md` | A new service or package was created |
|
|
41
|
+
| New significant flow | `06-runtime.md` | A new lifecycle step, replication strategy, event flow |
|
|
42
|
+
| New cross-cutting concept or pattern | `08-concepts.md` | A pattern applied in several places |
|
|
43
|
+
| Architectural decision (A vs. B, not obvious) | `09-decisions.md` | A decision that permanently constrains the solution space |
|
|
44
|
+
|
|
45
|
+
**No hit** (no update, no follow-up question) for: bug fixes, styling, small UI tweaks, pure refactorings without decision character.
|
|
46
|
+
|
|
47
|
+
## Ask Workflow
|
|
48
|
+
|
|
49
|
+
On a hit, ask concretely and with context, in {{kit.responseLanguage}} — never a generic "update the docs?". Name the change, the section, and why it qualifies:
|
|
50
|
+
|
|
51
|
+
> "The union type for the selector shapes every call site permanently — an API decision. Shall I add
|
|
52
|
+
> an ADR to `docs/architecture/09-decisions.md`?"
|
|
53
|
+
|
|
54
|
+
On "yes": add a concrete task to the plan's `## {{kit.heading.documentation}}` section, or write the section directly. For multi-phase plans, schedule the arc42 derivation as the last phase.
|
|
55
|
+
|
|
56
|
+
## Building Block View (05) — levels
|
|
57
|
+
|
|
58
|
+
The building block view is **hierarchical**. Every level is a whitebox: an overview diagram, the contained building blocks, and the important interfaces.
|
|
59
|
+
|
|
60
|
+
- **Level 1 — whitebox of the overall system (mandatory):** a `graph TB` overview of the topmost building blocks, plus tables of the contained packages, apps and services, each with one responsibility.
|
|
61
|
+
- **Level 2 — whitebox of selected building blocks:** decompose important ones further (a service into handler → use-cases → adapter), only where the complexity justifies it.
|
|
62
|
+
- **Level 3:** only on real demand.
|
|
63
|
+
|
|
64
|
+
One coarse overview diagram plus detail diagrams for complex subsystems. A 30-node graph is unreadable.
|
|
65
|
+
|
|
66
|
+
## Runtime View (06)
|
|
67
|
+
|
|
68
|
+
One `sequenceDiagram` **per significant flow**; state machines as `stateDiagram-v2`. Document only flows that are not trivially readable from the code.
|
|
69
|
+
|
|
70
|
+
## Cross-cutting Concepts (08)
|
|
71
|
+
|
|
72
|
+
A concept is a pattern that applies in **several** places. Data model tables plus a short description of the rule. Not a feature-by-feature log.
|
|
73
|
+
|
|
74
|
+
## Decisions (09) — ADR format
|
|
75
|
+
|
|
76
|
+
Numbered chronologically (`ADR-001`, `ADR-002`, …). Existing ADRs stay as written — new ones go on top, an old one is marked `Status: Superseded by ADR-NNN` when needed. Every ADR carries: date, status (`Proposed | Accepted | Superseded by ADR-MMM`), context (why the question arises, which options), decision, rationale (error cost, constraints, trade-offs), consequences (what follows, the accepted price).
|
|
77
|
+
|
|
78
|
+
The source for ADRs are the `## {{kit.heading.decisions}}` tables of the plans in `{{kit.planDirectory}}/`.
|
|
79
|
+
|
|
80
|
+
## Diagram Convention: Mermaid
|
|
81
|
+
|
|
82
|
+
All diagrams as **Mermaid** in markdown code blocks. GitHub and editors render it natively.
|
|
83
|
+
|
|
84
|
+
| Section | Mermaid type |
|
|
85
|
+
| ------- | ------------ |
|
|
86
|
+
| `01-context.md` | `C4Context` (or `graph LR`) |
|
|
87
|
+
| `05-building-blocks.md` | `graph TB` (overview + detail per subsystem) |
|
|
88
|
+
| `06-runtime.md` | `sequenceDiagram` (one flow per diagram) |
|
|
89
|
+
| `08-concepts.md` | `stateDiagram-v2` (state machines) |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "services/**"
|
|
4
|
+
- "packages/**"
|
|
5
|
+
- "**/ARCHITECTURE.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# ARCHITECTURE.md — Durable Architecture Contracts
|
|
9
|
+
|
|
10
|
+
Single source of truth for `ARCHITECTURE.md` files next to services and domain packages. Skills (`/plan`, `/implement`, `/pre-commit`) reference this file for the filter.
|
|
11
|
+
|
|
12
|
+
> **Not the same thing as `docs/architecture/`.** `docs/architecture/` (arc42, see `arc42.md`)
|
|
13
|
+
> documents the system as a whole. `ARCHITECTURE.md` is module-level: what one service or package
|
|
14
|
+
> promises about itself. A decision that spans several services belongs in the ADRs; a fact only
|
|
15
|
+
> this unit knows about itself belongs here.
|
|
16
|
+
|
|
17
|
+
## Core Principle: state, not change
|
|
18
|
+
|
|
19
|
+
| Artifact | Question | Lifecycle |
|
|
20
|
+
| -------- | -------- | --------- |
|
|
21
|
+
| `README.md` | How do I run this? | Updated ad hoc |
|
|
22
|
+
| `{{kit.planDirectory}}/*.md` | How do we get from current to target? | Archived once done |
|
|
23
|
+
| `ARCHITECTURE.md` | What must stay true going forward? | Updated in place, never archived |
|
|
24
|
+
|
|
25
|
+
`ARCHITECTURE.md` holds what only this service or package knows about itself: ownership, invariants, and trade-offs deliberately accepted. Conventions for every service belong in a rule, the system view in `docs/architecture/`.
|
|
26
|
+
|
|
27
|
+
## Template
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
# Architecture — <path>
|
|
31
|
+
|
|
32
|
+
<One-sentence scope.>
|
|
33
|
+
|
|
34
|
+
## Owns
|
|
35
|
+
|
|
36
|
+
- <table/topic/route this unit is the exclusive writer of>
|
|
37
|
+
|
|
38
|
+
## Reads, does not own
|
|
39
|
+
|
|
40
|
+
- <data it reads but another unit owns>
|
|
41
|
+
|
|
42
|
+
## Invariants
|
|
43
|
+
|
|
44
|
+
| Invariant | Why |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
|
|
47
|
+
## Known accepted risks
|
|
48
|
+
|
|
49
|
+
| Risk | Status | Source |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Language: English, like `README.md` — `ARCHITECTURE.md` lives next to code.
|
|
54
|
+
|
|
55
|
+
## Filter — which change touches an ARCHITECTURE.md?
|
|
56
|
+
|
|
57
|
+
On at least one hit: **ask the user concretely**, the same discipline as the arc42 filter.
|
|
58
|
+
|
|
59
|
+
| Criterion | Update when… |
|
|
60
|
+
| --------- | ------------ |
|
|
61
|
+
| New service or package created | Create the initial `ARCHITECTURE.md`, **if it clears the bar below** |
|
|
62
|
+
| Ownership boundary changes | A table/topic/route starts or stops being exclusively written by this unit |
|
|
63
|
+
| A deliberate trade-off is decided | A review finding is accepted rather than fixed, or a plan decision records a permanent constraint scoped to one unit |
|
|
64
|
+
| An invariant is introduced whose silent violation would be a bug, not a crash | Null-vs-zero distinctions, ordering guarantees, exactly-once/dedup guarantees, "field X is set once and never recalculated" |
|
|
65
|
+
| A schema migration adds a non-obvious backfill or version rule | A nullable field needs an explicit backfill; a version bump and its migration must land in one edit |
|
|
66
|
+
|
|
67
|
+
**No hit** for: bug fixes without a new invariant, styling, pure refactorings, dependency bumps.
|
|
68
|
+
|
|
69
|
+
### Not every package needs one
|
|
70
|
+
|
|
71
|
+
A package earns the file when it has at least one of:
|
|
72
|
+
|
|
73
|
+
- **global or process-wide state** it claims (patching `console.*`, a singleton connection),
|
|
74
|
+
- an **ownership boundary** other units must respect (event subjects, a table only it writes),
|
|
75
|
+
- an **invariant whose silent violation is a bug, not a crash**.
|
|
76
|
+
|
|
77
|
+
A presentation package whose contract is "these are the components and their props" does not. When in doubt, ask rather than create; an empty `ARCHITECTURE.md` reads as "nothing to know here".
|
|
78
|
+
|
|
79
|
+
## Ask Workflow
|
|
80
|
+
|
|
81
|
+
On a hit, ask concretely and with context, in {{kit.responseLanguage}} — never a generic "update the architecture file?":
|
|
82
|
+
|
|
83
|
+
> "`invoice_lines.id` is now taken over from the client as primary key instead of being assigned by
|
|
84
|
+
> the server. That is an accepted risk, not a bug. Shall I add it to `services/billing/ARCHITECTURE.md`
|
|
85
|
+
> under 'Known accepted risks'?"
|
|
86
|
+
|
|
87
|
+
On "yes": add a concrete task to the plan's `## {{kit.heading.documentation}}` section, or write the file directly for a small, single-phase change.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Code Style
|
|
2
|
+
|
|
3
|
+
The kit's core. Project-specific conventions (UI framework, shared component library, class utilities) live in the project's own rules beside `.claude/rules/kit/`; where they are stricter, they win.
|
|
4
|
+
|
|
5
|
+
## Formatting
|
|
6
|
+
|
|
7
|
+
The formatter and linter config is the authority; these are the conventions it encodes:
|
|
8
|
+
|
|
9
|
+
- Single quotes, no semicolons, 2-space indent, trailing commas.
|
|
10
|
+
- Sorted imports; no unused imports.
|
|
11
|
+
- Arrow functions over `function` declarations.
|
|
12
|
+
- Template literals over string concatenation.
|
|
13
|
+
- Blank lines separate logical sections; decorative separator comments (`// ─────────`) stay out.
|
|
14
|
+
|
|
15
|
+
## Language
|
|
16
|
+
|
|
17
|
+
- **All code in English**: file names, identifiers, type and component names, comments. User-facing strings go through the project's i18n system.
|
|
18
|
+
- **Commit messages in English**, subject and body. The subject is a Conventional Commit (`fix(scope): …`); the body explains cause and consequence in prose.
|
|
19
|
+
- **Plans and ADRs** are written in {{kit.planLanguage}}.
|
|
20
|
+
|
|
21
|
+
## Naming
|
|
22
|
+
|
|
23
|
+
- **No abbreviations in variable names**: `scrollElement`, not `scrollEl`; `domObserver`, not `mo`. Established exceptions: `i`/`j` in tight loops, `el` in single-line callbacks, `err` in catch blocks. When in doubt, spell it out.
|
|
24
|
+
- Keep naming aligned with the existing conventions and folder structure.
|
|
25
|
+
|
|
26
|
+
## Comments & Documentation
|
|
27
|
+
|
|
28
|
+
- Self-documenting code over comments: clear naming, pure functions, obvious control flow.
|
|
29
|
+
- **No section-label comments** (`// Types`, `// Pure functions`, `// Imports`). The code order speaks for itself.
|
|
30
|
+
|
|
31
|
+
### When to add JSDoc
|
|
32
|
+
|
|
33
|
+
Add JSDoc to an exported item when **any** of these is true:
|
|
34
|
+
|
|
35
|
+
- Parameters aren't self-evident from their types (a `duration` param: what unit? what happens at 0?)
|
|
36
|
+
- The function has non-obvious constraints or behaviour (serialization limits, a required context, a side-effect contract)
|
|
37
|
+
- It has edge cases or a subtle contract a caller could easily violate
|
|
38
|
+
|
|
39
|
+
A well-named function with typed parameters documents itself; one-liners that state the obvious (`/** Returns the sum */`) are noise.
|
|
40
|
+
|
|
41
|
+
## Code Organization
|
|
42
|
+
|
|
43
|
+
**Follow this order strictly:**
|
|
44
|
+
|
|
45
|
+
1. **Types first**: interfaces, types, type aliases at the top (after imports)
|
|
46
|
+
2. **Pure functions**: no side effects, no I/O, predictable outputs
|
|
47
|
+
3. **Higher-order functions**: functions that create side effects, orchestrate I/O, or compose other functions
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { foo } from './bar'
|
|
51
|
+
|
|
52
|
+
export type Config = { ... }
|
|
53
|
+
|
|
54
|
+
const parse = (input: string): Config => { ... }
|
|
55
|
+
const validate = (config: Config): boolean => { ... }
|
|
56
|
+
|
|
57
|
+
export const processFile = async (path: string): Promise<void> => {
|
|
58
|
+
const config = parse(await readFile(path))
|
|
59
|
+
if (validate(config)) await saveResult(config)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Framework as an Adapter, not Domain
|
|
64
|
+
|
|
65
|
+
The UI framework is a **replaceable adapter layer**, not where domain logic lives. Keep business logic framework-free so the framework stays swappable and the logic stays unit-testable without a component harness.
|
|
66
|
+
|
|
67
|
+
- **Domain logic lives in framework-free `.ts` modules** — calculations, validation, state machines, mapping, formatting. These modules import nothing from the UI framework.
|
|
68
|
+
- **Components consume logic, they don't contain it.** A component body wires state to pure functions; a derived value that grows past trivial derivation becomes a pure function the component calls.
|
|
69
|
+
- **Backend logic never touches the frontend framework.**
|
|
70
|
+
|
|
71
|
+
**Litmus test:** if swapping the framework would force you to rewrite a `.ts` file that has no UI in it, that file has leaked framework coupling — extract the logic.
|
|
72
|
+
|
|
73
|
+
## Code Reuse
|
|
74
|
+
|
|
75
|
+
**Before implementing new functionality:**
|
|
76
|
+
|
|
77
|
+
1. **Search the codebase** for existing utilities, helpers, components and services.
|
|
78
|
+
2. **Reuse > reinvent**: if something does 80% of what you need, use it and extend it.
|
|
79
|
+
3. **One copy of each piece of logic**: extract and reuse.
|
|
80
|
+
4. **Shared placement**: generic helpers (formatters, validators) belong in the nearest shared module, not in a feature file. If more than one module could use it, it is shared.
|
|
81
|
+
|
|
82
|
+
For UI: check the shared component library before building a button, input, modal or similar. Build a custom one only when the shared one is fundamentally incompatible, and write down why.
|
|
83
|
+
|
|
84
|
+
## Design Review (Clean Code)
|
|
85
|
+
|
|
86
|
+
After implementing, review every changed file through these lenses:
|
|
87
|
+
|
|
88
|
+
1. **Single Responsibility**: each function/module does one thing. If a description needs "and", split it.
|
|
89
|
+
2. **DRY**: more than 3 identical lines in two places → shared helper.
|
|
90
|
+
3. **Dead code**: remove unused exports, unreachable branches, commented-out code.
|
|
91
|
+
4. **Naming**: names reveal intent. If you need a comment to explain what a variable holds, rename it.
|
|
92
|
+
5. **Small functions**: functions over ~20 lines are a smell; extract named sub-steps. Where the linter measures cognitive complexity, every `&&`, `||`, `??`, ternary and optional chain counts; extracting a named helper gives it its own budget, extracting a variable does not.
|
|
93
|
+
6. **Dependency direction**: pure utilities never import side-effectful modules. Arrows point inward.
|
|
94
|
+
7. **No flag arguments**: a boolean that switches behaviour → two functions.
|
|
95
|
+
8. **Error surface**: handle only errors you can meaningfully recover from; let the rest propagate.
|
|
96
|
+
|
|
97
|
+
Apply these checks to **your own output** before presenting it, and fix violations right away.
|
|
98
|
+
|
|
99
|
+
## Generators
|
|
100
|
+
|
|
101
|
+
Before editing a file that might be generated:
|
|
102
|
+
|
|
103
|
+
1. **Check `package.json` scripts** for `gen`, `generate`, `codegen`.
|
|
104
|
+
2. **Run the generator** instead of editing by hand; a generated file is overwritten on the next run.
|
|
105
|
+
|
|
106
|
+
## Validation & Types
|
|
107
|
+
|
|
108
|
+
- Zod for schemas, with types inferred via `z.infer`.
|
|
109
|
+
- Validation lives once, at the boundary, not again in every layer.
|
|
110
|
+
- Strict typing; `unknown` and a parse over `any`.
|
|
111
|
+
- Zod v4 top-level API: `z.uuid()`, `z.email()`, `z.url()`, `z.iso.datetime()`, `.loose()` — not the deprecated v3 chains (`z.string().uuid()`, `.passthrough()`).
|
|
112
|
+
|
|
113
|
+
### A fixed vocabulary is a typed constant
|
|
114
|
+
|
|
115
|
+
Roles, modes, statuses, channels: every closed set of values has **one** `as const` object next to its type, and every comparison, option id and schema literal reads from it.
|
|
116
|
+
|
|
117
|
+
TypeScript already checks a literal against a union inside the core. The constant earns its place at the **seams where a `string` arrives**: a query parameter, a form option id, a row read from the database. There a literal comparison is unchecked, and its usual companion — `mode === 'per_km' ? 'per_km' : 'tiers'` — maps every unknown value to a default in silence.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// ✅ the constant is the vocabulary; a guard narrows the string, an unknown value is refused
|
|
121
|
+
export const FEE_MODE = { tiers: 'tiers', perKm: 'per_km' } as const
|
|
122
|
+
export type FeeMode = (typeof FEE_MODE)[keyof typeof FEE_MODE]
|
|
123
|
+
export const isFeeMode = (value: string): value is FeeMode =>
|
|
124
|
+
(Object.values(FEE_MODE) as string[]).includes(value)
|
|
125
|
+
|
|
126
|
+
if (isFeeMode(id)) handleChange(switchMode(form, id))
|
|
127
|
+
|
|
128
|
+
// ❌ unchecked literal at the seam, and a default nobody chose
|
|
129
|
+
mode: id === 'per_km' ? 'per_km' : 'tiers'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Derive the schema from the constant (`z.literal(FEE_MODE.perKm)`), never the other way round, so the constant, the type and the parser cannot disagree. Test fixtures may spell the literal.
|
|
133
|
+
|
|
134
|
+
## Database Writes
|
|
135
|
+
|
|
136
|
+
### Never `upsert` on a generated natural key
|
|
137
|
+
|
|
138
|
+
`upsert(row, { onConflict: '<natural key>' })` means "write into whichever row already holds that key" — and when the key was generated rather than supplied by the caller, that row belongs to someone else.
|
|
139
|
+
|
|
140
|
+
The lesson behind this rule: order numbers drawn from a truncated clock collided for orders created within the same second, and an upsert on that number merged two customers' orders into one row — one name, both carts, the other order gone. The unique index was in place the whole time; the upsert asked it not to complain.
|
|
141
|
+
|
|
142
|
+
- **Create and update are separate statements.** `insert` for new rows, `update … where key = ?` for known ones.
|
|
143
|
+
- **Scope an update by tenant as well as by key**, so a key replayed from a URL cannot reach another tenant's row.
|
|
144
|
+
- **An update that matches nothing is an error, not an insert.** Silently creating a row from an unmatched identifier turns a query parameter into a write primitive.
|
|
145
|
+
- An upsert stays legitimate where the conflict target is a key the **caller owns** and the merge is the intent (a client-generated id, a per-user settings row).
|
|
146
|
+
|
|
147
|
+
### A pre-flight existence check is not uniqueness
|
|
148
|
+
|
|
149
|
+
`SELECT count(*) WHERE key = ?` followed by an insert leaves a gap between two statements, and concurrent writers sit in it. **The unique index is the guarantee: insert against it and retry on the unique-violation error.** When retrying, **name the column** — a table with several unique indexes will otherwise answer a duplicate in another column by burning fresh values until the attempts run out.
|
|
150
|
+
|
|
151
|
+
### Generated identifiers carry entropy, never a clock
|
|
152
|
+
|
|
153
|
+
A value drawn from the clock is a bucket, not an identifier, and truncating it widens the bucket. Draw from `crypto.getRandomValues`, keep the drawing in a **pure function** that takes the random values as an argument, and unit-test it. A generator whose only seam is a server function with a database call in it is a generator no test reaches.
|
|
154
|
+
|
|
155
|
+
## External Measurements
|
|
156
|
+
|
|
157
|
+
### Normalised to whole units at the adapter, like money to cents
|
|
158
|
+
|
|
159
|
+
A routing provider answers in fractional metres and seconds, a payment provider in decimal currency. The adapter that receives the value rounds it **once**, to the unit the domain counts in (whole metres, whole seconds, whole cents), and nothing past the adapter rounds again. Two callers rounding for themselves round differently, or one forgets — and a billing boundary sits between them. The domain core may keep a defensive rounding as a guarantee against callers that are not the adapter; the adapter is where the rounding is decided, and its contract says so.
|