@lamentis/naome 1.3.9 → 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 +10 -0
- package/bin/naome.js +1 -1
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/architecture_commands.rs +127 -0
- package/crates/naome-cli/src/cli_args.rs +4 -0
- package/crates/naome-cli/src/dispatcher.rs +2 -0
- package/crates/naome-cli/src/main.rs +6 -0
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/architecture/config/parser/scalar.rs +26 -0
- package/crates/naome-core/src/architecture/config/parser/sections.rs +154 -0
- package/crates/naome-core/src/architecture/config/parser.rs +97 -0
- package/crates/naome-core/src/architecture/config.rs +126 -0
- package/crates/naome-core/src/architecture/model.rs +80 -0
- package/crates/naome-core/src/architecture/output.rs +178 -0
- package/crates/naome-core/src/architecture/rules.rs +212 -0
- package/crates/naome-core/src/architecture/scan/graph_builder/emit.rs +118 -0
- package/crates/naome-core/src/architecture/scan/graph_builder/facts.rs +87 -0
- package/crates/naome-core/src/architecture/scan/graph_builder.rs +211 -0
- 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/path_scan.rs +92 -0
- package/crates/naome-core/src/architecture/scan.rs +95 -0
- package/crates/naome-core/src/architecture.rs +31 -0
- package/crates/naome-core/src/install_plan.rs +2 -0
- package/crates/naome-core/src/lib.rs +16 -8
- package/crates/naome-core/tests/architecture.rs +548 -0
- package/crates/naome-core/tests/harness_health.rs +1 -0
- package/installer/harness-files.js +3 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +7 -7
- package/templates/naome-root/.naome/bin/check-task-state.js +7 -7
- package/templates/naome-root/.naome/bin/naome.js +2 -2
- package/templates/naome-root/.naome/manifest.json +10 -8
- package/templates/naome-root/.naome/verification.json +15 -1
- package/templates/naome-root/docs/naome/architecture-fitness.md +109 -0
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/testing.md +6 -3
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
5
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
6
|
+
|
|
7
|
+
use naome_core::{
|
|
8
|
+
default_architecture_config_text, scan_architecture, validate_architecture, ArchitectureConfig,
|
|
9
|
+
ArchitectureEdgeKind, ArchitectureScanOptions,
|
|
10
|
+
};
|
|
11
|
+
|
|
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";
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn parses_starter_architecture_config() {
|
|
17
|
+
let config = ArchitectureConfig::parse(default_architecture_config_text(), "test").unwrap();
|
|
18
|
+
|
|
19
|
+
assert!(config.layers.contains_key("application"));
|
|
20
|
+
assert_eq!(config.rule("max_file_lines").value, Some(400));
|
|
21
|
+
assert_eq!(
|
|
22
|
+
config.ignore[0].reason,
|
|
23
|
+
"Generated code is not architecture-owned."
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[test]
|
|
28
|
+
fn path_extractor_builds_language_agnostic_graph_for_mixed_repo() {
|
|
29
|
+
let repo = FixtureRepo::new();
|
|
30
|
+
repo.write("src/domain/event.ts", "export const event = 1;\n");
|
|
31
|
+
repo.write("src/infrastructure/db.py", "client = object()\n");
|
|
32
|
+
repo.write("cmd/server/main.go", "package main\n");
|
|
33
|
+
repo.write(
|
|
34
|
+
"naome.arch.yaml",
|
|
35
|
+
r#"
|
|
36
|
+
layers:
|
|
37
|
+
domain:
|
|
38
|
+
paths:
|
|
39
|
+
- "src/domain/**"
|
|
40
|
+
infrastructure:
|
|
41
|
+
paths:
|
|
42
|
+
- "src/infrastructure/**"
|
|
43
|
+
contexts:
|
|
44
|
+
app:
|
|
45
|
+
paths:
|
|
46
|
+
- "src/**"
|
|
47
|
+
public_api:
|
|
48
|
+
- "src/index.ts"
|
|
49
|
+
rules:
|
|
50
|
+
max_file_lines:
|
|
51
|
+
enabled: true
|
|
52
|
+
value: 400
|
|
53
|
+
severity: warning
|
|
54
|
+
"#,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
let scan = scan_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
58
|
+
assert!(scan
|
|
59
|
+
.graph
|
|
60
|
+
.nodes
|
|
61
|
+
.iter()
|
|
62
|
+
.any(|node| node.id == "file:src/domain/event.ts"));
|
|
63
|
+
assert!(scan
|
|
64
|
+
.graph
|
|
65
|
+
.nodes
|
|
66
|
+
.iter()
|
|
67
|
+
.any(|node| node.id == "layer:domain"));
|
|
68
|
+
assert!(scan
|
|
69
|
+
.graph
|
|
70
|
+
.edges
|
|
71
|
+
.iter()
|
|
72
|
+
.any(|edge| { edge.from == "layer:domain" && edge.to == "file:src/domain/event.ts" }));
|
|
73
|
+
assert_eq!(
|
|
74
|
+
scan.file_facts
|
|
75
|
+
.get("cmd/server/main.go")
|
|
76
|
+
.unwrap()
|
|
77
|
+
.language
|
|
78
|
+
.as_deref(),
|
|
79
|
+
Some("go")
|
|
80
|
+
);
|
|
81
|
+
}
|
|
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
|
+
|
|
420
|
+
#[test]
|
|
421
|
+
fn validates_file_size_budget_with_stable_json_shape() {
|
|
422
|
+
let repo = FixtureRepo::new();
|
|
423
|
+
repo.write(
|
|
424
|
+
"naome.arch.yaml",
|
|
425
|
+
r#"
|
|
426
|
+
rules:
|
|
427
|
+
max_file_lines:
|
|
428
|
+
enabled: true
|
|
429
|
+
value: 2
|
|
430
|
+
severity: error
|
|
431
|
+
"#,
|
|
432
|
+
);
|
|
433
|
+
repo.write("src/too_big.rs", "one\ntwo\nthree\n");
|
|
434
|
+
|
|
435
|
+
let report = validate_architecture(repo.path(), ArchitectureScanOptions::default()).unwrap();
|
|
436
|
+
let json = serde_json::to_string(&report).unwrap();
|
|
437
|
+
|
|
438
|
+
assert_eq!(report.status, "fail");
|
|
439
|
+
assert!(json.contains("\"schema\":\"naome.arch.validation.v1\""));
|
|
440
|
+
assert!(json.contains("\"id\":\"arch.max_file_lines\""));
|
|
441
|
+
let violation = report
|
|
442
|
+
.violations
|
|
443
|
+
.iter()
|
|
444
|
+
.find(|violation| violation.path.as_deref() == Some("src/too_big.rs"))
|
|
445
|
+
.expect("expected src/too_big.rs file-size violation");
|
|
446
|
+
assert_eq!(violation.agent_instruction, "Reduce src/too_big.rs below 2 lines or add a justified generated-code ignore rule if it is not manually owned.");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#[test]
|
|
450
|
+
fn changed_only_generated_boundary_reports_changed_generated_file() {
|
|
451
|
+
let repo = FixtureRepo::new();
|
|
452
|
+
repo.init_git();
|
|
453
|
+
repo.write(
|
|
454
|
+
"naome.arch.yaml",
|
|
455
|
+
r#"
|
|
456
|
+
rules:
|
|
457
|
+
generated_manual_boundary:
|
|
458
|
+
enabled: true
|
|
459
|
+
severity: error
|
|
460
|
+
ignore:
|
|
461
|
+
- path: "generated/**"
|
|
462
|
+
reason: "Generated code is not manually edited."
|
|
463
|
+
"#,
|
|
464
|
+
);
|
|
465
|
+
repo.write("generated/client.ts", "export const generated = true;\n");
|
|
466
|
+
|
|
467
|
+
let report = validate_architecture(
|
|
468
|
+
repo.path(),
|
|
469
|
+
ArchitectureScanOptions {
|
|
470
|
+
changed_only: true,
|
|
471
|
+
config_path: None,
|
|
472
|
+
},
|
|
473
|
+
)
|
|
474
|
+
.unwrap();
|
|
475
|
+
|
|
476
|
+
assert_eq!(report.status, "fail");
|
|
477
|
+
assert!(report.changed_only_degraded_to_full_scan);
|
|
478
|
+
assert_eq!(report.violations[0].id, "arch.generated_manual_boundary");
|
|
479
|
+
assert_eq!(
|
|
480
|
+
report.violations[0].path.as_deref(),
|
|
481
|
+
Some("generated/client.ts")
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
struct FixtureRepo {
|
|
486
|
+
root: PathBuf,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
impl FixtureRepo {
|
|
490
|
+
fn new() -> Self {
|
|
491
|
+
let nonce = SystemTime::now()
|
|
492
|
+
.duration_since(UNIX_EPOCH)
|
|
493
|
+
.unwrap()
|
|
494
|
+
.as_nanos();
|
|
495
|
+
let counter = FIXTURE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
496
|
+
let root = std::env::temp_dir().join(format!(
|
|
497
|
+
"naome-arch-fixture-{}-{nonce}-{counter}",
|
|
498
|
+
std::process::id()
|
|
499
|
+
));
|
|
500
|
+
fs::create_dir_all(&root).unwrap();
|
|
501
|
+
fs::write(
|
|
502
|
+
root.join(".naomeignore"),
|
|
503
|
+
".naome/archive/\n.naome/tasks/\n",
|
|
504
|
+
)
|
|
505
|
+
.unwrap();
|
|
506
|
+
Self { root }
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn path(&self) -> &Path {
|
|
510
|
+
&self.root
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn write(&self, relative_path: &str, content: &str) {
|
|
514
|
+
let path = self.root.join(relative_path);
|
|
515
|
+
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
516
|
+
fs::write(path, content).unwrap();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
fn init_git(&self) {
|
|
520
|
+
run_git(&self.root, &["init"]);
|
|
521
|
+
run_git(&self.root, &["config", "user.email", "naome@example.com"]);
|
|
522
|
+
run_git(&self.root, &["config", "user.name", "NAOME Test"]);
|
|
523
|
+
self.write("README.md", "# Fixture\n");
|
|
524
|
+
run_git(&self.root, &["add", "."]);
|
|
525
|
+
run_git(&self.root, &["commit", "-m", "baseline"]);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
fn run_git(root: &Path, args: &[&str]) {
|
|
530
|
+
let result = Command::new("git")
|
|
531
|
+
.args(args)
|
|
532
|
+
.current_dir(root)
|
|
533
|
+
.output()
|
|
534
|
+
.unwrap();
|
|
535
|
+
assert!(
|
|
536
|
+
result.status.success(),
|
|
537
|
+
"git {:?} failed: {}{}",
|
|
538
|
+
args,
|
|
539
|
+
String::from_utf8_lossy(&result.stdout),
|
|
540
|
+
String::from_utf8_lossy(&result.stderr)
|
|
541
|
+
);
|
|
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
|
+
}
|
|
@@ -19,6 +19,7 @@ const MACHINE_OWNED_PATHS: &[&str] = &[
|
|
|
19
19
|
"docs/naome/first-run.md",
|
|
20
20
|
"docs/naome/agent-workflow.md",
|
|
21
21
|
"docs/naome/context-economy.md",
|
|
22
|
+
"docs/naome/architecture-fitness.md",
|
|
22
23
|
"docs/naome/execution.md",
|
|
23
24
|
"docs/naome/task-ledger.md",
|
|
24
25
|
"docs/naome/upgrade.md",
|
|
@@ -22,6 +22,7 @@ export function ensureCoreHarnessFiles(ctx, archiveDirName) {
|
|
|
22
22
|
replaceHarnessFile(ctx, "docs/naome/task-ledger.md", archiveDirName);
|
|
23
23
|
ensureTemplateFile(ctx, "docs/naome/repository-model.md");
|
|
24
24
|
ensureTemplateFile(ctx, "docs/naome/security.md");
|
|
25
|
+
ensureTemplateFile(ctx, "docs/naome/architecture-fitness.md");
|
|
25
26
|
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -39,6 +40,7 @@ export function ensureTaskControlHarnessFiles(ctx, archiveDirName) {
|
|
|
39
40
|
replaceHarnessFile(ctx, "docs/naome/agent-workflow.md", archiveDirName);
|
|
40
41
|
replaceHarnessFile(ctx, "docs/naome/context-economy.md", archiveDirName);
|
|
41
42
|
replaceHarnessFile(ctx, "docs/naome/execution.md", archiveDirName);
|
|
43
|
+
ensureTemplateFile(ctx, "docs/naome/architecture-fitness.md");
|
|
42
44
|
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -57,6 +59,7 @@ export function ensureHarnessHealthFiles(ctx, archiveDirName) {
|
|
|
57
59
|
replaceHarnessFile(ctx, "docs/naome/context-economy.md", archiveDirName);
|
|
58
60
|
replaceHarnessFile(ctx, "docs/naome/execution.md", archiveDirName);
|
|
59
61
|
ensureTemplateFile(ctx, "docs/naome/security.md");
|
|
62
|
+
ensureTemplateFile(ctx, "docs/naome/architecture-fitness.md");
|
|
60
63
|
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
61
64
|
ensureNaomeIgnore(ctx);
|
|
62
65
|
}
|
|
Binary file
|
package/native/linux-x64/naome
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -10,18 +10,18 @@ const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
|
|
|
10
10
|
const expectedNativeBinaryIntegrity = "sha256:generated";
|
|
11
11
|
|
|
12
12
|
const expectedMachineOwnedIntegrity = Object.freeze({
|
|
13
|
-
"AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
|
|
14
|
-
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
15
|
-
".naome/bin/naome.js": "sha256:a34c2e50a68d15ff2722a57590ccddc504e025d3c98ea62fd700d7ec1a789b9a",
|
|
16
|
-
".naome/bin/check-task-state.js": "sha256:2612577b7e4ab45d9d39dd5ac54c8e7ed749d237d78f1a8d252f4dfa0b4eaaab",
|
|
17
13
|
".naome/bin/check-harness-health.js": "sha256:802d7419774981a6af1826b3882270ff8f41259d516f98c52a02b4ddc184c467",
|
|
14
|
+
".naome/bin/check-task-state.js": "sha256:2612577b7e4ab45d9d39dd5ac54c8e7ed749d237d78f1a8d252f4dfa0b4eaaab",
|
|
15
|
+
".naome/bin/naome.js": "sha256:f129c580fb70b3a1d92d0ab0de9fa6c131b2fc51a8dcdebe9d6f5a812a3be61b",
|
|
16
|
+
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
18
17
|
".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
|
|
19
|
-
"
|
|
20
|
-
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
18
|
+
"AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
|
|
21
19
|
"docs/naome/agent-workflow.md": "sha256:0be1c29adfbcd3fd73c4f904080ffc67237692fe413871a30243538c4db38ac7",
|
|
22
20
|
"docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
|
|
23
|
-
"docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
|
|
24
21
|
"docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
|
|
22
|
+
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
23
|
+
"docs/naome/index.md": "sha256:cac748ed375d86d288460456fc5d606b29bd99d91148522c303d5400a083dbc5",
|
|
24
|
+
"docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
|
|
25
25
|
"docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -10,18 +10,18 @@ const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
|
|
|
10
10
|
const expectedNativeBinaryIntegrity = "sha256:generated";
|
|
11
11
|
|
|
12
12
|
const expectedMachineOwnedIntegrity = Object.freeze({
|
|
13
|
-
"AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
|
|
14
|
-
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
15
|
-
".naome/bin/naome.js": "sha256:a34c2e50a68d15ff2722a57590ccddc504e025d3c98ea62fd700d7ec1a789b9a",
|
|
16
|
-
".naome/bin/check-task-state.js": "sha256:2612577b7e4ab45d9d39dd5ac54c8e7ed749d237d78f1a8d252f4dfa0b4eaaab",
|
|
17
13
|
".naome/bin/check-harness-health.js": "sha256:802d7419774981a6af1826b3882270ff8f41259d516f98c52a02b4ddc184c467",
|
|
14
|
+
".naome/bin/check-task-state.js": "sha256:2612577b7e4ab45d9d39dd5ac54c8e7ed749d237d78f1a8d252f4dfa0b4eaaab",
|
|
15
|
+
".naome/bin/naome.js": "sha256:f129c580fb70b3a1d92d0ab0de9fa6c131b2fc51a8dcdebe9d6f5a812a3be61b",
|
|
16
|
+
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
18
17
|
".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
|
|
19
|
-
"
|
|
20
|
-
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
18
|
+
"AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
|
|
21
19
|
"docs/naome/agent-workflow.md": "sha256:0be1c29adfbcd3fd73c4f904080ffc67237692fe413871a30243538c4db38ac7",
|
|
22
20
|
"docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
|
|
23
|
-
"docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
|
|
24
21
|
"docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
|
|
22
|
+
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
23
|
+
"docs/naome/index.md": "sha256:cac748ed375d86d288460456fc5d606b29bd99d91148522c303d5400a083dbc5",
|
|
24
|
+
"docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
|
|
25
25
|
"docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -27,7 +27,7 @@ function main(argv) {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (["status", "next", "intent", "route", "explain", "context", "doctor", "task", "quality", "semantic", "repo", "structure", "cleanup", "refresh-integrity", "workflow"].includes(command)) {
|
|
30
|
+
if (["status", "next", "intent", "route", "explain", "context", "doctor", "task", "quality", "semantic", "arch", "repo", "structure", "cleanup", "refresh-integrity", "workflow"].includes(command)) {
|
|
31
31
|
runNativeDecisionCommand(command, args);
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
@@ -367,7 +367,7 @@ function findAncestorWithAnyMarker(startPath, markers) {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
function printHelp() {
|
|
370
|
-
const commands = "naome status [--json]|naome next [--json]|naome intent --prompt-file <path> [--json]|naome intent --prompt <text> [--json]|naome route --prompt-file <path> [--execute] [--json]|naome route --prompt <text> [--execute] [--json]|naome explain --prompt-file <path> [--json]|naome explain --prompt <text> [--json]|naome context select --changed [--json]|naome context select --prompt-file <path> [--json]|naome context select --prompt <text> [--json]|naome doctor [--json]|naome task render-state [--write] [--json]|naome task migrate-ledger [--write] [--json]|naome quality init [--baseline|--deep-baseline] [--json]|naome quality check --changed [--include-scanned-paths] [--json]|naome quality check --path <path> [--path <path>...] [--include-scanned-paths] [--json]|naome quality report [--deep] [--include-scanned-paths] [--json]|naome quality cache status [--json]|naome quality cache clear|naome semantic report [--deep] [--json]|naome semantic check --changed [--json]|naome semantic check --path <path> [--path <path>...] [--json]|naome semantic route --finding <id> [--json]|naome semantic loop [--json]|naome repo model [--write] [--json]|naome repo check [--json]|naome repo explain --path <path> [--json]|naome structure report [--json]|naome structure explain --path <path> [--json]|naome cleanup plan [--json]|naome cleanup route --path <path> [--json]|naome refresh-integrity [--json]|naome workflow agent-plan|context-delta|proof-plan|capabilities|edit-watchdog|decision-gate|digest [--json]|naome workflow search-profile|check-search|phases|processes|mutations [--json]|naome install|naome sync|naome commit -m \"type(scope): message\"|node .naome/bin/naome.js commit -m \"type(scope): message\"".split("|");
|
|
370
|
+
const commands = "naome status [--json]|naome next [--json]|naome intent --prompt-file <path> [--json]|naome intent --prompt <text> [--json]|naome route --prompt-file <path> [--execute] [--json]|naome route --prompt <text> [--execute] [--json]|naome explain --prompt-file <path> [--json]|naome explain --prompt <text> [--json]|naome context select --changed [--json]|naome context select --prompt-file <path> [--json]|naome context select --prompt <text> [--json]|naome doctor [--json]|naome task render-state [--write] [--json]|naome task migrate-ledger [--write] [--json]|naome quality init [--baseline|--deep-baseline] [--json]|naome quality check --changed [--include-scanned-paths] [--json]|naome quality check --path <path> [--path <path>...] [--include-scanned-paths] [--json]|naome quality report [--deep] [--include-scanned-paths] [--json]|naome quality cache status [--json]|naome quality cache clear|naome semantic report [--deep] [--json]|naome semantic check --changed [--json]|naome semantic check --path <path> [--path <path>...] [--json]|naome semantic route --finding <id> [--json]|naome semantic loop [--json]|naome arch init [--config <path>] [--json]|naome arch explain [--config <path>] [--json]|naome arch scan [--config <path>] [--changed-only] [--write] [--output <path>] [--json]|naome arch validate [--config <path>] [--changed-only] [--json|--agent-feedback]|naome repo model [--write] [--json]|naome repo check [--json]|naome repo explain --path <path> [--json]|naome structure report [--json]|naome structure explain --path <path> [--json]|naome cleanup plan [--json]|naome cleanup route --path <path> [--json]|naome refresh-integrity [--json]|naome workflow agent-plan|context-delta|proof-plan|capabilities|edit-watchdog|decision-gate|digest [--json]|naome workflow search-profile|check-search|phases|processes|mutations [--json]|naome install|naome sync|naome commit -m \"type(scope): message\"|node .naome/bin/naome.js commit -m \"type(scope): message\"".split("|");
|
|
371
371
|
console.log(["Usage:", ...commands.map((command) => ` ${command}`)].join("\n"));
|
|
372
372
|
}
|
|
373
373
|
|