Since there are no answers, here's the (slightly hacky) way I ended up doing it:
I installed the After-Brunch plugin and added these two commands to my config file, which get run after every compile by Brunch:
plugins:
afterBrunch: [
'find public/ -type f -name "*.coffee" -delete'
'coffee --compile --output public app/assets/'
]
So this simply explicitly deletes the .coffee files that Brunch moved to public/, and then compiles all the .coffee files in assets/ and moves them to public/. Hacky but it works fine.
This annoyingly feels quite backwards: it would have felt much cleaner (even if only in my head) to compile those .coffee files that were already moved into public/ by Brunch, but Coffee doesn't have an in-place conversion option (i.e. replacing the files) that I know of, and running the converter on the files in public/ first and then deleting all *.coffee files didn't work, because the delete command executed before the compile command was finished... But again, this distinction is probably just in my head -- it's just as efficient doing it this way.
If anyone has a more Brunch-like solution, that would be great.