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
@@ -7,21 +7,26 @@
7
7
  import fs from 'node:fs';
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
- import { readInput, decide, isMain } from './shared.mjs';
10
+ import { readInput, decide, isMain, findRoot } from './shared.mjs';
11
+ import { t } from './lang.mjs';
11
12
  import { isTestFile, isTestConfig } from './test-guard.mjs';
12
13
  import { readLock, readPlan, allowedInLock, inScope, realPath, relPath, protectedRule } from './freeze-guard.mjs';
13
14
 
15
+ // Texts of ask decisions go to the user (message keys); texts of deny decisions are plain English for the AI.
16
+ // Helpers here have no root argument: the project root comes from findRoot().
17
+ const tr = (k, vars) => t(findRoot(), k, vars);
18
+
14
19
  const DESTRUCTIVE = [
15
- [/\bdrop\s+(table|database|schema|index|view|collection)\b/i, 'DROP ile veri yapısı siliniyor'],
16
- [/\btruncate\s+(table\s+)?\w/i, 'TRUNCATE ile tablo boşaltılıyor'],
17
- [/\bdelete\s+from\s+[\w."]+\s*(;|"|'|$)/im, 'WHERE koşulu olmayan DELETE: tablodaki tüm satırlar silinir'],
18
- [/\balter\s+table\b[^;]*\bdrop\s+column\b/i, 'ALTER TABLE ... DROP COLUMN ile sütun ve verisi siliniyor'],
19
- [/\b(supabase\s+db\s+reset|prisma\s+migrate\s+reset|prisma\s+db\s+push\b[^;]*--force-reset|drizzle-kit\s+drop)\b/i, 'Veritabanı sıfırlanıyor'],
20
- [/\b(alembic\s+downgrade\s+base|dropdb\b|rails\s+db:(drop|reset)|django-admin\s+flush|manage\.py\s+flush)\b/i, 'Veritabanı veya şema siliniyor'],
21
- [/\bdocker\s+(compose\s+down\b[^;]*\s-v\b|volume\s+(rm|prune)\b|system\s+prune\b[^;]*--volumes)/i, 'Docker volume (kalıcı veri) siliniyor'],
22
- [/\bgit\s+clean\b(?=[^;]*\s--?[a-z]*f)(?=[^;]*\s-[a-z]*d)/i, 'git clean ile takip edilmeyen dosyalar kalıcı siliniyor'],
23
- [/\bredis-cli\b[^;]*\b(flushall|flushdb)\b/i, 'Redis verisi siliniyor'],
24
- [/\bfirebase\s+firestore:delete\b/i, 'Firestore verisi siliniyor'],
20
+ [/\bdrop\s+(table|database|schema|index|view|collection)\b/i, 'DROP deletes a data structure'],
21
+ [/\btruncate\s+(table\s+)?\w/i, 'TRUNCATE empties a table'],
22
+ [/\bdelete\s+from\s+[\w."]+\s*(;|"|'|$)/im, 'DELETE without WHERE: every row in the table is deleted'],
23
+ [/\balter\s+table\b[^;]*\bdrop\s+column\b/i, 'ALTER TABLE ... DROP COLUMN deletes the column and its data'],
24
+ [/\b(supabase\s+db\s+reset|prisma\s+migrate\s+reset|prisma\s+db\s+push\b[^;]*--force-reset|drizzle-kit\s+drop)\b/i, 'Database is being reset'],
25
+ [/\b(alembic\s+downgrade\s+base|dropdb\b|rails\s+db:(drop|reset)|django-admin\s+flush|manage\.py\s+flush)\b/i, 'Database or schema is being deleted'],
26
+ [/\bdocker\s+(compose\s+down\b[^;]*\s-v\b|volume\s+(rm|prune)\b|system\s+prune\b[^;]*--volumes)/i, 'Docker volume (persistent data) is being deleted'],
27
+ [/\bgit\s+clean\b(?=[^;]*\s--?[a-z]*f)(?=[^;]*\s-[a-z]*d)/i, 'git clean permanently deletes untracked files'],
28
+ [/\bredis-cli\b[^;]*\b(flushall|flushdb)\b/i, 'Redis data is being deleted'],
29
+ [/\bfirebase\s+firestore:delete\b/i, 'Firestore data is being deleted'],
25
30
  ];
26
31
 
27
32
  // Database addresses other than localhost count as prod (or a shared environment)
@@ -36,15 +41,15 @@ const UNCLEAR_ENV = /\b(vercel|vc)(@[\w.-]+)?\b(?=[^;&|]*\s(env\s+pull|pull)\b)(
36
41
 
37
42
  const NEEDS_APPROVAL = [
38
43
  // unquote() opens quoted spellings; here only what the shell sees
39
- [/\b(vercel|vc)\b[^;&|]*(--prod\b|--target(=|\s+)prod(uction)?\b)/i, 'Prod ortamına deploy'],
44
+ [/\b(vercel|vc)\b[^;&|]*(--prod\b|--target(=|\s+)prod(uction)?\b)/i, 'guard.destructive.prodDeploy'],
40
45
  // The subcommand can also come after flags: vercel --scope team promote <url>
41
- [/\b(vercel|vc)(@[\w.-]+)?\b[^;&|]*\s(promote|rollback)\b/i, 'Prod ortamı değişiyor (promote/rollback)'],
42
- [UNCLEAR_ENV, 'Ortam değişkenleri çekiliyor ama ortam düz development/preview değil (değişken veya özel ortam: prod olabilir)'],
43
- [/\b(fly|flyctl)\s+deploy\b|\brailway\s+up\b|\bnetlify\s+deploy\b[^;]*--prod/i, 'Sunucuya deploy'],
44
- [/\beas(-cli(@[\w.-]+)?)?\s+(submit\b|update:(rollback|republish)\b|update\b(?=[^;\n]*--(branch|channel|environment)(=|\s+)['"]?prod(uction)?\b)|deploy\b(?=[^;\n]*--prod\b))/i, 'Mağazaya gönderim, prod OTA güncellemesi veya geri alma'],
45
- [/\bgit\s+push\b[^;]*\b(main|master|production)\b/i, 'Ana dala doğrudan push'],
46
- [/\bgit\s+(rebase|filter-branch|filter-repo)\b/i, 'Git geçmişi yeniden yazılıyor'],
47
- [/\b(alembic\s+upgrade|prisma\s+migrate\s+deploy|supabase\s+db\s+push)\b/i, 'Migration uygulanıyor (hedef ortamı kontrol et)'],
46
+ [/\b(vercel|vc)(@[\w.-]+)?\b[^;&|]*\s(promote|rollback)\b/i, 'guard.destructive.promoteRollback'],
47
+ [UNCLEAR_ENV, 'guard.destructive.unclearEnv'],
48
+ [/\b(fly|flyctl)\s+deploy\b|\brailway\s+up\b|\bnetlify\s+deploy\b[^;]*--prod/i, 'guard.destructive.serverDeploy'],
49
+ [/\beas(-cli(@[\w.-]+)?)?\s+(submit\b|update:(rollback|republish)\b|update\b(?=[^;\n]*--(branch|channel|environment)(=|\s+)['"]?prod(uction)?\b)|deploy\b(?=[^;\n]*--prod\b))/i, 'guard.destructive.storeSubmit'],
50
+ [/\bgit\s+push\b[^;]*\b(main|master|production)\b/i, 'guard.destructive.pushMain'],
51
+ [/\bgit\s+(rebase|filter-branch|filter-repo)\b/i, 'guard.destructive.rewriteHistory'],
52
+ [/\b(alembic\s+upgrade|prisma\s+migrate\s+deploy|supabase\s+db\s+push)\b/i, 'guard.destructive.migration'],
48
53
  ];
49
54
 
50
55
  // The test guard runs on file tools; changing a test or test config file from the shell (sed -i, rm, >, mv, git checkout) is asked.
@@ -106,10 +111,10 @@ export function shellTargets(command, dir) {
106
111
  // \\?\ and \\.\ drop only before a drive letter; \\?\UNC\ turns back into plain UNC; GLOBALROOT, pipe etc. stay unclear
107
112
  let h = t.replace(/^(\\\\|\/\/)[?.][\\/](?=[a-zA-Z]:)/, '').replace(/^(\\\\|\/\/)[?.][\\/]UNC[\\/]/i, '\\\\')
108
113
  .replace(/^(\\\\|\/\/)(localhost|127\.0\.0\.1)[\\/]([a-zA-Z])\$(?=[\\/]|$)/i, '$3:');
109
- if (/^(\\\\|\/\/)/.test(h)) return markUnclear(`hedef ağ yolu (${t})`), null;
114
+ if (/^(\\\\|\/\/)/.test(h)) return markUnclear(tr('guard.destructive.unclearNetPath', { path: t })), null;
110
115
  h = h.replace(/\$\{?PWD\}?/g, d).replace(/^~(?=\/|\\|$)/, os.homedir());
111
- if (/[$`]/.test(h)) return markUnclear(`hedef değişken veya komut içeriyor (${t})`), null;
112
- if (/\{[^}]*(,|\.\.)[^}]*\}/.test(h)) return markUnclear(`hedef süslü parantez açılımı içeriyor (${t})`), null;
116
+ if (/[$`]/.test(h)) return markUnclear(tr('guard.destructive.unclearVar', { path: t })), null;
117
+ if (/\{[^}]*(,|\.\.)[^}]*\}/.test(h)) return markUnclear(tr('guard.destructive.unclearBrace', { path: t })), null;
113
118
  if (process.platform === 'win32') h = h.replace(/^\/([a-zA-Z])(?=\/|$)/, '$1:');
114
119
  let p = path.resolve(d, h);
115
120
  // Glob: if missing on disk, the folder before the first wildcard is the target (src/*.ts → src, * → current folder)
@@ -139,7 +144,7 @@ export function shellTargets(command, dir) {
139
144
  const target = name === 'popd' ? stack.pop() : resolve(a.filter((x) => !/^-/.test(x) && !/^\/d$/i.test(x)).pop() ?? '$HOME');
140
145
  if (name === 'pushd') stack.push(dir);
141
146
  if (target && fs.existsSync(target)) dir = target;
142
- else markUnclear(`${name} hedefi doğrulanamadı (${a.join(' ') || 'ev dizini'}); sonraki komutların klasörü bilinmiyor`);
147
+ else markUnclear(tr('guard.destructive.unclearCd', { name, target: a.join(' ') || tr('guard.destructive.homeDir') }));
143
148
  close();
144
149
  continue;
145
150
  }
@@ -156,19 +161,19 @@ export function shellTargets(command, dir) {
156
161
  }
157
162
  if (name === 'xargs') {
158
163
  const inner = a.filter((x) => !x.startsWith('-'));
159
- if (writerKind(path.basename(inner[0] ?? '').toLowerCase(), inner.slice(1))) markUnclear(`xargs ${inner[0]} hedef dosyayı göstermiyor`);
164
+ if (writerKind(path.basename(inner[0] ?? '').toLowerCase(), inner.slice(1))) markUnclear(tr('guard.destructive.unclearXargs', { command: inner[0] }));
160
165
  close();
161
166
  continue;
162
167
  }
163
168
  const kind = writerKind(name, a);
164
- if (kind === 'unclear') markUnclear(`${name} ${a.find((x) => !x.startsWith('-')) ?? ''}`.trim() + ' hedef dosyayı göstermiyor');
169
+ if (kind === 'unclear') markUnclear(tr('guard.destructive.unclearNoTarget', { command: `${name} ${a.find((x) => !x.startsWith('-')) ?? ''}`.trim() }));
165
170
  if (kind === 'target') {
166
171
  for (const x of fileArgs(name, a)) {
167
172
  const p = resolve(x);
168
173
  if (!p) continue;
169
174
  targets.push(p);
170
175
  // git checkout <branch>: if it is not a path it is a branch switch, the whole working tree may change
171
- if (name === 'git' && !fs.existsSync(p) && !a.slice(0, a.indexOf(x)).includes('--')) markUnclear(`git ${a[0]} ${x}: yol değil (dal olabilir)`);
176
+ if (name === 'git' && !fs.existsSync(p) && !a.slice(0, a.indexOf(x)).includes('--')) markUnclear(tr('guard.destructive.unclearBranch', { sub: a[0], arg: x }));
172
177
  }
173
178
  }
174
179
  close();
@@ -214,34 +219,32 @@ export function scopeIssue(command, dir, root) {
214
219
  const touchesApproval = (y) => VIBE_KIT.test(y) || !/^\.\.|^$/.test(path.relative(y, vk)) && !path.isAbsolute(path.relative(y, vk));
215
220
  for (const h of targets) {
216
221
  if (touchesApproval(h) || touchesApproval(realPath(h))) return { decision: 'ask', reason: /tercihler\.md$/i.test(h)
217
- ? `${h}: kişisel tercih dosyası (kullanıcı klasörü); kabukla değiştirilmez, node .claude/hooks/prefs.mjs --add / --remove kullanılır. Kullanıcı onayı gerekli.`
218
- : `${h}: kapı komut onayı kaydı (kullanıcı klasörü); kabukla değiştirilmez, node .claude/hooks/gate.mjs --approve kullanılır. Kullanıcı onayı gerekli.` };
222
+ ? t(root, 'guard.destructive.prefsFile', { path: h })
223
+ : t(root, 'guard.destructive.gateApproval', { path: h }) };
219
224
  const rel = relPath(realPath(h), groot);
220
- const k = protectedRule(rel) ?? protectedRule(`${rel}/`);
221
- if (k) return { decision: k[0], reason: k[0] === 'deny' ? `${rel}: ${k[2]}`
222
- : `${rel}: ${k[2]} Kabuk komutuyla değiştiriliyor; kilit, plan ve onay durumları için ilgili kit komutunu kullan. Kullanıcı onayı gerekli.` };
225
+ const k = protectedRule(rel, root) ?? protectedRule(`${rel}/`, root);
226
+ if (k) return { decision: k[0], reason: k[0] === 'deny' ? `${rel}: ${k[2]}` : t(root, 'guard.destructive.protectedShell', { rel, text: k[2] }) };
223
227
  }
224
228
  // The target could not be resolved but the command names a rule/guard file ($D/AGENTS.md, {AGENTS,X}.md): asked even without a lock/plan
225
229
  if (unclear && /AGENTS|CLAUDE|\.(claude|git|github|codex|agents|env|mcp)\b|secrets|vibe-kit/i.test(command)) {
226
- return { decision: 'ask', reason: `${unclear}; komut kural veya koruma dosyası adını içeriyor (.claude, .git, AGENTS.md…), hedef doğrulanamadı. Kullanıcı onayı gerekli.` };
230
+ return { decision: 'ask', reason: t(root, 'guard.destructive.unclearRule', { unclear }) };
227
231
  }
228
232
  const lock = readLock(root), plan = readPlan(root);
229
233
  if (!lock && !plan) return null;
230
- const label = lock ? `Düzenleme kilidi (${lock}/)` : `Kapsam kalkanı (görev ${plan.task})`;
231
- if (unclear) return { decision: 'ask', reason: `${label}: ${unclear}; hangi dosyaların değişeceği bilinemiyor. Gerekliyse kullanıcıya sor.` };
234
+ const label = lock ? t(root, 'guard.destructive.labelLock', { lock }) : t(root, 'guard.destructive.labelPlan', { task: plan.task });
235
+ if (unclear) return { decision: 'ask', reason: t(root, 'guard.destructive.scopeUnclear', { label, unclear }) };
232
236
  for (const h of targets) {
233
237
  if (!fs.existsSync(h)) continue;
234
238
  const p = realPath(h), up = path.relative(p, groot), rel = relPath(p, groot);
235
239
  if (!up.startsWith('..') && !path.isAbsolute(up)) {
236
- return { decision: 'ask', reason: `${label}: komut proje kökünü veya üst klasörünü hedefliyor (${rel || '.'}); kilit/plan dışındaki dosyalar da değişebilir. Gerekliyse kullanıcıya sor.` };
240
+ return { decision: 'ask', reason: t(root, 'guard.destructive.scopeRoot', { label, rel: rel || '.' }) };
237
241
  }
238
242
  if (rel.startsWith('..') || path.isAbsolute(rel)) continue;
239
243
  if (lock && !allowedInLock(p, lock, groot)) {
240
- return { decision: 'ask', reason: `Düzenleme kilidi: ${rel} kilit (${lock}/) dışında ve komut onu değiştirebilir. Gerekliyse kullanıcıya sor.` };
244
+ return { decision: 'ask', reason: t(root, 'guard.destructive.lockOutside', { rel, lock }) };
241
245
  }
242
246
  if (plan && !inScope(fs.statSync(p).isDirectory() ? `${rel}/` : rel, plan.paths)) {
243
- return { decision: 'ask', reason: `Kapsam kalkanı: ${rel} onaylı planda yok (görev ${plan.task}) ve komut onu değiştirebilir. Gerekliyse kullanıcıya sor; ` +
244
- 'onaylarsa node .claude/hooks/freeze-guard.mjs --plan-add <yol>.' };
247
+ return { decision: 'ask', reason: t(root, 'guard.destructive.planOutside', { rel, task: plan.task }) };
245
248
  }
246
249
  }
247
250
  return null;
@@ -252,22 +255,22 @@ export function scopeIssue(command, dir, root) {
252
255
  // Only the real .claude/hooks/commit-gate.mjs --approve is kept out of the gate rule (separators optional: unquote drops backslashes;
253
256
  // anchored at the start of the path, so sandbox/.claude/hooks/commit-gate.mjs, a renamed gate.mjs, is not exempt).
254
257
  const PERMISSION_EQUIVALENT = [
255
- ['deny', /\brm\b(?=[^;&|]*\s--?[a-z]*r)(?=[^;&|]*\s--?[a-z]*f)/i, 'rm -rf ile toplu silme'],
256
- ['deny', /\b(Remove-Item|rm|ri|del|erase)\b[^;&|]*\s-Recurse\b|\b(rmdir|rd|del)\s+[^;&|]*\/s\b/i, 'Klasör toplu siliniyor'],
257
- ['deny', /\bgit\s+reset\s+[^;&|]*--hard\b/i, 'git reset --hard ile commit edilmemiş iş siliniyor'],
258
- ['deny', /\bgit\s+push\b[^;&|]*\s(--force\S*|-\w*f\w*|\+\S+)(?=\s|$)/i, 'git push --force ile uzak geçmiş eziliyor'],
259
- ['deny', /(^|[\s'"=<>/\\])\.env(?!\.example\b)(\.[\w.-]+|\*)?(?=$|[\s'";|&)<>])|(^|[\s'"/\\])secrets[/\\]/i, '.env veya secrets/ dosyasına erişiliyor (AI okumaz, yazmaz)'],
260
- ['ask', /freeze-guard\.mjs['"]?\s+['"]?--off\b/i, 'Düzenleme kilidi kaldırılıyor'],
261
- ['ask', /tools\.mjs['"]?\s+['"]?--commands\b/i, 'Paylaşılan .claude/settings.json → skillOverrides değişiyor (komut listesi)'],
262
- ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-add\b/i, 'Onaylı planın dosya listesi genişliyor (kapsam kalkanı)'],
263
- ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-save\b/i, 'Kapsam kalkanının dosya listesi yazılıyor'],
264
- ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-end\b/i, 'Kapsam kalkanı kapatılıyor'],
265
- ['ask', /commit-gate\.mjs\b[^;&|\n]*--skip\b/i, 'Commit kapısı ve kurul denetimi atlanıyor'],
266
- ['ask', /prefs\.mjs['"]?\s+['"]?--(add|remove)\b/i, 'Kişisel tercih dosyası (~/.vibe-kit/tercihler.md) değişiyor'],
267
- ['ask', /(?<!(?:^|[\s'"=])(?:\.[\\/]|\$\{?CLAUDE_PROJECT_DIR\}?[\\/]?)?\.claude[\\/]?hooks[\\/]?commit-)gate\.mjs['"]?\s+['"]?--approve\b/i, 'Deterministik kapının çalıştıracağı komutlar onaylanıyor'],
258
+ ['deny', /\brm\b(?=[^;&|]*\s--?[a-z]*r)(?=[^;&|]*\s--?[a-z]*f)/i, 'Bulk deletion with rm -rf'],
259
+ ['deny', /\b(Remove-Item|rm|ri|del|erase)\b[^;&|]*\s-Recurse\b|\b(rmdir|rd|del)\s+[^;&|]*\/s\b/i, 'A folder is being bulk deleted'],
260
+ ['deny', /\bgit\s+reset\s+[^;&|]*--hard\b/i, 'git reset --hard deletes uncommitted work'],
261
+ ['deny', /\bgit\s+push\b[^;&|]*\s(--force\S*|-\w*f\w*|\+\S+)(?=\s|$)/i, 'git push --force overwrites the remote history'],
262
+ ['deny', /(^|[\s'"=<>/\\])\.env(?!\.example\b)(\.[\w.-]+|\*)?(?=$|[\s'";|&)<>])|(^|[\s'"/\\])secrets[/\\]/i, 'Access to .env or secrets/ (the AI does not read or write them)'],
263
+ ['ask', /freeze-guard\.mjs['"]?\s+['"]?--off\b/i, 'guard.destructive.perm.freezeOff'],
264
+ ['ask', /tools\.mjs['"]?\s+['"]?--commands\b/i, 'guard.destructive.perm.toolsCommands'],
265
+ ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-add\b/i, 'guard.destructive.perm.planAdd'],
266
+ ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-save\b/i, 'guard.destructive.perm.planSave'],
267
+ ['ask', /freeze-guard\.mjs['"]?\s+['"]?--plan-end\b/i, 'guard.destructive.perm.planEnd'],
268
+ ['ask', /commit-gate\.mjs\b[^;&|\n]*--skip\b/i, 'guard.destructive.perm.commitSkip'],
269
+ ['ask', /prefs\.mjs['"]?\s+['"]?--(add|remove)\b/i, 'guard.destructive.perm.prefsEdit'],
270
+ ['ask', /(?<!(?:^|[\s'"=])(?:\.[\\/]|\$\{?CLAUDE_PROJECT_DIR\}?[\\/]?)?\.claude[\\/]?hooks[\\/]?commit-)gate\.mjs['"]?\s+['"]?--approve\b/i, 'guard.destructive.perm.gateApprove'],
268
271
  // Shell counterpart of the PROTECTED rule on file tools: a command that names a rule or guard file and writes/deletes is asked
269
272
  // (.git: the gate command approval and git hooks live there)
270
- ['ask', /(?=.*((^|[\s'"/\\])\.(claude|codex|agents|git)(?=$|[\s/\\'"])|\bAGENTS\.md|\bCLAUDE\.md))(?=.*(\b(rm|del|erase|rmdir|rd|mv|move|cp|copy|tee|truncate|checkout|restore|Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item)\b|\bsed\s+-i|>|\bnode\s+-e|\bpython\d?\s+-c))/i, 'Kural veya koruma dosyası kabuk komutuyla değiştiriliyor'],
273
+ ['ask', /(?=.*((^|[\s'"/\\])\.(claude|codex|agents|git)(?=$|[\s/\\'"])|\bAGENTS\.md|\bCLAUDE\.md))(?=.*(\b(rm|del|erase|rmdir|rd|mv|move|cp|copy|tee|truncate|checkout|restore|Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item)\b|\bsed\s+-i|>|\bnode\s+-e|\bpython\d?\s+-c))/i, 'guard.destructive.perm.protectedShell'],
271
274
  ];
272
275
 
273
276
  // What the shell will see: $'..' is opened, quotes, \ escapes and PowerShell ` escapes drop. For matching only.
@@ -292,7 +295,10 @@ export function permissionEquivalent(command) {
292
295
  function permissionOne(command) {
293
296
  const k = command.replace(/\s+/g, ' ').replace(/\b2>&1|\b2>\s*(\/dev\/null|nul)\b/gi, ''); // an error redirect is not a write
294
297
  const e = PERMISSION_EQUIVALENT.find(([, re]) => re.test(k));
295
- return e ? { decision: e[0], reason: `${e[2]}. ${e[0] === 'deny' ? 'AI bu komutu çalıştıramaz; gerekliyse kullanıcı kendisi çalıştırır.' : 'Kullanıcı onayı gerekli.'}` } : null;
298
+ if (!e) return null;
299
+ return e[0] === 'deny'
300
+ ? { decision: 'deny', reason: `${e[2]}. The AI cannot run this command; if needed, the user runs it themselves.` }
301
+ : { decision: 'ask', reason: tr('guard.destructive.approvalSuffix', { reason: tr(e[2]) }) };
296
302
  }
297
303
 
298
304
  // When in doubt, ask: instead of emulating the shell, if a protected word shows up while an obfuscation mark is present, the decision is "ask".
@@ -310,7 +316,7 @@ function obfuscationOne(command) {
310
316
  if (!OBFUSCATION.test(command)) return null;
311
317
  const g = loosen(command);
312
318
  return GUARDED.some((re) => re.test(g))
313
- ? { decision: 'ask', reason: 'Komut gizlenmiş görünüyor (satır sonu \\, $\'..\', $(...), `...` veya ${...}) ve korunan bir işlem içeriyor. Kullanıcı onayı gerekli.' } : null;
319
+ ? { decision: 'ask', reason: tr('guard.destructive.obfuscated') } : null;
314
320
  }
315
321
 
316
322
  // A kit guard script (.claude/hooks) run with an indirect spelling: $VAR, $(..), `..`, ${..}, $'..', a glob * ?, xargs, a pipe or < (commit-gate.mjs $S,
@@ -322,7 +328,7 @@ export function hookScriptIssue(command) {
322
328
  if (!/\bnode\b/i.test(k) || !/\.claude|hooks|commit-|gate|freeze/i.test(command)) return null;
323
329
  // Arguments from elsewhere count too: xargs, a pipe into node, an input redirect or <(...)
324
330
  return /\$\(|`|\$\{|\$'|\$[A-Za-z_]|[*?]|\bxargs\b|\|[^|;&]*\bnode\b|<(?!<)/.test(k)
325
- ? { decision: 'ask', reason: 'Kitin koruma betiği dolaylı yazımla çalıştırılıyor ($DEĞİŞKEN, $(...), `...`, ${...} veya * ?); kural metni bunun içini göremez. Kullanıcı onayı gerekli.' } : null;
331
+ ? { decision: 'ask', reason: tr('guard.destructive.indirectHook') } : null;
326
332
  }
327
333
 
328
334
  // Separators inside quotes turned into spaces: a quoted & or | ("a & b") must not split a command so its later flags escape the rules
@@ -331,7 +337,7 @@ const blankQuoted = (s) => s.replace(/"(?:[^"\\]|\\.)*"|'[^']*'/g, (q) => q.repl
331
337
  // path ("src/a.test.ts") is kept. Such commands are still asked through evaluateOne.
332
338
  const quotedTextOff = (s) => s.replace(/"(?:[^"\\]|\\.)*"|'[^']*'/g, (q) => (/\s/.test(q) ? ' ' : q.slice(1, -1)));
333
339
  const INLINE = /\b(node|deno|bun)\s+(-e|--eval|-p|--print)\b|\bpython[\d.]*\s+-c\b|\b(ruby|perl)\s+-e\b|\b(ba|z|da)?sh\s+-c\b|\b(pwsh|powershell)(\.exe)?\s+(-c|-Command)\b|\bcmd(\.exe)?\s+\/c\b/i;
334
- const TEST_WRITER_ONLY = 'Test dosyasına yalnız test-writer ajanı yazar: kodu yazan testi değiştiremez (AGENTS.md kural 7). Test eklemek veya değiştirmek gerekiyorsa test-writer ajanını çağır (varsa SendMessage ile sürdür).';
340
+ const TEST_WRITER_ONLY = 'Only the test-writer agent writes to test files: whoever writes the code cannot change tests (AGENTS.md rule 7). If a test must be added or changed, call the test-writer agent (continue it with SendMessage if it exists).';
335
341
  // ctx: { tool, agent } of the call; only Claude Code tells which subagent runs it. Test files are written by the
336
342
  // test-writer agent only; test config files stay a question (evaluateOne).
337
343
  export function evaluate(command, ctx = {}) {
@@ -339,28 +345,32 @@ export function evaluate(command, ctx = {}) {
339
345
  ? { decision: 'deny', reason: TEST_WRITER_ONLY } : null;
340
346
  // An inline script (node -e, python -c, bash -c) that names a test path may write it; the shell rules cannot see inside
341
347
  const inline = INLINE.test(command) && command.split(/[\s'"`(),;=<>]+/).some(isTestCode)
342
- ? { decision: 'ask', reason: 'Satır içi betik bir test yolunu anıyor; test dosyasına yazıyor olabilir. Test dosyasına yalnız test-writer ajanı yazar (AGENTS.md kural 7). Kullanıcı onayı gerekli.' } : null;
348
+ ? { decision: 'ask', reason: tr('guard.destructive.inlineTest') } : null;
343
349
  return strictest(testWrite, inline, evaluateOne(command), evaluateOne(unquote(command)), evaluateOne(blankQuoted(command)), obfuscationOne(command), hookScriptIssue(command));
344
350
  }
345
351
  function evaluateOne(command) {
346
352
  const k = command.replace(/\s+/g, ' ');
347
353
  const prodDb = PROD_DB.test(k) || PROD_MARK.test(k);
348
354
  if (PROD_SECRET.test(k)) {
349
- return { decision: 'deny', reason: 'Prod ortam değişkenleri (sırlar) bu makineye çekiliyor. Yerel ortam ve AI sadece dev projesine bağlanır; prod değerleri barındırma panelinde kalır. Gerekliyse kullanıcı kendisi çalıştırır.' };
355
+ return { decision: 'deny', reason: 'Prod environment variables (secrets) are being pulled onto this machine. The local environment and the AI connect to the dev project only; prod values stay in the hosting dashboard. If needed, the user runs it themselves.' };
350
356
  }
351
357
  for (const [re, reason] of DESTRUCTIVE) {
352
358
  if (re.test(k)) {
353
- return { decision: 'deny', reason: `${reason}${prodDb ? ' — PROD/uzak veritabanı hedefleniyor' : ''}. AI bu komutu çalıştıramaz. Gerekliyse kullanıcı, önce yedek alıp kendisi çalıştırır (Claude Code'da "! komut" veya kendi terminali).` };
359
+ return { decision: 'deny', reason: `${reason}${prodDb ? ' — a PROD/remote database is targeted' : ''}. The AI cannot run this command. If needed, the user takes a backup first and runs it themselves (in Claude Code with "! command" or in their own terminal).` };
354
360
  }
355
361
  }
356
- for (const [re, reason] of NEEDS_APPROVAL) {
357
- if (re.test(k)) return { decision: prodDb ? 'deny' : 'ask', reason: `${reason}. Kullanıcı onayı gerekli.${prodDb ? ' Komut prod veritabanı adresi içeriyor; AI çalıştıramaz.' : ''}` };
362
+ for (const [re, key] of NEEDS_APPROVAL) {
363
+ if (re.test(k)) {
364
+ return prodDb
365
+ ? { decision: 'deny', reason: `${tr(key)}. User approval required. The command contains a prod database address; the AI cannot run it.` }
366
+ : { decision: 'ask', reason: tr('guard.destructive.approvalSuffix', { reason: tr(key) }) };
367
+ }
358
368
  }
359
369
  if (prodDb && /\b(psql|mysql|mongosh|redis-cli|prisma|drizzle-kit|alembic|supabase)\b/i.test(k)) {
360
- return { decision: 'ask', reason: 'Komut localhost dışındaki bir veritabanına bağlanıyor (prod olabilir). Kullanıcı onayı gerekli; AI prod erişim bilgisi kullanmamalı.' };
370
+ return { decision: 'ask', reason: tr('guard.destructive.remoteDb') };
361
371
  }
362
372
  if (writesTest(k)) {
363
- return { decision: 'ask', reason: 'Test dosyası kabuk komutuyla değiştiriliyor, taşınıyor veya siliniyor. Testi geçirmek için testi zayıflatmak yasak (AGENTS.md kural 7); test düzenlemesini dosya aracıyla yap. Kullanıcı onayı gerekli.' };
373
+ return { decision: 'ask', reason: tr('guard.destructive.testShell') };
364
374
  }
365
375
  return null;
366
376
  }
@@ -370,6 +380,6 @@ if (isMain(import.meta.url)) {
370
380
  // Of the lists, the stricter wins (deny > ask)
371
381
  const results = g ? [evaluate(g.command, g), g.tool !== 'claude' ? permissionEquivalent(g.command) : null, scopeIssue(g.command, g.dir, g.root)].filter(Boolean) : [];
372
382
  const s = results.find((x) => x.decision === 'deny') ?? results[0];
373
- if (s) decide(g.tool, s.decision, `Kalkan: ${s.reason}`);
383
+ if (s) decide(g.tool, s.decision, s.decision === 'ask' ? tr('guard.destructive.prefix', { reason: s.reason }) : `Shield: ${s.reason}`);
374
384
  process.exit(0);
375
385
  }
@@ -30,9 +30,9 @@ function writeState(root, d) {
30
30
 
31
31
  // The AI runs the command in the note: $( ) or ` in a file name must not get into it, so the name is written only if it uses safe characters
32
32
  const SAFE = /^[\p{L}\p{N}_@./+-]+$/u;
33
- const noteText = (rel, n) => `${SAFE.test(rel) ? rel : 'Adı özel karakter içeren bir dosya'} bu oturumda ${n}. kez düzenlendi. Tekrarlanan AI düzenlemeleri güvenlik açığı biriktirebilir: ` +
34
- `devam etmeden bu dosyada Semgrep çalıştır (\`semgrep scan --config p/default --config p/owasp-top-ten -- "${SAFE.test(rel) ? rel : '<dosya yolu>'}"\`; kurulu değilse CI'daki semgrep işine bak) ` +
35
- 've değişikliği security-auditor ile gözden geçir. Aynı hatayı 2 denemede çözemediysen dur ve kullanıcıdan yön iste.';
33
+ const noteText = (rel, n) => `${SAFE.test(rel) ? rel : 'A file whose name has special characters'} has been edited ${n} times in this session. Repeated AI edits can pile up security holes: ` +
34
+ `before going on, run Semgrep on this file (\`semgrep scan --config p/default --config p/owasp-top-ten -- "${SAFE.test(rel) ? rel : '<file path>'}"\`; if it is not installed, see the semgrep job in CI) ` +
35
+ 'and review the change with security-auditor. If you cannot fix the same error in 2 attempts, stop and ask the user for direction.';
36
36
 
37
37
  function hook() {
38
38
  const g = readInput();
@@ -28,11 +28,11 @@ const SENSITIVE = /auth|login|session|passw|payment|ödeme|checkout|billing|webh
28
28
  // Same bands as the cost table in docs/workflow/usage-limits.md §3 (1-2, 3-6, more files)
29
29
  export function flow({ files = 0, paths, installed = [], context, level: lvl }) {
30
30
  if (paths) files = paths.length;
31
- const size = files <= 2 ? 'küçük' : files <= 6 ? 'orta' : 'büyük';
31
+ const size = files <= 2 ? 'small' : files <= 6 ? 'medium' : 'large';
32
32
  const clear = context != null && context >= 50;
33
33
  // A full main context is paid again on every turn: the code goes out of it
34
- const code = size === 'küçük' && !clear ? 'ana' : 'alt ajan';
35
- const base = { küçük: 1, orta: 2, büyük: 3 }[size];
34
+ const code = size === 'small' && !clear ? 'main' : 'subagent';
35
+ const base = { small: 1, medium: 2, large: 3 }[size];
36
36
  // Yellow halves the members (rules.md §4); red runs one, so a RET stops before the rest is paid for
37
37
  const members = lvl === 'red' ? 1 : lvl === 'yellow' ? Math.ceil(base / 2) : base;
38
38
  // Most matching files first; only members this project's board has
@@ -44,13 +44,13 @@ export function flow({ files = 0, paths, installed = [], context, level: lvl })
44
44
  }
45
45
 
46
46
  export function flowText(f) {
47
- const where = (w) => (w === 'ana' ? 'ana oturum' : 'alt ajan');
47
+ const where = (w) => (w === 'main' ? 'main session' : 'subagent');
48
48
  return [
49
- `Akış: ${f.files} dosya · ${f.size} · bağlam ${f.context == null ? 'bilinmiyor' : `%${f.context}`} · limit ${LEVEL_LABEL[f.level]}`,
50
- `Kod: ${where(f.code)}${f.code === 'ana' ? '' : ' (kod rolü)'}`,
51
- `Doküman işi (roadmap, progress, maliyet günlüğü progress.mjs ile; bu satır diğerleri için): ${where(f.docs)}${f.docs === 'ana' ? '' : ' (doküman rolü)'}`,
52
- `Kurul: ${f.board?.length ? f.board.join(', ') : f.board ? 'ilgili üye yok' : `ilgili ${f.members} üye`} + code-reviewer${f.security ? ' + security-auditor (hassas yol)' : ''}, ${f.parallel ? 'paralel' : 'sırayla'}${f.board ? '' : ' (hassas yolda + security-auditor)'}`,
53
- ...(f.clear ? [`Önce /clear öner: bağlam %${f.context}, her tur bunu yeniden okur.`] : []),
49
+ `Flow: ${f.files} files · ${f.size} · context ${f.context == null ? 'unknown' : `${f.context}%`} · limit ${LEVEL_LABEL[f.level]}`,
50
+ `Code: ${where(f.code)}${f.code === 'main' ? '' : ' (code role)'}`,
51
+ `Docs work (roadmap, progress and the cost log go through progress.mjs; this line is for the rest): ${where(f.docs)}${f.docs === 'main' ? '' : ' (docs role)'}`,
52
+ `Board: ${f.board?.length ? f.board.join(', ') : f.board ? 'no matching member' : `${f.members} matching members`} + code-reviewer${f.security ? ' + security-auditor (sensitive path)' : ''}, ${f.parallel ? 'in parallel' : 'in sequence'}${f.board ? '' : ' (plus security-auditor on a sensitive path)'}`,
53
+ ...(f.clear ? [`Suggest /clear first: context ${f.context}%, every turn rereads it.`] : []),
54
54
  ].join('\n');
55
55
  }
56
56
 
@@ -64,7 +64,7 @@ if (isMain(import.meta.url)) {
64
64
  const i = process.argv.indexOf(name);
65
65
  if (i < 0) return undefined;
66
66
  const n = Number(process.argv[i + 1]);
67
- if (!Number.isFinite(n) || n < 0) { console.error('Kullanım: node .claude/hooks/flow.mjs [--files N] [--context N] [--limit N] [--json]'); process.exit(1); }
67
+ if (!Number.isFinite(n) || n < 0) { console.error('Usage: node .claude/hooks/flow.mjs [--files N] [--context N] [--limit N] [--json]'); process.exit(1); }
68
68
  return n;
69
69
  };
70
70
  const files = arg('--files');
@@ -17,6 +17,7 @@
17
17
  import fs from 'node:fs';
18
18
  import path from 'node:path';
19
19
  import { readInput, decide, findRoot, isMain } from './shared.mjs';
20
+ import { t } from './lang.mjs';
20
21
 
21
22
  // Project root even when called from a subfolder: lock and plan are written where the hook reads them; paths are root-relative
22
23
  const root = findRoot();
@@ -27,10 +28,14 @@ const FREE = ['docs/workflow/progress.md', '.claude/state/'];
27
28
 
28
29
  // Counterpart of .claude/settings.json → permissions (deny: .env and secrets/, ask: rule and guard files)
29
30
  const PROTECTED = [
30
- ['deny', /^(\.env(\.local|\.production|\.[^/]+\.local)?|secrets(\/.*)?)$/i, 'Ortam ve sır dosyaları AI tarafından okunmaz ve yazılmaz.'],
31
- ['ask', /^(CLAUDE\.md|AGENTS\.md|\.mcp\.json|\.claude\/.*|\.codex\/.*|\.agents\/hooks\.json|\.github\/workflows\/.*|\.git(\/.*)?)$/i, 'Kural, koruma ve iş akışı dosyaları kullanıcı onayıyla değişir.'],
31
+ ['deny', /^(\.env(\.local|\.production|\.[^/]+\.local)?|secrets(\/.*)?)$/i, 'Environment and secret files are not read or written by the AI.'],
32
+ ['ask', /^(CLAUDE\.md|AGENTS\.md|\.mcp\.json|\.claude\/.*|\.codex\/.*|\.agents\/hooks\.json|\.github\/workflows\/.*|\.git(\/.*)?)$/i, 'guard.freeze.protectedAsk'],
32
33
  ];
33
- export const protectedRule = (rel) => PROTECTED.find(([, re]) => re.test(rel)) ?? null;
34
+ // Returns [decision, pattern, text]; the text of an ask rule is in the user's language (message key), a deny rule's is for the AI
35
+ export const protectedRule = (rel, r = root) => {
36
+ const e = PROTECTED.find(([, re]) => re.test(rel));
37
+ return e ? [e[0], e[1], e[0] === 'ask' ? t(r, e[2]) : e[2]] : null;
38
+ };
34
39
 
35
40
  export const relPath = (file, rootDir) => path.relative(rootDir, path.resolve(rootDir, file)).split(path.sep).join('/');
36
41
 
@@ -79,18 +84,18 @@ function setPlan(mode, args) {
79
84
  const current = readPlan();
80
85
  if (mode === '--plan-end') {
81
86
  fs.rmSync(planPath(), { force: true });
82
- return console.log(current ? `Kapsam kalkanı kapandı (${current.task}).` : 'Aktif plan yok.');
87
+ return console.log(current ? `Scope shield closed (${current.task}).` : 'No active plan.');
83
88
  }
84
89
  const [task, ...paths] = mode === '--plan-save' ? args : [null, ...args];
85
90
  const fail = (m) => { console.error(m); process.exit(1); };
86
- if (mode === '--plan-save' && current) fail(`Plan aktif (${current.task}). Kapsamı genişletmek kullanıcı onayıyla --plan-add; görev bittiyse önce --plan-end.`);
87
- if (mode === '--plan-add' && !current) fail('Aktif plan yok; önce --plan-save <görev> <yol...>.');
88
- if ((mode === '--plan-save' && !task) || !paths.length) fail(`Kullanım: ${mode === '--plan-save' ? '--plan-save <görev> <yol...>' : '--plan-add <yol...>'}`);
91
+ if (mode === '--plan-save' && current) fail(`A plan is active (${current.task}). To widen the scope, use --plan-add with user approval; if the task is done, run --plan-end first.`);
92
+ if (mode === '--plan-add' && !current) fail('No active plan; run --plan-save <task> <path...> first.');
93
+ if ((mode === '--plan-save' && !task) || !paths.length) fail(`Usage: ${mode === '--plan-save' ? '--plan-save <task> <path...>' : '--plan-add <path...>'}`);
89
94
  const normal = paths.map(normalPlanPath);
90
- if (normal.includes(null)) fail(`Proje dışındaki yol plana eklenmez: ${paths[normal.indexOf(null)]}`);
95
+ if (normal.includes(null)) fail(`A path outside the project is not added to the plan: ${paths[normal.indexOf(null)]}`);
91
96
  const plan = current ? { task: current.task, paths: [...new Set([...current.paths, ...normal])] } : { task, paths: [...new Set(normal)] };
92
97
  writePlan(plan);
93
- console.log(`Kapsam kalkanı: ${plan.task} · ${plan.paths.length} yol. Liste dışı mevcut dosyaya yazım sorulur.`);
98
+ console.log(`Scope shield: ${plan.task} · ${plan.paths.length} paths. Writing to an existing file outside the list is asked.`);
94
99
  }
95
100
 
96
101
  const MODES = ['--set', '--off', '--status', '--plan-save', '--plan-add', '--plan-end'];
@@ -100,25 +105,25 @@ const MODES = ['--set', '--off', '--status', '--plan-save', '--plan-add', '--pla
100
105
  function configure(mode, value) {
101
106
  if (mode === '--off') {
102
107
  fs.rmSync(LOCK, { force: true });
103
- console.log('Düzenleme kilidi kaldırıldı.');
108
+ console.log('Edit lock removed.');
104
109
  } else if (mode === '--set' && value) {
105
110
  const dir = relPath(value, root).replace(/\/+$/, '');
106
- if (!dir || dir.startsWith('..')) { console.error('Klasör proje içinde olmalı.'); process.exit(1); }
111
+ if (!dir || dir.startsWith('..')) { console.error('The folder must be inside the project.'); process.exit(1); }
107
112
  const current = readLock();
108
113
  if (current && !allowedInLock(`${dir}/x`, current)) {
109
- console.error(`Kilit açık (${current}/). Sadece daraltılabilir; genişletmek için önce kullanıcı onayıyla /freeze off.`);
114
+ console.error(`A lock is on (${current}/). It can only be narrowed; to widen it, first run /freeze off with user approval.`);
110
115
  process.exit(1);
111
116
  }
112
117
  fs.mkdirSync(path.dirname(LOCK), { recursive: true });
113
118
  fs.writeFileSync(LOCK, JSON.stringify({ dir, date: new Date().toISOString() }, null, 2));
114
- console.log(`Düzenleme kilidi: sadece ${dir}/ (kaldırmak için /freeze off)`);
119
+ console.log(`Edit lock: only ${dir}/ (to remove it: /freeze off)`);
115
120
  } else if (mode.startsWith('--plan-')) {
116
121
  setPlan(mode, process.argv.slice(3));
117
122
  } else {
118
123
  const k = readLock();
119
124
  const p = readPlan();
120
- console.log(k ? `Düzenleme kilidi açık: ${k}/` : 'Düzenleme kilidi yok.');
121
- console.log(p ? `Kapsam kalkanı: ${p.task} · ${p.paths.length} yol (${p.paths.join(', ')})` : 'Aktif plan yok.');
125
+ console.log(k ? `Edit lock is on: ${k}/` : 'No edit lock.');
126
+ console.log(p ? `Scope shield: ${p.task} · ${p.paths.length} paths (${p.paths.join(', ')})` : 'No active plan.');
122
127
  }
123
128
  }
124
129
 
@@ -130,30 +135,29 @@ function hook() {
130
135
  // Gate command approval record (gate.mjs, in the user folder ~/.vibe-kit/): the AI must not write it by hand
131
136
  const approval = paths.find((p) => /[\\/]\.vibe-kit([\\/]|$)/i.test(p));
132
137
  if (approval) decide(g.tool, 'ask', /tercihler\.md$/i.test(approval)
133
- ? `${approval}: kişisel tercih dosyası; elle yazılmaz, node .claude/hooks/prefs.mjs --add / --remove kullanılır.`
134
- : `${approval}: kapı komut onayı kaydı; elle yazılmaz, node .claude/hooks/gate.mjs --approve kullanılır.`);
138
+ ? t(g.root, 'guard.freeze.prefsFile', { path: approval })
139
+ : t(g.root, 'guard.freeze.gateApproval', { path: approval }));
135
140
  if (g.tool !== 'claude') {
136
141
  for (const p of paths) {
137
- const k = protectedRule(relPath(p, groot));
142
+ const k = protectedRule(relPath(p, groot), g.root);
138
143
  if (k) decide(g.tool, k[0], `${relPath(p, groot)}: ${k[2]}`);
139
144
  }
140
145
  // Reads (view_file etc.): only .env and secrets/ are denied (the counterpart of Read(./.env) in Claude)
141
146
  for (const p of g.reads) {
142
147
  const rel = relPath(realPath(path.resolve(g.dir, p)), groot);
143
- const k = protectedRule(rel);
148
+ const k = protectedRule(rel, g.root);
144
149
  if (k?.[0] === 'deny') decide(g.tool, 'deny', `${rel}: ${k[2]}`);
145
150
  }
146
151
  }
147
152
  const lock = readLock(g.root);
148
153
  const outside = lock && paths.find((p) => !allowedInLock(p, lock, groot));
149
- if (outside) decide(g.tool, 'deny', `Düzenleme kilidi: sadece ${lock}/ değiştirilebilir. Bu dosya gerekiyorsa kullanıcıya sor; kilidi kaldırmak için /freeze off.`);
154
+ if (outside) decide(g.tool, 'deny', `Edit lock: only ${lock}/ may be changed. If you need this file, ask the user; to remove the lock use /freeze off.`);
150
155
  const plan = readPlan(g.root);
151
156
  for (const [i, p] of (plan ? paths : []).entries()) {
152
157
  const rel = relPath(p, groot);
153
158
  // A new file is not asked (the structure guard looks at it); deleting and overwriting an existing one is
154
159
  if (inScope(rel, plan.paths) || !(g.files[i].deleted || fs.existsSync(p))) continue;
155
- decide(g.tool, 'ask', `Kapsam kalkanı: ${rel || '(yol bilinmiyor)'} onaylı planda yok (görev ${plan.task}). Gerekliyse kullanıcıya sor; ` +
156
- `onaylarsa node .claude/hooks/freeze-guard.mjs --plan-add <yol>. Görev bittiyse --plan-end.`);
160
+ decide(g.tool, 'ask', t(g.root, 'guard.freeze.scopeAsk', { rel: rel || t(g.root, 'guard.freeze.unknownPath'), task: plan.task }));
157
161
  }
158
162
  process.exit(0);
159
163
  }
@@ -162,7 +166,7 @@ if (isMain(import.meta.url)) {
162
166
  const [mode, value] = process.argv.slice(2);
163
167
  // A mistyped or removed flag must fail loudly: run as the hook it would pass silently and the AI would think the plan is saved
164
168
  if (mode?.startsWith('--') && !MODES.includes(mode)) {
165
- console.error(`Bilinmeyen bayrak: ${mode}. Kullanım: freeze-guard.mjs ${MODES.join(' | ')}`);
169
+ console.error(`Unknown flag: ${mode}. Usage: freeze-guard.mjs ${MODES.join(' | ')}`);
166
170
  process.exit(1);
167
171
  }
168
172
  MODES.includes(mode) ? configure(mode, value) : hook();