@lamentis/naome 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2 -2
- package/README.md +11 -2
- package/bin/naome.js +62 -24
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/context_commands.rs +47 -0
- package/crates/naome-cli/src/dispatcher.rs +6 -0
- package/crates/naome-cli/src/main.rs +43 -6
- package/crates/naome-cli/src/quality_commands.rs +31 -46
- package/crates/naome-cli/src/quality_output.rs +34 -0
- package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
- package/crates/naome-cli/src/repository_model_commands.rs +84 -0
- package/crates/naome-cli/src/task_commands.rs +62 -0
- package/crates/naome-cli/src/workflow_commands.rs +100 -3
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/context/helpers.rs +75 -0
- package/crates/naome-core/src/context/select.rs +134 -0
- package/crates/naome-core/src/context/types.rs +43 -0
- package/crates/naome-core/src/context.rs +6 -0
- package/crates/naome-core/src/decision/states.rs +1 -1
- package/crates/naome-core/src/decision.rs +4 -1
- package/crates/naome-core/src/install_plan.rs +18 -0
- package/crates/naome-core/src/journal.rs +2 -7
- package/crates/naome-core/src/lib.rs +33 -10
- package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
- package/crates/naome-core/src/quality/adapter_support.rs +67 -0
- package/crates/naome-core/src/quality/adapters.rs +81 -18
- package/crates/naome-core/src/quality/cache.rs +7 -9
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +4 -7
- package/crates/naome-core/src/quality/config.rs +21 -3
- package/crates/naome-core/src/quality/mod.rs +138 -7
- package/crates/naome-core/src/quality/reconcile.rs +138 -0
- package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
- package/crates/naome-core/src/quality/scanner/analysis.rs +20 -5
- package/crates/naome-core/src/quality/scanner.rs +62 -17
- package/crates/naome-core/src/quality/semantic/checks.rs +17 -0
- package/crates/naome-core/src/quality/semantic/route.rs +1 -1
- package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
- package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
- package/crates/naome-core/src/quality/structure/checks/directory.rs +6 -4
- package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
- package/crates/naome-core/src/quality/structure/config.rs +24 -3
- package/crates/naome-core/src/quality/structure/mod.rs +3 -0
- package/crates/naome-core/src/quality/types.rs +20 -1
- package/crates/naome-core/src/repository_model/detect.rs +188 -0
- package/crates/naome-core/src/repository_model/explain.rs +121 -0
- package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
- package/crates/naome-core/src/repository_model/path_support.rs +59 -0
- package/crates/naome-core/src/repository_model/types.rs +152 -0
- package/crates/naome-core/src/repository_model/world.rs +48 -0
- package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
- package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
- package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
- package/crates/naome-core/src/repository_model.rs +164 -0
- package/crates/naome-core/src/route/builtin_checks.rs +40 -1
- package/crates/naome-core/src/task_ledger/import.rs +142 -0
- package/crates/naome-core/src/task_ledger/model.rs +13 -0
- package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
- package/crates/naome-core/src/task_ledger/read.rs +118 -0
- package/crates/naome-core/src/task_ledger/render.rs +55 -0
- package/crates/naome-core/src/task_ledger/write.rs +38 -0
- package/crates/naome-core/src/task_ledger.rs +48 -0
- package/crates/naome-core/src/task_state/api.rs +4 -2
- package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
- package/crates/naome-core/src/task_state/diff.rs +2 -2
- package/crates/naome-core/src/task_state/evidence.rs +8 -3
- package/crates/naome-core/src/task_state/mod.rs +1 -1
- package/crates/naome-core/src/task_state/progress.rs +13 -0
- package/crates/naome-core/src/task_state/proof_model.rs +8 -8
- package/crates/naome-core/src/task_state/repair.rs +2 -2
- package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
- package/crates/naome-core/src/task_state/types.rs +24 -0
- package/crates/naome-core/src/verification.rs +29 -18
- package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
- package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
- package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
- package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
- package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
- package/crates/naome-core/src/workflow/agent/support.rs +58 -0
- package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
- package/crates/naome-core/src/workflow/agent.rs +34 -0
- package/crates/naome-core/src/workflow/agent_types.rs +105 -0
- package/crates/naome-core/src/workflow/doctor.rs +39 -0
- package/crates/naome-core/src/workflow/mod.rs +11 -0
- package/crates/naome-core/src/workflow/output.rs +8 -2
- package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
- package/crates/naome-core/tests/context.rs +99 -0
- package/crates/naome-core/tests/harness_health.rs +4 -0
- package/crates/naome-core/tests/install_plan.rs +12 -0
- package/crates/naome-core/tests/quality.rs +178 -2
- package/crates/naome-core/tests/quality_performance.rs +39 -2
- package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
- package/crates/naome-core/tests/repo_support/mod.rs +5 -1
- package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
- package/crates/naome-core/tests/repository_model.rs +281 -0
- package/crates/naome-core/tests/route_user_diff.rs +49 -1
- package/crates/naome-core/tests/semantic_legacy.rs +72 -38
- package/crates/naome-core/tests/task_ledger.rs +328 -0
- package/crates/naome-core/tests/task_state.rs +28 -0
- package/crates/naome-core/tests/verification.rs +29 -36
- package/crates/naome-core/tests/workflow_agent.rs +233 -0
- package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
- package/crates/naome-core/tests/workflow_doctor.rs +21 -0
- package/installer/codex-hooks.js +121 -0
- package/installer/context.js +10 -0
- package/installer/filesystem.js +4 -0
- package/installer/flows.js +8 -4
- package/installer/harness-files.js +6 -0
- package/installer/install-plan.js +4 -0
- package/installer/main.js +1 -1
- package/installer/native.js +1 -1
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.codex/config.toml +2 -0
- package/templates/naome-root/.codex/hooks.json +70 -0
- package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
- package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
- package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
- package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
- package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
- package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
- package/templates/naome-root/.naome/bin/naome.js +35 -4
- package/templates/naome-root/.naome/manifest.json +12 -6
- package/templates/naome-root/.naome/repository-model.json +6 -0
- package/templates/naome-root/.naome/repository-quality.json +3 -1
- package/templates/naome-root/.naome/verification.json +15 -1
- package/templates/naome-root/AGENTS.md +38 -83
- package/templates/naome-root/docs/naome/agent-workflow.md +54 -18
- package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
- package/templates/naome-root/docs/naome/context-economy.md +73 -0
- package/templates/naome-root/docs/naome/first-run.md +25 -14
- package/templates/naome-root/docs/naome/index.md +18 -10
- package/templates/naome-root/docs/naome/repository-model.md +92 -0
- package/templates/naome-root/docs/naome/repository-quality.md +47 -7
- package/templates/naome-root/docs/naome/repository-structure.md +10 -3
- package/templates/naome-root/docs/naome/task-ledger.md +71 -0
- package/templates/naome-root/docs/naome/testing.md +16 -3
|
@@ -3,8 +3,9 @@ mod repo_support;
|
|
|
3
3
|
use std::fs;
|
|
4
4
|
|
|
5
5
|
use naome_core::{
|
|
6
|
-
check_repository_quality,
|
|
7
|
-
init_repository_quality_with_mode, quality_cache_status,
|
|
6
|
+
check_repository_quality, check_repository_quality_paths, check_semantic_legacy,
|
|
7
|
+
init_repository_quality, init_repository_quality_with_mode, quality_cache_status,
|
|
8
|
+
QualityInitMode, QualityMode,
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
use repo_support::TestRepo;
|
|
@@ -67,6 +68,42 @@ fn changed_fast_scans_only_changed_file_contents() {
|
|
|
67
68
|
assert!(report.changed_paths.contains(&"src/changed.js".to_string()));
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
#[test]
|
|
72
|
+
fn path_scoped_quality_scans_only_requested_file_after_write() {
|
|
73
|
+
let repo = quality_repo("quality-path-scoped-focused");
|
|
74
|
+
repo.write_file("src/target.js", "export const before = 1;\n");
|
|
75
|
+
repo.write_file("src/unrelated.js", "export const before = 1;\n");
|
|
76
|
+
repo.commit_all("baseline");
|
|
77
|
+
repo.write_file("src/target.js", &large_file());
|
|
78
|
+
repo.write_file("src/unrelated.js", &large_file());
|
|
79
|
+
|
|
80
|
+
let report = check_repository_quality_paths(repo.path(), &["src/target.js"]).unwrap();
|
|
81
|
+
|
|
82
|
+
assert_eq!(report.mode, "path");
|
|
83
|
+
assert_eq!(report.changed_paths, vec!["src/target.js"]);
|
|
84
|
+
assert_eq!(report.summary.scanned_files, 1);
|
|
85
|
+
assert_eq!(report.summary.scanned_path_count, 1);
|
|
86
|
+
assert!(report
|
|
87
|
+
.violations
|
|
88
|
+
.iter()
|
|
89
|
+
.any(|violation| violation.check_id == "file-length" && violation.path == "src/target.js"));
|
|
90
|
+
assert!(!report
|
|
91
|
+
.violations
|
|
92
|
+
.iter()
|
|
93
|
+
.any(|violation| violation.path == "src/unrelated.js"));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#[test]
|
|
97
|
+
fn path_scoped_quality_rejects_paths_outside_repository() {
|
|
98
|
+
let repo = quality_repo("quality-path-scoped-boundary");
|
|
99
|
+
repo.write_file("src/target.js", "export const before = 1;\n");
|
|
100
|
+
repo.commit_all("baseline");
|
|
101
|
+
|
|
102
|
+
let error = check_repository_quality_paths(repo.path(), &["../outside.js"]).unwrap_err();
|
|
103
|
+
|
|
104
|
+
assert!(error.to_string().contains("repository-relative path"));
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
#[test]
|
|
71
108
|
fn semantic_changed_check_scans_only_changed_file_contents() {
|
|
72
109
|
let repo = quality_repo("semantic-changed-fast-focused");
|
|
@@ -40,6 +40,45 @@ fn javascript_typescript_adapter_recognizes_package_source_and_tests() {
|
|
|
40
40
|
assert_eq!(test.role, "test");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
#[test]
|
|
44
|
+
fn ios_swift_adapters_recognize_xcode_swiftui_tests_resources_and_generated_code() {
|
|
45
|
+
let repo = StructureFixture::new("structure-ios-swift-adapters");
|
|
46
|
+
repo.write(
|
|
47
|
+
"Package.swift",
|
|
48
|
+
"// swift-tools-version: 5.9\nimport PackageDescription\n",
|
|
49
|
+
);
|
|
50
|
+
repo.write(
|
|
51
|
+
"Demo.xcodeproj/project.pbxproj",
|
|
52
|
+
"// !$*UTF8*$!\n{ archiveVersion = 1; }\n",
|
|
53
|
+
);
|
|
54
|
+
repo.write(
|
|
55
|
+
"Sources/App/ContentView.swift",
|
|
56
|
+
"import SwiftUI\nstruct ContentView: View {}\n",
|
|
57
|
+
);
|
|
58
|
+
repo.write(
|
|
59
|
+
"Tests/AppTests/ContentViewTests.swift",
|
|
60
|
+
"import XCTest\nfinal class ContentViewTests: XCTestCase {}\n",
|
|
61
|
+
);
|
|
62
|
+
repo.write("Resources/Info.plist", "<?xml version=\"1.0\"?>\n");
|
|
63
|
+
repo.write("Resources/Assets.xcassets/Contents.json", "{}\n");
|
|
64
|
+
repo.write("Generated/R.generated.swift", "enum R {}\n");
|
|
65
|
+
|
|
66
|
+
let source =
|
|
67
|
+
explain_repository_structure(repo.path(), "Sources/App/ContentView.swift").unwrap();
|
|
68
|
+
let test =
|
|
69
|
+
explain_repository_structure(repo.path(), "Tests/AppTests/ContentViewTests.swift").unwrap();
|
|
70
|
+
let plist = explain_repository_structure(repo.path(), "Resources/Info.plist").unwrap();
|
|
71
|
+
let generated =
|
|
72
|
+
explain_repository_structure(repo.path(), "Generated/R.generated.swift").unwrap();
|
|
73
|
+
|
|
74
|
+
assert_eq!(source.role, "source");
|
|
75
|
+
assert_eq!(source.module.as_deref(), Some("App"));
|
|
76
|
+
assert_eq!(source.language.as_deref(), Some("swift"));
|
|
77
|
+
assert_eq!(test.role, "test");
|
|
78
|
+
assert_eq!(plist.role, "config");
|
|
79
|
+
assert_eq!(generated.role, "generated");
|
|
80
|
+
}
|
|
81
|
+
|
|
43
82
|
#[test]
|
|
44
83
|
fn unknown_repository_gets_generic_roles() {
|
|
45
84
|
let repo = StructureFixture::new("structure-generic-roles");
|
|
@@ -13,4 +13,8 @@ pub use routes::{
|
|
|
13
13
|
route_commit_request, route_new_task, route_readme_task, try_route_new_task,
|
|
14
14
|
try_route_readme_task,
|
|
15
15
|
};
|
|
16
|
-
pub use verification_values::{
|
|
16
|
+
pub use verification_values::{
|
|
17
|
+
change_type, diff_check, quality_check, repository_quality_config_source,
|
|
18
|
+
repository_quality_config_value, semantic_repository_quality_fixture_source,
|
|
19
|
+
verification_value,
|
|
20
|
+
};
|
|
@@ -73,6 +73,35 @@ pub fn verification_value(
|
|
|
73
73
|
})
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
pub fn repository_quality_config_value() -> serde_json::Value {
|
|
77
|
+
json!({
|
|
78
|
+
"schema": "naome.repository-quality.v1",
|
|
79
|
+
"version": 1,
|
|
80
|
+
"status": "ready",
|
|
81
|
+
"limits": {
|
|
82
|
+
"maxFileLines": 450,
|
|
83
|
+
"maxNewFileLines": 300,
|
|
84
|
+
"maxDiffAddedLines": 180,
|
|
85
|
+
"maxFunctionLines": 100,
|
|
86
|
+
"maxTopLevelSymbols": 30,
|
|
87
|
+
"duplicateBlockLines": 10,
|
|
88
|
+
"nearDuplicateSimilarity": 0.9
|
|
89
|
+
},
|
|
90
|
+
"enabledAdapters": [],
|
|
91
|
+
"disabledChecks": [],
|
|
92
|
+
"ignoredPaths": [],
|
|
93
|
+
"generatedPaths": [],
|
|
94
|
+
"pathRules": []
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub fn repository_quality_config_source() -> String {
|
|
99
|
+
format!(
|
|
100
|
+
"{}\n",
|
|
101
|
+
serde_json::to_string_pretty(&repository_quality_config_value()).unwrap()
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
pub fn change_type(
|
|
77
106
|
id: &str,
|
|
78
107
|
description: &str,
|
|
@@ -121,6 +150,32 @@ pub fn repository_quality_check() -> serde_json::Value {
|
|
|
121
150
|
})
|
|
122
151
|
}
|
|
123
152
|
|
|
153
|
+
pub fn semantic_repository_quality_fixture_source(name: &str) -> String {
|
|
154
|
+
[
|
|
155
|
+
format!("const {name} = {{"),
|
|
156
|
+
" schema: \"naome.repository-quality.v1\",".to_string(),
|
|
157
|
+
" version: 1,".to_string(),
|
|
158
|
+
" status: \"ready\",".to_string(),
|
|
159
|
+
" limits: {".to_string(),
|
|
160
|
+
" maxFileLines: 450,".to_string(),
|
|
161
|
+
" maxNewFileLines: 300,".to_string(),
|
|
162
|
+
" maxDiffAddedLines: 180,".to_string(),
|
|
163
|
+
" maxFunctionLines: 100,".to_string(),
|
|
164
|
+
" maxTopLevelSymbols: 30,".to_string(),
|
|
165
|
+
" duplicateBlockLines: 10,".to_string(),
|
|
166
|
+
" nearDuplicateSimilarity: 0.9".to_string(),
|
|
167
|
+
" },".to_string(),
|
|
168
|
+
" enabledAdapters: [],".to_string(),
|
|
169
|
+
" disabledChecks: [],".to_string(),
|
|
170
|
+
" ignoredPaths: [],".to_string(),
|
|
171
|
+
" generatedPaths: [],".to_string(),
|
|
172
|
+
" pathRules: []".to_string(),
|
|
173
|
+
"};".to_string(),
|
|
174
|
+
String::new(),
|
|
175
|
+
]
|
|
176
|
+
.join("\n")
|
|
177
|
+
}
|
|
178
|
+
|
|
124
179
|
pub fn mutating_diff_check() -> serde_json::Value {
|
|
125
180
|
json!({
|
|
126
181
|
"id": "diff-check",
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
5
|
+
|
|
6
|
+
use naome_core::{explain_repository_model_path, refresh_repository_model, repository_model_drift};
|
|
7
|
+
|
|
8
|
+
static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
9
|
+
|
|
10
|
+
#[test]
|
|
11
|
+
fn refresh_write_captures_deterministic_repository_facts() {
|
|
12
|
+
let repo = Repo::new("repository-model-refresh");
|
|
13
|
+
repo.write(
|
|
14
|
+
"Cargo.toml",
|
|
15
|
+
"[package]\nname = \"demo\"\nversion = \"0.1.0\"\n",
|
|
16
|
+
);
|
|
17
|
+
repo.write("package.json", "{\"scripts\":{\"test\":\"node --test\"}}\n");
|
|
18
|
+
repo.write("src/lib.rs", "pub fn run() {}\n");
|
|
19
|
+
repo.write("tests/app.test.ts", "test('works', () => {});\n");
|
|
20
|
+
repo.write(
|
|
21
|
+
".naome/verification.json",
|
|
22
|
+
"{\"checks\":[{\"id\":\"unit-test\",\"command\":\"cargo test\",\"cwd\":\".\",\"evidence\":[\"src/lib.rs\"]}]}\n",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
let report = refresh_repository_model(repo.path(), true).unwrap();
|
|
26
|
+
|
|
27
|
+
assert!(report.ok);
|
|
28
|
+
assert_eq!(report.updated_paths, vec![".naome/repository-model.json"]);
|
|
29
|
+
assert_fact(&report.model, "language", "rust");
|
|
30
|
+
assert_fact(&report.model, "language", "javascript-typescript");
|
|
31
|
+
assert_fact(&report.model, "packageManager", "cargo");
|
|
32
|
+
assert_fact(&report.model, "packageManager", "npm");
|
|
33
|
+
assert_fact(&report.model, "sourceRoot", "src");
|
|
34
|
+
assert_fact(&report.model, "testRoot", "tests");
|
|
35
|
+
assert_fact(&report.model, "verificationCheck", "unit-test");
|
|
36
|
+
let js_fact = fact(&report.model, "language", "javascript-typescript");
|
|
37
|
+
assert_eq!(js_fact.evidence, vec!["package.json", "tests/app.test.ts"]);
|
|
38
|
+
assert!(repo.path().join(".naome/repository-model.json").is_file());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn refresh_write_prefers_v2_world_model_sections() {
|
|
43
|
+
let repo = Repo::new("repository-world-model-v2");
|
|
44
|
+
repo.write(
|
|
45
|
+
"packages/mobile/Package.swift",
|
|
46
|
+
"// swift-tools-version: 5.9\n",
|
|
47
|
+
);
|
|
48
|
+
repo.write("packages/mobile/Sources/App/App.swift", "import SwiftUI\n");
|
|
49
|
+
repo.write(
|
|
50
|
+
"packages/mobile/Tests/AppTests/AppTests.swift",
|
|
51
|
+
"import XCTest\n",
|
|
52
|
+
);
|
|
53
|
+
repo.write(
|
|
54
|
+
"packages/web/package.json",
|
|
55
|
+
"{\"scripts\":{\"test\":\"vitest\"}}\n",
|
|
56
|
+
);
|
|
57
|
+
repo.write(
|
|
58
|
+
"packages/web/src/App.tsx",
|
|
59
|
+
"export function App() { return null; }\n",
|
|
60
|
+
);
|
|
61
|
+
repo.write(
|
|
62
|
+
"packages/web/src/App.test.tsx",
|
|
63
|
+
"test('works', () => {});\n",
|
|
64
|
+
);
|
|
65
|
+
repo.write(
|
|
66
|
+
".naome/verification.json",
|
|
67
|
+
"{\"checks\":[{\"id\":\"unit-test\",\"command\":\"npm test\",\"cwd\":\".\",\"evidence\":[\"packages/web/src/App.tsx\"]}]}\n",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
let report = refresh_repository_model(repo.path(), true).unwrap();
|
|
71
|
+
|
|
72
|
+
assert!(report.ok);
|
|
73
|
+
assert_eq!(report.model.schema, "naome.repository-model.v2");
|
|
74
|
+
assert_eq!(report.model.version, 2);
|
|
75
|
+
assert!(report
|
|
76
|
+
.model
|
|
77
|
+
.languages
|
|
78
|
+
.iter()
|
|
79
|
+
.any(|language| language.id == "swift"));
|
|
80
|
+
assert!(report
|
|
81
|
+
.model
|
|
82
|
+
.languages
|
|
83
|
+
.iter()
|
|
84
|
+
.any(|language| language.id == "javascript-typescript"));
|
|
85
|
+
assert!(report
|
|
86
|
+
.model
|
|
87
|
+
.adapters
|
|
88
|
+
.iter()
|
|
89
|
+
.any(|adapter| adapter.id == "swiftui"));
|
|
90
|
+
assert!(report
|
|
91
|
+
.model
|
|
92
|
+
.entities
|
|
93
|
+
.iter()
|
|
94
|
+
.any(|entity| entity.id == "package:packages/mobile"));
|
|
95
|
+
assert!(report
|
|
96
|
+
.model
|
|
97
|
+
.entities
|
|
98
|
+
.iter()
|
|
99
|
+
.any(|entity| entity.id == "package:packages/web"));
|
|
100
|
+
assert!(report
|
|
101
|
+
.model
|
|
102
|
+
.roots
|
|
103
|
+
.iter()
|
|
104
|
+
.any(|root| root.path == "packages/mobile/Sources" && root.role == "source"));
|
|
105
|
+
assert!(report
|
|
106
|
+
.model
|
|
107
|
+
.verification_checks
|
|
108
|
+
.iter()
|
|
109
|
+
.any(|check| check.id == "unit-test"));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn v1_model_files_remain_readable_for_path_explain() {
|
|
114
|
+
let repo = Repo::new("repository-model-v1-readable");
|
|
115
|
+
repo.write(
|
|
116
|
+
".naome/repository-model.json",
|
|
117
|
+
"{\"schema\":\"naome.repository-model.v1\",\"version\":1,\"status\":\"ready\",\"facts\":[{\"id\":\"language:rust\",\"category\":\"language\",\"value\":\"rust\",\"confidence\":\"high\",\"source\":\"deterministic-scan\",\"evidence\":[\"src/lib.rs\"]}]}\n",
|
|
118
|
+
);
|
|
119
|
+
repo.write("src/lib.rs", "pub fn run() {}\n");
|
|
120
|
+
|
|
121
|
+
let explanation = explain_repository_model_path(repo.path(), "src/lib.rs").unwrap();
|
|
122
|
+
|
|
123
|
+
assert_eq!(explanation.language.as_deref(), Some("rust"));
|
|
124
|
+
assert_eq!(explanation.role.as_deref(), Some("source"));
|
|
125
|
+
assert!(explanation
|
|
126
|
+
.facts
|
|
127
|
+
.iter()
|
|
128
|
+
.any(|fact| fact.id == "language:rust"));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn path_explain_returns_world_model_entity_and_role() {
|
|
133
|
+
let repo = Repo::new("repository-model-explain-world");
|
|
134
|
+
repo.write("packages/api/Cargo.toml", "[package]\nname = \"api\"\n");
|
|
135
|
+
repo.write("packages/api/src/lib.rs", "pub fn run() {}\n");
|
|
136
|
+
refresh_repository_model(repo.path(), true).unwrap();
|
|
137
|
+
|
|
138
|
+
let explanation =
|
|
139
|
+
explain_repository_model_path(repo.path(), "packages/api/src/lib.rs").unwrap();
|
|
140
|
+
|
|
141
|
+
assert_eq!(explanation.language.as_deref(), Some("rust"));
|
|
142
|
+
assert_eq!(explanation.role.as_deref(), Some("source"));
|
|
143
|
+
assert_eq!(explanation.module.as_deref(), Some("packages/api"));
|
|
144
|
+
assert_eq!(explanation.entity.as_deref(), Some("package:packages/api"));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn check_reports_stale_model_until_refresh_write_updates_it() {
|
|
149
|
+
let repo = Repo::new("repository-model-stale");
|
|
150
|
+
repo.write(".naome/repository-model.json", "{\"schema\":\"naome.repository-model.v1\",\"version\":1,\"status\":\"ready\",\"facts\":[]}\n");
|
|
151
|
+
repo.write("Package.swift", "// swift-tools-version: 5.9\n");
|
|
152
|
+
repo.write("Sources/App/App.swift", "public struct App {}\n");
|
|
153
|
+
|
|
154
|
+
let check = refresh_repository_model(repo.path(), false).unwrap();
|
|
155
|
+
assert!(!check.ok);
|
|
156
|
+
assert!(check.stale);
|
|
157
|
+
assert_fact(&check.model, "language", "swift");
|
|
158
|
+
|
|
159
|
+
let written = refresh_repository_model(repo.path(), true).unwrap();
|
|
160
|
+
assert!(written.ok);
|
|
161
|
+
assert!(!written.stale);
|
|
162
|
+
assert_eq!(written.updated_paths, vec![".naome/repository-model.json"]);
|
|
163
|
+
|
|
164
|
+
let repeated = refresh_repository_model(repo.path(), false).unwrap();
|
|
165
|
+
assert!(repeated.ok);
|
|
166
|
+
assert!(!repeated.stale);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn drift_detects_world_sections_when_fact_values_are_unchanged() {
|
|
171
|
+
let repo = Repo::new("repository-model-world-section-drift");
|
|
172
|
+
repo.write(
|
|
173
|
+
"packages/a/package.json",
|
|
174
|
+
"{\"scripts\":{\"test\":\"node --test\"}}\n",
|
|
175
|
+
);
|
|
176
|
+
repo.write("packages/a/src/index.ts", "export const a = 1;\n");
|
|
177
|
+
refresh_repository_model(repo.path(), true).unwrap();
|
|
178
|
+
|
|
179
|
+
let clean = repository_model_drift(repo.path()).unwrap();
|
|
180
|
+
assert!(clean.ok);
|
|
181
|
+
|
|
182
|
+
repo.write(
|
|
183
|
+
"packages/b/package.json",
|
|
184
|
+
"{\"scripts\":{\"test\":\"node --test\"}}\n",
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
let drift = repository_model_drift(repo.path()).unwrap();
|
|
188
|
+
|
|
189
|
+
assert!(!drift.ok);
|
|
190
|
+
assert!(drift.stale);
|
|
191
|
+
assert!(drift
|
|
192
|
+
.related_paths
|
|
193
|
+
.contains(&"packages/b/package.json".to_string()));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn explain_repository_model_path_uses_canonical_facts() {
|
|
198
|
+
let repo = Repo::new("repository-model-explain");
|
|
199
|
+
repo.write("Package.swift", "// swift-tools-version: 5.9\n");
|
|
200
|
+
repo.write("Sources/App/App.swift", "public struct App {}\n");
|
|
201
|
+
refresh_repository_model(repo.path(), true).unwrap();
|
|
202
|
+
|
|
203
|
+
let explanation = explain_repository_model_path(repo.path(), "Sources/App/App.swift").unwrap();
|
|
204
|
+
|
|
205
|
+
assert_eq!(explanation.path, "Sources/App/App.swift");
|
|
206
|
+
assert_eq!(explanation.language.as_deref(), Some("swift"));
|
|
207
|
+
assert_eq!(explanation.role.as_deref(), Some("source"));
|
|
208
|
+
assert!(explanation
|
|
209
|
+
.evidence
|
|
210
|
+
.contains(&"Sources/App/App.swift".to_string()));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn assert_fact(model: &naome_core::RepositoryModel, category: &str, value: &str) {
|
|
214
|
+
assert!(
|
|
215
|
+
model
|
|
216
|
+
.facts
|
|
217
|
+
.iter()
|
|
218
|
+
.any(|fact| fact.category == category && fact.value == value),
|
|
219
|
+
"missing {category}:{value}; facts: {:#?}",
|
|
220
|
+
model.facts
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fn fact<'a>(
|
|
225
|
+
model: &'a naome_core::RepositoryModel,
|
|
226
|
+
category: &str,
|
|
227
|
+
value: &str,
|
|
228
|
+
) -> &'a naome_core::RepositoryFact {
|
|
229
|
+
model
|
|
230
|
+
.facts
|
|
231
|
+
.iter()
|
|
232
|
+
.find(|fact| fact.category == category && fact.value == value)
|
|
233
|
+
.unwrap_or_else(|| panic!("missing {category}:{value}; facts: {:#?}", model.facts))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
struct Repo {
|
|
237
|
+
root: PathBuf,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
impl Repo {
|
|
241
|
+
fn new(name: &str) -> Self {
|
|
242
|
+
let root = std::env::temp_dir().join(format!(
|
|
243
|
+
"naome-{name}-{}-{}",
|
|
244
|
+
std::process::id(),
|
|
245
|
+
FIXTURE_COUNTER.fetch_add(1, Ordering::SeqCst)
|
|
246
|
+
));
|
|
247
|
+
let _ = fs::remove_dir_all(&root);
|
|
248
|
+
fs::create_dir_all(root.join(".naome")).unwrap();
|
|
249
|
+
git(&root, &["init"]);
|
|
250
|
+
git(&root, &["config", "user.email", "test@example.com"]);
|
|
251
|
+
git(&root, &["config", "user.name", "Test User"]);
|
|
252
|
+
Self { root }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn path(&self) -> &Path {
|
|
256
|
+
&self.root
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn write(&self, relative_path: &str, content: &str) {
|
|
260
|
+
let path = self.root.join(relative_path);
|
|
261
|
+
if let Some(parent) = path.parent() {
|
|
262
|
+
fs::create_dir_all(parent).unwrap();
|
|
263
|
+
}
|
|
264
|
+
fs::write(path, content).unwrap();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
impl Drop for Repo {
|
|
269
|
+
fn drop(&mut self) {
|
|
270
|
+
let _ = fs::remove_dir_all(&self.root);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fn git(root: &Path, args: &[&str]) {
|
|
275
|
+
let output = Command::new("git")
|
|
276
|
+
.args(args)
|
|
277
|
+
.current_dir(root)
|
|
278
|
+
.output()
|
|
279
|
+
.unwrap();
|
|
280
|
+
assert!(output.status.success());
|
|
281
|
+
}
|
|
@@ -5,7 +5,10 @@ use serde_json::json;
|
|
|
5
5
|
|
|
6
6
|
mod repo_support;
|
|
7
7
|
|
|
8
|
-
use repo_support::{
|
|
8
|
+
use repo_support::{
|
|
9
|
+
assert_user_diff_committed, change_type, quality_check, repository_quality_config_source,
|
|
10
|
+
route_commit_request, semantic_repository_quality_fixture_source, verification_value, TestRepo,
|
|
11
|
+
};
|
|
9
12
|
|
|
10
13
|
#[test]
|
|
11
14
|
fn execute_route_does_not_mutate_or_offer_clear_commit_for_dirty_unowned_diff() {
|
|
@@ -69,6 +72,51 @@ fn execute_route_runs_repository_quality_check_without_shelling_to_repo_command(
|
|
|
69
72
|
assert!(route.user_message.contains("file-length"));
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
#[test]
|
|
76
|
+
fn execute_route_refuses_semantic_changed_findings_through_repository_quality_gate() {
|
|
77
|
+
let repo = TestRepo::new("route-semantic-quality-gate");
|
|
78
|
+
repo.init_git();
|
|
79
|
+
repo.write_file(
|
|
80
|
+
".naome/repository-quality.json",
|
|
81
|
+
&repository_quality_config_source(),
|
|
82
|
+
);
|
|
83
|
+
repo.write_file("scripts/baseline.test.js", "const ok = { value: 1 };\n");
|
|
84
|
+
repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
|
|
85
|
+
repo.write_naome_json(
|
|
86
|
+
"verification.json",
|
|
87
|
+
verification_value(
|
|
88
|
+
"ready",
|
|
89
|
+
vec![quality_check(
|
|
90
|
+
"repository-quality-check",
|
|
91
|
+
"naome quality check --changed",
|
|
92
|
+
"Validate changed files against repository quality rules.",
|
|
93
|
+
"fast",
|
|
94
|
+
vec!["scripts/**"],
|
|
95
|
+
)],
|
|
96
|
+
vec![change_type(
|
|
97
|
+
"scripts",
|
|
98
|
+
"Script changes.",
|
|
99
|
+
vec!["scripts/**"],
|
|
100
|
+
vec!["repository-quality-check"],
|
|
101
|
+
)],
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
repo.commit_all("baseline");
|
|
105
|
+
repo.write_file(
|
|
106
|
+
"scripts/new.test.js",
|
|
107
|
+
&semantic_repository_quality_fixture_source("newQualityConfig"),
|
|
108
|
+
);
|
|
109
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
110
|
+
|
|
111
|
+
let route = route_commit_request(&repo);
|
|
112
|
+
|
|
113
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "scripts/new.test.js");
|
|
114
|
+
assert!(route.user_message.contains("repository-quality-check"));
|
|
115
|
+
assert!(route
|
|
116
|
+
.user_message
|
|
117
|
+
.contains("semantic-inline-legacy-fixture"));
|
|
118
|
+
}
|
|
119
|
+
|
|
72
120
|
#[test]
|
|
73
121
|
fn execute_route_refuses_product_named_checks_without_generic_builtin_policy() {
|
|
74
122
|
let repo = TestRepo::product_quality_repo("route-product-diff-quality-pass");
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
mod repo_support;
|
|
2
2
|
|
|
3
|
-
use naome_core::{
|
|
3
|
+
use naome_core::{
|
|
4
|
+
check_repository_quality, check_semantic_legacy, semantic_route_for_finding, QualityMode,
|
|
5
|
+
};
|
|
4
6
|
|
|
5
|
-
use repo_support::
|
|
7
|
+
use repo_support::{
|
|
8
|
+
repository_quality_config_value, semantic_repository_quality_fixture_source, TestRepo,
|
|
9
|
+
};
|
|
6
10
|
|
|
7
11
|
#[test]
|
|
8
12
|
fn report_groups_repeated_config_fixture_shapes_across_repository() {
|
|
9
13
|
let repo = semantic_repo("semantic-report-groups");
|
|
10
|
-
repo.write_file(
|
|
14
|
+
repo.write_file(
|
|
15
|
+
"scripts/a.test.js",
|
|
16
|
+
&semantic_repository_quality_fixture_source("qualityConfig"),
|
|
17
|
+
);
|
|
11
18
|
repo.write_file(
|
|
12
19
|
"scripts/b.test.js",
|
|
13
|
-
&
|
|
20
|
+
&semantic_repository_quality_fixture_source("repositoryQualityConfig"),
|
|
14
21
|
);
|
|
15
22
|
repo.commit_all("baseline");
|
|
16
23
|
|
|
@@ -36,14 +43,20 @@ fn report_groups_repeated_config_fixture_shapes_across_repository() {
|
|
|
36
43
|
#[test]
|
|
37
44
|
fn changed_check_blocks_new_inline_legacy_fixture_only() {
|
|
38
45
|
let repo = semantic_repo("semantic-changed-only");
|
|
39
|
-
repo.write_file(
|
|
46
|
+
repo.write_file(
|
|
47
|
+
"scripts/old.test.js",
|
|
48
|
+
&semantic_repository_quality_fixture_source("oldQualityConfig"),
|
|
49
|
+
);
|
|
40
50
|
repo.commit_all("baseline");
|
|
41
51
|
repo.write_file("README.md", "# harmless change\n");
|
|
42
52
|
|
|
43
53
|
let unrelated = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
|
|
44
54
|
assert!(unrelated.ok, "{:#?}", unrelated.findings);
|
|
45
55
|
|
|
46
|
-
repo.write_file(
|
|
56
|
+
repo.write_file(
|
|
57
|
+
"scripts/new.test.js",
|
|
58
|
+
&semantic_repository_quality_fixture_source("newQualityConfig"),
|
|
59
|
+
);
|
|
47
60
|
|
|
48
61
|
let changed = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
|
|
49
62
|
assert!(!changed.ok);
|
|
@@ -53,6 +66,24 @@ fn changed_check_blocks_new_inline_legacy_fixture_only() {
|
|
|
53
66
|
.any(|finding| finding.kind == "inline-legacy-fixture"));
|
|
54
67
|
}
|
|
55
68
|
|
|
69
|
+
#[test]
|
|
70
|
+
fn repository_quality_changed_includes_semantic_changed_findings() {
|
|
71
|
+
let repo = semantic_repo("semantic-quality-gate");
|
|
72
|
+
repo.commit_all("baseline");
|
|
73
|
+
repo.write_file(
|
|
74
|
+
"scripts/new.test.js",
|
|
75
|
+
&semantic_repository_quality_fixture_source("newQualityConfig"),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
|
|
79
|
+
|
|
80
|
+
assert!(!report.ok);
|
|
81
|
+
assert!(report.violations.iter().any(|violation| {
|
|
82
|
+
violation.check_id == "semantic-inline-legacy-fixture"
|
|
83
|
+
&& violation.path == "scripts/new.test.js"
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
56
87
|
#[test]
|
|
57
88
|
fn changed_check_allows_shared_fixture_factory_objects() {
|
|
58
89
|
let repo = semantic_repo("semantic-shared-factory");
|
|
@@ -80,13 +111,45 @@ fn changed_check_allows_shared_fixture_factory_objects() {
|
|
|
80
111
|
assert!(report.ok, "{:#?}", report.findings);
|
|
81
112
|
}
|
|
82
113
|
|
|
114
|
+
#[test]
|
|
115
|
+
fn changed_check_allows_shared_support_fixture_modules() {
|
|
116
|
+
let repo = semantic_repo("semantic-shared-support");
|
|
117
|
+
repo.commit_all("baseline");
|
|
118
|
+
repo.write_file(
|
|
119
|
+
"tests/repo_support/verification_values.rs",
|
|
120
|
+
&[
|
|
121
|
+
"pub fn completed_task_state() -> serde_json::Value {",
|
|
122
|
+
" serde_json::json!({",
|
|
123
|
+
" \"schema\": \"naome.task-state.v1\",",
|
|
124
|
+
" \"version\": 1,",
|
|
125
|
+
" \"status\": \"complete\",",
|
|
126
|
+
" \"activeTask\": {",
|
|
127
|
+
" \"id\": \"readme-task\",",
|
|
128
|
+
" \"requiredCheckIds\": [\"diff-check\"],",
|
|
129
|
+
" \"proofResults\": []",
|
|
130
|
+
" }",
|
|
131
|
+
" })",
|
|
132
|
+
"}",
|
|
133
|
+
"",
|
|
134
|
+
]
|
|
135
|
+
.join("\n"),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
let report = check_semantic_legacy(repo.path(), QualityMode::Changed).unwrap();
|
|
139
|
+
|
|
140
|
+
assert!(report.ok, "{:#?}", report.findings);
|
|
141
|
+
}
|
|
142
|
+
|
|
83
143
|
#[test]
|
|
84
144
|
fn semantic_route_returns_complete_agent_cleanup_task() {
|
|
85
145
|
let repo = semantic_repo("semantic-route");
|
|
86
|
-
repo.write_file(
|
|
146
|
+
repo.write_file(
|
|
147
|
+
"scripts/a.test.js",
|
|
148
|
+
&semantic_repository_quality_fixture_source("qualityConfig"),
|
|
149
|
+
);
|
|
87
150
|
repo.write_file(
|
|
88
151
|
"scripts/b.test.js",
|
|
89
|
-
&
|
|
152
|
+
&semantic_repository_quality_fixture_source("repositoryQualityConfig"),
|
|
90
153
|
);
|
|
91
154
|
repo.commit_all("baseline");
|
|
92
155
|
let report = check_semantic_legacy(repo.path(), QualityMode::DeepReport).unwrap();
|
|
@@ -106,35 +169,6 @@ fn semantic_repo(name: &str) -> TestRepo {
|
|
|
106
169
|
let repo = TestRepo::new(name);
|
|
107
170
|
repo.init_git();
|
|
108
171
|
repo.write_file("README.md", "# Semantic fixture\n");
|
|
109
|
-
repo.write_naome_json(
|
|
110
|
-
"repository-quality.json",
|
|
111
|
-
serde_json::json!({"schema":"naome.repository-quality.v1","version":1,"status":"ready","limits":{"maxFileLines":450,"maxNewFileLines":300,"maxDiffAddedLines":180,"maxFunctionLines":100,"maxTopLevelSymbols":30,"duplicateBlockLines":10,"nearDuplicateSimilarity":0.9},"enabledAdapters":[],"disabledChecks":[],"ignoredPaths":[],"generatedPaths":[],"pathRules":[]}),
|
|
112
|
-
);
|
|
172
|
+
repo.write_naome_json("repository-quality.json", repository_quality_config_value());
|
|
113
173
|
repo
|
|
114
174
|
}
|
|
115
|
-
|
|
116
|
-
fn fixture_object(name: &str) -> String {
|
|
117
|
-
[
|
|
118
|
-
format!("const {name} = {{"),
|
|
119
|
-
" schema: \"naome.repository-quality.v1\",".to_string(),
|
|
120
|
-
" version: 1,".to_string(),
|
|
121
|
-
" status: \"ready\",".to_string(),
|
|
122
|
-
" limits: {".to_string(),
|
|
123
|
-
" maxFileLines: 450,".to_string(),
|
|
124
|
-
" maxNewFileLines: 300,".to_string(),
|
|
125
|
-
" maxDiffAddedLines: 180,".to_string(),
|
|
126
|
-
" maxFunctionLines: 100,".to_string(),
|
|
127
|
-
" maxTopLevelSymbols: 30,".to_string(),
|
|
128
|
-
" duplicateBlockLines: 10,".to_string(),
|
|
129
|
-
" nearDuplicateSimilarity: 0.9".to_string(),
|
|
130
|
-
" },".to_string(),
|
|
131
|
-
" enabledAdapters: [],".to_string(),
|
|
132
|
-
" disabledChecks: [],".to_string(),
|
|
133
|
-
" ignoredPaths: [],".to_string(),
|
|
134
|
-
" generatedPaths: [],".to_string(),
|
|
135
|
-
" pathRules: []".to_string(),
|
|
136
|
-
"};".to_string(),
|
|
137
|
-
String::new(),
|
|
138
|
-
]
|
|
139
|
-
.join("\n")
|
|
140
|
-
}
|