@lamentis/naome 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +117 -47
  3. package/bin/naome.js +65 -12
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/context_commands.rs +47 -0
  6. package/crates/naome-cli/src/dispatcher.rs +12 -2
  7. package/crates/naome-cli/src/main.rs +78 -29
  8. package/crates/naome-cli/src/quality_commands.rs +238 -34
  9. package/crates/naome-cli/src/quality_output.rs +34 -0
  10. package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
  11. package/crates/naome-cli/src/repository_model_commands.rs +84 -0
  12. package/crates/naome-cli/src/task_commands.rs +62 -0
  13. package/crates/naome-cli/src/workflow_commands.rs +120 -3
  14. package/crates/naome-core/Cargo.toml +1 -1
  15. package/crates/naome-core/src/context/helpers.rs +75 -0
  16. package/crates/naome-core/src/context/select.rs +134 -0
  17. package/crates/naome-core/src/context/types.rs +43 -0
  18. package/crates/naome-core/src/context.rs +6 -0
  19. package/crates/naome-core/src/decision/states.rs +1 -1
  20. package/crates/naome-core/src/decision.rs +4 -1
  21. package/crates/naome-core/src/git.rs +4 -2
  22. package/crates/naome-core/src/install_plan.rs +20 -0
  23. package/crates/naome-core/src/journal.rs +2 -7
  24. package/crates/naome-core/src/lib.rs +35 -8
  25. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  26. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  27. package/crates/naome-core/src/quality/adapters.rs +81 -18
  28. package/crates/naome-core/src/quality/baseline.rs +8 -0
  29. package/crates/naome-core/src/quality/cache.rs +151 -0
  30. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +19 -8
  31. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  32. package/crates/naome-core/src/quality/checks.rs +7 -8
  33. package/crates/naome-core/src/quality/cleanup.rs +36 -3
  34. package/crates/naome-core/src/quality/config.rs +21 -3
  35. package/crates/naome-core/src/quality/mod.rs +189 -10
  36. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  37. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  38. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  39. package/crates/naome-core/src/quality/scanner/analysis.rs +175 -0
  40. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  41. package/crates/naome-core/src/quality/scanner.rs +235 -217
  42. package/crates/naome-core/src/quality/semantic/checks.rs +151 -0
  43. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  44. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  45. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  46. package/crates/naome-core/src/quality/semantic.rs +68 -0
  47. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  48. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  49. package/crates/naome-core/src/quality/structure/checks/directory.rs +13 -21
  50. package/crates/naome-core/src/quality/structure/checks.rs +1 -1
  51. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  52. package/crates/naome-core/src/quality/structure/classify.rs +52 -0
  53. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  54. package/crates/naome-core/src/quality/structure/mod.rs +5 -2
  55. package/crates/naome-core/src/quality/structure/model.rs +8 -1
  56. package/crates/naome-core/src/quality/types.rs +59 -2
  57. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  58. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  59. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  60. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  61. package/crates/naome-core/src/repository_model/types.rs +152 -0
  62. package/crates/naome-core/src/repository_model/world.rs +48 -0
  63. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  64. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  65. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  66. package/crates/naome-core/src/repository_model.rs +164 -0
  67. package/crates/naome-core/src/route/builtin_checks.rs +41 -16
  68. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  69. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  70. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  71. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  72. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  73. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  74. package/crates/naome-core/src/task_ledger.rs +48 -0
  75. package/crates/naome-core/src/task_state/api.rs +4 -2
  76. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  77. package/crates/naome-core/src/task_state/diff.rs +2 -2
  78. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  79. package/crates/naome-core/src/task_state/mod.rs +1 -1
  80. package/crates/naome-core/src/task_state/progress.rs +13 -0
  81. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  82. package/crates/naome-core/src/task_state/repair.rs +2 -2
  83. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  84. package/crates/naome-core/src/task_state/types.rs +24 -0
  85. package/crates/naome-core/src/verification.rs +29 -18
  86. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  87. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  88. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  89. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  90. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  91. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  92. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  93. package/crates/naome-core/src/workflow/agent.rs +34 -0
  94. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  95. package/crates/naome-core/src/workflow/doctor.rs +183 -0
  96. package/crates/naome-core/src/workflow/mod.rs +13 -0
  97. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  98. package/crates/naome-core/src/workflow/output.rs +8 -2
  99. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  100. package/crates/naome-core/tests/context.rs +99 -0
  101. package/crates/naome-core/tests/harness_health.rs +4 -0
  102. package/crates/naome-core/tests/install_plan.rs +14 -0
  103. package/crates/naome-core/tests/quality.rs +190 -5
  104. package/crates/naome-core/tests/quality_performance.rs +268 -0
  105. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  106. package/crates/naome-core/tests/quality_structure_policy.rs +19 -0
  107. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  108. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  109. package/crates/naome-core/tests/repository_model.rs +281 -0
  110. package/crates/naome-core/tests/route_user_diff.rs +59 -7
  111. package/crates/naome-core/tests/semantic_legacy.rs +174 -0
  112. package/crates/naome-core/tests/task_ledger.rs +328 -0
  113. package/crates/naome-core/tests/task_state.rs +28 -0
  114. package/crates/naome-core/tests/verification.rs +29 -36
  115. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  116. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  117. package/crates/naome-core/tests/workflow_doctor.rs +45 -0
  118. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  119. package/installer/codex-hooks.js +121 -0
  120. package/installer/context.js +10 -0
  121. package/installer/filesystem.js +4 -0
  122. package/installer/flows.js +8 -4
  123. package/installer/git-boundary.js +1 -0
  124. package/installer/harness-files.js +6 -0
  125. package/installer/install-plan.js +4 -0
  126. package/installer/main.js +1 -1
  127. package/installer/native.js +1 -1
  128. package/native/darwin-arm64/naome +0 -0
  129. package/native/linux-x64/naome +0 -0
  130. package/package.json +1 -1
  131. package/templates/naome-root/.codex/config.toml +2 -0
  132. package/templates/naome-root/.codex/hooks.json +70 -0
  133. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  134. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  135. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  136. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  137. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  138. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  139. package/templates/naome-root/.naome/bin/naome.js +45 -7
  140. package/templates/naome-root/.naome/manifest.json +12 -6
  141. package/templates/naome-root/.naome/repository-model.json +6 -0
  142. package/templates/naome-root/.naome/repository-quality.json +3 -1
  143. package/templates/naome-root/.naome/verification.json +15 -1
  144. package/templates/naome-root/.naomeignore +1 -0
  145. package/templates/naome-root/AGENTS.md +38 -83
  146. package/templates/naome-root/docs/naome/agent-workflow.md +66 -28
  147. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  148. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  149. package/templates/naome-root/docs/naome/first-run.md +25 -14
  150. package/templates/naome-root/docs/naome/index.md +18 -10
  151. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  152. package/templates/naome-root/docs/naome/repository-quality.md +104 -5
  153. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  154. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  155. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -0,0 +1,281 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::process::Command;
4
+ use std::sync::atomic::{AtomicU64, Ordering};
5
+
6
+ use naome_core::{explain_repository_model_path, refresh_repository_model, repository_model_drift};
7
+
8
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
9
+
10
+ #[test]
11
+ fn refresh_write_captures_deterministic_repository_facts() {
12
+ let repo = Repo::new("repository-model-refresh");
13
+ repo.write(
14
+ "Cargo.toml",
15
+ "[package]\nname = \"demo\"\nversion = \"0.1.0\"\n",
16
+ );
17
+ repo.write("package.json", "{\"scripts\":{\"test\":\"node --test\"}}\n");
18
+ repo.write("src/lib.rs", "pub fn run() {}\n");
19
+ repo.write("tests/app.test.ts", "test('works', () => {});\n");
20
+ repo.write(
21
+ ".naome/verification.json",
22
+ "{\"checks\":[{\"id\":\"unit-test\",\"command\":\"cargo test\",\"cwd\":\".\",\"evidence\":[\"src/lib.rs\"]}]}\n",
23
+ );
24
+
25
+ let report = refresh_repository_model(repo.path(), true).unwrap();
26
+
27
+ assert!(report.ok);
28
+ assert_eq!(report.updated_paths, vec![".naome/repository-model.json"]);
29
+ assert_fact(&report.model, "language", "rust");
30
+ assert_fact(&report.model, "language", "javascript-typescript");
31
+ assert_fact(&report.model, "packageManager", "cargo");
32
+ assert_fact(&report.model, "packageManager", "npm");
33
+ assert_fact(&report.model, "sourceRoot", "src");
34
+ assert_fact(&report.model, "testRoot", "tests");
35
+ assert_fact(&report.model, "verificationCheck", "unit-test");
36
+ let js_fact = fact(&report.model, "language", "javascript-typescript");
37
+ assert_eq!(js_fact.evidence, vec!["package.json", "tests/app.test.ts"]);
38
+ assert!(repo.path().join(".naome/repository-model.json").is_file());
39
+ }
40
+
41
+ #[test]
42
+ fn refresh_write_prefers_v2_world_model_sections() {
43
+ let repo = Repo::new("repository-world-model-v2");
44
+ repo.write(
45
+ "packages/mobile/Package.swift",
46
+ "// swift-tools-version: 5.9\n",
47
+ );
48
+ repo.write("packages/mobile/Sources/App/App.swift", "import SwiftUI\n");
49
+ repo.write(
50
+ "packages/mobile/Tests/AppTests/AppTests.swift",
51
+ "import XCTest\n",
52
+ );
53
+ repo.write(
54
+ "packages/web/package.json",
55
+ "{\"scripts\":{\"test\":\"vitest\"}}\n",
56
+ );
57
+ repo.write(
58
+ "packages/web/src/App.tsx",
59
+ "export function App() { return null; }\n",
60
+ );
61
+ repo.write(
62
+ "packages/web/src/App.test.tsx",
63
+ "test('works', () => {});\n",
64
+ );
65
+ repo.write(
66
+ ".naome/verification.json",
67
+ "{\"checks\":[{\"id\":\"unit-test\",\"command\":\"npm test\",\"cwd\":\".\",\"evidence\":[\"packages/web/src/App.tsx\"]}]}\n",
68
+ );
69
+
70
+ let report = refresh_repository_model(repo.path(), true).unwrap();
71
+
72
+ assert!(report.ok);
73
+ assert_eq!(report.model.schema, "naome.repository-model.v2");
74
+ assert_eq!(report.model.version, 2);
75
+ assert!(report
76
+ .model
77
+ .languages
78
+ .iter()
79
+ .any(|language| language.id == "swift"));
80
+ assert!(report
81
+ .model
82
+ .languages
83
+ .iter()
84
+ .any(|language| language.id == "javascript-typescript"));
85
+ assert!(report
86
+ .model
87
+ .adapters
88
+ .iter()
89
+ .any(|adapter| adapter.id == "swiftui"));
90
+ assert!(report
91
+ .model
92
+ .entities
93
+ .iter()
94
+ .any(|entity| entity.id == "package:packages/mobile"));
95
+ assert!(report
96
+ .model
97
+ .entities
98
+ .iter()
99
+ .any(|entity| entity.id == "package:packages/web"));
100
+ assert!(report
101
+ .model
102
+ .roots
103
+ .iter()
104
+ .any(|root| root.path == "packages/mobile/Sources" && root.role == "source"));
105
+ assert!(report
106
+ .model
107
+ .verification_checks
108
+ .iter()
109
+ .any(|check| check.id == "unit-test"));
110
+ }
111
+
112
+ #[test]
113
+ fn v1_model_files_remain_readable_for_path_explain() {
114
+ let repo = Repo::new("repository-model-v1-readable");
115
+ repo.write(
116
+ ".naome/repository-model.json",
117
+ "{\"schema\":\"naome.repository-model.v1\",\"version\":1,\"status\":\"ready\",\"facts\":[{\"id\":\"language:rust\",\"category\":\"language\",\"value\":\"rust\",\"confidence\":\"high\",\"source\":\"deterministic-scan\",\"evidence\":[\"src/lib.rs\"]}]}\n",
118
+ );
119
+ repo.write("src/lib.rs", "pub fn run() {}\n");
120
+
121
+ let explanation = explain_repository_model_path(repo.path(), "src/lib.rs").unwrap();
122
+
123
+ assert_eq!(explanation.language.as_deref(), Some("rust"));
124
+ assert_eq!(explanation.role.as_deref(), Some("source"));
125
+ assert!(explanation
126
+ .facts
127
+ .iter()
128
+ .any(|fact| fact.id == "language:rust"));
129
+ }
130
+
131
+ #[test]
132
+ fn path_explain_returns_world_model_entity_and_role() {
133
+ let repo = Repo::new("repository-model-explain-world");
134
+ repo.write("packages/api/Cargo.toml", "[package]\nname = \"api\"\n");
135
+ repo.write("packages/api/src/lib.rs", "pub fn run() {}\n");
136
+ refresh_repository_model(repo.path(), true).unwrap();
137
+
138
+ let explanation =
139
+ explain_repository_model_path(repo.path(), "packages/api/src/lib.rs").unwrap();
140
+
141
+ assert_eq!(explanation.language.as_deref(), Some("rust"));
142
+ assert_eq!(explanation.role.as_deref(), Some("source"));
143
+ assert_eq!(explanation.module.as_deref(), Some("packages/api"));
144
+ assert_eq!(explanation.entity.as_deref(), Some("package:packages/api"));
145
+ }
146
+
147
+ #[test]
148
+ fn check_reports_stale_model_until_refresh_write_updates_it() {
149
+ let repo = Repo::new("repository-model-stale");
150
+ repo.write(".naome/repository-model.json", "{\"schema\":\"naome.repository-model.v1\",\"version\":1,\"status\":\"ready\",\"facts\":[]}\n");
151
+ repo.write("Package.swift", "// swift-tools-version: 5.9\n");
152
+ repo.write("Sources/App/App.swift", "public struct App {}\n");
153
+
154
+ let check = refresh_repository_model(repo.path(), false).unwrap();
155
+ assert!(!check.ok);
156
+ assert!(check.stale);
157
+ assert_fact(&check.model, "language", "swift");
158
+
159
+ let written = refresh_repository_model(repo.path(), true).unwrap();
160
+ assert!(written.ok);
161
+ assert!(!written.stale);
162
+ assert_eq!(written.updated_paths, vec![".naome/repository-model.json"]);
163
+
164
+ let repeated = refresh_repository_model(repo.path(), false).unwrap();
165
+ assert!(repeated.ok);
166
+ assert!(!repeated.stale);
167
+ }
168
+
169
+ #[test]
170
+ fn drift_detects_world_sections_when_fact_values_are_unchanged() {
171
+ let repo = Repo::new("repository-model-world-section-drift");
172
+ repo.write(
173
+ "packages/a/package.json",
174
+ "{\"scripts\":{\"test\":\"node --test\"}}\n",
175
+ );
176
+ repo.write("packages/a/src/index.ts", "export const a = 1;\n");
177
+ refresh_repository_model(repo.path(), true).unwrap();
178
+
179
+ let clean = repository_model_drift(repo.path()).unwrap();
180
+ assert!(clean.ok);
181
+
182
+ repo.write(
183
+ "packages/b/package.json",
184
+ "{\"scripts\":{\"test\":\"node --test\"}}\n",
185
+ );
186
+
187
+ let drift = repository_model_drift(repo.path()).unwrap();
188
+
189
+ assert!(!drift.ok);
190
+ assert!(drift.stale);
191
+ assert!(drift
192
+ .related_paths
193
+ .contains(&"packages/b/package.json".to_string()));
194
+ }
195
+
196
+ #[test]
197
+ fn explain_repository_model_path_uses_canonical_facts() {
198
+ let repo = Repo::new("repository-model-explain");
199
+ repo.write("Package.swift", "// swift-tools-version: 5.9\n");
200
+ repo.write("Sources/App/App.swift", "public struct App {}\n");
201
+ refresh_repository_model(repo.path(), true).unwrap();
202
+
203
+ let explanation = explain_repository_model_path(repo.path(), "Sources/App/App.swift").unwrap();
204
+
205
+ assert_eq!(explanation.path, "Sources/App/App.swift");
206
+ assert_eq!(explanation.language.as_deref(), Some("swift"));
207
+ assert_eq!(explanation.role.as_deref(), Some("source"));
208
+ assert!(explanation
209
+ .evidence
210
+ .contains(&"Sources/App/App.swift".to_string()));
211
+ }
212
+
213
+ fn assert_fact(model: &naome_core::RepositoryModel, category: &str, value: &str) {
214
+ assert!(
215
+ model
216
+ .facts
217
+ .iter()
218
+ .any(|fact| fact.category == category && fact.value == value),
219
+ "missing {category}:{value}; facts: {:#?}",
220
+ model.facts
221
+ );
222
+ }
223
+
224
+ fn fact<'a>(
225
+ model: &'a naome_core::RepositoryModel,
226
+ category: &str,
227
+ value: &str,
228
+ ) -> &'a naome_core::RepositoryFact {
229
+ model
230
+ .facts
231
+ .iter()
232
+ .find(|fact| fact.category == category && fact.value == value)
233
+ .unwrap_or_else(|| panic!("missing {category}:{value}; facts: {:#?}", model.facts))
234
+ }
235
+
236
+ struct Repo {
237
+ root: PathBuf,
238
+ }
239
+
240
+ impl Repo {
241
+ fn new(name: &str) -> Self {
242
+ let root = std::env::temp_dir().join(format!(
243
+ "naome-{name}-{}-{}",
244
+ std::process::id(),
245
+ FIXTURE_COUNTER.fetch_add(1, Ordering::SeqCst)
246
+ ));
247
+ let _ = fs::remove_dir_all(&root);
248
+ fs::create_dir_all(root.join(".naome")).unwrap();
249
+ git(&root, &["init"]);
250
+ git(&root, &["config", "user.email", "test@example.com"]);
251
+ git(&root, &["config", "user.name", "Test User"]);
252
+ Self { root }
253
+ }
254
+
255
+ fn path(&self) -> &Path {
256
+ &self.root
257
+ }
258
+
259
+ fn write(&self, relative_path: &str, content: &str) {
260
+ let path = self.root.join(relative_path);
261
+ if let Some(parent) = path.parent() {
262
+ fs::create_dir_all(parent).unwrap();
263
+ }
264
+ fs::write(path, content).unwrap();
265
+ }
266
+ }
267
+
268
+ impl Drop for Repo {
269
+ fn drop(&mut self) {
270
+ let _ = fs::remove_dir_all(&self.root);
271
+ }
272
+ }
273
+
274
+ fn git(root: &Path, args: &[&str]) {
275
+ let output = Command::new("git")
276
+ .args(args)
277
+ .current_dir(root)
278
+ .output()
279
+ .unwrap();
280
+ assert!(output.status.success());
281
+ }
@@ -5,7 +5,10 @@ use serde_json::json;
5
5
 
6
6
  mod repo_support;
7
7
 
8
- use repo_support::{assert_user_diff_committed, route_commit_request, TestRepo};
8
+ use repo_support::{
9
+ assert_user_diff_committed, change_type, quality_check, repository_quality_config_source,
10
+ route_commit_request, semantic_repository_quality_fixture_source, verification_value, TestRepo,
11
+ };
9
12
 
10
13
  #[test]
11
14
  fn execute_route_does_not_mutate_or_offer_clear_commit_for_dirty_unowned_diff() {
@@ -70,20 +73,69 @@ fn execute_route_runs_repository_quality_check_without_shelling_to_repo_command(
70
73
  }
71
74
 
72
75
  #[test]
73
- fn execute_route_commits_product_diff_with_declared_safe_quality_checks() {
76
+ fn execute_route_refuses_semantic_changed_findings_through_repository_quality_gate() {
77
+ let repo = TestRepo::new("route-semantic-quality-gate");
78
+ repo.init_git();
79
+ repo.write_file(
80
+ ".naome/repository-quality.json",
81
+ &repository_quality_config_source(),
82
+ );
83
+ repo.write_file("scripts/baseline.test.js", "const ok = { value: 1 };\n");
84
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
85
+ repo.write_naome_json(
86
+ "verification.json",
87
+ verification_value(
88
+ "ready",
89
+ vec![quality_check(
90
+ "repository-quality-check",
91
+ "naome quality check --changed",
92
+ "Validate changed files against repository quality rules.",
93
+ "fast",
94
+ vec!["scripts/**"],
95
+ )],
96
+ vec![change_type(
97
+ "scripts",
98
+ "Script changes.",
99
+ vec!["scripts/**"],
100
+ vec!["repository-quality-check"],
101
+ )],
102
+ ),
103
+ );
104
+ repo.commit_all("baseline");
105
+ repo.write_file(
106
+ "scripts/new.test.js",
107
+ &semantic_repository_quality_fixture_source("newQualityConfig"),
108
+ );
109
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
110
+
111
+ let route = route_commit_request(&repo);
112
+
113
+ assert_quality_gate_blocked(&repo, &route, before_head, "scripts/new.test.js");
114
+ assert!(route.user_message.contains("repository-quality-check"));
115
+ assert!(route
116
+ .user_message
117
+ .contains("semantic-inline-legacy-fixture"));
118
+ }
119
+
120
+ #[test]
121
+ fn execute_route_refuses_product_named_checks_without_generic_builtin_policy() {
74
122
  let repo = TestRepo::product_quality_repo("route-product-diff-quality-pass");
75
123
  repo.write_file(
76
124
  "packages/naome/crates/naome-core/src/route.rs",
77
125
  "pub fn changed() {}\n",
78
126
  );
127
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
79
128
 
80
129
  let route = route_commit_request(&repo);
81
130
 
82
- assert_user_diff_committed(&route, &repo);
83
-
84
- let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
85
- assert!(committed_paths.contains("packages/naome/crates/naome-core/src/route.rs"));
86
- assert!(route.user_message.contains("quality gates passed"));
131
+ assert_quality_gate_blocked(
132
+ &repo,
133
+ &route,
134
+ before_head,
135
+ "packages/naome/crates/naome-core/src/route.rs",
136
+ );
137
+ assert!(route.user_message.contains("installer-tests"));
138
+ assert!(route.user_message.contains("not a built-in safe check"));
87
139
  }
88
140
 
89
141
  #[test]
@@ -0,0 +1,174 @@
1
+ mod repo_support;
2
+
3
+ use naome_core::{
4
+ check_repository_quality, check_semantic_legacy, semantic_route_for_finding, QualityMode,
5
+ };
6
+
7
+ use repo_support::{
8
+ repository_quality_config_value, semantic_repository_quality_fixture_source, TestRepo,
9
+ };
10
+
11
+ #[test]
12
+ fn report_groups_repeated_config_fixture_shapes_across_repository() {
13
+ let repo = semantic_repo("semantic-report-groups");
14
+ repo.write_file(
15
+ "scripts/a.test.js",
16
+ &semantic_repository_quality_fixture_source("qualityConfig"),
17
+ );
18
+ repo.write_file(
19
+ "scripts/b.test.js",
20
+ &semantic_repository_quality_fixture_source("repositoryQualityConfig"),
21
+ );
22
+ repo.commit_all("baseline");
23
+
24
+ let report = check_semantic_legacy(repo.path(), QualityMode::DeepReport).unwrap();
25
+
26
+ let finding = report
27
+ .findings
28
+ .iter()
29
+ .find(|finding| finding.kind == "copied-config-object")
30
+ .expect("expected copied config finding");
31
+ assert_eq!(finding.occurrences.len(), 2);
32
+ assert!(finding
33
+ .occurrences
34
+ .iter()
35
+ .any(|occurrence| occurrence.path == "scripts/a.test.js"));
36
+ assert!(finding
37
+ .cleanup_route
38
+ .agent_instructions
39
+ .iter()
40
+ .any(|instruction| instruction.contains("every occurrence")));
41
+ }
42
+
43
+ #[test]
44
+ fn changed_check_blocks_new_inline_legacy_fixture_only() {
45
+ let repo = semantic_repo("semantic-changed-only");
46
+ repo.write_file(
47
+ "scripts/old.test.js",
48
+ &semantic_repository_quality_fixture_source("oldQualityConfig"),
49
+ );
50
+ repo.commit_all("baseline");
51
+ repo.write_file("README.md", "# harmless change\n");
52
+
53
+ let unrelated = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
54
+ assert!(unrelated.ok, "{:#?}", unrelated.findings);
55
+
56
+ repo.write_file(
57
+ "scripts/new.test.js",
58
+ &semantic_repository_quality_fixture_source("newQualityConfig"),
59
+ );
60
+
61
+ let changed = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
62
+ assert!(!changed.ok);
63
+ assert!(changed
64
+ .findings
65
+ .iter()
66
+ .any(|finding| finding.kind == "inline-legacy-fixture"));
67
+ }
68
+
69
+ #[test]
70
+ fn repository_quality_changed_includes_semantic_changed_findings() {
71
+ let repo = semantic_repo("semantic-quality-gate");
72
+ repo.commit_all("baseline");
73
+ repo.write_file(
74
+ "scripts/new.test.js",
75
+ &semantic_repository_quality_fixture_source("newQualityConfig"),
76
+ );
77
+
78
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
79
+
80
+ assert!(!report.ok);
81
+ assert!(report.violations.iter().any(|violation| {
82
+ violation.check_id == "semantic-inline-legacy-fixture"
83
+ && violation.path == "scripts/new.test.js"
84
+ }));
85
+ }
86
+
87
+ #[test]
88
+ fn changed_check_allows_shared_fixture_factory_objects() {
89
+ let repo = semantic_repo("semantic-shared-factory");
90
+ repo.commit_all("baseline");
91
+ repo.write_file(
92
+ "scripts/test-support.js",
93
+ &[
94
+ "function verificationContract() {",
95
+ " return {",
96
+ " schema: \"naome.verification.v1\",",
97
+ " version: 1,",
98
+ " status: \"ready\",",
99
+ " checks: [],",
100
+ " changeTypes: [],",
101
+ " releaseGates: []",
102
+ " };",
103
+ "}",
104
+ "",
105
+ ]
106
+ .join("\n"),
107
+ );
108
+
109
+ let report = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
110
+
111
+ assert!(report.ok, "{:#?}", report.findings);
112
+ }
113
+
114
+ #[test]
115
+ fn changed_check_allows_shared_support_fixture_modules() {
116
+ let repo = semantic_repo("semantic-shared-support");
117
+ repo.commit_all("baseline");
118
+ repo.write_file(
119
+ "tests/repo_support/verification_values.rs",
120
+ &[
121
+ "pub fn completed_task_state() -> serde_json::Value {",
122
+ " serde_json::json!({",
123
+ " \"schema\": \"naome.task-state.v1\",",
124
+ " \"version\": 1,",
125
+ " \"status\": \"complete\",",
126
+ " \"activeTask\": {",
127
+ " \"id\": \"readme-task\",",
128
+ " \"requiredCheckIds\": [\"diff-check\"],",
129
+ " \"proofResults\": []",
130
+ " }",
131
+ " })",
132
+ "}",
133
+ "",
134
+ ]
135
+ .join("\n"),
136
+ );
137
+
138
+ let report = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
139
+
140
+ assert!(report.ok, "{:#?}", report.findings);
141
+ }
142
+
143
+ #[test]
144
+ fn semantic_route_returns_complete_agent_cleanup_task() {
145
+ let repo = semantic_repo("semantic-route");
146
+ repo.write_file(
147
+ "scripts/a.test.js",
148
+ &semantic_repository_quality_fixture_source("qualityConfig"),
149
+ );
150
+ repo.write_file(
151
+ "scripts/b.test.js",
152
+ &semantic_repository_quality_fixture_source("repositoryQualityConfig"),
153
+ );
154
+ repo.commit_all("baseline");
155
+ let report = check_semantic_legacy(repo.path(), QualityMode::DeepReport).unwrap();
156
+ let finding_id = report.findings[0].id.clone();
157
+
158
+ let finding = semantic_route_for_finding(&report, &finding_id).unwrap();
159
+
160
+ assert_eq!(finding.id, finding_id);
161
+ assert!(!finding.cleanup_route.target_suggestion.is_empty());
162
+ assert!(finding
163
+ .cleanup_route
164
+ .required_checks
165
+ .contains(&"naome semantic check --changed".to_string()));
166
+ }
167
+
168
+ fn semantic_repo(name: &str) -> TestRepo {
169
+ let repo = TestRepo::new(name);
170
+ repo.init_git();
171
+ repo.write_file("README.md", "# Semantic fixture\n");
172
+ repo.write_naome_json("repository-quality.json", repository_quality_config_value());
173
+ repo
174
+ }