@nerviq/cli 0.0.1 → 0.9.0-beta.2

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 (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,523 @@
1
+ const { DOMAIN_PACKS } = require('./domain-packs');
2
+ const { MCP_PACKS, mergeMcpServers, normalizeMcpPackKeys } = require('./mcp-packs');
3
+ const { getCodexGovernanceSummary } = require('./codex/governance');
4
+
5
+ const PERMISSION_PROFILES = [
6
+ {
7
+ key: 'read-only',
8
+ label: 'Read-Only',
9
+ risk: 'low',
10
+ defaultMode: 'plan',
11
+ useWhen: 'Security review, discovery, and first contact with a mature repo.',
12
+ behavior: 'No file writes. Safe for audits, workshops, and approval flows.',
13
+ deny: ['Write(**)', 'Edit(**)', 'MultiEdit(**)', 'Bash(rm -rf *)', 'Bash(git reset --hard *)'],
14
+ },
15
+ {
16
+ key: 'suggest-only',
17
+ label: 'Suggest-Only',
18
+ risk: 'low',
19
+ defaultMode: 'acceptEdits',
20
+ useWhen: 'Teams want structured proposals and exported plans without automatic apply.',
21
+ behavior: 'Generates plans and proposal bundles, but no source changes are applied.',
22
+ deny: ['Bash(rm -rf *)', 'Bash(git reset --hard *)', 'Bash(git clean *)', 'Read(./.env*)'],
23
+ },
24
+ {
25
+ key: 'safe-write',
26
+ label: 'Safe-Write',
27
+ risk: 'medium',
28
+ defaultMode: 'acceptEdits',
29
+ useWhen: 'Starter repos or tightly scoped apply flows with visible rollback.',
30
+ behavior: 'Allows creation of missing Claude artifacts while preserving existing files.',
31
+ deny: ['Read(./.env*)', 'Read(./secrets/**)', 'Bash(rm -rf *)', 'Bash(git push --force *)'],
32
+ },
33
+ {
34
+ key: 'power-user',
35
+ label: 'Power-User',
36
+ risk: 'medium',
37
+ defaultMode: 'acceptEdits',
38
+ useWhen: 'Experienced maintainers who understand the repo and want faster iteration.',
39
+ behavior: 'Broader local automation with fewer prompts, still without bypass defaults.',
40
+ deny: ['Read(./.env*)', 'Bash(rm -rf *)'],
41
+ },
42
+ {
43
+ key: 'internal-research',
44
+ label: 'Internal-Research',
45
+ risk: 'high',
46
+ defaultMode: 'bypassPermissions',
47
+ useWhen: 'Internal experiments only, never as a product-facing default.',
48
+ behavior: 'Maximum autonomy for research workflows, suitable only with explicit human oversight.',
49
+ deny: [],
50
+ },
51
+ ];
52
+
53
+ const HOOK_REGISTRY = [
54
+ {
55
+ key: 'protect-secrets',
56
+ file: '.claude/hooks/protect-secrets.sh',
57
+ triggerPoint: 'PreToolUse',
58
+ matcher: 'Read|Write|Edit',
59
+ purpose: 'Blocks direct access to secret or credential files before a tool runs.',
60
+ filesTouched: [],
61
+ sideEffects: ['Stops the action and returns a block decision when a secret path is targeted.'],
62
+ risk: 'low',
63
+ dryRunExample: 'Attempt to read `.env` and confirm the hook blocks the request.',
64
+ rollbackPath: 'Remove the PreToolUse registration from settings.json.',
65
+ },
66
+ {
67
+ key: 'on-edit-lint',
68
+ file: '.claude/hooks/on-edit-lint.sh',
69
+ triggerPoint: 'PostToolUse',
70
+ matcher: 'Write|Edit',
71
+ purpose: 'Runs the repo linter or formatter after file edits when tooling is available.',
72
+ filesTouched: ['Working tree files targeted by eslint/ruff fixes'],
73
+ sideEffects: ['May auto-fix formatting or lint issues.', 'Can modify the same files that were just edited.'],
74
+ risk: 'medium',
75
+ dryRunExample: 'Edit a JS or Python file and inspect whether eslint or ruff would run.',
76
+ rollbackPath: 'Remove the PostToolUse hook entry or delete the script.',
77
+ },
78
+ {
79
+ key: 'log-changes',
80
+ file: '.claude/hooks/log-changes.sh',
81
+ triggerPoint: 'PostToolUse',
82
+ matcher: 'Write|Edit',
83
+ purpose: 'Appends a durable file-change log under `.claude/logs/` for later review.',
84
+ filesTouched: ['.claude/logs/file-changes.log'],
85
+ sideEffects: ['Creates the logs directory on first use.', 'Adds a timestamped audit line per file change.'],
86
+ risk: 'low',
87
+ dryRunExample: 'Edit one file and verify the log entry is appended.',
88
+ rollbackPath: 'Remove the PostToolUse hook entry and delete the log file if desired.',
89
+ },
90
+ {
91
+ key: 'duplicate-id-check',
92
+ file: '.claude/hooks/check-duplicate-ids.sh',
93
+ triggerPoint: 'PostToolUse',
94
+ matcher: 'Write|Edit',
95
+ purpose: 'Detects duplicate IDs in catalog or structured data files after edits.',
96
+ filesTouched: [],
97
+ sideEffects: ['Returns a systemMessage warning if duplicates are found.'],
98
+ risk: 'low',
99
+ dryRunExample: 'Edit a catalog file and verify duplicate check runs without blocking.',
100
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
101
+ },
102
+ {
103
+ key: 'injection-defense',
104
+ file: '.claude/hooks/injection-defense.sh',
105
+ triggerPoint: 'PostToolUse',
106
+ matcher: 'WebFetch|WebSearch',
107
+ purpose: 'Scans web tool outputs for common prompt injection patterns.',
108
+ filesTouched: ['tools/failure-log.txt'],
109
+ sideEffects: ['Logs alerts to failure log.', 'Returns a systemMessage warning if patterns detected.'],
110
+ risk: 'low',
111
+ dryRunExample: 'Run a WebFetch and verify output is scanned for injection patterns.',
112
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
113
+ },
114
+ {
115
+ key: 'trust-drift-check',
116
+ file: '.claude/hooks/trust-drift-check.sh',
117
+ triggerPoint: 'PostToolUse',
118
+ matcher: 'Write|Edit',
119
+ purpose: 'Runs trust drift validation after file changes to catch metric/docs inconsistencies.',
120
+ filesTouched: [],
121
+ sideEffects: ['Returns a systemMessage warning if drift is detected.'],
122
+ risk: 'low',
123
+ dryRunExample: 'Edit a product-facing file and verify drift check runs.',
124
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
125
+ },
126
+ {
127
+ key: 'session-init',
128
+ file: '.claude/hooks/session-start.sh',
129
+ triggerPoint: 'SessionStart',
130
+ matcher: null,
131
+ purpose: 'Rotates large log files and loads workspace context at session start.',
132
+ filesTouched: ['tools/change-log.txt', 'tools/failure-log.txt'],
133
+ sideEffects: ['Archives logs over 500KB.', 'Returns a systemMessage with workspace info.'],
134
+ risk: 'low',
135
+ dryRunExample: 'Start a new session and verify log rotation runs.',
136
+ rollbackPath: 'Remove the SessionStart hook entry from settings.',
137
+ },
138
+ ];
139
+
140
+ const POLICY_PACKS = [
141
+ {
142
+ key: 'baseline-engineering',
143
+ label: 'Baseline Engineering',
144
+ modules: ['CLAUDE.md baseline', 'commands', 'rules', 'safe-write profile'],
145
+ useWhen: 'General product teams that want a pragmatic default.',
146
+ },
147
+ {
148
+ key: 'security-sensitive',
149
+ label: 'Security-Sensitive',
150
+ modules: ['read-only profile', 'suggest-only mode', 'protect-secrets hook', 'approval checklist'],
151
+ useWhen: 'Auth, payments, customer data, or regulated surfaces.',
152
+ },
153
+ {
154
+ key: 'oss-friendly',
155
+ label: 'OSS-Friendly',
156
+ modules: ['suggest-only profile', 'minimal commands', 'light rules', 'manual merge expectations'],
157
+ useWhen: 'Open-source repos with many external contributors.',
158
+ },
159
+ {
160
+ key: 'regulated-lite',
161
+ label: 'Regulated-Lite',
162
+ modules: ['suggest-only or safe-write profile', 'activity artifacts', 'rollback manifests', 'benchmark evidence'],
163
+ useWhen: 'Teams that need auditable change paths before broader adoption.',
164
+ },
165
+ ];
166
+
167
+ const PILOT_ROLLOUT_KIT = {
168
+ recommendedScope: [
169
+ 'Pick 1-2 repos with active maintainers and low blast radius.',
170
+ 'Run discover and suggest-only first; avoid direct writes on mature repos.',
171
+ 'Choose one permission profile before any pilot starts.',
172
+ 'Define success metrics before the first benchmark run.',
173
+ ],
174
+ approvals: [
175
+ 'Engineering owner approves scope and rollback expectations.',
176
+ 'Security owner approves the selected permission profile and hooks.',
177
+ 'Pilot owner records the benchmark baseline and acceptance criteria.',
178
+ ],
179
+ successMetrics: [
180
+ 'Score delta and organic score delta',
181
+ 'Number of recommendations accepted',
182
+ 'Time to first useful Claude workflow',
183
+ 'Rollback-free apply rate',
184
+ ],
185
+ rollbackExpectations: [
186
+ 'Every apply batch must emit a rollback artifact.',
187
+ 'If a created artifact is rejected, delete the files listed in the rollback manifest.',
188
+ 'Record the rollback event in the activity log for auditability.',
189
+ ],
190
+ };
191
+
192
+ function clone(value) {
193
+ return JSON.parse(JSON.stringify(value));
194
+ }
195
+
196
+ function mergeUnique(existing = [], additions = []) {
197
+ return [...new Set([...(Array.isArray(existing) ? existing : []), ...additions])];
198
+ }
199
+
200
+ function mergeHooks(existingHooks = {}, nextHooks = {}) {
201
+ const merged = clone(existingHooks || {});
202
+
203
+ for (const [stage, blocks] of Object.entries(nextHooks)) {
204
+ const targetBlocks = Array.isArray(merged[stage]) ? clone(merged[stage]) : [];
205
+ for (const incoming of blocks) {
206
+ const index = targetBlocks.findIndex(block => block.matcher === incoming.matcher);
207
+ if (index === -1) {
208
+ targetBlocks.push(clone(incoming));
209
+ continue;
210
+ }
211
+
212
+ const current = targetBlocks[index];
213
+ const existingCommands = new Set((current.hooks || []).map(hook => `${hook.type}:${hook.command}:${hook.timeout || ''}`));
214
+ const mergedHooks = [...(current.hooks || [])];
215
+ for (const hook of incoming.hooks || []) {
216
+ const signature = `${hook.type}:${hook.command}:${hook.timeout || ''}`;
217
+ if (!existingCommands.has(signature)) {
218
+ mergedHooks.push(clone(hook));
219
+ existingCommands.add(signature);
220
+ }
221
+ }
222
+ targetBlocks[index] = { ...current, hooks: mergedHooks };
223
+ }
224
+ merged[stage] = targetBlocks;
225
+ }
226
+
227
+ return merged;
228
+ }
229
+
230
+ function getPermissionProfile(key = 'safe-write') {
231
+ return PERMISSION_PROFILES.find(profile => profile.key === key) ||
232
+ PERMISSION_PROFILES.find(profile => profile.key === 'safe-write');
233
+ }
234
+
235
+ function isWritableProfile(key = 'safe-write') {
236
+ return ['safe-write', 'power-user', 'internal-research'].includes(getPermissionProfile(key).key);
237
+ }
238
+
239
+ function ensureWritableProfile(key = 'safe-write', commandName = 'apply', dryRun = false) {
240
+ const profile = getPermissionProfile(key);
241
+ if (!dryRun && !isWritableProfile(profile.key)) {
242
+ throw new Error(`${commandName} requires a writable profile. Use --profile safe-write or --dry-run.`);
243
+ }
244
+ return profile;
245
+ }
246
+
247
+ function buildHookConfig(hookFiles, profileKey) {
248
+ const profile = getPermissionProfile(profileKey);
249
+ if (!isWritableProfile(profile.key)) {
250
+ return {};
251
+ }
252
+
253
+ const uniqueFiles = [...new Set(hookFiles)].sort();
254
+ if (uniqueFiles.length === 0) {
255
+ return {};
256
+ }
257
+
258
+ // Detect hook runtime: .js files use node, .sh files use bash
259
+ const hookCommand = (file) => {
260
+ if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
261
+ return `bash .claude/hooks/${file}`;
262
+ };
263
+ const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
264
+ const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
265
+
266
+ const hookConfig = {
267
+ PostToolUse: [{
268
+ matcher: 'Write|Edit',
269
+ hooks: uniqueFiles
270
+ .filter(file => !isSecrets(file) && !isSession(file))
271
+ .map(file => ({
272
+ type: 'command',
273
+ command: hookCommand(file),
274
+ timeout: 10,
275
+ })),
276
+ }],
277
+ };
278
+
279
+ const secretsFile = uniqueFiles.find(isSecrets);
280
+ if (secretsFile) {
281
+ hookConfig.PreToolUse = [{
282
+ matcher: 'Read|Write|Edit',
283
+ hooks: [{
284
+ type: 'command',
285
+ command: hookCommand(secretsFile),
286
+ timeout: 5,
287
+ }],
288
+ }];
289
+ }
290
+
291
+ const sessionFile = uniqueFiles.find(isSession);
292
+ if (sessionFile) {
293
+ hookConfig.SessionStart = [{
294
+ matcher: '*',
295
+ hooks: [{
296
+ type: 'command',
297
+ command: hookCommand(sessionFile),
298
+ timeout: 5,
299
+ }],
300
+ }];
301
+ }
302
+
303
+ if ((hookConfig.PostToolUse[0].hooks || []).length === 0) {
304
+ delete hookConfig.PostToolUse;
305
+ }
306
+
307
+ return hookConfig;
308
+ }
309
+
310
+ function buildSettingsForProfile({ profileKey = 'safe-write', hookFiles = [], existingSettings = null, mcpPackKeys = [] } = {}) {
311
+ const profile = getPermissionProfile(profileKey);
312
+ const base = existingSettings ? clone(existingSettings) : {};
313
+ const selectedMcpPacks = normalizeMcpPackKeys(mcpPackKeys);
314
+ base.permissions = base.permissions || {};
315
+ base.permissions.defaultMode = profile.defaultMode;
316
+ base.permissions.deny = mergeUnique(base.permissions.deny, profile.deny);
317
+
318
+ const hookConfig = buildHookConfig(hookFiles, profile.key);
319
+ if (Object.keys(hookConfig).length > 0) {
320
+ base.hooks = mergeHooks(base.hooks, hookConfig);
321
+ }
322
+
323
+ if (selectedMcpPacks.length > 0) {
324
+ base.mcpServers = mergeMcpServers(base.mcpServers, selectedMcpPacks);
325
+ }
326
+
327
+ base.claudexSetup = {
328
+ ...(base.claudexSetup || {}),
329
+ profile: profile.key,
330
+ mcpPacks: selectedMcpPacks,
331
+ };
332
+
333
+ return base;
334
+ }
335
+
336
+ /**
337
+ * Return the full governance surface: permission profiles, hooks, policy packs, and pilot kit.
338
+ * @returns {Object} Summary containing permissionProfiles, hookRegistry, policyPacks, domainPacks, mcpPacks, and pilotRolloutKit.
339
+ */
340
+ function getGovernanceSummary(platform = 'claude') {
341
+ if (platform === 'codex') {
342
+ return getCodexGovernanceSummary();
343
+ }
344
+
345
+ return {
346
+ platform: 'claude',
347
+ platformLabel: 'Claude',
348
+ permissionProfiles: PERMISSION_PROFILES,
349
+ hookRegistry: HOOK_REGISTRY,
350
+ policyPacks: POLICY_PACKS,
351
+ domainPacks: DOMAIN_PACKS,
352
+ mcpPacks: MCP_PACKS,
353
+ pilotRolloutKit: PILOT_ROLLOUT_KIT,
354
+ };
355
+ }
356
+
357
+ function printGovernanceSummary(summary, options = {}) {
358
+ if (options.json) {
359
+ console.log(JSON.stringify(summary, null, 2));
360
+ return;
361
+ }
362
+
363
+ console.log('');
364
+ console.log(` nerviq ${summary.platformLabel.toLowerCase()} governance`);
365
+ console.log(' ═══════════════════════════════════════');
366
+ console.log(` Safe defaults, hook transparency, and pilot guidance for ${summary.platformLabel}.`);
367
+ console.log('');
368
+
369
+ console.log(' Permission Profiles');
370
+ for (const profile of summary.permissionProfiles) {
371
+ console.log(` - ${profile.label} [${profile.risk}]`);
372
+ console.log(` ${profile.useWhen}`);
373
+ console.log(` defaultMode=${profile.defaultMode}`);
374
+ }
375
+ console.log('');
376
+
377
+ console.log(' Hook Registry');
378
+ for (const hook of summary.hookRegistry) {
379
+ const riskColor = hook.risk === 'low' ? '\x1b[32m' : hook.risk === 'medium' ? '\x1b[33m' : '\x1b[31m';
380
+ console.log(` - ${hook.file} ${riskColor}[${hook.risk} risk]\x1b[0m`);
381
+ console.log(` ${hook.triggerPoint}${hook.matcher ? ` ${hook.matcher}` : ''} -> ${hook.purpose}`);
382
+ }
383
+ console.log('');
384
+
385
+ console.log(' Policy Packs');
386
+ for (const pack of summary.policyPacks) {
387
+ console.log(` - ${pack.label}: ${pack.modules.join(', ')}`);
388
+ }
389
+ console.log('');
390
+
391
+ console.log(' Domain Packs');
392
+ if ((summary.domainPacks || []).length === 0) {
393
+ console.log(' - none shipped yet for this platform');
394
+ }
395
+ for (const pack of summary.domainPacks || []) {
396
+ console.log(` - ${pack.label}: ${pack.useWhen}`);
397
+ }
398
+ console.log('');
399
+
400
+ console.log(' MCP Packs');
401
+ if ((summary.mcpPacks || []).length === 0) {
402
+ console.log(' - none shipped yet for this platform');
403
+ }
404
+ for (const pack of summary.mcpPacks || []) {
405
+ console.log(` - ${pack.label}: ${Object.keys(pack.servers).join(', ')}`);
406
+ }
407
+ console.log('');
408
+
409
+ if (Array.isArray(summary.platformCaveats) && summary.platformCaveats.length > 0) {
410
+ console.log(' Platform Caveats');
411
+ for (const item of summary.platformCaveats) {
412
+ console.log(` - ${item}`);
413
+ }
414
+ console.log('');
415
+ }
416
+
417
+ console.log(' Pilot Rollout Kit');
418
+ for (const item of summary.pilotRolloutKit.recommendedScope) {
419
+ console.log(` - ${item}`);
420
+ }
421
+ console.log('');
422
+ }
423
+
424
+ /**
425
+ * Render a governance summary as a formatted markdown string.
426
+ * @param {Object} summary - The summary object returned by getGovernanceSummary().
427
+ * @returns {string} Markdown-formatted governance report.
428
+ */
429
+ function renderGovernanceMarkdown(summary) {
430
+ const lines = [
431
+ '# Claudex Setup Governance Report',
432
+ '',
433
+ `Platform: ${summary.platformLabel}`,
434
+ '',
435
+ `This report summarizes the shipped governance surface for ${summary.platformLabel} rollout, review, and pilot approval.`,
436
+ '',
437
+ '## Permission Profiles',
438
+ ];
439
+
440
+ for (const profile of summary.permissionProfiles) {
441
+ lines.push(`- **${profile.label}** \`${profile.key}\` | risk: \`${profile.risk}\` | defaultMode: \`${profile.defaultMode}\``);
442
+ lines.push(` - Use when: ${profile.useWhen}`);
443
+ lines.push(` - Behavior: ${profile.behavior}`);
444
+ if (Array.isArray(profile.deny) && profile.deny.length > 0) {
445
+ lines.push(` - Deny rules: ${profile.deny.join(', ')}`);
446
+ }
447
+ }
448
+
449
+ lines.push('', '## Hook Registry');
450
+ for (const hook of summary.hookRegistry) {
451
+ lines.push(`- **${hook.key}** \`${hook.triggerPoint}${hook.matcher ? ` ${hook.matcher}` : ''}\` | risk: \`${hook.risk}\``);
452
+ lines.push(` - File: ${hook.file}`);
453
+ lines.push(` - Purpose: ${hook.purpose}`);
454
+ lines.push(` - Dry run: ${hook.dryRunExample}`);
455
+ lines.push(` - Rollback: ${hook.rollbackPath}`);
456
+ }
457
+
458
+ lines.push('', '## Policy Packs');
459
+ for (const pack of summary.policyPacks) {
460
+ lines.push(`- **${pack.label}**`);
461
+ lines.push(` - Use when: ${pack.useWhen}`);
462
+ lines.push(` - Modules: ${pack.modules.join(', ')}`);
463
+ }
464
+
465
+ lines.push('', `## Domain Packs (${(summary.domainPacks || []).length})`);
466
+ for (const pack of summary.domainPacks || []) {
467
+ lines.push(`- **${pack.label}**: ${pack.useWhen}`);
468
+ }
469
+ if ((summary.domainPacks || []).length === 0) {
470
+ lines.push('- None shipped yet for this platform.');
471
+ }
472
+
473
+ lines.push('', `## MCP Packs (${(summary.mcpPacks || []).length})`);
474
+ for (const pack of summary.mcpPacks || []) {
475
+ lines.push(`- **${pack.label}**: ${Object.keys(pack.servers).join(', ')}`);
476
+ }
477
+ if ((summary.mcpPacks || []).length === 0) {
478
+ lines.push('- None shipped yet for this platform.');
479
+ }
480
+
481
+ if (Array.isArray(summary.platformCaveats) && summary.platformCaveats.length > 0) {
482
+ lines.push('', '## Platform Caveats');
483
+ for (const item of summary.platformCaveats) {
484
+ lines.push(`- ${item}`);
485
+ }
486
+ }
487
+
488
+ lines.push('', '## Pilot Rollout Kit', '### Recommended Scope');
489
+ for (const item of summary.pilotRolloutKit.recommendedScope) {
490
+ lines.push(`- ${item}`);
491
+ }
492
+
493
+ lines.push('', '### Approvals');
494
+ for (const item of summary.pilotRolloutKit.approvals) {
495
+ lines.push(`- ${item}`);
496
+ }
497
+
498
+ lines.push('', '### Success Metrics');
499
+ for (const item of summary.pilotRolloutKit.successMetrics) {
500
+ lines.push(`- ${item}`);
501
+ }
502
+
503
+ lines.push('', '### Rollback Expectations');
504
+ for (const item of summary.pilotRolloutKit.rollbackExpectations) {
505
+ lines.push(`- ${item}`);
506
+ }
507
+
508
+ lines.push('');
509
+ lines.push('---');
510
+ lines.push(`*Generated by nerviq v${require('../package.json').version} on ${new Date().toISOString().split('T')[0]}*`);
511
+ return lines.join('\n');
512
+ }
513
+
514
+ module.exports = {
515
+ PERMISSION_PROFILES,
516
+ getPermissionProfile,
517
+ isWritableProfile,
518
+ ensureWritableProfile,
519
+ buildSettingsForProfile,
520
+ getGovernanceSummary,
521
+ printGovernanceSummary,
522
+ renderGovernanceMarkdown,
523
+ };