Crafter.Review/interfaces/Crafter.Review-Json.cppm

68 lines
2.9 KiB
Text
Raw Normal View History

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
#include "../lib/json.hpp"
export module Crafter.Review:Json;
import std;
export namespace Crafter::Review {
// nlohmann/json, vendored unmodified at lib/json.hpp. ordered_json keeps an
// object's keys in the order they were read or inserted, and a duplicate
// key keeps its first position and its last value. That is what lets a
// driver rewrite the files every driver on the machine shares — the seat
// board, the ledger, unit-state.json — with every key in place, the ones
// it does not know about included.
using Json = nlohmann::ordered_json;
// Indented by one space per level, one key or item per line, \uXXXX
// escapes for everything outside ASCII: every file this pipeline writes. A
// string that is not valid UTF-8 has its bad bytes replaced rather than
// throwing in the middle of a write.
//
// A float from 1e15 up to 1e16 prints with an exponent (AGENTS.md).
// Readers parse it either way, and none of the files depends on it.
inline std::string DumpFile(const Json& value) {
return value.dump(1, ' ', true, Json::error_handler_t::replace);
}
// Keys in code-point order, no whitespace, ASCII only. The
// structured-output schema goes to claude in this form and its bytes are
// part of the prompt cache, so they must not move by a byte (the goldens
// hold them); dedupe keys use it too.
inline std::string DumpCompactSorted(const Json& value) {
// Converted to nlohmann::json, which keeps objects in a std::map: that
// sorts the keys by byte, and byte order is code-point order in UTF-8.
const nlohmann::json sorted(value);
return sorted.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
}
// On one line, with ", " and ": " between items: where a reader matches
// on the bytes, such as the first line of an opencode output, `{"type":
// "review_gates_meta", …`, which salvage looks for.
inline std::string DumpSpaced(const Json& value) {
if (value.is_object()) {
std::string out = "{";
bool first = true;
for (const auto& [key, child] : value.items()) {
if (!first) out += ", ";
first = false;
out += Json(key).dump(-1, ' ', true, Json::error_handler_t::replace);
out += ": ";
out += DumpSpaced(child);
}
return std::format("{}}}", out);
}
if (value.is_array()) {
std::string out = "[";
bool first = true;
for (const Json& child : value) {
if (!first) out += ", ";
first = false;
out += DumpSpaced(child);
}
return std::format("{}]", out);
}
return value.dump(-1, ' ', true, Json::error_handler_t::replace);
}
}