@nerviq/cli 1.10.0 → 1.12.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 +176 -47
- package/bin/cli.js +842 -287
- package/package.json +2 -2
- package/src/activity.js +225 -59
- package/src/adoption-advisor.js +299 -0
- package/src/aider/freshness.js +28 -25
- package/src/aider/techniques.js +16 -11
- package/src/analyze.js +131 -1
- package/src/anti-patterns.js +17 -2
- package/src/audit.js +197 -96
- package/src/behavioral-drift.js +801 -0
- package/src/benchmark.js +15 -10
- package/src/continuous-ops.js +681 -0
- package/src/cost-tracking.js +61 -0
- package/src/cursor/techniques.js +17 -12
- package/src/deep-review.js +83 -0
- package/src/diff-only.js +280 -0
- package/src/doctor.js +118 -55
- package/src/governance.js +72 -50
- package/src/hook-validation.js +342 -0
- package/src/index.js +7 -1
- package/src/integrations.js +144 -60
- package/src/mcp-validation.js +337 -0
- package/src/opencode/techniques.js +12 -7
- package/src/operating-profile.js +574 -0
- package/src/org.js +97 -13
- package/src/permission-rules.js +218 -0
- package/src/plans.js +192 -8
- package/src/platform-change-manifest.js +86 -0
- package/src/policy-layers.js +210 -0
- package/src/profiles.js +4 -1
- package/src/prompt-injection.js +74 -0
- package/src/repo-archetype.js +386 -0
- package/src/secret-patterns.js +9 -0
- package/src/server.js +398 -3
- package/src/setup.js +36 -2
- package/src/source-urls.js +132 -132
- package/src/supplemental-checks.js +13 -12
- package/src/techniques/api.js +407 -0
- package/src/techniques/automation.js +316 -0
- package/src/techniques/compliance.js +257 -0
- package/src/techniques/hygiene.js +294 -0
- package/src/techniques/instructions.js +243 -0
- package/src/techniques/observability.js +226 -0
- package/src/techniques/optimization.js +142 -0
- package/src/techniques/quality.js +317 -0
- package/src/techniques/security.js +237 -0
- package/src/techniques/shared.js +443 -0
- package/src/techniques/stacks.js +2294 -0
- package/src/techniques/tools.js +106 -0
- package/src/techniques/workflow.js +413 -0
- package/src/techniques.js +78 -5611
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/watch.js +18 -0
- package/src/windsurf/techniques.js +17 -12
- package/src/workspace.js +105 -8
package/src/doctor.js
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* Checks: Node version, dependencies, platform detection, freshness gates.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { version } = require('../package.json');
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { version } = require('../package.json');
|
|
13
|
+
const { validateDeclaredMcpServers } = require('./mcp-validation');
|
|
14
|
+
const { validateDeclaredHooks } = require('./hook-validation');
|
|
13
15
|
|
|
14
16
|
const COLORS = {
|
|
15
17
|
reset: '\x1b[0m',
|
|
@@ -25,11 +27,11 @@ function c(text, color) {
|
|
|
25
27
|
return `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const PLATFORM_SIGNALS = {
|
|
29
|
-
claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json'],
|
|
30
|
-
codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
|
|
31
|
-
cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
|
|
32
|
-
copilot: ['.github/copilot-instructions.md', '.github/'],
|
|
30
|
+
const PLATFORM_SIGNALS = {
|
|
31
|
+
claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json', '.mcp.json'],
|
|
32
|
+
codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
|
|
33
|
+
cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
|
|
34
|
+
copilot: ['.github/copilot-instructions.md', '.github/', '.vscode/mcp.json'],
|
|
33
35
|
gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
|
|
34
36
|
windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
|
|
35
37
|
aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
|
|
@@ -162,46 +164,59 @@ function checkGitRepo(dir) {
|
|
|
162
164
|
|
|
163
165
|
// ─── Main doctor function ────────────────────────────────────────────────────
|
|
164
166
|
|
|
165
|
-
async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
|
|
166
|
-
const startMs = Date.now();
|
|
167
|
-
|
|
168
|
-
const checks = [
|
|
167
|
+
async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
|
|
168
|
+
const startMs = Date.now();
|
|
169
|
+
|
|
170
|
+
const checks = [
|
|
169
171
|
checkNodeVersion(),
|
|
170
172
|
checkDeps(),
|
|
171
173
|
checkJestInstalled(),
|
|
172
174
|
checkCliPermissions(),
|
|
173
175
|
checkGitRepo(dir),
|
|
174
176
|
checkPlatformDetection(dir),
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const overallOk = totalFail === 0;
|
|
187
|
-
const elapsed = Date.now() - startMs;
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const detectedPlatforms = (checks.find(c => c.detected) || {}).detected || [];
|
|
180
|
+
const freshnessChecks = checkFreshnessGates();
|
|
181
|
+
const mcpSummary = await validateDeclaredMcpServers({ dir, detectedPlatforms });
|
|
182
|
+
const hookSummary = validateDeclaredHooks({ dir, detectedPlatforms });
|
|
183
|
+
|
|
184
|
+
const totalPass = checks.filter(c => c.status === 'pass').length;
|
|
185
|
+
const totalWarn = checks.filter(c => c.status === 'warn').length;
|
|
186
|
+
const totalFail = checks.filter(c => c.status === 'fail').length;
|
|
188
187
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const freshPass = freshnessChecks.filter(f => f.status === 'pass').length;
|
|
189
|
+
const freshWarn = freshnessChecks.filter(f => f.status !== 'pass').length;
|
|
190
|
+
|
|
191
|
+
const overallOk = totalFail === 0 && mcpSummary.fail === 0 && hookSummary.fail === 0;
|
|
192
|
+
const elapsed = Date.now() - startMs;
|
|
193
|
+
|
|
194
|
+
if (json) {
|
|
195
|
+
return JSON.stringify({
|
|
196
|
+
nerviq: version,
|
|
192
197
|
node: process.version,
|
|
193
198
|
dir,
|
|
194
199
|
overallOk,
|
|
195
200
|
checks,
|
|
196
|
-
freshnessChecks,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
freshnessChecks,
|
|
202
|
+
mcpChecks: mcpSummary.checks,
|
|
203
|
+
hookChecks: hookSummary.checks,
|
|
204
|
+
totalPass,
|
|
205
|
+
totalWarn,
|
|
206
|
+
totalFail,
|
|
207
|
+
freshPass,
|
|
208
|
+
freshWarn,
|
|
209
|
+
mcpDeclared: mcpSummary.declared,
|
|
210
|
+
mcpPass: mcpSummary.pass,
|
|
211
|
+
mcpWarn: mcpSummary.warn,
|
|
212
|
+
mcpFail: mcpSummary.fail,
|
|
213
|
+
hookDeclared: hookSummary.declared,
|
|
214
|
+
hookPass: hookSummary.pass,
|
|
215
|
+
hookWarn: hookSummary.warn,
|
|
216
|
+
hookFail: hookSummary.fail,
|
|
217
|
+
elapsed,
|
|
218
|
+
}, null, 2);
|
|
219
|
+
}
|
|
205
220
|
|
|
206
221
|
const lines = [''];
|
|
207
222
|
lines.push(c(` nerviq doctor v${version}`, 'bold'));
|
|
@@ -219,10 +234,9 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
219
234
|
}
|
|
220
235
|
|
|
221
236
|
// Platform detection detail
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
lines.push('');
|
|
225
|
-
lines.push(c(' Detected Platforms', 'bold'));
|
|
237
|
+
if (detectedPlatforms.length > 0) {
|
|
238
|
+
lines.push('');
|
|
239
|
+
lines.push(c(' Detected Platforms', 'bold'));
|
|
226
240
|
for (const p of detectedPlatforms) {
|
|
227
241
|
lines.push(` ${c('✓', 'green')} ${p}`);
|
|
228
242
|
}
|
|
@@ -231,18 +245,67 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
231
245
|
// Freshness
|
|
232
246
|
lines.push('');
|
|
233
247
|
lines.push(c(' Freshness Gates', 'bold'));
|
|
234
|
-
for (const f of freshnessChecks) {
|
|
235
|
-
const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
|
|
236
|
-
const label = f.platform.padEnd(12);
|
|
237
|
-
lines.push(` ${icon} ${label} ${c(f.detail || f.status, f.status === 'pass' ? 'dim' : 'yellow')}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
lines.push('');
|
|
241
|
-
lines.push(c('
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
for (const f of freshnessChecks) {
|
|
249
|
+
const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
|
|
250
|
+
const label = f.platform.padEnd(12);
|
|
251
|
+
lines.push(` ${icon} ${label} ${c(f.detail || f.status, f.status === 'pass' ? 'dim' : 'yellow')}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push(c(' MCP Servers', 'bold'));
|
|
256
|
+
if (mcpSummary.checks.length === 0) {
|
|
257
|
+
lines.push(c(' No declared MCP servers found in the detected project surfaces.', 'dim'));
|
|
258
|
+
} else {
|
|
259
|
+
for (const item of mcpSummary.checks) {
|
|
260
|
+
const icon = item.status === 'pass'
|
|
261
|
+
? c('✓', 'green')
|
|
262
|
+
: item.status === 'warn'
|
|
263
|
+
? c('⚠', 'yellow')
|
|
264
|
+
: c('✗', 'red');
|
|
265
|
+
const label = `${item.platform}/${item.scope}`.padEnd(16);
|
|
266
|
+
lines.push(` ${icon} ${label} ${item.serverName} ${c(item.detail, item.status === 'pass' ? 'dim' : item.status === 'warn' ? 'yellow' : 'red')}`);
|
|
267
|
+
if (verbose && item.source) {
|
|
268
|
+
lines.push(c(` Source: ${item.source}`, 'dim'));
|
|
269
|
+
}
|
|
270
|
+
if (item.fix && (verbose || item.status === 'fail')) {
|
|
271
|
+
lines.push(c(` Fix: ${item.fix}`, item.status === 'fail' ? 'yellow' : 'dim'));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
lines.push('');
|
|
277
|
+
lines.push(c(' Hook Runtime', 'bold'));
|
|
278
|
+
if (hookSummary.checks.length === 0) {
|
|
279
|
+
lines.push(c(' No declared hooks found in the detected project surfaces.', 'dim'));
|
|
280
|
+
} else {
|
|
281
|
+
for (const item of hookSummary.checks) {
|
|
282
|
+
const icon = item.status === 'pass'
|
|
283
|
+
? c('✓', 'green')
|
|
284
|
+
: item.status === 'warn'
|
|
285
|
+
? c('⚠', 'yellow')
|
|
286
|
+
: c('✗', 'red');
|
|
287
|
+
const label = `${item.platform}/${item.validationMode}`.padEnd(16);
|
|
288
|
+
lines.push(` ${icon} ${label} ${item.label} ${c(item.detail, item.status === 'pass' ? 'dim' : item.status === 'warn' ? 'yellow' : 'red')}`);
|
|
289
|
+
if (verbose && item.script) {
|
|
290
|
+
lines.push(c(` Script: ${item.script}`, 'dim'));
|
|
291
|
+
}
|
|
292
|
+
if (verbose && item.executable) {
|
|
293
|
+
lines.push(c(` Runtime: ${item.executable}`, 'dim'));
|
|
294
|
+
}
|
|
295
|
+
if (item.fix && (verbose || item.status === 'fail')) {
|
|
296
|
+
lines.push(c(` Fix: ${item.fix}`, item.status === 'fail' ? 'yellow' : 'dim'));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
lines.push('');
|
|
302
|
+
lines.push(c(' Summary', 'bold'));
|
|
303
|
+
lines.push(` Checks: ${c(String(totalPass), 'green')} pass ${totalWarn > 0 ? c(String(totalWarn), 'yellow') + ' warn ' : ''}${totalFail > 0 ? c(String(totalFail), 'red') + ' fail' : ''}`);
|
|
304
|
+
lines.push(` Freshness: ${c(String(freshPass), 'green')} fresh ${freshWarn > 0 ? c(String(freshWarn), 'yellow') + ' stale/unverified' : ''}`);
|
|
305
|
+
lines.push(` MCP: ${c(String(mcpSummary.pass), 'green')} pass ${mcpSummary.warn > 0 ? c(String(mcpSummary.warn), 'yellow') + ' warn ' : ''}${mcpSummary.fail > 0 ? c(String(mcpSummary.fail), 'red') + ' fail' : ''}${c(`(${mcpSummary.declared} declared)`, 'dim')}`);
|
|
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
|
+
lines.push(` Status: ${overallOk ? c('✓ Healthy', 'green') : c('✗ Issues found', 'red')}`);
|
|
308
|
+
lines.push(` Duration: ${elapsed}ms`);
|
|
246
309
|
lines.push('');
|
|
247
310
|
|
|
248
311
|
if (!overallOk) {
|
package/src/governance.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const { DOMAIN_PACKS } = require('./domain-packs');
|
|
2
|
-
const { MCP_PACKS, mergeMcpServers, normalizeMcpPackKeys } = require('./mcp-packs');
|
|
3
|
-
const { getCodexGovernanceSummary } = require('./codex/governance');
|
|
1
|
+
const { DOMAIN_PACKS } = require('./domain-packs');
|
|
2
|
+
const { MCP_PACKS, mergeMcpServers, normalizeMcpPackKeys } = require('./mcp-packs');
|
|
3
|
+
const { getCodexGovernanceSummary } = require('./codex/governance');
|
|
4
|
+
const { formatTerminologyLines } = require('./terminology');
|
|
4
5
|
|
|
5
6
|
const PERMISSION_PROFILES = [
|
|
6
7
|
{
|
|
@@ -103,19 +104,19 @@ const HOOK_REGISTRY = [
|
|
|
103
104
|
dryRunExample: 'Edit a catalog file and verify duplicate check runs without blocking.',
|
|
104
105
|
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
105
106
|
},
|
|
106
|
-
{
|
|
107
|
-
key: 'injection-defense',
|
|
108
|
-
file: '.claude/hooks/injection-defense.
|
|
109
|
-
triggerPoint: 'PostToolUse',
|
|
110
|
-
matcher: 'WebFetch|WebSearch',
|
|
111
|
-
purpose: 'Scans
|
|
112
|
-
filesTouched: ['
|
|
113
|
-
sideEffects: ['
|
|
114
|
-
risk: 'low',
|
|
115
|
-
riskLevel: 'low',
|
|
116
|
-
dryRunExample: 'Run a WebFetch and verify
|
|
117
|
-
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
118
|
-
},
|
|
107
|
+
{
|
|
108
|
+
key: 'injection-defense',
|
|
109
|
+
file: '.claude/hooks/injection-defense.js',
|
|
110
|
+
triggerPoint: 'PostToolUse',
|
|
111
|
+
matcher: 'WebFetch|WebSearch|Read|Grep|Glob|mcp__.*',
|
|
112
|
+
purpose: 'Scans external content flows for common prompt injection patterns and logs suspicious findings.',
|
|
113
|
+
filesTouched: ['.claude/logs/prompt-injection-alerts.log'],
|
|
114
|
+
sideEffects: ['Appends an alert line when suspicious external content is detected.'],
|
|
115
|
+
risk: 'low',
|
|
116
|
+
riskLevel: 'low',
|
|
117
|
+
dryRunExample: 'Run a WebFetch or MCP-backed tool call and verify suspicious content is logged for review.',
|
|
118
|
+
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
119
|
+
},
|
|
119
120
|
{
|
|
120
121
|
key: 'trust-drift-check',
|
|
121
122
|
file: '.claude/hooks/trust-drift-check.sh',
|
|
@@ -298,23 +299,24 @@ function buildHookConfig(hookFiles, profileKey) {
|
|
|
298
299
|
return {};
|
|
299
300
|
}
|
|
300
301
|
|
|
301
|
-
// Detect hook runtime: .js files use node, .sh files use bash
|
|
302
|
-
const hookCommand = (file) => {
|
|
303
|
-
if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
|
|
304
|
-
return `bash .claude/hooks/${file}`;
|
|
305
|
-
};
|
|
306
|
-
const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
|
|
307
|
-
const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
302
|
+
// Detect hook runtime: .js files use node, .sh files use bash
|
|
303
|
+
const hookCommand = (file) => {
|
|
304
|
+
if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
|
|
305
|
+
return `bash .claude/hooks/${file}`;
|
|
306
|
+
};
|
|
307
|
+
const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
|
|
308
|
+
const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
|
|
309
|
+
const isInjection = (f) => f === 'injection-defense.sh' || f === 'injection-defense.js';
|
|
310
|
+
|
|
311
|
+
const hookConfig = {
|
|
312
|
+
PostToolUse: [{
|
|
313
|
+
matcher: 'Write|Edit',
|
|
314
|
+
hooks: uniqueFiles
|
|
315
|
+
.filter(file => !isSecrets(file) && !isSession(file) && !isInjection(file))
|
|
316
|
+
.map(file => ({
|
|
317
|
+
type: 'command',
|
|
318
|
+
command: hookCommand(file),
|
|
319
|
+
timeout: 10,
|
|
318
320
|
})),
|
|
319
321
|
}],
|
|
320
322
|
};
|
|
@@ -332,16 +334,29 @@ function buildHookConfig(hookFiles, profileKey) {
|
|
|
332
334
|
}
|
|
333
335
|
|
|
334
336
|
const sessionFile = uniqueFiles.find(isSession);
|
|
335
|
-
if (sessionFile) {
|
|
336
|
-
hookConfig.SessionStart = [{
|
|
337
|
-
matcher: '*',
|
|
338
|
-
hooks: [{
|
|
339
|
-
type: 'command',
|
|
337
|
+
if (sessionFile) {
|
|
338
|
+
hookConfig.SessionStart = [{
|
|
339
|
+
matcher: '*',
|
|
340
|
+
hooks: [{
|
|
341
|
+
type: 'command',
|
|
340
342
|
command: hookCommand(sessionFile),
|
|
341
343
|
timeout: 5,
|
|
342
|
-
}],
|
|
343
|
-
}];
|
|
344
|
-
}
|
|
344
|
+
}],
|
|
345
|
+
}];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const injectionFile = uniqueFiles.find(isInjection);
|
|
349
|
+
if (injectionFile) {
|
|
350
|
+
hookConfig.PostToolUse = hookConfig.PostToolUse || [];
|
|
351
|
+
hookConfig.PostToolUse.push({
|
|
352
|
+
matcher: 'WebFetch|WebSearch|Read|Grep|Glob|mcp__.*',
|
|
353
|
+
hooks: [{
|
|
354
|
+
type: 'command',
|
|
355
|
+
command: hookCommand(injectionFile),
|
|
356
|
+
timeout: 5,
|
|
357
|
+
}],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
345
360
|
|
|
346
361
|
if ((hookConfig.PostToolUse[0].hooks || []).length === 0) {
|
|
347
362
|
delete hookConfig.PostToolUse;
|
|
@@ -406,10 +421,15 @@ function printGovernanceSummary(summary, options = {}) {
|
|
|
406
421
|
console.log('');
|
|
407
422
|
console.log(` nerviq ${summary.platformLabel.toLowerCase()} governance`);
|
|
408
423
|
console.log(' ═══════════════════════════════════════');
|
|
409
|
-
console.log(` Safe defaults, hook transparency, and pilot guidance for ${summary.platformLabel}.`);
|
|
410
|
-
console.log('');
|
|
411
|
-
|
|
412
|
-
|
|
424
|
+
console.log(` Safe defaults, hook transparency, and pilot guidance for ${summary.platformLabel}.`);
|
|
425
|
+
console.log('');
|
|
426
|
+
|
|
427
|
+
for (const line of formatTerminologyLines(['governance', 'hooks', 'denyRules', 'mcp'])) {
|
|
428
|
+
console.log(line);
|
|
429
|
+
}
|
|
430
|
+
console.log('');
|
|
431
|
+
|
|
432
|
+
console.log(' Permission Profiles');
|
|
413
433
|
for (const profile of summary.permissionProfiles) {
|
|
414
434
|
console.log(` - ${profile.label} [${profile.risk}]`);
|
|
415
435
|
console.log(` ${profile.useWhen}`);
|
|
@@ -570,11 +590,13 @@ function renderGovernanceMarkdown(summary) {
|
|
|
570
590
|
return lines.join('\n');
|
|
571
591
|
}
|
|
572
592
|
|
|
573
|
-
module.exports = {
|
|
574
|
-
PERMISSION_PROFILES,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
593
|
+
module.exports = {
|
|
594
|
+
PERMISSION_PROFILES,
|
|
595
|
+
HOOK_REGISTRY,
|
|
596
|
+
POLICY_PACKS,
|
|
597
|
+
getPermissionProfile,
|
|
598
|
+
isWritableProfile,
|
|
599
|
+
ensureWritableProfile,
|
|
578
600
|
buildSettingsForProfile,
|
|
579
601
|
getGovernanceSummary,
|
|
580
602
|
printGovernanceSummary,
|