Build the agent.install.plan.v1 receipt (#388): a machine-readable list of * the config / instruction / hook files `install` WOULD write, produced by * running the real install dispatch in record-only mode (no mutation, no * network). Returns a heap JSON string (caller frees) or NULL. */
| 3671 | * running the real install dispatch in record-only mode (no mutation, no |
| 3672 | * network). Returns a heap JSON string (caller frees) or NULL. */ |
| 3673 | char *cbm_build_install_plan_json(const char *home, const char *binary_path) { |
| 3674 | if (!home || !binary_path) { |
| 3675 | return NULL; |
| 3676 | } |
| 3677 | |
| 3678 | /* Same code path as a real install, but mutations disabled and every write |
| 3679 | * site records into `plan` — so the receipt cannot drift from behavior. */ |
| 3680 | cbm_install_plan_t plan = {0}; |
| 3681 | g_install_plan = &plan; |
| 3682 | cbm_install_agent_configs(home, binary_path, false, true); |
| 3683 | g_install_plan = NULL; |
| 3684 | |
| 3685 | cbm_detected_agents_t det = cbm_detect_agents(home); |
| 3686 | struct { |
| 3687 | bool flag; |
| 3688 | const char *name; |
| 3689 | } names[] = { |
| 3690 | {det.claude_code, "claude-code"}, |
| 3691 | {det.codex, "codex"}, |
| 3692 | {det.gemini, "gemini"}, |
| 3693 | {det.zed, "zed"}, |
| 3694 | {det.opencode, "opencode"}, |
| 3695 | {det.antigravity, "antigravity"}, |
| 3696 | {det.aider, "aider"}, |
| 3697 | {det.kilocode, "kilocode"}, |
| 3698 | {det.vscode, "vscode"}, |
| 3699 | {det.cursor, "cursor"}, |
| 3700 | {det.openclaw, "openclaw"}, |
| 3701 | {det.kiro, "kiro"}, |
| 3702 | {det.junie, "junie"}, |
| 3703 | }; |
| 3704 | |
| 3705 | yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); |
| 3706 | yyjson_mut_val *root = yyjson_mut_obj(doc); |
| 3707 | yyjson_mut_doc_set_root(doc, root); |
| 3708 | yyjson_mut_obj_add_str(doc, root, "type", "agent.install.plan.v1"); |
| 3709 | |
| 3710 | yyjson_mut_val *agents = yyjson_mut_arr(doc); |
| 3711 | for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { |
| 3712 | if (names[i].flag) { |
| 3713 | yyjson_mut_arr_add_str(doc, agents, names[i].name); |
| 3714 | } |
| 3715 | } |
| 3716 | yyjson_mut_obj_add_val(doc, root, "agents_detected", agents); |
| 3717 | |
| 3718 | yyjson_mut_val *configs = yyjson_mut_arr(doc); |
| 3719 | yyjson_mut_val *instrs = yyjson_mut_arr(doc); |
| 3720 | yyjson_mut_val *hooks = yyjson_mut_arr(doc); |
| 3721 | for (int i = 0; i < plan.count; i++) { |
| 3722 | cbm_plan_entry_t *e = &plan.items[i]; |
| 3723 | if (strcmp(e->kind, "mcp_config") == 0) { |
| 3724 | yyjson_mut_arr_add_strcpy(doc, configs, e->path); |
| 3725 | } else if (strcmp(e->kind, "hook") == 0) { |
| 3726 | yyjson_mut_val *h = yyjson_mut_obj(doc); |
| 3727 | yyjson_mut_obj_add_strcpy(doc, h, "agent", e->agent); |
| 3728 | yyjson_mut_obj_add_strcpy(doc, h, "path", e->path); |
| 3729 | yyjson_mut_arr_add_val(hooks, h); |
| 3730 | } else { |
no test coverage detected