jorgex-stack 1.9.62 → 1.9.63

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.
@@ -1,554 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createLocalCapabilityReport,
4
- detectClaudeCode,
5
- hasManagedMarkdownSection,
6
- isCanonicalMcpServerEnabled,
7
- loadCanonicalDefaults,
8
- removeNativeHooks,
9
- removeSystemPromptSections,
10
- resolveAgentModel,
11
- upsertJson,
12
- upsertNativeHooks
13
- } from "./chunk-66WVKOU4.js";
14
- import {
15
- readTextIfExists,
16
- registerOfficialSetupVerifier
17
- } from "./chunk-B76ZJXXD.js";
18
- import {
19
- stackRoot
20
- } from "./chunk-NQZVJFV2.js";
21
-
22
- // src/adapters/claude-code.ts
23
- import path from "path";
24
- import fs from "fs";
25
- import { isDeepStrictEqual } from "util";
26
- function yamlString(value) {
27
- return JSON.stringify(value);
28
- }
29
- function isRecord(value) {
30
- return value !== null && typeof value === "object" && !Array.isArray(value);
31
- }
32
- function readMcpConfig(file) {
33
- let content;
34
- try {
35
- content = fs.readFileSync(file, "utf8");
36
- } catch (error) {
37
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
38
- if (code === "ENOENT") return null;
39
- throw new Error(`Claude Code: no se pudo leer la configuraci\xF3n MCP en ${file} (${code}).`);
40
- }
41
- if (content.trim() !== "") {
42
- try {
43
- if (!isRecord(JSON.parse(content))) throw new Error();
44
- } catch {
45
- throw new Error(`Claude Code: la configuraci\xF3n MCP en ${file} debe contener un objeto JSON v\xE1lido.`);
46
- }
47
- }
48
- return content;
49
- }
50
- function isCompatibleContext7Server(server, value) {
51
- if (server.transport !== "http" || typeof server.url !== "string" || !isRecord(value)) return false;
52
- return value.type === "http" && value.url === server.url;
53
- }
54
- function canonicalContext7Server(server) {
55
- const headers = {};
56
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
57
- headers[key] = /^\$\{(\w+)\}$/.test(raw) ? "" : raw;
58
- }
59
- return {
60
- type: "http",
61
- url: server.url,
62
- ...Object.keys(headers).length > 0 ? { headers } : {}
63
- };
64
- }
65
- function isCanonicalContext7Server(server, value) {
66
- return isCompatibleContext7Server(server, value) && isDeepStrictEqual(value, canonicalContext7Server(server));
67
- }
68
- function assertCompatibleContext7(server, value) {
69
- if (value !== void 0 && !isCompatibleContext7Server(server, value)) {
70
- throw new Error("Claude Code: MCP 'context7' entra en conflicto con una definici\xF3n existente (endpoint o tipo incompatible). Conserva la configuraci\xF3n y corrige el conflicto antes de reintentar.");
71
- }
72
- }
73
- function hasClaudeManualApproval(configDir) {
74
- const content = readTextIfExists(path.join(configDir, "settings.json"));
75
- if (content === null) return false;
76
- try {
77
- const root = JSON.parse(content);
78
- const permissions = isRecord(root) ? root.permissions : void 0;
79
- const expected = loadCanonicalDefaults(stackRoot())["claude-code"]?.["permissions"];
80
- return isRecord(permissions) && expected !== void 0 && isDeepStrictEqual(permissions, expected);
81
- } catch {
82
- return false;
83
- }
84
- }
85
- function toolsFor(agent) {
86
- if (agent.name === "engram") return null;
87
- if (!agent.readonly) return null;
88
- const tools = ["Read", "Grep", "Glob", "Skill"];
89
- if (agent.bash !== "none") tools.push("Bash");
90
- return tools.join(", ");
91
- }
92
- function hasEngramPlugin(configDir) {
93
- if (fs.existsSync(path.join(configDir, "plugins", "marketplaces", "engram"))) return true;
94
- const registry = readTextIfExists(path.join(configDir, "plugins", "installed_plugins.json"));
95
- if (registry === null) return false;
96
- try {
97
- const parsed = JSON.parse(registry);
98
- const keys = [...Object.keys(parsed), ...Object.keys(parsed["plugins"] ?? {})];
99
- return keys.some((k) => k === "engram" || k.startsWith("engram@"));
100
- } catch {
101
- return false;
102
- }
103
- }
104
- function isManagedOptionalStdioServer(server, value) {
105
- if (!server.optional || server.transport !== "stdio" || value === null || typeof value !== "object" || Array.isArray(value)) {
106
- return false;
107
- }
108
- const current = value;
109
- const expectedArgs = server.args ?? [];
110
- return Object.keys(current).length === 3 && current.type === "stdio" && current.command === server.command && Array.isArray(current.args) && current.args.length === expectedArgs.length && current.args.every((arg, index) => arg === expectedArgs[index]);
111
- }
112
- var claudeCodeAdapter = {
113
- id: "claude-code",
114
- name: "Claude Code",
115
- detect: detectClaudeCode,
116
- reportCapabilities(configDir) {
117
- const prompt = readTextIfExists(path.join(configDir, "CLAUDE.md"));
118
- return createLocalCapabilityReport("claude-code", [
119
- ...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
120
- id: "policy-guidance",
121
- state: "prompt-only",
122
- reason: "The managed policy prompt is advisory and cannot enforce the policy",
123
- evidence: { source: "jorgex-stack-system-prompt", version: "1" }
124
- }] : [],
125
- ...hasClaudeManualApproval(configDir) ? [{
126
- id: "tool-approval",
127
- state: "manual",
128
- reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
129
- evidence: { source: "jorgex-stack-claude-approval-policy", version: "1" }
130
- }] : []
131
- ]);
132
- },
133
- paths(configDir) {
134
- return {
135
- systemPromptFile: path.join(configDir, "CLAUDE.md"),
136
- agentsDir: path.join(configDir, "agents"),
137
- skillsDir: path.join(configDir, "skills"),
138
- commandsDir: path.join(configDir, "commands"),
139
- pluginsDir: null,
140
- scriptsDir: path.join(configDir, "scripts"),
141
- outputStylesDir: path.join(configDir, "output-styles"),
142
- profilesDir: null
143
- };
144
- },
145
- renderAgent(agent, models) {
146
- if (agent.mode === "primary") {
147
- const title = agent.name.charAt(0).toUpperCase() + agent.name.slice(1);
148
- const style = `---
149
- name: ${title}
150
- description: ${yamlString(agent.description)}
151
- keep-coding-instructions: true
152
- ---
153
- ${agent.body}`;
154
- return [{ file: `${agent.name}.md`, content: style, kind: "output-style" }];
155
- }
156
- const lines = [`name: ${agent.name}`, `description: ${yamlString(agent.description)}`];
157
- const tools = toolsFor(agent);
158
- if (tools !== null) lines.push(`tools: ${tools}`);
159
- lines.push(`model: ${resolveAgentModel(models, agent.name, agent.tier).model}`);
160
- return [
161
- {
162
- file: `${agent.name}.md`,
163
- content: `---
164
- ${lines.join("\n")}
165
- ---
166
- ${agent.body}`,
167
- kind: "agent"
168
- }
169
- ];
170
- },
171
- renderCommand(file, content) {
172
- return { file, content: content.replace(/\{\{input\}\}/g, "$ARGUMENTS") };
173
- },
174
- planHooks(canonical, ctx) {
175
- const actions = [];
176
- const { scriptsDir } = this.paths(ctx.configDir);
177
- const original = readTextIfExists(path.join(ctx.configDir, "settings.json"));
178
- const contentSource = original === null || original.trim() === "" ? null : original;
179
- const settingsFile = path.join(ctx.configDir, "settings.json");
180
- let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
181
- const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
182
- const canonicalPermissions = defaults?.["permissions"];
183
- if (contentSource === null) {
184
- if (canonicalPermissions !== void 0) {
185
- content = upsertJson(content, (root) => {
186
- if (root["permissions"] === void 0) {
187
- root["permissions"] = canonicalPermissions;
188
- ctx.warnings.push(
189
- "Claude Code: fresh config enables read-anywhere via Read/Grep/Glob allow rules; shell, writes and web egress remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
190
- );
191
- }
192
- });
193
- }
194
- } else if (canonicalPermissions !== void 0) {
195
- content = upsertJson(content, (root) => {
196
- if (isDeepStrictEqual(root["permissions"], canonicalPermissions)) return;
197
- if (ctx.upgradePermissions === true) {
198
- root["permissions"] = canonicalPermissions;
199
- } else {
200
- ctx.warnings.push(
201
- "Claude Code: permissions block differs from the stack default and was left untouched; re-run with --upgrade-permissions to replace it (a backup is created first), or edit it by hand. Overwriting discards your own permission changes, including any extra hardenings."
202
- );
203
- }
204
- });
205
- }
206
- actions.push({ kind: "write", target: settingsFile, content });
207
- const scriptsSource = path.join(ctx.stackDir, "scripts");
208
- if (fs.existsSync(scriptsSource)) {
209
- for (const f of fs.readdirSync(scriptsSource)) {
210
- actions.push({ kind: "copy", source: path.join(scriptsSource, f), target: path.join(scriptsDir, f) });
211
- }
212
- }
213
- return actions;
214
- },
215
- planMainConfig(canonical, ctx) {
216
- const file = path.join(path.dirname(ctx.configDir), `${path.basename(ctx.configDir)}.json`);
217
- const mcpOwnership = [];
218
- const content = upsertJson(readMcpConfig(file), (root) => {
219
- const rawServers = root["mcpServers"];
220
- if (rawServers !== void 0 && !isRecord(rawServers)) {
221
- throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar sync.");
222
- }
223
- const existingServers = rawServers;
224
- const context7 = canonical.servers.context7;
225
- if (context7 !== void 0) assertCompatibleContext7(context7, existingServers?.["context7"]);
226
- const servers = root["mcpServers"] ??= {};
227
- for (const [name, server] of Object.entries(canonical.servers)) {
228
- if (name === "engram" && hasEngramPlugin(ctx.configDir)) {
229
- if (ctx.ownedMcpServers?.has(name) === true) {
230
- mcpOwnership.push({ server: name, owned: false });
231
- }
232
- ctx.warnings.push(
233
- "Claude Code: plugin oficial de Engram activo \u2014 sus hooks y skill no incluyen el MCP; el setup registra un MCP user separado que Stack no posee ni muta."
234
- );
235
- continue;
236
- }
237
- const existing = servers[name];
238
- const owned = ctx.ownedMcpServers?.has(name) === true;
239
- if (name === "context7" && existing !== void 0) {
240
- if (owned && !isCanonicalContext7Server(server, existing)) {
241
- mcpOwnership.push({ server: name, owned: false });
242
- }
243
- continue;
244
- }
245
- if (!isCanonicalMcpServerEnabled(name, server, ctx.enabledMcpServers)) {
246
- if (owned) {
247
- if (isManagedOptionalStdioServer(server, existing)) delete servers[name];
248
- mcpOwnership.push({ server: name, owned: false });
249
- }
250
- continue;
251
- }
252
- if (server.optional && existing !== void 0) {
253
- if (!owned || !isManagedOptionalStdioServer(server, existing)) {
254
- if (owned) mcpOwnership.push({ server: name, owned: false });
255
- ctx.warnings.push(`Claude Code: MCP opcional '${name}' ya pertenece a la configuraci\xF3n del usuario; se conserva.`);
256
- continue;
257
- }
258
- }
259
- if (server.transport === "stdio") {
260
- if (server.command === "{{ENGRAM_BIN}}" && ctx.engramBin === null) {
261
- ctx.warnings.push(
262
- "Engram no detectado: el MCP 'engram' no se registra. Inst\xE1lalo (github.com/Gentleman-Programming/engram) y re-ejecuta sync."
263
- );
264
- continue;
265
- }
266
- const command = server.command === "{{ENGRAM_BIN}}" ? ctx.engramBin : server.command;
267
- servers[name] = { type: "stdio", command, args: server.args ?? [] };
268
- if (server.optional && existing === void 0 && !owned) mcpOwnership.push({ server: name, owned: true });
269
- } else {
270
- const previous = servers[name];
271
- const headers = {};
272
- for (const [key, raw] of Object.entries(server.headers ?? {})) {
273
- const envRef = /^\$\{(\w+)\}$/.exec(raw);
274
- headers[key] = previous?.headers?.[key] || (envRef ? "" : raw);
275
- }
276
- servers[name] = {
277
- type: "http",
278
- url: server.url,
279
- ...Object.keys(headers).length > 0 ? { headers } : {}
280
- };
281
- if (name === "context7" && existing === void 0 && !owned) {
282
- mcpOwnership.push({ server: name, owned: true });
283
- }
284
- }
285
- }
286
- });
287
- return [{ kind: "write", target: file, content, ...mcpOwnership.length > 0 ? { mcpOwnership } : {} }];
288
- },
289
- planUnmerge(mcp, hooks, ctx) {
290
- const actions = [];
291
- const { systemPromptFile } = this.paths(ctx.configDir);
292
- const prompt = readTextIfExists(systemPromptFile);
293
- if (prompt !== null) {
294
- const content = removeSystemPromptSections(prompt);
295
- actions.push({ kind: "write", target: systemPromptFile, content });
296
- }
297
- const settingsFile = path.join(ctx.configDir, "settings.json");
298
- const settings = readTextIfExists(settingsFile);
299
- if (settings !== null) {
300
- const content = removeNativeHooks(settings, hooks);
301
- if (content !== null) {
302
- actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
303
- }
304
- }
305
- const mainFile = path.join(path.dirname(ctx.configDir), `${path.basename(ctx.configDir)}.json`);
306
- const main = readMcpConfig(mainFile);
307
- if (main !== null) {
308
- const mcpOwnership = [];
309
- const content = upsertJson(main, (root) => {
310
- const rawServers = root["mcpServers"];
311
- if (rawServers === void 0) return;
312
- if (!isRecord(rawServers)) {
313
- throw new Error("Claude Code: la clave 'mcpServers' debe ser un objeto; corr\xEDgela antes de reintentar uninstall.");
314
- }
315
- const servers = rawServers;
316
- for (const [name, server] of Object.entries(mcp.servers)) {
317
- if (name === "context7") {
318
- const canonical = isCanonicalContext7Server(server, servers[name]);
319
- const owned = ctx.ownedMcpServers?.has(name) === true;
320
- if (owned) {
321
- if (canonical) delete servers[name];
322
- mcpOwnership.push({ server: name, owned: false });
323
- }
324
- continue;
325
- }
326
- if (name === "engram" && hasEngramPlugin(ctx.configDir)) {
327
- ctx.warnings.push(
328
- "Claude Code: MCP 'engram' oficial (plugin) se conserva; usa el desinstalador oficial de Engram para retirarlo."
329
- );
330
- continue;
331
- }
332
- if (!server.optional) {
333
- delete servers[name];
334
- continue;
335
- }
336
- if (ctx.ownedMcpServers?.has(name) === true) {
337
- if (isManagedOptionalStdioServer(server, servers[name])) delete servers[name];
338
- mcpOwnership.push({ server: name, owned: false });
339
- }
340
- }
341
- if (Object.keys(servers).length === 0) delete root["mcpServers"];
342
- });
343
- actions.push({ kind: "write", target: mainFile, content, ...mcpOwnership.length > 0 ? { mcpOwnership } : {} });
344
- }
345
- return actions;
346
- }
347
- };
348
- function errnoCodeOf(error) {
349
- return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
350
- }
351
- function resolveStrictInstallPath(configDir) {
352
- const registryFile = path.join(configDir, "plugins", "installed_plugins.json");
353
- let raw;
354
- try {
355
- raw = fs.readFileSync(registryFile, "utf8");
356
- } catch (error) {
357
- const code = errnoCodeOf(error);
358
- if (code === "ENOENT") {
359
- return { ok: false, detail: `installPath:missing (registry ausente: ${registryFile})` };
360
- }
361
- return { ok: false, detail: `installPath:unreadable (${registryFile}: ${code}, ilegible)` };
362
- }
363
- let parsed;
364
- try {
365
- parsed = JSON.parse(raw);
366
- } catch {
367
- return { ok: false, detail: `installPath:malformed (${registryFile}: JSON inv\xE1lido, ilegible)` };
368
- }
369
- if (!isRecord(parsed) || parsed["version"] !== 2) {
370
- return { ok: false, detail: `installPath:malformed (${registryFile}: registry version 2 requerida, inv\xE1lido)` };
371
- }
372
- const plugins = parsed["plugins"];
373
- if (!isRecord(plugins)) return { ok: false, detail: `plugin:malformed (plugins no es objeto en ${registryFile})` };
374
- const entry = plugins["engram@engram"];
375
- if (!Array.isArray(entry) || entry.length === 0 || !isRecord(entry[0])) {
376
- return { ok: false, detail: `plugin:missing (engram@engram ausente en ${registryFile})` };
377
- }
378
- const first = entry[0];
379
- if (first["scope"] !== "user") return { ok: false, detail: `plugin:disabled (scope user requerido en ${registryFile})` };
380
- const installPath = first["installPath"];
381
- if (typeof installPath !== "string" || installPath === "" || !path.isAbsolute(installPath)) {
382
- return { ok: false, detail: `installPath:missing (absoluto requerido en ${registryFile})` };
383
- }
384
- const expectedRoot = path.resolve(path.join(configDir, "plugins", "cache", "engram"));
385
- const resolved = path.resolve(installPath);
386
- if (resolved !== expectedRoot && !resolved.startsWith(expectedRoot + path.sep)) {
387
- return { ok: false, detail: `installPath:foreign (${resolved} fuera de ${expectedRoot})` };
388
- }
389
- const configResolved = path.resolve(configDir);
390
- let cursor = resolved;
391
- while (cursor !== null && cursor !== configResolved) {
392
- try {
393
- if (fs.lstatSync(cursor).isSymbolicLink()) {
394
- return { ok: false, detail: `installPath:symlink rechazado (${cursor} es un alias; no se sigue)` };
395
- }
396
- } catch (error) {
397
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
398
- if (code !== "ENOENT") {
399
- return { ok: false, detail: `installPath:ilegible (${cursor}: ${code}, no se puede descartar alias)` };
400
- }
401
- }
402
- const parent = path.dirname(cursor);
403
- if (parent === cursor) break;
404
- cursor = parent === configResolved || parent.startsWith(configResolved + path.sep) ? parent : null;
405
- }
406
- try {
407
- if (!fs.statSync(resolved).isDirectory()) {
408
- return { ok: false, detail: "installPath: no es un directorio" };
409
- }
410
- } catch {
411
- return { ok: false, detail: "installPath:missing (no existe)" };
412
- }
413
- return { ok: true, installPath: resolved };
414
- }
415
- function checkStrictEnabled(configDir) {
416
- const settingsFile = path.join(configDir, "settings.json");
417
- let raw;
418
- try {
419
- raw = fs.readFileSync(settingsFile, "utf8");
420
- } catch (error) {
421
- const code = errnoCodeOf(error);
422
- if (code === "ENOENT") {
423
- return { ok: false, detail: `plugin:missing (settings ausente: ${settingsFile})` };
424
- }
425
- return { ok: false, detail: `plugin:unreadable (${settingsFile}: ${code}, ilegible)` };
426
- }
427
- let parsed;
428
- try {
429
- parsed = JSON.parse(raw);
430
- } catch {
431
- return { ok: false, detail: `plugin:malformed (${settingsFile}: JSON inv\xE1lido, ilegible)` };
432
- }
433
- if (!isRecord(parsed)) {
434
- return { ok: false, detail: `plugin:malformed (${settingsFile}: objeto JSON requerido, inv\xE1lido)` };
435
- }
436
- const enabled = parsed["enabledPlugins"];
437
- if (enabled === void 0) {
438
- return { ok: false, detail: `plugin:disabled (enabledPlugins ausente en ${settingsFile})` };
439
- }
440
- if (!isRecord(enabled)) {
441
- return { ok: false, detail: `plugin:malformed (${settingsFile}: enabledPlugins no es objeto, inv\xE1lido)` };
442
- }
443
- if (enabled["engram@engram"] === true) {
444
- return { ok: true };
445
- }
446
- return { ok: false, detail: `plugin:disabled (enabledPlugins en ${settingsFile})` };
447
- }
448
- function isExactClaudeEngramMcp(value, engramBin) {
449
- if (!isRecord(value)) return false;
450
- return value["type"] === "stdio" && value["command"] === engramBin && Array.isArray(value["args"]) && value["args"].length === 2 && value["args"][0] === "mcp" && value["args"][1] === "--tools=agent";
451
- }
452
- function isExplicitClaudeMode(isExplicit) {
453
- if (isExplicit !== void 0) return isExplicit;
454
- return process.env.CLAUDE_CONFIG_DIR !== void 0;
455
- }
456
- function isDefaultClaudeConfigDir(configDir, homeDir, isExplicit) {
457
- if (isExplicitClaudeMode(isExplicit)) return false;
458
- if (homeDir !== void 0) {
459
- return path.resolve(configDir) === path.resolve(path.join(homeDir, ".claude"));
460
- }
461
- return path.basename(path.resolve(configDir)) === ".claude";
462
- }
463
- function claudeOfficialMcpFile(configDir, homeDir, isExplicit) {
464
- if (isDefaultClaudeConfigDir(configDir, homeDir, isExplicit)) {
465
- const home = homeDir ?? path.dirname(path.resolve(configDir));
466
- return path.join(home, ".claude.json");
467
- }
468
- return path.join(configDir, ".claude.json");
469
- }
470
- function checkClaudeOfficialMcp(configDir, engramBin, homeDir, isExplicit) {
471
- const file = claudeOfficialMcpFile(configDir, homeDir, isExplicit);
472
- let content;
473
- try {
474
- content = fs.readFileSync(file, "utf8");
475
- } catch {
476
- return false;
477
- }
478
- try {
479
- const parsed = JSON.parse(content);
480
- if (!isRecord(parsed)) return false;
481
- const servers = parsed["mcpServers"];
482
- if (!isRecord(servers)) return false;
483
- if (isExactClaudeEngramMcp(servers["engram"], engramBin)) return true;
484
- } catch {
485
- return false;
486
- }
487
- return false;
488
- }
489
- function hasObsoleteClaudeMcp(configDir) {
490
- try {
491
- return fs.statSync(path.join(configDir, "mcp", "engram.json"), { throwIfNoEntry: false })?.isFile() === true;
492
- } catch {
493
- return false;
494
- }
495
- }
496
- function checkClaudeOfficialHooksAt(installPath) {
497
- let parsed;
498
- try {
499
- parsed = JSON.parse(fs.readFileSync(path.join(installPath, "hooks", "hooks.json"), "utf8"));
500
- } catch {
501
- return false;
502
- }
503
- if (!isRecord(parsed)) return false;
504
- const hooks = parsed["hooks"];
505
- if (!isRecord(hooks) || Object.keys(hooks).length === 0) return false;
506
- try {
507
- const entries = fs.readdirSync(path.join(installPath, "scripts"));
508
- if (!entries.some((entry) => entry.endsWith(".sh"))) return false;
509
- } catch {
510
- return false;
511
- }
512
- return true;
513
- }
514
- async function verifyOfficialSetup(args) {
515
- const explicit = isExplicitClaudeMode(args.isExplicitClaudeConfigDir);
516
- const resolved = resolveStrictInstallPath(args.configDir);
517
- const enabled = checkStrictEnabled(args.configDir);
518
- const hasPlugin = resolved.ok && enabled.ok;
519
- const pluginDetail = !resolved.ok ? resolved.detail ?? "plugin:missing" : !enabled.ok ? enabled.detail ?? "plugin:disabled (enabledPlugins)" : null;
520
- const hasMcp = typeof args.engramBin === "string" && args.engramBin !== "" ? checkClaudeOfficialMcp(args.configDir, args.engramBin, args.homeDir, explicit) : false;
521
- const hasHooks = resolved.ok && resolved.installPath !== void 0 ? checkClaudeOfficialHooksAt(resolved.installPath) : false;
522
- const passed = [];
523
- const missing = [];
524
- if (hasPlugin) passed.push("plugin");
525
- else missing.push(pluginDetail ?? "plugin:missing");
526
- if (hasMcp) passed.push("mcp");
527
- else missing.push("mcp:missing");
528
- if (hasHooks) passed.push("hooks");
529
- else missing.push("hooks:missing");
530
- if (missing.length === 0) {
531
- return { ok: true, layers: passed, duplicates: false };
532
- }
533
- const obsolete = hasObsoleteClaudeMcp(args.configDir);
534
- if (obsolete && !hasMcp) {
535
- return {
536
- ok: false,
537
- layers: [...passed, ...missing],
538
- duplicates: false,
539
- reason: `Claude Code: incompatible existing setup (obsolete mcp/engram.json without valid MCP in ${claudeOfficialMcpFile(args.configDir, args.homeDir, explicit)}). Update to Engram 2.0.0+ and rerun install; never auto-replace existing binary. (falta: ${missing.join(", ")}).`
540
- };
541
- }
542
- return {
543
- ok: false,
544
- layers: [...passed, ...missing],
545
- duplicates: false,
546
- reason: `Claude Code: setup oficial Engram incompleto (falta: ${missing.join(", ")}).`
547
- };
548
- }
549
- registerOfficialSetupVerifier("claude-code", verifyOfficialSetup);
550
-
551
- export {
552
- claudeCodeAdapter,
553
- verifyOfficialSetup
554
- };