@itcase/config 1.7.15 → 1.7.17
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/README.md +1 -1
- package/package.json +1 -1
- package/smart-commit/cli.js +102 -2
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ npm install --save-dev commitizen @commitlint/cz-commitlint
|
|
|
103
103
|
|
|
104
104
|
## Smart commit
|
|
105
105
|
|
|
106
|
-
`smart-commit` — Cursor Agent (`ask`) планирует коммиты по `SCOPES`, печатает сообщения, спрашивает `[y/N]`. Перед коммитом ждёт и снимает устаревший `.git/index.lock`, если его никто не держит. Lockfile (`package-lock.json` и аналоги) всегда кладётся в тот же коммит, что и соседний `package.json`; при одном только bump deps агент не вызывается.
|
|
106
|
+
`smart-commit` — Cursor Agent (`ask`) планирует коммиты по `SCOPES`, печатает сообщения, спрашивает `[y/N]`. Перед вызовом агента запускается та же проверка типов, что у husky в lint-staged: `tsc --noEmit --pretty`, либо `npm run typecheck`, если конфиг — `lintStagedMonorepo`. Проверка идёт только когда среди изменений есть существующие `.ts`/`.tsx`. Если типы не сходятся, агент не вызывается. `--no-verify` пропускает и эту проверку, и хуки. Перед коммитом CLI ждёт и снимает устаревший `.git/index.lock`, если его никто не держит. Lockfile (`package-lock.json` и аналоги) всегда кладётся в тот же коммит, что и соседний `package.json`; при одном только bump deps агент не вызывается.
|
|
107
107
|
|
|
108
108
|
```text
|
|
109
109
|
[random pixel animation]
|
package/package.json
CHANGED
package/smart-commit/cli.js
CHANGED
|
@@ -50,6 +50,15 @@ const UPYACHKA_FRAME_MS = 100
|
|
|
50
50
|
const INDEX_LOCK_RETRIES = 8
|
|
51
51
|
const INDEX_LOCK_WAIT_MS = 250
|
|
52
52
|
const STALE_LOCK_MS = 1500
|
|
53
|
+
const LINT_STAGED_CONFIGS = [
|
|
54
|
+
'lint-staged.config.mjs',
|
|
55
|
+
'lint-staged.config.js',
|
|
56
|
+
'lint-staged.config.cjs',
|
|
57
|
+
'.lintstagedrc.mjs',
|
|
58
|
+
'.lintstagedrc.js',
|
|
59
|
+
'.lintstagedrc.cjs',
|
|
60
|
+
]
|
|
61
|
+
const LINT_STAGED_TS_KEY = '*.{js,jsx,mjs,cjs,ts,tsx}'
|
|
53
62
|
const AGENT_WAIT_STEPS = [
|
|
54
63
|
'Asking Cursor Agent',
|
|
55
64
|
'Still thinking',
|
|
@@ -67,6 +76,11 @@ Default model: ${DEFAULT_MODEL} (override with --model / SMART_COMMIT_MODEL).
|
|
|
67
76
|
(husky pre-commit / lint-staged and commit-msg). Commit message
|
|
68
77
|
is still checked by smart-commit before git runs.
|
|
69
78
|
|
|
79
|
+
Before the agent, the same typecheck as husky lint-staged runs
|
|
80
|
+
when changes include .ts/.tsx (tsc --noEmit, or npm run typecheck
|
|
81
|
+
from lint-staged config). A type error exits before the agent
|
|
82
|
+
is called. --no-verify skips this check too.
|
|
83
|
+
|
|
70
84
|
Progress sprite is random (nyan cat, banana, or upyachka).
|
|
71
85
|
Force with --cat, --banana, or --upyachka. Agent log is below.
|
|
72
86
|
`
|
|
@@ -104,6 +118,11 @@ async function main(argv) {
|
|
|
104
118
|
return
|
|
105
119
|
}
|
|
106
120
|
|
|
121
|
+
// Husky typechecks in pre-commit, after the agent has already spent tokens.
|
|
122
|
+
if (!option.noVerify) {
|
|
123
|
+
await runHuskyTypecheck(cwd, changedFiles)
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
let plans = tryFastPlan(changedFiles, cwd, scopes, types)
|
|
108
127
|
if (!plans) {
|
|
109
128
|
plans = await planWithAgent({
|
|
@@ -371,6 +390,82 @@ function readReleaseBranches(cwd) {
|
|
|
371
390
|
return match[1].split(/\s+/).filter(Boolean)
|
|
372
391
|
}
|
|
373
392
|
|
|
393
|
+
async function runHuskyTypecheck(cwd, files) {
|
|
394
|
+
const command = await typecheckCommandForFiles(cwd, files)
|
|
395
|
+
if (!command) return
|
|
396
|
+
|
|
397
|
+
process.stdout.write(`${paint('1', 'Typecheck')} ${dim(command)}\n`)
|
|
398
|
+
const bin = path.join(cwd, 'node_modules', '.bin')
|
|
399
|
+
const result = spawnSync(command, {
|
|
400
|
+
cwd: cwd,
|
|
401
|
+
env: {
|
|
402
|
+
...process.env,
|
|
403
|
+
PATH: `${bin}${path.delimiter}${process.env.PATH || ''}`,
|
|
404
|
+
},
|
|
405
|
+
shell: true,
|
|
406
|
+
stdio: 'inherit',
|
|
407
|
+
})
|
|
408
|
+
if (result.error) {
|
|
409
|
+
throw new Error(`Typecheck failed to start: ${result.error.message}`)
|
|
410
|
+
}
|
|
411
|
+
if (result.status !== 0) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
'Typecheck failed. Fix the errors above — the agent was not called.',
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
process.stdout.write('Typecheck passed.\n\n')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function typecheckCommandForFiles(cwd, files) {
|
|
420
|
+
const tsFiles = files.filter(
|
|
421
|
+
(file) =>
|
|
422
|
+
/\.(ts|tsx)$/.test(file) && fs.existsSync(path.resolve(cwd, file)),
|
|
423
|
+
)
|
|
424
|
+
if (!tsFiles.length) return ''
|
|
425
|
+
|
|
426
|
+
const config = await loadLintStagedConfig(cwd)
|
|
427
|
+
const task = config?.[LINT_STAGED_TS_KEY]
|
|
428
|
+
if (typeof task !== 'function') return 'tsc --noEmit --pretty'
|
|
429
|
+
|
|
430
|
+
const absolute = tsFiles.map((file) => path.resolve(cwd, file))
|
|
431
|
+
const commands = await task(absolute)
|
|
432
|
+
return pickTypecheckCommand(commands)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function pickTypecheckCommand(commands) {
|
|
436
|
+
const list = (Array.isArray(commands) ? commands : [commands]).filter(
|
|
437
|
+
(command) => typeof command === 'string' && command.trim(),
|
|
438
|
+
)
|
|
439
|
+
const explicit = list.find((command) =>
|
|
440
|
+
/^(?:tsc|npm(?:\s+run)?\s+typecheck|pnpm(?:\s+run)?\s+typecheck|yarn(?:\s+run)?\s+typecheck)\b/.test(
|
|
441
|
+
command.trim(),
|
|
442
|
+
),
|
|
443
|
+
)
|
|
444
|
+
if (explicit) return explicit.trim()
|
|
445
|
+
|
|
446
|
+
const other = list.find(
|
|
447
|
+
(command) => !/^\s*"?(?:eslint|stylelint)\b/.test(command),
|
|
448
|
+
)
|
|
449
|
+
return other ? other.trim() : ''
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function loadLintStagedConfig(cwd) {
|
|
453
|
+
const file = LINT_STAGED_CONFIGS.map((name) => path.join(cwd, name)).find(
|
|
454
|
+
(candidate) => fs.existsSync(candidate),
|
|
455
|
+
)
|
|
456
|
+
if (file) {
|
|
457
|
+
const loaded = await import(pathToFileURL(file).href)
|
|
458
|
+
return loaded.default ?? loaded
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
462
|
+
if (!fs.existsSync(packageJsonPath)) return null
|
|
463
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
464
|
+
const field = packageJson['lint-staged']
|
|
465
|
+
if (field && typeof field === 'object') return field
|
|
466
|
+
return null
|
|
467
|
+
}
|
|
468
|
+
|
|
374
469
|
function listFiles(cwd, option) {
|
|
375
470
|
if (option.staged) {
|
|
376
471
|
return lines(cwd, [
|
|
@@ -1447,9 +1542,14 @@ function stageFiles(cwd, files) {
|
|
|
1447
1542
|
async function confirm(count) {
|
|
1448
1543
|
if (!input.isTTY) throw new Error('No TTY — use --yes or --dry-run')
|
|
1449
1544
|
const rl = readline.createInterface({ input, output })
|
|
1545
|
+
const prompt = `Create ${count} commit(s)? [y/N] `
|
|
1450
1546
|
try {
|
|
1451
|
-
|
|
1452
|
-
|
|
1547
|
+
for (;;) {
|
|
1548
|
+
const answer = (await rl.question(prompt)).trim()
|
|
1549
|
+
if (/^y(es)?$/i.test(answer)) return true
|
|
1550
|
+
if (/^(n(o)?)?$/i.test(answer)) return false
|
|
1551
|
+
output.write('Enter y or n.\n')
|
|
1552
|
+
}
|
|
1453
1553
|
} finally {
|
|
1454
1554
|
rl.close()
|
|
1455
1555
|
}
|