@lamentis/naome 1.3.10 → 1.3.11
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.
- package/Cargo.lock +2 -2
- package/README.md +5 -0
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/architecture_commands.rs +6 -2
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/architecture/config/parser/sections.rs +17 -0
- package/crates/naome-core/src/architecture/config/parser.rs +1 -0
- package/crates/naome-core/src/architecture/config.rs +12 -0
- package/crates/naome-core/src/architecture/output.rs +1 -1
- package/crates/naome-core/src/architecture/rules.rs +72 -0
- package/crates/naome-core/src/architecture/scan/graph_builder/emit.rs +63 -1
- package/crates/naome-core/src/architecture/scan/graph_builder/facts.rs +2 -3
- package/crates/naome-core/src/architecture/scan/graph_builder.rs +78 -1
- package/crates/naome-core/src/architecture/scan/imports/extractors.rs +407 -0
- package/crates/naome-core/src/architecture/scan/imports/resolver.rs +334 -0
- package/crates/naome-core/src/architecture/scan/imports.rs +59 -0
- package/crates/naome-core/src/architecture/scan.rs +20 -0
- package/crates/naome-core/tests/architecture.rs +346 -7
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/manifest.json +2 -2
- package/templates/naome-root/docs/naome/architecture-fitness.md +19 -7
|
@@ -6,21 +6,18 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
6
6
|
|
|
7
7
|
use naome_core::{
|
|
8
8
|
default_architecture_config_text, scan_architecture, validate_architecture, ArchitectureConfig,
|
|
9
|
-
ArchitectureScanOptions,
|
|
9
|
+
ArchitectureEdgeKind, ArchitectureScanOptions,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
13
|
+
const FORBIDDEN_DOMAIN_TO_INFRASTRUCTURE_CONFIG: &str = "layers:\n domain:\n paths:\n - \"src/domain/**\"\n infrastructure:\n paths:\n - \"src/infrastructure/**\"\nallowed_dependencies:\n domain:\n infrastructure:\n - domain\nrules:\n no_forbidden_layer_dependencies:\n enabled: true\n severity: error\n";
|
|
13
14
|
|
|
14
15
|
#[test]
|
|
15
16
|
fn parses_starter_architecture_config() {
|
|
16
17
|
let config = ArchitectureConfig::parse(default_architecture_config_text(), "test").unwrap();
|
|
17
18
|
|
|
18
19
|
assert!(config.layers.contains_key("application"));
|
|
19
|
-
assert_eq!(
|
|
20
|
-
config.rule("max_file_lines").value,
|
|
21
|
-
Some(400),
|
|
22
|
-
"starter config should seed a production file-size budget"
|
|
23
|
-
);
|
|
20
|
+
assert_eq!(config.rule("max_file_lines").value, Some(400));
|
|
24
21
|
assert_eq!(
|
|
25
22
|
config.ignore[0].reason,
|
|
26
23
|
"Generated code is not architecture-owned."
|
|
@@ -58,7 +55,6 @@ rules:
|
|
|
58
55
|
);
|
|
59
56
|
|
|
60
57
|
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
61
|
-
|
|
62
58
|
assert!(scan
|
|
63
59
|
.graph
|
|
64
60
|
.nodes
|
|
@@ -84,6 +80,343 @@ rules:
|
|
|
84
80
|
);
|
|
85
81
|
}
|
|
86
82
|
|
|
83
|
+
#[test]
|
|
84
|
+
fn import_extractors_emit_file_and_external_dependency_edges() {
|
|
85
|
+
let repo = FixtureRepo::new();
|
|
86
|
+
repo.write(
|
|
87
|
+
"src/domain/event.ts",
|
|
88
|
+
"import { db } from '../infrastructure/db';\nimport React from 'react';\n",
|
|
89
|
+
);
|
|
90
|
+
repo.write("src/infrastructure/db.ts", "export const db = 1;\n");
|
|
91
|
+
repo.write(
|
|
92
|
+
"src/domain/service.py",
|
|
93
|
+
"from ..infrastructure.client import db\nimport requests\n",
|
|
94
|
+
);
|
|
95
|
+
repo.write("src/infrastructure/client.py", "db = object()\n");
|
|
96
|
+
repo.write(
|
|
97
|
+
"src/domain/lib.rs",
|
|
98
|
+
"use crate::infrastructure::db;\nextern crate serde;\n",
|
|
99
|
+
);
|
|
100
|
+
repo.write("src/infrastructure/db.rs", "pub fn connect() {}\n");
|
|
101
|
+
repo.write("cmd/server/main.go", "package main\nimport \"fmt\"\n");
|
|
102
|
+
|
|
103
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
104
|
+
|
|
105
|
+
assert!(scan.graph.edges.iter().any(|edge| {
|
|
106
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
107
|
+
&& edge.from == "file:src/domain/event.ts"
|
|
108
|
+
&& edge.to == "file:src/infrastructure/db.ts"
|
|
109
|
+
}));
|
|
110
|
+
assert!(scan
|
|
111
|
+
.graph
|
|
112
|
+
.nodes
|
|
113
|
+
.iter()
|
|
114
|
+
.any(|node| node.id == "external:react"));
|
|
115
|
+
assert!(scan
|
|
116
|
+
.graph
|
|
117
|
+
.nodes
|
|
118
|
+
.iter()
|
|
119
|
+
.any(|node| node.id == "external:requests"));
|
|
120
|
+
assert!(scan
|
|
121
|
+
.graph
|
|
122
|
+
.nodes
|
|
123
|
+
.iter()
|
|
124
|
+
.any(|node| node.id == "external:fmt"));
|
|
125
|
+
assert!(scan.graph.edges.iter().any(|edge| {
|
|
126
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
127
|
+
&& edge.from == "file:src/domain/service.py"
|
|
128
|
+
&& edge.to == "file:src/infrastructure/client.py"
|
|
129
|
+
}));
|
|
130
|
+
assert!(scan.graph.edges.iter().any(|edge| {
|
|
131
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
132
|
+
&& edge.from == "file:src/domain/lib.rs"
|
|
133
|
+
&& edge.to == "file:src/infrastructure/db.rs"
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn import_resolution_covers_common_language_forms_without_dropping_layer_edges() {
|
|
139
|
+
let repo = FixtureRepo::new();
|
|
140
|
+
repo.write("naome.arch.yaml", FORBIDDEN_DOMAIN_TO_INFRASTRUCTURE_CONFIG);
|
|
141
|
+
repo.write(
|
|
142
|
+
"src/domain/event.ts",
|
|
143
|
+
"import {\n db\n} from '../infrastructure/db';\n",
|
|
144
|
+
);
|
|
145
|
+
repo.write("src/infrastructure/db.ts", "export const db = 1;\n");
|
|
146
|
+
repo.write(
|
|
147
|
+
"src/domain/rust_item.rs",
|
|
148
|
+
"use crate::infrastructure::pool::Pool;\n",
|
|
149
|
+
);
|
|
150
|
+
repo.write("src/infrastructure/pool.rs", "pub struct Pool;\n");
|
|
151
|
+
repo.write(
|
|
152
|
+
"src/domain/python_from_relative.py",
|
|
153
|
+
"from ..infrastructure import client\n",
|
|
154
|
+
);
|
|
155
|
+
repo.write(
|
|
156
|
+
"src/domain/python_from_absolute.py",
|
|
157
|
+
"from src.infrastructure.client import db\n",
|
|
158
|
+
);
|
|
159
|
+
repo.write(
|
|
160
|
+
"src/domain/python_import_list.py",
|
|
161
|
+
"import requests, src.infrastructure.client\n",
|
|
162
|
+
);
|
|
163
|
+
repo.write("src/infrastructure/client.py", "db = object()\n");
|
|
164
|
+
|
|
165
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
166
|
+
|
|
167
|
+
assert_eq!(report.summary.errors, 5);
|
|
168
|
+
let violation_paths = report
|
|
169
|
+
.violations
|
|
170
|
+
.iter()
|
|
171
|
+
.map(|violation| violation.path.as_deref().unwrap_or(""))
|
|
172
|
+
.collect::<Vec<_>>();
|
|
173
|
+
assert!(violation_paths.contains(&"src/domain/event.ts"));
|
|
174
|
+
assert!(violation_paths.contains(&"src/domain/rust_item.rs"));
|
|
175
|
+
assert!(violation_paths.contains(&"src/domain/python_from_relative.py"));
|
|
176
|
+
assert!(violation_paths.contains(&"src/domain/python_from_absolute.py"));
|
|
177
|
+
assert!(violation_paths.contains(&"src/domain/python_import_list.py"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn relative_import_resolution_prefers_source_language_extensions() {
|
|
182
|
+
let repo = FixtureRepo::new();
|
|
183
|
+
repo.write("src/domain/mod.rs", "mod db;\n");
|
|
184
|
+
repo.write("src/domain/db.ts", "export const wrong = true;\n");
|
|
185
|
+
repo.write("src/domain/db.rs", "pub fn right() {}\n");
|
|
186
|
+
|
|
187
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
188
|
+
|
|
189
|
+
assert!(scan.graph.edges.iter().any(|edge| {
|
|
190
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
191
|
+
&& edge.from == "file:src/domain/mod.rs"
|
|
192
|
+
&& edge.to == "file:src/domain/db.rs"
|
|
193
|
+
}));
|
|
194
|
+
assert!(!scan.graph.edges.iter().any(|edge| {
|
|
195
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
196
|
+
&& edge.from == "file:src/domain/mod.rs"
|
|
197
|
+
&& edge.to == "file:src/domain/db.ts"
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn import_resolver_handles_language_specific_module_roots() {
|
|
203
|
+
let repo = FixtureRepo::new();
|
|
204
|
+
repo.write(
|
|
205
|
+
"packages/app/src/domain/event.rs",
|
|
206
|
+
"use crate::infrastructure::db::Pool;\n",
|
|
207
|
+
);
|
|
208
|
+
repo.write(
|
|
209
|
+
"packages/app/src/infrastructure/db.rs",
|
|
210
|
+
"pub struct Pool;\n",
|
|
211
|
+
);
|
|
212
|
+
repo.write(
|
|
213
|
+
"src/domain/service.rs",
|
|
214
|
+
"mod helper;\nuse self::helper::Thing;\nuse super::infra;\n",
|
|
215
|
+
);
|
|
216
|
+
repo.write("src/domain/helper.rs", "pub fn wrong() {}\n");
|
|
217
|
+
repo.write("src/domain/service/helper.rs", "pub fn right() {}\n");
|
|
218
|
+
repo.write("src/infra.rs", "pub fn wrong() {}\n");
|
|
219
|
+
repo.write("src/domain/infra.rs", "pub fn right() {}\n");
|
|
220
|
+
repo.write("go.mod", "module github.com/acme/app\n");
|
|
221
|
+
repo.write(
|
|
222
|
+
"domain/event.go",
|
|
223
|
+
"package domain\nimport \"github.com/acme/app/infrastructure/db\"\n",
|
|
224
|
+
);
|
|
225
|
+
repo.write(
|
|
226
|
+
"domain/external.go",
|
|
227
|
+
"package domain\nimport \"github.com/other/db\"\n",
|
|
228
|
+
);
|
|
229
|
+
repo.write("infrastructure/db/db.go", "package db\n");
|
|
230
|
+
|
|
231
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
232
|
+
|
|
233
|
+
let has = |from, to| has_import_edge(&scan, from, to);
|
|
234
|
+
assert!(has(
|
|
235
|
+
"file:packages/app/src/domain/event.rs",
|
|
236
|
+
"file:packages/app/src/infrastructure/db.rs"
|
|
237
|
+
));
|
|
238
|
+
assert!(has("file:src/domain/service.rs", "file:src/domain/service/helper.rs"));
|
|
239
|
+
assert!(!has("file:src/domain/service.rs", "file:src/domain/helper.rs"));
|
|
240
|
+
assert!(has("file:src/domain/service.rs", "file:src/domain/infra.rs"));
|
|
241
|
+
assert!(!has("file:src/domain/service.rs", "file:src/infra.rs"));
|
|
242
|
+
assert!(has("file:domain/event.go", "file:infrastructure/db/db.go"));
|
|
243
|
+
assert!(!has("file:domain/external.go", "file:infrastructure/db/db.go"));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn missing_layer_allowlist_is_treated_as_no_allowed_dependencies() {
|
|
248
|
+
let repo = FixtureRepo::new();
|
|
249
|
+
repo.write(
|
|
250
|
+
"naome.arch.yaml",
|
|
251
|
+
r#"
|
|
252
|
+
layers:
|
|
253
|
+
domain:
|
|
254
|
+
paths:
|
|
255
|
+
- "src/domain/**"
|
|
256
|
+
infrastructure:
|
|
257
|
+
paths:
|
|
258
|
+
- "src/infrastructure/**"
|
|
259
|
+
rules:
|
|
260
|
+
no_forbidden_layer_dependencies:
|
|
261
|
+
enabled: true
|
|
262
|
+
severity: error
|
|
263
|
+
"#,
|
|
264
|
+
);
|
|
265
|
+
repo.write(
|
|
266
|
+
"src/domain/event.ts",
|
|
267
|
+
"import { db } from '../infrastructure/db';\n",
|
|
268
|
+
);
|
|
269
|
+
repo.write("src/infrastructure/db.ts", "export const db = 1;\n");
|
|
270
|
+
|
|
271
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
272
|
+
|
|
273
|
+
assert_eq!(report.status, "fail");
|
|
274
|
+
assert!(report.violations.iter().any(|violation| {
|
|
275
|
+
violation.id == "arch.no_forbidden_layer_dependencies"
|
|
276
|
+
&& violation.path.as_deref() == Some("src/domain/event.ts")
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#[test]
|
|
281
|
+
fn overlapping_target_layers_do_not_create_false_forbidden_dependency() {
|
|
282
|
+
let repo = FixtureRepo::new();
|
|
283
|
+
repo.write("naome.arch.yaml", "layers:\n application:\n paths:\n - \"src/**\"\n domain:\n paths:\n - \"src/domain/**\"\nallowed_dependencies:\n application:\n - domain\n domain:\nrules:\n no_forbidden_layer_dependencies:\n enabled: true\n severity: error\n");
|
|
284
|
+
repo.write("src/domain/event.ts", "import { type } from './types';\n");
|
|
285
|
+
repo.write("src/domain/types.ts", "export const type = 1;\n");
|
|
286
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
287
|
+
assert_eq!(report.status, "pass");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn late_review_import_forms_keep_forbidden_layer_edges_visible() {
|
|
292
|
+
let repo = FixtureRepo::new();
|
|
293
|
+
repo.write("naome.arch.yaml", FORBIDDEN_DOMAIN_TO_INFRASTRUCTURE_CONFIG);
|
|
294
|
+
repo.write("src/domain/grouped.rs", "use crate::{domain::types, infrastructure::db};\n");
|
|
295
|
+
repo.write("src/domain/types.rs", "pub struct Event;\n");
|
|
296
|
+
repo.write("src/infrastructure/db.rs", "pub fn connect() {}\n");
|
|
297
|
+
repo.write("src/domain/visible.rs", "pub(crate) use crate::infrastructure::db as database;\n");
|
|
298
|
+
repo.write("src/domain/nested.rs", "use crate::{domain::types, infrastructure::{db, cache}};\n");
|
|
299
|
+
repo.write("src/infrastructure/cache.rs", "pub fn cache() {}\n");
|
|
300
|
+
repo.write("src/domain/parenthesized.py", "from src.infrastructure import (\n client,\n)\n");
|
|
301
|
+
repo.write("src/infrastructure/client.py", "db = object()\n");
|
|
302
|
+
repo.write("src/domain/dynamic.ts", "const db = await import(\n '../infrastructure/db_client'\n);\n");
|
|
303
|
+
repo.write("src/infrastructure/db_client.ts", "export const db = 1;\n");
|
|
304
|
+
repo.write("src/domain/export_literal.ts", "export const fixture = '../infrastructure/db_client';\n");
|
|
305
|
+
repo.write("src/domain/commented.ts", "// await import('../infrastructure/db_client')\n");
|
|
306
|
+
repo.write("go.mod", "module github.com/acme/app\n");
|
|
307
|
+
repo.write("src/domain/commented.go", "package domain\nimport (\n // \"github.com/acme/app/src/infrastructure/dbgo\"\n)\n");
|
|
308
|
+
repo.write("src/infrastructure/dbgo/dbgo.go", "package dbgo\n");
|
|
309
|
+
|
|
310
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
311
|
+
let violation_paths = report
|
|
312
|
+
.violations
|
|
313
|
+
.iter()
|
|
314
|
+
.map(|violation| violation.path.as_deref().unwrap_or(""))
|
|
315
|
+
.collect::<Vec<_>>();
|
|
316
|
+
|
|
317
|
+
assert!(violation_paths.contains(&"src/domain/grouped.rs"));
|
|
318
|
+
assert!(violation_paths.contains(&"src/domain/visible.rs"));
|
|
319
|
+
assert!(violation_paths.contains(&"src/domain/nested.rs"));
|
|
320
|
+
assert!(violation_paths.contains(&"src/domain/parenthesized.py"));
|
|
321
|
+
assert!(violation_paths.contains(&"src/domain/dynamic.ts"));
|
|
322
|
+
assert!(!violation_paths.contains(&"src/domain/export_literal.ts"));
|
|
323
|
+
assert!(!violation_paths.contains(&"src/domain/commented.ts"));
|
|
324
|
+
assert!(!violation_paths.contains(&"src/domain/commented.go"));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[test]
|
|
328
|
+
fn unresolved_relative_imports_are_represented_explicitly() {
|
|
329
|
+
let repo = FixtureRepo::new();
|
|
330
|
+
repo.write("src/app.ts", "import missing from './missing';\n");
|
|
331
|
+
|
|
332
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
333
|
+
|
|
334
|
+
assert!(scan
|
|
335
|
+
.graph
|
|
336
|
+
.nodes
|
|
337
|
+
.iter()
|
|
338
|
+
.any(|node| node.id == "unknown-import:./missing"));
|
|
339
|
+
assert!(scan.graph.edges.iter().any(|edge| {
|
|
340
|
+
edge.kind == ArchitectureEdgeKind::Imports
|
|
341
|
+
&& edge.from == "file:src/app.ts"
|
|
342
|
+
&& edge.to == "unknown-import:./missing"
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#[test]
|
|
347
|
+
fn validates_forbidden_layer_dependency_from_import_edges() {
|
|
348
|
+
let repo = FixtureRepo::new();
|
|
349
|
+
repo.write("naome.arch.yaml", FORBIDDEN_DOMAIN_TO_INFRASTRUCTURE_CONFIG);
|
|
350
|
+
repo.write(
|
|
351
|
+
"src/domain/event.ts",
|
|
352
|
+
"import { db } from '../infrastructure/db';\n",
|
|
353
|
+
);
|
|
354
|
+
repo.write("src/infrastructure/db.ts", "export const db = 1;\n");
|
|
355
|
+
|
|
356
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
357
|
+
|
|
358
|
+
assert_eq!(report.status, "fail");
|
|
359
|
+
let violation = report
|
|
360
|
+
.violations
|
|
361
|
+
.iter()
|
|
362
|
+
.find(|violation| violation.id == "arch.no_forbidden_layer_dependencies")
|
|
363
|
+
.expect("expected forbidden layer dependency violation");
|
|
364
|
+
assert_eq!(violation.path.as_deref(), Some("src/domain/event.ts"));
|
|
365
|
+
assert_eq!(violation.source_range.as_ref().unwrap().start_line, 1);
|
|
366
|
+
assert!(violation
|
|
367
|
+
.agent_instruction
|
|
368
|
+
.contains("Do not import infrastructure from domain"));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[test]
|
|
372
|
+
fn exact_entrypoint_layer_paths_keep_bootstrap_out_of_cli_layer() {
|
|
373
|
+
let repo = FixtureRepo::new();
|
|
374
|
+
repo.write(
|
|
375
|
+
"naome.arch.yaml",
|
|
376
|
+
r#"
|
|
377
|
+
layers:
|
|
378
|
+
cli:
|
|
379
|
+
paths:
|
|
380
|
+
- "packages/naome/bin/naome.js"
|
|
381
|
+
installer:
|
|
382
|
+
paths:
|
|
383
|
+
- "packages/naome/bin/naome-node.js"
|
|
384
|
+
- "packages/naome/installer/**"
|
|
385
|
+
allowed_dependencies:
|
|
386
|
+
cli:
|
|
387
|
+
installer:
|
|
388
|
+
rules:
|
|
389
|
+
no_forbidden_layer_dependencies:
|
|
390
|
+
enabled: true
|
|
391
|
+
severity: error
|
|
392
|
+
"#,
|
|
393
|
+
);
|
|
394
|
+
repo.write(
|
|
395
|
+
"packages/naome/bin/naome.js",
|
|
396
|
+
"console.log('native wrapper');\n",
|
|
397
|
+
);
|
|
398
|
+
repo.write(
|
|
399
|
+
"packages/naome/bin/naome-node.js",
|
|
400
|
+
"import { runNaomeNodeCli } from '../installer/main.js';\nawait runNaomeNodeCli();\n",
|
|
401
|
+
);
|
|
402
|
+
repo.write(
|
|
403
|
+
"packages/naome/installer/main.js",
|
|
404
|
+
"export async function runNaomeNodeCli() {}\n",
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
408
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
409
|
+
|
|
410
|
+
assert_eq!(report.status, "pass");
|
|
411
|
+
assert_eq!(
|
|
412
|
+
scan.file_facts
|
|
413
|
+
.get("packages/naome/bin/naome-node.js")
|
|
414
|
+
.unwrap()
|
|
415
|
+
.layers,
|
|
416
|
+
vec!["installer"]
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
87
420
|
#[test]
|
|
88
421
|
fn validates_file_size_budget_with_stable_json_shape() {
|
|
89
422
|
let repo = FixtureRepo::new();
|
|
@@ -207,3 +540,9 @@ fn run_git(root: &Path, args: &[&str]) {
|
|
|
207
540
|
String::from_utf8_lossy(&result.stderr)
|
|
208
541
|
);
|
|
209
542
|
}
|
|
543
|
+
|
|
544
|
+
fn has_import_edge(scan: &naome_core::ArchitectureScanReport, from: &str, to: &str) -> bool {
|
|
545
|
+
scan.graph.edges.iter().any(|edge| {
|
|
546
|
+
edge.kind == ArchitectureEdgeKind::Imports && edge.from == from && edge.to == to
|
|
547
|
+
})
|
|
548
|
+
}
|
|
Binary file
|
package/native/linux-x64/naome
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"harnessVersion": "1.3.
|
|
2
|
+
"harnessVersion": "1.3.11",
|
|
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:
|
|
12
|
+
"docs/naome/architecture-fitness.md": "sha256:a1f08dc02eff4e4c37934cf48e3d7ed2453e34d55dac475bc24ab913e03f263d",
|
|
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",
|
|
@@ -50,6 +50,9 @@ contexts:
|
|
|
50
50
|
- "src/billing/index.ts"
|
|
51
51
|
|
|
52
52
|
rules:
|
|
53
|
+
no_forbidden_layer_dependencies:
|
|
54
|
+
enabled: true
|
|
55
|
+
severity: error
|
|
53
56
|
max_file_lines:
|
|
54
57
|
enabled: true
|
|
55
58
|
value: 400
|
|
@@ -58,6 +61,11 @@ rules:
|
|
|
58
61
|
enabled: true
|
|
59
62
|
severity: error
|
|
60
63
|
|
|
64
|
+
allowed_dependencies:
|
|
65
|
+
domain:
|
|
66
|
+
infrastructure:
|
|
67
|
+
- domain
|
|
68
|
+
|
|
61
69
|
ignore:
|
|
62
70
|
- path: "generated/**"
|
|
63
71
|
reason: "Generated code is not architecture-owned."
|
|
@@ -69,6 +77,8 @@ ignore:
|
|
|
69
77
|
- `arch.generated_manual_boundary` prevents changed files under explicitly
|
|
70
78
|
ignored generated paths from being accepted without regeneration or a config
|
|
71
79
|
change.
|
|
80
|
+
- `arch.no_forbidden_layer_dependencies` rejects import edges whose source
|
|
81
|
+
layer is not allowed to depend on the imported file's layer.
|
|
72
82
|
|
|
73
83
|
## Agent Integration
|
|
74
84
|
|
|
@@ -85,13 +95,15 @@ fails only on configured error rules.
|
|
|
85
95
|
|
|
86
96
|
## Language Support
|
|
87
97
|
|
|
88
|
-
The v1.3.
|
|
89
|
-
Java, Kotlin, and Swift files by path extension.
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
The v1.3.11 foundation classifies TypeScript, JavaScript, Rust, Python, Go,
|
|
99
|
+
Java, Kotlin, and Swift files by path extension. It extracts import facts for
|
|
100
|
+
TypeScript, JavaScript, Rust, Python, and Go, resolves relative imports and
|
|
101
|
+
simple repository-absolute aliases, and represents unresolved imports
|
|
102
|
+
explicitly as graph nodes instead of dropping them.
|
|
92
103
|
|
|
93
104
|
## Limitations
|
|
94
105
|
|
|
95
|
-
This release intentionally
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
This release intentionally validates direct file-level import edges. Manifest
|
|
107
|
+
extractors, cross-context public API boundaries, cycle detection, transitive
|
|
108
|
+
forbidden dependencies, SARIF output, and persistent scan caching remain planned
|
|
109
|
+
follow-up slices before v1.4.0.
|