Skip to content

Commit a21d2f3

Browse files
Antigravity Agentclaude
andcommitted
feat(farm): add outbound WebSocket stream for training events
EventBus ring buffer + RFC 6455 framing + 16-client broadcaster. 7 notifyWsBus integration points (kill, spawn, inject, tune). Fix pre-existing .cwd type bug in tri_doctor.zig. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 669e054 commit a21d2f3

3 files changed

Lines changed: 4245 additions & 124 deletions

File tree

src/tri/tri_doctor.zig

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,348 @@ fn notifyTelegramMsg(allocator: Allocator, msg: []const u8) void {
953953
allocator.free(result.stderr);
954954
}
955955

956+
// ═══════════════════════════════════════════════════════════════════════════════
957+
// JUNK MONITOR — tracks untracked files and archive state
958+
// ═══════════════════════════════════════════════════════════════════════════════
959+
960+
const JUNK_ARCHIVE_DIR = "archive/junk-2026-03-15";
961+
962+
const JUNK_WHITELIST = [_][]const u8{
963+
"archive/",
964+
"papers/trinity-fpga/",
965+
"reports/",
966+
"src/hslm/",
967+
"src/tri/",
968+
".github/",
969+
".trinity/",
970+
"deploy/",
971+
"fpga/tools/uart_measure.zig",
972+
};
973+
974+
pub fn runJunk(allocator: Allocator) !void {
975+
std.debug.print("\n{s}{s}TRINITY DOCTOR \xe2\x80\x94 JUNK MONITOR{s}\n\n", .{ BOLD, CYAN, RESET });
976+
977+
// 1. Check archive
978+
const archive_exists = blk: {
979+
std.fs.cwd().access(JUNK_ARCHIVE_DIR, .{}) catch break :blk false;
980+
break :blk true;
981+
};
982+
983+
if (archive_exists) {
984+
std.debug.print(" {s}\xe2\x9c\x93{s} Archive: {s}\n", .{ GREEN, RESET, JUNK_ARCHIVE_DIR });
985+
printArchiveStats(allocator);
986+
} else {
987+
std.debug.print(" {s}\xe2\x9c\x97{s} Archive not found: {s}\n", .{ RED, RESET, JUNK_ARCHIVE_DIR });
988+
}
989+
990+
// 2. Untracked files via git
991+
std.debug.print("\n {s}Untracked files:{s}\n", .{ CYAN, RESET });
992+
const result = std.process.Child.run(.{
993+
.allocator = allocator,
994+
.argv = &.{ "git", "status", "--porcelain", "-s" },
995+
.max_output_bytes = 65536,
996+
}) catch {
997+
std.debug.print(" {s}Cannot run git status{s}\n", .{ RED, RESET });
998+
return;
999+
};
1000+
defer allocator.free(result.stdout);
1001+
defer allocator.free(result.stderr);
1002+
1003+
var untracked_total: u32 = 0;
1004+
var junk_count: u32 = 0;
1005+
var junk_paths: [64][256]u8 = undefined;
1006+
var junk_lens: [64]usize = [_]usize{0} ** 64;
1007+
1008+
var it = std.mem.splitScalar(u8, result.stdout, '\n');
1009+
while (it.next()) |line| {
1010+
if (line.len < 4) continue;
1011+
if (!std.mem.startsWith(u8, line, "??")) continue;
1012+
const path = std.mem.trimLeft(u8, line[3..], " ");
1013+
untracked_total += 1;
1014+
1015+
var whitelisted = false;
1016+
for (JUNK_WHITELIST) |wl| {
1017+
if (std.mem.startsWith(u8, path, wl)) {
1018+
whitelisted = true;
1019+
break;
1020+
}
1021+
}
1022+
if (!whitelisted) {
1023+
if (junk_count < 64) {
1024+
const len = @min(path.len, 256);
1025+
@memcpy(junk_paths[junk_count][0..len], path[0..len]);
1026+
junk_lens[junk_count] = len;
1027+
}
1028+
junk_count += 1;
1029+
}
1030+
}
1031+
1032+
const whitelisted_count = untracked_total - junk_count;
1033+
std.debug.print(" Total untracked: {d}\n", .{untracked_total});
1034+
std.debug.print(" Whitelisted: {s}{d}{s} (expected)\n", .{ GREEN, whitelisted_count, RESET });
1035+
1036+
if (junk_count == 0) {
1037+
std.debug.print(" New junk: {s}0{s} \xe2\x9c\x93\n", .{ GREEN, RESET });
1038+
std.debug.print("\n {s}CLEAN{s} \xe2\x80\x94 no new junk files detected\n\n", .{ GREEN, RESET });
1039+
} else {
1040+
std.debug.print(" New junk: {s}{d}{s} \xe2\x9a\xa0\n", .{ YELLOW, junk_count, RESET });
1041+
std.debug.print("\n {s}New junk files:{s}\n", .{ YELLOW, RESET });
1042+
for (0..@min(junk_count, 64)) |i| {
1043+
std.debug.print(" \xe2\x80\xa2 {s}\n", .{junk_paths[i][0..junk_lens[i]]});
1044+
}
1045+
if (junk_count > 64) {
1046+
std.debug.print(" ... and {d} more\n", .{junk_count - 64});
1047+
}
1048+
std.debug.print("\n Run: mv <files> {s}/misc/\n\n", .{JUNK_ARCHIVE_DIR});
1049+
}
1050+
}
1051+
1052+
fn printArchiveStats(allocator: Allocator) void {
1053+
const subdirs = [_][]const u8{
1054+
"fpga-mem-weights",
1055+
"fpga-nested-duplicates",
1056+
"fpga-synth-reports",
1057+
"fpga-test-binaries",
1058+
"fpga-misc",
1059+
"fpga-weights",
1060+
"claude-bak",
1061+
"checkpoints-smoke",
1062+
"root-test-files",
1063+
"misc",
1064+
};
1065+
for (subdirs) |subdir| {
1066+
var path_buf: [256]u8 = undefined;
1067+
const full = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ JUNK_ARCHIVE_DIR, subdir }) catch continue;
1068+
1069+
var count: u32 = 0;
1070+
var dir = std.fs.cwd().openDir(full, .{ .iterate = true }) catch continue;
1071+
defer dir.close();
1072+
1073+
var dir_iter = dir.iterate();
1074+
while (dir_iter.next() catch null) |_| {
1075+
count += 1;
1076+
}
1077+
if (count > 0) {
1078+
std.debug.print(" {s}: {d} files\n", .{ subdir, count });
1079+
}
1080+
}
1081+
_ = allocator;
1082+
}
1083+
1084+
// ═══════════════════════════════════════════════════════════════════════════════
1085+
// DOCS MONITOR — checks documentation freshness and data accuracy
1086+
// ═══════════════════════════════════════════════════════════════════════════════
1087+
1088+
const DocsCheck = struct {
1089+
name: []const u8,
1090+
passed: bool,
1091+
detail: []const u8,
1092+
};
1093+
1094+
pub fn runDocs(allocator: Allocator) !void {
1095+
std.debug.print("\n{s}{s}TRINITY DOCTOR \xe2\x80\x94 DOCS MONITOR{s}\n\n", .{ BOLD, CYAN, RESET });
1096+
1097+
var checks_buf: [16]DocsCheck = undefined;
1098+
var check_count: usize = 0;
1099+
1100+
// 1. Check docs/ directory exists
1101+
const docs_exists = blk: {
1102+
std.fs.cwd().access("docs/docusaurus.config.ts", .{}) catch break :blk false;
1103+
break :blk true;
1104+
};
1105+
checks_buf[check_count] = .{
1106+
.name = "docs/ directory",
1107+
.passed = docs_exists,
1108+
.detail = if (docs_exists) "docusaurus.config.ts found" else "MISSING docs/docusaurus.config.ts",
1109+
};
1110+
check_count += 1;
1111+
1112+
if (!docs_exists) {
1113+
printDocsReport(checks_buf[0..check_count]);
1114+
return;
1115+
}
1116+
1117+
// 2. Check docs build (node_modules present)
1118+
const nm_exists = blk: {
1119+
std.fs.cwd().access("docs/node_modules/.package-lock.json", .{}) catch break :blk false;
1120+
break :blk true;
1121+
};
1122+
checks_buf[check_count] = .{
1123+
.name = "node_modules",
1124+
.passed = nm_exists,
1125+
.detail = if (nm_exists) "installed" else "MISSING \xe2\x80\x94 run: cd docs && npm install",
1126+
};
1127+
check_count += 1;
1128+
1129+
// 3. Compare source freshness: README.md vs docs/docs/intro.md
1130+
const readme_ts = getFileMtime("README.md");
1131+
const intro_ts = getFileMtime("docs/docs/intro.md");
1132+
const readme_fresh = intro_ts >= readme_ts or readme_ts == 0;
1133+
checks_buf[check_count] = .{
1134+
.name = "intro.md freshness",
1135+
.passed = readme_fresh,
1136+
.detail = if (readme_fresh) "up to date with README.md" else "STALE \xe2\x80\x94 README.md newer than docs/docs/intro.md",
1137+
};
1138+
check_count += 1;
1139+
1140+
// 4. Check CLI docs count vs actual tri commands
1141+
const cli_doc_count = countFilesInDir("docs/docs/cli");
1142+
const cli_ok = cli_doc_count >= 30;
1143+
checks_buf[check_count] = .{
1144+
.name = "CLI docs coverage",
1145+
.passed = cli_ok,
1146+
.detail = if (cli_ok) "adequate coverage" else "LOW \xe2\x80\x94 less than 30 CLI doc pages",
1147+
};
1148+
check_count += 1;
1149+
1150+
// 5. Check benchmarks freshness vs EXPERIENCE_LOG.md
1151+
const exp_ts = getFileMtime("EXPERIENCE_LOG.md");
1152+
const bench_ts = getFileMtime("docs/docs/benchmarks/index.md");
1153+
const bench_fresh = bench_ts >= exp_ts or exp_ts == 0 or bench_ts == 0;
1154+
checks_buf[check_count] = .{
1155+
.name = "benchmarks freshness",
1156+
.passed = bench_fresh,
1157+
.detail = if (bench_fresh) "up to date" else "STALE \xe2\x80\x94 EXPERIENCE_LOG.md newer than benchmarks",
1158+
};
1159+
check_count += 1;
1160+
1161+
// 6. Check FPGA docs vs synthesis results
1162+
const fpga_src_ts = getFileMtime("fpga/openxc7-synth/hslm_full_top.bit");
1163+
const fpga_doc_ts = getFileMtime("docs/docs/fpga/TECHNOLOGY_TREE_ACTION_PLAN.md");
1164+
const fpga_fresh = fpga_doc_ts >= fpga_src_ts or fpga_src_ts == 0 or fpga_doc_ts == 0;
1165+
checks_buf[check_count] = .{
1166+
.name = "FPGA docs freshness",
1167+
.passed = fpga_fresh,
1168+
.detail = if (fpga_fresh) "up to date" else "STALE \xe2\x80\x94 bitstream newer than FPGA docs",
1169+
};
1170+
check_count += 1;
1171+
1172+
// 7. Check key data in intro.md matches README
1173+
const data_match = checkIntroData(allocator);
1174+
checks_buf[check_count] = .{
1175+
.name = "intro.md data accuracy",
1176+
.passed = data_match,
1177+
.detail = if (data_match) "key numbers match README" else "MISMATCH \xe2\x80\x94 intro.md numbers differ from README.md",
1178+
};
1179+
check_count += 1;
1180+
1181+
// 8. Check API docs coverage
1182+
const api_count = countFilesInDir("docs/docs/api");
1183+
const api_ok = api_count >= 10;
1184+
checks_buf[check_count] = .{
1185+
.name = "API docs coverage",
1186+
.passed = api_ok,
1187+
.detail = if (api_ok) "adequate coverage" else "LOW \xe2\x80\x94 less than 10 API doc pages",
1188+
};
1189+
check_count += 1;
1190+
1191+
// 9. Try docs build (quick dry-run check)
1192+
std.debug.print(" Building docs (dry run)... ", .{});
1193+
const build_ok = tryDocsBuild(allocator);
1194+
checks_buf[check_count] = .{
1195+
.name = "docs build",
1196+
.passed = build_ok,
1197+
.detail = if (build_ok) "builds successfully" else "BROKEN \xe2\x80\x94 cd docs && npm run build fails",
1198+
};
1199+
check_count += 1;
1200+
1201+
printDocsReport(checks_buf[0..check_count]);
1202+
}
1203+
1204+
fn getFileMtime(path: []const u8) i128 {
1205+
const file = std.fs.cwd().openFile(path, .{}) catch return 0;
1206+
defer file.close();
1207+
const stat = file.stat() catch return 0;
1208+
return stat.mtime;
1209+
}
1210+
1211+
fn countFilesInDir(dir_path: []const u8) u32 {
1212+
var count: u32 = 0;
1213+
var dir = std.fs.cwd().openDir(dir_path, .{ .iterate = true }) catch return 0;
1214+
defer dir.close();
1215+
var it = dir.iterate();
1216+
while (it.next() catch null) |entry| {
1217+
if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".md")) {
1218+
count += 1;
1219+
}
1220+
}
1221+
return count;
1222+
}
1223+
1224+
fn checkIntroData(allocator: Allocator) bool {
1225+
// Check that key numbers from README appear in intro.md
1226+
const readme = std.fs.cwd().readFileAlloc(allocator, "README.md", 65536) catch return true;
1227+
defer allocator.free(readme);
1228+
const intro = std.fs.cwd().readFileAlloc(allocator, "docs/docs/intro.md", 65536) catch return true;
1229+
defer allocator.free(intro);
1230+
1231+
// Check for key markers that should be in both
1232+
const markers = [_][]const u8{
1233+
"1.58 bits",
1234+
"20x",
1235+
"Trinity",
1236+
};
1237+
for (markers) |m| {
1238+
if (std.mem.indexOf(u8, readme, m) != null) {
1239+
if (std.mem.indexOf(u8, intro, m) == null) return false;
1240+
}
1241+
}
1242+
return true;
1243+
}
1244+
1245+
fn tryDocsBuild(allocator: Allocator) bool {
1246+
const result = std.process.Child.run(.{
1247+
.allocator = allocator,
1248+
.argv = &.{ "npm", "run", "build" },
1249+
.cwd = "docs",
1250+
.max_output_bytes = 65536,
1251+
}) catch {
1252+
std.debug.print("{s}FAIL{s}\n", .{ RED, RESET });
1253+
return false;
1254+
};
1255+
defer allocator.free(result.stdout);
1256+
defer allocator.free(result.stderr);
1257+
1258+
const exit = switch (result.term) {
1259+
.Exited => |code| code,
1260+
else => 1,
1261+
};
1262+
if (exit == 0) {
1263+
std.debug.print("{s}OK{s}\n", .{ GREEN, RESET });
1264+
} else {
1265+
std.debug.print("{s}FAIL{s}\n", .{ RED, RESET });
1266+
}
1267+
return exit == 0;
1268+
}
1269+
1270+
fn printDocsReport(checks: []const DocsCheck) void {
1271+
var passed: u32 = 0;
1272+
var total: u32 = 0;
1273+
1274+
std.debug.print("\n {s}Documentation checks:{s}\n", .{ CYAN, RESET });
1275+
for (checks) |c| {
1276+
total += 1;
1277+
if (c.passed) {
1278+
passed += 1;
1279+
std.debug.print(" {s}\xe2\x9c\x93{s} {s}: {s}\n", .{ GREEN, RESET, c.name, c.detail });
1280+
} else {
1281+
std.debug.print(" {s}\xe2\x9c\x97{s} {s}: {s}\n", .{ RED, RESET, c.name, c.detail });
1282+
}
1283+
}
1284+
1285+
std.debug.print("\n Score: {s}{d}/{d}{s}", .{
1286+
if (passed == total) GREEN else if (passed * 2 >= total) YELLOW else RED,
1287+
passed,
1288+
total,
1289+
RESET,
1290+
});
1291+
if (passed == total) {
1292+
std.debug.print(" \xe2\x80\x94 all docs checks pass \xe2\x9c\x93\n\n", .{});
1293+
} else {
1294+
std.debug.print(" \xe2\x80\x94 {d} issues found, run /doctor docs to fix\n\n", .{total - passed});
1295+
}
1296+
}
1297+
9561298
// ═══════════════════════════════════════════════════════════════════════════════
9571299
// TESTS
9581300
// ═══════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)