@hone-ai/cli 1.8.0 → 1.8.1

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/hone-cli.js CHANGED
@@ -4429,6 +4429,216 @@ process.on('SIGINT', () => {
4429
4429
  process.exit(0);
4430
4430
  });
4431
4431
 
4432
+ // ── Release Review (pre-deployment holistic review) ──────────────────────────
4433
+ program
4434
+ .command('release-review')
4435
+ .description('Holistic code review of all changed files before deployment (runs Opus)')
4436
+ .option('--base <branch>', 'Base branch to diff against', 'main')
4437
+ .option('--format <fmt>', 'Output format: pretty or json', 'pretty')
4438
+ .option('--dry-run', 'Show what would be reviewed without calling the LLM', false)
4439
+ .option('--max-files <n>', 'Max source files to include in review', '40')
4440
+ .action(async (opts) => {
4441
+ const { execSync } = require('child_process');
4442
+ const fs = require('fs');
4443
+ const repoRoot = process.cwd();
4444
+
4445
+ // 1. Get changed files
4446
+ let changedFiles;
4447
+ try {
4448
+ const raw = execSync(`git diff --name-only origin/${opts.base}...HEAD`, { encoding: 'utf8', cwd: repoRoot });
4449
+ changedFiles = raw.trim().split('\n').filter(Boolean);
4450
+ } catch {
4451
+ try {
4452
+ const raw = execSync('git diff --name-only HEAD~10', { encoding: 'utf8', cwd: repoRoot });
4453
+ changedFiles = raw.trim().split('\n').filter(Boolean);
4454
+ } catch {
4455
+ console.error('Could not determine changed files. Run from a git repo.');
4456
+ process.exit(1);
4457
+ }
4458
+ }
4459
+
4460
+ if (changedFiles.length === 0) {
4461
+ console.log('No changed files found. Nothing to review.');
4462
+ process.exit(0);
4463
+ }
4464
+
4465
+ // 2. Filter to source files
4466
+ const sourceExts = ['.js', '.ts', '.py', '.go', '.java', '.rb', '.rs', '.sql', '.yml', '.yaml'];
4467
+ const sourceFiles = changedFiles.filter(f =>
4468
+ sourceExts.some(ext => f.endsWith(ext)) &&
4469
+ !f.includes('node_modules') && !f.includes('.test.') && !f.includes('/test/')
4470
+ );
4471
+
4472
+ const maxFiles = parseInt(opts.maxFiles, 10) || 40;
4473
+ const filesToReview = sourceFiles.slice(0, maxFiles);
4474
+
4475
+ console.log('');
4476
+ console.log('Hone AI — Production Review');
4477
+ console.log('================================');
4478
+ console.log(`Base: ${opts.base}`);
4479
+ console.log(`Changed files: ${changedFiles.length} total, ${sourceFiles.length} source, ${filesToReview.length} to review`);
4480
+ console.log('');
4481
+
4482
+ if (opts.dryRun) {
4483
+ console.log('Source files that would be reviewed:');
4484
+ for (const f of filesToReview) console.log(` ${f}`);
4485
+ if (sourceFiles.length > maxFiles) console.log(` ... and ${sourceFiles.length - maxFiles} more (increase --max-files)`);
4486
+ process.exit(0);
4487
+ }
4488
+
4489
+ // 3. Check for API key
4490
+ const apiKey = process.env.ANTHROPIC_API_KEY;
4491
+ if (!apiKey) {
4492
+ console.error('ANTHROPIC_API_KEY not set. Required for production review (Opus model).');
4493
+ console.error('Set it: export ANTHROPIC_API_KEY=sk-ant-...');
4494
+ process.exit(1);
4495
+ }
4496
+
4497
+ // 4. Build the diff content (truncated per-file to stay within context)
4498
+ let diffContent;
4499
+ try {
4500
+ diffContent = execSync(`git diff origin/${opts.base}...HEAD -- ${filesToReview.map(f => `'${f}'`).join(' ')}`, {
4501
+ encoding: 'utf8', cwd: repoRoot, maxBuffer: 10 * 1024 * 1024,
4502
+ });
4503
+ } catch {
4504
+ try {
4505
+ diffContent = execSync('git diff HEAD~10', { encoding: 'utf8', cwd: repoRoot, maxBuffer: 10 * 1024 * 1024 });
4506
+ } catch (e) {
4507
+ console.error(`Could not generate diff: ${e.message}`);
4508
+ process.exit(1);
4509
+ }
4510
+ }
4511
+
4512
+ // Truncate if over 100k chars (~25k tokens) to stay within budget
4513
+ const MAX_DIFF_CHARS = 100000;
4514
+ if (diffContent.length > MAX_DIFF_CHARS) {
4515
+ diffContent = diffContent.slice(0, MAX_DIFF_CHARS) + '\n\n[... diff truncated at 100k chars ...]';
4516
+ }
4517
+
4518
+ // 5. Build the prompt
4519
+ const systemPrompt = [
4520
+ '# Production Reviewer — Pre-Deployment Gate',
4521
+ '',
4522
+ 'You are reviewing ALL changed files holistically before deployment.',
4523
+ 'Your job is to catch cross-file issues that per-story reviews miss.',
4524
+ '',
4525
+ '## Check each file for:',
4526
+ '1. SQL injection (parameterized queries?)',
4527
+ '2. Tenant isolation (org_id in every query?)',
4528
+ '3. Error handling (unhandled promises?)',
4529
+ '4. Race conditions (concurrent access?)',
4530
+ '5. Resource leaks (connections released?)',
4531
+ '6. Security (secrets exposed? auth correct?)',
4532
+ '7. Logic bugs (null handling? type coercion?)',
4533
+ '8. Dead code (built but never wired?)',
4534
+ '9. Performance (N+1? unbounded? large payloads?)',
4535
+ '',
4536
+ '## Check cross-file interactions:',
4537
+ '- Do modules wire together correctly?',
4538
+ '- Is auth middleware in correct order?',
4539
+ '- Are exported functions imported somewhere?',
4540
+ '- Do CLI messages reference options that exist?',
4541
+ '- Are DB constraints consistent with app validation?',
4542
+ '',
4543
+ '## Rate each finding: CRITICAL / HIGH / MEDIUM / LOW',
4544
+ '- CRITICAL: tenant isolation breach, data leak, security bypass',
4545
+ '- HIGH: race condition, dead safety code, UX broken',
4546
+ '- MEDIUM: performance waste, missing validation, resource leak',
4547
+ '- LOW: dead code, cosmetic',
4548
+ '',
4549
+ '## Output as JSON:',
4550
+ '```json',
4551
+ '{',
4552
+ ' "findings": [{ "severity": "...", "file": "...", "line": N, "issue": "...", "recommendation": "..." }],',
4553
+ ' "crossFileChecks": [{ "check": "...", "status": "ok|issue", "detail": "..." }],',
4554
+ ' "summary": { "critical": N, "high": N, "medium": N, "low": N },',
4555
+ ' "recommendation": "DEPLOY | FIX_FIRST | DO_NOT_DEPLOY"',
4556
+ '}',
4557
+ '```',
4558
+ ].join('\n');
4559
+
4560
+ const userPrompt = [
4561
+ `## Files changed (${filesToReview.length} source files):`,
4562
+ filesToReview.map(f => `- ${f}`).join('\n'),
4563
+ '',
4564
+ '## Full diff:',
4565
+ '```diff',
4566
+ diffContent,
4567
+ '```',
4568
+ '',
4569
+ 'Review ALL files holistically. Return findings as JSON.',
4570
+ ].join('\n');
4571
+
4572
+ console.log('Calling Anthropic API (claude-opus-4-20250514)...');
4573
+ console.log('');
4574
+
4575
+ // 6. Call Anthropic Messages API
4576
+ try {
4577
+ const { data } = await axios.post('https://api.anthropic.com/v1/messages', {
4578
+ model: 'claude-opus-4-20250514',
4579
+ max_tokens: 8192,
4580
+ system: systemPrompt,
4581
+ messages: [{ role: 'user', content: userPrompt }],
4582
+ }, {
4583
+ headers: {
4584
+ 'x-api-key': apiKey,
4585
+ 'anthropic-version': '2023-06-01',
4586
+ 'content-type': 'application/json',
4587
+ },
4588
+ timeout: 120000,
4589
+ });
4590
+
4591
+ const responseText = data.content?.[0]?.text || '';
4592
+
4593
+ // 7. Parse and display results
4594
+ if (opts.format === 'json') {
4595
+ // Try to extract JSON from response
4596
+ const jsonMatch = responseText.match(/\{[\s\S]*\}/);
4597
+ if (jsonMatch) {
4598
+ try {
4599
+ const parsed = JSON.parse(jsonMatch[0]);
4600
+ console.log(JSON.stringify({
4601
+ base: opts.base,
4602
+ totalFiles: changedFiles.length,
4603
+ sourceFiles: sourceFiles.length,
4604
+ reviewedFiles: filesToReview.length,
4605
+ model: 'claude-opus-4-20250514',
4606
+ inputTokens: data.usage?.input_tokens || 0,
4607
+ outputTokens: data.usage?.output_tokens || 0,
4608
+ ...parsed,
4609
+ }, null, 2));
4610
+ } catch {
4611
+ console.log(JSON.stringify({ raw: responseText }, null, 2));
4612
+ }
4613
+ } else {
4614
+ console.log(JSON.stringify({ raw: responseText }, null, 2));
4615
+ }
4616
+ } else {
4617
+ console.log(responseText);
4618
+ }
4619
+
4620
+ // 8. Exit code based on findings
4621
+ const hasCritical = responseText.includes('"CRITICAL"') || responseText.includes('"critical"');
4622
+ const recommendation = responseText.includes('DO_NOT_DEPLOY');
4623
+ if (hasCritical || recommendation) {
4624
+ console.log('');
4625
+ console.log('CRITICAL issues found. Fix before deploying.');
4626
+ process.exit(1);
4627
+ }
4628
+ } catch (e) {
4629
+ const status = e.response?.status;
4630
+ const msg = e.response?.data?.error?.message || e.message;
4631
+ if (status === 401) {
4632
+ console.error('Invalid ANTHROPIC_API_KEY. Check your key and try again.');
4633
+ } else if (status === 429) {
4634
+ console.error('Rate limited by Anthropic API. Try again shortly.');
4635
+ } else {
4636
+ console.error(`Production review failed: ${msg}`);
4637
+ }
4638
+ process.exit(1);
4639
+ }
4640
+ });
4641
+
4432
4642
  // ── CLI setup ─────────────────────────────────────────────────────────────────
4433
4643
  program
4434
4644
  .name('hone')
@@ -56,6 +56,32 @@ const PIPELINE_CONTRACTS = [
56
56
  outputGate: 'step_3',
57
57
  metadataField: 'step_3.gate_result',
58
58
  },
59
+ {
60
+ agent: 'e2e-test-spec-writer',
61
+ step: '5a',
62
+ inputArtifact: 'step-4-implementation.md',
63
+ inputGate: 'step_4.gate_result',
64
+ outputArtifact: null,
65
+ outputGate: null,
66
+ metadataField: null,
67
+ extraChecks: [
68
+ { text: 'Playwright', check: 'playwright', detail: 'generates Playwright specs' },
69
+ { text: 'data-testid', check: 'data_testid', detail: 'uses data-testid selectors' },
70
+ ],
71
+ },
72
+ {
73
+ agent: 'e2e-qa-spec-healer',
74
+ step: 'independent',
75
+ inputArtifact: null,
76
+ inputGate: null,
77
+ outputArtifact: null,
78
+ outputGate: null,
79
+ metadataField: null,
80
+ extraChecks: [
81
+ { text: 'DIAGNOSIS', check: 'diagnosis', detail: 'provides diagnosis category' },
82
+ { text: 'Application bug', check: 'app_bug_handling', detail: 'handles application bugs (skip + file bug)' },
83
+ ],
84
+ },
59
85
  {
60
86
  agent: 'code-builder',
61
87
  step: 4,
@@ -106,6 +132,21 @@ const PIPELINE_CONTRACTS = [
106
132
  { text: 'test_strategy', check: 'test_strategy', detail: 'includes test_strategy in plan' },
107
133
  ],
108
134
  },
135
+ {
136
+ agent: 'release-reviewer',
137
+ step: 'independent',
138
+ inputArtifact: null,
139
+ inputGate: null,
140
+ outputArtifact: null,
141
+ outputGate: null,
142
+ metadataField: null,
143
+ extraChecks: [
144
+ { text: 'CRITICAL', check: 'severity_critical', detail: 'defines CRITICAL severity level' },
145
+ { text: 'cross-file', check: 'cross_file_review', detail: 'checks cross-file interactions' },
146
+ { text: 'tenant', check: 'tenant_isolation', detail: 'checks tenant isolation' },
147
+ { text: 'DEPLOY', check: 'deploy_recommendation', detail: 'provides deploy/no-deploy recommendation' },
148
+ ],
149
+ },
109
150
  ];
110
151
 
111
152
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hone-ai/cli",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Hone AI — Enterprise SDLC Pipeline CLI",
5
5
  "main": "hone-cli.js",
6
6
  "bin": {