@lamentis/naome 1.2.0 → 1.3.0

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 (139) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +108 -47
  3. package/bin/naome-node.js +2 -1579
  4. package/bin/naome.js +34 -5
  5. package/crates/naome-cli/Cargo.toml +1 -1
  6. package/crates/naome-cli/src/dispatcher.rs +7 -2
  7. package/crates/naome-cli/src/main.rs +37 -22
  8. package/crates/naome-cli/src/quality_commands.rs +317 -10
  9. package/crates/naome-cli/src/workflow_commands.rs +21 -1
  10. package/crates/naome-core/Cargo.toml +1 -1
  11. package/crates/naome-core/src/decision/checks.rs +64 -0
  12. package/crates/naome-core/src/decision/idle.rs +67 -0
  13. package/crates/naome-core/src/decision/json.rs +36 -0
  14. package/crates/naome-core/src/decision/states.rs +165 -0
  15. package/crates/naome-core/src/decision.rs +131 -353
  16. package/crates/naome-core/src/git.rs +4 -2
  17. package/crates/naome-core/src/install_plan.rs +4 -0
  18. package/crates/naome-core/src/lib.rs +12 -6
  19. package/crates/naome-core/src/paths.rs +3 -1
  20. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  21. package/crates/naome-core/src/quality/adapters.rs +20 -67
  22. package/crates/naome-core/src/quality/baseline.rs +8 -0
  23. package/crates/naome-core/src/quality/cache.rs +153 -0
  24. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +25 -11
  25. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  26. package/crates/naome-core/src/quality/checks.rs +7 -8
  27. package/crates/naome-core/src/quality/cleanup.rs +48 -3
  28. package/crates/naome-core/src/quality/config.rs +8 -15
  29. package/crates/naome-core/src/quality/config_support.rs +24 -0
  30. package/crates/naome-core/src/quality/mod.rs +72 -6
  31. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  32. package/crates/naome-core/src/quality/scanner/analysis.rs +160 -0
  33. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  34. package/crates/naome-core/src/quality/scanner.rs +200 -215
  35. package/crates/naome-core/src/quality/semantic/checks.rs +134 -0
  36. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  37. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  38. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  39. package/crates/naome-core/src/quality/semantic.rs +68 -0
  40. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  41. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  42. package/crates/naome-core/src/quality/structure/checks/directory.rs +134 -0
  43. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  44. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  45. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  46. package/crates/naome-core/src/quality/structure/classify.rs +146 -0
  47. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  48. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  49. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  50. package/crates/naome-core/src/quality/structure/model.rs +131 -0
  51. package/crates/naome-core/src/quality/types.rs +43 -2
  52. package/crates/naome-core/src/route/builtin_checks.rs +141 -0
  53. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  54. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  55. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  56. package/crates/naome-core/src/route/context.rs +180 -0
  57. package/crates/naome-core/src/route/execution.rs +96 -0
  58. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  59. package/crates/naome-core/src/route/execution_support.rs +57 -0
  60. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  61. package/crates/naome-core/src/route/git_ops.rs +72 -0
  62. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  63. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  64. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  65. package/crates/naome-core/src/route/worktree.rs +75 -0
  66. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  67. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  68. package/crates/naome-core/src/route.rs +44 -1217
  69. package/crates/naome-core/src/verification.rs +1 -0
  70. package/crates/naome-core/src/workflow/doctor.rs +144 -0
  71. package/crates/naome-core/src/workflow/mod.rs +2 -0
  72. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  73. package/crates/naome-core/tests/decision.rs +24 -118
  74. package/crates/naome-core/tests/harness_health.rs +2 -0
  75. package/crates/naome-core/tests/install_plan.rs +2 -0
  76. package/crates/naome-core/tests/quality.rs +26 -123
  77. package/crates/naome-core/tests/quality_performance.rs +231 -0
  78. package/crates/naome-core/tests/quality_structure.rs +116 -0
  79. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  80. package/crates/naome-core/tests/quality_structure_policy.rs +144 -0
  81. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  82. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  83. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  84. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  85. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  86. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  87. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  88. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  89. package/crates/naome-core/tests/route.rs +1 -1376
  90. package/crates/naome-core/tests/route_baseline.rs +86 -0
  91. package/crates/naome-core/tests/route_completion.rs +141 -0
  92. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  93. package/crates/naome-core/tests/route_user_diff.rs +202 -0
  94. package/crates/naome-core/tests/route_worktree.rs +54 -0
  95. package/crates/naome-core/tests/semantic_legacy.rs +140 -0
  96. package/crates/naome-core/tests/task_state.rs +60 -432
  97. package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
  98. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  99. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  100. package/crates/naome-core/tests/verification.rs +4 -45
  101. package/crates/naome-core/tests/verification_contract.rs +22 -78
  102. package/crates/naome-core/tests/workflow_doctor.rs +24 -0
  103. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  104. package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
  105. package/installer/agents.js +90 -0
  106. package/installer/context.js +67 -0
  107. package/installer/filesystem.js +166 -0
  108. package/installer/flows.js +84 -0
  109. package/installer/git-boundary.js +171 -0
  110. package/installer/git-hook-content.js +36 -0
  111. package/installer/git-hooks.js +134 -0
  112. package/installer/git-local.js +2 -0
  113. package/installer/git-shared.js +35 -0
  114. package/installer/harness-file-ops.js +140 -0
  115. package/installer/harness-files.js +56 -0
  116. package/installer/harness-verification.js +123 -0
  117. package/installer/install-plan.js +66 -0
  118. package/installer/main.js +25 -0
  119. package/installer/manifest-state.js +167 -0
  120. package/installer/native-build.js +24 -0
  121. package/installer/native-format.js +6 -0
  122. package/installer/native.js +162 -0
  123. package/installer/output.js +131 -0
  124. package/installer/version.js +32 -0
  125. package/native/darwin-arm64/naome +0 -0
  126. package/native/linux-x64/naome +0 -0
  127. package/package.json +2 -1
  128. package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
  129. package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
  130. package/templates/naome-root/.naome/bin/naome.js +32 -21
  131. package/templates/naome-root/.naome/manifest.json +5 -3
  132. package/templates/naome-root/.naome/repository-structure.json +90 -0
  133. package/templates/naome-root/.naome/verification.json +1 -0
  134. package/templates/naome-root/.naomeignore +1 -0
  135. package/templates/naome-root/docs/naome/agent-workflow.md +16 -14
  136. package/templates/naome-root/docs/naome/index.md +4 -3
  137. package/templates/naome-root/docs/naome/repository-quality.md +66 -4
  138. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  139. package/templates/naome-root/docs/naome/testing.md +2 -1
@@ -0,0 +1,231 @@
1
+ mod repo_support;
2
+
3
+ use std::fs;
4
+
5
+ use naome_core::{
6
+ check_repository_quality, check_semantic_legacy, init_repository_quality,
7
+ init_repository_quality_with_mode, quality_cache_status, QualityInitMode, QualityMode,
8
+ };
9
+
10
+ use repo_support::TestRepo;
11
+
12
+ #[test]
13
+ fn quality_init_seeds_policy_without_baseline_scan() {
14
+ let repo = quality_repo("quality-init-seed-only");
15
+ repo.write_file("src/large.js", &large_file());
16
+ repo.commit_all("legacy debt");
17
+
18
+ let init = init_repository_quality(repo.path()).unwrap();
19
+
20
+ assert!(init.config_written);
21
+ assert!(init.structure_config_written);
22
+ assert!(!init.baseline_written);
23
+ assert!(init.baseline_pending);
24
+ assert_eq!(init.baseline_violations, 0);
25
+ assert!(repo
26
+ .path()
27
+ .join(".naome/repository-quality-baseline.json")
28
+ .is_file());
29
+ }
30
+
31
+ #[test]
32
+ fn quality_baseline_modes_are_explicit() {
33
+ let repo = quality_repo("quality-init-explicit-baseline");
34
+ repo.write_file("src/large.js", &large_file());
35
+ repo.commit_all("legacy debt");
36
+
37
+ let baseline =
38
+ init_repository_quality_with_mode(repo.path(), QualityInitMode::Baseline).unwrap();
39
+ assert!(baseline.baseline_written);
40
+ assert!(!baseline.baseline_pending);
41
+ assert!(baseline.baseline_violations > 0);
42
+
43
+ let deep =
44
+ init_repository_quality_with_mode(repo.path(), QualityInitMode::DeepBaseline).unwrap();
45
+ assert_eq!(deep.mode, "deep-baseline");
46
+ assert!(deep.baseline_violations >= baseline.baseline_violations);
47
+ }
48
+
49
+ #[test]
50
+ fn changed_fast_scans_only_changed_file_contents() {
51
+ let repo = quality_repo("quality-changed-fast-focused");
52
+ for index in 0..1000 {
53
+ repo.write_file(
54
+ &format!("src/unchanged_{index}.js"),
55
+ "export const value = 1;\n",
56
+ );
57
+ }
58
+ repo.write_file("src/changed.js", "export const before = 1;\n");
59
+ repo.commit_all("large baseline");
60
+ repo.write_file("src/changed.js", "export const after = 2;\n");
61
+
62
+ let report = check_repository_quality(repo.path(), QualityMode::ChangedFast).unwrap();
63
+
64
+ assert_eq!(report.mode, "changed");
65
+ assert_eq!(report.summary.scanned_files, 1);
66
+ assert_eq!(report.summary.scanned_path_count, 1);
67
+ assert!(report.changed_paths.contains(&"src/changed.js".to_string()));
68
+ }
69
+
70
+ #[test]
71
+ fn semantic_changed_check_scans_only_changed_file_contents() {
72
+ let repo = quality_repo("semantic-changed-fast-focused");
73
+ for index in 0..1000 {
74
+ repo.write_file(
75
+ &format!("src/unchanged_{index}.js"),
76
+ &legacy_fixture("oldConfig"),
77
+ );
78
+ }
79
+ repo.write_file("scripts/changed.test.js", "const value = 1;\n");
80
+ repo.commit_all("large baseline");
81
+ repo.write_file("scripts/changed.test.js", &legacy_fixture("newConfig"));
82
+
83
+ let report = check_semantic_legacy(repo.path(), QualityMode::ChangedFast).unwrap();
84
+
85
+ assert_eq!(report.mode, "changed");
86
+ assert_eq!(report.summary.scanned_files, 1);
87
+ }
88
+
89
+ #[test]
90
+ fn second_report_uses_file_analysis_cache() {
91
+ let repo = quality_repo("quality-cache-second-report");
92
+ repo.write_file("src/a.js", "export const value = 1;\n");
93
+ repo.write_file("src/b.js", "export const other = 2;\n");
94
+ repo.commit_all("baseline");
95
+
96
+ let first = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
97
+ let cache = quality_cache_status(repo.path()).unwrap();
98
+ let second = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
99
+
100
+ assert_eq!(first.summary.scanned_files, 2);
101
+ assert!(cache.entry_count >= 2);
102
+ assert_eq!(second.summary.cache_hits, 2);
103
+ assert_eq!(second.summary.cache_misses, 0);
104
+ }
105
+
106
+ #[test]
107
+ fn report_budget_marks_truncated_reports() {
108
+ let repo = quality_repo("quality-report-budget");
109
+ for index in 0..5005 {
110
+ repo.write_file(&format!("src/file_{index}.js"), "export const value = 1;\n");
111
+ }
112
+ repo.commit_all("large baseline");
113
+
114
+ let report = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
115
+
116
+ assert!(report.summary.truncated);
117
+ assert!(report
118
+ .summary
119
+ .reason_codes
120
+ .contains(&"max_scanned_files".to_string()));
121
+ assert_eq!(report.summary.scanned_files, 5000);
122
+ }
123
+
124
+ #[test]
125
+ fn report_skips_deep_only_duplicate_checks() {
126
+ let repo = quality_repo("quality-deep-only-duplicates");
127
+ repo.write_file("src/a.js", &duplicate_block("one"));
128
+ repo.write_file("src/b.js", &duplicate_block("two"));
129
+ repo.commit_all("duplicate baseline");
130
+
131
+ let report = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
132
+ let deep = check_repository_quality(repo.path(), QualityMode::DeepReport).unwrap();
133
+
134
+ assert!(report
135
+ .summary
136
+ .reason_codes
137
+ .contains(&"deep_checks_skipped".to_string()));
138
+ assert!(!report
139
+ .violations
140
+ .iter()
141
+ .any(|violation| violation.check_id == "duplicate-blocks"));
142
+ assert!(deep
143
+ .violations
144
+ .iter()
145
+ .any(|violation| violation.check_id == "duplicate-blocks"));
146
+ }
147
+
148
+ #[test]
149
+ fn changed_fast_compares_changed_files_to_cached_candidates() {
150
+ let repo = quality_repo("quality-changed-cache-duplicates");
151
+ repo.write_file("src/existing.js", &duplicate_block("existing"));
152
+ repo.write_file("src/new.js", "export const value = 1;\n");
153
+ repo.commit_all("baseline");
154
+ let _ = check_repository_quality(repo.path(), QualityMode::DeepReport).unwrap();
155
+
156
+ repo.write_file("src/new.js", &duplicate_block("newCopy"));
157
+ let report = check_repository_quality(repo.path(), QualityMode::ChangedFast).unwrap();
158
+
159
+ assert!(report.summary.cache_hits >= 1);
160
+ assert!(report.violations.iter().any(|violation| {
161
+ violation.check_id == "duplicate-blocks" && violation.path == "src/new.js"
162
+ }));
163
+ }
164
+
165
+ #[test]
166
+ fn changed_fast_compares_changed_files_to_cold_cache_candidates() {
167
+ let repo = quality_repo("quality-changed-cold-cache-duplicates");
168
+ repo.write_file("src/existing.js", &duplicate_block("existing"));
169
+ repo.write_file("src/new.js", "export const value = 1;\n");
170
+ repo.commit_all("baseline");
171
+
172
+ repo.write_file("src/new.js", &duplicate_block("newCopy"));
173
+ let report = check_repository_quality(repo.path(), QualityMode::ChangedFast).unwrap();
174
+
175
+ assert_eq!(report.summary.cache_hits, 0);
176
+ assert!(report.summary.cache_misses >= 2);
177
+ assert_eq!(report.summary.scanned_files, 1);
178
+ assert!(report.violations.iter().any(|violation| {
179
+ violation.check_id == "duplicate-blocks"
180
+ && violation.path == "src/new.js"
181
+ && violation
182
+ .related_paths
183
+ .contains(&"src/existing.js".to_string())
184
+ }));
185
+ }
186
+
187
+ fn quality_repo(name: &str) -> TestRepo {
188
+ let repo = TestRepo::new(name);
189
+ repo.init_git();
190
+ fs::create_dir_all(repo.path().join(".naome")).unwrap();
191
+ repo
192
+ }
193
+
194
+ fn large_file() -> String {
195
+ (0..520)
196
+ .map(|index| format!("export const value{index} = {index};\n"))
197
+ .collect()
198
+ }
199
+
200
+ fn legacy_fixture(name: &str) -> String {
201
+ format!(
202
+ r#"const {name} = {{
203
+ schema: "naome.performance-fixture.v1",
204
+ version: 1,
205
+ status: "ready",
206
+ checks: [],
207
+ changeTypes: [],
208
+ releaseGates: []
209
+ }};
210
+ "#
211
+ )
212
+ }
213
+
214
+ fn duplicate_block(name: &str) -> String {
215
+ format!(
216
+ r#"export function {name}() {{
217
+ const one = 1;
218
+ const two = 2;
219
+ const three = 3;
220
+ const four = 4;
221
+ const five = 5;
222
+ const six = 6;
223
+ const seven = 7;
224
+ const eight = 8;
225
+ const nine = 9;
226
+ const ten = 10;
227
+ return one + two + three + four + five + six + seven + eight + nine + ten;
228
+ }}
229
+ "#
230
+ )
231
+ }
@@ -0,0 +1,116 @@
1
+ mod quality_structure_support;
2
+
3
+ use naome_core::{check_repository_quality, QualityMode};
4
+ use serde_json::json;
5
+
6
+ use quality_structure_support::{assert_has, StructureFixture};
7
+
8
+ #[test]
9
+ fn source_and_test_files_in_known_roots_are_ok() {
10
+ let repo = StructureFixture::new("structure-good-roots");
11
+ repo.write_js_package();
12
+ repo.write(
13
+ "src/feature.js",
14
+ "export function feature() {\n return 1;\n}\n",
15
+ );
16
+ repo.write("tests/feature.test.js", "import '../src/feature.js';\n");
17
+
18
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
19
+
20
+ assert!(report.ok, "{:#?}", report.violations);
21
+ }
22
+
23
+ #[test]
24
+ fn misplaced_test_file_creates_structure_finding() {
25
+ let repo = StructureFixture::new("structure-misplaced-test");
26
+ repo.write_js_package();
27
+ repo.write(
28
+ "src/feature.test.js",
29
+ "export function testFeature() {\n return 1;\n}\n",
30
+ );
31
+
32
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
33
+
34
+ assert_has(&report, "src/feature.test.js", "misplaced-file-role");
35
+ }
36
+
37
+ #[test]
38
+ fn generated_or_artifact_file_in_source_folder_creates_finding() {
39
+ let repo = StructureFixture::new("structure-generated-source");
40
+ repo.write_js_package();
41
+ repo.write(
42
+ "src/generated/client.ts",
43
+ "export const generated = true;\n",
44
+ );
45
+
46
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
47
+
48
+ assert_has(&report, "src/generated/client.ts", "directory-role-mixing");
49
+ }
50
+
51
+ #[test]
52
+ fn unknown_root_files_create_root_sprawl_findings() {
53
+ let repo = StructureFixture::new("structure-root-sprawl");
54
+ repo.write("notes.tmp", "temporary notes\n");
55
+
56
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
57
+
58
+ assert_has(&report, "notes.tmp", "root-file-sprawl");
59
+ }
60
+
61
+ #[test]
62
+ fn dumping_ground_directory_flags_new_feature_logic() {
63
+ let repo = StructureFixture::new("structure-dumping-ground");
64
+ repo.write_js_package();
65
+ repo.write(
66
+ "src/payments/service.js",
67
+ "export function charge() {\n return 1;\n}\n",
68
+ );
69
+ repo.commit_all("module baseline");
70
+ repo.write(
71
+ "src/utils/payment-flow.js",
72
+ "export function capturePayment() {\n return charge();\n}\n",
73
+ );
74
+
75
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
76
+
77
+ assert_has(
78
+ &report,
79
+ "src/utils/payment-flow.js",
80
+ "dumping-ground-directory",
81
+ );
82
+ }
83
+
84
+ #[test]
85
+ fn excessive_path_depth_and_directory_size_are_reported() {
86
+ let repo = StructureFixture::new("structure-limits");
87
+ repo.write_structure_config(json!({
88
+ "limits": {
89
+ "maxDirectoryFiles": 2,
90
+ "maxPathDepth": 4,
91
+ "maxDirectoryRoles": 2,
92
+ "maxDumpingGroundFiles": 8
93
+ }
94
+ }));
95
+ repo.write("src/a/one.js", "export const one = 1;\n");
96
+ repo.write("src/a/two.js", "export const two = 2;\n");
97
+ repo.write("src/a/three.js", "export const three = 3;\n");
98
+ repo.write("src/a/b/c/d/e/deep.js", "export const deep = 1;\n");
99
+
100
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
101
+
102
+ assert_has(&report, "src/a", "directory-size");
103
+ assert_has(&report, "src/a/b/c/d/e/deep.js", "path-depth");
104
+ }
105
+
106
+ #[test]
107
+ fn case_insensitive_path_collisions_block_changed_paths() {
108
+ let repo = StructureFixture::new("structure-case-collision");
109
+ repo.add_index_only_path("src/User.ts", "export const upper = 1;\n");
110
+ repo.add_index_only_path("src/user.ts", "export const lower = 1;\n");
111
+
112
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
113
+
114
+ assert_has(&report, "src/User.ts", "case-collision");
115
+ assert_has(&report, "src/user.ts", "case-collision");
116
+ }
@@ -0,0 +1,98 @@
1
+ mod quality_structure_support;
2
+
3
+ use naome_core::{check_repository_quality, explain_repository_structure, QualityMode};
4
+
5
+ use quality_structure_support::{assert_has, StructureFixture};
6
+
7
+ #[test]
8
+ fn rust_adapter_recognizes_cargo_source_and_test_structure() {
9
+ let repo = StructureFixture::new("structure-rust-adapter");
10
+ repo.write(
11
+ "Cargo.toml",
12
+ "[package]\nname = \"demo\"\nversion = \"0.1.0\"\n",
13
+ );
14
+ repo.write("src/lib.rs", "pub fn value() -> u8 {\n 1\n}\n");
15
+ repo.write(
16
+ "tests/lib_test.rs",
17
+ "#[test]\nfn value() {\n assert_eq!(1, 1);\n}\n",
18
+ );
19
+
20
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
21
+ let source = explain_repository_structure(repo.path(), "src/lib.rs").unwrap();
22
+
23
+ assert!(report.ok, "{:#?}", report.violations);
24
+ assert_eq!(source.role, "source");
25
+ assert_eq!(source.language.as_deref(), Some("rust"));
26
+ }
27
+
28
+ #[test]
29
+ fn javascript_typescript_adapter_recognizes_package_source_and_tests() {
30
+ let repo = StructureFixture::new("structure-js-ts-adapter");
31
+ repo.write_js_package();
32
+ repo.write("src/app.ts", "export function app() {\n return 1;\n}\n");
33
+ repo.write("src/app.test.ts", "import { app } from './app';\napp();\n");
34
+
35
+ let source = explain_repository_structure(repo.path(), "src/app.ts").unwrap();
36
+ let test = explain_repository_structure(repo.path(), "src/app.test.ts").unwrap();
37
+
38
+ assert_eq!(source.role, "source");
39
+ assert_eq!(source.language.as_deref(), Some("typescript"));
40
+ assert_eq!(test.role, "test");
41
+ }
42
+
43
+ #[test]
44
+ fn unknown_repository_gets_generic_roles() {
45
+ let repo = StructureFixture::new("structure-generic-roles");
46
+ repo.write("docs/readme.md", "# Docs\n");
47
+ repo.write("scripts/build.sh", "echo build\n");
48
+ repo.write("src/main.go", "package main\nfunc main() {}\n");
49
+
50
+ assert_eq!(
51
+ explain_repository_structure(repo.path(), "docs/readme.md")
52
+ .unwrap()
53
+ .role,
54
+ "docs"
55
+ );
56
+ assert_eq!(
57
+ explain_repository_structure(repo.path(), "scripts/build.sh")
58
+ .unwrap()
59
+ .role,
60
+ "script"
61
+ );
62
+ assert_eq!(
63
+ explain_repository_structure(repo.path(), "src/main.go")
64
+ .unwrap()
65
+ .language
66
+ .as_deref(),
67
+ Some("go")
68
+ );
69
+ }
70
+
71
+ #[test]
72
+ fn source_changes_without_nearby_tests_create_pairing_finding() {
73
+ let repo = StructureFixture::new("structure-missing-test-pair");
74
+ repo.write_js_package();
75
+ repo.write(
76
+ "src/billing/invoice.ts",
77
+ "export function invoice() {\n return 1;\n}\n",
78
+ );
79
+
80
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
81
+
82
+ assert_has(&report, "src/billing/invoice.ts", "test-source-pairing");
83
+ }
84
+
85
+ #[test]
86
+ fn repositories_without_structure_config_remain_valid() {
87
+ let repo = StructureFixture::new("structure-config-backcompat");
88
+ repo.write_quality_config();
89
+ repo.write(
90
+ "src/index.js",
91
+ "export function index() {\n return 1;\n}\n",
92
+ );
93
+ repo.write("tests/index.test.js", "import '../src/index.js';\n");
94
+
95
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
96
+
97
+ assert!(report.ok, "{:#?}", report.violations);
98
+ }
@@ -0,0 +1,144 @@
1
+ mod quality_structure_support;
2
+
3
+ use naome_core::{
4
+ check_repository_quality, explain_repository_structure, route_quality_cleanup, QualityMode,
5
+ };
6
+
7
+ use quality_structure_support::{assert_has, StructureFixture};
8
+
9
+ #[test]
10
+ fn legacy_structure_debt_is_reported_but_not_changed_blocking() {
11
+ let repo = StructureFixture::new("structure-legacy-debt");
12
+ repo.write("random.tmp", "legacy scratch\n");
13
+ repo.commit_all("legacy debt");
14
+
15
+ let changed = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
16
+ let report = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
17
+
18
+ assert!(changed.ok);
19
+ assert_has(&report, "random.tmp", "root-file-sprawl");
20
+ }
21
+
22
+ #[test]
23
+ fn touched_file_inside_debt_directory_blocks_changed_gate() {
24
+ let repo = StructureFixture::new("structure-touched-debt-dir");
25
+ repo.write_js_package();
26
+ repo.write(
27
+ "src/utils/legacy-helper.js",
28
+ "export function legacy() {\n return 1;\n}\n",
29
+ );
30
+ repo.commit_all("legacy dumping ground");
31
+ repo.write(
32
+ "src/utils/legacy-helper.js",
33
+ "export function legacy() {\n return 2;\n}\n",
34
+ );
35
+
36
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
37
+
38
+ assert_has(
39
+ &report,
40
+ "src/utils/legacy-helper.js",
41
+ "dumping-ground-directory",
42
+ );
43
+ }
44
+
45
+ #[test]
46
+ fn quality_report_and_cleanup_route_include_structure_findings() {
47
+ let repo = StructureFixture::new("structure-report-cleanup");
48
+ repo.write(
49
+ "src/utils/random.js",
50
+ "export function random() {\n return 1;\n}\n",
51
+ );
52
+ repo.commit_all("legacy structure debt");
53
+
54
+ let report = check_repository_quality(repo.path(), QualityMode::Report).unwrap();
55
+ let route = route_quality_cleanup(repo.path(), "src/utils/random.js").unwrap();
56
+
57
+ assert_has(&report, "src/utils/random.js", "dumping-ground-directory");
58
+ assert!(route.agent_instructions.contains("structure"));
59
+ assert!(route
60
+ .violations
61
+ .iter()
62
+ .any(|violation| violation.check_id == "dumping-ground-directory"));
63
+ }
64
+
65
+ #[test]
66
+ fn cleanup_route_gives_specific_test_pairing_instructions() {
67
+ let repo = StructureFixture::new("structure-cleanup-test-pairing");
68
+ repo.write_js_package();
69
+ repo.write(
70
+ "src/billing/invoice.js",
71
+ "export function invoiceTotal() {\n return 1;\n}\n",
72
+ );
73
+
74
+ let route = route_quality_cleanup(repo.path(), "src/billing/invoice.js").unwrap();
75
+
76
+ assert!(route
77
+ .agent_instructions
78
+ .contains("Create or update a nearby or module-matched test"));
79
+ assert!(route
80
+ .required_checks
81
+ .contains(&"naome quality check --changed".to_string()));
82
+ }
83
+
84
+ #[test]
85
+ fn wildcard_module_roots_explain_monorepo_module_names() {
86
+ let repo = StructureFixture::new("structure-wildcard-module-root");
87
+ repo.write_structure_config(serde_json::json!({
88
+ "sourceRoots": ["packages/*/src/**"],
89
+ "moduleRoots": ["packages/*/src/**"]
90
+ }));
91
+ repo.write("packages/payments/src/lib.rs", "pub fn charge() {}\n");
92
+
93
+ let explanation =
94
+ explain_repository_structure(repo.path(), "packages/payments/src/lib.rs").unwrap();
95
+
96
+ assert_eq!(explanation.module.as_deref(), Some("payments"));
97
+ }
98
+
99
+ #[test]
100
+ fn disabled_structure_checks_are_not_reported() {
101
+ let repo = StructureFixture::new("structure-disabled-checks");
102
+ repo.write_structure_config(serde_json::json!({
103
+ "allowedRootFiles": [".naomeignore", "package.json", "README.md"],
104
+ "disabledChecks": ["test-source-pairing"]
105
+ }));
106
+ repo.write(
107
+ "src/payment-flow.js",
108
+ "export function paymentFlow() {\n return 1;\n}\n",
109
+ );
110
+
111
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
112
+
113
+ assert!(report.ok, "{:#?}", report.violations);
114
+ }
115
+
116
+ #[test]
117
+ fn directory_role_rules_can_allow_configured_role_mixes() {
118
+ let repo = StructureFixture::new("structure-directory-role-rule");
119
+ repo.write_structure_config(serde_json::json!({
120
+ "directoryRoleRules": [
121
+ {
122
+ "id": "generated-source-client",
123
+ "paths": ["src/generated/**"],
124
+ "allowedRoles": ["generated"],
125
+ "maxRoles": 1
126
+ }
127
+ ]
128
+ }));
129
+ repo.write(
130
+ "src/generated/client.ts",
131
+ "export const generated = true;\n",
132
+ );
133
+
134
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
135
+
136
+ assert!(
137
+ !report
138
+ .violations
139
+ .iter()
140
+ .any(|violation| violation.check_id == "directory-role-mixing"),
141
+ "{:#?}",
142
+ report.violations
143
+ );
144
+ }