@nerviq/cli 1.8.6 → 1.8.8
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 +349 -362
- package/bin/cli.js +131 -34
- package/package.json +1 -1
- package/src/aider/activity.js +226 -226
- package/src/audit.js +1443 -1443
- package/src/benchmark.js +346 -346
- package/src/codex/activity.js +324 -324
- package/src/context.js +27 -1
- package/src/convert.js +6 -2
- package/src/copilot/patch.js +238 -238
- package/src/cursor/patch.js +243 -243
- package/src/gemini/activity.js +402 -402
- package/src/gemini/patch.js +229 -229
- package/src/governance.js +583 -583
- package/src/harmony/audit.js +306 -306
- package/src/insights.js +119 -119
- package/src/{claudex-sync.json → nerviq-sync.json} +1 -1
- package/src/opencode/activity.js +286 -286
- package/src/setup.js +47 -9
- package/src/state-paths.js +85 -85
- package/src/techniques.js +5498 -5494
- package/src/windsurf/patch.js +231 -231
package/bin/cli.js
CHANGED
|
@@ -285,6 +285,53 @@ function printWorkspaceSummary(summary, options) {
|
|
|
285
285
|
console.log('');
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
function printScanDetail(summary, options) {
|
|
289
|
+
if (options.json) {
|
|
290
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log('\x1b[1m nerviq scan — per-repo comparison\x1b[0m');
|
|
296
|
+
console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
|
|
297
|
+
console.log(` Platform: ${summary.platform} | Repos: ${summary.repoCount} | Average: \x1b[1m${summary.averageScore}/100\x1b[0m`);
|
|
298
|
+
console.log('');
|
|
299
|
+
|
|
300
|
+
for (const item of summary.repos) {
|
|
301
|
+
if (item.error) {
|
|
302
|
+
console.log(` \x1b[31m✗ ${item.name}\x1b[0m — ${item.error}`);
|
|
303
|
+
console.log('');
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const scoreColor = item.score >= 80 ? '\x1b[32m' : item.score >= 50 ? '\x1b[33m' : '\x1b[31m';
|
|
307
|
+
console.log(` \x1b[1m${item.name}\x1b[0m ${scoreColor}${item.score}/100\x1b[0m (${item.passed}/${item.total} checks passed)`);
|
|
308
|
+
|
|
309
|
+
// Show per-category breakdown if result is available
|
|
310
|
+
if (item.result && item.result.results) {
|
|
311
|
+
const categories = {};
|
|
312
|
+
for (const r of item.result.results) {
|
|
313
|
+
const cat = r.category || 'other';
|
|
314
|
+
if (!categories[cat]) categories[cat] = { passed: 0, total: 0 };
|
|
315
|
+
categories[cat].total++;
|
|
316
|
+
if (r.passed) categories[cat].passed++;
|
|
317
|
+
}
|
|
318
|
+
const catEntries = Object.entries(categories).sort((a, b) => (a[1].passed / a[1].total) - (b[1].passed / b[1].total));
|
|
319
|
+
const catLine = catEntries.map(([cat, v]) => `${cat}: ${v.passed}/${v.total}`).join(' ');
|
|
320
|
+
console.log(` \x1b[2m${catLine}\x1b[0m`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Show top 3 gaps
|
|
324
|
+
if (item.result && item.result.topNextActions && item.result.topNextActions.length > 0) {
|
|
325
|
+
const gaps = item.result.topNextActions.slice(0, 3);
|
|
326
|
+
console.log(' Top gaps:');
|
|
327
|
+
for (const gap of gaps) {
|
|
328
|
+
console.log(` \x1b[33m→\x1b[0m ${gap.name || gap.key}${gap.impact ? ` \x1b[2m(+${gap.impact})\x1b[0m` : ''}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
288
335
|
function printOrgSummary(summary, options) {
|
|
289
336
|
if (options.json) {
|
|
290
337
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -525,6 +572,21 @@ async function main() {
|
|
|
525
572
|
}
|
|
526
573
|
}
|
|
527
574
|
|
|
575
|
+
// Apply built-in governance profile (--profile flag) to audit options
|
|
576
|
+
if (parsed.profile && parsed.profile !== 'safe-write') {
|
|
577
|
+
const { getPermissionProfile } = require('../src/governance');
|
|
578
|
+
const govProfile = getPermissionProfile(parsed.profile);
|
|
579
|
+
if (govProfile) {
|
|
580
|
+
options.governanceProfile = govProfile;
|
|
581
|
+
if (govProfile.deny && govProfile.deny.length > 0) {
|
|
582
|
+
options.suppressedChecks = options.suppressedChecks || [];
|
|
583
|
+
}
|
|
584
|
+
if (!options.json) {
|
|
585
|
+
console.log(` Using governance profile: ${govProfile.label} (${govProfile.risk} risk)`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
528
590
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'copilot', 'cursor', 'windsurf', 'aider', 'opencode'];
|
|
529
591
|
if (!SUPPORTED_PLATFORMS.includes(options.platform)) {
|
|
530
592
|
console.error(`\n Error: Unsupported platform '${options.platform}'.`);
|
|
@@ -595,7 +657,7 @@ async function main() {
|
|
|
595
657
|
// Harmony + Synergy (cross-platform)
|
|
596
658
|
'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise',
|
|
597
659
|
'harmony-watch', 'harmony-governance', 'harmony-add', 'synergy-report', 'anti-patterns', 'rules-export',
|
|
598
|
-
'freshness', 'profile',
|
|
660
|
+
'freshness', 'profile', 'migrate',
|
|
599
661
|
]);
|
|
600
662
|
|
|
601
663
|
if (options.platform === 'codex') {
|
|
@@ -648,7 +710,7 @@ async function main() {
|
|
|
648
710
|
process.exit(1);
|
|
649
711
|
}
|
|
650
712
|
const summary = await scanOrg(scanDirs, options.platform);
|
|
651
|
-
|
|
713
|
+
printScanDetail(summary, options);
|
|
652
714
|
if (options.threshold !== null && summary.averageScore < options.threshold) {
|
|
653
715
|
process.exit(1);
|
|
654
716
|
}
|
|
@@ -740,7 +802,7 @@ async function main() {
|
|
|
740
802
|
process.exit(0);
|
|
741
803
|
} else if (normalizedCommand === 'insights') {
|
|
742
804
|
const https = require('https');
|
|
743
|
-
const url = 'https://
|
|
805
|
+
const url = 'https://nerviq-insights.nerviq.workers.dev/v1/stats';
|
|
744
806
|
const req = https.get(url, (res) => {
|
|
745
807
|
let data = '';
|
|
746
808
|
res.on('data', chunk => data += chunk);
|
|
@@ -748,7 +810,7 @@ async function main() {
|
|
|
748
810
|
try {
|
|
749
811
|
const stats = JSON.parse(data);
|
|
750
812
|
console.log('');
|
|
751
|
-
console.log('\x1b[1m
|
|
813
|
+
console.log('\x1b[1m NERVIQ Community Insights\x1b[0m');
|
|
752
814
|
console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
|
|
753
815
|
console.log(` Total audits run: \x1b[1m${stats.totalRuns}\x1b[0m`);
|
|
754
816
|
console.log(` Average score: \x1b[1m${stats.averageScore}/100\x1b[0m`);
|
|
@@ -1341,11 +1403,39 @@ async function main() {
|
|
|
1341
1403
|
process.exit(1);
|
|
1342
1404
|
}
|
|
1343
1405
|
const profile = loadProfile(options.dir, profileArg);
|
|
1406
|
+
|
|
1407
|
+
// Apply profile settings to .claude/settings.json
|
|
1408
|
+
const fs = require('fs');
|
|
1409
|
+
const settingsPath = require('path').join(options.dir, '.claude', 'settings.json');
|
|
1410
|
+
let settings = {};
|
|
1411
|
+
if (fs.existsSync(settingsPath)) {
|
|
1412
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch {}
|
|
1413
|
+
}
|
|
1414
|
+
// Apply deny rules from governance profile if platforms include claude
|
|
1415
|
+
if (profile.platforms && profile.platforms.includes('claude')) {
|
|
1416
|
+
const { getPermissionProfile } = require('../src/governance');
|
|
1417
|
+
const govProfile = getPermissionProfile(profileArg);
|
|
1418
|
+
if (govProfile && govProfile.deny && govProfile.deny.length > 0) {
|
|
1419
|
+
settings.deny = govProfile.deny;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
// Apply threshold and suppressed checks
|
|
1423
|
+
if (profile.threshold != null) {
|
|
1424
|
+
settings.threshold = profile.threshold;
|
|
1425
|
+
}
|
|
1426
|
+
if (profile.suppressedChecks && profile.suppressedChecks.length > 0) {
|
|
1427
|
+
settings.suppressedChecks = profile.suppressedChecks;
|
|
1428
|
+
}
|
|
1429
|
+
const settingsDir = require('path').dirname(settingsPath);
|
|
1430
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
1431
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
1432
|
+
|
|
1344
1433
|
if (options.json) {
|
|
1345
1434
|
console.log(JSON.stringify(profile, null, 2));
|
|
1346
1435
|
} else {
|
|
1347
1436
|
console.log('');
|
|
1348
1437
|
console.log(formatProfile(profile));
|
|
1438
|
+
console.log(`\n Settings applied to ${settingsPath}`);
|
|
1349
1439
|
console.log('');
|
|
1350
1440
|
}
|
|
1351
1441
|
process.exit(0);
|
|
@@ -1404,7 +1494,8 @@ async function main() {
|
|
|
1404
1494
|
const fixKey = parsed.extraArgs[0] || null;
|
|
1405
1495
|
const allCritical = flags.includes('--all-critical');
|
|
1406
1496
|
const promptOnly = flags.includes('--prompt');
|
|
1407
|
-
const autoApply = options.auto
|
|
1497
|
+
const autoApply = options.auto;
|
|
1498
|
+
const isDryRun = options.dryRun;
|
|
1408
1499
|
|
|
1409
1500
|
// Step 1: Run silent audit to find failed checks (only actual failures, not skipped/null)
|
|
1410
1501
|
const auditResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
@@ -1447,6 +1538,13 @@ async function main() {
|
|
|
1447
1538
|
for (const entry of denyEntries) {
|
|
1448
1539
|
if (!settings.permissions.deny.includes(entry)) settings.permissions.deny.push(entry);
|
|
1449
1540
|
}
|
|
1541
|
+
// Remove overly broad allow:["*"] if present
|
|
1542
|
+
if (Array.isArray(settings.permissions.allow) && settings.permissions.allow.includes('*')) {
|
|
1543
|
+
settings.permissions.allow = settings.permissions.allow.filter(a => a !== '*');
|
|
1544
|
+
if (settings.permissions.allow.length === 0) {
|
|
1545
|
+
delete settings.permissions.allow;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1450
1548
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1451
1549
|
return true;
|
|
1452
1550
|
},
|
|
@@ -1558,7 +1656,7 @@ async function main() {
|
|
|
1558
1656
|
const predictedScore = maxScore > 0 ? Math.round((simulatedEarned / maxScore) * 100) : 0;
|
|
1559
1657
|
const predictedDelta = predictedScore - preScore;
|
|
1560
1658
|
|
|
1561
|
-
if (!autoApply) {
|
|
1659
|
+
if (!autoApply && !isDryRun) {
|
|
1562
1660
|
console.log('');
|
|
1563
1661
|
if (allCritical && fixableTargets.length > 1) {
|
|
1564
1662
|
// Multi-fix summary
|
|
@@ -1607,9 +1705,9 @@ async function main() {
|
|
|
1607
1705
|
const allCreatedFiles = [];
|
|
1608
1706
|
const fixResults = []; // { key, name, status, delta }
|
|
1609
1707
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1708
|
+
const snapshotFiles = {};
|
|
1709
|
+
if (!isDryRun && targetKeys.length > 0) {
|
|
1710
|
+
// Snapshot existing files for rollback (before applying fixes)
|
|
1613
1711
|
for (const key of targetKeys) {
|
|
1614
1712
|
const technique = TECHNIQUES[key];
|
|
1615
1713
|
if (technique && technique.template && technique.template.path) {
|
|
@@ -1619,14 +1717,6 @@ async function main() {
|
|
|
1619
1717
|
}
|
|
1620
1718
|
}
|
|
1621
1719
|
}
|
|
1622
|
-
const rollbackArtifact = writeRollbackArtifact(options.dir, {
|
|
1623
|
-
sourcePlan: 'fix-batch',
|
|
1624
|
-
preSnapshot: snapshotFiles,
|
|
1625
|
-
createdFiles: [],
|
|
1626
|
-
patchedFiles: Object.keys(snapshotFiles),
|
|
1627
|
-
rollbackInstructions: ['Use nerviq rollback to undo these fixes'],
|
|
1628
|
-
});
|
|
1629
|
-
rollbackId = rollbackArtifact.id;
|
|
1630
1720
|
}
|
|
1631
1721
|
|
|
1632
1722
|
// Step 3b: Apply fixes sequentially with progress
|
|
@@ -1641,7 +1731,7 @@ async function main() {
|
|
|
1641
1731
|
const progress = isBatch ? `${i + 1}/${targetKeys.length}: ` : '';
|
|
1642
1732
|
|
|
1643
1733
|
if (technique && technique.template) {
|
|
1644
|
-
if (
|
|
1734
|
+
if (isDryRun) {
|
|
1645
1735
|
console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
|
|
1646
1736
|
fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
|
|
1647
1737
|
fixed++;
|
|
@@ -1671,7 +1761,7 @@ async function main() {
|
|
|
1671
1761
|
}
|
|
1672
1762
|
}
|
|
1673
1763
|
} else if (INLINE_FIXERS[key]) {
|
|
1674
|
-
if (
|
|
1764
|
+
if (isDryRun) {
|
|
1675
1765
|
console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
|
|
1676
1766
|
fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
|
|
1677
1767
|
fixed++;
|
|
@@ -1716,26 +1806,33 @@ async function main() {
|
|
|
1716
1806
|
}
|
|
1717
1807
|
|
|
1718
1808
|
// Record accepted patterns for successfully fixed checks
|
|
1719
|
-
if (!
|
|
1809
|
+
if (!isDryRun) {
|
|
1720
1810
|
for (const key of targetKeys) {
|
|
1721
1811
|
const fr = fixResults.find(r => r.key === key);
|
|
1722
1812
|
recordPattern(options.dir, key, fr && fr.status === 'fixed' ? 'accepted' : 'rejected');
|
|
1723
1813
|
}
|
|
1724
1814
|
}
|
|
1725
1815
|
|
|
1726
|
-
//
|
|
1727
|
-
if (!
|
|
1728
|
-
const
|
|
1729
|
-
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
} catch { /* best effort */ }
|
|
1816
|
+
// Write rollback artifact AFTER fixes are applied (with actual file lists)
|
|
1817
|
+
if (!isDryRun && targetKeys.length > 0 && fixed > 0) {
|
|
1818
|
+
const allPatchedFiles = Object.keys(snapshotFiles);
|
|
1819
|
+
// Also track inline-fixer patched files
|
|
1820
|
+
for (const fr of fixResults) {
|
|
1821
|
+
if (fr.status === 'fixed' && INLINE_FIXERS[fr.key]) {
|
|
1822
|
+
const inlinePath = fr.key === 'gitIgnoreEnv' ? '.gitignore' : fr.key === 'secretsProtection' ? '.claude/settings.json' : null;
|
|
1823
|
+
if (inlinePath && !allPatchedFiles.includes(inlinePath)) {
|
|
1824
|
+
allPatchedFiles.push(inlinePath);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1738
1827
|
}
|
|
1828
|
+
const rollbackArtifact = writeRollbackArtifact(options.dir, {
|
|
1829
|
+
sourcePlan: 'fix-batch',
|
|
1830
|
+
preSnapshot: snapshotFiles,
|
|
1831
|
+
createdFiles: allCreatedFiles,
|
|
1832
|
+
patchedFiles: allPatchedFiles,
|
|
1833
|
+
rollbackInstructions: ['Use nerviq rollback to undo these fixes'],
|
|
1834
|
+
});
|
|
1835
|
+
rollbackId = rollbackArtifact.id;
|
|
1739
1836
|
}
|
|
1740
1837
|
|
|
1741
1838
|
// Step 4: Show batch summary or simple score impact
|
|
@@ -1751,10 +1848,10 @@ async function main() {
|
|
|
1751
1848
|
const totalDelta = runningScore - preScore;
|
|
1752
1849
|
console.log('');
|
|
1753
1850
|
console.log(` Score: ${preScore} → ${runningScore} (${totalDelta >= 0 ? '+' : ''}${totalDelta})`);
|
|
1754
|
-
if (rollbackId && !
|
|
1851
|
+
if (rollbackId && !isDryRun) {
|
|
1755
1852
|
console.log(` Rollback available: nerviq rollback --id ${rollbackId}`);
|
|
1756
1853
|
}
|
|
1757
|
-
} else if (fixed > 0 && !
|
|
1854
|
+
} else if (fixed > 0 && !isDryRun) {
|
|
1758
1855
|
const postResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
1759
1856
|
const delta = postResult.score - preScore;
|
|
1760
1857
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"description": "The intelligent nervous system for AI coding agents — 2,431 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|