software-engineer 0.1.20 → 0.1.21

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # software-engineer
2
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.
3
+ A CLI tool that automates the software development workflow using Claude AI. It runs an 8-step pipeline to implement features, simplify code, review, ensure quality, test, commit, and update changelogs with real-time progress visualization.
4
4
 
5
5
  ## Installation
6
6
 
@@ -44,8 +44,10 @@ sf --log pipeline.log "implement caching layer"
44
44
  |--------|-------------|
45
45
  | `-d, --dry-run` | Print commands without executing |
46
46
  | `-r, --reviews <n>` | Number of review iterations (default: 2) |
47
+ | `-a, --adaptive` | Enable adaptive step execution (AI decides which steps to skip) |
47
48
  | `--skip-tests` | Skip the testing step |
48
49
  | `--skip-push` | Commit but don't push to remote |
50
+ | `--skip-branch-management` | Skip smart branch management |
49
51
  | `--log <file>` | Log output to file |
50
52
  | `--dangerously-skip-permissions` | Skip Claude permission prompts |
51
53
  | `-h, --help` | Display help |
@@ -58,10 +60,12 @@ All options can be set via environment variables:
58
60
  | Variable | Description |
59
61
  |----------|-------------|
60
62
  | `SF_REVIEW_ITERATIONS` | Number of review iterations |
63
+ | `SF_ADAPTIVE_EXECUTION` | `true`/`false` for adaptive execution |
61
64
  | `SF_DRY_RUN` | `true`/`false` for dry run |
62
65
  | `SF_LOG_FILE` | Path to log file |
63
66
  | `SF_SKIP_TESTS` | `true`/`false` to skip tests |
64
67
  | `SF_SKIP_PUSH` | `true`/`false` to skip push |
68
+ | `SF_SKIP_BRANCH_MANAGEMENT` | `true`/`false` to skip branch management |
65
69
  | `SF_DANGEROUSLY_SKIP_PERMISSIONS` | `true`/`false` to skip Claude permissions |
66
70
 
67
71
  Example:
@@ -69,41 +73,63 @@ Example:
69
73
  SF_REVIEW_ITERATIONS=3 sf "add feature X"
70
74
  ```
71
75
 
72
- ## Pipeline Steps
76
+ ## Features
77
+
78
+ - **Real-time Progress Visualization**: See exactly what Claude is doing with colorized, emoji-enhanced output
79
+ - 📖 File reads in cyan
80
+ - ✍️ File writes in green
81
+ - ✏️ File edits in yellow
82
+ - ⚡ Command execution in magenta
83
+ - 🔍 Code searches in blue
84
+ - **Smart Branch Management**: Automatically creates feature branches based on requirement type
85
+ - **Adaptive Execution**: AI-powered step optimization that skips unnecessary stages
86
+ - **Comprehensive Code Quality**: Multi-stage review process ensuring best practices
73
87
 
74
- ### 1. Implement
75
- Takes your requirement and asks Claude to implement it following project conventions.
88
+ ## Pipeline Steps
76
89
 
77
- ### 2. Code Simplification
78
- Refines the implementation for clarity, consistency, and maintainability while preserving functionality:
90
+ ### 1. Smart Branch Management
91
+ Analyzes your requirement and automatically creates appropriate feature branches:
92
+ - Detects change type (feature/fix/refactor/docs/chore)
93
+ - Creates semantically named branches
94
+ - Warns about potential conflicts
95
+
96
+ ### 2. Implement
97
+ Claude understands your codebase and implements the requirement:
98
+ - Analyzes project structure and patterns
99
+ - Follows project conventions
100
+ - Handles edge cases appropriately
101
+ - Minimal, focused changes
102
+
103
+ ### 3. Code Simplification
104
+ Refines the implementation for clarity, consistency, and maintainability:
79
105
  - Follows project standards (ES modules, explicit types)
80
106
  - Enhances clarity by avoiding nested ternaries
81
107
  - Removes redundant abstractions
82
108
 
83
- ### 3. Code Review (configurable iterations)
109
+ ### 4. Code Review (configurable iterations)
84
110
  Reviews the implementation for:
85
111
  - Bugs (logic errors, null refs, race conditions)
86
112
  - Security issues (injection, auth problems)
87
113
  - Performance (N+1 queries, memory leaks)
88
114
  - Maintainability (clarity, naming, complexity)
89
115
 
90
- ### 4. SOLID & Clean Code
116
+ ### 5. SOLID & Clean Code
91
117
  Ensures compliance with:
92
118
  - SOLID principles (SRP, OCP, LSP, ISP, DIP)
93
119
  - Clean code practices (naming, small functions, no magic numbers)
94
120
 
95
- ### 5. Testing
121
+ ### 6. Testing
96
122
  - Runs existing tests
97
123
  - Adds new tests for changed code
98
124
  - Verifies coverage
99
125
 
100
- ### 6. Commit
126
+ ### 7. Commit
101
127
  Creates a well-formatted commit with:
102
128
  - Conventional commit format
103
129
  - Clear subject line
104
130
  - Detailed body explaining why and what
105
131
 
106
- ### 7. Changelog
132
+ ### 8. Changelog
107
133
  Updates CHANGELOG.md following Keep a Changelog format.
108
134
 
109
135
  ## License
package/dist/claude.js CHANGED
@@ -1,8 +1,73 @@
1
1
  import spawn from 'cross-spawn';
2
+ import chalk from 'chalk';
2
3
  import { logInfo, logSuccess, logError, logDryRun } from './logger.js';
3
4
  // Exit codes
4
5
  const EXIT_SUCCESS = 0;
5
6
  const EXIT_INTERRUPTED = 130;
7
+ function formatToolCall(name, input) {
8
+ const toolColors = {
9
+ Read: 'cyan',
10
+ Write: 'green',
11
+ Edit: 'yellow',
12
+ Bash: 'magenta',
13
+ Grep: 'blue',
14
+ Glob: 'blue',
15
+ };
16
+ const color = toolColors[name] || 'white';
17
+ const colorFn = chalk[color];
18
+ if (name === 'Read' && input && typeof input === 'object' && 'file_path' in input) {
19
+ return colorFn(`📖 Reading: ${input.file_path}`);
20
+ }
21
+ if (name === 'Write' && input && typeof input === 'object' && 'file_path' in input) {
22
+ return colorFn(`✍️ Writing: ${input.file_path}`);
23
+ }
24
+ if (name === 'Edit' && input && typeof input === 'object' && 'file_path' in input) {
25
+ return colorFn(`✏️ Editing: ${input.file_path}`);
26
+ }
27
+ if (name === 'Bash' && input && typeof input === 'object' && 'description' in input) {
28
+ return colorFn(`⚡ Running: ${input.description}`);
29
+ }
30
+ if (name === 'Grep' && input && typeof input === 'object' && 'pattern' in input) {
31
+ return colorFn(`🔍 Searching: ${input.pattern}`);
32
+ }
33
+ if (name === 'Glob' && input && typeof input === 'object' && 'pattern' in input) {
34
+ return colorFn(`📁 Finding: ${input.pattern}`);
35
+ }
36
+ return colorFn(`🔧 ${name}`);
37
+ }
38
+ let lastPrintedMessage = '';
39
+ function processStreamEvent(line) {
40
+ try {
41
+ const event = JSON.parse(line);
42
+ if (event.type === 'system' && event.subtype === 'init') {
43
+ console.log(chalk.dim('━'.repeat(60)));
44
+ console.log(chalk.bold.cyan('🤖 Claude is ready'));
45
+ console.log(chalk.dim('━'.repeat(60)));
46
+ lastPrintedMessage = ''; // Reset on new session
47
+ return;
48
+ }
49
+ if (event.type === 'assistant' && event.message?.content) {
50
+ for (const content of event.message.content) {
51
+ if (content.type === 'text' && content.text) {
52
+ // Print assistant text responses (always print, don't deduplicate text)
53
+ console.log(chalk.white(content.text));
54
+ lastPrintedMessage = ''; // Reset after text message
55
+ }
56
+ else if (content.type === 'tool_use' && content.name) {
57
+ // Print formatted tool calls (deduplicate consecutive identical messages)
58
+ const message = formatToolCall(content.name, content.input);
59
+ if (message !== lastPrintedMessage) {
60
+ console.log(message);
61
+ lastPrintedMessage = message;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ catch (error) {
68
+ // Silently ignore JSON parse errors for non-JSON lines
69
+ }
70
+ }
6
71
  export async function runClaude(options, config) {
7
72
  // Build the full prompt with exit instruction (like Python version)
8
73
  const fullPrompt = `${options.prompt}. Once the work is completed, exit.`;
@@ -10,9 +75,8 @@ export async function runClaude(options, config) {
10
75
  if (config.dangerouslySkipPermissions) {
11
76
  args.push('--dangerously-skip-permissions');
12
77
  }
13
- if (config.printOutput) {
14
- args.push('-p');
15
- }
78
+ // Always pass -p for output and add streaming JSON output format
79
+ args.push('-p', '--output-format=stream-json', '--verbose');
16
80
  if (options.continueConversation) {
17
81
  args.push('-c');
18
82
  }
@@ -24,16 +88,38 @@ export async function runClaude(options, config) {
24
88
  }
25
89
  logInfo('Calling Claude...');
26
90
  return new Promise((resolve) => {
27
- // Spawn Claude using cross-spawn for cross-platform compatibility
28
- // cross-spawn handles Windows .cmd/.bat files automatically without shell:true
29
- // Use 'inherit' for stdio to allow Claude's interactive UI to work properly
91
+ let outputBuffer = '';
92
+ // Spawn Claude with piped stdio to capture stream-json output
30
93
  const child = spawn('claude', args, {
31
94
  cwd: process.cwd(),
32
95
  env: process.env,
33
- stdio: 'inherit',
96
+ stdio: ['inherit', 'pipe', 'inherit'], // pipe stdout, inherit stdin and stderr
97
+ });
98
+ // Process stdout line by line
99
+ child.stdout?.on('data', (data) => {
100
+ const lines = data.toString().split('\n');
101
+ for (let i = 0; i < lines.length; i++) {
102
+ if (i === lines.length - 1) {
103
+ // Last line might be incomplete, buffer it
104
+ outputBuffer += lines[i];
105
+ }
106
+ else {
107
+ // Complete line, process it
108
+ const line = outputBuffer + lines[i];
109
+ outputBuffer = '';
110
+ if (line.trim()) {
111
+ processStreamEvent(line);
112
+ }
113
+ }
114
+ }
34
115
  });
35
116
  // Handle process exit
36
117
  child.on('close', (exitCode) => {
118
+ // Process any remaining buffered output
119
+ if (outputBuffer.trim()) {
120
+ processStreamEvent(outputBuffer);
121
+ }
122
+ console.log(chalk.dim('━'.repeat(60)));
37
123
  if (exitCode === EXIT_INTERRUPTED || exitCode === 2) {
38
124
  logError('Claude interrupted');
39
125
  process.exit(EXIT_INTERRUPTED);
package/dist/config.d.ts CHANGED
@@ -6,7 +6,6 @@ export interface Config {
6
6
  skipPush: boolean;
7
7
  skipBranchManagement: boolean;
8
8
  dangerouslySkipPermissions: boolean;
9
- printOutput: boolean;
10
9
  requirement: string;
11
10
  adaptiveExecution: boolean;
12
11
  }
package/dist/config.js CHANGED
@@ -19,7 +19,6 @@ 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
- printOutput: parseBoolEnv(process.env.SF_PRINT_OUTPUT, false),
23
22
  adaptiveExecution: parseBoolEnv(process.env.SF_ADAPTIVE_EXECUTION, false),
24
23
  };
25
24
  }
@@ -32,7 +31,6 @@ export function mergeConfig(envConfig, cliConfig) {
32
31
  skipPush: cliConfig.skipPush ?? envConfig.skipPush ?? false,
33
32
  skipBranchManagement: cliConfig.skipBranchManagement ?? envConfig.skipBranchManagement ?? false,
34
33
  dangerouslySkipPermissions: cliConfig.dangerouslySkipPermissions ?? envConfig.dangerouslySkipPermissions ?? false,
35
- printOutput: cliConfig.printOutput ?? envConfig.printOutput ?? false,
36
34
  requirement: cliConfig.requirement ?? '',
37
35
  adaptiveExecution: cliConfig.adaptiveExecution ?? envConfig.adaptiveExecution ?? false,
38
36
  };
package/dist/index.js CHANGED
@@ -4,9 +4,34 @@ import chalk from 'chalk';
4
4
  import { readFileSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, join } from 'path';
7
+ import { createInterface } from 'readline';
7
8
  import { loadConfigFromEnv, mergeConfig } from './config.js';
8
9
  import { runPipeline } from './pipeline.js';
9
10
  import { checkForUpdates } from './utils/updateNotifier.js';
11
+ async function promptForRequirement() {
12
+ console.log(chalk.cyan('Enter your requirement (press Ctrl+D or type END on a new line when done):'));
13
+ console.log(chalk.gray('─'.repeat(60)));
14
+ const rl = createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ terminal: process.stdin.isTTY ?? false,
18
+ });
19
+ const lines = [];
20
+ return new Promise((resolve) => {
21
+ rl.on('line', (line) => {
22
+ if (line.trim().toUpperCase() === 'END') {
23
+ rl.close();
24
+ }
25
+ else {
26
+ lines.push(line);
27
+ }
28
+ });
29
+ rl.on('close', () => {
30
+ console.log(chalk.gray('─'.repeat(60)));
31
+ resolve(lines.join('\n').trim());
32
+ });
33
+ });
34
+ }
10
35
  const __filename = fileURLToPath(import.meta.url);
11
36
  const __dirname = dirname(__filename);
12
37
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -15,7 +40,7 @@ program
15
40
  .name('sf')
16
41
  .description('Software Factory Pipeline - Automate development workflow with Claude AI')
17
42
  .version(pkg.version)
18
- .argument('<requirement>', 'The requirement or task to implement')
43
+ .argument('[requirement]', 'The requirement or task to implement')
19
44
  .option('-d, --dry-run', 'Print commands without executing')
20
45
  .option('-r, --reviews <n>', 'Number of review iterations', '2')
21
46
  .option('-a, --adaptive', 'Enable adaptive step execution (AI decides which steps to skip)')
@@ -24,13 +49,17 @@ program
24
49
  .option('--skip-branch-management', 'Skip smart branch management')
25
50
  .option('--log <file>', 'Log output to file')
26
51
  .option('--dangerously-skip-permissions', 'Pass flag to claude to skip permission prompts')
27
- .option('-p, --print', 'Print Claude output (pass -p to claude CLI)')
28
52
  .action(async (requirement, options) => {
29
53
  // Check for updates (non-blocking, fails silently)
30
54
  await checkForUpdates(pkg.name, pkg.version).catch(() => { });
55
+ // Prompt for requirement if not provided
56
+ let finalRequirement = requirement;
57
+ if (!finalRequirement) {
58
+ finalRequirement = await promptForRequirement();
59
+ }
31
60
  const envConfig = loadConfigFromEnv();
32
61
  const cliConfig = {
33
- requirement,
62
+ requirement: finalRequirement,
34
63
  dryRun: options.dryRun ?? undefined,
35
64
  reviewIterations: options.reviews ? parseInt(options.reviews, 10) : undefined,
36
65
  skipTests: options.skipTests ?? undefined,
@@ -38,7 +67,6 @@ program
38
67
  skipBranchManagement: options.skipBranchManagement ?? undefined,
39
68
  logFile: options.log ?? undefined,
40
69
  dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? undefined,
41
- printOutput: options.print ?? undefined,
42
70
  adaptiveExecution: options.adaptive ?? undefined,
43
71
  };
44
72
  const config = mergeConfig(envConfig, cliConfig);
package/dist/pipeline.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { log, logHeader, setLogFile, logInfo } from './logger.js';
3
- import { stepBranchManagement, stepUnderstandCodebase, stepImplement, stepSimplify, stepReview, stepSolidCleanCode, stepTest, stepCommit, stepChangelog, } from './steps/index.js';
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) {
6
6
  // Setup logging
@@ -31,13 +31,7 @@ export async function runPipeline(config) {
31
31
  // Extract adaptive analysis for step decisions
32
32
  const adaptive = branchResult.adaptiveAnalysis;
33
33
  const rec = adaptive?.stepRecommendation;
34
- // Step 2: Understand Codebase
35
- const understandSuccess = await stepUnderstandCodebase(config);
36
- if (!understandSuccess) {
37
- console.log(chalk.red('\nUnderstand codebase step failed. Exiting.'));
38
- process.exit(1);
39
- }
40
- // Step 3: Implement
34
+ // Step 2: Implement
41
35
  const implSuccess = await stepImplement(config);
42
36
  if (!implSuccess) {
43
37
  console.log(chalk.red('\nImplementation step failed. Exiting.'));
@@ -46,7 +46,7 @@ function branchMatchesRequirement(currentBranch, analysis) {
46
46
  return overlap || (prefixMatches && suggestedKeywords.length === 0);
47
47
  }
48
48
  export async function stepBranchManagement(config) {
49
- logStep('1/9', 'SMART BRANCH MANAGEMENT');
49
+ logStep('1/8', 'SMART BRANCH MANAGEMENT');
50
50
  const adaptiveAnalysis = config.adaptiveExecution ? await performAdaptiveAnalysis(config) : null;
51
51
  if (config.skipBranchManagement) {
52
52
  logInfo('Branch management skipped (--skip-branch-management)');
@@ -1,7 +1,7 @@
1
1
  import { logStep } from '../logger.js';
2
2
  import { runClaude } from '../claude.js';
3
3
  export async function stepChangelog(config) {
4
- logStep('9/9', 'UPDATE CHANGELOG');
4
+ logStep('8/8', 'UPDATE CHANGELOG');
5
5
  const prompt = `Update CHANGELOG.md:
6
6
 
7
7
  ## Format (Keep a Changelog):
@@ -1,7 +1,7 @@
1
1
  import { logStep } from '../logger.js';
2
2
  import { runClaude } from '../claude.js';
3
3
  export async function stepCommit(config) {
4
- logStep('8/9', 'COMMIT CHANGES');
4
+ logStep('7/8', 'COMMIT CHANGES');
5
5
  const pushInstruction = config.skipPush ? '' : 'Then push to remote.';
6
6
  const prompt = `Commit the changes:
7
7
 
@@ -1,14 +1,15 @@
1
1
  import { logStep } from '../logger.js';
2
2
  import { runClaude } from '../claude.js';
3
3
  export async function stepImplement(config) {
4
- logStep('3/9', 'IMPLEMENT REQUIREMENT');
4
+ logStep('2/8', 'IMPLEMENT REQUIREMENT');
5
5
  const prompt = `${config.requirement}
6
6
 
7
7
  ## Implementation Guidelines:
8
+ - First, understand the codebase structure and identify relevant files for this requirement
8
9
  - Write clean, idiomatic code following project conventions
9
10
  - Handle edge cases and errors appropriately
10
11
  - Add necessary comments for complex logic
11
12
  - Keep changes focused and minimal. Once the work is completed, exit.`;
12
- const result = await runClaude({ prompt, continueConversation: true }, config);
13
+ const result = await runClaude({ prompt }, config);
13
14
  return result.success;
14
15
  }
@@ -1,5 +1,4 @@
1
1
  export { stepBranchManagement, type BranchResult } from './branchManagement.js';
2
- export { stepUnderstandCodebase } from './understand.js';
3
2
  export { stepImplement } from './implement.js';
4
3
  export { stepSimplify } from './simplify.js';
5
4
  export { stepReview, type ReviewResult, type ReviewDepth } from './review.js';
@@ -1,5 +1,4 @@
1
1
  export { stepBranchManagement } from './branchManagement.js';
2
- export { stepUnderstandCodebase } from './understand.js';
3
2
  export { stepImplement } from './implement.js';
4
3
  export { stepSimplify } from './simplify.js';
5
4
  export { stepReview } from './review.js';
@@ -60,7 +60,7 @@ function getPromptForDepth(depth) {
60
60
  }
61
61
  export async function stepReview(iteration, config, depth = 'standard') {
62
62
  const depthLabel = depth !== 'standard' ? ` [${depth}]` : '';
63
- logStep('5/9', `CODE REVIEW (Round ${iteration}/${config.reviewIterations})${depthLabel}`);
63
+ logStep('4/8', `CODE REVIEW (Round ${iteration}/${config.reviewIterations})${depthLabel}`);
64
64
  const prompt = getPromptForDepth(depth);
65
65
  const result = await runClaude({ prompt, continueConversation: true }, config);
66
66
  const noIssuesFound = result.success && detectNoIssues(result.output);
@@ -1,7 +1,7 @@
1
1
  import { logStep } from '../logger.js';
2
2
  import { runClaude } from '../claude.js';
3
3
  export async function stepSimplify(config) {
4
- logStep('4/9', 'CODE SIMPLIFICATION');
4
+ logStep('3/8', 'CODE SIMPLIFICATION');
5
5
  const prompt = `Refine the recently modified code for clarity, consistency, and maintainability while preserving all functionality.
6
6
 
7
7
  ## Core Principles:
@@ -1,7 +1,7 @@
1
1
  import { logStep } from '../logger.js';
2
2
  import { runClaude } from '../claude.js';
3
3
  export async function stepSolidCleanCode(config) {
4
- logStep('6/9', 'SOLID PRINCIPLES & CLEAN CODE');
4
+ logStep('5/8', 'SOLID PRINCIPLES & CLEAN CODE');
5
5
  const prompt = `Review the code for SOLID principles and Clean Code compliance:
6
6
 
7
7
  ## SOLID Principles:
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { logStep } from '../logger.js';
3
3
  import { runClaude } from '../claude.js';
4
4
  export async function stepTest(config) {
5
- logStep('7/9', 'TESTING');
5
+ logStep('6/8', 'TESTING');
6
6
  if (config.skipTests) {
7
7
  console.log(chalk.yellow('[SKIPPED]') + ' Testing step skipped via configuration');
8
8
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "software-engineer",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
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",