Sprint 288: LibrarySymbolAdvisor (steps 1968-1972)

Specific API symbol recommendation layer. Five new headers:
- LibrarySymbolRecord: functionName/signature/usagePattern/caveats
- LibrarySymbolCatalog: multi-symbol registry per (library, domain)
- LSPSymbolExtractor: parses LSP JSON into LibrarySymbolRecords
- LibrarySymbolAdvisor: ranked symbols for (library, domain) alphabetically
- Sprint288IntegrationSummary: CUDA+nlohmann catalog; cublasSgemm+json::parse verified

25/25 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-03-02 10:11:23 -07:00
parent 2a40cb1ba5
commit a410c9c756
11 changed files with 720 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
#pragma once
// Step 1970: LSPSymbolExtractor
// Parses a simplified LSP-like JSON string into LibrarySymbolRecords.
// Input format: {"symbols": [{"name": "...", "signature": "..."}]}
// Requires nlohmann_json (available via vcpkg).
#include "LibrarySymbolRecord.h"
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
namespace whetstone {
class LSPSymbolExtractor {
public:
struct ExtractionResult {
std::vector<LibrarySymbolRecord> records;
bool success = true;
std::string error;
};
// Parse lspJson string and produce records for the given (libraryId, operationDomain).
ExtractionResult extract(const std::string& libraryId,
const std::string& operationDomain,
const std::string& lspJson) const {
try {
auto j = nlohmann::json::parse(lspJson);
if (!j.contains("symbols") || !j["symbols"].is_array()) {
return {{}, false, "missing or non-array 'symbols' key"};
}
std::vector<LibrarySymbolRecord> records;
for (const auto& sym : j["symbols"]) {
LibrarySymbolRecord r;
r.libraryId = libraryId;
r.operationDomain = operationDomain;
r.functionName = sym.value("name", std::string{});
r.signature = sym.value("signature", std::string{});
r.usagePattern = sym.value("usage", std::string{});
if (sym.contains("caveats") && sym["caveats"].is_array()) {
for (const auto& c : sym["caveats"])
r.caveats.push_back(c.get<std::string>());
}
if (!r.functionName.empty()) records.push_back(std::move(r));
}
return {std::move(records), true, ""};
} catch (const nlohmann::json::exception& e) {
return {{}, false, std::string("json parse error: ") + e.what()};
}
}
};
} // namespace whetstone

View File

@@ -0,0 +1,40 @@
#pragma once
// Step 1971: LibrarySymbolAdvisor
// Given (libraryId, operationDomain, catalog) -> ranked symbol recommendations.
// Ranking: exact domain match, sorted by functionName alphabetically.
#include "LibrarySymbolCatalog.h"
#include "LibrarySymbolRecord.h"
#include <algorithm>
#include <optional>
#include <string>
#include <vector>
namespace whetstone {
class LibrarySymbolAdvisor {
public:
// Returns symbols for (libraryId, operationDomain), sorted by functionName.
// Empty if no symbols registered for this pair.
std::vector<LibrarySymbolRecord> advise(const std::string& libraryId,
const std::string& operationDomain,
const LibrarySymbolCatalog& catalog) const {
auto symbols = catalog.get(libraryId, operationDomain);
std::sort(symbols.begin(), symbols.end(),
[](const LibrarySymbolRecord& a, const LibrarySymbolRecord& b) {
return a.functionName < b.functionName;
});
return symbols;
}
// Convenience: returns the top (first alphabetically) symbol, or nullopt.
std::optional<LibrarySymbolRecord> topSymbol(const std::string& libraryId,
const std::string& operationDomain,
const LibrarySymbolCatalog& catalog) const {
auto symbols = advise(libraryId, operationDomain, catalog);
if (symbols.empty()) return std::nullopt;
return symbols[0];
}
};
} // namespace whetstone

View File

@@ -0,0 +1,68 @@
#pragma once
// Step 1969: LibrarySymbolCatalog
// Registry of LibrarySymbolRecords. Multiple symbols per (libraryId, operationDomain).
// Key: libraryId + "::" + operationDomain
#include "LibrarySymbolRecord.h"
#include <string>
#include <unordered_map>
#include <vector>
namespace whetstone {
class LibrarySymbolCatalog {
public:
void add(LibrarySymbolRecord record) {
auto key = makeKey(record.libraryId, record.operationDomain);
catalog_[key].push_back(std::move(record));
}
// Returns all symbols for (libraryId, operationDomain); empty if none.
std::vector<LibrarySymbolRecord> get(const std::string& libraryId,
const std::string& operationDomain) const {
auto it = catalog_.find(makeKey(libraryId, operationDomain));
if (it == catalog_.end()) return {};
return it->second;
}
bool has(const std::string& libraryId, const std::string& operationDomain) const {
auto it = catalog_.find(makeKey(libraryId, operationDomain));
return it != catalog_.end() && !it->second.empty();
}
// Total number of symbol records across all keys
size_t size() const {
size_t total = 0;
for (const auto& [k, v] : catalog_) total += v.size();
return total;
}
std::vector<LibrarySymbolRecord> allForLibrary(const std::string& libraryId) const {
std::vector<LibrarySymbolRecord> result;
for (const auto& [k, v] : catalog_) {
for (const auto& r : v) {
if (r.libraryId == libraryId) result.push_back(r);
}
}
return result;
}
std::vector<LibrarySymbolRecord> allForDomain(const std::string& operationDomain) const {
std::vector<LibrarySymbolRecord> result;
for (const auto& [k, v] : catalog_) {
for (const auto& r : v) {
if (r.operationDomain == operationDomain) result.push_back(r);
}
}
return result;
}
private:
static std::string makeKey(const std::string& lib, const std::string& domain) {
return lib + "::" + domain;
}
std::unordered_map<std::string, std::vector<LibrarySymbolRecord>> catalog_;
};
} // namespace whetstone

View File

@@ -0,0 +1,19 @@
#pragma once
// Step 1968: LibrarySymbolRecord
// Specific API symbol recommendation for a (library, operationDomain) pair.
#include <string>
#include <vector>
namespace whetstone {
struct LibrarySymbolRecord {
std::string libraryId; // e.g. "cublas"
std::string functionName; // e.g. "cublasSgemm"
std::string signature; // e.g. "cublasStatus_t cublasSgemm(cublasHandle_t, ...)"
std::string operationDomain; // e.g. "compute.matrix"
std::string usagePattern; // example call showing typical invocation
std::vector<std::string> caveats; // known footguns or preconditions
};
} // namespace whetstone

View File

@@ -0,0 +1,83 @@
#pragma once
// Step 1972: Sprint 288 Integration Summary
// Steps 19681972: LibrarySymbolAdvisor
//
// 1968: LibrarySymbolRecord — functionName/signature/operationDomain/usagePattern/caveats
// 1969: LibrarySymbolCatalog — multi-symbol registry per (library, domain)
// 1970: LSPSymbolExtractor — parses LSP JSON into LibrarySymbolRecords
// 1971: LibrarySymbolAdvisor — returns ranked symbols for (library, domain)
// 1972: Integration — CUDA + nlohmann catalog, verify cublasSgemm + json::parse
#include "LibrarySymbolAdvisor.h"
#include "LibrarySymbolCatalog.h"
#include <string>
namespace whetstone {
struct Sprint288IntegrationSummary {
int stepsCompleted = 5;
bool success = true;
std::string sprintName() const {
return "Sprint 288: LibrarySymbolAdvisor";
}
static LibrarySymbolCatalog buildSeedCatalog() {
LibrarySymbolCatalog cat;
// cublas: compute.matrix
cat.add({"cublas", "cublasSgemm",
"cublasStatus_t cublasSgemm(cublasHandle_t, cublasOperation_t, cublasOperation_t,"
" int m, int n, int k, const float* alpha, const float* A, int lda,"
" const float* B, int ldb, const float* beta, float* C, int ldc)",
"compute.matrix",
"cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, A, lda, B, ldb, &beta, C, ldc)",
{"requires-cublasCreate-before-use", "device-memory-only"}});
cat.add({"cublas", "cublasDgemm",
"cublasStatus_t cublasDgemm(cublasHandle_t, cublasOperation_t, cublasOperation_t,"
" int m, int n, int k, const double* alpha, const double* A, int lda,"
" const double* B, int ldb, const double* beta, double* C, int ldc)",
"compute.matrix",
"cublasDgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, A, lda, B, ldb, &beta, C, ldc)",
{"double-precision", "requires-cublasCreate-before-use"}});
// nlohmann_json: serialization.json
cat.add({"nlohmann_json", "json::dump",
"std::string nlohmann::json::dump(int indent = -1) const",
"serialization.json",
"std::string s = j.dump(2); // pretty-print with indent=2",
{}});
cat.add({"nlohmann_json", "json::parse",
"static nlohmann::json nlohmann::json::parse(const std::string& s)",
"serialization.json",
"auto j = nlohmann::json::parse(input_string);",
{"throws-parse_error-on-invalid-json"}});
return cat;
}
static bool verifyCUDALookup(const LibrarySymbolCatalog& cat) {
LibrarySymbolAdvisor adv;
auto syms = adv.advise("cublas", "compute.matrix", cat);
if (syms.empty()) return false;
// cublasDgemm < cublasSgemm alphabetically, so [0]=cublasDgemm, [1]=cublasSgemm
bool foundSgemm = false;
for (const auto& s : syms) {
if (s.functionName == "cublasSgemm") { foundSgemm = true; break; }
}
return foundSgemm;
}
static bool verifyNlohmannLookup(const LibrarySymbolCatalog& cat) {
LibrarySymbolAdvisor adv;
auto syms = adv.advise("nlohmann_json", "serialization.json", cat);
if (syms.empty()) return false;
// json::dump < json::parse alphabetically, so [0]=json::dump, [1]=json::parse
bool foundParse = false;
for (const auto& s : syms) {
if (s.functionName == "json::parse") { foundParse = true; break; }
}
return foundParse;
}
};
} // namespace whetstone