// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module Crafter.Review:Core_impl; import :Core; import :Platform; import :Text; import :Crypto; import std; namespace fs = std::filesystem; namespace Crafter::Review { namespace { std::optional& HomeOverride() { static std::optional home; return home; } bool HoldsLenses(const fs::path& dir) { std::error_code error; return !dir.empty() && fs::is_directory(dir / "process" / "lenses", error); } fs::path FindHome() { if (!Platform::InSandbox()) { if (const std::optional configured = Platform::GetEnv("CRAFTER_REVIEW_HOME"); configured && !configured->empty()) { if (HoldsLenses(*configured)) return *configured; throw std::runtime_error(std::format("CRAFTER_REVIEW_HOME={} holds no process/lenses", *configured)); } } const fs::path exe = Platform::SelfExe(); const fs::path installed = exe.parent_path().parent_path() / "share" / "crafter-review"; if (HoldsLenses(installed)) return installed; // /bin/crafter.review-exe-/crafter-review const fs::path variant = exe.parent_path(); if (variant.filename().string().starts_with("crafter.review-exe-") && variant.parent_path().filename() == "bin") { const fs::path checkout = variant.parent_path().parent_path(); if (HoldsLenses(checkout)) return checkout; } throw std::runtime_error(std::format("no process/lenses beside {} (neither {} nor a checkout's bin/): set CRAFTER_REVIEW_HOME", exe.string(), installed.string())); } } std::string_view Version() { return CRAFTER_REVIEW_VERSION; } std::string DriverLine() { return std::format("crafter-review {} (prompt v{})", Version(), PromptVersion); } const fs::path& Home() { if (!HomeOverride()) HomeOverride() = FindHome(); return *HomeOverride(); } void SetHome(fs::path home) { HomeOverride() = std::move(home); } fs::path LensDir() { return Home() / "process" / "lenses"; } std::vector> CharterHashes() { std::vector> hashes; std::error_code error; for (const fs::directory_entry& entry : fs::directory_iterator(LensDir(), error)) { if (entry.path().extension() != ".md") continue; hashes.emplace_back(entry.path().filename().string(), Sha256Hex(Dyn::ReadText(entry.path()))); } std::ranges::sort(hashes); return hashes; } }