Skip to content

refactor(bot): split fsm.rs into fsm/{state,status,machine,flow,encoding}#48

Merged
BlindMaster24 merged 2 commits intomainfrom
devin/1776939597-split-bot-fsm
Apr 24, 2026
Merged

refactor(bot): split fsm.rs into fsm/{state,status,machine,flow,encoding}#48
BlindMaster24 merged 2 commits intomainfrom
devin/1776939597-split-bot-fsm

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 23, 2026

Summary

bot/fsm.rs had grown to 559 lines holding five distinct concerns
behind a single file:

  • lifecycle enums (DialogStatus, DialogTimeoutPolicy) with their
    wire encode/decode helpers;
  • the persistent DialogState record with its netstring-based
    encode() / decode() plus builder methods;
  • the self-contained DialogFlow declarative step sequence;
  • the DialogMachine mutating façade over &mut dyn StateStore
    (lifecycle, timeout enforcement, metadata, flow navigation); and
  • shared low-level plumbing (netstring codec, wall-clock helpers,
    session-id generator, internal metadata keys, and the encoding
    version constant).

Split along those natural boundaries per the AGENTS.md guideline
("split files when a module grows beyond ~400-600 lines or mixes
multiple responsibilities"):

  • fsm/mod.rs - module wiring + public re-exports.
  • fsm/status.rs - DialogStatus, DialogTimeoutPolicy, encode/decode.
  • fsm/flow.rs - DialogFlow (self-contained).
  • fsm/encoding.rs - netstring codec, now_unix_ms /
    duration_to_millis, generate_session_id, DIALOG_ENCODING_VERSION,
    INTERNAL_SESSION_KEY, INTERNAL_TIMEOUT_POLICY_KEY. Everything here
    is pub(super) so the encoding helpers stay invisible outside the
    fsm/ module tree.
  • fsm/state.rs - DialogState + encode/decode + metadata helpers.
  • fsm/machine.rs - DialogMachine lifecycle/timeout/flow operations.

External call sites are unchanged

bot/mod.rs kept its re-export:

pub use fsm::{DialogFlow, DialogMachine, DialogState, DialogStatus, DialogTimeoutPolicy};

So bot::{DialogFlow, DialogMachine, DialogState, DialogStatus, DialogTimeoutPolicy} continue to resolve bit-for-bit. The external
users (lib.rs, bot/context.rs with 26 references) compile unchanged.

Structural only, no semantic change

  • Public items keep their paths (bot::*) and their public
    visibilities.
  • DialogState fields remain pub (the struct was pub +
    #[non_exhaustive] before).
  • Internal helpers (netstring, parse_netstring, now_unix_ms,
    duration_to_millis, generate_session_id) downgraded from
    private-to-single-file to pub(super) so siblings inside fsm/
    can call them; they were already invisible outside the file, and
    remain invisible outside the module tree.
  • DIALOG_ENCODING_VERSION, INTERNAL_SESSION_KEY, and
    INTERNAL_TIMEOUT_POLICY_KEY lived as private module-level
    constants in fsm.rs; now pub(super) in fsm/encoding.rs so
    state.rs and machine.rs can reference them. Same effective
    privacy.
  • Method bodies in all five impls (DialogState, DialogStatus,
    DialogTimeoutPolicy, DialogFlow, DialogMachine) are
    byte-for-byte identical to the pre-split file.
  • Doc comments added on public items where missing_docs = "deny"
    started flagging them in their new homes. No behavioral effect.

Net diff: fsm.rs deleted (559 lines), six new files created
(mod.rs ~35, status.rs ~55, flow.rs ~100, encoding.rs ~75,
state.rs ~250, machine.rs ~215). Git detects
fsm.rs -> fsm/state.rs as the primary rename.

Local verification:

  • cargo fmt --all --check clean
  • cargo clippy --workspace --all-targets --all-features -- -D warnings clean
  • cargo test --workspace --all-features -> 287 passed / 0 failed

Review & Testing Checklist for Human

  • Spot-check fsm/mod.rs re-exports: bot::DialogFlow,
    bot::DialogMachine, bot::DialogState, bot::DialogStatus,
    bot::DialogTimeoutPolicy must all still resolve from
    downstream crates. bot/context.rs (26 references) is the
    biggest internal user and compiles unchanged.
  • Confirm the visibility changes are acceptable: the private
    constants and helpers that were previously private to a single
    file are now pub(super) inside the fsm/ module tree. The
    struct DialogState keeps its pub fields as before; the
    DialogStatus/DialogTimeoutPolicy encode/decode helpers
    are pub(super) (previously private; they are only called by
    state.rs).
  • Legacy decoder path still works: DialogState::decode retains
    the dialog|step fallback for values that predate
    DIALOG_ENCODING_VERSION = "v2". This matters for stores that
    were populated by an older bot build.

Notes

This is the next PR in the P0 structural refactor queue after #44
(dedupe can_issue_logged_in_command), #45 (extract
poll_command_completion), #46 (split client/bus.rs), and #47
(split bot/storage.rs, currently open).

Next up: client/hooks/builders.rs (574 lines),
types/entities/media_common.rs (458 lines), and the
client/backend.rs (1334 lines) + client/backend_mock.rs (1101
lines) pair. Then the P1 API tweaks (jitter, StreamTypes,
SdkErrorCode, TimeoutKind, SecretString, indexed dispatch) all
with their own tests. Finally a big #58 test-fill PR driven by
scripts/audit_teamtalk_coverage.py.

Branches from clean main; all the refactor PRs are independent
against main so they can be merged in any order. If any touch the
same file I'll rebase the later one after its predecessor lands.

The pre-existing semver CI gate is expected to remain red;
release-plz handles the eventual version bump. Every other check is
expected to pass.

Link to Devin session: https://app.devin.ai/sessions/71fdd6196cb74723a2e277bb81993a9c
Requested by: @BlindMaster24


Open in Devin Review

…ing}

bot/fsm.rs had grown to 559 lines holding five distinct concerns
behind a single file:

* lifecycle enums (DialogStatus, DialogTimeoutPolicy) with their wire
  encode/decode helpers;
* the persistent DialogState record with its netstring-based encode()
  / decode() plus builder methods;
* the self-contained DialogFlow declarative step sequence;
* the DialogMachine mutating facade over &mut dyn StateStore (lifecycle,
  timeout enforcement, metadata, flow navigation); and
* shared low-level plumbing (netstring codec, wall-clock helpers,
  session-id generator, internal metadata keys, and the encoding
  version constant).

Split along those natural boundaries per the AGENTS.md guideline
("split files when a module grows beyond ~400-600 lines or mixes
multiple responsibilities"):

* fsm/mod.rs - module wiring + public re-exports.
* fsm/status.rs - DialogStatus, DialogTimeoutPolicy, encode/decode.
* fsm/flow.rs - DialogFlow (self-contained).
* fsm/encoding.rs - netstring codec, now_unix_ms / duration_to_millis,
  generate_session_id, DIALOG_ENCODING_VERSION, INTERNAL_SESSION_KEY,
  INTERNAL_TIMEOUT_POLICY_KEY. Everything here is pub(super) so the
  encoding helpers stay invisible outside the fsm/ module tree.
* fsm/state.rs - DialogState + encode/decode + metadata helpers.
* fsm/machine.rs - DialogMachine lifecycle/timeout/flow operations.

External call sites are unchanged. bot/mod.rs kept its re-export:

    pub use fsm::{DialogFlow, DialogMachine, DialogState, DialogStatus,
                  DialogTimeoutPolicy};

So bot::{DialogFlow, DialogMachine, DialogState, DialogStatus,
DialogTimeoutPolicy} continue to resolve bit-for-bit. The external
users (lib.rs, bot/context.rs with 26 references) compile unchanged.

Structural only, no semantic change:

* Public items keep their paths (bot::*) and their public
  visibilities.
* DialogState fields remain pub (the struct was pub + #[non_exhaustive]
  before).
* Internal helpers (netstring, parse_netstring, now_unix_ms,
  duration_to_millis, generate_session_id) downgraded from private-
  to-single-file to pub(super) so siblings inside fsm/ can call them;
  they were already invisible outside the file, and remain invisible
  outside the module tree.
* DIALOG_ENCODING_VERSION, INTERNAL_SESSION_KEY, and
  INTERNAL_TIMEOUT_POLICY_KEY lived as private module-level constants
  in fsm.rs; now pub(super) in fsm/encoding.rs so state.rs and
  machine.rs can reference them. Same effective privacy.
* Method bodies in all five impls (DialogState, DialogStatus,
  DialogTimeoutPolicy, DialogFlow, DialogMachine) are byte-for-byte
  identical to the pre-split file.
* Doc comments added on public items where missing_docs = "deny"
  started flagging them in their new homes (no behavioral effect).

Local verification:
* cargo fmt --all --check clean
* cargo clippy --workspace --all-targets --all-features -- -D warnings clean
* cargo test --workspace --all-features -> 287 passed / 0 failed
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review flagged: AGENTS.md 'Current Module Baseline' still
described src/bot/fsm.rs as a single file. Update the entry to
describe the new src/bot/fsm/ directory-first layout, matching the
pattern used for bot/router and client.
@BlindMaster24 BlindMaster24 merged commit 3b57ac1 into main Apr 24, 2026
11 of 12 checks passed
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.

1 participant