@lamentis/naome 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +117 -47
  3. package/bin/naome.js +65 -12
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/context_commands.rs +47 -0
  6. package/crates/naome-cli/src/dispatcher.rs +12 -2
  7. package/crates/naome-cli/src/main.rs +78 -29
  8. package/crates/naome-cli/src/quality_commands.rs +238 -34
  9. package/crates/naome-cli/src/quality_output.rs +34 -0
  10. package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
  11. package/crates/naome-cli/src/repository_model_commands.rs +84 -0
  12. package/crates/naome-cli/src/task_commands.rs +62 -0
  13. package/crates/naome-cli/src/workflow_commands.rs +120 -3
  14. package/crates/naome-core/Cargo.toml +1 -1
  15. package/crates/naome-core/src/context/helpers.rs +75 -0
  16. package/crates/naome-core/src/context/select.rs +134 -0
  17. package/crates/naome-core/src/context/types.rs +43 -0
  18. package/crates/naome-core/src/context.rs +6 -0
  19. package/crates/naome-core/src/decision/states.rs +1 -1
  20. package/crates/naome-core/src/decision.rs +4 -1
  21. package/crates/naome-core/src/git.rs +4 -2
  22. package/crates/naome-core/src/install_plan.rs +20 -0
  23. package/crates/naome-core/src/journal.rs +2 -7
  24. package/crates/naome-core/src/lib.rs +35 -8
  25. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  26. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  27. package/crates/naome-core/src/quality/adapters.rs +81 -18
  28. package/crates/naome-core/src/quality/baseline.rs +8 -0
  29. package/crates/naome-core/src/quality/cache.rs +151 -0
  30. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +19 -8
  31. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  32. package/crates/naome-core/src/quality/checks.rs +7 -8
  33. package/crates/naome-core/src/quality/cleanup.rs +36 -3
  34. package/crates/naome-core/src/quality/config.rs +21 -3
  35. package/crates/naome-core/src/quality/mod.rs +189 -10
  36. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  37. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  38. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  39. package/crates/naome-core/src/quality/scanner/analysis.rs +175 -0
  40. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  41. package/crates/naome-core/src/quality/scanner.rs +235 -217
  42. package/crates/naome-core/src/quality/semantic/checks.rs +151 -0
  43. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  44. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  45. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  46. package/crates/naome-core/src/quality/semantic.rs +68 -0
  47. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  48. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  49. package/crates/naome-core/src/quality/structure/checks/directory.rs +13 -21
  50. package/crates/naome-core/src/quality/structure/checks.rs +1 -1
  51. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  52. package/crates/naome-core/src/quality/structure/classify.rs +52 -0
  53. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  54. package/crates/naome-core/src/quality/structure/mod.rs +5 -2
  55. package/crates/naome-core/src/quality/structure/model.rs +8 -1
  56. package/crates/naome-core/src/quality/types.rs +59 -2
  57. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  58. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  59. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  60. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  61. package/crates/naome-core/src/repository_model/types.rs +152 -0
  62. package/crates/naome-core/src/repository_model/world.rs +48 -0
  63. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  64. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  65. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  66. package/crates/naome-core/src/repository_model.rs +164 -0
  67. package/crates/naome-core/src/route/builtin_checks.rs +41 -16
  68. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  69. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  70. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  71. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  72. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  73. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  74. package/crates/naome-core/src/task_ledger.rs +48 -0
  75. package/crates/naome-core/src/task_state/api.rs +4 -2
  76. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  77. package/crates/naome-core/src/task_state/diff.rs +2 -2
  78. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  79. package/crates/naome-core/src/task_state/mod.rs +1 -1
  80. package/crates/naome-core/src/task_state/progress.rs +13 -0
  81. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  82. package/crates/naome-core/src/task_state/repair.rs +2 -2
  83. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  84. package/crates/naome-core/src/task_state/types.rs +24 -0
  85. package/crates/naome-core/src/verification.rs +29 -18
  86. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  87. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  88. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  89. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  90. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  91. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  92. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  93. package/crates/naome-core/src/workflow/agent.rs +34 -0
  94. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  95. package/crates/naome-core/src/workflow/doctor.rs +183 -0
  96. package/crates/naome-core/src/workflow/mod.rs +13 -0
  97. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  98. package/crates/naome-core/src/workflow/output.rs +8 -2
  99. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  100. package/crates/naome-core/tests/context.rs +99 -0
  101. package/crates/naome-core/tests/harness_health.rs +4 -0
  102. package/crates/naome-core/tests/install_plan.rs +14 -0
  103. package/crates/naome-core/tests/quality.rs +190 -5
  104. package/crates/naome-core/tests/quality_performance.rs +268 -0
  105. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  106. package/crates/naome-core/tests/quality_structure_policy.rs +19 -0
  107. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  108. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  109. package/crates/naome-core/tests/repository_model.rs +281 -0
  110. package/crates/naome-core/tests/route_user_diff.rs +59 -7
  111. package/crates/naome-core/tests/semantic_legacy.rs +174 -0
  112. package/crates/naome-core/tests/task_ledger.rs +328 -0
  113. package/crates/naome-core/tests/task_state.rs +28 -0
  114. package/crates/naome-core/tests/verification.rs +29 -36
  115. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  116. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  117. package/crates/naome-core/tests/workflow_doctor.rs +45 -0
  118. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  119. package/installer/codex-hooks.js +121 -0
  120. package/installer/context.js +10 -0
  121. package/installer/filesystem.js +4 -0
  122. package/installer/flows.js +8 -4
  123. package/installer/git-boundary.js +1 -0
  124. package/installer/harness-files.js +6 -0
  125. package/installer/install-plan.js +4 -0
  126. package/installer/main.js +1 -1
  127. package/installer/native.js +1 -1
  128. package/native/darwin-arm64/naome +0 -0
  129. package/native/linux-x64/naome +0 -0
  130. package/package.json +1 -1
  131. package/templates/naome-root/.codex/config.toml +2 -0
  132. package/templates/naome-root/.codex/hooks.json +70 -0
  133. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  134. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  135. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  136. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  137. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  138. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  139. package/templates/naome-root/.naome/bin/naome.js +45 -7
  140. package/templates/naome-root/.naome/manifest.json +12 -6
  141. package/templates/naome-root/.naome/repository-model.json +6 -0
  142. package/templates/naome-root/.naome/repository-quality.json +3 -1
  143. package/templates/naome-root/.naome/verification.json +15 -1
  144. package/templates/naome-root/.naomeignore +1 -0
  145. package/templates/naome-root/AGENTS.md +38 -83
  146. package/templates/naome-root/docs/naome/agent-workflow.md +66 -28
  147. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  148. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  149. package/templates/naome-root/docs/naome/first-run.md +25 -14
  150. package/templates/naome-root/docs/naome/index.md +18 -10
  151. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  152. package/templates/naome-root/docs/naome/repository-quality.md +104 -5
  153. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  154. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  155. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -5,17 +5,21 @@ use crate::quality::adapter_support::{
5
5
  find_adapter_by_id, validate_ids, AdapterDescriptor, RepoSignals,
6
6
  };
7
7
 
8
+ use super::adapter_ios;
8
9
  use super::model::RepositoryStructureConfig;
9
10
 
10
11
  const CONFIG_PATH: &str = ".naome/repository-structure.json";
11
12
 
12
- struct StructureAdapter {
13
- id: &'static str,
14
- detect: fn(&RepoSignals<'_>) -> bool,
15
- source_roots: &'static [&'static str],
16
- test_roots: &'static [&'static str],
17
- module_roots: &'static [&'static str],
18
- allowed_root_files: &'static [&'static str],
13
+ #[derive(Clone, Copy)]
14
+ pub(super) struct StructureAdapter {
15
+ pub(super) id: &'static str,
16
+ pub(super) detect: fn(&RepoSignals<'_>) -> bool,
17
+ pub(super) source_roots: &'static [&'static str],
18
+ pub(super) test_roots: &'static [&'static str],
19
+ pub(super) generated_roots: &'static [&'static str],
20
+ pub(super) artifact_roots: &'static [&'static str],
21
+ pub(super) module_roots: &'static [&'static str],
22
+ pub(super) allowed_root_files: &'static [&'static str],
19
23
  }
20
24
 
21
25
  impl AdapterDescriptor for StructureAdapter {
@@ -29,17 +33,21 @@ impl AdapterDescriptor for StructureAdapter {
29
33
  }
30
34
 
31
35
  pub fn detected_structure_adapter_ids(paths: &[String]) -> Vec<String> {
32
- detected_ids(paths, registry())
36
+ let registry = registry();
37
+ detected_ids(paths, &registry)
33
38
  }
34
39
 
35
40
  pub fn apply_structure_adapters(
36
41
  mut config: RepositoryStructureConfig,
37
42
  ) -> Result<RepositoryStructureConfig, NaomeError> {
38
- validate_structure_adapter_ids(&config.enabled_adapters)?;
43
+ let registry = registry();
44
+ validate_ids(&config.enabled_adapters, &registry, CONFIG_PATH)?;
39
45
  for adapter_id in config.enabled_adapters.clone() {
40
- let adapter = find_adapter_by_id(registry(), &adapter_id, CONFIG_PATH)?;
46
+ let adapter = find_adapter_by_id(&registry, &adapter_id, CONFIG_PATH)?;
41
47
  extend_unique(&mut config.source_roots, adapter.source_roots);
42
48
  extend_unique(&mut config.test_roots, adapter.test_roots);
49
+ extend_unique(&mut config.generated_roots, adapter.generated_roots);
50
+ extend_unique(&mut config.artifact_roots, adapter.artifact_roots);
43
51
  extend_unique(&mut config.module_roots, adapter.module_roots);
44
52
  extend_unique(&mut config.allowed_root_files, adapter.allowed_root_files);
45
53
  }
@@ -47,38 +55,48 @@ pub fn apply_structure_adapters(
47
55
  }
48
56
 
49
57
  pub fn validate_structure_adapter_ids(ids: &[String]) -> Result<(), NaomeError> {
50
- validate_ids(ids, registry(), CONFIG_PATH)
58
+ let registry = registry();
59
+ validate_ids(ids, &registry, CONFIG_PATH)
51
60
  }
52
61
 
53
- fn registry() -> &'static [StructureAdapter] {
54
- &[
55
- StructureAdapter {
56
- id: "rust",
57
- detect: detects_rust_project,
58
- source_roots: &["src/**", "crates/*/src/**", "**/crates/*/src/**"],
59
- test_roots: &["tests/**", "crates/*/tests/**", "**/crates/*/tests/**"],
60
- module_roots: &["src/**", "crates/*/src/**", "**/crates/*/src/**"],
61
- allowed_root_files: &["Cargo.toml", "Cargo.lock"],
62
- },
63
- StructureAdapter {
64
- id: "javascript-typescript",
65
- detect: detects_javascript_typescript_project,
66
- source_roots: &[
67
- "src/**",
68
- "app/**",
69
- "pages/**",
70
- "components/**",
71
- "packages/*/src/**",
72
- "**/packages/*/src/**",
73
- ],
74
- test_roots: &["test/**", "tests/**", "__tests__/**", "packages/*/tests/**"],
75
- module_roots: &["src/**", "app/**", "packages/*/src/**"],
76
- allowed_root_files: &[
77
- "package.json",
78
- "tsconfig.json",
79
- "vite.config.ts",
80
- "next.config.js",
81
- ],
82
- },
83
- ]
62
+ const BASE_STRUCTURE_ADAPTERS: &[StructureAdapter] = &[
63
+ StructureAdapter {
64
+ id: "rust",
65
+ detect: detects_rust_project,
66
+ source_roots: &["src/**", "crates/*/src/**", "**/crates/*/src/**"],
67
+ test_roots: &["tests/**", "crates/*/tests/**", "**/crates/*/tests/**"],
68
+ generated_roots: &[],
69
+ artifact_roots: &[],
70
+ module_roots: &["src/**", "crates/*/src/**", "**/crates/*/src/**"],
71
+ allowed_root_files: &["Cargo.toml", "Cargo.lock"],
72
+ },
73
+ StructureAdapter {
74
+ id: "javascript-typescript",
75
+ detect: detects_javascript_typescript_project,
76
+ source_roots: &[
77
+ "src/**",
78
+ "app/**",
79
+ "pages/**",
80
+ "components/**",
81
+ "packages/*/src/**",
82
+ "**/packages/*/src/**",
83
+ ],
84
+ test_roots: &["test/**", "tests/**", "__tests__/**", "packages/*/tests/**"],
85
+ generated_roots: &[],
86
+ artifact_roots: &["coverage/**", "**/coverage/**", ".next/**", "**/.next/**"],
87
+ module_roots: &["src/**", "app/**", "packages/*/src/**"],
88
+ allowed_root_files: &[
89
+ "package.json",
90
+ "tsconfig.json",
91
+ "vite.config.ts",
92
+ "next.config.js",
93
+ ],
94
+ },
95
+ ];
96
+
97
+ fn registry() -> Vec<StructureAdapter> {
98
+ let mut adapters = Vec::new();
99
+ adapters.extend_from_slice(BASE_STRUCTURE_ADAPTERS);
100
+ adapters.extend(adapter_ios::adapters());
101
+ adapters
84
102
  }
@@ -1,5 +1,3 @@
1
- use std::collections::BTreeMap;
2
-
3
1
  use crate::quality::structure::model::{RepositoryStructureModel, StructurePath};
4
2
  use crate::quality::types::{QualityMode, QualityViolation};
5
3
 
@@ -38,7 +36,7 @@ pub(super) fn directory_size(
38
36
  if directory.file_count <= model.config.limits.max_directory_files {
39
37
  continue;
40
38
  }
41
- if mode == QualityMode::Changed && directory.direct_changed_paths.is_empty() {
39
+ if mode.is_changed() && directory.direct_changed_paths.is_empty() {
42
40
  continue;
43
41
  }
44
42
  push_with_limit(
@@ -85,20 +83,13 @@ pub(super) fn case_collisions(
85
83
  mode: QualityMode,
86
84
  violations: &mut Vec<QualityViolation>,
87
85
  ) {
88
- let mut groups: BTreeMap<String, Vec<String>> = BTreeMap::new();
89
- for path in &model.paths {
90
- groups
91
- .entry(path.explanation.path.to_ascii_lowercase())
92
- .or_default()
93
- .push(path.explanation.path.clone());
94
- }
95
- for group in groups.values().filter(|group| group.len() > 1) {
96
- let changed_group = group.iter().any(|path| {
97
- model.paths.iter().any(|candidate| {
98
- candidate.explanation.path == *path && candidate.explanation.changed
99
- })
100
- });
101
- if mode == QualityMode::Changed && !changed_group {
86
+ for group in model
87
+ .lowercase_paths
88
+ .values()
89
+ .filter(|group| group.len() > 1)
90
+ {
91
+ let changed_group = group.iter().any(|path| model.changed_paths.contains(path));
92
+ if mode.is_changed() && !changed_group {
102
93
  continue;
103
94
  }
104
95
  for path in group {
@@ -134,10 +125,11 @@ fn related_module_paths(model: &RepositoryStructureModel, path: &StructurePath)
134
125
  return Vec::new();
135
126
  };
136
127
  model
137
- .paths
138
- .iter()
139
- .filter(|candidate| candidate.explanation.module.as_ref() == Some(module))
140
- .map(|candidate| candidate.explanation.path.clone())
128
+ .module_paths
129
+ .get(module)
130
+ .into_iter()
131
+ .flatten()
132
+ .cloned()
141
133
  .filter(|candidate| candidate != &path.explanation.path)
142
134
  .take(10)
143
135
  .collect()
@@ -53,7 +53,7 @@ fn check_enabled(model: &RepositoryStructureModel, check_id: &str) -> bool {
53
53
  }
54
54
 
55
55
  fn applies(path: &StructurePath, mode: QualityMode) -> bool {
56
- mode == QualityMode::Report || path.explanation.changed
56
+ !mode.is_changed() || path.explanation.changed
57
57
  }
58
58
 
59
59
  fn push(
@@ -2,13 +2,37 @@ use crate::paths;
2
2
 
3
3
  use crate::quality::structure::model::RepositoryStructureConfig;
4
4
 
5
+ const ROLE_SEGMENTS: &[&str] = &[
6
+ "src",
7
+ "source",
8
+ "sources",
9
+ "test",
10
+ "tests",
11
+ "docs",
12
+ "doc",
13
+ "scripts",
14
+ "generated",
15
+ "resources",
16
+ "views",
17
+ ];
18
+
5
19
  pub fn role_for(path: &str, segments: &[String], config: &RepositoryStructureConfig) -> String {
6
20
  let lower = path.to_ascii_lowercase();
7
21
  if has_segment(segments, &["node_modules", "vendor", "third_party"]) {
8
22
  return "dependency/vendor".to_string();
9
23
  }
10
24
  if paths::matches_any(path, &config.generated_roots)
11
- || has_segment(segments, &["generated", "__generated__", "codegen"])
25
+ || has_segment(
26
+ segments,
27
+ &[
28
+ "generated",
29
+ "__generated__",
30
+ "codegen",
31
+ "swiftgen",
32
+ "sourcery",
33
+ ],
34
+ )
35
+ || is_generated_ios_path(&lower)
12
36
  {
13
37
  return "generated".to_string();
14
38
  }
@@ -131,7 +155,13 @@ fn is_test_path(lower: &str, segments: &[String]) -> bool {
131
155
  lower.contains(".test.")
132
156
  || lower.contains(".spec.")
133
157
  || lower.ends_with("_test.rs")
158
+ || lower.ends_with("tests.swift")
159
+ || lower.ends_with("uitests.swift")
134
160
  || has_segment(segments, &["test", "tests", "__tests__"])
161
+ || segments.iter().any(|segment| {
162
+ let lower = segment.to_ascii_lowercase();
163
+ lower.ends_with("tests") || lower.ends_with("uitests")
164
+ })
135
165
  }
136
166
 
137
167
  fn is_docs_path(lower: &str, segments: &[String]) -> bool {
@@ -150,8 +180,16 @@ fn is_config_path(
150
180
  paths::matches_any(path, &config.allowed_root_files)
151
181
  || segments.len() == 1 && (lower.starts_with('.') || lower.ends_with(".toml"))
152
182
  || lower.ends_with(".json")
183
+ || lower.ends_with(".plist")
184
+ || lower.ends_with(".entitlements")
185
+ || lower.ends_with(".xcconfig")
186
+ || lower.ends_with(".strings")
187
+ || lower.ends_with(".stringsdict")
153
188
  || lower.ends_with(".yaml")
154
189
  || lower.ends_with(".yml")
190
+ || lower.ends_with("package.swift")
191
+ || lower.ends_with("podfile")
192
+ || lower.ends_with("cartfile")
155
193
  }
156
194
 
157
195
  fn is_script_path(lower: &str, segments: &[String]) -> bool {
@@ -163,6 +201,16 @@ fn is_artifact_path(lower: &str) -> bool {
163
201
  || lower.ends_with(".log")
164
202
  || lower.ends_with(".map")
165
203
  || lower.ends_with(".min.js")
204
+ || lower.contains("/deriveddata/")
205
+ || lower.ends_with(".xcarchive")
206
+ || lower.ends_with(".dsym")
207
+ }
208
+
209
+ fn is_generated_ios_path(lower: &str) -> bool {
210
+ lower.ends_with(".generated.swift")
211
+ || lower.ends_with(".pb.swift")
212
+ || lower.ends_with(".grpc.swift")
213
+ || lower.ends_with("/r.generated.swift")
166
214
  }
167
215
 
168
216
  fn has_segment(segments: &[String], expected: &[&str]) -> bool {
@@ -173,10 +221,8 @@ fn has_segment(segments: &[String], expected: &[&str]) -> bool {
173
221
  }
174
222
 
175
223
  fn is_role_segment(segment: &str) -> bool {
176
- matches!(
177
- segment,
178
- "src" | "test" | "tests" | "docs" | "doc" | "scripts" | "generated"
179
- )
224
+ let lower = segment.to_ascii_lowercase();
225
+ ROLE_SEGMENTS.contains(&lower.as_str())
180
226
  }
181
227
 
182
228
  fn stem(file_name: &str) -> Option<String> {
@@ -24,10 +24,16 @@ pub fn build_structure_model(
24
24
  .collect::<Vec<_>>();
25
25
  paths.sort_by(|left, right| left.explanation.path.cmp(&right.explanation.path));
26
26
  let directories = directories_for(&paths);
27
+ let indexes = indexes_for(&paths);
27
28
  RepositoryStructureModel {
28
29
  config,
29
30
  paths,
30
31
  directories,
32
+ module_paths: indexes.module_paths,
33
+ directory_paths: indexes.directory_paths,
34
+ role_paths: indexes.role_paths,
35
+ lowercase_paths: indexes.lowercase_paths,
36
+ changed_paths: indexes.changed_paths,
31
37
  }
32
38
  }
33
39
 
@@ -92,3 +98,49 @@ fn directory_of(path: &str) -> String {
92
98
  .map(|(directory, _)| directory.to_string())
93
99
  .unwrap_or_else(|| ".".to_string())
94
100
  }
101
+
102
+ struct StructureIndexes {
103
+ module_paths: BTreeMap<String, Vec<String>>,
104
+ directory_paths: BTreeMap<String, Vec<String>>,
105
+ role_paths: BTreeMap<String, Vec<String>>,
106
+ lowercase_paths: BTreeMap<String, Vec<String>>,
107
+ changed_paths: BTreeSet<String>,
108
+ }
109
+
110
+ fn indexes_for(paths: &[StructurePath]) -> StructureIndexes {
111
+ let mut module_paths = BTreeMap::new();
112
+ let mut directory_paths = BTreeMap::new();
113
+ let mut role_paths = BTreeMap::new();
114
+ let mut lowercase_paths = BTreeMap::new();
115
+ let mut changed_paths = BTreeSet::new();
116
+ for path in paths {
117
+ let value = path.explanation.path.clone();
118
+ if let Some(module) = &path.explanation.module {
119
+ push_index(&mut module_paths, module, &value);
120
+ }
121
+ push_index(&mut directory_paths, &path.explanation.directory, &value);
122
+ push_index(&mut role_paths, &path.explanation.role, &value);
123
+ push_index(
124
+ &mut lowercase_paths,
125
+ &path.explanation.path.to_ascii_lowercase(),
126
+ &value,
127
+ );
128
+ if path.explanation.changed {
129
+ changed_paths.insert(value);
130
+ }
131
+ }
132
+ StructureIndexes {
133
+ module_paths,
134
+ directory_paths,
135
+ role_paths,
136
+ lowercase_paths,
137
+ changed_paths,
138
+ }
139
+ }
140
+
141
+ fn push_index(index: &mut BTreeMap<String, Vec<String>>, key: &str, value: &str) {
142
+ index
143
+ .entry(key.to_string())
144
+ .or_default()
145
+ .push(value.to_string());
146
+ }
@@ -19,6 +19,15 @@ pub fn structure_config_relative_path() -> &'static str {
19
19
  pub fn read_structure_config(
20
20
  root: &Path,
21
21
  paths: &[String],
22
+ ) -> Result<RepositoryStructureConfig, NaomeError> {
23
+ let config = read_policy_structure_config(root, paths)?;
24
+ validate_structure_config(&config)?;
25
+ apply_structure_adapters(config)
26
+ }
27
+
28
+ pub(crate) fn read_policy_structure_config(
29
+ root: &Path,
30
+ paths: &[String],
22
31
  ) -> Result<RepositoryStructureConfig, NaomeError> {
23
32
  let path = root.join(CONFIG_RELATIVE_PATH);
24
33
  let config = if path.is_file() {
@@ -27,7 +36,20 @@ pub fn read_structure_config(
27
36
  generated_structure_config(paths)
28
37
  };
29
38
  validate_structure_config(&config)?;
30
- apply_structure_adapters(config)
39
+ Ok(config)
40
+ }
41
+
42
+ pub(crate) fn write_policy_structure_config(
43
+ root: &Path,
44
+ config: &RepositoryStructureConfig,
45
+ ) -> Result<(), NaomeError> {
46
+ let path = root.join(CONFIG_RELATIVE_PATH);
47
+ if let Some(parent) = path.parent() {
48
+ fs::create_dir_all(parent)?;
49
+ }
50
+ let content = serde_json::to_string_pretty(config)?;
51
+ fs::write(path, format!("{content}\n"))?;
52
+ Ok(())
31
53
  }
32
54
 
33
55
  pub fn write_default_structure_config_if_missing(
@@ -41,8 +63,7 @@ pub fn write_default_structure_config_if_missing(
41
63
  if let Some(parent) = path.parent() {
42
64
  fs::create_dir_all(parent)?;
43
65
  }
44
- let content = serde_json::to_string_pretty(&generated_structure_config(paths))?;
45
- fs::write(path, format!("{content}\n"))?;
66
+ write_policy_structure_config(root, &generated_structure_config(paths))?;
46
67
  Ok(true)
47
68
  }
48
69
 
@@ -1,3 +1,4 @@
1
+ mod adapter_ios;
1
2
  mod adapters;
2
3
  mod checks;
3
4
  mod classify;
@@ -11,10 +12,12 @@ use std::path::Path;
11
12
  use crate::models::NaomeError;
12
13
 
13
14
  use super::scanner::QualityContext;
14
- use super::types::{QualityMode, QualityViolation};
15
+ use super::types::QualityViolation;
16
+ pub(crate) use adapters::detected_structure_adapter_ids;
15
17
  use checks::run_structure_checks;
16
18
  use classify::build_structure_model;
17
19
  use config::read_structure_config;
20
+ pub(crate) use config::{read_policy_structure_config, write_policy_structure_config};
18
21
  pub use config::{structure_config_relative_path, write_default_structure_config_if_missing};
19
22
  pub use model::{RepositoryStructureConfig, StructurePathExplanation};
20
23
 
@@ -33,7 +36,7 @@ pub fn run_repository_structure_checks(
33
36
  .find(|file| file.path == violation.path)
34
37
  .is_some_and(|file| !file.symbols.is_empty())
35
38
  });
36
- if context.mode == QualityMode::Report {
39
+ if !context.mode.is_changed() {
37
40
  for violation in &mut violations {
38
41
  violation.baseline = baseline_fingerprints.contains(&violation.fingerprint);
39
42
  }
@@ -1,4 +1,4 @@
1
- use std::collections::BTreeSet;
1
+ use std::collections::{BTreeMap, BTreeSet};
2
2
 
3
3
  use serde::{Deserialize, Serialize};
4
4
 
@@ -121,4 +121,11 @@ pub struct RepositoryStructureModel {
121
121
  pub config: RepositoryStructureConfig,
122
122
  pub paths: Vec<StructurePath>,
123
123
  pub directories: Vec<StructureDirectory>,
124
+ pub module_paths: BTreeMap<String, Vec<String>>,
125
+ #[allow(dead_code)]
126
+ pub directory_paths: BTreeMap<String, Vec<String>>,
127
+ #[allow(dead_code)]
128
+ pub role_paths: BTreeMap<String, Vec<String>>,
129
+ pub lowercase_paths: BTreeMap<String, Vec<String>>,
130
+ pub changed_paths: BTreeSet<String>,
124
131
  }
@@ -4,15 +4,47 @@ use crate::paths;
4
4
 
5
5
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
6
  pub enum QualityMode {
7
- Changed,
7
+ ChangedFast,
8
+ PathScoped,
8
9
  Report,
10
+ DeepReport,
9
11
  }
10
12
 
11
13
  impl QualityMode {
14
+ #[allow(non_upper_case_globals)]
15
+ pub const Changed: Self = Self::ChangedFast;
16
+
12
17
  pub fn as_str(self) -> &'static str {
13
18
  match self {
14
- Self::Changed => "changed",
19
+ Self::ChangedFast => "changed",
20
+ Self::PathScoped => "path",
15
21
  Self::Report => "report",
22
+ Self::DeepReport => "deep-report",
23
+ }
24
+ }
25
+
26
+ pub fn is_changed(self) -> bool {
27
+ matches!(self, Self::ChangedFast | Self::PathScoped)
28
+ }
29
+
30
+ pub fn is_deep(self) -> bool {
31
+ self == Self::DeepReport
32
+ }
33
+ }
34
+
35
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
36
+ pub enum QualityInitMode {
37
+ SeedOnly,
38
+ Baseline,
39
+ DeepBaseline,
40
+ }
41
+
42
+ impl QualityInitMode {
43
+ pub fn as_str(self) -> &'static str {
44
+ match self {
45
+ Self::SeedOnly => "init",
46
+ Self::Baseline => "baseline",
47
+ Self::DeepBaseline => "deep-baseline",
16
48
  }
17
49
  }
18
50
  }
@@ -187,9 +219,14 @@ pub struct QualityReport {
187
219
  #[serde(rename_all = "camelCase")]
188
220
  pub struct QualitySummary {
189
221
  pub scanned_files: usize,
222
+ pub scanned_path_count: usize,
190
223
  pub violation_count: usize,
191
224
  pub baseline_violation_count: usize,
192
225
  pub blocking_violation_count: usize,
226
+ pub truncated: bool,
227
+ pub reason_codes: Vec<String>,
228
+ pub cache_hits: usize,
229
+ pub cache_misses: usize,
193
230
  }
194
231
 
195
232
  #[derive(Debug, Clone, Serialize)]
@@ -211,15 +248,34 @@ pub struct QualityViolation {
211
248
  #[serde(rename_all = "camelCase")]
212
249
  pub struct QualityInitResult {
213
250
  pub schema: String,
251
+ pub mode: String,
214
252
  pub config_written: bool,
215
253
  pub structure_config_written: bool,
216
254
  pub baseline_written: bool,
255
+ pub baseline_pending: bool,
217
256
  pub baseline_violations: usize,
218
257
  pub config_path: String,
219
258
  pub structure_config_path: String,
220
259
  pub baseline_path: String,
221
260
  }
222
261
 
262
+ #[derive(Debug, Clone, Serialize)]
263
+ #[serde(rename_all = "camelCase")]
264
+ pub struct QualityReconcileReport {
265
+ pub schema: String,
266
+ pub ok: bool,
267
+ pub stale: bool,
268
+ pub write: bool,
269
+ pub detected_quality_adapters: Vec<String>,
270
+ pub enabled_quality_adapters: Vec<String>,
271
+ pub missing_quality_adapters: Vec<String>,
272
+ pub detected_structure_adapters: Vec<String>,
273
+ pub enabled_structure_adapters: Vec<String>,
274
+ pub missing_structure_adapters: Vec<String>,
275
+ pub updated_paths: Vec<String>,
276
+ pub reason_codes: Vec<String>,
277
+ }
278
+
223
279
  #[derive(Debug, Clone, Serialize)]
224
280
  #[serde(rename_all = "camelCase")]
225
281
  pub struct QualityCleanupPlan {
@@ -258,6 +314,7 @@ pub fn default_generated_paths() -> Vec<String> {
258
314
  const DEFAULT_IGNORED_PATHS: &str = r#"
259
315
  .git/**
260
316
  .naome/archive/**
317
+ .naome/cache/**
261
318
  .naome/task-state.json
262
319
  .naome/task-journal.jsonl
263
320
  .naome/repository-quality.json