@nerviq/cli 1.8.8 → 1.9.0
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 +17 -4
- package/bin/cli.js +74 -6
- package/package.json +59 -59
- package/src/audit.js +11 -10
- package/src/benchmark.js +7 -3
- package/src/certification.js +32 -4
- package/src/codex/freshness.js +167 -167
- package/src/copilot/freshness.js +197 -197
- package/src/cursor/freshness.js +214 -214
- package/src/freshness.js +19 -19
- package/src/gemini/freshness.js +204 -204
- package/src/governance.js +2 -2
- package/src/i18n.js +63 -0
- package/src/locales/en.json +34 -0
- package/src/locales/es.json +34 -0
- package/src/opencode/freshness.js +158 -158
- package/src/server.js +14 -5
- package/src/setup.js +56 -3
- package/src/techniques.js +113 -0
- package/src/windsurf/freshness.js +215 -215
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@nerviq/cli)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[](https://github.com/nerviq/nerviq)
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ Nerviq audits, sets up, and governs AI coding agent configurations for **8 platf
|
|
|
14
14
|
|
|
15
15
|
| Platform | Checks | Status |
|
|
16
16
|
|----------|--------|--------|
|
|
17
|
-
| Claude Code |
|
|
17
|
+
| Claude Code | 400 | Full |
|
|
18
18
|
| Codex (OpenAI) | 272 | Full |
|
|
19
19
|
| Gemini CLI (Google) | 300 | Full |
|
|
20
20
|
| GitHub Copilot | 299 | Full |
|
|
@@ -85,7 +85,7 @@ No install required. Zero dependencies.
|
|
|
85
85
|
| **Team lead / DevEx** | `nerviq governance` → `nerviq audit --json` | CI threshold + `nerviq watch` |
|
|
86
86
|
| **Enterprise / Platform** | `nerviq harmony-audit` → `nerviq harmony-drift` | Policy packs + `nerviq certify` |
|
|
87
87
|
|
|
88
|
-
## 2,
|
|
88
|
+
## 2,438 Checks Across 96 Categories (8 Platforms × ~300 Governance Rules)
|
|
89
89
|
|
|
90
90
|
| Category Group | Checks | Examples |
|
|
91
91
|
|----------------|--------|---------|
|
|
@@ -290,7 +290,7 @@ Nerviq is built on the NERVIQ knowledge engine — the largest verified catalog
|
|
|
290
290
|
|
|
291
291
|
- **448+ research documents** covering all 8 platforms
|
|
292
292
|
- **332+ experiments** with tested, rated results
|
|
293
|
-
- **2,
|
|
293
|
+
- **2,438 checks** across 8 platforms (~300 unique governance rules × 8 platform adaptations), each with `sourceUrl` and `confidence` level (0.0-1.0)
|
|
294
294
|
- Every check is traceable to primary documentation or verified experiment
|
|
295
295
|
- 90-day freshness cycle: stale findings are re-verified or pruned
|
|
296
296
|
|
|
@@ -347,3 +347,16 @@ If Nerviq helped you, consider giving it a ⭐ on [GitHub](https://github.com/ne
|
|
|
347
347
|
| `BETA` | Works but has limited real-world testing. API may change |
|
|
348
348
|
| `EXPERIMENTAL` | Early stage, static rules, results may vary |
|
|
349
349
|
|
|
350
|
+
## Previously nerviq-cli
|
|
351
|
+
|
|
352
|
+
Nerviq was previously published as `nerviq-cli`. If you were using it:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Old
|
|
356
|
+
npx nerviq-cli
|
|
357
|
+
|
|
358
|
+
# New
|
|
359
|
+
npx @nerviq/cli audit
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
All features are preserved and expanded.
|
package/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ const { auditWorkspaces } = require('../src/workspace');
|
|
|
14
14
|
const { scanOrg } = require('../src/org');
|
|
15
15
|
const { detectAntiPatterns, printAntiPatterns, printAntiPatternCatalog } = require('../src/anti-patterns');
|
|
16
16
|
const { VERIFICATION_DATES, getVerificationDate, getVerificationStats } = require('../src/verification-metadata');
|
|
17
|
+
const { init: initI18n, t } = require('../src/i18n');
|
|
17
18
|
const { version } = require('../package.json');
|
|
18
19
|
|
|
19
20
|
const args = process.argv.slice(2);
|
|
@@ -91,11 +92,12 @@ function parseArgs(rawArgs) {
|
|
|
91
92
|
let external = null;
|
|
92
93
|
let repos = [];
|
|
93
94
|
let teamProfile = null;
|
|
95
|
+
let lang = null;
|
|
94
96
|
|
|
95
97
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
96
98
|
const arg = rawArgs[i];
|
|
97
99
|
|
|
98
|
-
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--external' || arg === '--team-profile') {
|
|
100
|
+
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--external' || arg === '--team-profile' || arg === '--lang') {
|
|
99
101
|
const value = rawArgs[i + 1];
|
|
100
102
|
if (!value || value.startsWith('--')) {
|
|
101
103
|
throw new Error(`${arg} requires a value`);
|
|
@@ -123,10 +125,16 @@ function parseArgs(rawArgs) {
|
|
|
123
125
|
if (arg === '--webhook') webhookUrl = value.trim();
|
|
124
126
|
if (arg === '--external') external = value.trim();
|
|
125
127
|
if (arg === '--team-profile') teamProfile = value.trim();
|
|
128
|
+
if (arg === '--lang') lang = value.trim().toLowerCase();
|
|
126
129
|
i++;
|
|
127
130
|
continue;
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
if (arg.startsWith('--lang=')) {
|
|
134
|
+
lang = arg.split('=').slice(1).join('=').trim().toLowerCase();
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
if (arg.startsWith('--team-profile=')) {
|
|
131
139
|
teamProfile = arg.split('=').slice(1).join('=').trim();
|
|
132
140
|
continue;
|
|
@@ -258,7 +266,7 @@ function parseArgs(rawArgs) {
|
|
|
258
266
|
|
|
259
267
|
const normalizedCommand = COMMAND_ALIASES[command] || command;
|
|
260
268
|
|
|
261
|
-
return { flags, command, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, port, workspace, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, external, repos, teamProfile };
|
|
269
|
+
return { flags, command, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, port, workspace, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, external, repos, teamProfile, lang };
|
|
262
270
|
}
|
|
263
271
|
|
|
264
272
|
function printWorkspaceSummary(summary, options) {
|
|
@@ -308,6 +316,7 @@ function printScanDetail(summary, options) {
|
|
|
308
316
|
|
|
309
317
|
// Show per-category breakdown if result is available
|
|
310
318
|
if (item.result && item.result.results) {
|
|
319
|
+
const STACK_LANGUAGES = new Set(['python', 'go', 'rust', 'java', 'ruby', 'dotnet', 'php', 'flutter', 'swift', 'kotlin']);
|
|
311
320
|
const categories = {};
|
|
312
321
|
for (const r of item.result.results) {
|
|
313
322
|
const cat = r.category || 'other';
|
|
@@ -315,7 +324,9 @@ function printScanDetail(summary, options) {
|
|
|
315
324
|
categories[cat].total++;
|
|
316
325
|
if (r.passed) categories[cat].passed++;
|
|
317
326
|
}
|
|
318
|
-
const catEntries = Object.entries(categories)
|
|
327
|
+
const catEntries = Object.entries(categories)
|
|
328
|
+
.filter(([cat, v]) => v.passed > 0 || !STACK_LANGUAGES.has(cat))
|
|
329
|
+
.sort((a, b) => (a[1].passed / a[1].total) - (b[1].passed / b[1].total));
|
|
319
330
|
const catLine = catEntries.map(([cat, v]) => `${cat}: ${v.passed}/${v.total}`).join(' ');
|
|
320
331
|
console.log(` \x1b[2m${catLine}\x1b[0m`);
|
|
321
332
|
}
|
|
@@ -509,6 +520,11 @@ async function main() {
|
|
|
509
520
|
|
|
510
521
|
const { flags, command, normalizedCommand } = parsed;
|
|
511
522
|
|
|
523
|
+
// Initialize i18n with --lang flag or NERVIQ_LANG env var
|
|
524
|
+
if (parsed.lang) {
|
|
525
|
+
initI18n(parsed.lang);
|
|
526
|
+
}
|
|
527
|
+
|
|
512
528
|
if (flags.includes('--help') || command === 'help') {
|
|
513
529
|
console.log(HELP);
|
|
514
530
|
process.exit(0);
|
|
@@ -544,6 +560,7 @@ async function main() {
|
|
|
544
560
|
port: parsed.port !== null ? Number(parsed.port) : null,
|
|
545
561
|
workspace: parsed.workspace || null,
|
|
546
562
|
webhookUrl: parsed.webhookUrl || null,
|
|
563
|
+
lang: parsed.lang || null,
|
|
547
564
|
external: parsed.external || null,
|
|
548
565
|
dir: process.cwd()
|
|
549
566
|
};
|
|
@@ -1402,7 +1419,21 @@ async function main() {
|
|
|
1402
1419
|
console.error('\n Error: Profile name required. Usage: nerviq profile load <name>\n');
|
|
1403
1420
|
process.exit(1);
|
|
1404
1421
|
}
|
|
1405
|
-
|
|
1422
|
+
let profile;
|
|
1423
|
+
try {
|
|
1424
|
+
profile = loadProfile(options.dir, profileArg);
|
|
1425
|
+
} catch {
|
|
1426
|
+
// Not found as a user-saved profile — try built-in governance profiles
|
|
1427
|
+
const { getPermissionProfile } = require('../src/governance');
|
|
1428
|
+
const builtIn = getPermissionProfile(profileArg);
|
|
1429
|
+
if (builtIn && builtIn.key === profileArg) {
|
|
1430
|
+
profile = { name: builtIn.label, platforms: ['claude'], threshold: builtIn.threshold || 0, ...builtIn };
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
if (!profile) {
|
|
1434
|
+
console.error(`\n Error: Profile '${profileArg}' not found. Run 'nerviq profile list' to see available profiles.\n`);
|
|
1435
|
+
process.exit(1);
|
|
1436
|
+
}
|
|
1406
1437
|
|
|
1407
1438
|
// Apply profile settings to .claude/settings.json
|
|
1408
1439
|
const fs = require('fs');
|
|
@@ -1458,8 +1489,35 @@ async function main() {
|
|
|
1458
1489
|
process.exit(1);
|
|
1459
1490
|
}
|
|
1460
1491
|
} else if (normalizedCommand === 'synergy-report') {
|
|
1461
|
-
|
|
1462
|
-
|
|
1492
|
+
const { formatSynergyReport } = require('../src/synergy/report');
|
|
1493
|
+
const { detectActivePlatforms: detectSynergyPlatforms } = require('../src/harmony/canon');
|
|
1494
|
+
const presentPlatforms = detectSynergyPlatforms(options.dir).map(p => p.platform);
|
|
1495
|
+
if (presentPlatforms.length === 0) {
|
|
1496
|
+
console.log('\n No platform configurations detected.');
|
|
1497
|
+
console.log(' Run "nerviq harmony-audit" first, or "nerviq setup" to bootstrap a platform.\n');
|
|
1498
|
+
process.exit(0);
|
|
1499
|
+
}
|
|
1500
|
+
const platformAudits = {};
|
|
1501
|
+
const activePlatforms = [];
|
|
1502
|
+
for (const plat of presentPlatforms) {
|
|
1503
|
+
try {
|
|
1504
|
+
const result = await audit({ dir: options.dir, silent: true, platform: plat });
|
|
1505
|
+
if (result && typeof result.score === 'number') {
|
|
1506
|
+
platformAudits[plat] = result;
|
|
1507
|
+
activePlatforms.push(plat);
|
|
1508
|
+
}
|
|
1509
|
+
} catch (_e) { /* platform not available */ }
|
|
1510
|
+
}
|
|
1511
|
+
if (activePlatforms.length === 0) {
|
|
1512
|
+
console.log('\n No auditable platforms found. Run "nerviq harmony-audit" first.\n');
|
|
1513
|
+
process.exit(0);
|
|
1514
|
+
}
|
|
1515
|
+
const report = formatSynergyReport({ platformAudits, activePlatforms });
|
|
1516
|
+
if (options.json) {
|
|
1517
|
+
console.log(JSON.stringify({ activePlatforms, platformAudits }, null, 2));
|
|
1518
|
+
} else {
|
|
1519
|
+
console.log(report);
|
|
1520
|
+
}
|
|
1463
1521
|
process.exit(0);
|
|
1464
1522
|
} else if (normalizedCommand === 'doctor') {
|
|
1465
1523
|
const { runDoctor } = require('../src/doctor');
|
|
@@ -1888,6 +1946,16 @@ async function main() {
|
|
|
1888
1946
|
process.exit(0);
|
|
1889
1947
|
}
|
|
1890
1948
|
const result = await audit(options);
|
|
1949
|
+
if (options.out) {
|
|
1950
|
+
const fs = require('fs');
|
|
1951
|
+
const path = require('path');
|
|
1952
|
+
const outPath = path.resolve(options.out);
|
|
1953
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
1954
|
+
fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf8');
|
|
1955
|
+
if (!options.json) {
|
|
1956
|
+
console.log(`\n Audit report written to ${options.out}\n`);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1891
1959
|
if (options.webhookUrl) {
|
|
1892
1960
|
try {
|
|
1893
1961
|
const { sendWebhook, formatSlackMessage } = require('../src/integrations');
|
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The intelligent nervous system for AI coding agents — 2,
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"nerviq": "bin/cli.js",
|
|
8
|
-
"@nerviq/cli": "bin/cli.js",
|
|
9
|
-
"nerviq-mcp": "src/mcp-server.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"bin",
|
|
13
|
-
"src",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"start": "node bin/cli.js",
|
|
18
|
-
"build": "npm pack --dry-run",
|
|
19
|
-
"test": "node test/run.js",
|
|
20
|
-
"test:jest": "jest",
|
|
21
|
-
"test:coverage": "jest --coverage",
|
|
22
|
-
"test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
|
|
23
|
-
"benchmark:perf": "node tools/benchmark.js",
|
|
24
|
-
"catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"nerviq",
|
|
28
|
-
"ai-agents",
|
|
29
|
-
"agent-governance",
|
|
30
|
-
"agent-config",
|
|
31
|
-
"harmony",
|
|
32
|
-
"synergy",
|
|
33
|
-
"audit",
|
|
34
|
-
"claude",
|
|
35
|
-
"codex",
|
|
36
|
-
"gemini",
|
|
37
|
-
"copilot",
|
|
38
|
-
"cursor",
|
|
39
|
-
"windsurf",
|
|
40
|
-
"aider",
|
|
41
|
-
"developer-tools",
|
|
42
|
-
"cli",
|
|
43
|
-
"mcp",
|
|
44
|
-
"multi-agent"
|
|
45
|
-
],
|
|
46
|
-
"author": "Nerviq <hello@nerviq.net>",
|
|
47
|
-
"license": "AGPL-3.0",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/nerviq/nerviq.git"
|
|
51
|
-
},
|
|
52
|
-
"homepage": "https://nerviq.net",
|
|
53
|
-
"engines": {
|
|
54
|
-
"node": ">=18.0.0"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"jest": "^30.3.0"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nerviq/cli",
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "The intelligent nervous system for AI coding agents — 2,438 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nerviq": "bin/cli.js",
|
|
8
|
+
"@nerviq/cli": "bin/cli.js",
|
|
9
|
+
"nerviq-mcp": "src/mcp-server.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"src",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node bin/cli.js",
|
|
18
|
+
"build": "npm pack --dry-run",
|
|
19
|
+
"test": "node test/run.js",
|
|
20
|
+
"test:jest": "jest",
|
|
21
|
+
"test:coverage": "jest --coverage",
|
|
22
|
+
"test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
|
|
23
|
+
"benchmark:perf": "node tools/benchmark.js",
|
|
24
|
+
"catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"nerviq",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"agent-governance",
|
|
30
|
+
"agent-config",
|
|
31
|
+
"harmony",
|
|
32
|
+
"synergy",
|
|
33
|
+
"audit",
|
|
34
|
+
"claude",
|
|
35
|
+
"codex",
|
|
36
|
+
"gemini",
|
|
37
|
+
"copilot",
|
|
38
|
+
"cursor",
|
|
39
|
+
"windsurf",
|
|
40
|
+
"aider",
|
|
41
|
+
"developer-tools",
|
|
42
|
+
"cli",
|
|
43
|
+
"mcp",
|
|
44
|
+
"multi-agent"
|
|
45
|
+
],
|
|
46
|
+
"author": "Nerviq <hello@nerviq.net>",
|
|
47
|
+
"license": "AGPL-3.0",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/nerviq/nerviq.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://nerviq.net",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"jest": "^30.3.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/audit.js
CHANGED
|
@@ -33,6 +33,7 @@ const { loadPlugins, mergePluginChecks } = require('./plugins');
|
|
|
33
33
|
const { hasWorkspaceConfig, detectWorkspaceGlobs, detectWorkspaces } = require('./workspace');
|
|
34
34
|
const { detectDeprecationWarnings } = require('./deprecation');
|
|
35
35
|
const { version: packageVersion } = require('../package.json');
|
|
36
|
+
const { t } = require('./i18n');
|
|
36
37
|
|
|
37
38
|
const COLORS = {
|
|
38
39
|
reset: '\x1b[0m',
|
|
@@ -872,27 +873,27 @@ function getCodexDomainPackSignals(ctx) {
|
|
|
872
873
|
|
|
873
874
|
function printLiteAudit(result, dir) {
|
|
874
875
|
console.log('');
|
|
875
|
-
const productLabel = result.platform === 'codex' ? '
|
|
876
|
+
const productLabel = result.platform === 'codex' ? t('audit.codexQuickScan') : t('audit.quickScan');
|
|
876
877
|
console.log(colorize(` ${productLabel}`, 'bold'));
|
|
877
878
|
console.log(colorize(' ═══════════════════════════════════════', 'dim'));
|
|
878
|
-
console.log(colorize(`
|
|
879
|
+
console.log(colorize(` ${t('audit.scanning', { dir })}`, 'dim'));
|
|
879
880
|
console.log('');
|
|
880
881
|
if (result.detectedConfigFiles && result.detectedConfigFiles.length > 0) {
|
|
881
882
|
console.log(colorize(` Found: ${result.detectedConfigFiles.join(', ')}`, 'dim'));
|
|
882
883
|
}
|
|
883
884
|
console.log('');
|
|
884
|
-
console.log(`
|
|
885
|
+
console.log(` ${t('audit.score', { score: colorize(`${result.score}/100`, 'bold'), passed: result.passed, total: result.passed + result.failed })}`);
|
|
885
886
|
|
|
886
887
|
// Score explanation line (lite mode only)
|
|
887
888
|
const _critCount = (result.results || []).filter(r => r.passed === false && r.impact === 'critical').length;
|
|
888
889
|
const _highCount = (result.results || []).filter(r => r.passed === false && r.impact === 'high').length;
|
|
889
890
|
let scoreExplanation;
|
|
890
891
|
if (result.score >= 90) {
|
|
891
|
-
scoreExplanation = '
|
|
892
|
+
scoreExplanation = t('audit.excellent');
|
|
892
893
|
} else if (result.score >= 70) {
|
|
893
|
-
scoreExplanation =
|
|
894
|
+
scoreExplanation = t('audit.strong', { count: _critCount });
|
|
894
895
|
} else if (result.score >= 50) {
|
|
895
|
-
scoreExplanation =
|
|
896
|
+
scoreExplanation = t('audit.good', { count: _critCount + _highCount });
|
|
896
897
|
} else if (result.score >= 30) {
|
|
897
898
|
// Find weakest category (most failures)
|
|
898
899
|
const catFailures = {};
|
|
@@ -901,9 +902,9 @@ function printLiteAudit(result, dir) {
|
|
|
901
902
|
catFailures[cat] = (catFailures[cat] || 0) + 1;
|
|
902
903
|
});
|
|
903
904
|
const weakestCategory = Object.keys(catFailures).sort((a, b) => catFailures[b] - catFailures[a])[0] || 'config';
|
|
904
|
-
scoreExplanation =
|
|
905
|
+
scoreExplanation = t('audit.basic', { category: weakestCategory });
|
|
905
906
|
} else {
|
|
906
|
-
scoreExplanation = '
|
|
907
|
+
scoreExplanation = t('audit.early');
|
|
907
908
|
}
|
|
908
909
|
console.log(colorize(` ${scoreExplanation}`, 'dim'));
|
|
909
910
|
|
|
@@ -1233,10 +1234,10 @@ async function audit(options) {
|
|
|
1233
1234
|
|
|
1234
1235
|
// Display results
|
|
1235
1236
|
console.log('');
|
|
1236
|
-
const auditTitle = spec.platform === 'codex' ? '
|
|
1237
|
+
const auditTitle = spec.platform === 'codex' ? t('audit.codexTitle') : t('audit.title');
|
|
1237
1238
|
console.log(colorize(` ${auditTitle}`, 'bold'));
|
|
1238
1239
|
console.log(colorize(' ═══════════════════════════════════════', 'dim'));
|
|
1239
|
-
console.log(colorize(`
|
|
1240
|
+
console.log(colorize(` ${t('audit.scanning', { dir: options.dir })}`, 'dim'));
|
|
1240
1241
|
if (spec.platformVersion) {
|
|
1241
1242
|
console.log(colorize(` Platform: ${spec.platformLabel} (${spec.platformVersion})`, 'blue'));
|
|
1242
1243
|
}
|
package/src/benchmark.js
CHANGED
|
@@ -321,9 +321,13 @@ function printBenchmark(report, options = {}) {
|
|
|
321
321
|
console.log(' ═══════════════════════════════════════');
|
|
322
322
|
console.log(' Runs in an isolated temp copy. Your current repo is not modified.');
|
|
323
323
|
console.log('');
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
console.log(`
|
|
324
|
+
const orgDeltaSign = report.delta.organicScore >= 0 ? '+' : '';
|
|
325
|
+
const totalDeltaSign = report.delta.score >= 0 ? '+' : '';
|
|
326
|
+
console.log(` Organic improvement: \x1b[1m${orgDeltaSign}${report.delta.organicScore} points\x1b[0m (your actual config quality)`);
|
|
327
|
+
console.log(` Total with nerviq setup: ${totalDeltaSign}${report.delta.score} points`);
|
|
328
|
+
console.log('');
|
|
329
|
+
console.log(` Before: organic ${report.before.organicScore}/100, total ${report.before.score}/100`);
|
|
330
|
+
console.log(` After: organic ${report.after.organicScore}/100, total ${report.after.score}/100`);
|
|
327
331
|
console.log('');
|
|
328
332
|
console.log(` ${report.executiveSummary.headline}`);
|
|
329
333
|
console.log(` Recommendation: ${report.executiveSummary.decisionGuidance}`);
|
package/src/certification.js
CHANGED
|
@@ -37,10 +37,14 @@ async function certifyProject(dir) {
|
|
|
37
37
|
|
|
38
38
|
// Run per-platform audits
|
|
39
39
|
const platformScores = {};
|
|
40
|
+
const allAuditResults = [];
|
|
40
41
|
for (const platform of platforms) {
|
|
41
42
|
try {
|
|
42
43
|
const result = await audit({ dir: resolvedDir, platform, silent: true });
|
|
43
44
|
platformScores[platform] = result.score;
|
|
45
|
+
if (Array.isArray(result.results)) {
|
|
46
|
+
allAuditResults.push(...result.results);
|
|
47
|
+
}
|
|
44
48
|
} catch {
|
|
45
49
|
platformScores[platform] = 0;
|
|
46
50
|
}
|
|
@@ -55,18 +59,36 @@ async function certifyProject(dir) {
|
|
|
55
59
|
harmonyScore = 0;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
// Determine certification level
|
|
62
|
+
// Determine certification level with security gates
|
|
59
63
|
const scores = Object.values(platformScores);
|
|
60
64
|
const allAbove70 = scores.length > 0 && scores.every(s => s >= 70);
|
|
61
65
|
const allAbove50 = scores.length > 0 && scores.every(s => s >= 50);
|
|
62
66
|
const anyAbove40 = scores.some(s => s >= 40);
|
|
63
67
|
|
|
68
|
+
// Security gate helpers — check whether specific audit checks passed
|
|
69
|
+
const checkPassed = (key) => {
|
|
70
|
+
const match = allAuditResults.find(r => r.key === key);
|
|
71
|
+
return match ? match.passed === true : false;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const gitIgnoreOk = checkPassed('gitIgnoreEnv');
|
|
75
|
+
const secretsOk = checkPassed('secretsProtection');
|
|
76
|
+
const criticalAntiPatterns = allAuditResults.filter(
|
|
77
|
+
r => r.passed === false && r.impact === 'critical'
|
|
78
|
+
);
|
|
79
|
+
const noCriticalAntiPatterns = criticalAntiPatterns.length === 0;
|
|
80
|
+
|
|
81
|
+
// Bronze gate: score >= 40 AND basic security (gitignore + secrets protection)
|
|
82
|
+
const bronzeSecurityGate = gitIgnoreOk && secretsOk;
|
|
83
|
+
// Silver gate: Bronze requirements AND no critical anti-patterns
|
|
84
|
+
const silverSecurityGate = bronzeSecurityGate && noCriticalAntiPatterns;
|
|
85
|
+
|
|
64
86
|
let level;
|
|
65
|
-
if (harmonyScore >= 80 && allAbove70) {
|
|
87
|
+
if (harmonyScore >= 80 && allAbove70 && silverSecurityGate) {
|
|
66
88
|
level = LEVELS.GOLD;
|
|
67
|
-
} else if (harmonyScore >= 60 && allAbove50) {
|
|
89
|
+
} else if (harmonyScore >= 60 && allAbove50 && silverSecurityGate) {
|
|
68
90
|
level = LEVELS.SILVER;
|
|
69
|
-
} else if (anyAbove40) {
|
|
91
|
+
} else if (anyAbove40 && bronzeSecurityGate) {
|
|
70
92
|
level = LEVELS.BRONZE;
|
|
71
93
|
} else {
|
|
72
94
|
level = LEVELS.NONE;
|
|
@@ -80,6 +102,12 @@ async function certifyProject(dir) {
|
|
|
80
102
|
platformScores,
|
|
81
103
|
platforms,
|
|
82
104
|
badge,
|
|
105
|
+
securityGates: {
|
|
106
|
+
gitIgnoreEnv: gitIgnoreOk,
|
|
107
|
+
secretsProtection: secretsOk,
|
|
108
|
+
noCriticalAntiPatterns,
|
|
109
|
+
criticalAntiPatternCount: criticalAntiPatterns.length,
|
|
110
|
+
},
|
|
83
111
|
};
|
|
84
112
|
}
|
|
85
113
|
|