sumulige-claude 1.1.0 → 1.1.1

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.
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pre-commit Quality Gate
4
+ *
5
+ * Runs quality checks before committing.
6
+ * Fails the commit if critical or error issues are found.
7
+ *
8
+ * Install: ln -s ../../.claude/hooks/pre-commit.cjs .git/hooks/pre-commit
9
+ * Or use: smc hooks install
10
+ */
11
+
12
+ const { execSync } = require('child_process');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
+
18
+ async function main() {
19
+ // Check if quality gate is enabled
20
+ const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
21
+ let config = { enabled: true, gates: { preCommit: true } };
22
+
23
+ if (fs.existsSync(configPath)) {
24
+ try {
25
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
26
+ } catch {}
27
+ }
28
+
29
+ if (!config.enabled || !config.gates?.preCommit) {
30
+ process.exit(0);
31
+ }
32
+
33
+ // Get staged files
34
+ let stagedFiles = [];
35
+ try {
36
+ const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
37
+ encoding: 'utf-8',
38
+ stdio: 'pipe'
39
+ });
40
+ stagedFiles = output.trim().split('\n').filter(Boolean);
41
+ } catch {
42
+ // Not in git repo or no staged files
43
+ process.exit(0);
44
+ }
45
+
46
+ if (stagedFiles.length === 0) {
47
+ process.exit(0);
48
+ }
49
+
50
+ // Filter to checkable files
51
+ const checkable = stagedFiles.filter(f => {
52
+ const ext = path.extname(f);
53
+ return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
54
+ });
55
+
56
+ if (checkable.length === 0) {
57
+ process.exit(0);
58
+ }
59
+
60
+ console.log(`Running pre-commit quality checks on ${checkable.length} file(s)...`);
61
+
62
+ // Run quality gate
63
+ const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
64
+ const gate = new QualityGate({
65
+ projectDir,
66
+ config
67
+ });
68
+
69
+ const result = await gate.check({
70
+ files: checkable.map(f => path.join(projectDir, f)),
71
+ severity: 'error' // Block on errors and critical only
72
+ });
73
+
74
+ if (!result.passed) {
75
+ console.error('\nPre-commit quality gate failed.');
76
+ console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
77
+ process.exit(1);
78
+ }
79
+
80
+ console.log('Pre-commit quality checks passed.\n');
81
+ }
82
+
83
+ main().catch(err => {
84
+ console.error('Pre-commit hook error:', err.message);
85
+ process.exit(1);
86
+ });
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pre-push Quality Gate
4
+ *
5
+ * Runs full quality checks before pushing.
6
+ * More comprehensive than pre-commit.
7
+ *
8
+ * Install: ln -s ../../.claude/hooks/pre-push.cjs .git/hooks/pre-push
9
+ * Or use: smc hooks install
10
+ */
11
+
12
+ const { execSync } = require('child_process');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
+
18
+ async function main() {
19
+ // Check if quality gate is enabled
20
+ const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
21
+ let config = { enabled: true, gates: { prePush: true } };
22
+
23
+ if (fs.existsSync(configPath)) {
24
+ try {
25
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
26
+ } catch {}
27
+ }
28
+
29
+ if (!config.enabled || !config.gates?.prePush) {
30
+ process.exit(0);
31
+ }
32
+
33
+ console.log('Running pre-push quality checks...\n');
34
+
35
+ // Get all files that will be pushed
36
+ let filesToCheck = [];
37
+ try {
38
+ // Get files changed since last push
39
+ const upstream = execSync('git rev-parse --abbrev-ref --symbolic-full-name @{u}', {
40
+ encoding: 'utf-8',
41
+ stdio: 'pipe'
42
+ }).trim() || 'origin/main';
43
+
44
+ const output = execSync(`git diff --name-only ${upstream}...HEAD`, {
45
+ encoding: 'utf-8',
46
+ stdio: 'pipe'
47
+ });
48
+ filesToCheck = output.trim().split('\n').filter(Boolean);
49
+ } catch {
50
+ // No upstream or other git error - check staged files only
51
+ try {
52
+ const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
53
+ encoding: 'utf-8',
54
+ stdio: 'pipe'
55
+ });
56
+ filesToCheck = output.trim().split('\n').filter(Boolean);
57
+ } catch {
58
+ // Not in git repo
59
+ process.exit(0);
60
+ }
61
+ }
62
+
63
+ if (filesToCheck.length === 0) {
64
+ process.exit(0);
65
+ }
66
+
67
+ // Filter to checkable files
68
+ const checkable = filesToCheck.filter(f => {
69
+ const ext = path.extname(f);
70
+ return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
71
+ });
72
+
73
+ if (checkable.length === 0) {
74
+ process.exit(0);
75
+ }
76
+
77
+ console.log(`Checking ${checkable.length} changed file(s)...\n`);
78
+
79
+ // Run quality gate
80
+ const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
81
+ const gate = new QualityGate({
82
+ projectDir,
83
+ config
84
+ });
85
+
86
+ const result = await gate.check({
87
+ files: checkable.map(f => path.join(projectDir, f)),
88
+ severity: 'warn' // Block on warnings too for push
89
+ });
90
+
91
+ if (!result.passed) {
92
+ console.error('\nPush blocked by quality gate.');
93
+ console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log('All quality checks passed. Proceeding with push.\n');
98
+ }
99
+
100
+ main().catch(err => {
101
+ console.error('Pre-push hook error:', err.message);
102
+ process.exit(1);
103
+ });
@@ -0,0 +1,61 @@
1
+ {
2
+ "enabled": true,
3
+ "severity": "warn",
4
+ "rules": [
5
+ {
6
+ "id": "line-count-limit",
7
+ "enabled": true,
8
+ "severity": "error",
9
+ "config": {
10
+ "maxLines": 800
11
+ }
12
+ },
13
+ {
14
+ "id": "file-size-limit",
15
+ "enabled": true,
16
+ "severity": "warn",
17
+ "config": {
18
+ "maxSize": 819200
19
+ }
20
+ },
21
+ {
22
+ "id": "no-empty-files",
23
+ "enabled": true,
24
+ "severity": "warn"
25
+ },
26
+ {
27
+ "id": "no-trailing-whitespace",
28
+ "enabled": true,
29
+ "severity": "warn"
30
+ },
31
+ {
32
+ "id": "todo-comments",
33
+ "enabled": true,
34
+ "severity": "info"
35
+ },
36
+ {
37
+ "id": "directory-depth",
38
+ "enabled": false,
39
+ "severity": "warn"
40
+ },
41
+ {
42
+ "id": "no-console-logs",
43
+ "enabled": false,
44
+ "severity": "warn"
45
+ },
46
+ {
47
+ "id": "function-length",
48
+ "enabled": false,
49
+ "severity": "warn"
50
+ }
51
+ ],
52
+ "gates": {
53
+ "preCommit": true,
54
+ "prePush": true,
55
+ "onToolUse": false
56
+ },
57
+ "reporting": {
58
+ "format": "console",
59
+ "outputFile": null
60
+ }
61
+ }
@@ -87,7 +87,8 @@
87
87
  "Bash(git init:*)",
88
88
  "Bash(git branch:*)",
89
89
  "Bash(gh repo create:*)",
90
- "Bash(npm view:*)"
90
+ "Bash(npm view:*)",
91
+ "Bash(npm update:*)"
91
92
  ]
92
93
  },
93
94
  "hooks": {
package/cli.js CHANGED
@@ -132,6 +132,34 @@ const COMMANDS = {
132
132
  help: 'Manage configuration',
133
133
  args: '[get|set] [key] [value]'
134
134
  },
135
+ 'config:validate': {
136
+ help: 'Validate configuration files',
137
+ args: ''
138
+ },
139
+ 'config:backup': {
140
+ help: 'Create configuration backup',
141
+ args: ''
142
+ },
143
+ 'config:rollback': {
144
+ help: 'Rollback to previous config',
145
+ args: '[version]'
146
+ },
147
+ 'config:diff': {
148
+ help: 'Show config diff',
149
+ args: '[file1] [file2]'
150
+ },
151
+ 'qg:check': {
152
+ help: 'Run quality gate check',
153
+ args: '[severity]'
154
+ },
155
+ 'qg:rules': {
156
+ help: 'List available quality rules',
157
+ args: ''
158
+ },
159
+ 'qg:init': {
160
+ help: 'Initialize quality gate config',
161
+ args: ''
162
+ },
135
163
  changelog: {
136
164
  help: 'Generate changelog from git commits',
137
165
  args: '[--from <tag>] [--to <tag>] [--json]'
@@ -0,0 +1,61 @@
1
+ {
2
+ "enabled": true,
3
+ "severity": "warn",
4
+ "rules": [
5
+ {
6
+ "id": "line-count-limit",
7
+ "enabled": true,
8
+ "severity": "error",
9
+ "config": {
10
+ "maxLines": 800
11
+ }
12
+ },
13
+ {
14
+ "id": "file-size-limit",
15
+ "enabled": true,
16
+ "severity": "warn",
17
+ "config": {
18
+ "maxSize": 819200
19
+ }
20
+ },
21
+ {
22
+ "id": "no-empty-files",
23
+ "enabled": true,
24
+ "severity": "warn"
25
+ },
26
+ {
27
+ "id": "no-trailing-whitespace",
28
+ "enabled": true,
29
+ "severity": "warn"
30
+ },
31
+ {
32
+ "id": "todo-comments",
33
+ "enabled": true,
34
+ "severity": "info"
35
+ },
36
+ {
37
+ "id": "directory-depth",
38
+ "enabled": false,
39
+ "severity": "warn"
40
+ },
41
+ {
42
+ "id": "no-console-logs",
43
+ "enabled": false,
44
+ "severity": "warn"
45
+ },
46
+ {
47
+ "id": "function-length",
48
+ "enabled": false,
49
+ "severity": "warn"
50
+ }
51
+ ],
52
+ "gates": {
53
+ "preCommit": true,
54
+ "prePush": true,
55
+ "onToolUse": false
56
+ },
57
+ "reporting": {
58
+ "format": "console",
59
+ "outputFile": null
60
+ }
61
+ }
package/lib/commands.js CHANGED
@@ -2321,6 +2321,214 @@ All notable changes to this project will be documented in this file.
2321
2321
 
2322
2322
  log('=====================================', 'gray');
2323
2323
  log('', 'gray');
2324
+ },
2325
+
2326
+ // ==========================================================================
2327
+ // Quality Gate Commands
2328
+ // ==========================================================================
2329
+
2330
+ 'qg:check': async (severity = 'warn') => {
2331
+ const { QualityGate } = require('./quality-gate');
2332
+ const gate = new QualityGate({ projectDir: process.cwd() });
2333
+ const result = await gate.check({ severity });
2334
+ process.exit(result.passed ? 0 : 1);
2335
+ },
2336
+
2337
+ 'qg:rules': () => {
2338
+ const registry = require('./quality-rules').registry;
2339
+ const rules = registry.getAll();
2340
+
2341
+ console.log('📋 Available Quality Rules');
2342
+ console.log('');
2343
+ console.log('Rules are checked when running quality gate:');
2344
+ console.log('');
2345
+
2346
+ const byCategory = {};
2347
+ for (const rule of rules) {
2348
+ if (!byCategory[rule.category]) byCategory[rule.category] = [];
2349
+ byCategory[rule.category].push(rule);
2350
+ }
2351
+
2352
+ for (const [category, rules] of Object.entries(byCategory)) {
2353
+ console.log(`${category.toUpperCase()}:`);
2354
+ for (const rule of rules) {
2355
+ const status = rule.enabled ? '✅' : '⊝';
2356
+ const sev = { info: 'I', warn: 'W', error: 'E', critical: 'X' }[rule.severity];
2357
+ console.log(` ${status} ${rule.id} [${sev}] - ${rule.name}`);
2358
+ }
2359
+ console.log('');
2360
+ }
2361
+ },
2362
+
2363
+ 'qg:init': () => {
2364
+ const projectDir = process.cwd();
2365
+ const configDir = path.join(projectDir, '.claude');
2366
+ const targetPath = path.join(configDir, 'quality-gate.json');
2367
+ const sourcePath = path.join(__dirname, '../config/quality-gate.json');
2368
+
2369
+ if (fs.existsSync(targetPath)) {
2370
+ console.log('⚠️ quality-gate.json already exists');
2371
+ return;
2372
+ }
2373
+
2374
+ ensureDir(configDir);
2375
+ fs.copyFileSync(sourcePath, targetPath);
2376
+ console.log('✅ Created .claude/quality-gate.json');
2377
+ console.log('');
2378
+ console.log('To enable Git hooks:');
2379
+ console.log(' ln -s .claude/hooks/pre-commit.cjs .git/hooks/pre-commit');
2380
+ console.log(' ln -s .claude/hooks/pre-push.cjs .git/hooks/pre-push');
2381
+ },
2382
+
2383
+ // ==========================================================================
2384
+ // Config Commands
2385
+ // ==========================================================================
2386
+
2387
+ 'config:validate': () => {
2388
+ const { ConfigValidator } = require('./config-validator');
2389
+ const validator = new ConfigValidator();
2390
+
2391
+ console.log('🔍 Validating configuration...');
2392
+ console.log('');
2393
+
2394
+ let hasErrors = false;
2395
+ let hasWarnings = false;
2396
+
2397
+ // Check global config
2398
+ const globalConfigPath = path.join(process.env.HOME, '.claude', 'config.json');
2399
+ console.log(`Global: ${globalConfigPath}`);
2400
+ const globalResult = validator.validateFile(globalConfigPath);
2401
+
2402
+ if (globalResult.valid) {
2403
+ console.log(' ✅ Valid');
2404
+ } else {
2405
+ if (globalResult.errors.length > 0) {
2406
+ hasErrors = true;
2407
+ console.log(` ❌ ${globalResult.errors.length} error(s)`);
2408
+ globalResult.errors.forEach(e => {
2409
+ console.log(` [${e.severity}] ${e.path}: ${e.message}`);
2410
+ });
2411
+ }
2412
+ if (globalResult.warnings.length > 0) {
2413
+ hasWarnings = true;
2414
+ console.log(` ⚠️ ${globalResult.warnings.length} warning(s)`);
2415
+ globalResult.warnings.forEach(e => {
2416
+ console.log(` [${e.severity}] ${e.path}: ${e.message}`);
2417
+ });
2418
+ }
2419
+ if (globalResult.errors.length === 0 && globalResult.warnings.length === 0) {
2420
+ console.log(' ❌ Validation failed');
2421
+ }
2422
+ }
2423
+ console.log('');
2424
+
2425
+ // Check project config if in project
2426
+ const projectDir = process.cwd();
2427
+ const projectConfigPath = path.join(projectDir, '.claude', 'settings.json');
2428
+ if (fs.existsSync(projectConfigPath)) {
2429
+ console.log(`Project: ${projectConfigPath}`);
2430
+ const projectResult = validator.validateFile(projectConfigPath, 'settings');
2431
+ if (projectResult.valid) {
2432
+ console.log(' ✅ Valid');
2433
+ } else {
2434
+ if (projectResult.errors.length > 0) {
2435
+ hasErrors = true;
2436
+ console.log(` ❌ ${projectResult.errors.length} error(s)`);
2437
+ projectResult.errors.forEach(e => {
2438
+ console.log(` [${e.severity}] ${e.path}: ${e.message}`);
2439
+ });
2440
+ }
2441
+ if (projectResult.warnings.length > 0) {
2442
+ hasWarnings = true;
2443
+ console.log(` ⚠️ ${projectResult.warnings.length} warning(s)`);
2444
+ projectResult.warnings.forEach(e => {
2445
+ console.log(` [${e.severity}] ${e.path}: ${e.message}`);
2446
+ });
2447
+ }
2448
+ if (projectResult.errors.length === 0 && projectResult.warnings.length === 0) {
2449
+ console.log(' ❌ Validation failed');
2450
+ }
2451
+ }
2452
+ }
2453
+ console.log('');
2454
+
2455
+ if (hasErrors) {
2456
+ console.log('❌ Configuration validation failed');
2457
+ process.exit(1);
2458
+ } else if (hasWarnings) {
2459
+ console.log('⚠️ Configuration has warnings (but is valid)');
2460
+ } else {
2461
+ console.log('✅ All configurations are valid');
2462
+ }
2463
+ },
2464
+
2465
+ 'config:backup': () => {
2466
+ const { ConfigManager } = require('./config-manager');
2467
+ const manager = new ConfigManager();
2468
+ const backupPath = manager._createBackup();
2469
+ console.log('✅ Config backed up to:', backupPath);
2470
+ },
2471
+
2472
+ 'config:rollback': (version) => {
2473
+ const { ConfigManager } = require('./config-manager');
2474
+ const manager = new ConfigManager();
2475
+
2476
+ const backups = manager.listBackups();
2477
+ if (backups.length === 0) {
2478
+ console.log('❌ No backups available');
2479
+ return;
2480
+ }
2481
+
2482
+ if (!version) {
2483
+ console.log('Available backups:');
2484
+ backups.forEach((b, i) => {
2485
+ console.log(` ${i + 1}. ${b.version} (${new Date(b.timestamp).toLocaleString()})`);
2486
+ });
2487
+ console.log('');
2488
+ console.log('Usage: smc config:rollback <version>');
2489
+ return;
2490
+ }
2491
+
2492
+ try {
2493
+ const result = manager.rollback(version);
2494
+ console.log('✅ Rolled back to:', result.restoredFrom);
2495
+ } catch (e) {
2496
+ console.log('❌', e.message);
2497
+ }
2498
+ },
2499
+
2500
+ 'config:diff': (file1, file2) => {
2501
+ const { ConfigManager } = require('./config-manager');
2502
+ const manager = new ConfigManager();
2503
+
2504
+ if (!file1) {
2505
+ const backups = manager.listBackups();
2506
+ if (backups.length === 0) {
2507
+ console.log('❌ No backups available');
2508
+ return;
2509
+ }
2510
+ file1 = path.join(manager.backupDir, backups[0].file);
2511
+ file2 = null; // Current config
2512
+ }
2513
+
2514
+ const changes = manager.diff(file1, file2);
2515
+ if (changes.length === 0) {
2516
+ console.log('✅ No differences found');
2517
+ return;
2518
+ }
2519
+
2520
+ console.log('📊 Config Diff:');
2521
+ console.log('');
2522
+ for (const change of changes) {
2523
+ const icon = { added: '+', removed: '-', changed: '~' }[change.type];
2524
+ console.log(` ${icon} ${change.path}`);
2525
+ if (change.type !== 'removed') {
2526
+ console.log(` from: ${JSON.stringify(change.from)}`);
2527
+ }
2528
+ if (change.type !== 'added') {
2529
+ console.log(` to: ${JSON.stringify(change.to)}`);
2530
+ }
2531
+ }
2324
2532
  }
2325
2533
  };
2326
2534