list_projects: scan cache directory for .db files. * Each project is a single .db file — no central registry needed. */
| 2852 | /* list_projects: scan cache directory for .db files. |
| 2853 | * Each project is a single .db file — no central registry needed. */ |
| 2854 | static char *handle_list_projects(cbm_mcp_server_t *srv, const char *args) { |
| 2855 | (void)srv; |
| 2856 | bool metadata_only = false; |
| 2857 | yyjson_doc *args_doc = args ? yyjson_read(args, strlen(args), 0) : NULL; |
| 2858 | yyjson_val *args_root = args_doc ? yyjson_doc_get_root(args_doc) : NULL; |
| 2859 | yyjson_val *metadata_value = |
| 2860 | args_root && yyjson_is_obj(args_root) ? yyjson_obj_get(args_root, "metadata_only") : NULL; |
| 2861 | metadata_only = metadata_value && yyjson_is_true(metadata_value); |
| 2862 | char *detail = cbm_mcp_get_string_arg(args, "detail"); |
| 2863 | /* `include_details` is the boolean spelling main shipped for the same |
| 2864 | * request; both select the stats projection. */ |
| 2865 | bool include_stats = !metadata_only && ((detail && strcmp(detail, "stats") == 0) || |
| 2866 | cbm_mcp_get_bool_arg(args, "include_details")); |
| 2867 | free(detail); |
| 2868 | if (args_doc) { |
| 2869 | yyjson_doc_free(args_doc); |
| 2870 | } |
| 2871 | |
| 2872 | char dir_path[CBM_SZ_1K]; |
| 2873 | cache_dir(dir_path, sizeof(dir_path)); |
| 2874 | |
| 2875 | cbm_dir_t *d = cbm_opendir(dir_path); |
| 2876 | |
| 2877 | if (!d) { |
| 2878 | char msg[CBM_SZ_1K]; |
| 2879 | snprintf(msg, sizeof(msg), |
| 2880 | "{\"error\":\"cannot read cache directory: %s\",\"hint\":" |
| 2881 | "\"Check directory permissions or run index_repository first.\"}", |
| 2882 | dir_path); |
| 2883 | return cbm_mcp_text_result(msg, true); |
| 2884 | } |
| 2885 | |
| 2886 | mcp_project_record_t *records = NULL; |
| 2887 | int record_count = 0; |
| 2888 | int record_cap = 0; |
| 2889 | bool oom = false; |
| 2890 | cbm_dirent_t *entry; |
| 2891 | while ((entry = cbm_readdir(d)) != NULL) { |
| 2892 | const char *name = entry->name; |
| 2893 | size_t len = strlen(name); |
| 2894 | if (!is_project_db_file(name, len)) { |
| 2895 | continue; |
| 2896 | } |
| 2897 | char full_path[CBM_SZ_2K]; |
| 2898 | snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, name); |
| 2899 | int64_t size_bytes = cbm_file_size(full_path); |
| 2900 | if (size_bytes < 0) { |
| 2901 | continue; |
| 2902 | } |
| 2903 | mcp_project_record_t record = {0}; |
| 2904 | project_record_status_t read_status = |
| 2905 | read_project_record_identity(dir_path, name, size_bytes, &record); |
| 2906 | if (read_status == PROJECT_RECORD_OOM) { |
| 2907 | oom = true; |
| 2908 | break; |
| 2909 | } |
| 2910 | if (read_status != PROJECT_RECORD_OK) { |
| 2911 | continue; |
no test coverage detected