@nerviq/cli 1.29.0 → 1.29.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +1527 -1493
  2. package/README.md +550 -538
  3. package/SECURITY.md +82 -82
  4. package/bin/cli.js +2562 -2558
  5. package/docs/api-reference.md +356 -356
  6. package/docs/audit-fix.md +109 -0
  7. package/docs/autofix.md +3 -62
  8. package/docs/getting-started.md +1 -1
  9. package/docs/index.html +592 -592
  10. package/docs/integration-contracts.md +287 -287
  11. package/docs/maintenance.md +128 -128
  12. package/docs/new-platform-guide.md +202 -202
  13. package/docs/release-process.md +63 -0
  14. package/docs/shallow-risk.md +244 -244
  15. package/docs/why-nerviq.md +82 -82
  16. package/package.json +67 -67
  17. package/src/aider/activity.js +226 -226
  18. package/src/aider/context.js +162 -162
  19. package/src/aider/freshness.js +123 -123
  20. package/src/aider/techniques.js +3465 -3465
  21. package/src/audit/layers.js +180 -180
  22. package/src/audit.js +1032 -1032
  23. package/src/benchmark.js +299 -299
  24. package/src/codex/activity.js +324 -324
  25. package/src/codex/freshness.js +142 -142
  26. package/src/codex/techniques.js +4895 -4895
  27. package/src/context.js +326 -326
  28. package/src/continuous-ops.js +11 -1
  29. package/src/convert.js +340 -340
  30. package/src/copilot/config-parser.js +280 -280
  31. package/src/copilot/context.js +218 -218
  32. package/src/copilot/freshness.js +177 -177
  33. package/src/copilot/patch.js +238 -238
  34. package/src/copilot/techniques.js +3578 -3578
  35. package/src/cursor/freshness.js +194 -194
  36. package/src/cursor/patch.js +243 -243
  37. package/src/cursor/techniques.js +3735 -3735
  38. package/src/doctor.js +201 -201
  39. package/src/fix-engine.js +511 -8
  40. package/src/formatters/csv.js +86 -86
  41. package/src/formatters/junit.js +123 -123
  42. package/src/formatters/markdown.js +164 -164
  43. package/src/formatters/otel.js +151 -151
  44. package/src/freshness.js +156 -156
  45. package/src/gemini/activity.js +402 -402
  46. package/src/gemini/context.js +290 -290
  47. package/src/gemini/freshness.js +183 -183
  48. package/src/gemini/patch.js +229 -229
  49. package/src/gemini/techniques.js +3811 -3811
  50. package/src/governance.js +533 -533
  51. package/src/harmony/audit.js +306 -306
  52. package/src/i18n.js +63 -63
  53. package/src/insights.js +119 -119
  54. package/src/integrations.js +134 -134
  55. package/src/locales/en.json +33 -33
  56. package/src/locales/es.json +33 -33
  57. package/src/migrate.js +354 -354
  58. package/src/opencode/activity.js +286 -286
  59. package/src/opencode/freshness.js +137 -137
  60. package/src/opencode/techniques.js +3450 -3450
  61. package/src/setup/analysis.js +12 -12
  62. package/src/setup.js +7 -6
  63. package/src/shallow-risk/index.js +56 -56
  64. package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +50 -50
  65. package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +46 -46
  66. package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +46 -46
  67. package/src/shallow-risk/patterns/agent-config-missing-file.js +317 -317
  68. package/src/shallow-risk/patterns/agent-config-secret-literal.js +49 -49
  69. package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +34 -34
  70. package/src/shallow-risk/patterns/hook-script-missing.js +70 -70
  71. package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +52 -52
  72. package/src/shallow-risk/shared.js +648 -648
  73. package/src/source-urls.js +295 -295
  74. package/src/state-paths.js +85 -85
  75. package/src/supplemental-checks.js +805 -805
  76. package/src/telemetry.js +160 -160
  77. package/src/windsurf/context.js +359 -359
  78. package/src/windsurf/freshness.js +194 -194
  79. package/src/windsurf/patch.js +231 -231
  80. package/src/windsurf/techniques.js +3779 -3779
package/src/fix-engine.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const readline = require('readline');
4
+ const { spawnSync } = require('child_process');
4
5
 
5
6
  const { ProjectContext } = require('./context');
6
7
  const { TECHNIQUES, STACKS } = require('./techniques');
@@ -33,7 +34,10 @@ const CUSTOM_FIXER_KEYS = new Set([
33
34
  'changelog',
34
35
  'contributing',
35
36
  'gitIgnoreEnv',
37
+ 'gitIgnoreClaudeLocal',
38
+ 'gitignoreClaudeLocal',
36
39
  'secretsProtection',
40
+ 'editorconfig',
37
41
  ]);
38
42
 
39
43
  const AUDIT_FIX_KEYS = new Set([
@@ -45,6 +49,11 @@ const AUDIT_FIX_KEYS = new Set([
45
49
  'license',
46
50
  'changelog',
47
51
  'contributing',
52
+ 'gitIgnoreEnv',
53
+ 'gitIgnoreClaudeLocal',
54
+ 'gitignoreClaudeLocal',
55
+ 'secretsProtection',
56
+ 'editorconfig',
48
57
  ]);
49
58
 
50
59
  const INSTRUCTION_KEYS = new Set([
@@ -62,6 +71,25 @@ const QUALITY_COMMAND_KEYS = new Set([
62
71
  'buildCommand',
63
72
  ]);
64
73
 
74
+ const AUDIT_FIX_ALLOWED_PATHS = new Set([
75
+ '.claude/CLAUDE.md',
76
+ '.claude/settings.json',
77
+ '.editorconfig',
78
+ '.gitignore',
79
+ '.codex/AGENTS.md',
80
+ 'AGENTS.md',
81
+ 'CHANGELOG.md',
82
+ 'CLAUDE.md',
83
+ 'CONTRIBUTING.md',
84
+ 'LICENSE',
85
+ ]);
86
+
87
+ const GITIGNORE_ENTRIES_BY_KEY = {
88
+ gitIgnoreEnv: ['.env', '.env.*'],
89
+ gitIgnoreClaudeLocal: ['.claude/settings.local.json'],
90
+ gitignoreClaudeLocal: ['CLAUDE.local.md'],
91
+ };
92
+
65
93
  function normalizeNewlines(content) {
66
94
  return String(content || '').replace(/\r\n/g, '\n');
67
95
  }
@@ -301,6 +329,23 @@ function buildContributingTemplate(ctx, commands) {
301
329
  ].join('\n');
302
330
  }
303
331
 
332
+ function buildEditorConfigTemplate() {
333
+ return [
334
+ 'root = true',
335
+ '',
336
+ '[*]',
337
+ 'charset = utf-8',
338
+ 'end_of_line = lf',
339
+ 'indent_style = space',
340
+ 'indent_size = 2',
341
+ 'insert_final_newline = true',
342
+ 'trim_trailing_whitespace = true',
343
+ '',
344
+ '[*.md]',
345
+ 'trim_trailing_whitespace = false',
346
+ ].join('\n');
347
+ }
348
+
304
349
  function buildInstructionOperation({ ctx, stacks, failedByKey, platform, targetKeys }) {
305
350
  const keys = targetKeys.filter((key) => INSTRUCTION_KEYS.has(key));
306
351
  if (keys.length === 0) return null;
@@ -356,10 +401,20 @@ function buildSimpleCreateOperation(filePath, content, keys, failedByKey) {
356
401
  };
357
402
  }
358
403
 
359
- function buildGitIgnoreOperation(ctx, failedByKey) {
404
+ function buildGitIgnoreOperation(ctx, failedByKey, keys = ['gitIgnoreEnv']) {
360
405
  const existing = ctx.fileContent('.gitignore');
361
406
  const normalized = normalizeNewlines(existing || '');
362
- if (/(^|\n)\.env(\n|$)/.test(normalized) || normalized.includes('.env.*')) {
407
+ const normalizedKeys = unique(keys);
408
+ const entries = unique(normalizedKeys.flatMap((key) => GITIGNORE_ENTRIES_BY_KEY[key] || []));
409
+ const existingEntries = new Set(
410
+ normalized
411
+ .split('\n')
412
+ .map((line) => line.trim())
413
+ .filter((line) => line && !line.startsWith('#')),
414
+ );
415
+ const missingEntries = entries.filter((entry) => !existingEntries.has(entry));
416
+
417
+ if (missingEntries.length === 0) {
363
418
  return null;
364
419
  }
365
420
 
@@ -369,9 +424,9 @@ function buildGitIgnoreOperation(ctx, failedByKey) {
369
424
  path: '.gitignore',
370
425
  action: existing === null ? 'create' : 'patch',
371
426
  before: existing,
372
- after: ensureTrailingNewline(`${normalized}${prefix}.env\n.env.*\n`),
373
- keys: ['gitIgnoreEnv'],
374
- impact: highestImpact(['gitIgnoreEnv'], failedByKey),
427
+ after: ensureTrailingNewline(`${normalized}${prefix}${missingEntries.join('\n')}\n`),
428
+ keys: normalizedKeys,
429
+ impact: highestImpact(normalizedKeys, failedByKey),
375
430
  };
376
431
  }
377
432
 
@@ -389,7 +444,14 @@ function buildSecretsProtectionOperation(ctx, failedByKey) {
389
444
 
390
445
  settings.permissions = settings.permissions || {};
391
446
  settings.permissions.deny = Array.isArray(settings.permissions.deny) ? settings.permissions.deny : [];
392
- const denyEntries = ['.env', '.env.*', '**/.env', '**/*.pem', '**/secrets/**'];
447
+ const denyEntries = [
448
+ 'Read(.env)',
449
+ 'Read(.env.*)',
450
+ 'Read(**/.env)',
451
+ 'Read(**/.env.*)',
452
+ 'Read(**/*.pem)',
453
+ 'Read(**/secrets/**)',
454
+ ];
393
455
  for (const entry of denyEntries) {
394
456
  if (!settings.permissions.deny.includes(entry)) {
395
457
  settings.permissions.deny.push(entry);
@@ -418,6 +480,14 @@ function buildSecretsProtectionOperation(ctx, failedByKey) {
418
480
  };
419
481
  }
420
482
 
483
+ function buildEditorConfigOperation(ctx, failedByKey) {
484
+ if (ctx.fileContent('.editorconfig') !== null) {
485
+ return null;
486
+ }
487
+
488
+ return buildSimpleCreateOperation('.editorconfig', buildEditorConfigTemplate(), ['editorconfig'], failedByKey);
489
+ }
490
+
421
491
  function buildFixPlan({ dir, platform, auditResult, targetKeys }) {
422
492
  const ctx = new ProjectContext(dir);
423
493
  const stacks = ctx.detectStacks(STACKS);
@@ -457,8 +527,10 @@ function buildFixPlan({ dir, platform, auditResult, targetKeys }) {
457
527
  failedByKey,
458
528
  ));
459
529
  }
460
- if (customKeys.includes('gitIgnoreEnv')) {
461
- const gitIgnoreOperation = buildGitIgnoreOperation(ctx, failedByKey);
530
+ const gitIgnoreKeys = ['gitIgnoreEnv', 'gitIgnoreClaudeLocal', 'gitignoreClaudeLocal']
531
+ .filter((key) => customKeys.includes(key));
532
+ if (gitIgnoreKeys.length > 0) {
533
+ const gitIgnoreOperation = buildGitIgnoreOperation(ctx, failedByKey, gitIgnoreKeys);
462
534
  if (gitIgnoreOperation) {
463
535
  operations.push(gitIgnoreOperation);
464
536
  }
@@ -469,6 +541,12 @@ function buildFixPlan({ dir, platform, auditResult, targetKeys }) {
469
541
  operations.push(secretsOperation);
470
542
  }
471
543
  }
544
+ if (customKeys.includes('editorconfig')) {
545
+ const editorconfigOperation = buildEditorConfigOperation(ctx, failedByKey);
546
+ if (editorconfigOperation) {
547
+ operations.push(editorconfigOperation);
548
+ }
549
+ }
472
550
 
473
551
  for (const key of templateKeys) {
474
552
  operations.push({
@@ -486,6 +564,211 @@ function buildFixPlan({ dir, platform, auditResult, targetKeys }) {
486
564
  });
487
565
  }
488
566
 
567
+ function isAuditAllowedPath(filePath) {
568
+ return AUDIT_FIX_ALLOWED_PATHS.has(String(filePath || '').replace(/\\/g, '/'));
569
+ }
570
+
571
+ function normalizeOperationForAudit(operation, failedByKey) {
572
+ if (!operation || operation.type !== 'file' || !isAuditAllowedPath(operation.path)) {
573
+ return null;
574
+ }
575
+
576
+ const evidence = unique((operation.keys || []).map((key) => {
577
+ const failed = failedByKey.get(key);
578
+ const file = failed?.file || operation.path;
579
+ const line = Number.isFinite(failed?.line) ? failed.line : 1;
580
+ return `${key}|${file}|${line}`;
581
+ })).map((entry) => {
582
+ const [key, file, rawLine] = entry.split('|');
583
+ const failed = failedByKey.get(key) || {};
584
+ return {
585
+ key,
586
+ name: failed.name || key,
587
+ fix: failed.fix || null,
588
+ file: file || operation.path,
589
+ line: Number.isFinite(Number(rawLine)) ? Number(rawLine) : 1,
590
+ };
591
+ });
592
+
593
+ const summaryLocation = evidence[0]
594
+ ? `${evidence[0].file}:${evidence[0].line}`
595
+ : `${operation.path}:1`;
596
+
597
+ return {
598
+ ...operation,
599
+ evidence,
600
+ summaryLocation,
601
+ };
602
+ }
603
+
604
+ function buildAuditFixPlan({ dir, platform, auditResult, targetKeys }) {
605
+ const failedResults = ((auditResult && auditResult.results) || []).filter((item) => item && item.passed === false);
606
+ const failedByKey = new Map(failedResults.map((item) => [item.key, item]));
607
+ const requestedKeys = unique(targetKeys).filter((key) => AUDIT_FIX_KEYS.has(key));
608
+ const filePlan = buildFixPlan({
609
+ dir,
610
+ platform,
611
+ auditResult,
612
+ targetKeys: requestedKeys,
613
+ })
614
+ .map((operation) => normalizeOperationForAudit(operation, failedByKey))
615
+ .filter(Boolean);
616
+
617
+ const plannedKeySet = new Set(filePlan.flatMap((operation) => operation.keys || []));
618
+ const advisoryOnly = failedResults
619
+ .filter((item) => !plannedKeySet.has(item.key))
620
+ .map((item) => ({
621
+ key: item.key,
622
+ name: item.name || item.key,
623
+ impact: item.impact || 'medium',
624
+ fix: item.fix || 'Manual fix required.',
625
+ file: item.file || null,
626
+ line: Number.isFinite(item.line) ? item.line : null,
627
+ }))
628
+ .sort(sortFailedResults);
629
+
630
+ return {
631
+ requestedKeys,
632
+ plan: filePlan,
633
+ advisoryOnly,
634
+ failedByKey,
635
+ };
636
+ }
637
+
638
+ function trimTrailingEmptyLine(lines) {
639
+ if (lines.length > 0 && lines[lines.length - 1] === '') {
640
+ return lines.slice(0, -1);
641
+ }
642
+ return lines;
643
+ }
644
+
645
+ function formatUnifiedDiff(operation) {
646
+ const beforeLines = trimTrailingEmptyLine(
647
+ operation.before === null ? [] : normalizeNewlines(operation.before).split('\n'),
648
+ );
649
+ const afterLines = trimTrailingEmptyLine(normalizeNewlines(operation.after).split('\n'));
650
+
651
+ let start = 0;
652
+ while (
653
+ start < beforeLines.length &&
654
+ start < afterLines.length &&
655
+ beforeLines[start] === afterLines[start]
656
+ ) {
657
+ start += 1;
658
+ }
659
+
660
+ let endBefore = beforeLines.length - 1;
661
+ let endAfter = afterLines.length - 1;
662
+ while (
663
+ endBefore >= start &&
664
+ endAfter >= start &&
665
+ beforeLines[endBefore] === afterLines[endAfter]
666
+ ) {
667
+ endBefore -= 1;
668
+ endAfter -= 1;
669
+ }
670
+
671
+ const removed = beforeLines.slice(start, endBefore + 1);
672
+ const added = afterLines.slice(start, endAfter + 1);
673
+ const oldStart = operation.before === null ? 0 : start + 1;
674
+ const oldCount = operation.before === null ? 0 : removed.length;
675
+ const newStart = start + 1;
676
+ const newCount = added.length;
677
+
678
+ return [
679
+ `diff --git a/${operation.path} b/${operation.path}`,
680
+ operation.action === 'create' ? 'new file mode 100644' : null,
681
+ `--- ${operation.before === null ? '/dev/null' : `a/${operation.path}`}`,
682
+ `+++ b/${operation.path}`,
683
+ `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
684
+ ...removed.map((line) => `-${line}`),
685
+ ...added.map((line) => `+${line}`),
686
+ ].filter(Boolean).join('\n');
687
+ }
688
+
689
+ function renderAuditFixPatch(plan) {
690
+ if (!Array.isArray(plan) || plan.length === 0) {
691
+ return '';
692
+ }
693
+ return `${plan.map((operation) => formatUnifiedDiff(operation)).join('\n\n')}\n`;
694
+ }
695
+
696
+ function resolvePatchOutputPath(dir, outputPath) {
697
+ if (outputPath === '-') {
698
+ return '-';
699
+ }
700
+ if (outputPath) {
701
+ return path.isAbsolute(outputPath) ? outputPath : path.join(dir, outputPath);
702
+ }
703
+ return path.join(dir, 'audit-fix.patch');
704
+ }
705
+
706
+ function writeAuditFixPatch({ dir, outputPath, patch, logger }) {
707
+ const targetPath = resolvePatchOutputPath(dir, outputPath);
708
+ if (targetPath === '-') {
709
+ logger.log('');
710
+ logger.log(patch.trimEnd());
711
+ logger.log('');
712
+ return {
713
+ filePath: null,
714
+ relativePath: 'stdout',
715
+ };
716
+ }
717
+
718
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
719
+ fs.writeFileSync(targetPath, patch, 'utf8');
720
+ return {
721
+ filePath: targetPath,
722
+ relativePath: path.relative(dir, targetPath),
723
+ };
724
+ }
725
+
726
+ function formatAuditFixSummary(plan) {
727
+ return plan.map((operation) => {
728
+ const status = operation.action === 'create' ? 'A ' : 'M ';
729
+ const keys = (operation.keys || []).join(', ');
730
+ return ` ${status} ${operation.path} (${operation.summaryLocation}) [${keys}]`;
731
+ });
732
+ }
733
+
734
+ function formatAdvisoryItem(item) {
735
+ const where = item.file ? `${item.file}${item.line ? `:${item.line}` : ''}` : 'repo-level';
736
+ return ` - ${item.key} (${item.impact}) at ${where}: ${item.fix}`;
737
+ }
738
+
739
+ function runGit(args, dir) {
740
+ return spawnSync('git', args, {
741
+ cwd: dir,
742
+ encoding: 'utf8',
743
+ timeout: 30000,
744
+ });
745
+ }
746
+
747
+ function createAuditFixBranch(dir) {
748
+ const stamp = new Date().toISOString().replace(/[-:.TZ]/g, '').toLowerCase();
749
+ const branchName = `nerviq/autofix-${stamp}`;
750
+ let result = runGit(['switch', '-c', branchName], dir);
751
+ if (result.status !== 0) {
752
+ result = runGit(['checkout', '-b', branchName], dir);
753
+ }
754
+ if (result.status !== 0) {
755
+ throw new Error(result.stderr || result.stdout || 'Failed to create autofix branch.');
756
+ }
757
+ return branchName;
758
+ }
759
+
760
+ function stageAuditFixFiles(dir, plan, patchPath) {
761
+ const paths = unique([
762
+ ...plan.map((operation) => operation.path),
763
+ patchPath && patchPath !== 'stdout' ? patchPath : null,
764
+ ]);
765
+ if (paths.length === 0) return;
766
+ const result = runGit(['add', '--', ...paths], dir);
767
+ if (result.status !== 0) {
768
+ throw new Error(result.stderr || result.stdout || 'Failed to stage autofix files.');
769
+ }
770
+ }
771
+
489
772
  function formatDiff(filePath, before, after) {
490
773
  const beforeLines = before === null ? [] : normalizeNewlines(before).split('\n');
491
774
  const afterLines = normalizeNewlines(after).split('\n');
@@ -773,11 +1056,231 @@ async function applyFixes({
773
1056
  };
774
1057
  }
775
1058
 
1059
+ async function runAuditFixWorkflow({
1060
+ dir,
1061
+ platform,
1062
+ auditResult,
1063
+ targetKeys,
1064
+ auto = false,
1065
+ apply = false,
1066
+ pr = false,
1067
+ outputPath = null,
1068
+ logger = console,
1069
+ }) {
1070
+ const { requestedKeys, plan, advisoryOnly, failedByKey } = buildAuditFixPlan({
1071
+ dir,
1072
+ platform,
1073
+ auditResult,
1074
+ targetKeys,
1075
+ });
1076
+
1077
+ if (requestedKeys.length === 0 || plan.length === 0) {
1078
+ logger.log('');
1079
+ logger.log(' No deterministic audit autofixes are available for this repo.');
1080
+ if (advisoryOnly.length > 0) {
1081
+ logger.log(' Advisory only — manual fix required:');
1082
+ for (const item of advisoryOnly.slice(0, 12)) {
1083
+ logger.log(formatAdvisoryItem(item));
1084
+ }
1085
+ if (advisoryOnly.length > 12) {
1086
+ logger.log(` ... and ${advisoryOnly.length - 12} more advisory findings.`);
1087
+ }
1088
+ }
1089
+ logger.log('');
1090
+ return {
1091
+ exitCode: 2,
1092
+ requestedKeys,
1093
+ plan,
1094
+ advisoryOnly,
1095
+ patchArtifact: null,
1096
+ rollbackArtifact: null,
1097
+ reAudit: auditResult,
1098
+ unresolvedKeys: [],
1099
+ branchName: null,
1100
+ };
1101
+ }
1102
+
1103
+ if (apply && !auto && !pr) {
1104
+ logger.error('\n Error: `nerviq audit --fix --apply` requires `--auto`.\n');
1105
+ return {
1106
+ exitCode: 2,
1107
+ requestedKeys,
1108
+ plan,
1109
+ advisoryOnly,
1110
+ patchArtifact: null,
1111
+ rollbackArtifact: null,
1112
+ reAudit: auditResult,
1113
+ unresolvedKeys: [],
1114
+ branchName: null,
1115
+ };
1116
+ }
1117
+
1118
+ const patch = renderAuditFixPatch(plan);
1119
+ const patchArtifact = writeAuditFixPatch({
1120
+ dir,
1121
+ outputPath,
1122
+ patch,
1123
+ logger,
1124
+ });
1125
+
1126
+ logger.log('');
1127
+ logger.log(' Audit autofix plan');
1128
+ logger.log(' ═══════════════════════════════════════');
1129
+ for (const line of formatAuditFixSummary(plan)) {
1130
+ logger.log(line);
1131
+ }
1132
+ logger.log('');
1133
+ logger.log(` Patch: ${patchArtifact.relativePath}`);
1134
+ if (advisoryOnly.length > 0) {
1135
+ logger.log('');
1136
+ logger.log(' Advisory only — manual fix required:');
1137
+ for (const item of advisoryOnly.slice(0, 12)) {
1138
+ logger.log(formatAdvisoryItem(item));
1139
+ }
1140
+ if (advisoryOnly.length > 12) {
1141
+ logger.log(` ... and ${advisoryOnly.length - 12} more advisory findings.`);
1142
+ }
1143
+ }
1144
+
1145
+ if (!apply && !pr) {
1146
+ logger.log('');
1147
+ logger.log(' Dry run complete. No files were written.');
1148
+ logger.log(' Run `nerviq audit --fix --apply --auto` to apply these changes.');
1149
+ logger.log(' Run `nerviq audit --fix --pr` to create a local autofix branch and stage the files.');
1150
+ logger.log('');
1151
+ return {
1152
+ exitCode: 0,
1153
+ requestedKeys,
1154
+ plan,
1155
+ advisoryOnly,
1156
+ patchArtifact,
1157
+ rollbackArtifact: null,
1158
+ reAudit: auditResult,
1159
+ unresolvedKeys: [],
1160
+ branchName: null,
1161
+ };
1162
+ }
1163
+
1164
+ let branchName = null;
1165
+ const createdFiles = [];
1166
+ const patchedFiles = [];
1167
+ const warnings = [];
1168
+
1169
+ try {
1170
+ if (pr) {
1171
+ const repoCheck = runGit(['rev-parse', '--is-inside-work-tree'], dir);
1172
+ if (repoCheck.status !== 0) {
1173
+ throw new Error('`--pr` requires a git repository.');
1174
+ }
1175
+ branchName = createAuditFixBranch(dir);
1176
+ }
1177
+
1178
+ for (const operation of plan) {
1179
+ if (hasDoNotAutoEditMarker(operation.before)) {
1180
+ const warning = `Skipped ${operation.path}: DO NOT AUTOEDIT marker found.`;
1181
+ warnings.push(warning);
1182
+ logger.warn(` Warning: ${warning}`);
1183
+ continue;
1184
+ }
1185
+
1186
+ if (!isAuditAllowedPath(operation.path)) {
1187
+ const warning = `Skipped ${operation.path}: outside audit autofix allowlist.`;
1188
+ warnings.push(warning);
1189
+ logger.warn(` Warning: ${warning}`);
1190
+ continue;
1191
+ }
1192
+
1193
+ const fullPath = path.join(dir, operation.path);
1194
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1195
+ fs.writeFileSync(fullPath, operation.after, 'utf8');
1196
+ if (operation.action === 'create') {
1197
+ createdFiles.push(operation.path);
1198
+ } else {
1199
+ patchedFiles.push({ path: operation.path, previousContent: operation.before });
1200
+ }
1201
+ logger.log(` Applied ${operation.action === 'create' ? 'create' : 'patch'}: ${operation.path}`);
1202
+ }
1203
+ } catch (error) {
1204
+ const rollbackArtifact = createRollbackArtifact(dir, createdFiles, patchedFiles, 'audit-fix');
1205
+ logger.error(`\n Error: ${error.message}\n`);
1206
+ return {
1207
+ exitCode: 1,
1208
+ requestedKeys,
1209
+ plan,
1210
+ advisoryOnly,
1211
+ patchArtifact,
1212
+ rollbackArtifact,
1213
+ reAudit: auditResult,
1214
+ unresolvedKeys: requestedKeys,
1215
+ branchName,
1216
+ warnings,
1217
+ };
1218
+ }
1219
+
1220
+ const rollbackArtifact = createRollbackArtifact(dir, createdFiles, patchedFiles, 'audit-fix');
1221
+ const reAudit = await audit({ dir, platform, silent: true });
1222
+ const unresolvedKeys = requestedKeys.filter((key) => {
1223
+ const planned = plan.some((operation) => (operation.keys || []).includes(key));
1224
+ if (!planned) return false;
1225
+ const failed = failedByKey.get(key);
1226
+ if (!failed) return false;
1227
+ const check = (reAudit.results || []).find((item) => item.key === key);
1228
+ return !check || check.passed !== true;
1229
+ });
1230
+
1231
+ if (pr) {
1232
+ stageAuditFixFiles(dir, plan, patchArtifact.relativePath);
1233
+ }
1234
+
1235
+ logger.log('');
1236
+ logger.log(` Re-audit score: ${auditResult.score} -> ${reAudit.score}`);
1237
+ if (rollbackArtifact) {
1238
+ logger.log(` Rollback: ${rollbackArtifact.relativePath}`);
1239
+ }
1240
+ if (branchName) {
1241
+ logger.log(` Branch: ${branchName}`);
1242
+ logger.log(' Files are staged for review.');
1243
+ }
1244
+ if (unresolvedKeys.length > 0) {
1245
+ logger.log(` Unresolved targeted checks: ${unresolvedKeys.join(', ')}`);
1246
+ logger.log('');
1247
+ return {
1248
+ exitCode: 1,
1249
+ requestedKeys,
1250
+ plan,
1251
+ advisoryOnly,
1252
+ patchArtifact,
1253
+ rollbackArtifact,
1254
+ reAudit,
1255
+ unresolvedKeys,
1256
+ branchName,
1257
+ warnings,
1258
+ };
1259
+ }
1260
+
1261
+ logger.log(' Audit autofix completed successfully.');
1262
+ logger.log('');
1263
+ return {
1264
+ exitCode: 0,
1265
+ requestedKeys,
1266
+ plan,
1267
+ advisoryOnly,
1268
+ patchArtifact,
1269
+ rollbackArtifact,
1270
+ reAudit,
1271
+ unresolvedKeys,
1272
+ branchName,
1273
+ warnings,
1274
+ };
1275
+ }
1276
+
776
1277
  module.exports = {
777
1278
  AUDIT_FIX_KEYS,
778
1279
  CUSTOM_FIXER_KEYS,
779
1280
  applyFixes,
780
1281
  buildFixPlan,
1282
+ buildAuditFixPlan,
781
1283
  getFixableFailedResults,
782
1284
  isFixableKey,
1285
+ runAuditFixWorkflow,
783
1286
  };