@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,249 @@
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::QualityReport;
7
+ use serde_json::json;
8
+
9
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
10
+
11
+ pub struct StructureFixture {
12
+ root: PathBuf,
13
+ }
14
+
15
+ #[allow(dead_code)]
16
+ impl StructureFixture {
17
+ pub fn new(name: &str) -> Self {
18
+ let root = std::env::temp_dir().join(format!(
19
+ "naome-{name}-{}-{}",
20
+ std::process::id(),
21
+ FIXTURE_COUNTER.fetch_add(1, Ordering::SeqCst)
22
+ ));
23
+ let _ = fs::remove_dir_all(&root);
24
+ fs::create_dir_all(root.join(".naome")).unwrap();
25
+ fs::write(root.join(".naomeignore"), ".naome/archive/\n").unwrap();
26
+ git(&root, &["init"]);
27
+ git(&root, &["config", "user.email", "test@example.com"]);
28
+ git(&root, &["config", "user.name", "Test User"]);
29
+ Self { root }
30
+ }
31
+
32
+ pub fn path(&self) -> &Path {
33
+ &self.root
34
+ }
35
+
36
+ pub fn write(&self, relative_path: impl AsRef<Path>, content: &str) {
37
+ let destination = self.root.join(relative_path.as_ref());
38
+ let parent = destination.parent().expect("fixture paths are file paths");
39
+ fs::create_dir_all(parent)
40
+ .unwrap_or_else(|error| panic!("create {}: {error}", parent.display()));
41
+ fs::write(&destination, content)
42
+ .unwrap_or_else(|error| panic!("write {}: {error}", destination.display()));
43
+ }
44
+
45
+ pub fn write_js_package(&self) {
46
+ self.write("package.json", "{\"scripts\":{\"test\":\"node --test\"}}\n");
47
+ }
48
+
49
+ pub fn write_quality_config(&self) {
50
+ self.write(
51
+ ".naome/repository-quality.json",
52
+ concat!(
53
+ "{\n",
54
+ " \"schema\": \"naome.repository-quality.v1\",\n",
55
+ " \"version\": 1,\n",
56
+ " \"status\": \"ready\",\n",
57
+ " \"limits\": {\n",
58
+ " \"maxFileLines\": 450,\n",
59
+ " \"maxNewFileLines\": 300,\n",
60
+ " \"maxDiffAddedLines\": 180,\n",
61
+ " \"maxFunctionLines\": 100,\n",
62
+ " \"maxTopLevelSymbols\": 30,\n",
63
+ " \"duplicateBlockLines\": 10,\n",
64
+ " \"nearDuplicateSimilarity\": 0.9\n",
65
+ " },\n",
66
+ " \"enabledAdapters\": [],\n",
67
+ " \"disabledChecks\": [],\n",
68
+ " \"ignoredPaths\": [],\n",
69
+ " \"generatedPaths\": [],\n",
70
+ " \"pathRules\": []\n",
71
+ "}\n"
72
+ ),
73
+ );
74
+ }
75
+
76
+ pub fn write_quality_limits(
77
+ &self,
78
+ max_file_lines: usize,
79
+ max_function_lines: usize,
80
+ duplicate_block_lines: usize,
81
+ ) {
82
+ self.write_quality_config_with_adapters(
83
+ max_file_lines,
84
+ max_function_lines,
85
+ duplicate_block_lines,
86
+ &[],
87
+ );
88
+ }
89
+
90
+ pub fn write_quality_config_with_adapters(
91
+ &self,
92
+ max_file_lines: usize,
93
+ max_function_lines: usize,
94
+ duplicate_block_lines: usize,
95
+ enabled_adapters: &[&str],
96
+ ) {
97
+ self.write(
98
+ ".naome/repository-quality.json",
99
+ &format!(
100
+ "{}\n",
101
+ serde_json::to_string_pretty(&json!({
102
+ "schema": "naome.repository-quality.v1",
103
+ "version": 1,
104
+ "status": "ready",
105
+ "limits": {
106
+ "maxFileLines": max_file_lines,
107
+ "maxNewFileLines": max_file_lines,
108
+ "maxDiffAddedLines": 200,
109
+ "maxFunctionLines": max_function_lines,
110
+ "maxTopLevelSymbols": 30,
111
+ "duplicateBlockLines": duplicate_block_lines,
112
+ "nearDuplicateSimilarity": 0.9
113
+ },
114
+ "enabledAdapters": enabled_adapters,
115
+ "disabledChecks": [],
116
+ "ignoredPaths": [],
117
+ "generatedPaths": [],
118
+ "pathRules": []
119
+ }))
120
+ .unwrap()
121
+ ),
122
+ );
123
+ }
124
+ }
125
+
126
+ #[allow(dead_code)]
127
+ impl StructureFixture {
128
+ pub fn write_structure_config(&self, overrides: serde_json::Value) {
129
+ let mut config = json!({
130
+ "schema": "naome.repository-structure.v1",
131
+ "version": 1,
132
+ "status": "ready",
133
+ "enabledAdapters": [],
134
+ "sourceRoots": ["src/**"],
135
+ "testRoots": ["tests/**", "test/**"],
136
+ "docsRoots": ["docs/**"],
137
+ "generatedRoots": ["**/generated/**"],
138
+ "artifactRoots": ["dist/**", "build/**"],
139
+ "moduleRoots": ["src/**"],
140
+ "allowedRootFiles": ["README.md", "LICENSE", "package.json", "Cargo.toml"],
141
+ "directoryRoleRules": [],
142
+ "layerRules": [],
143
+ "ignoredPaths": [],
144
+ "disabledChecks": [],
145
+ "changedCodePolicy": "block",
146
+ "debtPolicy": "report",
147
+ "limits": {
148
+ "maxDirectoryFiles": 40,
149
+ "maxPathDepth": 10,
150
+ "maxDirectoryRoles": 2,
151
+ "maxDumpingGroundFiles": 8
152
+ }
153
+ });
154
+ merge(&mut config, overrides);
155
+ self.write(
156
+ ".naome/repository-structure.json",
157
+ &format!("{}\n", serde_json::to_string_pretty(&config).unwrap()),
158
+ );
159
+ }
160
+
161
+ pub fn commit_all(&self, message: &str) {
162
+ git(&self.root, &["add", "."]);
163
+ git(&self.root, &["commit", "-m", message]);
164
+ }
165
+
166
+ pub fn add_index_only_path(&self, relative_path: &str, content: &str) {
167
+ let mut hash = Command::new("git")
168
+ .args(["hash-object", "-w", "--stdin"])
169
+ .current_dir(&self.root)
170
+ .stdin(std::process::Stdio::piped())
171
+ .stdout(std::process::Stdio::piped())
172
+ .spawn()
173
+ .unwrap();
174
+ {
175
+ use std::io::Write;
176
+ hash.stdin
177
+ .as_mut()
178
+ .unwrap()
179
+ .write_all(content.as_bytes())
180
+ .unwrap();
181
+ }
182
+ let output = hash.wait_with_output().unwrap();
183
+ assert!(output.status.success());
184
+ let oid = String::from_utf8_lossy(&output.stdout).trim().to_string();
185
+ git(
186
+ &self.root,
187
+ &[
188
+ "update-index",
189
+ "--add",
190
+ "--cacheinfo",
191
+ "100644",
192
+ &oid,
193
+ relative_path,
194
+ ],
195
+ );
196
+ }
197
+ }
198
+
199
+ impl Drop for StructureFixture {
200
+ fn drop(&mut self) {
201
+ let _ = fs::remove_dir_all(&self.root);
202
+ }
203
+ }
204
+
205
+ pub fn assert_has(report: &QualityReport, path: &str, check_id: &str) {
206
+ assert!(
207
+ report
208
+ .violations
209
+ .iter()
210
+ .any(|violation| violation.path == path && violation.check_id == check_id),
211
+ "missing {check_id} for {path}; violations: {:#?}",
212
+ report.violations
213
+ );
214
+ }
215
+
216
+ #[allow(dead_code)]
217
+ fn merge(target: &mut serde_json::Value, overrides: serde_json::Value) {
218
+ let Some(target_object) = target.as_object_mut() else {
219
+ return;
220
+ };
221
+ let Some(overrides_object) = overrides.as_object() else {
222
+ return;
223
+ };
224
+ for (key, value) in overrides_object {
225
+ if let (Some(target_value), Some(_)) = (target_object.get_mut(key), value.as_object()) {
226
+ merge(target_value, value.clone());
227
+ } else {
228
+ target_object.insert(key.clone(), value.clone());
229
+ }
230
+ }
231
+ }
232
+
233
+ fn git(root: &Path, args: &[&str]) {
234
+ let output = Command::new("git")
235
+ .args(args)
236
+ .current_dir(root)
237
+ .output()
238
+ .unwrap();
239
+ if output.status.success() {
240
+ return;
241
+ }
242
+ panic!(
243
+ "git failed in {}\ncommand: git {}\nstdout: {}\nstderr: {}",
244
+ root.display(),
245
+ args.join(" "),
246
+ String::from_utf8_lossy(&output.stdout),
247
+ String::from_utf8_lossy(&output.stderr)
248
+ );
249
+ }
@@ -0,0 +1,16 @@
1
+ #![allow(dead_code, unused_imports)]
2
+
3
+ mod repo;
4
+ mod repo_factories;
5
+ mod repo_helpers;
6
+ mod routes;
7
+ mod verification;
8
+ mod verification_values;
9
+
10
+ pub use repo::TestRepo;
11
+ pub use routes::{
12
+ assert_commit_paths, assert_isolated_worktree_ready, assert_user_diff_committed,
13
+ route_commit_request, route_new_task, route_readme_task, try_route_new_task,
14
+ try_route_readme_task,
15
+ };
16
+ pub use verification_values::{change_type, diff_check, verification_value};
@@ -0,0 +1,113 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::process::Command;
4
+ use std::time::{SystemTime, UNIX_EPOCH};
5
+
6
+ pub struct TestRepo {
7
+ root: PathBuf,
8
+ }
9
+
10
+ impl TestRepo {
11
+ pub fn new(name: &str) -> Self {
12
+ let nonce = SystemTime::now()
13
+ .duration_since(UNIX_EPOCH)
14
+ .unwrap()
15
+ .as_nanos();
16
+ let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
17
+ fs::create_dir_all(root.join(".naome")).unwrap();
18
+ Self { root }
19
+ }
20
+
21
+ pub fn path(&self) -> &Path {
22
+ &self.root
23
+ }
24
+
25
+ pub fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
26
+ let path = self.root.join(".naome").join(file_name);
27
+ fs::write(
28
+ path,
29
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
30
+ )
31
+ .unwrap();
32
+ }
33
+
34
+ pub fn write_naome_file(&self, file_name: &str, content: &str) {
35
+ fs::write(self.root.join(".naome").join(file_name), content).unwrap();
36
+ }
37
+
38
+ pub fn read_naome_json(&self, file_name: &str) -> serde_json::Value {
39
+ serde_json::from_str(&fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap())
40
+ .unwrap()
41
+ }
42
+
43
+ pub fn read_naome_file(&self, file_name: &str) -> String {
44
+ fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap()
45
+ }
46
+
47
+ pub fn write_testing_markdown(&self, content: &str) {
48
+ fs::create_dir_all(self.root.join("docs").join("naome")).unwrap();
49
+ fs::write(
50
+ self.root.join("docs").join("naome").join("testing.md"),
51
+ content,
52
+ )
53
+ .unwrap();
54
+ }
55
+
56
+ pub fn write_file(&self, relative_path: &str, content: &str) {
57
+ let path = self.root.join(relative_path);
58
+ if let Some(parent) = path.parent() {
59
+ fs::create_dir_all(parent).unwrap();
60
+ }
61
+ fs::write(path, content).unwrap();
62
+ }
63
+
64
+ pub fn init_git(&self) {
65
+ self.git(&["init"]);
66
+ self.git(&["config", "user.email", "naome@example.com"]);
67
+ self.git(&["config", "user.name", "NAOME Test"]);
68
+ }
69
+
70
+ pub fn git(&self, args: &[&str]) {
71
+ let output = Command::new("git")
72
+ .args(args)
73
+ .current_dir(&self.root)
74
+ .output()
75
+ .unwrap();
76
+ assert!(
77
+ output.status.success(),
78
+ "git {:?} failed\nstdout:\n{}\nstderr:\n{}",
79
+ args,
80
+ String::from_utf8_lossy(&output.stdout),
81
+ String::from_utf8_lossy(&output.stderr)
82
+ );
83
+ }
84
+
85
+ pub fn git_stdout(&self, args: &[&str]) -> String {
86
+ let output = Command::new("git")
87
+ .args(args)
88
+ .current_dir(&self.root)
89
+ .output()
90
+ .unwrap();
91
+ assert!(output.status.success());
92
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
93
+ }
94
+
95
+ pub fn git_status_short(&self) -> String {
96
+ self.git_stdout(&["status", "--short"])
97
+ }
98
+
99
+ pub fn git_status_short_at(root: &Path) -> String {
100
+ let output = Command::new("git")
101
+ .args(["status", "--short"])
102
+ .current_dir(root)
103
+ .output()
104
+ .unwrap();
105
+ assert!(output.status.success());
106
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
107
+ }
108
+
109
+ pub fn commit_all(&self, message: &str) {
110
+ self.git(&["add", "."]);
111
+ self.git(&["commit", "-m", message]);
112
+ }
113
+ }
@@ -0,0 +1,99 @@
1
+ use serde_json::{json, Value};
2
+
3
+ use super::repo::TestRepo;
4
+ use super::verification_values::completed_task_state;
5
+
6
+ impl TestRepo {
7
+ pub fn completed_task_with_diff(name: &str) -> Self {
8
+ let repo = Self::new(name);
9
+ repo.init_git();
10
+ repo.write_file("README.md", "# Baseline\n");
11
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
12
+ repo.git(&["add", "."]);
13
+ repo.git(&["commit", "-m", "baseline"]);
14
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
15
+ repo.write_base_naome_state(completed_task_state(&admission_head));
16
+ repo.git(&["add", ".naome/task-state.json"]);
17
+ repo.git(&["commit", "-m", "task state"]);
18
+ repo.write_file("README.md", "# Changed\n");
19
+ repo
20
+ }
21
+
22
+ pub fn completed_task_with_unrelated_user_edit(name: &str) -> Self {
23
+ let repo = Self::new(name);
24
+ repo.init_git();
25
+ repo.write_file("README.md", "# Baseline\n");
26
+ repo.write_file("USER.md", "user baseline\n");
27
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
28
+ repo.git(&["add", "."]);
29
+ repo.git(&["commit", "-m", "baseline"]);
30
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
31
+ repo.write_base_naome_state(completed_task_state(&admission_head));
32
+ repo.git(&["add", ".naome/task-state.json"]);
33
+ repo.git(&["commit", "-m", "task state"]);
34
+ repo.write_file("README.md", "# Changed\n");
35
+ repo.write_file("USER.md", "user local edit\n");
36
+ repo
37
+ }
38
+
39
+ pub fn completed_task_with_harness_refresh_diff(name: &str) -> Self {
40
+ let repo = Self::completed_task_with_diff(name);
41
+ repo.write_current_harness_refresh();
42
+ repo
43
+ }
44
+
45
+ pub fn completed_task_with_harness_refresh_only(name: &str) -> Self {
46
+ let repo = Self::new(name);
47
+ repo.init_git();
48
+ repo.write_file("README.md", "# Baseline\n");
49
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nOld harness.\n");
50
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
51
+ repo.write_harness_manifest("old");
52
+ repo.commit_all("baseline");
53
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
54
+ repo.write_base_naome_state(completed_task_state(&admission_head));
55
+ repo.git(&["add", ".naome/task-state.json"]);
56
+ repo.git(&["commit", "-m", "task state"]);
57
+ repo.write_current_harness_refresh();
58
+ repo
59
+ }
60
+
61
+ pub fn dirty_harness_refresh_repo(name: &str, with_user_edit: bool) -> Self {
62
+ let repo = Self::new(name);
63
+ repo.init_git();
64
+ repo.write_file("README.md", "# Baseline\n");
65
+ repo.write_file("USER.md", "user baseline\n");
66
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nOld harness.\n");
67
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
68
+ repo.write_harness_manifest("old");
69
+ repo.commit_all("baseline");
70
+ repo.write_current_harness_refresh();
71
+ if with_user_edit {
72
+ repo.write_file("USER.md", "user local edit\n");
73
+ }
74
+ repo
75
+ }
76
+
77
+ pub fn idle_readme(name: &str) -> Self {
78
+ let repo = Self::new(name);
79
+ repo.init_git();
80
+ repo.write_file("README.md", "# Baseline\n");
81
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
82
+ repo.commit_all("baseline");
83
+ repo
84
+ }
85
+
86
+ pub fn readme_quality_repo(
87
+ name: &str,
88
+ extra_checks: Vec<Value>,
89
+ required_checks: Vec<&str>,
90
+ ) -> Self {
91
+ let repo = Self::new(name);
92
+ repo.init_git();
93
+ repo.write_file("README.md", "# Baseline\n");
94
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
95
+ repo.write_readme_quality_verification(extra_checks, required_checks);
96
+ repo.commit_all("baseline");
97
+ repo
98
+ }
99
+ }
@@ -0,0 +1,123 @@
1
+ use std::fs;
2
+
3
+ use serde_json::json;
4
+
5
+ use super::repo::TestRepo;
6
+
7
+ impl TestRepo {
8
+ pub fn product_quality_repo(name: &str) -> Self {
9
+ let repo = Self::new(name);
10
+ repo.init_git();
11
+ repo.write_file(
12
+ "packages/naome/crates/naome-core/src/route.rs",
13
+ "pub fn baseline() {}\n",
14
+ );
15
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
16
+ repo.write_product_quality_verification();
17
+ repo.commit_all("baseline");
18
+ repo
19
+ }
20
+
21
+ pub fn repository_quality_failure_repo(name: &str) -> Self {
22
+ let repo = Self::new(name);
23
+ repo.init_git();
24
+ repo.write_file(".naome/repository-quality.json", quality_limits_fixture());
25
+ repo.write_file(
26
+ "src/large.js",
27
+ "export function legacy() {\n one();\n two();\n three();\n four();\n}\n",
28
+ );
29
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
30
+ repo.write_source_quality_verification();
31
+ repo.commit_all("baseline");
32
+ repo
33
+ }
34
+
35
+ pub fn readme_unsafe_diff_check_repo(name: &str) -> Self {
36
+ let repo = Self::new(name);
37
+ repo.init_git();
38
+ repo.write_file("README.md", "# Baseline\n");
39
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
40
+ repo.write_unsafe_readme_diff_verification();
41
+ repo.commit_all("baseline");
42
+ repo
43
+ }
44
+
45
+ pub fn dirty_user_edit_repo(name: &str) -> Self {
46
+ let repo = Self::new(name);
47
+ repo.init_git();
48
+ repo.write_file("README.md", "# Baseline\n");
49
+ repo.write_file("USER.md", "user baseline\n");
50
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
51
+ repo.commit_all("baseline");
52
+ repo.write_file("USER.md", "user local edit\n");
53
+ repo
54
+ }
55
+
56
+ pub fn create_stale_task_worktrees(&self, count: usize) {
57
+ let common_dir = self.git_stdout(&["rev-parse", "--git-common-dir"]);
58
+ let worktree_root = self.path().join(common_dir).join("naome").join("worktrees");
59
+ fs::create_dir_all(&worktree_root).unwrap();
60
+ for index in 0..count {
61
+ fs::create_dir_all(worktree_root.join(format!("stale-{index}"))).unwrap();
62
+ }
63
+ }
64
+
65
+ pub fn head_commit_paths(&self) -> String {
66
+ self.git_stdout(&["show", "--name-only", "--format=", "HEAD"])
67
+ }
68
+
69
+ pub fn write_implementing_task_state(&self, allowed_path: &str, check_id: &str) {
70
+ self.write_naome_json(
71
+ "task-state.json",
72
+ json!({
73
+ "status": "implementing",
74
+ "activeTask": {
75
+ "id": "rust-task",
76
+ "allowedPaths": [allowed_path],
77
+ "requiredCheckIds": [check_id],
78
+ "proofResults": []
79
+ }
80
+ }),
81
+ );
82
+ }
83
+
84
+ pub fn write_current_harness_refresh(&self) {
85
+ self.write_file("AGENTS.md", "# Agent Instructions\n\nUpdated by sync.\n");
86
+ self.write_harness_manifest("standard");
87
+ }
88
+
89
+ pub fn write_harness_manifest(&self, profile: &str) {
90
+ self.write_naome_json(
91
+ "manifest.json",
92
+ json!({
93
+ "name": "naome",
94
+ "harnessVersion": "1.1.0",
95
+ "profile": profile,
96
+ "machineOwned": ["AGENTS.md"],
97
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
98
+ "integrity": {}
99
+ }),
100
+ );
101
+ }
102
+ }
103
+
104
+ fn quality_limits_fixture() -> &'static str {
105
+ r#"{
106
+ "schema": "naome.repository-quality.v1",
107
+ "version": 1,
108
+ "status": "ready",
109
+ "limits": {
110
+ "maxFileLines": 5,
111
+ "maxNewFileLines": 5,
112
+ "maxDiffAddedLines": 200,
113
+ "maxFunctionLines": 40,
114
+ "maxTopLevelSymbols": 30,
115
+ "duplicateBlockLines": 4,
116
+ "nearDuplicateSimilarity": 0.9
117
+ },
118
+ "disabledChecks": [],
119
+ "ignoredPaths": [],
120
+ "generatedPaths": []
121
+ }
122
+ "#
123
+ }
@@ -0,0 +1,81 @@
1
+ use std::path::Path;
2
+
3
+ use naome_core::{evaluate_route, EvaluationOptions, NaomeError, RouteDecision, RouteOptions};
4
+
5
+ use super::TestRepo;
6
+
7
+ const NEW_README_TASK_PROMPT: &str = "Add another line to README as a new task.";
8
+
9
+ pub fn route_commit_request(repo: &TestRepo) -> RouteDecision {
10
+ evaluate_route(
11
+ repo.path(),
12
+ "commit my changes",
13
+ RouteOptions {
14
+ execute: true,
15
+ evaluation: EvaluationOptions::offline(),
16
+ },
17
+ )
18
+ .unwrap()
19
+ }
20
+
21
+ pub fn route_new_task(repo: &TestRepo, prompt: &str, execute: bool) -> RouteDecision {
22
+ try_route_new_task(repo, prompt, execute).unwrap()
23
+ }
24
+
25
+ pub fn try_route_new_task(
26
+ repo: &TestRepo,
27
+ prompt: &str,
28
+ execute: bool,
29
+ ) -> Result<RouteDecision, NaomeError> {
30
+ evaluate_route(
31
+ repo.path(),
32
+ prompt,
33
+ RouteOptions {
34
+ execute,
35
+ evaluation: EvaluationOptions::offline(),
36
+ },
37
+ )
38
+ }
39
+
40
+ pub fn route_readme_task(repo: &TestRepo, execute: bool) -> RouteDecision {
41
+ route_new_task(repo, NEW_README_TASK_PROMPT, execute)
42
+ }
43
+
44
+ pub fn try_route_readme_task(repo: &TestRepo, execute: bool) -> Result<RouteDecision, NaomeError> {
45
+ try_route_new_task(repo, NEW_README_TASK_PROMPT, execute)
46
+ }
47
+
48
+ pub fn assert_isolated_worktree_ready(route: &RouteDecision, repo: &TestRepo, status: &str) {
49
+ assert!(route.can_create_task);
50
+ assert_eq!(route.next_decision.state, "ready_for_task");
51
+ assert!(route.worktree.is_some());
52
+ assert_eq!(repo.git_status_short(), status);
53
+ assert_eq!(
54
+ TestRepo::git_status_short_at(Path::new(&route.task_root)),
55
+ ""
56
+ );
57
+ }
58
+
59
+ pub fn assert_commit_paths(repo: &TestRepo, present: &[&str], absent: &[&str]) {
60
+ let committed_paths = repo.head_commit_paths();
61
+ for path in present {
62
+ assert!(committed_paths.contains(path));
63
+ }
64
+ for path in absent {
65
+ assert!(!committed_paths.contains(path));
66
+ }
67
+ }
68
+
69
+ pub fn assert_user_diff_committed(route: &RouteDecision, repo: &TestRepo) {
70
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
71
+ assert!(route.allowed);
72
+ assert!(route.mutation_performed);
73
+ assert_eq!(
74
+ route.executed_actions,
75
+ vec![
76
+ "run_user_diff_quality_gate".to_string(),
77
+ "commit_user_diff".to_string()
78
+ ]
79
+ );
80
+ assert_eq!(repo.git_status_short(), "");
81
+ }