Sprint 287: PerTaskLibrarySelector (steps 1963-1967)
Deterministic library dispatch layer. Five new headers: - LibrarySelection: result struct with avoidedLibraries + justification - OperationClassifier: wraps taxonomy with ClassificationResult - PerTaskLibrarySelector: sort by score, pick max, build avoided list - SelectionJustificationLog: append-only auditable selection log - Sprint287IntegrationSummary: 8-task spec, all selections verified 25/25 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
28
editor/src/LibrarySelection.h
Normal file
28
editor/src/LibrarySelection.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
// Step 1963: LibrarySelection
|
||||
// Result struct from PerTaskLibrarySelector — fully specifies which library
|
||||
// to use for a given operation domain and why.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace whetstone {
|
||||
|
||||
struct AvoidedLibrary {
|
||||
std::string name;
|
||||
std::string reason; // e.g. "score 0.60 < selected 0.92"
|
||||
float score = 0.0f;
|
||||
};
|
||||
|
||||
struct LibrarySelection {
|
||||
std::string selectedLibrary; // empty if no library qualifies
|
||||
std::string operationDomain;
|
||||
float capabilityScore = 0.0f;
|
||||
std::vector<std::string> preferredAPIs;
|
||||
std::vector<AvoidedLibrary> avoidedLibraries; // sorted by score desc
|
||||
std::string justification;
|
||||
|
||||
bool hasSelection() const { return !selectedLibrary.empty(); }
|
||||
};
|
||||
|
||||
} // namespace whetstone
|
||||
39
editor/src/OperationClassifier.h
Normal file
39
editor/src/OperationClassifier.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
// Step 1964: OperationClassifier
|
||||
// Wraps OperationTaxonomy with a ClassificationResult that includes
|
||||
// confidence and isKnown flags.
|
||||
|
||||
#include "OperationTaxonomy.h"
|
||||
#include <string>
|
||||
|
||||
namespace whetstone {
|
||||
|
||||
struct ClassificationResult {
|
||||
std::string domain; // e.g. "serialization.json" or "unknown"
|
||||
float confidence; // 1.0 if known, 0.0 if "unknown"
|
||||
bool isKnown;
|
||||
};
|
||||
|
||||
class OperationClassifier {
|
||||
public:
|
||||
explicit OperationClassifier(OperationTaxonomy taxonomy)
|
||||
: taxonomy_(std::move(taxonomy)) {}
|
||||
|
||||
ClassificationResult classify(const std::string& text) const {
|
||||
auto domain = taxonomy_.classify(text);
|
||||
bool known = (domain != "unknown");
|
||||
return {domain, known ? 1.0f : 0.0f, known};
|
||||
}
|
||||
|
||||
// Convenience: build with the default 40+ domain taxonomy
|
||||
static OperationClassifier withDefaultTaxonomy() {
|
||||
return OperationClassifier(OperationTaxonomy::buildDefault());
|
||||
}
|
||||
|
||||
const OperationTaxonomy& taxonomy() const { return taxonomy_; }
|
||||
|
||||
private:
|
||||
OperationTaxonomy taxonomy_;
|
||||
};
|
||||
|
||||
} // namespace whetstone
|
||||
90
editor/src/PerTaskLibrarySelector.h
Normal file
90
editor/src/PerTaskLibrarySelector.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
// Step 1965: PerTaskLibrarySelector
|
||||
// Deterministic library dispatch: given (operationDomain, availableLibraries, ledger)
|
||||
// rank by score, pick max, build avoidedLibraries + justification.
|
||||
|
||||
#include "LibraryCapabilityLedger.h"
|
||||
#include "LibrarySelection.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace whetstone {
|
||||
|
||||
class PerTaskLibrarySelector {
|
||||
public:
|
||||
// Core selection: deterministic, no inference.
|
||||
LibrarySelection select(const std::string& operationDomain,
|
||||
const std::vector<std::string>& availableLibraries,
|
||||
const LibraryCapabilityLedger& ledger) const {
|
||||
LibrarySelection result;
|
||||
result.operationDomain = operationDomain;
|
||||
|
||||
if (availableLibraries.empty()) {
|
||||
result.justification = "no library with score > 0";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Score all candidates
|
||||
struct Candidate { std::string lib; float score; };
|
||||
std::vector<Candidate> candidates;
|
||||
candidates.reserve(availableLibraries.size());
|
||||
for (const auto& lib : availableLibraries) {
|
||||
candidates.push_back({lib, ledger.score(lib, operationDomain)});
|
||||
}
|
||||
|
||||
// Stable sort by score descending (stable for deterministic tie-breaking)
|
||||
std::stable_sort(candidates.begin(), candidates.end(),
|
||||
[](const Candidate& a, const Candidate& b) {
|
||||
return a.score > b.score;
|
||||
});
|
||||
|
||||
if (candidates[0].score == 0.0f) {
|
||||
result.justification = "no library with score > 0";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Selected: highest score
|
||||
result.selectedLibrary = candidates[0].lib;
|
||||
result.capabilityScore = candidates[0].score;
|
||||
|
||||
// Preferred APIs from ledger record
|
||||
auto rec = ledger.get(candidates[0].lib, operationDomain);
|
||||
if (rec) result.preferredAPIs = rec->preferredAPIs;
|
||||
|
||||
// Avoided: remaining with score > 0, sorted desc (already sorted)
|
||||
for (size_t i = 1; i < candidates.size(); ++i) {
|
||||
if (candidates[i].score > 0.0f) {
|
||||
result.avoidedLibraries.push_back({
|
||||
candidates[i].lib,
|
||||
"score " + fmt(candidates[i].score) + " < selected " + fmt(candidates[0].score),
|
||||
candidates[i].score
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Justification string
|
||||
if (result.avoidedLibraries.empty()) {
|
||||
result.justification = result.selectedLibrary + " scores " +
|
||||
fmt(result.capabilityScore) + " (only qualified library)";
|
||||
} else {
|
||||
result.justification = result.selectedLibrary + " scores " +
|
||||
fmt(result.capabilityScore) + " vs best alternative " +
|
||||
result.avoidedLibraries[0].name + " at " +
|
||||
fmt(result.avoidedLibraries[0].score);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string fmt(float s) {
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(2) << s;
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace whetstone
|
||||
59
editor/src/SelectionJustificationLog.h
Normal file
59
editor/src/SelectionJustificationLog.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
// Step 1966: SelectionJustificationLog
|
||||
// Append-only auditable log of every library selection decision.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace whetstone {
|
||||
|
||||
struct SelectionLogEntry {
|
||||
std::string taskId;
|
||||
std::string operationDomain;
|
||||
std::string selectedLibrary; // empty if no selection was made
|
||||
float capabilityScore = 0.0f;
|
||||
std::string justification;
|
||||
std::string timestamp; // ISO date/time string
|
||||
};
|
||||
|
||||
class SelectionJustificationLog {
|
||||
public:
|
||||
void append(SelectionLogEntry entry) {
|
||||
entries_.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
const std::vector<SelectionLogEntry>& entries() const { return entries_; }
|
||||
|
||||
size_t size() const { return entries_.size(); }
|
||||
|
||||
std::vector<SelectionLogEntry> findByDomain(const std::string& domain) const {
|
||||
std::vector<SelectionLogEntry> result;
|
||||
for (const auto& e : entries_) {
|
||||
if (e.operationDomain == domain) result.push_back(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<SelectionLogEntry> findByLibrary(const std::string& lib) const {
|
||||
std::vector<SelectionLogEntry> result;
|
||||
for (const auto& e : entries_) {
|
||||
if (e.selectedLibrary == lib) result.push_back(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<SelectionLogEntry> findByTask(const std::string& taskId) const {
|
||||
std::vector<SelectionLogEntry> result;
|
||||
for (const auto& e : entries_) {
|
||||
if (e.taskId == taskId) result.push_back(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void clear() { entries_.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<SelectionLogEntry> entries_;
|
||||
};
|
||||
|
||||
} // namespace whetstone
|
||||
60
editor/src/Sprint287IntegrationSummary.h
Normal file
60
editor/src/Sprint287IntegrationSummary.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
// Step 1967: Sprint 287 Integration Summary
|
||||
// Steps 1963–1967: PerTaskLibrarySelector
|
||||
//
|
||||
// 1963: LibrarySelection — result struct with avoidedLibraries + justification
|
||||
// 1964: OperationClassifier — wraps taxonomy with ClassificationResult
|
||||
// 1965: PerTaskLibrarySelector — deterministic max-score dispatch
|
||||
// 1966: SelectionJustificationLog — append-only auditable selection log
|
||||
// 1967: Integration — 8-task spec, all selections verified
|
||||
|
||||
#include "LibraryCapabilityLedger.h"
|
||||
#include "PerTaskLibrarySelector.h"
|
||||
#include "Sprint286IntegrationSummary.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace whetstone {
|
||||
|
||||
struct Sprint287IntegrationSummary {
|
||||
int stepsCompleted = 5;
|
||||
bool success = true;
|
||||
std::string sprintName() const {
|
||||
return "Sprint 287: PerTaskLibrarySelector";
|
||||
}
|
||||
|
||||
struct Task {
|
||||
std::string taskId;
|
||||
std::string operationDomain;
|
||||
std::vector<std::string> availableLibraries;
|
||||
std::string expectedLibrary;
|
||||
};
|
||||
|
||||
// 8 tasks covering all seed libraries; each task has a verifiable expected selection.
|
||||
static std::vector<Task> buildTaskSpec() {
|
||||
return {
|
||||
{"t1", "serialization.json", {"nlohmann_json", "protobuf"}, "nlohmann_json"},
|
||||
{"t2", "compute.matrix", {"cublas", "numpy"}, "cublas"},
|
||||
{"t3", "serialization.binary", {"protobuf", "nlohmann_json"}, "protobuf"},
|
||||
{"t4", "compute.statistics", {"numpy"}, "numpy"},
|
||||
{"t5", "network.async", {"tokio", "boost.asio"}, "tokio"},
|
||||
{"t6", "compute.parallel", {"thrust"}, "thrust"},
|
||||
{"t7", "io.stream", {"tokio", "boost.asio"}, "tokio"},
|
||||
{"t8", "network.http.client", {"boost.asio", "tokio"}, "boost.asio"},
|
||||
};
|
||||
}
|
||||
|
||||
// Run selector over all 8 tasks; return true if all selections match expected.
|
||||
static bool verifyAllSelections() {
|
||||
auto ledger = Sprint286IntegrationSummary::buildSeedLedger();
|
||||
PerTaskLibrarySelector selector;
|
||||
for (const auto& task : buildTaskSpec()) {
|
||||
auto sel = selector.select(task.operationDomain,
|
||||
task.availableLibraries, ledger);
|
||||
if (sel.selectedLibrary != task.expectedLibrary) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace whetstone
|
||||
Reference in New Issue
Block a user