Skip to content

feat: add Rust, Solidity, and Dart scanner adapters - #44

Merged
neomaike merged 1 commit into
mainfrom
feat/rust-solidity-dart-adapters
Jul 30, 2026
Merged

neomaike merged 1 commit into
mainfrom
feat/rust-solidity-dart-adapters

Conversation

@neomaike

Copy link
Copy Markdown
Contributor

Closes #43.

Item 7 remainder — #10 closed the TypeScript/Python adapter work but scoped Rust/Solidity/Dart out on purpose. Regex-based, same philosophy as the existing adapters (not a full parse, limitations documented in coverage_text). Confirmed against real source in the portfolio before writing any pattern (Quantitativo's MEV bot, statum-fi, the HZ02 token, Rodax's Flutter app) — not guessed syntax.

RustAdapter

Top-level pub fn/struct/enum/trait/const/static/type, plus public methods of impl Type/impl Trait for Type blocks — the same "capability lives in a member block, not a top-level export" shape that TypeScript needed a follow-up fix for (issue #35), handled here from day one. pub(crate)/pub(super) excluded implicitly (no space between pub and the paren, so the regex never matches them).

SolidityAdapter

contract/interface/library names, plus event names and public/external function names inside them. Solidity puts the visibility modifier after the parameter list, not before the function name (unlike TS/Rust), and signatures commonly wrap across lines (seen in the real HZ02.sol) — handled by joining a bounded window of lines from the function match up to its first {/; and checking visibility on the joined text. Skips lib/out/cache/artifacts/typechain-types (Foundry/Hardhat vendored deps and build output, not in core.py's generic exclude list) and *.t.sol/*.s.sol (tests/scripts).

DartAdapter

Top-level class/abstract class/mixin/enum/typedef, plus a best-effort match of methods (a lazy prefix skips the return type, including nested generics, without parsing it). Members of a class/mixin whose own name starts with _ are skipped entirely, not just filtered by their own name — confirmed against the real, ubiquitous _FooState extends State<Foo> pattern every StatefulWidget uses, where a public-looking build() override is framework-invoked, never citable capability. This is a real difference from TypeScript's class-method handling (a non-exported TS class can still be reached through an exported instance; Dart has no equivalent escape hatch once the type itself is private).

Found and fixed during testing: the lazy return-type prefix swallows leading whitespace by design, which meant an unguarded version matched control-flow statements nested deep inside method bodies (if (...) reads exactly like name-then-paren too). Fixed with a (?=\S) guard requiring the actual member/function line to start immediately, no swallowed indentation — covered by test_nested_indentation_inside_method_body_not_matched_as_member.

Known gap, disclosed not hidden: generic methods (name<T>(...)) aren't extracted — confirmed against the real api_client.dart, all 4 HTTP-verb methods (get<T>, post<T>, put<T>, delete<T>) use this exact generic shape and are missed, only the non-generic setAccessToken is caught. Fixing this properly needs balanced-bracket matching for nested generics, not a regex tweak — flagging as a real follow-up opportunity rather than silently shipping a narrower adapter than it looks.

Wiring

  • Registered in scan/adapters/__init__.py's ADAPTERS dict.
  • cli.py's two --language choices lists (scan run, verify) updated — they're hardcoded separately from the ADAPTERS dict (pre-existing gap, not introduced here).
  • Fixed portfolio.py's LANGUAGE_MARKERS: Anchor.toml was labeled "Solidity/Anchor", which is wrong — Anchor.toml marks Solana's Rust-based Anchor framework, not Solidity (an EVM/Ethereum language). Now "Rust/Anchor".

Not in scope

Onboarding Quantitativo/statum-fi/the Flutter apps with real .agent-architecture/project.yaml manifests — that's portfolio rollout (Track D), a separate, bigger decision.

Verification

  • pytest: 88/88 (74 baseline + 14 new adapter tests).
  • Also ran scan_file() directly against real files (not just synthetic fixtures), not just synthetic fixtures:
    • Quantitativo's engine.rs: 5/5 real pub items captured correctly, including impl-block methods (DecisionEngine, ExecutionOrder, new, evaluate, set_capital).
    • Executor.sol and HZ02.sol: perfect on both, including the multi-line mintFromBatch signature.
    • Rodax's api_client.dart: correctly conservative — ApiClient + setAccessToken, generic methods correctly not matched rather than mismatched.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11b8fac670

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +79 to +80
first = parts[0]
return "lib/shared" if first == "shared" else first

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive layers relative to each source root

When a conventional manifest uses sourceRoots: ["lib"], scan_repo passes repository-relative paths such as lib/core/api.dart, so this selects lib rather than the intended core layer described above. The identical helpers in the new Rust and Solidity adapters flatten typical src/decision/... and src/core/... trees to src as well, making the generated architecture maps lose their layer distinctions; strip the active source-root prefix before inferring the layer.

Useful? React with 👍 / 👎.

EXPORT_STRUCT_RE = re.compile(r"^pub\s+struct\s+(\w+)", re.MULTILINE)
EXPORT_ENUM_RE = re.compile(r"^pub\s+enum\s+(\w+)", re.MULTILINE)
EXPORT_TRAIT_RE = re.compile(r"^pub\s+trait\s+(\w+)", re.MULTILINE)
EXPORT_CONST_RE = re.compile(r"^pub\s+const\s+(\w+)", re.MULTILINE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid treating fn as a const export

For valid public const functions such as pub const fn new() -> Self, this regex captures the literal token fn; the function regex does not accept const, so the resulting component advertises a phantom export named fn and omits new. Distinguish pub const NAME declarations from pub const fn NAME and extract the latter as a function.

Useful? React with 👍 / 👎.


EXCLUDE_SUFFIXES = (".g.dart", ".freezed.dart", "_test.dart")

EXPORT_CLASS_RE = re.compile(r"^(?:abstract\s+)?class\s+(\w+)", re.MULTILINE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize Dart 3 class modifiers

Public Dart declarations using supported modifiers such as final class, base class, sealed class, or interface class do not match this expression. If such a file has no other recognized declaration, the adapter drops the component entirely, which is common in Dart 3 codebases; accept the class modifiers before class while retaining the existing underscore privacy filter.

Useful? React with 👍 / 👎.

Item 7 remainder -- #10 closed the TypeScript/Python adapter work but
scoped these three out on purpose. Regex-based, same philosophy as the
existing adapters (not a full parse, limitations documented in
coverage_text). Confirmed against real source in the portfolio before
writing any pattern (Quantitativo MEV bot, statum-fi, HZ02 token,
Rodax Flutter app) -- not guessed syntax.

- RustAdapter: top-level pub fn/struct/enum/trait/const/static/type, plus
  public methods of impl blocks (impl Type / impl Trait for Type) -- the
  same "capability lives in a member block, not a top-level export" shape
  that TypeScript needed a follow-up fix for (issue #35), handled here
  from day one. pub(crate)/pub(super) excluded implicitly (no space
  between pub and the paren, so the regex never matches them).
- SolidityAdapter: contract/interface/library names, plus event names and
  public/external function names inside them. Solidity puts the
  visibility modifier *after* the parameter list, not before the function
  name (unlike TS/Rust), and signatures commonly wrap across lines (seen
  in the real HZ02.sol) -- handled by joining a bounded window of lines
  from the function match up to its first '{' or ';' and checking
  visibility on the joined text. Skips lib/out/cache/artifacts/
  typechain-types (Foundry/Hardhat vendored deps and build output, not in
  core.py's generic exclude list) and *.t.sol/*.s.sol (tests/scripts).
- DartAdapter: top-level class/abstract class/mixin/enum/typedef, plus a
  best-effort match of methods (a lazy prefix skips the return type,
  including nested generics, without parsing it). Members of a class/
  mixin whose own name starts with '_' are skipped entirely, not just
  filtered by their own name -- confirmed against the real, ubiquitous
  `_FooState extends State<Foo>` pattern every StatefulWidget uses, where
  a public-looking `build()` override is framework-invoked, never citable
  capability (a real difference from TypeScript's class-method handling,
  where a non-exported class can still be reached through an exported
  instance -- Dart has no equivalent escape hatch once the type itself is
  private). Found and fixed during testing: the lazy return-type prefix
  swallows leading whitespace by design, which meant an unguarded version
  matched control-flow statements nested deep inside method bodies
  (`if (...)` reads exactly like name-then-paren too) -- fixed with a
  `(?=\S)` guard requiring the actual member/function line to start
  immediately, no swallowed indentation. Known gap, disclosed in
  coverage_text not hidden: generic methods (`name<T>(...)`) aren't
  extracted -- confirmed against the real api_client.dart, all 4
  HTTP-verb methods use this exact generic shape and are missed, only the
  non-generic setAccessToken is caught.

Wiring: registered in scan/adapters/__init__.py ADAPTERS; cli.py's two
--language choices lists (scan run, verify) updated -- they're hardcoded
separately from the ADAPTERS dict, a pre-existing gap, not something new
introduced here. Also fixed portfolio.py's LANGUAGE_MARKERS: Anchor.toml
was labeled "Solidity/Anchor", which is wrong -- Anchor.toml marks
Solana's Rust-based Anchor framework, not Solidity (an EVM/Ethereum
language). Now "Rust/Anchor".

Not in scope: onboarding Quantitativo/statum-fi/the Flutter apps with
real .agent-architecture/project.yaml manifests -- that's portfolio
rollout (Track D), a separate, bigger decision.

Verification: pytest 88/88 (74 baseline + 14 new adapter tests). Also ran
scan_file() directly against real files (not just synthetic fixtures):
Quantitativo's engine.rs (5/5 real pub items captured correctly,
including impl-block methods), Executor.sol and HZ02.sol (perfect on
both, including the multi-line mintFromBatch signature), Rodax's
api_client.dart (correctly conservative -- ApiClient + setAccessToken,
generic methods correctly not matched rather than mismatched).
@neomaike
neomaike force-pushed the feat/rust-solidity-dart-adapters branch from 11b8fac to 268521f Compare July 30, 2026 23:14
@neomaike
neomaike merged commit be70813 into main Jul 30, 2026
3 checks passed
@neomaike
neomaike deleted the feat/rust-solidity-dart-adapters branch July 30, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Rust, Solidity, and Dart scanner adapters (item 7 remainder)

1 participant