@lamentis/naome 1.2.0 → 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 (113) hide show
  1. package/Cargo.lock +2 -2
  2. package/bin/naome-node.js +2 -1579
  3. package/bin/naome.js +19 -5
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/dispatcher.rs +2 -1
  6. package/crates/naome-cli/src/main.rs +3 -0
  7. package/crates/naome-cli/src/quality_commands.rs +90 -2
  8. package/crates/naome-core/Cargo.toml +1 -1
  9. package/crates/naome-core/src/decision/checks.rs +64 -0
  10. package/crates/naome-core/src/decision/idle.rs +67 -0
  11. package/crates/naome-core/src/decision/json.rs +36 -0
  12. package/crates/naome-core/src/decision/states.rs +165 -0
  13. package/crates/naome-core/src/decision.rs +131 -353
  14. package/crates/naome-core/src/install_plan.rs +2 -0
  15. package/crates/naome-core/src/lib.rs +5 -3
  16. package/crates/naome-core/src/paths.rs +3 -1
  17. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  18. package/crates/naome-core/src/quality/adapters.rs +20 -67
  19. package/crates/naome-core/src/quality/cleanup.rs +13 -1
  20. package/crates/naome-core/src/quality/config.rs +8 -15
  21. package/crates/naome-core/src/quality/config_support.rs +24 -0
  22. package/crates/naome-core/src/quality/mod.rs +18 -0
  23. package/crates/naome-core/src/quality/scanner.rs +20 -8
  24. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  25. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  26. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  27. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  28. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  29. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  30. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  31. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  32. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  33. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  34. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  35. package/crates/naome-core/src/quality/types.rs +3 -0
  36. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  37. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  38. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  39. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  40. package/crates/naome-core/src/route/context.rs +180 -0
  41. package/crates/naome-core/src/route/execution.rs +96 -0
  42. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  43. package/crates/naome-core/src/route/execution_support.rs +57 -0
  44. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  45. package/crates/naome-core/src/route/git_ops.rs +72 -0
  46. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  47. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  48. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  49. package/crates/naome-core/src/route/worktree.rs +75 -0
  50. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  51. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  52. package/crates/naome-core/src/route.rs +44 -1217
  53. package/crates/naome-core/src/verification.rs +1 -0
  54. package/crates/naome-core/tests/decision.rs +24 -118
  55. package/crates/naome-core/tests/harness_health.rs +2 -0
  56. package/crates/naome-core/tests/quality.rs +12 -118
  57. package/crates/naome-core/tests/quality_structure.rs +116 -0
  58. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  59. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  60. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  61. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  62. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  63. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  64. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  65. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  66. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  67. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  68. package/crates/naome-core/tests/route.rs +1 -1376
  69. package/crates/naome-core/tests/route_baseline.rs +86 -0
  70. package/crates/naome-core/tests/route_completion.rs +141 -0
  71. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  72. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  73. package/crates/naome-core/tests/route_worktree.rs +54 -0
  74. package/crates/naome-core/tests/task_state.rs +60 -432
  75. package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
  76. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  77. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  78. package/crates/naome-core/tests/verification.rs +4 -45
  79. package/crates/naome-core/tests/verification_contract.rs +22 -78
  80. package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
  81. package/installer/agents.js +90 -0
  82. package/installer/context.js +67 -0
  83. package/installer/filesystem.js +166 -0
  84. package/installer/flows.js +84 -0
  85. package/installer/git-boundary.js +170 -0
  86. package/installer/git-hook-content.js +36 -0
  87. package/installer/git-hooks.js +134 -0
  88. package/installer/git-local.js +2 -0
  89. package/installer/git-shared.js +35 -0
  90. package/installer/harness-file-ops.js +140 -0
  91. package/installer/harness-files.js +56 -0
  92. package/installer/harness-verification.js +123 -0
  93. package/installer/install-plan.js +66 -0
  94. package/installer/main.js +25 -0
  95. package/installer/manifest-state.js +167 -0
  96. package/installer/native-build.js +24 -0
  97. package/installer/native-format.js +6 -0
  98. package/installer/native.js +162 -0
  99. package/installer/output.js +131 -0
  100. package/installer/version.js +32 -0
  101. package/native/darwin-arm64/naome +0 -0
  102. package/native/linux-x64/naome +0 -0
  103. package/package.json +2 -1
  104. package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
  105. package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
  106. package/templates/naome-root/.naome/bin/naome.js +25 -21
  107. package/templates/naome-root/.naome/manifest.json +4 -2
  108. package/templates/naome-root/.naome/repository-structure.json +90 -0
  109. package/templates/naome-root/.naome/verification.json +1 -0
  110. package/templates/naome-root/docs/naome/index.md +4 -3
  111. package/templates/naome-root/docs/naome/repository-quality.md +3 -0
  112. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  113. package/templates/naome-root/docs/naome/testing.md +2 -1
@@ -243,6 +243,7 @@ fn builtin_checks() -> Vec<BuiltinCheck> {
243
243
  purpose: "Validate changed files against deterministic NAOME repository quality rules.",
244
244
  evidence: &[
245
245
  ".naome/repository-quality.json",
246
+ ".naome/repository-structure.json",
246
247
  ".naome/repository-quality-baseline.json",
247
248
  ],
248
249
  },
@@ -1,12 +1,14 @@
1
1
  use std::fs;
2
- use std::path::{Path, PathBuf};
3
- use std::process::Command;
4
2
  use std::sync::Mutex;
5
3
  use std::time::{SystemTime, UNIX_EPOCH};
6
4
 
7
5
  use naome_core::{evaluate_decision, format_decision, EvaluationOptions};
8
6
  use serde_json::json;
9
7
 
8
+ mod repo_support;
9
+
10
+ use repo_support::TestRepo;
11
+
10
12
  static ENV_LOCK: Mutex<()> = Mutex::new(());
11
13
 
12
14
  #[test]
@@ -34,20 +36,7 @@ fn completed_task_diff_returns_baseline_choices() {
34
36
  let repo = TestRepo::new("completed-task");
35
37
  repo.init_git();
36
38
  repo.write_file("README.md", "# Baseline\n");
37
- repo.write_naome_json("init-state.json", json!({ "initialized": true }));
38
- repo.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
39
- repo.write_naome_json(
40
- "task-state.json",
41
- json!({
42
- "status": "complete",
43
- "activeTask": {
44
- "id": "readme-task",
45
- "allowedPaths": ["README.md"],
46
- "requiredCheckIds": ["diff-check"],
47
- "proofResults": []
48
- }
49
- }),
50
- );
39
+ repo.write_base_naome_state(completed_without_proof_state());
51
40
  repo.git(&["add", "."]);
52
41
  repo.git(&["commit", "-m", "baseline"]);
53
42
  repo.write_file("README.md", "# Changed\n");
@@ -71,17 +60,7 @@ fn completed_task_diff_returns_baseline_choices() {
71
60
 
72
61
  #[test]
73
62
  fn idle_unowned_diff_blocks_new_feature_work() {
74
- let repo = TestRepo::new("unowned-diff");
75
- repo.init_git();
76
- repo.write_file("README.md", "# Baseline\n");
77
- repo.write_naome_json("init-state.json", json!({ "initialized": true }));
78
- repo.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
79
- repo.write_naome_json(
80
- "task-state.json",
81
- json!({ "status": "idle", "activeTask": null }),
82
- );
83
- repo.git(&["add", "."]);
84
- repo.git(&["commit", "-m", "baseline"]);
63
+ let repo = TestRepo::idle_readme("unowned-diff");
85
64
  repo.write_file("README.md", "# Manual change\n");
86
65
 
87
66
  let decision = evaluate_decision(repo.path(), EvaluationOptions::offline()).unwrap();
@@ -96,12 +75,7 @@ fn changed_paths_handle_worktree_renames_without_corrupting_old_path_tokens() {
96
75
  let repo = TestRepo::new("worktree-rename");
97
76
  repo.init_git();
98
77
  repo.write_file("packages/old/file.txt", "baseline\n");
99
- repo.write_naome_json("init-state.json", json!({ "initialized": true }));
100
- repo.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
101
- repo.write_naome_json(
102
- "task-state.json",
103
- json!({ "status": "idle", "activeTask": null }),
104
- );
78
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
105
79
  repo.git(&["add", "."]);
106
80
  repo.git(&["commit", "-m", "baseline"]);
107
81
 
@@ -129,19 +103,10 @@ fn active_task_allows_control_state_and_untracked_files_inside_scope() {
129
103
  let repo = TestRepo::new("active-scope");
130
104
  repo.init_git();
131
105
  repo.write_file("README.md", "# Baseline\n");
132
- repo.write_naome_json("init-state.json", json!({ "initialized": true }));
133
- repo.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
134
- repo.write_naome_json(
135
- "task-state.json",
136
- json!({
137
- "status": "implementing",
138
- "activeTask": {
139
- "id": "rust-task",
140
- "allowedPaths": ["packages/naome/crates/naome-core/src/lib.rs"],
141
- "requiredCheckIds": ["decision-engine-tests"],
142
- "proofResults": []
143
- }
144
- }),
106
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
107
+ repo.write_implementing_task_state(
108
+ "packages/naome/crates/naome-core/src/lib.rs",
109
+ "decision-engine-tests",
145
110
  );
146
111
  repo.git(&["add", "."]);
147
112
  repo.git(&["commit", "-m", "baseline"]);
@@ -149,17 +114,9 @@ fn active_task_allows_control_state_and_untracked_files_inside_scope() {
149
114
  "packages/naome/crates/naome-core/src/lib.rs",
150
115
  "pub fn marker() {}\n",
151
116
  );
152
- repo.write_naome_json(
153
- "task-state.json",
154
- json!({
155
- "status": "implementing",
156
- "activeTask": {
157
- "id": "rust-task",
158
- "allowedPaths": ["packages/naome/crates/naome-core/src/lib.rs"],
159
- "requiredCheckIds": ["decision-engine-tests"],
160
- "proofResults": []
161
- }
162
- }),
117
+ repo.write_implementing_task_state(
118
+ "packages/naome/crates/naome-core/src/lib.rs",
119
+ "decision-engine-tests",
163
120
  );
164
121
 
165
122
  let decision = evaluate_decision(repo.path(), EvaluationOptions::offline()).unwrap();
@@ -202,12 +159,7 @@ fn online_checks_use_explicit_node_binary_from_environment() {
202
159
  let _guard = ENV_LOCK.lock().unwrap();
203
160
  let repo = TestRepo::new("explicit-node-bin");
204
161
  repo.init_git();
205
- repo.write_naome_json("init-state.json", json!({ "initialized": true }));
206
- repo.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
207
- repo.write_naome_json(
208
- "task-state.json",
209
- json!({ "status": "idle", "activeTask": null }),
210
- );
162
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
211
163
  repo.git(&["add", "."]);
212
164
  repo.git(&["commit", "-m", "baseline"]);
213
165
  let fake_node = std::env::temp_dir().join(format!(
@@ -240,60 +192,14 @@ fn online_checks_use_explicit_node_binary_from_environment() {
240
192
  );
241
193
  }
242
194
 
243
- struct TestRepo {
244
- root: PathBuf,
245
- }
246
-
247
- impl TestRepo {
248
- fn new(name: &str) -> Self {
249
- let nonce = SystemTime::now()
250
- .duration_since(UNIX_EPOCH)
251
- .unwrap()
252
- .as_nanos();
253
- let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
254
- fs::create_dir_all(root.join(".naome")).unwrap();
255
- Self { root }
256
- }
257
-
258
- fn path(&self) -> &Path {
259
- &self.root
260
- }
261
-
262
- fn write_file(&self, relative_path: &str, content: &str) {
263
- let path = self.root.join(relative_path);
264
- if let Some(parent) = path.parent() {
265
- fs::create_dir_all(parent).unwrap();
195
+ fn completed_without_proof_state() -> serde_json::Value {
196
+ json!({
197
+ "status": "complete",
198
+ "activeTask": {
199
+ "id": "readme-task",
200
+ "allowedPaths": ["README.md"],
201
+ "requiredCheckIds": ["diff-check"],
202
+ "proofResults": []
266
203
  }
267
- fs::write(path, content).unwrap();
268
- }
269
-
270
- fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
271
- let path = self.root.join(".naome").join(file_name);
272
- fs::write(
273
- path,
274
- format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
275
- )
276
- .unwrap();
277
- }
278
-
279
- fn init_git(&self) {
280
- self.git(&["init"]);
281
- self.git(&["config", "user.email", "naome@example.com"]);
282
- self.git(&["config", "user.name", "NAOME Test"]);
283
- }
284
-
285
- fn git(&self, args: &[&str]) {
286
- let output = Command::new("git")
287
- .args(args)
288
- .current_dir(&self.root)
289
- .output()
290
- .unwrap();
291
- assert!(
292
- output.status.success(),
293
- "git {:?} failed\nstdout:\n{}\nstderr:\n{}",
294
- args,
295
- String::from_utf8_lossy(&output.stdout),
296
- String::from_utf8_lossy(&output.stderr)
297
- );
298
- }
204
+ })
299
205
  }
@@ -30,11 +30,13 @@ const PROJECT_OWNED_PATHS: &[&str] = &[
30
30
  ".naome/upgrade-state.json",
31
31
  ".naome/verification.json",
32
32
  ".naome/repository-quality.json",
33
+ ".naome/repository-structure.json",
33
34
  ".naome/repository-quality-baseline.json",
34
35
  "docs/naome/architecture.md",
35
36
  "docs/naome/decisions.md",
36
37
  "docs/naome/repo-profile.md",
37
38
  "docs/naome/repository-quality.md",
39
+ "docs/naome/repository-structure.md",
38
40
  "docs/naome/security.md",
39
41
  "docs/naome/testing.md",
40
42
  ];
@@ -1,20 +1,19 @@
1
1
  use std::fs;
2
- use std::path::{Path, PathBuf};
3
- use std::process::Command;
4
- use std::sync::atomic::{AtomicU64, Ordering};
5
2
 
6
3
  use naome_core::{
7
4
  check_repository_quality, init_repository_quality, plan_quality_cleanup, route_quality_cleanup,
8
5
  seed_builtin_verification_checks, QualityMode,
9
6
  };
10
- use serde_json::{json, Value};
7
+ use serde_json::Value;
11
8
 
12
- static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
9
+ mod quality_structure_support;
10
+
11
+ use quality_structure_support::StructureFixture as QualityFixture;
13
12
 
14
13
  #[test]
15
14
  fn changed_check_blocks_touched_legacy_file_until_it_conforms() {
16
15
  let repo = QualityFixture::new("quality-touched-legacy");
17
- repo.write_quality_config(5, 40, 4);
16
+ repo.write_quality_limits(5, 40, 4);
18
17
  repo.write(
19
18
  "src/large.js",
20
19
  "export function legacy() {\n one();\n two();\n three();\n four();\n}\n",
@@ -38,7 +37,7 @@ fn changed_check_blocks_touched_legacy_file_until_it_conforms() {
38
37
  #[test]
39
38
  fn whole_repo_debt_is_visible_without_blocking_clean_changed_check() {
40
39
  let repo = QualityFixture::new("quality-clean-legacy-report");
41
- repo.write_quality_config(5, 40, 4);
40
+ repo.write_quality_limits(5, 40, 4);
42
41
  repo.write(
43
42
  "src/large.js",
44
43
  "export function legacy() {\n one();\n two();\n three();\n four();\n}\n",
@@ -60,7 +59,7 @@ fn whole_repo_debt_is_visible_without_blocking_clean_changed_check() {
60
59
  #[test]
61
60
  fn changed_check_blocks_new_duplicate_blocks_against_existing_code() {
62
61
  let repo = QualityFixture::new("quality-duplicate-block");
63
- repo.write_quality_config(200, 40, 4);
62
+ repo.write_quality_limits(200, 40, 4);
64
63
  repo.write(
65
64
  "src/existing.js",
66
65
  "export function existing() {\n const alpha = input.alpha;\n const beta = input.beta;\n const total = alpha + beta;\n return total;\n}\n",
@@ -87,7 +86,7 @@ fn changed_check_blocks_new_duplicate_blocks_against_existing_code() {
87
86
  #[test]
88
87
  fn duplicate_blocks_report_one_finding_per_overlapping_region() {
89
88
  let repo = QualityFixture::new("quality-duplicate-region");
90
- repo.write_quality_config(200, 80, 4);
89
+ repo.write_quality_limits(200, 80, 4);
91
90
  repo.write(
92
91
  "src/existing.js",
93
92
  "export function existing() {\n const alpha = input.alpha;\n const beta = input.beta;\n const gamma = input.gamma;\n const total = alpha + beta + gamma;\n return total;\n}\n",
@@ -114,7 +113,7 @@ fn duplicate_blocks_report_one_finding_per_overlapping_region() {
114
113
  #[test]
115
114
  fn duplicate_blocks_detect_repeated_regions_in_the_same_file() {
116
115
  let repo = QualityFixture::new("quality-same-file-duplicate-region");
117
- repo.write_quality_config(200, 80, 4);
116
+ repo.write_quality_limits(200, 80, 4);
118
117
  repo.commit_all("empty baseline");
119
118
 
120
119
  repo.write(
@@ -150,7 +149,7 @@ fn duplicate_blocks_detect_repeated_regions_in_the_same_file() {
150
149
  #[test]
151
150
  fn duplicate_blocks_ignore_generated_sha256_maps() {
152
151
  let repo = QualityFixture::new("quality-generated-hash-map");
153
- repo.write_quality_config(200, 80, 4);
152
+ repo.write_quality_limits(200, 80, 4);
154
153
  let generated_map = concat!(
155
154
  "const expected = Object.freeze({\n",
156
155
  " \"one.js\": \"sha256:1111111111111111111111111111111111111111111111111111111111111111\",\n",
@@ -173,7 +172,7 @@ fn duplicate_blocks_ignore_generated_sha256_maps() {
173
172
  #[test]
174
173
  fn near_duplicate_functions_do_not_compare_containers_with_child_symbols() {
175
174
  let repo = QualityFixture::new("quality-container-child-similarity");
176
- repo.write_quality_config(200, 120, 10);
175
+ repo.write_quality_limits(200, 120, 10);
177
176
  repo.write(
178
177
  "src/lib.rs",
179
178
  "pub struct Runner;\n\nimpl Runner {\n pub fn execute(&self) {\n alpha();\n beta();\n gamma();\n delta();\n epsilon();\n zeta();\n eta();\n theta();\n iota();\n kappa();\n lambda();\n mu();\n nu();\n xi();\n omicron();\n pi();\n }\n}\n",
@@ -300,7 +299,7 @@ fn seeding_builtin_verification_wires_repository_quality_into_change_types() {
300
299
  #[test]
301
300
  fn cleanup_plan_and_route_turn_debt_into_agent_work() {
302
301
  let repo = QualityFixture::new("quality-cleanup-plan");
303
- repo.write_quality_config(5, 40, 4);
302
+ repo.write_quality_limits(5, 40, 4);
304
303
  repo.write(
305
304
  "src/large.js",
306
305
  "export function legacy() {\n one();\n two();\n three();\n four();\n}\n",
@@ -318,108 +317,3 @@ fn cleanup_plan_and_route_turn_debt_into_agent_work() {
318
317
  .required_checks
319
318
  .contains(&"naome quality check --changed".to_string()));
320
319
  }
321
-
322
- struct QualityFixture {
323
- root: PathBuf,
324
- }
325
-
326
- impl QualityFixture {
327
- fn new(name: &str) -> Self {
328
- let root = std::env::temp_dir().join(format!(
329
- "naome-{name}-{}-{}",
330
- std::process::id(),
331
- FIXTURE_COUNTER.fetch_add(1, Ordering::SeqCst)
332
- ));
333
- let _ = fs::remove_dir_all(&root);
334
- fs::create_dir_all(root.join(".naome")).unwrap();
335
- fs::write(root.join(".naomeignore"), ".naome/archive/\n").unwrap();
336
- git(&root, &["init"]);
337
- git(&root, &["config", "user.email", "test@example.com"]);
338
- git(&root, &["config", "user.name", "Test User"]);
339
- Self { root }
340
- }
341
-
342
- fn path(&self) -> &Path {
343
- &self.root
344
- }
345
-
346
- fn write(&self, relative_path: &str, content: &str) {
347
- let path = self.root.join(relative_path);
348
- if let Some(parent) = path.parent() {
349
- fs::create_dir_all(parent).unwrap();
350
- }
351
- fs::write(path, content).unwrap();
352
- }
353
-
354
- fn write_quality_config(
355
- &self,
356
- max_file_lines: usize,
357
- max_function_lines: usize,
358
- duplicate_block_lines: usize,
359
- ) {
360
- self.write_quality_config_with_adapters(
361
- max_file_lines,
362
- max_function_lines,
363
- duplicate_block_lines,
364
- &[],
365
- );
366
- }
367
-
368
- fn write_quality_config_with_adapters(
369
- &self,
370
- max_file_lines: usize,
371
- max_function_lines: usize,
372
- duplicate_block_lines: usize,
373
- enabled_adapters: &[&str],
374
- ) {
375
- let config = json!({
376
- "schema": "naome.repository-quality.v1",
377
- "version": 1,
378
- "status": "ready",
379
- "limits": {
380
- "maxFileLines": max_file_lines,
381
- "maxNewFileLines": max_file_lines,
382
- "maxDiffAddedLines": 200,
383
- "maxFunctionLines": max_function_lines,
384
- "maxTopLevelSymbols": 30,
385
- "duplicateBlockLines": duplicate_block_lines,
386
- "nearDuplicateSimilarity": 0.9
387
- },
388
- "enabledAdapters": enabled_adapters,
389
- "disabledChecks": [],
390
- "ignoredPaths": [],
391
- "generatedPaths": [],
392
- "pathRules": []
393
- });
394
- self.write(
395
- ".naome/repository-quality.json",
396
- &format!("{}\n", serde_json::to_string_pretty(&config).unwrap()),
397
- );
398
- }
399
-
400
- fn commit_all(&self, message: &str) {
401
- git(&self.root, &["add", "."]);
402
- git(&self.root, &["commit", "-m", message]);
403
- }
404
- }
405
-
406
- impl Drop for QualityFixture {
407
- fn drop(&mut self) {
408
- let _ = fs::remove_dir_all(&self.root);
409
- }
410
- }
411
-
412
- fn git(root: &Path, args: &[&str]) {
413
- let output = Command::new("git")
414
- .args(args)
415
- .current_dir(root)
416
- .output()
417
- .unwrap();
418
- assert!(
419
- output.status.success(),
420
- "git {} failed\nstdout: {}\nstderr: {}",
421
- args.join(" "),
422
- String::from_utf8_lossy(&output.stdout),
423
- String::from_utf8_lossy(&output.stderr)
424
- );
425
- }
@@ -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
+ }