@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,145 @@
1
+ use super::path_scan::{evidence, has_any_suffix, has_path, has_suffix};
2
+ use super::types::RepositoryWorldSignal;
3
+
4
+ pub(super) fn detect_adapter_signals(paths: &[String]) -> Vec<RepositoryWorldSignal> {
5
+ let mut signals = adapter_specs(paths)
6
+ .into_iter()
7
+ .filter(|(_, detected, evidence)| *detected && !evidence.is_empty())
8
+ .map(|(id, _, evidence)| RepositoryWorldSignal {
9
+ id: id.to_string(),
10
+ value: id.to_string(),
11
+ confidence: "high".to_string(),
12
+ source: "deterministic-scan".to_string(),
13
+ evidence,
14
+ })
15
+ .collect::<Vec<_>>();
16
+ signals.sort_by(|left, right| left.id.cmp(&right.id));
17
+ signals
18
+ }
19
+
20
+ fn adapter_specs(paths: &[String]) -> Vec<(&'static str, bool, Vec<String>)> {
21
+ vec![
22
+ (
23
+ "rust",
24
+ detects_rust(paths),
25
+ evidence(paths, &["Cargo.toml", ".rs"]),
26
+ ),
27
+ (
28
+ "javascript-typescript",
29
+ detects_javascript_typescript(paths),
30
+ evidence(paths, &["package.json", ".js", ".ts", ".tsx", ".jsx"]),
31
+ ),
32
+ (
33
+ "swift",
34
+ detects_swift(paths),
35
+ evidence(paths, &["Package.swift", ".swift"]),
36
+ ),
37
+ (
38
+ "xcode",
39
+ paths
40
+ .iter()
41
+ .any(|path| path.contains(".xcodeproj/") || path.contains(".xcworkspace/")),
42
+ evidence(paths, &[".xcodeproj/", ".xcworkspace/"]),
43
+ ),
44
+ (
45
+ "xctest",
46
+ detects_xctest(paths),
47
+ evidence(paths, &["Tests", ".swift"]),
48
+ ),
49
+ (
50
+ "swiftui",
51
+ detects_swiftui(paths),
52
+ evidence(paths, &["View.swift", "App.swift"]),
53
+ ),
54
+ (
55
+ "ios-app-structure",
56
+ detects_ios_app(paths),
57
+ evidence(paths, &["Info.plist", ".xcassets/"]),
58
+ ),
59
+ (
60
+ "swift-package",
61
+ detects_swift_package(paths),
62
+ evidence(paths, &["Package.swift", "Sources/"]),
63
+ ),
64
+ (
65
+ "ios-resources",
66
+ detects_ios_resources(paths),
67
+ evidence(paths, &[".strings", ".plist", ".xib"]),
68
+ ),
69
+ (
70
+ "generated-ios",
71
+ detects_generated_ios(paths),
72
+ evidence(paths, &["Generated/", ".generated.swift"]),
73
+ ),
74
+ ]
75
+ }
76
+
77
+ fn detects_rust(paths: &[String]) -> bool {
78
+ has_path(paths, "Cargo.toml") || has_suffix(paths, ".rs")
79
+ }
80
+
81
+ fn detects_javascript_typescript(paths: &[String]) -> bool {
82
+ has_path(paths, "package.json") || has_any_suffix(paths, &[".js", ".jsx", ".ts", ".tsx"])
83
+ }
84
+
85
+ fn detects_swift(paths: &[String]) -> bool {
86
+ has_path(paths, "Package.swift") || has_suffix(paths, ".swift")
87
+ }
88
+
89
+ fn detects_xctest(paths: &[String]) -> bool {
90
+ paths.iter().any(|path| {
91
+ let lower = path.to_ascii_lowercase();
92
+ lower.contains("/tests/") && lower.ends_with("tests.swift")
93
+ })
94
+ }
95
+
96
+ fn detects_swiftui(paths: &[String]) -> bool {
97
+ paths.iter().any(|path| {
98
+ let lower = path.to_ascii_lowercase();
99
+ lower.ends_with("view.swift")
100
+ || lower.ends_with("app.swift")
101
+ || lower.contains("preview content/")
102
+ })
103
+ }
104
+
105
+ fn detects_ios_app(paths: &[String]) -> bool {
106
+ paths.iter().any(|path| {
107
+ let lower = path.to_ascii_lowercase();
108
+ lower.ends_with("info.plist")
109
+ || lower.ends_with(".entitlements")
110
+ || lower.contains(".xcassets/")
111
+ })
112
+ }
113
+
114
+ fn detects_swift_package(paths: &[String]) -> bool {
115
+ has_path(paths, "Package.swift")
116
+ || paths
117
+ .iter()
118
+ .any(|path| path.starts_with("Sources/") || path.contains("/Sources/"))
119
+ }
120
+
121
+ fn detects_ios_resources(paths: &[String]) -> bool {
122
+ paths.iter().any(|path| {
123
+ has_any_suffix(
124
+ &[path.clone()],
125
+ &[
126
+ ".strings",
127
+ ".stringsdict",
128
+ ".plist",
129
+ ".entitlements",
130
+ ".storyboard",
131
+ ".xib",
132
+ ],
133
+ ) || path.to_ascii_lowercase().contains(".xcassets/")
134
+ })
135
+ }
136
+
137
+ fn detects_generated_ios(paths: &[String]) -> bool {
138
+ paths.iter().any(|path| {
139
+ let lower = path.to_ascii_lowercase();
140
+ lower.contains("/generated/")
141
+ || lower.contains("/swiftgen/")
142
+ || lower.contains("/sourcery/")
143
+ || lower.ends_with(".generated.swift")
144
+ })
145
+ }
@@ -0,0 +1,55 @@
1
+ use super::path_support::{
2
+ entity_for_path, is_package_manifest, language_for_path, module_for_path,
3
+ };
4
+ use super::types::{RepositoryEntity, RepositoryPathFact, RepositoryRoot};
5
+
6
+ pub(super) fn detect_path_facts(
7
+ paths: &[String],
8
+ roots: &[RepositoryRoot],
9
+ entities: &[RepositoryEntity],
10
+ ) -> Vec<RepositoryPathFact> {
11
+ let mut facts = paths
12
+ .iter()
13
+ .filter(|path| is_model_evidence_path(path))
14
+ .map(|path| RepositoryPathFact {
15
+ path: path.clone(),
16
+ role: role_for_path(path, roots).unwrap_or_else(|| "unknown".to_string()),
17
+ module: module_for_path(path, roots),
18
+ entity: entity_for_path(path, entities),
19
+ language: language_for_path(path),
20
+ flags: flags_for_path(path),
21
+ })
22
+ .collect::<Vec<_>>();
23
+ facts.sort_by(|left, right| left.path.cmp(&right.path));
24
+ facts.dedup_by(|left, right| left.path == right.path);
25
+ facts
26
+ }
27
+
28
+ fn is_model_evidence_path(path: &str) -> bool {
29
+ is_package_manifest(path)
30
+ || path.ends_with(".xcodeproj/project.pbxproj")
31
+ || path.ends_with("App.swift")
32
+ || path.ends_with("Info.plist")
33
+ || path.ends_with(".entitlements")
34
+ || path.ends_with(".xcassets/Contents.json")
35
+ }
36
+
37
+ fn role_for_path(path: &str, roots: &[RepositoryRoot]) -> Option<String> {
38
+ roots
39
+ .iter()
40
+ .filter(|root| path == root.path || path.starts_with(&format!("{}/", root.path)))
41
+ .max_by_key(|root| root.path.len())
42
+ .map(|root| root.role.clone())
43
+ }
44
+
45
+ fn flags_for_path(path: &str) -> Vec<String> {
46
+ let lower = path.to_ascii_lowercase();
47
+ let mut flags = Vec::new();
48
+ if lower.contains("/generated/") || lower.ends_with(".generated.swift") {
49
+ flags.push("generated".to_string());
50
+ }
51
+ if lower.contains("/build/") || lower.contains("/dist/") || lower.contains("/deriveddata/") {
52
+ flags.push("artifact".to_string());
53
+ }
54
+ flags
55
+ }
@@ -0,0 +1,168 @@
1
+ use super::path_support::{is_package_manifest, language_for_path, parent_path, root_at_segment};
2
+ use super::types::{RepositoryEntity, RepositoryRoot};
3
+
4
+ pub(super) fn detect_roots(paths: &[String]) -> Vec<RepositoryRoot> {
5
+ let mut roots = Vec::new();
6
+ for path in paths {
7
+ for (role, root) in root_candidates(path) {
8
+ push_root(&mut roots, role, &root);
9
+ }
10
+ }
11
+ for (role, defaults) in default_roots() {
12
+ for root in defaults {
13
+ if paths
14
+ .iter()
15
+ .any(|path| path == *root || path.starts_with(&format!("{root}/")))
16
+ {
17
+ push_root(&mut roots, role, root);
18
+ }
19
+ }
20
+ }
21
+ roots.sort_by(|left, right| left.id.cmp(&right.id));
22
+ roots.dedup_by(|left, right| left.id == right.id);
23
+ roots
24
+ }
25
+
26
+ pub(super) fn detect_entities(paths: &[String]) -> Vec<RepositoryEntity> {
27
+ let mut entities = Vec::new();
28
+ for path in paths {
29
+ if is_package_manifest(path) {
30
+ push_entity(&mut entities, "package", &parent_path(path), path, paths);
31
+ }
32
+ if path.ends_with(".xcodeproj/project.pbxproj") {
33
+ push_entity(
34
+ &mut entities,
35
+ "app",
36
+ path.trim_end_matches("/project.pbxproj"),
37
+ path,
38
+ paths,
39
+ );
40
+ }
41
+ if path.ends_with("App.swift") {
42
+ push_entity(&mut entities, "app", &parent_path(path), path, paths);
43
+ }
44
+ }
45
+ entities.sort_by(|left, right| left.id.cmp(&right.id));
46
+ entities.dedup_by(|left, right| left.id == right.id);
47
+ entities
48
+ }
49
+
50
+ fn root_candidates(path: &str) -> Vec<(&'static str, String)> {
51
+ [
52
+ (
53
+ "source",
54
+ root_at_segment(path, &["src", "Sources", "Source", "app"]),
55
+ ),
56
+ (
57
+ "test",
58
+ root_at_segment(path, &["test", "tests", "Tests", "__tests__"]),
59
+ ),
60
+ ("docs", root_at_segment(path, &["docs"])),
61
+ (
62
+ "generated",
63
+ root_at_segment(path, &["generated", "Generated"]),
64
+ ),
65
+ (
66
+ "artifact",
67
+ root_at_segment(path, &["dist", "build", "DerivedData"]),
68
+ ),
69
+ ]
70
+ .into_iter()
71
+ .filter_map(|(role, root)| root.map(|root| (role, root)))
72
+ .collect()
73
+ }
74
+
75
+ fn default_roots() -> [(&'static str, &'static [&'static str]); 5] {
76
+ [
77
+ ("source", &["src", "Sources", "app", "packages"]),
78
+ ("test", &["test", "tests", "Tests", "__tests__"]),
79
+ ("docs", &["docs"]),
80
+ ("generated", &["generated", "Generated"]),
81
+ ("artifact", &["dist", "build", "DerivedData"]),
82
+ ]
83
+ }
84
+
85
+ fn push_root(roots: &mut Vec<RepositoryRoot>, role: &str, path: &str) {
86
+ let normalized = path.trim_end_matches('/').to_string();
87
+ roots.push(RepositoryRoot {
88
+ id: format!("{role}:{normalized}"),
89
+ role: role.to_string(),
90
+ path: normalized.clone(),
91
+ source: "deterministic-scan".to_string(),
92
+ evidence: vec![normalized],
93
+ });
94
+ }
95
+
96
+ fn push_entity(
97
+ entities: &mut Vec<RepositoryEntity>,
98
+ kind: &str,
99
+ path: &str,
100
+ evidence_path: &str,
101
+ all_paths: &[String],
102
+ ) {
103
+ let normalized = if path.is_empty() { "." } else { path };
104
+ let entity_paths = all_paths
105
+ .iter()
106
+ .filter(|candidate| normalized == "." || candidate.starts_with(&format!("{normalized}/")))
107
+ .cloned()
108
+ .collect::<Vec<_>>();
109
+ entities.push(RepositoryEntity {
110
+ id: format!("{kind}:{normalized}"),
111
+ kind: kind.to_string(),
112
+ name: entity_name(normalized),
113
+ path: normalized.to_string(),
114
+ languages: entity_languages(&entity_paths),
115
+ roots: entity_roots(normalized, &entity_paths),
116
+ source: "deterministic-scan".to_string(),
117
+ evidence: vec![evidence_path.to_string()],
118
+ });
119
+ }
120
+
121
+ fn entity_name(path: &str) -> String {
122
+ path.rsplit('/')
123
+ .next()
124
+ .filter(|name| !name.is_empty())
125
+ .unwrap_or("root")
126
+ .to_string()
127
+ }
128
+
129
+ fn entity_languages(paths: &[String]) -> Vec<String> {
130
+ let mut languages = paths
131
+ .iter()
132
+ .filter_map(|path| language_for_path(path))
133
+ .collect::<Vec<_>>();
134
+ languages.sort();
135
+ languages.dedup();
136
+ languages
137
+ }
138
+
139
+ fn entity_roots(entity_path: &str, paths: &[String]) -> Vec<String> {
140
+ let mut roots = paths
141
+ .iter()
142
+ .filter_map(|path| entity_root_path(entity_path, path))
143
+ .collect::<Vec<_>>();
144
+ roots.sort();
145
+ roots.dedup();
146
+ roots
147
+ }
148
+
149
+ fn entity_root_path(entity_path: &str, path: &str) -> Option<String> {
150
+ let relative = if entity_path == "." {
151
+ path
152
+ } else {
153
+ path.strip_prefix(&format!("{entity_path}/"))?
154
+ };
155
+ let first = relative.split('/').next()?;
156
+ if matches!(
157
+ first,
158
+ "src" | "Sources" | "test" | "tests" | "Tests" | "Resources"
159
+ ) {
160
+ Some(if entity_path == "." {
161
+ first.to_string()
162
+ } else {
163
+ format!("{entity_path}/{first}")
164
+ })
165
+ } else {
166
+ None
167
+ }
168
+ }
@@ -0,0 +1,164 @@
1
+ mod detect;
2
+ mod explain;
3
+ mod path_scan;
4
+ mod path_support;
5
+ mod types;
6
+ mod world;
7
+ mod world_adapters;
8
+ mod world_path_facts;
9
+ mod world_paths;
10
+
11
+ use std::fs;
12
+ use std::path::Path;
13
+
14
+ use crate::models::NaomeError;
15
+
16
+ use detect::detect_repository_model;
17
+ use explain::explain_path;
18
+ pub use types::{
19
+ RepositoryEntity, RepositoryFact, RepositoryModel, RepositoryModelDrift,
20
+ RepositoryModelRefresh, RepositoryPathExplanation, RepositoryPathFact, RepositoryRoot,
21
+ RepositoryVerificationCheck, RepositoryWorldSignal,
22
+ };
23
+
24
+ const MODEL_PATH: &str = ".naome/repository-model.json";
25
+
26
+ pub fn refresh_repository_model(
27
+ root: &Path,
28
+ write: bool,
29
+ ) -> Result<RepositoryModelRefresh, NaomeError> {
30
+ let model = detect_repository_model(root)?;
31
+ let existing = read_existing_model(root)?;
32
+ let stale = existing.as_ref() != Some(&model);
33
+ let canonical_drift = write && !model_file_matches(root, &model)?;
34
+ let mut updated_paths = Vec::new();
35
+
36
+ if write && (stale || canonical_drift) {
37
+ write_model(root, &model)?;
38
+ updated_paths.push(MODEL_PATH.to_string());
39
+ }
40
+
41
+ let stale_after_write = stale && !write;
42
+ Ok(RepositoryModelRefresh {
43
+ schema: "naome.repository-model-refresh.v1".to_string(),
44
+ ok: !stale_after_write,
45
+ stale: stale_after_write,
46
+ write,
47
+ model_path: MODEL_PATH.to_string(),
48
+ updated_paths,
49
+ reason_codes: if stale {
50
+ vec!["repository_model_stale".to_string()]
51
+ } else {
52
+ Vec::new()
53
+ },
54
+ model,
55
+ })
56
+ }
57
+
58
+ pub fn explain_repository_model_path(
59
+ root: &Path,
60
+ path: impl AsRef<str>,
61
+ ) -> Result<RepositoryPathExplanation, NaomeError> {
62
+ let model = read_existing_model(root)?.unwrap_or(detect_repository_model(root)?);
63
+ Ok(explain_path(&model, path.as_ref()))
64
+ }
65
+
66
+ pub fn repository_model_drift(root: &Path) -> Result<RepositoryModelDrift, NaomeError> {
67
+ let path = root.join(MODEL_PATH);
68
+ let model_present = path.is_file();
69
+ if !model_present {
70
+ return Ok(RepositoryModelDrift {
71
+ schema: "naome.repository-model-drift.v1".to_string(),
72
+ ok: true,
73
+ model_present,
74
+ stale: false,
75
+ model_path: MODEL_PATH.to_string(),
76
+ reason_codes: Vec::new(),
77
+ related_paths: Vec::new(),
78
+ messages: Vec::new(),
79
+ });
80
+ }
81
+
82
+ let existing = read_existing_model(root)?.unwrap_or_default();
83
+ let current = detect_repository_model(root)?;
84
+ let stale = existing != current;
85
+ let related_paths = if stale {
86
+ model_evidence_paths(&current)
87
+ } else {
88
+ Vec::new()
89
+ };
90
+ let messages = if stale {
91
+ vec![format!(
92
+ "NAOME repository model is stale; run naome repo model --write to refresh deterministic repository facts in {MODEL_PATH}."
93
+ )]
94
+ } else {
95
+ Vec::new()
96
+ };
97
+
98
+ Ok(RepositoryModelDrift {
99
+ schema: "naome.repository-model-drift.v1".to_string(),
100
+ ok: !stale,
101
+ model_present,
102
+ stale,
103
+ model_path: MODEL_PATH.to_string(),
104
+ reason_codes: if stale {
105
+ vec!["repository_model_stale".to_string()]
106
+ } else {
107
+ Vec::new()
108
+ },
109
+ related_paths,
110
+ messages,
111
+ })
112
+ }
113
+
114
+ fn read_existing_model(root: &Path) -> Result<Option<RepositoryModel>, NaomeError> {
115
+ let path = root.join(MODEL_PATH);
116
+ if !path.is_file() {
117
+ return Ok(None);
118
+ }
119
+ Ok(Some(serde_json::from_str(&fs::read_to_string(path)?)?))
120
+ }
121
+
122
+ fn model_evidence_paths(model: &RepositoryModel) -> Vec<String> {
123
+ let mut paths = model
124
+ .facts
125
+ .iter()
126
+ .flat_map(|fact| fact.evidence.iter())
127
+ .chain(model.roots.iter().flat_map(|root| root.evidence.iter()))
128
+ .chain(model.entities.iter().flat_map(|entity| entity.evidence.iter()))
129
+ .chain(model.path_facts.iter().map(|fact| &fact.path))
130
+ .chain(
131
+ model
132
+ .verification_checks
133
+ .iter()
134
+ .flat_map(|check| check.evidence.iter()),
135
+ )
136
+ .filter(|path| path.as_str() != ".naome/verification.json")
137
+ .cloned()
138
+ .collect::<std::collections::BTreeSet<_>>()
139
+ .into_iter()
140
+ .collect::<Vec<_>>();
141
+ paths.sort();
142
+ paths
143
+ }
144
+
145
+ fn write_model(root: &Path, model: &RepositoryModel) -> Result<(), NaomeError> {
146
+ let path = root.join(MODEL_PATH);
147
+ if let Some(parent) = path.parent() {
148
+ fs::create_dir_all(parent)?;
149
+ }
150
+ fs::write(path, model_content(model)?)?;
151
+ Ok(())
152
+ }
153
+
154
+ fn model_file_matches(root: &Path, model: &RepositoryModel) -> Result<bool, NaomeError> {
155
+ let path = root.join(MODEL_PATH);
156
+ if !path.is_file() {
157
+ return Ok(false);
158
+ }
159
+ Ok(fs::read_to_string(path)? == model_content(model)?)
160
+ }
161
+
162
+ fn model_content(model: &RepositoryModel) -> Result<String, NaomeError> {
163
+ Ok(format!("{}\n", serde_json::to_string(model)?))
164
+ }
@@ -2,7 +2,7 @@ use std::path::Path;
2
2
 
3
3
  use crate::harness_health::{validate_harness_health, HarnessHealthOptions};
4
4
  use crate::models::NaomeError;
5
- use crate::quality::{check_repository_quality, QualityMode};
5
+ use crate::quality::{check_repository_quality, check_semantic_legacy, QualityMode};
6
6
  use crate::route::git_ops::{command_output, git_output};
7
7
  use crate::route::quality_gate::QualityCheck;
8
8
  use crate::task_state::{validate_task_state, TaskStateMode, TaskStateOptions};
@@ -18,16 +18,6 @@ pub(super) fn run_quality_check(
18
18
  check: &QualityCheck,
19
19
  ) -> Result<(), NaomeError> {
20
20
  match check_id {
21
- "installer-tests" => require_builtin_quality_check(
22
- check_id,
23
- check,
24
- "npm run test:naome-installer",
25
- ),
26
- "rust-build" => require_builtin_quality_check(check_id, check, "npm run build:rust"),
27
- "decision-engine-tests" => {
28
- require_builtin_quality_check(check_id, check, "npm run test:decision-engine")
29
- }
30
- "package-dry-run" => require_builtin_quality_check(check_id, check, "npm run pack:dry-run"),
31
21
  "diff-check" => {
32
22
  require_builtin_quality_check(check_id, check, "git diff --check")?;
33
23
  let output = git_output(root, &["diff", "--check"])?;
@@ -46,10 +36,6 @@ pub(super) fn run_quality_check(
46
36
  )?;
47
37
  run_harness_health_check(root)
48
38
  }
49
- "dogfood-health" => {
50
- require_builtin_quality_check(check_id, check, "npm run dogfood:health")?;
51
- run_harness_health_check(root)
52
- }
53
39
  "task-state-check" => {
54
40
  require_builtin_quality_check(check_id, check, "npm run check:task-state")?;
55
41
  run_template_task_state_check(root)
@@ -78,14 +64,53 @@ pub(super) fn run_quality_check(
78
64
  )?;
79
65
  run_repository_quality_check(root)
80
66
  }
67
+ "repository-semantic-check" => {
68
+ require_builtin_quality_check_any(
69
+ check_id,
70
+ check,
71
+ &[
72
+ "naome semantic check --changed",
73
+ "node .naome/bin/naome.js semantic check --changed",
74
+ "npm run check:repository-semantic",
75
+ ],
76
+ )?;
77
+ run_repository_semantic_check(root)
78
+ }
81
79
  _ => Err(NaomeError::new(format!(
82
80
  "Quality check {check_id} is not a built-in safe check; NAOME will not execute repository-controlled verification commands."
83
81
  ))),
84
82
  }
85
83
  }
86
84
 
85
+ fn run_repository_semantic_check(root: &Path) -> Result<(), NaomeError> {
86
+ let report = check_semantic_legacy(root, QualityMode::ChangedFast)?;
87
+ if report.ok {
88
+ return Ok(());
89
+ }
90
+
91
+ let details = report
92
+ .findings
93
+ .iter()
94
+ .take(20)
95
+ .flat_map(|finding| {
96
+ finding.occurrences.iter().take(1).map(move |occurrence| {
97
+ format!(
98
+ "{}:{} semantic-{}: {}",
99
+ occurrence.path, occurrence.start_line, finding.kind, finding.summary
100
+ )
101
+ })
102
+ })
103
+ .collect::<Vec<_>>()
104
+ .join("\n");
105
+ Err(NaomeError::new(format!(
106
+ "repository-semantic-check failed with {} finding(s).\n{}",
107
+ report.findings.len(),
108
+ details
109
+ )))
110
+ }
111
+
87
112
  fn run_repository_quality_check(root: &Path) -> Result<(), NaomeError> {
88
- let report = check_repository_quality(root, QualityMode::Changed)?;
113
+ let report = check_repository_quality(root, QualityMode::ChangedFast)?;
89
114
  if report.ok {
90
115
  return Ok(());
91
116
  }