@lamentis/naome 1.3.0 → 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 (137) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +11 -2
  3. package/bin/naome.js +62 -24
  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 +6 -0
  7. package/crates/naome-cli/src/main.rs +43 -6
  8. package/crates/naome-cli/src/quality_commands.rs +31 -46
  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 +100 -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/install_plan.rs +18 -0
  22. package/crates/naome-core/src/journal.rs +2 -7
  23. package/crates/naome-core/src/lib.rs +33 -10
  24. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  25. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  26. package/crates/naome-core/src/quality/adapters.rs +81 -18
  27. package/crates/naome-core/src/quality/cache.rs +7 -9
  28. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +4 -7
  29. package/crates/naome-core/src/quality/config.rs +21 -3
  30. package/crates/naome-core/src/quality/mod.rs +138 -7
  31. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  32. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  33. package/crates/naome-core/src/quality/scanner/analysis.rs +20 -5
  34. package/crates/naome-core/src/quality/scanner.rs +62 -17
  35. package/crates/naome-core/src/quality/semantic/checks.rs +17 -0
  36. package/crates/naome-core/src/quality/semantic/route.rs +1 -1
  37. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  38. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  39. package/crates/naome-core/src/quality/structure/checks/directory.rs +6 -4
  40. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  41. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  42. package/crates/naome-core/src/quality/structure/mod.rs +3 -0
  43. package/crates/naome-core/src/quality/types.rs +20 -1
  44. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  45. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  46. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  47. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  48. package/crates/naome-core/src/repository_model/types.rs +152 -0
  49. package/crates/naome-core/src/repository_model/world.rs +48 -0
  50. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  51. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  52. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  53. package/crates/naome-core/src/repository_model.rs +164 -0
  54. package/crates/naome-core/src/route/builtin_checks.rs +40 -1
  55. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  56. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  57. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  58. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  59. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  60. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  61. package/crates/naome-core/src/task_ledger.rs +48 -0
  62. package/crates/naome-core/src/task_state/api.rs +4 -2
  63. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  64. package/crates/naome-core/src/task_state/diff.rs +2 -2
  65. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  66. package/crates/naome-core/src/task_state/mod.rs +1 -1
  67. package/crates/naome-core/src/task_state/progress.rs +13 -0
  68. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  69. package/crates/naome-core/src/task_state/repair.rs +2 -2
  70. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  71. package/crates/naome-core/src/task_state/types.rs +24 -0
  72. package/crates/naome-core/src/verification.rs +29 -18
  73. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  74. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  75. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  76. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  77. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  78. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  79. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  80. package/crates/naome-core/src/workflow/agent.rs +34 -0
  81. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  82. package/crates/naome-core/src/workflow/doctor.rs +39 -0
  83. package/crates/naome-core/src/workflow/mod.rs +11 -0
  84. package/crates/naome-core/src/workflow/output.rs +8 -2
  85. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  86. package/crates/naome-core/tests/context.rs +99 -0
  87. package/crates/naome-core/tests/harness_health.rs +4 -0
  88. package/crates/naome-core/tests/install_plan.rs +12 -0
  89. package/crates/naome-core/tests/quality.rs +178 -2
  90. package/crates/naome-core/tests/quality_performance.rs +39 -2
  91. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  92. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  93. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  94. package/crates/naome-core/tests/repository_model.rs +281 -0
  95. package/crates/naome-core/tests/route_user_diff.rs +49 -1
  96. package/crates/naome-core/tests/semantic_legacy.rs +72 -38
  97. package/crates/naome-core/tests/task_ledger.rs +328 -0
  98. package/crates/naome-core/tests/task_state.rs +28 -0
  99. package/crates/naome-core/tests/verification.rs +29 -36
  100. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  101. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  102. package/crates/naome-core/tests/workflow_doctor.rs +21 -0
  103. package/installer/codex-hooks.js +121 -0
  104. package/installer/context.js +10 -0
  105. package/installer/filesystem.js +4 -0
  106. package/installer/flows.js +8 -4
  107. package/installer/harness-files.js +6 -0
  108. package/installer/install-plan.js +4 -0
  109. package/installer/main.js +1 -1
  110. package/installer/native.js +1 -1
  111. package/native/darwin-arm64/naome +0 -0
  112. package/native/linux-x64/naome +0 -0
  113. package/package.json +1 -1
  114. package/templates/naome-root/.codex/config.toml +2 -0
  115. package/templates/naome-root/.codex/hooks.json +70 -0
  116. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  117. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  118. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  119. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  120. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  121. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  122. package/templates/naome-root/.naome/bin/naome.js +35 -4
  123. package/templates/naome-root/.naome/manifest.json +12 -6
  124. package/templates/naome-root/.naome/repository-model.json +6 -0
  125. package/templates/naome-root/.naome/repository-quality.json +3 -1
  126. package/templates/naome-root/.naome/verification.json +15 -1
  127. package/templates/naome-root/AGENTS.md +38 -83
  128. package/templates/naome-root/docs/naome/agent-workflow.md +54 -18
  129. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  130. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  131. package/templates/naome-root/docs/naome/first-run.md +25 -14
  132. package/templates/naome-root/docs/naome/index.md +18 -10
  133. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  134. package/templates/naome-root/docs/naome/repository-quality.md +47 -7
  135. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  136. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  137. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -0,0 +1,188 @@
1
+ use std::path::Path;
2
+
3
+ use crate::models::NaomeError;
4
+
5
+ use super::path_scan::{collect_repo_paths, evidence, has_any_suffix, has_path, has_suffix};
6
+ use super::types::{RepositoryFact, RepositoryModel, RepositoryVerificationCheck};
7
+ use super::world::build_world_sections;
8
+
9
+ pub(super) fn detect_repository_model(root: &Path) -> Result<RepositoryModel, NaomeError> {
10
+ let paths = collect_repo_paths(root)?;
11
+ let mut facts = Vec::new();
12
+ detect_stack_facts(&paths, &mut facts);
13
+ detect_root_facts(&paths, &mut facts);
14
+ let verification_checks = detect_verification_facts(root, &mut facts)?;
15
+ facts.sort_by(|left, right| left.id.cmp(&right.id));
16
+ facts.dedup_by(|left, right| left.id == right.id);
17
+ let world = build_world_sections(&paths, &facts);
18
+ Ok(RepositoryModel {
19
+ schema: "naome.repository-model.v2".to_string(),
20
+ version: 2,
21
+ facts,
22
+ languages: world.languages,
23
+ package_managers: world.package_managers,
24
+ build_systems: world.build_systems,
25
+ adapters: world.adapters,
26
+ roots: world.roots,
27
+ entities: world.entities,
28
+ path_facts: world.path_facts,
29
+ verification_checks,
30
+ ..RepositoryModel::default()
31
+ })
32
+ }
33
+
34
+ fn detect_stack_facts(paths: &[String], facts: &mut Vec<RepositoryFact>) {
35
+ if has_path(paths, "Cargo.toml") || has_suffix(paths, ".rs") {
36
+ push_fact(
37
+ facts,
38
+ "language",
39
+ "rust",
40
+ evidence(paths, &["Cargo.toml", ".rs"]),
41
+ );
42
+ push_fact(
43
+ facts,
44
+ "packageManager",
45
+ "cargo",
46
+ evidence(paths, &["Cargo.toml"]),
47
+ );
48
+ push_fact(
49
+ facts,
50
+ "buildSystem",
51
+ "cargo",
52
+ evidence(paths, &["Cargo.toml"]),
53
+ );
54
+ }
55
+ if has_path(paths, "package.json") || has_any_suffix(paths, &[".js", ".ts", ".tsx", ".jsx"]) {
56
+ push_fact(
57
+ facts,
58
+ "language",
59
+ "javascript-typescript",
60
+ evidence(paths, &["package.json", ".js", ".ts", ".tsx", ".jsx"]),
61
+ );
62
+ push_fact(
63
+ facts,
64
+ "packageManager",
65
+ "npm",
66
+ evidence(paths, &["package.json"]),
67
+ );
68
+ }
69
+ if has_path(paths, "Package.swift") || has_suffix(paths, ".swift") {
70
+ push_fact(
71
+ facts,
72
+ "language",
73
+ "swift",
74
+ evidence(paths, &["Package.swift", ".swift"]),
75
+ );
76
+ push_fact(
77
+ facts,
78
+ "packageManager",
79
+ "swift-package-manager",
80
+ evidence(paths, &["Package.swift"]),
81
+ );
82
+ }
83
+ if paths
84
+ .iter()
85
+ .any(|path| path.contains(".xcodeproj/") || path.contains(".xcworkspace/"))
86
+ {
87
+ push_fact(
88
+ facts,
89
+ "buildSystem",
90
+ "xcode",
91
+ evidence(paths, &[".xcodeproj/", ".xcworkspace/"]),
92
+ );
93
+ push_fact(
94
+ facts,
95
+ "appType",
96
+ "apple-platform",
97
+ evidence(paths, &[".xcodeproj/", ".xcworkspace/"]),
98
+ );
99
+ }
100
+ }
101
+
102
+ fn detect_root_facts(paths: &[String], facts: &mut Vec<RepositoryFact>) {
103
+ const SOURCE_ROOTS: &[&str] = &["src", "Sources", "app", "packages"];
104
+ const TEST_ROOTS: &[&str] = &["test", "tests", "Tests", "__tests__"];
105
+ const DOCS_ROOTS: &[&str] = &["docs"];
106
+ const GENERATED_ROOTS: &[&str] = &["generated", "Generated"];
107
+ const ARTIFACT_ROOTS: &[&str] = &["dist", "build", "DerivedData"];
108
+
109
+ for (category, roots) in [
110
+ ("sourceRoot", SOURCE_ROOTS),
111
+ ("testRoot", TEST_ROOTS),
112
+ ("docsRoot", DOCS_ROOTS),
113
+ ("generatedRoot", GENERATED_ROOTS),
114
+ ("artifactRoot", ARTIFACT_ROOTS),
115
+ ] {
116
+ for root in roots {
117
+ if paths
118
+ .iter()
119
+ .any(|path| path == *root || path.starts_with(&format!("{root}/")))
120
+ {
121
+ push_fact(facts, category, root, vec![root.to_string()]);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ fn detect_verification_facts(
128
+ root: &Path,
129
+ facts: &mut Vec<RepositoryFact>,
130
+ ) -> Result<Vec<RepositoryVerificationCheck>, NaomeError> {
131
+ let path = root.join(".naome/verification.json");
132
+ if !path.is_file() {
133
+ return Ok(Vec::new());
134
+ }
135
+ let value: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;
136
+ let Some(checks) = value.get("checks").and_then(serde_json::Value::as_array) else {
137
+ return Ok(Vec::new());
138
+ };
139
+ let mut verification_checks = Vec::new();
140
+ for check in checks {
141
+ if let Some(id) = check.get("id").and_then(serde_json::Value::as_str) {
142
+ push_fact(
143
+ facts,
144
+ "verificationCheck",
145
+ id,
146
+ vec![".naome/verification.json".to_string()],
147
+ );
148
+ verification_checks.push(RepositoryVerificationCheck {
149
+ id: id.to_string(),
150
+ command: check
151
+ .get("command")
152
+ .and_then(serde_json::Value::as_str)
153
+ .unwrap_or("")
154
+ .to_string(),
155
+ cwd: check
156
+ .get("cwd")
157
+ .and_then(serde_json::Value::as_str)
158
+ .unwrap_or(".")
159
+ .to_string(),
160
+ evidence: check
161
+ .get("evidence")
162
+ .and_then(serde_json::Value::as_array)
163
+ .into_iter()
164
+ .flatten()
165
+ .filter_map(serde_json::Value::as_str)
166
+ .map(str::to_string)
167
+ .collect(),
168
+ });
169
+ }
170
+ }
171
+ verification_checks.sort_by(|left, right| left.id.cmp(&right.id));
172
+ verification_checks.dedup_by(|left, right| left.id == right.id);
173
+ Ok(verification_checks)
174
+ }
175
+
176
+ fn push_fact(facts: &mut Vec<RepositoryFact>, category: &str, value: &str, evidence: Vec<String>) {
177
+ if evidence.is_empty() {
178
+ return;
179
+ }
180
+ facts.push(RepositoryFact {
181
+ id: format!("{category}:{value}"),
182
+ category: category.to_string(),
183
+ value: value.to_string(),
184
+ confidence: "high".to_string(),
185
+ source: "deterministic-scan".to_string(),
186
+ evidence,
187
+ });
188
+ }
@@ -0,0 +1,121 @@
1
+ use super::types::{RepositoryFact, RepositoryModel, RepositoryPathExplanation};
2
+
3
+ pub(super) fn explain_path(model: &RepositoryModel, path: &str) -> RepositoryPathExplanation {
4
+ let normalized = path.replace('\\', "/");
5
+ let mut facts = facts_for_path(model, &normalized);
6
+ facts.sort_by(|left, right| left.id.cmp(&right.id));
7
+ let mut evidence = facts
8
+ .iter()
9
+ .flat_map(|fact| fact.evidence.clone())
10
+ .collect::<std::collections::BTreeSet<_>>()
11
+ .into_iter()
12
+ .collect::<Vec<_>>();
13
+ if !evidence.contains(&normalized) {
14
+ evidence.push(normalized.clone());
15
+ }
16
+ evidence.sort();
17
+
18
+ RepositoryPathExplanation {
19
+ schema: "naome.repository-model-explanation.v1".to_string(),
20
+ role: path_fact_for_path(model, &normalized)
21
+ .map(|fact| fact.role.clone())
22
+ .or_else(|| role_for_path(&normalized)),
23
+ language: path_fact_for_path(model, &normalized)
24
+ .and_then(|fact| fact.language.clone())
25
+ .or_else(|| language_for_path(&normalized)),
26
+ module: path_fact_for_path(model, &normalized)
27
+ .and_then(|fact| fact.module.clone())
28
+ .or_else(|| module_for_path(model, &normalized)),
29
+ entity: path_fact_for_path(model, &normalized)
30
+ .and_then(|fact| fact.entity.clone())
31
+ .or_else(|| entity_for_path(model, &normalized)),
32
+ path: normalized,
33
+ facts,
34
+ evidence,
35
+ }
36
+ }
37
+
38
+ fn path_fact_for_path<'a>(
39
+ model: &'a RepositoryModel,
40
+ path: &str,
41
+ ) -> Option<&'a super::types::RepositoryPathFact> {
42
+ model.path_facts.iter().find(|fact| fact.path == path)
43
+ }
44
+
45
+ fn facts_for_path(model: &RepositoryModel, path: &str) -> Vec<RepositoryFact> {
46
+ model
47
+ .facts
48
+ .iter()
49
+ .filter(|fact| {
50
+ fact.evidence
51
+ .iter()
52
+ .any(|evidence| path_matches_fact(path, evidence))
53
+ })
54
+ .cloned()
55
+ .collect()
56
+ }
57
+
58
+ fn path_matches_fact(path: &str, evidence: &str) -> bool {
59
+ path == evidence || path.starts_with(&format!("{evidence}/")) || evidence.starts_with(path)
60
+ }
61
+
62
+ fn role_for_path(path: &str) -> Option<String> {
63
+ let lower = path.to_ascii_lowercase();
64
+ if lower.contains("/tests/") || lower.starts_with("tests/") || lower.ends_with("test.ts") {
65
+ Some("test".to_string())
66
+ } else if lower.starts_with("docs/") || lower.ends_with(".md") {
67
+ Some("docs".to_string())
68
+ } else if lower.contains("/generated/") || lower.starts_with("generated/") {
69
+ Some("generated".to_string())
70
+ } else if lower.starts_with("src/") || lower.starts_with("sources/") || source_suffix(&lower) {
71
+ Some("source".to_string())
72
+ } else {
73
+ None
74
+ }
75
+ }
76
+
77
+ fn language_for_path(path: &str) -> Option<String> {
78
+ match path.rsplit('.').next() {
79
+ Some("rs") => Some("rust".to_string()),
80
+ Some("js" | "jsx" | "ts" | "tsx") => Some("javascript-typescript".to_string()),
81
+ Some("swift") => Some("swift".to_string()),
82
+ _ => None,
83
+ }
84
+ }
85
+
86
+ fn source_suffix(path: &str) -> bool {
87
+ path.ends_with(".rs")
88
+ || path.ends_with(".js")
89
+ || path.ends_with(".jsx")
90
+ || path.ends_with(".ts")
91
+ || path.ends_with(".tsx")
92
+ || path.ends_with(".swift")
93
+ }
94
+
95
+ fn module_for_path(model: &RepositoryModel, path: &str) -> Option<String> {
96
+ parent_before(path, "/src/")
97
+ .or_else(|| parent_before(path, "/Sources/"))
98
+ .or_else(|| {
99
+ model
100
+ .roots
101
+ .iter()
102
+ .filter(|root| path.starts_with(&format!("{}/", root.path)))
103
+ .max_by_key(|root| root.path.len())
104
+ .map(|root| root.path.clone())
105
+ })
106
+ }
107
+
108
+ fn entity_for_path(model: &RepositoryModel, path: &str) -> Option<String> {
109
+ model
110
+ .entities
111
+ .iter()
112
+ .filter(|entity| entity.path == "." || path.starts_with(&format!("{}/", entity.path)))
113
+ .max_by_key(|entity| entity.path.len())
114
+ .map(|entity| entity.id.clone())
115
+ }
116
+
117
+ fn parent_before(path: &str, marker: &str) -> Option<String> {
118
+ path.split_once(marker)
119
+ .map(|(parent, _)| parent.to_string())
120
+ .filter(|parent| !parent.is_empty())
121
+ }
@@ -0,0 +1,67 @@
1
+ use std::collections::BTreeSet;
2
+ use std::path::Path;
3
+ use std::process::Command;
4
+
5
+ use crate::models::NaomeError;
6
+
7
+ pub(super) fn collect_repo_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
8
+ let output = Command::new("git")
9
+ .args([
10
+ "ls-files",
11
+ "-z",
12
+ "--cached",
13
+ "--others",
14
+ "--exclude-standard",
15
+ ])
16
+ .current_dir(root)
17
+ .output()?;
18
+ if !output.status.success() {
19
+ return Ok(Vec::new());
20
+ }
21
+ let mut paths = output
22
+ .stdout
23
+ .split(|byte| *byte == 0)
24
+ .filter(|entry| !entry.is_empty())
25
+ .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
26
+ .collect::<Vec<_>>();
27
+ paths.sort();
28
+ paths.dedup();
29
+ Ok(paths)
30
+ }
31
+
32
+ pub(super) fn evidence(paths: &[String], markers: &[&str]) -> Vec<String> {
33
+ let mut evidence = BTreeSet::new();
34
+ for marker in markers {
35
+ for path in paths {
36
+ if matches_marker(path, marker) {
37
+ evidence.insert(path.clone());
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ evidence.into_iter().collect()
43
+ }
44
+
45
+ pub(super) fn has_path(paths: &[String], expected: &str) -> bool {
46
+ paths
47
+ .iter()
48
+ .any(|path| path == expected || path.ends_with(&format!("/{expected}")))
49
+ }
50
+
51
+ pub(super) fn has_suffix(paths: &[String], suffix: &str) -> bool {
52
+ paths.iter().any(|path| path.ends_with(suffix))
53
+ }
54
+
55
+ pub(super) fn has_any_suffix(paths: &[String], suffixes: &[&str]) -> bool {
56
+ suffixes.iter().any(|suffix| has_suffix(paths, suffix))
57
+ }
58
+
59
+ fn matches_marker(path: &str, marker: &str) -> bool {
60
+ if marker.starts_with('.') {
61
+ return path.ends_with(marker);
62
+ }
63
+ if marker.ends_with('/') {
64
+ return path.contains(marker);
65
+ }
66
+ path == marker || path.ends_with(&format!("/{marker}"))
67
+ }
@@ -0,0 +1,59 @@
1
+ use super::types::{RepositoryEntity, RepositoryRoot};
2
+
3
+ pub(super) fn language_for_path(path: &str) -> Option<String> {
4
+ match path.rsplit('.').next() {
5
+ Some("rs") => Some("rust".to_string()),
6
+ Some("js" | "jsx" | "ts" | "tsx") => Some("javascript-typescript".to_string()),
7
+ Some("swift") => Some("swift".to_string()),
8
+ _ => None,
9
+ }
10
+ }
11
+
12
+ pub(super) fn parent_path(path: &str) -> String {
13
+ path.rsplit_once('/')
14
+ .map(|(parent, _)| parent.to_string())
15
+ .unwrap_or_default()
16
+ }
17
+
18
+ pub(super) fn module_for_path(path: &str, roots: &[RepositoryRoot]) -> Option<String> {
19
+ parent_path(path)
20
+ .split_once("/src")
21
+ .map(|(module, _)| module.to_string())
22
+ .or_else(|| {
23
+ parent_path(path)
24
+ .split_once("/Sources")
25
+ .map(|(module, _)| module.to_string())
26
+ })
27
+ .or_else(|| {
28
+ roots
29
+ .iter()
30
+ .filter(|root| path.starts_with(&format!("{}/", root.path)))
31
+ .max_by_key(|root| root.path.len())
32
+ .map(|root| root.path.clone())
33
+ })
34
+ }
35
+
36
+ pub(super) fn entity_for_path(path: &str, entities: &[RepositoryEntity]) -> Option<String> {
37
+ entities
38
+ .iter()
39
+ .filter(|entity| entity.path == "." || path.starts_with(&format!("{}/", entity.path)))
40
+ .max_by_key(|entity| entity.path.len())
41
+ .map(|entity| entity.id.clone())
42
+ }
43
+
44
+ pub(super) fn root_at_segment(path: &str, names: &[&str]) -> Option<String> {
45
+ let segments = path.split('/').collect::<Vec<_>>();
46
+ let index = segments
47
+ .iter()
48
+ .position(|segment| names.iter().any(|name| segment == name))?;
49
+ if index + 1 >= segments.len() {
50
+ return None;
51
+ }
52
+ Some(segments[..=index].join("/"))
53
+ }
54
+
55
+ pub(super) fn is_package_manifest(path: &str) -> bool {
56
+ path.ends_with("Cargo.toml")
57
+ || path.ends_with("package.json")
58
+ || path.ends_with("Package.swift")
59
+ }
@@ -0,0 +1,152 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4
+ #[serde(rename_all = "camelCase")]
5
+ pub struct RepositoryModel {
6
+ pub schema: String,
7
+ pub version: u32,
8
+ pub status: String,
9
+ pub facts: Vec<RepositoryFact>,
10
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
11
+ pub languages: Vec<RepositoryWorldSignal>,
12
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
13
+ pub package_managers: Vec<RepositoryWorldSignal>,
14
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
15
+ pub build_systems: Vec<RepositoryWorldSignal>,
16
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
17
+ pub adapters: Vec<RepositoryWorldSignal>,
18
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
19
+ pub roots: Vec<RepositoryRoot>,
20
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
21
+ pub entities: Vec<RepositoryEntity>,
22
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
23
+ pub path_facts: Vec<RepositoryPathFact>,
24
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
25
+ pub verification_checks: Vec<RepositoryVerificationCheck>,
26
+ }
27
+
28
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29
+ #[serde(rename_all = "camelCase")]
30
+ pub struct RepositoryFact {
31
+ pub id: String,
32
+ pub category: String,
33
+ pub value: String,
34
+ pub confidence: String,
35
+ pub source: String,
36
+ pub evidence: Vec<String>,
37
+ }
38
+
39
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40
+ #[serde(rename_all = "camelCase")]
41
+ pub struct RepositoryWorldSignal {
42
+ pub id: String,
43
+ pub value: String,
44
+ pub confidence: String,
45
+ pub source: String,
46
+ pub evidence: Vec<String>,
47
+ }
48
+
49
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50
+ #[serde(rename_all = "camelCase")]
51
+ pub struct RepositoryRoot {
52
+ pub id: String,
53
+ pub role: String,
54
+ pub path: String,
55
+ pub source: String,
56
+ pub evidence: Vec<String>,
57
+ }
58
+
59
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60
+ #[serde(rename_all = "camelCase")]
61
+ pub struct RepositoryEntity {
62
+ pub id: String,
63
+ pub kind: String,
64
+ pub name: String,
65
+ pub path: String,
66
+ pub languages: Vec<String>,
67
+ pub roots: Vec<String>,
68
+ pub source: String,
69
+ pub evidence: Vec<String>,
70
+ }
71
+
72
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73
+ #[serde(rename_all = "camelCase")]
74
+ pub struct RepositoryPathFact {
75
+ pub path: String,
76
+ pub role: String,
77
+ #[serde(skip_serializing_if = "Option::is_none")]
78
+ pub module: Option<String>,
79
+ #[serde(skip_serializing_if = "Option::is_none")]
80
+ pub entity: Option<String>,
81
+ #[serde(skip_serializing_if = "Option::is_none")]
82
+ pub language: Option<String>,
83
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
84
+ pub flags: Vec<String>,
85
+ }
86
+
87
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88
+ #[serde(rename_all = "camelCase")]
89
+ pub struct RepositoryVerificationCheck {
90
+ pub id: String,
91
+ pub command: String,
92
+ pub cwd: String,
93
+ pub evidence: Vec<String>,
94
+ }
95
+
96
+ #[derive(Debug, Clone, Serialize)]
97
+ #[serde(rename_all = "camelCase")]
98
+ pub struct RepositoryModelRefresh {
99
+ pub schema: String,
100
+ pub ok: bool,
101
+ pub stale: bool,
102
+ pub write: bool,
103
+ pub model_path: String,
104
+ pub updated_paths: Vec<String>,
105
+ pub reason_codes: Vec<String>,
106
+ pub model: RepositoryModel,
107
+ }
108
+
109
+ #[derive(Debug, Clone, Serialize)]
110
+ #[serde(rename_all = "camelCase")]
111
+ pub struct RepositoryModelDrift {
112
+ pub schema: String,
113
+ pub ok: bool,
114
+ pub model_present: bool,
115
+ pub stale: bool,
116
+ pub model_path: String,
117
+ pub reason_codes: Vec<String>,
118
+ pub related_paths: Vec<String>,
119
+ pub messages: Vec<String>,
120
+ }
121
+
122
+ #[derive(Debug, Clone, Serialize)]
123
+ #[serde(rename_all = "camelCase")]
124
+ pub struct RepositoryPathExplanation {
125
+ pub schema: String,
126
+ pub path: String,
127
+ pub role: Option<String>,
128
+ pub language: Option<String>,
129
+ pub module: Option<String>,
130
+ pub entity: Option<String>,
131
+ pub facts: Vec<RepositoryFact>,
132
+ pub evidence: Vec<String>,
133
+ }
134
+
135
+ impl Default for RepositoryModel {
136
+ fn default() -> Self {
137
+ Self {
138
+ schema: "naome.repository-model.v1".to_string(),
139
+ version: 1,
140
+ status: "ready".to_string(),
141
+ facts: Vec::new(),
142
+ languages: Vec::new(),
143
+ package_managers: Vec::new(),
144
+ build_systems: Vec::new(),
145
+ adapters: Vec::new(),
146
+ roots: Vec::new(),
147
+ entities: Vec::new(),
148
+ path_facts: Vec::new(),
149
+ verification_checks: Vec::new(),
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,48 @@
1
+ use super::types::{
2
+ RepositoryEntity, RepositoryFact, RepositoryPathFact, RepositoryRoot, RepositoryWorldSignal,
3
+ };
4
+ use super::world_adapters::detect_adapter_signals;
5
+ use super::world_path_facts::detect_path_facts;
6
+ use super::world_paths::{detect_entities, detect_roots};
7
+
8
+ pub(super) struct RepositoryWorldSections {
9
+ pub languages: Vec<RepositoryWorldSignal>,
10
+ pub package_managers: Vec<RepositoryWorldSignal>,
11
+ pub build_systems: Vec<RepositoryWorldSignal>,
12
+ pub adapters: Vec<RepositoryWorldSignal>,
13
+ pub roots: Vec<RepositoryRoot>,
14
+ pub entities: Vec<RepositoryEntity>,
15
+ pub path_facts: Vec<RepositoryPathFact>,
16
+ }
17
+
18
+ pub(super) fn build_world_sections(
19
+ paths: &[String],
20
+ facts: &[RepositoryFact],
21
+ ) -> RepositoryWorldSections {
22
+ let roots = detect_roots(paths);
23
+ let entities = detect_entities(paths);
24
+ let path_facts = detect_path_facts(paths, &roots, &entities);
25
+ RepositoryWorldSections {
26
+ languages: signals_from_facts(facts, "language"),
27
+ package_managers: signals_from_facts(facts, "packageManager"),
28
+ build_systems: signals_from_facts(facts, "buildSystem"),
29
+ adapters: detect_adapter_signals(paths),
30
+ roots,
31
+ entities,
32
+ path_facts,
33
+ }
34
+ }
35
+
36
+ fn signals_from_facts(facts: &[RepositoryFact], category: &str) -> Vec<RepositoryWorldSignal> {
37
+ facts
38
+ .iter()
39
+ .filter(|fact| fact.category == category)
40
+ .map(|fact| RepositoryWorldSignal {
41
+ id: fact.value.clone(),
42
+ value: fact.value.clone(),
43
+ confidence: fact.confidence.clone(),
44
+ source: fact.source.clone(),
45
+ evidence: fact.evidence.clone(),
46
+ })
47
+ .collect()
48
+ }