tillerhand 3.49.6 → 3.49.8

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.
@@ -54,7 +54,8 @@ const NEEDS_APPROVAL = [
54
54
 
55
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.
56
56
  // Each part of a command chain is checked alone: "rm -r node_modules && npx vitest run a.test.ts" does not change a test.
57
- const WRITER = /\b(rm|del|erase|rmdir|rd|unlink|mv|move|cp|copy|tee|truncate|touch|ln|dd|Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|Clear-Content)\b|\bcurl\b[^;&|]*\s(-o|--output)\b|\bwget\b[^;&|]*\s(-O|--output-document)\b|\bsed\b[^;&|]*\s(-[a-zA-Z]*i\S*|--in-place\b)|\bperl\b[^;&|]*\s-[a-zA-Z]*i|\bgit\s+(checkout|restore|rm|mv)\b/i;
57
+ // A flag is not a command name: grep -ln, ls -rd, tar -rm are not writers
58
+ const WRITER = /(?<![\w-])(rm|del|erase|rmdir|rd|unlink|mv|move|cp|copy|tee|truncate|touch|ln|dd|Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|Clear-Content)\b|\bcurl\b[^;&|]*\s(-o|--output)\b|\bwget\b[^;&|]*\s(-O|--output-document)\b|\bsed\b[^;&|]*\s(-[a-zA-Z]*i\S*|--in-place\b)|\bperl\b[^;&|]*\s-[a-zA-Z]*i|\bgit\s+(checkout|restore|rm|mv)\b/i;
58
59
  // Dependency and git internals are not the project's tests; a URL (curl … https://x/tests/a) or a dd input is not a target
59
60
  // Normalized first: node_modules/../tests/a.test.ts is a test path; only a path that stays under them is skipped
60
61
  const isTestCode = (t) => {
@@ -7,15 +7,24 @@
7
7
  import { execSync } from 'node:child_process';
8
8
  import { readInput, decide, isMain } from './shared.mjs';
9
9
 
10
+ // (?<![A-Za-z0-9]) instead of \b: a key right after _ (OPENAI_sk-…, prod_sk_live_…) is found too.
11
+ // Lookaheads and runs are bounded: an unbounded one is quadratic on a long run (-sb_secret_ × 50 000) and a hook
12
+ // that times out lets the write through.
10
13
  export const PATTERNS = [
11
- ['Anthropic/OpenAI key', /\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{32,}/],
12
- ['Stripe secret key', /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}/],
13
- ['AWS access key', /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/],
14
- ['GitHub token', /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{50,}/],
15
- ['Slack token', /\bxox[abprs]-[A-Za-z0-9-]{10,}/],
16
- ['Private key', /-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----/],
17
- ['Resend key', /\bre_[A-Za-z0-9]{8,}_[A-Za-z0-9]{16,}/],
18
- ['Supabase secret key', /\bsb_secret_(?=[\w-]*\d)[\w-]{16,}/],
14
+ ['Anthropic/OpenAI key', /(?<![A-Za-z0-9])sk-(?:ant-|proj-)?[A-Za-z0-9_-]{32,}/],
15
+ ['Stripe secret key', /(?<![A-Za-z0-9])(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}/],
16
+ ['Stripe webhook secret', /(?<![A-Za-z0-9])whsec_[A-Za-z0-9+/]{24,}/],
17
+ ['AWS access key', /(?<![A-Za-z0-9])(?:AKIA|ASIA)[0-9A-Z]{16}\b/],
18
+ ['GitHub token', /(?<![A-Za-z0-9])(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}/],
19
+ ['GitLab token', /(?<![A-Za-z0-9])glpat-[\w-]{20,}/],
20
+ ['Google API key', /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}/],
21
+ ['Hugging Face token', /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34,}/],
22
+ ['npm token', /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36,}/],
23
+ ['SendGrid key', /(?<![A-Za-z0-9])SG\.[\w-]{22}\.[\w-]{43}/],
24
+ ['Slack token', /(?<![A-Za-z0-9])(?:xox[abprse]|xapp)-[A-Za-z0-9-]{10,}/],
25
+ ['Private key', /-----BEGIN (?:[A-Z]+ ){0,4}PRIVATE KEY(?: BLOCK)?-----/],
26
+ ['Resend key', /(?<![A-Za-z0-9])re_[A-Za-z0-9]{8,}_[A-Za-z0-9]{16,}/],
27
+ ['Supabase secret key', /(?<![A-Za-z0-9])sb_secret_(?=[\w-]{0,100}\d)[\w-]{16,}/],
19
28
  ];
20
29
 
21
30
  export function findSecrets(text) {
@@ -41,11 +50,14 @@ function hookMode() {
41
50
 
42
51
  function stagedMode() {
43
52
  const diff = execSync('git diff --cached -U0 --no-color', { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
44
- let file = '';
53
+ let file = '', hunk = false;
45
54
  const findings = [];
55
+ // Inside a hunk every + line is content, "+++ …" included (a line starting with ++); headers come before the first @@
46
56
  for (const line of diff.split('\n')) {
47
- if (line.startsWith('+++ ')) file = line.slice(6);
48
- else if (line.startsWith('+') && !line.startsWith('+++')) {
57
+ if (line.startsWith('diff --git ')) hunk = false;
58
+ else if (!hunk && line.startsWith('+++ ')) file = line.slice(6);
59
+ else if (line.startsWith('@@')) hunk = true;
60
+ else if (hunk && line.startsWith('+')) {
49
61
  const f = findSecrets(line);
50
62
  if (f.length) findings.push(`${file}: ${f.join(', ')}`);
51
63
  }
@@ -444,6 +444,8 @@ export const MESSAGES = {
444
444
  'guard.package.newPypi': { en: 'first release was published {age} days ago' },
445
445
  'guard.package.timeout': { en: 'could not be verified (timeout)' },
446
446
  'guard.package.network': { en: 'could not be verified (network error)' },
447
+ 'guard.package.tooMany': { en: 'Package gate: {count} packages in one command (more than {max}) are not checked one by one. User approval required.' },
448
+ 'guard.package.error': { en: 'The package gate could not check this command ({error}). User approval required.' },
447
449
  'guard.package.ask': { en: 'Package gate warning: {list}. Before installing, make sure the package is really the one you want.' },
448
450
  'guard.structure.offMap': { en: '"{rel}" is not in the structure map (.claude/structure.json). The right place is probably one of: {top}. If it is temporary or a trial, write it under sandbox/. If a new permanent place is needed, ask it as a technical decision and update the map (docs/architecture/structure.md).' },
449
451
  'guard.structure.prefix': { en: 'Structure: {reason}' },
@@ -24,19 +24,20 @@ const POPULAR = {
24
24
  'langchain', 'transformers', 'torch', 'scikit-learn', 'aiohttp', 'tenacity', 'ruff', 'black', 'mypy'],
25
25
  };
26
26
 
27
+ // Case-insensitive (the Windows shell is); npm's install aliases (in, ins, isntall, it…) count as install
27
28
  const COMMANDS = [
28
- [/^(npm|pnpm|bun)\s+(i|install|add)\b/, 'npm'], [/^yarn\s+(global\s+)?add\b/, 'npm'], [/^npx\s+expo\s+install\b/, 'npm'],
29
- [/^uv\s+(add|pip\s+install)\b/, 'pypi'], [/^(python3?\s+-m\s+)?pip3?\s+install\b/, 'pypi'], [/^poetry\s+add\b/, 'pypi'],
29
+ [/^(npm|pnpm|bun)\s+(i|in|ins|inst|insta|instal|install|isnt|isnta|isntal|isntall|it|add)\b/i, 'npm'], [/^yarn\s+(global\s+)?add\b/i, 'npm'], [/^npx\s+expo\s+install\b/i, 'npm'],
30
+ [/^uv\s+(add|pip\s+install)\b/i, 'pypi'], [/^(python3?\s+-m\s+)?pip3?\s+install\b/i, 'pypi'], [/^poetry\s+add\b/i, 'pypi'],
30
31
  ];
31
32
 
32
33
  // Download-and-run: the first positional is the package (the rest are its arguments), or the values of the package flags.
33
34
  // local: the runner uses a bin the project already has before downloading (npx, npm exec, bunx); pnpm/yarn dlx always download.
34
35
  // positional: false for uv run (its positional is a script; only --with names packages).
35
36
  const EXEC = [
36
- { re: /^(npx|npm\s+(exec|x)|bunx|bun\s+x)\b/, eco: 'npm', local: true, positional: true },
37
- { re: /^(pnpx|pnpm\s+dlx|yarn\s+dlx)\b/, eco: 'npm', local: false, positional: true },
38
- { re: /^(uvx|uv\s+tool\s+run|pipx\s+run)\b/, eco: 'pypi', local: false, positional: true },
39
- { re: /^uv\s+run\b/, eco: 'pypi', local: false, positional: false },
37
+ { re: /^(npx|npm\s+(exec|x)|bunx|bun\s+x)\b/i, eco: 'npm', local: true, positional: true },
38
+ { re: /^(pnpx|pnpm\s+dlx|yarn\s+dlx)\b/i, eco: 'npm', local: false, positional: true },
39
+ { re: /^(uvx|uv\s+tool\s+run|pipx\s+run)\b/i, eco: 'pypi', local: false, positional: true },
40
+ { re: /^uv\s+run\b/i, eco: 'pypi', local: false, positional: false },
40
41
  ];
41
42
  const FLAGS = {
42
43
  npm: { pkg: /^(-p|--package)$/, value: /^(-p|--package|-c|--call|--registry|--prefix|--cache|--userconfig|--globalconfig|-w|--workspace|--node-options|--script-shell|--shell|--loglevel|-C|--dir|--filter)$/ },
@@ -68,14 +69,17 @@ function execArgs(args, eco, positional) {
68
69
 
69
70
  // What runs a segment: wrappers, VAR=x, a path or .cmd/.exe on the tool, and the manager's own flags before its subcommand are
70
71
  // dropped (FOO=1 npx x, cmd /c npx x, bash -c "npx x", /usr/bin/npx x, npx.cmd x, pnpm -C . dlx x, npm --silent exec x).
71
- const WRAPPER = /^(?:\(|[A-Za-z_]\w*=\S*\s+|(?:sudo|env|time|nohup|exec|command|xargs|corepack|call|start)\s+|(?:bash|sh|zsh|dash|cmd|pwsh|powershell)(?:\.exe)?\s+(?:-c|\/c|\/k|-Command)\s+["']?)/i;
72
+ // Also timeout/nice/stdbuf, shell keywords (then, do, else, !) and combined shell flags (bash -lc).
73
+ const WRAPPER = /^(?:\(|\{|!\s*|[A-Za-z_]\w*=\S*\s+|(?:sudo|env|time|nohup|exec|command|xargs|corepack|call|start|then|do|else|ionice|stdbuf)\s+|timeout\s+(?:-\S+\s+)*\S+\s+|nice\s+(?:-n\s*\S+\s+|-\d+\s+)?|(?:bash|sh|zsh|dash|cmd|pwsh|powershell)(?:\.exe)?\s+(?:-\w*c|\/c|\/k|-Command)\s+["']?)/i;
72
74
  const SUBCOMMAND = /^(install|i|add|exec|x|dlx|tool|pip|run|global)$/;
73
75
  export function normalize(part) {
74
76
  let s = part.trim(), prev;
75
77
  do { prev = s; s = s.replace(WRAPPER, '').trim(); } while (s !== prev);
76
- s = s.replace(/^["']?(?:[^\s"']*[\/])?([\w.-]+?)(?:\.cmd|\.exe|\.ps1)?["']?(?=\s|$)/i, '$1');
78
+ // A path (Windows \ too, quoted with spaces) and .cmd/.exe on the tool: "C:\Program Files\nodejs\npm.cmd" i x → npm i x
79
+ s = s.replace(/^"(?:[^"]*[\\/])?([\w.-]+?)(?:\.cmd|\.exe|\.ps1)?"(?=\s|$)/i, '$1')
80
+ .replace(/^["']?(?:[^\s"']*[\\/])?([\w.-]+?)(?:\.cmd|\.exe|\.ps1)?["']?(?=\s|$)/i, '$1');
77
81
  const t = s.split(/\s+/);
78
- if (/^(npm|pnpm|yarn|bun|uv)$/.test(t[0])) {
82
+ if (/^(npm|pnpm|yarn|bun|uv)$/i.test(t[0])) {
79
83
  let i = 1;
80
84
  while (i < t.length && t[i].startsWith('-')) i += !t[i].includes('=') && t[i + 1] && !t[i + 1].startsWith('-') && !SUBCOMMAND.test(t[i + 1]) ? 2 : 1;
81
85
  s = [t[0], ...t.slice(i)].join(' ');
@@ -85,13 +89,15 @@ export function normalize(part) {
85
89
 
86
90
  const NOT_REGISTRY = /^(\.|\/|file:|link:|git\+|git:|https?:|github:|workspace:)/;
87
91
  function npmName(arg) {
88
- if (NOT_REGISTRY.test(arg)) return null;
89
- const at = arg.indexOf('@', 1), spec = at > 0 ? arg.slice(at + 1) : '';
90
- // name@npm:other installs "other": the alias target is checked. name@github:x/y, @https:… are not from the registry
91
- // (like a bare URL, not checked here); the name in front is not what gets installed.
92
- if (spec.startsWith('npm:')) return npmName(spec.slice(4));
93
- if (/[:/]/.test(spec)) return null;
94
- return (at > 0 ? arg.slice(0, at) : arg) || null;
92
+ // name@npm:other installs "other": the alias target is checked (a loop: a deep a@npm:a@npm:… chain must not overflow
93
+ // the stack). name@github:x/y, @https:… are not from the registry (like a bare URL, not checked here).
94
+ for (;;) {
95
+ if (NOT_REGISTRY.test(arg)) return null;
96
+ const at = arg.indexOf('@', 1), spec = at > 0 ? arg.slice(at + 1) : '';
97
+ if (spec.startsWith('npm:')) { arg = spec.slice(4); continue; }
98
+ if (/[:/]/.test(spec)) return null;
99
+ return (at > 0 ? arg.slice(0, at) : arg) || null;
100
+ }
95
101
  }
96
102
 
97
103
  function pypiName(arg) {
@@ -101,7 +107,9 @@ function pypiName(arg) {
101
107
 
102
108
  export function findPackages(command) {
103
109
  const result = [];
104
- for (const part of command.split(/&&|\|\||;|\|/)) {
110
+ // Every place a new command can start: chains, a newline, a background &, $( ), backticks, ( ) and { } groups.
111
+ // A redirect's & (2>&1, &>) is not a separator.
112
+ for (const part of command.split(/\n|&&|\|\||;|\||(?<![>&])&(?![>&])|\$\(|`|[(){}]/)) {
105
113
  const s = normalize(part);
106
114
  const match = COMMANDS.find(([re]) => re.test(s));
107
115
  const exec = match ? null : EXEC.find((e) => e.re.test(s));
@@ -182,15 +190,23 @@ export async function check({ eco, name }, get = fetch, now = Date.now()) {
182
190
  }
183
191
  }
184
192
 
193
+ // More names than this in one command are asked without checking each one (unbounded fetches would hit the hook timeout)
194
+ const MAX_PACKAGES = 50;
185
195
  async function hook() {
186
196
  const g = readInput();
187
197
  if (!g) process.exit(0);
198
+ // A gate that throws or times out lets the command run: any error asks instead
199
+ try { await gate(g); } catch (e) { decide(g.tool, 'ask', t(g.root, 'guard.package.error', { error: String(e?.message ?? e).slice(0, 200) })); }
200
+ process.exit(0);
201
+ }
202
+ async function gate(g) {
188
203
  // npx/bunx run a bin the project already installed without downloading: a local bin (tsc, eas) is not a package name
189
204
  const bin = (name) => /^\w[\w.-]*$/.test(name) && [g.dir, g.root].some((d) => fs.existsSync(path.join(d, 'node_modules', '.bin', name)));
190
205
  // The kit's own package (/start and /adopt run npx -y tillerhand@<version> install): the kit already trusts itself
191
206
  // Both only for the plain spelling: name@1.2 or name@npm:other downloads, it does not run the local bin or the kit's release
192
207
  const packages = findPackages(g.command).filter((p) => !(p.local && p.spec === p.name && bin(p.name)) && !(p.eco === 'npm' && /^tillerhand(@\d+\.\d+\.\d+)?$/.test(p.spec ?? '')));
193
- if (!packages.length) process.exit(0);
208
+ if (!packages.length) return;
209
+ if (packages.length > MAX_PACKAGES) decide(g.tool, 'ask', t(g.root, 'guard.package.tooMany', { count: packages.length, max: MAX_PACKAGES }));
194
210
  const results = await Promise.all(packages.map(async (p) => ({ ...p, ...(await check(p)) })));
195
211
  const missing = results.filter((s) => s.status === 'missing' && !s.unsure);
196
212
  if (missing.length) {
@@ -104,7 +104,8 @@ export function parseInput(g) {
104
104
  const text = isText(t.content) ? t.content
105
105
  : isText(t.new_string) ? t.new_string
106
106
  : Array.isArray(t.edits) ? t.edits.map((e) => e?.new_string ?? '').join('\n')
107
- : isText(t.new_source) ? t.new_source : '';
107
+ : isText(t.new_source) ? t.new_source
108
+ : JSON.stringify(t); // an unexpected shape (content or new_source as an array): every argument is scanned
108
109
  const before = isText(t.old_string) ? t.old_string
109
110
  : Array.isArray(t.edits) ? t.edits.map((e) => e?.old_string ?? '').join('\n') : undefined;
110
111
  const edits = isText(t.old_string) ? [{ old: t.old_string, new: isText(t.new_string) ? t.new_string : '', all: t.replace_all === true }]
@@ -441,6 +441,8 @@
441
441
  "guard.package.newPypi": "ilk sürümü {age} gün önce yayımlanmış",
442
442
  "guard.package.timeout": "doğrulanamadı (zaman aşımı)",
443
443
  "guard.package.network": "doğrulanamadı (ağ hatası)",
444
+ "guard.package.tooMany": "Paket kapısı: tek komutta {count} paket var ({max} sınırının üstünde), tek tek denetlenmiyor. Kullanıcı onayı gerekli.",
445
+ "guard.package.error": "Paket kapısı bu komutu denetleyemedi ({error}). Kullanıcı onayı gerekli.",
444
446
  "guard.package.ask": "Paket kapısı uyarısı: {list}. Kurmadan önce paketin gerçekten istenen paket olduğundan emin ol.",
445
447
  "guard.structure.offMap": "\"{rel}\" yapı haritasında yok (.claude/structure.json). Doğru yer muhtemelen şunlardan biri: {top}. 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).",
446
448
  "guard.structure.prefix": "Yapı: {reason}",
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="kit/CHANGELOG.md"><img alt="Version" src="https://img.shields.io/badge/version-3.49.6-2a78d6?style=flat-square"></a>
9
+ <a href="kit/CHANGELOG.md"><img alt="Version" src="https://img.shields.io/badge/version-3.49.8-2a78d6?style=flat-square"></a>
10
10
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-2a78d6?style=flat-square"></a>
11
11
  <img alt="Claude Code" src="https://img.shields.io/badge/Claude%20Code-ready-d97757?style=flat-square">
12
12
  <img alt="Also Codex and Antigravity" src="https://img.shields.io/badge/also-Codex%20%C2%B7%20Antigravity-5b6577?style=flat-square">
package/README.tr.md CHANGED
@@ -6,7 +6,7 @@
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="kit/CHANGELOG.md"><img alt="Sürüm" src="https://img.shields.io/badge/s%C3%BCr%C3%BCm-3.49.6-2a78d6?style=flat-square"></a>
9
+ <a href="kit/CHANGELOG.md"><img alt="Sürüm" src="https://img.shields.io/badge/s%C3%BCr%C3%BCm-3.49.8-2a78d6?style=flat-square"></a>
10
10
  <a href="LICENSE"><img alt="Lisans: MIT" src="https://img.shields.io/badge/lisans-MIT-2a78d6?style=flat-square"></a>
11
11
  <img alt="Claude Code" src="https://img.shields.io/badge/Claude%20Code-haz%C4%B1r-d97757?style=flat-square">
12
12
  <img alt="Codex ve Antigravity" src="https://img.shields.io/badge/ayr%C4%B1ca-Codex%20%C2%B7%20Antigravity-5b6577?style=flat-square">
package/kit/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  The kit version is in `kit/VERSION`. To update a project, run `/kit-update` in it.
4
4
  Items with an "Adapt by hand" note concern files owned by the project; the update does not touch them.
5
5
 
6
+ ## [3.49.8] - 2026-10-30
7
+
8
+ ### Security
9
+ - **The package gate sees installs in more places.** An `npm i` after a newline, a background `&`, inside `$( )`, backticks, `if … then`, `( )` or `{ }` is checked, and so are `timeout`/`nice`/`bash -lc` in front, a quoted Windows path (`"C:\Program Files\nodejs\npm.cmd" i`), upper case (`NPM i`) and npm's install aliases (`npm in`, `npm isntall`, `npm it`…).
10
+ - A command the package gate cannot check (a parser error, more than 50 packages at once) asks you instead of running unchecked.
11
+
12
+ ## [3.49.7] - 2026-10-30
13
+
14
+ ### Security
15
+ - **The secret guard can no longer be timed out.** A long run of `-sb_secret_` made one pattern quadratic; a hook that times out lets the write through. The patterns are bounded now.
16
+ - **More key formats are stopped:** Google API keys (`AIza…`), GitLab (`glpat-`), Hugging Face (`hf_`), npm (`npm_`), SendGrid (`SG.…`), Stripe webhook secrets (`whsec_`), Slack app and refresh tokens (`xapp-`, `xoxe-`) and PGP private key blocks. A key right after `_` or a letter (`OPENAI_sk-…`, `prod_sk_live_…`) is found too. JWTs and database URLs with a password are still not matched: local dev values would raise false alarms.
17
+ - A write whose content is not plain text (a `content` or notebook `new_source` array) is scanned as a whole instead of skipped.
18
+ - The pre-commit scan (`guard-secrets.mjs --staged`) also checks added lines that start with `++`.
19
+
20
+ ### Fixed
21
+ - `grep -ln`, `ls -rd` and other commands with a flag that spells a file command no longer count as writing a test file.
22
+
6
23
  ## [3.49.6] - 2026-10-30
7
24
 
8
25
  ### Fixed
package/kit/VERSION CHANGED
@@ -1 +1 @@
1
- 3.49.6
1
+ 3.49.8
package/kit/guide.html CHANGED
@@ -361,7 +361,7 @@ footer .container { display: flex; flex-wrap: wrap; gap: 16px; justify-content:
361
361
  <section class="hero">
362
362
  <div class="container">
363
363
  <div>
364
- <span class="pill"><b>v3.49.6</b> Starter kit for Claude Code · MIT</span>
364
+ <span class="pill"><b>v3.49.8</b> Starter kit for Claude Code · MIT</span>
365
365
  <h1>The AI builds.<br><em>You steer.</em></h1>
366
366
  <p class="sub">TillerHand takes a product from idea to production and maintenance with Claude Code. At every important decision the AI gives you the options, its recommendation and the cost of each option. An expert board reviews the work, and guardrails stop mistakes before they happen.</p>
367
367
  <div class="setup">
@@ -1241,7 +1241,7 @@ Next: G-013 password reset · pending decision: email service</pre>
1241
1241
  <footer>
1242
1242
  <div class="container">
1243
1243
  <a class="brand" href="#top" style="font-size:16px"><svg class="mark" style="width:22px;height:22px"><use href="#mark"/></svg>TillerHand</a>
1244
- <span>Version 3.49.6 · <a href="https://github.com/burakdogrul/TillerHand">GitHub</a> · <a href="https://github.com/burakdogrul/TillerHand/blob/main/LICENSE">MIT license</a> · The KVKK section is a checklist, not legal advice.</span>
1244
+ <span>Version 3.49.8 · <a href="https://github.com/burakdogrul/TillerHand">GitHub</a> · <a href="https://github.com/burakdogrul/TillerHand/blob/main/LICENSE">MIT license</a> · The KVKK section is a checklist, not legal advice.</span>
1245
1245
  </div>
1246
1246
  </footer>
1247
1247
 
package/kit/guide.tr.html CHANGED
@@ -361,7 +361,7 @@ footer .container { display: flex; flex-wrap: wrap; gap: 16px; justify-content:
361
361
  <section class="hero">
362
362
  <div class="container">
363
363
  <div>
364
- <span class="pill"><b>v3.49.6</b> Claude Code için başlangıç kiti · MIT</span>
364
+ <span class="pill"><b>v3.49.8</b> Claude Code için başlangıç kiti · MIT</span>
365
365
  <h1>Kodu AI yazar.<br><em>Dümen sende.</em></h1>
366
366
  <p class="sub">TillerHand, Claude Code ile bir ürünü fikirden canlıya ve bakıma kadar götürür. Her önemli kararda AI sana seçenekleri, önerisini ve her seçeneğin bedelini sunar. Bir uzman kurulu işi denetler, korumalar hatayı daha olmadan durdurur.</p>
367
367
  <div class="setup">
@@ -1245,7 +1245,7 @@ Sıradaki: G-013 şifre sıfırlama · bekleyen karar: e-posta servisi</pre>
1245
1245
  <footer>
1246
1246
  <div class="container">
1247
1247
  <a class="brand" href="#top" style="font-size:16px"><svg class="mark" style="width:22px;height:22px"><use href="#mark"/></svg>TillerHand</a>
1248
- <span>Sürüm 3.49.6 · <a href="https://github.com/burakdogrul/TillerHand">GitHub</a> · <a href="https://github.com/burakdogrul/TillerHand/blob/main/LICENSE">MIT lisansı</a> · KVKK bölümü kontrol listesidir, hukuki görüş değildir.</span>
1248
+ <span>Sürüm 3.49.8 · <a href="https://github.com/burakdogrul/TillerHand">GitHub</a> · <a href="https://github.com/burakdogrul/TillerHand/blob/main/LICENSE">MIT lisansı</a> · KVKK bölümü kontrol listesidir, hukuki görüş değildir.</span>
1249
1249
  </div>
1250
1250
  </footer>
1251
1251
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tillerhand",
3
- "version": "3.49.6",
3
+ "version": "3.49.8",
4
4
  "description": "Vibe coding kit for Claude Code, Codex and Antigravity: rules, commands, guards and project templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",