@itcase/config 1.6.75 → 1.6.77
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 +2 -2
- package/package.json +4 -4
- package/smart-commit/cli.js +126 -14
- package/smart-commit/dancing-banana.js +339 -224
- package/smart-commit/nyan-cat.js +327 -0
- package/smart-commit/scripts/extract-banana.py +0 -255
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ npm install --save-dev commitizen @commitlint/cz-commitlint
|
|
|
87
87
|
|
|
88
88
|
## Smart commit
|
|
89
89
|
|
|
90
|
-
`smart-commit` — Cursor Agent (`ask`) планирует коммиты по `SCOPES`, печатает сообщения, спрашивает `[y/N]`.
|
|
90
|
+
`smart-commit` — Cursor Agent (`ask`) планирует коммиты по `SCOPES`, печатает сообщения, спрашивает `[y/N]`. Перед коммитом ждёт и снимает устаревший `.git/index.lock`, если его никто не держит.
|
|
91
91
|
|
|
92
92
|
```text
|
|
93
93
|
[yellow pixel banana animation]
|
|
@@ -146,7 +146,7 @@ CLI ставится в `~/.local/bin`. Логин в Cursor IDE этот CLI н
|
|
|
146
146
|
|
|
147
147
|
Обновление CLI: `agent update`.
|
|
148
148
|
|
|
149
|
-
Пока Agent думает, в терминале крутится pixel-анимация dancing banana + статус `(Ns)` на одной области экрана (`log-update`). Для полного лога: `npm run smart-commit -- --verbose --dry-run`.
|
|
149
|
+
Пока Agent думает, в терминале крутится pixel-анимация (случайно nyan cat или dancing banana) + статус `(Ns)` на одной области экрана (`log-update`). Принудительно: `--cat` / `--banana`. Для полного лога: `npm run smart-commit -- --verbose --dry-run`.
|
|
150
150
|
|
|
151
151
|
> **Важно:** в npm есть одноимённый пакет `smart-commit`. Используйте `npm run smart-commit` из проекта с `@itcase/config` — bin берётся из `node_modules`. `npx smart-commit` без локальной установки может подтянуть чужой пакет; CLI перед запуском проверяет, что выполняется именно `@itcase/config`.
|
|
152
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itcase/config",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.77",
|
|
4
4
|
"author": "ITCase",
|
|
5
5
|
"description": "ITCase Config",
|
|
6
6
|
"engines": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"chokidar-cli": "^3.0.0",
|
|
68
68
|
"commitizen": "^4.3.2",
|
|
69
69
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
70
|
-
"cssnano": "^9.0.
|
|
71
|
-
"cssnano-preset-default": "^9.0.
|
|
70
|
+
"cssnano": "^9.0.2",
|
|
71
|
+
"cssnano-preset-default": "^9.0.2",
|
|
72
72
|
"inquirer": "^14.2.0",
|
|
73
73
|
"log-update": "^8.0.0",
|
|
74
74
|
"postcss": "^8.5.26",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"postcss-combine-duplicated-selectors": "^10.0.3",
|
|
80
80
|
"postcss-convert-unit": "^0.1.3",
|
|
81
81
|
"postcss-dark-theme-class": "^2.0.0",
|
|
82
|
-
"postcss-discard-duplicates": "^9.0.
|
|
82
|
+
"postcss-discard-duplicates": "^9.0.1",
|
|
83
83
|
"postcss-each": "^1.1.0",
|
|
84
84
|
"postcss-easings": "^4.0.0",
|
|
85
85
|
"postcss-extend-rule": "^4.0.0",
|
package/smart-commit/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
promptSubjectRules,
|
|
17
17
|
} from '../commitlint/commitlintCommon.js'
|
|
18
18
|
import { getBananaFrame } from './dancing-banana.js'
|
|
19
|
+
import { getNyanFrame } from './nyan-cat.js'
|
|
19
20
|
|
|
20
21
|
const PACKAGE_NAME = '@itcase/config'
|
|
21
22
|
const CLI_RELATIVE = 'smart-commit/cli.js'
|
|
@@ -23,17 +24,24 @@ const CLI_RELATIVE = 'smart-commit/cli.js'
|
|
|
23
24
|
const MAX_DIFF = 80_000
|
|
24
25
|
const AGENT_TIMEOUT_MS = 120_000
|
|
25
26
|
const AGENT_STEP_MS = 2_500
|
|
27
|
+
const NYAN_FRAME_MS = 90
|
|
26
28
|
const BANANA_FRAME_MS = 100
|
|
29
|
+
const INDEX_LOCK_RETRIES = 8
|
|
30
|
+
const INDEX_LOCK_WAIT_MS = 250
|
|
31
|
+
const STALE_LOCK_MS = 1500
|
|
27
32
|
const AGENT_WAIT_STEPS = [
|
|
28
33
|
'Asking Cursor Agent',
|
|
29
34
|
'Still thinking',
|
|
30
35
|
'Waiting for response',
|
|
31
36
|
]
|
|
32
37
|
const HELP = `smart-commit [--dry-run] [--yes|-y] [--type t] [--model id] [--verbose]
|
|
38
|
+
[--cat|--nyan] [--banana]
|
|
33
39
|
|
|
34
40
|
Cursor Agent → commit messages by SCOPES → [y/N] → git commit
|
|
35
41
|
Run via: npm exec smart-commit (from project with @itcase/config)
|
|
36
42
|
Needs: agent login (or CURSOR_API_KEY) + commitlint.config.* with SCOPES
|
|
43
|
+
|
|
44
|
+
Progress sprite is random (nyan cat or banana). Force with --cat or --banana.
|
|
37
45
|
`
|
|
38
46
|
const AGENT_MISSING = `Cursor Agent CLI not found — smart-commit cannot run.
|
|
39
47
|
|
|
@@ -71,7 +79,11 @@ async function main(argv) {
|
|
|
71
79
|
|
|
72
80
|
const prompt = buildPrompt(scopes, types, files, cwd, option.type)
|
|
73
81
|
const raw = await withAgentProgress(
|
|
74
|
-
{
|
|
82
|
+
{
|
|
83
|
+
fileCount: files.length,
|
|
84
|
+
sprite: pickSprite(option.sprite),
|
|
85
|
+
verbose: option.verbose,
|
|
86
|
+
},
|
|
75
87
|
(progress) =>
|
|
76
88
|
askAgent(
|
|
77
89
|
cwd,
|
|
@@ -115,6 +127,8 @@ async function main(argv) {
|
|
|
115
127
|
)
|
|
116
128
|
}
|
|
117
129
|
|
|
130
|
+
ensureGitIndexReady(cwd)
|
|
131
|
+
|
|
118
132
|
let created = 0
|
|
119
133
|
for (const plan of activePlans) {
|
|
120
134
|
const files = plan.files.filter((file) => fileHasChanges(cwd, file))
|
|
@@ -135,12 +149,13 @@ async function main(argv) {
|
|
|
135
149
|
)
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
|
|
139
|
-
cwd:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
try {
|
|
153
|
+
git(cwd, ['commit', '-m', plan.message], { inherit: true })
|
|
154
|
+
} catch (error) {
|
|
155
|
+
throw new Error(`git commit failed: ${plan.message}\n${error.message}`, {
|
|
156
|
+
cause: error,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
144
159
|
created++
|
|
145
160
|
}
|
|
146
161
|
process.stdout.write(`Created ${created} commit(s).\n`)
|
|
@@ -151,6 +166,7 @@ function parseArgs(argv) {
|
|
|
151
166
|
type: '',
|
|
152
167
|
dryRun: false,
|
|
153
168
|
model: '',
|
|
169
|
+
sprite: '',
|
|
154
170
|
staged: false,
|
|
155
171
|
verbose: false,
|
|
156
172
|
yes: false,
|
|
@@ -161,6 +177,8 @@ function parseArgs(argv) {
|
|
|
161
177
|
else if (arg === '--yes' || arg === '-y') option.yes = true
|
|
162
178
|
else if (arg === '--verbose') option.verbose = true
|
|
163
179
|
else if (arg === '--staged') option.staged = true
|
|
180
|
+
else if (arg === '--cat' || arg === '--nyan') option.sprite = 'cat'
|
|
181
|
+
else if (arg === '--banana') option.sprite = 'banana'
|
|
164
182
|
else if (arg === '--type') option.type = argv[++i] || ''
|
|
165
183
|
else if (arg === '--model') option.model = argv[++i] || ''
|
|
166
184
|
else if (arg.startsWith('--type=')) option.type = arg.slice(7)
|
|
@@ -170,6 +188,11 @@ function parseArgs(argv) {
|
|
|
170
188
|
return option
|
|
171
189
|
}
|
|
172
190
|
|
|
191
|
+
function pickSprite(forced) {
|
|
192
|
+
if (forced === 'cat' || forced === 'banana') return forced
|
|
193
|
+
return Math.random() < 0.5 ? 'cat' : 'banana'
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
function assertItcaseConfigPackage(cwd) {
|
|
174
197
|
const scriptPath = fileURLToPath(import.meta.url)
|
|
175
198
|
|
|
@@ -375,13 +398,21 @@ async function withAgentProgress(context, callback) {
|
|
|
375
398
|
return callback({ onAgentActivity })
|
|
376
399
|
}
|
|
377
400
|
|
|
401
|
+
const columns = process.stderr.columns || process.stdout.columns || 80
|
|
402
|
+
const rows = process.stderr.rows || process.stdout.rows || 24
|
|
403
|
+
const getFrame =
|
|
404
|
+
context.sprite === 'cat'
|
|
405
|
+
? (index) => getNyanFrame(index, columns)
|
|
406
|
+
: (index) => getBananaFrame(index, columns, rows)
|
|
407
|
+
const frameMs = context.sprite === 'cat' ? NYAN_FRAME_MS : BANANA_FRAME_MS
|
|
408
|
+
process.stderr.write('\x1b[?7l')
|
|
409
|
+
|
|
378
410
|
const render = () => {
|
|
379
|
-
logUpdateStderr(`${
|
|
411
|
+
logUpdateStderr(`${getFrame(frameIndex)}\n${formatText()}`)
|
|
380
412
|
frameIndex++
|
|
381
413
|
}
|
|
382
414
|
|
|
383
|
-
render
|
|
384
|
-
const frameTimer = setInterval(render, BANANA_FRAME_MS)
|
|
415
|
+
const frameTimer = setInterval(render, frameMs)
|
|
385
416
|
const stepTimer = setInterval(() => {
|
|
386
417
|
if (phase === 'early') {
|
|
387
418
|
if (earlyStepIndex < earlySteps.length - 1) {
|
|
@@ -395,11 +426,13 @@ async function withAgentProgress(context, callback) {
|
|
|
395
426
|
}, AGENT_STEP_MS)
|
|
396
427
|
|
|
397
428
|
try {
|
|
429
|
+
render()
|
|
398
430
|
return await callback({ onAgentActivity })
|
|
399
431
|
} finally {
|
|
400
432
|
clearInterval(frameTimer)
|
|
401
433
|
clearInterval(stepTimer)
|
|
402
434
|
logUpdateStderr.clear()
|
|
435
|
+
process.stderr.write('\x1b[?7h')
|
|
403
436
|
}
|
|
404
437
|
}
|
|
405
438
|
|
|
@@ -593,10 +626,89 @@ async function confirm(count) {
|
|
|
593
626
|
}
|
|
594
627
|
}
|
|
595
628
|
|
|
596
|
-
function
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
629
|
+
function isIndexLockError(stderr) {
|
|
630
|
+
return /index\.lock/.test(stderr || '')
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function sleepSync(ms) {
|
|
634
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function indexLockPath(cwd) {
|
|
638
|
+
const result = spawnSync('git', ['rev-parse', '--git-path', 'index.lock'], {
|
|
639
|
+
cwd: cwd,
|
|
640
|
+
encoding: 'utf8',
|
|
641
|
+
})
|
|
642
|
+
const gitPath = (result.stdout || '').trim()
|
|
643
|
+
if (!gitPath) return path.join(cwd, '.git', 'index.lock')
|
|
644
|
+
return path.resolve(cwd, gitPath)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function lockHolders(lockPath) {
|
|
648
|
+
const result = spawnSync('lsof', ['-t', lockPath], { encoding: 'utf8' })
|
|
649
|
+
if (result.error || result.status === 127) return null
|
|
650
|
+
return (result.stdout || '').trim().split('\n').filter(Boolean)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function clearStaleIndexLock(cwd) {
|
|
654
|
+
const lockPath = indexLockPath(cwd)
|
|
655
|
+
let stat
|
|
656
|
+
try {
|
|
657
|
+
stat = fs.statSync(lockPath)
|
|
658
|
+
} catch {
|
|
659
|
+
return false
|
|
660
|
+
}
|
|
661
|
+
if (Date.now() - stat.mtimeMs < STALE_LOCK_MS) return false
|
|
662
|
+
const holders = lockHolders(lockPath)
|
|
663
|
+
if (holders && holders.length) return false
|
|
664
|
+
try {
|
|
665
|
+
fs.unlinkSync(lockPath)
|
|
666
|
+
return true
|
|
667
|
+
} catch {
|
|
668
|
+
return false
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function ensureGitIndexReady(cwd) {
|
|
673
|
+
for (let attempt = 1; attempt <= INDEX_LOCK_RETRIES; attempt++) {
|
|
674
|
+
const lockPath = indexLockPath(cwd)
|
|
675
|
+
if (!fs.existsSync(lockPath)) return
|
|
676
|
+
if (attempt === 1) {
|
|
677
|
+
process.stderr.write('Waiting for git index.lock to clear…\n')
|
|
678
|
+
}
|
|
679
|
+
clearStaleIndexLock(cwd)
|
|
680
|
+
if (!fs.existsSync(lockPath)) return
|
|
681
|
+
sleepSync(INDEX_LOCK_WAIT_MS * attempt)
|
|
682
|
+
}
|
|
683
|
+
const lockPath = indexLockPath(cwd)
|
|
684
|
+
if (!fs.existsSync(lockPath)) return
|
|
685
|
+
throw new Error(
|
|
686
|
+
`git index.lock still exists: ${lockPath}\n` +
|
|
687
|
+
'Another git process may be running. If it crashed, remove the file and retry.',
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function git(cwd, args, option = {}) {
|
|
692
|
+
for (let attempt = 1; attempt <= INDEX_LOCK_RETRIES; attempt++) {
|
|
693
|
+
const result = spawnSync('git', args, {
|
|
694
|
+
cwd: cwd,
|
|
695
|
+
encoding: 'utf8',
|
|
696
|
+
stdio: option.inherit ? ['ignore', 'inherit', 'pipe'] : undefined,
|
|
697
|
+
})
|
|
698
|
+
if (!result.status) return result
|
|
699
|
+
|
|
700
|
+
const stderr = result.stderr || ''
|
|
701
|
+
if (!isIndexLockError(stderr) || attempt === INDEX_LOCK_RETRIES) {
|
|
702
|
+
if (option.inherit && stderr) process.stderr.write(stderr)
|
|
703
|
+
throw new Error(stderr.trim() || `git ${args.join(' ')}`)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (attempt === 1) {
|
|
707
|
+
process.stderr.write('Waiting for git index.lock to clear…\n')
|
|
708
|
+
}
|
|
709
|
+
clearStaleIndexLock(cwd)
|
|
710
|
+
sleepSync(INDEX_LOCK_WAIT_MS * attempt)
|
|
711
|
+
}
|
|
600
712
|
}
|
|
601
713
|
|
|
602
714
|
function out(cwd, args) {
|