hackmyagent 0.8.1 → 0.9.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/dist/cli.js CHANGED
@@ -3530,5 +3530,224 @@ Examples:
3530
3530
  process.exit(1);
3531
3531
  }
3532
3532
  });
3533
+ // Grade display colors
3534
+ function gradeColor(grade) {
3535
+ switch (grade) {
3536
+ case 'A': return colors.green;
3537
+ case 'B': return colors.green;
3538
+ case 'C': return colors.yellow;
3539
+ case 'D': return colors.red;
3540
+ case 'F': return colors.brightRed;
3541
+ }
3542
+ }
3543
+ // Domain percentage bar for text output
3544
+ function domainBar(pct) {
3545
+ if (pct >= 80)
3546
+ return colors.green;
3547
+ if (pct >= 60)
3548
+ return colors.yellow;
3549
+ if (pct >= 40)
3550
+ return colors.yellow;
3551
+ return colors.red;
3552
+ }
3553
+ program
3554
+ .command('scan-soul')
3555
+ .description(`Scan behavioral governance coverage
3556
+
3557
+ Analyzes SOUL.md (or equivalent governance file) for coverage
3558
+ across 8 behavioral governance domains with 26 security controls.
3559
+
3560
+ Searches for governance files in priority order:
3561
+ SOUL.md > system-prompt.md > SYSTEM_PROMPT.md > .cursorrules
3562
+ > .github/copilot-instructions.md > CLAUDE.md > .clinerules
3563
+ > instructions.md > constitution.md > agent-config.yaml
3564
+
3565
+ Domains checked (OASB v2):
3566
+ 7. Trust Hierarchy 8. Capability Boundaries
3567
+ 9. Injection Hardening 10. Data Handling
3568
+ 11. Hardcoded Behaviors 12. Agentic Safety
3569
+ 13. Honesty & Transparency 14. Human Oversight
3570
+
3571
+ Grade: A (80-100), B (60-79), C (40-59), D (20-39), F (0-19)
3572
+ Critical floor: Missing SOUL-IH-003 or SOUL-HB-001 caps grade at C.
3573
+
3574
+ Examples:
3575
+ $ hackmyagent scan-soul Scan current directory
3576
+ $ hackmyagent scan-soul ./my-agent Scan specific directory
3577
+ $ hackmyagent scan-soul --json Machine-readable output
3578
+ $ hackmyagent scan-soul --verbose Show all controls`)
3579
+ .argument('[directory]', 'Directory to scan (defaults to current directory)', '.')
3580
+ .option('--json', 'Output as JSON')
3581
+ .option('-v, --verbose', 'Show individual control results')
3582
+ .option('--tier <tier>', 'Override agent tier detection (BASIC, TOOL-USING, AGENTIC, MULTI-AGENT)')
3583
+ .option('--fail-below <score>', 'Exit 1 if score below threshold (0-100)')
3584
+ .action(async (directory, options) => {
3585
+ try {
3586
+ const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
3587
+ if (!require('fs').existsSync(targetDir)) {
3588
+ process.stderr.write(`Error: Directory '${targetDir}' does not exist.\n`);
3589
+ process.exit(1);
3590
+ }
3591
+ const scanner = new index_1.SoulScanner();
3592
+ const result = await scanner.scanSoul(targetDir, {
3593
+ verbose: options.verbose,
3594
+ tier: options.tier,
3595
+ });
3596
+ // JSON output
3597
+ if (options.json) {
3598
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
3599
+ // Check fail threshold
3600
+ if (options.failBelow) {
3601
+ const threshold = parseInt(options.failBelow, 10);
3602
+ if (!isNaN(threshold) && result.score < threshold) {
3603
+ process.exit(1);
3604
+ }
3605
+ }
3606
+ return;
3607
+ }
3608
+ // Text output
3609
+ process.stdout.write('\nOASB v2 Behavioral Governance Scan\n');
3610
+ process.stdout.write('----------------------------------------------------\n\n');
3611
+ if (result.file) {
3612
+ process.stdout.write(`File: ${result.file} (${result.fileSize.toLocaleString()} chars)\n`);
3613
+ }
3614
+ else {
3615
+ process.stdout.write(`File: ${colors.red}No governance file found${colors.reset}\n`);
3616
+ process.stdout.write(` Searched: ${['SOUL.md', 'system-prompt.md', 'CLAUDE.md', '...'].join(', ')}\n`);
3617
+ }
3618
+ process.stdout.write(`Agent Tier: ${result.agentTier} (auto-detected)\n\n`);
3619
+ process.stdout.write('Domain Scores:\n');
3620
+ for (const domain of result.domains) {
3621
+ const pctColor = domainBar(domain.percentage);
3622
+ const label = (domain.domain + ':').padEnd(26);
3623
+ process.stdout.write(` ${label}${pctColor}${domain.passed}/${domain.total} (${domain.percentage}%)${colors.reset}\n`);
3624
+ // Verbose: show individual controls
3625
+ if (options.verbose) {
3626
+ for (const ctrl of domain.controls) {
3627
+ const status = ctrl.passed
3628
+ ? `${colors.green}PASS${colors.reset}`
3629
+ : `${colors.red}FAIL${colors.reset}`;
3630
+ process.stdout.write(` ${ctrl.id}: ${status} ${ctrl.name}\n`);
3631
+ }
3632
+ }
3633
+ }
3634
+ process.stdout.write('\n');
3635
+ // Score and grade
3636
+ const gc = gradeColor(result.grade);
3637
+ process.stdout.write(`Governance Score: ${gc}${result.score}/100 (Grade: ${result.grade})${colors.reset}\n`);
3638
+ if (result.criticalFloor) {
3639
+ process.stdout.write(`${colors.yellow}Critical Floor: APPLIED${colors.reset} (${result.criticalMissing.join(', ')} missing)\n`);
3640
+ }
3641
+ // Path forward
3642
+ const missing = result.totalControls - result.totalPassed;
3643
+ if (missing > 0) {
3644
+ process.stdout.write(`\n${missing} control${missing === 1 ? '' : 's'} missing.`);
3645
+ process.stdout.write(` Run '${colors.cyan}hackmyagent harden-soul${colors.reset}' to remediate.\n`);
3646
+ }
3647
+ else {
3648
+ process.stdout.write(`\n${colors.green}All ${result.totalControls} governance controls covered.${colors.reset}\n`);
3649
+ }
3650
+ process.stdout.write('\n');
3651
+ // Check fail threshold
3652
+ if (options.failBelow) {
3653
+ const threshold = parseInt(options.failBelow, 10);
3654
+ if (!isNaN(threshold) && result.score < threshold) {
3655
+ process.stderr.write(`Score ${result.score} is below threshold ${threshold}\n`);
3656
+ process.exit(1);
3657
+ }
3658
+ }
3659
+ }
3660
+ catch (error) {
3661
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`);
3662
+ process.exit(1);
3663
+ }
3664
+ });
3665
+ program
3666
+ .command('harden-soul')
3667
+ .description(`Generate or update SOUL.md with missing governance sections
3668
+
3669
+ Runs scan-soul internally to identify missing controls, then generates
3670
+ template content for each missing domain. Existing content is preserved.
3671
+
3672
+ Modes:
3673
+ Default: Append missing sections to SOUL.md (or create it)
3674
+ --dry-run: Preview what would be added without modifying files
3675
+
3676
+ Examples:
3677
+ $ hackmyagent harden-soul Add missing sections
3678
+ $ hackmyagent harden-soul --dry-run Preview changes
3679
+ $ hackmyagent harden-soul ./my-agent Target specific directory
3680
+ $ hackmyagent harden-soul --json Machine-readable output`)
3681
+ .argument('[directory]', 'Directory to harden (defaults to current directory)', '.')
3682
+ .option('--dry-run', 'Preview changes without modifying files')
3683
+ .option('--json', 'Output as JSON')
3684
+ .action(async (directory, options) => {
3685
+ try {
3686
+ const targetDir = directory.startsWith('/') ? directory : process.cwd() + '/' + directory;
3687
+ if (!require('fs').existsSync(targetDir)) {
3688
+ process.stderr.write(`Error: Directory '${targetDir}' does not exist.\n`);
3689
+ process.exit(1);
3690
+ }
3691
+ const scanner = new index_1.SoulScanner();
3692
+ const result = await scanner.hardenSoul(targetDir, { dryRun: options.dryRun });
3693
+ // JSON output
3694
+ if (options.json) {
3695
+ // Exclude full content from JSON to keep it concise
3696
+ const jsonResult = {
3697
+ file: result.file,
3698
+ sectionsAdded: result.sectionsAdded,
3699
+ controlsAdded: result.controlsAdded,
3700
+ dryRun: result.dryRun,
3701
+ existedBefore: result.existedBefore,
3702
+ };
3703
+ process.stdout.write(JSON.stringify(jsonResult, null, 2) + '\n');
3704
+ return;
3705
+ }
3706
+ // Text output
3707
+ if (result.sectionsAdded.length === 0) {
3708
+ process.stdout.write(`\n${colors.green}All governance domains already have sections in ${result.file}.${colors.reset}\n`);
3709
+ process.stdout.write(`Run 'hackmyagent scan-soul --verbose' to see individual control coverage.\n\n`);
3710
+ return;
3711
+ }
3712
+ if (result.dryRun) {
3713
+ process.stdout.write('\nHarden SOUL (dry-run)\n');
3714
+ process.stdout.write('----------------------------------------------------\n\n');
3715
+ process.stdout.write(`Target: ${result.file}`);
3716
+ if (result.existedBefore) {
3717
+ process.stdout.write(' (append)\n');
3718
+ }
3719
+ else {
3720
+ process.stdout.write(' (create)\n');
3721
+ }
3722
+ process.stdout.write(`Sections to add: ${result.sectionsAdded.length}\n`);
3723
+ process.stdout.write(`Controls covered: +${result.controlsAdded}\n\n`);
3724
+ process.stdout.write('Sections:\n');
3725
+ for (const section of result.sectionsAdded) {
3726
+ process.stdout.write(` ${colors.cyan}+${colors.reset} ${section}\n`);
3727
+ }
3728
+ process.stdout.write(`\nRun without --dry-run to apply changes.\n\n`);
3729
+ }
3730
+ else {
3731
+ process.stdout.write('\nHarden SOUL\n');
3732
+ process.stdout.write('----------------------------------------------------\n\n');
3733
+ if (result.existedBefore) {
3734
+ process.stdout.write(`Updated: ${result.file}\n`);
3735
+ }
3736
+ else {
3737
+ process.stdout.write(`Created: ${result.file}\n`);
3738
+ }
3739
+ process.stdout.write(`Added ${result.sectionsAdded.length} section${result.sectionsAdded.length === 1 ? '' : 's'}:\n`);
3740
+ for (const section of result.sectionsAdded) {
3741
+ process.stdout.write(` ${colors.green}+${colors.reset} ${section}\n`);
3742
+ }
3743
+ process.stdout.write(`Controls covered: +${result.controlsAdded}\n\n`);
3744
+ process.stdout.write(`Run '${colors.cyan}hackmyagent scan-soul${colors.reset}' to verify coverage.\n\n`);
3745
+ }
3746
+ }
3747
+ catch (error) {
3748
+ process.stderr.write(`Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`);
3749
+ process.exit(1);
3750
+ }
3751
+ });
3533
3752
  program.parse();
3534
3753
  //# sourceMappingURL=cli.js.map