@evomap/evolver-mcp 2.0.0-beta.2 → 2.0.0-beta.22

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.
@@ -0,0 +1,103 @@
1
+ import type { McpServerCmd, RuntimeId } from './injection.js';
2
+ /** Minimal gene projection consumed by the Cursor rules renderer. */
3
+ export interface CursorGene {
4
+ id: string;
5
+ category?: string;
6
+ hint?: string;
7
+ }
8
+ /** Default command used by runtimes that support a SessionStart hook. The flag enables session-id capture. */
9
+ export declare const DEFAULT_HOOK_COMMAND = "evolver inject session-start --hook-stdin";
10
+ export declare const DEFAULT_PROMPT_RECALL_HOOK_COMMAND = "evolver inject prompt-recall --hook-stdin";
11
+ export declare const EVOLVER_HOOK_STATUS = "evolver-managed-hook";
12
+ /** Bind custom-command ownership to that exact command without adding undocumented hook-schema fields. */
13
+ export declare function evolverManagedHookStatus(command: string): string;
14
+ /** Remove only Evolver-owned handlers, retaining user handlers that share the same matcher group. */
15
+ export declare function stripEvolverHookEntries(entries: readonly unknown[], trustManagedStatus?: boolean, managedCommands?: ReadonlySet<string>): {
16
+ changed: boolean;
17
+ entries: unknown[];
18
+ };
19
+ /** Runtime configuration scope. Project is the default when omitted. */
20
+ export type InstallScope = 'user' | 'project';
21
+ export interface InstallOptions {
22
+ /** Runtime config root. Runtime-specific user scopes may resolve their own home-anchored paths. */
23
+ configRoot: string;
24
+ /** Claude Code scope. Other runtimes may also use this to select project or user configuration. */
25
+ scope?: InstallScope;
26
+ /** The MCP server launch command registered in the runtime configuration. */
27
+ server: McpServerCmd;
28
+ /** Command the SessionStart hook runs to inject memory. */
29
+ hookCommand?: string;
30
+ /** Command the UserPromptSubmit hook runs. Default is local-only, default-off prompt recall. */
31
+ promptRecallHookCommand?: string;
32
+ /** Reinstall even if an Evolver install is already present. */
33
+ force?: boolean;
34
+ /** Plan and validate without writing config or backup files. */
35
+ dryRun?: boolean;
36
+ /** Cursor only: genes rendered into the managed project rules file. */
37
+ genes?: readonly CursorGene[];
38
+ /** Cursor only: cap on genes rendered into the always-on rules body. */
39
+ maxGenes?: number;
40
+ /** Antigravity only: override the user home used to resolve ~/.gemini config roots. */
41
+ homeDir?: string;
42
+ /** Kiro user scope only: direct replacement for ~/.kiro, matching KIRO_HOME semantics. */
43
+ kiroHome?: string;
44
+ /** OpenCode user scope only: explicit XDG_CONFIG_HOME used to resolve the global config. */
45
+ xdgConfigHome?: string;
46
+ /** OpenCode user scope only: explicit OPENCODE_CONFIG file override. */
47
+ opencodeConfig?: string;
48
+ /** OpenCode user scope only: explicit OPENCODE_CONFIG_DIR override. */
49
+ opencodeConfigDir?: string;
50
+ /** OpenCode only: inline config loaded after project/custom-directory config. */
51
+ opencodeConfigContent?: string;
52
+ /** OpenCode only: mirrors truthy OPENCODE_DISABLE_PROJECT_CONFIG handling. */
53
+ opencodeDisableProjectConfig?: boolean;
54
+ /** OpenCode only: injectable managed-config directory for hermetic tests. */
55
+ opencodeManagedConfigDir?: string;
56
+ /** OpenCode only: injectable macOS managed-preference paths for hermetic tests. */
57
+ opencodeManagedPreferencePaths?: readonly string[];
58
+ /** OpenCode only: injectable platform used to resolve system managed paths. */
59
+ opencodePlatform?: NodeJS.Platform;
60
+ /** OpenCode only: injectable ProgramData used to resolve the Windows managed path. */
61
+ opencodeProgramData?: string;
62
+ /** OpenCode only: injectable username used to resolve macOS managed preferences. */
63
+ opencodeUsername?: string;
64
+ }
65
+ export interface UninstallOptions {
66
+ configRoot: string;
67
+ scope?: InstallScope;
68
+ /** Antigravity only: override the user home used to resolve ~/.gemini config roots. */
69
+ homeDir?: string;
70
+ /** Kiro user scope only: direct replacement for ~/.kiro, matching KIRO_HOME semantics. */
71
+ kiroHome?: string;
72
+ /** Validate and report the uninstall without changing config or backup files. */
73
+ dryRun?: boolean;
74
+ /** OpenCode user scope only: explicit XDG_CONFIG_HOME used to resolve the global config. */
75
+ xdgConfigHome?: string;
76
+ /** OpenCode user scope only: explicit OPENCODE_CONFIG file override. */
77
+ opencodeConfig?: string;
78
+ /** OpenCode user scope only: explicit OPENCODE_CONFIG_DIR override. */
79
+ opencodeConfigDir?: string;
80
+ }
81
+ export interface InstallResult {
82
+ ok: boolean;
83
+ runtime: RuntimeId;
84
+ mode: string;
85
+ /** Absolute paths written (install) or cleaned (uninstall). */
86
+ files: string[];
87
+ alreadyInstalled?: boolean;
88
+ dryRun?: boolean;
89
+ verified?: boolean;
90
+ backups?: string[];
91
+ error?: string;
92
+ }
93
+ export declare class SymlinkRefusedError extends Error {
94
+ constructor(label: string, path: string);
95
+ }
96
+ export declare class UnparseableConfigError extends Error {
97
+ constructor(label: string, path: string, owner?: string);
98
+ }
99
+ export declare class EmptySharedConfigError extends Error {
100
+ constructor(label: string, path: string, owner?: string);
101
+ }
102
+ /** Refuse to read or write through a symlink at an adapter-owned path. */
103
+ export declare function assertNotSymlink(path: string, label: string): void;
@@ -0,0 +1,99 @@
1
+ import { lstatSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ /** Default command used by runtimes that support a SessionStart hook. The flag enables session-id capture. */
4
+ export const DEFAULT_HOOK_COMMAND = 'evolver inject session-start --hook-stdin';
5
+ export const DEFAULT_PROMPT_RECALL_HOOK_COMMAND = 'evolver inject prompt-recall --hook-stdin';
6
+ export const EVOLVER_HOOK_STATUS = 'evolver-managed-hook';
7
+ const LEGACY_EVOLVER_HOOK_STATUS = 'Loading Evolver memory';
8
+ function isObj(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ const LEGACY_NODE_COMMAND = /^"?node(?:\.exe)?"?\s+/i;
12
+ const LEGACY_EVOLVER_SCRIPT_BASENAME = /(?:^|[\\/'"\s])(?:evolver-session-start|evolver-session-end|evolver-signal-detect|evolver-task-recall|evolver-daemon-start)\.js(?=$|[\\/'"\s])/i;
13
+ const LEGACY_V2_CLI_HOOK_COMMAND = /^"?evolver(?:\.cmd|\.exe)?"?\s+inject\s+(?:session-start|prompt-recall)(?:\s|$)/i;
14
+ function isKnownEvolverHookCommand(command) {
15
+ const trimmed = command.trim();
16
+ return LEGACY_V2_CLI_HOOK_COMMAND.test(trimmed)
17
+ || (LEGACY_NODE_COMMAND.test(trimmed) && LEGACY_EVOLVER_SCRIPT_BASENAME.test(trimmed));
18
+ }
19
+ /** Bind custom-command ownership to that exact command without adding undocumented hook-schema fields. */
20
+ export function evolverManagedHookStatus(command) {
21
+ if (isKnownEvolverHookCommand(command))
22
+ return EVOLVER_HOOK_STATUS;
23
+ const commandTag = createHash('sha256').update(command, 'utf8').digest('hex').slice(0, 16);
24
+ return `${EVOLVER_HOOK_STATUS} [evolver:${commandTag}]`;
25
+ }
26
+ function legacyEvolverManagedHookStatus(command) {
27
+ const currentStatus = evolverManagedHookStatus(command);
28
+ return `${LEGACY_EVOLVER_HOOK_STATUS}${currentStatus.slice(EVOLVER_HOOK_STATUS.length)}`;
29
+ }
30
+ const NO_MANAGED_HOOK_COMMANDS = new Set();
31
+ const isEvolverHandler = (handler, trustManagedStatus, managedCommands) => {
32
+ if (!isObj(handler) || typeof handler['command'] !== 'string')
33
+ return false;
34
+ const command = handler['command'];
35
+ return isKnownEvolverHookCommand(command)
36
+ || (trustManagedStatus && (managedCommands.has(command)
37
+ || handler['statusMessage'] === evolverManagedHookStatus(command)
38
+ || handler['statusMessage'] === legacyEvolverManagedHookStatus(command)));
39
+ };
40
+ /** Remove only Evolver-owned handlers, retaining user handlers that share the same matcher group. */
41
+ export function stripEvolverHookEntries(entries, trustManagedStatus = false, managedCommands = NO_MANAGED_HOOK_COMMANDS) {
42
+ let changed = false;
43
+ const keptEntries = [];
44
+ for (const entry of entries) {
45
+ if (!isObj(entry)) {
46
+ keptEntries.push(entry);
47
+ continue;
48
+ }
49
+ const handlers = entry['hooks'];
50
+ if (!Array.isArray(handlers)) {
51
+ if (isEvolverHandler(entry, trustManagedStatus, managedCommands))
52
+ changed = true;
53
+ else
54
+ keptEntries.push(entry);
55
+ continue;
56
+ }
57
+ const keptHandlers = handlers.filter((handler) => !isEvolverHandler(handler, trustManagedStatus, managedCommands));
58
+ if (keptHandlers.length === handlers.length) {
59
+ keptEntries.push(entry);
60
+ continue;
61
+ }
62
+ changed = true;
63
+ if (keptHandlers.length > 0)
64
+ keptEntries.push({ ...entry, hooks: keptHandlers });
65
+ }
66
+ return { changed, entries: keptEntries };
67
+ }
68
+ export class SymlinkRefusedError extends Error {
69
+ constructor(label, path) {
70
+ super(`[setup-hooks] refusing to operate: ${label} ${path} is a symbolic link — evolver will not follow symlinks for adapter-owned paths (a hostile workspace could redirect writes/unlinks outside the project). Replace it with a real directory/file and rerun.`);
71
+ this.name = 'SymlinkRefusedError';
72
+ }
73
+ }
74
+ export class UnparseableConfigError extends Error {
75
+ constructor(label, path, owner = 'Claude Code') {
76
+ super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists and is non-empty but is not valid JSON. This is ${owner}'s own shared config; merging into it would replace the whole file and could wipe its contents. Fix or remove the corrupt file, then rerun.`);
77
+ this.name = 'UnparseableConfigError';
78
+ }
79
+ }
80
+ export class EmptySharedConfigError extends Error {
81
+ constructor(label, path, owner = 'Claude Code') {
82
+ super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists but is empty or contains only whitespace. ${owner} may be in the middle of a truncating write, and treating it as fresh config could wipe shared config data. Fix the empty file or retry after ${owner} finishes writing it.`);
83
+ this.name = 'EmptySharedConfigError';
84
+ }
85
+ }
86
+ /** Refuse to read or write through a symlink at an adapter-owned path. */
87
+ export function assertNotSymlink(path, label) {
88
+ let stat;
89
+ try {
90
+ stat = lstatSync(path);
91
+ }
92
+ catch (error) {
93
+ if (error.code === 'ENOENT')
94
+ return;
95
+ throw error;
96
+ }
97
+ if (stat.isSymbolicLink())
98
+ throw new SymlinkRefusedError(label, path);
99
+ }
@@ -0,0 +1,79 @@
1
+ import type { InjectionPlan, McpServerCmd, RuntimeId } from './injection.js';
2
+ import { type InstallOptions, type InstallResult, type UninstallOptions } from './installerShared.js';
3
+ type BeforeReplaceHook = (path: string) => void;
4
+ export declare function _setJsonMcpBeforeReplaceHookForTest(hook?: BeforeReplaceHook): void;
5
+ export declare function _setJsonMcpAfterReplaceHookForTest(hook?: BeforeReplaceHook): void;
6
+ export declare function _setJsonMcpAfterSharedFileValidateHookForTest(hook?: BeforeReplaceHook): void;
7
+ export declare function _setJsonMcpBeforeBackupRemoveHookForTest(hook?: BeforeReplaceHook): void;
8
+ export declare function _setJsonMcpBeforeSharedFileCommitHookForTest(hook?: BeforeReplaceHook): void;
9
+ export interface JsonMcpRuntimeSpec {
10
+ runtime: 'opencode' | 'kiro';
11
+ configPath(opts: JsonMcpPathOptions): string;
12
+ resolveConfig?(opts: JsonMcpPathOptions): JsonMcpRuntimeResolution;
13
+ safeRoot?(opts: JsonMcpPathOptions): string;
14
+ conflictingPaths?(opts: JsonMcpPathOptions): string[];
15
+ containerKey: 'mcp' | 'mcpServers';
16
+ entry(server: McpServerCmd): Record<string, unknown>;
17
+ installPreflight?(resolution: JsonMcpRuntimeResolution, opts: InstallOptions, expected: Record<string, unknown>): JsonMcpInstallPreflight | undefined;
18
+ }
19
+ export interface JsonMcpInstallPreflight {
20
+ alreadyInstalled?: boolean;
21
+ assertUnchanged(): void;
22
+ }
23
+ type JsonMcpPathOptions = Pick<InstallOptions, 'configRoot' | 'scope' | 'homeDir' | 'kiroHome' | 'xdgConfigHome' | 'opencodeConfig' | 'opencodeConfigDir'>;
24
+ export interface JsonMcpRuntimeResolution {
25
+ configPath: string;
26
+ safeRoot: string;
27
+ conflictingPaths: string[];
28
+ /** An active sibling at the same precedence slot that must remain user-owned and read-only. */
29
+ activePrecedencePath?: string;
30
+ /** The writable target was retargeted to a uniquely managed config discovered outside the active path. */
31
+ managedRetarget?: true;
32
+ /** Runtime config candidates ordered from lowest to highest precedence; the first path is the default target. */
33
+ topologyCandidatePaths?: string[];
34
+ evidencePaths?: string[];
35
+ /** Config locations that may contain a managed backup from an earlier active-precedence decision. */
36
+ uninstallCandidatePaths?: string[];
37
+ /** Broader locations used to reuse a managed target selected from an ancestor project directory. */
38
+ installDiscoveryPaths?: string[];
39
+ /** Safe root for install discovery and a selected managed target; does not affect default install writes. */
40
+ installSafeRoot?: string;
41
+ /** Broader read-only locations used only to discover managed backups during uninstall. */
42
+ uninstallDiscoveryPaths?: string[];
43
+ /** Safe root for uninstall discovery and a selected managed target; does not affect install writes. */
44
+ uninstallSafeRoot?: string;
45
+ /** Candidate-local conflicts that must be re-evaluated after uninstall retargeting. */
46
+ uninstallConflictingPaths?: (configPath: string) => string[];
47
+ }
48
+ export declare class McpConfigConflictError extends Error {
49
+ readonly diff: {
50
+ path: string;
51
+ expected: unknown;
52
+ actual: unknown;
53
+ };
54
+ constructor(runtime: string, path: string, expected: unknown, actual: unknown);
55
+ }
56
+ export declare class McpConfigShapeError extends Error {
57
+ constructor(runtime: string, path: string, detail: string);
58
+ }
59
+ export declare class McpConfigOwnershipError extends Error {
60
+ constructor(runtime: string, detail: string);
61
+ }
62
+ export declare class McpConfigVerificationError extends Error {
63
+ private readonly runtime;
64
+ private readonly path;
65
+ restored: boolean;
66
+ constructor(runtime: string, path: string);
67
+ markRestored(): void;
68
+ }
69
+ export declare class McpConfigChangedError extends Error {
70
+ constructor(runtime: string, path: string);
71
+ }
72
+ export declare class McpServerValidationError extends Error {
73
+ constructor(message: string);
74
+ }
75
+ declare function retrySharedConfigTransaction<T>(operation: () => T): T;
76
+ export declare const _retrySharedConfigTransactionForTest: typeof retrySharedConfigTransaction;
77
+ export declare function installJsonMcpRuntime(spec: JsonMcpRuntimeSpec, plan: InjectionPlan, opts: InstallOptions): InstallResult;
78
+ export declare function uninstallJsonMcpRuntime(spec: JsonMcpRuntimeSpec, runtime: RuntimeId, opts: UninstallOptions): InstallResult;
79
+ export {};