@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,41 +1,41 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const crypto = require('crypto');
4
- const { getConfigDir } = require('./paths');
5
- const logger = require('./logger');
6
-
7
- const getIdentityPath = () => path.join(getConfigDir(), 'identity.json');
8
-
9
- /**
10
- * Get or create the anonymous user identity
11
- * @returns {Promise<{id: string, created: number}>} Identity object
12
- */
13
- async function getIdentity() {
14
- try {
15
- const identityPath = getIdentityPath();
16
-
17
- if (await fs.pathExists(identityPath)) {
18
- return await fs.readJson(identityPath);
19
- }
20
-
21
- // Create new identity
22
- const identity = {
23
- id: crypto.randomUUID(),
24
- created: Date.now()
25
- };
26
-
27
- await fs.ensureDir(getConfigDir());
28
- await fs.writeJson(identityPath, identity, { spaces: 2 });
29
- logger.debug(`Created new anonymous identity: ${identity.id}`);
30
-
31
- return identity;
32
- } catch (error) {
33
- logger.error(`Failed to manage identity: ${error.message}`);
34
- // Fallback to memory-only ID if filesystem fails
35
- return { id: crypto.randomUUID(), created: Date.now() };
36
- }
37
- }
38
-
39
- module.exports = {
40
- getIdentity
41
- };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const { getConfigDir } = require('./paths');
5
+ const logger = require('./logger');
6
+
7
+ const getIdentityPath = () => path.join(getConfigDir(), 'identity.json');
8
+
9
+ /**
10
+ * Get or create the anonymous user identity
11
+ * @returns {Promise<{id: string, created: number}>} Identity object
12
+ */
13
+ async function getIdentity() {
14
+ try {
15
+ const identityPath = getIdentityPath();
16
+
17
+ if (await fs.pathExists(identityPath)) {
18
+ return await fs.readJson(identityPath);
19
+ }
20
+
21
+ // Create new identity
22
+ const identity = {
23
+ id: crypto.randomUUID(),
24
+ created: Date.now()
25
+ };
26
+
27
+ await fs.ensureDir(getConfigDir());
28
+ await fs.writeJson(identityPath, identity, { spaces: 2 });
29
+ logger.debug(`Created new anonymous identity: ${identity.id}`);
30
+
31
+ return identity;
32
+ } catch (error) {
33
+ logger.error(`Failed to manage identity: ${error.message}`);
34
+ // Fallback to memory-only ID if filesystem fails
35
+ return { id: crypto.randomUUID(), created: Date.now() };
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ getIdentity
41
+ };
@@ -1,68 +1,86 @@
1
- /**
2
- * Simple logger utility for consistent CLI output
3
- * Built by Praise Masunga (PraiseTechzw)
4
- */
5
-
6
- const logger = {
7
- colors: {
8
- cyan: (text) => `\x1b[36m${text}\x1b[0m`,
9
- green: (text) => `\x1b[32m${text}\x1b[0m`,
10
- yellow: (text) => `\x1b[33m${text}\x1b[0m`,
11
- red: (text) => `\x1b[31m${text}\x1b[0m`,
12
- blue: (text) => `\x1b[34m${text}\x1b[0m`,
13
- bold: (text) => `\x1b[1m${text}\x1b[0m`
14
- },
15
-
16
- /**
17
- * Log informational message
18
- * @param {string} message - Message to log
19
- */
20
- info: (message) => {
21
- console.log(`${logger.colors.cyan('ℹ️')} ${message}`);
22
- },
23
-
24
- /**
25
- * Log debug message (only visible if DEBUG env var is set)
26
- * @param {string} message - Message to log
27
- */
28
- debug: (message) => {
29
- if (process.env.DEBUG) {
30
- console.log(`${logger.colors.blue('🔍')} ${message}`);
31
- }
32
- },
33
-
34
- /**
35
- * Log success message
36
- * @param {string} message - Message to log
37
- */
38
- success: (message) => {
39
- console.log(`${logger.colors.green('')} ${message}`);
40
- },
41
-
42
- /**
43
- * Log warning message
44
- * @param {string} message - Message to log
45
- */
46
- warn: (message) => {
47
- console.warn(`${logger.colors.yellow('⚠️')} ${message}`);
48
- },
49
-
50
- /**
51
- * Log error message
52
- * @param {string} message - Message to log
53
- */
54
- error: (message) => {
55
- console.error(`${logger.colors.red('❌')} ${message}`);
56
- },
57
-
58
- /**
59
- * Log section header
60
- * @param {string} title - Section title
61
- */
62
- section: (title) => {
63
- console.log(`\n${logger.colors.bold(logger.colors.cyan(title))}`);
64
- console.log(logger.colors.cyan('─'.repeat(50)));
65
- },
66
- };
67
-
68
- module.exports = logger;
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const LOG_FILENAME = '.autopilot.log';
5
+ const MAX_LOG_SIZE = 500 * 1024; // 500KB
6
+ const KEEP_LINES = 200;
7
+
8
+ let targetPath = null;
9
+
10
+ const logger = {
11
+ colors: {
12
+ cyan: (text) => `\x1b[36m${text}\x1b[0m`,
13
+ green: (text) => `\x1b[32m${text}\x1b[0m`,
14
+ yellow: (text) => `\x1b[33m${text}\x1b[0m`,
15
+ red: (text) => `\x1b[31m${text}\x1b[0m`,
16
+ blue: (text) => `\x1b[34m${text}\x1b[0m`,
17
+ bold: (text) => `\x1b[1m${text}\x1b[0m`
18
+ },
19
+
20
+ setTargetPath: (dir) => {
21
+ targetPath = dir;
22
+ },
23
+
24
+ _writeToFile: (message) => {
25
+ if (!targetPath) return; // Only log to file if target path is set
26
+
27
+ try {
28
+ const logPath = path.join(targetPath, LOG_FILENAME);
29
+ const timestamp = new Date().toISOString();
30
+ const line = `[${timestamp}] ${message}\n`;
31
+
32
+ // Check size for rotation - only occasionally to avoid heavy I/O
33
+ // We'll trust append for speed and only rotate if we happen to check
34
+ if (Math.random() < 0.05 && fs.existsSync(logPath)) {
35
+ const stats = fs.statSync(logPath);
36
+ if (stats.size > MAX_LOG_SIZE) {
37
+ const content = fs.readFileSync(logPath, 'utf8');
38
+ const lines = content.split('\n');
39
+ const keptLines = lines.slice(-KEEP_LINES).join('\n');
40
+ fs.writeFileSync(logPath, keptLines + '\n');
41
+ }
42
+ }
43
+
44
+ fs.appendFileSync(logPath, line);
45
+ } catch (err) {
46
+ // Silent fail
47
+ }
48
+ },
49
+
50
+ info: (message) => {
51
+ console.log(`${logger.colors.cyan('ℹ️')} ${message}`);
52
+ logger._writeToFile(`INFO: ${message}`);
53
+ },
54
+
55
+ debug: (message) => {
56
+ if (process.env.DEBUG) {
57
+ console.log(`${logger.colors.blue('🔍')} ${message}`);
58
+ }
59
+ logger._writeToFile(`DEBUG: ${message}`);
60
+ },
61
+
62
+ success: (message) => {
63
+ console.log(`${logger.colors.green('✅')} ${message}`);
64
+ logger._writeToFile(`SUCCESS: ${message}`);
65
+ },
66
+
67
+ warn: (message) => {
68
+ console.warn(`${logger.colors.yellow('⚠️')} ${message}`);
69
+ logger._writeToFile(`WARN: ${message}`);
70
+ },
71
+
72
+ error: (message) => {
73
+ console.error(`${logger.colors.red('❌')} ${message}`);
74
+ logger._writeToFile(`ERROR: ${message}`);
75
+ },
76
+
77
+ section: (title) => {
78
+ const sep = '─'.repeat(50);
79
+ console.log(`\n${logger.colors.bold(logger.colors.cyan(title))}`);
80
+ console.log(logger.colors.cyan(sep));
81
+ logger._writeToFile(`SECTION: ${title}`);
82
+ },
83
+ };
84
+
85
+ module.exports = logger;
86
+
@@ -1,62 +1,62 @@
1
- /**
2
- * Path utilities for Autopilot
3
- * Built by Praise Masunga (PraiseTechzw)
4
- */
5
-
6
- const path = require('path');
7
- const os = require('os');
8
- const fs = require('fs-extra');
9
-
10
- /**
11
- * Get path to config file in repository
12
- * @param {string} repoPath - Repository path
13
- * @returns {string} Config file path
14
- */
15
- const getConfigPath = (repoPath) => {
16
- return path.join(repoPath, '.autopilotrc.json');
17
- };
18
-
19
- /**
20
- * Get path to ignore file in repository
21
- * @param {string} repoPath - Repository path
22
- * @returns {string} Ignore file path
23
- */
24
- const getIgnorePath = (repoPath) => {
25
- return path.join(repoPath, '.autopilotignore');
26
- };
27
-
28
- /**
29
- * Get path to .git directory
30
- * @param {string} repoPath - Repository path
31
- * @returns {string} .git directory path
32
- */
33
- const getGitPath = (repoPath) => {
34
- return path.join(repoPath, '.git');
35
- };
36
-
37
- /**
38
- * Get global config directory
39
- * @returns {string} Config directory path
40
- */
41
- const getConfigDir = () => {
42
- if (process.env.AUTOPILOT_CONFIG_DIR) {
43
- return process.env.AUTOPILOT_CONFIG_DIR;
44
- }
45
- return path.join(os.homedir(), '.autopilot');
46
- };
47
-
48
- /**
49
- * Ensure config directory exists
50
- * @returns {Promise<void>}
51
- */
52
- const ensureConfigDir = async () => {
53
- await fs.ensureDir(getConfigDir());
54
- };
55
-
56
- module.exports = {
57
- getConfigPath,
58
- getIgnorePath,
59
- getGitPath,
60
- getConfigDir,
61
- ensureConfigDir,
62
- };
1
+ /**
2
+ * Path utilities for Autopilot
3
+ * Built by Praise Masunga (PraiseTechzw)
4
+ */
5
+
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const fs = require('fs-extra');
9
+
10
+ /**
11
+ * Get path to config file in repository
12
+ * @param {string} repoPath - Repository path
13
+ * @returns {string} Config file path
14
+ */
15
+ const getConfigPath = (repoPath) => {
16
+ return path.join(repoPath, '.autopilotrc.json');
17
+ };
18
+
19
+ /**
20
+ * Get path to ignore file in repository
21
+ * @param {string} repoPath - Repository path
22
+ * @returns {string} Ignore file path
23
+ */
24
+ const getIgnorePath = (repoPath) => {
25
+ return path.join(repoPath, '.autopilotignore');
26
+ };
27
+
28
+ /**
29
+ * Get path to .git directory
30
+ * @param {string} repoPath - Repository path
31
+ * @returns {string} .git directory path
32
+ */
33
+ const getGitPath = (repoPath) => {
34
+ return path.join(repoPath, '.git');
35
+ };
36
+
37
+ /**
38
+ * Get global config directory
39
+ * @returns {string} Config directory path
40
+ */
41
+ const getConfigDir = () => {
42
+ if (process.env.AUTOPILOT_CONFIG_DIR) {
43
+ return process.env.AUTOPILOT_CONFIG_DIR;
44
+ }
45
+ return path.join(os.homedir(), '.autopilot');
46
+ };
47
+
48
+ /**
49
+ * Ensure config directory exists
50
+ * @returns {Promise<void>}
51
+ */
52
+ const ensureConfigDir = async () => {
53
+ await fs.ensureDir(getConfigDir());
54
+ };
55
+
56
+ module.exports = {
57
+ getConfigPath,
58
+ getIgnorePath,
59
+ getGitPath,
60
+ getConfigDir,
61
+ ensureConfigDir,
62
+ };
@@ -1,141 +1,141 @@
1
- /**
2
- * Process management utilities
3
- * Built by Praise Masunga (PraiseTechzw)
4
- */
5
-
6
- const fs = require('fs-extra');
7
- const path = require('path');
8
- const logger = require('./logger');
9
- const { ensureConfigDir } = require('./paths');
10
-
11
- /**
12
- * Get path to PID file for a repository or global
13
- * @param {string} [repoPath] - Repository path
14
- * @returns {string} PID file path
15
- */
16
- const getPidPath = (repoPath) => {
17
- if (repoPath) {
18
- return path.join(repoPath, '.autopilot.pid');
19
- }
20
- // Fallback to global pid if needed, though mostly used per-repo
21
- return path.join(require('os').homedir(), '.autopilot', 'autopilot.pid');
22
- };
23
-
24
- /**
25
- * Save current process PID to file
26
- * @param {string} repoPath - Repository path
27
- */
28
- const savePid = async (repoPath) => {
29
- try {
30
- const pidPath = getPidPath(repoPath);
31
- await fs.writeFile(pidPath, process.pid.toString());
32
- } catch (error) {
33
- logger.error(`Failed to save PID file: ${error.message}`);
34
- }
35
- };
36
-
37
- /**
38
- * Remove PID file
39
- * @param {string} repoPath - Repository path
40
- */
41
- const removePid = async (repoPath) => {
42
- try {
43
- const pidPath = getPidPath(repoPath);
44
- if (await fs.pathExists(pidPath)) {
45
- await fs.remove(pidPath);
46
- }
47
- } catch (error) {
48
- // Ignore errors during cleanup
49
- }
50
- };
51
-
52
- /**
53
- * Check if a process is running
54
- * @param {number} pid - Process ID
55
- * @returns {boolean} True if running
56
- */
57
- const isProcessRunning = (pid) => {
58
- try {
59
- process.kill(pid, 0);
60
- return true;
61
- } catch (e) {
62
- return false;
63
- }
64
- };
65
-
66
- /**
67
- * Read PID from file and check if running
68
- * @param {string} repoPath - Repository path
69
- * @returns {Promise<number|null>} PID if running, null otherwise
70
- */
71
- const getRunningPid = async (repoPath) => {
72
- try {
73
- const pidPath = getPidPath(repoPath);
74
- if (await fs.pathExists(pidPath)) {
75
- const pid = parseInt(await fs.readFile(pidPath, 'utf-8'), 10);
76
- if (isProcessRunning(pid)) {
77
- return pid;
78
- }
79
- // Stale PID file
80
- await removePid(repoPath);
81
- }
82
- return null;
83
- } catch (error) {
84
- return null;
85
- }
86
- };
87
-
88
- /**
89
- * Register process signal handlers for graceful shutdown
90
- * @param {Function} cleanupFn - Async cleanup function to run on exit
91
- */
92
- const registerProcessHandlers = (cleanupFn) => {
93
- let cleaningUp = false;
94
-
95
- const handleSignal = async (signal) => {
96
- if (cleaningUp) return;
97
- cleaningUp = true;
98
-
99
- logger.info(`Received ${signal}, shutting down...`);
100
-
101
- try {
102
- if (cleanupFn) {
103
- await cleanupFn();
104
- }
105
- } catch (error) {
106
- logger.error(`Error during cleanup: ${error.message}`);
107
- } finally {
108
- process.exit(0);
109
- }
110
- };
111
-
112
- process.on('SIGINT', () => handleSignal('SIGINT'));
113
- process.on('SIGTERM', () => handleSignal('SIGTERM'));
114
-
115
- // Handle uncaught errors to try to cleanup if possible
116
- process.on('uncaughtException', async (error) => {
117
- logger.error(`Uncaught Exception: ${error.message}`);
118
- logger.error(error.stack);
119
- if (!cleaningUp) {
120
- cleaningUp = true;
121
- try {
122
- if (cleanupFn) await cleanupFn();
123
- } catch (e) {
124
- // Ignore
125
- }
126
- process.exit(1);
127
- }
128
- });
129
-
130
- process.on('unhandledRejection', async (reason) => {
131
- logger.error(`Unhandled Rejection: ${reason}`);
132
- });
133
- };
134
-
135
- module.exports = {
136
- savePid,
137
- removePid,
138
- getRunningPid,
139
- isProcessRunning,
140
- registerProcessHandlers
141
- };
1
+ /**
2
+ * Process management utilities
3
+ * Built by Praise Masunga (PraiseTechzw)
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const logger = require('./logger');
9
+ const { ensureConfigDir } = require('./paths');
10
+
11
+ /**
12
+ * Get path to PID file for a repository or global
13
+ * @param {string} [repoPath] - Repository path
14
+ * @returns {string} PID file path
15
+ */
16
+ const getPidPath = (repoPath) => {
17
+ if (repoPath) {
18
+ return path.join(repoPath, '.autopilot.pid');
19
+ }
20
+ // Fallback to global pid if needed, though mostly used per-repo
21
+ return path.join(require('os').homedir(), '.autopilot', 'autopilot.pid');
22
+ };
23
+
24
+ /**
25
+ * Save current process PID to file
26
+ * @param {string} repoPath - Repository path
27
+ */
28
+ const savePid = async (repoPath) => {
29
+ try {
30
+ const pidPath = getPidPath(repoPath);
31
+ await fs.writeFile(pidPath, process.pid.toString());
32
+ } catch (error) {
33
+ logger.error(`Failed to save PID file: ${error.message}`);
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Remove PID file
39
+ * @param {string} repoPath - Repository path
40
+ */
41
+ const removePid = async (repoPath) => {
42
+ try {
43
+ const pidPath = getPidPath(repoPath);
44
+ if (await fs.pathExists(pidPath)) {
45
+ await fs.remove(pidPath);
46
+ }
47
+ } catch (error) {
48
+ // Ignore errors during cleanup
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Check if a process is running
54
+ * @param {number} pid - Process ID
55
+ * @returns {boolean} True if running
56
+ */
57
+ const isProcessRunning = (pid) => {
58
+ try {
59
+ process.kill(pid, 0);
60
+ return true;
61
+ } catch (e) {
62
+ return false;
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Read PID from file and check if running
68
+ * @param {string} repoPath - Repository path
69
+ * @returns {Promise<number|null>} PID if running, null otherwise
70
+ */
71
+ const getRunningPid = async (repoPath) => {
72
+ try {
73
+ const pidPath = getPidPath(repoPath);
74
+ if (await fs.pathExists(pidPath)) {
75
+ const pid = parseInt(await fs.readFile(pidPath, 'utf-8'), 10);
76
+ if (isProcessRunning(pid)) {
77
+ return pid;
78
+ }
79
+ // Stale PID file
80
+ await removePid(repoPath);
81
+ }
82
+ return null;
83
+ } catch (error) {
84
+ return null;
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Register process signal handlers for graceful shutdown
90
+ * @param {Function} cleanupFn - Async cleanup function to run on exit
91
+ */
92
+ const registerProcessHandlers = (cleanupFn) => {
93
+ let cleaningUp = false;
94
+
95
+ const handleSignal = async (signal) => {
96
+ if (cleaningUp) return;
97
+ cleaningUp = true;
98
+
99
+ logger.info(`Received ${signal}, shutting down...`);
100
+
101
+ try {
102
+ if (cleanupFn) {
103
+ await cleanupFn();
104
+ }
105
+ } catch (error) {
106
+ logger.error(`Error during cleanup: ${error.message}`);
107
+ } finally {
108
+ process.exit(0);
109
+ }
110
+ };
111
+
112
+ process.on('SIGINT', () => handleSignal('SIGINT'));
113
+ process.on('SIGTERM', () => handleSignal('SIGTERM'));
114
+
115
+ // Handle uncaught errors to try to cleanup if possible
116
+ process.on('uncaughtException', async (error) => {
117
+ logger.error(`Uncaught Exception: ${error.message}`);
118
+ logger.error(error.stack);
119
+ if (!cleaningUp) {
120
+ cleaningUp = true;
121
+ try {
122
+ if (cleanupFn) await cleanupFn();
123
+ } catch (e) {
124
+ // Ignore
125
+ }
126
+ process.exit(1);
127
+ }
128
+ });
129
+
130
+ process.on('unhandledRejection', async (reason) => {
131
+ logger.error(`Unhandled Rejection: ${reason}`);
132
+ });
133
+ };
134
+
135
+ module.exports = {
136
+ savePid,
137
+ removePid,
138
+ getRunningPid,
139
+ isProcessRunning,
140
+ registerProcessHandlers
141
+ };