tillerhand 3.43.0 → 3.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.claude/agents/board-chair.md +20 -20
  2. package/.claude/agents/code-reviewer.md +20 -20
  3. package/.claude/agents/council-advisor.md +14 -14
  4. package/.claude/agents/security-auditor.md +30 -30
  5. package/.claude/agents/test-writer.md +22 -22
  6. package/.claude/board/board-ai.md +25 -25
  7. package/.claude/board/board-api.md +25 -25
  8. package/.claude/board/board-architect.md +28 -28
  9. package/.claude/board/board-devops.md +25 -25
  10. package/.claude/board/board-mobile.md +25 -25
  11. package/.claude/board/board-performance.md +24 -24
  12. package/.claude/board/board-privacy.md +25 -25
  13. package/.claude/board/board-product.md +26 -26
  14. package/.claude/board/board-qa.md +28 -28
  15. package/.claude/board/board-ux.md +26 -26
  16. package/.claude/board/council.md +76 -76
  17. package/.claude/board/rules.md +61 -61
  18. package/.claude/claude-security-guidance.md +10 -10
  19. package/.claude/commands/adopt.md +70 -69
  20. package/.claude/commands/ask-mode.md +9 -9
  21. package/.claude/commands/board.md +15 -15
  22. package/.claude/commands/build.md +47 -47
  23. package/.claude/commands/dashboard.md +17 -17
  24. package/.claude/commands/design.md +31 -31
  25. package/.claude/commands/findings-all.md +14 -14
  26. package/.claude/commands/findings-business.md +7 -7
  27. package/.claude/commands/findings-legal.md +7 -7
  28. package/.claude/commands/findings-perf.md +7 -7
  29. package/.claude/commands/findings-security.md +9 -9
  30. package/.claude/commands/findings-tech.md +7 -7
  31. package/.claude/commands/findings-ux.md +7 -7
  32. package/.claude/commands/fix-bug.md +15 -15
  33. package/.claude/commands/freeze.md +8 -8
  34. package/.claude/commands/handoff.md +38 -38
  35. package/.claude/commands/kit-update.md +17 -14
  36. package/.claude/commands/language.md +13 -0
  37. package/.claude/commands/maintain.md +19 -19
  38. package/.claude/commands/plan-task.md +32 -32
  39. package/.claude/commands/qa.md +17 -17
  40. package/.claude/commands/release.md +40 -40
  41. package/.claude/commands/research.md +31 -31
  42. package/.claude/commands/retro.md +32 -32
  43. package/.claude/commands/review-changes.md +15 -15
  44. package/.claude/commands/rollback.md +24 -24
  45. package/.claude/commands/start.md +116 -113
  46. package/.claude/commands/tools.md +11 -11
  47. package/.claude/commands/user-feedback.md +11 -11
  48. package/.claude/commands/wrap-up.md +18 -18
  49. package/.claude/findings/lenses/business.md +24 -24
  50. package/.claude/findings/lenses/legal.md +29 -29
  51. package/.claude/findings/lenses/perf.md +19 -19
  52. package/.claude/findings/lenses/security.md +30 -30
  53. package/.claude/findings/lenses/tech.md +23 -23
  54. package/.claude/findings/lenses/ux.md +19 -19
  55. package/.claude/findings/protocol.md +54 -54
  56. package/.claude/findings/report-template.md +30 -30
  57. package/.claude/hooks/budget.mjs +7 -7
  58. package/.claude/hooks/checkpoint.mjs +12 -12
  59. package/.claude/hooks/commit-gate.mjs +38 -37
  60. package/.claude/hooks/cost.mjs +2 -2
  61. package/.claude/hooks/council.mjs +3 -3
  62. package/.claude/hooks/dashboard-data.mjs +4 -3
  63. package/.claude/hooks/dashboard.mjs +164 -149
  64. package/.claude/hooks/destructive-guard.mjs +74 -64
  65. package/.claude/hooks/edit-rounds.mjs +3 -3
  66. package/.claude/hooks/flow.mjs +10 -10
  67. package/.claude/hooks/freeze-guard.mjs +27 -23
  68. package/.claude/hooks/gate.mjs +25 -25
  69. package/.claude/hooks/guard-secrets.mjs +11 -11
  70. package/.claude/hooks/lang.mjs +114 -0
  71. package/.claude/hooks/messages.mjs +495 -0
  72. package/.claude/hooks/models.mjs +14 -11
  73. package/.claude/hooks/notify.mjs +3 -2
  74. package/.claude/hooks/package-gate.mjs +17 -15
  75. package/.claude/hooks/plugins-sync.mjs +9 -9
  76. package/.claude/hooks/prefs.mjs +19 -19
  77. package/.claude/hooks/preview.mjs +11 -11
  78. package/.claude/hooks/progress.mjs +14 -14
  79. package/.claude/hooks/qa-report.mjs +11 -10
  80. package/.claude/hooks/reminder.mjs +6 -3
  81. package/.claude/hooks/session-start.mjs +23 -17
  82. package/.claude/hooks/shared.mjs +1 -1
  83. package/.claude/hooks/status-read.mjs +5 -4
  84. package/.claude/hooks/statusline.mjs +14 -11
  85. package/.claude/hooks/structure-guard.mjs +9 -9
  86. package/.claude/hooks/test-guard.mjs +12 -12
  87. package/.claude/hooks/tools.mjs +28 -29
  88. package/.claude/hooks/usage.mjs +18 -15
  89. package/.claude/output-styles/anlatici.md +28 -28
  90. package/.claude/security-patterns.json +3 -3
  91. package/.claude/settings.json +2 -0
  92. package/.claude/skills/logo-design/SKILL.md +42 -42
  93. package/.claude/skills/motion-video/SKILL.md +52 -52
  94. package/.claude/skills/source-research/SKILL.md +35 -35
  95. package/.claude/skills/ui-motion/SKILL.md +38 -38
  96. package/.github/workflows/kit-tests.yml +7 -7
  97. package/.github/workflows/npm-publish.yml +16 -16
  98. package/AGENTS.md +56 -56
  99. package/CHANGELOG.md +7 -7
  100. package/CLAUDE.md +5 -5
  101. package/README.md +199 -172
  102. package/README.tr.md +292 -0
  103. package/docs/architecture/overview.md +31 -31
  104. package/docs/architecture/stack.md +2 -2
  105. package/docs/archive/README.md +4 -4
  106. package/docs/decisions/adr-template.md +14 -14
  107. package/docs/decisions/log.md +10 -10
  108. package/docs/findings/README.md +13 -13
  109. package/docs/findings/config.md +13 -13
  110. package/docs/legal/privacy-kvkk.md +1 -1
  111. package/docs/operations/accounts.md +25 -25
  112. package/docs/operations/deploy-setup.md +64 -64
  113. package/docs/operations/feature-flags.md +17 -17
  114. package/docs/operations/maintenance.md +48 -48
  115. package/docs/operations/release-checklist.md +61 -61
  116. package/docs/operations/spend-alarms.md +28 -28
  117. package/docs/product/feedback.md +9 -8
  118. package/docs/product/glossary.md +16 -16
  119. package/docs/product/launch.md +28 -28
  120. package/docs/product/prd.md +43 -42
  121. package/docs/product/roadmap.md +31 -30
  122. package/docs/product/specs/_template.md +13 -12
  123. package/docs/product/unit-economics.md +23 -22
  124. package/docs/quality/metrics.md +14 -14
  125. package/docs/quality/retros.md +11 -11
  126. package/docs/quality/test-plan.md +48 -48
  127. package/docs/security/checklist.md +57 -57
  128. package/docs/workflow/board.md +7 -7
  129. package/docs/workflow/cost-log.md +4 -4
  130. package/docs/workflow/decision-protocol.md +70 -70
  131. package/docs/workflow/learning.md +4 -4
  132. package/docs/workflow/mcp-catalog.md +31 -31
  133. package/docs/workflow/models.md +35 -35
  134. package/docs/workflow/progress.md +7 -7
  135. package/docs/workflow/skills.md +108 -108
  136. package/docs/workflow/usage-limits.md +62 -62
  137. package/kit/CHANGELOG.md +882 -841
  138. package/kit/VERSION +1 -1
  139. package/kit/guide.html +713 -477
  140. package/kit/guide.tr.html +1328 -0
  141. package/kit/kit.mjs +56 -54
  142. package/kit/project/README.md +29 -29
  143. package/kit/project/gitignore +12 -12
  144. package/kit/templates/addons/ai/.env.example.append +5 -5
  145. package/kit/templates/addons/ai/ai/evals/README.md +11 -11
  146. package/kit/templates/addons/ai/ai/evals/cases.jsonl +2 -2
  147. package/kit/templates/addons/ai/ai/prompts/README.md +8 -8
  148. package/kit/templates/addons/ai/docs/ai/guide.md +56 -56
  149. package/kit/templates/backend/.claude/hooks/format.mjs +1 -1
  150. package/kit/templates/backend/.claude/structure.json +1 -1
  151. package/kit/templates/backend/.env.example +6 -6
  152. package/kit/templates/backend/.github/dependabot.yml +1 -1
  153. package/kit/templates/backend/.github/workflows/ci.yml +13 -13
  154. package/kit/templates/backend/.github/workflows/claude-ci-fix.yml +19 -19
  155. package/kit/templates/backend/.github/workflows/claude-maintenance.yml +18 -18
  156. package/kit/templates/backend/.github/workflows/live-monitoring.yml +14 -14
  157. package/kit/templates/backend/.pre-commit-config.yaml +3 -3
  158. package/kit/templates/backend/docs/architecture/stack.md +98 -98
  159. package/kit/templates/backend/docs/architecture/structure.md +34 -34
  160. package/kit/templates/mobile/.claude/structure.json +1 -1
  161. package/kit/templates/mobile/.env.example +6 -6
  162. package/kit/templates/mobile/.github/dependabot.yml +2 -2
  163. package/kit/templates/mobile/.github/workflows/ci.yml +18 -18
  164. package/kit/templates/mobile/.github/workflows/claude-ci-fix.yml +19 -19
  165. package/kit/templates/mobile/.github/workflows/claude-maintenance.yml +18 -18
  166. package/kit/templates/mobile/.gitignore.append +2 -2
  167. package/kit/templates/mobile/docs/architecture/stack.md +113 -113
  168. package/kit/templates/mobile/docs/architecture/structure.md +33 -33
  169. package/kit/templates/mobile/docs/design/design-guide.md +37 -37
  170. package/kit/templates/mobile/tests/security/rls.mjs +22 -22
  171. package/kit/templates/web-saas/.claude/structure.json +1 -1
  172. package/kit/templates/web-saas/.env.example +11 -11
  173. package/kit/templates/web-saas/.github/dependabot.yml +3 -3
  174. package/kit/templates/web-saas/.github/workflows/ci.yml +20 -20
  175. package/kit/templates/web-saas/.github/workflows/claude-ci-fix.yml +19 -19
  176. package/kit/templates/web-saas/.github/workflows/claude-maintenance.yml +18 -18
  177. package/kit/templates/web-saas/.github/workflows/live-monitoring.yml +12 -12
  178. package/kit/templates/web-saas/docs/architecture/stack.md +116 -116
  179. package/kit/templates/web-saas/docs/architecture/structure.md +44 -44
  180. package/kit/templates/web-saas/docs/design/design-guide.md +38 -38
  181. package/kit/templates/web-saas/tests/live/smoke.spec.ts +4 -4
  182. package/kit/templates/web-saas/tests/security/rls.mjs +22 -22
  183. package/kit/tools/codex/.codex/hooks.json +1 -1
  184. package/package.json +2 -2
  185. package/README.en.md +0 -267
@@ -4,6 +4,7 @@
4
4
  // Must stay fast: the status line calls it on every refresh (git and heavy reads are in dashboard.mjs).
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { t } from './lang.mjs';
7
8
 
8
9
  // Only a regular file: a folder or a link to a folder named like a test or report file must not throw (EISDIR)
9
10
  export const readText = (p) => { try { return fs.statSync(p).isFile() ? fs.readFileSync(p, 'utf8') : ''; } catch { return ''; } };
@@ -70,9 +71,9 @@ export function readStatus(root) {
70
71
  .map(([no, date, stage, topic, question, chosen, door, decidedBy]) => ({ no, date, stage, topic, question, chosen, door, decidedBy }));
71
72
 
72
73
  const checklists = [
73
- ['Güvenlik', 'docs/security/checklist.md'], ['Canlıya alma', 'docs/operations/release-checklist.md'], ['Yasal / KVKK', 'docs/legal/privacy-kvkk.md'],
74
- ['AI', 'docs/ai/guide.md'], ['Lansman', 'docs/product/launch.md'],
75
- ].filter(([, f]) => fs.existsSync(d(f))).map(([name, f]) => ({ name, file: f, ...tally(readText(d(f))) }));
74
+ ['statusRead.checklist.security', 'docs/security/checklist.md'], ['statusRead.checklist.release', 'docs/operations/release-checklist.md'],
75
+ ['statusRead.checklist.legal', 'docs/legal/privacy-kvkk.md'], ['statusRead.checklist.ai', 'docs/ai/guide.md'], ['statusRead.checklist.launch', 'docs/product/launch.md'],
76
+ ].filter(([, f]) => fs.existsSync(d(f))).map(([name, f]) => ({ name: t(root, name), file: f, ...tally(readText(d(f))) }));
76
77
 
77
78
  let kit = null;
78
79
  try { kit = JSON.parse(readText(d('.kit.json'))); } catch { /* not set up */ }
@@ -91,7 +92,7 @@ export function readStatus(root) {
91
92
  const title = (f) => readText(d(f)).match(/^# (.+)$/m)?.[1].trim();
92
93
  return {
93
94
  // The project name is in AGENTS.md; in projects before 3.9.0 in CLAUDE.md (the update adds AGENTS.md with a placeholder)
94
- project: [title('AGENTS.md'), title('CLAUDE.md')].find((a) => a && a !== '[PROJE ADI]') ?? title('AGENTS.md') ?? 'Proje',
95
+ project: [title('AGENTS.md'), title('CLAUDE.md')].find((a) => a && a !== '[PROJE ADI]') ?? title('AGENTS.md') ?? t(root, 'statusRead.defaultProject'),
95
96
  // type: null = set up by init, no type chosen yet (waiting for /start or /adopt)
96
97
  installed: !!kit && kit.type !== null,
97
98
  kitPending: kit ? kit.type === null : fs.existsSync(d('kit/kit.mjs')),
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  // Claude Code bottom status line: phase, task progress, question mode and usage limits.
3
- // Example: ▶ Faz 1 · G-012 · görev 7/23 · soru: açık (kapalı: tasarim) · 5s %34 · hf %5
3
+ // Example: ▶ Faz 1 · G-012 · task 7/23 · questions: open (closed: tasarim) · 5h 34% · weekly 5%
4
4
  // Also writes the rate_limits Claude Code sends into .claude/state/usage.json (usage.mjs reads it).
5
5
  import fs from 'node:fs';
6
6
  import { readStatus } from './status-read.mjs';
7
7
  import { writeLimits, readLimits, level } from './usage.mjs';
8
+ import { t } from './lang.mjs';
8
9
 
9
10
  let root = process.cwd();
10
11
  let input = {};
@@ -34,8 +35,8 @@ function limitPart() {
34
35
  const l = readLimits(root);
35
36
  if (!l) return null;
36
37
  const p = [];
37
- if (l.five_hour) p.push(`5s %${Math.round(l.five_hour.percent)}`);
38
- if (l.seven_day) p.push(`hf %${Math.round(l.seven_day.percent)}`);
38
+ if (l.five_hour) p.push(t(root, 'statusline.limit5h', { percent: Math.round(l.five_hour.percent) }));
39
+ if (l.seven_day) p.push(t(root, 'statusline.limitWeek', { percent: Math.round(l.seven_day.percent) }));
39
40
  const s = level(l);
40
41
  return p.join(' · ') + (s === 'yellow' ? ' ⚠' : s === 'red' ? ' ⛔' : '');
41
42
  }
@@ -44,22 +45,24 @@ try {
44
45
  const d = readStatus(root);
45
46
  const parts = [];
46
47
  if (d.kitPending) {
47
- parts.push('◆ Kit kurulmadı · başlamak için: /start [fikrin] · var olan kod: /adopt');
48
+ parts.push(t(root, 'statusline.notSetUp'));
48
49
  } else {
49
50
  const a = d.fields;
51
+ // The values in progress.md stay Turkish (açık / kapalı); only what the line shows is localized
50
52
  const question = (a['Soru modu'] ?? 'açık')
53
+ .replace(/^(açık|kapalı)(?=$|\s)/i, (m) => t(root, m.toLowerCase() === 'açık' ? 'statusline.modeOpen' : 'statusline.modeOff'))
51
54
  .replace(/\s*·\s*kapalı konular:\s*yok/i, '')
52
- .replace(/\s*·\s*kapalı konular:\s*(.*)$/i, ' (kapalı: $1)');
55
+ .replace(/\s*·\s*kapalı konular:\s*(.*)$/i, (_, topics) => ` ${t(root, 'statusline.closed', { topics })}`);
53
56
  parts.push(`▶ ${a['Aktif faz'] ?? '-'}`);
54
- if (/başlangıç/i.test(a['Aktif faz'] ?? '')) parts.push(`adım ${a['Başlangıç adımı'] ?? '-'}`);
55
- else parts.push(a['Aktif görev'] ?? '-', `görev ${d.taskCount.done}/${d.taskCount.total}`);
56
- parts.push(`soru: ${question}`);
57
- if (d.pending.length) parts.push(`bekleyen karar: ${d.pending.length}`);
57
+ if (/başlangıç/i.test(a['Aktif faz'] ?? '')) parts.push(t(root, 'statusline.step', { step: a['Başlangıç adımı'] ?? '-' }));
58
+ else parts.push(a['Aktif görev'] ?? '-', t(root, 'statusline.task', { done: d.taskCount.done, total: d.taskCount.total }));
59
+ parts.push(t(root, 'statusline.question', { mode: question }));
60
+ if (d.pending.length) parts.push(t(root, 'statusline.pending', { count: d.pending.length }));
58
61
  }
59
62
  const limit = limitPart();
60
63
  if (limit) parts.push(limit);
61
- if (context != null) parts.push(`bağlam %${context}${context >= 70 ? ' ⛔' : context >= 50 ? ' ⚠' : ''}`);
64
+ if (context != null) parts.push(t(root, 'statusline.context', { percent: context }) + (context >= 70 ? ' ⛔' : context >= 50 ? ' ⚠' : ''));
62
65
  console.log(parts.join(' · '));
63
66
  } catch {
64
- console.log('▶ durum okunamadı');
67
+ console.log(t(root, 'statusline.unreadable'));
65
68
  }
@@ -8,6 +8,7 @@ import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { execFileSync } from 'node:child_process';
10
10
  import { readInput, decide, findRoot, isMain } from './shared.mjs';
11
+ import { t } from './lang.mjs';
11
12
 
12
13
  const root = findRoot();
13
14
 
@@ -41,14 +42,13 @@ export function check(rel, map) {
41
42
  if (rel.startsWith('sandbox/')) return null;
42
43
  const denied = map.deny.find((re) => re.test(rel));
43
44
  if (denied) {
44
- return { decision: 'deny', reason: `"${rel}" dağınıklık kalıbına uyuyor (yedek, eski sürüm, kopya veya geçici dosya). Git geçmişi zaten yedektir; deneme için sandbox/ kullan.` };
45
+ return { decision: 'deny', reason: `"${rel}" matches a clutter pattern (backup, old version, copy or temp file). Git history is already the backup; use sandbox/ for experiments.` };
45
46
  }
46
47
  if (map.allow.some((re) => re.test(rel))) return null;
47
48
  const top = [...new Set((map.raw.allow ?? []).map((d) => d.split('/')[0]).filter((d) => !d.includes('.') || d.startsWith('.')))].slice(0, 12);
48
49
  return {
49
50
  decision: 'ask',
50
- reason: `"${rel}" yapı haritasında yok (.claude/structure.json). Doğru yer muhtemelen şunlardan biri: ${top.join(', ')}. ` +
51
- 'Geçici veya deneme ise sandbox/ altına yaz. Kalıcı yeni bir yer gerekiyorsa bunu teknik karar olarak sor ve haritayı güncelle (docs/architecture/structure.md).',
51
+ reason: t(root, 'guard.structure.offMap', { rel, top: top.join(', ') }),
52
52
  };
53
53
  }
54
54
 
@@ -62,20 +62,20 @@ function hook() {
62
62
  const rel = path.relative(g.root, full);
63
63
  if (rel.startsWith('..') || path.isAbsolute(rel)) continue; // outside the project
64
64
  const s = check(rel, map);
65
- if (s) decide(g.tool, s.decision, `Yapı: ${s.reason}`);
65
+ if (s) decide(g.tool, s.decision, s.decision === 'ask' ? t(g.root, 'guard.structure.prefix', { reason: s.reason }) : `Structure: ${s.reason}`);
66
66
  }
67
67
  process.exit(0);
68
68
  }
69
69
 
70
70
  function checkAll() {
71
71
  const map = readMap();
72
- if (!map) { console.log('Yapı haritası yok (.claude/structure.json); kontrol atlandı.'); return; }
72
+ if (!map) { console.log('No structure map (.claude/structure.json); check skipped.'); return; }
73
73
  const files = execFileSync('git', ['ls-files'], { cwd: root, encoding: 'utf8' }).split('\n').filter(Boolean);
74
74
  const violations = files.map((f) => [f, check(f, map)]).filter(([, s]) => s);
75
- if (!violations.length) { console.log(`Yapı uyumlu: ${files.length} dosya.`); return; }
76
- console.error(`Yapı ihlali (${violations.length}):`);
77
- for (const [f, s] of violations) console.error(` ${s.decision === 'deny' ? 'YASAK' : 'HARİTA DIŞI'} ${f}`);
78
- console.error('Dosyayı doğru yere taşı, sandbox/ altına al veya haritayı (teknik kararla) güncelle.');
75
+ if (!violations.length) { console.log(`Structure OK: ${files.length} files.`); return; }
76
+ console.error(`Structure violations (${violations.length}):`);
77
+ for (const [f, s] of violations) console.error(` ${s.decision === 'deny' ? 'FORBIDDEN' : 'OFF MAP'} ${f}`);
78
+ console.error('Move the file to the right place, put it under sandbox/, or update the map (as a technical decision).');
79
79
  process.exit(1);
80
80
  }
81
81
 
@@ -11,7 +11,8 @@
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
13
  import crypto from 'node:crypto';
14
- import { readInput, decide, isMain } from './shared.mjs';
14
+ import { readInput, decide, isMain, findRoot } from './shared.mjs';
15
+ import { t } from './lang.mjs';
15
16
 
16
17
  const TEST = /(^|\/)(tests?|__tests__|e2e|spec)\/|\.(test|spec|e2e-spec)\.[cm]?[jt]sx?$|(^|\/)test_[^/]*\.py$|_test\.(py|go)$/i;
17
18
  // Windows resolves "a.test.ts.", "a.test.ts " and "a.test.ts::$DATA" (alternate stream) to the same file
@@ -37,9 +38,9 @@ const count = (s, re) => (stripComments(s).match(re) ?? []).length;
37
38
 
38
39
  // Returns: null (no problem) | a description of the weakening
39
40
  export function weakening(before, after) {
40
- if (count(after, SKIP) > count(before, SKIP)) return 'test atlanıyor veya tek teste odaklanıyor (.skip / .only / skipIf / xit / pytest skip)';
41
- if (count(after, EXPECT) < count(before, EXPECT)) return 'beklenti (expect/assert) siliniyor veya yoruma alınıyor';
42
- if (count(after, WEAK) > count(before, WEAK)) return 'beklenti gevşetiliyor (toBeTruthy, toBeDefined, expect.anything, matcher\'sız expect, assert True)';
41
+ if (count(after, SKIP) > count(before, SKIP)) return t(findRoot(), 'guard.test.skip');
42
+ if (count(after, EXPECT) < count(before, EXPECT)) return t(findRoot(), 'guard.test.expectRemoved');
43
+ if (count(after, WEAK) > count(before, WEAK)) return t(findRoot(), 'guard.test.expectWeak');
43
44
  return null;
44
45
  }
45
46
 
@@ -47,7 +48,7 @@ export function weakening(before, after) {
47
48
  export function configRisk(before, after) {
48
49
  const a = new Set(before.split('\n')), b = new Set(after.split('\n'));
49
50
  const changed = [...a].filter((s) => !b.has(s)).concat([...b].filter((s) => !a.has(s)));
50
- return changed.some((s) => RISKY.test(s)) ? 'testlerin kapsamını belirleyen ayar değişiyor (include/exclude, testMatch, passWithNoTests, eşik, test script\'i)' : null;
51
+ return changed.some((s) => RISKY.test(s)) ? t(findRoot(), 'guard.test.configRisk') : null;
51
52
  }
52
53
 
53
54
  // The real path too: a link or junction into the test folder, a Windows short name (TESTS~1)
@@ -76,21 +77,20 @@ function hook() {
76
77
  // Tests are written by the test-writer agent only (AGENTS.md rule 7): whoever writes the code must not shape the test
77
78
  // to it. Only Claude Code reports the subagent; Codex and Antigravity keep the rule in text. Config files stay a question.
78
79
  if (g.tool === 'claude' && g.agent !== 'test-writer' && !config) {
79
- decide(g.tool, 'deny', `Test ayrımı: ${name.split(path.sep).join('/')} bir test dosyası; test dosyalarına yalnız test-writer ajanı yazar, kodu yazan testi değiştiremez (AGENTS.md kural 7). ` +
80
- 'Test eklemek veya değiştirmek gerekiyorsa test-writer ajanını çağır (varsa SendMessage ile sürdür); kriter değişiyorsa kullanıcıya sor.');
80
+ decide(g.tool, 'deny', `Test separation: ${name.split(path.sep).join('/')} is a test file; only the test-writer agent writes test files, and whoever writes the code cannot change tests (AGENTS.md rule 7). ` +
81
+ 'If a test must be added or changed, call the test-writer agent (continue it with SendMessage if it exists); if the criterion changes, ask the user.');
81
82
  }
82
83
  let before = f.before;
83
84
  if (before === undefined) { try { before = fs.readFileSync(full, 'utf8'); } catch { before = ''; } }
84
85
  const after = f.after ?? f.text;
85
86
  let reason;
86
- if (f.deleted) reason = config ? 'test ayar dosyası siliniyor' : 'test dosyası siliniyor';
87
+ if (f.deleted) reason = t(g.root, config ? 'guard.test.configDeleted' : 'guard.test.fileDeleted');
87
88
  // Codex Move: a test moved to a path that does not run is as good as deleted; moving onto an existing file overwrites it
88
- else if (source && (!isTestFile(name) || (TEST_NAME.test(path.basename(source)) && !TEST_NAME.test(path.basename(name))))) reason = `test dosyası çalışmayan bir yola taşınıyor (${f.source})`;
89
- else if (f.source && fs.existsSync(full)) reason = `taşıma var olan dosyanın üstüne yazıyor (${f.source} → ${f.path})`;
89
+ else if (source && (!isTestFile(name) || (TEST_NAME.test(path.basename(source)) && !TEST_NAME.test(path.basename(name))))) reason = t(g.root, 'guard.test.movedAway', { source: f.source });
90
+ else if (f.source && fs.existsSync(full)) reason = t(g.root, 'guard.test.moveOverwrite', { source: f.source, path: f.path });
90
91
  else reason = config ? configRisk(before, after) : weakening(before, after);
91
92
  if (reason) {
92
- decide(g.tool, 'ask', `Test koruması: ${name.split(path.sep).join('/')} içinde ${reason}. Testi geçirmek için testi zayıflatmak yasak (AGENTS.md kural 7). ` +
93
- 'Gerekçe gerçekse (yanlış test, değişen kabul kriteri) kullanıcıya söyle ve onayını al; değilse kodu düzelt.');
93
+ decide(g.tool, 'ask', t(g.root, 'guard.test.ask', { name: name.split(path.sep).join('/'), reason }));
94
94
  }
95
95
  }
96
96
  process.exit(0);
@@ -17,41 +17,41 @@ import { findRoot, isMain } from './shared.mjs';
17
17
  // Keys are the package names users type (/tools ac tasarim): they stay Turkish
18
18
  export const PACKAGES = {
19
19
  tasarim: {
20
- description: 'Stil yönleri, palet ve yazı tipi, GSAP',
20
+ description: 'Style directions, palette and typefaces, GSAP',
21
21
  command: '/design',
22
22
  plugins: ['taste-skill@taste-skill', 'ui-ux-pro-max@ui-ux-pro-max-skill', 'gsap-skills@gsap-skills'],
23
23
  },
24
24
  guvenlik: {
25
- description: 'Derin güvenlik taraması',
25
+ description: 'Deep security scan',
26
26
  command: '/findings-security, /release',
27
27
  plugins: ['differential-review', 'insecure-defaults', 'sharp-edges', 'static-analysis', 'supply-chain-risk-auditor', 'agentic-actions-auditor', 'fp-check']
28
28
  .map((e) => `${e}@trailofbits`).concat('claude-security@claude-plugins-official'),
29
29
  },
30
30
  yayin: {
31
- description: 'İnsan için kod haritası, oturum raporu',
31
+ description: 'Code map for humans, session report',
32
32
  command: '/release, /handoff, /adopt',
33
33
  plugins: ['understand-anything@understand-anything', 'session-report@claude-plugins-official'],
34
34
  },
35
- video: { description: 'Remotion ile video', command: 'motion-video', plugins: ['remotion@remotion'] },
35
+ video: { description: 'Video with Remotion', command: 'motion-video', plugins: ['remotion@remotion'] },
36
36
  };
37
37
 
38
38
  // Command descriptions are also in the skill list. Light: non-core commands are listed by name only (skillOverrides "name-only"),
39
39
  // still in the / menu and callable by name. Kit settings.json ships this list; a test keeps the two in sync.
40
40
  export const CORE_COMMANDS = ['start', 'adopt', 'build', 'fix-bug', 'plan-task', 'design', 'tools', 'review-changes', 'wrap-up', 'release'];
41
41
  export const LIGHT_COMMANDS = ['ask-mode', 'board', 'dashboard', 'findings-all', 'findings-business', 'findings-legal', 'findings-perf',
42
- 'findings-security', 'findings-tech', 'findings-ux', 'freeze', 'handoff', 'kit-update', 'maintain', 'qa', 'research', 'retro', 'rollback', 'user-feedback'];
42
+ 'findings-security', 'findings-tech', 'findings-ux', 'freeze', 'handoff', 'kit-update', 'language', 'maintain', 'qa', 'research', 'retro', 'rollback', 'user-feedback'];
43
43
 
44
44
  const settingsPaths = (root) => ({ project: path.join(root, '.claude', 'settings.json'), local: path.join(root, '.claude', 'settings.local.json') });
45
45
 
46
46
  function readJson(p) {
47
47
  if (!fs.existsSync(p)) return {};
48
48
  try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch {
49
- throw new Error(`${p} geçerli JSON değil; düzeltmeden paket değiştirilemez.`);
49
+ throw new Error(`${p} is not valid JSON; fix it before changing a package.`);
50
50
  }
51
51
  }
52
52
 
53
53
  function pkg(name) {
54
- if (!Object.hasOwn(PACKAGES, name ?? '')) throw new Error(`Bilinmeyen paket: ${name}. Seçenekler: ${Object.keys(PACKAGES).join(', ')}`);
54
+ if (!Object.hasOwn(PACKAGES, name ?? '')) throw new Error(`Unknown package: ${name}. Options: ${Object.keys(PACKAGES).join(', ')}`);
55
55
  return PACKAGES[name];
56
56
  }
57
57
 
@@ -84,12 +84,12 @@ export function setPackage(root, name, enabled) {
84
84
  // Writes the shared project settings (the profile is team-wide). Only kit light commands without a value change; the user's
85
85
  // own values (another skill, or "on"/"off" on a kit command) stay. full keeps an empty key so the update does not add the list back.
86
86
  export function setCommandMode(root, mode) {
87
- if (mode !== 'light' && mode !== 'full') throw new Error(`Bilinmeyen mod: ${mode}. Seçenekler: light, full`);
87
+ if (mode !== 'light' && mode !== 'full') throw new Error(`Unknown mode: ${mode}. Options: light, full`);
88
88
  const file = settingsPaths(root).project;
89
- if (!fs.existsSync(file)) throw new Error(`${file} yok; komut listesi yazılmadı.`);
89
+ if (!fs.existsSync(file)) throw new Error(`${file} does not exist; the command list was not written.`);
90
90
  const s = readJson(file);
91
91
  const isObject = (x) => x && typeof x === 'object' && !Array.isArray(x);
92
- if (!isObject(s) || (s.skillOverrides !== undefined && !isObject(s.skillOverrides))) throw new Error(`${file} → skillOverrides bir nesne değil; düzeltmeden yazılmaz.`);
92
+ if (!isObject(s) || (s.skillOverrides !== undefined && !isObject(s.skillOverrides))) throw new Error(`${file} → skillOverrides is not an object; fix it before writing.`);
93
93
  const o = { ...(s.skillOverrides ?? {}) };
94
94
  for (const c of LIGHT_COMMANDS) {
95
95
  if (mode === 'light' && o[c] === undefined) o[c] = 'name-only';
@@ -139,57 +139,56 @@ export function measure() {
139
139
  const installedList = () => Object.entries(installRecords()).flatMap(([id, k]) => [k].flat().map((e) => ({ id, scope: e?.scope, projectPath: e?.projectPath })));
140
140
  const tokenOf = (measurement, ids) => {
141
141
  const found = measurement.filter((o) => ids.includes(o.id));
142
- return found.length ? `~${found.reduce((s, o) => s + o.skillTokens + o.agentTokens, 0)} token` : '? token (kurulu değil)';
142
+ return found.length ? `~${found.reduce((s, o) => s + o.skillTokens + o.agentTokens, 0)} token` : '? token (not installed)';
143
143
  };
144
- const NOTE = 'Açtıktan sonra /reload-plugins. Oturum ortasında açmak prompt önbelleğini bozar; en iyisi faz sınırında, /clear ile.';
144
+ const NOTE = 'After enabling, run /reload-plugins. Enabling mid-session breaks the prompt cache; best at a phase boundary, together with /clear.';
145
145
  if (isMain(import.meta.url)) {
146
146
  const root = findRoot();
147
147
  const [flag, name] = process.argv.slice(2);
148
148
  try {
149
149
  if (flag === '--enable' || flag === '--disable') {
150
150
  const ids = setPackage(root, name, flag === '--enable');
151
- if (!ids.length) { console.log(`${name} paketinin eklentisi bu projede tanımlı değil; değişiklik yok.`); process.exit(0); }
152
- console.log(`${name} paketi ${flag === '--enable' ? 'açıldı' : 'proje varsayılanına döndü'} (.claude/settings.local.json): ${ids.join(', ')}`);
151
+ if (!ids.length) { console.log(`The plugins of package ${name} are not defined in this project; no change.`); process.exit(0); }
152
+ console.log(`Package ${name} ${flag === '--enable' ? 'enabled' : 'back to the project default'} (.claude/settings.local.json): ${ids.join(', ')}`);
153
153
  const missing = missingPlugins(Object.fromEntries(ids.map((id) => [id, true])), installedList(), root).map((e) => e.id);
154
- if (flag === '--enable' && missing.length) console.log(`Kurulu değil: ${missing.join(', ')}. Kullanıcıya sor, onaylarsa: node .claude/hooks/plugins-sync.mjs --install`);
154
+ if (flag === '--enable' && missing.length) console.log(`Not installed: ${missing.join(', ')}. Ask the user; if they approve: node .claude/hooks/plugins-sync.mjs --install`);
155
155
  console.log(NOTE);
156
156
  } else if (flag === '--required') {
157
157
  pkg(name);
158
158
  const d = packageStatus(root).find((x) => x.name === name);
159
- if (!d.plugins.length) { console.log(`${name} paketi bu projede yok; adımı atla.`); process.exit(0); }
160
- if (d.enabled) { console.log(`${name} paketi açık.`); process.exit(0); }
161
- console.log(`${name} paketi kapalı (${tokenOf(measure(), d.plugins)}/oturum: ${d.plugins.join(', ')}). ` +
162
- `Kullanıcıya sor; onaylarsa: node .claude/hooks/tools.mjs --enable ${name}, sonra /reload-plugins. Reddederse adımı paketsiz yap.`);
159
+ if (!d.plugins.length) { console.log(`Package ${name} is not in this project; skip the step.`); process.exit(0); }
160
+ if (d.enabled) { console.log(`Package ${name} is enabled.`); process.exit(0); }
161
+ console.log(`Package ${name} is disabled (${tokenOf(measure(), d.plugins)}/session: ${d.plugins.join(', ')}). ` +
162
+ `Ask the user; if they approve: node .claude/hooks/tools.mjs --enable ${name}, then /reload-plugins. If they decline, do the step without the package.`);
163
163
  process.exit(2);
164
164
  } else if (flag === '--measure') {
165
165
  const project = readJson(settingsPaths(root).project).enabledPlugins ?? {};
166
166
  const effective = { ...project, ...(readJson(settingsPaths(root).local).enabledPlugins ?? {}) };
167
- const packageOf = (id) => Object.keys(PACKAGES).find((a) => PACKAGES[a].plugins.includes(id)) ?? (id in project ? 'çekirdek' : '-');
167
+ const packageOf = (id) => Object.keys(PACKAGES).find((a) => PACKAGES[a].plugins.includes(id)) ?? (id in project ? 'core' : '-');
168
168
  const rows = measure().map((o) => ({ plugin: o.id, pkg: packageOf(o.id), enabled: effective[o.id] === true, skill: o.skill, descTokens: o.skillTokens, agentTokens: o.agentTokens }))
169
169
  .sort((a, b) => b.descTokens + b.agentTokens - a.descTokens - a.agentTokens);
170
- // Table headers are shown to the user: Turkish
171
- console.table(rows.map((r) => ({ eklenti: r.plugin, paket: r.pkg, açık: r.enabled, skill: r.skill, açıklamaTok: r.descTokens, ajanTok: r.agentTokens })));
170
+ console.table(rows.map((r) => ({ plugin: r.plugin, package: r.pkg, enabled: r.enabled, skill: r.skill, descTokens: r.descTokens, agentTokens: r.agentTokens })));
172
171
  const top = (l) => l.reduce((s, r) => s + r.descTokens + r.agentTokens, 0);
173
- console.log(`Açık eklentiler: ~${top(rows.filter((r) => r.enabled))} token · hepsi açık olsaydı: ~${top(rows)} token. Kesin değer: /context, /doctor.`);
172
+ console.log(`Enabled plugins: ~${top(rows.filter((r) => r.enabled))} tokens · if all were enabled: ~${top(rows)} tokens. Exact value: /context, /doctor.`);
174
173
  } else if (flag === '--commands') {
175
174
  setCommandMode(root, name);
176
175
  console.log(name === 'light'
177
- ? `Komut listesi hafif: ${LIGHT_COMMANDS.length} komut sadece adıyla listelenir (/ menüsünde durur). Yeni oturumda geçerli.`
178
- : 'Komut listesi tam: bütün komutlar açıklamasıyla listelenir. Yeni oturumda geçerli.');
176
+ ? `Command list is light: ${LIGHT_COMMANDS.length} commands are listed by name only (they stay in the / menu). Takes effect in a new session.`
177
+ : 'Command list is full: all commands are listed with their descriptions. Takes effect in a new session.');
179
178
  } else if (!flag) {
180
179
  const state = packageStatus(root);
181
180
  const project = readJson(settingsPaths(root).project).enabledPlugins ?? {};
182
181
  const packaged = new Set(Object.values(PACKAGES).flatMap((p) => p.plugins));
183
- console.log(`Çekirdek (hep açık): ${Object.keys(project).filter((id) => project[id] !== false && !packaged.has(id)).join(', ') || '-'}`);
182
+ console.log(`Core (always on): ${Object.keys(project).filter((id) => project[id] !== false && !packaged.has(id)).join(', ') || '-'}`);
184
183
  const measurement = measure();
185
184
  for (const d of state.filter((x) => x.plugins.length)) {
186
- console.log(` ${d.name.padEnd(9)} ${d.enabled ? 'açık ' : 'kapalı'} ${tokenOf(measurement, d.plugins).padEnd(24)} ${d.command} · ${d.description}`);
185
+ console.log(` ${d.name.padEnd(9)} ${d.enabled ? 'on ' : 'off'} ${tokenOf(measurement, d.plugins).padEnd(24)} ${d.command} · ${d.description}`);
187
186
  }
188
187
  const cm = commandMode(root);
189
- console.log(`Komut listesi: ${{ light: 'hafif', full: 'tam', mixed: 'karışık' }[cm.mode]} (${cm.named}/${LIGHT_COMMANDS.length} komut sadece adıyla; değiştirmek: --commands light|full)`);
188
+ console.log(`Command list: ${cm.mode} (${cm.named}/${LIGHT_COMMANDS.length} commands by name only; to change: --commands light|full)`);
190
189
  console.log(NOTE);
191
190
  } else {
192
- console.error('Kullanım: tools.mjs [--enable <paket> | --disable <paket> | --required <paket> | --measure | --commands light|full]');
191
+ console.error('Usage: tools.mjs [--enable <package> | --disable <package> | --required <package> | --measure | --commands light|full]');
193
192
  process.exit(1);
194
193
  }
195
194
  } catch (h) {
@@ -7,11 +7,13 @@
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { findRoot, isMain } from './shared.mjs';
10
+ import { t, readLang } from './lang.mjs';
10
11
 
11
12
  export const THRESHOLDS = { five_hour: { yellow: 60, red: 85 }, seven_day: { yellow: 70, red: 90 } };
12
- const WINDOWS = { five_hour: '5 saatlik', seven_day: 'haftalık' };
13
- // Level shown to the user
14
- export const LEVEL_LABEL = { green: 'yeşil', yellow: 'sarı', red: 'kırmızı', unknown: 'bilinmiyor' };
13
+ // Window → message key of its name
14
+ const WINDOWS = { five_hour: 'usage.window5h', seven_day: 'usage.windowWeek' };
15
+ // Plain level names (flow.mjs, read by the AI); summary() shows them in the project language through usage.level.* keys
16
+ export const LEVEL_LABEL = { green: 'green', yellow: 'yellow', red: 'red', unknown: 'unknown' };
15
17
  const file = (root) => path.join(root, '.claude', 'state', 'usage.json');
16
18
 
17
19
  export function writeLimits(root, rateLimits, now = Date.now()) {
@@ -50,7 +52,7 @@ export function readLimits(root, now = Date.now()) {
50
52
  return data;
51
53
  }
52
54
 
53
- // green | yellow | red | unknown (LEVEL_LABEL has the Turkish label)
55
+ // green | yellow | red | unknown (LEVEL_LABEL has the plain label)
54
56
  export function level(data) {
55
57
  if (!data) return 'unknown';
56
58
  let s = 'green';
@@ -63,23 +65,24 @@ export function level(data) {
63
65
  return s;
64
66
  }
65
67
 
66
- function timeLeft(resetsAt, now) {
68
+ function timeLeft(resetsAt, now, root) {
67
69
  if (!resetsAt) return '';
68
70
  const min = Math.max(0, Math.round((resetsAt * 1000 - now) / 60000));
69
- const clock = new Date(resetsAt * 1000).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
70
- const left = min >= 1440 ? `${Math.floor(min / 1440)} gün ${Math.floor((min % 1440) / 60)} sa` : `${Math.floor(min / 60)} sa ${min % 60} dk`;
71
- return `sıfırlanma ${clock}, ${left} sonra`;
71
+ const clock = new Date(resetsAt * 1000).toLocaleString(readLang(root) === 'tr' ? 'tr-TR' : 'en-GB', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
72
+ const left = min >= 1440
73
+ ? t(root, 'usage.leftDays', { days: Math.floor(min / 1440), hours: Math.floor((min % 1440) / 60) })
74
+ : t(root, 'usage.leftHours', { hours: Math.floor(min / 60), minutes: min % 60 });
75
+ return t(root, 'usage.resets', { clock, left });
72
76
  }
73
77
 
74
- export function summary(data, now = Date.now()) {
75
- if (!data) {
76
- return 'Limit bilgisi yok. Masaüstü uygulamasındaysan get_usage aracını kullan. Terminal/IDE\'de durum çubuğu ilk API yanıtından sonra yazar. İkisi de yoksa kullanıcıdan /usage sonucunu iste.';
77
- }
78
+ // The dashboard shows this text to the user, so it follows the project language (root)
79
+ export function summary(data, now = Date.now(), root = findRoot()) {
80
+ if (!data) return t(root, 'usage.none');
78
81
  const parts = Object.entries(WINDOWS)
79
82
  .filter(([k]) => data[k])
80
- .map(([k, name]) => `${name}: %${Math.round(data[k].percent)}${data[k].renewed ? ' (yenilendi)' : ` (${timeLeft(data[k].resetsAt, now)})`}`);
81
- const stale = data.ageMin > 30 ? ' — eski olabilir, get_usage veya /usage ile doğrula' : '';
82
- return `${parts.join(' · ')} · seviye: ${LEVEL_LABEL[level(data)]} · ${data.ageMin} dk önce okundu${stale}`;
83
+ .map(([k, name]) => t(root, 'usage.windowPart', { name: t(root, name), percent: Math.round(data[k].percent), tail: data[k].renewed ? ` ${t(root, 'usage.renewed')}` : ` (${timeLeft(data[k].resetsAt, now, root)})` }));
84
+ const stale = data.ageMin > 30 ? t(root, 'usage.stale') : '';
85
+ return t(root, 'usage.summary', { parts: parts.join(' · '), level: t(root, `usage.level.${level(data)}`), age: data.ageMin, stale });
83
86
  }
84
87
 
85
88
  if (isMain(import.meta.url)) {
@@ -1,47 +1,47 @@
1
1
  ---
2
2
  name: anlatici
3
- description: Anlatıcı mod. Her değişikliği ve hatayı yazılım bilmeyen birine anlatır gibi sade Türkçe ile açıklar. Kod yazma davranışı değişmez.
3
+ description: Narrator mode. Explains every change and error in plain language in the conversation language, as if to someone who knows no software. Coding behavior does not change.
4
4
  keep-coding-instructions: true
5
5
  ---
6
6
 
7
- # Anlatıcı mod
7
+ # Narrator mode
8
8
 
9
- Kullanıcı yazılım bilmiyor veya yeni öğreniyor. İşi her zamanki gibi yap; ama yaptığını, kullanıcının anlayacağı ve gerekirse başkasına anlatabileceği şekilde açıkla. Açıklamaları sade Türkçe ve tam cümleyle yaz. Rapor dili kısaltma kuralları (caveman) bu açıklamalara uygulanmaz; sadece durum bloğuna uygulanır.
9
+ The user knows no software or is just learning. Do the work as usual, but explain what you did so the user understands it and can explain it to someone else if needed. Write explanations in plain language in the conversation language, in full sentences. The report language shortening rules (caveman) do not apply to these explanations; they apply only to the status block.
10
10
 
11
- ## Her iş adımından sonra
11
+ ## After every work step
12
12
 
13
- Kod veya ayar değiştirdiysen, cevabın sonunda (durum bloğundan önce) şu bloğu yaz:
13
+ If you changed code or settings, write this block at the end of your answer (before the status block):
14
14
 
15
15
  ```
16
- ── Ne oldu? ─────────────────────
17
- Ne yaptım: <günlük dille 1-2 cümle; dosya adı yerine ne işe yaradığı: "kayıt formu", "giriş sayfası">
18
- Neden: <bu olmasaydı kullanıcının ne sorunu olurdu>
19
- Sen nasıl görürsün: <tıklanacak yer, açılacak sayfa veya çalıştırılacak tek komut>
20
- Yeni kavram: <bu adımda ilk kez geçen teknik terim varsa: "terim: tek cümlelik açıklama"; yoksa satırı yazma>
16
+ ── What happened? ───────────────
17
+ What I did: <1-2 sentences in everyday language; say what a file is for, not its name: "sign-up form", "login page">
18
+ Why: <what problem the user would have had without it>
19
+ How you see it: <the place to click, the page to open or the single command to run>
20
+ New concept: <a technical term that appears for the first time in this step: "term: one-sentence explanation"; leave the line out if there is none>
21
21
  ```
22
22
 
23
- ## Görev sonunda anlama kontrolü
23
+ ## Understanding check at the end of a task
24
24
 
25
- `/build` veya `/fix-bug` raporundan sonra (durum bloğundan önce) bu görevdeki değişiklik hakkında en fazla 2 soru sor. Amaç sınav yapmak değil, kullanıcının neyin neden yapıldığını anlaması.
25
+ After the `/build` or `/fix-bug` report (before the status block), ask at most 2 questions about the change in this task. The goal is not to give an exam but for the user to understand what was done and why.
26
26
 
27
- - AskUserQuestion ile sor. 3 seçenek koy: biri doğru, ikisi akla yatkın ama yanlış. Doğru seçeneğin sırasını değiştir ve "(Önerilen)" yazma. Son seçenek "Şimdi değil" olsun.
28
- - Soru "Bu değişiklik neden gerekiyordu?" veya "Bu olmasaydı ne olurdu?" türünden olsun. Dosya adı ya da kod ayrıntısı sorma.
29
- - Doğru cevaba tek cümleyle karşılık ver. Yanlış cevapta doğrusunu 1-2 cümleyle anlat, yanlışı büyütme.
30
- - Cevaplanan her soruyu `docs/workflow/learning.md` → "Kayıtlar" tablosuna bir satır olarak ekle: `| YYYY-AA-GG | <görev kodu> | <kavram, 1-3 kelime> | doğru |` (ya da `yanlış`). "Şimdi değil" ve cevapsız soruları yazma. Dosya yoksa aynı başlık ve sütunlarla oluştur.
27
+ - Ask with AskUserQuestion. Give 3 options: one correct, two plausible but wrong. Shuffle the position of the correct option and do not write "(Recommended)". Make the last option "Not now".
28
+ - Make the question of the type "Why was this change needed?" or "What would happen without it?". Do not ask about file names or code details.
29
+ - Respond to a correct answer in one sentence. For a wrong answer, explain the correct one in 1-2 sentences and do not make a big deal of the mistake.
30
+ - Add every answered question as one row to the "Kayıtlar" table in `docs/workflow/learning.md`: `| YYYY-AA-GG | <task id> | <concept, 1-3 words> | doğru |` (or `yanlış`). Do not write "Not now" and unanswered questions. If the file does not exist, create it with the same heading and columns.
31
31
 
32
- ## Hata olduğunda
32
+ ## When an error occurs
33
33
 
34
- Hata mesajını olduğu gibi kopyalama. Şöyle anlat:
34
+ Do not copy the error message as it is. Explain it like this:
35
35
 
36
- 1. **Ne oldu:** Hatanın günlük dildeki karşılığı. Örnek: "Uygulama veritabanına bağlanamadı, çünkü bağlantı adresi eksik."
37
- 2. **Senin hatan mı:** Kullanıcının bir şey yapması gerekiyor mu, yoksa düzeltmeyi sen mi yapacaksın?
38
- 3. **Ne yapıyoruz:** Düzeltme adımı. Kullanıcının yapması gereken bir şey varsa (panelden ayar, şifre girmek) adım adım ve tek tek yaz.
36
+ 1. **What happened:** The everyday-language equivalent of the error. Example: "The app could not connect to the database because the connection address is missing."
37
+ 2. **Is it your fault:** Does the user need to do something, or will you make the fix?
38
+ 3. **What we do:** The fix step. If the user has to do something (a setting in a panel, entering a password), write it step by step, one at a time.
39
39
 
40
- ## Kurallar
40
+ ## Rules
41
41
 
42
- - Teknik terimi ilk kullandığında tek cümleyle açıkla. Örnek: "commit, yaptığımız değişikliklerin kaydedilmiş bir fotoğrafıdır". Sonraki kullanımlarda tekrar açıklama.
43
- - Benzetme kullanabilirsin, ama yanlış anlamaya yol açacak kadar basitleştirme.
44
- - Karar sorarken (AskUserQuestion) her seçeneğin açıklamasında "bunu seçersen ne değişir" kısmını teknik olmayan dille yaz.
45
- - Uzun açıklama yapma: Her blok en fazla 4-5 satır olsun. Kullanıcı daha fazlasını isterse detaya gir.
46
- - Kullanıcının bir şeyi bilmemesini eksiklik gibi gösterme. "Bunu zaten biliyorsundur" gibi ifadeler kullanma.
47
- - Bir şey bozulursa son çalışan hale dönme yolunu hatırlat: `/rollback`.
42
+ - Explain a technical term in one sentence the first time you use it. Example: "a commit is a saved snapshot of the changes we made". Do not explain it again on later uses.
43
+ - You may use analogies, but do not simplify so much that it leads to misunderstanding.
44
+ - When asking for a decision (AskUserQuestion), write the "what changes if you choose this" part of each option's description in non-technical language.
45
+ - Do not explain at length: each block is 4-5 lines at most. If the user wants more, go into detail.
46
+ - Do not present not knowing something as a shortcoming. Do not use phrases like "you probably know this already".
47
+ - If something breaks, remind the user of the way back to the last working state: `/rollback`.
@@ -4,17 +4,17 @@
4
4
  "rule_name": "supabase_service_role_client",
5
5
  "regex": "(service_role|SUPABASE_SERVICE_ROLE|SUPABASE_SECRET_KEY|sb_secret_)",
6
6
  "paths": ["**/src/app/**/*.tsx", "**/src/components/**", "**/src/features/**/components/**", "**/app/**/*.tsx"],
7
- "reminder": "Supabase gizli anahtarı (sb_secret_…, eski service_role) istemci koduna girmez; RLS'yi atlar. Sunucu tarafına (server action, route handler, edge function) taşı."
7
+ "reminder": "The Supabase secret key (sb_secret_…, the old service_role) must not go into client code; it bypasses RLS. Move it to the server side (server action, route handler, edge function)."
8
8
  },
9
9
  {
10
10
  "rule_name": "public_env_secret",
11
11
  "regex": "(NEXT_PUBLIC|EXPO_PUBLIC)_[A-Z_]*(SECRET|SERVICE_ROLE|PRIVATE)",
12
- "reminder": "NEXT_PUBLIC_ / EXPO_PUBLIC_ değişkenleri tarayıcıya ve uygulama paketine gömülür. Gizli değer bu önekle tanımlanmaz."
12
+ "reminder": "NEXT_PUBLIC_ / EXPO_PUBLIC_ variables are embedded in the browser and the app bundle. Never define a secret value with this prefix."
13
13
  },
14
14
  {
15
15
  "rule_name": "personal_data_log",
16
16
  "regex": "console\\.(log|info)\\([^)]*(email|phone|tckn|tc_kimlik|password|token)",
17
- "reminder": "Kişisel veri veya kimlik bilgisi loglanmaz (KVKK). Kimliği maskele veya sadece kayıt id'sini yaz."
17
+ "reminder": "Do not log personal data or credentials (KVKK, the Turkish data protection law). Mask the identity or log only the record id."
18
18
  }
19
19
  ]
20
20
  }
@@ -24,6 +24,7 @@
24
24
  "Bash(node .claude/hooks/tools.mjs --measure)",
25
25
  "Bash(node .claude/hooks/tools.mjs --disable:*)",
26
26
  "Bash(node .claude/hooks/prefs.mjs --list)",
27
+ "Bash(node .claude/hooks/lang.mjs:*)",
27
28
  "Bash(node .claude/hooks/models.mjs --show)",
28
29
  "Bash(node .claude/hooks/models.mjs --set:*)",
29
30
  "Bash(node .claude/hooks/commit-gate.mjs --status)",
@@ -294,6 +295,7 @@
294
295
  "freeze": "name-only",
295
296
  "handoff": "name-only",
296
297
  "kit-update": "name-only",
298
+ "language": "name-only",
297
299
  "maintain": "name-only",
298
300
  "qa": "name-only",
299
301
  "research": "name-only",