tillerhand 3.48.1 → 3.49.0
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/agents/board-chair.md +1 -1
- package/.claude/agents/security-auditor.md +1 -1
- package/.claude/agents/test-writer.md +1 -1
- package/.claude/board/board-product.md +1 -1
- package/.claude/board/council.md +1 -1
- package/.claude/board/rules.md +6 -6
- package/.claude/commands/adopt.md +3 -3
- package/.claude/commands/ask-mode.md +4 -4
- package/.claude/commands/board.md +1 -1
- package/.claude/commands/build.md +1 -1
- package/.claude/commands/design.md +4 -4
- package/.claude/commands/findings-business.md +1 -1
- package/.claude/commands/findings-legal.md +1 -1
- package/.claude/commands/findings-perf.md +1 -1
- package/.claude/commands/findings-security.md +2 -2
- package/.claude/commands/findings-tech.md +1 -1
- package/.claude/commands/findings-ux.md +1 -1
- package/.claude/commands/fix-bug.md +2 -2
- package/.claude/commands/handoff.md +2 -2
- package/.claude/commands/kit-update.md +2 -2
- package/.claude/commands/qa.md +1 -1
- package/.claude/commands/release.md +2 -2
- package/.claude/commands/research.md +3 -3
- package/.claude/commands/retro.md +1 -1
- package/.claude/commands/rollback.md +3 -3
- package/.claude/commands/start.md +4 -4
- package/.claude/commands/tools.md +1 -1
- package/.claude/commands/user-feedback.md +1 -1
- package/.claude/findings/lenses/business.md +1 -1
- package/.claude/findings/protocol.md +2 -2
- package/.claude/hooks/budget.mjs +1 -1
- package/.claude/hooks/checkpoint.mjs +2 -2
- package/.claude/hooks/commit-gate.mjs +14 -15
- package/.claude/hooks/council.mjs +37 -52
- package/.claude/hooks/dashboard-data.mjs +34 -42
- package/.claude/hooks/dashboard.mjs +18 -20
- package/.claude/hooks/destructive-guard.mjs +2 -2
- package/.claude/hooks/flow.mjs +1 -1
- package/.claude/hooks/freeze-guard.mjs +2 -2
- package/.claude/hooks/gate.mjs +18 -11
- package/.claude/hooks/labels.mjs +92 -152
- package/.claude/hooks/lang.mjs +25 -9
- package/.claude/hooks/messages.mjs +496 -494
- package/.claude/hooks/notify.mjs +1 -1
- package/.claude/hooks/prefs.mjs +10 -18
- package/.claude/hooks/preview.mjs +1 -1
- package/.claude/hooks/progress.mjs +7 -9
- package/.claude/hooks/qa-report.mjs +1 -3
- package/.claude/hooks/session-start.mjs +2 -3
- package/.claude/hooks/status-read.mjs +5 -5
- package/.claude/hooks/statusline.mjs +4 -4
- package/.claude/hooks/tools.mjs +3 -7
- package/.claude/hooks/usage.mjs +2 -2
- package/.claude/i18n/tr.json +495 -0
- package/.claude/security-patterns.json +1 -1
- package/.claude/skills/logo-design/SKILL.md +2 -2
- package/.claude/skills/motion-video/SKILL.md +1 -1
- package/.claude/skills/source-research/SKILL.md +1 -1
- package/.claude/skills/ui-motion/SKILL.md +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +1 -1
- package/README.md +2 -2
- package/README.tr.md +2 -2
- package/docs/architecture/overview.md +1 -1
- package/docs/decisions/log.md +1 -1
- package/docs/legal/privacy-kvkk.md +35 -35
- package/docs/operations/deploy-setup.md +1 -1
- package/docs/operations/feature-flags.md +1 -1
- package/docs/product/glossary.md +3 -3
- package/docs/product/launch.md +1 -1
- package/docs/product/prd.md +2 -2
- package/docs/product/roadmap.md +1 -1
- package/docs/product/specs/_template.md +1 -1
- package/docs/product/unit-economics.md +1 -1
- package/docs/quality/test-plan.md +5 -5
- package/docs/workflow/decision-protocol.md +1 -2
- package/docs/workflow/mcp-catalog.md +1 -1
- package/docs/workflow/skills.md +2 -2
- package/docs/workflow/usage-limits.md +1 -1
- package/kit/CHANGELOG.md +28 -0
- package/kit/VERSION +1 -1
- package/kit/guide.html +56 -56
- package/kit/guide.tr.html +10 -10
- package/kit/kit.mjs +7 -42
- package/kit/templates/addons/ai/.env.example.append +3 -3
- package/kit/templates/addons/tr/docs/legal/turkiye.md +55 -55
- package/kit/templates/addons/tr/types/backend/.env.example.append +2 -2
- package/kit/templates/addons/tr/types/backend/app/shared/payments.py +1 -1
- package/kit/templates/addons/tr/types/backend/tests/security/test_payment_webhook.py +3 -3
- package/kit/templates/addons/tr/types/web-saas/.env.example.append +2 -2
- package/kit/templates/addons/tr/types/web-saas/.github/workflows/payment-webhook.yml +3 -3
- package/kit/templates/addons/tr/types/web-saas/src/lib/payments/webhook-signature.ts +1 -1
- package/kit/templates/addons/tr/types/web-saas/tests/security/payment-webhook.mjs +4 -4
- package/kit/templates/backend/.github/dependabot.yml +1 -1
- package/kit/templates/backend/.github/workflows/live-monitoring.yml +1 -1
- package/kit/templates/backend/docs/architecture/stack.md +3 -3
- package/kit/templates/mobile/.github/dependabot.yml +1 -1
- package/kit/templates/mobile/.github/workflows/ci.yml +2 -2
- package/kit/templates/mobile/docs/architecture/stack.md +4 -4
- package/kit/templates/mobile/docs/architecture/structure.md +1 -1
- package/kit/templates/mobile/docs/design/design-guide.md +1 -1
- package/kit/templates/web-saas/.github/dependabot.yml +1 -1
- package/kit/templates/web-saas/.github/workflows/ci.yml +2 -2
- package/kit/templates/web-saas/.github/workflows/live-monitoring.yml +1 -1
- package/kit/templates/web-saas/docs/architecture/stack.md +4 -4
- package/kit/templates/web-saas/docs/design/design-guide.md +1 -1
- package/kit/tools/antigravity/.agents/hooks.json +5 -5
- package/package.json +1 -1
package/.claude/hooks/notify.mjs
CHANGED
|
@@ -38,7 +38,7 @@ function main() {
|
|
|
38
38
|
if (process.argv.includes('--stop')) {
|
|
39
39
|
if (input.stop_hook_active) process.exit(0);
|
|
40
40
|
let startedAt = 0;
|
|
41
|
-
try { const r = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'state', 'reminder.json'), 'utf8')); startedAt = r.turnAt ??
|
|
41
|
+
try { const r = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'state', 'reminder.json'), 'utf8')); startedAt = r.turnAt ?? 0; } catch { /* */ }
|
|
42
42
|
const duration = (Date.now() - startedAt) / 1000;
|
|
43
43
|
if (!startedAt || duration < THRESHOLD_SEC) process.exit(0);
|
|
44
44
|
message = t(root, 'notify.done', { minutes: Math.round(duration / 60) });
|
package/.claude/hooks/prefs.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Personal preferences: how this user likes to work (talk, questions, reports, code style, things to avoid).
|
|
3
|
-
// One file for all projects: ~/.vibe-kit/preferences.md
|
|
3
|
+
// One file for all projects: ~/.vibe-kit/preferences.md. Git never sees
|
|
4
4
|
// it; the session-start hook loads it every session.
|
|
5
5
|
// node .claude/hooks/prefs.mjs --list numbered list
|
|
6
6
|
// node .claude/hooks/prefs.mjs --add <category> "<preference>" only with the user's approval (asked)
|
|
7
7
|
// node .claude/hooks/prefs.mjs --remove <no> (asked)
|
|
8
|
-
// Categories: conversation, questions, reports, code, avoid
|
|
8
|
+
// Categories: conversation, questions, reports, code, avoid.
|
|
9
9
|
// A project rule goes to AGENTS.md → Learned rules, not here.
|
|
10
10
|
// Why a script: the AI never writes this file directly; one line per preference, no secrets, a size cap.
|
|
11
11
|
// The same rules apply when reading: any program on the machine can write this file, so what is loaded is checked
|
|
@@ -17,27 +17,19 @@ import { findSecrets } from './guard-secrets.mjs';
|
|
|
17
17
|
import { isMain } from './shared.mjs';
|
|
18
18
|
|
|
19
19
|
export const CATEGORIES = { conversation: 'Conversation', questions: 'Questions and decisions', reports: 'Reports', code: 'Code', avoid: 'Avoid' };
|
|
20
|
-
|
|
21
|
-
const OLD = { konusma: ['conversation', 'Konuşma'], sorular: ['questions', 'Sorular ve kararlar'], raporlar: ['reports', 'Raporlar'], kod: ['code', 'Kod'], kacin: ['avoid', 'Kaçınılacaklar'] };
|
|
22
|
-
export const categoryKey = (k) => (Object.hasOwn(CATEGORIES, k ?? '') ? k : Object.hasOwn(OLD, k ?? '') ? OLD[k][0] : null);
|
|
20
|
+
export const categoryKey = (k) => (Object.hasOwn(CATEGORIES, k ?? '') ? k : null);
|
|
23
21
|
export const MAX_ITEMS = 40;
|
|
24
22
|
const MAX_LENGTH = 200;
|
|
25
23
|
const MAX_BYTES = 64 * 1024;
|
|
26
24
|
|
|
27
25
|
// os.homedir() follows HOME / USERPROFILE; the file name is fixed, so no other file can be written through it.
|
|
28
|
-
|
|
29
|
-
export const prefsFile = () => {
|
|
30
|
-
const file = path.join(os.homedir(), '.vibe-kit', 'preferences.md');
|
|
31
|
-
const old = path.join(os.homedir(), '.vibe-kit', 'tercihler.md');
|
|
32
|
-
if (fs.existsSync(file) || !fs.existsSync(old)) return file;
|
|
33
|
-
try { fs.renameSync(old, file); return file; } catch { return old; }
|
|
34
|
-
};
|
|
26
|
+
export const prefsFile = () => path.join(os.homedir(), '.vibe-kit', 'preferences.md');
|
|
35
27
|
|
|
36
|
-
// A heading is matched by key or title, ignoring case
|
|
37
|
-
const fold = (s) => s.
|
|
38
|
-
const categoryOf = (heading) => Object.keys(CATEGORIES).find((k) => fold(k) === fold(heading) || fold(CATEGORIES[k]) === fold(heading))
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
// A heading is matched by key or title, ignoring case (## code, ## Questions and decisions)
|
|
29
|
+
const fold = (s) => s.toLowerCase().trim();
|
|
30
|
+
const categoryOf = (heading) => Object.keys(CATEGORIES).find((k) => fold(k) === fold(heading) || fold(CATEGORIES[k]) === fold(heading));
|
|
31
|
+
// Duplicate check on user text in any language: case and accents folded, dotless i as i ("KISA" = "kısa", "İ" = "i")
|
|
32
|
+
const folded = (s) => s.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().replace(/\u0131/g, 'i');
|
|
41
33
|
const clean = (s) => String(s ?? '').replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
42
34
|
const valid = (t) => t && t.length <= MAX_LENGTH && !findSecrets(t).length;
|
|
43
35
|
|
|
@@ -85,7 +77,7 @@ export function addPref(key, text, file = prefsFile()) {
|
|
|
85
77
|
if (findSecrets(t).length) return { error: 'The preference contains a value that looks like a secret; secrets are not written to this file.' };
|
|
86
78
|
const { items, unknown } = parsePrefs(file);
|
|
87
79
|
if (unknown.length) return { error: `The file has unreadable lines; nothing was written, so they are not lost. Fix or remove them (${file}):\n${unknown.slice(0, 5).join('\n')}` };
|
|
88
|
-
const same = items.find((p) => p.text
|
|
80
|
+
const same = items.find((p) => folded(p.text) === folded(t));
|
|
89
81
|
if (same) return { existing: same.category };
|
|
90
82
|
if (items.length >= MAX_ITEMS) return { error: `At most ${MAX_ITEMS} preferences are kept. Delete an old one first: --list, then --remove <no>.` };
|
|
91
83
|
const error = writePrefs([...items, { category, text: t }], file);
|
|
@@ -57,7 +57,7 @@ export async function preview(root, task) {
|
|
|
57
57
|
if (!url) return fail('AGENTS.md → Commands has no `preview:` line, or it is empty. Add it (example: `preview: http://localhost:3000`) or produce the evidence by hand in this task.');
|
|
58
58
|
if (!LOCAL.test(url)) return fail(`the preview address must be local (http://localhost:<port>, 127.0.0.1 or [::1]). Now: ${url}`);
|
|
59
59
|
if (!isApproved(root, list)) return { code: 1, text: `Preview · ${approvalNeeded(list)}` };
|
|
60
|
-
const dir = `.previews/${String(task || '
|
|
60
|
+
const dir = `.previews/${String(task || 'latest').replace(/[^\w-]/g, '-')}`;
|
|
61
61
|
// A linked folder from the repo would send the files elsewhere
|
|
62
62
|
for (const d of ['.previews', dir]) {
|
|
63
63
|
try { if (fs.lstatSync(path.join(root, d)).isSymbolicLink()) return fail(`${d} is a link (symlink/junction); it must be a real folder.`); } catch { /* missing: created below */ }
|
|
@@ -6,27 +6,25 @@
|
|
|
6
6
|
// node .claude/hooks/progress.mjs --cost "<job>" <size> ["<note>"] [--limit N] start from --start, end now (once)
|
|
7
7
|
// node .claude/hooks/progress.mjs --task "<active task>" --did "<done>" --next "<next step>"
|
|
8
8
|
// node .claude/hooks/progress.mjs --check progress.md labels the status line and dashboard read
|
|
9
|
-
// Every progress write also sets **Last updated:**
|
|
9
|
+
// Every progress write also sets **Last updated:** to today. Flags can be combined in one call.
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { findRoot, isMain } from './shared.mjs';
|
|
13
13
|
import { clean, labels } from './status-read.mjs';
|
|
14
|
-
import { label
|
|
14
|
+
import { label } from './labels.mjs';
|
|
15
15
|
import { readLimits } from './usage.mjs';
|
|
16
16
|
|
|
17
17
|
const REQUIRED = ['lastUpdated', 'activePhase', 'activeTask', 'setupStep', 'questionMode', 'profile'];
|
|
18
18
|
const SIZES = ['S', 'M', 'L', 'BOARD', 'DESIGN', 'FINDINGS'];
|
|
19
|
-
// Sizes before 3.48 (küçük / orta / büyük); still accepted, written as the English one
|
|
20
|
-
export const OLD_SIZES = { K: 'S', O: 'M', B: 'L' };
|
|
21
19
|
const USAGE = 'Usage: node .claude/hooks/progress.mjs [--done G-NNN] [--start] [--cost "<job>" <S|M|L|BOARD|DESIGN|FINDINGS> [<start %> <end %> <min>] ["<note>"]] [--limit N] [--task "<text>"] [--did "<text>"] [--next "<text>"] [--check]';
|
|
22
20
|
// One line per value: a newline would break a label, a list item or a table row
|
|
23
21
|
const line = (s) => clean(String(s).replace(/\s*\r?\n\s*/g, ' ')).trim();
|
|
24
22
|
const today = () => new Date().toLocaleDateString('sv-SE');
|
|
25
|
-
// Labels and headings are written in
|
|
23
|
+
// Labels and headings are written in English. Function replacements: a value
|
|
26
24
|
// with $& or $' is text, not a replacement pattern.
|
|
27
25
|
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
-
const setLabel = (text, key, v) => text.replace(new RegExp(`^(\\*\\*${esc(
|
|
29
|
-
const sectionRe = (text, key) => new RegExp(`^(## ${esc(
|
|
26
|
+
const setLabel = (text, key, v) => text.replace(new RegExp(`^(\\*\\*${esc(label(key))}:\\*\\*).*$`, 'm'), (_, l) => `${l} ${v}`);
|
|
27
|
+
const sectionRe = (text, key) => new RegExp(`^(## ${esc(label(key))}[^\\n]*\\n)([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, 'm');
|
|
30
28
|
const setSection = (text, key, body) => text.replace(sectionRe(text, key), (_, h) => `${h}\n${body}\n\n`);
|
|
31
29
|
const section = (text, key) => text.match(sectionRe(text, key))?.[2] ?? '';
|
|
32
30
|
|
|
@@ -40,7 +38,7 @@ export function markDone(text, id) {
|
|
|
40
38
|
|
|
41
39
|
export function costRow(args, date = today()) {
|
|
42
40
|
const [job, typed, start, end, min, note = ''] = args;
|
|
43
|
-
const size =
|
|
41
|
+
const size = typed;
|
|
44
42
|
const nums = [start, end, min].map(Number);
|
|
45
43
|
if (!job || !SIZES.includes(size) || nums.some((n) => !Number.isFinite(n) || n < 0) || args.length > 6) return null;
|
|
46
44
|
// The end below the start means the 5-hour window renewed in between: the difference is unknown
|
|
@@ -62,7 +60,7 @@ export function updateProgress(text, { task, did, next }) {
|
|
|
62
60
|
|
|
63
61
|
// By their English names, whichever language the file uses; an example inside a code block does not count
|
|
64
62
|
export const missingLabels = (text) => {
|
|
65
|
-
const found = labels(
|
|
63
|
+
const found = labels(text.replace(/^```[\s\S]*?^```.*$/gm, ''));
|
|
66
64
|
return REQUIRED.map((k) => label(k)).filter((k) => !(k in found));
|
|
67
65
|
};
|
|
68
66
|
|
|
@@ -42,9 +42,7 @@ export function qaReport(root, junit = 'test-results-qa/junit.xml', now = new Da
|
|
|
42
42
|
const rows = criteria.map((k) => {
|
|
43
43
|
const r = results.get(k.key);
|
|
44
44
|
let evidence = '';
|
|
45
|
-
|
|
46
|
-
const names = [k.id, k.id.replace(/^Ö-/, 'AC-'), k.id.replace(/^AC-/, 'Ö-')];
|
|
47
|
-
const shot = [...new Set(names)].map((n) => path.join(root, path.dirname(junit), 'shots', `${n}.png`))
|
|
45
|
+
const shot = [path.join(root, path.dirname(junit), 'shots', `${k.id}.png`)]
|
|
48
46
|
.find((p) => { try { return fs.lstatSync(p).isFile(); } catch { return false; } });
|
|
49
47
|
// Only a real screenshot file: a shots/<id>.png link would copy any local file into .previews/
|
|
50
48
|
const real = !!shot;
|
|
@@ -21,7 +21,7 @@ const root = findRoot();
|
|
|
21
21
|
|
|
22
22
|
const lang = readLang(root);
|
|
23
23
|
notes.push(lang
|
|
24
|
-
? `Conversation language: ${lang}. Write every question, option, answer, status block and user-facing document text in this language, even when the user, a file or a tool writes in another; code, commands and paths stay as they are.`
|
|
24
|
+
? `Conversation language: ${lang}. Write every question, option, answer, status block and user-facing document text in this language, even when the user, a file or a tool writes in another; code, commands and paths stay as they are. Labels, headings, keys and verdict words in project files (**Active phase:**, ## Where we left off, type check:, APPROVE / CONDITIONAL / REJECT) stay English in every language: tools read them.`
|
|
25
25
|
: 'Language not chosen yet: /start and /adopt ask it first, in English.');
|
|
26
26
|
|
|
27
27
|
try {
|
|
@@ -33,8 +33,7 @@ try {
|
|
|
33
33
|
const a = d.fields;
|
|
34
34
|
notes.push(`Phase: ${a['Active phase'] ?? '-'} · Task: ${a['Active task'] ?? '-'} · Question mode: ${a['Question mode'] ?? 'on'} · Board: ${d.board.state}`);
|
|
35
35
|
if (isValue(a['Active phase'], 'setup') && d.installed) {
|
|
36
|
-
|
|
37
|
-
const command = /\((adopt|uyum)\)/i.test(a['Active phase']) ? '/adopt' : '/start';
|
|
36
|
+
const command = /\(adopt\)/i.test(a['Active phase']) ? '/adopt' : '/start';
|
|
38
37
|
notes.push(`${command} was left unfinished (step ${a['Setup step']}). Suggest the user continue with ${command} where they left off.`);
|
|
39
38
|
next = t(root, 'session.resume', { command, step: a['Setup step'] });
|
|
40
39
|
} else {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Reads the project state from the docs. Used by statusline, session-start and the dashboard.
|
|
2
2
|
// Formats read: **Label:** lines and ## sections in docs/workflow/progress.md, checkboxes under "## Phase" headings
|
|
3
3
|
// in docs/product/roadmap.md, the "Decision log" in docs/decisions/log.md and the docs/workflow/board.md tables.
|
|
4
|
-
// Labels are read in English
|
|
4
|
+
// Labels are read in English.
|
|
5
5
|
// Must stay fast: the status line calls it on every refresh (git and heavy reads are in dashboard.mjs).
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { t } from './lang.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import { isValue } from './labels.mjs';
|
|
10
10
|
|
|
11
11
|
// Only a regular file: a folder or a link to a folder named like a test or report file must not throw (EISDIR)
|
|
12
|
-
// The project name placeholder in AGENTS.md
|
|
13
|
-
export const PLACEHOLDER_NAMES = ['[PROJECT NAME]'
|
|
14
|
-
export const readText = (p) => { try { return fs.statSync(p).isFile() ?
|
|
12
|
+
// The project name placeholder in AGENTS.md
|
|
13
|
+
export const PLACEHOLDER_NAMES = ['[PROJECT NAME]'];
|
|
14
|
+
export const readText = (p) => { try { return fs.statSync(p).isFile() ? fs.readFileSync(p, 'utf8') : ''; } catch { return ''; } };
|
|
15
15
|
|
|
16
16
|
export function sections(text, level = '## ') {
|
|
17
17
|
const result = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Claude Code bottom status line: phase, task progress, question mode and usage limits.
|
|
3
|
-
// Example: ▶
|
|
3
|
+
// Example: ▶ Phase 1 · G-012 · task 7/23 · questions: open (closed: design) · 5h 34% · weekly 5%
|
|
4
4
|
// Also writes the rate_limits Claude Code sends into .claude/state/usage.json (usage.mjs reads it).
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import { readStatus } from './status-read.mjs';
|
|
@@ -49,12 +49,12 @@ try {
|
|
|
49
49
|
parts.push(t(root, 'statusline.notSetUp'));
|
|
50
50
|
} else {
|
|
51
51
|
const a = d.fields;
|
|
52
|
-
// progress.md says on / off
|
|
52
|
+
// progress.md says on / off; only what the line shows is localized
|
|
53
53
|
const mode = a['Question mode'] ?? 'on';
|
|
54
54
|
const question = mode
|
|
55
55
|
.replace(/^\S+/, (m) => (isValue(m, 'on') ? t(root, 'statusline.modeOpen') : isValue(m, 'off') ? t(root, 'statusline.modeOff') : m))
|
|
56
|
-
.replace(/\s*·\s*
|
|
57
|
-
.replace(/\s*·\s*
|
|
56
|
+
.replace(/\s*·\s*closed topics:\s*none\s*$/i, '')
|
|
57
|
+
.replace(/\s*·\s*closed topics:\s*(.*)$/i, (_, topics) => ` ${t(root, 'statusline.closed', { topics })}`);
|
|
58
58
|
parts.push(`▶ ${a['Active phase'] ?? '-'}`);
|
|
59
59
|
if (isValue(a['Active phase'], 'setup')) parts.push(t(root, 'statusline.step', { step: a['Setup step'] ?? '-' }));
|
|
60
60
|
else parts.push(a['Active task'] ?? '-', t(root, 'statusline.task', { done: d.taskCount.done, total: d.taskCount.total }));
|
package/.claude/hooks/tools.mjs
CHANGED
|
@@ -50,13 +50,9 @@ function readJson(p) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Package names before 3.48; they still work as input
|
|
54
|
-
const OLD_NAMES = { tasarim: 'design', guvenlik: 'security', yayin: 'release' };
|
|
55
|
-
export const packageName = (name) => (Object.hasOwn(OLD_NAMES, name ?? '') ? OLD_NAMES[name] : name);
|
|
56
|
-
|
|
57
53
|
function pkg(name) {
|
|
58
|
-
if (!Object.hasOwn(PACKAGES,
|
|
59
|
-
return PACKAGES[
|
|
54
|
+
if (!Object.hasOwn(PACKAGES, name ?? '')) throw new Error(`Unknown package: ${name}. Options: ${Object.keys(PACKAGES).join(', ')}`);
|
|
55
|
+
return PACKAGES[name];
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
export function packageStatus(root) {
|
|
@@ -149,7 +145,7 @@ const NOTE = 'After enabling, run /reload-plugins. Enabling mid-session breaks t
|
|
|
149
145
|
if (isMain(import.meta.url)) {
|
|
150
146
|
const root = findRoot();
|
|
151
147
|
const [flag, typed] = process.argv.slice(2);
|
|
152
|
-
const name =
|
|
148
|
+
const name = typed;
|
|
153
149
|
try {
|
|
154
150
|
if (flag === '--enable' || flag === '--disable') {
|
|
155
151
|
const ids = setPackage(root, name, flag === '--enable');
|
package/.claude/hooks/usage.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { findRoot, isMain } from './shared.mjs';
|
|
10
|
-
import { t, readLang } from './lang.mjs';
|
|
10
|
+
import { t, readLang, locale } from './lang.mjs';
|
|
11
11
|
|
|
12
12
|
export const THRESHOLDS = { five_hour: { yellow: 60, red: 85 }, seven_day: { yellow: 70, red: 90 } };
|
|
13
13
|
// Window → message key of its name
|
|
@@ -68,7 +68,7 @@ export function level(data) {
|
|
|
68
68
|
function timeLeft(resetsAt, now, root) {
|
|
69
69
|
if (!resetsAt) return '';
|
|
70
70
|
const min = Math.max(0, Math.round((resetsAt * 1000 - now) / 60000));
|
|
71
|
-
const clock = new Date(resetsAt * 1000).toLocaleString(readLang(root)
|
|
71
|
+
const clock = new Date(resetsAt * 1000).toLocaleString(locale(readLang(root)), { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
72
72
|
const left = min >= 1440
|
|
73
73
|
? t(root, 'usage.leftDays', { days: Math.floor(min / 1440), hours: Math.floor((min % 1440) / 60) })
|
|
74
74
|
: t(root, 'usage.leftHours', { hours: Math.floor(min / 60), minutes: min % 60 });
|