Complete Step 493: self-host codebase parse audit

This commit is contained in:
Bill
2026-02-16 22:01:39 -07:00
parent 1c6c6879c7
commit 9ce1ad3386
4 changed files with 359 additions and 0 deletions

View File

@@ -3316,4 +3316,13 @@ target_link_libraries(step492_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step493_test tests/step493_test.cpp)
target_include_directories(step493_test PRIVATE src)
target_link_libraries(step493_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,139 @@
#pragma once
// Step 493: Parse Full Whetstone Codebase
// Audits C++ parser coverage across header files.
#include "SelfHostHarness.h"
#include <filesystem>
#include <fstream>
#include <regex>
#include <set>
#include <string>
#include <vector>
struct FileParseAudit {
std::string path;
bool parseSuccess = false;
int classCount = 0;
int functionCount = 0;
int annotationNodeCount = 0;
int textualClassHints = 0;
int textualFunctionHints = 0;
std::vector<std::string> skippedConstructs;
std::vector<std::string> warnings;
};
struct CodebaseParseAudit {
std::vector<FileParseAudit> files;
int totalFiles = 0;
int parsedFiles = 0;
int totalClasses = 0;
int totalFunctions = 0;
int totalAnnotationNodes = 0;
int skippedClassConstructs = 0;
int skippedFunctionConstructs = 0;
std::vector<std::string> unparseableConstructLog;
double parseRate() const {
return totalFiles > 0 ? static_cast<double>(parsedFiles) / totalFiles : 1.0;
}
bool meetsTarget(double target = 0.95) const {
return parseRate() >= target;
}
};
class SelfHostCodebaseAudit {
public:
static std::vector<std::string> listHeaderFiles(const std::vector<std::string>& roots) {
std::set<std::string> unique;
std::error_code ec;
for (const auto& root : roots) {
if (!std::filesystem::exists(root, ec)) continue;
for (const auto& entry : std::filesystem::recursive_directory_iterator(root, ec)) {
if (ec) break;
if (!entry.is_regular_file()) continue;
const auto path = entry.path().string();
if (endsWith(path, ".h")) unique.insert(path);
}
}
return {unique.begin(), unique.end()};
}
static CodebaseParseAudit auditDirectories(const std::vector<std::string>& roots) {
return auditFiles(listHeaderFiles(roots));
}
static CodebaseParseAudit auditFiles(const std::vector<std::string>& files) {
CodebaseParseAudit out;
out.totalFiles = static_cast<int>(files.size());
for (const auto& path : files) {
auto fa = auditFile(path);
out.files.push_back(fa);
if (fa.parseSuccess) out.parsedFiles++;
out.totalClasses += fa.classCount;
out.totalFunctions += fa.functionCount;
out.totalAnnotationNodes += fa.annotationNodeCount;
for (const auto& s : fa.skippedConstructs) {
if (s == "class") out.skippedClassConstructs++;
if (s == "function") out.skippedFunctionConstructs++;
out.unparseableConstructLog.push_back(path + ": skipped " + s);
}
for (const auto& w : fa.warnings) {
out.unparseableConstructLog.push_back(path + ": " + w);
}
}
return out;
}
private:
static FileParseAudit auditFile(const std::string& path) {
FileParseAudit out;
out.path = path;
const std::string source = SelfHostHarness::readFile(path);
out.textualClassHints = countRegex(source, std::regex(R"(\b(class|struct)\s+[A-Za-z_])"));
out.textualFunctionHints = countRegex(source, std::regex(R"(\b[A-Za-z_~][A-Za-z0-9_:<>~]*\s+[A-Za-z_~][A-Za-z0-9_]*\s*\([^;\)]*\)\s*[\{;])"));
auto parsed = SelfHostHarness::parseFile(path);
out.parseSuccess = parsed.parseSuccess;
out.warnings = parsed.warnings;
out.classCount = SelfHostHarness::countClasses(parsed.ast.get());
out.functionCount = SelfHostHarness::countFunctions(parsed.ast.get());
out.annotationNodeCount = countAnnotations(parsed.ast.get());
if (out.textualClassHints > out.classCount) {
out.skippedConstructs.push_back("class");
}
if (out.textualFunctionHints > out.functionCount) {
out.skippedConstructs.push_back("function");
}
return out;
}
static int countRegex(const std::string& text, const std::regex& re) {
if (text.empty()) return 0;
auto begin = std::sregex_iterator(text.begin(), text.end(), re);
auto end = std::sregex_iterator();
int count = 0;
for (auto it = begin; it != end; ++it) ++count;
return count;
}
static bool endsWith(const std::string& value, const std::string& suffix) {
return value.size() >= suffix.size() &&
value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0;
}
static int countAnnotations(const ASTNode* node) {
if (!node) return 0;
int count = 0;
if (node->conceptType.find("Annotation") != std::string::npos) ++count;
for (auto* child : node->allChildren()) {
count += countAnnotations(child);
}
return count;
}
};

View File

@@ -0,0 +1,172 @@
// Step 493: Parse Full Whetstone Codebase Tests (12 tests)
#include "SelfHostCodebaseAudit.h"
#include <filesystem>
#include <fstream>
#include <iostream>
namespace fs = std::filesystem;
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
static CodebaseParseAudit runRealAudit() {
return SelfHostCodebaseAudit::auditDirectories({"editor/src", "editor/src/ast"});
}
void test_lists_headers_from_target_directories() {
TEST(lists_headers_from_target_directories);
auto files = SelfHostCodebaseAudit::listHeaderFiles({"editor/src", "editor/src/ast"});
CHECK(!files.empty(), "expected header files");
bool foundParser = false;
for (const auto& f : files) {
if (f.find("Parser.h") != std::string::npos) foundParser = true;
}
CHECK(foundParser, "expected parser-related header");
PASS();
}
void test_audit_reports_total_file_count_nonzero() {
TEST(audit_reports_total_file_count_nonzero);
auto audit = runRealAudit();
CHECK(audit.totalFiles > 0, "expected non-zero total files");
PASS();
}
void test_parse_rate_is_within_0_to_1() {
TEST(parse_rate_is_within_0_to_1);
auto audit = runRealAudit();
CHECK(audit.parseRate() >= 0.0, "parse rate below 0");
CHECK(audit.parseRate() <= 1.0, "parse rate above 1");
PASS();
}
void test_audit_collects_class_function_and_annotation_counts() {
TEST(audit_collects_class_function_and_annotation_counts);
auto audit = runRealAudit();
CHECK(audit.totalClasses >= 0, "class count should be non-negative");
CHECK(audit.totalFunctions >= 0, "function count should be non-negative");
CHECK(audit.totalAnnotationNodes >= 0, "annotation count should be non-negative");
PASS();
}
void test_each_file_has_path_and_parse_flag() {
TEST(each_file_has_path_and_parse_flag);
auto audit = runRealAudit();
CHECK(!audit.files.empty(), "expected file audits");
for (const auto& f : audit.files) {
CHECK(!f.path.empty(), "missing file path");
}
PASS();
}
void test_skipped_constructs_log_is_populated_when_hint_exceeds_parse() {
TEST(skipped_constructs_log_is_populated_when_hint_exceeds_parse);
fs::path dir = fs::temp_directory_path() / "whetstone_step493_skip";
fs::remove_all(dir);
fs::create_directories(dir);
fs::path file = dir / "sample.h";
{
std::ofstream out(file.string());
out << "class A {};\nclass B {};\nint f();\n";
}
auto audit = SelfHostCodebaseAudit::auditFiles({file.string()});
CHECK(!audit.files.empty(), "expected single audit file");
bool hasSkipSignal = !audit.files[0].skippedConstructs.empty() || !audit.unparseableConstructLog.empty();
CHECK(hasSkipSignal, "expected skipped/log signal");
fs::remove_all(dir);
PASS();
}
void test_unreadable_or_empty_header_is_logged() {
TEST(unreadable_or_empty_header_is_logged);
fs::path dir = fs::temp_directory_path() / "whetstone_step493_empty";
fs::remove_all(dir);
fs::create_directories(dir);
fs::path file = dir / "empty.h";
{
std::ofstream out(file.string());
}
auto audit = SelfHostCodebaseAudit::auditFiles({file.string()});
CHECK(!audit.unparseableConstructLog.empty(), "expected warning log for empty file");
fs::remove_all(dir);
PASS();
}
void test_meets_target_function_matches_threshold_semantics() {
TEST(meets_target_function_matches_threshold_semantics);
CodebaseParseAudit a;
a.totalFiles = 100;
a.parsedFiles = 95;
CHECK(a.meetsTarget(0.95), "95/100 should meet 95% target");
CHECK(!a.meetsTarget(0.96), "95/100 should not meet 96% target");
PASS();
}
void test_audit_files_supports_direct_file_list_mode() {
TEST(audit_files_supports_direct_file_list_mode);
auto files = SelfHostCodebaseAudit::listHeaderFiles({"editor/src"});
CHECK(!files.empty(), "expected files from src");
std::vector<std::string> subset;
subset.push_back(files[0]);
auto audit = SelfHostCodebaseAudit::auditFiles(subset);
CHECK(audit.totalFiles == 1, "expected one file in direct mode");
PASS();
}
void test_unparseable_construct_log_contains_file_path_prefixes() {
TEST(unparseable_construct_log_contains_file_path_prefixes);
fs::path dir = fs::temp_directory_path() / "whetstone_step493_log";
fs::remove_all(dir);
fs::create_directories(dir);
fs::path file = dir / "empty.h";
{ std::ofstream out(file.string()); }
auto audit = SelfHostCodebaseAudit::auditFiles({file.string()});
CHECK(!audit.unparseableConstructLog.empty(), "expected log entries");
CHECK(audit.unparseableConstructLog[0].find(file.string()) != std::string::npos,
"log missing file path prefix");
fs::remove_all(dir);
PASS();
}
void test_parse_rate_matches_parsed_over_total_ratio() {
TEST(parse_rate_matches_parsed_over_total_ratio);
CodebaseParseAudit a;
a.totalFiles = 4;
a.parsedFiles = 3;
CHECK(a.parseRate() > 0.74 && a.parseRate() < 0.76, "expected 0.75 parse rate");
PASS();
}
void test_real_codebase_audit_has_high_reasonable_coverage_signal() {
TEST(real_codebase_audit_has_high_reasonable_coverage_signal);
auto audit = runRealAudit();
CHECK(audit.totalFiles >= 50, "expected substantial header corpus");
CHECK(audit.parsedFiles >= 1, "expected at least one parsed file");
PASS();
}
int main() {
std::cout << "Step 493: Parse Full Whetstone Codebase Tests\n";
test_lists_headers_from_target_directories(); // 1
test_audit_reports_total_file_count_nonzero(); // 2
test_parse_rate_is_within_0_to_1(); // 3
test_audit_collects_class_function_and_annotation_counts(); // 4
test_each_file_has_path_and_parse_flag(); // 5
test_skipped_constructs_log_is_populated_when_hint_exceeds_parse(); // 6
test_unreadable_or_empty_header_is_logged(); // 7
test_meets_target_function_matches_threshold_semantics(); // 8
test_audit_files_supports_direct_file_list_mode(); // 9
test_unparseable_construct_log_contains_file_path_prefixes(); // 10
test_parse_rate_matches_parsed_over_total_ratio(); // 11
test_real_codebase_audit_has_high_reasonable_coverage_signal(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -7353,3 +7353,42 @@ analysis, and security test skeleton generation.
- trust boundary + data-flow threat diagnostics (`E1300` range)
- routed security test skeleton generation
- end-to-end security pipeline integration
### Step 493: Parse Full Whetstone Codebase
**Status:** PASS (12/12 tests)
Starts Sprint 25 self-hosting by auditing parser coverage across the
Whetstone header corpus (`editor/src/` + `editor/src/ast/`), including
parse-rate metrics, construct counts, and skipped-construct logging.
**Files added:**
- `editor/src/SelfHostCodebaseAudit.h` — codebase parse audit layer:
- `FileParseAudit`, `CodebaseParseAudit`
- header discovery over target directories (`.h` recursive)
- per-file parse via existing `SelfHostHarness::parseFile(...)`
- per-file metrics:
- class count
- function count
- annotation-node count (`conceptType` contains `Annotation`)
- heuristic skipped-construct detection from textual hints vs parsed counts
- aggregate parse-rate computation + target check (`meetsTarget(0.95)`)
- unparseable/skipped construct audit log for follow-up coverage work
- `editor/tests/step493_test.cpp` — 12 tests covering:
- real directory header discovery
- aggregate metric sanity and parse-rate bounds
- per-file audit shape guarantees
- skipped/unparseable logging paths
- threshold semantics
- direct-file-list audit mode
- real-corpus scale signal checks
- `editor/CMakeLists.txt``step493_test` target
**Verification run:**
- `cmake --build editor/build-native --target step493_test step492_test` — PASS
- `./editor/build-native/step493_test` — PASS (12/12)
- `./editor/build-native/step492_test` — PASS (8/8) regression coverage
**Architecture gate check:**
- `editor/src/SelfHostCodebaseAudit.h` within header-size limit (`139` <= `600`)
- `editor/tests/step493_test.cpp` within test-file size guidance (`172` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`