@lamentis/naome 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/Cargo.lock +2 -2
  2. package/Cargo.toml +1 -1
  3. package/LICENSE +180 -21
  4. package/README.md +49 -6
  5. package/bin/naome-node.js +2 -1579
  6. package/bin/naome.js +68 -16
  7. package/crates/naome-cli/Cargo.toml +1 -1
  8. package/crates/naome-cli/src/check_commands.rs +135 -0
  9. package/crates/naome-cli/src/cli_args.rs +5 -0
  10. package/crates/naome-cli/src/dispatcher.rs +37 -0
  11. package/crates/naome-cli/src/install_bridge.rs +83 -0
  12. package/crates/naome-cli/src/main.rs +60 -341
  13. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  14. package/crates/naome-cli/src/quality_commands.rs +229 -0
  15. package/crates/naome-cli/src/simple_commands.rs +53 -0
  16. package/crates/naome-cli/src/workflow_commands.rs +153 -0
  17. package/crates/naome-core/Cargo.toml +1 -1
  18. package/crates/naome-core/src/decision/checks.rs +64 -0
  19. package/crates/naome-core/src/decision/idle.rs +67 -0
  20. package/crates/naome-core/src/decision/json.rs +36 -0
  21. package/crates/naome-core/src/decision/states.rs +165 -0
  22. package/crates/naome-core/src/decision.rs +131 -353
  23. package/crates/naome-core/src/harness_health/integrity.rs +96 -0
  24. package/crates/naome-core/src/harness_health.rs +14 -126
  25. package/crates/naome-core/src/install_plan.rs +5 -0
  26. package/crates/naome-core/src/intent/classifier.rs +171 -0
  27. package/crates/naome-core/src/intent/envelope.rs +108 -0
  28. package/crates/naome-core/src/intent/legacy.rs +138 -0
  29. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  30. package/crates/naome-core/src/intent/model.rs +71 -0
  31. package/crates/naome-core/src/intent/patterns.rs +170 -0
  32. package/crates/naome-core/src/intent/resolver.rs +162 -0
  33. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  34. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  35. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  36. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  37. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  38. package/crates/naome-core/src/intent/risk.rs +40 -0
  39. package/crates/naome-core/src/intent/segment.rs +170 -0
  40. package/crates/naome-core/src/intent.rs +64 -879
  41. package/crates/naome-core/src/journal.rs +9 -20
  42. package/crates/naome-core/src/lib.rs +15 -0
  43. package/crates/naome-core/src/paths.rs +3 -1
  44. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  45. package/crates/naome-core/src/quality/adapters.rs +131 -0
  46. package/crates/naome-core/src/quality/baseline.rs +75 -0
  47. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  48. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  49. package/crates/naome-core/src/quality/checks.rs +228 -0
  50. package/crates/naome-core/src/quality/cleanup.rs +84 -0
  51. package/crates/naome-core/src/quality/config.rs +102 -0
  52. package/crates/naome-core/src/quality/config_support.rs +24 -0
  53. package/crates/naome-core/src/quality/mod.rs +108 -0
  54. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  55. package/crates/naome-core/src/quality/scanner.rs +379 -0
  56. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  57. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  58. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  59. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  60. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  61. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  62. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  63. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  64. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  65. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  66. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  67. package/crates/naome-core/src/quality/types.rs +292 -0
  68. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  69. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  70. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  71. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  72. package/crates/naome-core/src/route/context.rs +180 -0
  73. package/crates/naome-core/src/route/execution.rs +96 -0
  74. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  75. package/crates/naome-core/src/route/execution_support.rs +57 -0
  76. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  77. package/crates/naome-core/src/route/git_ops.rs +72 -0
  78. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  79. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  80. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  81. package/crates/naome-core/src/route/worktree.rs +75 -0
  82. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  83. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  84. package/crates/naome-core/src/route.rs +44 -1155
  85. package/crates/naome-core/src/task_state/admission.rs +63 -0
  86. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  87. package/crates/naome-core/src/task_state/api.rs +130 -0
  88. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  89. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  90. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  91. package/crates/naome-core/src/task_state/completion.rs +72 -0
  92. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  93. package/crates/naome-core/src/task_state/diff.rs +95 -0
  94. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  95. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  96. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  97. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  98. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  99. package/crates/naome-core/src/task_state/mod.rs +38 -0
  100. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  101. package/crates/naome-core/src/task_state/progress.rs +123 -0
  102. package/crates/naome-core/src/task_state/proof.rs +139 -0
  103. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  104. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  105. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  106. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  107. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  108. package/crates/naome-core/src/task_state/repair.rs +168 -0
  109. package/crates/naome-core/src/task_state/shape.rs +117 -0
  110. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  111. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  112. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  113. package/crates/naome-core/src/task_state/types.rs +87 -0
  114. package/crates/naome-core/src/task_state/util.rs +137 -0
  115. package/crates/naome-core/src/verification/render.rs +122 -0
  116. package/crates/naome-core/src/verification.rs +177 -58
  117. package/crates/naome-core/src/verification_contract.rs +49 -21
  118. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  119. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  120. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  121. package/crates/naome-core/src/workflow/mod.rs +18 -0
  122. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  123. package/crates/naome-core/src/workflow/output.rs +111 -0
  124. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  125. package/crates/naome-core/src/workflow/phases.rs +169 -0
  126. package/crates/naome-core/src/workflow/policy.rs +156 -0
  127. package/crates/naome-core/src/workflow/processes.rs +91 -0
  128. package/crates/naome-core/src/workflow/types.rs +42 -0
  129. package/crates/naome-core/tests/decision.rs +24 -118
  130. package/crates/naome-core/tests/harness_health.rs +5 -0
  131. package/crates/naome-core/tests/intent.rs +97 -792
  132. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  133. package/crates/naome-core/tests/intent_v2.rs +90 -0
  134. package/crates/naome-core/tests/quality.rs +319 -0
  135. package/crates/naome-core/tests/quality_structure.rs +116 -0
  136. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  137. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  138. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  139. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  140. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  141. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  142. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  143. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  144. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  145. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  146. package/crates/naome-core/tests/route.rs +1 -1476
  147. package/crates/naome-core/tests/route_baseline.rs +86 -0
  148. package/crates/naome-core/tests/route_completion.rs +141 -0
  149. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  150. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  151. package/crates/naome-core/tests/route_worktree.rs +54 -0
  152. package/crates/naome-core/tests/task_state.rs +60 -429
  153. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  154. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  155. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  156. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  157. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  158. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  159. package/crates/naome-core/tests/verification.rs +4 -45
  160. package/crates/naome-core/tests/verification_contract.rs +22 -78
  161. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  162. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  163. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  164. package/installer/agents.js +90 -0
  165. package/installer/context.js +67 -0
  166. package/installer/filesystem.js +166 -0
  167. package/installer/flows.js +84 -0
  168. package/installer/git-boundary.js +170 -0
  169. package/installer/git-hook-content.js +36 -0
  170. package/installer/git-hooks.js +134 -0
  171. package/installer/git-local.js +2 -0
  172. package/installer/git-shared.js +35 -0
  173. package/installer/harness-file-ops.js +140 -0
  174. package/installer/harness-files.js +56 -0
  175. package/installer/harness-verification.js +123 -0
  176. package/installer/install-plan.js +66 -0
  177. package/installer/main.js +25 -0
  178. package/installer/manifest-state.js +167 -0
  179. package/installer/native-build.js +24 -0
  180. package/installer/native-format.js +6 -0
  181. package/installer/native.js +162 -0
  182. package/installer/output.js +131 -0
  183. package/installer/version.js +32 -0
  184. package/native/darwin-arm64/naome +0 -0
  185. package/native/linux-x64/naome +0 -0
  186. package/package.json +3 -2
  187. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  188. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  189. package/templates/naome-root/.naome/bin/naome.js +51 -76
  190. package/templates/naome-root/.naome/manifest.json +22 -18
  191. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  192. package/templates/naome-root/.naome/repository-quality.json +24 -0
  193. package/templates/naome-root/.naome/repository-structure.json +90 -0
  194. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  195. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  196. package/templates/naome-root/.naome/verification.json +38 -0
  197. package/templates/naome-root/AGENTS.md +3 -0
  198. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  199. package/templates/naome-root/docs/naome/execution.md +25 -21
  200. package/templates/naome-root/docs/naome/index.md +5 -3
  201. package/templates/naome-root/docs/naome/repository-quality.md +46 -0
  202. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  203. package/templates/naome-root/docs/naome/testing.md +13 -0
  204. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -0,0 +1,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,125 @@
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 wildcard_module_roots_explain_monorepo_module_names() {
67
+ let repo = StructureFixture::new("structure-wildcard-module-root");
68
+ repo.write_structure_config(serde_json::json!({
69
+ "sourceRoots": ["packages/*/src/**"],
70
+ "moduleRoots": ["packages/*/src/**"]
71
+ }));
72
+ repo.write("packages/payments/src/lib.rs", "pub fn charge() {}\n");
73
+
74
+ let explanation =
75
+ explain_repository_structure(repo.path(), "packages/payments/src/lib.rs").unwrap();
76
+
77
+ assert_eq!(explanation.module.as_deref(), Some("payments"));
78
+ }
79
+
80
+ #[test]
81
+ fn disabled_structure_checks_are_not_reported() {
82
+ let repo = StructureFixture::new("structure-disabled-checks");
83
+ repo.write_structure_config(serde_json::json!({
84
+ "allowedRootFiles": [".naomeignore", "package.json", "README.md"],
85
+ "disabledChecks": ["test-source-pairing"]
86
+ }));
87
+ repo.write(
88
+ "src/payment-flow.js",
89
+ "export function paymentFlow() {\n return 1;\n}\n",
90
+ );
91
+
92
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
93
+
94
+ assert!(report.ok, "{:#?}", report.violations);
95
+ }
96
+
97
+ #[test]
98
+ fn directory_role_rules_can_allow_configured_role_mixes() {
99
+ let repo = StructureFixture::new("structure-directory-role-rule");
100
+ repo.write_structure_config(serde_json::json!({
101
+ "directoryRoleRules": [
102
+ {
103
+ "id": "generated-source-client",
104
+ "paths": ["src/generated/**"],
105
+ "allowedRoles": ["generated"],
106
+ "maxRoles": 1
107
+ }
108
+ ]
109
+ }));
110
+ repo.write(
111
+ "src/generated/client.ts",
112
+ "export const generated = true;\n",
113
+ );
114
+
115
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
116
+
117
+ assert!(
118
+ !report
119
+ .violations
120
+ .iter()
121
+ .any(|violation| violation.check_id == "directory-role-mixing"),
122
+ "{:#?}",
123
+ report.violations
124
+ );
125
+ }
@@ -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
+ }