software-engineer 0.1.11 → 0.1.13
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/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/index.js +5 -0
- package/dist/logger.js +11 -1
- package/dist/pipeline.js +61 -31
- package/dist/steps/branchManagement.d.ts +2 -0
- package/dist/steps/branchManagement.js +65 -13
- package/dist/steps/index.d.ts +1 -1
- package/dist/steps/review.d.ts +2 -1
- package/dist/steps/review.js +37 -3
- package/dist/utils/stepAnalyzer.d.ts +21 -0
- package/dist/utils/stepAnalyzer.js +141 -0
- package/dist/utils/updateNotifier.d.ts +58 -0
- package/dist/utils/updateNotifier.js +194 -0
- package/package.json +2 -2
package/dist/config.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Config {
|
|
|
7
7
|
skipBranchManagement: boolean;
|
|
8
8
|
dangerouslySkipPermissions: boolean;
|
|
9
9
|
requirement: string;
|
|
10
|
+
adaptiveExecution: boolean;
|
|
10
11
|
}
|
|
11
12
|
export declare function loadConfigFromEnv(): Partial<Config>;
|
|
12
13
|
export declare function mergeConfig(envConfig: Partial<Config>, cliConfig: Partial<Config>): Config;
|
package/dist/config.js
CHANGED
|
@@ -19,6 +19,7 @@ export function loadConfigFromEnv() {
|
|
|
19
19
|
skipPush: parseBoolEnv(process.env.SF_SKIP_PUSH, false),
|
|
20
20
|
skipBranchManagement: parseBoolEnv(process.env.SF_SKIP_BRANCH_MANAGEMENT, false),
|
|
21
21
|
dangerouslySkipPermissions: parseBoolEnv(process.env.SF_DANGEROUSLY_SKIP_PERMISSIONS, false),
|
|
22
|
+
adaptiveExecution: parseBoolEnv(process.env.SF_ADAPTIVE_EXECUTION, false),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
export function mergeConfig(envConfig, cliConfig) {
|
|
@@ -31,5 +32,6 @@ export function mergeConfig(envConfig, cliConfig) {
|
|
|
31
32
|
skipBranchManagement: cliConfig.skipBranchManagement ?? envConfig.skipBranchManagement ?? false,
|
|
32
33
|
dangerouslySkipPermissions: cliConfig.dangerouslySkipPermissions ?? envConfig.dangerouslySkipPermissions ?? false,
|
|
33
34
|
requirement: cliConfig.requirement ?? '',
|
|
35
|
+
adaptiveExecution: cliConfig.adaptiveExecution ?? envConfig.adaptiveExecution ?? false,
|
|
34
36
|
};
|
|
35
37
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { dirname, join } from 'path';
|
|
7
7
|
import { loadConfigFromEnv, mergeConfig } from './config.js';
|
|
8
8
|
import { runPipeline } from './pipeline.js';
|
|
9
|
+
import { checkForUpdates } from './utils/updateNotifier.js';
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
11
12
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -17,12 +18,15 @@ program
|
|
|
17
18
|
.argument('<requirement>', 'The requirement or task to implement')
|
|
18
19
|
.option('-d, --dry-run', 'Print commands without executing')
|
|
19
20
|
.option('-r, --reviews <n>', 'Number of review iterations', '2')
|
|
21
|
+
.option('-a, --adaptive', 'Enable adaptive step execution (AI decides which steps to skip)')
|
|
20
22
|
.option('--skip-tests', 'Skip the testing step')
|
|
21
23
|
.option('--skip-push', 'Commit but do not push')
|
|
22
24
|
.option('--skip-branch-management', 'Skip smart branch management')
|
|
23
25
|
.option('--log <file>', 'Log output to file')
|
|
24
26
|
.option('--dangerously-skip-permissions', 'Pass flag to claude to skip permission prompts')
|
|
25
27
|
.action(async (requirement, options) => {
|
|
28
|
+
// Check for updates (non-blocking, fails silently)
|
|
29
|
+
await checkForUpdates(pkg.name, pkg.version).catch(() => { });
|
|
26
30
|
const envConfig = loadConfigFromEnv();
|
|
27
31
|
const cliConfig = {
|
|
28
32
|
requirement,
|
|
@@ -33,6 +37,7 @@ program
|
|
|
33
37
|
skipBranchManagement: options.skipBranchManagement ?? undefined,
|
|
34
38
|
logFile: options.log ?? undefined,
|
|
35
39
|
dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? undefined,
|
|
40
|
+
adaptiveExecution: options.adaptive ?? undefined,
|
|
36
41
|
};
|
|
37
42
|
const config = mergeConfig(envConfig, cliConfig);
|
|
38
43
|
if (!config.requirement) {
|
package/dist/logger.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { appendFileSync } from 'fs';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const pkg = require('../package.json');
|
|
3
6
|
const BOX_WIDTH = 62;
|
|
4
7
|
let logFilePath;
|
|
5
8
|
export function setLogFile(path) {
|
|
@@ -40,7 +43,14 @@ export function logHeader(config) {
|
|
|
40
43
|
const line = '═'.repeat(BOX_WIDTH);
|
|
41
44
|
console.log();
|
|
42
45
|
console.log(chalk.green(`╔${line}╗`));
|
|
43
|
-
|
|
46
|
+
const title = `SOFTWARE FACTORY PIPELINE v${pkg.version}`;
|
|
47
|
+
const padding = Math.floor((BOX_WIDTH - title.length) / 2);
|
|
48
|
+
const titleLine = ' '.repeat(padding) + title + ' '.repeat(BOX_WIDTH - padding - title.length);
|
|
49
|
+
console.log(chalk.green('║') + titleLine + chalk.green('║'));
|
|
50
|
+
const author = 'Built by Muthukrishnan';
|
|
51
|
+
const authorPadding = Math.floor((BOX_WIDTH - author.length) / 2);
|
|
52
|
+
const authorLine = ' '.repeat(authorPadding) + author + ' '.repeat(BOX_WIDTH - authorPadding - author.length);
|
|
53
|
+
console.log(chalk.green('║') + chalk.dim(authorLine) + chalk.green('║'));
|
|
44
54
|
console.log(chalk.green(`╠${line}╣`));
|
|
45
55
|
const configLine = ` Reviews: ${config.reviewIterations} | Dry-run: ${config.dryRun}`;
|
|
46
56
|
console.log(chalk.green('║') + configLine.padEnd(BOX_WIDTH) + chalk.green('║'));
|
package/dist/pipeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { log, logHeader, setLogFile } from './logger.js';
|
|
2
|
+
import { log, logHeader, setLogFile, logInfo } from './logger.js';
|
|
3
3
|
import { stepBranchManagement, stepImplement, stepSimplify, stepReview, stepSolidCleanCode, stepTest, stepCommit, stepChangelog, } from './steps/index.js';
|
|
4
4
|
let signalHandlersRegistered = false;
|
|
5
5
|
export async function runPipeline(config) {
|
|
@@ -22,48 +22,73 @@ export async function runPipeline(config) {
|
|
|
22
22
|
// Display header
|
|
23
23
|
logHeader(config);
|
|
24
24
|
log(`Starting pipeline for: ${config.requirement}`);
|
|
25
|
-
// Step 1: Smart Branch Management
|
|
25
|
+
// Step 1: Smart Branch Management (also performs adaptive analysis if enabled)
|
|
26
26
|
const branchResult = await stepBranchManagement(config);
|
|
27
27
|
if (!branchResult.success) {
|
|
28
28
|
console.log(chalk.red('\nBranch management step failed. Exiting.'));
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
|
+
// Extract adaptive analysis for step decisions
|
|
32
|
+
const adaptive = branchResult.adaptiveAnalysis;
|
|
33
|
+
const rec = adaptive?.stepRecommendation;
|
|
31
34
|
// Step 2: Implement
|
|
32
35
|
const implSuccess = await stepImplement(config);
|
|
33
36
|
if (!implSuccess) {
|
|
34
37
|
console.log(chalk.red('\nImplementation step failed. Exiting.'));
|
|
35
38
|
process.exit(1);
|
|
36
39
|
}
|
|
37
|
-
// Step 3: Simplify
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(chalk.red('\nSimplification step failed. Exiting.'));
|
|
41
|
-
process.exit(1);
|
|
40
|
+
// Step 3: Simplify (can be skipped by adaptive execution)
|
|
41
|
+
if (rec?.skipSimplify) {
|
|
42
|
+
logInfo('Simplify step skipped (adaptive execution)');
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log(chalk.red('\nReview step failed. Exiting.'));
|
|
44
|
+
else {
|
|
45
|
+
const simplifySuccess = await stepSimplify(config);
|
|
46
|
+
if (!simplifySuccess) {
|
|
47
|
+
console.log(chalk.red('\nSimplification step failed. Exiting.'));
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
}
|
|
51
|
+
// Step 4: Review loop (can be skipped or adjusted by adaptive execution)
|
|
52
|
+
if (rec?.skipReview) {
|
|
53
|
+
logInfo('Review step skipped (adaptive execution)');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const reviewIterations = rec?.reviewIterations ?? config.reviewIterations;
|
|
57
|
+
for (let i = 1; i <= reviewIterations; i++) {
|
|
58
|
+
const reviewResult = await stepReview(i, config, rec?.reviewDepth);
|
|
59
|
+
if (!reviewResult.success) {
|
|
60
|
+
console.log(chalk.red('\nReview step failed. Exiting.'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
// Skip remaining reviews if no issues were found
|
|
64
|
+
if (reviewResult.noIssuesFound) {
|
|
65
|
+
console.log(chalk.green('\n✓ Code review passed - no issues found, skipping remaining reviews'));
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
54
68
|
}
|
|
55
69
|
}
|
|
56
|
-
// Step 5: SOLID & Clean Code
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log(chalk.red('\nSOLID review step failed. Exiting.'));
|
|
60
|
-
process.exit(1);
|
|
70
|
+
// Step 5: SOLID & Clean Code (can be skipped by adaptive execution)
|
|
71
|
+
if (rec?.skipSolid) {
|
|
72
|
+
logInfo('SOLID review step skipped (adaptive execution)');
|
|
61
73
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
else {
|
|
75
|
+
const solidSuccess = await stepSolidCleanCode(config);
|
|
76
|
+
if (!solidSuccess) {
|
|
77
|
+
console.log(chalk.red('\nSOLID review step failed. Exiting.'));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Step 6: Test (can be skipped by adaptive execution or CLI flag)
|
|
82
|
+
const skipTestsReason = rec?.skipTests ? 'adaptive execution' : config.skipTests ? '--skip-tests' : null;
|
|
83
|
+
if (skipTestsReason) {
|
|
84
|
+
logInfo(`Test step skipped (${skipTestsReason})`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const testSuccess = await stepTest(config);
|
|
88
|
+
if (!testSuccess) {
|
|
89
|
+
console.log(chalk.red('\nTest step failed. Exiting.'));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
67
92
|
}
|
|
68
93
|
// Step 7: Commit
|
|
69
94
|
const commitSuccess = await stepCommit(config);
|
|
@@ -71,11 +96,16 @@ export async function runPipeline(config) {
|
|
|
71
96
|
console.log(chalk.red('\nCommit step failed. Exiting.'));
|
|
72
97
|
process.exit(1);
|
|
73
98
|
}
|
|
74
|
-
// Step 8: Changelog
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
// Step 8: Changelog (can be skipped by adaptive execution)
|
|
100
|
+
if (rec?.skipChangelog) {
|
|
101
|
+
logInfo('Changelog step skipped (adaptive execution)');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const changelogSuccess = await stepChangelog(config);
|
|
105
|
+
if (!changelogSuccess) {
|
|
106
|
+
console.log(chalk.red('\nChangelog step failed. Exiting.'));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
79
109
|
}
|
|
80
110
|
console.log(chalk.green('\n✓ Pipeline completed successfully\n'));
|
|
81
111
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type BranchAnalysis } from '../utils/branchAnalyzer.js';
|
|
2
|
+
import { type AdaptiveAnalysis } from '../utils/stepAnalyzer.js';
|
|
2
3
|
import type { Config } from '../config.js';
|
|
3
4
|
export interface BranchResult {
|
|
4
5
|
success: boolean;
|
|
5
6
|
branchCreated: boolean;
|
|
6
7
|
branchName: string;
|
|
7
8
|
analysis: BranchAnalysis | null;
|
|
9
|
+
adaptiveAnalysis: AdaptiveAnalysis | null;
|
|
8
10
|
}
|
|
9
11
|
export declare function stepBranchManagement(config: Config): Promise<BranchResult>;
|
|
@@ -1,29 +1,81 @@
|
|
|
1
1
|
import { logStep, logInfo, logSuccess, logWarning, logDryRun } from '../logger.js';
|
|
2
2
|
import { getGitState, branchExists, createBranch, findSimilarBranches } from '../utils/git.js';
|
|
3
3
|
import { analyzeRequirement } from '../utils/branchAnalyzer.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { analyzeSteps } from '../utils/stepAnalyzer.js';
|
|
5
|
+
function createResult(branchName, analysis = null, branchCreated = false, adaptiveAnalysis = null) {
|
|
6
|
+
return { success: true, branchCreated, branchName, analysis, adaptiveAnalysis };
|
|
7
|
+
}
|
|
8
|
+
function logAdaptiveAnalysis(analysis) {
|
|
9
|
+
const { complexity, riskLevel, affectedAreas, stepRecommendation: rec } = analysis;
|
|
10
|
+
logInfo(`Complexity: ${complexity}, Risk: ${riskLevel}`);
|
|
11
|
+
logInfo(`Affected areas: ${affectedAreas.join(', ')}`);
|
|
12
|
+
const skipMap = [
|
|
13
|
+
[rec.skipSimplify, 'simplify'],
|
|
14
|
+
[rec.skipReview, 'review'],
|
|
15
|
+
[rec.skipSolid, 'SOLID'],
|
|
16
|
+
[rec.skipTests, 'tests'],
|
|
17
|
+
[rec.skipChangelog, 'changelog'],
|
|
18
|
+
];
|
|
19
|
+
const skipped = skipMap.filter(([skip]) => skip).map(([, name]) => name);
|
|
20
|
+
if (skipped.length > 0) {
|
|
21
|
+
logInfo(`Steps to skip: ${skipped.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
logInfo('All steps will be executed');
|
|
25
|
+
}
|
|
26
|
+
const iterationLabel = rec.reviewIterations === 1 ? 'iteration' : 'iterations';
|
|
27
|
+
logInfo(`Review depth: ${rec.reviewDepth} (${rec.reviewIterations} ${iterationLabel})`);
|
|
28
|
+
logInfo(`Reasoning: ${rec.reasoning}`);
|
|
29
|
+
}
|
|
30
|
+
async function performAdaptiveAnalysis(config) {
|
|
31
|
+
logInfo('Adaptive execution enabled - analyzing step requirements...');
|
|
32
|
+
const analysis = await analyzeSteps(config.requirement, config);
|
|
33
|
+
logAdaptiveAnalysis(analysis);
|
|
34
|
+
return analysis;
|
|
35
|
+
}
|
|
36
|
+
function branchMatchesRequirement(currentBranch, analysis) {
|
|
37
|
+
const currentLower = currentBranch.toLowerCase();
|
|
38
|
+
const suggestedLower = analysis.shortDescription.toLowerCase();
|
|
39
|
+
// Extract keywords from both branches
|
|
40
|
+
const currentKeywords = currentLower.split(/[-_/]/).filter((k) => k.length > 2);
|
|
41
|
+
const suggestedKeywords = suggestedLower.split(/[-_/]/).filter((k) => k.length > 2);
|
|
42
|
+
// Check if any significant keywords overlap
|
|
43
|
+
const overlap = currentKeywords.some((ck) => suggestedKeywords.some((sk) => ck.includes(sk) || sk.includes(ck)));
|
|
44
|
+
// Also check if the branch prefix matches the change type
|
|
45
|
+
const prefixMatches = currentLower.startsWith(analysis.branchPrefix);
|
|
46
|
+
return overlap || (prefixMatches && suggestedKeywords.length === 0);
|
|
6
47
|
}
|
|
7
48
|
export async function stepBranchManagement(config) {
|
|
8
49
|
logStep('1/8', 'SMART BRANCH MANAGEMENT');
|
|
50
|
+
const adaptiveAnalysis = config.adaptiveExecution ? await performAdaptiveAnalysis(config) : null;
|
|
9
51
|
if (config.skipBranchManagement) {
|
|
10
52
|
logInfo('Branch management skipped (--skip-branch-management)');
|
|
11
|
-
return createResult('');
|
|
53
|
+
return createResult('', null, false, adaptiveAnalysis);
|
|
12
54
|
}
|
|
13
55
|
const gitState = getGitState();
|
|
14
56
|
logInfo(`Current branch: ${gitState.currentBranch}`);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return createResult(gitState.currentBranch);
|
|
18
|
-
}
|
|
19
|
-
logInfo('On main branch - analyzing requirement...');
|
|
20
|
-
// Analyze requirement to determine change type
|
|
57
|
+
// Always analyze the requirement first
|
|
58
|
+
logInfo('Analyzing requirement...');
|
|
21
59
|
const analysis = await analyzeRequirement(config.requirement, config);
|
|
22
60
|
logInfo(`Change type: ${analysis.changeType}`);
|
|
23
61
|
logInfo(`Suggested branch: ${analysis.suggestedBranchName}`);
|
|
62
|
+
if (!gitState.isMainBranch) {
|
|
63
|
+
// Check if current branch matches the requirement
|
|
64
|
+
if (branchMatchesRequirement(gitState.currentBranch, analysis)) {
|
|
65
|
+
logInfo(`Current branch '${gitState.currentBranch}' matches requirement - continuing`);
|
|
66
|
+
return createResult(gitState.currentBranch, analysis, false, adaptiveAnalysis);
|
|
67
|
+
}
|
|
68
|
+
// Branch doesn't match - warn and suggest switching
|
|
69
|
+
logWarning(`Current branch '${gitState.currentBranch}' may not match this requirement`);
|
|
70
|
+
logWarning(`Suggested branch for this requirement: ${analysis.suggestedBranchName}`);
|
|
71
|
+
logWarning('Consider switching to main and re-running, or use --skip-branch-management to continue');
|
|
72
|
+
// Continue on current branch but flag the mismatch
|
|
73
|
+
return createResult(gitState.currentBranch, analysis, false, adaptiveAnalysis);
|
|
74
|
+
}
|
|
75
|
+
// On main branch
|
|
24
76
|
if (analysis.isTrivial) {
|
|
25
77
|
logInfo('Trivial change detected - staying on current branch');
|
|
26
|
-
return createResult(gitState.currentBranch, analysis);
|
|
78
|
+
return createResult(gitState.currentBranch, analysis, false, adaptiveAnalysis);
|
|
27
79
|
}
|
|
28
80
|
// Check for similar/conflicting branches
|
|
29
81
|
const conflicts = findSimilarBranches(analysis.shortDescription, gitState.remoteBranches);
|
|
@@ -43,14 +95,14 @@ export async function stepBranchManagement(config) {
|
|
|
43
95
|
}
|
|
44
96
|
if (config.dryRun) {
|
|
45
97
|
logDryRun(`git checkout -b ${branchName}`);
|
|
46
|
-
return createResult(branchName, analysis);
|
|
98
|
+
return createResult(branchName, analysis, false, adaptiveAnalysis);
|
|
47
99
|
}
|
|
48
100
|
logInfo(`Creating branch: ${branchName}`);
|
|
49
101
|
const created = createBranch(branchName);
|
|
50
102
|
if (created) {
|
|
51
103
|
logSuccess(`Switched to new branch '${branchName}'`);
|
|
52
|
-
return createResult(branchName, analysis, true);
|
|
104
|
+
return createResult(branchName, analysis, true, adaptiveAnalysis);
|
|
53
105
|
}
|
|
54
106
|
logWarning(`Failed to create branch '${branchName}' - continuing on current branch`);
|
|
55
|
-
return createResult(gitState.currentBranch, analysis);
|
|
107
|
+
return createResult(gitState.currentBranch, analysis, false, adaptiveAnalysis);
|
|
56
108
|
}
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { stepBranchManagement, type BranchResult } from './branchManagement.js';
|
|
2
2
|
export { stepImplement } from './implement.js';
|
|
3
3
|
export { stepSimplify } from './simplify.js';
|
|
4
|
-
export { stepReview, type ReviewResult } from './review.js';
|
|
4
|
+
export { stepReview, type ReviewResult, type ReviewDepth } from './review.js';
|
|
5
5
|
export { stepSolidCleanCode } from './solid.js';
|
|
6
6
|
export { stepTest } from './test.js';
|
|
7
7
|
export { stepCommit } from './commit.js';
|
package/dist/steps/review.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export interface ReviewResult {
|
|
|
3
3
|
success: boolean;
|
|
4
4
|
noIssuesFound: boolean;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export type ReviewDepth = 'minimal' | 'standard' | 'thorough';
|
|
7
|
+
export declare function stepReview(iteration: number, config: Config, depth?: ReviewDepth): Promise<ReviewResult>;
|
package/dist/steps/review.js
CHANGED
|
@@ -14,9 +14,37 @@ const NO_ISSUES_PATTERNS = [
|
|
|
14
14
|
function detectNoIssues(output) {
|
|
15
15
|
return NO_ISSUES_PATTERNS.some((pattern) => pattern.test(output));
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function getPromptForDepth(depth) {
|
|
18
|
+
switch (depth) {
|
|
19
|
+
case 'minimal':
|
|
20
|
+
return `Quick review of the changes:
|
|
21
|
+
|
|
22
|
+
## Focus Areas:
|
|
23
|
+
1. **Bugs**: Only obvious logic errors or crashes
|
|
24
|
+
2. **Security**: Only critical vulnerabilities
|
|
25
|
+
|
|
26
|
+
## Action Required:
|
|
27
|
+
- FIX only critical issues
|
|
28
|
+
- If code is acceptable, say "No issues found" or "LGTM"`;
|
|
29
|
+
case 'thorough':
|
|
30
|
+
return `Thorough code review of all changes:
|
|
31
|
+
|
|
32
|
+
## Comprehensive Review Points:
|
|
33
|
+
1. **Bugs**: Logic errors, edge cases, off-by-one, null refs, race conditions, error handling
|
|
34
|
+
2. **Security**: Input validation, injection, auth issues, data exposure, OWASP top 10
|
|
35
|
+
3. **Performance**: N+1 queries, unnecessary loops, memory leaks, blocking operations, caching
|
|
36
|
+
4. **Maintainability**: Code clarity, naming, complexity, duplication, single responsibility
|
|
37
|
+
5. **Testing**: Are edge cases testable? Missing test scenarios?
|
|
38
|
+
6. **Architecture**: Does this follow project patterns? Any coupling issues?
|
|
39
|
+
|
|
40
|
+
## Action Required:
|
|
41
|
+
- FIX any issues found immediately
|
|
42
|
+
- Be thorough and specific about what you changed and why
|
|
43
|
+
- Suggest improvements even if not critical
|
|
44
|
+
- If code is clean, say "No issues found" or "LGTM"`;
|
|
45
|
+
case 'standard':
|
|
46
|
+
default:
|
|
47
|
+
return `Review and improve the changes:
|
|
20
48
|
|
|
21
49
|
## Critical Review Points:
|
|
22
50
|
1. **Bugs**: Logic errors, off-by-one, null refs, race conditions
|
|
@@ -28,6 +56,12 @@ export async function stepReview(iteration, config) {
|
|
|
28
56
|
- FIX any issues found immediately
|
|
29
57
|
- Be specific about what you changed and why
|
|
30
58
|
- If code is clean, say "No issues found" or "LGTM"`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function stepReview(iteration, config, depth = 'standard') {
|
|
62
|
+
const depthLabel = depth !== 'standard' ? ` [${depth}]` : '';
|
|
63
|
+
logStep('4/8', `CODE REVIEW (Round ${iteration}/${config.reviewIterations})${depthLabel}`);
|
|
64
|
+
const prompt = getPromptForDepth(depth);
|
|
31
65
|
const result = await runClaude({ prompt, continueConversation: true }, config);
|
|
32
66
|
const noIssuesFound = result.success && detectNoIssues(result.output);
|
|
33
67
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Config } from '../config.js';
|
|
2
|
+
import type { ChangeType } from './branchAnalyzer.js';
|
|
3
|
+
export interface StepRecommendation {
|
|
4
|
+
skipSimplify: boolean;
|
|
5
|
+
skipReview: boolean;
|
|
6
|
+
skipSolid: boolean;
|
|
7
|
+
skipTests: boolean;
|
|
8
|
+
skipChangelog: boolean;
|
|
9
|
+
reviewDepth: 'minimal' | 'standard' | 'thorough';
|
|
10
|
+
reviewIterations: number;
|
|
11
|
+
reasoning: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AdaptiveAnalysis {
|
|
14
|
+
changeType: ChangeType;
|
|
15
|
+
isTrivial: boolean;
|
|
16
|
+
complexity: 'low' | 'medium' | 'high';
|
|
17
|
+
riskLevel: 'low' | 'medium' | 'high';
|
|
18
|
+
affectedAreas: string[];
|
|
19
|
+
stepRecommendation: StepRecommendation;
|
|
20
|
+
}
|
|
21
|
+
export declare function analyzeSteps(requirement: string, config: Config): Promise<AdaptiveAnalysis>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
const MIN_REVIEW_ITERATIONS = 1;
|
|
3
|
+
const MAX_REVIEW_ITERATIONS = 3;
|
|
4
|
+
const DEFAULT_REVIEW_ITERATIONS = 2;
|
|
5
|
+
const ADAPTIVE_ANALYSIS_PROMPT = `Analyze the following requirement and determine which pipeline steps should be executed.
|
|
6
|
+
|
|
7
|
+
Requirement: "{REQUIREMENT}"
|
|
8
|
+
|
|
9
|
+
Respond in EXACTLY this format (no markdown, no extra text):
|
|
10
|
+
CHANGE_TYPE: <one of: feature, fix, refactor, docs, chore, trivial>
|
|
11
|
+
IS_TRIVIAL: <true or false>
|
|
12
|
+
COMPLEXITY: <one of: low, medium, high>
|
|
13
|
+
RISK_LEVEL: <one of: low, medium, high>
|
|
14
|
+
AFFECTED_AREAS: <comma-separated list: code, tests, config, docs, build, deps>
|
|
15
|
+
SKIP_SIMPLIFY: <true or false>
|
|
16
|
+
SKIP_REVIEW: <true or false>
|
|
17
|
+
SKIP_SOLID: <true or false>
|
|
18
|
+
SKIP_TESTS: <true or false>
|
|
19
|
+
SKIP_CHANGELOG: <true or false>
|
|
20
|
+
REVIEW_DEPTH: <one of: minimal, standard, thorough>
|
|
21
|
+
REVIEW_ITERATIONS: <1, 2, or 3>
|
|
22
|
+
REASONING: <brief one-line explanation for the recommendations>
|
|
23
|
+
|
|
24
|
+
Guidelines for step skipping:
|
|
25
|
+
- Documentation-only changes (docs): skip tests, simplify, and SOLID review
|
|
26
|
+
- Config file changes (chore for config): skip SOLID review, reduce review iterations
|
|
27
|
+
- Typo fixes or trivial changes: skip most steps, minimal review
|
|
28
|
+
- Refactoring with no behavior change: reduce test focus, thorough SOLID review
|
|
29
|
+
- New features with business logic: all steps, thorough review
|
|
30
|
+
- Bug fixes: standard review, focus on tests
|
|
31
|
+
- Build/CI changes: skip SOLID, skip tests unless test config changed
|
|
32
|
+
|
|
33
|
+
Risk assessment:
|
|
34
|
+
- Low: docs, typos, comments, formatting
|
|
35
|
+
- Medium: config changes, refactoring, minor features
|
|
36
|
+
- High: new features with business logic, bug fixes, dependency changes, security-related
|
|
37
|
+
|
|
38
|
+
Complexity assessment:
|
|
39
|
+
- Low: single file, few lines, isolated change
|
|
40
|
+
- Medium: multiple files, moderate scope
|
|
41
|
+
- High: many files, architectural impact, complex logic`;
|
|
42
|
+
const DEFAULT_STEP_RECOMMENDATION = {
|
|
43
|
+
skipSimplify: false,
|
|
44
|
+
skipReview: false,
|
|
45
|
+
skipSolid: false,
|
|
46
|
+
skipTests: false,
|
|
47
|
+
skipChangelog: false,
|
|
48
|
+
reviewDepth: 'standard',
|
|
49
|
+
reviewIterations: DEFAULT_REVIEW_ITERATIONS,
|
|
50
|
+
reasoning: 'Default configuration - standard pipeline execution',
|
|
51
|
+
};
|
|
52
|
+
const DEFAULT_ADAPTIVE_ANALYSIS = {
|
|
53
|
+
changeType: 'feature',
|
|
54
|
+
isTrivial: false,
|
|
55
|
+
complexity: 'medium',
|
|
56
|
+
riskLevel: 'medium',
|
|
57
|
+
affectedAreas: ['code'],
|
|
58
|
+
stepRecommendation: DEFAULT_STEP_RECOMMENDATION,
|
|
59
|
+
};
|
|
60
|
+
function extractField(output, field, pattern) {
|
|
61
|
+
const regex = new RegExp(`${field}:\\s*(${pattern})`, 'i');
|
|
62
|
+
const match = output.match(regex);
|
|
63
|
+
return match ? match[1].toLowerCase() : null;
|
|
64
|
+
}
|
|
65
|
+
function extractBool(output, field) {
|
|
66
|
+
const value = extractField(output, field, 'true|false');
|
|
67
|
+
return value !== null ? value === 'true' : null;
|
|
68
|
+
}
|
|
69
|
+
function parseAdaptiveResponse(output) {
|
|
70
|
+
const changeType = extractField(output, 'CHANGE_TYPE', 'feature|fix|refactor|docs|chore|trivial') ?? 'feature';
|
|
71
|
+
const isTrivial = extractBool(output, 'IS_TRIVIAL') ?? false;
|
|
72
|
+
const complexity = extractField(output, 'COMPLEXITY', 'low|medium|high') ?? 'medium';
|
|
73
|
+
const riskLevel = extractField(output, 'RISK_LEVEL', 'low|medium|high') ?? 'medium';
|
|
74
|
+
const areasMatch = output.match(/AFFECTED_AREAS:\s*(.+)/i);
|
|
75
|
+
const affectedAreas = areasMatch ? areasMatch[1].split(',').map((a) => a.trim().toLowerCase()) : ['code'];
|
|
76
|
+
const reviewDepth = extractField(output, 'REVIEW_DEPTH', 'minimal|standard|thorough') ?? 'standard';
|
|
77
|
+
const iterationsMatch = output.match(/REVIEW_ITERATIONS:\s*(\d+)/i);
|
|
78
|
+
const reviewIterations = iterationsMatch
|
|
79
|
+
? Math.min(Math.max(parseInt(iterationsMatch[1], 10), MIN_REVIEW_ITERATIONS), MAX_REVIEW_ITERATIONS)
|
|
80
|
+
: DEFAULT_REVIEW_ITERATIONS;
|
|
81
|
+
const reasoningMatch = output.match(/REASONING:\s*(.+)/i);
|
|
82
|
+
const reasoning = reasoningMatch ? reasoningMatch[1].trim() : 'AI-analyzed step configuration';
|
|
83
|
+
return {
|
|
84
|
+
changeType,
|
|
85
|
+
isTrivial,
|
|
86
|
+
complexity,
|
|
87
|
+
riskLevel,
|
|
88
|
+
affectedAreas,
|
|
89
|
+
stepRecommendation: {
|
|
90
|
+
skipSimplify: extractBool(output, 'SKIP_SIMPLIFY') ?? false,
|
|
91
|
+
skipReview: extractBool(output, 'SKIP_REVIEW') ?? false,
|
|
92
|
+
skipSolid: extractBool(output, 'SKIP_SOLID') ?? false,
|
|
93
|
+
skipTests: extractBool(output, 'SKIP_TESTS') ?? false,
|
|
94
|
+
skipChangelog: extractBool(output, 'SKIP_CHANGELOG') ?? false,
|
|
95
|
+
reviewDepth,
|
|
96
|
+
reviewIterations,
|
|
97
|
+
reasoning,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export async function analyzeSteps(requirement, config) {
|
|
102
|
+
const prompt = ADAPTIVE_ANALYSIS_PROMPT.replace('{REQUIREMENT}', requirement);
|
|
103
|
+
const args = ['--print'];
|
|
104
|
+
if (config.dangerouslySkipPermissions) {
|
|
105
|
+
args.push('--dangerously-skip-permissions');
|
|
106
|
+
}
|
|
107
|
+
args.push(prompt);
|
|
108
|
+
if (config.dryRun) {
|
|
109
|
+
return {
|
|
110
|
+
...DEFAULT_ADAPTIVE_ANALYSIS,
|
|
111
|
+
stepRecommendation: {
|
|
112
|
+
...DEFAULT_STEP_RECOMMENDATION,
|
|
113
|
+
reasoning: 'Dry run mode - using default configuration',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
let output = '';
|
|
119
|
+
const child = spawn('claude', args, {
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
env: process.env,
|
|
122
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
123
|
+
});
|
|
124
|
+
if (child.stdout) {
|
|
125
|
+
child.stdout.on('data', (data) => {
|
|
126
|
+
output += data.toString();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
child.on('close', (exitCode) => {
|
|
130
|
+
if (exitCode === 0 && output.trim()) {
|
|
131
|
+
resolve(parseAdaptiveResponse(output));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
resolve(DEFAULT_ADAPTIVE_ANALYSIS);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
child.on('error', () => {
|
|
138
|
+
resolve(DEFAULT_ADAPTIVE_ANALYSIS);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update notifier - checks for new versions and prompts users to update
|
|
3
|
+
*/
|
|
4
|
+
export declare class UpdateNotifier {
|
|
5
|
+
private packageName;
|
|
6
|
+
private currentVersion;
|
|
7
|
+
private checkInterval;
|
|
8
|
+
private cacheFile;
|
|
9
|
+
constructor(packageName: string, currentVersion: string, checkInterval?: number);
|
|
10
|
+
/**
|
|
11
|
+
* Check for updates and notify user if a new version is available.
|
|
12
|
+
*/
|
|
13
|
+
notify(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Check if we should check for updates (based on cache and interval)
|
|
16
|
+
*/
|
|
17
|
+
private shouldCheckForUpdates;
|
|
18
|
+
/**
|
|
19
|
+
* Fetch latest version from npm registry
|
|
20
|
+
*/
|
|
21
|
+
private fetchLatestVersion;
|
|
22
|
+
/**
|
|
23
|
+
* Load version info from cache
|
|
24
|
+
*/
|
|
25
|
+
private loadVersionInfo;
|
|
26
|
+
/**
|
|
27
|
+
* Save version info to cache
|
|
28
|
+
*/
|
|
29
|
+
private saveVersionInfo;
|
|
30
|
+
/**
|
|
31
|
+
* Compare two semantic versions
|
|
32
|
+
*/
|
|
33
|
+
private isNewerVersion;
|
|
34
|
+
/**
|
|
35
|
+
* Display update notification message
|
|
36
|
+
*/
|
|
37
|
+
private displayUpdateMessage;
|
|
38
|
+
/**
|
|
39
|
+
* Detect package manager (npm, yarn, pnpm)
|
|
40
|
+
*/
|
|
41
|
+
private detectPackageManager;
|
|
42
|
+
/**
|
|
43
|
+
* Center text within given width (accounts for ANSI codes)
|
|
44
|
+
*/
|
|
45
|
+
private centerText;
|
|
46
|
+
/**
|
|
47
|
+
* Pad text to given width (accounts for ANSI codes)
|
|
48
|
+
*/
|
|
49
|
+
private padText;
|
|
50
|
+
/**
|
|
51
|
+
* Strip ANSI escape codes from string
|
|
52
|
+
*/
|
|
53
|
+
private stripAnsi;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create and run update notifier
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkForUpdates(packageName: string, currentVersion: string): Promise<void>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update notifier - checks for new versions and prompts users to update
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
export class UpdateNotifier {
|
|
9
|
+
packageName;
|
|
10
|
+
currentVersion;
|
|
11
|
+
checkInterval;
|
|
12
|
+
cacheFile;
|
|
13
|
+
constructor(packageName, currentVersion, checkInterval = 24 * 60 * 60 * 1000) {
|
|
14
|
+
this.packageName = packageName;
|
|
15
|
+
this.currentVersion = currentVersion;
|
|
16
|
+
this.checkInterval = checkInterval; // Default: 24 hours
|
|
17
|
+
this.cacheFile = path.join(os.tmpdir(), `.${packageName.replace('/', '-')}-update-check.json`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check for updates and notify user if a new version is available.
|
|
21
|
+
*/
|
|
22
|
+
async notify() {
|
|
23
|
+
try {
|
|
24
|
+
const shouldCheck = await this.shouldCheckForUpdates();
|
|
25
|
+
if (shouldCheck) {
|
|
26
|
+
const latestVersion = await this.fetchLatestVersion();
|
|
27
|
+
if (latestVersion) {
|
|
28
|
+
await this.saveVersionInfo(latestVersion);
|
|
29
|
+
if (this.isNewerVersion(latestVersion, this.currentVersion)) {
|
|
30
|
+
this.displayUpdateMessage(latestVersion);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const cachedInfo = await this.loadVersionInfo();
|
|
36
|
+
if (cachedInfo && this.isNewerVersion(cachedInfo.latest, this.currentVersion)) {
|
|
37
|
+
this.displayUpdateMessage(cachedInfo.latest);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Silently fail - don't disrupt user experience
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if we should check for updates (based on cache and interval)
|
|
47
|
+
*/
|
|
48
|
+
async shouldCheckForUpdates() {
|
|
49
|
+
try {
|
|
50
|
+
const versionInfo = await this.loadVersionInfo();
|
|
51
|
+
if (!versionInfo) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const timeSinceLastCheck = Date.now() - versionInfo.lastChecked;
|
|
55
|
+
return timeSinceLastCheck >= this.checkInterval;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fetch latest version from npm registry
|
|
63
|
+
*/
|
|
64
|
+
async fetchLatestVersion() {
|
|
65
|
+
try {
|
|
66
|
+
const registryUrl = `https://registry.npmjs.org/${this.packageName}/latest`;
|
|
67
|
+
const response = await fetch(registryUrl, {
|
|
68
|
+
headers: { 'Accept': 'application/json' },
|
|
69
|
+
signal: AbortSignal.timeout(5000),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
return data.version;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load version info from cache
|
|
83
|
+
*/
|
|
84
|
+
async loadVersionInfo() {
|
|
85
|
+
try {
|
|
86
|
+
const data = await fs.readFile(this.cacheFile, 'utf-8');
|
|
87
|
+
return JSON.parse(data);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Save version info to cache
|
|
95
|
+
*/
|
|
96
|
+
async saveVersionInfo(latestVersion) {
|
|
97
|
+
const versionInfo = {
|
|
98
|
+
current: this.currentVersion,
|
|
99
|
+
latest: latestVersion,
|
|
100
|
+
lastChecked: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
await fs.writeFile(this.cacheFile, JSON.stringify(versionInfo, null, 2), 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Ignore cache write errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compare two semantic versions
|
|
111
|
+
*/
|
|
112
|
+
isNewerVersion(latest, current) {
|
|
113
|
+
const parseVersion = (version) => {
|
|
114
|
+
return version.replace(/^v/, '').split('.').map(Number);
|
|
115
|
+
};
|
|
116
|
+
const latestParts = parseVersion(latest);
|
|
117
|
+
const currentParts = parseVersion(current);
|
|
118
|
+
for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
|
|
119
|
+
const latestPart = latestParts[i] || 0;
|
|
120
|
+
const currentPart = currentParts[i] || 0;
|
|
121
|
+
if (latestPart > currentPart) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (latestPart < currentPart) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Display update notification message
|
|
132
|
+
*/
|
|
133
|
+
displayUpdateMessage(latestVersion) {
|
|
134
|
+
const boxWidth = 60;
|
|
135
|
+
const border = '═'.repeat(boxWidth);
|
|
136
|
+
const packageManager = this.detectPackageManager();
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(chalk.yellow(`╔${border}╗`));
|
|
139
|
+
console.log(chalk.yellow('║') + ' '.repeat(boxWidth) + chalk.yellow('║'));
|
|
140
|
+
console.log(chalk.yellow('║') + this.centerText(chalk.bold('Update Available!'), boxWidth) + chalk.yellow('║'));
|
|
141
|
+
console.log(chalk.yellow('║') + ' '.repeat(boxWidth) + chalk.yellow('║'));
|
|
142
|
+
console.log(chalk.yellow('║') + this.centerText(`${chalk.red(this.currentVersion)} → ${chalk.green(latestVersion)}`, boxWidth) + chalk.yellow('║'));
|
|
143
|
+
console.log(chalk.yellow('║') + ' '.repeat(boxWidth) + chalk.yellow('║'));
|
|
144
|
+
console.log(chalk.yellow('║') + this.padText(' Run to update:', boxWidth) + chalk.yellow('║'));
|
|
145
|
+
console.log(chalk.yellow('║') + this.padText(chalk.cyan(` ${packageManager.global} ${this.packageName}`), boxWidth) + chalk.yellow('║'));
|
|
146
|
+
console.log(chalk.yellow('║') + ' '.repeat(boxWidth) + chalk.yellow('║'));
|
|
147
|
+
console.log(chalk.yellow(`╚${border}╝`));
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Detect package manager (npm, yarn, pnpm)
|
|
152
|
+
*/
|
|
153
|
+
detectPackageManager() {
|
|
154
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
155
|
+
if (userAgent.includes('yarn')) {
|
|
156
|
+
return { global: 'yarn global add', local: 'yarn add' };
|
|
157
|
+
}
|
|
158
|
+
if (userAgent.includes('pnpm')) {
|
|
159
|
+
return { global: 'pnpm add -g', local: 'pnpm add' };
|
|
160
|
+
}
|
|
161
|
+
return { global: 'npm install -g', local: 'npm install' };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Center text within given width (accounts for ANSI codes)
|
|
165
|
+
*/
|
|
166
|
+
centerText(text, width) {
|
|
167
|
+
const visibleLength = this.stripAnsi(text).length;
|
|
168
|
+
const padding = Math.max(0, width - visibleLength);
|
|
169
|
+
const leftPad = Math.floor(padding / 2);
|
|
170
|
+
const rightPad = padding - leftPad;
|
|
171
|
+
return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Pad text to given width (accounts for ANSI codes)
|
|
175
|
+
*/
|
|
176
|
+
padText(text, width) {
|
|
177
|
+
const visibleLength = this.stripAnsi(text).length;
|
|
178
|
+
return text + ' '.repeat(Math.max(0, width - visibleLength));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Strip ANSI escape codes from string
|
|
182
|
+
*/
|
|
183
|
+
stripAnsi(str) {
|
|
184
|
+
// eslint-disable-next-line no-control-regex
|
|
185
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Create and run update notifier
|
|
190
|
+
*/
|
|
191
|
+
export async function checkForUpdates(packageName, currentVersion) {
|
|
192
|
+
const notifier = new UpdateNotifier(packageName, currentVersion);
|
|
193
|
+
await notifier.notify();
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "software-engineer",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.13",
|
|
4
|
+
"description": "CLI that automates the full dev workflow with Claude AI - implement, review, test, and commit code with a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|