bit-cli-ai 1.0.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.
@@ -0,0 +1,233 @@
1
+ import { execSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+
7
+ // Gitignore templates for different project types
8
+ const gitignoreTemplates = {
9
+ node: `# Dependencies
10
+ node_modules/
11
+ package-lock.json
12
+
13
+ # Environment
14
+ .env
15
+ .env.local
16
+ .env.*.local
17
+
18
+ # Build
19
+ dist/
20
+ build/
21
+ .next/
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # Logs
32
+ *.log
33
+ npm-debug.log*
34
+ `,
35
+ python: `# Byte-compiled
36
+ __pycache__/
37
+ *.py[cod]
38
+ *$py.class
39
+
40
+ # Virtual environments
41
+ .venv/
42
+ venv/
43
+ ENV/
44
+
45
+ # Environment
46
+ .env
47
+
48
+ # IDE
49
+ .vscode/
50
+ .idea/
51
+
52
+ # Distribution
53
+ dist/
54
+ build/
55
+ *.egg-info/
56
+ `,
57
+ rust: `# Build
58
+ /target/
59
+ Cargo.lock
60
+
61
+ # IDE
62
+ .vscode/
63
+ .idea/
64
+
65
+ # Environment
66
+ .env
67
+ `,
68
+ go: `# Binaries
69
+ *.exe
70
+ *.exe~
71
+ *.dll
72
+ *.so
73
+ *.dylib
74
+
75
+ # Build
76
+ /bin/
77
+ /pkg/
78
+
79
+ # IDE
80
+ .vscode/
81
+ .idea/
82
+
83
+ # Environment
84
+ .env
85
+ `,
86
+ generic: `# Environment
87
+ .env
88
+ .env.local
89
+
90
+ # IDE
91
+ .vscode/
92
+ .idea/
93
+
94
+ # OS
95
+ .DS_Store
96
+ Thumbs.db
97
+
98
+ # Logs
99
+ *.log
100
+ `
101
+ };
102
+
103
+ // Detect project type based on files present
104
+ function detectProjectType() {
105
+ const detectors = [
106
+ { file: 'package.json', type: 'node' },
107
+ { file: 'requirements.txt', type: 'python' },
108
+ { file: 'Pipfile', type: 'python' },
109
+ { file: 'pyproject.toml', type: 'python' },
110
+ { file: 'Cargo.toml', type: 'rust' },
111
+ { file: 'go.mod', type: 'go' },
112
+ ];
113
+
114
+ for (const detector of detectors) {
115
+ if (fs.existsSync(detector.file)) {
116
+ return detector.type;
117
+ }
118
+ }
119
+
120
+ return 'generic';
121
+ }
122
+
123
+ // Initialize .bit directory for Bit's intelligence layer
124
+ function initBitDirectory() {
125
+ const bitDir = '.bit';
126
+
127
+ if (!fs.existsSync(bitDir)) {
128
+ fs.mkdirSync(bitDir);
129
+ }
130
+
131
+ // Create metadata files
132
+ const metadata = {
133
+ version: '1.0.0',
134
+ createdAt: new Date().toISOString(),
135
+ ghostBranches: [],
136
+ hotZones: [],
137
+ symbolHistory: []
138
+ };
139
+
140
+ fs.writeFileSync(
141
+ path.join(bitDir, 'metadata.json'),
142
+ JSON.stringify(metadata, null, 2)
143
+ );
144
+
145
+ // Create symbol tracking database
146
+ fs.writeFileSync(
147
+ path.join(bitDir, 'symbols.json'),
148
+ JSON.stringify({ functions: {}, classes: {}, lastAnalyzed: null }, null, 2)
149
+ );
150
+
151
+ // Create hot zones tracking
152
+ fs.writeFileSync(
153
+ path.join(bitDir, 'hotzones.json'),
154
+ JSON.stringify({ zones: [], lastChecked: null }, null, 2)
155
+ );
156
+
157
+ // Add .bit to .gitignore if not already there
158
+ const gitignorePath = '.gitignore';
159
+ if (fs.existsSync(gitignorePath)) {
160
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
161
+ if (!content.includes('.bit/')) {
162
+ fs.appendFileSync(gitignorePath, '\n# Bit intelligence layer\n.bit/\n');
163
+ }
164
+ }
165
+
166
+ return bitDir;
167
+ }
168
+
169
+ export async function smartInit() {
170
+ const spinner = ora('Initializing repository...').start();
171
+
172
+ try {
173
+ // Check if already a git repo
174
+ const isGitRepo = fs.existsSync('.git');
175
+
176
+ if (!isGitRepo) {
177
+ // Initialize git repository
178
+ execSync('git init', { stdio: 'pipe' });
179
+ spinner.succeed('Git repository initialized');
180
+ } else {
181
+ spinner.info('Git repository already exists');
182
+ }
183
+
184
+ // Detect project type
185
+ spinner.start('Detecting project type...');
186
+ const projectType = detectProjectType();
187
+ spinner.succeed(`Detected project type: ${chalk.green(projectType)}`);
188
+
189
+ // Create or update .gitignore
190
+ spinner.start('Setting up .gitignore...');
191
+ const gitignoreContent = gitignoreTemplates[projectType];
192
+
193
+ if (fs.existsSync('.gitignore')) {
194
+ // Append to existing .gitignore
195
+ const existing = fs.readFileSync('.gitignore', 'utf-8');
196
+ const newEntries = gitignoreContent
197
+ .split('\n')
198
+ .filter(line => line.trim() && !line.startsWith('#') && !existing.includes(line.trim()));
199
+
200
+ if (newEntries.length > 0) {
201
+ fs.appendFileSync('.gitignore', '\n# Added by Bit\n' + newEntries.join('\n') + '\n');
202
+ spinner.succeed(`Updated .gitignore with ${newEntries.length} new entries`);
203
+ } else {
204
+ spinner.info('.gitignore already up to date');
205
+ }
206
+ } else {
207
+ fs.writeFileSync('.gitignore', gitignoreContent);
208
+ spinner.succeed('Created .gitignore');
209
+ }
210
+
211
+ // Initialize Bit directory
212
+ spinner.start('Initializing Bit intelligence layer...');
213
+ const bitDir = initBitDirectory();
214
+ spinner.succeed(`Bit intelligence layer created at ${chalk.cyan(bitDir)}/`);
215
+
216
+ // Summary
217
+ console.log('\n' + chalk.green('Bit initialized successfully!'));
218
+ console.log(chalk.gray('─'.repeat(40)));
219
+ console.log(` Project type: ${chalk.cyan(projectType)}`);
220
+ console.log(` Git repo: ${chalk.cyan('.git/')}`);
221
+ console.log(` Bit brain: ${chalk.cyan('.bit/')}`);
222
+ console.log(chalk.gray('─'.repeat(40)));
223
+ console.log('\nNext steps:');
224
+ console.log(` ${chalk.yellow('bit commit')} - AI-powered commits`);
225
+ console.log(` ${chalk.yellow('bit branch --ghost <name>')} - Create ghost branch`);
226
+ console.log(` ${chalk.yellow('bit merge --preview <branch>')} - Preview merge`);
227
+
228
+ } catch (error) {
229
+ spinner.fail('Initialization failed');
230
+ console.error(chalk.red(error.message));
231
+ process.exit(1);
232
+ }
233
+ }
@@ -0,0 +1,191 @@
1
+ import { execSync } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+
5
+ // Parse merge-tree output to find conflicts
6
+ function parseConflicts(output) {
7
+ const conflicts = [];
8
+ const lines = output.split('\n');
9
+
10
+ let currentFile = null;
11
+ let inConflict = false;
12
+
13
+ for (const line of lines) {
14
+ // Detect file markers
15
+ if (line.includes('CONFLICT')) {
16
+ const match = line.match(/CONFLICT.*?: (.+)/);
17
+ if (match) {
18
+ conflicts.push({
19
+ file: match[1].trim(),
20
+ type: 'content conflict'
21
+ });
22
+ }
23
+ }
24
+
25
+ // Detect merge conflicts in content
26
+ if (line.startsWith('<<<<<<< ')) {
27
+ inConflict = true;
28
+ }
29
+ if (line.startsWith('>>>>>>> ')) {
30
+ inConflict = false;
31
+ }
32
+ }
33
+
34
+ return conflicts;
35
+ }
36
+
37
+ // Get files that will be modified in merge
38
+ function getAffectedFiles(branch) {
39
+ try {
40
+ const currentBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
41
+
42
+ // Get diff between current and target branch
43
+ const diff = execSync(`git diff --name-only ${currentBranch}...${branch}`, { encoding: 'utf-8' });
44
+
45
+ return diff.split('\n').filter(f => f.trim());
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+
51
+ // Perform virtual merge preview
52
+ function virtualMerge(branch) {
53
+ const spinner = ora('Analyzing merge...').start();
54
+
55
+ try {
56
+ const currentBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
57
+
58
+ // Use git merge-tree for conflict detection
59
+ let mergeResult;
60
+ let hasConflicts = false;
61
+
62
+ try {
63
+ // Get merge base
64
+ const mergeBase = execSync(`git merge-base ${currentBranch} ${branch}`, { encoding: 'utf-8' }).trim();
65
+
66
+ // Perform merge-tree (virtual merge)
67
+ mergeResult = execSync(`git merge-tree ${mergeBase} ${currentBranch} ${branch}`, { encoding: 'utf-8' });
68
+
69
+ // Check for conflict markers
70
+ hasConflicts = mergeResult.includes('<<<<<<<') || mergeResult.includes('>>>>>>>');
71
+
72
+ } catch (error) {
73
+ // merge-tree might fail or indicate conflicts
74
+ mergeResult = error.stdout || '';
75
+ hasConflicts = true;
76
+ }
77
+
78
+ // Get affected files
79
+ const affectedFiles = getAffectedFiles(branch);
80
+
81
+ spinner.stop();
82
+
83
+ // Display results
84
+ console.log(chalk.cyan('\n--- Merge Preview ---\n'));
85
+ console.log(`Current branch: ${chalk.green(currentBranch)}`);
86
+ console.log(`Target branch: ${chalk.yellow(branch)}`);
87
+ console.log(chalk.gray('─'.repeat(50)));
88
+
89
+ // Show affected files
90
+ console.log(chalk.yellow(`\nFiles to be modified (${affectedFiles.length}):`));
91
+ if (affectedFiles.length === 0) {
92
+ console.log(chalk.gray(' No files will be modified'));
93
+ } else {
94
+ affectedFiles.forEach(file => {
95
+ console.log(chalk.gray(` ${file}`));
96
+ });
97
+ }
98
+
99
+ // Show conflict analysis
100
+ console.log(chalk.gray('\n' + '─'.repeat(50)));
101
+
102
+ if (hasConflicts) {
103
+ console.log(chalk.red('\nPotential Conflicts Detected:'));
104
+
105
+ const conflicts = parseConflicts(mergeResult);
106
+ if (conflicts.length > 0) {
107
+ conflicts.forEach(c => {
108
+ console.log(chalk.red(` - ${c.file}: ${c.type}`));
109
+ });
110
+ } else {
111
+ console.log(chalk.yellow(' Content conflicts detected in merge'));
112
+ console.log(chalk.gray(' Review changes carefully before merging'));
113
+ }
114
+
115
+ console.log(chalk.yellow('\nRecommendation: Review conflicts before merging'));
116
+ console.log(chalk.gray(' To proceed: git merge ' + branch));
117
+
118
+ } else {
119
+ console.log(chalk.green('\nNo conflicts detected!'));
120
+ console.log(chalk.green('This merge should be clean.'));
121
+ console.log(chalk.gray('\n To merge: bit merge ' + branch));
122
+ }
123
+
124
+ console.log('');
125
+
126
+ return { hasConflicts, affectedFiles };
127
+
128
+ } catch (error) {
129
+ spinner.fail('Merge preview failed');
130
+ console.error(chalk.red(error.message));
131
+ return { hasConflicts: true, affectedFiles: [] };
132
+ }
133
+ }
134
+
135
+ // Perform actual merge
136
+ function performMerge(branch) {
137
+ const spinner = ora(`Merging ${branch}...`).start();
138
+
139
+ try {
140
+ execSync(`git merge ${branch}`, { stdio: 'pipe' });
141
+ spinner.succeed(chalk.green(`Successfully merged ${branch}`));
142
+ } catch (error) {
143
+ spinner.fail('Merge failed');
144
+
145
+ // Check if it's a conflict
146
+ const status = execSync('git status', { encoding: 'utf-8' });
147
+
148
+ if (status.includes('Unmerged')) {
149
+ console.log(chalk.yellow('\nMerge conflicts detected. Resolve them manually:'));
150
+ console.log(chalk.gray(' 1. Edit conflicting files'));
151
+ console.log(chalk.gray(' 2. git add <resolved-files>'));
152
+ console.log(chalk.gray(' 3. git commit'));
153
+ } else {
154
+ console.error(chalk.red(error.message));
155
+ }
156
+ }
157
+ }
158
+
159
+ export function mergePreview(branch, options) {
160
+ // Check if branch exists
161
+ try {
162
+ execSync(`git rev-parse --verify ${branch}`, { stdio: 'pipe' });
163
+ } catch {
164
+ // Try with ghost prefix
165
+ try {
166
+ execSync(`git rev-parse --verify ghost/${branch}`, { stdio: 'pipe' });
167
+ branch = `ghost/${branch}`;
168
+ } catch {
169
+ console.error(chalk.red(`Branch not found: ${branch}`));
170
+ console.log(chalk.gray('\nAvailable branches:'));
171
+ execSync('git branch', { stdio: 'inherit' });
172
+ return;
173
+ }
174
+ }
175
+
176
+ if (options.preview) {
177
+ // Just preview, don't merge
178
+ virtualMerge(branch);
179
+ } else {
180
+ // First show preview, then merge
181
+ const { hasConflicts } = virtualMerge(branch);
182
+
183
+ if (!hasConflicts) {
184
+ console.log(chalk.gray('Proceeding with merge...\n'));
185
+ performMerge(branch);
186
+ } else {
187
+ console.log(chalk.yellow('Use --preview to see details without merging'));
188
+ console.log(chalk.gray('Or use: git merge ' + branch + ' to proceed anyway'));
189
+ }
190
+ }
191
+ }
package/src/index.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { execSync } from 'child_process';
5
+ import chalk from 'chalk';
6
+
7
+ import { smartInit } from './commands/init.js';
8
+ import { aiCommit } from './commands/commit.js';
9
+ import { ghostBranch, listBranches, checkoutGhost } from './commands/branch.js';
10
+ import { mergePreview } from './commands/merge.js';
11
+ import { checkHotZones } from './commands/hotzone.js';
12
+ import { analyzeSymbols } from './commands/analyze.js';
13
+
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('bit')
18
+ .description(chalk.cyan('Bit — Git with a brain'))
19
+ .version('1.0.0');
20
+
21
+ // ============ SMART INIT ============
22
+ program
23
+ .command('init')
24
+ .description('Initialize repo with smart .gitignore and .bit directory')
25
+ .action(smartInit);
26
+
27
+ // ============ AI COMMIT ============
28
+ program
29
+ .command('commit')
30
+ .description('AI-powered commit message generation')
31
+ .option('-m, --message <msg>', 'Manual commit message (skip AI)')
32
+ .action(aiCommit);
33
+
34
+ // ============ GHOST BRANCHES ============
35
+ program
36
+ .command('branch')
37
+ .description('List all branches including ghost branches')
38
+ .option('--ghost <name>', 'Create a ghost branch')
39
+ .option('--list-ghost', 'List only ghost branches')
40
+ .action(ghostBranch);
41
+
42
+ program
43
+ .command('checkout')
44
+ .description('Checkout a branch')
45
+ .argument('<branch>', 'Branch name to checkout')
46
+ .option('--ghost', 'Checkout a ghost branch')
47
+ .action(checkoutGhost);
48
+
49
+ // ============ MERGE PREVIEW ============
50
+ program
51
+ .command('merge')
52
+ .description('Merge branches with optional preview')
53
+ .argument('<branch>', 'Branch to merge')
54
+ .option('--preview', 'Preview merge conflicts without merging')
55
+ .action(mergePreview);
56
+
57
+ // ============ HOT ZONE DETECTION ============
58
+ program
59
+ .command('hotzone')
60
+ .description('Detect overlapping work with other developers')
61
+ .action(checkHotZones);
62
+
63
+ // ============ SYMBOL ANALYSIS ============
64
+ program
65
+ .command('analyze')
66
+ .description('Analyze code changes at function/symbol level')
67
+ .action(analyzeSymbols);
68
+
69
+ // ============ STATUS WITH INTELLIGENCE ============
70
+ program
71
+ .command('status')
72
+ .description('Enhanced git status with Bit intelligence')
73
+ .action(() => {
74
+ try {
75
+ console.log(chalk.cyan('\n--- Bit Status ---\n'));
76
+ execSync('git status', { stdio: 'inherit' });
77
+
78
+ // Add Bit intelligence
79
+ checkHotZones(true); // silent mode
80
+ } catch (error) {
81
+ console.error(chalk.red('Not a git repository'));
82
+ }
83
+ });
84
+
85
+ // ============ GIT PASSTHROUGH ============
86
+ // Any unknown command goes directly to git
87
+ program
88
+ .arguments('<command> [args...]')
89
+ .action((command, args) => {
90
+ try {
91
+ const gitCmd = `git ${command} ${args.join(' ')}`;
92
+ execSync(gitCmd, { stdio: 'inherit' });
93
+ } catch (error) {
94
+ // Git will print its own error message
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ // Handle no arguments
100
+ if (process.argv.length === 2) {
101
+ program.outputHelp();
102
+ }
103
+
104
+ program.parse();