tillerhand 3.28.0 → 3.28.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.
- package/.claude/hooks/budget.mjs +2 -2
- package/.claude/hooks/checkpoint.mjs +2 -2
- package/.claude/hooks/commit-gate.mjs +17 -7
- package/.claude/hooks/destructive-guard.mjs +3 -2
- package/.claude/hooks/notify.mjs +2 -2
- package/.claude/hooks/package-gate.mjs +11 -5
- package/.claude/hooks/plugins-sync.mjs +2 -2
- package/.claude/hooks/reminder.mjs +2 -1
- package/.claude/hooks/structure-guard.mjs +2 -2
- package/.claude/hooks/tools.mjs +2 -2
- package/.claude/hooks/usage.mjs +2 -2
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/kit/CHANGELOG.md +13 -0
- package/kit/VERSION +1 -1
- package/kit/guide.html +1 -1
- package/kit/kit.mjs +5 -0
- package/package.json +1 -1
package/.claude/hooks/budget.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
11
11
|
import { isTestFile } from './test-guard.mjs';
|
|
12
|
-
import { isMain } from './shared.mjs';
|
|
12
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
13
13
|
|
|
14
14
|
export const BUDGET = { complexity: 15, function: 80, file: 400 };
|
|
15
15
|
const LIZARD = ['pipx', 'run', 'lizard==1.24.1'];
|
|
@@ -41,7 +41,7 @@ export function changedFiles(root, base) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function check() {
|
|
44
|
-
const root =
|
|
44
|
+
const root = findRoot();
|
|
45
45
|
const i = process.argv.indexOf('--base');
|
|
46
46
|
const files = selectFiles(changedFiles(root, i > 0 ? process.argv[i + 1] : ''));
|
|
47
47
|
if (!files.length) { console.log('Karmaşıklık bütçesi: değişen kod dosyası yok.'); return; }
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Rollback does not delete history: unsaved work is stashed first, files are set to the target state and
|
|
7
7
|
// a NEW commit is made. So a rollback can be undone too. reset --hard is not used.
|
|
8
8
|
import { execFileSync } from 'node:child_process';
|
|
9
|
-
import { isMain } from './shared.mjs';
|
|
9
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
10
10
|
|
|
11
11
|
// Tag prefix in users' repos: it stays as it is, existing tags must keep matching
|
|
12
12
|
const TAG_PREFIX = 'calisan/';
|
|
@@ -48,7 +48,7 @@ export function restore(root, target) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (isMain(import.meta.url)) {
|
|
51
|
-
const root =
|
|
51
|
+
const root = findRoot();
|
|
52
52
|
const arg = process.argv.slice(2);
|
|
53
53
|
// ONLY the first argument sets the mode: the permission list matches by prefix ("--list:*", "--save:*"),
|
|
54
54
|
// so a trailing "--restore ... --yes" must not lead to an unapproved rollback.
|
|
@@ -20,15 +20,23 @@ const git = (c) => execSync(`git ${c}`, { cwd: root, encoding: 'utf8', stdio: ['
|
|
|
20
20
|
|
|
21
21
|
// Changes that will go into the commit: staged, unstaged in tracked files (git commit -a) and untracked new
|
|
22
22
|
// files (git add -A && git commit). -z: Turkish or spaced paths are not quoted and escaped
|
|
23
|
+
// The signature is each changed file's content on disk, so "git add" of the approved content keeps the approval and the gate.
|
|
24
|
+
// The commit takes the index: a file staged with other content than the disk (partial staging) also adds its index blob.
|
|
25
|
+
// --no-renames: a staged rename must list the same two paths as the unstaged delete + new file.
|
|
23
26
|
export function changes() {
|
|
27
|
+
const list = (c) => git(c).split('\0').filter(Boolean);
|
|
24
28
|
// .claude/state/ (approval, gate result) is not part of the signature: writing an approval must not change it even if not in .gitignore
|
|
25
|
-
const untracked =
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
+
const untracked = list('ls-files --others --exclude-standard -z').filter((f) => !f.startsWith('.claude/state/'));
|
|
30
|
+
const staged = new Set(list('diff --cached --no-renames --name-only -z')), unstaged = new Set(list('diff --no-renames --name-only -z'));
|
|
31
|
+
const files = [...new Set([...staged, ...unstaged, ...untracked])].sort();
|
|
32
|
+
const partial = files.filter((f) => staged.has(f) && unstaged.has(f));
|
|
33
|
+
const blobs = new Map(partial.length ? list('ls-files -s -z').map((l) => [l.slice(l.indexOf('\t') + 1), l.split(' ')[1]]) : []);
|
|
34
|
+
const h = crypto.createHash('sha256');
|
|
35
|
+
for (const f of files) {
|
|
29
36
|
h.update(`\0${f}\0`);
|
|
30
|
-
try { h.update(fs.readFileSync(path.join(root, f))); } catch {
|
|
37
|
+
try { h.update(fs.readFileSync(path.join(root, f))); } catch { h.update('\0silindi'); }
|
|
31
38
|
}
|
|
39
|
+
for (const f of partial) h.update(`\0index:${f}:${blobs.get(f)}`);
|
|
32
40
|
return { files, signature: h.digest('hex') };
|
|
33
41
|
}
|
|
34
42
|
|
|
@@ -98,11 +106,13 @@ function readApproval() {
|
|
|
98
106
|
|
|
99
107
|
// This project's own commit-gate.mjs, resolved from the shell's folder ($CLAUDE_PROJECT_DIR, absolute, relative or ./ spelling),
|
|
100
108
|
// is masked so it is not read as the gate approval; any other *gate.mjs (sandbox/.claude/hooks/commit-gate.mjs, a renamed
|
|
101
|
-
// copy of gate.mjs) still is.
|
|
109
|
+
// copy of gate.mjs) still is. Only a single plain "node <path> --approve <note>" is masked: a chain, subshell, cd, env -C or
|
|
110
|
+
// expansion can change the folder or the file the text names.
|
|
102
111
|
const real = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
|
|
103
112
|
const samePath = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
113
|
+
const PLAIN_APPROVE = /^\s*node\s+("[^"\n]*"|'[^'\n]*'|[^\s"'`;&|<>(){}]+)\s+--approve(\s+("[^"\n$`]*"|'[^'\n]*'|[^\s"'`$;&|<>(){}]+))?\s*$/;
|
|
104
114
|
export function maskOwnHook(command, dir) {
|
|
105
|
-
if (
|
|
115
|
+
if (!PLAIN_APPROVE.test(command)) return command;
|
|
106
116
|
const own = real(path.join(root, '.claude', 'hooks', 'commit-gate.mjs'));
|
|
107
117
|
return command.replace(/"([^"\n]*commit-gate\.mjs)"|'([^'\n]*commit-gate\.mjs)'|([^\s"';&|]*commit-gate\.mjs)\b/gi, (m, a, b, c) => {
|
|
108
118
|
const p = (a ?? b ?? c).replace(/\$\{?CLAUDE_PROJECT_DIR\}?|%CLAUDE_PROJECT_DIR%|\$env:CLAUDE_PROJECT_DIR/gi, process.env.CLAUDE_PROJECT_DIR || root);
|
|
@@ -294,14 +294,15 @@ function obfuscationOne(command) {
|
|
|
294
294
|
? { 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;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
// A kit guard script (.claude/hooks) run with an indirect spelling: $VAR, $(..), `..`, ${..}, $'..'
|
|
297
|
+
// A kit guard script (.claude/hooks) run with an indirect spelling: $VAR, $(..), `..`, ${..}, $'..', a glob * ?, xargs, a pipe or < (commit-gate.mjs $S,
|
|
298
298
|
// --sk$()ip, commit-*.mjs). The text rules for --skip, --approve and --plan-* cannot see through them: asked (user decision 2026-10-25).
|
|
299
299
|
// Heredocs and single-quoted text are not expanded; $CLAUDE_PROJECT_DIR is the documented way to name the folder.
|
|
300
300
|
export function hookScriptIssue(command) {
|
|
301
301
|
const k = command.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, '').replace(/'[^']*'/g, "''")
|
|
302
302
|
.replace(/"(?:[^"\\]|\\.)*"/g, (q) => q.replace(/[*?]/g, '')).replace(/\$\{?CLAUDE_PROJECT_DIR\}?|\$env:CLAUDE_PROJECT_DIR/gi, '');
|
|
303
303
|
if (!/\bnode\b/i.test(k) || !/\.claude|hooks|commit-|gate|freeze/i.test(command)) return null;
|
|
304
|
-
|
|
304
|
+
// Arguments from elsewhere count too: xargs, a pipe into node, an input redirect or <(...)
|
|
305
|
+
return /\$\(|`|\$\{|\$'|\$[A-Za-z_]|[*?]|\bxargs\b|\|[^|;&]*\bnode\b|<(?!<)/.test(k)
|
|
305
306
|
? { 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;
|
|
306
307
|
}
|
|
307
308
|
|
package/.claude/hooks/notify.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { isMain } from './shared.mjs';
|
|
9
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
10
10
|
|
|
11
11
|
export const THRESHOLD_SEC = 90;
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ export function winCommand(title, message) {
|
|
|
21
21
|
if (isMain(import.meta.url)) main();
|
|
22
22
|
|
|
23
23
|
function main() {
|
|
24
|
-
const root =
|
|
24
|
+
const root = findRoot();
|
|
25
25
|
|
|
26
26
|
let input = {};
|
|
27
27
|
try { input = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { /* no input */ }
|
|
@@ -82,10 +82,15 @@ export function normalize(part) {
|
|
|
82
82
|
return s;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
const NOT_REGISTRY = /^(\.|\/|file:|link:|git\+|git:|https?:|github:|workspace:)/;
|
|
85
86
|
function npmName(arg) {
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (NOT_REGISTRY.test(arg)) return null;
|
|
88
|
+
const at = arg.indexOf('@', 1), spec = at > 0 ? arg.slice(at + 1) : '';
|
|
89
|
+
// name@npm:other installs "other": the alias target is checked. name@github:x/y, @https:… are not from the registry
|
|
90
|
+
// (like a bare URL, not checked here); the name in front is not what gets installed.
|
|
91
|
+
if (spec.startsWith('npm:')) return npmName(spec.slice(4));
|
|
92
|
+
if (/[:/]/.test(spec)) return null;
|
|
93
|
+
return (at > 0 ? arg.slice(0, at) : arg) || null;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function pypiName(arg) {
|
|
@@ -106,7 +111,7 @@ export function findPackages(command) {
|
|
|
106
111
|
if (exec) {
|
|
107
112
|
for (const { arg, fromFlag, unsure } of execArgs(args, eco, exec.positional)) {
|
|
108
113
|
const name = eco === 'npm' ? npmName(arg) : pypiName(arg);
|
|
109
|
-
if (name) result.push({ eco, name, exec: true, ...(exec.local && !fromFlag && { local: true }), ...(unsure && { unsure: true }) });
|
|
114
|
+
if (name) result.push({ eco, name, exec: true, spec: arg, ...(exec.local && !fromFlag && { local: true }), ...(unsure && { unsure: true }) });
|
|
110
115
|
}
|
|
111
116
|
continue;
|
|
112
117
|
}
|
|
@@ -181,7 +186,8 @@ async function hook() {
|
|
|
181
186
|
// npx/bunx run a bin the project already installed without downloading: a local bin (tsc, eas) is not a package name
|
|
182
187
|
const bin = (name) => /^\w[\w.-]*$/.test(name) && [g.dir, g.root].some((d) => fs.existsSync(path.join(d, 'node_modules', '.bin', name)));
|
|
183
188
|
// The kit's own package (/start and /adopt run npx -y tillerhand@<version> install): the kit already trusts itself
|
|
184
|
-
|
|
189
|
+
// 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
|
|
190
|
+
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 ?? '')));
|
|
185
191
|
if (!packages.length) process.exit(0);
|
|
186
192
|
const results = await Promise.all(packages.map(async (p) => ({ ...p, ...(await check(p)) })));
|
|
187
193
|
const missing = results.filter((s) => s.status === 'missing' && !s.unsure);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { execFileSync } from 'node:child_process';
|
|
12
|
-
import { isMain } from './shared.mjs';
|
|
12
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
13
13
|
|
|
14
14
|
// Windows paths are case-insensitive; other systems are case-sensitive
|
|
15
15
|
const norm = (p) => (process.platform === 'win32' ? path.resolve(p).toLowerCase() : path.resolve(p));
|
|
@@ -34,7 +34,7 @@ export function disabledHere(wanted, installed, root) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
if (isMain(import.meta.url)) {
|
|
37
|
-
const root =
|
|
37
|
+
const root = findRoot();
|
|
38
38
|
// No shell: arguments go straight to claude (claude is a single-file executable, .exe on Windows)
|
|
39
39
|
const run = (...arg) => execFileSync('claude', arg, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 180000 });
|
|
40
40
|
const reason = (h) => (h.stderr || h.stdout || h.message || '').toString().trim().split('\n')[0];
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// Kept short: every reminder costs tokens.
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import { findRoot } from './shared.mjs';
|
|
7
8
|
|
|
8
9
|
export const INTERVAL = 12; // every 12 messages
|
|
9
|
-
const root =
|
|
10
|
+
const root = findRoot();
|
|
10
11
|
const counterFile = path.join(root, '.claude', 'state', 'reminder.json');
|
|
11
12
|
|
|
12
13
|
let data = { session: '', count: 0 };
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execFileSync } from 'node:child_process';
|
|
10
|
-
import { readInput, decide, isMain } from './shared.mjs';
|
|
10
|
+
import { readInput, decide, findRoot, isMain } from './shared.mjs';
|
|
11
11
|
|
|
12
|
-
const root =
|
|
12
|
+
const root = findRoot();
|
|
13
13
|
|
|
14
14
|
// "**" any depth, "*" any name inside one folder, "?" one character
|
|
15
15
|
export function globRegex(pattern) {
|
package/.claude/hooks/tools.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { missingPlugins } from './plugins-sync.mjs';
|
|
15
|
-
import { isMain } from './shared.mjs';
|
|
15
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
16
16
|
|
|
17
17
|
// Keys are the package names users type (/tools ac tasarim): they stay Turkish
|
|
18
18
|
export const PACKAGES = {
|
|
@@ -143,7 +143,7 @@ const tokenOf = (measurement, ids) => {
|
|
|
143
143
|
};
|
|
144
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.';
|
|
145
145
|
if (isMain(import.meta.url)) {
|
|
146
|
-
const root =
|
|
146
|
+
const root = findRoot();
|
|
147
147
|
const [flag, name] = process.argv.slice(2);
|
|
148
148
|
try {
|
|
149
149
|
if (flag === '--enable' || flag === '--disable') {
|
package/.claude/hooks/usage.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// node .claude/hooks/usage.mjs --json machine-readable
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { isMain } from './shared.mjs';
|
|
9
|
+
import { findRoot, isMain } from './shared.mjs';
|
|
10
10
|
|
|
11
11
|
export const THRESHOLDS = { five_hour: { yellow: 60, red: 85 }, seven_day: { yellow: 70, red: 90 } };
|
|
12
12
|
const WINDOWS = { five_hour: '5 saatlik', seven_day: 'haftalık' };
|
|
@@ -83,7 +83,7 @@ export function summary(data, now = Date.now()) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
if (isMain(import.meta.url)) {
|
|
86
|
-
const root =
|
|
86
|
+
const root = findRoot();
|
|
87
87
|
const data = readLimits(root);
|
|
88
88
|
if (process.argv.includes('--json')) console.log(JSON.stringify({ ...data, level: level(data), thresholds: THRESHOLDS }));
|
|
89
89
|
else console.log(summary(data));
|
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.1-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.1-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,19 @@
|
|
|
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.1] - 2026-10-26
|
|
7
|
+
|
|
8
|
+
### Düzeltilen
|
|
9
|
+
- **Onaylanan değişiklikleri `git add` ile sahnelemek commit kapısını ve kurul onayını geçersiz kılıyordu.** Değişiklik imzası artık dosyaların diskteki içeriğinden çıkıyor; aynı içeriği sahnelemek imzayı değiştirmiyor. Diskten farklı içerikle sahnelenen dosya (kısmi sahneleme) imzada ayrıca görünür, çünkü commit sahnedeki içeriği alır.
|
|
10
|
+
- **Kitin klon klasöründe `kit.mjs install` commit edilmemiş işi siliyordu** (`kit/` klasörü, `README.md`). Commit edilmemiş değişiklik varsa kurulum artık başlamadan durur ve dosyaları listeler.
|
|
11
|
+
- **Dokuz betik proje kökünü çalıştırıldığı klasörden buluyordu** (kayıt noktası, bütçe, bildirim, eklenti eşitleme, hatırlatıcı, yapı koruyucu, araçlar, limit). Codex alt klasörden çalıştırınca örneğin `checkpoint.mjs --restore` yalnız o klasörü geri alıyor ama "döndü" diyordu. Hepsi artık kökü aynı yoldan buluyor.
|
|
12
|
+
- **3.28.0'daki güvenlik düzeltmelerinde kalan açıklar:**
|
|
13
|
+
- npm takma adı (`zod@npm:baska-paket`) asıl kurulan paketi gizliyordu. Paket kapısı artık hedef paketi denetliyor. Bu açık 3.28.0'dan önce de vardı.
|
|
14
|
+
- `tillerhand` muafiyeti ve projede kurulu komut muafiyeti yalnız düz yazımda geçerli. `tillerhand@npm:…` ya da `tsc@5` gibi yazımlar denetleniyor.
|
|
15
|
+
- Commit kapısındaki muafiyet yalnız tek başına çalışan `node <yol> --approve <not>` komutu için geçerli. Zincir, alt kabuk ya da `env -C` içeren komutta muafiyet yok.
|
|
16
|
+
- Koruma betiğine argüman `xargs`, pipe ya da `<` ile verilirse kullanıcıya sorulur.
|
|
17
|
+
- Elle uyarla: yok.
|
|
18
|
+
|
|
6
19
|
## [3.28.0] - 2026-10-25
|
|
7
20
|
|
|
8
21
|
### Değişen
|
package/kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.28.
|
|
1
|
+
3.28.1
|
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.1</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
|
@@ -355,6 +355,11 @@ function install({ type, addons, keepKit, project, tool }) {
|
|
|
355
355
|
if (project && path.resolve(project) !== ROOT) return installIntoProject(path.resolve(project), type, addons, tool);
|
|
356
356
|
const tools = validateTools(tool);
|
|
357
357
|
if (fs.existsSync(path.join(ROOT, '.kit.json'))) throw new Error('Kit bu projede zaten kurulu (.kit.json var).');
|
|
358
|
+
// This folder is about to lose kit/, README.md and the kit-only files: uncommitted work there could not be brought back
|
|
359
|
+
const st = spawnSync('git', ['status', '--porcelain', '--', '.'], { cwd: ROOT, encoding: 'utf8' });
|
|
360
|
+
if (st.status === 0 && st.stdout.trim()) {
|
|
361
|
+
throw new Error(`Commit edilmemiş değişiklik var; kurulum kit/ klasörünü siler ve README.md'yi değiştirir. Önce commit et ya da git stash ile kaydet:\n${st.stdout.trimEnd().split('\n').slice(0, 10).join('\n')}`);
|
|
362
|
+
}
|
|
358
363
|
|
|
359
364
|
const { copies, patches } = layers(null, layerNames(type, addons));
|
|
360
365
|
for (const [rel, source] of copies) copy(source, path.join(ROOT, rel));
|
package/package.json
CHANGED