@lamentis/naome 1.3.13 → 1.3.15

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 (77) hide show
  1. package/Cargo.lock +2 -2
  2. package/crates/naome-cli/Cargo.toml +1 -1
  3. package/crates/naome-cli/src/architecture_commands.rs +1 -1
  4. package/crates/naome-cli/src/quality_commands.rs +4 -4
  5. package/crates/naome-core/Cargo.toml +1 -1
  6. package/crates/naome-core/src/architecture/config/parser/sections.rs +1 -1
  7. package/crates/naome-core/src/architecture/config.rs +1 -1
  8. package/crates/naome-core/src/architecture/model.rs +19 -0
  9. package/crates/naome-core/src/architecture/output.rs +27 -24
  10. package/crates/naome-core/src/architecture/rules/budgets.rs +2 -1
  11. package/crates/naome-core/src/architecture/rules.rs +3 -1
  12. package/crates/naome-core/src/architecture/scan/cache.rs +145 -0
  13. package/crates/naome-core/src/architecture/scan/graph_builder.rs +31 -10
  14. package/crates/naome-core/src/architecture/scan.rs +232 -14
  15. package/crates/naome-core/src/architecture.rs +3 -3
  16. package/crates/naome-core/src/intent/legacy.rs +1 -1
  17. package/crates/naome-core/src/intent/model.rs +27 -0
  18. package/crates/naome-core/src/intent/resolver.rs +3 -28
  19. package/crates/naome-core/src/intent/resolver_baseline.rs +1 -2
  20. package/crates/naome-core/src/intent/resolver_policy.rs +1 -2
  21. package/crates/naome-core/src/intent.rs +2 -1
  22. package/crates/naome-core/src/quality/adapter_ios.rs +3 -1
  23. package/crates/naome-core/src/quality/adapter_support.rs +32 -0
  24. package/crates/naome-core/src/quality/adapters.rs +8 -25
  25. package/crates/naome-core/src/quality/analysis_model.rs +33 -0
  26. package/crates/naome-core/src/quality/cache.rs +1 -1
  27. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +2 -1
  28. package/crates/naome-core/src/quality/checks/near_duplicates.rs +2 -1
  29. package/crates/naome-core/src/quality/mod.rs +1 -0
  30. package/crates/naome-core/src/quality/scanner/analysis.rs +1 -1
  31. package/crates/naome-core/src/quality/scanner.rs +1 -32
  32. package/crates/naome-core/src/quality/semantic/extract.rs +2 -1
  33. package/crates/naome-core/src/quality/structure/adapter_ios.rs +1 -1
  34. package/crates/naome-core/src/quality/structure/adapter_model.rs +23 -0
  35. package/crates/naome-core/src/quality/structure/adapters.rs +2 -23
  36. package/crates/naome-core/src/quality/structure/config.rs +2 -1
  37. package/crates/naome-core/src/quality/structure/defaults.rs +2 -2
  38. package/crates/naome-core/src/quality/structure/mod.rs +1 -0
  39. package/crates/naome-core/src/quality/structure/model.rs +6 -6
  40. package/crates/naome-core/src/repository_model.rs +6 -1
  41. package/crates/naome-core/src/route/builtin_checks.rs +1 -1
  42. package/crates/naome-core/src/route/builtin_require.rs +1 -1
  43. package/crates/naome-core/src/route/execution.rs +2 -34
  44. package/crates/naome-core/src/route/execution_baselines.rs +1 -1
  45. package/crates/naome-core/src/route/execution_model.rs +37 -0
  46. package/crates/naome-core/src/route/execution_support.rs +1 -1
  47. package/crates/naome-core/src/route/execution_tasks.rs +1 -1
  48. package/crates/naome-core/src/route/quality_gate.rs +0 -1
  49. package/crates/naome-core/src/route.rs +3 -1
  50. package/crates/naome-core/src/task_state/api.rs +5 -4
  51. package/crates/naome-core/src/task_state/commit_gate.rs +1 -1
  52. package/crates/naome-core/src/task_state/compact_proof.rs +2 -2
  53. package/crates/naome-core/src/task_state/completed_refresh.rs +0 -13
  54. package/crates/naome-core/src/task_state/completion.rs +13 -4
  55. package/crates/naome-core/src/task_state/mod.rs +1 -1
  56. package/crates/naome-core/src/task_state/progress.rs +1 -1
  57. package/crates/naome-core/src/task_state/proof.rs +1 -4
  58. package/crates/naome-core/src/task_state/proof_entry.rs +1 -1
  59. package/crates/naome-core/src/task_state/proof_model.rs +2 -18
  60. package/crates/naome-core/src/task_state/proof_sources.rs +2 -2
  61. package/crates/naome-core/src/task_state/proof_types.rs +17 -0
  62. package/crates/naome-core/src/task_state/shape.rs +1 -1
  63. package/crates/naome-core/src/task_state/task_diff_api.rs +1 -1
  64. package/crates/naome-core/src/verification_contract_policy.rs +2 -6
  65. package/crates/naome-core/src/workflow/agent/proof.rs +1 -1
  66. package/crates/naome-core/src/workflow/agent.rs +1 -1
  67. package/crates/naome-core/src/workflow/mod.rs +3 -1
  68. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  69. package/crates/naome-core/src/workflow/phase_model.rs +22 -0
  70. package/crates/naome-core/src/workflow/phases.rs +1 -21
  71. package/crates/naome-core/tests/architecture_cache.rs +212 -0
  72. package/native/darwin-arm64/naome +0 -0
  73. package/native/linux-x64/naome +0 -0
  74. package/package.json +1 -1
  75. package/templates/naome-root/.naome/manifest.json +1 -1
  76. package/templates/naome-root/docs/naome/architecture-fitness.md +29 -8
  77. package/crates/naome-core/src/task_state/reconcile.rs +0 -7
package/Cargo.lock CHANGED
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
76
76
 
77
77
  [[package]]
78
78
  name = "naome-cli"
79
- version = "1.3.13"
79
+ version = "1.3.15"
80
80
  dependencies = [
81
81
  "naome-core",
82
82
  "serde_json",
@@ -84,7 +84,7 @@ dependencies = [
84
84
 
85
85
  [[package]]
86
86
  name = "naome-core"
87
- version = "1.3.13"
87
+ version = "1.3.15"
88
88
  dependencies = [
89
89
  "serde",
90
90
  "serde_json",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-cli"
3
- version = "1.3.13"
3
+ version = "1.3.15"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -53,7 +53,7 @@ fn run_arch_explain(root: &Path, args: &[String]) -> Result<(), Box<dyn std::err
53
53
  "layers": scan.config.layers.keys().collect::<Vec<_>>(),
54
54
  "contexts": scan.config.contexts.keys().collect::<Vec<_>>(),
55
55
  "rules": ARCHITECTURE_RULE_IDS,
56
- "extractors": ["path", "typescript", "javascript", "rust", "python", "go"]
56
+ "extractors": ["path", "typescript", "javascript", "rust", "python", "go", "swift"]
57
57
  }))?
58
58
  );
59
59
  } else {
@@ -1,10 +1,10 @@
1
1
  use std::path::Path;
2
2
 
3
3
  use naome_core::{
4
- check_repository_quality, check_repository_quality_paths, check_semantic_legacy, check_semantic_legacy_paths,
5
- clear_quality_cache, explain_repository_structure, init_repository_quality_with_mode,
6
- plan_quality_cleanup, quality_cache_status, route_quality_cleanup, semantic_route_for_finding,
7
- QualityInitMode, QualityMode,
4
+ check_repository_quality, check_repository_quality_paths, check_semantic_legacy,
5
+ check_semantic_legacy_paths, clear_quality_cache, explain_repository_structure,
6
+ init_repository_quality_with_mode, plan_quality_cleanup, quality_cache_status,
7
+ route_quality_cleanup, semantic_route_for_finding, QualityInitMode, QualityMode,
8
8
  };
9
9
 
10
10
  use crate::cli_args::option_value;
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.3.13"
3
+ version = "1.3.15"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -5,7 +5,7 @@ use super::ConfigParser;
5
5
  use crate::architecture::config::{
6
6
  ContextConfig, ExternalDependencyPolicy, IgnoreRule, LayerConfig, RuleConfig,
7
7
  };
8
- use crate::architecture::output::Severity;
8
+ use crate::architecture::model::Severity;
9
9
 
10
10
  pub(super) fn parse_layers(parser: &mut ConfigParser<'_>) -> Result<(), NaomeError> {
11
11
  while let Some((_, line)) = parser.peek_line() {
@@ -4,7 +4,7 @@ use std::path::Path;
4
4
 
5
5
  use crate::models::NaomeError;
6
6
 
7
- use super::output::Severity;
7
+ use super::model::Severity;
8
8
 
9
9
  mod parser;
10
10
 
@@ -38,6 +38,25 @@ pub struct SourceRange {
38
38
  pub end_column: usize,
39
39
  }
40
40
 
41
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
42
+ #[serde(rename_all = "snake_case")]
43
+ pub enum Severity {
44
+ Error,
45
+ Warning,
46
+ Info,
47
+ }
48
+
49
+ impl Severity {
50
+ pub fn parse(value: &str) -> Option<Self> {
51
+ match value {
52
+ "error" => Some(Self::Error),
53
+ "warning" | "warn" => Some(Self::Warning),
54
+ "info" => Some(Self::Info),
55
+ _ => None,
56
+ }
57
+ }
58
+ }
59
+
41
60
  #[derive(Debug, Clone, Serialize, Deserialize)]
42
61
  pub struct ArchitectureMetadata {
43
62
  pub path: Option<String>,
@@ -1,16 +1,8 @@
1
1
  use serde::{Deserialize, Serialize};
2
2
 
3
- use super::model::SourceRange;
3
+ use super::model::{Severity, SourceRange};
4
4
  use super::scan::ArchitectureScanReport;
5
5
 
6
- #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
7
- #[serde(rename_all = "snake_case")]
8
- pub enum Severity {
9
- Error,
10
- Warning,
11
- Info,
12
- }
13
-
14
6
  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15
7
  #[serde(rename_all = "camelCase")]
16
8
  pub struct ViolationSummary {
@@ -54,6 +46,8 @@ pub struct ArchitectureValidation {
54
46
  pub rules_executed: Vec<String>,
55
47
  pub changed_only_requested: bool,
56
48
  pub changed_only_degraded_to_full_scan: bool,
49
+ pub changed_only_mode: String,
50
+ pub changed_only_degradation_reason: Option<String>,
57
51
  pub violations: Vec<ArchitectureViolation>,
58
52
  #[serde(rename = "agent_feedback")]
59
53
  pub agent_feedback: Vec<ArchitectureAgentFeedback>,
@@ -72,17 +66,6 @@ pub const ARCHITECTURE_RULE_IDS: &[&str] = &[
72
66
  "arch.external_dependency_policy",
73
67
  ];
74
68
 
75
- impl Severity {
76
- pub fn parse(value: &str) -> Option<Self> {
77
- match value {
78
- "error" => Some(Self::Error),
79
- "warning" | "warn" => Some(Self::Warning),
80
- "info" => Some(Self::Info),
81
- _ => None,
82
- }
83
- }
84
- }
85
-
86
69
  pub fn architecture_agent_feedback(
87
70
  violations: &[ArchitectureViolation],
88
71
  ) -> Vec<ArchitectureAgentFeedback> {
@@ -134,7 +117,15 @@ pub fn format_architecture_validation(report: &ArchitectureValidation) -> String
134
117
  ];
135
118
 
136
119
  if report.changed_only_degraded_to_full_scan {
137
- lines.push("changed-only requested: degraded to full scan for soundness".to_string());
120
+ let reason = report
121
+ .changed_only_degradation_reason
122
+ .as_deref()
123
+ .unwrap_or("soundness");
124
+ lines.push(format!(
125
+ "changed-only requested: degraded to full scan for soundness ({reason})"
126
+ ));
127
+ } else if report.changed_only_requested {
128
+ lines.push("changed-only requested: using incremental architecture cache".to_string());
138
129
  }
139
130
 
140
131
  for violation in report.violations.iter().take(10) {
@@ -152,12 +143,24 @@ pub fn format_architecture_validation(report: &ArchitectureValidation) -> String
152
143
  }
153
144
 
154
145
  pub fn format_architecture_scan(report: &ArchitectureScanReport) -> String {
155
- format!(
146
+ let mut output = format!(
156
147
  "NAOME architecture scan\nfiles scanned: {}\ngraph nodes: {}\ngraph edges: {}\n",
157
148
  report.files_scanned,
158
149
  report.graph.nodes.len(),
159
150
  report.graph.edges.len()
160
- )
151
+ );
152
+ if report.changed_only_degraded_to_full_scan {
153
+ let reason = report
154
+ .changed_only_degradation_reason
155
+ .as_deref()
156
+ .unwrap_or("soundness");
157
+ output.push_str(&format!(
158
+ "changed-only requested: degraded to full scan for soundness ({reason})\n"
159
+ ));
160
+ } else if report.changed_only_requested {
161
+ output.push_str("changed-only requested: using incremental architecture cache\n");
162
+ }
163
+ output
161
164
  }
162
165
 
163
166
  pub fn format_architecture_explain(scan: &ArchitectureScanReport) -> String {
@@ -176,7 +179,7 @@ pub fn format_architecture_explain(scan: &ArchitectureScanReport) -> String {
176
179
  .collect::<Vec<_>>()
177
180
  .join(", ");
178
181
  format!(
179
- "NAOME Architecture Fitness\nrules: {}\nlayers: {}\ncontexts: {}\npath extractor: enabled for every repository\nimport extractors: typescript, javascript, rust, python, go\n",
182
+ "NAOME Architecture Fitness\nrules: {}\nlayers: {}\ncontexts: {}\npath extractor: enabled for every repository\nimport extractors: typescript, javascript, rust, python, go, swift\n",
180
183
  ARCHITECTURE_RULE_IDS.join(", "),
181
184
  empty_label(&layers),
182
185
  empty_label(&contexts)
@@ -1,4 +1,5 @@
1
- use crate::architecture::output::{ArchitectureViolation, Severity};
1
+ use crate::architecture::model::Severity;
2
+ use crate::architecture::output::ArchitectureViolation;
2
3
  use crate::architecture::scan::{ArchitectureScanReport, FileFact, ImportTarget};
3
4
 
4
5
  pub(super) fn validate_file_size_budget(
@@ -1,8 +1,8 @@
1
1
  use crate::paths;
2
2
 
3
+ use super::model::Severity;
3
4
  use super::output::{
4
5
  architecture_agent_feedback, summary_for, ArchitectureValidation, ArchitectureViolation,
5
- Severity,
6
6
  };
7
7
  use super::scan::ArchitectureScanReport;
8
8
  use crate::architecture::model::ArchitectureEdgeKind;
@@ -55,6 +55,8 @@ pub fn validate_scan(scan: ArchitectureScanReport) -> ArchitectureValidation {
55
55
  rules_executed,
56
56
  changed_only_requested: scan.changed_only_requested,
57
57
  changed_only_degraded_to_full_scan: scan.changed_only_degraded_to_full_scan,
58
+ changed_only_mode: scan.changed_only_mode,
59
+ changed_only_degradation_reason: scan.changed_only_degradation_reason,
58
60
  violations,
59
61
  agent_feedback,
60
62
  }
@@ -0,0 +1,145 @@
1
+ use std::collections::{BTreeMap, BTreeSet};
2
+ use std::fs;
3
+ use std::path::Path;
4
+
5
+ use serde::{Deserialize, Serialize};
6
+ use sha2::{Digest, Sha256};
7
+
8
+ use super::FileFact;
9
+ use crate::models::NaomeError;
10
+
11
+ const CACHE_SCHEMA: &str = "naome.architecture-cache.v1";
12
+ const EXTRACTOR_VERSION: &str = "architecture-cache-v1.3.14";
13
+ const CACHE_PATH: &str = ".naome/cache/architecture/cache.json";
14
+
15
+ #[derive(Debug, Clone, Serialize, Deserialize)]
16
+ #[serde(rename_all = "camelCase")]
17
+ pub(super) struct ArchitectureCache {
18
+ pub schema: String,
19
+ pub extractor_version: String,
20
+ pub config_hash: String,
21
+ pub file_list_hash: String,
22
+ pub files: BTreeMap<String, CachedFileFact>,
23
+ }
24
+
25
+ #[derive(Debug, Clone, Serialize, Deserialize)]
26
+ #[serde(rename_all = "camelCase")]
27
+ pub(super) struct CachedFileFact {
28
+ pub content_hash: String,
29
+ pub fact: FileFact,
30
+ }
31
+
32
+ impl ArchitectureCache {
33
+ pub(super) fn is_compatible(&self, config_hash: &str, file_list_hash: &str) -> CacheStatus {
34
+ if self.schema != CACHE_SCHEMA || self.extractor_version != EXTRACTOR_VERSION {
35
+ return CacheStatus::Miss("cache_miss");
36
+ }
37
+ if self.config_hash != config_hash {
38
+ return CacheStatus::Miss("config_changed");
39
+ }
40
+ if self.file_list_hash != file_list_hash {
41
+ return CacheStatus::Miss("file_set_changed");
42
+ }
43
+ CacheStatus::Hit
44
+ }
45
+ }
46
+
47
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
48
+ pub(super) enum CacheStatus {
49
+ Hit,
50
+ Miss(&'static str),
51
+ }
52
+
53
+ pub(super) fn read(root: &Path) -> Option<ArchitectureCache> {
54
+ let content = fs::read_to_string(root.join(CACHE_PATH)).ok()?;
55
+ serde_json::from_str(&content).ok()
56
+ }
57
+
58
+ pub(super) fn write(
59
+ root: &Path,
60
+ config_hash: String,
61
+ file_list_hash: String,
62
+ file_facts: &BTreeMap<String, FileFact>,
63
+ ) -> Result<(), NaomeError> {
64
+ let cache = ArchitectureCache {
65
+ schema: CACHE_SCHEMA.to_string(),
66
+ extractor_version: EXTRACTOR_VERSION.to_string(),
67
+ config_hash,
68
+ file_list_hash,
69
+ files: file_facts
70
+ .iter()
71
+ .filter_map(|(path, fact)| {
72
+ let content_hash = content_hash(root, path).ok()?;
73
+ Some((
74
+ path.clone(),
75
+ CachedFileFact {
76
+ content_hash,
77
+ fact: fact.clone(),
78
+ },
79
+ ))
80
+ })
81
+ .collect(),
82
+ };
83
+ let path = root.join(CACHE_PATH);
84
+ if let Some(parent) = path.parent() {
85
+ fs::create_dir_all(parent)?;
86
+ }
87
+ fs::write(path, serde_json::to_string_pretty(&cache)?)?;
88
+ Ok(())
89
+ }
90
+
91
+ pub(super) fn config_hash(
92
+ root: &Path,
93
+ explicit_path: Option<&Path>,
94
+ default_content: &str,
95
+ ) -> Result<String, NaomeError> {
96
+ let path = explicit_path
97
+ .map(Path::to_path_buf)
98
+ .unwrap_or_else(|| root.join("naome.arch.yaml"));
99
+ let source = if path.exists() {
100
+ fs::read_to_string(&path)?
101
+ } else {
102
+ default_content.to_string()
103
+ };
104
+ let label = display_path(root, &path);
105
+ Ok(stable_hash([
106
+ b"architecture-config-v1".as_slice(),
107
+ label.as_bytes(),
108
+ source.as_bytes(),
109
+ ]))
110
+ }
111
+
112
+ pub(super) fn file_list_hash(files: &[String]) -> String {
113
+ let mut hasher = Sha256::new();
114
+ hasher.update(b"architecture-file-list-v1");
115
+ for path in files {
116
+ hasher.update([0]);
117
+ hasher.update(path.as_bytes());
118
+ }
119
+ format!("sha256:{:x}", hasher.finalize())
120
+ }
121
+
122
+ pub(super) fn content_hash(root: &Path, path: &str) -> Result<String, NaomeError> {
123
+ let bytes = fs::read(root.join(path))?;
124
+ Ok(stable_hash([b"architecture-file-v1".as_slice(), &bytes]))
125
+ }
126
+
127
+ pub(super) fn changed_set(changed_paths: &[String]) -> BTreeSet<String> {
128
+ changed_paths.iter().cloned().collect()
129
+ }
130
+
131
+ fn stable_hash<'a>(parts: impl IntoIterator<Item = &'a [u8]>) -> String {
132
+ let mut hasher = Sha256::new();
133
+ for part in parts {
134
+ hasher.update(part);
135
+ hasher.update([0]);
136
+ }
137
+ format!("sha256:{:x}", hasher.finalize())
138
+ }
139
+
140
+ fn display_path(root: &Path, path: &Path) -> String {
141
+ path.strip_prefix(root)
142
+ .unwrap_or(path)
143
+ .to_string_lossy()
144
+ .replace('\\', "/")
145
+ }
@@ -18,25 +18,46 @@ pub(super) fn build_path_graph(
18
18
  config: &ArchitectureConfig,
19
19
  manifests: &[ManifestFact],
20
20
  ) -> (ArchitectureGraph, BTreeMap<String, FileFact>) {
21
- let mut graph = ArchitectureGraph::default();
22
21
  let mut file_facts = BTreeMap::new();
23
- let mut emitted_external_nodes = BTreeSet::new();
24
22
  let file_set = files.iter().cloned().collect::<BTreeSet<_>>();
23
+ for path in &files {
24
+ file_facts.insert(path.clone(), scan_file_fact(root, path, config, &file_set));
25
+ }
26
+ let graph = build_graph_from_facts(&files, &file_facts, config, manifests);
27
+ (graph, file_facts)
28
+ }
29
+
30
+ pub(super) fn scan_file_fact(
31
+ root: &Path,
32
+ path: &str,
33
+ config: &ArchitectureConfig,
34
+ file_set: &BTreeSet<String>,
35
+ ) -> FileFact {
36
+ let content = fs::read_to_string(root.join(path)).unwrap_or_default();
37
+ let mut fact = facts::file_fact(path, &content, config);
38
+ fact.imports = imports::extract_imports(root, path, &content, file_set);
39
+ fact
40
+ }
41
+
42
+ pub(super) fn build_graph_from_facts(
43
+ files: &[String],
44
+ file_facts: &BTreeMap<String, FileFact>,
45
+ config: &ArchitectureConfig,
46
+ manifests: &[ManifestFact],
47
+ ) -> ArchitectureGraph {
48
+ let mut graph = ArchitectureGraph::default();
49
+ let mut emitted_external_nodes = BTreeSet::new();
25
50
 
26
51
  push_repository_and_policy_nodes(&mut graph, config);
27
- push_directories(&mut graph, &files);
52
+ push_directories(&mut graph, files);
28
53
  push_manifests(&mut graph, manifests, &mut emitted_external_nodes);
29
54
 
30
- for path in files {
31
- let content = fs::read_to_string(root.join(&path)).unwrap_or_default();
32
- let mut fact = facts::file_fact(&path, &content, config);
33
- fact.imports = imports::extract_imports(root, &path, &content, &file_set);
55
+ for fact in file_facts.values() {
34
56
  push_file(&mut graph, &fact);
35
- file_facts.insert(path, fact);
36
57
  }
37
58
 
38
- push_imports(&mut graph, &file_facts, &mut emitted_external_nodes);
39
- (graph, file_facts)
59
+ push_imports(&mut graph, file_facts, &mut emitted_external_nodes);
60
+ graph
40
61
  }
41
62
 
42
63
  fn push_repository_and_policy_nodes(graph: &mut ArchitectureGraph, config: &ArchitectureConfig) {