@itcase/config 1.7.25 → 1.7.26

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.
Files changed (2) hide show
  1. package/package.json +16 -16
  2. package/smart-commit/cli.js +127 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itcase/config",
3
- "version": "1.7.25",
3
+ "version": "1.7.26",
4
4
  "author": "ITCase",
5
5
  "description": "ITCase Config",
6
6
  "engines": {
@@ -43,11 +43,11 @@
43
43
  "@commitlint/config-conventional": "^21.2.3",
44
44
  "@commitlint/cz-commitlint": "^21.2.3",
45
45
  "@lehoczky/postcss-fluid": "^1.0.3",
46
- "@lerna-lite/cli": "^5.6.2",
47
- "@lerna-lite/exec": "^5.6.2",
48
- "@lerna-lite/list": "^5.6.2",
49
- "@lerna-lite/run": "^5.6.2",
50
- "@lerna-lite/watch": "^5.6.2",
46
+ "@lerna-lite/cli": "^5.6.3",
47
+ "@lerna-lite/exec": "^5.6.3",
48
+ "@lerna-lite/list": "^5.6.3",
49
+ "@lerna-lite/run": "^5.6.3",
50
+ "@lerna-lite/watch": "^5.6.3",
51
51
  "@rollup/plugin-alias": "^6.0.0",
52
52
  "@rollup/plugin-babel": "^7.1.0",
53
53
  "@rollup/plugin-commonjs": "^29.0.3",
@@ -68,11 +68,11 @@
68
68
  "commitizen": "^4.3.2",
69
69
  "conventional-changelog-conventionalcommits": "9.3.1",
70
70
  "conventional-changelog-writer": "9.3.0",
71
- "cssnano": "^9.3.2",
72
- "cssnano-preset-default": "^9.3.2",
71
+ "cssnano": "^9.8.0",
72
+ "cssnano-preset-default": "^9.8.0",
73
73
  "inquirer": "^14.2.3",
74
74
  "log-update": "^8.0.0",
75
- "postcss": "^8.5.29",
75
+ "postcss": "^8.5.30",
76
76
  "postcss-aspect-ratio-polyfill": "^2.0.0",
77
77
  "postcss-clamp": "^4.1.0",
78
78
  "postcss-cli": "^12.0.0",
@@ -80,19 +80,19 @@
80
80
  "postcss-combine-duplicated-selectors": "^11.0.0",
81
81
  "postcss-convert-unit": "^0.1.3",
82
82
  "postcss-dark-theme-class": "^2.0.0",
83
- "postcss-discard-duplicates": "^9.0.5",
83
+ "postcss-discard-duplicates": "^9.0.7",
84
84
  "postcss-each": "^1.1.0",
85
85
  "postcss-easings": "^4.0.0",
86
86
  "postcss-extend-rule": "^4.0.0",
87
87
  "postcss-flexbugs-fixes": "^5.0.2",
88
88
  "postcss-hexrgba": "^2.1.0",
89
- "postcss-import": "^17.0.0",
89
+ "postcss-import": "^17.0.1",
90
90
  "postcss-import-ext-glob": "^2.1.1",
91
91
  "postcss-loader": "^8.2.1",
92
92
  "postcss-merge-at-rules": "^2.0.1",
93
93
  "postcss-mixins": "^12.1.2",
94
94
  "postcss-mq-extract": "^1.0.0",
95
- "postcss-nested": "^8.0.1",
95
+ "postcss-nested": "^9.0.0",
96
96
  "postcss-nested-ancestors": "^3.0.0",
97
97
  "postcss-normalize": "^13.0.1",
98
98
  "postcss-prepend-imports": "^1.0.1",
@@ -110,17 +110,17 @@
110
110
  "rollup-plugin-peer-deps-external": "^2.2.4",
111
111
  "rollup-preserve-directives": "^1.1.3",
112
112
  "semantic-release": "^25.0.9",
113
- "semantic-release-lerna": "^3.6.5"
113
+ "semantic-release-lerna": "^3.6.7"
114
114
  },
115
115
  "devDependencies": {
116
116
  "@itcase/agent-skills": "^1.0.13",
117
- "@itcase/lint": "^1.1.126",
117
+ "@itcase/lint": "^1.1.130",
118
118
  "eslint": "9.39.2",
119
119
  "husky": "^9.1.7",
120
120
  "inquirer": "^14.2.3",
121
121
  "lint-staged": "^17.6.0",
122
- "prettier": "^3.9.9",
123
- "storybook": "^10.6.1",
122
+ "prettier": "^3.9.10",
123
+ "storybook": "^10.6.2",
124
124
  "stylelint": "^17.16.0",
125
125
  "typescript": "^6.0.3"
126
126
  }
@@ -24,10 +24,18 @@ const CLI_RELATIVE = 'smart-commit/cli.js'
24
24
  // Cheap Cursor Models pool — commit messages do not need Auto / frontier models.
25
25
  const DEFAULT_MODEL = 'composer-2.5'
26
26
 
27
- const MAX_DIFF = 80_000
27
+ // A small change set still sends the patch. A large tree used to be many
28
+ // agent calls with up to 80k chars of diff each. Compact numstat keeps that
29
+ // to one call.
30
+ const MAX_DIFF = 20_000
31
+ const COMPACT_SUMMARY_MAX = 12_000
32
+ const SMALL_PLAN_FILES = 12
33
+ const PER_FILE_EXCERPT = 500
34
+ const EXCERPT_SKIP_LINES = 400
35
+ const EXCERPT_MAX_BYTES = 256 * 1024
28
36
  const GIT_MAX_BUFFER = 32 * 1024 * 1024
29
37
  const GIT_PATH_BATCH = 40
30
- const PLAN_FILE_BATCH = 40
38
+ const PLAN_FILE_BATCH = 250
31
39
  const ARG_SAFE = 100_000
32
40
  const LOCKFILE_NAMES = new Set([
33
41
  'package-lock.json',
@@ -608,8 +616,10 @@ function buildPrompt(scopes, types, files, cwd, type) {
608
616
 
609
617
  const sourceFiles = files.filter((file) => !isLockfile(file))
610
618
  const lockFiles = files.filter((file) => isLockfile(file))
619
+ const diffLimit =
620
+ sourceFiles.length > SMALL_PLAN_FILES ? COMPACT_SUMMARY_MAX : MAX_DIFF
611
621
  let diff = buildSourceDiff(cwd, sourceFiles)
612
- if (diff.length > MAX_DIFF) diff = `${diff.slice(0, MAX_DIFF)}\n…truncated`
622
+ if (diff.length > diffLimit) diff = `${diff.slice(0, diffLimit)}\n…truncated`
613
623
 
614
624
  return `Plan git commits. Do not edit files or run git commit.
615
625
  Return ONLY JSON: {"commits":[{"message":"type(scope): subject","files":["a.ts"]}]}
@@ -622,6 +632,7 @@ Prefer one commit when changes belong to the same feature.
622
632
  Split by scope. Each listed file in exactly one commit — including deleted files (D) and new/untracked files (??).
623
633
  Put each lockfile in the same commit as its sibling package.json. Do not make a lockfile-only commit.
624
634
  Do not add Co-authored-by or Made-with trailers.
635
+ Do not use tools or read files. The file list and diff below are the only source.
625
636
  ${type ? `Prefer type "${type}".\n` : ''}Files:
626
637
  ${files
627
638
  .map((file) => {
@@ -644,6 +655,11 @@ ${diff || '(none)'}
644
655
  }
645
656
 
646
657
  function buildSourceDiff(cwd, files) {
658
+ if (files.length > SMALL_PLAN_FILES) return buildCompactSourceDiff(cwd, files)
659
+ return buildFullSourceDiff(cwd, files, MAX_DIFF)
660
+ }
661
+
662
+ function buildFullSourceDiff(cwd, files, limit) {
647
663
  const tracked = []
648
664
  const untracked = []
649
665
  for (const file of files) {
@@ -654,13 +670,13 @@ function buildSourceDiff(cwd, files) {
654
670
  const parts = []
655
671
  let used = 0
656
672
  const pushLimited = (text) => {
657
- if (used >= MAX_DIFF) return false
673
+ if (used >= limit) return false
658
674
  // Empty batch (no changes / ENOBUFS skip) — keep scanning remaining files.
659
675
  if (!text) return true
660
- const room = MAX_DIFF - used
676
+ const room = limit - used
661
677
  if (text.length > room) {
662
678
  parts.push(`${text.slice(0, room)}\n…truncated`)
663
- used = MAX_DIFF
679
+ used = limit
664
680
  return false
665
681
  }
666
682
  parts.push(text)
@@ -669,17 +685,120 @@ function buildSourceDiff(cwd, files) {
669
685
  }
670
686
 
671
687
  forEachBatch(tracked, (batch) => {
672
- if (used >= MAX_DIFF) return false
688
+ if (used >= limit) return false
673
689
  return pushLimited(tryOut(cwd, ['diff', 'HEAD', '--', ...batch]))
674
690
  })
675
691
  for (const file of untracked) {
676
- if (used >= MAX_DIFF) break
692
+ if (used >= limit) break
677
693
  const piece = buildUntrackedDiff(cwd, file)
678
694
  if (!pushLimited(piece)) break
679
695
  }
680
696
  return parts.join('\n\n')
681
697
  }
682
698
 
699
+ function buildCompactSourceDiff(cwd, files) {
700
+ const tracked = []
701
+ const untracked = new Set()
702
+ for (const file of files) {
703
+ if (isUntracked(cwd, file)) untracked.add(file)
704
+ else tracked.push(file)
705
+ }
706
+
707
+ const statLines = []
708
+ forEachBatch(tracked, (batch) => {
709
+ const text = tryOut(cwd, ['diff', '--numstat', 'HEAD', '--', ...batch])
710
+ if (text) statLines.push(...text.split('\n').filter(Boolean))
711
+ })
712
+ for (const file of untracked) statLines.push(untrackedNumstatLine(cwd, file))
713
+
714
+ const weightByFile = new Map()
715
+ for (const line of statLines) {
716
+ const file = line.split('\t').slice(2).join('\t')
717
+ weightByFile.set(file, parseNumstatWeight(line))
718
+ }
719
+
720
+ const header = `numstat (added, removed, path):\n${statLines.join('\n')}`
721
+ if (header.length >= COMPACT_SUMMARY_MAX) {
722
+ return `${header.slice(0, COMPACT_SUMMARY_MAX)}\n…truncated`
723
+ }
724
+
725
+ const ranked = [...files].sort((left, right) => {
726
+ const leftWeight = weightByFile.get(left)
727
+ const rightWeight = weightByFile.get(right)
728
+ return (rightWeight ?? -1) - (leftWeight ?? -1)
729
+ })
730
+
731
+ const excerptHeader = '\n\nchanged lines:\n'
732
+ let room = COMPACT_SUMMARY_MAX - header.length - excerptHeader.length
733
+ const excerpts = []
734
+ for (const file of ranked) {
735
+ if (room < 40) break
736
+ const weight = weightByFile.get(file)
737
+ if (weight == null || weight > EXCERPT_SKIP_LINES) continue
738
+ if (weight === 0 && !untracked.has(file)) continue
739
+ const excerpt = changedLineExcerpt(
740
+ cwd,
741
+ file,
742
+ untracked.has(file),
743
+ Math.min(PER_FILE_EXCERPT, room),
744
+ )
745
+ if (!excerpt) continue
746
+ excerpts.push(excerpt)
747
+ room -= excerpt.length + 1
748
+ }
749
+
750
+ if (!excerpts.length) return header
751
+ return `${header}${excerptHeader}${excerpts.join('\n')}`
752
+ }
753
+
754
+ function parseNumstatWeight(line) {
755
+ const [added, removed] = line.split('\t')
756
+ if (added === '-' || removed === '-') return null
757
+ return (Number(added) || 0) + (Number(removed) || 0)
758
+ }
759
+
760
+ function untrackedNumstatLine(cwd, file) {
761
+ const text = readTextFileHead(cwd, file)
762
+ if (text == null) return `-\t-\t${file}`
763
+ const lines = text ? text.split('\n').length : 0
764
+ return `${lines}\t0\t${file}`
765
+ }
766
+
767
+ function readTextFileHead(cwd, file) {
768
+ const filePath = path.join(cwd, file)
769
+ let stat
770
+ try {
771
+ stat = fs.statSync(filePath)
772
+ } catch {
773
+ return null
774
+ }
775
+ if (!stat.isFile() || stat.size > EXCERPT_MAX_BYTES) return null
776
+ const buffer = fs.readFileSync(filePath)
777
+ if (buffer.includes(0)) return null
778
+ return buffer.toString('utf8')
779
+ }
780
+
781
+ function changedLineExcerpt(cwd, file, untracked, cap) {
782
+ if (untracked) {
783
+ const text = readTextFileHead(cwd, file)
784
+ if (!text) return ''
785
+ const body = text.length > cap ? `${text.slice(0, cap)}…` : text
786
+ return `${file}:\n${body}`
787
+ }
788
+
789
+ const raw = tryOut(cwd, ['diff', '-U0', '--no-color', 'HEAD', '--', file])
790
+ if (!raw) return ''
791
+ const lines = []
792
+ for (const line of raw.split('\n')) {
793
+ if (line.startsWith('+++') || line.startsWith('---')) continue
794
+ if (line.startsWith('+') || line.startsWith('-')) lines.push(line)
795
+ }
796
+ if (!lines.length) return ''
797
+ let body = lines.join('\n')
798
+ if (body.length > cap) body = `${body.slice(0, cap)}…`
799
+ return `${file}:\n${body}`
800
+ }
801
+
683
802
  function buildUntrackedDiff(cwd, file) {
684
803
  const filePath = path.join(cwd, file)
685
804
  if (!fs.existsSync(filePath)) return ''