Rename action from 'SpecSync Check' to 'SpecSync'#9
Merged
Conversation
0xLeif
added a commit
that referenced
this pull request
Jul 3, 2026
…in const) + Rust comment tokenization (#311) * Fix: export-parser language-idiom gaps + Rust comment/string tokenization Four export-scanner bugs where a correctly-documented public symbol was reported as "no matching export found" (failing check --strict) and an undocumented one was silently dropped from coverage. #4 Swift: `public final class Foo` never matched — the decl regex had slots for `static`/`class` modifiers but not `final`. Replaced with `(?:(?:final|static|class)\s+)*` so any combination between the access keyword and the declaration keyword is allowed. #5 Go: items in grouped `const (...)` / `var (...)` / `type (...)` blocks (on their own lines, no keyword prefix) were missed by the line-anchored regex. Added a stateful pass that captures each grouped item's leading exported (uppercase) identifier, tracking brace depth so struct/interface fields inside a grouped `type` are not mistaken for top-level exports. #9 Kotlin: top-level `const val NAME` was missed — `const` wasn't in the modifier chain. Added `(?:const\s+)?` before the declaration keyword. Rust (new, discovered via the dogfood self-check): the scanner stripped string literals BEFORE comments with a DOTALL regex, so a `"` inside a `//`/`///` doc comment was read as a string opener and swallowed code up to the next real `"` — deleting `pub fn` declarations in between (any doc comment with an odd number of `"` silently hid exports). Replaced the regex string+comment stripping with a single linear pass that recognizes `//` and `/* */` (nesting) before `"`, and keeps a `//` inside a string as content. Raw strings and double-quote char literals are still blanked up front by the existing (proven) regexes. Tests: Swift final/static/class modifiers; Kotlin const val (incl. internal exclusion); Go grouped const/var/type with brace-depth (struct fields not captured); Rust quotes-in-comments don't hide exports + pub-fn-in-string not extracted. All existing parser tests (incl. the ai.rs raw-string case) pass. 712 unit + 168 integration, self-check 100% (36990 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: address review regressions in the export-parser cluster Round-2 adversarial review (per-language lenses) found three regressions in the first cut. Fixed with tests reproducing each: Rust — byte/C raw strings not blanked: the RAW_STR regexes required `\b` before `r`, which fails when `r` is preceded by `b`, so `br#"..."#` (and `cr#"..."#`) were never blanked. With an odd number of interior quotes the linear scanner then read a trailing quote as a string opener and swallowed every following `pub` declaration to EOF (a real regression vs main). Widened the prefix to `(?:b|c)?r`. Go — brace/paren counting ignored string literals: a `{`/`}`/`(`/`)` inside a grouped item's value (`OpenBrace = "{"`, a struct tag `` `json:"a}"` ``, or a multi-line `X = f(\n...\n)` value) corrupted brace/paren depth, dropping later items and — because the group never returned to depth 0 — swallowing every subsequent grouped block in the file. Now depth is tracked on a copy with string/rune literals blanked (`blank_go_strings`), and a value's own parens are tracked separately so a value-closing `)` is not mistaken for the group's closer. Swift — declarations inside string literals leaked: extract_exports stripped comments but not strings, so a `public final class X` inside a code-gen template string was extracted as a phantom export (widening the modifier group to include `final` incrementally worsened a pre-existing weakness). Replaced the comment-only regex stripping with a single linear pass that blanks `"..."`, `"""..."""`, `//`, and nesting `/* */` together — also repairing Swift's previously-broken multi-line `//` comment handling. Tests: Rust byte raw string doesn't hide exports; Go grouped delimiters-in-strings (brace string, multi-line paren value, later block still seen) + struct-tag brace; Swift declaration inside single-line and multi-line strings not extracted. 717 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37264 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: multi-line tokenization regressions in export parsers (round 3) Round-3 adversarial review found two more regressions, both from constructs that span lines — which the earlier fixed-count regex (Rust) and per-line blanking (Go) could not see. Fixed with tests reproducing each reviewer input: Rust — raw strings with 4+ hashes: the RAW_STR regexes only covered 0..3 hashes, so `r####"..."####` was never blanked and an interior quote leaked, opening an unterminated string that swallowed every later pub decl to EOF (regression vs main). Replaced the fixed 0..3-hash regexes AND the char-literal regex with a single self-contained linear scanner that counts hashes, so raw strings of ANY hash count (and the `br`/`cr` byte/C prefixes, guarded by an identifier boundary) are handled, along with `'"'`/`'\"'` char literals, regular/byte/C strings, and nesting comments. Go — multi-line backtick raw strings: blank_go_strings ran per line, so a `schema = ` + "`...multi line SQL...`" template inside a grouped const/var/type block was treated as code — leaking an uppercase body word (e.g. `CREATE`) as a phantom export, and letting a `)`/`{`/`(` in the body close the group early or leave it stuck open (dropping later items and subsequent groups). blank_go_strings now blanks literal contents across the WHOLE file (backtick strings span lines, newlines preserved), and every pass — GO_DECL, GO_METHOD, and the grouped scan — runs on the blanked copy, so no string body is ever read as code or as a delimiter. Not addressed here (pre-existing, confirmed identical on main by the review, NOT regressions — deferred to a focused follow-up): Swift `public private(set) var` / `weak` / `lazy` / `mutating` / `indirect`, and Kotlin `@JvmField const val` / `lateinit`, are still missed by the modifier chains. Tests: Rust raw strings with 4/5-hash + interior quotes don't hide later exports; Go grouped block with a multi-line backtick SQL template (body not captured, later items + later group still seen) and with an unbalanced `)`/`{` in the body. 720 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37382 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: unify Go comment+string tokenization; revert Swift string gold-plating (round 4) Round-4 review found that round 3's "run every pass on the blanked copy" change exposed the pre-existing broken comment stripping, and that the Swift string scanner (added in round 2, beyond the actual finding) desynced on raw strings. Go — replace the regex comment stripping + string-only blanker with ONE linear pass (strip_go_strings_and_comments) that recognizes `//` and `/* */` BEFORE `"`/`` ` ``/`'`. The old `//.*$` regex lacked the multiline flag, so it stripped only the file's LAST comment; every other comment survived into the string blanker, where an apostrophe in a contraction ("can't") or a stray `"` was read as a rune/string opener and swallowed following declarations across newlines — dropping documented exports on ordinary Go files (a real regression). Unbalanced `(`/`{`/`}` in a surviving comment likewise corrupted the grouped pass's depth (skipping later items, leaking struct fields, or missing a block whose `(` had a trailing comment). Recognizing comments first, then blanking string/rune literals (incl. multi-line backtick raw strings), fixes all of these; every pass now reads the same correct copy. Swift — revert the string/comment scanner added in round 2. It was gold-plating beyond finding #4 (which only asked for `public final class`), and it introduced a raw-string (`#"..."#`) desync that swallowed later declarations. Swift is back to the original comment-regex stripping plus ONLY the `final`/`static`/`class` modifier widening the finding required. Declarations embedded inside string literals are once again a pre-existing, low-severity false-positive — identical to main, not a regression — and are left for a separate, consistent all-parser follow-up. Not addressed (pre-existing, confirmed identical on main, NOT regressions): Rust `pub fn r#raw_ident` captures `r` (PUB_DECL `\w+`); Swift `override` / `indirect` / `private(set)`; Kotlin `@JvmField`/`@JvmStatic const val`. Tests: Go comment with apostrophe/quote doesn't drop exports; Go grouped-pass depth not corrupted by `(`/`{`/`}` in comments (item line, group-open line, struct field). 720 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37347 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: anchor Go grouped blocks to column 0; broaden Swift modifier set (round 5) Round-5 review found one blocking regression plus a completeness gap worth closing. Go (blocking regression) — the grouped-declaration pass matched the group-open on the trimmed line, so a function-LOCAL (indented) `const (` / `var (` / `type (` block was scanned and its uppercase items emitted as package exports (e.g. a local `const ( MaxAttempts = 5 )` inside `func Run()` leaked `MaxAttempts`). Main's GO_DECL/GO_METHOD are `(?m)^`-anchored to column 0 and never captured indented decls, so this fabricated non-exports vs main. The group-open now matches on the un-trimmed line (`line.trim_end()`), firing only at column 0 — where gofmt always places package-level blocks — so every legitimate top-level block is still captured and function-local ones are ignored. Swift (completeness, in the spirit of finding #4) — the modifier group allowed only `final`/`static`/`class`, but `public override func`, `public mutating func`, `public lazy var`, and `public weak var` are all at least as common and were still dropped. Broadened the group to also allow override/mutating/nonmutating/lazy/weak/ unowned/dynamic/nonisolated, so those public members are extracted too. Still deferred (pre-existing, not regressions): Go multi-name grouped item lines (`Foo, Bar = ...` keeps only `Foo`) and inline single-line groups (`const ( Foo = 1 )`); Swift `private(set)` / `borrowing` / `consuming` and declaration-shaped tokens inside string literals (a low, pre-existing false-positive as on main); Kotlin `@JvmField`/ `@JvmStatic const val`; Rust `pub fn r#raw_ident` capturing `r`. Tests: Go function-local grouped block not exported (column-0 block still is); Swift override/mutating/lazy/weak/dynamic/nonisolated/final-override members extracted. 722 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37423 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: don't capture multi-line value continuations as grouped exports (round 6) Round-6 review confirmed 3 of 4 fixes clean (Rust tokenizer byte-identical to main across all 78 real source files; Swift a strict regex superset; Kotlin const val) and found one remaining Go false-positive regression. A package-level grouped `const`/`var` item whose value spans lines via a trailing `.` (builder chain) or a trailing binary operator leaves brace/paren depth at 0, so the continuation line's leading identifier was captured as a phantom export — e.g. `Client = New().\n WithTimeout(5)` leaked `WithTimeout`, and `Path = Base +\n Suffix` leaked `Suffix`. main never captured these (no grouped pass), so it was a regression that fails `check --strict` with spurious undocumented-export findings on idiomatic Go. Fix uses Go's automatic-semicolon-insertion rule (go_line_continues): a new grouped item is only recognized when the previous in-group line ended a statement (last token an identifier/literal/`)`/`]`/`}`/`++`/`--`). A line ending in an operator, `.`, `,`, `(`, `{`, `[`, or `=` continues the value, so its next line is skipped for item matching. To keep a string-only value (`X = "y"`) reading as a completed statement, strip_go_strings_and_comments now emits a single `_` placeholder per string/rune/ backtick literal (instead of nothing) — the placeholder is never at line start, so it is never captured, but it prevents `X = ` from looking unfinished. Tests: builder-chain and operator-continued grouped values don't leak the continuation identifier (item after them is still captured); a string/backtick-valued item doesn't swallow the following item. 724 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37521 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m * Fix: constrain Go receiver regex to one line (round 7 nit) Round-7 review verdict: READY_WITH_NITS, no blocking items, all four fixes correct and regression-free. Folded in the reviewer's top recommended nit — a pre-existing GO_DECL false positive that lands in the exact grouped-`type` struct-field area this PR adds and tests, so worth closing here to make the "fields aren't exports" guarantee complete. GO_DECL's optional receiver group used `[^)]*`, which matches newlines; on a grouped `type (` opener line the regex could leap across the block body to the first interior `)` (e.g. a func-typed struct field) and capture the following uppercase field as a phantom top-level export (`type ( Config struct { fn func(x int); Secret string } )` leaked `Secret`). Constraining the receiver to `[^)\n]*` stops it spanning newlines; real method receivers are always single-line, so legitimate `func (r *R) Name` capture is unchanged. Test: grouped `type` struct with a func-typed field followed by an exported field extracts the types (Config, Helper) but not the field (Secret). 725 unit + 168 integration, fmt / clippy (bin) / self-check 100% (37549 LOC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDJxU4R8hUEuq1Y5jzft5m --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.