Wave 9: fix audit findings, harden ABI, deduplicate config (W12.1-W12.6)
- W12.1 context_plugin (engineer-zhou): wrap C ABI surface in try/catch, add OOM-safe strdup_message_fields helper, make g_max_tokens drive message-count trim (option A). - W12.2 config refactor (architect-lin): introduce plugins/config/include/toml_parse.h to eliminate 74-line parser duplication; config_plugin delegates to host->config_get/set, collapsing the dual-store data island; ConfigStore::get() now copies via thread_local std::string to remove c_str() dangling under concurrent set(). Zero ABI changes. - W12.3 CLI command parsing (engineer-zhao): guard /clear and /context on missing session service; refactor /file dispatch so bare /file write hits usage instead of unknown-command. - W12.4 build path unification (devops-hu): set per-target RUNTIME_OUTPUT_DIRECTORY on dstalk-cli; remove stale build/dstalk-cli/dstalk-cli.exe so build/bin/ is the sole binary. - W12.5 STATUS.md auto-refresh (engineer-li): run W11.6 script to regenerate STATUS from live profile/group data. - W12.6 plugin-abi.md (writer-deng): add §8 exception safety across ABI boundary and §9 string return lifetime; reference real audit-found violations as anti-examples. Verified: cmake build 0 error 0 warning, ctest 4/4 pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@ add_executable(dstalk-cli
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(dstalk-cli PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
)
|
||||
|
||||
target_link_libraries(dstalk-cli
|
||||
PRIVATE dstalk
|
||||
)
|
||||
|
||||
@@ -166,8 +166,12 @@ static void handle_command(const char* line)
|
||||
|
||||
// /clear
|
||||
if (std::strcmp(line, "/clear") == 0) {
|
||||
if (g_session) g_session->clear();
|
||||
std::printf(CLR_GREEN "[OK] 会话已清空\n" CLR_RESET);
|
||||
if (g_session) {
|
||||
g_session->clear();
|
||||
std::printf(CLR_GREEN "[OK] 会话已清空\n" CLR_RESET);
|
||||
} else {
|
||||
std::fprintf(stderr, CLR_RED "[ERROR] session service not available\n" CLR_RESET);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,6 +184,8 @@ static void handle_command(const char* line)
|
||||
std::printf(CLR_DIM "消息条数: " CLR_RESET "%d | "
|
||||
CLR_DIM "Token 估算: " CLR_RESET "%d\n",
|
||||
count, tokens);
|
||||
} else {
|
||||
std::fprintf(stderr, CLR_RED "[ERROR] context service not available\n" CLR_RESET);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -226,42 +232,67 @@ static void handle_command(const char* line)
|
||||
return;
|
||||
}
|
||||
|
||||
// /file list [path]
|
||||
if (std::strcmp(line, "/file list") == 0 || std::strncmp(line, "/file list ", 11) == 0) {
|
||||
const char* path = line + 10;
|
||||
list_files(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// /file show <path>
|
||||
if (std::strncmp(line, "/file show ", 11) == 0) {
|
||||
print_file(line + 11);
|
||||
return;
|
||||
}
|
||||
|
||||
// /file read <path>
|
||||
if (std::strncmp(line, "/file read ", 11) == 0) {
|
||||
print_file(line + 11);
|
||||
return;
|
||||
}
|
||||
|
||||
// /file write <path> <content...>
|
||||
if (std::strncmp(line, "/file write ", 12) == 0) {
|
||||
const char* rest = line + 12;
|
||||
// /file <subcommand> [args...] —— 统一入口,避免 strncmp 空格匹配遗漏
|
||||
if (std::strncmp(line, "/file", 5) == 0) {
|
||||
const char* rest = line + 5;
|
||||
while (*rest == ' ') rest++;
|
||||
const char* space = std::strchr(rest, ' ');
|
||||
if (!space) {
|
||||
std::printf(CLR_RED "[ERROR] 用法: /file write <path> <content>\n" CLR_RESET);
|
||||
|
||||
const char* sub_end = rest;
|
||||
while (*sub_end != ' ' && *sub_end != '\0') sub_end++;
|
||||
size_t sub_len = sub_end - rest;
|
||||
if (sub_len == 0) {
|
||||
std::printf(CLR_RED "[ERROR] 用法: /file <list|show|read|write> ...\n" CLR_RESET);
|
||||
return;
|
||||
}
|
||||
std::string path(rest, space - rest);
|
||||
const char* content = space + 1;
|
||||
while (*content == ' ') content++;
|
||||
if (g_file_io && g_file_io->write(path.c_str(), content) == 0) {
|
||||
std::printf(CLR_GREEN "[OK] 已写入: %s\n" CLR_RESET, path.c_str());
|
||||
} else {
|
||||
std::printf(CLR_RED "[ERROR] 写入失败: %s\n" CLR_RESET, path.c_str());
|
||||
|
||||
const char* args = sub_end;
|
||||
while (*args == ' ') args++;
|
||||
|
||||
// /file list [path]
|
||||
if (sub_len == 4 && std::strncmp(rest, "list", 4) == 0) {
|
||||
list_files(args);
|
||||
return;
|
||||
}
|
||||
|
||||
// /file show <path> | /file read <path>
|
||||
if ((sub_len == 4 && std::strncmp(rest, "show", 4) == 0) ||
|
||||
(sub_len == 4 && std::strncmp(rest, "read", 4) == 0)) {
|
||||
while (*args == ' ') args++;
|
||||
if (*args == '\0') {
|
||||
std::printf(CLR_RED "[ERROR] 用法: /file %.*s <path>\n" CLR_RESET,
|
||||
static_cast<int>(sub_len), rest);
|
||||
return;
|
||||
}
|
||||
print_file(args);
|
||||
return;
|
||||
}
|
||||
|
||||
// /file write <path> <content>
|
||||
if (sub_len == 5 && std::strncmp(rest, "write", 5) == 0) {
|
||||
while (*args == ' ') args++;
|
||||
if (*args == '\0') {
|
||||
std::printf(CLR_RED "[ERROR] 用法: /file write <path> <content>\n" CLR_RESET);
|
||||
return;
|
||||
}
|
||||
const char* path_end = args;
|
||||
while (*path_end != ' ' && *path_end != '\0') path_end++;
|
||||
std::string path(args, path_end - args);
|
||||
const char* content = path_end;
|
||||
while (*content == ' ') content++;
|
||||
if (*content == '\0') {
|
||||
std::printf(CLR_RED "[ERROR] 用法: /file write <path> <content>\n" CLR_RESET);
|
||||
return;
|
||||
}
|
||||
if (g_file_io && g_file_io->write(path.c_str(), content) == 0) {
|
||||
std::printf(CLR_GREEN "[OK] 已写入: %s\n" CLR_RESET, path.c_str());
|
||||
} else {
|
||||
std::printf(CLR_RED "[ERROR] 写入失败: %s\n" CLR_RESET, path.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::printf(CLR_RED "[ERROR] 未知 /file 子命令: %.*s (可用: list, show, read, write)\n" CLR_RESET,
|
||||
static_cast<int>(sub_len), rest);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user