Skip to content

[Repo] Migration assistant tool: parser + metaparser for .res→.affine skeletons #57

Closed

Description

@hyperpolymath

Proposal

Build a migration assistant: a tool that reads .res files, recognises common patterns, and emits .affine skeletons. Not a transliterator — the human picks the decomposition; the tool emits the AffineScript syntax.

Capabilities

  • Read .res AST (via ReScript's bs-tools or similar)
  • Map known patterns:
    • let _ = X.constructor → call out "explicit registration needed"
    • Promise.catch(e => ...) → "Promise umbrella, propose Result + sum"
    • Named-arg lambda blob → "row-poly handler record candidate"
    • 4+ tuple match → "Validation chain candidate"
  • Surface patterns without a clean translation (effect handlers, exceptions, mutable globals) for human decision
  • Output .affine skeletons + a comment-block listing the migration considerations

Why this is also the metaparser story

This tool is a parser for .res that produces an AST and emits .affine syntax — a metaparser, in the architectural-recommendation sense. Once it exists, the same machinery can drive other migrations (.ts→.affine, .js→.affine, etc.). It's the first concrete user of the "tree-sitter as canonical grammar source" architecture.

Why this matters for idaptik

idaptik has 542 .res + ~80 .ts files to migrate. By-hand is impractical; a per-pattern transliterator misses the point of re-decomposition. The migration assistant is the middle ground.


Filed from idaptik Wave 3 pilot. See LESSONS.md and PILOT.md. Idaptik commit hyperpolymath/idaptik@aef38db.

Activity

  1. hyperpolymath commented on May 21, 2026

    @hyperpolymath
    OwnerAuthor

    Corpus run 2026-05-21

    Phase 1 (#314) ran end-to-end against the estate's 491 deduplicated .res files (idaptik + gitbot-fleet). Two high-impact false-positive sources surfaced; precision pass landing in #319.

    Before After
    Total markers 2,146 348
    Files w/ markers 216 94
    side-effect-import 1,181 36
    mutable-global 653 0
    raw-js 198 198
    untyped-exception 114 114

    Full report and follow-ups in tools/res-to-affine/CORPUS-RUN.md (with CORPUS-RUN.json machine-readable sidecar) landing in #319. Phase 2 (tree-sitter AST walker) and Phase 3 (partial translation) remain as originally scoped.

  2. hyperpolymath commented on May 25, 2026

    @hyperpolymath
    OwnerAuthor

    Status update (2026-05-25)

    Re-checked at the owner's request while resolving the AffineScript-side blockers for the gitbot-fleet/sustainabot migration tracker. The picture:

    Phase Scope PR State
    1 Text-scan CLI, 4 of 6 anti-patterns, emits .affine skeleton + migration markers #314 ✅ merged 2026-05-20
    1 (precision) Corpus run (491 .res files) + column-0 anchoring on side-effect-import / mutable-global (markers 2,146 → 348, −84%) #319 ✅ merged 2026-05-21
    2a tree-sitter-cli build pipeline + migration-assistant CI job + grammar-pin drift signal #321 ✅ merged 2026-05-21
    2b First AST-based detection — tree-sitter walker for side-effect-import (--engine=walker); structural elimination of the column-0 regex false-positive class #322 ✅ merged 2026-05-21
    2c Walker ports for raw-js, untyped-exception, mutable-global; the two anti-patterns deferred from Phase 1 (inline lambda callback records, oversized functions); flip --engine=walker to default — 🟡 open
    3 Partial translation of pure-structural forms — the phase at which mechanical porting becomes meaningful (the explicit gate in sustainabot's tracker exit criteria) — 🟡 open

    Why this issue stays open. The original proposal is explicitly multi-phase; every landed PR uses Refs #57, not Closes #57. Phase 2c brings the walker to parity with the Phase-1 scanner across all six anti-patterns (and lets the default switch from scanner to walker); Phase 3 is the partial-translation phase that lets a consumer like gitbot-fleet/sustainabot actually do the mechanical port + diff-against-hand-port re-validation step its tracker calls for.

    bots/sustainabot/bot-integration/src/ hand-port (gitbot-fleet#148 / affinescript#199) is unblocked as a one-off today against the Phase-1+2a+2b surface (Json/Dict primitives now landed, see #161 #162); the residual re-validation against the mechanical path remains gated on Phase 3 here.


    Generated by Claude Code

  3. 92 remaining items

  4. hyperpolymath commented on May 30, 2026

    @hyperpolymath
    OwnerAuthor

    Phase 3 state update (2026-05-30) — slices 1+2 landed, tooling provisioned

    res-to-affine --translate now turns the fully-structural type declarations into compiler-verified AffineScript. Two slices merged to main, both with the generated output validated by the compiler itself (main.exe check → Type checking passed):

    Slice PR / commit Covers
    3.1 #477 / 2763909 primitive type aliases (type userId = int → type UserId = Int), simple sum types (type color = Red | Green → type Color = | Red | Green, payloads mapped)
    3.2 #481 / 3484010 record types → struct (type point = {x: int} → struct Point { x: Int }), generics ('a → [A], threaded through aliases / sums / records)
    tooling #482 / 138baac SessionStart hook provisioning the OCaml toolchain + tree-sitter so web sessions can dune build/dune runtest; STATE.a2ml note

    Conservative by construction: a declaration translates only when every part is representable; qualified-path RHS, non-primitive refs, nested generics (array<int>), object types, GADT returns, variant spreads, and mutable/optional-? records are skipped (kept in the marker block + quoted original), never mis-translated.

    Remaining Phase-3 work — this issue stays open:

    Per the repo's ISSUE-CLOSURE rule I'm not self-closing. If you consider the type-declaration coverage sufficient to call the assistant "done", close at your discretion (or say the word and I'll keep going on the next slice).


    Generated by Claude Code

  5. hyperpolymath commented on May 31, 2026

    @hyperpolymath
    OwnerAuthor

    Phase 3 — declaration translation complete (2026-05-31)

    res-to-affine --translate now translates every self-contained top-level declaration that can be lowered to compiler-verified AffineScript. Three slices landed; each generated form is confirmed by the compiler itself (main.exe check → Type checking passed):

    Slice PR / commit Adds
    3.1 #477 / 2763909 primitive type aliases; simple sum types (payloads mapped)
    3.2 #481 / 3484010 record types → struct; generics ('a → [A] across aliases/sums/records)
    3.3 #484 / ea8bef5 literal let → typed const (let answer = 42 → const answer: Int = 42;) for int/float/string/bool
    tooling #482 / 138baac SessionStart hook provisioning the OCaml toolchain so web sessions build/test out of the box

    Conservative throughout — qualified paths, non-primitive refs, nested generics, GADT returns, variant spreads, mutable/optional records, and non-literal/ref/destructuring lets are skipped (kept in the marker block + quoted original), never mis-translated.

    Scope boundary — the two items that remain

    These are out of the "every emitted form type-checks standalone" model by construction, not just unimplemented:

    • switch→match — a match is an expression, only meaningful inside a function body. Producing a type-checkable result means translating the whole enclosing function, but ReScript bindings are usually un-annotated (let f = x => …) while AffineScript fn requires param/return types — so the output wouldn't type-check. This belongs to a future partial-port mode (translate-with-TODO-holes) that drops the standalone-type-check guarantee. Different model, worth its own issue.
    • module-qualified references — Belt.Map.t now parses (the LANG: type/effect grammar has no module-qualified path — Pkg.Type/Pkg.Effect unrepresentable (estate-wide port blocker; ADR-014) #228 grammar gap closed) but won't resolve against a target module that doesn't exist yet; gated on a module-mapping story.

    Closure

    The declaration-translation milestone is complete within the type-checks-guarantee scope. The remaining work (partial-port mode for expressions/functions + module mapping) is a distinct effort. Per the repo's ISSUE-CLOSURE rule I'm not self-closing — your call whether to close this and track the partial-port mode as a fresh issue, or keep it open as the umbrella for those two items.


    Generated by Claude Code

  6. hyperpolymath commented on May 31, 2026

    @hyperpolymath
    OwnerAuthor

    Closing per owner direction (2026-05-31).

    The goal of this issue — the .res→.affine migration assistant (parser + metaparser + skeleton emission, through to translating self-contained top-level declarations) — is delivered:

    The remaining work — switch→match / function translation (partial-port mode) and module-qualified reference resolution — is a distinct model (it deliberately drops the standalone-type-check guarantee) and is now tracked in the successor #488.

    Closed as completed; further work continues under #488.


    Generated by Claude Code

  7. added 2 commits that reference this issue on May 31, 2026
  8. hyperpolymath commented on Jun 1, 2026

    @hyperpolymath
    OwnerAuthor

    Status update — AS stdlib codegen pattern now established (2026-06-01) 📌

    For ticket coordinators / anyone tracking this gap:

    What this means for the gap tracked in this ticket:

    • The codegen pattern is now a known repeatable shape: declare in stdlib/*.affine → wire in deno_builtins table + prelude → smoke-test in tests/codegen-deno/*.
    • This ticket remains open as a tracked gap; no new mass-port is being kicked off from this post.
    • Surfacing status so the broader picture stays accurate as more sub-tickets / dependent campaigns reference it.

    🤖 Cross-estate AS-status sweep on 2026-06-01.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions