Phase 4 — categorization + bucket routing (fula-mcp)
Pure-logic phase (no network, no crypto). Port FxFiles file classifier + category->bucket mapping to Rust so AI-stored files get the EXACT same ShelfCategory and native v8 bucket as the FxFiles app, enabling later adoption.
Scope
category module in crates/fula-mcp/src/:
Category enum mirroring Dart ShelfCategory (link, note, screenshot, image, video, audio, document, file, other) with the same order.
classify(mime: Option<&str>, filename: &str, text: Option<&str>) -> Category porting ShelfClassifier.classify exactly: text/plain (or no-MIME) + non-empty payload short-circuits to link (single URL) or note; image/* -> screenshot (filename matches /screenshot/i) else image; video/* -> video; audio/* -> audio; application/pdf -> document; text/* -> note; application/* -> file; else other. When MIME absent, derive from the filename extension using the FxFiles isImage/isVideo/isAudio/isDocument lists (dependency-light; no mime crate) so behavior matches the app.
native_category_bucket(cat) -> Option<&'static str> returning the FxFiles native v8 content bucket (images->images-v8, videos->videos-v8, audio->audio-v8, documents->documents-v8; document maps to documents-v8). Categories with no native content bucket (link/note/screenshot/file/other) return None. Document the AI-workspace convention: AI writes to its OWN workspace; the category tells FxFiles where to adopt the file later.
- Wire
pub mod category; into lib.rs.
Source of truth (FxFiles, ported verbatim)
lib/core/services/shelf_classifier.dart
lib/core/models/shelf_item.dart (ShelfCategory enum + order)
lib/core/services/bucket_version_resolver.dart (category->bucket, -v8 routing)
lib/core/models/fula_object.dart (extension lists for the no-MIME fallback)
Tests
Table-driven (mime, filename, text) -> Category covering every ShelfClassifier branch (each case cites the Dart rule it pins) + native_category_bucket per category + extension-fallback (no-MIME) cases. Offline; cargo test -p fula-mcp green.
Out of scope
No MCP protocol, no network, no crypto, no actual bucket writes (that is P5 fula_store_file). This phase is the pure classifier + bucket-name resolver P5 will call.
Phase 4 — categorization + bucket routing (fula-mcp)
Pure-logic phase (no network, no crypto). Port FxFiles file classifier + category->bucket mapping to Rust so AI-stored files get the EXACT same
ShelfCategoryand native v8 bucket as the FxFiles app, enabling later adoption.Scope
categorymodule incrates/fula-mcp/src/:Categoryenum mirroring DartShelfCategory(link, note, screenshot, image, video, audio, document, file, other) with the same order.classify(mime: Option<&str>, filename: &str, text: Option<&str>) -> CategoryportingShelfClassifier.classifyexactly: text/plain (or no-MIME) + non-empty payload short-circuits to link (single URL) or note; image/* -> screenshot (filename matches /screenshot/i) else image; video/* -> video; audio/* -> audio; application/pdf -> document; text/* -> note; application/* -> file; else other. When MIME absent, derive from the filename extension using the FxFilesisImage/isVideo/isAudio/isDocumentlists (dependency-light; no mime crate) so behavior matches the app.native_category_bucket(cat) -> Option<&'static str>returning the FxFiles native v8 content bucket (images->images-v8, videos->videos-v8, audio->audio-v8, documents->documents-v8; document maps to documents-v8). Categories with no native content bucket (link/note/screenshot/file/other) return None. Document the AI-workspace convention: AI writes to its OWN workspace; the category tells FxFiles where to adopt the file later.pub mod category;intolib.rs.Source of truth (FxFiles, ported verbatim)
lib/core/services/shelf_classifier.dartlib/core/models/shelf_item.dart(ShelfCategory enum + order)lib/core/services/bucket_version_resolver.dart(category->bucket,-v8routing)lib/core/models/fula_object.dart(extension lists for the no-MIME fallback)Tests
Table-driven
(mime, filename, text) -> Categorycovering everyShelfClassifierbranch (each case cites the Dart rule it pins) +native_category_bucketper category + extension-fallback (no-MIME) cases. Offline;cargo test -p fula-mcpgreen.Out of scope
No MCP protocol, no network, no crypto, no actual bucket writes (that is P5
fula_store_file). This phase is the pure classifier + bucket-name resolver P5 will call.