@traisetech/autopilot 0.1.4 → 0.1.6

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.
package/CHANGELOG.md CHANGED
@@ -25,11 +25,16 @@ This project follows Semantic Versioning (https://semver.org).
25
25
  ## [0.1.4] – 2026-02-01
26
26
 
27
27
  ### Fixed
28
+ - **Windows Compatibility**: Fixed critical issue where absolute paths on Windows caused ignore rules to fail.
29
+ - **Watcher Noise**: Fixed infinite commit loops caused by `.vscode/time-analytics.json` and self-logging.
28
30
  - Fixed a critical CLI crash where `autopilot start` failed due to miswired Commander action handlers.
29
31
  - Improved command registration to ensure all CLI commands are correctly bound and validated at runtime.
30
32
  - Prevented undefined command handlers from causing runtime exceptions.
31
33
 
32
34
  ### Added
35
+ - **Release Gates**: Added `npm run verify` and `prepublishOnly` hooks to prevent broken releases.
36
+ - **Integration Tests**: Added full end-to-end test suite using `node:test`.
37
+ - **Smart Init**: `autopilot init` now automatically adds `autopilot.log` to `.gitignore`.
33
38
  - Pre-publish verification pipeline to block publishing broken builds.
34
39
  - CLI smoke tests to ensure core commands (`init`, `start`, `status`, `doctor`) do not crash.
35
40
  - Test-only dry-run mode for watcher to allow safe automated testing.
package/README.md CHANGED
@@ -1,227 +1,2 @@
1
- # 🚀 Autopilot
2
1
 
3
- <div align="center">
4
-
5
- ![Autopilot Logo](https://img.shields.io/badge/Autopilot-blue?style=for-the-badge&logo=git&logoColor=white)
6
-
7
- **Intelligent Git automation that commits and pushes your code, so you can focus on building.**
8
-
9
- [![npm version](https://img.shields.io/npm/v/@traisetech/autopilot?style=flat-square&color=success)](https://www.npmjs.com/package/@traisetech/autopilot)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
11
- [![Node Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square)](https://nodejs.org)
12
- [![Downloads](https://img.shields.io/npm/dm/@traisetech/autopilot?style=flat-square&color=blue)](https://www.npmjs.com/package/@traisetech/autopilot)
13
- [![GitHub Stars](https://img.shields.io/github/stars/PraiseTechzw/autopilot?style=flat-square&color=gold)](https://github.com/PraiseTechzw/autopilot/stargazers)
14
- [![Build Status](https://img.shields.io/github/actions/workflow/status/PraiseTechzw/autopilot/ci.yml?style=flat-square)](https://github.com/PraiseTechzw/autopilot/actions)
15
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
16
-
17
- **Built by [Praise Masunga](https://github.com/PraiseTechzw) (PraiseTechzw)**
18
-
19
- [Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Configuration](#-configuration) • [Commands](#-commands)
20
-
21
- </div>
22
-
23
- ---
24
-
25
- ## 📖 Table of Contents
26
-
27
- - [Why Autopilot?](#-why-autopilot)
28
- - [Features](#-features)
29
- - [Installation](#-installation)
30
- - [Quick Start](#-quick-start)
31
- - [Commands](#-commands)
32
- - [Configuration](#-configuration)
33
- - [Safety Features](#-safety-features)
34
- - [Troubleshooting](#-troubleshooting)
35
- - [Contributing](#-contributing)
36
- - [License](#-license)
37
-
38
- ---
39
-
40
- ## 🎯 Why Autopilot?
41
-
42
- Autopilot is designed for developers who want to stay in the flow. It solves manual Git workflow fatigue by handling the repetitive cycle of staging, committing, and pushing changes, allowing you to focus entirely on writing code.
43
-
44
- <table>
45
- <tr>
46
- <td width="50%">
47
-
48
- ### ❌ Before Autopilot
49
-
50
- ```bash
51
- # Every. Single. Time.
52
- git add .
53
- git commit -m "update stuff"
54
- git push
55
-
56
- # Repeat 50+ times a day...
57
- # Lose focus on coding
58
- # Forget to commit
59
- # Inconsistent messages
60
- ```
61
-
62
- </td>
63
- <td width="50%">
64
-
65
- ### ✅ With Autopilot
66
-
67
- ```bash
68
- # One time setup
69
- autopilot init
70
- autopilot start
71
-
72
- # That's it!
73
- # Focus on coding
74
- # Auto-commits with smart messages
75
- # Never lose work again
76
- ```
77
-
78
- </td>
79
- </tr>
80
- </table>
81
-
82
- ---
83
-
84
- ## ✨ Features
85
-
86
- - **🧠 Smart Commits**: Generates professional conventional commit messages automatically.
87
- - **⚡ Watcher Engine**: Real-time file monitoring with smart debouncing using `chokidar`.
88
- - **🛡️ Safety First**: Blocks commits on protected branches and checks remote status.
89
- - **🔄 Automated Flow**: Fetches, stages, commits, and pushes (optional) automatically.
90
- - **⚙️ Zero Config**: Works out of the box, but fully configurable via `.autopilotrc.json`.
91
- - **🩺 Self-Healing**: Includes a `doctor` command to diagnose and fix issues.
92
-
93
- ---
94
-
95
- ## ⬇️ Installation
96
-
97
- Install Autopilot globally using npm:
98
-
99
- ```bash
100
- npm install -g @traisetech/autopilot
101
- ```
102
-
103
- ---
104
-
105
- ## 🚀 Quick Start
106
-
107
- ### 1. Initialize
108
- Navigate to your Git repository and initialize Autopilot:
109
-
110
- ```bash
111
- cd my-project
112
- autopilot init
113
- ```
114
-
115
- ### 2. Start Watching
116
- Start the background daemon to begin monitoring your files:
117
-
118
- ```bash
119
- autopilot start
120
- ```
121
-
122
- ### 3. Check Status
123
- Verify that Autopilot is running:
124
-
125
- ```bash
126
- autopilot status
127
- ```
128
-
129
- ### 4. Stop
130
- When you're done for the day:
131
-
132
- ```bash
133
- autopilot stop
134
- ```
135
-
136
- ---
137
-
138
- ## 💻 Commands
139
-
140
- | Command | Description |
141
- |---------|-------------|
142
- | `autopilot init` | Initializes configuration and ignore files in the current directory. |
143
- | `autopilot start` | Starts the background watcher daemon. |
144
- | `autopilot stop` | Stops the running watcher daemon. |
145
- | `autopilot status` | Shows the current status of the watcher process. |
146
- | `autopilot doctor` | Runs diagnostics to verify environment and configuration. |
147
- | `autopilot --help` | Displays help information. |
148
-
149
- ---
150
-
151
- ## ⚙️ Configuration
152
-
153
- Autopilot uses a `.autopilotrc.json` file for configuration. It is created automatically when you run `autopilot init`.
154
-
155
- ```json
156
- {
157
- "minInterval": 30,
158
- "autoPush": true,
159
- "blockedBranches": ["main", "production"],
160
- "requireChecks": false,
161
- "ignore": [
162
- "*.log",
163
- "temp/",
164
- "dist/",
165
- "node_modules"
166
- ]
167
- }
168
- ```
169
-
170
- | Option | Type | Default | Description |
171
- |--------|------|---------|-------------|
172
- | `minInterval` | `number` | `30` | Minimum seconds between commits. |
173
- | `autoPush` | `boolean` | `true` | Whether to push changes automatically after commit. |
174
- | `blockedBranches` | `array` | `[]` | List of branches to disable auto-commit on. |
175
- | `requireChecks` | `boolean` | `false` | Run custom checks before committing. |
176
- | `ignore` | `array` | `[]` | Additional glob patterns to ignore. |
177
-
178
- ---
179
-
180
- ## 🛡️ Safety Features
181
-
182
- Autopilot includes several safety mechanisms to prevent accidents:
183
-
184
- 1. **Branch Protection**: Will not run on branches listed in `blockedBranches`.
185
- 2. **Remote Sync**: Checks if local branch is behind remote before acting.
186
- 3. **Debouncing**: Waits for file changes to settle before committing.
187
- 4. **PID Management**: Ensures only one instance runs per repository.
188
-
189
- ---
190
-
191
- ## 🔧 Troubleshooting
192
-
193
- If you encounter issues, run the doctor command:
194
-
195
- ```bash
196
- autopilot doctor
197
- ```
198
-
199
- This will check for:
200
- - Git repository status
201
- - Configuration validity
202
- - Node.js version
203
- - Permissions
204
-
205
- ---
206
-
207
- ## 🤝 Contributing
208
-
209
- Contributions are welcome! Please feel free to submit a Pull Request.
210
-
211
- 1. Fork the repository
212
- 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
213
- 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
214
- 4. Push to the branch (`git push origin feature/AmazingFeature`)
215
- 5. Open a Pull Request
216
-
217
- ---
218
-
219
- ## 📜 License
220
-
221
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
222
-
223
- ---
224
-
225
- <div align="center">
226
- <b>Built by <a href="https://github.com/PraiseTechzw">Praise Masunga (PraiseTechzw)</a></b>
227
- </div>
2
+ <!-- Autopilot test run: 2026-02-01 -->
@@ -1,40 +1,21 @@
1
- # Troubleshooting
2
-
3
- **Built by Praise Masunga (PraiseTechzw)**
4
-
5
- ---
6
-
7
- ## Autopilot won’t start
8
-
9
- - Ensure you are inside a git repository
10
- - Run `autopilot doctor`
11
- - Remove stale PID if needed: delete `.autopilot.pid`
12
-
13
- ---
14
-
15
- ## It won’t commit
16
-
17
- Common reasons:
18
- - You are on a blocked branch (`blockBranches`)
19
- - Remote is ahead (pull first)
20
- - `requireChecks` is enabled and a check failed
21
- - No changes detected by git
22
-
23
- ---
24
-
25
- ## It won’t push
26
-
27
- - Verify `autoPush` is true
28
- - Check your `origin` remote and authentication
29
- - Ensure you have access to the remote
30
-
31
- ---
32
-
33
- ## Logs
34
-
35
- - `autopilot.log` is created in the repo root
36
- - `autopilot status` shows the last log line
37
-
38
- ---
39
-
40
- **Built by Praise Masunga (PraiseTechzw)**
1
+ # TROUBLESHOOTING
2
+
3
+ ## Common Issues
4
+
5
+ ### Watcher Loop / High CPU Usage (Windows)
6
+ If Autopilot seems to be constantly triggering or consuming high CPU on Windows, it is likely due to "noisy" files that change frequently but should be ignored.
7
+
8
+ **Common Culprits:**
9
+ - `.vscode/time-analytics.json`: Some VS Code extensions write to this file constantly.
10
+ - `autopilot.log`: If the watcher logs to a file that it is also watching, it creates a feedback loop.
11
+
12
+ **Solution:**
13
+ Autopilot v0.1.4+ includes enhanced Windows path handling to automatically ignore these files. If you still see issues:
14
+ 1. Ensure `.vscode/` and `*.log` are in your `.autopilotignore` file.
15
+ 2. Run `autopilot doctor` to verify your configuration.
16
+
17
+ ### "git.status is not a function"
18
+ This was a known issue in older versions. Please upgrade to the latest version.
19
+
20
+ ### Permissions Errors
21
+ Ensure you have write access to the repository and that no other process has locked the files (common on Windows with anti-virus software).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@traisetech/autopilot",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,7 +34,7 @@
34
34
  "dev": "node bin/autopilot.js",
35
35
  "test": "node --test",
36
36
  "lint": "node -c bin/autopilot.js && node -c src/index.js",
37
- "verify": "npm run test && npm run lint && node bin/autopilot.js --help",
37
+ "verify": "node bin/autopilot.js --help && npm run test && node bin/autopilot.js doctor",
38
38
  "prepublishOnly": "npm run verify",
39
39
  "release:patch": "npm run verify && npm version patch && git push --follow-tags && echo \"\n🚀 Ready to publish! Run: npm publish --access public\""
40
40
  },
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  const fs = require('fs-extra');
7
+ const path = require('path');
7
8
  const logger = require('../utils/logger');
8
9
  const { getConfigPath, getIgnorePath, getGitPath } = require('../utils/paths');
9
10
  const { DEFAULT_CONFIG, DEFAULT_IGNORE_PATTERNS } = require('../config/defaults');
@@ -54,6 +55,41 @@ async function createConfigFile(repoPath) {
54
55
  return true;
55
56
  }
56
57
 
58
+ /**
59
+ * Update .gitignore with Autopilot specific files
60
+ * @param {string} repoPath
61
+ */
62
+ async function updateGitIgnore(repoPath) {
63
+ const gitIgnorePath = path.join(repoPath, '.gitignore');
64
+ const toIgnore = ['autopilot.log', '.autopilot.pid'];
65
+ let content = '';
66
+
67
+ try {
68
+ if (await fs.pathExists(gitIgnorePath)) {
69
+ content = await fs.readFile(gitIgnorePath, 'utf-8');
70
+ }
71
+
72
+ const lines = content.split('\n').map(l => l.trim());
73
+ const newLines = [];
74
+ let added = false;
75
+
76
+ for (const item of toIgnore) {
77
+ if (!lines.includes(item)) {
78
+ newLines.push(item);
79
+ added = true;
80
+ }
81
+ }
82
+
83
+ if (added) {
84
+ const newContent = content + (content && !content.endsWith('\n') ? '\n' : '') + newLines.join('\n') + '\n';
85
+ await fs.writeFile(gitIgnorePath, newContent);
86
+ logger.success('Updated .gitignore');
87
+ }
88
+ } catch (error) {
89
+ logger.warn(`Could not update .gitignore: ${error.message}`);
90
+ }
91
+ }
92
+
57
93
  /**
58
94
  * Initialize Autopilot in current repository
59
95
  */
@@ -76,6 +112,7 @@ async function initRepo() {
76
112
  // Create files
77
113
  await createIgnoreFile(repoPath);
78
114
  await createConfigFile(repoPath);
115
+ await updateGitIgnore(repoPath);
79
116
 
80
117
  logger.section('✨ Initialization Complete');
81
118
  logger.info('Next steps:');
@@ -22,6 +22,8 @@ const DEFAULT_IGNORE_PATTERNS = [
22
22
  '.env.*',
23
23
  'coverage/',
24
24
  '*.log',
25
+ 'autopilot.log',
26
+ '.autopilot.pid',
25
27
  '.DS_Store',
26
28
  '.git/',
27
29
  '.idea/',
@@ -1,37 +1,136 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const logger = require('../utils/logger');
4
-
5
- const readIgnoreFile = async (repoPath) => {
6
- const ignorePath = path.join(repoPath, '.autopilot-ignore');
7
- try {
8
- if (await fs.pathExists(ignorePath)) {
9
- const content = await fs.readFile(ignorePath, 'utf-8');
10
- return content
11
- .split('\n')
12
- .map((line) => line.trim())
13
- .filter((line) => line && !line.startsWith('#'));
14
- }
15
- } catch (error) {
16
- logger.debug(`Error reading ignore file: ${error.message}`);
17
- }
18
- return [];
19
- };
20
-
21
- const createIgnoreFile = async (repoPath, patterns = []) => {
22
- const ignorePath = path.join(repoPath, '.autopilot-ignore');
23
- try {
24
- const content =
25
- `# Autopilot Ignore File\n# Add patterns to exclude from autopilot watching\n\n` +
26
- patterns.join('\n');
27
- await fs.writeFile(ignorePath, content);
28
- logger.success('Created .autopilot-ignore file');
29
- } catch (error) {
30
- logger.error(`Failed to create ignore file: ${error.message}`);
31
- }
32
- };
33
-
34
- module.exports = {
35
- readIgnoreFile,
36
- createIgnoreFile,
37
- };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const logger = require('../utils/logger');
4
+ const { getIgnorePath } = require('../utils/paths');
5
+
6
+ /**
7
+ * Standardize path to use forward slashes
8
+ * @param {string} p - Path to normalize
9
+ * @returns {string} Normalized path
10
+ */
11
+ const normalizePath = (p) => p.split(path.sep).join('/');
12
+
13
+ /**
14
+ * Read ignore file patterns
15
+ * @param {string} repoPath
16
+ * @returns {Promise<string[]>} Array of ignore patterns
17
+ */
18
+ const readIgnoreFile = async (repoPath) => {
19
+ const ignorePath = getIgnorePath(repoPath);
20
+ try {
21
+ if (await fs.pathExists(ignorePath)) {
22
+ const content = await fs.readFile(ignorePath, 'utf-8');
23
+ return content
24
+ .split('\n')
25
+ .map((line) => line.trim())
26
+ .filter((line) => line && !line.startsWith('#'));
27
+ }
28
+ } catch (error) {
29
+ logger.debug(`Error reading ignore file: ${error.message}`);
30
+ }
31
+ return [];
32
+ };
33
+
34
+ /**
35
+ * Create ignore file
36
+ */
37
+ const createIgnoreFile = async (repoPath, patterns = []) => {
38
+ const ignorePath = getIgnorePath(repoPath);
39
+ try {
40
+ const content =
41
+ `# Autopilot Ignore File\n# Add patterns to exclude from autopilot watching\n\n` +
42
+ patterns.join('\n');
43
+ await fs.writeFile(ignorePath, content);
44
+ logger.success('Created .autopilotignore file');
45
+ } catch (error) {
46
+ logger.error(`Failed to create ignore file: ${error.message}`);
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Create a filter function for Chokidar
52
+ * @param {string} repoPath - Root of the repository
53
+ * @param {string[]} userPatterns - Custom ignore patterns
54
+ * @returns {function} Filter function (path => boolean)
55
+ */
56
+ const createIgnoredFilter = (repoPath, userPatterns = []) => {
57
+ const normalizedRepoPath = normalizePath(repoPath);
58
+
59
+ // Always ignore these critical paths to prevent loops and noise
60
+ const criticalIgnores = [
61
+ '.git',
62
+ 'node_modules',
63
+ '.vscode',
64
+ '.idea',
65
+ 'dist',
66
+ 'build',
67
+ 'coverage',
68
+ '.next'
69
+ ];
70
+
71
+ const criticalFiles = [
72
+ 'autopilot.log',
73
+ '.autopilot.pid',
74
+ '.DS_Store'
75
+ ];
76
+
77
+ return (absolutePath) => {
78
+ // 1. Normalize paths
79
+ const normalizedAbs = normalizePath(absolutePath);
80
+
81
+ // 2. Get relative path
82
+ let relativePath = normalizedAbs;
83
+ if (normalizedAbs.startsWith(normalizedRepoPath)) {
84
+ relativePath = normalizedAbs.slice(normalizedRepoPath.length);
85
+ if (relativePath.startsWith('/')) {
86
+ relativePath = relativePath.slice(1);
87
+ }
88
+ }
89
+
90
+ // Handle root path case
91
+ if (!relativePath) return false;
92
+
93
+ // 3. Check critical directory prefixes
94
+ // We check if any path segment matches a critical ignore
95
+ const parts = relativePath.split('/');
96
+ for (const part of parts) {
97
+ if (criticalIgnores.includes(part)) return true;
98
+ }
99
+
100
+ // 4. Check file extensions and exact matches
101
+ if (relativePath.endsWith('.log')) return true;
102
+ if (criticalFiles.some(f => relativePath.endsWith(f))) return true;
103
+
104
+ // 5. Check user patterns (simple prefix/suffix matching for now)
105
+ // For robust glob support without adding dependencies, we rely on basic checks
106
+ // Most users use simple dir/ or *.ext patterns
107
+ for (const pattern of userPatterns) {
108
+ // Remove leading/trailing slashes for comparison
109
+ const cleanPattern = pattern.replace(/^\/+|\/+$/g, '');
110
+
111
+ // Directory match (e.g., "temp/")
112
+ if (pattern.endsWith('/') && (relativePath === cleanPattern || relativePath.startsWith(cleanPattern + '/'))) {
113
+ return true;
114
+ }
115
+
116
+ // Extension match (e.g., "*.tmp")
117
+ if (pattern.startsWith('*.') && relativePath.endsWith(pattern.slice(1))) {
118
+ return true;
119
+ }
120
+
121
+ // Exact match
122
+ if (relativePath === cleanPattern) {
123
+ return true;
124
+ }
125
+ }
126
+
127
+ return false;
128
+ };
129
+ };
130
+
131
+ module.exports = {
132
+ readIgnoreFile,
133
+ createIgnoreFile,
134
+ createIgnoredFilter,
135
+ normalizePath
136
+ };
@@ -6,13 +6,12 @@
6
6
  const fs = require('fs-extra');
7
7
  const path = require('path');
8
8
  const chokidar = require('chokidar');
9
- const { execa } = require('execa');
10
9
  const logger = require('../utils/logger');
11
10
  const git = require('./git');
12
11
  const { generateCommitMessage } = require('./commit');
13
- const { getIgnorePath } = require('../utils/paths');
14
12
  const { savePid, removePid, registerProcessHandlers } = require('../utils/process');
15
13
  const { loadConfig } = require('../config/loader');
14
+ const { readIgnoreFile, createIgnoredFilter, normalizePath } = require('../config/ignore');
16
15
 
17
16
  class Watcher {
18
17
  constructor(repoPath) {
@@ -27,6 +26,23 @@ class Watcher {
27
26
  this.ignorePatterns = [];
28
27
  }
29
28
 
29
+ logVerbose(message) {
30
+ // Helper to log debug messages to file/stdout if needed
31
+ // For now, we use logger.debug which writes to stdout if DEBUG is set
32
+ // We also want to ensure we don't create a loop by logging to the file we watch
33
+ // But since we ignore autopilot.log, it should be fine.
34
+ logger.debug(message);
35
+ // TODO: Append to log file if configured
36
+ }
37
+
38
+ async reloadConfig() {
39
+ this.config = await loadConfig(this.repoPath);
40
+ }
41
+
42
+ async reloadIgnore() {
43
+ this.ignorePatterns = await readIgnoreFile(this.repoPath);
44
+ }
45
+
30
46
  /**
31
47
  * Initialize and start the watcher
32
48
  */
@@ -41,7 +57,7 @@ class Watcher {
41
57
  await fs.ensureFile(this.logFilePath);
42
58
  await savePid(this.repoPath);
43
59
 
44
- this.logVerbose('Starting Autopilot watcher...');
60
+ logger.info('Starting Autopilot watcher...');
45
61
 
46
62
  // Load configuration
47
63
  await this.reloadConfig();
@@ -51,23 +67,16 @@ class Watcher {
51
67
  const currentBranch = await git.getBranch(this.repoPath);
52
68
  if (currentBranch && this.config.blockedBranches?.includes(currentBranch)) {
53
69
  logger.error(`Branch '${currentBranch}' is blocked in config. Stopping.`);
54
- this.logVerbose(`Blocked branch detected: ${currentBranch}`);
55
70
  await this.stop();
56
71
  return;
57
72
  }
58
73
 
59
- // Combine defaults + loaded patterns
60
- const finalIgnored = [
61
- ...this.ignorePatterns,
62
- /(^|[\/\\])\.git([\/\\]|$)/, // Regex for .git folder
63
- '**/autopilot.log',
64
- '**/.autopilot.pid',
65
- 'node_modules' // Sensible default
66
- ];
74
+ // Create robust ignore filter
75
+ const ignoredFilter = createIgnoredFilter(this.repoPath, this.ignorePatterns);
67
76
 
68
- // Start Chokidar
77
+ // Start Chokidar with function-based ignore
69
78
  this.watcher = chokidar.watch(this.repoPath, {
70
- ignored: finalIgnored,
79
+ ignored: ignoredFilter,
71
80
  ignoreInitial: true,
72
81
  persistent: true,
73
82
  awaitWriteFinish: {
@@ -93,17 +102,16 @@ class Watcher {
93
102
 
94
103
  // Test Mode Support
95
104
  if (process.env.AUTOPILOT_TEST_MODE) {
96
- logger.warn('TEST MODE: Running in dry-run mode for 3 seconds...');
105
+ logger.warn('TEST MODE: Running in dry-run mode for 8 seconds...');
97
106
  setTimeout(async () => {
98
107
  logger.info('TEST MODE: Auto-stopping watcher...');
99
108
  await this.stop();
100
109
  process.exit(0);
101
- }, 3000);
110
+ }, 8000);
102
111
  }
103
112
 
104
113
  } catch (error) {
105
114
  logger.error(`Failed to start watcher: ${error.message}`);
106
- this.logVerbose(`Start error: ${error.stack}`);
107
115
  await this.stop();
108
116
  }
109
117
  }
@@ -137,10 +145,15 @@ class Watcher {
137
145
  onFsEvent(type, filePath) {
138
146
  if (this.isProcessing) return;
139
147
 
140
- // Double check ignore (safety net)
141
- if (filePath.includes('.git') || filePath.endsWith('autopilot.log')) return;
148
+ // Normalize path relative to repo for logging and checks
149
+ const relativePath = normalizePath(path.relative(this.repoPath, filePath));
150
+
151
+ // Double check ignore (safety net) - although chokidar should catch most
152
+ if (relativePath.includes('.git/') || relativePath.endsWith('autopilot.log') || relativePath.includes('.vscode/')) {
153
+ return;
154
+ }
142
155
 
143
- this.logVerbose(`File event: ${type} ${filePath}`);
156
+ this.logVerbose(`File event: ${type} ${relativePath}`);
144
157
  this.scheduleProcess();
145
158
  }
146
159
 
@@ -154,11 +167,22 @@ class Watcher {
154
167
  clearTimeout(this.debounceTimer);
155
168
  }
156
169
 
170
+ logger.debug('Debounce fired. Waiting...');
171
+
157
172
  this.debounceTimer = setTimeout(() => {
158
173
  this.processChanges();
159
174
  }, debounceMs);
160
175
  }
161
176
 
177
+ handleError(error) {
178
+ logger.error(`Watcher error: ${error.message}`);
179
+ }
180
+
181
+ async runChecks() {
182
+ // Placeholder for custom checks
183
+ return true;
184
+ }
185
+
162
186
  /**
163
187
  * Main processing loop
164
188
  */
@@ -167,153 +191,84 @@ class Watcher {
167
191
  this.isProcessing = true;
168
192
 
169
193
  try {
194
+ logger.debug('Checking git status...');
195
+
170
196
  // 1. Min interval check
171
197
  const now = Date.now();
172
- const minInterval = (this.config?.minIntervalSeconds || 30) * 1000;
173
- if (now - this.lastCommitAt < minInterval) {
174
- this.logVerbose('Skipping: Minimum interval not met');
198
+ const minInterval = (this.config?.minSecondsBetweenCommits || 180) * 1000;
199
+ if (this.lastCommitAt > 0 && now - this.lastCommitAt < minInterval) {
200
+ logger.debug(`Skip commit: Minimum interval not met (${Math.round((minInterval - (now - this.lastCommitAt))/1000)}s remaining)`);
201
+ return;
202
+ }
203
+
204
+ // 2. Check if dirty
205
+ const statusObj = await git.getPorcelainStatus(this.repoPath);
206
+ const isDirty = statusObj.ok && statusObj.files.length > 0;
207
+ logger.debug(`Git dirty: ${isDirty}`);
208
+
209
+ if (!isDirty) {
175
210
  return;
176
211
  }
177
212
 
178
- // 2. Safety: Branch check
213
+ // 3. Safety: Branch check
179
214
  const branch = await git.getBranch(this.repoPath);
180
215
  if (this.config?.blockedBranches?.includes(branch)) {
181
- logger.warn(`Current branch '${branch}' is blocked. Skipping.`);
216
+ logger.warn(`Skip commit: Branch '${branch}' is blocked`);
182
217
  return;
183
218
  }
184
219
 
185
- // 3. Safety: Remote check (fetch -> behind?)
186
- this.logVerbose('Checking remote status...');
187
- const remoteStatus = await git.isRemoteAhead(this.repoPath);
188
- if (remoteStatus.behind) {
189
- logger.warn('Local branch is behind remote. Please pull changes.');
190
- this.logVerbose('Behind remote. Pausing auto-commit.');
191
- return;
220
+ // 4. Safety: Remote check (fetch -> behind?)
221
+ logger.debug('Checking remote status...');
222
+ // Note: isRemoteAhead might need network, timeout safely?
223
+ try {
224
+ const remoteStatus = await git.isRemoteAhead(this.repoPath);
225
+ if (remoteStatus.behind) {
226
+ logger.warn('Skip commit: Local branch is behind remote. Please pull changes.');
227
+ return;
228
+ }
229
+ } catch (e) {
230
+ logger.debug(`Remote check failed (offline?): ${e.message}`);
192
231
  }
193
232
 
194
- // 4. Safety: Custom checks
233
+ // 5. Safety: Custom checks
195
234
  if (this.config?.requireChecks) {
196
235
  const checksPassed = await this.runChecks();
197
236
  if (!checksPassed) {
198
- logger.warn('Checks failed. Skipping commit.');
237
+ logger.warn('Skip commit: Checks failed');
199
238
  return;
200
239
  }
201
240
  }
202
241
 
203
- // 5. Flow: Status -> Add -> Commit -> Push
204
- const status = await git.getPorcelainStatus(this.repoPath);
205
- if (!status.ok || status.files.length === 0) {
206
- this.logVerbose('No changes to commit');
207
- return;
208
- }
209
-
210
- this.logVerbose(`Detecting ${status.files.length} changed files`);
242
+ // 6. Commit
243
+ logger.info('Committing changes...');
211
244
 
212
- // Add all
245
+ // Add all changes
213
246
  await git.addAll(this.repoPath);
214
247
 
215
248
  // Generate message
216
- const message = generateCommitMessage(status.files);
217
- this.logVerbose(`Generated message: ${message}`);
218
-
219
- // Commit
220
- const commitResult = await git.commit(this.repoPath, message);
221
- if (commitResult.ok) {
222
- logger.success(`Committed: ${message}`);
223
- this.lastCommitAt = Date.now();
224
-
225
- // Push if enabled
226
- if (this.config?.autoPush) {
227
- logger.info('Pushing to remote...');
228
- const pushResult = await git.push(this.repoPath, branch);
229
- if (pushResult.ok) {
230
- logger.success('Pushed successfully');
231
- } else {
232
- logger.error(`Push failed: ${pushResult.stderr}`);
233
- this.logVerbose(`Push error: ${pushResult.stderr}`);
234
- }
235
- }
236
- } else {
237
- logger.error(`Commit failed: ${commitResult.stderr}`);
238
- this.logVerbose(`Commit error: ${commitResult.stderr}`);
249
+ const changedFiles = statusObj.files;
250
+ const message = this.config?.commitMessageMode === 'simple'
251
+ ? 'chore: auto-commit changes'
252
+ : generateCommitMessage(changedFiles);
253
+
254
+ await git.commit(this.repoPath, message);
255
+ this.lastCommitAt = Date.now();
256
+ logger.success('Commit done');
257
+
258
+ // 7. Auto-push
259
+ if (this.config?.autoPush) {
260
+ logger.info('Pushing to remote...');
261
+ await git.push(this.repoPath);
262
+ logger.success('Push complete');
239
263
  }
240
264
 
241
265
  } catch (error) {
242
266
  logger.error(`Process error: ${error.message}`);
243
- this.logVerbose(`Process exception: ${error.stack}`);
244
267
  } finally {
245
268
  this.isProcessing = false;
246
269
  this.debounceTimer = null;
247
270
  }
248
271
  }
249
-
250
- /**
251
- * Run user-defined checks
252
- */
253
- async runChecks() {
254
- const checks = this.config?.checks || [];
255
- if (checks.length === 0) return true;
256
-
257
- this.logVerbose(`Running checks: ${checks.join(', ')}`);
258
-
259
- for (const cmd of checks) {
260
- try {
261
- logger.info(`Running check: ${cmd}`);
262
- await execa(cmd, { cwd: this.repoPath, shell: true });
263
- } catch (error) {
264
- logger.error(`Check failed: ${cmd}`);
265
- this.logVerbose(`Check output: ${error.message}`);
266
- return false;
267
- }
268
- }
269
- return true;
270
- }
271
-
272
- /**
273
- * Reload configuration
274
- */
275
- async reloadConfig() {
276
- this.config = await loadConfig(this.repoPath);
277
- this.logVerbose('Config reloaded');
278
- }
279
-
280
- /**
281
- * Reload ignore patterns
282
- */
283
- async reloadIgnore() {
284
- const ignorePath = getIgnorePath(this.repoPath);
285
- this.ignorePatterns = [];
286
-
287
- if (await fs.pathExists(ignorePath)) {
288
- try {
289
- const content = await fs.readFile(ignorePath, 'utf-8');
290
- const lines = content.split('\n')
291
- .map(l => l.trim())
292
- .filter(l => l && !l.startsWith('#'));
293
-
294
- this.ignorePatterns.push(...lines);
295
- this.logVerbose(`Loaded ${lines.length} ignore patterns`);
296
- } catch (error) {
297
- logger.warn(`Failed to load ignore file: ${error.message}`);
298
- }
299
- }
300
-
301
- // Also load ignore patterns from config json if they exist
302
- if (this.config?.ignore && Array.isArray(this.config.ignore)) {
303
- this.ignorePatterns.push(...this.config.ignore);
304
- }
305
- }
306
-
307
- handleError(error) {
308
- logger.error(`Watcher error: ${error.message}`);
309
- this.logVerbose(`Watcher error: ${error.stack}`);
310
- }
311
-
312
- logVerbose(message) {
313
- const timestamp = new Date().toISOString();
314
- const logLine = `[${timestamp}] ${message}\n`;
315
- fs.appendFile(this.logFilePath, logLine).catch(() => {});
316
- }
317
272
  }
318
273
 
319
274
  module.exports = Watcher;