snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
This commit is contained in:
48
.claude/rules/agent-output-format.md
Normal file
48
.claude/rules/agent-output-format.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
paths: []
|
||||
---
|
||||
|
||||
# Agent output format
|
||||
|
||||
All ticket agents are spawned one-shot (in the background) by the chat orchestrator. Each agent's last line of output is parsed by regex against an output contract. Anything else in the body is informational and ignored by the orchestrator's parser.
|
||||
|
||||
The developer and quality-reviewer read their ticket from `${TICKET_FILE}` (an absolute path passed in the spawn prompt). The merger additionally receives `${TICKETS_DIR}` (the per-session directory holding all ticket JSON files) so it can update the ticket's `status` field after a successful merge.
|
||||
|
||||
## Reviewer agent (quality-reviewer)
|
||||
|
||||
Last line of output MUST be exactly one of:
|
||||
|
||||
- `APPROVED`
|
||||
- `REJECTED: <feedback>` — `<feedback>` is a bulleted list (one bullet per issue) the developer must address on retry. Be specific: file path + symptom + what to change.
|
||||
|
||||
Anything above the contract line is informational and discarded by the orchestrator. Warning-level findings that do not require a retry should not be emitted — write `APPROVED` and stop.
|
||||
|
||||
Any other shape is treated by the orchestrator as `REJECTED: <malformed reviewer output>`.
|
||||
|
||||
(The single-shot `migration-review` reviewer keeps its own `APPROVED` / `BLOCKED:` text contract — see quality-reviewer.md. The orchestrator parses that in the migration round, not with the wave parser above.)
|
||||
|
||||
## Developer agent
|
||||
|
||||
Last line of output MUST be exactly one of:
|
||||
|
||||
- `DONE: branch=<branch_name> commit=<short_sha> files=[<comma-separated paths>]`
|
||||
- `FAILED: <one-line reason>`
|
||||
|
||||
When the spawn prompt includes a `RETRY_FEEDBACK=...` block, the developer is on a retry attempt: the worktree and previous commits already exist; apply targeted fixes and commit additively. The final line is the new HEAD commit sha.
|
||||
|
||||
Any other shape is treated by the orchestrator as `FAILED`.
|
||||
|
||||
(The two single-shot skill-driven developer dispatches define their OWN contract in their skill — these supersede the line above for that dispatch only: `writing-migrations` emits `DONE: branch=<short>/simple migration=<filename> summary=<...>` or `NO_MIGRATION_NEEDED`; `resolving-rollback-conflicts` emits `DONE: branch=<short>/simple files=[<...>]`. The orchestrator parses these in their respective states, PD-MIG-DEV / STATE RB-DEV.)
|
||||
|
||||
## Merger agent
|
||||
|
||||
Last line of output MUST be exactly one of:
|
||||
|
||||
- `DONE: <TASK_ID> commit=<short_sha>` — `<TASK_ID>` matches the spawn prompt (e.g. `TASK-003`, or the literal `MIGRATION` / `ROLLBACK` / `PROMOTE`).
|
||||
- `FAILED: <TASK_ID> <one-line reason>`
|
||||
|
||||
Any other shape is treated by the orchestrator as `FAILED: <TASK_ID> <malformed merger output>`.
|
||||
|
||||
## Status updates (wave flow only)
|
||||
|
||||
The merger updates `${TICKETS_DIR}/TASK-XXX.json` `status` to `merged` after a successful merge. The developer does not write the ticket file — its `DONE: ...` line is sufficient for the orchestrator.
|
||||
20
.claude/rules/ask-state-cartouche.md
Normal file
20
.claude/rules/ask-state-cartouche.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# ask-state cartouche
|
||||
|
||||
The chat-service shows a yes/no cartouche when the orchestrator writes this file.
|
||||
**Only the orchestrator writes it** never (simple-)developer (worktree scope).
|
||||
|
||||
`<session_dir>` is `TICKETS_DIR` (e.g. `/chat-service/logs/<uuid>`). Write **valid JSON** (quoted, escaped values), in the same turn as the question, all values in the user's language except `kind`:
|
||||
|
||||
```
|
||||
Write("<session_dir>/ask-state.json",
|
||||
'{"kind":"satisfaction","header":"Preview ready","body":"Everything is visible in the demo but hasn'\''t been saved to your data yet. Happy with the result?","yes":"Yes, save the changes","no":"No, I want to adjust something"}')
|
||||
```
|
||||
|
||||
`header` ≤ 30 chars; `body` one plain sentence (no "database"/"migration"/"Supabase").
|
||||
|
||||
`kind` picks the question (not interchangeable):
|
||||
|
||||
| `kind` | When | Asks |
|
||||
|---|---|---|
|
||||
| `satisfaction` | DB-impacting change, before migration (S-DONE non-empty, PD-ASK) | save the preview to your data? |
|
||||
| `live-switch` | demo mode, after migration (PD-LIVE-ASK) | switch the app to your real data? |
|
||||
71
.claude/rules/coding-style.md
Normal file
71
.claude/rules/coding-style.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
paths: []
|
||||
---
|
||||
|
||||
# Coding Style
|
||||
|
||||
## Immutability (critical)
|
||||
|
||||
Always create new objects, never mutate existing ones:
|
||||
|
||||
- Wrong: modify original object in-place
|
||||
- Correct: return a new copy with the change applied
|
||||
|
||||
Rationale: prevents hidden side effects, makes debugging easier,
|
||||
enables safe concurrency.
|
||||
|
||||
## Core principles
|
||||
|
||||
KISS — prefer the simplest solution that works. Optimize for clarity
|
||||
over cleverness.
|
||||
|
||||
DRY — extract repeated logic into shared functions. Introduce
|
||||
abstractions when repetition is real, not speculative.
|
||||
|
||||
YAGNI — do not build features before they are needed. Start simple,
|
||||
refactor when the pressure is real.
|
||||
|
||||
Scope discipline: implement exactly what the task asks, no more. An
|
||||
adjacent problem you notice (a nearby bug, a tempting refactor, missing
|
||||
tests elsewhere) is REPORTED, never fixed in the same change. Never expand
|
||||
scope silently.
|
||||
|
||||
## File organization
|
||||
|
||||
Many small files over few large files:
|
||||
- 200-400 lines typical, 800 max
|
||||
- High cohesion, low coupling
|
||||
- Organize by feature/domain, not by type
|
||||
- Extract utilities from large modules
|
||||
- Grow the file *count*, not the file: when an edit would push a file past
|
||||
the ~400-line typical ceiling, extract a new focused module instead of
|
||||
appending to the existing one
|
||||
|
||||
## Error handling
|
||||
|
||||
- Handle errors explicitly at every level
|
||||
- User-facing code: provide friendly error messages
|
||||
- Server-side: log detailed error context
|
||||
- Never silently swallow errors
|
||||
|
||||
## Input validation
|
||||
|
||||
- Validate all user input before processing
|
||||
- Use schema-based validation where available
|
||||
- Fail fast with clear error messages
|
||||
- Never trust external data (API responses, user input, file content)
|
||||
|
||||
## Naming conventions
|
||||
|
||||
- Variables and functions: camelCase
|
||||
- Booleans: is, has, should, or can prefix
|
||||
- Interfaces, types, components: PascalCase
|
||||
- Constants: UPPER_SNAKE_CASE
|
||||
- Custom hooks: camelCase with use prefix
|
||||
|
||||
## Code smells to avoid
|
||||
|
||||
- Deep nesting (>4 levels) — use early returns
|
||||
- Magic numbers — use named constants
|
||||
- Long functions (>50 lines) — split into focused pieces
|
||||
- Large files (>800 lines) — extract modules
|
||||
25
.claude/rules/dependency-safety.md
Normal file
25
.claude/rules/dependency-safety.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Dependency safety
|
||||
|
||||
Adding a new package is a supply-chain decision, so agents never do it silently.
|
||||
|
||||
## Rule
|
||||
|
||||
- New-package installs are denied by permissions (`settings.json` -> `permissions.deny`:
|
||||
`Bash(npm install *)`, `Bash(npm i *)`, `Bash(pnpm add *)`, `Bash(yarn add *)`). A
|
||||
deny is ~100% respected; a CLAUDE.md rule or a post-action warning is not.
|
||||
- Bare `npm install` / `pnpm install` / `yarn install` (no package argument, restoring
|
||||
the lockfile) stays allowed: the literal space in the deny glob requires an argument,
|
||||
so an argument-less install is not matched.
|
||||
- To add a package, the human validates THAT specific package first: minimum 21 days
|
||||
since publication, healthy download/maintainer count, no open advisory
|
||||
(`npm audit`, Snyk).
|
||||
|
||||
## Third-party skills and MCP servers
|
||||
|
||||
- Treat a downloaded skill/plugin (marketplace or external repo) like an npm dependency:
|
||||
the same audits apply (they have shipped hardcoded secrets and malicious payloads).
|
||||
- Pin an MCP server's version once validated; do not let it auto-update without a fresh
|
||||
human check.
|
||||
|
||||
Automatic blocking reduces risk, it does not remove it: a periodic security review is
|
||||
still required for anything touching auth, payments, or sensitive data.
|
||||
40
.claude/rules/english-only.md
Normal file
40
.claude/rules/english-only.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# English-only in committed files
|
||||
|
||||
All content written into the repository is in **English**, regardless of
|
||||
the conversational language in the chat. This applies to:
|
||||
|
||||
- Source code: variable / function / class names, identifiers
|
||||
- Comments and docstrings
|
||||
- Markdown docs, agent prompts, skills, rules, hooks
|
||||
- Commit messages, PR titles, PR bodies
|
||||
- Hook output strings, log strings, error messages
|
||||
- Templates and example placeholders inside agent prompts
|
||||
- Configuration keys (JSON / YAML keys), default config values
|
||||
|
||||
The only place where another language may appear in a committed file is a
|
||||
**runtime data value** that legitimately reflects the user's domain (e.g.
|
||||
the user's chosen `industry: "boulangerie"` or `entity name: "facture"`
|
||||
inside `project-context.json` or fake-data fixtures). Even then, prefer
|
||||
English when it doesn't change the semantics.
|
||||
|
||||
## Speaking another language at runtime
|
||||
|
||||
Agents that converse with the user (orchestrator, project-manager,
|
||||
etc.) MUST reply in the user's language at runtime.
|
||||
Achieve this with English instructions plus a directive like *"reply in
|
||||
the user's language"* — never by hard-coding non-English templates inside
|
||||
the agent prompt.
|
||||
|
||||
English template inside the prompt:
|
||||
> *"Project scoping started…"*
|
||||
|
||||
Then the agent translates at runtime to French / Spanish / Japanese / etc.
|
||||
based on the user's most recent message.
|
||||
|
||||
## Quick checklist before `Write` / `Edit` / `git commit`
|
||||
|
||||
1. Scan the new content for accents (é, à, ç, ñ, ü, …) and non-English
|
||||
words (je, tu, le, la, voici, equipo, scrivere, …).
|
||||
2. If any found, rewrite in English (keep runtime-translation directives
|
||||
if user-facing strings are involved).
|
||||
3. Commit messages: English imperative, e.g. `Add SETUP path to orchestrator`.
|
||||
40
.claude/rules/git-policy.md
Normal file
40
.claude/rules/git-policy.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*"
|
||||
---
|
||||
|
||||
# Git policy for agents
|
||||
|
||||
The user owns the REMOTE and the promotion of shared history. Agents never push,
|
||||
pull, or force-push. What an agent MAY do LOCALLY depends on its role and is scoped
|
||||
to its own worktree / branch.
|
||||
|
||||
## Developer / simple-developer - commit your OWN task branch (required)
|
||||
|
||||
Building your task branch IS the mechanism the reviewer reads and the merger merges,
|
||||
so you MUST commit. Allowed, scoped to `<WORKTREE_PATH>` on `BRANCH_NAME`:
|
||||
|
||||
- `git add`, `git commit` - atomic commits, subject `feat(TASK-XXX):` / `fix(TASK-XXX):`
|
||||
- `git rebase session/<SESSION_SHORT_ID>` - rebase your OWN branch onto the session
|
||||
branch (NEVER onto the base branch: that pulls other sessions' work into yours)
|
||||
- `git status` / `git diff` / `git log` / `git branch` / `git stash`
|
||||
|
||||
Not allowed: committing on the base branch / `session/<id>` / `main`; `git push` /
|
||||
`git pull`; force-push; `git reset --hard` onto a shared ref.
|
||||
|
||||
## Merger - the ONLY agent that mutates shared branches
|
||||
|
||||
Merging task branches into `session/<id>`, promoting `session/<id>` into the base
|
||||
branch under `.promote.lock`, and the promotion-lock command set in MIGRATION / PROMOTE
|
||||
mode (including the destructive git ops the lock needs) ARE its job. See `merger.md`.
|
||||
|
||||
## quality-reviewer / planner / orchestrator / documentator - read-only git
|
||||
|
||||
`git diff` / `status` / `log` / `branch --show-current` / `git fetch` (read) /
|
||||
`git stash`. No `commit` / `rebase` / `merge` / `push` / `pull` / `reset` / branch
|
||||
`checkout`.
|
||||
|
||||
## Never (any role)
|
||||
|
||||
`git push`, `git pull`, force-push, or any write to the remote. The user owns the
|
||||
remote and the base branch.
|
||||
30
.claude/rules/launcher-interface.md
Normal file
30
.claude/rules/launcher-interface.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Launcher interface
|
||||
|
||||
A "managed launcher" is an external surface that drives the harness (CRM
|
||||
Builder's chat-service is the reference launcher). The harness core stays neutral:
|
||||
every launcher-specific fact is an extension point under `config.launcher` in
|
||||
`harness.config.json`, and each consuming hook is INERT when its point is unset.
|
||||
CRM Builder / chat-service specifics live in Atomic CRM's project layer (its
|
||||
`harness.config.json`), never in a core hook.
|
||||
|
||||
## Extension points (`config.launcher`)
|
||||
|
||||
| Key | Meaning | Consumer | Unset behavior |
|
||||
|---|---|---|---|
|
||||
| `sessionDirEnv` | Env var carrying the managed session dir (default `CHAT_SESSION_DIR`) | `context.mjs`, `reviews.mjs`, `session-bootstrap.mjs` read this env directly | falls back to the recomputed `/tmp/<repo>/<id>` session dir |
|
||||
| `turnSentinelDir` | Dir where `turn-complete.mjs` drops `pty-turn-done-<sid>` so the launcher knows the turn ended | `turn-complete.mjs` | hook writes nothing (inert) |
|
||||
| `postCheckoutScript` | Script the merger runs after checkout to materialize the app variant | `merger.md` (runs it), `block-orchestrator-merge.mjs` (gates it merger-only) | not run; the guard clause is inert |
|
||||
| `logsDir` | Managed-launcher log dir agents may redirect into | `bash-guard.mjs` (redirect exemption) | only `/dev/null` is exempt |
|
||||
|
||||
`CHAT_SESSION_DIR` is kept as the generic "managed session dir" variable name:
|
||||
it is set by the launcher, not by the core. Persona injection stays the
|
||||
launcher's job too, via `--append-system-prompt` (the web-chat orchestrator is
|
||||
this same `orchestrator` with a non-technical persona layered on at launch); the
|
||||
core never hardcodes a persona.
|
||||
|
||||
## Adding a launcher
|
||||
|
||||
Set the four keys under `config.launcher` in your project's `harness.config.json`.
|
||||
Leave a key unset (or the whole `launcher` block absent) and the matching hook
|
||||
stays inert, so a project with no managed launcher runs the harness with zero
|
||||
launcher coupling.
|
||||
82
.claude/rules/lsp-usage.md
Normal file
82
.claude/rules/lsp-usage.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# LSP — use semantic code intelligence when possible
|
||||
|
||||
Applies to: developer, quality-reviewer, planner.
|
||||
|
||||
This repo has an `LSP` tool backed by a TypeScript language server (configured in
|
||||
`.claude/settings.json`, covering `.ts`, `.tsx`, `.mjs`, `.js`, and `.jsx`). Use it
|
||||
for any **semantic** question about TypeScript/JavaScript code. It resolves symbols
|
||||
through the type system, so it is exact where text search only guesses.
|
||||
|
||||
## The reflex to break: `grep`/`rg` in Bash to find a symbol
|
||||
|
||||
In practice the `Grep` tool is rarely used — code search here happens through
|
||||
`grep`/`rg` **inside `Bash`** (`grep -rn "DealStage" src/`). That reflex is the
|
||||
thing to stop for symbol questions. `grep` matches strings: it can't tell a
|
||||
definition from a comment, a type from a same-named variable, or one `handleSubmit`
|
||||
from another, and it silently misses re-exports and aliased imports. The `LSP` tool
|
||||
resolves the actual symbol.
|
||||
|
||||
**Hard rule:** to find where a TypeScript identifier (a PascalCase type/component,
|
||||
a camelCase function/hook, an exported const) is **defined** or **used** in
|
||||
`.ts/.tsx/.js/.jsx`, call `LSP` — never `grep -rn "<Symbol>" src/` in Bash and never
|
||||
the `Grep` tool. Reach for LSP first whenever the question is "where / what / who",
|
||||
not "which files contain this string".
|
||||
|
||||
| Instead of this bash-grep… | …use this LSP call |
|
||||
|---|---|
|
||||
| `grep -rn "DealStage" src/` (every use of a type) | `findReferences` (impact/blast radius) |
|
||||
| `grep -rn "mergeContacts" src/` (where defined) | `goToDefinition` |
|
||||
| `grep -rn "LatestNotes\|useContactImport" src/` (locate a symbol) | `workspaceSymbol` |
|
||||
| `grep -n "export" file.tsx` (what a file exports) | `documentSymbol` |
|
||||
| `grep -rn "buildActivityLog" src/` (who calls it) | `incomingCalls` |
|
||||
|
||||
| Operation | Use it to |
|
||||
|---|---|
|
||||
| `goToDefinition` | jump to where a symbol is declared (type, function, component, prop) |
|
||||
| `findReferences` | find every real use of a symbol before changing or renaming it — impact analysis |
|
||||
| `hover` | get a symbol's resolved type / signature / JSDoc without opening its file |
|
||||
| `documentSymbol` | outline a file's exports/functions/components before reading it whole |
|
||||
| `workspaceSymbol` | locate a symbol by name across the repo (`query` required) — faster than grepping for a file path |
|
||||
| `goToImplementation` | find concrete implementations of an interface or abstract member |
|
||||
| `prepareCallHierarchy` | get the call-hierarchy anchor at a position (precedes incoming/outgoing calls) |
|
||||
| `incomingCalls` / `outgoingCalls` | trace the call graph into / out of a function |
|
||||
|
||||
Positions are **1-based** (`line`, `character`), exactly as shown in `Read` output
|
||||
and editors.
|
||||
|
||||
## Per-agent fit
|
||||
|
||||
- **developer** — before editing a symbol's signature,
|
||||
`findReferences` to size the blast radius; `hover` to confirm a type;
|
||||
`goToDefinition` to reach the source. Do not `grep -rn "<Symbol>" src/` in Bash
|
||||
to answer these — that is the exact reflex this rule replaces.
|
||||
- **quality-reviewer** — `findReferences` / `incomingCalls` to check that every
|
||||
call site of a changed function is handled; `goToDefinition` to confirm a type
|
||||
is what the diff assumes; `findReferences` / `workspaceSymbol` to confirm a new
|
||||
component or resource is actually wired into the app (imported, registered),
|
||||
not merely created.
|
||||
- **planner** — `workspaceSymbol` to locate probable `files_to_modify` faster than
|
||||
grep. Still light discovery — no deep reading.
|
||||
|
||||
## When `grep`/`rg` IS the right tool
|
||||
|
||||
LSP is for TS/JS symbols. Keep using `grep`/`rg` (in Bash) — and do not try LSP — for:
|
||||
|
||||
- **Non-TS/JS files**: `.sql`, `.md`, `.json`, `.css` — no server covers them.
|
||||
- **Text / domain-word sweeps** that deliberately include strings, comments, and
|
||||
non-code: e.g. deleting a resource with `grep -rniE "\bdeals?\b|deal_notes?"`,
|
||||
which must catch SQL, fixtures, and labels — not just the TS symbol.
|
||||
- **Database identifiers** (column / view names like `company_id`,
|
||||
`contacts_summary`) that live in SQL and string literals, not as TS symbols.
|
||||
- Plain "which files mention 'boulangerie'" lookups.
|
||||
|
||||
LSP is **read-only code intelligence, not a validation command** — it does not run
|
||||
`tsc`, so it is exempt from `validation-commands.md` and you may use it freely. It
|
||||
does not replace the hook-run typecheck.
|
||||
|
||||
## Worktree note
|
||||
|
||||
Pass **absolute paths inside your own worktree** (`<WORKTREE_PATH>/src/...`), per
|
||||
`worktree-scope.md` — never the base-branch checkout under `$CLAUDE_PROJECT_DIR`.
|
||||
Fall back to `grep` / `Read` only when LSP returns an actual error or empty result
|
||||
for a worktree file — not as the default. Do not block on it.
|
||||
19
.claude/rules/security-triggers.md
Normal file
19
.claude/rules/security-triggers.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
paths: []
|
||||
---
|
||||
|
||||
# Security Review Triggers
|
||||
|
||||
Always dispatch SECURITY-REVIEWER when the diff touches any of:
|
||||
|
||||
- Authentication or authorization code
|
||||
- User input handling (forms, URL params, request body)
|
||||
- Database queries or migrations
|
||||
- File system operations
|
||||
- External API calls
|
||||
- Cryptographic operations
|
||||
- Payment or financial code
|
||||
- Supabase RLS policies
|
||||
|
||||
When in doubt: dispatch SECURITY-REVIEWER. False positives are cheap,
|
||||
missed vulnerabilities are not.
|
||||
79
.claude/rules/testing.md
Normal file
79
.claude/rules/testing.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
paths: []
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
## Minimum coverage: 80%
|
||||
|
||||
New code paths without tests are a blocking issue in code review. But coverage is a
|
||||
FLOOR, not a target: never write a test just to reach the number (that is what produces
|
||||
filler tests). Pertinence outranks the percentage.
|
||||
|
||||
## Pertinence: test behavior, not implementation
|
||||
|
||||
- Assert user-observable behavior / rendered output (Testing Library principle: "the more
|
||||
your tests resemble the way the software is used, the more confidence they give"). Never
|
||||
assert internal state, props, private methods, or merely that a mock was called.
|
||||
- Weight toward integration for this React app (the Testing Trophy): a component with its
|
||||
real collaborators over many shallow unit tests. Never test the framework or a third-party
|
||||
library.
|
||||
- Delete a test ONLY for lack of intrinsic value (cannot name a realistic bug it would catch,
|
||||
tautological, implementation-detail, snapshot-only, redundant with a better test), NEVER
|
||||
because "the code works now" or to cap the count: a passing test exists to guard FUTURE
|
||||
regressions. Shrink the suite by CONSOLIDATING redundancy, not by deleting coverage.
|
||||
- Forbidden (blocking in review): asserting a mock returns its configured value; a snapshot
|
||||
as the primary assertion; reading component state / props / internals; a test written only
|
||||
to raise the coverage number.
|
||||
|
||||
## TDD orientation: acceptance-test-driven
|
||||
|
||||
- Derive each test from the ticket's `acceptance_criteria`; every test should map to a
|
||||
criterion or a user-visible behavior. A test with no traceable criterion is filler.
|
||||
- Enforce the OUTCOME (behavior tests present and green), not literal red-green ordering
|
||||
(unverifiable when one agent writes both the test and the code).
|
||||
|
||||
## Test structure — AAA pattern
|
||||
|
||||
Prefer Arrange-Act-Assert for all tests:
|
||||
|
||||
test('calculates similarity correctly', () => {
|
||||
// Arrange
|
||||
const input = buildInput()
|
||||
|
||||
// Act
|
||||
const result = compute(input)
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(expected)
|
||||
})
|
||||
|
||||
## Test naming
|
||||
|
||||
Use descriptive names that explain the behavior under test:
|
||||
|
||||
- returns empty array when no items match the query
|
||||
- throws error when required field is missing
|
||||
- falls back to default value when API is unavailable
|
||||
|
||||
Avoid: test1, testFoo, should work.
|
||||
|
||||
## Test isolation
|
||||
|
||||
Each test must be independent — no shared mutable state.
|
||||
No test should depend on the execution order of other tests.
|
||||
Reset mocks and fixtures in beforeEach, not globally.
|
||||
|
||||
## E2E shape (Playwright)
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('ticket list loads', async ({ page }) => {
|
||||
await page.goto('/tickets')
|
||||
await expect(page.locator('h1')).toBeVisible()
|
||||
})
|
||||
|
||||
Never use timeout-based assertions (waitForTimeout).
|
||||
Always prefer deterministic waits: waitForResponse, waitForSelector,
|
||||
expect(locator).toBeVisible(). Flaky tests must be quarantined with
|
||||
test.fixme() and a tracking reference before merge.
|
||||
91
.claude/rules/typescript.md
Normal file
91
.claude/rules/typescript.md
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.ts"
|
||||
- "**/*.tsx"
|
||||
- "**/*.js"
|
||||
- "**/*.jsx"
|
||||
---
|
||||
|
||||
# TypeScript / JavaScript
|
||||
|
||||
## Types and interfaces
|
||||
|
||||
Use interface for object shapes that may be extended or implemented.
|
||||
Use type for unions, intersections, tuples, and utility types.
|
||||
Prefer string literal unions over enum unless interoperability requires it.
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
email: string
|
||||
}
|
||||
|
||||
type UserRole = 'admin' | 'member'
|
||||
type UserWithRole = User & { role: UserRole }
|
||||
|
||||
Add explicit parameter and return types to exported functions and public
|
||||
class methods. Let TypeScript infer obvious local variable types.
|
||||
|
||||
## Avoid any
|
||||
|
||||
Never use any in application code.
|
||||
Use unknown for external or untrusted input, then narrow safely.
|
||||
Use generics when a value's type depends on the caller.
|
||||
|
||||
// Wrong
|
||||
function getErrorMessage(error: any) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
// Correct
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) return error.message
|
||||
return 'Unexpected error'
|
||||
}
|
||||
|
||||
## React props
|
||||
|
||||
Define component props with a named interface or type.
|
||||
Type callback props explicitly.
|
||||
Do not use React.FC unless there is a specific reason.
|
||||
|
||||
interface UserCardProps {
|
||||
user: User
|
||||
onSelect: (id: string) => void
|
||||
}
|
||||
|
||||
function UserCard({ user, onSelect }: UserCardProps) {
|
||||
return <button onClick={() => onSelect(user.id)}>{user.email}</button>
|
||||
}
|
||||
|
||||
## Input validation with Zod
|
||||
|
||||
Use Zod for schema-based validation and infer types from the schema.
|
||||
Do not duplicate type definitions — infer from the schema.
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
const userSchema = z.object({
|
||||
email: z.string().email(),
|
||||
age: z.number().int().min(0).max(150)
|
||||
})
|
||||
|
||||
type UserInput = z.infer<typeof userSchema>
|
||||
const validated: UserInput = userSchema.parse(input)
|
||||
|
||||
## Error handling
|
||||
|
||||
Use async/await with try-catch. Always narrow unknown errors before
|
||||
accessing properties.
|
||||
|
||||
async function loadUser(userId: string): Promise<User> {
|
||||
try {
|
||||
return await riskyOperation(userId)
|
||||
} catch (error: unknown) {
|
||||
logger.error('Operation failed', error)
|
||||
throw new Error(getErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
## console.log
|
||||
|
||||
No console.log in production code. Use a proper logging library.
|
||||
40
.claude/rules/validation-commands.md
Normal file
40
.claude/rules/validation-commands.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
paths: []
|
||||
---
|
||||
|
||||
# Validation commands: DO NOT RUN
|
||||
|
||||
Validation is automated and config-driven. The steps live in ONE place,
|
||||
`harness.config.json` -> `validation.steps`, consumed by both the runner and the
|
||||
guard, so they can never drift:
|
||||
|
||||
- `validate-on-stop.mjs` (SubagentStop) runs the whole chain after every
|
||||
developer / test-writer stop: the format step (prettier auto-fix + commit),
|
||||
typecheck, lint (eslint, scoped to the stop's changed files), the unit steps,
|
||||
then e2e once in the repo (full mode only). A failed step rejects the stop; the
|
||||
agent's internal loop fixes it and only a green stop returns control to the
|
||||
orchestrator.
|
||||
- `bash-guard.mjs` (PreToolUse Bash) blocks `developer` / `quality-reviewer` from
|
||||
running those same commands manually, plus `validation.extraForbidden` (build).
|
||||
The forbidden set is DERIVED from `validation.steps`, not hardcoded here.
|
||||
|
||||
## Why blocked (developer / quality-reviewer)
|
||||
|
||||
- Burns tool budget: each manual call the hook already runs is wasted.
|
||||
- Can hang: `npx vitest` launches a headed Chromium; without a display it waits
|
||||
forever. The hooks set `CI=true` to force `chromium-headless-shell`; manual
|
||||
calls do not.
|
||||
- Duplicates hook work: the validation hooks already run these; failures come back
|
||||
on stderr.
|
||||
|
||||
To change what runs (or what is forbidden), edit `harness.config.json`, never a
|
||||
command string in a hook or in this file.
|
||||
|
||||
## What to do instead
|
||||
|
||||
- **Developer / test-writer**: after implementation + commit, stop. The hooks
|
||||
run and inject any failures via stderr. Fix and commit again next turn. Do not
|
||||
run the merge yourself: that is the merger's job.
|
||||
- **Reviewers**: focus on what hooks can't check (semantic review, integration
|
||||
wiring, e2e spec presence). To verify TypeScript, `Read` the source, do not run
|
||||
the compiler.
|
||||
63
.claude/rules/web-patterns.md
Normal file
63
.claude/rules/web-patterns.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.tsx"
|
||||
- "**/*.ts"
|
||||
---
|
||||
|
||||
# Web Patterns
|
||||
|
||||
## State management
|
||||
|
||||
Treat these concerns separately — do not mix them:
|
||||
|
||||
Server state → TanStack Query, SWR, or tRPC
|
||||
Client state → Zustand, Jotai, or signals
|
||||
URL state → search params, route segments
|
||||
Form state → React Hook Form or equivalent
|
||||
|
||||
Do not duplicate server state into client stores.
|
||||
Derive values instead of storing redundant computed state.
|
||||
|
||||
## URL as state
|
||||
|
||||
Persist shareable state in the URL: filters, sort order, pagination,
|
||||
active tab, search query. If refreshing the page should restore the
|
||||
state, it belongs in the URL.
|
||||
|
||||
## Component composition
|
||||
|
||||
Container components own data loading and side effects.
|
||||
Presentational components receive props and render UI — keep them pure.
|
||||
|
||||
Use compound components when related UI shares state and interaction
|
||||
semantics (parent owns state, children consume via context).
|
||||
Prefer this over prop drilling for complex widgets.
|
||||
|
||||
## Data fetching
|
||||
|
||||
Fetch independent data in parallel — avoid parent-child request waterfalls.
|
||||
|
||||
For optimistic updates:
|
||||
1. Snapshot current state
|
||||
2. Apply optimistic update immediately
|
||||
3. Roll back on failure
|
||||
4. Emit visible error feedback when rolling back
|
||||
|
||||
Prefer stale-while-revalidate: return cached data immediately,
|
||||
revalidate in the background. Use existing libraries rather than
|
||||
rolling this by hand.
|
||||
|
||||
## API response format
|
||||
|
||||
Use a consistent envelope for all API responses:
|
||||
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
meta?: {
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}
|
||||
}
|
||||
55
.claude/rules/web-security.md
Normal file
55
.claude/rules/web-security.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.tsx"
|
||||
- "**/*.ts"
|
||||
- "**/*.html"
|
||||
---
|
||||
|
||||
# Web Security
|
||||
|
||||
## Required HTTP headers
|
||||
|
||||
Every production response must include:
|
||||
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: DENY
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||
|
||||
## XSS prevention
|
||||
|
||||
Never inject unsanitized HTML.
|
||||
Avoid innerHTML and dangerouslySetInnerHTML unless sanitized first.
|
||||
Escape all dynamic template values.
|
||||
When user-generated HTML is absolutely required, sanitize with a
|
||||
vetted local sanitizer — never trust raw user input.
|
||||
|
||||
## Content Security Policy
|
||||
|
||||
Configure a production CSP. Use a per-request nonce for scripts
|
||||
instead of unsafe-inline.
|
||||
|
||||
Content-Security-Policy:
|
||||
default-src 'self';
|
||||
script-src 'self' 'nonce-{RANDOM}';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: https:;
|
||||
connect-src 'self';
|
||||
frame-src 'none';
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
|
||||
Adjust origins to the actual project. Do not copy this block unchanged.
|
||||
|
||||
## Third-party scripts
|
||||
|
||||
Load asynchronously. Use SRI (Subresource Integrity) when serving
|
||||
from a CDN. Audit third-party dependencies quarterly. Prefer
|
||||
self-hosting for critical dependencies when practical.
|
||||
|
||||
## Forms
|
||||
|
||||
CSRF protection on all state-changing forms.
|
||||
Rate limiting on all submission endpoints.
|
||||
Validate both client-side and server-side — never rely on one alone.
|
||||
79
.claude/rules/worktree-scope.md
Normal file
79
.claude/rules/worktree-scope.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Worktree scope — strict file access for ticket work
|
||||
|
||||
Applies to: developer, quality-reviewer. Any agent dispatched by the orchestrator (background, single-shot) to work on a specific ticket, with a suffixed name (e.g. `developer-TASK-006`).
|
||||
|
||||
Throughout this rule, `$REPO` is the project root (`$CLAUDE_PROJECT_DIR` — wherever the repo is checked out). `<WORKTREE_PATH>` is the absolute worktree path handed to you in your spawn prompt.
|
||||
|
||||
Not applicable to: planner (searches `$REPO/src/` for file discovery), merger (operates in `$REPO` to merge), orchestrator (doesn't touch files), project-manager (operates on `$REPO/docs/project-context.json` directly on main — config only, no code), documentator (writes `$REPO/MEMORY.md` directly on main in Mode 2; never touches application code).
|
||||
|
||||
Developer ADRs follow the standard worktree rule: write `<WORKTREE_PATH>/adr/ADR-<SESSION_SHORT_ID>-<TASK-XXX>-<slug>.md` inside your worktree, commit alongside the implementation, the merger ships it to `$REPO/adr/` like any other change. See `Skill({skill: "adr-writing"})` for the full rules and template.
|
||||
|
||||
## Why
|
||||
|
||||
Each ticket gets its own git worktree under a session-scoped base, `<WORKTREE_BASE>/TASK-XXX/`, where `<WORKTREE_BASE>` is `/tmp/<$REPO with every "/" replaced by "_">/<SESSION_ID>` (session-scoped to prevent stale worktrees from a previous stopped session from interfering). Reading/editing `$REPO/src/...` while you have the same file at `<WORKTREE_PATH>/src/...` is:
|
||||
|
||||
1. Duplicate work — same bytes, twice the token cost
|
||||
2. Incorrect — `$REPO` is on the base branch, missing the ticket's changes
|
||||
3. Dangerous — editing `$REPO/src/App.tsx` pollutes the base branch with changes outside the ticket's scope. This happened in a past session and left 20+ files uncommitted on the base branch.
|
||||
|
||||
## Session-branch topology
|
||||
|
||||
Each session owns an integration branch `session/<SESSION_SHORT_ID>` (forked from the repo's base branch at session start) and a fixed anchor ref `session-base/<SESSION_SHORT_ID>`. `SESSION_SHORT_ID` is the first dash-segment of the full session id. Task worktrees fork from `session/<SESSION_SHORT_ID>`. The merger merges task branches into the session branch inside the dedicated `<WORKTREE_BASE>/_session` worktree, then promotes `session/<SESSION_SHORT_ID>` into the base branch once per request under `$REPO/.promote.lock`.
|
||||
|
||||
- Developers rebase onto `session/<SESSION_SHORT_ID>`, never onto the base branch directly.
|
||||
- The `_session` worktree is the merger's; developers/reviewers never touch it.
|
||||
- Only a `promotion-conflict-resolver` developer may edit `$REPO` on the base branch, and only to resolve a `session->base` merge conflict under the lock.
|
||||
|
||||
## Allowed paths
|
||||
|
||||
| Path prefix | Read | Write/Edit | Bash cwd |
|
||||
|---|---|---|---|
|
||||
| `<WORKTREE_PATH>/**` (i.e. `<WORKTREE_BASE>/TASK-XXX/`) | ✅ | ✅ | ✅ |
|
||||
| `${TICKETS_DIR}/TASK-XXX.json` (per-session folder passed in your prompt) | ✅ (ticket source of truth) | ⚠️ merger writes the `status` field — no other writes | — |
|
||||
| `${TICKETS_DIR}/reviews/<TASK-XXX>-quality-reviewer` (`reviews/` sibling of the ticket file) | ✅ | ⚠️ quality-reviewer ONLY, and ONLY its own ticket's flag: `touch` on APPROVED / `rm -f` on REJECTED (records its verdict; see quality-reviewer.md). No other agent, no other file under `reviews/`. | — |
|
||||
| `$REPO/adr/**` | ✅ (learn from past structural decisions) | ❌ (developer writes ADRs inside the worktree at `<WORKTREE_PATH>/adr/`; the merger ships them to `$REPO/adr/`) | — |
|
||||
| `~/.claude/**` (`$CLAUDE_CONFIG_DIR`) | ✅ (skills, rules) | ❌ | — |
|
||||
|
||||
Everything else under `$REPO/` — `$REPO/src/`, `$REPO/e2e/`, `$REPO/supabase/`, `$REPO/package.json`, `$REPO/*.ts`, `$REPO/*.json` — is **off-limits**. If you need information from these, read the copy inside your worktree.
|
||||
|
||||
## Bash — every call needs `cd`
|
||||
|
||||
Bash tool invocations are **stateless shells**. `cd <WORKTREE_PATH>` in one call does NOT persist to the next — the next call starts again in `$REPO` by default.
|
||||
|
||||
**Mandatory prefix for every Bash when working inside a worktree:**
|
||||
|
||||
```bash
|
||||
cd <WORKTREE_PATH> && <your command>
|
||||
```
|
||||
|
||||
`WORKTREE_PATH` is provided in your spawn prompt (e.g. `/tmp/_home_user_code_atomic-crm/46bc14c5-.../TASK-XXX`).
|
||||
|
||||
## Violation examples
|
||||
|
||||
```
|
||||
Read("$REPO/src/components/atomic-crm/types.ts")
|
||||
```
|
||||
❌ The worktree has this file at `<WORKTREE_PATH>/src/components/atomic-crm/types.ts`. Read there instead.
|
||||
|
||||
```
|
||||
Bash("npm run typecheck")
|
||||
```
|
||||
❌ Runs in `$REPO` (default cwd), not your worktree. Use `Bash("cd <WORKTREE_PATH> && npm run typecheck")`.
|
||||
|
||||
```
|
||||
Edit("$REPO/src/App.tsx", ...)
|
||||
```
|
||||
❌ Never edit inside `$REPO/`. Always your worktree. If `App.tsx` genuinely belongs to the ticket, edit `<WORKTREE_PATH>/src/App.tsx`.
|
||||
|
||||
```
|
||||
Bash("npm run prettier:apply")
|
||||
```
|
||||
❌ No `cd` prefix → runs in `$REPO`, reformats the base branch. Use `Bash("cd <WORKTREE_PATH> && npm run prettier:apply")`.
|
||||
|
||||
## When you genuinely need `$REPO` state
|
||||
|
||||
You (almost) never do. Specific exceptions:
|
||||
- Reading the ticket JSON: `Read("${TICKETS_DIR}/TASK-XXX.json")` — source of truth, read-only. `TICKETS_DIR` is the absolute per-session path passed in your prompt; substitute the literal value.
|
||||
- Reading past ADRs: `Read("$REPO/adr/ADR-<SESSION_SHORT_ID>-<TASK-XXX>-<slug>.md")` (research)
|
||||
|
||||
If you think you need something else from `$REPO/`, stop and flag it to the caller. Do not silently edit `$REPO/` or run commands there.
|
||||
Reference in New Issue
Block a user