| 2384 | } |
| 2385 | |
| 2386 | int main(int argc, char **argv) { |
| 2387 | /* Must remain the first statement: see allocator binding contract above. */ |
| 2388 | cbm_alloc_init(); |
| 2389 | #ifndef _WIN32 |
| 2390 | pid_t process_initial_ppid = getppid(); |
| 2391 | #endif |
| 2392 | #ifdef _WIN32 |
| 2393 | { |
| 2394 | int win_argc = 0; |
| 2395 | char **win_argv = cbm_win_utf8_argv(&win_argc); |
| 2396 | if (win_argv) { |
| 2397 | argc = win_argc; |
| 2398 | argv = win_argv; |
| 2399 | } |
| 2400 | } |
| 2401 | #endif |
| 2402 | cbm_daemon_process_role_t role = cbm_daemon_process_role(argc, argv); |
| 2403 | if (role == CBM_DAEMON_PROCESS_WORKER) { |
| 2404 | /* Before this process writes ANYTHING. A worker's stderr is a file the |
| 2405 | * supervisor keeps for post-mortem, and setvbuf only binds before a |
| 2406 | * stream's first operation — claim it here so even the "could not |
| 2407 | * start" messages below reach disk. The header follows once the argv |
| 2408 | * grammar has been validated. */ |
| 2409 | cbm_log_set_crash_durable(true); |
| 2410 | } |
| 2411 | if (role == CBM_DAEMON_PROCESS_INVALID) { |
| 2412 | (void)fprintf(stderr, "codebase-memory-mcp: invalid internal process arguments\n"); |
| 2413 | return EXIT_FAILURE; |
| 2414 | } |
| 2415 | #ifndef _WIN32 |
| 2416 | if (role == CBM_DAEMON_PROCESS_DAEMON) { |
| 2417 | (void)umask(077); |
| 2418 | } |
| 2419 | #endif |
| 2420 | |
| 2421 | cbm_cli_set_version(CBM_VERSION); |
| 2422 | cbm_profile_init(); |
| 2423 | cbm_log_init_from_env(); |
| 2424 | |
| 2425 | cbm_mcp_tool_profile_t tool_profile = CBM_MCP_TOOL_PROFILE_ALL; |
| 2426 | if (role == CBM_DAEMON_PROCESS_MCP_CLIENT && |
| 2427 | cbm_mcp_parse_tool_profile_args(argc, (const char *const *)argv, &tool_profile) != 0) { |
| 2428 | (void)fprintf(stderr, "codebase-memory-mcp: --tool-profile requires the supported value " |
| 2429 | "'analysis' or 'scout'\n"); |
| 2430 | return 2; |
| 2431 | } |
| 2432 | const char *hook_event = NULL; |
| 2433 | const char *hook_dialect = NULL; |
| 2434 | if (role == CBM_DAEMON_PROCESS_HOOK_CLIENT && |
| 2435 | !main_hook_options(argc, argv, &hook_event, &hook_dialect)) { |
| 2436 | return EXIT_SUCCESS; /* hook adapters are contractually fail-open */ |
| 2437 | } |
| 2438 | |
| 2439 | /* Hook augmentation is contractually fail-open and time-bounded. It is |
| 2440 | * daemon-backed but CONNECT-ONLY: a hook never spawns a daemon (a cold |
| 2441 | * spawn cannot fit the fail-open budget and livelocks against the |
| 2442 | * last-client-exit teardown), it recycles whichever daemon an MCP |
| 2443 | * session or `daemon start` already brought up. Arm the deadline before |
nothing calls this directly
no test coverage detected