@lamentis/naome 1.1.2 → 1.2.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 (204) hide show
  1. package/Cargo.lock +2 -2
  2. package/Cargo.toml +1 -1
  3. package/LICENSE +180 -21
  4. package/README.md +49 -6
  5. package/bin/naome-node.js +2 -1579
  6. package/bin/naome.js +68 -16
  7. package/crates/naome-cli/Cargo.toml +1 -1
  8. package/crates/naome-cli/src/check_commands.rs +135 -0
  9. package/crates/naome-cli/src/cli_args.rs +5 -0
  10. package/crates/naome-cli/src/dispatcher.rs +37 -0
  11. package/crates/naome-cli/src/install_bridge.rs +83 -0
  12. package/crates/naome-cli/src/main.rs +60 -341
  13. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  14. package/crates/naome-cli/src/quality_commands.rs +229 -0
  15. package/crates/naome-cli/src/simple_commands.rs +53 -0
  16. package/crates/naome-cli/src/workflow_commands.rs +153 -0
  17. package/crates/naome-core/Cargo.toml +1 -1
  18. package/crates/naome-core/src/decision/checks.rs +64 -0
  19. package/crates/naome-core/src/decision/idle.rs +67 -0
  20. package/crates/naome-core/src/decision/json.rs +36 -0
  21. package/crates/naome-core/src/decision/states.rs +165 -0
  22. package/crates/naome-core/src/decision.rs +131 -353
  23. package/crates/naome-core/src/harness_health/integrity.rs +96 -0
  24. package/crates/naome-core/src/harness_health.rs +14 -126
  25. package/crates/naome-core/src/install_plan.rs +5 -0
  26. package/crates/naome-core/src/intent/classifier.rs +171 -0
  27. package/crates/naome-core/src/intent/envelope.rs +108 -0
  28. package/crates/naome-core/src/intent/legacy.rs +138 -0
  29. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  30. package/crates/naome-core/src/intent/model.rs +71 -0
  31. package/crates/naome-core/src/intent/patterns.rs +170 -0
  32. package/crates/naome-core/src/intent/resolver.rs +162 -0
  33. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  34. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  35. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  36. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  37. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  38. package/crates/naome-core/src/intent/risk.rs +40 -0
  39. package/crates/naome-core/src/intent/segment.rs +170 -0
  40. package/crates/naome-core/src/intent.rs +64 -879
  41. package/crates/naome-core/src/journal.rs +9 -20
  42. package/crates/naome-core/src/lib.rs +15 -0
  43. package/crates/naome-core/src/paths.rs +3 -1
  44. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  45. package/crates/naome-core/src/quality/adapters.rs +131 -0
  46. package/crates/naome-core/src/quality/baseline.rs +75 -0
  47. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  48. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  49. package/crates/naome-core/src/quality/checks.rs +228 -0
  50. package/crates/naome-core/src/quality/cleanup.rs +84 -0
  51. package/crates/naome-core/src/quality/config.rs +102 -0
  52. package/crates/naome-core/src/quality/config_support.rs +24 -0
  53. package/crates/naome-core/src/quality/mod.rs +108 -0
  54. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  55. package/crates/naome-core/src/quality/scanner.rs +379 -0
  56. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  57. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  58. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  59. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  60. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  61. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  62. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  63. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  64. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  65. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  66. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  67. package/crates/naome-core/src/quality/types.rs +292 -0
  68. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  69. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  70. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  71. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  72. package/crates/naome-core/src/route/context.rs +180 -0
  73. package/crates/naome-core/src/route/execution.rs +96 -0
  74. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  75. package/crates/naome-core/src/route/execution_support.rs +57 -0
  76. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  77. package/crates/naome-core/src/route/git_ops.rs +72 -0
  78. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  79. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  80. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  81. package/crates/naome-core/src/route/worktree.rs +75 -0
  82. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  83. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  84. package/crates/naome-core/src/route.rs +44 -1155
  85. package/crates/naome-core/src/task_state/admission.rs +63 -0
  86. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  87. package/crates/naome-core/src/task_state/api.rs +130 -0
  88. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  89. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  90. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  91. package/crates/naome-core/src/task_state/completion.rs +72 -0
  92. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  93. package/crates/naome-core/src/task_state/diff.rs +95 -0
  94. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  95. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  96. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  97. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  98. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  99. package/crates/naome-core/src/task_state/mod.rs +38 -0
  100. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  101. package/crates/naome-core/src/task_state/progress.rs +123 -0
  102. package/crates/naome-core/src/task_state/proof.rs +139 -0
  103. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  104. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  105. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  106. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  107. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  108. package/crates/naome-core/src/task_state/repair.rs +168 -0
  109. package/crates/naome-core/src/task_state/shape.rs +117 -0
  110. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  111. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  112. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  113. package/crates/naome-core/src/task_state/types.rs +87 -0
  114. package/crates/naome-core/src/task_state/util.rs +137 -0
  115. package/crates/naome-core/src/verification/render.rs +122 -0
  116. package/crates/naome-core/src/verification.rs +177 -58
  117. package/crates/naome-core/src/verification_contract.rs +49 -21
  118. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  119. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  120. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  121. package/crates/naome-core/src/workflow/mod.rs +18 -0
  122. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  123. package/crates/naome-core/src/workflow/output.rs +111 -0
  124. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  125. package/crates/naome-core/src/workflow/phases.rs +169 -0
  126. package/crates/naome-core/src/workflow/policy.rs +156 -0
  127. package/crates/naome-core/src/workflow/processes.rs +91 -0
  128. package/crates/naome-core/src/workflow/types.rs +42 -0
  129. package/crates/naome-core/tests/decision.rs +24 -118
  130. package/crates/naome-core/tests/harness_health.rs +5 -0
  131. package/crates/naome-core/tests/intent.rs +97 -792
  132. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  133. package/crates/naome-core/tests/intent_v2.rs +90 -0
  134. package/crates/naome-core/tests/quality.rs +319 -0
  135. package/crates/naome-core/tests/quality_structure.rs +116 -0
  136. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  137. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  138. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  139. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  140. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  141. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  142. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  143. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  144. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  145. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  146. package/crates/naome-core/tests/route.rs +1 -1476
  147. package/crates/naome-core/tests/route_baseline.rs +86 -0
  148. package/crates/naome-core/tests/route_completion.rs +141 -0
  149. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  150. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  151. package/crates/naome-core/tests/route_worktree.rs +54 -0
  152. package/crates/naome-core/tests/task_state.rs +60 -429
  153. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  154. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  155. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  156. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  157. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  158. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  159. package/crates/naome-core/tests/verification.rs +4 -45
  160. package/crates/naome-core/tests/verification_contract.rs +22 -78
  161. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  162. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  163. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  164. package/installer/agents.js +90 -0
  165. package/installer/context.js +67 -0
  166. package/installer/filesystem.js +166 -0
  167. package/installer/flows.js +84 -0
  168. package/installer/git-boundary.js +170 -0
  169. package/installer/git-hook-content.js +36 -0
  170. package/installer/git-hooks.js +134 -0
  171. package/installer/git-local.js +2 -0
  172. package/installer/git-shared.js +35 -0
  173. package/installer/harness-file-ops.js +140 -0
  174. package/installer/harness-files.js +56 -0
  175. package/installer/harness-verification.js +123 -0
  176. package/installer/install-plan.js +66 -0
  177. package/installer/main.js +25 -0
  178. package/installer/manifest-state.js +167 -0
  179. package/installer/native-build.js +24 -0
  180. package/installer/native-format.js +6 -0
  181. package/installer/native.js +162 -0
  182. package/installer/output.js +131 -0
  183. package/installer/version.js +32 -0
  184. package/native/darwin-arm64/naome +0 -0
  185. package/native/linux-x64/naome +0 -0
  186. package/package.json +3 -2
  187. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  188. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  189. package/templates/naome-root/.naome/bin/naome.js +51 -76
  190. package/templates/naome-root/.naome/manifest.json +22 -18
  191. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  192. package/templates/naome-root/.naome/repository-quality.json +24 -0
  193. package/templates/naome-root/.naome/repository-structure.json +90 -0
  194. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  195. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  196. package/templates/naome-root/.naome/verification.json +38 -0
  197. package/templates/naome-root/AGENTS.md +3 -0
  198. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  199. package/templates/naome-root/docs/naome/execution.md +25 -21
  200. package/templates/naome-root/docs/naome/index.md +5 -3
  201. package/templates/naome-root/docs/naome/repository-quality.md +46 -0
  202. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  203. package/templates/naome-root/docs/naome/testing.md +13 -0
  204. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -0,0 +1,228 @@
1
+ mod duplicate_blocks;
2
+ mod near_duplicates;
3
+
4
+ use std::collections::HashSet;
5
+
6
+ use super::scanner::{stable_fingerprint, QualityContext};
7
+ use super::types::{QualityMode, QualityViolation};
8
+ use duplicate_blocks::DuplicateBlockCheck;
9
+ use near_duplicates::NearDuplicateFunctionCheck;
10
+
11
+ pub fn run_quality_checks(context: &QualityContext) -> Vec<QualityViolation> {
12
+ let checks = registry();
13
+ let disabled = context
14
+ .config
15
+ .disabled_checks
16
+ .iter()
17
+ .map(String::as_str)
18
+ .collect::<HashSet<_>>();
19
+ let mut violations = Vec::new();
20
+
21
+ for check in checks {
22
+ if !disabled.contains(check.id()) {
23
+ check.evaluate(context, &mut violations);
24
+ }
25
+ }
26
+
27
+ violations.sort_by(|left, right| {
28
+ left.path
29
+ .cmp(&right.path)
30
+ .then(left.check_id.cmp(&right.check_id))
31
+ .then(left.line.cmp(&right.line))
32
+ .then(left.fingerprint.cmp(&right.fingerprint))
33
+ });
34
+ violations
35
+ }
36
+
37
+ pub(super) trait QualityCheck {
38
+ fn id(&self) -> &'static str;
39
+ fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>);
40
+ }
41
+
42
+ fn registry() -> Vec<Box<dyn QualityCheck>> {
43
+ vec![
44
+ Box::new(FileLengthCheck),
45
+ Box::new(DiffGrowthCheck),
46
+ Box::new(FunctionLengthCheck),
47
+ Box::new(TopLevelSymbolCountCheck),
48
+ Box::new(DuplicateBlockCheck),
49
+ Box::new(NearDuplicateFunctionCheck),
50
+ ]
51
+ }
52
+
53
+ struct FileLengthCheck;
54
+
55
+ impl QualityCheck for FileLengthCheck {
56
+ fn id(&self) -> &'static str {
57
+ "file-length"
58
+ }
59
+
60
+ fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
61
+ for file in context
62
+ .files
63
+ .iter()
64
+ .filter(|file| context.check_applies_to(self.id(), &file.path))
65
+ {
66
+ let limits = context.limits_for(&file.path);
67
+ let limit =
68
+ if context.mode == QualityMode::Changed && file.added_lines == file.line_count {
69
+ limits.max_new_file_lines
70
+ } else {
71
+ limits.max_file_lines
72
+ };
73
+ if file.line_count > limit {
74
+ violations.push(violation(
75
+ self.id(),
76
+ &file.path,
77
+ None,
78
+ format!(
79
+ "{} has {} lines, exceeding the configured limit of {}.",
80
+ file.path, file.line_count, limit
81
+ ),
82
+ Some(file.line_count as f64),
83
+ Some(limit as f64),
84
+ Vec::new(),
85
+ ));
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ struct DiffGrowthCheck;
92
+
93
+ impl QualityCheck for DiffGrowthCheck {
94
+ fn id(&self) -> &'static str {
95
+ "diff-growth"
96
+ }
97
+
98
+ fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
99
+ if context.mode != QualityMode::Changed {
100
+ return;
101
+ }
102
+ for file in context
103
+ .files
104
+ .iter()
105
+ .filter(|file| context.check_applies_to(self.id(), &file.path))
106
+ {
107
+ let limit = context.limits_for(&file.path).max_diff_added_lines;
108
+ if file.added_lines > limit {
109
+ violations.push(violation(
110
+ self.id(),
111
+ &file.path,
112
+ None,
113
+ format!(
114
+ "{} adds {} lines in this task, exceeding the configured limit of {}.",
115
+ file.path, file.added_lines, limit
116
+ ),
117
+ Some(file.added_lines as f64),
118
+ Some(limit as f64),
119
+ Vec::new(),
120
+ ));
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ struct FunctionLengthCheck;
127
+
128
+ impl QualityCheck for FunctionLengthCheck {
129
+ fn id(&self) -> &'static str {
130
+ "function-length"
131
+ }
132
+
133
+ fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
134
+ for file in context.files.iter().filter(|file| {
135
+ context.check_applies_to(self.id(), &file.path) && is_code_like_path(&file.path)
136
+ }) {
137
+ let limit = context.limits_for(&file.path).max_function_lines;
138
+ for symbol in &file.symbols {
139
+ if symbol.line_count() > limit {
140
+ violations.push(violation(
141
+ self.id(),
142
+ &file.path,
143
+ Some(symbol.start_line),
144
+ format!(
145
+ "{} {} has {} lines, exceeding the configured limit of {}.",
146
+ symbol.kind,
147
+ symbol.name,
148
+ symbol.line_count(),
149
+ limit
150
+ ),
151
+ Some(symbol.line_count() as f64),
152
+ Some(limit as f64),
153
+ Vec::new(),
154
+ ));
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ struct TopLevelSymbolCountCheck;
162
+
163
+ impl QualityCheck for TopLevelSymbolCountCheck {
164
+ fn id(&self) -> &'static str {
165
+ "top-level-symbol-count"
166
+ }
167
+
168
+ fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
169
+ for file in context.files.iter().filter(|file| {
170
+ context.check_applies_to(self.id(), &file.path) && is_code_like_path(&file.path)
171
+ }) {
172
+ let limit = context.limits_for(&file.path).max_top_level_symbols;
173
+ let count = file
174
+ .symbols
175
+ .iter()
176
+ .filter(|symbol| symbol.indent <= 2)
177
+ .count();
178
+ if count > limit {
179
+ violations.push(violation(
180
+ self.id(),
181
+ &file.path,
182
+ None,
183
+ format!(
184
+ "{} defines {} top-level symbols, exceeding the configured limit of {}.",
185
+ file.path, count, limit
186
+ ),
187
+ Some(count as f64),
188
+ Some(limit as f64),
189
+ Vec::new(),
190
+ ));
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ pub(super) fn is_code_like_path(path: &str) -> bool {
197
+ let lower = path.to_ascii_lowercase();
198
+ [
199
+ ".rs", ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".py", ".go", ".swift", ".kt",
200
+ ".java", ".c", ".cc", ".cpp", ".h", ".hpp", ".rb", ".php", ".cs", ".sh", ".bash", ".zsh",
201
+ ]
202
+ .iter()
203
+ .any(|extension| lower.ends_with(extension))
204
+ }
205
+
206
+ pub(super) fn violation(
207
+ check_id: &str,
208
+ path: &str,
209
+ line: Option<usize>,
210
+ message: String,
211
+ value: Option<f64>,
212
+ limit: Option<f64>,
213
+ related_paths: Vec<String>,
214
+ ) -> QualityViolation {
215
+ let line_part = line.map(|value| value.to_string()).unwrap_or_default();
216
+ QualityViolation {
217
+ check_id: check_id.to_string(),
218
+ severity: "blocking".to_string(),
219
+ path: path.to_string(),
220
+ line,
221
+ message: message.clone(),
222
+ value,
223
+ limit,
224
+ fingerprint: stable_fingerprint(&[check_id, path, &line_part, &message]),
225
+ related_paths,
226
+ baseline: false,
227
+ }
228
+ }
@@ -0,0 +1,84 @@
1
+ use std::collections::{BTreeMap, BTreeSet};
2
+
3
+ use super::types::{QualityCleanupPlan, QualityCleanupRoute, QualityCleanupTask, QualityViolation};
4
+
5
+ pub fn cleanup_plan_from_violations(violations: &[QualityViolation]) -> QualityCleanupPlan {
6
+ let mut grouped: BTreeMap<String, Vec<&QualityViolation>> = BTreeMap::new();
7
+ for violation in violations {
8
+ grouped
9
+ .entry(violation.path.clone())
10
+ .or_default()
11
+ .push(violation);
12
+ }
13
+
14
+ let mut tasks = grouped
15
+ .into_iter()
16
+ .map(|(path, violations)| {
17
+ let check_ids = violations
18
+ .iter()
19
+ .map(|violation| violation.check_id.clone())
20
+ .collect::<BTreeSet<_>>()
21
+ .into_iter()
22
+ .collect::<Vec<_>>();
23
+ QualityCleanupTask {
24
+ path,
25
+ violation_count: violations.len(),
26
+ summary: format!(
27
+ "Resolve {} repository-quality violation(s): {}.",
28
+ violations.len(),
29
+ check_ids.join(", ")
30
+ ),
31
+ check_ids,
32
+ }
33
+ })
34
+ .collect::<Vec<_>>();
35
+ tasks.sort_by(|left, right| {
36
+ right
37
+ .violation_count
38
+ .cmp(&left.violation_count)
39
+ .then(left.path.cmp(&right.path))
40
+ });
41
+
42
+ QualityCleanupPlan {
43
+ schema: "naome.quality-cleanup-plan.v1".to_string(),
44
+ tasks,
45
+ }
46
+ }
47
+
48
+ pub fn cleanup_route_for_path(
49
+ path: &str,
50
+ violations: Vec<QualityViolation>,
51
+ ) -> QualityCleanupRoute {
52
+ let mut related_paths = violations
53
+ .iter()
54
+ .flat_map(|violation| violation.related_paths.clone())
55
+ .collect::<BTreeSet<_>>()
56
+ .into_iter()
57
+ .collect::<Vec<_>>();
58
+ related_paths.retain(|related| related != path);
59
+ let topic = if violations.iter().any(|violation| {
60
+ violation.check_id.starts_with("directory-")
61
+ || violation.check_id == "root-file-sprawl"
62
+ || violation.check_id == "dumping-ground-directory"
63
+ || violation.check_id == "path-depth"
64
+ || violation.check_id == "case-collision"
65
+ || violation.check_id == "test-source-pairing"
66
+ }) {
67
+ "repository structure"
68
+ } else {
69
+ "repository quality"
70
+ };
71
+ QualityCleanupRoute {
72
+ schema: "naome.quality-cleanup-route.v1".to_string(),
73
+ path: path.to_string(),
74
+ violations,
75
+ related_paths,
76
+ agent_instructions: format!(
77
+ "Reduce or split {path} until every {topic} violation is gone. Prefer named modules and reusable helpers/components over dumping logic into generic directories. Keep behavior unchanged, add or preserve focused tests, then run naome quality check --changed before task completion."
78
+ ),
79
+ required_checks: vec![
80
+ "naome quality check --changed".to_string(),
81
+ "git diff --check".to_string(),
82
+ ],
83
+ }
84
+ }
@@ -0,0 +1,102 @@
1
+ use std::fs;
2
+ use std::path::Path;
3
+
4
+ use crate::models::NaomeError;
5
+
6
+ use super::adapters::{apply_enabled_adapters, detected_adapter_ids, validate_adapter_ids};
7
+ use super::config_support::validate_ready_schema;
8
+ use super::scanner::collect_repo_paths;
9
+ use super::types::RepositoryQualityConfig;
10
+
11
+ const CONFIG_RELATIVE_PATH: &str = ".naome/repository-quality.json";
12
+
13
+ pub fn config_relative_path() -> &'static str {
14
+ CONFIG_RELATIVE_PATH
15
+ }
16
+
17
+ pub fn read_config(root: &Path) -> Result<RepositoryQualityConfig, NaomeError> {
18
+ let path = root.join(CONFIG_RELATIVE_PATH);
19
+ let config = if path.is_file() {
20
+ serde_json::from_str(&fs::read_to_string(path)?)?
21
+ } else {
22
+ generated_config(root)?
23
+ };
24
+ validate_config(&config)?;
25
+ apply_enabled_adapters(config)
26
+ }
27
+
28
+ pub fn write_default_config_if_missing(root: &Path) -> Result<bool, NaomeError> {
29
+ let path = root.join(CONFIG_RELATIVE_PATH);
30
+ if path.exists() {
31
+ return Ok(false);
32
+ }
33
+
34
+ if let Some(parent) = path.parent() {
35
+ fs::create_dir_all(parent)?;
36
+ }
37
+ let content = serde_json::to_string_pretty(&generated_config(root)?)?;
38
+ fs::write(path, format!("{content}\n"))?;
39
+ Ok(true)
40
+ }
41
+
42
+ fn generated_config(root: &Path) -> Result<RepositoryQualityConfig, NaomeError> {
43
+ let paths = collect_repo_paths(root)?;
44
+ let mut config = RepositoryQualityConfig::default();
45
+ config.enabled_adapters = detected_adapter_ids(&paths);
46
+ Ok(config)
47
+ }
48
+
49
+ fn validate_config(config: &RepositoryQualityConfig) -> Result<(), NaomeError> {
50
+ validate_ready_schema(
51
+ CONFIG_RELATIVE_PATH,
52
+ &config.schema,
53
+ "naome.repository-quality.v1",
54
+ config.version,
55
+ &config.status,
56
+ )?;
57
+ if config.limits.duplicate_block_lines < 3 {
58
+ return Err(NaomeError::new(
59
+ ".naome/repository-quality.json duplicateBlockLines must be at least 3.",
60
+ ));
61
+ }
62
+ if !(0.5..=1.0).contains(&config.limits.near_duplicate_similarity) {
63
+ return Err(NaomeError::new(
64
+ ".naome/repository-quality.json nearDuplicateSimilarity must be between 0.5 and 1.0.",
65
+ ));
66
+ }
67
+ validate_adapter_ids(&config.enabled_adapters)?;
68
+ for rule in &config.path_rules {
69
+ if rule.id.trim().is_empty() {
70
+ return Err(NaomeError::new(
71
+ ".naome/repository-quality.json pathRules entries must have a non-empty id.",
72
+ ));
73
+ }
74
+ if rule.paths.is_empty() {
75
+ return Err(NaomeError::new(format!(
76
+ ".naome/repository-quality.json pathRules.{} must list at least one path.",
77
+ rule.id
78
+ )));
79
+ }
80
+ if rule
81
+ .limits
82
+ .duplicate_block_lines
83
+ .is_some_and(|value| value < 3)
84
+ {
85
+ return Err(NaomeError::new(format!(
86
+ ".naome/repository-quality.json pathRules.{} duplicateBlockLines must be at least 3.",
87
+ rule.id
88
+ )));
89
+ }
90
+ if rule
91
+ .limits
92
+ .near_duplicate_similarity
93
+ .is_some_and(|value| !(0.5..=1.0).contains(&value))
94
+ {
95
+ return Err(NaomeError::new(format!(
96
+ ".naome/repository-quality.json pathRules.{} nearDuplicateSimilarity must be between 0.5 and 1.0.",
97
+ rule.id
98
+ )));
99
+ }
100
+ }
101
+ Ok(())
102
+ }
@@ -0,0 +1,24 @@
1
+ use crate::models::NaomeError;
2
+
3
+ pub(crate) fn validate_ready_schema(
4
+ config_path: &str,
5
+ actual_schema: &str,
6
+ expected_schema: &str,
7
+ version: u32,
8
+ status: &str,
9
+ ) -> Result<(), NaomeError> {
10
+ if actual_schema != expected_schema {
11
+ return Err(NaomeError::new(format!(
12
+ "{config_path} schema must be {expected_schema}."
13
+ )));
14
+ }
15
+ if version != 1 {
16
+ return Err(NaomeError::new(format!("{config_path} version must be 1.")));
17
+ }
18
+ if status != "ready" {
19
+ return Err(NaomeError::new(format!(
20
+ "{config_path} status must be ready."
21
+ )));
22
+ }
23
+ Ok(())
24
+ }
@@ -0,0 +1,108 @@
1
+ mod adapter_support;
2
+ mod adapters;
3
+ mod baseline;
4
+ mod checks;
5
+ mod cleanup;
6
+ mod config;
7
+ mod config_support;
8
+ mod scanner;
9
+ mod structure;
10
+ mod types;
11
+
12
+ use std::path::Path;
13
+
14
+ use crate::models::NaomeError;
15
+
16
+ pub use cleanup::{cleanup_plan_from_violations, cleanup_route_for_path};
17
+ pub use structure::{
18
+ explain_repository_structure, RepositoryStructureConfig, StructurePathExplanation,
19
+ };
20
+ pub use types::{
21
+ QualityCleanupPlan, QualityCleanupRoute, QualityCleanupTask, QualityInitResult, QualityMode,
22
+ QualityReport, QualitySummary, QualityViolation, RepositoryQualityConfig,
23
+ };
24
+
25
+ use self::baseline::{baseline_relative_path, read_baseline_fingerprints, write_baseline};
26
+ use self::checks::run_quality_checks;
27
+ use self::config::{config_relative_path, read_config, write_default_config_if_missing};
28
+ use self::scanner::scan_repository;
29
+ use self::structure::{
30
+ run_repository_structure_checks, structure_config_relative_path,
31
+ write_default_structure_config_if_missing,
32
+ };
33
+
34
+ pub fn check_repository_quality(
35
+ root: &Path,
36
+ mode: QualityMode,
37
+ ) -> Result<QualityReport, NaomeError> {
38
+ let config = read_config(root)?;
39
+ let context = scan_repository(root, mode, config)?;
40
+ let baseline = read_baseline_fingerprints(root)?;
41
+ let mut violations = run_quality_checks(&context);
42
+ violations.extend(run_repository_structure_checks(root, &context, &baseline)?);
43
+ for violation in &mut violations {
44
+ violation.baseline = baseline.contains(&violation.fingerprint);
45
+ }
46
+ let blocking_violation_count = violations.len();
47
+ let baseline_violation_count = violations
48
+ .iter()
49
+ .filter(|violation| violation.baseline)
50
+ .count();
51
+ let ok = blocking_violation_count == 0;
52
+
53
+ Ok(QualityReport {
54
+ schema: "naome.repository-quality-report.v1".to_string(),
55
+ mode: mode.as_str().to_string(),
56
+ ok,
57
+ changed_paths: context.changed_paths.clone(),
58
+ scanned_paths: context.scanned_paths(),
59
+ summary: QualitySummary {
60
+ scanned_files: context.files.len(),
61
+ violation_count: violations.len(),
62
+ baseline_violation_count,
63
+ blocking_violation_count,
64
+ },
65
+ violations,
66
+ })
67
+ }
68
+
69
+ pub fn init_repository_quality(root: &Path) -> Result<QualityInitResult, NaomeError> {
70
+ let config_written = write_default_config_if_missing(root)?;
71
+ let structure_config_written = {
72
+ let config = read_config(root)?;
73
+ let context = scan_repository(root, QualityMode::Report, config)?;
74
+ write_default_structure_config_if_missing(root, &context.repo_paths)?
75
+ };
76
+ let report = check_repository_quality(root, QualityMode::Report)?;
77
+ let baseline_written = write_baseline(root, &report.violations)?;
78
+
79
+ Ok(QualityInitResult {
80
+ schema: "naome.repository-quality-init.v1".to_string(),
81
+ config_written,
82
+ structure_config_written,
83
+ baseline_written,
84
+ baseline_violations: report.violations.len(),
85
+ config_path: config_relative_path().to_string(),
86
+ structure_config_path: structure_config_relative_path().to_string(),
87
+ baseline_path: baseline_relative_path().to_string(),
88
+ })
89
+ }
90
+
91
+ pub fn plan_quality_cleanup(root: &Path) -> Result<QualityCleanupPlan, NaomeError> {
92
+ let report = check_repository_quality(root, QualityMode::Report)?;
93
+ Ok(cleanup_plan_from_violations(&report.violations))
94
+ }
95
+
96
+ pub fn route_quality_cleanup(
97
+ root: &Path,
98
+ path: impl AsRef<str>,
99
+ ) -> Result<QualityCleanupRoute, NaomeError> {
100
+ let path = path.as_ref().replace('\\', "/");
101
+ let report = check_repository_quality(root, QualityMode::Report)?;
102
+ let violations = report
103
+ .violations
104
+ .into_iter()
105
+ .filter(|violation| violation.path == path)
106
+ .collect::<Vec<_>>();
107
+ Ok(cleanup_route_for_path(&path, violations))
108
+ }
@@ -0,0 +1,103 @@
1
+ use std::collections::HashMap;
2
+ use std::fs;
3
+ use std::path::Path;
4
+ use std::process::Command;
5
+
6
+ use crate::models::NaomeError;
7
+
8
+ pub(crate) fn collect_repo_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
9
+ let output = Command::new("git")
10
+ .args([
11
+ "ls-files",
12
+ "-z",
13
+ "--cached",
14
+ "--others",
15
+ "--exclude-standard",
16
+ ])
17
+ .current_dir(root)
18
+ .output();
19
+ if let Ok(output) = output {
20
+ if output.status.success() {
21
+ let mut paths = output
22
+ .stdout
23
+ .split(|byte| *byte == 0)
24
+ .filter(|entry| !entry.is_empty())
25
+ .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
26
+ .collect::<Vec<_>>();
27
+ paths.sort();
28
+ paths.dedup();
29
+ return Ok(paths);
30
+ }
31
+ }
32
+
33
+ let mut paths = Vec::new();
34
+ collect_files_recursive(root, root, &mut paths)?;
35
+ paths.sort();
36
+ Ok(paths)
37
+ }
38
+
39
+ pub(super) fn added_lines_by_path(root: &Path) -> Result<HashMap<String, usize>, NaomeError> {
40
+ let mut added = HashMap::new();
41
+ for args in [
42
+ vec!["diff", "--numstat"],
43
+ vec!["diff", "--cached", "--numstat"],
44
+ ] {
45
+ let output = Command::new("git").args(args).current_dir(root).output()?;
46
+ if !output.status.success() {
47
+ continue;
48
+ }
49
+ for line in String::from_utf8_lossy(&output.stdout).lines() {
50
+ let mut parts = line.split('\t');
51
+ let Some(additions) = parts.next() else {
52
+ continue;
53
+ };
54
+ let _deletions = parts.next();
55
+ let Some(path) = parts.next() else { continue };
56
+ let Ok(count) = additions.parse::<usize>() else {
57
+ continue;
58
+ };
59
+ *added.entry(path.replace('\\', "/")).or_insert(0) += count;
60
+ }
61
+ }
62
+ for path in untracked_paths(root)? {
63
+ if let Ok(content) = fs::read_to_string(root.join(&path)) {
64
+ added.insert(path, content.lines().count());
65
+ }
66
+ }
67
+ Ok(added)
68
+ }
69
+
70
+ fn collect_files_recursive(
71
+ root: &Path,
72
+ dir: &Path,
73
+ paths: &mut Vec<String>,
74
+ ) -> Result<(), NaomeError> {
75
+ for entry in fs::read_dir(dir)? {
76
+ let entry = entry?;
77
+ let path = entry.path();
78
+ if path.is_dir() {
79
+ collect_files_recursive(root, &path, paths)?;
80
+ } else if path.is_file() {
81
+ if let Ok(relative) = path.strip_prefix(root) {
82
+ paths.push(relative.to_string_lossy().replace('\\', "/"));
83
+ }
84
+ }
85
+ }
86
+ Ok(())
87
+ }
88
+
89
+ fn untracked_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
90
+ let output = Command::new("git")
91
+ .args(["ls-files", "--others", "--exclude-standard", "-z"])
92
+ .current_dir(root)
93
+ .output()?;
94
+ if !output.status.success() {
95
+ return Ok(Vec::new());
96
+ }
97
+ Ok(output
98
+ .stdout
99
+ .split(|byte| *byte == 0)
100
+ .filter(|entry| !entry.is_empty())
101
+ .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
102
+ .collect())
103
+ }