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:
92
editor/tests/step1963_test.cpp
Normal file
92
editor/tests/step1963_test.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
// Step 1963: LibrarySelection
|
||||
//
|
||||
// t1: default LibrarySelection has no selection (hasSelection=false)
|
||||
// t2: populated selection fields are correct
|
||||
// t3: avoidedLibraries struct fields
|
||||
// t4: multiple avoided libraries
|
||||
// t5: selection with no avoidedLibraries (only one qualified)
|
||||
|
||||
#include "LibrarySelection.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
static int p = 0, f = 0;
|
||||
#define T(n) { std::cout << " " << #n << "... "; }
|
||||
#define P() { std::cout << "PASS\n"; ++p; }
|
||||
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
|
||||
#define C(c, m) if (!(c)) { F(m); return; }
|
||||
|
||||
void t1() {
|
||||
T(default_selection_has_no_selection);
|
||||
ws::LibrarySelection s;
|
||||
C(!s.hasSelection(), "hasSelection false by default");
|
||||
C(s.selectedLibrary.empty(), "empty selectedLibrary");
|
||||
C(s.capabilityScore == 0.0f, "score 0");
|
||||
C(s.preferredAPIs.empty(), "no APIs");
|
||||
C(s.avoidedLibraries.empty(), "no avoided");
|
||||
P();
|
||||
}
|
||||
|
||||
void t2() {
|
||||
T(populated_selection_fields);
|
||||
ws::LibrarySelection s;
|
||||
s.selectedLibrary = "nlohmann_json";
|
||||
s.operationDomain = "serialization.json";
|
||||
s.capabilityScore = 0.92f;
|
||||
s.preferredAPIs = {"nlohmann::json::dump()", "nlohmann::json::parse()"};
|
||||
s.justification = "nlohmann_json scores 0.92 vs best alternative protobuf at 0.60";
|
||||
C(s.hasSelection(), "hasSelection true");
|
||||
C(s.selectedLibrary == "nlohmann_json", "selectedLibrary");
|
||||
C(s.capabilityScore == 0.92f, "capabilityScore");
|
||||
C(s.preferredAPIs.size() == 2, "2 preferredAPIs");
|
||||
C(!s.justification.empty(), "justification set");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3() {
|
||||
T(avoided_library_struct_fields);
|
||||
ws::AvoidedLibrary av;
|
||||
av.name = "protobuf";
|
||||
av.reason = "score 0.60 < selected 0.92";
|
||||
av.score = 0.60f;
|
||||
C(av.name == "protobuf", "name");
|
||||
C(av.score == 0.60f, "score");
|
||||
C(av.reason.find("0.60") != std::string::npos, "reason contains score");
|
||||
C(av.reason.find("0.92") != std::string::npos, "reason contains selected score");
|
||||
P();
|
||||
}
|
||||
|
||||
void t4() {
|
||||
T(multiple_avoided_libraries_sorted_by_score_desc);
|
||||
ws::LibrarySelection s;
|
||||
s.selectedLibrary = "serde_json";
|
||||
s.capabilityScore = 0.98f;
|
||||
s.avoidedLibraries = {
|
||||
{"nlohmann_json", "score 0.92 < selected 0.98", 0.92f},
|
||||
{"protobuf", "score 0.60 < selected 0.98", 0.60f},
|
||||
};
|
||||
C(s.avoidedLibraries.size() == 2, "2 avoided");
|
||||
C(s.avoidedLibraries[0].name == "nlohmann_json", "best avoided first");
|
||||
C(s.avoidedLibraries[0].score > s.avoidedLibraries[1].score, "sorted desc");
|
||||
P();
|
||||
}
|
||||
|
||||
void t5() {
|
||||
T(selection_with_no_avoided_only_one_qualified);
|
||||
ws::LibrarySelection s;
|
||||
s.selectedLibrary = "thrust";
|
||||
s.operationDomain = "compute.parallel";
|
||||
s.capabilityScore = 0.97f;
|
||||
s.justification = "thrust scores 0.97 (only qualified library)";
|
||||
C(s.hasSelection(), "hasSelection");
|
||||
C(s.avoidedLibraries.empty(), "no avoided when only one qualifies");
|
||||
C(s.justification.find("only") != std::string::npos, "justification says only");
|
||||
P();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 1963: LibrarySelection\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
75
editor/tests/step1964_test.cpp
Normal file
75
editor/tests/step1964_test.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Step 1964: OperationClassifier
|
||||
//
|
||||
// t1: classify known text returns isKnown=true, confidence=1.0
|
||||
// t2: classify unknown text returns isKnown=false, confidence=0.0
|
||||
// t3: classify "serialize to JSON" -> serialization.json
|
||||
// t4: classify "matrix multiply" -> compute.matrix
|
||||
// t5: withDefaultTaxonomy() builds correctly; taxonomy() accessible
|
||||
|
||||
#include "OperationClassifier.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
static int p = 0, f = 0;
|
||||
#define T(n) { std::cout << " " << #n << "... "; }
|
||||
#define P() { std::cout << "PASS\n"; ++p; }
|
||||
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
|
||||
#define C(c, m) if (!(c)) { F(m); return; }
|
||||
|
||||
void t1() {
|
||||
T(known_text_returns_confidence_1);
|
||||
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
|
||||
auto r = cls.classify("serialize to JSON");
|
||||
C(r.isKnown, "isKnown true");
|
||||
C(r.confidence == 1.0f, "confidence 1.0");
|
||||
C(!r.domain.empty(), "domain not empty");
|
||||
C(r.domain != "unknown", "domain is not unknown");
|
||||
P();
|
||||
}
|
||||
|
||||
void t2() {
|
||||
T(unknown_text_returns_confidence_0);
|
||||
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
|
||||
auto r = cls.classify("xyzzy frobble zogzog");
|
||||
C(!r.isKnown, "isKnown false");
|
||||
C(r.confidence == 0.0f, "confidence 0.0");
|
||||
C(r.domain == "unknown", "domain is unknown");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3() {
|
||||
T(classify_json_text);
|
||||
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
|
||||
auto r = cls.classify("serialize to JSON");
|
||||
C(r.domain == "serialization.json",
|
||||
"expected serialization.json, got " + r.domain);
|
||||
P();
|
||||
}
|
||||
|
||||
void t4() {
|
||||
T(classify_matrix_multiply_text);
|
||||
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
|
||||
auto r = cls.classify("matrix multiply on GPU");
|
||||
C(r.domain == "compute.matrix",
|
||||
"expected compute.matrix, got " + r.domain);
|
||||
P();
|
||||
}
|
||||
|
||||
void t5() {
|
||||
T(withDefaultTaxonomy_and_taxonomy_accessor);
|
||||
auto cls = ws::OperationClassifier::withDefaultTaxonomy();
|
||||
C(cls.taxonomy().domainCount() >= 40, ">=40 domains");
|
||||
auto cats = cls.taxonomy().topLevelCategories();
|
||||
C(cats.size() >= 6, ">=6 top-level categories");
|
||||
// Classify a few more domains
|
||||
C(cls.classify("sql select query").domain == "db.query.sql", "sql");
|
||||
C(cls.classify("compute sha256 hash").domain == "crypto.hash", "hash");
|
||||
P();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 1964: OperationClassifier\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
88
editor/tests/step1965_test.cpp
Normal file
88
editor/tests/step1965_test.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Step 1965: PerTaskLibrarySelector
|
||||
//
|
||||
// t1: selects nlohmann_json (0.92) over protobuf (0.60) for serialization.json
|
||||
// t2: selects cublas (0.99) over numpy (0.95) for compute.matrix
|
||||
// t3: avoidedLibraries populated correctly (sorted desc)
|
||||
// t4: returns empty selection when no library has score > 0
|
||||
// t5: single qualified library: no avoided, justification says "only"
|
||||
|
||||
#include "PerTaskLibrarySelector.h"
|
||||
#include "Sprint286IntegrationSummary.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
static int p = 0, f = 0;
|
||||
#define T(n) { std::cout << " " << #n << "... "; }
|
||||
#define P() { std::cout << "PASS\n"; ++p; }
|
||||
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
|
||||
#define C(c, m) if (!(c)) { F(m); return; }
|
||||
|
||||
void t1() {
|
||||
T(selects_nlohmann_over_protobuf_for_json);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
auto r = sel.select("serialization.json", {"nlohmann_json", "protobuf"}, ledger);
|
||||
C(r.hasSelection(), "hasSelection");
|
||||
C(r.selectedLibrary == "nlohmann_json", "nlohmann_json selected");
|
||||
C(r.capabilityScore == 0.92f, "score 0.92");
|
||||
C(!r.justification.empty(), "justification set");
|
||||
C(r.justification.find("nlohmann_json") != std::string::npos, "justification names winner");
|
||||
P();
|
||||
}
|
||||
|
||||
void t2() {
|
||||
T(selects_cublas_over_numpy_for_compute_matrix);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
auto r = sel.select("compute.matrix", {"cublas", "numpy"}, ledger);
|
||||
C(r.selectedLibrary == "cublas", "cublas selected");
|
||||
C(r.capabilityScore == 0.99f, "score 0.99");
|
||||
C(!r.preferredAPIs.empty(), "preferredAPIs populated");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3() {
|
||||
T(avoided_libraries_sorted_desc);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
// Add serde_json so there are 3 candidates for serialization.json
|
||||
ledger.set({"serde_json", "serialization.json", 0.98f});
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
auto r = sel.select("serialization.json",
|
||||
{"nlohmann_json", "protobuf", "serde_json"}, ledger);
|
||||
C(r.selectedLibrary == "serde_json", "serde_json selected (0.98)");
|
||||
C(r.avoidedLibraries.size() == 2, "2 avoided");
|
||||
C(r.avoidedLibraries[0].name == "nlohmann_json", "nlohmann first avoided (0.92)");
|
||||
C(r.avoidedLibraries[0].score > r.avoidedLibraries[1].score, "sorted desc");
|
||||
C(r.avoidedLibraries[1].name == "protobuf", "protobuf second avoided (0.60)");
|
||||
P();
|
||||
}
|
||||
|
||||
void t4() {
|
||||
T(empty_selection_when_no_score);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
// "cublas" has no score for "serialization.json"
|
||||
auto r = sel.select("serialization.json", {"cublas"}, ledger);
|
||||
C(!r.hasSelection(), "no selection");
|
||||
C(r.selectedLibrary.empty(), "empty selectedLibrary");
|
||||
C(r.justification.find("no library") != std::string::npos, "justification says no library");
|
||||
P();
|
||||
}
|
||||
|
||||
void t5() {
|
||||
T(single_qualified_library_no_avoided);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
auto r = sel.select("compute.parallel", {"thrust"}, ledger);
|
||||
C(r.selectedLibrary == "thrust", "thrust selected");
|
||||
C(r.avoidedLibraries.empty(), "no avoided");
|
||||
C(r.justification.find("only") != std::string::npos, "justification says only");
|
||||
P();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 1965: PerTaskLibrarySelector\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
99
editor/tests/step1966_test.cpp
Normal file
99
editor/tests/step1966_test.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// Step 1966: SelectionJustificationLog
|
||||
//
|
||||
// t1: append and size
|
||||
// t2: findByDomain returns matching entries only
|
||||
// t3: findByLibrary filters correctly
|
||||
// t4: findByTask returns correct entries
|
||||
// t5: clear resets the log; entries() ordering preserved
|
||||
|
||||
#include "SelectionJustificationLog.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
static int p = 0, f = 0;
|
||||
#define T(n) { std::cout << " " << #n << "... "; }
|
||||
#define P() { std::cout << "PASS\n"; ++p; }
|
||||
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
|
||||
#define C(c, m) if (!(c)) { F(m); return; }
|
||||
|
||||
static ws::SelectionJustificationLog makeLog() {
|
||||
ws::SelectionJustificationLog log;
|
||||
log.append({"t1", "serialization.json", "nlohmann_json", 0.92f,
|
||||
"nlohmann_json scores 0.92 vs protobuf at 0.60", "2026-03-02"});
|
||||
log.append({"t2", "compute.matrix", "cublas", 0.99f,
|
||||
"cublas scores 0.99 vs numpy at 0.95", "2026-03-02"});
|
||||
log.append({"t3", "serialization.json", "serde_json", 0.98f,
|
||||
"serde_json scores 0.98 (only qualified)", "2026-03-02"});
|
||||
log.append({"t4", "compute.statistics", "numpy", 0.93f,
|
||||
"numpy scores 0.93 (only qualified library)", "2026-03-02"});
|
||||
return log;
|
||||
}
|
||||
|
||||
void t1() {
|
||||
T(append_and_size);
|
||||
ws::SelectionJustificationLog log;
|
||||
C(log.size() == 0, "empty initially");
|
||||
log.append({"t1", "serialization.json", "nlohmann_json", 0.92f, "j", "2026-03-02"});
|
||||
C(log.size() == 1, "size 1 after append");
|
||||
log.append({"t2", "compute.matrix", "cublas", 0.99f, "j", "2026-03-02"});
|
||||
C(log.size() == 2, "size 2 after second append");
|
||||
C(log.entries().size() == 2, "entries() matches size");
|
||||
P();
|
||||
}
|
||||
|
||||
void t2() {
|
||||
T(findByDomain_filters_correctly);
|
||||
auto log = makeLog();
|
||||
auto json_entries = log.findByDomain("serialization.json");
|
||||
C(json_entries.size() == 2, "2 serialization.json entries");
|
||||
C(json_entries[0].taskId == "t1", "first is t1");
|
||||
C(json_entries[1].taskId == "t3", "second is t3");
|
||||
auto none = log.findByDomain("network.async");
|
||||
C(none.empty(), "no entries for network.async");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3() {
|
||||
T(findByLibrary_filters_correctly);
|
||||
auto log = makeLog();
|
||||
auto cublas = log.findByLibrary("cublas");
|
||||
C(cublas.size() == 1, "1 cublas entry");
|
||||
C(cublas[0].operationDomain == "compute.matrix", "correct domain");
|
||||
auto numpy = log.findByLibrary("numpy");
|
||||
C(numpy.size() == 1, "1 numpy entry");
|
||||
C(numpy[0].capabilityScore == 0.93f, "correct score");
|
||||
P();
|
||||
}
|
||||
|
||||
void t4() {
|
||||
T(findByTask_returns_correct_entry);
|
||||
auto log = makeLog();
|
||||
auto t2 = log.findByTask("t2");
|
||||
C(t2.size() == 1, "1 t2 entry");
|
||||
C(t2[0].selectedLibrary == "cublas", "cublas for t2");
|
||||
auto missing = log.findByTask("t99");
|
||||
C(missing.empty(), "no t99 entry");
|
||||
P();
|
||||
}
|
||||
|
||||
void t5() {
|
||||
T(clear_resets_and_ordering_preserved);
|
||||
auto log = makeLog();
|
||||
C(log.size() == 4, "4 entries before clear");
|
||||
log.clear();
|
||||
C(log.size() == 0, "empty after clear");
|
||||
C(log.entries().empty(), "entries() empty after clear");
|
||||
// Re-append and verify ordering preserved
|
||||
log.append({"a", "serialization.json", "nlohmann_json", 0.92f, "j", "2026-03-02"});
|
||||
log.append({"b", "compute.matrix", "cublas", 0.99f, "j", "2026-03-02"});
|
||||
C(log.entries()[0].taskId == "a", "first entry preserved");
|
||||
C(log.entries()[1].taskId == "b", "second entry preserved");
|
||||
P();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 1966: SelectionJustificationLog\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
83
editor/tests/step1967_test.cpp
Normal file
83
editor/tests/step1967_test.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// Step 1967: Sprint 287 Integration
|
||||
//
|
||||
// t1: all 8 tasks produce correct library selection
|
||||
// t2: verifyAllSelections() passes
|
||||
// t3: avoidedLibraries present for tasks with multiple candidates
|
||||
// t4: tasks with single qualified library have no avoided entries
|
||||
// t5: Sprint287IntegrationSummary metadata correct
|
||||
|
||||
#include "Sprint287IntegrationSummary.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
static int p = 0, f = 0;
|
||||
#define T(n) { std::cout << " " << #n << "... "; }
|
||||
#define P() { std::cout << "PASS\n"; ++p; }
|
||||
#define F(m) { std::cout << "FAIL: " << m << "\n"; ++f; }
|
||||
#define C(c, m) if (!(c)) { F(m); return; }
|
||||
|
||||
void t1() {
|
||||
T(all_8_tasks_correct_library);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
auto tasks = ws::Sprint287IntegrationSummary::buildTaskSpec();
|
||||
C(tasks.size() == 8, "8 tasks");
|
||||
for (const auto& task : tasks) {
|
||||
auto r = sel.select(task.operationDomain, task.availableLibraries, ledger);
|
||||
C(r.selectedLibrary == task.expectedLibrary,
|
||||
task.taskId + ": expected " + task.expectedLibrary + " got " + r.selectedLibrary);
|
||||
}
|
||||
P();
|
||||
}
|
||||
|
||||
void t2() {
|
||||
T(verifyAllSelections_passes);
|
||||
C(ws::Sprint287IntegrationSummary::verifyAllSelections(), "verifyAllSelections true");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3() {
|
||||
T(avoided_libraries_present_for_multi_candidate_tasks);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
// t1: nlohmann vs protobuf -> 1 avoided
|
||||
auto r1 = sel.select("serialization.json", {"nlohmann_json", "protobuf"}, ledger);
|
||||
C(!r1.avoidedLibraries.empty(), "t1 has avoided");
|
||||
C(r1.avoidedLibraries[0].name == "protobuf", "t1 avoided is protobuf");
|
||||
// t5: tokio vs boost.asio -> 1 avoided
|
||||
auto r5 = sel.select("network.async", {"tokio", "boost.asio"}, ledger);
|
||||
C(!r5.avoidedLibraries.empty(), "t5 has avoided");
|
||||
C(r5.avoidedLibraries[0].name == "boost.asio", "t5 avoided is boost.asio");
|
||||
P();
|
||||
}
|
||||
|
||||
void t4() {
|
||||
T(single_qualified_has_no_avoided);
|
||||
auto ledger = ws::Sprint286IntegrationSummary::buildSeedLedger();
|
||||
ws::PerTaskLibrarySelector sel;
|
||||
// t4: only numpy for compute.statistics
|
||||
auto r4 = sel.select("compute.statistics", {"numpy"}, ledger);
|
||||
C(r4.selectedLibrary == "numpy", "numpy selected");
|
||||
C(r4.avoidedLibraries.empty(), "no avoided for t4");
|
||||
// t6: only thrust for compute.parallel
|
||||
auto r6 = sel.select("compute.parallel", {"thrust"}, ledger);
|
||||
C(r6.selectedLibrary == "thrust", "thrust selected");
|
||||
C(r6.avoidedLibraries.empty(), "no avoided for t6");
|
||||
P();
|
||||
}
|
||||
|
||||
void t5() {
|
||||
T(sprint287_metadata);
|
||||
ws::Sprint287IntegrationSummary s;
|
||||
C(s.stepsCompleted == 5, "5 steps");
|
||||
C(s.success, "success");
|
||||
C(s.sprintName() == "Sprint 287: PerTaskLibrarySelector", "name");
|
||||
P();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 1967: Sprint 287 Integration\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p + f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user