@snapcommit/cli 3.11.1 → 3.11.2
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/dist/commands/autopilot.js +282 -0
- package/dist/commands/cursor-style.js +40 -2
- package/dist/utils/git.js +9 -1
- package/dist/utils/metrics.js +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -9,6 +42,7 @@ const child_process_1 = require("child_process");
|
|
|
9
42
|
const fs_1 = require("fs");
|
|
10
43
|
const path_1 = __importDefault(require("path"));
|
|
11
44
|
const auth_1 = require("../lib/auth");
|
|
45
|
+
const github = __importStar(require("../lib/github"));
|
|
12
46
|
const git_1 = require("../utils/git");
|
|
13
47
|
const ui_1 = require("../utils/ui");
|
|
14
48
|
const prompt_1 = require("../utils/prompt");
|
|
@@ -211,14 +245,176 @@ const WORKFLOWS = [
|
|
|
211
245
|
},
|
|
212
246
|
],
|
|
213
247
|
},
|
|
248
|
+
{
|
|
249
|
+
id: 'pr-polish',
|
|
250
|
+
name: 'PR Polish',
|
|
251
|
+
headline: 'Push, summarize, and open a reviewer-ready pull request.',
|
|
252
|
+
description: 'Ensure your working tree is clean, run optional tests, capture reviewer notes, push your branch, and open a GitHub PR with consistent copy.',
|
|
253
|
+
idealFor: ['feature branches', 'handoffs to reviewers', 'Product Hunt launches'],
|
|
254
|
+
prerequisites: [
|
|
255
|
+
'Branch has commits ready for review',
|
|
256
|
+
'GitHub authentication configured (`snap github connect`)',
|
|
257
|
+
],
|
|
258
|
+
steps: [
|
|
259
|
+
{
|
|
260
|
+
id: 'review-status',
|
|
261
|
+
title: 'Confirm branch status',
|
|
262
|
+
description: 'Check branch tracking info and ensure the working tree is ready.',
|
|
263
|
+
action: async (ctx) => {
|
|
264
|
+
const status = (0, git_1.getGitStatus)();
|
|
265
|
+
const branch = (0, git_1.getCurrentBranch)();
|
|
266
|
+
const tracking = getBranchTrackingStatus();
|
|
267
|
+
const dirty = status.unstaged > 0 || status.untracked > 0;
|
|
268
|
+
ctx.recordInsight('branch', branch && branch !== 'unknown' ? branch : 'unknown');
|
|
269
|
+
if (tracking) {
|
|
270
|
+
ctx.recordInsight('tracking', tracking);
|
|
271
|
+
(0, ui_1.displayInfo)('Branch tracking', [tracking]);
|
|
272
|
+
}
|
|
273
|
+
if (dirty) {
|
|
274
|
+
const warnings = [];
|
|
275
|
+
if (status.unstaged > 0)
|
|
276
|
+
warnings.push(`${status.unstaged} unstaged file(s)`);
|
|
277
|
+
if (status.untracked > 0)
|
|
278
|
+
warnings.push(`${status.untracked} untracked file(s)`);
|
|
279
|
+
(0, ui_1.displayWarning)('Working tree has pending changes.', warnings);
|
|
280
|
+
const continueDirty = ctx.autoContinue || (await (0, prompt_1.promptConfirm)('Continue with dirty working tree?', false));
|
|
281
|
+
if (!continueDirty) {
|
|
282
|
+
throw new Error('Please commit, stash, or clean your working tree before continuing.');
|
|
283
|
+
}
|
|
284
|
+
ctx.recordInsight('workingTree', 'Dirty (user accepted)');
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
ctx.recordInsight('workingTree', 'Clean');
|
|
288
|
+
(0, ui_1.displaySuccess)('Working tree clean.');
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'pr-tests',
|
|
294
|
+
title: 'Optional: run tests',
|
|
295
|
+
description: 'Run your preferred test command so reviewers trust the PR.',
|
|
296
|
+
skippable: true,
|
|
297
|
+
action: async (ctx) => {
|
|
298
|
+
const defaultCommand = getPreferredTestCommand();
|
|
299
|
+
const shouldRun = ctx.autoContinue ||
|
|
300
|
+
(await (0, prompt_1.promptConfirm)('Run tests before pushing?', Boolean(defaultCommand)));
|
|
301
|
+
if (!shouldRun) {
|
|
302
|
+
ctx.recordInsight('tests', 'Skipped');
|
|
303
|
+
ctx.prTestResult = 'Tests not run';
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const command = ctx.autoContinue
|
|
307
|
+
? defaultCommand || ''
|
|
308
|
+
: await (0, prompt_1.promptInput)('Test command', defaultCommand || 'npm test');
|
|
309
|
+
if (!command) {
|
|
310
|
+
ctx.recordInsight('tests', 'Skipped (no command)');
|
|
311
|
+
ctx.prTestResult = 'Tests not run';
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
await runShellCommand(command);
|
|
316
|
+
(0, memory_1.rememberPreference)('autopilot', 'testCommand', command);
|
|
317
|
+
ctx.recordInsight('tests', `Passed (${command})`);
|
|
318
|
+
ctx.prTestResult = `✅ ${command}`;
|
|
319
|
+
(0, ui_1.displaySuccess)('Tests passed successfully.');
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
ctx.recordInsight('tests', `Failed (${command})`);
|
|
323
|
+
ctx.prTestResult = `⚠️ Failed (${command})`;
|
|
324
|
+
(0, ui_1.displayError)('Test command failed.', [
|
|
325
|
+
error.message,
|
|
326
|
+
'Fix the failures or continue at your own risk.',
|
|
327
|
+
]);
|
|
328
|
+
const continueAnyway = ctx.autoContinue || (await (0, prompt_1.promptConfirm)('Continue despite failing tests?', false));
|
|
329
|
+
if (!continueAnyway) {
|
|
330
|
+
throw new Error('Aborted because tests failed.');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'sync',
|
|
337
|
+
title: 'Push branch to origin',
|
|
338
|
+
description: 'Make sure your branch is on GitHub before opening a PR.',
|
|
339
|
+
action: async (ctx) => {
|
|
340
|
+
const branch = (0, git_1.getCurrentBranch)();
|
|
341
|
+
const hasUpstream = branchHasUpstream();
|
|
342
|
+
const command = hasUpstream ? 'git push' : `git push --set-upstream origin ${branch}`;
|
|
343
|
+
try {
|
|
344
|
+
await runShellCommand(command);
|
|
345
|
+
ctx.recordInsight('push', `Pushed via "${command}"`);
|
|
346
|
+
(0, ui_1.displaySuccess)(`Branch ${chalk_1.default.cyan(branch)} pushed to origin.`);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
ctx.recordInsight('push', 'Failed');
|
|
350
|
+
throw new Error(error.message || 'Failed to push branch.');
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'capture-summary',
|
|
356
|
+
title: 'Capture reviewer summary',
|
|
357
|
+
description: 'Gather highlights so reviewers instantly understand the change.',
|
|
358
|
+
action: async (ctx) => {
|
|
359
|
+
const defaultSummary = getRecentCommitSummary();
|
|
360
|
+
let summary = defaultSummary;
|
|
361
|
+
if (!ctx.autoContinue) {
|
|
362
|
+
summary =
|
|
363
|
+
(await (0, prompt_1.promptInput)('Describe what reviewers should know (markdown ok)', defaultSummary || 'Add a quick summary of your changes')) || defaultSummary;
|
|
364
|
+
}
|
|
365
|
+
summary = (summary || defaultSummary || '').trim();
|
|
366
|
+
if (!summary) {
|
|
367
|
+
summary = '- Updated files\n- Ready for review';
|
|
368
|
+
}
|
|
369
|
+
ctx.prSummary = summary;
|
|
370
|
+
ctx.recordInsight('prSummary', summary);
|
|
371
|
+
(0, ui_1.displayInfo)('PR summary captured', [summary]);
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: 'open-pr',
|
|
376
|
+
title: 'Create pull request',
|
|
377
|
+
description: 'Generate title + body and open a GitHub PR with one approval.',
|
|
378
|
+
action: async (ctx) => {
|
|
379
|
+
const branch = (0, git_1.getCurrentBranch)();
|
|
380
|
+
const defaultTitle = getDefaultPRTitle();
|
|
381
|
+
const storedSummary = ctx.prSummary || getRecentCommitSummary();
|
|
382
|
+
const testResult = ctx.prTestResult;
|
|
383
|
+
const defaultBody = buildPRBody(storedSummary, testResult);
|
|
384
|
+
const title = ctx.autoContinue
|
|
385
|
+
? defaultTitle
|
|
386
|
+
: await (0, prompt_1.promptInput)('PR title', defaultTitle || `Updates from ${branch}`);
|
|
387
|
+
const body = ctx.autoContinue
|
|
388
|
+
? defaultBody
|
|
389
|
+
: await (0, prompt_1.promptInput)('PR body (markdown supported)', defaultBody);
|
|
390
|
+
(0, ui_1.displayInfo)('Opening pull request on GitHub...', [
|
|
391
|
+
`Title: ${title}`,
|
|
392
|
+
testResult ? `Tests: ${testResult}` : 'Tests: not provided',
|
|
393
|
+
]);
|
|
394
|
+
const pr = await github.createPullRequest({
|
|
395
|
+
title: title || defaultTitle,
|
|
396
|
+
body: body || defaultBody,
|
|
397
|
+
});
|
|
398
|
+
ctx.recordInsight('pullRequest', `#${pr.number} ${pr.title}`);
|
|
399
|
+
ctx.recordInsight('prUrl', pr.html_url);
|
|
400
|
+
(0, ui_1.displaySuccess)('Pull request created.', [
|
|
401
|
+
`#${pr.number} ${pr.title}`,
|
|
402
|
+
pr.html_url,
|
|
403
|
+
]);
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
214
408
|
];
|
|
215
409
|
const WORKFLOW_TIME_SAVINGS = {
|
|
216
410
|
'conflict-crusher': 25,
|
|
217
411
|
'release-ready': 18,
|
|
412
|
+
'pr-polish': 15,
|
|
218
413
|
};
|
|
219
414
|
const WORKFLOW_EVENT_IDS = {
|
|
220
415
|
'conflict-crusher': 'autopilot:conflict-crusher',
|
|
221
416
|
'release-ready': 'autopilot:release-ready',
|
|
417
|
+
'pr-polish': 'autopilot:pr-polish',
|
|
222
418
|
};
|
|
223
419
|
async function autopilotCommand(workflowId, rawOptions) {
|
|
224
420
|
const options = {
|
|
@@ -462,3 +658,89 @@ function inferProjectTestCommand() {
|
|
|
462
658
|
}
|
|
463
659
|
return null;
|
|
464
660
|
}
|
|
661
|
+
function branchHasUpstream() {
|
|
662
|
+
try {
|
|
663
|
+
(0, child_process_1.execSync)('git rev-parse --abbrev-ref --symbolic-full-name @{u}', {
|
|
664
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
665
|
+
});
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function getBranchTrackingStatus() {
|
|
673
|
+
try {
|
|
674
|
+
const firstLine = (0, child_process_1.execSync)('git status -sb', {
|
|
675
|
+
encoding: 'utf-8',
|
|
676
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
677
|
+
})
|
|
678
|
+
.split('\n')[0]
|
|
679
|
+
.trim();
|
|
680
|
+
const match = firstLine.match(/\[(.+)\]/);
|
|
681
|
+
if (match && match[1]) {
|
|
682
|
+
return match[1].replace(',', ' • ');
|
|
683
|
+
}
|
|
684
|
+
return 'Up to date with upstream';
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function getRecentCommitSummary(limit = 3) {
|
|
691
|
+
try {
|
|
692
|
+
const log = (0, child_process_1.execSync)(`git log -${limit} --oneline`, {
|
|
693
|
+
encoding: 'utf-8',
|
|
694
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
695
|
+
})
|
|
696
|
+
.trim()
|
|
697
|
+
.split('\n')
|
|
698
|
+
.filter(Boolean)
|
|
699
|
+
.map((line) => `- ${line.trim()}`)
|
|
700
|
+
.join('\n');
|
|
701
|
+
return log;
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return '';
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function getDefaultPRTitle() {
|
|
708
|
+
try {
|
|
709
|
+
const title = (0, child_process_1.execSync)('git log -1 --pretty=%s', {
|
|
710
|
+
encoding: 'utf-8',
|
|
711
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
712
|
+
}).trim();
|
|
713
|
+
if (title) {
|
|
714
|
+
return title;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// no-op
|
|
719
|
+
}
|
|
720
|
+
const branch = (0, git_1.getCurrentBranch)();
|
|
721
|
+
return branch && branch !== 'unknown' ? `Updates from ${branch}` : 'Updates from SnapCommit';
|
|
722
|
+
}
|
|
723
|
+
function buildPRBody(summary, tests) {
|
|
724
|
+
const lines = [];
|
|
725
|
+
lines.push('## Summary');
|
|
726
|
+
if (summary && summary.trim()) {
|
|
727
|
+
lines.push(summary.trim());
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
lines.push('- Describe the key changes');
|
|
731
|
+
}
|
|
732
|
+
lines.push('');
|
|
733
|
+
lines.push('## Testing');
|
|
734
|
+
if (tests && tests.trim()) {
|
|
735
|
+
lines.push(`- ${tests.trim()}`);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
lines.push('- [ ] Tests not run');
|
|
739
|
+
}
|
|
740
|
+
lines.push('');
|
|
741
|
+
lines.push('## Checklist');
|
|
742
|
+
lines.push('- [ ] Linked issue / ticket');
|
|
743
|
+
lines.push('- [ ] Added or updated tests');
|
|
744
|
+
lines.push('- [ ] Updated documentation (if needed)');
|
|
745
|
+
return lines.join('\n');
|
|
746
|
+
}
|
|
@@ -113,7 +113,7 @@ async function handleAICommand(userInput) {
|
|
|
113
113
|
async function showStatus() {
|
|
114
114
|
const status = (0, git_1.getGitStatus)();
|
|
115
115
|
const branch = (0, git_1.getCurrentBranch)();
|
|
116
|
-
const hasChanges = status.
|
|
116
|
+
const hasChanges = status.entries.length > 0;
|
|
117
117
|
console.log(chalk_1.default.blue(`\nBranch: ${branch}`));
|
|
118
118
|
if (!hasChanges) {
|
|
119
119
|
console.log(chalk_1.default.gray('✓ Branch clean - no changes\n'));
|
|
@@ -123,10 +123,48 @@ async function showStatus() {
|
|
|
123
123
|
if (status.unstaged > 0)
|
|
124
124
|
console.log(chalk_1.default.yellow(` • ${status.unstaged} modified`));
|
|
125
125
|
if (status.untracked > 0)
|
|
126
|
-
console.log(chalk_1.default.
|
|
126
|
+
console.log(chalk_1.default.cyan(` • ${status.untracked} new`));
|
|
127
127
|
if (status.staged > 0)
|
|
128
128
|
console.log(chalk_1.default.green(` • ${status.staged} staged`));
|
|
129
129
|
console.log();
|
|
130
|
+
const stagedFiles = [];
|
|
131
|
+
const unstagedFiles = [];
|
|
132
|
+
const untrackedFiles = [];
|
|
133
|
+
const pushUnique = (list, value) => {
|
|
134
|
+
if (!list.includes(value)) {
|
|
135
|
+
list.push(value);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
status.entries.forEach((entry) => {
|
|
139
|
+
const file = entry.file;
|
|
140
|
+
const stageCode = entry.stagedCode;
|
|
141
|
+
const worktreeCode = entry.worktreeCode;
|
|
142
|
+
if (stageCode === '?' && worktreeCode === '?') {
|
|
143
|
+
pushUnique(untrackedFiles, file);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (stageCode !== ' ' && stageCode !== '?') {
|
|
147
|
+
pushUnique(stagedFiles, file);
|
|
148
|
+
}
|
|
149
|
+
if (worktreeCode !== ' ' && worktreeCode !== '?') {
|
|
150
|
+
pushUnique(unstagedFiles, file);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const printSection = (label, files, formatter) => {
|
|
154
|
+
if (!files.length) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
console.log(label);
|
|
158
|
+
files.forEach((file) => {
|
|
159
|
+
console.log(formatter(file));
|
|
160
|
+
});
|
|
161
|
+
console.log();
|
|
162
|
+
};
|
|
163
|
+
printSection(chalk_1.default.green('Staged:'), stagedFiles, (file) => chalk_1.default.green(` ✓ ${file}`));
|
|
164
|
+
printSection(chalk_1.default.yellow('Modified (unstaged):'), unstagedFiles, (file) => chalk_1.default.yellow(` ✎ ${file}`));
|
|
165
|
+
printSection(chalk_1.default.cyan('Untracked:'), untrackedFiles, (file) => chalk_1.default.cyan(` + ${file}`));
|
|
166
|
+
console.log(chalk_1.default.gray('Tip: say "show diff <file>", "stage <file>", or "commit these changes" next.'));
|
|
167
|
+
console.log();
|
|
130
168
|
}
|
|
131
169
|
/**
|
|
132
170
|
* Execute commit - EXACTLY like Cursor!
|
package/dist/utils/git.js
CHANGED
|
@@ -38,8 +38,16 @@ function getGitStatus() {
|
|
|
38
38
|
let staged = 0;
|
|
39
39
|
let unstaged = 0;
|
|
40
40
|
let untracked = 0;
|
|
41
|
+
const entries = [];
|
|
41
42
|
lines.forEach((line) => {
|
|
42
43
|
const statusCode = line.substring(0, 2);
|
|
44
|
+
const file = line.substring(3).trim();
|
|
45
|
+
entries.push({
|
|
46
|
+
code: statusCode,
|
|
47
|
+
file,
|
|
48
|
+
stagedCode: statusCode[0],
|
|
49
|
+
worktreeCode: statusCode[1],
|
|
50
|
+
});
|
|
43
51
|
if (statusCode[0] !== ' ' && statusCode[0] !== '?')
|
|
44
52
|
staged++;
|
|
45
53
|
if (statusCode[1] !== ' ')
|
|
@@ -47,7 +55,7 @@ function getGitStatus() {
|
|
|
47
55
|
if (statusCode[0] === '?' && statusCode[1] === '?')
|
|
48
56
|
untracked++;
|
|
49
57
|
});
|
|
50
|
-
return { staged, unstaged, untracked };
|
|
58
|
+
return { staged, unstaged, untracked, entries };
|
|
51
59
|
}
|
|
52
60
|
catch (error) {
|
|
53
61
|
throw new Error(`Git error: ${error.message}`);
|
package/dist/utils/metrics.js
CHANGED