software-engineer 0.1.10 → 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 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
- console.log(chalk.green('║') + ' SOFTWARE FACTORY PIPELINE v2.0 ' + chalk.green('║'));
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
- const simplifySuccess = await stepSimplify(config);
39
- if (!simplifySuccess) {
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
- // Step 4: Review loop
44
- for (let i = 1; i <= config.reviewIterations; i++) {
45
- const reviewResult = await stepReview(i, config);
46
- if (!reviewResult.success) {
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
- // Skip remaining reviews if no issues were found
51
- if (reviewResult.noIssuesFound) {
52
- console.log(chalk.green('\n✓ Code review passed - no issues found, skipping remaining reviews'));
53
- break;
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
- const solidSuccess = await stepSolidCleanCode(config);
58
- if (!solidSuccess) {
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
- // Step 6: Test
63
- const testSuccess = await stepTest(config);
64
- if (!testSuccess) {
65
- console.log(chalk.red('\nTest step failed. Exiting.'));
66
- process.exit(1);
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
- const changelogSuccess = await stepChangelog(config);
76
- if (!changelogSuccess) {
77
- console.log(chalk.red('\nChangelog step failed. Exiting.'));
78
- process.exit(1);
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
- function createResult(branchName, analysis = null, branchCreated = false) {
5
- return { success: true, branchCreated, branchName, analysis };
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
- if (!gitState.isMainBranch) {
16
- logInfo(`Already on branch '${gitState.currentBranch}' - skipping branch creation`);
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
  }
@@ -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';
@@ -3,4 +3,5 @@ export interface ReviewResult {
3
3
  success: boolean;
4
4
  noIssuesFound: boolean;
5
5
  }
6
- export declare function stepReview(iteration: number, config: Config): Promise<ReviewResult>;
6
+ export type ReviewDepth = 'minimal' | 'standard' | 'thorough';
7
+ export declare function stepReview(iteration: number, config: Config, depth?: ReviewDepth): Promise<ReviewResult>;
@@ -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
- export async function stepReview(iteration, config) {
18
- logStep('4/8', `CODE REVIEW (Round ${iteration}/${config.reviewIterations})`);
19
- const prompt = `Review and improve the changes:
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.10",
4
- "description": "Software Factory Pipeline - Automate development workflow with Claude AI",
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": {
@@ -20,8 +20,16 @@
20
20
  "development",
21
21
  "code-review"
22
22
  ],
23
- "author": "",
23
+ "author": "Muthu Krishnan",
24
24
  "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/muthuspark/software-engineer.git"
28
+ },
29
+ "homepage": "https://github.com/muthuspark/software-engineer#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/muthuspark/software-engineer/issues"
32
+ },
25
33
  "dependencies": {
26
34
  "chalk": "^5.3.0",
27
35
  "commander": "^12.1.0"