@lamentis/naome 1.2.1 → 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 +117 -47
- package/bin/naome.js +65 -12
- 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 +12 -2
- package/crates/naome-cli/src/main.rs +78 -29
- package/crates/naome-cli/src/quality_commands.rs +238 -34
- 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 +120 -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/git.rs +4 -2
- package/crates/naome-core/src/install_plan.rs +20 -0
- package/crates/naome-core/src/journal.rs +2 -7
- package/crates/naome-core/src/lib.rs +35 -8
- 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/baseline.rs +8 -0
- package/crates/naome-core/src/quality/cache.rs +151 -0
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +19 -8
- package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
- package/crates/naome-core/src/quality/checks.rs +7 -8
- package/crates/naome-core/src/quality/cleanup.rs +36 -3
- package/crates/naome-core/src/quality/config.rs +21 -3
- package/crates/naome-core/src/quality/mod.rs +189 -10
- 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/normalize.rs +78 -0
- package/crates/naome-core/src/quality/scanner/analysis.rs +175 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
- package/crates/naome-core/src/quality/scanner.rs +235 -217
- package/crates/naome-core/src/quality/semantic/checks.rs +151 -0
- package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
- package/crates/naome-core/src/quality/semantic/model.rs +85 -0
- package/crates/naome-core/src/quality/semantic/route.rs +52 -0
- package/crates/naome-core/src/quality/semantic.rs +68 -0
- 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 +13 -21
- package/crates/naome-core/src/quality/structure/checks.rs +1 -1
- package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
- package/crates/naome-core/src/quality/structure/classify.rs +52 -0
- package/crates/naome-core/src/quality/structure/config.rs +24 -3
- package/crates/naome-core/src/quality/structure/mod.rs +5 -2
- package/crates/naome-core/src/quality/structure/model.rs +8 -1
- package/crates/naome-core/src/quality/types.rs +59 -2
- 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 +41 -16
- 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 +183 -0
- package/crates/naome-core/src/workflow/mod.rs +13 -0
- package/crates/naome-core/src/workflow/mutation.rs +1 -2
- 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 +14 -0
- package/crates/naome-core/tests/quality.rs +190 -5
- package/crates/naome-core/tests/quality_performance.rs +268 -0
- package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
- package/crates/naome-core/tests/quality_structure_policy.rs +19 -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 +59 -7
- package/crates/naome-core/tests/semantic_legacy.rs +174 -0
- 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 +45 -0
- package/crates/naome-core/tests/workflow_policy.rs +6 -1
- 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/git-boundary.js +1 -0
- 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 +45 -7
- 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/.naomeignore +1 -0
- package/templates/naome-root/AGENTS.md +38 -83
- package/templates/naome-root/docs/naome/agent-workflow.md +66 -28
- 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 +104 -5
- 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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
use super::adapters::{path_rule, test_file_limits};
|
|
2
|
+
use super::types::{QualityLimitOverrides, QualityPathRule};
|
|
3
|
+
|
|
4
|
+
pub(super) fn swift_path_rules() -> Vec<QualityPathRule> {
|
|
5
|
+
vec![path_rule(
|
|
6
|
+
"swift-source",
|
|
7
|
+
&["**/*.swift"],
|
|
8
|
+
source_limits(450, 300, 180, 100, 30, 10, 0.9),
|
|
9
|
+
&[],
|
|
10
|
+
)]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub(super) fn xctest_path_rules() -> Vec<QualityPathRule> {
|
|
14
|
+
vec![path_rule(
|
|
15
|
+
"xctest-tests",
|
|
16
|
+
&[
|
|
17
|
+
"**/*Tests.swift",
|
|
18
|
+
"**/*UITests.swift",
|
|
19
|
+
"Tests/**/*.swift",
|
|
20
|
+
"**/Tests/**/*.swift",
|
|
21
|
+
"**/*Tests/**/*.swift",
|
|
22
|
+
"**/*UITests/**/*.swift",
|
|
23
|
+
],
|
|
24
|
+
test_file_limits(80),
|
|
25
|
+
&[],
|
|
26
|
+
)]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub(super) fn swiftui_path_rules() -> Vec<QualityPathRule> {
|
|
30
|
+
vec![path_rule(
|
|
31
|
+
"swiftui-views",
|
|
32
|
+
&[
|
|
33
|
+
"**/*View.swift",
|
|
34
|
+
"**/Views/**/*.swift",
|
|
35
|
+
"**/Preview Content/**",
|
|
36
|
+
],
|
|
37
|
+
source_limits(360, 240, 160, 80, 24, 10, 0.92),
|
|
38
|
+
&[],
|
|
39
|
+
)]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub(super) fn ios_app_structure_path_rules() -> Vec<QualityPathRule> {
|
|
43
|
+
vec![path_rule(
|
|
44
|
+
"ios-app-entrypoints",
|
|
45
|
+
&[
|
|
46
|
+
"**/*App.swift",
|
|
47
|
+
"**/AppDelegate.swift",
|
|
48
|
+
"**/SceneDelegate.swift",
|
|
49
|
+
"**/Info.plist",
|
|
50
|
+
"**/*.entitlements",
|
|
51
|
+
],
|
|
52
|
+
source_limits(300, 220, 140, 80, 20, 10, 0.92),
|
|
53
|
+
&[],
|
|
54
|
+
)]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(super) fn swift_package_path_rules() -> Vec<QualityPathRule> {
|
|
58
|
+
vec![path_rule(
|
|
59
|
+
"swift-package-layout",
|
|
60
|
+
&["Package.swift", "Sources/**/*.swift", "Tests/**/*.swift"],
|
|
61
|
+
source_limits(450, 300, 180, 100, 30, 10, 0.9),
|
|
62
|
+
&[],
|
|
63
|
+
)]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub(super) fn ios_resource_path_rules() -> Vec<QualityPathRule> {
|
|
67
|
+
vec![path_rule(
|
|
68
|
+
"ios-resources",
|
|
69
|
+
&[
|
|
70
|
+
"**/*.xcassets/**",
|
|
71
|
+
"**/*.strings",
|
|
72
|
+
"**/*.stringsdict",
|
|
73
|
+
"**/*.plist",
|
|
74
|
+
"**/*.entitlements",
|
|
75
|
+
"**/*.storyboard",
|
|
76
|
+
"**/*.xib",
|
|
77
|
+
],
|
|
78
|
+
source_limits(700, 350, 250, 120, 80, 14, 0.96),
|
|
79
|
+
&["top-level-symbols", "function-length"],
|
|
80
|
+
)]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub(super) fn generated_ios_path_rules() -> Vec<QualityPathRule> {
|
|
84
|
+
vec![path_rule(
|
|
85
|
+
"generated-ios",
|
|
86
|
+
GENERATED_IOS_PATHS,
|
|
87
|
+
source_limits(2_000, 2_000, 2_000, 300, 300, 30, 1.0),
|
|
88
|
+
&[
|
|
89
|
+
"file-length",
|
|
90
|
+
"new-file-length",
|
|
91
|
+
"diff-size",
|
|
92
|
+
"function-length",
|
|
93
|
+
"top-level-symbols",
|
|
94
|
+
"duplicate-blocks",
|
|
95
|
+
"near-duplicate-functions",
|
|
96
|
+
],
|
|
97
|
+
)]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub(super) const GENERATED_IOS_PATHS: &[&str] = &[
|
|
101
|
+
"Generated/**",
|
|
102
|
+
"**/Generated/**",
|
|
103
|
+
"SwiftGen/**",
|
|
104
|
+
"**/SwiftGen/**",
|
|
105
|
+
"Sourcery/**",
|
|
106
|
+
"**/Sourcery/**",
|
|
107
|
+
"**/*.generated.swift",
|
|
108
|
+
"**/*.pb.swift",
|
|
109
|
+
"**/*.grpc.swift",
|
|
110
|
+
"**/R.generated.swift",
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
fn source_limits(
|
|
114
|
+
max_file_lines: usize,
|
|
115
|
+
max_new_file_lines: usize,
|
|
116
|
+
max_diff_added_lines: usize,
|
|
117
|
+
max_function_lines: usize,
|
|
118
|
+
max_top_level_symbols: usize,
|
|
119
|
+
duplicate_block_lines: usize,
|
|
120
|
+
near_duplicate_similarity: f64,
|
|
121
|
+
) -> QualityLimitOverrides {
|
|
122
|
+
QualityLimitOverrides {
|
|
123
|
+
max_file_lines: Some(max_file_lines),
|
|
124
|
+
max_new_file_lines: Some(max_new_file_lines),
|
|
125
|
+
max_diff_added_lines: Some(max_diff_added_lines),
|
|
126
|
+
max_function_lines: Some(max_function_lines),
|
|
127
|
+
max_top_level_symbols: Some(max_top_level_symbols),
|
|
128
|
+
duplicate_block_lines: Some(duplicate_block_lines),
|
|
129
|
+
near_duplicate_similarity: Some(near_duplicate_similarity),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -23,6 +23,25 @@ impl<'a> RepoSignals<'a> {
|
|
|
23
23
|
.iter()
|
|
24
24
|
.any(|path| extensions.iter().any(|extension| path.ends_with(extension)))
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
pub(crate) fn has_path_containing(&self, fragments: &[&str]) -> bool {
|
|
28
|
+
self.paths.iter().any(|path| {
|
|
29
|
+
let lower = path.to_ascii_lowercase();
|
|
30
|
+
fragments.iter().any(|fragment| lower.contains(fragment))
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(crate) fn has_file_name(&self, names: &[&str]) -> bool {
|
|
35
|
+
self.paths.iter().any(|path| {
|
|
36
|
+
let lower = path.to_ascii_lowercase();
|
|
37
|
+
path.rsplit('/').next().is_some_and(|file_name| {
|
|
38
|
+
names
|
|
39
|
+
.iter()
|
|
40
|
+
.any(|name| file_name.eq_ignore_ascii_case(name))
|
|
41
|
+
|| names.iter().any(|name| lower.ends_with(name))
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
}
|
|
26
45
|
}
|
|
27
46
|
|
|
28
47
|
pub(crate) trait AdapterDescriptor {
|
|
@@ -87,3 +106,51 @@ pub(crate) fn detects_javascript_typescript_project(signals: &RepoSignals<'_>) -
|
|
|
87
106
|
signals.has_manifest("package.json")
|
|
88
107
|
|| signals.has_extension(&[".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"])
|
|
89
108
|
}
|
|
109
|
+
|
|
110
|
+
pub(crate) fn detects_swift_project(signals: &RepoSignals<'_>) -> bool {
|
|
111
|
+
signals.has_manifest("Package.swift") || signals.has_extension(&[".swift"])
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub(crate) fn detects_xcode_project(signals: &RepoSignals<'_>) -> bool {
|
|
115
|
+
signals.has_path_containing(&[".xcodeproj/", ".xcworkspace/"])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pub(crate) fn detects_xctest_project(signals: &RepoSignals<'_>) -> bool {
|
|
119
|
+
signals.has_path_containing(&["tests/"])
|
|
120
|
+
&& signals.has_file_name(&["tests.swift", "uitests.swift"])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pub(crate) fn detects_swiftui_project(signals: &RepoSignals<'_>) -> bool {
|
|
124
|
+
signals.has_file_name(&["view.swift", "app.swift"])
|
|
125
|
+
|| signals.has_path_containing(&["preview content/"])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pub(crate) fn detects_ios_app_structure_project(signals: &RepoSignals<'_>) -> bool {
|
|
129
|
+
detects_xcode_project(signals)
|
|
130
|
+
|| signals.has_file_name(&["appdelegate.swift", "scenedelegate.swift", "info.plist"])
|
|
131
|
+
|| signals.has_path_containing(&[".xcassets/"])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub(crate) fn detects_swift_package_project(signals: &RepoSignals<'_>) -> bool {
|
|
135
|
+
signals.has_manifest("Package.swift")
|
|
136
|
+
|| signals.has_path_containing(&["sources/", "tests/"])
|
|
137
|
+
&& signals.has_extension(&[".swift"])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pub(crate) fn detects_ios_resources_project(signals: &RepoSignals<'_>) -> bool {
|
|
141
|
+
signals.has_path_containing(&[".xcassets/"])
|
|
142
|
+
|| signals.has_extension(&[
|
|
143
|
+
".strings",
|
|
144
|
+
".stringsdict",
|
|
145
|
+
".plist",
|
|
146
|
+
".entitlements",
|
|
147
|
+
".storyboard",
|
|
148
|
+
".xib",
|
|
149
|
+
])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pub(crate) fn detects_generated_ios_project(signals: &RepoSignals<'_>) -> bool {
|
|
153
|
+
signals.has_path_containing(&["generated/", "swiftgen/", "sourcery/"])
|
|
154
|
+
|| signals.has_file_name(&["r.generated.swift"])
|
|
155
|
+
|| signals.has_extension(&[".generated.swift", ".pb.swift", ".grpc.swift"])
|
|
156
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
use crate::models::NaomeError;
|
|
2
2
|
|
|
3
|
+
use super::adapter_ios;
|
|
3
4
|
use super::adapter_support::{
|
|
4
|
-
detected_ids,
|
|
5
|
-
|
|
5
|
+
detected_ids, detects_generated_ios_project, detects_ios_app_structure_project,
|
|
6
|
+
detects_ios_resources_project, detects_javascript_typescript_project, detects_rust_project,
|
|
7
|
+
detects_swift_package_project, detects_swift_project, detects_swiftui_project,
|
|
8
|
+
detects_xcode_project, detects_xctest_project, extend_unique, find_adapter_by_id, validate_ids,
|
|
9
|
+
AdapterDescriptor, RepoSignals,
|
|
6
10
|
};
|
|
7
11
|
use super::types::{QualityLimitOverrides, QualityPathRule, RepositoryQualityConfig};
|
|
8
12
|
|
|
@@ -49,21 +53,76 @@ pub(crate) fn validate_adapter_ids(ids: &[String]) -> Result<(), NaomeError> {
|
|
|
49
53
|
validate_ids(ids, registry(), CONFIG_PATH)
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
const QUALITY_ADAPTERS: &[QualityAdapter] = &[
|
|
57
|
+
QualityAdapter {
|
|
58
|
+
id: "rust",
|
|
59
|
+
generated_paths: &[],
|
|
60
|
+
detect: detects_rust_project,
|
|
61
|
+
path_rules: rust_path_rules,
|
|
62
|
+
},
|
|
63
|
+
QualityAdapter {
|
|
64
|
+
id: "javascript-typescript",
|
|
65
|
+
generated_paths: &["coverage/**", "**/coverage/**", ".next/**", "**/.next/**"],
|
|
66
|
+
detect: detects_javascript_typescript_project,
|
|
67
|
+
path_rules: javascript_typescript_path_rules,
|
|
68
|
+
},
|
|
69
|
+
QualityAdapter {
|
|
70
|
+
id: "swift",
|
|
71
|
+
generated_paths: &[],
|
|
72
|
+
detect: detects_swift_project,
|
|
73
|
+
path_rules: adapter_ios::swift_path_rules,
|
|
74
|
+
},
|
|
75
|
+
QualityAdapter {
|
|
76
|
+
id: "xcode",
|
|
77
|
+
generated_paths: &[
|
|
78
|
+
"DerivedData/**",
|
|
79
|
+
"**/DerivedData/**",
|
|
80
|
+
"**/xcuserdata/**",
|
|
81
|
+
"**/*.xcuserstate",
|
|
82
|
+
],
|
|
83
|
+
detect: detects_xcode_project,
|
|
84
|
+
path_rules: empty_path_rules,
|
|
85
|
+
},
|
|
86
|
+
QualityAdapter {
|
|
87
|
+
id: "xctest",
|
|
88
|
+
generated_paths: &[],
|
|
89
|
+
detect: detects_xctest_project,
|
|
90
|
+
path_rules: adapter_ios::xctest_path_rules,
|
|
91
|
+
},
|
|
92
|
+
QualityAdapter {
|
|
93
|
+
id: "swiftui",
|
|
94
|
+
generated_paths: &["**/Preview Content/**"],
|
|
95
|
+
detect: detects_swiftui_project,
|
|
96
|
+
path_rules: adapter_ios::swiftui_path_rules,
|
|
97
|
+
},
|
|
98
|
+
QualityAdapter {
|
|
99
|
+
id: "ios-app-structure",
|
|
100
|
+
generated_paths: &[],
|
|
101
|
+
detect: detects_ios_app_structure_project,
|
|
102
|
+
path_rules: adapter_ios::ios_app_structure_path_rules,
|
|
103
|
+
},
|
|
104
|
+
QualityAdapter {
|
|
105
|
+
id: "swift-package",
|
|
106
|
+
generated_paths: &[],
|
|
107
|
+
detect: detects_swift_package_project,
|
|
108
|
+
path_rules: adapter_ios::swift_package_path_rules,
|
|
109
|
+
},
|
|
110
|
+
QualityAdapter {
|
|
111
|
+
id: "ios-resources",
|
|
112
|
+
generated_paths: &[],
|
|
113
|
+
detect: detects_ios_resources_project,
|
|
114
|
+
path_rules: adapter_ios::ios_resource_path_rules,
|
|
115
|
+
},
|
|
116
|
+
QualityAdapter {
|
|
117
|
+
id: "generated-ios",
|
|
118
|
+
generated_paths: adapter_ios::GENERATED_IOS_PATHS,
|
|
119
|
+
detect: detects_generated_ios_project,
|
|
120
|
+
path_rules: adapter_ios::generated_ios_path_rules,
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
|
|
52
124
|
fn registry() -> &'static [QualityAdapter] {
|
|
53
|
-
|
|
54
|
-
QualityAdapter {
|
|
55
|
-
id: "rust",
|
|
56
|
-
generated_paths: &[],
|
|
57
|
-
detect: detects_rust_project,
|
|
58
|
-
path_rules: rust_path_rules,
|
|
59
|
-
},
|
|
60
|
-
QualityAdapter {
|
|
61
|
-
id: "javascript-typescript",
|
|
62
|
-
generated_paths: &["coverage/**", "**/coverage/**", ".next/**", "**/.next/**"],
|
|
63
|
-
detect: detects_javascript_typescript_project,
|
|
64
|
-
path_rules: javascript_typescript_path_rules,
|
|
65
|
-
},
|
|
66
|
-
]
|
|
125
|
+
QUALITY_ADAPTERS
|
|
67
126
|
}
|
|
68
127
|
|
|
69
128
|
fn rust_path_rules() -> Vec<QualityPathRule> {
|
|
@@ -84,6 +143,10 @@ fn javascript_typescript_path_rules() -> Vec<QualityPathRule> {
|
|
|
84
143
|
}]
|
|
85
144
|
}
|
|
86
145
|
|
|
146
|
+
fn empty_path_rules() -> Vec<QualityPathRule> {
|
|
147
|
+
Vec::new()
|
|
148
|
+
}
|
|
149
|
+
|
|
87
150
|
fn javascript_typescript_test_paths() -> Vec<String> {
|
|
88
151
|
let mut paths = Vec::new();
|
|
89
152
|
for marker in ["test", "spec"] {
|
|
@@ -100,7 +163,7 @@ fn javascript_typescript_test_paths() -> Vec<String> {
|
|
|
100
163
|
paths
|
|
101
164
|
}
|
|
102
165
|
|
|
103
|
-
fn test_file_limits(max_top_level_symbols: usize) -> QualityLimitOverrides {
|
|
166
|
+
pub(super) fn test_file_limits(max_top_level_symbols: usize) -> QualityLimitOverrides {
|
|
104
167
|
QualityLimitOverrides {
|
|
105
168
|
max_file_lines: Some(650),
|
|
106
169
|
max_diff_added_lines: Some(220),
|
|
@@ -112,7 +175,7 @@ fn test_file_limits(max_top_level_symbols: usize) -> QualityLimitOverrides {
|
|
|
112
175
|
}
|
|
113
176
|
}
|
|
114
177
|
|
|
115
|
-
fn path_rule(
|
|
178
|
+
pub(super) fn path_rule(
|
|
116
179
|
id: &str,
|
|
117
180
|
paths: &[&str],
|
|
118
181
|
limits: QualityLimitOverrides,
|
|
@@ -73,3 +73,11 @@ pub fn write_baseline(root: &Path, violations: &[QualityViolation]) -> Result<bo
|
|
|
73
73
|
}
|
|
74
74
|
Ok(changed)
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
pub fn write_empty_baseline_if_missing(root: &Path) -> Result<bool, NaomeError> {
|
|
78
|
+
let path = root.join(BASELINE_RELATIVE_PATH);
|
|
79
|
+
if path.is_file() {
|
|
80
|
+
return Ok(false);
|
|
81
|
+
}
|
|
82
|
+
write_baseline(root, &[])
|
|
83
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use sha2::{Digest, Sha256};
|
|
6
|
+
|
|
7
|
+
use crate::models::NaomeError;
|
|
8
|
+
|
|
9
|
+
use super::scanner::FileAnalysis;
|
|
10
|
+
use super::types::RepositoryQualityConfig;
|
|
11
|
+
|
|
12
|
+
const CACHE_RELATIVE_PATH: &str = ".naome/cache/quality";
|
|
13
|
+
const CACHE_SCHEMA: &str = "naome.quality-cache-entry.v1";
|
|
14
|
+
const ADAPTER_VERSION: &str = "quality-core-v1";
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone)]
|
|
17
|
+
pub(crate) struct QualityCache {
|
|
18
|
+
root: PathBuf,
|
|
19
|
+
config_hash: String,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
23
|
+
#[serde(rename_all = "camelCase")]
|
|
24
|
+
struct CacheEntry {
|
|
25
|
+
schema: String,
|
|
26
|
+
naome_version: String,
|
|
27
|
+
config_hash: String,
|
|
28
|
+
adapter_version: String,
|
|
29
|
+
path: String,
|
|
30
|
+
content_hash: String,
|
|
31
|
+
analysis: FileAnalysis,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[derive(Debug, Clone, Serialize)]
|
|
35
|
+
#[serde(rename_all = "camelCase")]
|
|
36
|
+
pub struct QualityCacheStatus {
|
|
37
|
+
pub schema: String,
|
|
38
|
+
pub path: String,
|
|
39
|
+
pub entry_count: usize,
|
|
40
|
+
pub bytes: u64,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
impl QualityCache {
|
|
44
|
+
pub(crate) fn new(root: &Path, config: &RepositoryQualityConfig) -> Self {
|
|
45
|
+
Self {
|
|
46
|
+
root: root.to_path_buf(),
|
|
47
|
+
config_hash: config_hash(config),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub(crate) fn read(&self, path: &str, content_hash: &str) -> Option<FileAnalysis> {
|
|
52
|
+
let entry = fs::read_to_string(self.entry_path(path, content_hash)).ok()?;
|
|
53
|
+
let entry: CacheEntry = serde_json::from_str(&entry).ok()?;
|
|
54
|
+
if entry.schema == CACHE_SCHEMA
|
|
55
|
+
&& entry.naome_version == env!("CARGO_PKG_VERSION")
|
|
56
|
+
&& entry.config_hash == self.config_hash
|
|
57
|
+
&& entry.adapter_version == ADAPTER_VERSION
|
|
58
|
+
&& entry.path == path
|
|
59
|
+
&& entry.content_hash == content_hash
|
|
60
|
+
{
|
|
61
|
+
Some(entry.analysis)
|
|
62
|
+
} else {
|
|
63
|
+
None
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
pub(crate) fn write(
|
|
68
|
+
&self,
|
|
69
|
+
path: &str,
|
|
70
|
+
content_hash: &str,
|
|
71
|
+
analysis: &FileAnalysis,
|
|
72
|
+
) -> Result<(), NaomeError> {
|
|
73
|
+
let cache_path = self.entry_path(path, content_hash);
|
|
74
|
+
if let Some(parent) = cache_path.parent() {
|
|
75
|
+
fs::create_dir_all(parent)?;
|
|
76
|
+
}
|
|
77
|
+
let entry = CacheEntry {
|
|
78
|
+
schema: CACHE_SCHEMA.to_string(),
|
|
79
|
+
naome_version: env!("CARGO_PKG_VERSION").to_string(),
|
|
80
|
+
config_hash: self.config_hash.clone(),
|
|
81
|
+
adapter_version: ADAPTER_VERSION.to_string(),
|
|
82
|
+
path: path.to_string(),
|
|
83
|
+
content_hash: content_hash.to_string(),
|
|
84
|
+
analysis: analysis.clone(),
|
|
85
|
+
};
|
|
86
|
+
let content = format!("{}\n", serde_json::to_string(&entry)?);
|
|
87
|
+
if fs::read_to_string(&cache_path).map_or(true, |current| current != content) {
|
|
88
|
+
fs::write(cache_path, content)?;
|
|
89
|
+
}
|
|
90
|
+
Ok(())
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn entry_path(&self, path: &str, content_hash: &str) -> PathBuf {
|
|
94
|
+
self.root.join(CACHE_RELATIVE_PATH).join(stable_key(&[
|
|
95
|
+
env!("CARGO_PKG_VERSION"),
|
|
96
|
+
&self.config_hash,
|
|
97
|
+
ADAPTER_VERSION,
|
|
98
|
+
path,
|
|
99
|
+
content_hash,
|
|
100
|
+
]))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn quality_cache_status(root: &Path) -> Result<QualityCacheStatus, NaomeError> {
|
|
105
|
+
let path = root.join(CACHE_RELATIVE_PATH);
|
|
106
|
+
let mut entry_count = 0;
|
|
107
|
+
let mut bytes = 0;
|
|
108
|
+
if path.is_dir() {
|
|
109
|
+
for entry in fs::read_dir(&path)? {
|
|
110
|
+
let entry = entry?;
|
|
111
|
+
let metadata = entry.metadata()?;
|
|
112
|
+
if metadata.is_file() {
|
|
113
|
+
entry_count += 1;
|
|
114
|
+
bytes += metadata.len();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
Ok(QualityCacheStatus {
|
|
119
|
+
schema: "naome.quality-cache-status.v1".to_string(),
|
|
120
|
+
path: CACHE_RELATIVE_PATH.to_string(),
|
|
121
|
+
entry_count,
|
|
122
|
+
bytes,
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub fn clear_quality_cache(root: &Path) -> Result<QualityCacheStatus, NaomeError> {
|
|
127
|
+
let path = root.join(CACHE_RELATIVE_PATH);
|
|
128
|
+
if path.exists() {
|
|
129
|
+
fs::remove_dir_all(&path)?;
|
|
130
|
+
}
|
|
131
|
+
quality_cache_status(root)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub(crate) fn content_hash(content: &str) -> String {
|
|
135
|
+
stable_key(&[content])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn config_hash(config: &RepositoryQualityConfig) -> String {
|
|
139
|
+
serde_json::to_string(config)
|
|
140
|
+
.map(|content| stable_key(&[&content]))
|
|
141
|
+
.unwrap_or_else(|_| stable_key(&["invalid-config"]))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn stable_key(parts: &[&str]) -> String {
|
|
145
|
+
let mut hasher = Sha256::new();
|
|
146
|
+
for part in parts {
|
|
147
|
+
hasher.update(part.as_bytes());
|
|
148
|
+
hasher.update(b"\0");
|
|
149
|
+
}
|
|
150
|
+
format!("{:x}.json", hasher.finalize())
|
|
151
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
use std::collections::{HashMap, HashSet};
|
|
2
2
|
|
|
3
|
-
use
|
|
3
|
+
use sha2::{Digest, Sha256};
|
|
4
|
+
|
|
5
|
+
use super::super::scanner::{stable_fingerprint, NormalizedLine, QualityContext};
|
|
4
6
|
use super::super::types::QualityViolation;
|
|
5
7
|
use super::{is_code_like_path, QualityCheck};
|
|
6
8
|
|
|
@@ -12,8 +14,11 @@ impl QualityCheck for DuplicateBlockCheck {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
|
|
17
|
+
if context.mode == super::super::types::QualityMode::Report {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
15
20
|
let mut occurrences: HashMap<String, Vec<DuplicateOccurrence>> = HashMap::new();
|
|
16
|
-
for file in context.
|
|
21
|
+
for file in context.comparison_candidate_files().filter(|file| {
|
|
17
22
|
is_code_like_path(&file.path)
|
|
18
23
|
&& context.config.check_enabled_for_path(self.id(), &file.path)
|
|
19
24
|
}) {
|
|
@@ -22,12 +27,7 @@ impl QualityCheck for DuplicateBlockCheck {
|
|
|
22
27
|
continue;
|
|
23
28
|
}
|
|
24
29
|
for lines in file.normalized_lines.windows(window) {
|
|
25
|
-
let
|
|
26
|
-
.iter()
|
|
27
|
-
.map(|line| line.value.as_str())
|
|
28
|
-
.collect::<Vec<_>>()
|
|
29
|
-
.join("\n");
|
|
30
|
-
let fingerprint = stable_fingerprint(&[self.id(), &joined]);
|
|
30
|
+
let fingerprint = window_fingerprint(self.id(), lines);
|
|
31
31
|
occurrences
|
|
32
32
|
.entry(fingerprint.clone())
|
|
33
33
|
.or_default()
|
|
@@ -72,6 +72,17 @@ impl QualityCheck for DuplicateBlockCheck {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
fn window_fingerprint(check_id: &str, lines: &[NormalizedLine]) -> String {
|
|
76
|
+
let mut hasher = Sha256::new();
|
|
77
|
+
hasher.update(check_id.as_bytes());
|
|
78
|
+
hasher.update(b"\0");
|
|
79
|
+
for line in lines {
|
|
80
|
+
hasher.update(line.value.as_bytes());
|
|
81
|
+
hasher.update(b"\n");
|
|
82
|
+
}
|
|
83
|
+
format!("sha256:{:x}", hasher.finalize())
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
#[derive(Debug, Clone)]
|
|
76
87
|
struct DuplicateOccurrence {
|
|
77
88
|
path: String,
|
|
@@ -12,6 +12,9 @@ impl QualityCheck for NearDuplicateFunctionCheck {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
|
|
15
|
+
if context.mode == super::super::types::QualityMode::Report {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
15
18
|
let symbols = collect_function_occurrences(context, self.id());
|
|
16
19
|
let mut emitted = HashSet::new();
|
|
17
20
|
|
|
@@ -59,8 +62,7 @@ fn collect_function_occurrences<'a>(
|
|
|
59
62
|
check_id: &str,
|
|
60
63
|
) -> Vec<FunctionOccurrence<'a>> {
|
|
61
64
|
context
|
|
62
|
-
.
|
|
63
|
-
.iter()
|
|
65
|
+
.comparison_candidate_files()
|
|
64
66
|
.filter(|file| {
|
|
65
67
|
is_code_like_path(&file.path)
|
|
66
68
|
&& context.config.check_enabled_for_path(check_id, &file.path)
|
|
@@ -4,7 +4,7 @@ mod near_duplicates;
|
|
|
4
4
|
use std::collections::HashSet;
|
|
5
5
|
|
|
6
6
|
use super::scanner::{stable_fingerprint, QualityContext};
|
|
7
|
-
use super::types::
|
|
7
|
+
use super::types::QualityViolation;
|
|
8
8
|
use duplicate_blocks::DuplicateBlockCheck;
|
|
9
9
|
use near_duplicates::NearDuplicateFunctionCheck;
|
|
10
10
|
|
|
@@ -64,12 +64,11 @@ impl QualityCheck for FileLengthCheck {
|
|
|
64
64
|
.filter(|file| context.check_applies_to(self.id(), &file.path))
|
|
65
65
|
{
|
|
66
66
|
let limits = context.limits_for(&file.path);
|
|
67
|
-
let limit =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
67
|
+
let limit = if context.mode.is_changed() && file.added_lines == file.line_count {
|
|
68
|
+
limits.max_new_file_lines
|
|
69
|
+
} else {
|
|
70
|
+
limits.max_file_lines
|
|
71
|
+
};
|
|
73
72
|
if file.line_count > limit {
|
|
74
73
|
violations.push(violation(
|
|
75
74
|
self.id(),
|
|
@@ -96,7 +95,7 @@ impl QualityCheck for DiffGrowthCheck {
|
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
fn evaluate(&self, context: &QualityContext, violations: &mut Vec<QualityViolation>) {
|
|
99
|
-
if context.mode
|
|
98
|
+
if !context.mode.is_changed() {
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
101
|
for file in context
|
|
@@ -68,17 +68,50 @@ pub fn cleanup_route_for_path(
|
|
|
68
68
|
} else {
|
|
69
69
|
"repository quality"
|
|
70
70
|
};
|
|
71
|
+
let agent_instructions = cleanup_instructions(path, topic, &violations);
|
|
71
72
|
QualityCleanupRoute {
|
|
72
73
|
schema: "naome.quality-cleanup-route.v1".to_string(),
|
|
73
74
|
path: path.to_string(),
|
|
74
75
|
violations,
|
|
75
76
|
related_paths,
|
|
76
|
-
agent_instructions
|
|
77
|
-
"Reduce or split {path} until every {topic} violation is gone. Prefer named modules and reusable helpers/components over dumping logic into generic directories. Keep behavior unchanged, add or preserve focused tests, then run naome quality check --changed before task completion."
|
|
78
|
-
),
|
|
77
|
+
agent_instructions,
|
|
79
78
|
required_checks: vec![
|
|
80
79
|
"naome quality check --changed".to_string(),
|
|
81
80
|
"git diff --check".to_string(),
|
|
82
81
|
],
|
|
83
82
|
}
|
|
84
83
|
}
|
|
84
|
+
|
|
85
|
+
fn cleanup_instructions(path: &str, topic: &str, violations: &[QualityViolation]) -> String {
|
|
86
|
+
let mut instructions = Vec::new();
|
|
87
|
+
instructions.push(format!(
|
|
88
|
+
"Clean up {path} until every {topic} violation is gone while preserving behavior."
|
|
89
|
+
));
|
|
90
|
+
for check_id in violations
|
|
91
|
+
.iter()
|
|
92
|
+
.map(|violation| violation.check_id.as_str())
|
|
93
|
+
.collect::<BTreeSet<_>>()
|
|
94
|
+
{
|
|
95
|
+
instructions.push(match check_id {
|
|
96
|
+
"test-source-pairing" => "Create or update a nearby or module-matched test that exercises the changed source behavior.".to_string(),
|
|
97
|
+
"dumping-ground-directory" => "Move feature logic out of generic utils/helpers/common/shared directories into a named module, or extract a reusable helper with a clear owner.".to_string(),
|
|
98
|
+
"directory-role-mixing" => "Separate source, generated, artifact, and dependency/vendor roles into directories that match repository policy.".to_string(),
|
|
99
|
+
"misplaced-file-role" => "Move the file to a configured root for its role, such as source, test, docs, config, or script.".to_string(),
|
|
100
|
+
"root-file-sprawl" => "Move new root-level work into an existing module, docs, config, or script directory unless it is a recognized root manifest.".to_string(),
|
|
101
|
+
"directory-size" => "Split the directory by module or responsibility until direct file count is below the configured limit.".to_string(),
|
|
102
|
+
"path-depth" => "Shorten the path by moving the file closer to its owning module boundary.".to_string(),
|
|
103
|
+
"case-collision" => "Rename colliding paths so they are distinct on case-insensitive filesystems.".to_string(),
|
|
104
|
+
"file-length" => "Reduce or split the file into focused modules with stable public behavior.".to_string(),
|
|
105
|
+
"function-length" => "Extract cohesive helper functions or components until each function fits the configured limit.".to_string(),
|
|
106
|
+
"top-level-symbols" => "Group related symbols into smaller modules and export only the intended public surface.".to_string(),
|
|
107
|
+
"duplicate-block" | "near-duplicate-function" => "Extract shared behavior into a reusable helper, fixture, builder, or component used by all duplicate call sites.".to_string(),
|
|
108
|
+
"diff-growth" => "Reduce the diff by narrowing scope, splitting generated changes, or moving unrelated cleanup to a separate task.".to_string(),
|
|
109
|
+
_ => format!("Resolve the {check_id} finding without weakening generic policy."),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
instructions.push(
|
|
113
|
+
"Run naome quality check --changed and git diff --check before task completion."
|
|
114
|
+
.to_string(),
|
|
115
|
+
);
|
|
116
|
+
instructions.join(" ")
|
|
117
|
+
}
|