-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/mcb loc reduction #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b95111a
315c20e
ead4d7c
1c65796
c298af6
3017ab7
248006a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,13 +26,13 @@ use std::path::{Path, PathBuf}; | |||||||||||||||
| use std::sync::Arc; | ||||||||||||||||
| use std::time::Instant; | ||||||||||||||||
|
|
||||||||||||||||
| use ignore::WalkBuilder; | ||||||||||||||||
| use mcb_domain::constants::{INDEXING_STATUS_COMPLETED, INDEXING_STATUS_STARTED}; | ||||||||||||||||
| use mcb_domain::error::Result; | ||||||||||||||||
| use mcb_domain::events::DomainEvent; | ||||||||||||||||
| use mcb_domain::ports::{ | ||||||||||||||||
| ContextServiceInterface, EventBusProvider, FileHashRepository, IndexingOperationsInterface, | ||||||||||||||||
| IndexingResult, IndexingServiceInterface, IndexingStatus, LanguageChunkingProvider, | ||||||||||||||||
| ContextServiceInterface, EventBusProvider, FileHashRepository, FileSystemProvider, | ||||||||||||||||
| IndexingOperationsInterface, IndexingResult, IndexingServiceInterface, IndexingStatus, | ||||||||||||||||
| LanguageChunkingProvider, TaskRunnerProvider, | ||||||||||||||||
| }; | ||||||||||||||||
| use mcb_domain::value_objects::{CollectionId, OperationId}; | ||||||||||||||||
| use tracing::{error, info, warn}; | ||||||||||||||||
|
|
@@ -102,11 +102,14 @@ pub struct IndexingServiceImpl { | |||||||||||||||
| language_chunker: Arc<dyn LanguageChunkingProvider>, | ||||||||||||||||
| indexing_ops: Arc<dyn IndexingOperationsInterface>, | ||||||||||||||||
| event_bus: Arc<dyn EventBusProvider>, | ||||||||||||||||
| file_system_provider: Arc<dyn FileSystemProvider>, | ||||||||||||||||
| task_runner_provider: Arc<dyn TaskRunnerProvider>, | ||||||||||||||||
| file_hash_repository: Option<Arc<dyn FileHashRepository>>, | ||||||||||||||||
| supported_extensions: Vec<String>, | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /// `IndexingServiceDeps` struct. | ||||||||||||||||
| #[allow(missing_docs)] | ||||||||||||||||
| pub struct IndexingServiceDeps { | ||||||||||||||||
| /// Service for Context operations | ||||||||||||||||
| pub context_service: Arc<dyn ContextServiceInterface>, | ||||||||||||||||
|
|
@@ -116,6 +119,8 @@ pub struct IndexingServiceDeps { | |||||||||||||||
| pub indexing_ops: Arc<dyn IndexingOperationsInterface>, | ||||||||||||||||
| /// Event bus | ||||||||||||||||
| pub event_bus: Arc<dyn EventBusProvider>, | ||||||||||||||||
| pub file_system_provider: Arc<dyn FileSystemProvider>, | ||||||||||||||||
| pub task_runner_provider: Arc<dyn TaskRunnerProvider>, | ||||||||||||||||
| /// Supported file extensions | ||||||||||||||||
| pub supported_extensions: Vec<String>, | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -139,13 +144,17 @@ impl IndexingServiceImpl { | |||||||||||||||
| language_chunker: Arc<dyn LanguageChunkingProvider>, | ||||||||||||||||
| indexing_ops: Arc<dyn IndexingOperationsInterface>, | ||||||||||||||||
| event_bus: Arc<dyn EventBusProvider>, | ||||||||||||||||
| file_system_provider: Arc<dyn FileSystemProvider>, | ||||||||||||||||
| task_runner_provider: Arc<dyn TaskRunnerProvider>, | ||||||||||||||||
| supported_extensions: Vec<String>, | ||||||||||||||||
| ) -> Self { | ||||||||||||||||
| Self { | ||||||||||||||||
| context_service, | ||||||||||||||||
| language_chunker, | ||||||||||||||||
| indexing_ops, | ||||||||||||||||
| event_bus, | ||||||||||||||||
| file_system_provider, | ||||||||||||||||
| task_runner_provider, | ||||||||||||||||
| file_hash_repository: None, | ||||||||||||||||
| supported_extensions: Self::normalize_supported_extensions(supported_extensions), | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -163,6 +172,8 @@ impl IndexingServiceImpl { | |||||||||||||||
| language_chunker: service.language_chunker, | ||||||||||||||||
| indexing_ops: service.indexing_ops, | ||||||||||||||||
| event_bus: service.event_bus, | ||||||||||||||||
| file_system_provider: service.file_system_provider, | ||||||||||||||||
| task_runner_provider: service.task_runner_provider, | ||||||||||||||||
| file_hash_repository: Some(file_hash_repository), | ||||||||||||||||
| supported_extensions: Self::normalize_supported_extensions( | ||||||||||||||||
| service.supported_extensions, | ||||||||||||||||
|
|
@@ -185,31 +196,32 @@ impl IndexingServiceImpl { | |||||||||||||||
| progress: &mut IndexingProgress, | ||||||||||||||||
| ) -> Vec<std::path::PathBuf> { | ||||||||||||||||
| let mut files = Vec::new(); | ||||||||||||||||
| let walker = WalkBuilder::new(path) | ||||||||||||||||
| .hidden(false) | ||||||||||||||||
| .filter_entry(|entry| { | ||||||||||||||||
| if !entry.file_type().is_some_and(|ft| ft.is_dir()) { | ||||||||||||||||
| return true; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| entry | ||||||||||||||||
| .file_name() | ||||||||||||||||
| .to_str() | ||||||||||||||||
| .is_none_or(|name| !SKIP_DIRS.contains(&name)) | ||||||||||||||||
| }) | ||||||||||||||||
| .build(); | ||||||||||||||||
|
|
||||||||||||||||
| for entry_result in walker { | ||||||||||||||||
| match entry_result { | ||||||||||||||||
| Ok(entry) => { | ||||||||||||||||
| if entry.file_type().is_some_and(|ft| ft.is_file()) | ||||||||||||||||
| && self.is_supported_file(entry.path()) | ||||||||||||||||
| { | ||||||||||||||||
| files.push(entry.path().to_path_buf()); | ||||||||||||||||
| let mut dirs = vec![path.to_path_buf()]; | ||||||||||||||||
|
|
||||||||||||||||
| while let Some(dir) = dirs.pop() { | ||||||||||||||||
| match self.file_system_provider.read_dir_entries(&dir).await { | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Files excluded by Prompt for AI agentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A single unreadable or concurrently removed entry now causes every sibling in that directory to be omitted from indexing. Because Prompt for AI agents |
||||||||||||||||
| Ok(entries) => { | ||||||||||||||||
| for entry in entries { | ||||||||||||||||
| if entry.is_dir { | ||||||||||||||||
| let should_skip = entry | ||||||||||||||||
| .path | ||||||||||||||||
| .file_name() | ||||||||||||||||
| .and_then(|n| n.to_str()) | ||||||||||||||||
| .is_some_and(|name| SKIP_DIRS.contains(&name)); | ||||||||||||||||
|
|
||||||||||||||||
| if !should_skip { | ||||||||||||||||
| dirs.push(entry.path); | ||||||||||||||||
| } | ||||||||||||||||
| continue; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if entry.is_file && self.is_supported_file(&entry.path) { | ||||||||||||||||
| files.push(entry.path); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+199
to
+220
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 8. Indexing loses ignore filtering discover_files now performs a manual directory traversal and only skips directories listed in SKIP_DIRS, which removes ignore-aware walking behavior from the previous approach. This can cause indexing to traverse and embed many additional files (generated/vendor/etc.), increasing runtime and index noise. Agent Prompt
|
||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| Err(e) => { | ||||||||||||||||
| progress.record_error("Failed to read directory entry", path, e); | ||||||||||||||||
| progress.record_error("Failed to read directory entries", &dir, e); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -286,10 +298,14 @@ impl IndexingServiceInterface for IndexingServiceImpl { | |||||||||||||||
|
|
||||||||||||||||
| // Fire-and-forget: caller gets operation_id immediately, polling for completion. | ||||||||||||||||
| // Sync execution path available via run_indexing_task() directly in tests. | ||||||||||||||||
| let _handle = tokio::spawn(async move { | ||||||||||||||||
| let task = Box::pin(async move { | ||||||||||||||||
| Self::run_indexing_task(service, files, workspace_root, collection_id, op_id).await; | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| if let Err(e) = self.task_runner_provider.spawn(task) { | ||||||||||||||||
| warn!("Failed to spawn indexing background task: {}", e); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+305
to
+307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When Prompt for AI agents
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| // Return immediately with operation_id | ||||||||||||||||
| Ok(IndexingResult { | ||||||||||||||||
| files_processed: 0, | ||||||||||||||||
|
|
@@ -424,19 +440,16 @@ impl IndexingServiceImpl { | |||||||||||||||
| .update_progress(operation_id, Some(relative_path.clone()), index); | ||||||||||||||||
|
|
||||||||||||||||
| // Read file content | ||||||||||||||||
| let content = std::fs::read_to_string(file_path) | ||||||||||||||||
| .map_err(|e| mcb_domain::error::Error::internal(format!("Failed to read file: {e}")))?; | ||||||||||||||||
| let content = self.file_system_provider.read_to_string(file_path).await?; | ||||||||||||||||
|
|
||||||||||||||||
| // Incremental check using file hashes | ||||||||||||||||
| let current_hash = mcb_domain::utils::compute_content_hash(&content); | ||||||||||||||||
| #[allow(clippy::collapsible_if)] | ||||||||||||||||
| if let Some(repo) = &self.file_hash_repository { | ||||||||||||||||
| if !repo | ||||||||||||||||
| if let Some(repo) = &self.file_hash_repository | ||||||||||||||||
| && !repo | ||||||||||||||||
| .has_changed(&collection.to_string(), &relative_path, ¤t_hash) | ||||||||||||||||
| .await? | ||||||||||||||||
| { | ||||||||||||||||
| return Ok(ProcessResult::Skipped); | ||||||||||||||||
| } | ||||||||||||||||
| { | ||||||||||||||||
| return Ok(ProcessResult::Skipped); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Generate semantic chunks | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,3 +26,27 @@ crate::define_entity! { | |
| pub revoked_at: Option<i64>, | ||
| } | ||
| } | ||
|
|
||
| crate::impl_table_schema!(ApiKey, "api_keys", | ||
| columns: [ | ||
| ("id", Text, pk), | ||
| ("user_id", Text), | ||
| ("org_id", Text), | ||
| ("key_hash", Text), | ||
| ("name", Text), | ||
| ("scopes_json", Text), | ||
| ("expires_at", Integer, nullable), | ||
| ("created_at", Integer), | ||
| ("revoked_at", Integer, nullable), | ||
| ], | ||
| indexes: [ | ||
| "idx_api_keys_user" => ["user_id"], | ||
| "idx_api_keys_org" => ["org_id"], | ||
| "idx_api_keys_key_hash" => ["key_hash"], | ||
| ], | ||
| foreign_keys: [ | ||
| ("user_id", "users", "id"), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: An API key can be persisted under one organization while referencing a user from another, because these independent foreign keys validate existence but not the user/org relationship. Consider enforcing Prompt for AI agents |
||
| ("org_id", "organizations", "id"), | ||
| ], | ||
| unique_constraints: [], | ||
| ); | ||
|
Comment on lines
+30
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Add schema-sync coverage for This adds columns, indexes, and foreign keys without a corresponding 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,3 +58,41 @@ pub enum TeamMemberRole { | |
| } | ||
|
|
||
| crate::impl_as_str_from_as_ref!(TeamMemberRole); | ||
|
|
||
| crate::impl_table_schema!(Team, "teams", | ||
| columns: [ | ||
| ("id", Text, pk), | ||
| ("org_id", Text), | ||
| ("name", Text), | ||
| ("created_at", Integer), | ||
| ], | ||
| indexes: [ | ||
| "idx_teams_org" => ["org_id"], | ||
| ], | ||
| foreign_keys: [ | ||
| ("org_id", "organizations", "id"), | ||
| ], | ||
| unique_constraints: [ | ||
| ["org_id", "name"], | ||
| ], | ||
| ); | ||
|
|
||
| crate::impl_table_schema!(TeamMember, "team_members", | ||
| columns: [ | ||
| ("team_id", Text, pk), | ||
| ("user_id", Text, pk), | ||
| ("role", Text), | ||
| ("joined_at", Integer), | ||
| ], | ||
| indexes: [ | ||
| "idx_team_members_team" => ["team_id"], | ||
| "idx_team_members_user" => ["user_id"], | ||
| ], | ||
| foreign_keys: [ | ||
| ("team_id", "teams", "id"), | ||
| ("user_id", "users", "id"), | ||
| ], | ||
| unique_constraints: [ | ||
| ["team_id", "user_id"], | ||
| ], | ||
| ); | ||
|
Comment on lines
+80
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd schema.rs crates/mcb-domain/src/macros
fd schema_entity_sync_tests.rs crates/mcb-domain
cat -n crates/mcb-domain/src/macros/schema.rs 2>/dev/null | sed -n '1,200p'
cat -n crates/mcb-domain/tests/unit/schema_entity_sync_tests.rs 2>/dev/null | sed -n '1,200p'Repository: marlonsc/mcb Length of output: 16450 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files 'crates/mcb-domain/src/entities/team.rs' 'crates/mcb-domain/src/entities/*.rs' 'crates/mcb-domain/src/schema/**' 'crates/mcb-domain/src/schema.rs' 'crates/mcb-domain/tests/unit/schema_entity_sync_tests.rs' 'crates/mcb-domain/src/macros/schema.rs'
echo "== team.rs outline =="
ast-grep outline crates/mcb-domain/src/entities/team.rs || true
echo "== team.rs relevant =="
cat -n crates/mcb-domain/src/entities/team.rs | sed -n '1,220p'
echo "== schema entity sync tests =="
cat -n crates/mcb-domain/tests/unit/schema_entity_sync_tests.rs | sed -n '1,360p'
echo "== Schema and HasTableSchema usages =="
rg -n "struct Schema|impl Schema|fn definition|HasTableSchema|impl_table_schema|team_members|TeamMember" crates/mcb-domain/src crates/mcb-domain/tests/unit/schema_entity_sync_tests.rsRepository: marlonsc/mcb Length of output: 21238 Add an explicit test allowance for computed schema fields.
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,3 +50,27 @@ pub enum UserRole { | |
| } | ||
|
|
||
| crate::impl_as_str_from_as_ref!(UserRole); | ||
|
|
||
| crate::impl_table_schema!(User, "users", | ||
| columns: [ | ||
| ("id", Text, pk), | ||
| ("org_id", Text), | ||
| ("email", Text), | ||
| ("display_name", Text), | ||
| ("role", Text), | ||
| ("api_key_hash", Text, nullable), | ||
| ("created_at", Integer), | ||
| ("updated_at", Integer), | ||
| ], | ||
| indexes: [ | ||
| "idx_users_org" => ["org_id"], | ||
| "idx_users_email" => ["email"], | ||
| "idx_users_api_key_hash" => ["api_key_hash"], | ||
| ], | ||
| foreign_keys: [ | ||
| ("org_id", "organizations", "id"), | ||
| ], | ||
| unique_constraints: [ | ||
| ["org_id", "email"], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Upgraded databases still permit duplicate Prompt for AI agents |
||
| ], | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /// Generates `Arc`-cloning getter methods for DI container fields. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Getter generation is duplicated across all four parsing arms, so future signature, documentation, or body changes can easily diverge between aliases and list endings. A private Prompt for AI agents |
||
| #[macro_export] | ||
| macro_rules! arc_getters { | ||
| () => {}; | ||
|
|
||
| ($name:ident : $ty:ty => $field:ident $(= $impl:ty)? , $($rest:tt)*) => { | ||
| #[doc = concat!("Get `", stringify!($name), "`.")] | ||
| #[must_use] | ||
| pub fn $name(&self) -> std::sync::Arc<$ty> { | ||
| std::sync::Arc::clone(&self.$field) | ||
| } | ||
|
|
||
| $crate::arc_getters! { $($rest)* } | ||
| }; | ||
|
|
||
| ($name:ident : $ty:ty => $field:ident $(= $impl:ty)? $(,)?) => { | ||
| #[doc = concat!("Get `", stringify!($name), "`.")] | ||
| #[must_use] | ||
| pub fn $name(&self) -> std::sync::Arc<$ty> { | ||
| std::sync::Arc::clone(&self.$field) | ||
| } | ||
| }; | ||
|
|
||
| ($name:ident : $ty:ty $(= $impl:ty)? , $($rest:tt)*) => { | ||
| #[doc = concat!("Get `", stringify!($name), "`.")] | ||
| #[must_use] | ||
| pub fn $name(&self) -> std::sync::Arc<$ty> { | ||
| std::sync::Arc::clone(&self.$name) | ||
| } | ||
|
|
||
| $crate::arc_getters! { $($rest)* } | ||
| }; | ||
|
|
||
| ($name:ident : $ty:ty $(= $impl:ty)? $(,)?) => { | ||
| #[doc = concat!("Get `", stringify!($name), "`.")] | ||
| #[must_use] | ||
| pub fn $name(&self) -> std::sync::Arc<$ty> { | ||
| std::sync::Arc::clone(&self.$name) | ||
| } | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,3 +19,5 @@ mod ports; | |
| mod schema; | ||
| #[macro_use] | ||
| mod registry; | ||
| #[macro_use] | ||
| mod di; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: This SQLite-only integration pulls SeaORM's default datatype features, adding several unused dependency trees and avoidable compile cost. Disabling defaults and explicitly retaining
macroskeeps the derive-based entities working with a smaller dependency surface.Prompt for AI agents