tillerhand 3.28.1 → 3.28.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/commit-gate.mjs +27 -10
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/kit/CHANGELOG.md +15 -0
- package/kit/VERSION +1 -1
- package/kit/guide.html +1 -1
- package/kit/kit.mjs +3 -1
- package/package.json +1 -1
|
@@ -11,7 +11,6 @@ import crypto from 'node:crypto';
|
|
|
11
11
|
import { execSync } from 'node:child_process';
|
|
12
12
|
import { findRoot, isMain } from './shared.mjs';
|
|
13
13
|
import { unquote } from './destructive-guard.mjs';
|
|
14
|
-
import { approvedCommands, commandList } from './gate.mjs'; // circular import: used at run time only
|
|
15
14
|
|
|
16
15
|
// Project root (the script's location) even when called from a subfolder: approval and gate must not be written elsewhere
|
|
17
16
|
const root = findRoot();
|
|
@@ -29,7 +28,9 @@ export function changes() {
|
|
|
29
28
|
const untracked = list('ls-files --others --exclude-standard -z').filter((f) => !f.startsWith('.claude/state/'));
|
|
30
29
|
const staged = new Set(list('diff --cached --no-renames --name-only -z')), unstaged = new Set(list('diff --no-renames --name-only -z'));
|
|
31
30
|
const files = [...new Set([...staged, ...unstaged, ...untracked])].sort();
|
|
32
|
-
|
|
31
|
+
// A file marked assume-unchanged or skip-worktree hides index-vs-disk changes from "git diff": its index blob always counts
|
|
32
|
+
const hidden = list('ls-files -v -z').filter((l) => /^([a-z]|S) /.test(l)).map((l) => l.slice(2));
|
|
33
|
+
const partial = [...new Set([...files.filter((f) => staged.has(f) && unstaged.has(f)), ...hidden])].sort();
|
|
33
34
|
const blobs = new Map(partial.length ? list('ls-files -s -z').map((l) => [l.slice(l.indexOf('\t') + 1), l.split(' ')[1]]) : []);
|
|
34
35
|
const h = crypto.createHash('sha256');
|
|
35
36
|
for (const f of files) {
|
|
@@ -40,12 +41,24 @@ export function changes() {
|
|
|
40
41
|
return { files, signature: h.digest('hex') };
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
// git's global options may come before commit: git --no-pager commit, git -C x commit, git --git-dir=x commit
|
|
44
|
-
const
|
|
44
|
+
// git's global options may come before commit: git --no-pager commit, git -C x commit, git --git-dir=x commit; git.exe on Windows
|
|
45
|
+
const commitRe = (verbs) => new RegExp(`\\bgit(?:\\.exe)?["']?(?:\\s+(?:-[cC]\\s+\\S+|--?[\\w-]+(?:=\\S+)?))*\\s+(?:${verbs.join('|')})(?=\\s|$|["';&|)])`);
|
|
46
|
+
const COMMIT = commitRe(['commit']);
|
|
47
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
48
|
+
|
|
49
|
+
// git aliases that run commit ([alias] ci = commit, cm = !git commit), from git config and from an inline -c alias.x=commit
|
|
50
|
+
export function commitPattern(command) {
|
|
51
|
+
if (!/\bgit/.test(command)) return COMMIT; // this hook runs on every Bash call: git config only when git is named
|
|
52
|
+
let config = '';
|
|
53
|
+
try { config = git('config --get-regexp ^alias\\.'); } catch { /* no alias or no repo */ }
|
|
54
|
+
const names = [...config.matchAll(/^alias\.(\S+)\s+(.*)$/gm), ...command.matchAll(/alias\.([\w-]+)=["']?([^"'\s][^"'\n]*)/g)]
|
|
55
|
+
.filter(([, , value]) => /^(!\s*git\s+)?commit\b/.test(value.trim())).map(([, name]) => escapeRe(name));
|
|
56
|
+
return names.length ? commitRe(['commit', ...names]) : COMMIT;
|
|
57
|
+
}
|
|
45
58
|
|
|
46
59
|
// The hook sees the command before it runs: anything that can change files before the commit defeats the review.
|
|
47
60
|
// Allowed: commit alone, with "git add" and "cd" before it. A heredoc and a $(cat <<'EOF' … EOF) message do not count as commands.
|
|
48
|
-
export function chainIssue(command) {
|
|
61
|
+
export function chainIssue(command, commit = COMMIT) {
|
|
49
62
|
const k = command.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, '<<HEREDOC')
|
|
50
63
|
.replace(/\$\(\s*cat\s+<<HEREDOC\s*\)/g, 'MESAJ')
|
|
51
64
|
.replace(/'[^']*'/g, "''")
|
|
@@ -53,7 +66,7 @@ export function chainIssue(command) {
|
|
|
53
66
|
.replace(/\d*>&\d+/g, '');
|
|
54
67
|
if (/\$\(|`/.test(k)) return 'komutta ikame var ($(...) veya `...`)';
|
|
55
68
|
const parts = k.split(/&&|\|\||[;|&\n]/).map((s) => s.trim()).filter(Boolean);
|
|
56
|
-
return parts.every((s) =>
|
|
69
|
+
return parts.every((s) => commit.test(s) || /^git\s+add\b/.test(s) || /^cd\b/.test(s)) ? null : 'commit başka komutlarla zincirli';
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
export const docsOnly = (files) => files.every((f) => /\.(md|txt)$/i.test(f) || f.startsWith('docs/'));
|
|
@@ -120,7 +133,7 @@ export function maskOwnHook(command, dir) {
|
|
|
120
133
|
});
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
function hook() {
|
|
136
|
+
async function hook() {
|
|
124
137
|
let command = '', dir = root;
|
|
125
138
|
try {
|
|
126
139
|
const input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
@@ -130,8 +143,10 @@ function hook() {
|
|
|
130
143
|
process.exit(0);
|
|
131
144
|
}
|
|
132
145
|
// --skip bypasses the gate and the board: the user is asked in every spelling (the AI must not write it on its own)
|
|
146
|
+
// A commit whose message names --skip goes on to the commit checks below (a real --skip chained to a commit is stopped there).
|
|
133
147
|
const open = unquote(command);
|
|
134
|
-
|
|
148
|
+
const commit = commitPattern(command);
|
|
149
|
+
if (!commit.test(command) && [command, open].some((c) => /commit-gate\.mjs\b[^;&|\n]*--skip\b/i.test(c))) {
|
|
135
150
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask',
|
|
136
151
|
permissionDecisionReason: 'Commit kapısı: --skip kurul denetimini ve deterministik kapıyı aşar. Sadece kullanıcı açıkça isterse onayla.' } }));
|
|
137
152
|
process.exit(0);
|
|
@@ -147,14 +162,16 @@ function hook() {
|
|
|
147
162
|
permissionDecisionReason: 'Kapı komut onayı sadece tam bu yazımla, tek başına çalıştırılır (zincir, ortam değişkeni, bayrak, yönlendirme yok): node .claude/hooks/gate.mjs --approve' } }));
|
|
148
163
|
process.exit(0);
|
|
149
164
|
}
|
|
165
|
+
// Loaded only here: every Bash call runs this hook, and gate.mjs pulls in the dashboard and edit-round modules
|
|
166
|
+
const { approvedCommands, commandList } = await import('./gate.mjs');
|
|
150
167
|
const list = commandList(approvedCommands(root)) || '(tanımlı komut yok)';
|
|
151
168
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask',
|
|
152
169
|
permissionDecisionReason: `Kapı komut onayı: AGENTS.md → Komutlar'daki şu komutlar bundan sonra her kapıda çalışır:\n${list}\nGüvendiğin komutlarsa onayla.` } }));
|
|
153
170
|
process.exit(0);
|
|
154
171
|
}
|
|
155
|
-
if (!
|
|
172
|
+
if (!commit.test(command) && !commit.test(open)) process.exit(0);
|
|
156
173
|
if (!hasBoard() || reviewOff()) process.exit(0);
|
|
157
|
-
const chain = chainIssue(command);
|
|
174
|
+
const chain = chainIssue(command, commit);
|
|
158
175
|
if (chain) {
|
|
159
176
|
process.stderr.write(`Commit durduruldu: ${chain}. Kapı komut çalışmadan önceki değişiklikleri denetler; commit'i tek başına ` +
|
|
160
177
|
'(veya yalnızca "git add … && git commit" ile) çalıştır. Push ve diğer komutları ayrı çalıştır.\n');
|
package/README.en.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.28.
|
|
9
|
+
<a href="kit/CHANGELOG.md"><img alt="Version" src="https://img.shields.io/badge/version-3.28.3-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.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.28.
|
|
9
|
+
<a href="kit/CHANGELOG.md"><img alt="Sürüm" src="https://img.shields.io/badge/s%C3%BCr%C3%BCm-3.28.3-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,21 @@
|
|
|
3
3
|
Kit sürümü `kit/VERSION` dosyasında. Bir projeyi güncellemek: projede `/kit-update`.
|
|
4
4
|
"Elle uyarla" notu olan maddeler projeye ait dosyalardadır; güncelleme bunlara dokunmaz.
|
|
5
5
|
|
|
6
|
+
## [3.28.3] - 2026-10-26
|
|
7
|
+
|
|
8
|
+
### Düzeltilen
|
|
9
|
+
- **Commit kapısı `git.exe commit`, tam yolla yazılan git ve commit'e açılan git alias'larını da görüyor** (`[alias] ci = commit`, `!git commit`, `git -c alias.x=commit x`). Bunlar önceden kapıya takılmıyordu.
|
|
10
|
+
- **Mesajında `commit-gate.mjs --skip` geçen bir commit, kurul ve kapı denetiminden sorusuz geçebiliyordu.** Böyle bir commit artık normal commit denetiminden geçiyor.
|
|
11
|
+
- **3.10.0'dan kalan geniş `tools.mjs` izni (`Bash(node .claude/hooks/tools.mjs:*)`) `/kit-update` ile kaldırılıyor.** Bu izin, sorulması gereken `--enable` ve `--commands` komutlarını da serbest bırakıyordu.
|
|
12
|
+
- Elle uyarla: yok.
|
|
13
|
+
|
|
14
|
+
## [3.28.2] - 2026-10-26
|
|
15
|
+
|
|
16
|
+
### Düzeltilen
|
|
17
|
+
- **`skip-worktree` ya da `assume-unchanged` işaretli bir dosyada sahneye konan içerik commit onayını delebiliyordu.** Bu işaretler sahne ile disk arasındaki farkı `git diff`'ten gizliyor. 3.28.1'deki yeni imza da bu yüzden onaylanmamış içeriği göremiyordu. İşaretli dosyaların sahnedeki içeriği artık her zaman imzaya giriyor.
|
|
18
|
+
- **Commit kapısı her Bash komutunda daha hızlı.** Kapı modülü yalnız kapı komut onayında yükleniyor.
|
|
19
|
+
- Elle uyarla: yok.
|
|
20
|
+
|
|
6
21
|
## [3.28.1] - 2026-10-26
|
|
7
22
|
|
|
8
23
|
### Düzeltilen
|
package/kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.28.
|
|
1
|
+
3.28.3
|
package/kit/guide.html
CHANGED
|
@@ -287,7 +287,7 @@ footer .container { display: flex; flex-wrap: wrap; gap: 16px; justify-content:
|
|
|
287
287
|
<section class="hero">
|
|
288
288
|
<div class="container">
|
|
289
289
|
<div>
|
|
290
|
-
<span class="pill"><b>v3.28.
|
|
290
|
+
<span class="pill"><b>v3.28.3</b> Claude Code için başlangıç kiti · MIT</span>
|
|
291
291
|
<h1>Kodu AI yazar.<br><em>Dümen sende.</em></h1>
|
|
292
292
|
<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>
|
|
293
293
|
<div class="install"><span id="cmd">npx tillerhand init</span><button class="copy" type="button" data-copy="cmd">Kopyala</button></div>
|
package/kit/kit.mjs
CHANGED
|
@@ -119,7 +119,9 @@ const OLD_RULES = {
|
|
|
119
119
|
// 3.19.1: in the project folder the CLI loads a repo's .playwright/cli.config.json (it can run a program); asked, not allowed
|
|
120
120
|
...['open', 'snapshot', 'console', 'requests', 'screenshot'].map((c) => `Bash(npx -y @playwright/cli@0.1.22 ${c}:*)`),
|
|
121
121
|
// 3.28.0: --plan-end + --plan-save widened or dropped the scope shield unasked; both are asked now
|
|
122
|
-
'Bash(node .claude/hooks/freeze-guard.mjs --plan-save:*)', 'Bash(node .claude/hooks/freeze-guard.mjs --plan-end)'
|
|
122
|
+
'Bash(node .claude/hooks/freeze-guard.mjs --plan-save:*)', 'Bash(node .claude/hooks/freeze-guard.mjs --plan-end)',
|
|
123
|
+
// 3.10.0 allowed every tools.mjs flag; --enable and --commands are asked since 3.10.1, the broad rule skipped that
|
|
124
|
+
'Bash(node .claude/hooks/tools.mjs:*)'],
|
|
123
125
|
ask: ['Bash(node .claude/hooks/freeze-guard.mjs --plan-ekle:*)', 'Bash(node .claude/hooks/gate.mjs --onayla)', 'Bash(node .claude/hooks/tools.mjs --ac:*)'],
|
|
124
126
|
};
|
|
125
127
|
|
package/package.json
CHANGED