33namespace fs = std::filesystem;
47std::string trim(
const std::string&
s) {
48 auto begin =
s.find_first_not_of(
" \t\r\n");
49 auto end =
s.find_last_not_of(
" \t\r\n");
50 return (
begin == std::string::npos)
63 auto s =
p.generic_string();
80 case '.':
case '+':
case '(':
case ')':
81 case '|':
case '^':
case '$':
case '{':
108 static const std::vector<std::string>
kSkipDirs = {
109 ".git",
"node_modules",
"__pycache__",
".venv"
203 case '?': out +=
"[^/]";
break;
212 default:
handled =
false;
break;
236std::string IgnoreMatcher::pattern_to_regex(
const std::string&
pattern) {
238 out.reserve(
pattern.size() * 2);
264void strip_flags(std::string& body, IgnoreMatcher::Rule& rule) {
265 if (!body.empty() && body[0] ==
'!') {
269 if (!body.empty() && body.back() ==
'/') {
270 rule.dir_only =
true;
289 std::string out = base;
290 if (out.rfind(
"./", 0) == 0) { out.erase(0, 2); }
291 if (out ==
".") { out.clear(); }
292 while (!out.empty() && out.back() ==
'/') { out.pop_back(); }
306 if (base.empty()) {
return {}; }
307 std::string
raw = base +
"/";
336 return std::regex(
src);
337 }
catch (
const std::regex_error&
e) {
338 logger->warn(
"Skipping malformed ignore pattern '{}': {}",
340 return std::regex(
"(?!)");
367IgnoreMatcher::Rule IgnoreMatcher::compile_pattern(
368 const std::string&
pattern,
const std::string& base) {
378 || body.find(
'/') != std::string::npos;
380 std::string
regex_body = pattern_to_regex(body);
406void IgnoreMatcher::push_rule(Rule&& rule) {
407 std::string base = rule.base;
408 rules_.push_back(std::move(rule));
409 by_base_[base].push_back(rules_.size() - 1);
424 const fs::path& base) {
442void IgnoreMatcher::load_file(
const fs::path& path,
443 const std::string& base) {
444 std::ifstream
in(path);
445 if (!
in.is_open()) {
return; }
448 while (std::getline(
in,
line)) {
451 push_rule(compile_pattern(
trimmed, base));
454 std::string
base_label = base.empty() ? std::string(
"<root>") : base;
455 logger->info(
"Loaded {} ignore rules from {} (base='{}')",
476 if (!fs::exists(root) || !fs::is_directory(root)) {
477 logger->warn(
"IgnoreMatcher::load: root does not exist: {}",
499void IgnoreMatcher::load_nested_gitignores(
502 fs::recursive_directory_iterator
it(
504 fs::directory_options::skip_permission_denied);
505 const fs::recursive_directory_iterator
end;
509 }
catch (
const std::exception&
e) {
510 logger->warn(
"Recursive gitignore scan aborted: {}",
e.what());
535void IgnoreMatcher::visit_discovery_entry(
536 fs::recursive_directory_iterator&
it,
538 const auto& entry = *
it;
539 if (entry.is_directory()) {
541 it.disable_recursion_pending();
545 if (entry.path().filename() !=
".gitignore"
549 auto rel_dir = fs::relative(entry.path().parent_path(),
568bool IgnoreMatcher::prunes_discovery(
581 std::unordered_map<std::string, std::vector<std::size_t>>;
584constexpr std::size_t
kNoRule =
static_cast<std::size_t
>(-1);
612 std::vector<BucketCursor>& out) {
613 auto take = [&](
const std::string& base) {
614 auto found = index.find(base);
615 if (
found != index.end() && !
found->second.empty()) {
616 out.push_back({&
found->second,
found->second.size()});
620 for (std::size_t
i = 0;
i <
rel_path.size(); ++
i) {
640 BucketCursor*
from =
nullptr;
642 if (
cursor.remaining == 0) {
continue; }
649 if (
from !=
nullptr) { --
from->remaining; }
671 std::atomic<std::uint64_t>&
evals) {
672 evals.fetch_add(1, std::memory_order_relaxed);
678 evals.fetch_add(1, std::memory_order_relaxed);
709 std::vector<BucketCursor>
cursors;
static bool is_skipped_dir_name(const std::string &name)
Directory names never walked, by name alone (gh#161).
void load(const std::filesystem::path &root)
Load gitignore + explorerignore from a workspace root.
void add_pattern(const std::string &pattern, const std::filesystem::path &base={})
Add a single pattern programmatically (test surface).
bool is_ignored(const std::string &rel_path, bool is_dir) const
Test whether a path is ignored.
void reset_regex_evals() const
Reset the regex-evaluation counter (gh#161, test surface).
std::size_t remaining
Unexamined count.
const std::vector< std::size_t > * indices
Ascending indices.
Path-relative ignore matching honoring .gitignore + .explorerignore.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Activate model on GPU (WARM → ACTIVE).
@ skipped
Validation did not run (skip_tiers / pure-tool-call / empty)
@ optional
optional: every escape asks the host's path approver