tillerhand 3.47.1 → 3.48.0
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/.claude/agents/board-chair.md +9 -9
- package/.claude/agents/code-reviewer.md +2 -2
- package/.claude/agents/council-advisor.md +2 -2
- package/.claude/agents/security-auditor.md +6 -6
- package/.claude/agents/test-writer.md +1 -1
- package/.claude/board/board-ai.md +6 -6
- package/.claude/board/board-api.md +6 -6
- package/.claude/board/board-architect.md +7 -7
- package/.claude/board/board-devops.md +6 -6
- package/.claude/board/board-mobile.md +6 -6
- package/.claude/board/board-performance.md +6 -6
- package/.claude/board/board-privacy.md +6 -6
- package/.claude/board/board-product.md +7 -7
- package/.claude/board/board-qa.md +7 -7
- package/.claude/board/board-ux.md +6 -6
- package/.claude/board/council.md +32 -32
- package/.claude/board/rules.md +8 -8
- package/.claude/commands/adopt.md +10 -10
- package/.claude/commands/ask-mode.md +6 -5
- package/.claude/commands/board.md +1 -1
- package/.claude/commands/build.md +8 -8
- package/.claude/commands/design.md +8 -8
- package/.claude/commands/findings-all.md +2 -2
- package/.claude/commands/findings-security.md +1 -1
- package/.claude/commands/fix-bug.md +2 -2
- package/.claude/commands/handoff.md +12 -12
- package/.claude/commands/language.md +1 -1
- package/.claude/commands/maintain.md +2 -2
- package/.claude/commands/plan-task.md +3 -3
- package/.claude/commands/qa.md +5 -5
- package/.claude/commands/release.md +3 -3
- package/.claude/commands/research.md +2 -2
- package/.claude/commands/retro.md +2 -2
- package/.claude/commands/review-changes.md +2 -2
- package/.claude/commands/rollback.md +1 -1
- package/.claude/commands/start.md +20 -20
- package/.claude/commands/tools.md +8 -6
- package/.claude/commands/user-feedback.md +3 -3
- package/.claude/commands/wrap-up.md +3 -3
- package/.claude/findings/protocol.md +2 -2
- package/.claude/findings/report-template.md +1 -1
- package/.claude/hooks/commit-gate.mjs +11 -9
- package/.claude/hooks/council.mjs +51 -24
- package/.claude/hooks/dashboard-data.mjs +73 -53
- package/.claude/hooks/dashboard.mjs +80 -71
- package/.claude/hooks/destructive-guard.mjs +1 -1
- package/.claude/hooks/freeze-guard.mjs +1 -1
- package/.claude/hooks/gate.mjs +14 -11
- package/.claude/hooks/labels.mjs +177 -0
- package/.claude/hooks/messages.mjs +8 -7
- package/.claude/hooks/notify.mjs +2 -1
- package/.claude/hooks/prefs.mjs +24 -10
- package/.claude/hooks/preview.mjs +6 -6
- package/.claude/hooks/progress.mjs +27 -15
- package/.claude/hooks/qa-report.mjs +12 -10
- package/.claude/hooks/session-start.mjs +9 -7
- package/.claude/hooks/status-read.mjs +22 -18
- package/.claude/hooks/statusline.mjs +10 -8
- package/.claude/hooks/tools.mjs +13 -8
- package/.claude/output-styles/{anlatici.md → narrator.md} +2 -2
- package/.claude/skills/logo-design/SKILL.md +2 -2
- package/.claude/skills/source-research/SKILL.md +1 -1
- package/.claude/skills/ui-motion/SKILL.md +2 -2
- package/AGENTS.md +12 -12
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/README.tr.md +3 -3
- package/docs/decisions/adr-template.md +1 -1
- package/docs/decisions/log.md +2 -2
- package/docs/findings/README.md +1 -1
- package/docs/operations/accounts.md +7 -7
- package/docs/operations/deploy-setup.md +3 -3
- package/docs/operations/feature-flags.md +2 -2
- package/docs/operations/maintenance.md +1 -1
- package/docs/operations/release-checklist.md +2 -2
- package/docs/operations/spend-alarms.md +2 -2
- package/docs/product/feedback.md +4 -4
- package/docs/product/launch.md +1 -1
- package/docs/product/prd.md +10 -10
- package/docs/product/roadmap.md +8 -8
- package/docs/product/specs/_template.md +8 -8
- package/docs/product/unit-economics.md +10 -10
- package/docs/quality/metrics.md +2 -2
- package/docs/quality/test-plan.md +5 -5
- package/docs/security/checklist.md +2 -2
- package/docs/workflow/board.md +5 -5
- package/docs/workflow/cost-log.md +1 -1
- package/docs/workflow/decision-protocol.md +18 -16
- package/docs/workflow/learning.md +2 -2
- package/docs/workflow/mcp-catalog.md +1 -1
- package/docs/workflow/models.md +1 -1
- package/docs/workflow/progress.md +14 -14
- package/docs/workflow/skills.md +9 -9
- package/docs/workflow/usage-limits.md +5 -5
- package/kit/CHANGELOG.md +19 -0
- package/kit/VERSION +1 -1
- package/kit/guide.html +21 -21
- package/kit/guide.tr.html +21 -21
- package/kit/kit.mjs +29 -0
- package/kit/project/README.md +1 -1
- package/kit/templates/backend/docs/architecture/stack.md +7 -7
- package/kit/templates/backend/docs/architecture/structure.md +2 -2
- package/kit/templates/mobile/docs/architecture/stack.md +5 -5
- package/kit/templates/mobile/docs/architecture/structure.md +1 -1
- package/kit/templates/mobile/docs/design/design-guide.md +1 -1
- package/kit/templates/mobile/tests/security/rls.mjs +4 -4
- package/kit/templates/web-saas/docs/architecture/stack.md +12 -12
- package/kit/templates/web-saas/docs/architecture/structure.md +2 -2
- package/kit/templates/web-saas/docs/design/design-guide.md +1 -1
- package/kit/templates/web-saas/tests/security/rls.mjs +4 -4
- package/package.json +1 -1
|
@@ -59,13 +59,13 @@ function publicTables(t) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
test('
|
|
62
|
+
test('ATTACK: RLS is on for every public table', (t) => {
|
|
63
63
|
const tables = publicTables(t);
|
|
64
64
|
if (!tables) return;
|
|
65
65
|
assert.deepEqual(tables.filter((x) => !x.rls).map((x) => x.name), [], 'table with RLS off: write "alter table ... enable row level security" and a policy');
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
test('
|
|
68
|
+
test('ATTACK: RLS every public table is registered in this test', (t) => {
|
|
69
69
|
const tables = publicTables(t);
|
|
70
70
|
if (!tables) return;
|
|
71
71
|
const registered = new Set([...TABLES, ...PUBLIC_TABLES].map((x) => x.name));
|
|
@@ -73,7 +73,7 @@ test('SALDIRI: RLS every public table is registered in this test', (t) => {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
for (const { name, owner, sample, key = 'id' } of TABLES) {
|
|
76
|
-
test(`
|
|
76
|
+
test(`ATTACK: RLS ${name} is closed to other users and to anonymous`, async () => {
|
|
77
77
|
const [a, b] = users;
|
|
78
78
|
const anon = client(ANON);
|
|
79
79
|
const { data: row, error } = await a.c.from(name).insert({ ...sample(), [owner]: a.id }).select().single();
|
|
@@ -92,7 +92,7 @@ for (const { name, owner, sample, key = 'id' } of TABLES) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
for (const { name, sample } of PUBLIC_TABLES) {
|
|
95
|
-
test(`
|
|
95
|
+
test(`ATTACK: RLS ${name} is public to read but closed to writes`, async () => {
|
|
96
96
|
assert.ok((await client(ANON).from(name).insert(sample())).error, 'anonymous can write');
|
|
97
97
|
assert.ok((await users[1].c.from(name).insert(sample())).error, 'a signed-in user can write');
|
|
98
98
|
});
|
|
@@ -5,24 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
## Decision points
|
|
7
7
|
|
|
8
|
-
`/start` asks these decisions in order (`docs/workflow/decision-protocol.md`). For each decision, offer the 2-4 options that best fit this project. The **recommended** label is the default, but if another option fits the PRD better, recommend that one and say why. If a claim that can change (price, free tier, limit) carries a `(
|
|
8
|
+
`/start` asks these decisions in order (`docs/workflow/decision-protocol.md`). For each decision, offer the 2-4 options that best fit this project. The **recommended** label is the default, but if another option fits the PRD better, recommend that one and say why. If a claim that can change (price, free tier, limit) carries a `(source: <url>, YYYY-MM-DD)` note newer than 90 days, do not research it again.
|
|
9
9
|
|
|
10
10
|
| # | Decision | Options (when each is better) |
|
|
11
11
|
|---|---|---|
|
|
12
12
|
| 1 | Framework | **Next.js** (recommended: SEO + dashboard + API in one project, the ecosystem the AI knows best) · Astro (content/landing-page heavy, fastest pages) · SvelteKit (less code, fast; smaller ecosystem) · React + Vite SPA (app only behind login, no SEO need, simplest hosting) |
|
|
13
13
|
| 2 | Language | **TypeScript** (recommended: catches AI mistakes at compile time) · JavaScript (only for a very small prototype) |
|
|
14
|
-
| 3 | Database + identity | **Supabase** (recommended: Postgres + Auth + Storage + RLS in one place; free tier has 2 active projects, 500 MB database per project, a project idle for 1 week is paused (
|
|
14
|
+
| 3 | Database + identity | **Supabase** (recommended: Postgres + Auth + Storage + RLS in one place; free tier has 2 active projects, 500 MB database per project, a project idle for 1 week is paused (source: https://supabase.com/pricing, 2026-10-26)) · Neon + Better Auth (more control, less provider lock-in) · Firebase (realtime, NoSQL, shared backend with mobile) · Clerk + Postgres (ready-made, polished identity UI; first 50,000 monthly returning users free, then per-user fee (source: https://clerk.com/billing, 2026-10-26)) |
|
|
15
15
|
| 4 | Data access | **Supabase client + RLS** (if Supabase is chosen) · Drizzle ORM (type-safe SQL, provider-independent) · Prisma (mature, schema-driven; heavier) |
|
|
16
16
|
| 5 | UI components | **shadcn/ui + Tailwind** (recommended: code lives in the project, fully customizable, works with the design skills) · Tailwind + own components (fully original design) · Mantine (rich ready-made components, dashboard heavy) |
|
|
17
|
-
| 6 | Payment | None (free in the first release) · **iyzico** (Turkish cards, installments, TRY) · Stripe (customers abroad; may be restricted for a Turkish company: Turkey does not appear in the supported-country list (
|
|
18
|
-
| 7 | Hosting | **Vercel** (recommended for Next.js: zero config, preview environments; the free Hobby plan is for personal, non-commercial use only, a product that takes payments or shows ads needs Pro (
|
|
19
|
-
| 8 | Email | **Resend** (recommended: developer friendly, React email templates; free plan gives 3,000 emails per month, 100 per day (
|
|
20
|
-
| 9 | Analytics | **Plausible** (cookieless, KVKK friendly; no free plan, starts at 9 dollars per month, 30-day trial (
|
|
17
|
+
| 6 | Payment | None (free in the first release) · **iyzico** (Turkish cards, installments, TRY) · Stripe (customers abroad; may be restricted for a Turkish company: Turkey does not appear in the supported-country list (source: https://stripe.com/global, 2026-10-26)) · Paddle / Lemon Squeezy (merchant of record: they handle tax and invoicing; subscriptions sold abroad) |
|
|
18
|
+
| 7 | Hosting | **Vercel** (recommended for Next.js: zero config, preview environments; the free Hobby plan is for personal, non-commercial use only, a product that takes payments or shows ads needs Pro (source: https://vercel.com/docs/limits/fair-use-guidelines, 2026-10-26)) · Cloudflare (cheap and fast; free Workers are limited to 100,000 requests per day and 10 ms CPU per request (source: https://developers.cloudflare.com/workers/platform/limits/, 2026-10-26)) · Netlify · Your own server + Coolify (fixed cost, maintenance is yours) |
|
|
19
|
+
| 8 | Email | **Resend** (recommended: developer friendly, React email templates; free plan gives 3,000 emails per month, 100 per day (source: https://resend.com/pricing, 2026-10-26)) · Postmark (very high deliverability) · Amazon SES (cheapest, hard to set up) |
|
|
20
|
+
| 9 | Analytics | **Plausible** (cookieless, KVKK friendly; no free plan, starts at 9 dollars per month, 30-day trial (source: https://plausible.io/docs/billing, 2026-10-26)) · PostHog (product analytics, funnels, feature flags) · Umami (self-host, free) · None |
|
|
21
21
|
| 10 | Error tracking | **Sentry** (recommended) · Highlight / Better Stack |
|
|
22
22
|
| 11 | Language support | **Turkish only** (recommended for the MVP) · Turkish + English (next-intl; setting it up from the start is cheaper than adding it later) |
|
|
23
23
|
| 12 | Test | **Vitest + Playwright** (recommended) · Playwright only (little logic, mostly UI) |
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Chosen technologies
|
|
26
26
|
|
|
27
27
|
> `/start` fills this in at Step 4 from the decision points. The table below is the **recommended** stack and counts as a proposal until approved.
|
|
28
28
|
> If the choice differs, rewrite the commands, folder, rules, verification and checks sections of this file to match the choice.
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
| Analytics | Plausible / PostHog | Watch cookie consent |
|
|
42
42
|
| Test | Vitest (unit), Playwright (e2e) | |
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Commands (commands)
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
npm run dev # development
|
|
@@ -83,7 +83,7 @@ src/
|
|
|
83
83
|
└── middleware.ts
|
|
84
84
|
public/ # static files
|
|
85
85
|
tests/
|
|
86
|
-
├── e2e/ # Playwright acceptance tests (
|
|
86
|
+
├── e2e/ # Playwright acceptance tests (AC-01.1 ...)
|
|
87
87
|
└── live/ # live monitoring tests (that delete no data)
|
|
88
88
|
supabase/migrations/ # database changes ONLY here
|
|
89
89
|
```
|
|
@@ -103,7 +103,7 @@ supabase/migrations/ # database changes ONLY here
|
|
|
103
103
|
|
|
104
104
|
## Verification (evidence before a task is done)
|
|
105
105
|
|
|
106
|
-
- UI: `node .claude/hooks/preview.mjs <task id>` takes a desktop (1440) and mobile (390) screenshot (the `
|
|
106
|
+
- UI: `node .claude/hooks/preview.mjs <task id>` takes a desktop (1440) and mobile (390) screenshot (the `dev:` and `preview:` lines in `AGENTS.md`). To try scenarios, with `npm run dev` running, open the page with the Playwright CLI (`npx -y @playwright/cli@0.1.22 open http://localhost:3000`, then `snapshot`, `console`, `screenshot`) or with Claude's browser, and try the main scenario and one error scenario.
|
|
107
107
|
- Proving acceptance criteria in the browser: `/qa` (one Playwright test per criterion, report in `docs/quality/qa/`).
|
|
108
108
|
- Authorization: with a second test user, try to reach the first user's record; it must fail.
|
|
109
109
|
- No errors/warnings in the console.
|
|
@@ -120,7 +120,7 @@ supabase/migrations/ # database changes ONLY here
|
|
|
120
120
|
|
|
121
121
|
## Acceptance tests and feature flags
|
|
122
122
|
|
|
123
|
-
**Acceptance tests** (`docs/quality/test-plan.md` §1a): The test name carries the criterion id (`test('
|
|
123
|
+
**Acceptance tests** (`docs/quality/test-plan.md` §1a): The test name carries the criterion id (`test('AC-01.1 ...')`). The dashboard reads the JUnit report:
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
126
|
PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-results/junit.xml npx playwright test --reporter=list,junit
|
|
@@ -129,7 +129,7 @@ npx vitest run --reporter=default --reporter=junit --outputFile.junit=test-resul
|
|
|
129
129
|
|
|
130
130
|
**Feature flags** (`docs/operations/feature-flags.md`): Emergency shutdown must not need a deploy. So use a source read at runtime, not an environment variable:
|
|
131
131
|
- PostHog feature flags if PostHog is chosen; otherwise
|
|
132
|
-
- A `feature_flags` table in Supabase (`
|
|
132
|
+
- A `feature_flags` table in Supabase (`name`, `enabled`, `percent`) + a 60 s cache on the server. Reading goes through one helper: `lib/flags.ts` → `isEnabled('name', user)`.
|
|
133
133
|
- The flag check runs on the server; the client only sees the result.
|
|
134
134
|
|
|
135
135
|
## Common AI mistakes (watch for these)
|
|
@@ -32,7 +32,7 @@ src/
|
|
|
32
32
|
└── middleware.ts
|
|
33
33
|
public/ # static files
|
|
34
34
|
tests/
|
|
35
|
-
├── e2e/ # Playwright acceptance tests (
|
|
35
|
+
├── e2e/ # Playwright acceptance tests (AC-01.1 ...)
|
|
36
36
|
└── live/ # live monitoring tests (that delete no data)
|
|
37
37
|
supabase/migrations/ # database changes ONLY here
|
|
38
38
|
```
|
|
@@ -68,4 +68,4 @@ supabase/migrations/ # database changes ONLY here
|
|
|
68
68
|
4. **Naming:** Folders and files use `kebab-case`. React component files may use `PascalCase.tsx`; Python modules use `snake_case.py`. A feature folder name is singular and English (`invoice`, `customer`).
|
|
69
69
|
5. **Temporary and unrelated work** goes under `sandbox/`. If something useful comes out, it moves to the right place; `sandbox/` is cleaned regularly.
|
|
70
70
|
6. **Old documents** are not deleted; they move under `docs/archive/` with a line at the top saying why they were archived.
|
|
71
|
-
7. **Changing the map** (a new top-level folder, a new layer) is a `
|
|
71
|
+
7. **Changing the map** (a new top-level folder, a new layer) is a `tech` decision: ask the user, record it under `docs/decisions/`, then update `.claude/structure.json` and this file.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
> Decisions are made with `/design` using **visual previews** (`docs/workflow/decision-protocol.md` §5). If a new color, size or component
|
|
9
9
|
> is needed, it is asked as a decision first, then added to DESIGN.md. No raw colors or arbitrary sizes in code.
|
|
10
10
|
|
|
11
|
-
## 1.
|
|
11
|
+
## 1. Chosen direction (Chosen direction)
|
|
12
12
|
|
|
13
13
|
| Area | Choice | Skill | Preview | Date |
|
|
14
14
|
|---|---|---|---|---|
|
|
@@ -59,13 +59,13 @@ function publicTables(t) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
test('
|
|
62
|
+
test('ATTACK: RLS is on for every public table', (t) => {
|
|
63
63
|
const tables = publicTables(t);
|
|
64
64
|
if (!tables) return;
|
|
65
65
|
assert.deepEqual(tables.filter((x) => !x.rls).map((x) => x.name), [], 'table with RLS off: write "alter table ... enable row level security" and a policy');
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
test('
|
|
68
|
+
test('ATTACK: RLS every public table is registered in this test', (t) => {
|
|
69
69
|
const tables = publicTables(t);
|
|
70
70
|
if (!tables) return;
|
|
71
71
|
const registered = new Set([...TABLES, ...PUBLIC_TABLES].map((x) => x.name));
|
|
@@ -73,7 +73,7 @@ test('SALDIRI: RLS every public table is registered in this test', (t) => {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
for (const { name, owner, sample, key = 'id' } of TABLES) {
|
|
76
|
-
test(`
|
|
76
|
+
test(`ATTACK: RLS ${name} is closed to other users and to anonymous`, async () => {
|
|
77
77
|
const [a, b] = users;
|
|
78
78
|
const anon = client(ANON);
|
|
79
79
|
const { data: row, error } = await a.c.from(name).insert({ ...sample(), [owner]: a.id }).select().single();
|
|
@@ -92,7 +92,7 @@ for (const { name, owner, sample, key = 'id' } of TABLES) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
for (const { name, sample } of PUBLIC_TABLES) {
|
|
95
|
-
test(`
|
|
95
|
+
test(`ATTACK: RLS ${name} is public to read but closed to writes`, async () => {
|
|
96
96
|
assert.ok((await client(ANON).from(name).insert(sample())).error, 'anonymous can write');
|
|
97
97
|
assert.ok((await users[1].c.from(name).insert(sample())).error, 'a signed-in user can write');
|
|
98
98
|
});
|