@neurcode-ai/cli 0.14.0 → 0.15.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.
Files changed (188) hide show
  1. package/README.md +60 -8
  2. package/dist/api-client.d.ts +284 -0
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +111 -0
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/activate.d.ts +82 -0
  7. package/dist/commands/activate.d.ts.map +1 -0
  8. package/dist/commands/activate.js +551 -0
  9. package/dist/commands/activate.js.map +1 -0
  10. package/dist/commands/admission.d.ts +67 -0
  11. package/dist/commands/admission.d.ts.map +1 -0
  12. package/dist/commands/admission.js +350 -0
  13. package/dist/commands/admission.js.map +1 -0
  14. package/dist/commands/agent.d.ts +3 -0
  15. package/dist/commands/agent.d.ts.map +1 -0
  16. package/dist/commands/agent.js +2045 -0
  17. package/dist/commands/agent.js.map +1 -0
  18. package/dist/commands/demo.d.ts +3 -0
  19. package/dist/commands/demo.d.ts.map +1 -0
  20. package/dist/commands/demo.js +102 -0
  21. package/dist/commands/demo.js.map +1 -0
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/init.js +58 -44
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/commands/login.d.ts +1 -1
  26. package/dist/commands/login.d.ts.map +1 -1
  27. package/dist/commands/login.js +44 -22
  28. package/dist/commands/login.js.map +1 -1
  29. package/dist/commands/profile.d.ts +14 -0
  30. package/dist/commands/profile.d.ts.map +1 -0
  31. package/dist/commands/profile.js +118 -0
  32. package/dist/commands/profile.js.map +1 -0
  33. package/dist/commands/quickstart.d.ts +2 -2
  34. package/dist/commands/quickstart.d.ts.map +1 -1
  35. package/dist/commands/quickstart.js +31 -30
  36. package/dist/commands/quickstart.js.map +1 -1
  37. package/dist/commands/remediate-export.d.ts +6 -1
  38. package/dist/commands/remediate-export.d.ts.map +1 -1
  39. package/dist/commands/remediate-export.js +359 -7
  40. package/dist/commands/remediate-export.js.map +1 -1
  41. package/dist/commands/replay.d.ts.map +1 -1
  42. package/dist/commands/replay.js +84 -0
  43. package/dist/commands/replay.js.map +1 -1
  44. package/dist/commands/run.d.ts +3 -0
  45. package/dist/commands/run.d.ts.map +1 -0
  46. package/dist/commands/run.js +98 -0
  47. package/dist/commands/run.js.map +1 -0
  48. package/dist/commands/runtime-adapter.d.ts +8 -0
  49. package/dist/commands/runtime-adapter.d.ts.map +1 -0
  50. package/dist/commands/runtime-adapter.js +375 -0
  51. package/dist/commands/runtime-adapter.js.map +1 -0
  52. package/dist/commands/runtime-doctor.d.ts +6 -0
  53. package/dist/commands/runtime-doctor.d.ts.map +1 -0
  54. package/dist/commands/runtime-doctor.js +478 -0
  55. package/dist/commands/runtime-doctor.js.map +1 -0
  56. package/dist/commands/runtime-report.d.ts +13 -0
  57. package/dist/commands/runtime-report.d.ts.map +1 -0
  58. package/dist/commands/runtime-report.js +81 -0
  59. package/dist/commands/runtime-report.js.map +1 -0
  60. package/dist/commands/runtime-sync.d.ts +17 -0
  61. package/dist/commands/runtime-sync.d.ts.map +1 -0
  62. package/dist/commands/runtime-sync.js +656 -0
  63. package/dist/commands/runtime-sync.js.map +1 -0
  64. package/dist/commands/runtime.d.ts +16 -0
  65. package/dist/commands/runtime.d.ts.map +1 -0
  66. package/dist/commands/runtime.js +380 -0
  67. package/dist/commands/runtime.js.map +1 -0
  68. package/dist/commands/session-hook.d.ts +35 -0
  69. package/dist/commands/session-hook.d.ts.map +1 -0
  70. package/dist/commands/session-hook.js +1297 -0
  71. package/dist/commands/session-hook.js.map +1 -0
  72. package/dist/commands/session.d.ts +91 -0
  73. package/dist/commands/session.d.ts.map +1 -1
  74. package/dist/commands/session.js +1226 -0
  75. package/dist/commands/session.js.map +1 -1
  76. package/dist/commands/whoami.d.ts +7 -4
  77. package/dist/commands/whoami.d.ts.map +1 -1
  78. package/dist/commands/whoami.js +59 -34
  79. package/dist/commands/whoami.js.map +1 -1
  80. package/dist/config.d.ts.map +1 -1
  81. package/dist/config.js +24 -5
  82. package/dist/config.js.map +1 -1
  83. package/dist/daemon/routes.d.ts.map +1 -1
  84. package/dist/daemon/routes.js +8 -0
  85. package/dist/daemon/routes.js.map +1 -1
  86. package/dist/daemon/server.d.ts.map +1 -1
  87. package/dist/daemon/server.js +88 -0
  88. package/dist/daemon/server.js.map +1 -1
  89. package/dist/governance/impact-analysis.d.ts +27 -0
  90. package/dist/governance/impact-analysis.d.ts.map +1 -0
  91. package/dist/governance/impact-analysis.js +274 -0
  92. package/dist/governance/impact-analysis.js.map +1 -0
  93. package/dist/index.js +472 -29
  94. package/dist/index.js.map +1 -1
  95. package/dist/intent-engine/matcher.d.ts.map +1 -1
  96. package/dist/intent-engine/matcher.js +3 -12
  97. package/dist/intent-engine/matcher.js.map +1 -1
  98. package/dist/utils/admission-artifact.d.ts +59 -0
  99. package/dist/utils/admission-artifact.d.ts.map +1 -0
  100. package/dist/utils/admission-artifact.js +410 -0
  101. package/dist/utils/admission-artifact.js.map +1 -0
  102. package/dist/utils/agent-adapter-setup.d.ts +80 -0
  103. package/dist/utils/agent-adapter-setup.d.ts.map +1 -0
  104. package/dist/utils/agent-adapter-setup.js +577 -0
  105. package/dist/utils/agent-adapter-setup.js.map +1 -0
  106. package/dist/utils/agent-guard-supervisor.d.ts +75 -0
  107. package/dist/utils/agent-guard-supervisor.d.ts.map +1 -0
  108. package/dist/utils/agent-guard-supervisor.js +388 -0
  109. package/dist/utils/agent-guard-supervisor.js.map +1 -0
  110. package/dist/utils/agent-guard.d.ts +92 -0
  111. package/dist/utils/agent-guard.d.ts.map +1 -0
  112. package/dist/utils/agent-guard.js +326 -0
  113. package/dist/utils/agent-guard.js.map +1 -0
  114. package/dist/utils/agent-session-launcher.d.ts +89 -0
  115. package/dist/utils/agent-session-launcher.d.ts.map +1 -0
  116. package/dist/utils/agent-session-launcher.js +308 -0
  117. package/dist/utils/agent-session-launcher.js.map +1 -0
  118. package/dist/utils/bash-command-analysis.d.ts +19 -0
  119. package/dist/utils/bash-command-analysis.d.ts.map +1 -0
  120. package/dist/utils/bash-command-analysis.js +295 -0
  121. package/dist/utils/bash-command-analysis.js.map +1 -0
  122. package/dist/utils/consequence-nudges.d.ts +30 -0
  123. package/dist/utils/consequence-nudges.d.ts.map +1 -0
  124. package/dist/utils/consequence-nudges.js +313 -0
  125. package/dist/utils/consequence-nudges.js.map +1 -0
  126. package/dist/utils/drift-intelligence.d.ts.map +1 -1
  127. package/dist/utils/drift-intelligence.js +29 -7
  128. package/dist/utils/drift-intelligence.js.map +1 -1
  129. package/dist/utils/git-coverage.d.ts +57 -0
  130. package/dist/utils/git-coverage.d.ts.map +1 -0
  131. package/dist/utils/git-coverage.js +302 -0
  132. package/dist/utils/git-coverage.js.map +1 -0
  133. package/dist/utils/gitignore.d.ts.map +1 -1
  134. package/dist/utils/gitignore.js +2 -1
  135. package/dist/utils/gitignore.js.map +1 -1
  136. package/dist/utils/governed-intent.d.ts +10 -0
  137. package/dist/utils/governed-intent.d.ts.map +1 -0
  138. package/dist/utils/governed-intent.js +108 -0
  139. package/dist/utils/governed-intent.js.map +1 -0
  140. package/dist/utils/hook-heartbeat.d.ts +55 -0
  141. package/dist/utils/hook-heartbeat.d.ts.map +1 -0
  142. package/dist/utils/hook-heartbeat.js +116 -0
  143. package/dist/utils/hook-heartbeat.js.map +1 -0
  144. package/dist/utils/intent-continuity.d.ts +21 -0
  145. package/dist/utils/intent-continuity.d.ts.map +1 -0
  146. package/dist/utils/intent-continuity.js +192 -0
  147. package/dist/utils/intent-continuity.js.map +1 -0
  148. package/dist/utils/messages.d.ts +1 -1
  149. package/dist/utils/messages.d.ts.map +1 -1
  150. package/dist/utils/messages.js +24 -21
  151. package/dist/utils/messages.js.map +1 -1
  152. package/dist/utils/runtime-companion.d.ts +137 -0
  153. package/dist/utils/runtime-companion.d.ts.map +1 -0
  154. package/dist/utils/runtime-companion.js +231 -0
  155. package/dist/utils/runtime-companion.js.map +1 -0
  156. package/dist/utils/runtime-connection.d.ts +46 -0
  157. package/dist/utils/runtime-connection.d.ts.map +1 -0
  158. package/dist/utils/runtime-connection.js +148 -0
  159. package/dist/utils/runtime-connection.js.map +1 -0
  160. package/dist/utils/runtime-evidence.d.ts +68 -0
  161. package/dist/utils/runtime-evidence.d.ts.map +1 -0
  162. package/dist/utils/runtime-evidence.js +248 -0
  163. package/dist/utils/runtime-evidence.js.map +1 -0
  164. package/dist/utils/runtime-live.d.ts +33 -0
  165. package/dist/utils/runtime-live.d.ts.map +1 -0
  166. package/dist/utils/runtime-live.js +361 -0
  167. package/dist/utils/runtime-live.js.map +1 -0
  168. package/dist/utils/runtime-outbox.d.ts +76 -0
  169. package/dist/utils/runtime-outbox.d.ts.map +1 -0
  170. package/dist/utils/runtime-outbox.js +410 -0
  171. package/dist/utils/runtime-outbox.js.map +1 -0
  172. package/dist/utils/runtime-receipt.d.ts +50 -0
  173. package/dist/utils/runtime-receipt.d.ts.map +1 -0
  174. package/dist/utils/runtime-receipt.js +223 -0
  175. package/dist/utils/runtime-receipt.js.map +1 -0
  176. package/dist/utils/state.d.ts +21 -0
  177. package/dist/utils/state.d.ts.map +1 -1
  178. package/dist/utils/state.js +30 -0
  179. package/dist/utils/state.js.map +1 -1
  180. package/dist/utils/structural-understanding.d.ts +334 -0
  181. package/dist/utils/structural-understanding.d.ts.map +1 -0
  182. package/dist/utils/structural-understanding.js +2316 -0
  183. package/dist/utils/structural-understanding.js.map +1 -0
  184. package/dist/utils/v0-governance.d.ts +197 -0
  185. package/dist/utils/v0-governance.d.ts.map +1 -0
  186. package/dist/utils/v0-governance.js +904 -0
  187. package/dist/utils/v0-governance.js.map +1 -0
  188. package/package.json +5 -5
@@ -0,0 +1,904 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLAUDE_GOVERNANCE_HOOKS = exports.MANIFEST_CANDIDATES = exports.CODEOWNERS_CANDIDATES = void 0;
4
+ exports.resolveRepoRoot = resolveRepoRoot;
5
+ exports.gitLsFiles = gitLsFiles;
6
+ exports.governanceConfigPath = governanceConfigPath;
7
+ exports.readRuntimeGovernanceConfig = readRuntimeGovernanceConfig;
8
+ exports.readModuleImports = readModuleImports;
9
+ exports.buildCurrentGovernanceProfile = buildCurrentGovernanceProfile;
10
+ exports.profilePath = profilePath;
11
+ exports.readGovernanceProfile = readGovernanceProfile;
12
+ exports.writeGovernanceProfile = writeGovernanceProfile;
13
+ exports.buildProfileFreshnessSignal = buildProfileFreshnessSignal;
14
+ exports.profileFreshnessActionForSession = profileFreshnessActionForSession;
15
+ exports.getProfileStaleness = getProfileStaleness;
16
+ exports.ensureFreshGovernanceProfile = ensureFreshGovernanceProfile;
17
+ exports.parseHookEntrypoint = parseHookEntrypoint;
18
+ exports.installClaudeGovernanceHooks = installClaudeGovernanceHooks;
19
+ exports.copilotHooksPath = copilotHooksPath;
20
+ exports.installCopilotGovernanceHooks = installCopilotGovernanceHooks;
21
+ exports.installClaudeMcpConfig = installClaudeMcpConfig;
22
+ exports.inspectClaudeActivation = inspectClaudeActivation;
23
+ exports.inspectCopilotActivation = inspectCopilotActivation;
24
+ const child_process_1 = require("child_process");
25
+ const fs_1 = require("fs");
26
+ const os_1 = require("os");
27
+ const path_1 = require("path");
28
+ const governance_runtime_1 = require("@neurcode-ai/governance-runtime");
29
+ exports.CODEOWNERS_CANDIDATES = ['CODEOWNERS', '.github/CODEOWNERS', 'docs/CODEOWNERS'];
30
+ exports.MANIFEST_CANDIDATES = ['package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml'];
31
+ const CLAUDE_MCP_ENTRY = Object.freeze({
32
+ command: 'npx',
33
+ args: ['-y', '@neurcode-ai/mcp-server'],
34
+ });
35
+ const CLAUDE_PRE_TOOL_MATCHER = 'Bash|Edit|Write|MultiEdit';
36
+ const COPILOT_HOOK_EVENTS = ['UserPromptSubmit', 'PreToolUse', 'Stop'];
37
+ exports.CLAUDE_GOVERNANCE_HOOKS = {
38
+ UserPromptSubmit: [
39
+ {
40
+ hooks: [
41
+ {
42
+ type: 'command',
43
+ command: 'neurcode session-hook start',
44
+ },
45
+ ],
46
+ },
47
+ ],
48
+ PreToolUse: [
49
+ {
50
+ matcher: CLAUDE_PRE_TOOL_MATCHER,
51
+ hooks: [
52
+ {
53
+ type: 'command',
54
+ command: 'neurcode session-hook check',
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ Stop: [
60
+ {
61
+ hooks: [
62
+ {
63
+ type: 'command',
64
+ command: 'neurcode session-hook finish',
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ };
70
+ const CLAUDE_EVENT_SESSION_HOOK = {
71
+ UserPromptSubmit: 'start',
72
+ PreToolUse: 'check',
73
+ Stop: 'finish',
74
+ };
75
+ const COPILOT_EVENT_SESSION_HOOK = {
76
+ UserPromptSubmit: 'start',
77
+ PreToolUse: 'check',
78
+ Stop: 'finish',
79
+ };
80
+ function shellQuote(value) {
81
+ return JSON.stringify(value);
82
+ }
83
+ function resolveCliEntrypoint() {
84
+ const candidates = [
85
+ (0, path_1.resolve)(__dirname, '..', 'index.js'),
86
+ (0, path_1.resolve)(__dirname, '..', '..', 'dist', 'index.js'),
87
+ ];
88
+ return candidates.find((candidate) => (0, fs_1.existsSync)(candidate)) ?? null;
89
+ }
90
+ function sessionHookCommand(subcommand) {
91
+ const entrypoint = resolveCliEntrypoint();
92
+ if (entrypoint)
93
+ return `node ${shellQuote(entrypoint)} session-hook ${subcommand}`;
94
+ return `neurcode session-hook ${subcommand}`;
95
+ }
96
+ function claudeGovernanceHooks() {
97
+ return {
98
+ UserPromptSubmit: [
99
+ {
100
+ hooks: [
101
+ {
102
+ type: 'command',
103
+ command: sessionHookCommand('start'),
104
+ },
105
+ ],
106
+ },
107
+ ],
108
+ PreToolUse: [
109
+ {
110
+ matcher: CLAUDE_PRE_TOOL_MATCHER,
111
+ hooks: [
112
+ {
113
+ type: 'command',
114
+ command: sessionHookCommand('check'),
115
+ },
116
+ ],
117
+ },
118
+ ],
119
+ Stop: [
120
+ {
121
+ hooks: [
122
+ {
123
+ type: 'command',
124
+ command: sessionHookCommand('finish'),
125
+ },
126
+ ],
127
+ },
128
+ ],
129
+ };
130
+ }
131
+ function copilotGovernanceHooks() {
132
+ return {
133
+ UserPromptSubmit: [
134
+ {
135
+ type: 'command',
136
+ command: sessionHookCommand('start'),
137
+ bash: sessionHookCommand('start'),
138
+ timeoutSec: 30,
139
+ },
140
+ ],
141
+ PreToolUse: [
142
+ {
143
+ type: 'command',
144
+ command: sessionHookCommand('check'),
145
+ bash: sessionHookCommand('check'),
146
+ timeoutSec: 30,
147
+ },
148
+ ],
149
+ Stop: [
150
+ {
151
+ type: 'command',
152
+ command: sessionHookCommand('finish'),
153
+ bash: sessionHookCommand('finish'),
154
+ timeoutSec: 30,
155
+ },
156
+ ],
157
+ };
158
+ }
159
+ function resolveRepoRoot(cwd = process.cwd()) {
160
+ const target = (0, path_1.resolve)(cwd);
161
+ try {
162
+ return (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
163
+ cwd: target,
164
+ encoding: 'utf8',
165
+ stdio: ['ignore', 'pipe', 'ignore'],
166
+ }).trim();
167
+ }
168
+ catch {
169
+ return target;
170
+ }
171
+ }
172
+ function gitLsFiles(cwd) {
173
+ try {
174
+ const out = (0, child_process_1.execSync)('git ls-files', {
175
+ cwd,
176
+ maxBuffer: 50 * 1024 * 1024,
177
+ encoding: 'utf8',
178
+ stdio: ['ignore', 'pipe', 'ignore'],
179
+ });
180
+ return out.split('\n').map((line) => line.trim()).filter(Boolean);
181
+ }
182
+ catch {
183
+ return [];
184
+ }
185
+ }
186
+ function readFirstExisting(cwd, candidates) {
187
+ for (const rel of candidates) {
188
+ const path = (0, path_1.join)(cwd, rel);
189
+ if (!(0, fs_1.existsSync)(path))
190
+ continue;
191
+ try {
192
+ return { path, content: (0, fs_1.readFileSync)(path, 'utf8') };
193
+ }
194
+ catch {
195
+ return { path, content: null };
196
+ }
197
+ }
198
+ return { path: null, content: null };
199
+ }
200
+ function prefixCodeownersContent(content, baseDir) {
201
+ const prefix = baseDir.replace(/\\/g, '/').replace(/\/$/, '');
202
+ if (!prefix || prefix === '.')
203
+ return content;
204
+ return content.split('\n').map((rawLine) => {
205
+ const commentIndex = rawLine.indexOf('#');
206
+ const body = commentIndex >= 0 ? rawLine.slice(0, commentIndex) : rawLine;
207
+ const comment = commentIndex >= 0 ? rawLine.slice(commentIndex) : '';
208
+ const trimmed = body.trim();
209
+ if (!trimmed)
210
+ return rawLine;
211
+ const parts = trimmed.split(/\s+/);
212
+ const pattern = parts[0];
213
+ const owners = parts.slice(1);
214
+ if (!pattern || owners.length === 0)
215
+ return rawLine;
216
+ const normalizedPattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
217
+ return `${prefix}/${normalizedPattern} ${owners.join(' ')}${comment ? ` ${comment}` : ''}`;
218
+ }).join('\n');
219
+ }
220
+ function readCodeownersBundle(cwd, paths) {
221
+ const chunks = [];
222
+ let firstPath = null;
223
+ const seen = new Set();
224
+ for (const candidate of exports.CODEOWNERS_CANDIDATES) {
225
+ seen.add(candidate);
226
+ const found = readFirstExisting(cwd, [candidate]);
227
+ if (found.content !== null) {
228
+ firstPath ??= found.path;
229
+ chunks.push(found.content);
230
+ }
231
+ }
232
+ for (const rel of paths) {
233
+ const normalized = rel.replace(/\\/g, '/');
234
+ if (seen.has(normalized))
235
+ continue;
236
+ if (!/(^|\/)CODEOWNERS$/i.test(normalized))
237
+ continue;
238
+ seen.add(normalized);
239
+ try {
240
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(cwd, normalized), 'utf8');
241
+ firstPath ??= (0, path_1.join)(cwd, normalized);
242
+ chunks.push(prefixCodeownersContent(content, (0, path_1.dirname)(normalized)));
243
+ }
244
+ catch {
245
+ // Ignore unreadable nested CODEOWNERS files. Sensitive boundary detection
246
+ // still provides a conservative approval gate.
247
+ }
248
+ }
249
+ return {
250
+ path: firstPath,
251
+ content: chunks.length > 0 ? chunks.join('\n') : null,
252
+ };
253
+ }
254
+ const EMPTY_GOVERNANCE_CONFIG = {
255
+ approvalRequiredGlobs: [],
256
+ sensitiveGlobs: [],
257
+ safeSupportGlobs: [],
258
+ ignoredGlobs: [],
259
+ planCoherence: 'warn',
260
+ architectureObligations: { mode: 'warn', ruleModes: {} },
261
+ };
262
+ function normalizeStringArray(value, field, errors) {
263
+ if (value === undefined)
264
+ return [];
265
+ if (!Array.isArray(value)) {
266
+ errors.push(`${field} must be an array of strings`);
267
+ return [];
268
+ }
269
+ const out = [];
270
+ for (const item of value) {
271
+ if (typeof item !== 'string') {
272
+ errors.push(`${field} entries must be strings`);
273
+ continue;
274
+ }
275
+ const normalized = item.trim().replace(/^\.\//, '').replace(/\/+$/, '');
276
+ if (normalized)
277
+ out.push(normalized);
278
+ }
279
+ return Array.from(new Set(out)).sort();
280
+ }
281
+ function normalizePlanCoherence(value, errors) {
282
+ if (value === undefined || value === null || value === '')
283
+ return 'warn';
284
+ if (value === 'off' || value === 'warn' || value === 'block')
285
+ return value;
286
+ errors.push('planCoherence must be one of: off, warn, block');
287
+ return 'warn';
288
+ }
289
+ function governanceConfigPath(repoRoot) {
290
+ return (0, path_1.join)(repoRoot, '.neurcode', 'governance.json');
291
+ }
292
+ function readRuntimeGovernanceConfig(repoRoot) {
293
+ const path = governanceConfigPath(repoRoot);
294
+ if (!(0, fs_1.existsSync)(path)) {
295
+ return {
296
+ path,
297
+ exists: false,
298
+ config: { ...EMPTY_GOVERNANCE_CONFIG },
299
+ };
300
+ }
301
+ let parsed;
302
+ try {
303
+ parsed = JSON.parse((0, fs_1.readFileSync)(path, 'utf8'));
304
+ }
305
+ catch (error) {
306
+ return {
307
+ path,
308
+ exists: true,
309
+ config: { ...EMPTY_GOVERNANCE_CONFIG },
310
+ error: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
311
+ };
312
+ }
313
+ const errors = [];
314
+ const config = {
315
+ approvalRequiredGlobs: normalizeStringArray(parsed.approvalRequiredGlobs, 'approvalRequiredGlobs', errors),
316
+ sensitiveGlobs: normalizeStringArray(parsed.sensitiveGlobs, 'sensitiveGlobs', errors),
317
+ safeSupportGlobs: normalizeStringArray(parsed.safeSupportGlobs, 'safeSupportGlobs', errors),
318
+ ignoredGlobs: normalizeStringArray(parsed.ignoredGlobs, 'ignoredGlobs', errors),
319
+ planCoherence: normalizePlanCoherence(parsed.planCoherence, errors),
320
+ architectureObligations: (0, governance_runtime_1.normalizeArchitectureObligationPolicy)(parsed.architectureObligations),
321
+ };
322
+ return {
323
+ path,
324
+ exists: true,
325
+ config,
326
+ error: errors.length > 0 ? errors.join('; ') : undefined,
327
+ };
328
+ }
329
+ // Architecture-graph import extraction is deterministic local analysis. To keep
330
+ // `neurcode profile` / session start fast on large repos we cap the number of
331
+ // files scanned and only read each file's head (imports live at the top). Only
332
+ // import *specifiers* are kept; raw source is read, scanned, and discarded.
333
+ const GRAPH_SOURCE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|mts|cts|py|pyi)$/i;
334
+ const MAX_GRAPH_FILES = 6000;
335
+ const MAX_GRAPH_FILE_BYTES = 2 * 1024 * 1024;
336
+ const MAX_GRAPH_HEAD_LINES = 400;
337
+ /**
338
+ * Read per-file import specifiers from the local working tree. Source-free
339
+ * output: returns only module specifier strings (e.g. "../billing/charge"),
340
+ * never file contents. Bounded + deterministic.
341
+ */
342
+ function readModuleImports(repoRoot, paths) {
343
+ const sourcePaths = paths
344
+ .filter((p) => GRAPH_SOURCE_EXT.test(p))
345
+ .sort()
346
+ .slice(0, MAX_GRAPH_FILES);
347
+ const records = [];
348
+ for (const rel of sourcePaths) {
349
+ const abs = (0, path_1.join)(repoRoot, rel);
350
+ let content;
351
+ try {
352
+ if (!(0, fs_1.existsSync)(abs))
353
+ continue;
354
+ const raw = (0, fs_1.readFileSync)(abs, 'utf8');
355
+ if (raw.length > MAX_GRAPH_FILE_BYTES)
356
+ continue;
357
+ content = raw.split('\n').slice(0, MAX_GRAPH_HEAD_LINES).join('\n');
358
+ }
359
+ catch {
360
+ continue; // unreadable / binary — skip
361
+ }
362
+ const specifiers = (0, governance_runtime_1.extractImportSpecifiers)(rel, content);
363
+ if (specifiers.length > 0)
364
+ records.push({ filePath: rel, specifiers });
365
+ }
366
+ return records;
367
+ }
368
+ function buildCurrentGovernanceProfile(repoRoot) {
369
+ const paths = gitLsFiles(repoRoot);
370
+ const codeowners = readCodeownersBundle(repoRoot, paths);
371
+ const manifest = readFirstExisting(repoRoot, exports.MANIFEST_CANDIDATES);
372
+ const governance = readRuntimeGovernanceConfig(repoRoot);
373
+ const imports = readModuleImports(repoRoot, paths);
374
+ return (0, governance_runtime_1.buildRepoGovernanceProfile)({
375
+ paths,
376
+ codeownersContent: codeowners.content,
377
+ manifestContent: manifest.content,
378
+ repoName: (0, path_1.basename)(repoRoot),
379
+ source: 'local',
380
+ runtimeConfig: governance.config,
381
+ imports,
382
+ });
383
+ }
384
+ function profilePath(repoRoot) {
385
+ return (0, path_1.join)(repoRoot, '.neurcode', 'profile.json');
386
+ }
387
+ function readGovernanceProfile(repoRoot) {
388
+ const path = profilePath(repoRoot);
389
+ if (!(0, fs_1.existsSync)(path))
390
+ return { profile: null, path };
391
+ try {
392
+ return { profile: JSON.parse((0, fs_1.readFileSync)(path, 'utf8')), path };
393
+ }
394
+ catch (error) {
395
+ return {
396
+ profile: null,
397
+ path,
398
+ error: error instanceof Error ? error.message : String(error),
399
+ };
400
+ }
401
+ }
402
+ function writeGovernanceProfile(repoRoot, profile) {
403
+ const path = profilePath(repoRoot);
404
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(path), { recursive: true });
405
+ (0, fs_1.writeFileSync)(path, JSON.stringify(profile, null, 2) + '\n', 'utf8');
406
+ return path;
407
+ }
408
+ function topologyHash(profile) {
409
+ const maybe = profile;
410
+ return typeof maybe?.topology?.hash === 'string' ? maybe.topology.hash : null;
411
+ }
412
+ function buildProfileFreshnessSignal(result, action = 'none') {
413
+ const currentTopologyHash = topologyHash(result.currentProfile) || result.currentProfile.profileHash;
414
+ const cachedTopology = topologyHash(result.cachedProfile);
415
+ const refreshed = 'refreshed' in result ? result.refreshed : false;
416
+ return {
417
+ status: result.status,
418
+ refreshed,
419
+ action: refreshed && action === 'none' ? 'auto_refreshed' : action,
420
+ checkedAt: new Date().toISOString(),
421
+ profilePath: result.profilePath,
422
+ reasons: [...result.reasons],
423
+ cachedProfileHash: result.cachedProfile?.profileHash,
424
+ cachedTopologyHash: cachedTopology || undefined,
425
+ currentProfileHash: result.currentProfile.profileHash,
426
+ currentTopologyHash,
427
+ trackedFileCount: result.currentProfile.topology.trackedFileCount,
428
+ };
429
+ }
430
+ function profileFreshnessActionForSession(result, sessionProfileHash) {
431
+ const currentProfileHash = result.currentProfile.profileHash;
432
+ if (!sessionProfileHash || sessionProfileHash === currentProfileHash) {
433
+ return 'none';
434
+ }
435
+ return 'session_restart_required';
436
+ }
437
+ function getProfileStaleness(repoRoot) {
438
+ const currentProfile = buildCurrentGovernanceProfile(repoRoot);
439
+ const cached = readGovernanceProfile(repoRoot);
440
+ const reasons = [];
441
+ if (cached.error) {
442
+ return {
443
+ status: 'unreadable',
444
+ profilePath: cached.path,
445
+ cachedProfile: null,
446
+ currentProfile,
447
+ reasons: [`profile could not be parsed: ${cached.error}`],
448
+ };
449
+ }
450
+ if (!cached.profile) {
451
+ return {
452
+ status: 'missing',
453
+ profilePath: cached.path,
454
+ cachedProfile: null,
455
+ currentProfile,
456
+ reasons: ['profile is missing'],
457
+ };
458
+ }
459
+ const cachedTopology = topologyHash(cached.profile);
460
+ const currentTopology = topologyHash(currentProfile);
461
+ if (!cachedTopology || !currentTopology) {
462
+ reasons.push('profile lacks topology fingerprint');
463
+ }
464
+ else if (cachedTopology !== currentTopology) {
465
+ reasons.push('repo topology changed since profile generation');
466
+ }
467
+ if (cached.profile.profileHash !== currentProfile.profileHash) {
468
+ reasons.push('profile hash differs from current repo metadata');
469
+ }
470
+ return {
471
+ status: reasons.length > 0 ? 'stale' : 'fresh',
472
+ profilePath: cached.path,
473
+ cachedProfile: cached.profile,
474
+ currentProfile,
475
+ reasons,
476
+ };
477
+ }
478
+ function ensureFreshGovernanceProfile(repoRoot, options = {}) {
479
+ const staleness = getProfileStaleness(repoRoot);
480
+ const shouldRefresh = options.force === true || staleness.status !== 'fresh';
481
+ const profile = shouldRefresh
482
+ ? staleness.currentProfile
483
+ : staleness.cachedProfile ?? staleness.currentProfile;
484
+ if (shouldRefresh) {
485
+ writeGovernanceProfile(repoRoot, profile);
486
+ }
487
+ return {
488
+ ...staleness,
489
+ profile,
490
+ refreshed: shouldRefresh,
491
+ };
492
+ }
493
+ function ensureDirOf(path) {
494
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(path), { recursive: true });
495
+ }
496
+ function parseJsonFile(path) {
497
+ if (!(0, fs_1.existsSync)(path))
498
+ return { data: {} };
499
+ const raw = (0, fs_1.readFileSync)(path, 'utf8').trim();
500
+ if (!raw)
501
+ return { data: {} };
502
+ try {
503
+ return { data: JSON.parse(raw) };
504
+ }
505
+ catch (error) {
506
+ return {
507
+ data: {},
508
+ error: error instanceof Error ? error.message : String(error),
509
+ };
510
+ }
511
+ }
512
+ function isPlainRecord(value) {
513
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
514
+ }
515
+ function expectedClaudeMcpEntry() {
516
+ return {
517
+ command: CLAUDE_MCP_ENTRY.command,
518
+ args: [...CLAUDE_MCP_ENTRY.args],
519
+ };
520
+ }
521
+ function normalizeClaudeMcpEntry(value) {
522
+ if (!isPlainRecord(value))
523
+ return null;
524
+ const command = typeof value.command === 'string' ? value.command : undefined;
525
+ const args = Array.isArray(value.args) && value.args.every((arg) => typeof arg === 'string')
526
+ ? [...value.args]
527
+ : undefined;
528
+ return { command, args };
529
+ }
530
+ function claudeMcpEntryStaleReasons(value) {
531
+ const entry = normalizeClaudeMcpEntry(value);
532
+ const expected = expectedClaudeMcpEntry();
533
+ if (!entry)
534
+ return ['mcpServers.neurcode must be an object'];
535
+ const reasons = [];
536
+ if (entry.command !== expected.command) {
537
+ reasons.push(`expected command "${expected.command}", found "${entry.command || 'missing'}"`);
538
+ }
539
+ if (!entry.args) {
540
+ reasons.push(`expected args ${JSON.stringify(expected.args)}, found missing/non-string args`);
541
+ }
542
+ else if (entry.args.length !== expected.args.length ||
543
+ entry.args.some((arg, index) => arg !== expected.args[index])) {
544
+ reasons.push(`expected args ${JSON.stringify(expected.args)}, found ${JSON.stringify(entry.args)}`);
545
+ }
546
+ return reasons;
547
+ }
548
+ function claudeMcpEntryIsCurrent(value) {
549
+ return claudeMcpEntryStaleReasons(value).length === 0;
550
+ }
551
+ function hookCommands(entry) {
552
+ if (!entry || typeof entry !== 'object')
553
+ return [];
554
+ const directCommand = entry['command'];
555
+ const directBash = entry['bash'];
556
+ const direct = [
557
+ typeof directCommand === 'string' ? directCommand : null,
558
+ typeof directBash === 'string' ? directBash : null,
559
+ ].filter((value) => Boolean(value));
560
+ if (direct.length > 0)
561
+ return direct;
562
+ const hooks = entry['hooks'];
563
+ if (!Array.isArray(hooks))
564
+ return [];
565
+ return hooks.flatMap((hook) => {
566
+ if (!hook || typeof hook !== 'object')
567
+ return [];
568
+ const command = hook['command'];
569
+ return typeof command === 'string' ? [command] : [];
570
+ });
571
+ }
572
+ /**
573
+ * Parse the node entrypoint path out of a pinned hook command.
574
+ * Pinned form: `node "<entrypoint>" session-hook <sub>` (entrypoint may be quoted or bare).
575
+ * Returns null for the legacy bare `neurcode session-hook <sub>` form (no entrypoint to verify).
576
+ */
577
+ function parseHookEntrypoint(command) {
578
+ const quoted = command.match(/^node\s+"([^"]+)"\s+session-hook\b/);
579
+ if (quoted)
580
+ return quoted[1];
581
+ const bare = command.match(/^node\s+(\S+)\s+session-hook\b/);
582
+ if (bare)
583
+ return bare[1];
584
+ return null;
585
+ }
586
+ /** First installed Neurcode session-hook command found for an event (current or stale). */
587
+ function installedSessionHookCommand(entries) {
588
+ for (const entry of entries) {
589
+ const match = hookCommands(entry).find(commandHasNeurcodeSessionHook);
590
+ if (match)
591
+ return match;
592
+ }
593
+ return null;
594
+ }
595
+ function commandHasNeurcodeSessionHook(command) {
596
+ return /\bsession-hook\s+(start|check|finish|approve)\b/.test(command);
597
+ }
598
+ function entryHasAnyNeurcodeSessionHook(entry) {
599
+ return hookCommands(entry).some(commandHasNeurcodeSessionHook);
600
+ }
601
+ function expectedSessionHookCommand(event) {
602
+ return sessionHookCommand(CLAUDE_EVENT_SESSION_HOOK[event]);
603
+ }
604
+ function expectedCopilotSessionHookCommand(event) {
605
+ return sessionHookCommand(COPILOT_EVENT_SESSION_HOOK[event]);
606
+ }
607
+ function entryHasCurrentNeurcodeSessionHook(entry, event) {
608
+ const expected = expectedSessionHookCommand(event);
609
+ if (!hookCommands(entry).some((command) => command === expected))
610
+ return false;
611
+ if (event !== 'PreToolUse')
612
+ return true;
613
+ const matcher = typeof entry['matcher'] === 'string'
614
+ ? String(entry['matcher'])
615
+ : '';
616
+ return ['Bash', 'Edit', 'Write', 'MultiEdit'].every((tool) => matcher.split('|').map((part) => part.trim()).includes(tool));
617
+ }
618
+ function entryHasCurrentCopilotSessionHook(entry, event) {
619
+ const expected = expectedCopilotSessionHookCommand(event);
620
+ return hookCommands(entry).some((command) => command === expected);
621
+ }
622
+ function staleNeurcodeHookCommands(entries, event) {
623
+ const expected = expectedSessionHookCommand(event);
624
+ const stale = [];
625
+ for (const entry of entries) {
626
+ for (const command of hookCommands(entry).filter(commandHasNeurcodeSessionHook)) {
627
+ if (command !== expected) {
628
+ stale.push(command);
629
+ }
630
+ else if (event === 'PreToolUse' && !entryHasCurrentNeurcodeSessionHook(entry, event)) {
631
+ stale.push(`${command} (matcher missing Bash)`);
632
+ }
633
+ }
634
+ }
635
+ return stale;
636
+ }
637
+ function staleCopilotHookCommands(entries, event) {
638
+ const expected = expectedCopilotSessionHookCommand(event);
639
+ const stale = [];
640
+ for (const entry of entries) {
641
+ for (const command of hookCommands(entry).filter(commandHasNeurcodeSessionHook)) {
642
+ if (command !== expected)
643
+ stale.push(command);
644
+ }
645
+ }
646
+ return stale;
647
+ }
648
+ function installClaudeGovernanceHooks(repoRoot, options = {}) {
649
+ const settingsPath = (0, path_1.join)(repoRoot, '.claude', 'settings.json');
650
+ const parsed = parseJsonFile(settingsPath);
651
+ if (parsed.error) {
652
+ throw new Error(`Refusing to update ${settingsPath}: invalid JSON (${parsed.error})`);
653
+ }
654
+ const existing = parsed.data;
655
+ const hooks = (existing.hooks && typeof existing.hooks === 'object' && !Array.isArray(existing.hooks)
656
+ ? existing.hooks
657
+ : {});
658
+ const added = [];
659
+ const preserved = [];
660
+ const repaired = [];
661
+ for (const [event, entries] of Object.entries(claudeGovernanceHooks())) {
662
+ const hookEvent = event;
663
+ const current = Array.isArray(hooks[event]) ? hooks[event] : [];
664
+ const alreadyCurrent = current.some((entry) => entryHasCurrentNeurcodeSessionHook(entry, hookEvent));
665
+ if (alreadyCurrent && !options.force) {
666
+ preserved.push(`hooks.${event}`);
667
+ continue;
668
+ }
669
+ const hadPriorNeurcodeHook = current.some((entry) => entryHasAnyNeurcodeSessionHook(entry));
670
+ const filtered = current.filter((entry) => !entryHasAnyNeurcodeSessionHook(entry));
671
+ hooks[event] = [...filtered, ...entries];
672
+ if (hadPriorNeurcodeHook)
673
+ repaired.push(`hooks.${event}`);
674
+ else
675
+ added.push(`hooks.${event}`);
676
+ }
677
+ if (!options.dryRun) {
678
+ ensureDirOf(settingsPath);
679
+ (0, fs_1.writeFileSync)(settingsPath, JSON.stringify({ ...existing, hooks }, null, 2) + '\n', 'utf8');
680
+ }
681
+ return {
682
+ settingsPath,
683
+ added,
684
+ preserved,
685
+ repaired,
686
+ restartRequired: added.length + repaired.length > 0,
687
+ };
688
+ }
689
+ function copilotHooksPath(repoRoot) {
690
+ return (0, path_1.join)(repoRoot, '.github', 'hooks', 'neurcode.json');
691
+ }
692
+ function installCopilotGovernanceHooks(repoRoot, options = {}) {
693
+ const hooksPath = copilotHooksPath(repoRoot);
694
+ const parsed = parseJsonFile(hooksPath);
695
+ if (parsed.error) {
696
+ throw new Error(`Refusing to update ${hooksPath}: invalid JSON (${parsed.error})`);
697
+ }
698
+ const existing = parsed.data;
699
+ const hooks = (existing.hooks && typeof existing.hooks === 'object' && !Array.isArray(existing.hooks)
700
+ ? existing.hooks
701
+ : {});
702
+ const added = [];
703
+ const preserved = [];
704
+ const repaired = [];
705
+ for (const [event, entries] of Object.entries(copilotGovernanceHooks())) {
706
+ const hookEvent = event;
707
+ const current = Array.isArray(hooks[event]) ? hooks[event] : [];
708
+ const alreadyCurrent = current.some((entry) => entryHasCurrentCopilotSessionHook(entry, hookEvent));
709
+ if (alreadyCurrent && !options.force) {
710
+ preserved.push(`hooks.${event}`);
711
+ continue;
712
+ }
713
+ const hadPriorNeurcodeHook = current.some((entry) => entryHasAnyNeurcodeSessionHook(entry));
714
+ const filtered = current.filter((entry) => !entryHasAnyNeurcodeSessionHook(entry));
715
+ hooks[event] = [...filtered, ...entries];
716
+ if (hadPriorNeurcodeHook)
717
+ repaired.push(`hooks.${event}`);
718
+ else
719
+ added.push(`hooks.${event}`);
720
+ }
721
+ if (!options.dryRun) {
722
+ ensureDirOf(hooksPath);
723
+ (0, fs_1.writeFileSync)(hooksPath, JSON.stringify({ version: 1, ...existing, hooks }, null, 2) + '\n', 'utf8');
724
+ }
725
+ return {
726
+ hooksPath,
727
+ added,
728
+ preserved,
729
+ repaired,
730
+ restartRequired: added.length + repaired.length > 0,
731
+ };
732
+ }
733
+ function installClaudeMcpConfig(options = {}) {
734
+ const configPath = (0, path_1.join)(options.homeDir || (0, os_1.homedir)(), '.claude.json');
735
+ const parsed = parseJsonFile(configPath);
736
+ if (parsed.error) {
737
+ throw new Error(`Refusing to update ${configPath}: invalid JSON (${parsed.error})`);
738
+ }
739
+ const existing = parsed.data;
740
+ const servers = (existing.mcpServers && typeof existing.mcpServers === 'object' && !Array.isArray(existing.mcpServers)
741
+ ? existing.mcpServers
742
+ : {});
743
+ const added = [];
744
+ const preserved = [];
745
+ const repaired = [];
746
+ for (const key of Object.keys(servers)) {
747
+ if (key !== 'neurcode')
748
+ preserved.push(`mcpServers.${key}`);
749
+ }
750
+ const hasNeurcode = Object.prototype.hasOwnProperty.call(servers, 'neurcode');
751
+ const current = hasNeurcode && claudeMcpEntryIsCurrent(servers.neurcode);
752
+ if (hasNeurcode && current && !options.force) {
753
+ preserved.push('mcpServers.neurcode');
754
+ }
755
+ else {
756
+ servers.neurcode = expectedClaudeMcpEntry();
757
+ if (hasNeurcode)
758
+ repaired.push('mcpServers.neurcode');
759
+ else
760
+ added.push('mcpServers.neurcode');
761
+ }
762
+ if (!options.dryRun) {
763
+ ensureDirOf(configPath);
764
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify({ ...existing, mcpServers: servers }, null, 2) + '\n', 'utf8');
765
+ }
766
+ return {
767
+ configPath,
768
+ added,
769
+ preserved,
770
+ repaired,
771
+ restartRequired: added.length + repaired.length > 0,
772
+ };
773
+ }
774
+ function inspectClaudeActivation(repoRoot, options = {}) {
775
+ const settingsPath = (0, path_1.join)(repoRoot, '.claude', 'settings.json');
776
+ const eventStatus = {
777
+ UserPromptSubmit: false,
778
+ PreToolUse: false,
779
+ Stop: false,
780
+ };
781
+ const expectedCommands = {
782
+ UserPromptSubmit: expectedSessionHookCommand('UserPromptSubmit'),
783
+ PreToolUse: expectedSessionHookCommand('PreToolUse'),
784
+ Stop: expectedSessionHookCommand('Stop'),
785
+ };
786
+ const staleCommands = [];
787
+ let hookError;
788
+ let installedCommand = null;
789
+ const settings = parseJsonFile(settingsPath);
790
+ if (settings.error) {
791
+ hookError = settings.error;
792
+ }
793
+ else {
794
+ const hooks = (settings.data.hooks && typeof settings.data.hooks === 'object' && !Array.isArray(settings.data.hooks)
795
+ ? settings.data.hooks
796
+ : {});
797
+ for (const event of Object.keys(eventStatus)) {
798
+ const current = Array.isArray(hooks[event]) ? hooks[event] : [];
799
+ eventStatus[event] = current.some((entry) => entryHasCurrentNeurcodeSessionHook(entry, event));
800
+ staleCommands.push(...staleNeurcodeHookCommands(current, event));
801
+ }
802
+ // PreToolUse (the write-blocking hook) is the representative installed command.
803
+ const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
804
+ installedCommand = installedSessionHookCommand(preToolUse);
805
+ }
806
+ const entrypoint = installedCommand ? parseHookEntrypoint(installedCommand) : null;
807
+ const entrypointExists = entrypoint ? (0, fs_1.existsSync)(entrypoint) : null;
808
+ const entrypointPortable = entrypoint ? !(0, path_1.isAbsolute)(entrypoint) : null;
809
+ const configPath = (0, path_1.join)(options.homeDir || (0, os_1.homedir)(), '.claude.json');
810
+ const mcp = parseJsonFile(configPath);
811
+ let mcpConfigured = false;
812
+ let mcpPresent = false;
813
+ let mcpStale = false;
814
+ let mcpEntry = null;
815
+ let mcpStaleReasons = [];
816
+ if (!mcp.error) {
817
+ const servers = (mcp.data.mcpServers && typeof mcp.data.mcpServers === 'object' && !Array.isArray(mcp.data.mcpServers)
818
+ ? mcp.data.mcpServers
819
+ : {});
820
+ mcpPresent = Object.prototype.hasOwnProperty.call(servers, 'neurcode');
821
+ if (mcpPresent) {
822
+ mcpEntry = normalizeClaudeMcpEntry(servers.neurcode);
823
+ mcpStaleReasons = claudeMcpEntryStaleReasons(servers.neurcode);
824
+ mcpStale = mcpStaleReasons.length > 0;
825
+ mcpConfigured = !mcpStale;
826
+ }
827
+ }
828
+ return {
829
+ hooks: {
830
+ installed: Object.values(eventStatus).every(Boolean) && staleCommands.length === 0,
831
+ settingsPath,
832
+ events: eventStatus,
833
+ expectedCommands,
834
+ stale: staleCommands.length > 0,
835
+ staleCommands: Array.from(new Set(staleCommands)).sort(),
836
+ installedCommand,
837
+ entrypoint,
838
+ entrypointExists,
839
+ entrypointPortable,
840
+ error: hookError,
841
+ },
842
+ mcp: {
843
+ configured: mcpConfigured,
844
+ present: mcpPresent,
845
+ stale: mcpStale,
846
+ configPath,
847
+ entry: mcpEntry,
848
+ expectedEntry: expectedClaudeMcpEntry(),
849
+ staleReasons: mcpStaleReasons,
850
+ error: mcp.error,
851
+ },
852
+ };
853
+ }
854
+ function inspectCopilotActivation(repoRoot) {
855
+ const hooksPath = copilotHooksPath(repoRoot);
856
+ const eventStatus = {
857
+ UserPromptSubmit: false,
858
+ PreToolUse: false,
859
+ Stop: false,
860
+ };
861
+ const expectedCommands = {
862
+ UserPromptSubmit: expectedCopilotSessionHookCommand('UserPromptSubmit'),
863
+ PreToolUse: expectedCopilotSessionHookCommand('PreToolUse'),
864
+ Stop: expectedCopilotSessionHookCommand('Stop'),
865
+ };
866
+ const staleCommands = [];
867
+ let hookError;
868
+ let installedCommand = null;
869
+ const settings = parseJsonFile(hooksPath);
870
+ if (settings.error) {
871
+ hookError = settings.error;
872
+ }
873
+ else {
874
+ const hooks = (settings.data.hooks && typeof settings.data.hooks === 'object' && !Array.isArray(settings.data.hooks)
875
+ ? settings.data.hooks
876
+ : {});
877
+ for (const event of COPILOT_HOOK_EVENTS) {
878
+ const current = Array.isArray(hooks[event]) ? hooks[event] : [];
879
+ eventStatus[event] = current.some((entry) => entryHasCurrentCopilotSessionHook(entry, event));
880
+ staleCommands.push(...staleCopilotHookCommands(current, event));
881
+ }
882
+ const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
883
+ installedCommand = installedSessionHookCommand(preToolUse);
884
+ }
885
+ const entrypoint = installedCommand ? parseHookEntrypoint(installedCommand) : null;
886
+ const entrypointExists = entrypoint ? (0, fs_1.existsSync)(entrypoint) : null;
887
+ const entrypointPortable = entrypoint ? !(0, path_1.isAbsolute)(entrypoint) : null;
888
+ return {
889
+ hooks: {
890
+ installed: Object.values(eventStatus).every(Boolean) && staleCommands.length === 0,
891
+ hooksPath,
892
+ events: eventStatus,
893
+ expectedCommands,
894
+ stale: staleCommands.length > 0,
895
+ staleCommands: Array.from(new Set(staleCommands)).sort(),
896
+ installedCommand,
897
+ entrypoint,
898
+ entrypointExists,
899
+ entrypointPortable,
900
+ error: hookError,
901
+ },
902
+ };
903
+ }
904
+ //# sourceMappingURL=v0-governance.js.map