Files
dstalk/CLAUDE.md
XiuChengWu c0af9c65c7
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / Sanitizer (ASan+UBSan) / ubuntu-24.04 (push) Has been cancelled
CI / Coverage (gcovr) / ubuntu-24.04 (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled
feat: Add LSP plugin unit tests and frontend common initialization library
- Introduced `dstalk_lsp_plugin_test` for testing LSP plugin functionalities including `lsp_trim`, `lsp_frame_message`, and `lsp_parse_content_length`.
- Created `dstalk_frontend_common` static library to encapsulate shared initialization logic for frontend components (CLI, GUI, Web).
- Implemented configuration file discovery and service querying in `dstalk_frontend_init`.
- Added internal headers for LSP and Anthropic plugins to facilitate unit testing.
- Established a mailroom system for asynchronous message passing between stateless agents, enhancing coordination and context management.
2026-06-01 08:51:40 +08:00

9.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build & run

The repo ships its own toolchain under tools/ (clang-cl/clang, CMake, Ninja, Conan 2 in a venv). Build scripts prefer those over PATH.

  • One-shot build (Windows): build.bat — checks/installs toolchain, runs Conan install against deps/conanfile.txt, configures CMake with clang-cl, builds with Ninja into build/.
  • One-shot build (Linux/macOS): bash build.sh [Release|Debug] — same flow with clang (or gcc fallback) and Ninja.
  • First-time setup: tools/setup.bat or bash tools/setup.sh installs CMake, Ninja, and Conan 2 into tools/ without touching system PATH.
  • CI-style build: scripts/ci-build.sh / scripts/ci-build.bat — minimal Conan + CMake + Ninja flow used by CI.
  • Incremental rebuild after edits: cd build && <tools/ninja or system ninja> -j<N> from inside build/. No need to re-run Conan unless deps/conanfile.txt changes.

Build outputs:

  • Executables → build/bin/ (dstalk_cli, test binaries, optional dstalk_gui/dstalk_web)
  • Plugin DLLs → build/plugins/ (each plugin_* target writes here, prefix stripped)

CMake options (root CMakeLists.txt):

  • DSTALK_BUILD_GUI=ON — also builds the SDL3 GUI frontend (off by default; requires SDL3 from Conan/system)
  • DSTALK_BUILD_WEB=ON — also builds the Boost.Beast web frontend
  • DSTALK_BUILD_TESTS=ON — on by default; turn off to skip tests/ subdir

Tests

CTest, hand-rolled CHECK-macro tests (no GoogleTest — see "Network-restricted environments" below).

  • Run all tests: cd build && ctest --output-on-failure
  • Run one test: cd build && ctest -R dstalk_smoke_test --output-on-failure (or any other target name from tests/CMakeLists.txt)
  • Run a test binary directly: build/bin/dstalk_smoke_test.exe
  • Coverage: cmake --build build --target coverage (requires gcovr + gcov/llvm-cov; produces build/coverage/index.html). Only meaningful when configured with --coverage flags.

Test targets and what they cover (see tests/CMakeLists.txt for the authoritative list):

  • dstalk_smoke_test — loads real plugins from build/plugins/, end-to-end integration; also carries regression cases (R1-R4, W21.5 tool calls)
  • dstalk_host_api_test, dstalk_event_bus_test, dstalk_service_registry_test — core unit tests; compile core sources directly
  • dstalk_plugin_loader_testPluginLoader regression tests; sets DSTALK_TEST_PLUGINS_DIR to build/plugins
  • dstalk_context_plugin_test — token/trim/UTF-8 boundaries
  • dstalk_anthropic_plugin_test, dstalk_openai_plugin_test — currently #include the plugin .cpp to reach static functions; link ai_common and need its include dir
  • dstalk_network_plugin_test#includes network_plugin.cpp; needs OpenSSL
  • dstalk_lsp_plugin_test — tests lsp_trim / lsp_frame_message / lsp_parse_content_length via lsp_internal.hpp

After changing the dstalk_host_api_t vtable layout, clean rebuild is mandatory — stale .obj files from incremental builds will surface as segfaults in dstalk_smoke_test / dstalk_host_api_test because the test binary and plugin DLLs disagree on struct layout. rm -rf build && bash build.sh (or delete the specific stale .obj files) before re-running ctest.

Architecture

Plugin-host design. One process loads a host DLL and many plugin DLLs over a C ABI.

Frontend (dstalk_cli / dstalk_gui / dstalk_web)
   │  links dstalk_frontend_common (shared bootstrap: config discovery, init,
   │  service queries, FrontendServices struct)
   ▼
dstalk_core.dll  ─  PluginLoader · ServiceRegistry · EventBus · Config · Logging · Memory
   ▲
   │  C ABI (dstalk_host.h)
   │
Plugins (loaded from build/plugins/, each plugin_*.dll exports dstalk_plugin_init)
   plugins_base/    config, file_io, lsp
   plugins_middle/  network, session, tools          (may depend on base)
   plugins_upper/   context, openai, anthropic       (may depend on base + middle)
                    ai_common  (static lib, shared by openai + anthropic)

Plugin tiers matter for build order and dependency direction: plugins_upper may depend on plugins_middle may depend on plugins_base. Never let a lower tier depend on a higher one.

Service vtables are the only cross-DLL contract. Plugins register service vtables (e.g. dstalk_ai_service_t, dstalk_http_service_t, dstalk_session_service_t) with the host's ServiceRegistry. Other plugins query them by (name, min_version). The vtable shapes live in dstalk_core/include/dstalk/dstalk_services.h; the host API the loader passes into each plugin's on_init(const dstalk_host_api_t* host) lives in dstalk_host.h.

ai_common (plugins_upper/ai_common/) is a static library, not a plugin. It holds shared types (PluginConfig, ToolCallAccum, StreamContext) and utilities (secure_zero, extract_host_port, serialize_tool_calls, free_chat_result) used by both openai and anthropic plugins, all under namespace dstalk_ai. Each plugin still compiles as its own DLL and links ai_common privately.

Hard rules — violating these is undefined behavior

These come from docs/reference/plugin-abi.md. They are not style; they are correctness.

  1. Cross-DLL heap discipline. Plugins must NOT call std::malloc/std::free/std::strdup/new/delete on data that crosses the host↔plugin boundary. Use host->alloc / host->free / host->strdup (passed in via on_init). Windows /MD CRTs and even some Linux/libc configs give each DLL its own heap — mismatched alloc/free crashes.

  2. API version is a hard match. dstalk_plugin_info_t.api_version must equal DSTALK_API_VERSION (currently 1). The loader rejects mismatches. There is no backward compat — rebuild plugins against the new host.

  3. String ownership. dstalk_chat_result_t.content / .error / .tool_calls_json are allocated with host->strdup by the producing plugin; the caller frees them with host->free. Never return std::string::c_str() or stack buffers across the ABI.

  4. C ABI + atomic globals. Service vtable function pointers and cached service pointers stored in plugin global state must be std::atomic<T*> with memory_order_acquire/release. Raw pointers race during shutdown (the anthropic plugin's g_config was a real data race fixed in W17). Same applies to g_host, g_http, etc.

  5. No new on plugin info strings. name/version/description in dstalk_plugin_info_t only need to live for the duration of dstalk_plugin_init() — string literals are fine; the host copies them.

  6. api_key lifecycle. On on_shutdown, overwrite key bytes via volatile char* loop, then clear the string. Use dstalk_ai::secure_zero() from ai_common.

Build system facts that bite

  • Conan provides only boost::boost (the umbrella target) — granular boost::json / boost::asio / boost::beast targets do not exist in this Conan setup. Don't migrate to them.
  • Every Boost-using target needs its own find_package(Boost REQUIRED CONFIG) call before target_link_libraries(... boost::boost). Doing it once at the root is not enough; Conan-generated config exposes the target per-subdir.
  • dstalk_boost_config (defined in dstalk_core/CMakeLists.txt) is an INTERFACE target carrying BOOST_ALL_NO_LIB / BOOST_ERROR_CODE_HEADER_ONLY / BOOST_JSON_HEADER_ONLY. Link it from any target using <boost/json.hpp>. Boost.JSON is header-only here, so each TU using it must #include <boost/json/src.hpp> in exactly one file (see dstalk_core/src/boost_json.cpp).
  • The network plugin links openssl::openssl directly and calls OpenSSL C APIs (SSL_set_tlsext_host_name, SSL_set1_host). Don't remove that link "because Boost.Asio already pulls SSL" — it doesn't, at least not for these symbols.
  • Plugin DLLs are written to build/plugins/ with PREFIX "" (so plugin_openai.dll, not libplugin_openai.dll). The smoke test and plugin_loader_test look there.

Network-restricted environments

This repo has been built in environments where outbound network is locked down. Two consequences:

  • Do not add FetchContent for GitHub-hosted deps. git clone over smart-HTTP fails with "early EOF" through restrictive proxies (W17.3 lesson — GoogleTest dropped, tests use hand-rolled CHECK macros instead). Vendor or skip.
  • Conan packages must be pre-cached in ~/.conan2/p/ or available through an allowed mirror; first-run conan install from a clean cache will fail offline.

Repo-specific conventions

  • README is in Chinese (Simplified). Comments and docs are routinely bilingual (Chinese first, English after a /). Match the surrounding style in any file you edit.
  • Plugin code style. Each AI/network plugin has the same skeleton: g_host (atomic), g_cfg, on_init/on_shutdown, service vtable, then dstalk_plugin_init() returning a static dstalk_plugin_info_t. Keep that shape when adding a provider.
  • CRT. CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL (/MD). All plugins and the host must agree.

The agents/ directory

agents/ (README, WORKFLOW.md, STATUS.md, per-agent profile.md files, groups/) describes a multi-agent collaboration mode used by the project owner with Claude. It is documentation, not code — nothing in the build references it. Treat its contents as historical context; do not invent or extend the "16-person team / waves / 6-stage workflow" apparatus unless the user explicitly asks for it. If the user does invoke it, the rules are in agents/WORKFLOW.md.