Feature, bug or emergent property?
I don’t think the distinction matters as much as the reality.
We’re being locked into using the AI tooling bc the code was generated with AI tooling.
These giant files of doom were being generated by humans anyway and were very hard to work with. With LLMs it’s at last manageable or feasible to edit, refactor etc.
I honestly think LLMs are going to save us from ourselves as the codebases became too large and “messy” for humans to comprehend. (Mono repos of doom)
On a personal level these giant files are abhorrent but that’s just personal taste and I don’t think any of the Martin Fowler refactor/cleanup stuff is going to matter at all anymore. Kinda sad on some level.
Refactoring towards good abstractions is more powerful than people realize. There's information theoretic bayesian math to back this up.
It's a bit of a divine coincidence that software that is more economically and energy efficient to process and run tends to also be more correct.
It's all about reducing the entropy of your code. https://benoitessiambre.com/entropy.html
Boring: The documentation should be in code, not in external Word documents uploaded to the company SharePoint server.
Exciting: The documentation for the AI should be in code, not in external Word documents uploaded to the company SharePoint server.
Boring: You should give your developers the big picture of the project, not just micromanage them using Jira tasks.
Exciting: You should give your AI the big picture of the project in CLAUDE.md, not just micromanage it using prompts.
Boring: Refactoring makes your developers more productive in long term.
Exciting: Refactoring makes your AI more productive in long term.
An agentic refactoring pass does make sense cause one LLM reviewing work can spot things the ‘generator’ LLM missed while focused on the initial task output
But can the reviewer agent ever actually have in mind what this project actually is? And how the code all comes together to do the work involved? In other words what parts of the code are redundant or can be made more elegant
Asking coding agents to refactor your codebase is maybe like asking trauma surgeons to increase your exercise capacity. The agents are gonna need a really holistic POV to do this properly
I guess part of my point is that just splitting big files into multiple files is only refactoring in a superficial sense without having a theory of what code belongs together and what can be extracted into utility functions etc. Is splitting files actually like decomposing factors or is it like splitting a larger number into smaller numbers that still eventually get added together
A good example of what I mean is that agents often don’t ~actually~ understand the whole system anyway. They might implement a system to store and calculate something that is already being fetched via API. Humans often have a dual perspective — a holistic sense of the project and (when applying our mind to a task) a precise scalpel: ‘oh if we just look at this this JSON it has a key with this data already’
I appreciate the effort to quantify the benefit rather than pontificate. It's worth mentioning Martin Fowler wrote a whole book on refactoring [1], in which he states, "to refactor, the essential precondition is [...] solid tests", which I think is the real benefit here, AI or not. Good tests protect against regressions, whether human or robot. They also help encode the spec, which humans and robots can read.
[1] https://www.oreilly.com/library/view/refactoring-improving-t...
async fn watch_item(&self, item_id: &str, user_id: &str) -> Result<()> async fn unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()> async fn watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>
This shows the limitations of vibe coding. It takes someone with a long history of software development to prompt for something like this.
Even though the model is writing 100% of the code, still needed someone with a lot of programming knowledge to write the prompt.
I am just sitting here waiting for Grady Booch to write "Architecture!"
Still a nice writeup and love how these meta analysises (presumeably) done via AI can now easily capture metrics that inform your workflow.
Make it right is the refactoring
Why is this something users have to distinguish? Why can't these generative AIs choose good names for things?
Right, I've never seen an agent produce code anywhere near as bad as some of the human-generated code that I've worked on.
And, if your agent is producing huge files or functions, you can just tell it not to and it'll comply.
> Claude is unable to look at code, look at refactorings in general and work out which are suitable to apply: a human needs to actively guide it.
Claude is happy to produce a very large Rust file. But you need human guidance to make it smaller.
It matches my experience which is that LLMs greatly benefit from well factored code, but are not particularly adept at creating such code.
Much like most human developers I suppose!
It is rather different but another piece of research I liked for the same reason was this report that interviewed Boko Haram members about how they used AI to assist terrorism. You get these interminable online debates that are so unproductive and reporting that is specific is such a breath of fresh air.
This just fundamentally isn't true and if this is your perspective then you're using LLMs wrong.
It was helped by having refactoring experience and approaches to codebases by others, and in my case, being the original architect and being able to speak to the original and current intents, where needed.
This was using a less common, but capable and easy language for the LLM without a ton of dependancy brittleness to manage.. once the effort to remove javascript/python bias was in place, it became so powerful that once the lightbulb went on, it really got cruising.
The project was playing in the world of JSR-223 languages, where you could script in many popular languages, but it all got to run in the JVM, which was an environmental requirement.
https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platfor...
If you ask it to implement a specific kind of refactoring over a specific section of code it seems that current LLMs can do just fine. Even things as complex as "use `functools.partial` to implement the Command pattern here, rather than dataclasses".
> Is splitting files actually like decomposing factors or is it like splitting a larger number into smaller numbers that still eventually get added together
The file boundaries represent logical subsystem boundaries in the code, making it easier to reason about. The training data abundantly represents the idea of treating the contents of another file as opaque by default while other functionality in the same file can be used freely. I think it's reasonable to assume that there's something objective about the benefits that humans get from this, and it isn't just a consequence of how human cognition works.
In all corners of our world and the universe at large, reducing entropy in anything can be thought of as “building.”
It's very important that you are using coding agents with the latest frontier models and know exactly what it can't and can't do if you want to be hired in this market.
In other words, this is not some new AI-bandwagon-jumping blogger trying to rebrand old practices as something new ... this is the guy who coined the very term "refactoring"! He's not pretending what's old is new, he's arguing that best practices from 20+ years ago remain relevant (and showing receipts).
Next up is demonstrating the AI is more productive with better programming languages.
There’s further work to do to understand exactly what’s going on here.
Most of software development is looking at some process and then decomposing it recursively until you get to those molecules/atoms of the computing world. Coding them is trivial, and while you can gain a certain boost from the AI, after a while you no longer have to write that much code. It will turn into a balancing act where the introduction of a new concept has to be done carefully.
In some projects, the code might not have much, or enough testing, or documentation/commenting.
Code is largely for others and the future if the creators of it want to move onto other projects.
I still get a big win from AI on the net, but you do need to budget some time to clean up. I'm still on team "read every line".
In fact this is a case where I deliberately deferred some review because I was a blocker for another team. Now that I've got something to them I'm going back and I'm going to eat a bigger chunk of debt than I normally would, but it's worth it for unblocking the other team sooner. AI has made tech debt easier to take out, in all senses of that term.
It is also pretty decent, in my experience, at being guided into how to fix tech debt. Some other people's experience varies: https://news.ycombinator.com/item?id=49035455 YMMV.
I've had some luck while refactoring by helping it shape how to refactor, which can improve how well factored code should look like.
Providing examples of well factored code can go a long way, even if it's an open source repo of what to do / not to do.
Key parts of the abstract:
> This raises a question the programming-language community has not previously had reason to ask: should error-message detail be calibrated differently for AI agents than for humans?
> We investigate this question through a controlled experiment using Shplait, an ML-style statically typed language. We construct a suite of programs containing a single deliberate type error each, and measure how often an AI agent repairs them under ablation: a detailed error context using the unification stack; a proximate error location; a minimal type error; and a dynamic (test suite) error only. An automated oracle uses a test suite to classify each repair attempt as a type error, semantically incorrect, or semantically correct.
> We find concrete evidence that more detailed error messages generally improve an agent's ability to fix type errors. We also find that the presence of a type system appears to help more than only test suite failure reports.
That actually would be pretty exciting!
But, wouldn't AI be biased toward more popular languages, since those will by nature of their popularity provide more sources for training material?
Even still, the AI could demonstrate which algorithms or maybe patterns and techniques are more productive, in the context of the popular languages.
Taking a component and turning it from 7K lines to 3K lines and maintaining functionality obviously means there’s less complexity introduced, less to go wrong now, and less overhead to modify in the future.
Sure it can go the other way, the component needs to support something it might need, we need to adjust larger patterns, this function needs to be refactored into something more robust.
But lines of code is a pretty decent metric of success for “trimming down and cleaning up” style refactoring, to me at least. It’s not everything of course, but it’s definitely an indicator.
- the code is essentially good but all is in one file, you split it up, lines of code stay the same
- the code is essentially good but lacks some structure, for a function that does five things directly, you extract the functionality into five functions and call them from the original function, lines of code goes very slightly up
But once the code is actually bad - code duplication, bad abstractions, inefficient language use, ... - I would generally expect the lines of code to significantly drop. What scenarios are there where the code is actually bad but refactoring does not reduce the lines of code? It is certainly possible but at moment I am having a hard time comming up with a good example.
I am currently working 3 remote jobs thanks to agentic coding and one of the jobs require me to interview and hire other devs. I find it amusing how many are irrationaly resistant and unwilling to use agentic coding. They are also the loudest in the room with opinions that are outdated. On the flip side, it's just as funny how to see how complicated engineers try to make agentic coding. It doesn't have to be and that's not its design. Don't get in its way as much as possible. The frontier models are good enough that adding complexity only wastes more tokens. We've come a long way from the summer you were fiddling with Sonnet 4.5 in Cursor or windsurf.
The job market isn't suddenly going to swoop in and save you by banning AI agents. Keep up or get left behind. Don't let other people's opinions about AI agents hinder your own progress, chances are they have no skin in the game.
As an example, 110v American outlet plugs are almost certainly not the most efficient way to power devices and appliances; but, because they are so standard and are good enough, using them massively eases the burden of making, buying and selling powered appliances in the US. Similar story with USB C.
Since LLMs are word generators, and have a propensity to generating words, they need to be shaped to understand simplest is best, more isn't more, and less isn't more always.
Trimming down and cleaning up could be formatting, standardization, commenting, or even some basic re-architecting that was overdue.
One of the biggest benefits of llms for refactoring I'm finding is reducing technical debt.
Total LOC is a garbage metric. Things like reducing line count in specific files or components is a big benefit, but those lines are often moved, not dropped.
IMX: the scenarios where people hold higher standards than what you describe :) (i.e. such that "the function does five things" is deemed "actually bad". Of course, that does depend on the refactoring not causing an unacceptable performance hit, which can happen depending on the environment.)
On the other hand, having a bunch of different languages is an advantage. People can pick the best tool for the job, and experiment with new language features.
The deferral of all agency as a product owner over to the stochastic-word-box-in-a-do-loop feels like the kind of thing that, if you mentioned you do that in an interview, would be grounds to not continue.
Re: your updated comment, I'm glad you've found success with this pattern of behavior, but I'd suggest that you might be extrapolating "I haven't been punished yet for this shortcut" to "This is the future of work and those who disagree are luddites".
"enhance"
fwiw codex is very good. arguably better than the sonnet equivalent (value-wise). I use both daily, professionally. And "refactor this code spin up subagents" is hardly an expert-level "I wouldn't hire you" flex.EDIT: agree with your comment updates. I'd just say it more positively leaning especially to people who are stressed financially. SOTA has advanced a lot so counter-intuitively being behind isn't really behind, it can almost be a way to leap-frog, lean on the Agents. But definitely you have to commit to putting in the reps and learning and not just mindlessly instructing AI to "fix it". Think that's where the discussion splits.
True, and to my point, "popular" isn't necessarily "better".
> because they are so standard and are good enough, using them massively eases the burden of making, buying and selling powered appliances
Yes, but LLMs can do the heavy lifting when analyzing the "better programming languages" for productivity. There will be a bias toward popularity in their training sources, though.
What I really want to get at is the distinction between leaving the actual code untouched and just moving it a bit around - to other functions, other classes, other files - and having to change the code - from deduplicating to completely rewritting it.
As part of getting to grips with the new world of agentic engineering, I built an application to support my work. It’s a sophisticated app: high-quality web UI with dynamic refresh and look-up, modals and auto-save, integrations to external systems, machine learning and text analysis, background jobs, and a proper environment setup with fully automated deployment. It’s approximately 150,000 lines of code, primarily in Rust (~120 kLoC) with the remainder in TypeScript and Terraform.
This was entirely written by agents. Mostly Claude Code, and some use of Cursor. I didn’t read or review any of the code, except occasionally, out of interest.
While building the application, I could see some things going awry. After watching an edit to line 4,000 of a file scroll by in the terminal, I had a closer look. The data access layer had grown to over 6,000 lines. As more features landed, this continued to grow. Every query, read or write, repeated the same HTTP request setup, the same JSON encoding and decoding. Eventually, it reached 17,155 lines. In a single Rust file.
The 17,155 line file was the entire data access layer. A single, self-contained module. Reviewing the code, there was no de-duplication, no internal language, limited extraction of functions, and very little extraction of classes. It did have a clear boundary with an interface to preserve. It was a great target for refactoring.
The goal of refactoring an agentic code base is to spend tokens now in refactoring to make token consumption for future work lower. An experiment should be able to show that as this file was refactored the token cost of making separate feature implementations in this code base would decrease.
Precisely because agents never learn this was now possible to run as an experiment. I could prompt a fresh agent to make exactly the same change after every refactoring stage. Unlike a human engineer, the experiment would not be tainted by learning from previous steps.
The prompt used for the representative change and the refactoring steps applied are shown in the appendices, below.
One caveat: Claude doesn’t provide reliable methods for counting tokens live despite showing token counts, reporting tokens consumed per session, and billing for tokens. I’m assuming this is a temporary issue that will improve over time. Instead, the sub-agent reported the number of characters received and sent and used tiktoken to approximate tokens, by dividing character count by four.
| Step | Data Access Layer LoC | Largest file LoC | Total Rust LoC | Input tokens per change | Output tokens per change | Time per change (s) |
|---|---|---|---|---|---|---|
| Baseline | 17,155 | 17,155 | 50,359 | 159,564 | 1,705 | 342 |
| Step 1 (FirestoreClient) | 16,706 | 16,706 | 49,910 | 155,205 | 1,723 | 530 |
| Step 2 (extract_doc_id, new_link) | 16,562 | 16,562 | 49,766 | 159,227 | 2,105 | 574 |
| Step 3 (link-query helpers) | 16,567 | 16,567 | 49,771 | 154,054 | 2,105 | 524 |
| Step 4 (FakeStore predicates) | 16,577 | 16,577 | 49,781 | 154,146 | 2,060 | 654 |
| Step 5 (value ctors) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| Step 6 (FieldsBuilder) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| Step 7 (queries.rs) | 16,474 | 15,670 | 49,678 | 151,850 | 1,800 | 587 |
| Step 8 (traits.rs) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| Step 9 (traits/ split) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| Step 10 (codec.rs) | 16,521 | 12,846 | 49,725 | 131,871 | 1,750 | 540 |
| Step 11 (fake_store.rs) | 16,535 | 11,122 | 49,739 | 133,016 | 2,460 | 600 |
| Step 12 (store/ split) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| Step 13 (co-locate tests) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| Step 14 (complete fake_store.rs) | 16,553 | 7,225 | 49,757 | 107,205 | 2,453 | 523 |
| Step 15 (store/ split) | 16,608 | 3,695 | 49,812 | 27,360 | 2,113 | 454 |
The interesting metrics here are the total lines of code in the data access layer, the total lines of code in the largest single file in the data access layer and the input tokens consumed while producing the change.

This chart shows four things. The first point is the baseline, step 0, and then the same metrics are repeated after each refactoring step has been applied.
The results are clear. Input tokens stay fairly flat until the largest file starts to fall, and then they drop before, in the words of Claude, falling off a cliff.
Between the base line and the final refactoring, input tokens for the same task reduced from 159,564 to 27,360. A saving of 132,204 tokens, or 83%. And that saving is not a one-off. Every single change that touches the data access layer from this point forward now costs significantly less.
How much of a saving? Assuming Sonnet 5 pricing at the time of writing of $3/MTok, 39.7 cents. Not a lot. Does it multiply? How will this play out across debugging? More complicated features? This is refactoring only one portion of the code base, can the whole code base be aggressively refactored to find savings everywhere? How much would those refactorings cost?
This saving is because the agent has to read less code. But it is not because there is less code to read. The overall code in the data access layer as a whole has stayed fairly constant. Therefore to be able to bank this saving, the agent must be able to successfully identify the smallest subset of files necessary to read. The results make it appear this was happening. Reading the Claude Code thinking output and file read summaries as the change was being applied also indicates the sub-agent was successfully reading smaller and smaller sections of code each time.
In other words, randomly cutting the file into smaller files is unlikely to help as much: even if each file were smaller, the agent would be forced to read through many files looking for the relevant code. While the step with the biggest effect happens at the end, the previous steps were refactorings to set up this saving. This was not planned. It was simply a result of how refactoring typically proceeds: local file changes to extract duplication, before breaking down into smaller files once a repeating core emerges.
The refactoring did not make the representative change smaller. The number of tokens produced when writing code was largely unaffected: the output tokens do not move very much. Those tokens are five times the price of the input tokens. But, there are a lot less of them. Are there refactorings that could be applied to reduce output token production? I need a more complex sample change to explore these questions. The noise of the non-deterministic code generation process is hiding any variance caused by changes in the factoring of the code.
Claude was not good at refactoring. If you read the prompt and the refactoring steps below, it’s clear that the refactorings produced were directly in response to the prompt. Claude is unable to look at code, look at refactorings in general and work out which are suitable to apply: a human needs to actively guide it. This marries with wider experience in this app. The development harness includes an explicit refactoring step. That refactoring step did not prompt Claude into improving this file. More anecdotally, Claude.ai was better than Claude Code. I used both interfaces to create the refactoring plan. Claude Code spotted extract function as the first step. Claude.ai went further and saw an entire client class to be extracted.
It was also bad at applying them. The mechanical act of refactoring was performed by writing Python scripts using grep and sed. These scripts frequently got confused by indentation. Oh, the irony. In addition, the single most valuable refactoring was missed in the first pass, and had to be re-applied as a follow-up step. This is why the number of steps in the figure don’t match the refactoring steps in the appendix.
It took about eight hours to complete the entire experiment. This was mostly unattended. The only intervention was after six hours 40 minutes when it appeared to have finished, but had skipped that step and needed to be redirected. This experiment was running on slow hotel WiFi. I wondered if that contributed to time taken. But on deeper analysis of the code base, the cargo temporary build cache had become very large. Test execution was suffering, significantly.
Unfortunately, it didn’t occur to me to perform a count of the tokens required to create and execute the refactoring plan until it was already complete. I’ve looked at my aggregate consumption across the time window where I was doing this work, including designing and running the experiment. I can’t say how many tokens were required to perform the refactoring. The upper bound is five million, however. This includes creating the refactoring plan twice, the work to design the experiment including the representative change, and various other tasks. Future work should include a more accurate count of tokens consumed to refactor.
This is just one experiment, on a significant application that is still greenfield and built and maintained by a single developer. But, I believe this is a potentially interesting first step. This effort shows the value, in time and money of refactoring. As well as measuring how expensive refactoring is. It would be interesting to look at more complex changes, at wider refactoring, refactoring continuously, and even the relative value of different refactoring approaches.
This is just the beginning.
Note: These appendices include the prompts that I used, and the output that was returned. The only editing applied has been to remove the specific code changes to be made. These are included without editing to show how the agents were directed. There are no hidden tricks. As such, there is some language in here that might be confusing. The error is in the original.
The representative change
This is the recorded prompt that was fed to each sub-agent, there was no further context supplied other than the code base and accompanying architecture documentation. Every sub-agent was starting with exactly the same information.
You are working in the Rust project at
~/dev/your-project-name.Add a new
ItemWatchStorepublic async trait to the Firestore layer, following existing patterns exactly. The trait must have three methods:
async fn watch_item(&self, item_id: &str, user_id: &str) -> Result<()>async fn unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()>async fn watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>Watches are stored in a
item_watchesFirestore collection. Each document has fields:itemId(string),userId(string),createdAt(timestamp). There is no Rust struct for a watch record — the methods returnVec<String>(item ids).Implement the trait for both
FakeStore(using an in-memoryVec<(String, String)>field added toFakeStoreInner) andFirestoreStore(using the same HTTP patterns used for other store impls in this file).At the very end of your response, output exactly this JSON block (fill in real values):
{ "files_read": [ {"path": "src/firestore.rs", "chars": 123456}, ... ], "response_chars": 7890 }Do NOT commit the change. Stop after writing the code.
Refactoring steps
This is the prompt that was used to create the refactoring plan.
Following the strict definition that a refactoring is a provably correctness preserving series of code edits, and using Martin Fowler’s 2nd edition of Refactoring as the source, examine @src/firestore.rs. This is a 17K LoC Rust file. No file should be that long. It is almost certainly not using an internal language to build and manage queries. Produce and describe, but don’t execute, a sequence of refactorings that would massively reduce the line count of that file, without changing the interface at all.
Following is the description of the refactorings applied, extracted from the plan built and followed by Claude. The actual plan includes predicted code changes. For each refactoring, the individual steps to follow were listed. Each of those steps was individually testable, and was individually tested. This is a stricter refactoring than most human engineers would follow.
The steps listed here don’t line up directly with the measured changes above as Claude skipped the most valuable single refactoring (splitting out the store into sub-files) on the first pass and had to complete that afterwards as two additional steps.
Fowler ref: Extract Class (7.5); Extract Function (6.1) for each primitive
FirestoreStore currently conflates two responsibilities:
Fowler §7.5 calls for extracting a new class when you can identify a coherent subset of a class’s data and behaviour. The transport responsibility owns: client: reqwest::Client, project_id: String, MetadataAuth, and documents_url() / auth_header(). Extract these into a new FirestoreClient struct.
Estimated savings: ~1,200 lines in FirestoreStore impls; FirestoreClient adds ~120 lines net.
extract_doc_id and new_link (Fowler §6.1)Fowler ref: Extract Function (6.1)
extract_doc_id — The expression doc.name.rsplit('/').next()?.to_string() appears verbatim at the start of all 20 parse_*_document functions. Extract it.
new_link — Building a Link struct with metadata: HashMap::new() and provenance: None and a fresh UUID appears 62 times. Extract a factory function.
Estimated savings: ~500 lines (62 × ~10-line structs → 62 × ~2-line calls; 20 parse functions each lose 1 line of boilerplate).
Fowler ref: Extract Function (6.1)
Two sub-patterns recur inside the FirestoreStore trait impls after running a link query:
Pattern A — collect all link documents from query rows (~15 sites).
Pattern B — query links and return exactly one target ID, error if missing (~8 sites):
Estimated savings: ~200 lines.
Fowler ref: Extract Function (6.1)
Inside the FakeStore impls, ~15 methods repeat variations of inner.links.iter()....
Extract two methods on FakeStoreInner. The 15 callsites then become single-line. Methods that additionally filter by a second predicate (e.g. also checking to_kind) chain .into_iter().filter(…) on the result of the helper.
Estimated savings: ~120 lines.
Fowler ref: Replace Inline Code with Function Call (8.5)
Add four private free functions (file-level, not methods) before the codec block. Replace all 128+ json!({"stringValue": …}) / json!({"timestampValue": …}) etc. inline expressions with calls to these functions. Each multi-word json macro call becomes a single short call.
Estimated savings: ~80 lines (mostly from multi-line json macros collapsing to one-liners).
Fowler ref: Extract Class (7.3)
The ~20 encoder functions all follow this shape:
let mut fields = serde_json::Map::new();
fields.insert("foo".to_string(), str_val(&x.foo));
fields.insert("bar".to_string(), ts_val(x.bar));
json!({"name": path, "fields": fields})
Extract a small builder. Rewrite each encoder function to use the builder. A ~40-line encoder shrinks to ~12 lines.
Estimated savings: ~500–600 lines across the 20 encoder functions.
Fowler ref: Move Function (8.1)
Convert src/firestore.rs to a module directory: rename to src/firestore/mod.rs. Then create src/firestore/queries.rs and move all 32 LinkQuery constants and the LinkQuery/EqFilter/EqValue/Ordering/Direction type definitions into it. Add pub(super) use queries::*; in mod.rs.
No behaviour changes; all callsites already reference names that were in scope via the flat file.
Reduces mod.rs by ~800 lines.
Fowler ref: Move Function (8.1)
Move all 17 pub trait definitions (and their associated error types) to src/firestore/traits.rs. Re-export them from mod.rs with pub use traits::*;.
Reduces mod.rs by ~1,900 lines. Produces a ~1,900-line traits.rs that needs further decomposition.
traits.rs into a traits/ module directoryFowler ref: Move Function (8.1)
Convert src/firestore/traits.rs to a module directory by grouping the 17 traits into four domain-aligned files:
| File | Traits | Approx lines |
|---|---|---|
traits/planning.rs |
ConcentrationStore, GoalStore, ItemStore, NoteStore, PursuitStore, FocusPassStore |
~650 |
traits/content.rs |
CaptureStore, TagStore, UrlReferenceStore, DocumentStore, PaperStore |
~550 |
traits/people.rs |
ThoughtworkerStore, ExternalContactStore, CompanyStore |
~300 |
traits/system.rs |
SessionState, LinkStore, SuggestionStore, SuggestionVetoStore, OAuthTokenStore, MigrationLedger, EmbeddingStore, RuntimeConfigStore, SalesforceSyncStateStore |
~400 |
traits/mod.rs becomes a pure re-export file (~20 lines). Associated error types (FocusPassError, SuggestionDecisionError, etc.) move with the trait that produces them.
No trait definition changes, no callsite changes — only relocation. Each resulting file is 300–650 lines.
Fowler ref: Move Function (8.1)
Move all document encoder/decoder functions (*_document, parse_*_document, kind_str, parse_kind, parse_capture_source, parse_outcome, etc.) plus FieldsBuilder and the value constructors from Steps 5 and 6 into src/firestore/codec.rs. Make them pub(super).
After Step 6 this module will be ~400–500 lines rather than ~1,200.
Reduces mod.rs by ~500 lines (post-Step-6).
Fowler ref: Move Function (8.1)
Move FakeStore, FakeStoreInner, and all 18 trait impl blocks for FakeStore into src/firestore/fake_store.rs. Re-export FakeStore from mod.rs with pub use fake_store::FakeStore;.
FakeStoreInner and helper methods stay private to the module.
Reduces mod.rs by ~4,700 lines.
FirestoreStore impls into per-trait files under src/firestore/store/Fowler ref: Move Function (8.1)
Create src/firestore/store/mod.rs with FirestoreStore struct definition, impl FirestoreStore (constructor + FirestoreClient from Step 1), and MetadataAuth.
Then create one file per logical domain grouping.
Each file contains only use super::*; (or explicit imports) and the trait impl block(s). No type definitions, no helpers. Helpers used by multiple impl blocks stay in store/mod.rs.
Reduces what would be a ~10,000-line file into ten files of 120–650 lines each. mod.rs becomes a ~100-line re-export manifest.
Fowler ref: Move Function (8.1)
The existing #[cfg(test)] modules test specific domain areas and belong with the modules created in Step 12 rather than in a single tests.rs.Each test module moves inside a #[cfg(test)] mod tests { … } block at the bottom of the target file, with use super::*; to access the module’s internals. No test is changed, only relocated.
Any shared test fixtures (FakeStore::new, helper builders) that are already in fake_store.rs are accessible via the existing use super::fake_store::FakeStore import chain.
Reduces mod.rs by ~2,000 lines; each target file gains 200–700 lines of tests that are directly adjacent to the code they exercise.