From 2a40cb1ba520bf8d9bf162b6bc919f1875e84267 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 2 Mar 2026 10:04:39 -0700 Subject: [PATCH] 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 --- editor/CMakeLists.txt | 15 ++++ editor/src/LibrarySelection.h | 28 +++++++ editor/src/OperationClassifier.h | 39 ++++++++++ editor/src/PerTaskLibrarySelector.h | 90 +++++++++++++++++++++ editor/src/SelectionJustificationLog.h | 59 ++++++++++++++ editor/src/Sprint287IntegrationSummary.h | 60 ++++++++++++++ editor/tests/step1963_test.cpp | 92 ++++++++++++++++++++++ editor/tests/step1964_test.cpp | 75 ++++++++++++++++++ editor/tests/step1965_test.cpp | 88 +++++++++++++++++++++ editor/tests/step1966_test.cpp | 99 ++++++++++++++++++++++++ editor/tests/step1967_test.cpp | 83 ++++++++++++++++++++ 11 files changed, 728 insertions(+) create mode 100644 editor/src/LibrarySelection.h create mode 100644 editor/src/OperationClassifier.h create mode 100644 editor/src/PerTaskLibrarySelector.h create mode 100644 editor/src/SelectionJustificationLog.h create mode 100644 editor/src/Sprint287IntegrationSummary.h create mode 100644 editor/tests/step1963_test.cpp create mode 100644 editor/tests/step1964_test.cpp create mode 100644 editor/tests/step1965_test.cpp create mode 100644 editor/tests/step1966_test.cpp create mode 100644 editor/tests/step1967_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 25db43f..03734b5 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -11099,3 +11099,18 @@ target_include_directories(step1961_test PRIVATE src) add_executable(step1962_test tests/step1962_test.cpp) target_include_directories(step1962_test PRIVATE src) + +add_executable(step1963_test tests/step1963_test.cpp) +target_include_directories(step1963_test PRIVATE src) + +add_executable(step1964_test tests/step1964_test.cpp) +target_include_directories(step1964_test PRIVATE src) + +add_executable(step1965_test tests/step1965_test.cpp) +target_include_directories(step1965_test PRIVATE src) + +add_executable(step1966_test tests/step1966_test.cpp) +target_include_directories(step1966_test PRIVATE src) + +add_executable(step1967_test tests/step1967_test.cpp) +target_include_directories(step1967_test PRIVATE src) diff --git a/editor/src/LibrarySelection.h b/editor/src/LibrarySelection.h new file mode 100644 index 0000000..8f2bab0 --- /dev/null +++ b/editor/src/LibrarySelection.h @@ -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 +#include + +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 preferredAPIs; + std::vector avoidedLibraries; // sorted by score desc + std::string justification; + + bool hasSelection() const { return !selectedLibrary.empty(); } +}; + +} // namespace whetstone diff --git a/editor/src/OperationClassifier.h b/editor/src/OperationClassifier.h new file mode 100644 index 0000000..1ad1b9f --- /dev/null +++ b/editor/src/OperationClassifier.h @@ -0,0 +1,39 @@ +#pragma once +// Step 1964: OperationClassifier +// Wraps OperationTaxonomy with a ClassificationResult that includes +// confidence and isKnown flags. + +#include "OperationTaxonomy.h" +#include + +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 diff --git a/editor/src/PerTaskLibrarySelector.h b/editor/src/PerTaskLibrarySelector.h new file mode 100644 index 0000000..dadb068 --- /dev/null +++ b/editor/src/PerTaskLibrarySelector.h @@ -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 +#include +#include +#include +#include + +namespace whetstone { + +class PerTaskLibrarySelector { +public: + // Core selection: deterministic, no inference. + LibrarySelection select(const std::string& operationDomain, + const std::vector& 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 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 diff --git a/editor/src/SelectionJustificationLog.h b/editor/src/SelectionJustificationLog.h new file mode 100644 index 0000000..77ad18c --- /dev/null +++ b/editor/src/SelectionJustificationLog.h @@ -0,0 +1,59 @@ +#pragma once +// Step 1966: SelectionJustificationLog +// Append-only auditable log of every library selection decision. + +#include +#include + +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& entries() const { return entries_; } + + size_t size() const { return entries_.size(); } + + std::vector findByDomain(const std::string& domain) const { + std::vector result; + for (const auto& e : entries_) { + if (e.operationDomain == domain) result.push_back(e); + } + return result; + } + + std::vector findByLibrary(const std::string& lib) const { + std::vector result; + for (const auto& e : entries_) { + if (e.selectedLibrary == lib) result.push_back(e); + } + return result; + } + + std::vector findByTask(const std::string& taskId) const { + std::vector result; + for (const auto& e : entries_) { + if (e.taskId == taskId) result.push_back(e); + } + return result; + } + + void clear() { entries_.clear(); } + +private: + std::vector entries_; +}; + +} // namespace whetstone diff --git a/editor/src/Sprint287IntegrationSummary.h b/editor/src/Sprint287IntegrationSummary.h new file mode 100644 index 0000000..3282ff2 --- /dev/null +++ b/editor/src/Sprint287IntegrationSummary.h @@ -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 +#include + +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 availableLibraries; + std::string expectedLibrary; + }; + + // 8 tasks covering all seed libraries; each task has a verifiable expected selection. + static std::vector 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 diff --git a/editor/tests/step1963_test.cpp b/editor/tests/step1963_test.cpp new file mode 100644 index 0000000..474a6ee --- /dev/null +++ b/editor/tests/step1963_test.cpp @@ -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 + +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; +} diff --git a/editor/tests/step1964_test.cpp b/editor/tests/step1964_test.cpp new file mode 100644 index 0000000..3e5e586 --- /dev/null +++ b/editor/tests/step1964_test.cpp @@ -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 + +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; +} diff --git a/editor/tests/step1965_test.cpp b/editor/tests/step1965_test.cpp new file mode 100644 index 0000000..789e2d7 --- /dev/null +++ b/editor/tests/step1965_test.cpp @@ -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 + +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; +} diff --git a/editor/tests/step1966_test.cpp b/editor/tests/step1966_test.cpp new file mode 100644 index 0000000..8d4b512 --- /dev/null +++ b/editor/tests/step1966_test.cpp @@ -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 + +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; +} diff --git a/editor/tests/step1967_test.cpp b/editor/tests/step1967_test.cpp new file mode 100644 index 0000000..a1934dc --- /dev/null +++ b/editor/tests/step1967_test.cpp @@ -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 + +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; +}