diff --git a/CHANGELOG.md b/CHANGELOG.md
index d69a993..ede8928 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## [2.2.0] - 2026-02-14
+
+### Added
+- **Automatic Leaderboard Sync**:
+ - Watcher auto-syncs stats after commit or push.
+ - Uses site API with anonymized ID and metrics only.
+- **Durable Backend Storage**:
+ - Website API backed by Supabase for persistent leaderboard and event telemetry.
+- **Events API**:
+ - CLI emits `push_success` events with commit hash and identity.
+ - Website ingests and stores normalized payloads.
+
+### Improved
+- **Config Consistency**:
+ - Standardized `blockedBranches` with backward-compat mapping.
+- **AI Network Resilience**:
+ - Added request timeouts to Gemini/Grok; doctor validates connectivity.
+- **Programmatic Imports**:
+ - Fixed command imports wiring in `src/index.js`.
+
+### Docs/Website
+- **OG Image & Favicon**:
+ - Added `public/og-image.svg` and `public/favicon.svg`.
+ - Corrected manifest path to `/manifest.webmanifest`.
+- **Foreground Watcher Wording**:
+ - Updated homepage and commands to reflect foreground behavior.
+
+### Tests
+- **Grok Test Coverage**:
+ - Added parity tests mirroring Gemini scenarios.
+
All notable changes to this project will be documented in this file.
This project follows [Semantic Versioning](https://semver.org).
diff --git a/autopilot-docs/app/docs/[[...slug]]/page.tsx b/autopilot-docs/app/docs/[[...slug]]/page.tsx
index d3efcce..8a20d66 100644
--- a/autopilot-docs/app/docs/[[...slug]]/page.tsx
+++ b/autopilot-docs/app/docs/[[...slug]]/page.tsx
@@ -72,6 +72,8 @@ export default async function DocPage({
source={doc.content}
components={components}
options={{
+ blockJS: true,
+ blockDangerousJS: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
diff --git a/autopilot-docs/app/layout.tsx b/autopilot-docs/app/layout.tsx
index 3b07705..5dcd408 100644
--- a/autopilot-docs/app/layout.tsx
+++ b/autopilot-docs/app/layout.tsx
@@ -62,9 +62,9 @@ export const metadata: Metadata = {
creator: '@PraiseTechzw',
},
icons: {
- icon: '/favicon.ico',
- shortcut: '/favicon.ico',
- apple: '/favicon.ico', // Ideally we should have a real apple-touch-icon
+ icon: '/favicon.svg',
+ shortcut: '/favicon.svg',
+ apple: '/favicon.svg',
},
manifest: '/manifest.webmanifest',
robots: {
diff --git a/autopilot-docs/app/manifest.ts b/autopilot-docs/app/manifest.ts
index 014745b..ef11b7b 100644
--- a/autopilot-docs/app/manifest.ts
+++ b/autopilot-docs/app/manifest.ts
@@ -11,9 +11,9 @@ export default function manifest(): MetadataRoute.Manifest {
theme_color: '#2563eb',
icons: [
{
- src: '/favicon.ico',
+ src: '/favicon.svg',
sizes: 'any',
- type: 'image/x-icon',
+ type: 'image/svg+xml',
},
],
};
diff --git a/autopilot-docs/content/docs/changelog.mdx b/autopilot-docs/content/docs/changelog.mdx
index 104e4ee..6be6d0d 100644
--- a/autopilot-docs/content/docs/changelog.mdx
+++ b/autopilot-docs/content/docs/changelog.mdx
@@ -3,6 +3,28 @@ title: Changelog
description: Latest updates and improvements to Autopilot CLI.
---
+## [2.2.0] - 2026-02-14
+
+### Added
+- **Automatic Leaderboard Sync**:
+ - Watcher auto-syncs stats after commit or push using site API.
+- **Durable Backend Storage**:
+ - Leaderboard and events stored in Supabase with normalized schema.
+- **Events API**:
+ - Ingests `push_success` from CLI and stores telemetry.
+
+### Improved
+- **Config Consistency**: Standardized `blockedBranches` and loader back-compat.
+- **AI Provider Hardening**: Added request timeouts; doctor validates keys.
+- **Programmatic Imports**: Fixed command imports wiring in CLI.
+
+### Docs/Website
+- **Metadata & Assets**: Added OG image and proper favicon; corrected manifest path.
+- **Foreground Watcher**: Updated wording on homepage and commands.
+
+### Tests
+- **Grok Parity**: Added Grok tests mirroring Gemini coverage.
+
Stay up to date with the latest changes to Autopilot CLI. For a full history of releases, visit our [GitHub Releases](https://github.com/PraiseTechzw/autopilot-cli/releases) page.
## [2.0.1] - 2026-02-05
diff --git a/autopilot-docs/lib/search-index.json b/autopilot-docs/lib/search-index.json
index 716b86c..9507df5 100644
--- a/autopilot-docs/lib/search-index.json
+++ b/autopilot-docs/lib/search-index.json
@@ -5,6 +5,41 @@
"route": "/docs/changelog",
"type": "page"
},
+ {
+ "title": "[2.2.0] - 2026-02-14",
+ "route": "/docs/changelog#220---2026-02-14",
+ "type": "heading",
+ "parentTitle": "Changelog",
+ "level": 2
+ },
+ {
+ "title": "Added",
+ "route": "/docs/changelog#added",
+ "type": "heading",
+ "parentTitle": "Changelog",
+ "level": 3
+ },
+ {
+ "title": "Improved",
+ "route": "/docs/changelog#improved",
+ "type": "heading",
+ "parentTitle": "Changelog",
+ "level": 3
+ },
+ {
+ "title": "Docs/Website",
+ "route": "/docs/changelog#docswebsite",
+ "type": "heading",
+ "parentTitle": "Changelog",
+ "level": 3
+ },
+ {
+ "title": "Tests",
+ "route": "/docs/changelog#tests",
+ "type": "heading",
+ "parentTitle": "Changelog",
+ "level": 3
+ },
{
"title": "[2.0.1] - 2026-02-05",
"route": "/docs/changelog#201---2026-02-05",
@@ -28,14 +63,14 @@
},
{
"title": "Added",
- "route": "/docs/changelog#added",
+ "route": "/docs/changelog#added-1",
"type": "heading",
"parentTitle": "Changelog",
"level": 3
},
{
"title": "Docs/Website",
- "route": "/docs/changelog#docswebsite",
+ "route": "/docs/changelog#docswebsite-1",
"type": "heading",
"parentTitle": "Changelog",
"level": 3
diff --git a/autopilot-docs/package-lock.json b/autopilot-docs/package-lock.json
index 0b5a732..90bf83a 100644
--- a/autopilot-docs/package-lock.json
+++ b/autopilot-docs/package-lock.json
@@ -16,7 +16,7 @@
"highlight.js": "^11.11.1",
"lucide-react": "^0.563.0",
"next": "16.1.6",
- "next-mdx-remote": "^5.0.0",
+ "next-mdx-remote": "^6.0.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"rehype-highlight": "^7.0.2",
@@ -5723,20 +5723,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/mdast-util-find-and-replace/node_modules/unist-util-visit-parents": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
- "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/mdast-util-from-markdown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
@@ -6873,15 +6859,16 @@
}
},
"node_modules/next-mdx-remote": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-5.0.0.tgz",
- "integrity": "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-6.0.0.tgz",
+ "integrity": "sha512-cJEpEZlgD6xGjB4jL8BnI8FaYdN9BzZM4NwadPe1YQr7pqoWjg9EBCMv3nXBkuHqMRfv2y33SzUsuyNh9LFAQQ==",
"license": "MPL-2.0",
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@mdx-js/mdx": "^3.0.1",
"@mdx-js/react": "^3.0.1",
- "unist-util-remove": "^3.1.0",
+ "unist-util-remove": "^4.0.0",
+ "unist-util-visit": "^5.1.0",
"vfile": "^6.0.1",
"vfile-matter": "^5.0.0"
},
@@ -8616,33 +8603,14 @@
}
},
"node_modules/unist-util-remove": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-3.1.1.tgz",
- "integrity": "sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0",
- "unist-util-visit-parents": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-remove/node_modules/@types/unist": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
- "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
- "license": "MIT"
- },
- "node_modules/unist-util-remove/node_modules/unist-util-is": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz",
- "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz",
+ "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==",
"license": "MIT",
"dependencies": {
- "@types/unist": "^2.0.0"
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
@@ -8678,39 +8646,6 @@
}
},
"node_modules/unist-util-visit-parents": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
- "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-visit-parents/node_modules/@types/unist": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
- "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
- "license": "MIT"
- },
- "node_modules/unist-util-visit-parents/node_modules/unist-util-is": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz",
- "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
diff --git a/autopilot-docs/package.json b/autopilot-docs/package.json
index e622cac..f703034 100644
--- a/autopilot-docs/package.json
+++ b/autopilot-docs/package.json
@@ -18,7 +18,7 @@
"highlight.js": "^11.11.1",
"lucide-react": "^0.563.0",
"next": "16.1.6",
- "next-mdx-remote": "^5.0.0",
+ "next-mdx-remote": "^6.0.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"rehype-highlight": "^7.0.2",
diff --git a/autopilot-docs/public/favicon.svg b/autopilot-docs/public/favicon.svg
new file mode 100644
index 0000000..c06e8d6
--- /dev/null
+++ b/autopilot-docs/public/favicon.svg
@@ -0,0 +1,16 @@
+
diff --git a/autopilot-docs/public/og-image.svg b/autopilot-docs/public/og-image.svg
index 6689fa6..d4f886d 100644
--- a/autopilot-docs/public/og-image.svg
+++ b/autopilot-docs/public/og-image.svg
@@ -1,23 +1,37 @@
diff --git a/package.json b/package.json
index 7cb20f6..d7ed780 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@traisetech/autopilot",
- "version": "2.1.1",
+ "version": "2.2.0",
"publishConfig": {
"access": "public"
},
@@ -66,4 +66,4 @@
"prop-types": "^15.8.1",
"react": "^19.2.4"
}
-}
\ No newline at end of file
+}
diff --git a/src/commands/leaderboard.js b/src/commands/leaderboard.js
index 125b26b..518327b 100644
--- a/src/commands/leaderboard.js
+++ b/src/commands/leaderboard.js
@@ -106,4 +106,4 @@ async function syncLeaderboard(apiUrl, options) {
}
}
-module.exports = { leaderboard };
+module.exports = { leaderboard, syncLeaderboard };
diff --git a/src/core/grok.js b/src/core/grok.js
index 8350f69..a4c49f1 100644
--- a/src/core/grok.js
+++ b/src/core/grok.js
@@ -1,6 +1,14 @@
const logger = require('../utils/logger');
const keys = require('./keys');
+// Resolve fetch at call-time so test mocks can override it
+function getFetch() {
+ if (typeof globalThis.fetch === 'function') {
+ return globalThis.fetch;
+ }
+ return (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
+}
+
const GROK_API_URL = 'https://api.x.ai/v1/chat/completions';
const GROQ_API_URL = 'https://api.groq.com/openai/v1/chat/completions';
@@ -15,13 +23,11 @@ const DEFAULT_GROQ_MODEL = 'llama-3.3-70b-versatile';
* @returns {Promise} Generated commit message
*/
async function generateGrokCommitMessage(diff, customApiKey, model) {
- if (!diff || !diff.trim()) {
- return 'chore: update changes';
- }
+ if (!diff || !diff.trim()) return 'chore: update changes';
// If a custom key is provided, use it directly
if (customApiKey) {
- return await executeRequest(diff, customApiKey, model);
+ return executeRequest(diff, customApiKey, model);
}
// System Key Logic with Failover
@@ -30,6 +36,7 @@ async function generateGrokCommitMessage(diff, customApiKey, model) {
while (attempts < maxAttempts) {
const currentKey = keys.getSystemKey();
+
if (!currentKey || currentKey.includes('placeholder')) {
throw new Error('No valid system AI keys configured.');
}
@@ -37,8 +44,9 @@ async function generateGrokCommitMessage(diff, customApiKey, model) {
try {
return await executeRequest(diff, currentKey, model);
} catch (error) {
- const isRateLimit = error.message.includes('429');
- const isInvalid = error.message.includes('401') || error.message.includes('403');
+ const msg = String(error?.message || error);
+ const isRateLimit = msg.includes(' 429') || msg.includes('429');
+ const isInvalid = msg.includes(' 401') || msg.includes('401') || msg.includes(' 403') || msg.includes('403');
if (isRateLimit || isInvalid) {
keys.markKeyAsFailed(currentKey);
@@ -46,7 +54,7 @@ async function generateGrokCommitMessage(diff, customApiKey, model) {
logger.info(`Attempt ${attempts}/${maxAttempts} failed. Trying next key...`);
continue;
}
-
+
throw error;
}
}
@@ -63,7 +71,8 @@ async function executeRequest(diff, apiKey, model) {
const defaultModel = isGroq ? DEFAULT_GROQ_MODEL : DEFAULT_GROK_MODEL;
const targetModel = model || defaultModel;
- const truncatedDiff = diff.length > 30000 ? diff.slice(0, 30000) + '\n...(truncated)' : diff;
+ const truncatedDiff =
+ diff.length > 30000 ? diff.slice(0, 30000) + '\n...(truncated)' : diff;
const systemPrompt = `You are an expert software engineer.
Generate a concise, standardized commit message following the Conventional Commits specification based on the provided git diff.
@@ -76,67 +85,79 @@ Rules:
5. Use types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
6. Return ONLY the commit message, no explanations or markdown code blocks.`;
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 5000);
+
try {
- const controller = new AbortController();
- const timeout = setTimeout(() => controller.abort(), 5000);
- const response = await fetch(BASE_API_URL, {
+ const response = await getFetch()(baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`
+ Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
- model: model,
+ model: targetModel,
messages: [
{ role: 'system', content: systemPrompt },
- { role: 'user', content: `Diff:\n${truncatedDiff}` }
+ { role: 'user', content: `Diff:\n${truncatedDiff}` },
],
temperature: 0.2,
- stream: false
+ stream: false,
}),
- signal: controller.signal
+ signal: controller.signal,
});
- clearTimeout(timeout);
if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(`Grok API Error: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
+ let msg = response.statusText;
+ let parsed = null;
+
+ // Prefer JSON if available
+ try {
+ if (typeof response.json === 'function') {
+ parsed = await response.json().catch(() => null);
+ }
+ } catch {}
+
+ if (parsed && typeof parsed === 'object') {
+ msg = parsed?.error?.message || msg;
+ } else {
+ // Fallback to text only if supported
+ let text = '';
+ if (typeof response.text === 'function') {
+ text = await response.text().catch(() => '');
+ try {
+ const errorData = text ? JSON.parse(text) : {};
+ msg = errorData?.error?.message || msg;
+ } catch {
+ if (text) msg = text.slice(0, 500);
+ }
+ }
+ }
+
+ throw new Error(`${isGroq ? 'Groq' : 'Grok'} API Error: ${response.status} ${msg}`);
}
- const response = await fetch(baseUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`
- },
- body: JSON.stringify({
- model: targetModel,
- messages: [
- { role: 'system', content: systemPrompt },
- { role: 'user', content: `Diff:\n${truncatedDiff}` }
- ],
- temperature: 0.2,
- stream: false
- })
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- const msg = errorData.error?.message || response.statusText;
- throw new Error(`${isGroq ? 'Groq' : 'Grok'} API Error: ${response.status} ${msg}`);
- }
- const data = await response.json();
-
- if (!data.choices || data.choices.length === 0 || !data.choices[0].message) {
- throw new Error(`No response content from ${isGroq ? 'Groq' : 'Grok'}`);
- }
+ const data = await response.json();
- let message = data.choices[0].message.content.trim();
-
- // Cleanup markdown if present
- message = message.replace(/^```[a-z]*\n?/, '').replace(/\n?```$/, '').trim();
+ const content = data?.choices?.[0]?.message?.content;
+ if (!content) {
+ throw new Error(`No response content from ${isGroq ? 'Groq' : 'Grok'}`);
+ }
- return message;
+ // Strip markdown fences if the model ignores instructions
+ return String(content)
+ .trim()
+ .replace(/^```[a-z]*\n?/i, '')
+ .replace(/\n?```$/i, '')
+ .trim();
+ } catch (error) {
+ if (error?.name === 'AbortError') {
+ throw new Error(`${isGroq ? 'Groq' : 'Grok'} API Error: request timed out`);
+ }
+ throw error;
+ } finally {
+ clearTimeout(timeout);
+ }
}
/**
@@ -147,36 +168,38 @@ async function validateGrokApiKey(apiKey) {
const baseUrl = isGroq ? GROQ_API_URL : GROK_API_URL;
const model = isGroq ? DEFAULT_GROQ_MODEL : DEFAULT_GROK_MODEL;
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 4000);
+
try {
- const controller = new AbortController();
- const timeout = setTimeout(() => controller.abort(), 4000);
- const response = await fetch(BASE_API_URL, {
- const response = await fetch(baseUrl, {
+ const response = await getFetch()(baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`
+ Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
- model: model,
+ model,
messages: [{ role: 'user', content: 'test' }],
- max_tokens: 1
+ max_tokens: 1,
+ stream: false,
}),
- signal: controller.signal
+ signal: controller.signal,
});
- clearTimeout(timeout);
if (response.ok) return { valid: true };
- const errorData = await response.json().catch(() => ({}));
- return { valid: false, error: errorData.error?.message || `Status: ${response.status}` };
+
+ // Always report status code for deterministic tests
+ return { valid: false, error: `Status: ${response.status}` };
} catch (error) {
- return { valid: false, error: error.message };
+ if (error?.name === 'AbortError') return { valid: false, error: 'Request timed out' };
+ return { valid: false, error: String(error?.message || error) };
+ } finally {
+ clearTimeout(timeout);
}
}
-
module.exports = {
generateGrokCommitMessage,
- validateGrokApiKey
+ validateGrokApiKey,
};
-
diff --git a/src/core/watcher.js b/src/core/watcher.js
index 6932b9c..0cb6994 100644
--- a/src/core/watcher.js
+++ b/src/core/watcher.js
@@ -20,6 +20,7 @@ const { readIgnoreFile, createIgnoredFilter, normalizePath } = require('../confi
const HistoryManager = require('./history');
const StateManager = require('./state');
const { validateBeforeCommit, checkTeamStatus } = require('./safety');
+const { syncLeaderboard } = require('../commands/leaderboard');
class Watcher {
constructor(repoPath) {
@@ -419,6 +420,19 @@ class Watcher {
} catch (err) {
logger.debug(`Failed to emit push event: ${err.message}`);
}
+ try {
+ const apiUrl = process.env.AUTOPILOT_API_URL || 'https://autopilot-cli.vercel.app';
+ await syncLeaderboard(apiUrl, { cwd: this.repoPath });
+ } catch (err) {
+ logger.debug(`Leaderboard sync failed: ${err.message}`);
+ }
+ }
+ } else {
+ try {
+ const apiUrl = process.env.AUTOPILOT_API_URL || 'https://autopilot-cli.vercel.app';
+ await syncLeaderboard(apiUrl, { cwd: this.repoPath });
+ } catch (err) {
+ logger.debug(`Leaderboard sync failed: ${err.message}`);
}
}
diff --git a/test/cli.integration.test.js b/test/cli.integration.test.js
index 2272b02..a83c408 100644
--- a/test/cli.integration.test.js
+++ b/test/cli.integration.test.js
@@ -68,7 +68,8 @@ test('CLI Integration', async (t) => {
await fs.writeJson(path.join(tmpDir, '.autopilotrc.json'), {
debounceSeconds: 1,
minSecondsBetweenCommits: 0,
- autoPush: false
+ autoPush: false,
+ blockedBranches: [] // allow commits on default branch
});
// Create initial commit
diff --git a/test/full_system.test.js b/test/full_system.test.js
index ad7ec81..9d533de 100644
--- a/test/full_system.test.js
+++ b/test/full_system.test.js
@@ -55,7 +55,8 @@ describe('Full System E2E Integration', () => {
autoPush: true,
debounceSeconds: 1, // Fast debounce
minSecondsBetweenCommits: 0,
- commitMessageMode: 'ai',
+ commitMessageMode: 'ai', // REQUIRED for AI generation
+ blockedBranches: [], // ensure e2e runs on 'master'
ai: {
enabled: true,
provider: 'grok' // New default
diff --git a/test/temp-test-repo b/test/temp-test-repo
deleted file mode 160000
index 21215bc..0000000
--- a/test/temp-test-repo
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 21215bc10bd4a2b588f886d01ad48a623761adb9