software-engineer 0.1.10

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/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # software-engineer
2
+
3
+ A CLI tool that automates the software development workflow using Claude AI. It runs a 7-step pipeline to implement features, simplify code, review, ensure quality, test, commit, and update changelogs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g software-engineer
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ - Node.js >= 18.0.0
14
+ - [Claude CLI](https://github.com/anthropics/claude-code) installed and configured
15
+
16
+ ## Usage
17
+
18
+ ```bash
19
+ sf "<requirement>"
20
+ ```
21
+
22
+ ### Examples
23
+
24
+ ```bash
25
+ # Basic usage
26
+ sf "add user authentication with JWT"
27
+
28
+ # Custom review iterations
29
+ sf --reviews 3 "refactor database layer"
30
+
31
+ # Dry run to preview commands
32
+ sf --dry-run "add dark mode toggle"
33
+
34
+ # Skip tests and push
35
+ sf --skip-tests --skip-push "update README"
36
+
37
+ # Log output to file
38
+ sf --log pipeline.log "implement caching layer"
39
+ ```
40
+
41
+ ## Options
42
+
43
+ | Option | Description |
44
+ |--------|-------------|
45
+ | `-d, --dry-run` | Print commands without executing |
46
+ | `-r, --reviews <n>` | Number of review iterations (default: 2) |
47
+ | `--skip-tests` | Skip the testing step |
48
+ | `--skip-push` | Commit but don't push to remote |
49
+ | `--log <file>` | Log output to file |
50
+ | `--dangerously-skip-permissions` | Skip Claude permission prompts |
51
+ | `-h, --help` | Display help |
52
+ | `-V, --version` | Display version |
53
+
54
+ ## Environment Variables
55
+
56
+ All options can be set via environment variables:
57
+
58
+ | Variable | Description |
59
+ |----------|-------------|
60
+ | `SF_REVIEW_ITERATIONS` | Number of review iterations |
61
+ | `SF_DRY_RUN` | `true`/`false` for dry run |
62
+ | `SF_LOG_FILE` | Path to log file |
63
+ | `SF_SKIP_TESTS` | `true`/`false` to skip tests |
64
+ | `SF_SKIP_PUSH` | `true`/`false` to skip push |
65
+ | `SF_DANGEROUSLY_SKIP_PERMISSIONS` | `true`/`false` to skip Claude permissions |
66
+
67
+ Example:
68
+ ```bash
69
+ SF_REVIEW_ITERATIONS=3 sf "add feature X"
70
+ ```
71
+
72
+ ## Pipeline Steps
73
+
74
+ ### 1. Implement
75
+ Takes your requirement and asks Claude to implement it following project conventions.
76
+
77
+ ### 2. Code Simplification
78
+ Refines the implementation for clarity, consistency, and maintainability while preserving functionality:
79
+ - Follows project standards (ES modules, explicit types)
80
+ - Enhances clarity by avoiding nested ternaries
81
+ - Removes redundant abstractions
82
+
83
+ ### 3. Code Review (configurable iterations)
84
+ Reviews the implementation for:
85
+ - Bugs (logic errors, null refs, race conditions)
86
+ - Security issues (injection, auth problems)
87
+ - Performance (N+1 queries, memory leaks)
88
+ - Maintainability (clarity, naming, complexity)
89
+
90
+ ### 4. SOLID & Clean Code
91
+ Ensures compliance with:
92
+ - SOLID principles (SRP, OCP, LSP, ISP, DIP)
93
+ - Clean code practices (naming, small functions, no magic numbers)
94
+
95
+ ### 5. Testing
96
+ - Runs existing tests
97
+ - Adds new tests for changed code
98
+ - Verifies coverage
99
+
100
+ ### 6. Commit
101
+ Creates a well-formatted commit with:
102
+ - Conventional commit format
103
+ - Clear subject line
104
+ - Detailed body explaining why and what
105
+
106
+ ### 7. Changelog
107
+ Updates CHANGELOG.md following Keep a Changelog format.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,10 @@
1
+ import type { Config } from './config.js';
2
+ export interface ClaudeOptions {
3
+ prompt: string;
4
+ continueConversation?: boolean;
5
+ }
6
+ export interface ClaudeResult {
7
+ success: boolean;
8
+ output: string;
9
+ }
10
+ export declare function runClaude(options: ClaudeOptions, config: Config): Promise<ClaudeResult>;
package/dist/claude.js ADDED
@@ -0,0 +1,68 @@
1
+ import { spawn } from 'child_process';
2
+ import { logInfo, logSuccess, logError, logDryRun } from './logger.js';
3
+ // Exit codes
4
+ const EXIT_SUCCESS = 0;
5
+ const EXIT_INTERRUPTED = 130;
6
+ export async function runClaude(options, config) {
7
+ // Build the full prompt with exit instruction (like Python version)
8
+ const fullPrompt = `${options.prompt}. Once the work is completed, exit.`;
9
+ const args = [];
10
+ if (config.dangerouslySkipPermissions) {
11
+ args.push('--dangerously-skip-permissions');
12
+ }
13
+ if (options.continueConversation) {
14
+ args.push('-c');
15
+ }
16
+ // Pass prompt directly like Python version (not with -p flag)
17
+ args.push(fullPrompt);
18
+ if (config.dryRun) {
19
+ logDryRun(`claude ${args.join(' ')}`);
20
+ return { success: true, output: '' };
21
+ }
22
+ logInfo('Calling Claude...');
23
+ return new Promise((resolve) => {
24
+ let capturedOutput = '';
25
+ // Spawn Claude using child_process
26
+ // Use 'inherit' for stdin to allow interactive input
27
+ // Use 'pipe' for stdout/stderr to capture output while passing through
28
+ const child = spawn('claude', args, {
29
+ cwd: process.cwd(),
30
+ env: process.env,
31
+ stdio: ['inherit', 'pipe', 'pipe'],
32
+ });
33
+ // Capture and pass through stdout
34
+ if (child.stdout) {
35
+ child.stdout.on('data', (data) => {
36
+ const text = data.toString();
37
+ process.stdout.write(text);
38
+ capturedOutput += text;
39
+ });
40
+ }
41
+ // Capture and pass through stderr
42
+ if (child.stderr) {
43
+ child.stderr.on('data', (data) => {
44
+ process.stderr.write(data.toString());
45
+ });
46
+ }
47
+ // Handle process exit
48
+ child.on('close', (exitCode) => {
49
+ if (exitCode === EXIT_INTERRUPTED || exitCode === 2) {
50
+ logError('Claude interrupted');
51
+ process.exit(EXIT_INTERRUPTED);
52
+ }
53
+ else if (exitCode === EXIT_SUCCESS) {
54
+ logSuccess('Claude completed');
55
+ resolve({ success: true, output: capturedOutput });
56
+ }
57
+ else {
58
+ logError(`Claude exited with code ${exitCode}`);
59
+ resolve({ success: false, output: capturedOutput });
60
+ }
61
+ });
62
+ // Handle spawn errors
63
+ child.on('error', (err) => {
64
+ logError(`Failed to start Claude: ${err.message}`);
65
+ resolve({ success: false, output: '' });
66
+ });
67
+ });
68
+ }
@@ -0,0 +1,12 @@
1
+ export interface Config {
2
+ reviewIterations: number;
3
+ dryRun: boolean;
4
+ logFile?: string;
5
+ skipTests: boolean;
6
+ skipPush: boolean;
7
+ skipBranchManagement: boolean;
8
+ dangerouslySkipPermissions: boolean;
9
+ requirement: string;
10
+ }
11
+ export declare function loadConfigFromEnv(): Partial<Config>;
12
+ export declare function mergeConfig(envConfig: Partial<Config>, cliConfig: Partial<Config>): Config;
package/dist/config.js ADDED
@@ -0,0 +1,35 @@
1
+ const DEFAULT_REVIEW_ITERATIONS = 2;
2
+ function parseBoolEnv(value, defaultValue) {
3
+ if (value === undefined)
4
+ return defaultValue;
5
+ return value.toLowerCase() === 'true';
6
+ }
7
+ function parseIntEnv(value, defaultValue) {
8
+ if (value === undefined)
9
+ return defaultValue;
10
+ const parsed = parseInt(value, 10);
11
+ return isNaN(parsed) ? defaultValue : parsed;
12
+ }
13
+ export function loadConfigFromEnv() {
14
+ return {
15
+ reviewIterations: parseIntEnv(process.env.SF_REVIEW_ITERATIONS, DEFAULT_REVIEW_ITERATIONS),
16
+ dryRun: parseBoolEnv(process.env.SF_DRY_RUN, false),
17
+ logFile: process.env.SF_LOG_FILE || undefined,
18
+ skipTests: parseBoolEnv(process.env.SF_SKIP_TESTS, false),
19
+ skipPush: parseBoolEnv(process.env.SF_SKIP_PUSH, false),
20
+ skipBranchManagement: parseBoolEnv(process.env.SF_SKIP_BRANCH_MANAGEMENT, false),
21
+ dangerouslySkipPermissions: parseBoolEnv(process.env.SF_DANGEROUSLY_SKIP_PERMISSIONS, false),
22
+ };
23
+ }
24
+ export function mergeConfig(envConfig, cliConfig) {
25
+ return {
26
+ reviewIterations: cliConfig.reviewIterations ?? envConfig.reviewIterations ?? DEFAULT_REVIEW_ITERATIONS,
27
+ dryRun: cliConfig.dryRun ?? envConfig.dryRun ?? false,
28
+ logFile: cliConfig.logFile ?? envConfig.logFile,
29
+ skipTests: cliConfig.skipTests ?? envConfig.skipTests ?? false,
30
+ skipPush: cliConfig.skipPush ?? envConfig.skipPush ?? false,
31
+ skipBranchManagement: cliConfig.skipBranchManagement ?? envConfig.skipBranchManagement ?? false,
32
+ dangerouslySkipPermissions: cliConfig.dangerouslySkipPermissions ?? envConfig.dangerouslySkipPermissions ?? false,
33
+ requirement: cliConfig.requirement ?? '',
34
+ };
35
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ import { readFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+ import { loadConfigFromEnv, mergeConfig } from './config.js';
8
+ import { runPipeline } from './pipeline.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
12
+ const program = new Command();
13
+ program
14
+ .name('sf')
15
+ .description('Software Factory Pipeline - Automate development workflow with Claude AI')
16
+ .version(pkg.version)
17
+ .argument('<requirement>', 'The requirement or task to implement')
18
+ .option('-d, --dry-run', 'Print commands without executing')
19
+ .option('-r, --reviews <n>', 'Number of review iterations', '2')
20
+ .option('--skip-tests', 'Skip the testing step')
21
+ .option('--skip-push', 'Commit but do not push')
22
+ .option('--skip-branch-management', 'Skip smart branch management')
23
+ .option('--log <file>', 'Log output to file')
24
+ .option('--dangerously-skip-permissions', 'Pass flag to claude to skip permission prompts')
25
+ .action(async (requirement, options) => {
26
+ const envConfig = loadConfigFromEnv();
27
+ const cliConfig = {
28
+ requirement,
29
+ dryRun: options.dryRun ?? undefined,
30
+ reviewIterations: options.reviews ? parseInt(options.reviews, 10) : undefined,
31
+ skipTests: options.skipTests ?? undefined,
32
+ skipPush: options.skipPush ?? undefined,
33
+ skipBranchManagement: options.skipBranchManagement ?? undefined,
34
+ logFile: options.log ?? undefined,
35
+ dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? undefined,
36
+ };
37
+ const config = mergeConfig(envConfig, cliConfig);
38
+ if (!config.requirement) {
39
+ console.error(chalk.red('Error:') + ' No requirement provided');
40
+ program.help();
41
+ return; // Ensure we don't continue if help() doesn't exit
42
+ }
43
+ try {
44
+ await runPipeline(config);
45
+ }
46
+ catch (error) {
47
+ console.error(chalk.red('Pipeline failed:'), error);
48
+ process.exit(1);
49
+ }
50
+ });
51
+ program.parse();
@@ -0,0 +1,14 @@
1
+ export declare function setLogFile(path: string | undefined): void;
2
+ export declare function log(message: string): void;
3
+ export declare function logRaw(message: string): void;
4
+ export declare function logStep(stepNum: string, title: string): void;
5
+ export declare function logHeader(config: {
6
+ reviewIterations: number;
7
+ dryRun: boolean;
8
+ dangerouslySkipPermissions: boolean;
9
+ }): void;
10
+ export declare function logSuccess(message: string): void;
11
+ export declare function logError(message: string): void;
12
+ export declare function logWarning(message: string): void;
13
+ export declare function logInfo(message: string): void;
14
+ export declare function logDryRun(command: string): void;
package/dist/logger.js ADDED
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ import { appendFileSync } from 'fs';
3
+ const BOX_WIDTH = 62;
4
+ let logFilePath;
5
+ export function setLogFile(path) {
6
+ logFilePath = path;
7
+ }
8
+ function stripAnsi(str) {
9
+ // eslint-disable-next-line no-control-regex
10
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
11
+ }
12
+ function getTimestamp() {
13
+ return new Date().toISOString().replace('T', ' ').substring(0, 19);
14
+ }
15
+ export function log(message) {
16
+ const timestamped = `[${getTimestamp()}] ${message}`;
17
+ console.log(timestamped);
18
+ if (logFilePath) {
19
+ appendFileSync(logFilePath, stripAnsi(timestamped) + '\n');
20
+ }
21
+ }
22
+ export function logRaw(message) {
23
+ console.log(message);
24
+ if (logFilePath) {
25
+ appendFileSync(logFilePath, stripAnsi(message) + '\n');
26
+ }
27
+ }
28
+ export function logStep(stepNum, title) {
29
+ const line = '─'.repeat(BOX_WIDTH);
30
+ console.log();
31
+ console.log(chalk.cyan(`┌${line}┐`));
32
+ console.log(chalk.cyan('│') + ' ' + chalk.green(`STEP ${stepNum}:`) + ' ' + title);
33
+ console.log(chalk.cyan(`└${line}┘`));
34
+ console.log();
35
+ if (logFilePath) {
36
+ appendFileSync(logFilePath, `\n┌${line}┐\n│ STEP ${stepNum}: ${title}\n└${line}┘\n\n`);
37
+ }
38
+ }
39
+ export function logHeader(config) {
40
+ const line = '═'.repeat(BOX_WIDTH);
41
+ console.log();
42
+ console.log(chalk.green(`╔${line}╗`));
43
+ console.log(chalk.green('║') + ' SOFTWARE FACTORY PIPELINE v2.0 ' + chalk.green('║'));
44
+ console.log(chalk.green(`╠${line}╣`));
45
+ const configLine = ` Reviews: ${config.reviewIterations} | Dry-run: ${config.dryRun}`;
46
+ console.log(chalk.green('║') + configLine.padEnd(BOX_WIDTH) + chalk.green('║'));
47
+ if (config.dangerouslySkipPermissions) {
48
+ const permLine = ' ' + chalk.yellow('⚠ Skip permissions: enabled');
49
+ console.log(chalk.green('║') + permLine + ' '.repeat(BOX_WIDTH - stripAnsi(permLine).length) + chalk.green('║'));
50
+ }
51
+ console.log(chalk.green(`╚${line}╝`));
52
+ console.log();
53
+ }
54
+ export function logSuccess(message) {
55
+ log(chalk.green(`✓ ${message}`));
56
+ }
57
+ export function logError(message) {
58
+ log(chalk.red(`✗ ${message}`));
59
+ }
60
+ export function logWarning(message) {
61
+ log(chalk.yellow(`⚠ ${message}`));
62
+ }
63
+ export function logInfo(message) {
64
+ log(chalk.blue(`▶ ${message}`));
65
+ }
66
+ export function logDryRun(command) {
67
+ const message = '[DRY-RUN] ' + command;
68
+ console.log(chalk.yellow('[DRY-RUN]') + ' ' + command);
69
+ if (logFilePath) {
70
+ appendFileSync(logFilePath, message + '\n');
71
+ }
72
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from './config.js';
2
+ export declare function runPipeline(config: Config): Promise<void>;
@@ -0,0 +1,81 @@
1
+ import chalk from 'chalk';
2
+ import { log, logHeader, setLogFile } from './logger.js';
3
+ import { stepBranchManagement, stepImplement, stepSimplify, stepReview, stepSolidCleanCode, stepTest, stepCommit, stepChangelog, } from './steps/index.js';
4
+ let signalHandlersRegistered = false;
5
+ export async function runPipeline(config) {
6
+ // Setup logging
7
+ if (config.logFile) {
8
+ setLogFile(config.logFile);
9
+ }
10
+ // Setup interrupt handler (only once to prevent duplicate registrations)
11
+ if (!signalHandlersRegistered) {
12
+ process.on('SIGINT', () => {
13
+ console.log(chalk.red('\n\nPipeline interrupted'));
14
+ process.exit(130);
15
+ });
16
+ process.on('SIGTERM', () => {
17
+ console.log(chalk.red('\n\nPipeline terminated'));
18
+ process.exit(130);
19
+ });
20
+ signalHandlersRegistered = true;
21
+ }
22
+ // Display header
23
+ logHeader(config);
24
+ log(`Starting pipeline for: ${config.requirement}`);
25
+ // Step 1: Smart Branch Management
26
+ const branchResult = await stepBranchManagement(config);
27
+ if (!branchResult.success) {
28
+ console.log(chalk.red('\nBranch management step failed. Exiting.'));
29
+ process.exit(1);
30
+ }
31
+ // Step 2: Implement
32
+ const implSuccess = await stepImplement(config);
33
+ if (!implSuccess) {
34
+ console.log(chalk.red('\nImplementation step failed. Exiting.'));
35
+ process.exit(1);
36
+ }
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);
42
+ }
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.'));
48
+ process.exit(1);
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;
54
+ }
55
+ }
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);
61
+ }
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);
67
+ }
68
+ // Step 7: Commit
69
+ const commitSuccess = await stepCommit(config);
70
+ if (!commitSuccess) {
71
+ console.log(chalk.red('\nCommit step failed. Exiting.'));
72
+ process.exit(1);
73
+ }
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);
79
+ }
80
+ console.log(chalk.green('\n✓ Pipeline completed successfully\n'));
81
+ }
@@ -0,0 +1,8 @@
1
+ import type { Config } from './config.js';
2
+ export declare enum ConfirmResult {
3
+ Continue = "continue",
4
+ Skip = "skip",
5
+ Quit = "quit"
6
+ }
7
+ export declare function confirm(config: Config): Promise<ConfirmResult>;
8
+ export declare function handleConfirm(config: Config): Promise<boolean>;
@@ -0,0 +1,36 @@
1
+ import inquirer from 'inquirer';
2
+ export var ConfirmResult;
3
+ (function (ConfirmResult) {
4
+ ConfirmResult["Continue"] = "continue";
5
+ ConfirmResult["Skip"] = "skip";
6
+ ConfirmResult["Quit"] = "quit";
7
+ })(ConfirmResult || (ConfirmResult = {}));
8
+ export async function confirm(config) {
9
+ if (config.autoMode) {
10
+ return ConfirmResult.Continue;
11
+ }
12
+ const { action } = await inquirer.prompt([
13
+ {
14
+ type: 'expand',
15
+ name: 'action',
16
+ message: 'Continue?',
17
+ default: 'y',
18
+ choices: [
19
+ { key: 'y', name: 'Yes - continue', value: 'continue' },
20
+ { key: 'n', name: 'No - exit', value: 'quit' },
21
+ { key: 's', name: 'Skip - skip this step', value: 'skip' },
22
+ { key: 'q', name: 'Quit - exit pipeline', value: 'quit' },
23
+ ],
24
+ },
25
+ ]);
26
+ return action;
27
+ }
28
+ export async function handleConfirm(config) {
29
+ const result = await confirm(config);
30
+ if (result === ConfirmResult.Quit) {
31
+ console.log('\nPipeline cancelled by user.');
32
+ process.exit(0);
33
+ }
34
+ // Continue or Skip both mean proceed
35
+ return true;
36
+ }
@@ -0,0 +1,9 @@
1
+ import { type BranchAnalysis } from '../utils/branchAnalyzer.js';
2
+ import type { Config } from '../config.js';
3
+ export interface BranchResult {
4
+ success: boolean;
5
+ branchCreated: boolean;
6
+ branchName: string;
7
+ analysis: BranchAnalysis | null;
8
+ }
9
+ export declare function stepBranchManagement(config: Config): Promise<BranchResult>;
@@ -0,0 +1,56 @@
1
+ import { logStep, logInfo, logSuccess, logWarning, logDryRun } from '../logger.js';
2
+ import { getGitState, branchExists, createBranch, findSimilarBranches } from '../utils/git.js';
3
+ import { analyzeRequirement } from '../utils/branchAnalyzer.js';
4
+ function createResult(branchName, analysis = null, branchCreated = false) {
5
+ return { success: true, branchCreated, branchName, analysis };
6
+ }
7
+ export async function stepBranchManagement(config) {
8
+ logStep('1/8', 'SMART BRANCH MANAGEMENT');
9
+ if (config.skipBranchManagement) {
10
+ logInfo('Branch management skipped (--skip-branch-management)');
11
+ return createResult('');
12
+ }
13
+ const gitState = getGitState();
14
+ 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
21
+ const analysis = await analyzeRequirement(config.requirement, config);
22
+ logInfo(`Change type: ${analysis.changeType}`);
23
+ logInfo(`Suggested branch: ${analysis.suggestedBranchName}`);
24
+ if (analysis.isTrivial) {
25
+ logInfo('Trivial change detected - staying on current branch');
26
+ return createResult(gitState.currentBranch, analysis);
27
+ }
28
+ // Check for similar/conflicting branches
29
+ const conflicts = findSimilarBranches(analysis.shortDescription, gitState.remoteBranches);
30
+ if (conflicts.length > 0) {
31
+ logWarning('Potential conflicting branches detected:');
32
+ for (const conflict of conflicts) {
33
+ logWarning(` - ${conflict.branchName} (${conflict.similarity})`);
34
+ }
35
+ logWarning('Consider checking these branches before proceeding');
36
+ }
37
+ // Generate unique branch name
38
+ let branchName = analysis.suggestedBranchName;
39
+ let counter = 1;
40
+ while (branchExists(branchName)) {
41
+ branchName = `${analysis.branchPrefix}/${analysis.shortDescription}-${counter}`;
42
+ counter++;
43
+ }
44
+ if (config.dryRun) {
45
+ logDryRun(`git checkout -b ${branchName}`);
46
+ return createResult(branchName, analysis);
47
+ }
48
+ logInfo(`Creating branch: ${branchName}`);
49
+ const created = createBranch(branchName);
50
+ if (created) {
51
+ logSuccess(`Switched to new branch '${branchName}'`);
52
+ return createResult(branchName, analysis, true);
53
+ }
54
+ logWarning(`Failed to create branch '${branchName}' - continuing on current branch`);
55
+ return createResult(gitState.currentBranch, analysis);
56
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepChangelog(config: Config): Promise<boolean>;
@@ -0,0 +1,26 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ export async function stepChangelog(config) {
4
+ logStep('8/8', 'UPDATE CHANGELOG');
5
+ const prompt = `Update CHANGELOG.md:
6
+
7
+ ## Format (Keep a Changelog):
8
+ \`\`\`markdown
9
+ ## [Unreleased]
10
+
11
+ ### Added
12
+ - New features
13
+
14
+ ### Changed
15
+ - Changes in existing functionality
16
+
17
+ ### Fixed
18
+ - Bug fixes
19
+ \`\`\`
20
+
21
+ 1. Add entry for this change under appropriate category
22
+ 2. Be concise but descriptive
23
+ 3. Commit the CHANGELOG update`;
24
+ const result = await runClaude({ prompt, continueConversation: true }, config);
25
+ return result.success;
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepCommit(config: Config): Promise<boolean>;
@@ -0,0 +1,36 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ export async function stepCommit(config) {
4
+ logStep('7/8', 'COMMIT CHANGES');
5
+ const pushInstruction = config.skipPush ? '' : 'Then push to remote.';
6
+ const prompt = `Commit the changes:
7
+
8
+ ## Commit Message Requirements (Target: 10/10):
9
+
10
+ **Subject Line (max 3 pts):**
11
+ - Imperative mood ('Add' not 'Added')
12
+ - ≤50 characters, no trailing period
13
+ - Format: type(scope): description
14
+
15
+ **Body - WHY (max 3 pts):**
16
+ - What problem does this solve?
17
+ - Why was this approach chosen?
18
+
19
+ **Body - WHAT (max 2 pts):**
20
+ - Key technical changes
21
+ - Files/components affected
22
+
23
+ **Structure (max 2 pts):**
24
+ - Blank line after subject
25
+ - Body wrapped at 72 chars
26
+
27
+ ## Tasks:
28
+ 1. Stage relevant files (git add)
29
+ 2. Commit with excellent message
30
+
31
+ Do not add any attribution or co author by.
32
+
33
+ ${pushInstruction}`;
34
+ const result = await runClaude({ prompt, continueConversation: true }, config);
35
+ return result.success;
36
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepImplement(config: Config): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ export async function stepImplement(config) {
4
+ logStep('2/8', 'IMPLEMENT REQUIREMENT');
5
+ const prompt = `${config.requirement}
6
+
7
+ ## Implementation Guidelines:
8
+ - Understand the existing codebase structure first
9
+ - Write clean, idiomatic code following project conventions
10
+ - Handle edge cases and errors appropriately
11
+ - Add necessary comments for complex logic
12
+ - Keep changes focused and minimal`;
13
+ const result = await runClaude({ prompt }, config);
14
+ return result.success;
15
+ }
@@ -0,0 +1,8 @@
1
+ export { stepBranchManagement, type BranchResult } from './branchManagement.js';
2
+ export { stepImplement } from './implement.js';
3
+ export { stepSimplify } from './simplify.js';
4
+ export { stepReview, type ReviewResult } from './review.js';
5
+ export { stepSolidCleanCode } from './solid.js';
6
+ export { stepTest } from './test.js';
7
+ export { stepCommit } from './commit.js';
8
+ export { stepChangelog } from './changelog.js';
@@ -0,0 +1,8 @@
1
+ export { stepBranchManagement } from './branchManagement.js';
2
+ export { stepImplement } from './implement.js';
3
+ export { stepSimplify } from './simplify.js';
4
+ export { stepReview } from './review.js';
5
+ export { stepSolidCleanCode } from './solid.js';
6
+ export { stepTest } from './test.js';
7
+ export { stepCommit } from './commit.js';
8
+ export { stepChangelog } from './changelog.js';
@@ -0,0 +1,6 @@
1
+ import type { Config } from '../config.js';
2
+ export interface ReviewResult {
3
+ success: boolean;
4
+ noIssuesFound: boolean;
5
+ }
6
+ export declare function stepReview(iteration: number, config: Config): Promise<ReviewResult>;
@@ -0,0 +1,37 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ // Patterns that indicate no issues were found during review
4
+ const NO_ISSUES_PATTERNS = [
5
+ /no\s+issues?\s+(found|detected|identified)/i,
6
+ /code\s+(is\s+)?clean/i,
7
+ /lgtm/i,
8
+ /looks\s+good(\s+to\s+me)?/i,
9
+ /no\s+(changes|modifications)\s+(needed|required|necessary)/i,
10
+ /nothing\s+to\s+(fix|change|improve)/i,
11
+ /all\s+(checks\s+)?pass(ed)?/i,
12
+ /code\s+quality\s+(is\s+)?(good|excellent|solid)/i,
13
+ ];
14
+ function detectNoIssues(output) {
15
+ return NO_ISSUES_PATTERNS.some((pattern) => pattern.test(output));
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:
20
+
21
+ ## Critical Review Points:
22
+ 1. **Bugs**: Logic errors, off-by-one, null refs, race conditions
23
+ 2. **Security**: Input validation, injection, auth issues
24
+ 3. **Performance**: N+1 queries, unnecessary loops, memory leaks
25
+ 4. **Maintainability**: Code clarity, naming, complexity
26
+
27
+ ## Action Required:
28
+ - FIX any issues found immediately
29
+ - Be specific about what you changed and why
30
+ - If code is clean, say "No issues found" or "LGTM"`;
31
+ const result = await runClaude({ prompt, continueConversation: true }, config);
32
+ const noIssuesFound = result.success && detectNoIssues(result.output);
33
+ return {
34
+ success: result.success,
35
+ noIssuesFound,
36
+ };
37
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepSimplify(config: Config): Promise<boolean>;
@@ -0,0 +1,47 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ export async function stepSimplify(config) {
4
+ logStep('3/8', 'CODE SIMPLIFICATION');
5
+ const prompt = `Refine the recently modified code for clarity, consistency, and maintainability while preserving all functionality.
6
+
7
+ ## Core Principles:
8
+
9
+ ### 1. Functionality Preservation
10
+ - Never change what the code does - only how it does it
11
+ - All original features, outputs, and behaviors must remain intact
12
+
13
+ ### 2. Project Standards Alignment
14
+ - Follow ES modules and project conventions
15
+ - Prefer \`function\` declarations over arrow functions for top-level functions
16
+ - Use explicit return type annotations
17
+ - Maintain established naming conventions
18
+
19
+ ### 3. Clarity Enhancement
20
+ - Reducing unnecessary complexity and nesting
21
+ - Eliminating redundant code and abstractions
22
+ - Improving readability through clear variable and function names
23
+ - Consolidating related logic
24
+ - Removing unnecessary comments that describe obvious code
25
+ - IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
26
+ - Choose clarity over brevity - explicit code is often better than overly compact code
27
+
28
+ ### 4. Balanced Approach
29
+ - Avoid over-simplification that could reduce maintainability
30
+ - Don't create overly clever solutions
31
+ - Don't combine too many concerns into single functions
32
+
33
+ ### 5. Scoped Focus
34
+ - Target the recently modified code sections
35
+ - Don't refactor unrelated code unless explicitly requested
36
+
37
+ ## Actions:
38
+ 1. IDENTIFY opportunities for simplification in recently modified code
39
+ 2. APPLY refinements that improve readability without changing behavior
40
+ 3. VERIFY all functionality is preserved
41
+ 4. EXPLAIN significant changes made
42
+
43
+ You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.
44
+ `;
45
+ const result = await runClaude({ prompt, continueConversation: true }, config);
46
+ return result.success;
47
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepSolidCleanCode(config: Config): Promise<boolean>;
@@ -0,0 +1,53 @@
1
+ import { logStep } from '../logger.js';
2
+ import { runClaude } from '../claude.js';
3
+ export async function stepSolidCleanCode(config) {
4
+ logStep('5/8', 'SOLID PRINCIPLES & CLEAN CODE');
5
+ const prompt = `Review the code for SOLID principles and Clean Code compliance:
6
+
7
+ ## SOLID Principles:
8
+
9
+ ### S - Single Responsibility
10
+ - Each class/function has ONE reason to change
11
+ - Split classes doing multiple things
12
+
13
+ ### O - Open/Closed
14
+ - Open for extension, closed for modification
15
+ - Replace switch/if-else chains with polymorphism
16
+
17
+ ### L - Liskov Substitution
18
+ - Subtypes must be substitutable for base types
19
+ - Prefer composition over inheritance when appropriate
20
+
21
+ ### I - Interface Segregation
22
+ - Small, focused interfaces
23
+ - Clients should not depend on unused methods
24
+
25
+ ### D - Dependency Inversion
26
+ - Depend on abstractions, not concretions
27
+ - Inject dependencies, dont hard-code them
28
+
29
+ ## Clean Code Checklist:
30
+
31
+ **Naming:**
32
+ - [ ] Intention-revealing names
33
+ - [ ] Pronounceable, searchable names
34
+ - [ ] Class=nouns, Methods=verbs, Booleans=questions
35
+
36
+ **Functions:**
37
+ - [ ] Small (<20 lines), single purpose
38
+ - [ ] Max 3 params (wrap in object if more)
39
+ - [ ] No side effects, no null returns
40
+
41
+ **Code Smells to Fix:**
42
+ - [ ] Magic numbers → named constants
43
+ - [ ] Deep nesting → early returns/extraction
44
+ - [ ] Duplicate code → DRY refactor
45
+ - [ ] Long methods → extract smaller functions
46
+
47
+ ## Actions:
48
+ 1. IDENTIFY all violations
49
+ 2. REFACTOR to fix each issue
50
+ 3. EXPLAIN your changes`;
51
+ const result = await runClaude({ prompt, continueConversation: true }, config);
52
+ return result.success;
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../config.js';
2
+ export declare function stepTest(config: Config): Promise<boolean>;
@@ -0,0 +1,23 @@
1
+ import chalk from 'chalk';
2
+ import { logStep } from '../logger.js';
3
+ import { runClaude } from '../claude.js';
4
+ export async function stepTest(config) {
5
+ logStep('6/8', 'TESTING');
6
+ if (config.skipTests) {
7
+ console.log(chalk.yellow('[SKIPPED]') + ' Testing step skipped via configuration');
8
+ return true;
9
+ }
10
+ const prompt = `Testing phase:
11
+
12
+ 1. **Run existing tests** - Fix any failures
13
+ 2. **Add new tests** for changed code:
14
+ - Happy path tests
15
+ - Edge cases
16
+ - Error conditions
17
+ 3. **Verify coverage** - Ensure critical paths are tested
18
+ 4. **Final test run** - All tests must pass
19
+
20
+ Report: tests run, passed, failed, new tests added`;
21
+ const result = await runClaude({ prompt, continueConversation: true }, config);
22
+ return result.success;
23
+ }
@@ -0,0 +1,10 @@
1
+ import type { Config } from '../config.js';
2
+ export type ChangeType = 'feature' | 'fix' | 'refactor' | 'docs' | 'chore' | 'trivial';
3
+ export interface BranchAnalysis {
4
+ changeType: ChangeType;
5
+ isTrivial: boolean;
6
+ suggestedBranchName: string;
7
+ branchPrefix: string;
8
+ shortDescription: string;
9
+ }
10
+ export declare function analyzeRequirement(requirement: string, config: Config): Promise<BranchAnalysis>;
@@ -0,0 +1,105 @@
1
+ import { spawn } from 'child_process';
2
+ const MAX_DESCRIPTION_LENGTH = 40;
3
+ function sanitizeDescription(input) {
4
+ return input
5
+ .trim()
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9\s-]/g, '')
8
+ .replace(/\s+/g, '-')
9
+ .substring(0, MAX_DESCRIPTION_LENGTH);
10
+ }
11
+ const ANALYSIS_PROMPT = `Analyze the following requirement and determine the type of change it represents.
12
+
13
+ Requirement: "{REQUIREMENT}"
14
+
15
+ Respond in EXACTLY this format (no markdown, no extra text):
16
+ CHANGE_TYPE: <one of: feature, fix, refactor, docs, chore, trivial>
17
+ IS_TRIVIAL: <true or false>
18
+ SHORT_DESC: <2-4 word kebab-case description for branch name>
19
+
20
+ Guidelines:
21
+ - feature = new functionality being added
22
+ - fix = bug fix or error correction
23
+ - refactor = code restructuring without changing behavior
24
+ - docs = documentation changes only
25
+ - chore = maintenance tasks (deps, config, build)
26
+ - trivial = tiny changes like typo fixes, single-line changes
27
+
28
+ IS_TRIVIAL should be true only for very minor changes that don't need a branch.
29
+ SHORT_DESC should be suitable for a branch name like "feature/SHORT_DESC"`;
30
+ const DEFAULT_ANALYSIS = {
31
+ changeType: 'feature',
32
+ isTrivial: false,
33
+ suggestedBranchName: 'feature/changes',
34
+ branchPrefix: 'feature',
35
+ shortDescription: 'changes',
36
+ };
37
+ function parseAnalysisResponse(output) {
38
+ const lines = output.split('\n').filter((line) => line.trim());
39
+ let changeType = 'feature';
40
+ let isTrivial = false;
41
+ let shortDescription = 'changes';
42
+ for (const line of lines) {
43
+ const changeMatch = line.match(/CHANGE_TYPE:\s*(feature|fix|refactor|docs|chore|trivial)/i);
44
+ if (changeMatch) {
45
+ changeType = changeMatch[1].toLowerCase();
46
+ }
47
+ const trivialMatch = line.match(/IS_TRIVIAL:\s*(true|false)/i);
48
+ if (trivialMatch) {
49
+ isTrivial = trivialMatch[1].toLowerCase() === 'true';
50
+ }
51
+ const descMatch = line.match(/SHORT_DESC:\s*(.+)/i);
52
+ if (descMatch) {
53
+ shortDescription = sanitizeDescription(descMatch[1]);
54
+ }
55
+ }
56
+ const branchPrefix = changeType === 'trivial' ? 'chore' : changeType;
57
+ return {
58
+ changeType,
59
+ isTrivial,
60
+ suggestedBranchName: `${branchPrefix}/${shortDescription}`,
61
+ branchPrefix,
62
+ shortDescription,
63
+ };
64
+ }
65
+ export async function analyzeRequirement(requirement, config) {
66
+ const prompt = ANALYSIS_PROMPT.replace('{REQUIREMENT}', requirement);
67
+ const args = ['--print'];
68
+ if (config.dangerouslySkipPermissions) {
69
+ args.push('--dangerously-skip-permissions');
70
+ }
71
+ args.push(prompt);
72
+ if (config.dryRun) {
73
+ return {
74
+ ...DEFAULT_ANALYSIS,
75
+ suggestedBranchName: 'feature/dry-run-changes',
76
+ shortDescription: 'dry-run-changes',
77
+ };
78
+ }
79
+ return new Promise((resolve) => {
80
+ let output = '';
81
+ const child = spawn('claude', args, {
82
+ cwd: process.cwd(),
83
+ env: process.env,
84
+ stdio: ['inherit', 'pipe', 'pipe'],
85
+ });
86
+ if (child.stdout) {
87
+ child.stdout.on('data', (data) => {
88
+ output += data.toString();
89
+ });
90
+ }
91
+ child.on('close', (exitCode) => {
92
+ if (exitCode === 0) {
93
+ const analysis = parseAnalysisResponse(output);
94
+ if (analysis) {
95
+ resolve(analysis);
96
+ return;
97
+ }
98
+ }
99
+ resolve(DEFAULT_ANALYSIS);
100
+ });
101
+ child.on('error', () => {
102
+ resolve(DEFAULT_ANALYSIS);
103
+ });
104
+ });
105
+ }
@@ -0,0 +1,19 @@
1
+ export interface GitState {
2
+ currentBranch: string;
3
+ isMainBranch: boolean;
4
+ hasUncommittedChanges: boolean;
5
+ remoteBranches: string[];
6
+ }
7
+ export interface BranchConflict {
8
+ branchName: string;
9
+ similarity: string;
10
+ }
11
+ export declare function getCurrentBranch(): string;
12
+ export declare function isMainBranch(branchName: string): boolean;
13
+ export declare function hasUncommittedChanges(): boolean;
14
+ export declare function getRemoteBranches(): string[];
15
+ export declare function getGitState(): GitState;
16
+ export declare function branchExists(branchName: string): boolean;
17
+ export declare function createBranch(branchName: string): boolean;
18
+ export declare function findSimilarBranches(keyword: string, remoteBranches: string[]): BranchConflict[];
19
+ export declare function generateBranchName(prefix: string, description: string): string;
@@ -0,0 +1,86 @@
1
+ import { execSync } from 'child_process';
2
+ const MAX_BRANCH_NAME_LENGTH = 40;
3
+ const MAIN_BRANCH_NAMES = ['main', 'master', 'develop', 'dev'];
4
+ function execGit(command) {
5
+ try {
6
+ return execSync(`git ${command}`, { encoding: 'utf-8' }).trim();
7
+ }
8
+ catch {
9
+ return '';
10
+ }
11
+ }
12
+ export function getCurrentBranch() {
13
+ return execGit('symbolic-ref --short HEAD') || execGit('rev-parse --short HEAD');
14
+ }
15
+ export function isMainBranch(branchName) {
16
+ return MAIN_BRANCH_NAMES.includes(branchName.toLowerCase());
17
+ }
18
+ export function hasUncommittedChanges() {
19
+ const status = execGit('status --porcelain');
20
+ return status.length > 0;
21
+ }
22
+ export function getRemoteBranches() {
23
+ const output = execGit('branch -r');
24
+ if (!output)
25
+ return [];
26
+ return output
27
+ .split('\n')
28
+ .map((b) => b.trim().replace(/^origin\//, ''))
29
+ .filter((b) => b && !b.includes('HEAD'));
30
+ }
31
+ export function getGitState() {
32
+ const currentBranch = getCurrentBranch();
33
+ return {
34
+ currentBranch,
35
+ isMainBranch: isMainBranch(currentBranch),
36
+ hasUncommittedChanges: hasUncommittedChanges(),
37
+ remoteBranches: getRemoteBranches(),
38
+ };
39
+ }
40
+ export function branchExists(branchName) {
41
+ const localBranches = execGit('branch --list');
42
+ const remoteBranches = getRemoteBranches();
43
+ const localList = localBranches.split('\n').map((b) => b.trim().replace(/^\*\s*/, ''));
44
+ return localList.includes(branchName) || remoteBranches.includes(branchName);
45
+ }
46
+ export function createBranch(branchName) {
47
+ // Validate branch name to prevent command injection
48
+ if (!/^[a-zA-Z0-9/_-]+$/.test(branchName)) {
49
+ return false;
50
+ }
51
+ try {
52
+ execSync(`git checkout -b ${branchName}`, { encoding: 'utf-8' });
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ export function findSimilarBranches(keyword, remoteBranches) {
60
+ const normalizedKeyword = keyword.toLowerCase();
61
+ if (!normalizedKeyword) {
62
+ return [];
63
+ }
64
+ const conflicts = [];
65
+ for (const branch of remoteBranches) {
66
+ const normalizedBranch = branch.toLowerCase();
67
+ const branchSuffix = normalizedBranch.split('/').pop() || '';
68
+ if (normalizedBranch.includes(normalizedKeyword) || (branchSuffix && normalizedKeyword.includes(branchSuffix))) {
69
+ conflicts.push({
70
+ branchName: branch,
71
+ similarity: 'keyword match',
72
+ });
73
+ }
74
+ }
75
+ return conflicts;
76
+ }
77
+ export function generateBranchName(prefix, description) {
78
+ const kebab = description
79
+ .toLowerCase()
80
+ .replace(/[^a-z0-9\s-]/g, '')
81
+ .replace(/\s+/g, '-')
82
+ .replace(/-+/g, '-')
83
+ .replace(/^-|-$/g, '')
84
+ .substring(0, MAX_BRANCH_NAME_LENGTH);
85
+ return `${prefix}/${kebab}`;
86
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "software-engineer",
3
+ "version": "0.1.10",
4
+ "description": "Software Factory Pipeline - Automate development workflow with Claude AI",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "sf": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "keywords": [
16
+ "claude",
17
+ "ai",
18
+ "pipeline",
19
+ "automation",
20
+ "development",
21
+ "code-review"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "chalk": "^5.3.0",
27
+ "commander": "^12.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.11.0",
31
+ "typescript": "^5.3.3"
32
+ },
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "package.json"
39
+ ]
40
+ }