tillerhand 3.49.9 → 3.49.11
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/messages.mjs +1 -0
- package/.claude/hooks/package-gate.mjs +79 -3
- package/.claude/i18n/tr.json +1 -0
- package/.claude/settings.json +5 -0
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/kit/CHANGELOG.md +11 -0
- package/kit/VERSION +1 -1
- package/kit/guide.html +2 -2
- package/kit/guide.tr.html +2 -2
- package/kit/tools/antigravity/.agents/hooks.json +2 -1
- package/kit/tools/codex/.codex/hooks.json +2 -1
- package/package.json +1 -1
|
@@ -446,6 +446,7 @@ export const MESSAGES = {
|
|
|
446
446
|
'guard.package.timeout': { en: 'could not be verified (timeout)' },
|
|
447
447
|
'guard.package.network': { en: 'could not be verified (network error)' },
|
|
448
448
|
'guard.package.tooMany': { en: 'Package gate: {count} packages in one command (more than {max}) are not checked one by one. User approval required.' },
|
|
449
|
+
'guard.package.depFileUnclear': { en: 'Package gate: the new dependencies in {path} could not be read (the file does not parse or the edit cannot be placed). User approval required.' },
|
|
449
450
|
'guard.package.error': { en: 'The package gate could not check this command ({error}). User approval required.' },
|
|
450
451
|
'guard.package.ask': { en: 'Package gate warning: {list}. Before installing, make sure the package is really the one you want.' },
|
|
451
452
|
'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).' },
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { readInput, decide, isMain, findRoot } from './shared.mjs';
|
|
11
|
+
import { boardAfter } from './freeze-guard.mjs';
|
|
11
12
|
import { t } from './lang.mjs';
|
|
12
13
|
|
|
13
14
|
export const THRESHOLD = { newDays: 30, npmWeeklyDownloads: 500 };
|
|
@@ -26,9 +27,20 @@ const POPULAR = {
|
|
|
26
27
|
|
|
27
28
|
// Case-insensitive (the Windows shell is); npm's install aliases (in, ins, isntall, it…) count as install
|
|
28
29
|
const COMMANDS = [
|
|
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
|
-
|
|
30
|
+
[/^(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'],
|
|
31
|
+
// create/init <name> downloads and runs create-<name>; deno takes npm:<name> specifiers
|
|
32
|
+
[/^(npm|pnpm|yarn|bun)\s+(create|init)\b/i, 'npm', 'create'], [/^deno\s+(add|install|run|x)\b/i, 'npm', 'deno'],
|
|
33
|
+
[/^uv\s+(add|pip\s+install|tool\s+install)\b/i, 'pypi'], [/^((python[\d.]*|py)(\s+-\d[\d.]*)?\s+-m\s+)?pip[\d.]*(\s+-\S+)*\s+install\b/i, 'pypi'],
|
|
34
|
+
[/^(poetry|pdm)\s+add\b|^(pipenv|pipx)\s+install\b/i, 'pypi'],
|
|
31
35
|
];
|
|
36
|
+
// create-vite for vite, @scope/create for @scope, @scope/create-x for @scope/x (npm's own mapping)
|
|
37
|
+
function createName(arg) {
|
|
38
|
+
const n = npmName(arg);
|
|
39
|
+
if (!n) return null;
|
|
40
|
+
if (!n.startsWith('@')) return `create-${n}`;
|
|
41
|
+
const [scope, rest] = n.split('/');
|
|
42
|
+
return rest ? `${scope}/create-${rest}` : `${scope}/create`;
|
|
43
|
+
}
|
|
32
44
|
|
|
33
45
|
// Download-and-run: the first positional is the package (the rest are its arguments), or the values of the package flags.
|
|
34
46
|
// local: the runner uses a bin the project already has before downloading (npx, npm exec, bunx); pnpm/yarn dlx always download.
|
|
@@ -72,6 +84,7 @@ function execArgs(args, eco, positional) {
|
|
|
72
84
|
// Also timeout/nice/stdbuf, shell keywords (then, do, else, !) and combined shell flags (bash -lc).
|
|
73
85
|
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;
|
|
74
86
|
const SUBCOMMAND = /^(install|i|add|exec|x|dlx|tool|pip|run|global)$/;
|
|
87
|
+
const VALUE_FLAG = /^(--prefix|-C|--dir|--cwd|--workspace|--filter|--registry|--cache|--userconfig)$/i;
|
|
75
88
|
export function normalize(part) {
|
|
76
89
|
let s = part.trim(), prev;
|
|
77
90
|
do { prev = s; s = s.replace(WRAPPER, '').trim(); } while (s !== prev);
|
|
@@ -81,7 +94,10 @@ export function normalize(part) {
|
|
|
81
94
|
const t = s.split(/\s+/);
|
|
82
95
|
if (/^(npm|pnpm|yarn|bun|uv)$/i.test(t[0])) {
|
|
83
96
|
let i = 1;
|
|
84
|
-
|
|
97
|
+
// A flag known to take a value always skips it (npm --prefix x i: x is a folder, not the exec subcommand)
|
|
98
|
+
while (i < t.length && t[i].startsWith('-')) i += VALUE_FLAG.test(t[i]) || (!t[i].includes('=') && t[i + 1] && !t[i + 1].startsWith('-') && !SUBCOMMAND.test(t[i + 1])) ? 2 : 1;
|
|
99
|
+
// yarn workspace <name> add x: the workspace name is not the subcommand
|
|
100
|
+
if (/^yarn$/i.test(t[0]) && /^workspace$/i.test(t[i] ?? '')) i += 2;
|
|
85
101
|
s = [t[0], ...t.slice(i)].join(' ');
|
|
86
102
|
}
|
|
87
103
|
return s;
|
|
@@ -124,6 +140,15 @@ export function findPackages(command) {
|
|
|
124
140
|
}
|
|
125
141
|
continue;
|
|
126
142
|
}
|
|
143
|
+
if (match[2] === 'create') {
|
|
144
|
+
const name = createName(args.find((a) => !a.startsWith('-')) ?? '');
|
|
145
|
+
if (name) result.push({ eco, name });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (match[2] === 'deno') {
|
|
149
|
+
for (const a of args) if (/^npm:/i.test(a)) { const name = npmName(a.slice(4)); if (name) result.push({ eco, name }); }
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
127
152
|
for (let i = 0; i < args.length; i++) {
|
|
128
153
|
const a = args[i];
|
|
129
154
|
if (a.startsWith('-')) {
|
|
@@ -137,6 +162,48 @@ export function findPackages(command) {
|
|
|
137
162
|
return result;
|
|
138
163
|
}
|
|
139
164
|
|
|
165
|
+
// Dependency files written with a file tool: the names the write ADDS are checked like an install (user decision
|
|
166
|
+
// 2026-10-30), so "edit package.json, then a bare npm ci" cannot skip the gate. null: the result does not parse (asked).
|
|
167
|
+
const DEP_FILE = /(^|\/)(package\.json|pyproject\.toml|requirements[^/]*\.txt|requirements\/[^/]+\.txt)$/i;
|
|
168
|
+
const NPM_SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
|
|
169
|
+
// An npm: alias installs its target: "x": "npm:evil@1" adds evil
|
|
170
|
+
const npmDeps = (j) => new Set(NPM_SECTIONS.flatMap((k) => Object.entries(j?.[k] && typeof j[k] === 'object' ? j[k] : {})
|
|
171
|
+
.map(([n, v]) => (typeof v === 'string' && /^npm:/i.test(v) ? npmName(v.slice(4)) : n))).filter(Boolean));
|
|
172
|
+
const requirementNames = (text) => new Set(text.split(/\r?\n/).map((s) => s.trim())
|
|
173
|
+
.filter((s) => s && !s.startsWith('#') && !s.startsWith('-')).map(pypiName).filter(Boolean));
|
|
174
|
+
function pyprojectNames(text) {
|
|
175
|
+
const names = new Set();
|
|
176
|
+
let section = '', inArray = false;
|
|
177
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
178
|
+
const line = raw.trim();
|
|
179
|
+
const head = line.match(/^\[([^\]"']+)\]$/);
|
|
180
|
+
if (head) { section = head[1].trim(); inArray = false; continue; }
|
|
181
|
+
if (section === 'project') {
|
|
182
|
+
if (/^dependencies\s*=\s*\[/.test(line)) inArray = true;
|
|
183
|
+
if (!inArray) continue;
|
|
184
|
+
for (const m of line.matchAll(/["']([^"']+)["']/g)) { const n = pypiName(m[1]); if (n) names.add(n); }
|
|
185
|
+
if (line.includes(']')) inArray = false;
|
|
186
|
+
} else if (/^tool\.poetry(\.group\.[\w-]+)?\.(dev-)?dependencies$/.test(section)) {
|
|
187
|
+
const k = line.match(/^["']?([A-Za-z0-9][\w.-]*)["']?\s*=/);
|
|
188
|
+
if (k && k[1].toLowerCase() !== 'python') names.add(k[1].toLowerCase());
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return names;
|
|
192
|
+
}
|
|
193
|
+
export function addedDependencies(file, before, after) {
|
|
194
|
+
const rel = file.replace(/\\/g, '/');
|
|
195
|
+
const added = (eco, a, b) => [...a].filter((n) => !b.has(n)).map((name) => ({ eco, name }));
|
|
196
|
+
if (/(^|\/)package\.json$/i.test(rel)) {
|
|
197
|
+
let a, b = {};
|
|
198
|
+
try { a = JSON.parse(after); } catch { return null; }
|
|
199
|
+
try { b = JSON.parse(before || '{}'); } catch { /* a broken file before: every name counts as added */ }
|
|
200
|
+
return added('npm', npmDeps(a), npmDeps(b));
|
|
201
|
+
}
|
|
202
|
+
if (/(^|\/)requirements[^/]*\.txt$|(^|\/)requirements\/[^/]+\.txt$/i.test(rel)) return added('pypi', requirementNames(after), requirementNames(before));
|
|
203
|
+
if (/(^|\/)pyproject\.toml$/i.test(rel)) return added('pypi', pyprojectNames(after), pyprojectNames(before));
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
140
207
|
function distance(a, b) {
|
|
141
208
|
const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
142
209
|
for (let j = 1; j <= b.length; j++) d[0][j] = j;
|
|
@@ -205,6 +272,15 @@ async function gate(g) {
|
|
|
205
272
|
// The kit's own package (/start and /adopt run npx -y tillerhand@<version> install): the kit already trusts itself
|
|
206
273
|
// 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
|
|
207
274
|
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 ?? '')));
|
|
275
|
+
for (const f of g.files) {
|
|
276
|
+
if (!DEP_FILE.test(String(f.path).replace(/\\/g, '/'))) continue;
|
|
277
|
+
const full = path.resolve(g.dir, f.path);
|
|
278
|
+
let disk = ''; try { disk = fs.readFileSync(full, 'utf8'); } catch { /* new file */ }
|
|
279
|
+
const after = boardAfter(disk, f);
|
|
280
|
+
const added = after === null ? null : addedDependencies(f.path, disk, after);
|
|
281
|
+
if (added === null) decide(g.tool, 'ask', t(g.root, 'guard.package.depFileUnclear', { path: f.path }));
|
|
282
|
+
packages.push(...added);
|
|
283
|
+
}
|
|
208
284
|
if (!packages.length) return;
|
|
209
285
|
if (packages.length > MAX_PACKAGES) decide(g.tool, 'ask', t(g.root, 'guard.package.tooMany', { count: packages.length, max: MAX_PACKAGES }));
|
|
210
286
|
const results = await Promise.all(packages.map(async (p) => ({ ...p, ...(await check(p)) })));
|
package/.claude/i18n/tr.json
CHANGED
|
@@ -443,6 +443,7 @@
|
|
|
443
443
|
"guard.package.timeout": "doğrulanamadı (zaman aşımı)",
|
|
444
444
|
"guard.package.network": "doğrulanamadı (ağ hatası)",
|
|
445
445
|
"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.",
|
|
446
|
+
"guard.package.depFileUnclear": "Paket kapısı: {path} içindeki yeni bağımlılıklar okunamadı (dosya ayrıştırılamıyor ya da düzenleme yerleştirilemiyor). Kullanıcı onayı gerekli.",
|
|
446
447
|
"guard.package.error": "Paket kapısı bu komutu denetleyemedi ({error}). Kullanıcı onayı gerekli.",
|
|
447
448
|
"guard.package.ask": "Paket kapısı uyarısı: {list}. Kurmadan önce paketin gerçekten istenen paket olduğundan emin ol.",
|
|
448
449
|
"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).",
|
package/.claude/settings.json
CHANGED
|
@@ -113,6 +113,11 @@
|
|
|
113
113
|
{
|
|
114
114
|
"type": "command",
|
|
115
115
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/test-guard.mjs\""
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"type": "command",
|
|
119
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/package-gate.mjs\"",
|
|
120
|
+
"timeout": 30
|
|
116
121
|
}
|
|
117
122
|
]
|
|
118
123
|
},
|
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.
|
|
9
|
+
<a href="kit/CHANGELOG.md"><img alt="Version" src="https://img.shields.io/badge/version-3.49.11-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.
|
|
9
|
+
<a href="kit/CHANGELOG.md"><img alt="Sürüm" src="https://img.shields.io/badge/s%C3%BCr%C3%BCm-3.49.11-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,17 @@
|
|
|
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.11] - 2026-10-30
|
|
7
|
+
|
|
8
|
+
### Security
|
|
9
|
+
- **The package gate also checks dependency files.** When the AI adds a dependency to `package.json` (any section, an `npm:` alias's target too), `requirements*.txt` or `pyproject.toml` (`[project] dependencies`, Poetry dependency tables) with a file tool, the new names are checked like an install: a missing package is blocked, a new, rarely downloaded or look-alike one is asked. Before, editing `package.json` and then running a bare `npm ci` skipped the gate. Version changes and other fields are not checked. Runs on Claude Code, Codex and Antigravity; a file that does not parse after the write asks.
|
|
10
|
+
|
|
11
|
+
## [3.49.10] - 2026-10-30
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
- **The package gate checks more install commands:** `npm create <x>` / `npm init <x>` (and the pnpm, yarn, bun versions) check the `create-<x>` package they download and run; `pipx install`, `uv tool install`, `pdm add`, `pipenv install`, a bare `expo install` and Deno `npm:` specifiers are checked too.
|
|
15
|
+
- Flags before the subcommand no longer hide the package: `npm --prefix x i`, `pnpm -C x add`, `yarn --cwd x add`, `yarn workspace web add`, `pip --quiet install`, `py -m pip install`, `python3.12 -m pip install`.
|
|
16
|
+
|
|
6
17
|
## [3.49.9] - 2026-10-30
|
|
7
18
|
|
|
8
19
|
### Security
|
package/kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.49.
|
|
1
|
+
3.49.11
|
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.
|
|
364
|
+
<span class="pill"><b>v3.49.11</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.
|
|
1244
|
+
<span>Version 3.49.11 · <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.
|
|
364
|
+
<span class="pill"><b>v3.49.11</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.
|
|
1248
|
+
<span>Sürüm 3.49.11 · <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
|
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
{ "type": "command", "command": "node .claude/hooks/guard-secrets.mjs" },
|
|
13
13
|
{ "type": "command", "command": "node .claude/hooks/freeze-guard.mjs" },
|
|
14
14
|
{ "type": "command", "command": "node .claude/hooks/structure-guard.mjs" },
|
|
15
|
-
{ "type": "command", "command": "node .claude/hooks/test-guard.mjs" }
|
|
15
|
+
{ "type": "command", "command": "node .claude/hooks/test-guard.mjs" },
|
|
16
|
+
{ "type": "command", "command": "node .claude/hooks/package-gate.mjs", "timeout": 30 }
|
|
16
17
|
]
|
|
17
18
|
}
|
|
18
19
|
]
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-secrets.mjs\"", "commandWindows": "node .claude/hooks/guard-secrets.mjs" },
|
|
16
16
|
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/freeze-guard.mjs\"", "commandWindows": "node .claude/hooks/freeze-guard.mjs" },
|
|
17
17
|
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/structure-guard.mjs\"", "commandWindows": "node .claude/hooks/structure-guard.mjs" },
|
|
18
|
-
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/test-guard.mjs\"", "commandWindows": "node .claude/hooks/test-guard.mjs" }
|
|
18
|
+
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/test-guard.mjs\"", "commandWindows": "node .claude/hooks/test-guard.mjs" },
|
|
19
|
+
{ "type": "command", "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/package-gate.mjs\"", "commandWindows": "node .claude/hooks/package-gate.mjs", "timeout": 30 }
|
|
19
20
|
]
|
|
20
21
|
},
|
|
21
22
|
{
|