@traisetech/autopilot 2.4.0 → 2.5.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +25 -9
  2. package/README.md +215 -106
  3. package/bin/autopilot.js +1 -1
  4. package/docs/CONFIGURATION.md +103 -103
  5. package/docs/DESIGN_PRINCIPLES.md +114 -114
  6. package/docs/TEAM-MODE.md +51 -51
  7. package/docs/TROUBLESHOOTING.md +21 -21
  8. package/package.json +75 -69
  9. package/src/commands/config.js +110 -110
  10. package/src/commands/dashboard.mjs +151 -151
  11. package/src/commands/doctor.js +127 -153
  12. package/src/commands/init.js +8 -9
  13. package/src/commands/insights.js +237 -237
  14. package/src/commands/leaderboard.js +116 -116
  15. package/src/commands/pause.js +18 -18
  16. package/src/commands/preset.js +121 -121
  17. package/src/commands/resume.js +17 -17
  18. package/src/commands/start.js +41 -41
  19. package/src/commands/status.js +73 -39
  20. package/src/commands/stop.js +58 -50
  21. package/src/commands/undo.js +84 -84
  22. package/src/config/defaults.js +23 -16
  23. package/src/config/ignore.js +14 -31
  24. package/src/config/loader.js +80 -80
  25. package/src/core/commit.js +45 -52
  26. package/src/core/commitMessageGenerator.js +130 -0
  27. package/src/core/configValidator.js +92 -0
  28. package/src/core/events.js +110 -110
  29. package/src/core/focus.js +2 -1
  30. package/src/core/gemini.js +15 -15
  31. package/src/core/git.js +29 -2
  32. package/src/core/history.js +69 -69
  33. package/src/core/notifier.js +61 -0
  34. package/src/core/retryQueue.js +152 -0
  35. package/src/core/safety.js +224 -210
  36. package/src/core/state.js +69 -71
  37. package/src/core/watcher.js +193 -66
  38. package/src/index.js +70 -70
  39. package/src/utils/banner.js +6 -6
  40. package/src/utils/crypto.js +18 -18
  41. package/src/utils/identity.js +41 -41
  42. package/src/utils/logger.js +86 -68
  43. package/src/utils/paths.js +62 -62
  44. package/src/utils/process.js +141 -141
@@ -1,237 +1,237 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const logger = require('../utils/logger');
4
- const git = require('../core/git');
5
- const { createObjectCsvWriter } = require('csv-writer');
6
-
7
- async function getGitStats(repoPath) {
8
- try {
9
- const { stdout } = await git.runGit(repoPath, [
10
- 'log',
11
- '--pretty=format:===C===%H|%an|%ad|%s|%b===E===',
12
- '--date=iso',
13
- '--numstat'
14
- ]);
15
-
16
- if (!stdout) return [];
17
-
18
- const commits = [];
19
- const rawCommits = stdout.split('===C===').filter(Boolean);
20
-
21
- for (const raw of rawCommits) {
22
- const [metadataPlusBody, ...statsParts] = raw.split('===E===');
23
- if (!metadataPlusBody) continue;
24
-
25
- const [hash, author, dateStr, subject, ...bodyParts] = metadataPlusBody.trim().split('|');
26
- const body = bodyParts.join('|'); // Rejoin in case body had pipes
27
-
28
- // Trust Verification: Only process autopilot commits
29
- if (!body.includes('Autopilot-Commit: true')) continue;
30
-
31
- const commit = {
32
- hash,
33
- author,
34
- date: new Date(dateStr),
35
- message: `${subject}\n${body}`.trim(),
36
- files: [],
37
- additions: 0,
38
- deletions: 0
39
- };
40
-
41
- const statsText = statsParts.join('===E===').trim();
42
- if (statsText) {
43
- const statLines = statsText.split('\n');
44
- for (const line of statLines) {
45
- const [add, del, file] = line.trim().split(/\s+/);
46
- if (file) {
47
- const additions = parseInt(add) || 0;
48
- const deletions = parseInt(del) || 0;
49
- commit.files.push({ file, additions, deletions });
50
- commit.additions += additions;
51
- commit.deletions += deletions;
52
- }
53
- }
54
- }
55
- commits.push(commit);
56
- }
57
-
58
- return commits;
59
- } catch (error) {
60
- logger.error(`Failed to analyze git history: ${error.message}`);
61
- return [];
62
- }
63
- }
64
-
65
- function calculateMetrics(commits) {
66
- const stats = {
67
- totalCommits: commits.length,
68
- totalFilesChanged: new Set(),
69
- totalAdditions: 0,
70
- totalDeletions: 0,
71
- commitsByDay: {},
72
- commitsByHour: {},
73
- authors: {},
74
- streak: { current: 0, max: 0 },
75
- topFiles: {},
76
- quality: {
77
- conventional: 0,
78
- issuesReferenced: 0,
79
- avgLength: 0,
80
- score: 0
81
- }
82
- };
83
-
84
- const dates = new Set();
85
- let totalMessageLength = 0;
86
-
87
- commits.forEach(c => {
88
- // Aggregates
89
- stats.totalAdditions += c.additions;
90
- stats.totalDeletions += c.deletions;
91
- c.files.forEach(f => {
92
- stats.totalFilesChanged.add(f.file);
93
- stats.topFiles[f.file] = (stats.topFiles[f.file] || 0) + 1;
94
- });
95
-
96
- // Time analysis
97
- const dateStr = c.date.toISOString().split('T')[0];
98
- const hour = c.date.getHours();
99
-
100
- stats.commitsByDay[dateStr] = (stats.commitsByDay[dateStr] || 0) + 1;
101
- stats.commitsByHour[hour] = (stats.commitsByHour[hour] || 0) + 1;
102
- dates.add(dateStr);
103
-
104
- // Quality
105
- const conventionalRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+/;
106
- if (conventionalRegex.test(c.message)) stats.quality.conventional++;
107
- if (/#\d+/.test(c.message)) stats.quality.issuesReferenced++;
108
- totalMessageLength += c.message.length;
109
- });
110
-
111
- // Calculate Averages
112
- stats.totalFilesCount = stats.totalFilesChanged.size;
113
- stats.quality.avgLength = commits.length ? Math.round(totalMessageLength / commits.length) : 0;
114
-
115
- // Calculate Score (0-100)
116
- // 40% Conventional, 30% Message Length (>30 chars), 30% Consistency
117
- const convScore = commits.length ? (stats.quality.conventional / commits.length) * 40 : 0;
118
- const lenScore = Math.min(stats.quality.avgLength / 50, 1) * 30; // Cap at 50 chars
119
- const consistencyScore = 30; // Placeholder for now
120
- stats.quality.score = Math.round(convScore + lenScore + consistencyScore);
121
-
122
- // Streak Calculation
123
- const sortedDates = Array.from(dates).sort();
124
- let currentStreak = 0;
125
- let maxStreak = 0;
126
- let lastDate = null;
127
-
128
- sortedDates.forEach(date => {
129
- const d = new Date(date);
130
- if (!lastDate) {
131
- currentStreak = 1;
132
- } else {
133
- const diffTime = Math.abs(d - lastDate);
134
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
135
- if (diffDays === 1) {
136
- currentStreak++;
137
- } else {
138
- maxStreak = Math.max(maxStreak, currentStreak);
139
- currentStreak = 1;
140
- }
141
- }
142
- lastDate = d;
143
- });
144
- stats.streak.max = Math.max(maxStreak, currentStreak);
145
-
146
- // Check if streak is active (last commit today or yesterday)
147
- const today = new Date().toISOString().split('T')[0];
148
- const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
149
- const lastCommitDate = sortedDates[sortedDates.length - 1];
150
-
151
- if (lastCommitDate === today || lastCommitDate === yesterday) {
152
- stats.streak.current = currentStreak;
153
- } else {
154
- stats.streak.current = 0;
155
- }
156
-
157
- return stats;
158
- }
159
-
160
- async function insights(options) {
161
- try {
162
- const repoPath = options.cwd || process.cwd();
163
- logger.info('Analyzing repository history...');
164
-
165
- const commits = await getGitStats(repoPath);
166
- if (commits.length === 0) {
167
- logger.warn('No git history found.');
168
- return;
169
- }
170
-
171
- const metrics = calculateMetrics(commits);
172
-
173
- if (options.format === 'json') {
174
- console.log(JSON.stringify(metrics, null, 2));
175
- return;
176
- }
177
-
178
- // Display Report
179
- console.log('');
180
- logger.section('📈 Project Analytics');
181
- console.log(`Total Commits: ${metrics.totalCommits}`);
182
- console.log(`Files Changed: ${metrics.totalFilesCount}`);
183
- console.log(`Lines Added: ${metrics.totalAdditions}`);
184
- console.log(`Lines Deleted: ${metrics.totalDeletions}`);
185
- console.log(`Current Streak: ${metrics.streak.current} days (Max: ${metrics.streak.max})`);
186
-
187
- // Find most productive hour
188
- const productiveHour = Object.entries(metrics.commitsByHour)
189
- .sort(([, a], [, b]) => b - a)[0];
190
- console.log(`Peak Productivity: ${productiveHour ? productiveHour[0] + ':00' : 'N/A'}`);
191
-
192
- console.log('');
193
- logger.section('💎 Quality Score: ' + metrics.quality.score + '/100');
194
- console.log(`Conventional Commits: ${Math.round((metrics.quality.conventional / metrics.totalCommits) * 100)}%`);
195
- console.log(`Avg Message Length: ${metrics.quality.avgLength} chars`);
196
- console.log(`Issues Referenced: ${metrics.quality.issuesReferenced}`);
197
-
198
- if (metrics.quality.score < 50) {
199
- logger.warn('Suggestion: Use conventional commits (feat:, fix:) to improve score.');
200
- } else if (metrics.quality.score > 80) {
201
- logger.success('Great job! High quality commit history.');
202
- }
203
-
204
- // CSV Export
205
- if (options.export === 'csv') {
206
- const csvPath = path.join(repoPath, 'autopilot-insights.csv');
207
- const csvWriter = createObjectCsvWriter({
208
- path: csvPath,
209
- header: [
210
- { id: 'hash', title: 'Hash' },
211
- { id: 'date', title: 'Date' },
212
- { id: 'author', title: 'Author' },
213
- { id: 'message', title: 'Message' },
214
- { id: 'additions', title: 'Additions' },
215
- { id: 'deletions', title: 'Deletions' }
216
- ]
217
- });
218
-
219
- const records = commits.map(c => ({
220
- hash: c.hash,
221
- date: c.date.toISOString(),
222
- author: c.author,
223
- message: c.message,
224
- additions: c.additions,
225
- deletions: c.deletions
226
- }));
227
-
228
- await csvWriter.writeRecords(records);
229
- logger.success(`Exported insights to ${csvPath}`);
230
- }
231
-
232
- } catch (error) {
233
- logger.error(`Failed to generate insights: ${error.message}`);
234
- }
235
- }
236
-
237
- module.exports = { insights, getGitStats, calculateMetrics };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const logger = require('../utils/logger');
4
+ const git = require('../core/git');
5
+ const { createObjectCsvWriter } = require('csv-writer');
6
+
7
+ async function getGitStats(repoPath) {
8
+ try {
9
+ const { stdout } = await git.runGit(repoPath, [
10
+ 'log',
11
+ '--pretty=format:===C===%H|%an|%ad|%s|%b===E===',
12
+ '--date=iso',
13
+ '--numstat'
14
+ ]);
15
+
16
+ if (!stdout) return [];
17
+
18
+ const commits = [];
19
+ const rawCommits = stdout.split('===C===').filter(Boolean);
20
+
21
+ for (const raw of rawCommits) {
22
+ const [metadataPlusBody, ...statsParts] = raw.split('===E===');
23
+ if (!metadataPlusBody) continue;
24
+
25
+ const [hash, author, dateStr, subject, ...bodyParts] = metadataPlusBody.trim().split('|');
26
+ const body = bodyParts.join('|'); // Rejoin in case body had pipes
27
+
28
+ // Trust Verification: Only process autopilot commits
29
+ if (!body.includes('Autopilot-Commit: true')) continue;
30
+
31
+ const commit = {
32
+ hash,
33
+ author,
34
+ date: new Date(dateStr),
35
+ message: `${subject}\n${body}`.trim(),
36
+ files: [],
37
+ additions: 0,
38
+ deletions: 0
39
+ };
40
+
41
+ const statsText = statsParts.join('===E===').trim();
42
+ if (statsText) {
43
+ const statLines = statsText.split('\n');
44
+ for (const line of statLines) {
45
+ const [add, del, file] = line.trim().split(/\s+/);
46
+ if (file) {
47
+ const additions = parseInt(add) || 0;
48
+ const deletions = parseInt(del) || 0;
49
+ commit.files.push({ file, additions, deletions });
50
+ commit.additions += additions;
51
+ commit.deletions += deletions;
52
+ }
53
+ }
54
+ }
55
+ commits.push(commit);
56
+ }
57
+
58
+ return commits;
59
+ } catch (error) {
60
+ logger.error(`Failed to analyze git history: ${error.message}`);
61
+ return [];
62
+ }
63
+ }
64
+
65
+ function calculateMetrics(commits) {
66
+ const stats = {
67
+ totalCommits: commits.length,
68
+ totalFilesChanged: new Set(),
69
+ totalAdditions: 0,
70
+ totalDeletions: 0,
71
+ commitsByDay: {},
72
+ commitsByHour: {},
73
+ authors: {},
74
+ streak: { current: 0, max: 0 },
75
+ topFiles: {},
76
+ quality: {
77
+ conventional: 0,
78
+ issuesReferenced: 0,
79
+ avgLength: 0,
80
+ score: 0
81
+ }
82
+ };
83
+
84
+ const dates = new Set();
85
+ let totalMessageLength = 0;
86
+
87
+ commits.forEach(c => {
88
+ // Aggregates
89
+ stats.totalAdditions += c.additions;
90
+ stats.totalDeletions += c.deletions;
91
+ c.files.forEach(f => {
92
+ stats.totalFilesChanged.add(f.file);
93
+ stats.topFiles[f.file] = (stats.topFiles[f.file] || 0) + 1;
94
+ });
95
+
96
+ // Time analysis
97
+ const dateStr = c.date.toISOString().split('T')[0];
98
+ const hour = c.date.getHours();
99
+
100
+ stats.commitsByDay[dateStr] = (stats.commitsByDay[dateStr] || 0) + 1;
101
+ stats.commitsByHour[hour] = (stats.commitsByHour[hour] || 0) + 1;
102
+ dates.add(dateStr);
103
+
104
+ // Quality
105
+ const conventionalRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+/;
106
+ if (conventionalRegex.test(c.message)) stats.quality.conventional++;
107
+ if (/#\d+/.test(c.message)) stats.quality.issuesReferenced++;
108
+ totalMessageLength += c.message.length;
109
+ });
110
+
111
+ // Calculate Averages
112
+ stats.totalFilesCount = stats.totalFilesChanged.size;
113
+ stats.quality.avgLength = commits.length ? Math.round(totalMessageLength / commits.length) : 0;
114
+
115
+ // Calculate Score (0-100)
116
+ // 40% Conventional, 30% Message Length (>30 chars), 30% Consistency
117
+ const convScore = commits.length ? (stats.quality.conventional / commits.length) * 40 : 0;
118
+ const lenScore = Math.min(stats.quality.avgLength / 50, 1) * 30; // Cap at 50 chars
119
+ const consistencyScore = 30; // Placeholder for now
120
+ stats.quality.score = Math.round(convScore + lenScore + consistencyScore);
121
+
122
+ // Streak Calculation
123
+ const sortedDates = Array.from(dates).sort();
124
+ let currentStreak = 0;
125
+ let maxStreak = 0;
126
+ let lastDate = null;
127
+
128
+ sortedDates.forEach(date => {
129
+ const d = new Date(date);
130
+ if (!lastDate) {
131
+ currentStreak = 1;
132
+ } else {
133
+ const diffTime = Math.abs(d - lastDate);
134
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
135
+ if (diffDays === 1) {
136
+ currentStreak++;
137
+ } else {
138
+ maxStreak = Math.max(maxStreak, currentStreak);
139
+ currentStreak = 1;
140
+ }
141
+ }
142
+ lastDate = d;
143
+ });
144
+ stats.streak.max = Math.max(maxStreak, currentStreak);
145
+
146
+ // Check if streak is active (last commit today or yesterday)
147
+ const today = new Date().toISOString().split('T')[0];
148
+ const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
149
+ const lastCommitDate = sortedDates[sortedDates.length - 1];
150
+
151
+ if (lastCommitDate === today || lastCommitDate === yesterday) {
152
+ stats.streak.current = currentStreak;
153
+ } else {
154
+ stats.streak.current = 0;
155
+ }
156
+
157
+ return stats;
158
+ }
159
+
160
+ async function insights(options) {
161
+ try {
162
+ const repoPath = options.cwd || process.cwd();
163
+ logger.info('Analyzing repository history...');
164
+
165
+ const commits = await getGitStats(repoPath);
166
+ if (commits.length === 0) {
167
+ logger.warn('No git history found.');
168
+ return;
169
+ }
170
+
171
+ const metrics = calculateMetrics(commits);
172
+
173
+ if (options.format === 'json') {
174
+ console.log(JSON.stringify(metrics, null, 2));
175
+ return;
176
+ }
177
+
178
+ // Display Report
179
+ console.log('');
180
+ logger.section('📈 Project Analytics');
181
+ console.log(`Total Commits: ${metrics.totalCommits}`);
182
+ console.log(`Files Changed: ${metrics.totalFilesCount}`);
183
+ console.log(`Lines Added: ${metrics.totalAdditions}`);
184
+ console.log(`Lines Deleted: ${metrics.totalDeletions}`);
185
+ console.log(`Current Streak: ${metrics.streak.current} days (Max: ${metrics.streak.max})`);
186
+
187
+ // Find most productive hour
188
+ const productiveHour = Object.entries(metrics.commitsByHour)
189
+ .sort(([, a], [, b]) => b - a)[0];
190
+ console.log(`Peak Productivity: ${productiveHour ? productiveHour[0] + ':00' : 'N/A'}`);
191
+
192
+ console.log('');
193
+ logger.section('💎 Quality Score: ' + metrics.quality.score + '/100');
194
+ console.log(`Conventional Commits: ${Math.round((metrics.quality.conventional / metrics.totalCommits) * 100)}%`);
195
+ console.log(`Avg Message Length: ${metrics.quality.avgLength} chars`);
196
+ console.log(`Issues Referenced: ${metrics.quality.issuesReferenced}`);
197
+
198
+ if (metrics.quality.score < 50) {
199
+ logger.warn('Suggestion: Use conventional commits (feat:, fix:) to improve score.');
200
+ } else if (metrics.quality.score > 80) {
201
+ logger.success('Great job! High quality commit history.');
202
+ }
203
+
204
+ // CSV Export
205
+ if (options.export === 'csv') {
206
+ const csvPath = path.join(repoPath, 'autopilot-insights.csv');
207
+ const csvWriter = createObjectCsvWriter({
208
+ path: csvPath,
209
+ header: [
210
+ { id: 'hash', title: 'Hash' },
211
+ { id: 'date', title: 'Date' },
212
+ { id: 'author', title: 'Author' },
213
+ { id: 'message', title: 'Message' },
214
+ { id: 'additions', title: 'Additions' },
215
+ { id: 'deletions', title: 'Deletions' }
216
+ ]
217
+ });
218
+
219
+ const records = commits.map(c => ({
220
+ hash: c.hash,
221
+ date: c.date.toISOString(),
222
+ author: c.author,
223
+ message: c.message,
224
+ additions: c.additions,
225
+ deletions: c.deletions
226
+ }));
227
+
228
+ await csvWriter.writeRecords(records);
229
+ logger.success(`Exported insights to ${csvPath}`);
230
+ }
231
+
232
+ } catch (error) {
233
+ logger.error(`Failed to generate insights: ${error.message}`);
234
+ }
235
+ }
236
+
237
+ module.exports = { insights, getGitStats, calculateMetrics };