From d7ae21f9f43ddbeee2384a0da86a1b550e2141fd Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:40:36 -0400 Subject: [PATCH 1/7] Test POSIX character classes in path-table and config globs --- packages/rust/src/matcher.rs | 7 +++ packages/rust/tests/path_table_cli_e2e.rs | 11 +++++ packages/rust/tests/path_table_globs.rs | 59 +++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/packages/rust/src/matcher.rs b/packages/rust/src/matcher.rs index 47a386303..68c45878d 100644 --- a/packages/rust/src/matcher.rs +++ b/packages/rust/src/matcher.rs @@ -500,4 +500,11 @@ mod tests { .is_empty() ); } + + #[test] + fn a_posix_class_in_a_table_glob_matches_its_characters() { + let matcher = TableMatcher::new(&[("notes/[[:digit:]].md", "n")], &[]).unwrap(); + assert_eq!(names(&matcher, "notes/7.md"), vec!["n"]); + assert!(matcher.match_all(Path::new("notes/x.md")).is_empty()); + } } diff --git a/packages/rust/tests/path_table_cli_e2e.rs b/packages/rust/tests/path_table_cli_e2e.rs index 7919a1b05..2fc7a0835 100644 --- a/packages/rust/tests/path_table_cli_e2e.rs +++ b/packages/rust/tests/path_table_cli_e2e.rs @@ -291,3 +291,14 @@ fn an_ordinary_syntax_error_carries_no_quoting_hint() { "a syntax error with no path in it must stay unhinted, got: {stderr}" ); } + +#[test] +fn a_posix_character_class_matches_like_bash() { + let root = TempDir::new().unwrap(); + for name in ["1.md", "5.md", "A.md", "b.md"] { + fs::write(root.path().join(name), name).unwrap(); + } + let out = run(&root, "SELECT path FROM './[[:digit:]].md'"); + + assert_eq!(paths(&out), vec!["1.md", "5.md"]); +} diff --git a/packages/rust/tests/path_table_globs.rs b/packages/rust/tests/path_table_globs.rs index 49274b35c..cee4d8eb7 100644 --- a/packages/rust/tests/path_table_globs.rs +++ b/packages/rust/tests/path_table_globs.rs @@ -399,3 +399,62 @@ fn a_prefixed_glob_starts_its_walk_at_the_named_directory() { "the walk must begin at docs/, not list the index root" ); } + +/// Names no two of which differ only by case, so the fixture holds on a +/// case-insensitive filesystem. +fn posix_class_fixture() -> TempDir { + let root = TempDir::new().unwrap(); + fs::create_dir_all(root.path().join("pc")).unwrap(); + for name in ["1.md", "5.md", "A.md", "b.md", "README.MD", "_x.md"] { + fs::write(root.path().join("pc").join(name), name).unwrap(); + } + root +} + +#[test] +fn a_digit_class_matches_each_digit() { + let root = posix_class_fixture(); + let db = open(&root); + + assert_eq!( + paths(&db, "SELECT path FROM './pc/[[:digit:]].md'"), + vec!["pc/1.md", "pc/5.md"] + ); +} + +#[test] +fn an_upper_class_matches_each_uppercase_letter() { + let root = posix_class_fixture(); + let db = open(&root); + + assert_eq!( + paths(&db, "SELECT path FROM './pc/[[:upper:]]*'"), + vec!["pc/A.md", "pc/README.MD"] + ); +} + +#[test] +fn a_negated_class_matches_everything_outside_it() { + let root = posix_class_fixture(); + let db = open(&root); + + assert_eq!( + paths(&db, "SELECT path FROM './pc/[![:digit:]].md'"), + vec!["pc/A.md", "pc/b.md"] + ); +} + +#[test] +fn a_punct_class_never_matches_the_separator() { + let root = posix_class_fixture(); + let db = open(&root); + + assert_eq!( + paths(&db, "SELECT path FROM './pc[[:punct:]]*.md'"), + Vec::::new() + ); + assert_eq!( + paths(&db, "SELECT path FROM './pc/[[:punct:]]*.md'"), + vec!["pc/_x.md"] + ); +} From 64944de34a9749921c1b65f36860e4eb9197e635 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:52:27 -0400 Subject: [PATCH 2/7] Expand POSIX character classes before globset compiles a glob --- .../2026-10-05-posix-character-classes.md | 6 + .../2026-10-05-posix-character-classes.md | 31 ++ packages/rust/src/lib.rs | 1 + packages/rust/src/matcher.rs | 5 +- packages/rust/src/posix_class.rs | 304 ++++++++++++++++++ packages/rust/src/scanner.rs | 5 +- 6 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 packages/rust/changelog.d/2026-10-05-posix-character-classes.md create mode 100644 packages/rust/migrations.d/2026-10-05-posix-character-classes.md create mode 100644 packages/rust/src/posix_class.rs diff --git a/packages/rust/changelog.d/2026-10-05-posix-character-classes.md b/packages/rust/changelog.d/2026-10-05-posix-character-classes.md new file mode 100644 index 000000000..459d711eb --- /dev/null +++ b/packages/rust/changelog.d/2026-10-05-posix-character-classes.md @@ -0,0 +1,6 @@ +**Fixed** POSIX character classes inside brackets match as they do in bash: +`[[:digit:]]`, `[[:upper:]]`, `[![:alpha:]]` and the rest of `alnum`, `alpha`, +`ascii`, `blank`, `cntrl`, `digit`, `graph`, `lower`, `print`, `punct`, +`space`, `upper`, `word` and `xdigit`. They used to match nothing. Applies to +path-tables and to declared `[[table]]` globs and `ignore` patterns. Classes +cover ASCII characters. diff --git a/packages/rust/migrations.d/2026-10-05-posix-character-classes.md b/packages/rust/migrations.d/2026-10-05-posix-character-classes.md new file mode 100644 index 000000000..b89ed7ae5 --- /dev/null +++ b/packages/rust/migrations.d/2026-10-05-posix-character-classes.md @@ -0,0 +1,31 @@ +### Core: POSIX character classes match in globs + +#### Summary + +A bracket holding a POSIX character class, such as `'./[[:digit:]].md'`, +used to match nothing. It now matches as bash does, on path-tables, declared +`[[table]]` globs and `ignore` patterns. Nothing breaks; such a pattern can +now return rows. + +#### Required changes + +_None._ + +#### Deprecations removed + +_None._ + +#### Behavior changes without code changes + +- `'./[[:digit:]].md'` lists `1.md` and `5.md`; `'./[![:digit:]].md'` lists + `a.md`. +- An unknown class name, such as `[[:foo:]]`, contributes no characters. + +#### Verification + +```bash +cd "$(mktemp -d)" +touch 1.md 5.md a.md +dirsql query "SELECT path FROM './[[:digit:]].md' ORDER BY path" +# expected: [{"path":"1.md"},{"path":"5.md"}] +``` diff --git a/packages/rust/src/lib.rs b/packages/rust/src/lib.rs index 15ad54e91..0865974c3 100644 --- a/packages/rust/src/lib.rs +++ b/packages/rust/src/lib.rs @@ -33,6 +33,7 @@ pub mod parsed_vtab; pub mod path_table; #[doc(hidden)] pub mod persist; +mod posix_class; #[doc(hidden)] pub mod progress; #[doc(hidden)] diff --git a/packages/rust/src/matcher.rs b/packages/rust/src/matcher.rs index 68c45878d..c9f73277e 100644 --- a/packages/rust/src/matcher.rs +++ b/packages/rust/src/matcher.rs @@ -1,3 +1,4 @@ +use crate::posix_class::expand_posix_classes; use globset::{Glob, GlobBuilder, GlobSet, GlobSetBuilder}; use std::path::Path; @@ -86,7 +87,9 @@ fn glob_with_placeholders_as_star(pattern: &str) -> String { /// One glob rule on every surface: `*` and `?` stop at `/`, `**` crosses it, /// exactly as `scanner::compile_glob` reads a path-table. fn compile(pattern: &str) -> Result { - GlobBuilder::new(pattern).literal_separator(true).build() + GlobBuilder::new(&expand_posix_classes(pattern)) + .literal_separator(true) + .build() } impl TableMatcher { diff --git a/packages/rust/src/posix_class.rs b/packages/rust/src/posix_class.rs new file mode 100644 index 000000000..6368b3552 --- /dev/null +++ b/packages/rust/src/posix_class.rs @@ -0,0 +1,304 @@ +//! POSIX character classes (`[[:digit:]]`) for globset, which has no +//! `[:name:]` syntax: each bracket expression holding one is rewritten into an +//! equivalent bracket of plain ranges before the glob is compiled. + +/// Rewrite every bracket expression in `pattern` that holds a `[:name:]` +/// class into plain ranges. Brackets without one, unclosed brackets and +/// backslash-escaped characters are left exactly as written. +pub(crate) fn expand_posix_classes(pattern: &str) -> String { + let chars: Vec = pattern.chars().collect(); + let mut out = String::with_capacity(pattern.len()); + let mut i = 0; + while i < chars.len() { + match chars[i] { + '\\' => { + let end = (i + 2).min(chars.len()); + out.extend(&chars[i..end]); + i = end; + } + '[' => match bracket(&chars, i) { + Some((end, Some(rewritten))) => { + out.push_str(&rewritten); + i = end; + } + Some((end, None)) => { + out.extend(&chars[i..end]); + i = end; + } + None => { + out.push('['); + i += 1; + } + }, + c => { + out.push(c); + i += 1; + } + } + } + out +} + +/// Parse the bracket expression opening at `start`. `None` when it never +/// closes; otherwise the index past its `]`, plus its rewrite when it holds a +/// class. +fn bracket(chars: &[char], start: usize) -> Option<(usize, Option)> { + let mut j = start + 1; + let negated = matches!(chars.get(j), Some('!' | '^')); + if negated { + j += 1; + } + let body_start = j; + let mut ranges: Vec<(char, char)> = Vec::new(); + let mut has_class = false; + // bash matches nothing at all when a range ends in a class, negated or not. + let mut range_into_class = false; + loop { + let c = *chars.get(j)?; + if c == ']' && j > body_start { + j += 1; + break; + } + if let Some((name, next)) = class_at(chars, j) { + ranges.extend_from_slice(class_ranges(&name)); + has_class = true; + j = next; + continue; + } + match (chars.get(j + 1), chars.get(j + 2)) { + (Some('-'), Some(&end)) if end != ']' => { + if let Some((_, next)) = class_at(chars, j + 2) { + has_class = true; + range_into_class = true; + j = next; + continue; + } + if c <= end { + ranges.push((c, end)); + } + j += 3; + } + _ => { + ranges.push((c, c)); + j += 1; + } + } + } + if !has_class { + return Some((j, None)); + } + let rewritten = if range_into_class { + "[\0]".to_string() + } else { + render(negated, ranges) + }; + Some((j, Some(rewritten))) +} + +/// The class `[:name:]` starting at `j`, with the index past its `:]`. A `]` +/// before the `:]` means the `[` is an ordinary character, as in bash. +fn class_at(chars: &[char], j: usize) -> Option<(String, usize)> { + if chars.get(j) != Some(&'[') || chars.get(j + 1) != Some(&':') { + return None; + } + let mut k = j + 2; + loop { + match (chars.get(k)?, chars.get(k + 1)) { + (':', Some(']')) => return Some((chars[j + 2..k].iter().collect(), k + 2)), + (']', _) => return None, + _ => k += 1, + } + } +} + +/// The ASCII members of a class; an unknown name has none, as in bash. +fn class_ranges(name: &str) -> &'static [(char, char)] { + match name { + "alnum" => &[('0', '9'), ('A', 'Z'), ('a', 'z')], + "alpha" => &[('A', 'Z'), ('a', 'z')], + "ascii" => &[('\0', '\x7f')], + "blank" => &[(' ', ' '), ('\t', '\t')], + "cntrl" => &[('\0', '\x1f'), ('\x7f', '\x7f')], + "digit" => &[('0', '9')], + "graph" => &[('!', '~')], + "lower" => &[('a', 'z')], + "print" => &[(' ', '~')], + "punct" => &[('!', '/'), (':', '@'), ('[', '`'), ('{', '~')], + "space" => &[('\t', '\r'), (' ', ' ')], + "upper" => &[('A', 'Z')], + "word" => &[('0', '9'), ('A', 'Z'), ('a', 'z'), ('_', '_')], + "xdigit" => &[('0', '9'), ('A', 'F'), ('a', 'f')], + _ => &[], + } +} + +/// Characters globset reads as syntax at some position inside a bracket, so +/// they are pulled out of ranges and placed where they are literal. +const POSITIONAL: [char; 4] = [']', '-', '!', '^']; + +/// A globset bracket matching exactly `ranges` (or everything else when +/// `negated`). A bracket never matches the separator, so `/` is dropped from a +/// positive set and added to a negated one. NUL, which no path holds, stands +/// in when nothing else may open the bracket. +fn render(negated: bool, ranges: Vec<(char, char)>) -> String { + let mut set: Vec<(u32, u32)> = ranges + .into_iter() + .map(|(a, b)| (a as u32, b as u32)) + .collect(); + set = without(set, '/'); + if negated { + set.push(('/' as u32, '/' as u32)); + } + let present = |c: char| set.iter().any(|&(a, b)| a <= c as u32 && c as u32 <= b); + let specials: Vec = POSITIONAL.into_iter().filter(|&c| present(c)).collect(); + let mut plain = POSITIONAL.into_iter().fold(set.clone(), without); + plain.sort_unstable(); + if plain.is_empty() && !specials.contains(&']') { + plain.push((0, 0)); + } + + let mut out = String::from("["); + if negated { + out.push('!'); + } + if specials.contains(&']') { + out.push(']'); + } + for (a, b) in plain { + out.extend(char::from_u32(a)); + if a != b { + out.push('-'); + out.extend(char::from_u32(b)); + } + } + for c in ['!', '^', '-'] { + if specials.contains(&c) { + out.push(c); + } + } + out.push(']'); + out +} + +/// `set` with `c` removed, splitting any range that spans it. `c` is never +/// NUL, so `c - 1` cannot underflow. +fn without(set: Vec<(u32, u32)>, c: char) -> Vec<(u32, u32)> { + let c = c as u32; + set.into_iter() + .flat_map(|(a, b)| [(a, b.min(c - 1)), (a.max(c + 1), b)]) + .filter(|&(a, b)| a <= b) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_class_becomes_its_ranges() { + assert_eq!(expand_posix_classes("[[:digit:]].md"), "[0-9].md"); + assert_eq!(expand_posix_classes("[[:upper:]]*"), "[A-Z]*"); + } + + #[test] + fn every_named_class_expands() { + let cases = [ + ("alnum", "[0-9A-Za-z]"), + ("alpha", "[A-Za-z]"), + ("ascii", "[]\0- \"-,.0-\\_-\x7f!^-]"), + ("blank", "[\t ]"), + ("cntrl", "[\0-\x1f\x7f]"), + ("digit", "[0-9]"), + ("graph", "[]\"-,.0-\\_-~!^-]"), + ("lower", "[a-z]"), + ("print", "[] \"-,.0-\\_-~!^-]"), + ("punct", "[]\"-,.:-@[-\\_-`{-~!^-]"), + ("space", "[\t-\r ]"), + ("upper", "[A-Z]"), + ("word", "[0-9A-Z_a-z]"), + ("xdigit", "[0-9A-Fa-f]"), + ]; + for (name, want) in cases { + assert_eq!( + expand_posix_classes(&format!("[[:{name}:]]")), + want, + "{name}" + ); + } + } + + #[test] + fn a_negated_class_also_excludes_the_separator() { + assert_eq!(expand_posix_classes("[![:digit:]]"), "[!/0-9]"); + assert_eq!(expand_posix_classes("[^[:digit:]]"), "[!/0-9]"); + } + + #[test] + fn classes_and_characters_combine() { + assert_eq!(expand_posix_classes("[a[:digit:]]"), "[0-9a]"); + assert_eq!(expand_posix_classes("[[:digit:][:upper:]]"), "[0-9A-Z]"); + assert_eq!(expand_posix_classes("[[:digit:]x-z]"), "[0-9x-z]"); + } + + #[test] + fn a_dash_after_a_class_is_literal() { + assert_eq!(expand_posix_classes("[[:digit:]-z]"), "[0-9z-]"); + assert_eq!(expand_posix_classes("[[:digit:]-]"), "[0-9-]"); + } + + #[test] + fn a_leading_close_bracket_is_a_member() { + assert_eq!(expand_posix_classes("[][:digit:]]"), "[]0-9]"); + } + + #[test] + fn an_unknown_class_contributes_nothing() { + assert_eq!(expand_posix_classes("[[:foo:]a]"), "[a]"); + assert_eq!(expand_posix_classes("[[:DIGIT:]]"), "[\0]"); + assert_eq!(expand_posix_classes("[![:foo:]]"), "[!/]"); + } + + #[test] + fn a_lone_bang_is_kept_off_the_negation_position() { + assert_eq!(expand_posix_classes("[[:foo:]!]"), "[\0!]"); + assert_eq!(expand_posix_classes("[[:foo:]^]"), "[\0^]"); + } + + #[test] + fn a_range_ending_in_a_class_matches_nothing() { + assert_eq!(expand_posix_classes("[x-[:digit:]]"), "[\0]"); + assert_eq!(expand_posix_classes("[!x-[:digit:]]"), "[\0]"); + } + + #[test] + fn a_reversed_range_beside_a_class_is_empty() { + assert_eq!(expand_posix_classes("[z-a[:digit:]]"), "[0-9]"); + } + + #[test] + fn a_bracket_without_a_class_is_untouched() { + for pattern in ["[a-c]*", "[!x].md", "[]]", "[[:a]", "{a,b}/[xy]"] { + assert_eq!(expand_posix_classes(pattern), pattern); + } + } + + #[test] + fn an_unclosed_bracket_is_untouched() { + assert_eq!(expand_posix_classes("[[:digit:]"), "[[:digit:]"); + assert_eq!(expand_posix_classes("a["), "a["); + } + + #[test] + fn an_escaped_bracket_is_untouched() { + assert_eq!(expand_posix_classes("\\[[:digit:]]"), "\\[[:digit:]]"); + assert_eq!(expand_posix_classes("a\\"), "a\\"); + } + + #[test] + fn text_around_brackets_is_kept() { + assert_eq!( + expand_posix_classes("**/v[[:digit:]]/*.md"), + "**/v[0-9]/*.md" + ); + } +} diff --git a/packages/rust/src/scanner.rs b/packages/rust/src/scanner.rs index 397ae89a0..b70bb4386 100644 --- a/packages/rust/src/scanner.rs +++ b/packages/rust/src/scanner.rs @@ -1,4 +1,5 @@ use crate::matcher::TableMatcher; +use crate::posix_class::expand_posix_classes; use globset::{GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder}; use ignore::Match; use ignore::gitignore::{Gitignore, GitignoreBuilder}; @@ -232,7 +233,9 @@ pub fn compile_glob(pattern: &str) -> Result { } fn compile_component(pattern: &str) -> Result { - GlobBuilder::new(pattern).literal_separator(true).build() + GlobBuilder::new(&expand_posix_classes(pattern)) + .literal_separator(true) + .build() } /// A pattern whose components do not compile on their own (an alternation From 86675043c73d6f45f6eba1c13bdf7ec04a49d5ce Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:03:20 -0400 Subject: [PATCH 3/7] e2e attestation for claude/1267-posix-classes --- .../python/e2e-attestations/claude-1267-posix-classes.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/python/e2e-attestations/claude-1267-posix-classes.json diff --git a/packages/python/e2e-attestations/claude-1267-posix-classes.json b/packages/python/e2e-attestations/claude-1267-posix-classes.json new file mode 100644 index 000000000..01abb5ec3 --- /dev/null +++ b/packages/python/e2e-attestations/claude-1267-posix-classes.json @@ -0,0 +1,7 @@ +{ + "command": "just test-e2e", + "ran_at": 1791216200, + "exit_code": 0, + "commit": "64944de34a9749921c1b65f36860e4eb9197e635", + "branch": "claude/1267-posix-classes" +} From 349e5d8ae9cbe675aec4412c47c24612211c3c03 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:04:36 -0400 Subject: [PATCH 4/7] e2e attestation for claude/1267-posix-classes --- .../ts/e2e-attestations/claude-1267-posix-classes.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/ts/e2e-attestations/claude-1267-posix-classes.json diff --git a/packages/ts/e2e-attestations/claude-1267-posix-classes.json b/packages/ts/e2e-attestations/claude-1267-posix-classes.json new file mode 100644 index 000000000..cc94249f0 --- /dev/null +++ b/packages/ts/e2e-attestations/claude-1267-posix-classes.json @@ -0,0 +1,7 @@ +{ + "command": "pnpm test:e2e", + "ran_at": 1791216276, + "exit_code": 0, + "commit": "86675043c73d6f45f6eba1c13bdf7ec04a49d5ce", + "branch": "claude/1267-posix-classes" +} From e97671de973e60b36484d790f4df9f40bab0baa6 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:08:59 -0400 Subject: [PATCH 5/7] Pin bracket parsing edges the mutation gate found unasserted --- packages/rust/src/posix_class.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/rust/src/posix_class.rs b/packages/rust/src/posix_class.rs index 6368b3552..075fa1162 100644 --- a/packages/rust/src/posix_class.rs +++ b/packages/rust/src/posix_class.rs @@ -244,6 +244,7 @@ mod tests { fn a_dash_after_a_class_is_literal() { assert_eq!(expand_posix_classes("[[:digit:]-z]"), "[0-9z-]"); assert_eq!(expand_posix_classes("[[:digit:]-]"), "[0-9-]"); + assert_eq!(expand_posix_classes("[[:digit:]x-]"), "[0-9x-]"); } #[test] @@ -277,7 +278,15 @@ mod tests { #[test] fn a_bracket_without_a_class_is_untouched() { - for pattern in ["[a-c]*", "[!x].md", "[]]", "[[:a]", "{a,b}/[xy]"] { + for pattern in [ + "[a-c]*", + "[!x].md", + "[]]", + "[[:a]", + "{a,b}/[xy]", + "[a:digit:]]", + "[[:a][:digit:]]", + ] { assert_eq!(expand_posix_classes(pattern), pattern); } } From 64eda35efa62818c4b2f74f67c0fb80408465858 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:27:48 -0400 Subject: [PATCH 6/7] e2e attestation for claude/1267-posix-classes --- .../python/e2e-attestations/claude-1267-posix-classes.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/e2e-attestations/claude-1267-posix-classes.json b/packages/python/e2e-attestations/claude-1267-posix-classes.json index 01abb5ec3..5537fe868 100644 --- a/packages/python/e2e-attestations/claude-1267-posix-classes.json +++ b/packages/python/e2e-attestations/claude-1267-posix-classes.json @@ -1,7 +1,7 @@ { "command": "just test-e2e", - "ran_at": 1791216200, + "ran_at": 1791224868, "exit_code": 0, - "commit": "64944de34a9749921c1b65f36860e4eb9197e635", + "commit": "ca8ccbb55b7f51f9373b8f774665b925ba10ac21", "branch": "claude/1267-posix-classes" } From 51587b6978f97169f65dbfa7afd65a8bf1cdcafa Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:28:51 -0400 Subject: [PATCH 7/7] e2e attestation for claude/1267-posix-classes --- packages/ts/e2e-attestations/claude-1267-posix-classes.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ts/e2e-attestations/claude-1267-posix-classes.json b/packages/ts/e2e-attestations/claude-1267-posix-classes.json index cc94249f0..85532ee8e 100644 --- a/packages/ts/e2e-attestations/claude-1267-posix-classes.json +++ b/packages/ts/e2e-attestations/claude-1267-posix-classes.json @@ -1,7 +1,7 @@ { "command": "pnpm test:e2e", - "ran_at": 1791216276, + "ran_at": 1791224931, "exit_code": 0, - "commit": "86675043c73d6f45f6eba1c13bdf7ec04a49d5ce", + "commit": "64eda35efa62818c4b2f74f67c0fb80408465858", "branch": "claude/1267-posix-classes" }