@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.
- package/CHANGELOG.md +1527 -1493
- package/README.md +550 -538
- package/SECURITY.md +82 -82
- package/bin/cli.js +2562 -2558
- package/docs/api-reference.md +356 -356
- package/docs/audit-fix.md +109 -0
- package/docs/autofix.md +3 -62
- package/docs/getting-started.md +1 -1
- package/docs/index.html +592 -592
- package/docs/integration-contracts.md +287 -287
- package/docs/maintenance.md +128 -128
- package/docs/new-platform-guide.md +202 -202
- package/docs/release-process.md +63 -0
- package/docs/shallow-risk.md +244 -244
- package/docs/why-nerviq.md +82 -82
- package/package.json +67 -67
- package/src/aider/activity.js +226 -226
- package/src/aider/context.js +162 -162
- package/src/aider/freshness.js +123 -123
- package/src/aider/techniques.js +3465 -3465
- package/src/audit/layers.js +180 -180
- package/src/audit.js +1032 -1032
- package/src/benchmark.js +299 -299
- package/src/codex/activity.js +324 -324
- package/src/codex/freshness.js +142 -142
- package/src/codex/techniques.js +4895 -4895
- package/src/context.js +326 -326
- package/src/continuous-ops.js +11 -1
- package/src/convert.js +340 -340
- package/src/copilot/config-parser.js +280 -280
- package/src/copilot/context.js +218 -218
- package/src/copilot/freshness.js +177 -177
- package/src/copilot/patch.js +238 -238
- package/src/copilot/techniques.js +3578 -3578
- package/src/cursor/freshness.js +194 -194
- package/src/cursor/patch.js +243 -243
- package/src/cursor/techniques.js +3735 -3735
- package/src/doctor.js +201 -201
- package/src/fix-engine.js +511 -8
- package/src/formatters/csv.js +86 -86
- package/src/formatters/junit.js +123 -123
- package/src/formatters/markdown.js +164 -164
- package/src/formatters/otel.js +151 -151
- package/src/freshness.js +156 -156
- package/src/gemini/activity.js +402 -402
- package/src/gemini/context.js +290 -290
- package/src/gemini/freshness.js +183 -183
- package/src/gemini/patch.js +229 -229
- package/src/gemini/techniques.js +3811 -3811
- package/src/governance.js +533 -533
- package/src/harmony/audit.js +306 -306
- package/src/i18n.js +63 -63
- package/src/insights.js +119 -119
- package/src/integrations.js +134 -134
- package/src/locales/en.json +33 -33
- package/src/locales/es.json +33 -33
- package/src/migrate.js +354 -354
- package/src/opencode/activity.js +286 -286
- package/src/opencode/freshness.js +137 -137
- package/src/opencode/techniques.js +3450 -3450
- package/src/setup/analysis.js +12 -12
- package/src/setup.js +7 -6
- package/src/shallow-risk/index.js +56 -56
- package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +50 -50
- package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +46 -46
- package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +46 -46
- package/src/shallow-risk/patterns/agent-config-missing-file.js +317 -317
- package/src/shallow-risk/patterns/agent-config-secret-literal.js +49 -49
- package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +34 -34
- package/src/shallow-risk/patterns/hook-script-missing.js +70 -70
- package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +52 -52
- package/src/shallow-risk/shared.js +648 -648
- package/src/source-urls.js +295 -295
- package/src/state-paths.js +85 -85
- package/src/supplemental-checks.js +805 -805
- package/src/telemetry.js +160 -160
- package/src/windsurf/context.js +359 -359
- package/src/windsurf/freshness.js +194 -194
- package/src/windsurf/patch.js +231 -231
- package/src/windsurf/techniques.js +3779 -3779
package/src/doctor.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nerviq Doctor
|
|
3
|
-
*
|
|
4
|
-
* Self-diagnostics for the nerviq CLI and the current project environment.
|
|
5
|
-
* Checks: Node version, dependencies, platform detection, freshness gates.
|
|
6
|
-
*/
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Nerviq Doctor
|
|
3
|
+
*
|
|
4
|
+
* Self-diagnostics for the nerviq CLI and the current project environment.
|
|
5
|
+
* Checks: Node version, dependencies, platform detection, freshness gates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
@@ -12,168 +12,168 @@ const path = require('path');
|
|
|
12
12
|
const { version } = require('../package.json');
|
|
13
13
|
const { validateDeclaredMcpServers } = require('./mcp-validation');
|
|
14
14
|
const { validateDeclaredHooks } = require('./hook-validation');
|
|
15
|
-
|
|
16
|
-
const COLORS = {
|
|
17
|
-
reset: '\x1b[0m',
|
|
18
|
-
bold: '\x1b[1m',
|
|
19
|
-
dim: '\x1b[2m',
|
|
20
|
-
red: '\x1b[31m',
|
|
21
|
-
green: '\x1b[32m',
|
|
22
|
-
yellow: '\x1b[33m',
|
|
23
|
-
blue: '\x1b[36m',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function c(text, color) {
|
|
27
|
-
return `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
15
|
+
|
|
16
|
+
const COLORS = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
bold: '\x1b[1m',
|
|
19
|
+
dim: '\x1b[2m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
yellow: '\x1b[33m',
|
|
23
|
+
blue: '\x1b[36m',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function c(text, color) {
|
|
27
|
+
return `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
30
|
const PLATFORM_SIGNALS = {
|
|
31
31
|
claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json', '.mcp.json'],
|
|
32
32
|
codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
|
|
33
33
|
cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
|
|
34
34
|
copilot: ['.github/copilot-instructions.md', '.github/', '.vscode/mcp.json'],
|
|
35
|
-
gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
|
|
36
|
-
windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
|
|
37
|
-
aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
|
|
38
|
-
opencode: ['opencode.json', '.opencode/'],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const FRESHNESS_MODULES = {
|
|
42
|
-
claude: './freshness',
|
|
43
|
-
codex: './codex/freshness',
|
|
44
|
-
cursor: './cursor/freshness',
|
|
45
|
-
copilot: './copilot/freshness',
|
|
46
|
-
gemini: './gemini/freshness',
|
|
47
|
-
windsurf: './windsurf/freshness',
|
|
48
|
-
aider: './aider/freshness',
|
|
49
|
-
opencode: './opencode/freshness',
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// ─── Individual checks ───────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
function checkNodeVersion() {
|
|
55
|
-
const raw = process.version.replace('v', '');
|
|
56
|
-
const [major] = raw.split('.').map(Number);
|
|
57
|
-
const ok = major >= 18;
|
|
58
|
-
return {
|
|
59
|
-
label: 'Node.js version',
|
|
60
|
-
status: ok ? 'pass' : 'fail',
|
|
61
|
-
detail: `${process.version} (${ok ? 'meets' : 'below'} minimum v18)`,
|
|
62
|
-
fix: ok ? null : 'Upgrade Node.js to v18 or later: https://nodejs.org',
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function checkDeps() {
|
|
67
|
-
const pkgPath = path.join(__dirname, '..', 'node_modules');
|
|
68
|
-
const exists = fs.existsSync(pkgPath);
|
|
69
|
-
return {
|
|
70
|
-
label: 'node_modules installed',
|
|
71
|
-
status: exists ? 'pass' : 'fail',
|
|
72
|
-
detail: exists ? `${pkgPath}` : 'node_modules missing',
|
|
73
|
-
fix: exists ? null : 'Run: npm install',
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function checkJestInstalled() {
|
|
78
|
-
const jestPath = path.join(__dirname, '..', 'node_modules', 'jest', 'package.json');
|
|
79
|
-
const exists = fs.existsSync(jestPath);
|
|
80
|
-
let jestVersion = null;
|
|
81
|
-
if (exists) {
|
|
82
|
-
try {
|
|
83
|
-
jestVersion = require(jestPath).version;
|
|
84
|
-
} catch {}
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
label: 'Jest test runner',
|
|
88
|
-
status: exists ? 'pass' : 'warn',
|
|
89
|
-
detail: exists ? `jest@${jestVersion}` : 'jest not found in node_modules',
|
|
90
|
-
fix: exists ? null : 'Run: npm install --save-dev jest',
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function checkPlatformDetection(dir) {
|
|
95
|
-
const detected = [];
|
|
96
|
-
for (const [platform, signals] of Object.entries(PLATFORM_SIGNALS)) {
|
|
97
|
-
for (const signal of signals) {
|
|
98
|
-
const signalPath = path.join(dir, signal);
|
|
99
|
-
if (fs.existsSync(signalPath)) {
|
|
100
|
-
detected.push(platform);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
label: 'Platform detection',
|
|
108
|
-
status: detected.length > 0 ? 'pass' : 'warn',
|
|
109
|
-
detail: detected.length > 0
|
|
110
|
-
? `Detected: ${detected.join(', ')}`
|
|
111
|
-
: 'No platform config files found in current directory',
|
|
112
|
-
detected,
|
|
113
|
-
fix: detected.length === 0
|
|
114
|
-
? 'Run `nerviq setup` to generate baseline config files for your platform'
|
|
115
|
-
: null,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function checkFreshnessGates() {
|
|
120
|
-
const results = [];
|
|
121
|
-
for (const [platform, modulePath] of Object.entries(FRESHNESS_MODULES)) {
|
|
122
|
-
try {
|
|
123
|
-
const freshness = require(modulePath);
|
|
124
|
-
const gate = freshness.checkReleaseGate({});
|
|
125
|
-
const staleCount = (gate.stale || []).length;
|
|
126
|
-
const freshCount = (gate.fresh || []).length;
|
|
127
|
-
const totalCount = (gate.results || []).length;
|
|
128
|
-
results.push({
|
|
129
|
-
platform,
|
|
130
|
-
status: staleCount === 0 ? 'pass' : 'warn',
|
|
131
|
-
fresh: freshCount,
|
|
132
|
-
total: totalCount,
|
|
133
|
-
stale: staleCount,
|
|
134
|
-
detail: staleCount === 0
|
|
135
|
-
? `All ${totalCount} P0 sources fresh`
|
|
136
|
-
: `${staleCount}/${totalCount} P0 sources unverified/stale`,
|
|
137
|
-
});
|
|
138
|
-
} catch (e) {
|
|
139
|
-
results.push({ platform, status: 'error', detail: e.message });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return results;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function checkCliPermissions() {
|
|
146
|
-
const cliBin = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
147
|
-
const exists = fs.existsSync(cliBin);
|
|
148
|
-
if (!exists) {
|
|
149
|
-
return { label: 'CLI binary (bin/cli.js)', status: 'fail', detail: 'bin/cli.js not found', fix: null };
|
|
150
|
-
}
|
|
151
|
-
return { label: 'CLI binary (bin/cli.js)', status: 'pass', detail: cliBin, fix: null };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function checkGitRepo(dir) {
|
|
155
|
-
const gitPath = path.join(dir, '.git');
|
|
156
|
-
const exists = fs.existsSync(gitPath);
|
|
157
|
-
return {
|
|
158
|
-
label: 'Git repository',
|
|
159
|
-
status: exists ? 'pass' : 'warn',
|
|
160
|
-
detail: exists ? '.git/ found' : 'Not a git repository',
|
|
161
|
-
fix: exists ? null : 'Run: git init (recommended for safety)',
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ─── Main doctor function ────────────────────────────────────────────────────
|
|
166
|
-
|
|
35
|
+
gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
|
|
36
|
+
windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
|
|
37
|
+
aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
|
|
38
|
+
opencode: ['opencode.json', '.opencode/'],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const FRESHNESS_MODULES = {
|
|
42
|
+
claude: './freshness',
|
|
43
|
+
codex: './codex/freshness',
|
|
44
|
+
cursor: './cursor/freshness',
|
|
45
|
+
copilot: './copilot/freshness',
|
|
46
|
+
gemini: './gemini/freshness',
|
|
47
|
+
windsurf: './windsurf/freshness',
|
|
48
|
+
aider: './aider/freshness',
|
|
49
|
+
opencode: './opencode/freshness',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ─── Individual checks ───────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function checkNodeVersion() {
|
|
55
|
+
const raw = process.version.replace('v', '');
|
|
56
|
+
const [major] = raw.split('.').map(Number);
|
|
57
|
+
const ok = major >= 18;
|
|
58
|
+
return {
|
|
59
|
+
label: 'Node.js version',
|
|
60
|
+
status: ok ? 'pass' : 'fail',
|
|
61
|
+
detail: `${process.version} (${ok ? 'meets' : 'below'} minimum v18)`,
|
|
62
|
+
fix: ok ? null : 'Upgrade Node.js to v18 or later: https://nodejs.org',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function checkDeps() {
|
|
67
|
+
const pkgPath = path.join(__dirname, '..', 'node_modules');
|
|
68
|
+
const exists = fs.existsSync(pkgPath);
|
|
69
|
+
return {
|
|
70
|
+
label: 'node_modules installed',
|
|
71
|
+
status: exists ? 'pass' : 'fail',
|
|
72
|
+
detail: exists ? `${pkgPath}` : 'node_modules missing',
|
|
73
|
+
fix: exists ? null : 'Run: npm install',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function checkJestInstalled() {
|
|
78
|
+
const jestPath = path.join(__dirname, '..', 'node_modules', 'jest', 'package.json');
|
|
79
|
+
const exists = fs.existsSync(jestPath);
|
|
80
|
+
let jestVersion = null;
|
|
81
|
+
if (exists) {
|
|
82
|
+
try {
|
|
83
|
+
jestVersion = require(jestPath).version;
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
label: 'Jest test runner',
|
|
88
|
+
status: exists ? 'pass' : 'warn',
|
|
89
|
+
detail: exists ? `jest@${jestVersion}` : 'jest not found in node_modules',
|
|
90
|
+
fix: exists ? null : 'Run: npm install --save-dev jest',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function checkPlatformDetection(dir) {
|
|
95
|
+
const detected = [];
|
|
96
|
+
for (const [platform, signals] of Object.entries(PLATFORM_SIGNALS)) {
|
|
97
|
+
for (const signal of signals) {
|
|
98
|
+
const signalPath = path.join(dir, signal);
|
|
99
|
+
if (fs.existsSync(signalPath)) {
|
|
100
|
+
detected.push(platform);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
label: 'Platform detection',
|
|
108
|
+
status: detected.length > 0 ? 'pass' : 'warn',
|
|
109
|
+
detail: detected.length > 0
|
|
110
|
+
? `Detected: ${detected.join(', ')}`
|
|
111
|
+
: 'No platform config files found in current directory',
|
|
112
|
+
detected,
|
|
113
|
+
fix: detected.length === 0
|
|
114
|
+
? 'Run `nerviq setup` to generate baseline config files for your platform'
|
|
115
|
+
: null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function checkFreshnessGates() {
|
|
120
|
+
const results = [];
|
|
121
|
+
for (const [platform, modulePath] of Object.entries(FRESHNESS_MODULES)) {
|
|
122
|
+
try {
|
|
123
|
+
const freshness = require(modulePath);
|
|
124
|
+
const gate = freshness.checkReleaseGate({});
|
|
125
|
+
const staleCount = (gate.stale || []).length;
|
|
126
|
+
const freshCount = (gate.fresh || []).length;
|
|
127
|
+
const totalCount = (gate.results || []).length;
|
|
128
|
+
results.push({
|
|
129
|
+
platform,
|
|
130
|
+
status: staleCount === 0 ? 'pass' : 'warn',
|
|
131
|
+
fresh: freshCount,
|
|
132
|
+
total: totalCount,
|
|
133
|
+
stale: staleCount,
|
|
134
|
+
detail: staleCount === 0
|
|
135
|
+
? `All ${totalCount} P0 sources fresh`
|
|
136
|
+
: `${staleCount}/${totalCount} P0 sources unverified/stale`,
|
|
137
|
+
});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
results.push({ platform, status: 'error', detail: e.message });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function checkCliPermissions() {
|
|
146
|
+
const cliBin = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
147
|
+
const exists = fs.existsSync(cliBin);
|
|
148
|
+
if (!exists) {
|
|
149
|
+
return { label: 'CLI binary (bin/cli.js)', status: 'fail', detail: 'bin/cli.js not found', fix: null };
|
|
150
|
+
}
|
|
151
|
+
return { label: 'CLI binary (bin/cli.js)', status: 'pass', detail: cliBin, fix: null };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function checkGitRepo(dir) {
|
|
155
|
+
const gitPath = path.join(dir, '.git');
|
|
156
|
+
const exists = fs.existsSync(gitPath);
|
|
157
|
+
return {
|
|
158
|
+
label: 'Git repository',
|
|
159
|
+
status: exists ? 'pass' : 'warn',
|
|
160
|
+
detail: exists ? '.git/ found' : 'Not a git repository',
|
|
161
|
+
fix: exists ? null : 'Run: git init (recommended for safety)',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Main doctor function ────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
167
|
async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
|
|
168
168
|
const startMs = Date.now();
|
|
169
169
|
|
|
170
170
|
const checks = [
|
|
171
|
-
checkNodeVersion(),
|
|
172
|
-
checkDeps(),
|
|
173
|
-
checkJestInstalled(),
|
|
174
|
-
checkCliPermissions(),
|
|
175
|
-
checkGitRepo(dir),
|
|
176
|
-
checkPlatformDetection(dir),
|
|
171
|
+
checkNodeVersion(),
|
|
172
|
+
checkDeps(),
|
|
173
|
+
checkJestInstalled(),
|
|
174
|
+
checkCliPermissions(),
|
|
175
|
+
checkGitRepo(dir),
|
|
176
|
+
checkPlatformDetection(dir),
|
|
177
177
|
];
|
|
178
178
|
|
|
179
179
|
const detectedPlatforms = (checks.find(c => c.detected) || {}).detected || [];
|
|
@@ -184,7 +184,7 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
184
184
|
const totalPass = checks.filter(c => c.status === 'pass').length;
|
|
185
185
|
const totalWarn = checks.filter(c => c.status === 'warn').length;
|
|
186
186
|
const totalFail = checks.filter(c => c.status === 'fail').length;
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
const freshPass = freshnessChecks.filter(f => f.status === 'pass').length;
|
|
189
189
|
const freshWarn = freshnessChecks.filter(f => f.status !== 'pass').length;
|
|
190
190
|
|
|
@@ -194,10 +194,10 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
194
194
|
if (json) {
|
|
195
195
|
return JSON.stringify({
|
|
196
196
|
nerviq: version,
|
|
197
|
-
node: process.version,
|
|
198
|
-
dir,
|
|
199
|
-
overallOk,
|
|
200
|
-
checks,
|
|
197
|
+
node: process.version,
|
|
198
|
+
dir,
|
|
199
|
+
overallOk,
|
|
200
|
+
checks,
|
|
201
201
|
freshnessChecks,
|
|
202
202
|
mcpChecks: mcpSummary.checks,
|
|
203
203
|
hookChecks: hookSummary.checks,
|
|
@@ -217,34 +217,34 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
217
217
|
elapsed,
|
|
218
218
|
}, null, 2);
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
const lines = [''];
|
|
222
|
-
lines.push(c(` nerviq doctor v${version}`, 'bold'));
|
|
223
|
-
lines.push(c(' ═══════════════════════════════════════', 'dim'));
|
|
224
|
-
lines.push('');
|
|
225
|
-
|
|
226
|
-
// Environment checks
|
|
227
|
-
lines.push(c(' Environment', 'bold'));
|
|
228
|
-
for (const chk of checks) {
|
|
229
|
-
const icon = chk.status === 'pass' ? c('✓', 'green') : chk.status === 'warn' ? c('⚠', 'yellow') : c('✗', 'red');
|
|
230
|
-
lines.push(` ${icon} ${chk.label.padEnd(32)} ${c(chk.detail, chk.status === 'pass' ? 'dim' : 'reset')}`);
|
|
231
|
-
if (chk.fix && (verbose || chk.status === 'fail')) {
|
|
232
|
-
lines.push(c(` Fix: ${chk.fix}`, 'yellow'));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Platform detection detail
|
|
220
|
+
|
|
221
|
+
const lines = [''];
|
|
222
|
+
lines.push(c(` nerviq doctor v${version}`, 'bold'));
|
|
223
|
+
lines.push(c(' ═══════════════════════════════════════', 'dim'));
|
|
224
|
+
lines.push('');
|
|
225
|
+
|
|
226
|
+
// Environment checks
|
|
227
|
+
lines.push(c(' Environment', 'bold'));
|
|
228
|
+
for (const chk of checks) {
|
|
229
|
+
const icon = chk.status === 'pass' ? c('✓', 'green') : chk.status === 'warn' ? c('⚠', 'yellow') : c('✗', 'red');
|
|
230
|
+
lines.push(` ${icon} ${chk.label.padEnd(32)} ${c(chk.detail, chk.status === 'pass' ? 'dim' : 'reset')}`);
|
|
231
|
+
if (chk.fix && (verbose || chk.status === 'fail')) {
|
|
232
|
+
lines.push(c(` Fix: ${chk.fix}`, 'yellow'));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Platform detection detail
|
|
237
237
|
if (detectedPlatforms.length > 0) {
|
|
238
238
|
lines.push('');
|
|
239
239
|
lines.push(c(' Detected Platforms', 'bold'));
|
|
240
|
-
for (const p of detectedPlatforms) {
|
|
241
|
-
lines.push(` ${c('✓', 'green')} ${p}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Freshness
|
|
246
|
-
lines.push('');
|
|
247
|
-
lines.push(c(' Freshness Gates', 'bold'));
|
|
240
|
+
for (const p of detectedPlatforms) {
|
|
241
|
+
lines.push(` ${c('✓', 'green')} ${p}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Freshness
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push(c(' Freshness Gates', 'bold'));
|
|
248
248
|
for (const f of freshnessChecks) {
|
|
249
249
|
const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
|
|
250
250
|
const label = f.platform.padEnd(12);
|
|
@@ -306,14 +306,14 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
306
306
|
lines.push(` Hooks: ${c(String(hookSummary.pass), 'green')} pass ${hookSummary.warn > 0 ? c(String(hookSummary.warn), 'yellow') + ' warn ' : ''}${hookSummary.fail > 0 ? c(String(hookSummary.fail), 'red') + ' fail' : ''}${c(`(${hookSummary.declared} declared)`, 'dim')}`);
|
|
307
307
|
lines.push(` Status: ${overallOk ? c('✓ Healthy', 'green') : c('✗ Issues found', 'red')}`);
|
|
308
308
|
lines.push(` Duration: ${elapsed}ms`);
|
|
309
|
-
lines.push('');
|
|
310
|
-
|
|
311
|
-
if (!overallOk) {
|
|
312
|
-
lines.push(c(' Run with --verbose for fix suggestions.', 'dim'));
|
|
313
|
-
lines.push('');
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return lines.join('\n');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
module.exports = { runDoctor };
|
|
309
|
+
lines.push('');
|
|
310
|
+
|
|
311
|
+
if (!overallOk) {
|
|
312
|
+
lines.push(c(' Run with --verbose for fix suggestions.', 'dim'));
|
|
313
|
+
lines.push('');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = { runDoctor };
|