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

This commit is contained in:
upstream-mirror
2026-09-18 01:48:50 +05:00
commit 93ecc1a6f8
787 changed files with 75240 additions and 0 deletions

View 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.

View 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? |

View 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

View 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.

View 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`.

View 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.

View 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.

View 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.

View 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
View 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.

View 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.

View 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.

View 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
}
}

View 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.

View 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.