@lamentis/naome 1.3.12 → 1.3.13

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 (24) hide show
  1. package/Cargo.lock +2 -2
  2. package/crates/naome-cli/Cargo.toml +1 -1
  3. package/crates/naome-core/Cargo.toml +1 -1
  4. package/crates/naome-core/src/architecture/rules/external.rs +59 -0
  5. package/crates/naome-core/src/architecture/scan/graph_builder.rs +130 -30
  6. package/crates/naome-core/src/architecture/scan/imports/extractors/swift.rs +48 -0
  7. package/crates/naome-core/src/architecture/scan/imports/extractors.rs +3 -0
  8. package/crates/naome-core/src/architecture/scan/imports/resolver.rs +41 -1
  9. package/crates/naome-core/src/architecture/scan/imports.rs +1 -0
  10. package/crates/naome-core/src/architecture/scan/manifest/common.rs +102 -0
  11. package/crates/naome-core/src/architecture/scan/manifest/parsers/json.rs +46 -0
  12. package/crates/naome-core/src/architecture/scan/manifest/parsers/other.rs +280 -0
  13. package/crates/naome-core/src/architecture/scan/manifest/parsers/toml.rs +184 -0
  14. package/crates/naome-core/src/architecture/scan/manifest/parsers.rs +3 -0
  15. package/crates/naome-core/src/architecture/scan/manifest.rs +33 -0
  16. package/crates/naome-core/src/architecture/scan.rs +27 -1
  17. package/crates/naome-core/tests/architecture_manifests.rs +289 -0
  18. package/crates/naome-core/tests/architecture_support/mod.rs +2 -0
  19. package/crates/naome-core/tests/architecture_swift.rs +111 -0
  20. package/native/darwin-arm64/naome +0 -0
  21. package/native/linux-x64/naome +0 -0
  22. package/package.json +1 -1
  23. package/templates/naome-root/.naome/manifest.json +2 -2
  24. package/templates/naome-root/docs/naome/architecture-fitness.md +17 -7
@@ -0,0 +1,289 @@
1
+ use naome_core::{scan_architecture, ArchitectureEdgeKind, ArchitectureScanOptions};
2
+
3
+ mod architecture_support;
4
+
5
+ use architecture_support::FixtureRepo;
6
+
7
+ #[test]
8
+ fn manifest_extractors_add_package_dependency_graph_nodes() {
9
+ let repo = FixtureRepo::new();
10
+ repo.write(
11
+ "package.json",
12
+ r#"{
13
+ "name": "@acme/web",
14
+ "dependencies": { "react": "^18.0.0" },
15
+ "devDependencies": { "vite": "^5.0.0" }
16
+ }
17
+ "#,
18
+ );
19
+ repo.write(
20
+ "crates/app/Cargo.toml",
21
+ "[package]\nname = \"app\"\nversion = \"0.1.0\"\n[dependencies]\nserde = \"1\"\n",
22
+ );
23
+ repo.write(
24
+ "pyproject.toml",
25
+ "[project]\nname = \"acme-python\"\ndependencies = [\"requests>=2\", \"fastapi\"]\n",
26
+ );
27
+ repo.write(
28
+ "go.mod",
29
+ "module github.com/acme/app\nrequire github.com/gin-gonic/gin v1.9.0\n",
30
+ );
31
+ repo.write(
32
+ "pom.xml",
33
+ "<project><groupId>com.acme</groupId><artifactId>service</artifactId><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></dependency></dependencies></project>",
34
+ );
35
+ repo.write(
36
+ "build.gradle",
37
+ "plugins { id 'java' }\ndependencies { implementation 'com.google.guava:guava:33.0.0-jre' }\n",
38
+ );
39
+
40
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
41
+
42
+ for node_id in [
43
+ "package:npm:package.json",
44
+ "package:cargo:crates/app/Cargo.toml",
45
+ "package:python:pyproject.toml",
46
+ "package:go:go.mod",
47
+ "package:maven:pom.xml",
48
+ "package:gradle:build.gradle",
49
+ "external:react",
50
+ "external:vite",
51
+ "external:serde",
52
+ "external:requests",
53
+ "external:fastapi",
54
+ "external:github.com/gin-gonic/gin",
55
+ "external:org.slf4j:slf4j-api",
56
+ "external:com.google.guava:guava",
57
+ ] {
58
+ assert!(
59
+ scan.graph.nodes.iter().any(|node| node.id == node_id),
60
+ "missing {node_id}"
61
+ );
62
+ }
63
+ assert!(scan.graph.edges.iter().any(|edge| {
64
+ edge.kind == ArchitectureEdgeKind::DependsOn
65
+ && edge.from == "package:npm:package.json"
66
+ && edge.to == "external:react"
67
+ && edge.metadata.extractor == "manifest:package.json"
68
+ }));
69
+ assert!(scan.graph.edges.iter().any(|edge| {
70
+ edge.kind == ArchitectureEdgeKind::DependsOn
71
+ && edge.from == "package:go:go.mod"
72
+ && edge.to == "external:github.com/gin-gonic/gin"
73
+ }));
74
+ assert!(scan
75
+ .graph
76
+ .nodes
77
+ .iter()
78
+ .any(|node| { node.id == "package:npm:package.json" && node.label == "@acme/web" }));
79
+ }
80
+
81
+ #[test]
82
+ fn manifest_package_ids_are_namespaced_and_parse_common_multiline_forms() {
83
+ let repo = FixtureRepo::new();
84
+ repo.write(
85
+ "package.json",
86
+ r#"{
87
+ "name": "core",
88
+ "dependencies": { "react": "^18.0.0" }
89
+ }
90
+ "#,
91
+ );
92
+ repo.write(
93
+ "crates/core/Cargo.toml",
94
+ "[package]\nname = \"core\"\nversion = \"0.1.0\"\n[dependencies.serde]\nversion = \"1\"\n",
95
+ );
96
+ repo.write(
97
+ "pyproject.toml",
98
+ "[project]\nname = \"core\"\ndependencies = [\n \"requests>=2\",\n \"fastapi\",\n]\n",
99
+ );
100
+ repo.write(
101
+ "build.gradle.kts",
102
+ "dependencies {\n // implementation(\"com.acme:old-lib:1.0\")\n implementation(\"com.google.guava:guava:33.0.0-jre\")\n implementation group: 'com.squareup.okio', name: 'okio', version: '3.9.0'\n}\n",
103
+ );
104
+ repo.write(
105
+ "child/pom.xml",
106
+ r#"<project>
107
+ <parent>
108
+ <groupId>com.acme.parent</groupId>
109
+ <artifactId>parent</artifactId>
110
+ </parent>
111
+ <groupId>com.acme.child</groupId>
112
+ <artifactId>service</artifactId>
113
+ <dependencyManagement>
114
+ <dependencies>
115
+ <dependency>
116
+ <groupId>com.acme</groupId>
117
+ <artifactId>managed-only</artifactId>
118
+ </dependency>
119
+ </dependencies>
120
+ </dependencyManagement>
121
+ <dependencies>
122
+ <dependency>
123
+ <groupId>org.slf4j</groupId>
124
+ <artifactId>slf4j-api</artifactId>
125
+ </dependency>
126
+ </dependencies>
127
+ <build>
128
+ <plugins>
129
+ <plugin>
130
+ <dependencies>
131
+ <dependency>
132
+ <groupId>com.acme</groupId>
133
+ <artifactId>plugin-only</artifactId>
134
+ </dependency>
135
+ </dependencies>
136
+ </plugin>
137
+ </plugins>
138
+ </build>
139
+ </project>"#,
140
+ );
141
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
142
+
143
+ for (node_id, label) in [
144
+ ("package:npm:package.json", "core"),
145
+ ("package:cargo:crates/core/Cargo.toml", "core"),
146
+ ("package:python:pyproject.toml", "core"),
147
+ ("package:gradle:build.gradle.kts", "gradle:build.gradle.kts"),
148
+ ("package:maven:child/pom.xml", "com.acme.child:service"),
149
+ ] {
150
+ assert!(
151
+ scan.graph
152
+ .nodes
153
+ .iter()
154
+ .any(|node| node.id == node_id && node.label == label),
155
+ "missing {node_id} with label {label}"
156
+ );
157
+ }
158
+ for (package_id, dependency_id) in [
159
+ ("package:npm:package.json", "external:react"),
160
+ ("package:cargo:crates/core/Cargo.toml", "external:serde"),
161
+ ("package:python:pyproject.toml", "external:requests"),
162
+ ("package:python:pyproject.toml", "external:fastapi"),
163
+ (
164
+ "package:gradle:build.gradle.kts",
165
+ "external:com.google.guava:guava",
166
+ ),
167
+ (
168
+ "package:gradle:build.gradle.kts",
169
+ "external:com.squareup.okio:okio",
170
+ ),
171
+ (
172
+ "package:maven:child/pom.xml",
173
+ "external:org.slf4j:slf4j-api",
174
+ ),
175
+ ] {
176
+ assert!(
177
+ scan.graph.edges.iter().any(|edge| {
178
+ edge.kind == ArchitectureEdgeKind::DependsOn
179
+ && edge.from == package_id
180
+ && edge.to == dependency_id
181
+ }),
182
+ "missing edge {package_id} -> {dependency_id}"
183
+ );
184
+ }
185
+ assert!(!scan
186
+ .graph
187
+ .nodes
188
+ .iter()
189
+ .any(|node| node.id == "external:com.acme:old-lib"));
190
+ assert!(!scan
191
+ .graph
192
+ .nodes
193
+ .iter()
194
+ .any(|node| node.id == "external:com.acme:managed-only"));
195
+ assert!(!scan
196
+ .graph
197
+ .nodes
198
+ .iter()
199
+ .any(|node| node.id == "external:com.acme:plugin-only"));
200
+ }
201
+
202
+ #[test]
203
+ fn swift_manifests_emit_package_and_dependency_nodes() {
204
+ let repo = FixtureRepo::new();
205
+ repo.write(
206
+ "Package.swift",
207
+ r#"// swift-tools-version: 5.9
208
+ import PackageDescription
209
+
210
+ let package = Package(
211
+ name: "Tickets",
212
+ dependencies: [
213
+ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0"),
214
+ .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.8.0")
215
+ ]
216
+ )
217
+ "#,
218
+ );
219
+ repo.write(
220
+ "Tickets.xcodeproj/project.pbxproj",
221
+ r#"
222
+ /* Begin XCRemoteSwiftPackageReference section */
223
+ repositoryURL = "https://github.com/onevcat/Kingfisher.git";
224
+ /* End XCRemoteSwiftPackageReference section */
225
+ "#,
226
+ );
227
+
228
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
229
+
230
+ for node_id in [
231
+ "package:swift:Package.swift",
232
+ "package:xcode:Tickets.xcodeproj/project.pbxproj",
233
+ "external:Alamofire",
234
+ "external:swift-composable-architecture",
235
+ "external:Kingfisher",
236
+ ] {
237
+ assert!(
238
+ scan.graph.nodes.iter().any(|node| node.id == node_id),
239
+ "missing {node_id}"
240
+ );
241
+ }
242
+ assert!(scan.graph.edges.iter().any(|edge| {
243
+ edge.kind == ArchitectureEdgeKind::DependsOn
244
+ && edge.from == "package:swift:Package.swift"
245
+ && edge.to == "external:Alamofire"
246
+ }));
247
+ assert!(scan.graph.edges.iter().any(|edge| {
248
+ edge.kind == ArchitectureEdgeKind::DependsOn
249
+ && edge.from == "package:xcode:Tickets.xcodeproj/project.pbxproj"
250
+ && edge.to == "external:Kingfisher"
251
+ }));
252
+ }
253
+
254
+ #[test]
255
+ fn swift_manifests_ignore_commented_package_declarations() {
256
+ let repo = FixtureRepo::new();
257
+ repo.write(
258
+ "Package.swift",
259
+ r#"// swift-tools-version: 5.9
260
+ import PackageDescription
261
+
262
+ let package = Package(
263
+ name: "CoreSwift",
264
+ dependencies: [
265
+ // .package(url: "https://github.com/Old/Old.git", from: "1.0.0"),
266
+ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
267
+ ]
268
+ )
269
+ "#,
270
+ );
271
+
272
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
273
+
274
+ assert!(scan
275
+ .graph
276
+ .nodes
277
+ .iter()
278
+ .any(|node| node.id == "package:swift:Package.swift" && node.label == "CoreSwift"));
279
+ assert!(scan.graph.edges.iter().any(|edge| {
280
+ edge.kind == ArchitectureEdgeKind::DependsOn
281
+ && edge.from == "package:swift:Package.swift"
282
+ && edge.to == "external:Alamofire"
283
+ }));
284
+ assert!(!scan
285
+ .graph
286
+ .nodes
287
+ .iter()
288
+ .any(|node| node.id == "external:Old"));
289
+ }
@@ -1,3 +1,5 @@
1
+ #![allow(dead_code)]
2
+
1
3
  use std::fs;
2
4
  use std::path::{Path, PathBuf};
3
5
  use std::process::Command;
@@ -0,0 +1,111 @@
1
+ use naome_core::{scan_architecture, validate_architecture, ArchitectureScanOptions};
2
+
3
+ mod architecture_support;
4
+
5
+ use architecture_support::FixtureRepo;
6
+
7
+ #[test]
8
+ fn swift_imports_emit_external_dependency_edges_for_ios_apps() {
9
+ let repo = FixtureRepo::new();
10
+ repo.write(
11
+ "Sources/Tickets/AppView.swift",
12
+ "import SwiftUI\nimport UIKit\n@testable import TicketsCore\n@preconcurrency import Alamofire\n@_implementationOnly import Firebase\n",
13
+ );
14
+
15
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
16
+ let fact = scan
17
+ .file_facts
18
+ .get("Sources/Tickets/AppView.swift")
19
+ .unwrap();
20
+
21
+ assert_eq!(fact.language.as_deref(), Some("swift"));
22
+ for package in ["SwiftUI", "UIKit", "TicketsCore", "Alamofire", "Firebase"] {
23
+ assert!(
24
+ scan.graph
25
+ .nodes
26
+ .iter()
27
+ .any(|node| { node.id == format!("external:{package}") && node.label == package }),
28
+ "missing external node for {package}"
29
+ );
30
+ }
31
+ }
32
+
33
+ #[test]
34
+ fn swift_apple_frameworks_do_not_violate_external_dependency_policy() {
35
+ let repo = FixtureRepo::new();
36
+ repo.write(
37
+ "naome.arch.yaml",
38
+ r#"
39
+ layers:
40
+ domain:
41
+ paths:
42
+ - "Sources/Domain/**"
43
+ rules:
44
+ external_dependency_policy:
45
+ enabled: true
46
+ severity: error
47
+ external_dependencies:
48
+ domain:
49
+ allow: []
50
+ "#,
51
+ );
52
+ repo.write(
53
+ "Sources/Domain/EventView.swift",
54
+ "import Foundation\nimport SwiftUI\nimport UIKit\nimport CoreBluetooth\nimport CoreImage\nimport CoreML\nimport Network\nimport Vision\nimport Alamofire\n",
55
+ );
56
+
57
+ let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
58
+
59
+ assert_eq!(report.summary.errors, 1);
60
+ assert_eq!(
61
+ report.violations[0].to.as_deref(),
62
+ Some("external:Alamofire")
63
+ );
64
+ }
65
+
66
+ #[test]
67
+ fn swiftpm_target_imports_resolve_to_local_target_files() {
68
+ let repo = FixtureRepo::new();
69
+ repo.write(
70
+ "naome.arch.yaml",
71
+ r#"
72
+ layers:
73
+ ui:
74
+ paths:
75
+ - "Sources/App/**"
76
+ - "Tests/**"
77
+ rules:
78
+ external_dependency_policy:
79
+ enabled: true
80
+ severity: error
81
+ external_dependencies:
82
+ ui:
83
+ allow: []
84
+ "#,
85
+ );
86
+ repo.write(
87
+ "Sources/App/AppView.swift",
88
+ "import SwiftUI\nimport Core\nimport Alamofire\n",
89
+ );
90
+ repo.write("Sources/Core/Model.swift", "public struct Model {}\n");
91
+ repo.write(
92
+ "Tests/CoreTests/CoreTests.swift",
93
+ "import XCTest\n@testable import Core\n",
94
+ );
95
+
96
+ let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
97
+ let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
98
+
99
+ assert!(scan.graph.edges.iter().any(|edge| {
100
+ edge.from == "file:Sources/App/AppView.swift" && edge.to == "file:Sources/Core/Model.swift"
101
+ }));
102
+ assert!(scan.graph.edges.iter().any(|edge| {
103
+ edge.from == "file:Tests/CoreTests/CoreTests.swift"
104
+ && edge.to == "file:Sources/Core/Model.swift"
105
+ }));
106
+ assert_eq!(report.summary.errors, 1);
107
+ assert_eq!(
108
+ report.violations[0].to.as_deref(),
109
+ Some("external:Alamofire")
110
+ );
111
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamentis/naome",
3
- "version": "1.3.12",
3
+ "version": "1.3.13",
4
4
  "description": "Native-first CLI for the NAOME agent harness.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  {
2
- "harnessVersion": "1.3.11",
2
+ "harnessVersion": "1.3.13",
3
3
  "installedAt": null,
4
4
  "integrity": {
5
5
  ".naome/bin/check-harness-health.js": "sha256:802d7419774981a6af1826b3882270ff8f41259d516f98c52a02b4ddc184c467",
@@ -9,7 +9,7 @@
9
9
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
10
10
  "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
11
11
  "docs/naome/agent-workflow.md": "sha256:0be1c29adfbcd3fd73c4f904080ffc67237692fe413871a30243538c4db38ac7",
12
- "docs/naome/architecture-fitness.md": "sha256:3963d9a92d6481d4e72815a4dd749549320f23c45100b4821e75b7e6f3ffd00b",
12
+ "docs/naome/architecture-fitness.md": "sha256:26f04884c3ada083380bee5c3cb1aa3f39d8c8a1cc31ccc84c4ae795c594de5b",
13
13
  "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
14
14
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
15
15
  "docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
@@ -137,16 +137,26 @@ fails only on configured error rules.
137
137
 
138
138
  ## Language Support
139
139
 
140
- The v1.3.12 foundation classifies TypeScript, JavaScript, Rust, Python, Go,
140
+ The v1.3.13 foundation classifies TypeScript, JavaScript, Rust, Python, Go,
141
141
  Java, Kotlin, and Swift files by path extension. It extracts import facts for
142
- TypeScript, JavaScript, Rust, Python, and Go, resolves relative imports and
143
- simple repository-absolute aliases, and represents unresolved imports
144
- explicitly as graph nodes instead of dropping them. Architecture rules now
145
- cover layers, bounded contexts, public APIs, cycles, transitive layer reach,
146
- import/fan-out budgets, and external dependency policies.
142
+ TypeScript, JavaScript, Rust, Python, Go, and Swift, resolves relative imports
143
+ and simple repository-absolute aliases where the language supports them, and
144
+ represents unresolved imports explicitly as graph nodes instead of dropping
145
+ them. Swift and iOS app repositories get Apple framework imports, SwiftPM
146
+ manifests, and Xcode Swift package references represented in the normalized
147
+ graph; Apple SDK frameworks are treated as platform APIs for external policy.
148
+ It also extracts package and dependency facts from `package.json`,
149
+ `Cargo.toml`, `pyproject.toml`, `go.mod`, lightweight `pom.xml` / Gradle
150
+ manifests, `Package.swift`, and `.xcodeproj/project.pbxproj`, then emits
151
+ manifest-identity package nodes and manifest-owned `DependsOn` edges to
152
+ external dependencies.
153
+ Architecture rules now cover layers, bounded contexts, public APIs, cycles,
154
+ transitive layer reach, import/fan-out budgets, and external dependency
155
+ policies.
147
156
 
148
157
  ## Limitations
149
158
 
150
159
  This release intentionally keeps validation file-graph based. Manifest
151
- extractors, SARIF output, persistent scan caching, and deeper symbol-level call
160
+ extractors are dependency-owner oriented and do not yet parse every build-tool
161
+ feature. SARIF output, persistent scan caching, and deeper symbol-level call
152
162
  analysis remain planned follow-up slices before v1.4.0.