@lamentis/naome 1.2.0 → 1.3.0
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 +108 -47
- package/bin/naome-node.js +2 -1579
- package/bin/naome.js +34 -5
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/dispatcher.rs +7 -2
- package/crates/naome-cli/src/main.rs +37 -22
- package/crates/naome-cli/src/quality_commands.rs +317 -10
- package/crates/naome-cli/src/workflow_commands.rs +21 -1
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/decision/checks.rs +64 -0
- package/crates/naome-core/src/decision/idle.rs +67 -0
- package/crates/naome-core/src/decision/json.rs +36 -0
- package/crates/naome-core/src/decision/states.rs +165 -0
- package/crates/naome-core/src/decision.rs +131 -353
- package/crates/naome-core/src/git.rs +4 -2
- package/crates/naome-core/src/install_plan.rs +4 -0
- package/crates/naome-core/src/lib.rs +12 -6
- package/crates/naome-core/src/paths.rs +3 -1
- package/crates/naome-core/src/quality/adapter_support.rs +89 -0
- package/crates/naome-core/src/quality/adapters.rs +20 -67
- package/crates/naome-core/src/quality/baseline.rs +8 -0
- package/crates/naome-core/src/quality/cache.rs +153 -0
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +25 -11
- 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 +48 -3
- package/crates/naome-core/src/quality/config.rs +8 -15
- package/crates/naome-core/src/quality/config_support.rs +24 -0
- package/crates/naome-core/src/quality/mod.rs +72 -6
- package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
- package/crates/naome-core/src/quality/scanner/analysis.rs +160 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
- package/crates/naome-core/src/quality/scanner.rs +200 -215
- package/crates/naome-core/src/quality/semantic/checks.rs +134 -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/adapters.rs +84 -0
- package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
- package/crates/naome-core/src/quality/structure/checks/directory.rs +134 -0
- package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
- package/crates/naome-core/src/quality/structure/checks.rs +124 -0
- package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
- package/crates/naome-core/src/quality/structure/classify.rs +146 -0
- package/crates/naome-core/src/quality/structure/config.rs +89 -0
- package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
- package/crates/naome-core/src/quality/structure/mod.rs +77 -0
- package/crates/naome-core/src/quality/structure/model.rs +131 -0
- package/crates/naome-core/src/quality/types.rs +43 -2
- package/crates/naome-core/src/route/builtin_checks.rs +141 -0
- package/crates/naome-core/src/route/builtin_context.rs +73 -0
- package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
- package/crates/naome-core/src/route/builtin_require.rs +40 -0
- package/crates/naome-core/src/route/context.rs +180 -0
- package/crates/naome-core/src/route/execution.rs +96 -0
- package/crates/naome-core/src/route/execution_baselines.rs +146 -0
- package/crates/naome-core/src/route/execution_support.rs +57 -0
- package/crates/naome-core/src/route/execution_tasks.rs +71 -0
- package/crates/naome-core/src/route/git_ops.rs +72 -0
- package/crates/naome-core/src/route/quality_gate.rs +73 -0
- package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
- package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
- package/crates/naome-core/src/route/worktree.rs +75 -0
- package/crates/naome-core/src/route/worktree_files.rs +32 -0
- package/crates/naome-core/src/route/worktree_plan.rs +131 -0
- package/crates/naome-core/src/route.rs +44 -1217
- package/crates/naome-core/src/verification.rs +1 -0
- package/crates/naome-core/src/workflow/doctor.rs +144 -0
- package/crates/naome-core/src/workflow/mod.rs +2 -0
- package/crates/naome-core/src/workflow/mutation.rs +1 -2
- package/crates/naome-core/tests/decision.rs +24 -118
- package/crates/naome-core/tests/harness_health.rs +2 -0
- package/crates/naome-core/tests/install_plan.rs +2 -0
- package/crates/naome-core/tests/quality.rs +26 -123
- package/crates/naome-core/tests/quality_performance.rs +231 -0
- package/crates/naome-core/tests/quality_structure.rs +116 -0
- package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
- package/crates/naome-core/tests/quality_structure_policy.rs +144 -0
- package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
- package/crates/naome-core/tests/repo_support/mod.rs +16 -0
- package/crates/naome-core/tests/repo_support/repo.rs +113 -0
- package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
- package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
- package/crates/naome-core/tests/repo_support/routes.rs +81 -0
- package/crates/naome-core/tests/repo_support/verification.rs +168 -0
- package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
- package/crates/naome-core/tests/route.rs +1 -1376
- package/crates/naome-core/tests/route_baseline.rs +86 -0
- package/crates/naome-core/tests/route_completion.rs +141 -0
- package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
- package/crates/naome-core/tests/route_user_diff.rs +202 -0
- package/crates/naome-core/tests/route_worktree.rs +54 -0
- package/crates/naome-core/tests/semantic_legacy.rs +140 -0
- package/crates/naome-core/tests/task_state.rs +60 -432
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
- package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
- package/crates/naome-core/tests/task_state_support/states.rs +84 -0
- package/crates/naome-core/tests/verification.rs +4 -45
- package/crates/naome-core/tests/verification_contract.rs +22 -78
- package/crates/naome-core/tests/workflow_doctor.rs +24 -0
- package/crates/naome-core/tests/workflow_policy.rs +6 -1
- package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
- package/installer/agents.js +90 -0
- package/installer/context.js +67 -0
- package/installer/filesystem.js +166 -0
- package/installer/flows.js +84 -0
- package/installer/git-boundary.js +171 -0
- package/installer/git-hook-content.js +36 -0
- package/installer/git-hooks.js +134 -0
- package/installer/git-local.js +2 -0
- package/installer/git-shared.js +35 -0
- package/installer/harness-file-ops.js +140 -0
- package/installer/harness-files.js +56 -0
- package/installer/harness-verification.js +123 -0
- package/installer/install-plan.js +66 -0
- package/installer/main.js +25 -0
- package/installer/manifest-state.js +167 -0
- package/installer/native-build.js +24 -0
- package/installer/native-format.js +6 -0
- package/installer/native.js +162 -0
- package/installer/output.js +131 -0
- package/installer/version.js +32 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +2 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
- package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
- package/templates/naome-root/.naome/bin/naome.js +32 -21
- package/templates/naome-root/.naome/manifest.json +5 -3
- package/templates/naome-root/.naome/repository-structure.json +90 -0
- package/templates/naome-root/.naome/verification.json +1 -0
- package/templates/naome-root/.naomeignore +1 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +16 -14
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +66 -4
- package/templates/naome-root/docs/naome/repository-structure.md +51 -0
- package/templates/naome-root/docs/naome/testing.md +2 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
|
|
4
|
+
use crate::models::NaomeError;
|
|
5
|
+
|
|
6
|
+
use super::adapters::{
|
|
7
|
+
apply_structure_adapters, detected_structure_adapter_ids, validate_structure_adapter_ids,
|
|
8
|
+
};
|
|
9
|
+
use crate::quality::config_support::validate_ready_schema;
|
|
10
|
+
|
|
11
|
+
use super::model::RepositoryStructureConfig;
|
|
12
|
+
|
|
13
|
+
const CONFIG_RELATIVE_PATH: &str = ".naome/repository-structure.json";
|
|
14
|
+
|
|
15
|
+
pub fn structure_config_relative_path() -> &'static str {
|
|
16
|
+
CONFIG_RELATIVE_PATH
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub fn read_structure_config(
|
|
20
|
+
root: &Path,
|
|
21
|
+
paths: &[String],
|
|
22
|
+
) -> Result<RepositoryStructureConfig, NaomeError> {
|
|
23
|
+
let path = root.join(CONFIG_RELATIVE_PATH);
|
|
24
|
+
let config = if path.is_file() {
|
|
25
|
+
serde_json::from_str(&fs::read_to_string(path)?)?
|
|
26
|
+
} else {
|
|
27
|
+
generated_structure_config(paths)
|
|
28
|
+
};
|
|
29
|
+
validate_structure_config(&config)?;
|
|
30
|
+
apply_structure_adapters(config)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn write_default_structure_config_if_missing(
|
|
34
|
+
root: &Path,
|
|
35
|
+
paths: &[String],
|
|
36
|
+
) -> Result<bool, NaomeError> {
|
|
37
|
+
let path = root.join(CONFIG_RELATIVE_PATH);
|
|
38
|
+
if path.exists() {
|
|
39
|
+
return Ok(false);
|
|
40
|
+
}
|
|
41
|
+
if let Some(parent) = path.parent() {
|
|
42
|
+
fs::create_dir_all(parent)?;
|
|
43
|
+
}
|
|
44
|
+
let content = serde_json::to_string_pretty(&generated_structure_config(paths))?;
|
|
45
|
+
fs::write(path, format!("{content}\n"))?;
|
|
46
|
+
Ok(true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn generated_structure_config(paths: &[String]) -> RepositoryStructureConfig {
|
|
50
|
+
let mut config = RepositoryStructureConfig::default();
|
|
51
|
+
config.enabled_adapters = detected_structure_adapter_ids(paths);
|
|
52
|
+
config
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn validate_structure_config(config: &RepositoryStructureConfig) -> Result<(), NaomeError> {
|
|
56
|
+
validate_ready_schema(
|
|
57
|
+
CONFIG_RELATIVE_PATH,
|
|
58
|
+
&config.schema,
|
|
59
|
+
"naome.repository-structure.v1",
|
|
60
|
+
config.version,
|
|
61
|
+
&config.status,
|
|
62
|
+
)?;
|
|
63
|
+
validate_structure_adapter_ids(&config.enabled_adapters)?;
|
|
64
|
+
if config.limits.max_directory_files == 0 {
|
|
65
|
+
return Err(NaomeError::new(
|
|
66
|
+
".naome/repository-structure.json maxDirectoryFiles must be greater than 0.",
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
if config.limits.max_path_depth == 0 {
|
|
70
|
+
return Err(NaomeError::new(
|
|
71
|
+
".naome/repository-structure.json maxPathDepth must be greater than 0.",
|
|
72
|
+
));
|
|
73
|
+
}
|
|
74
|
+
for rule in &config.directory_role_rules {
|
|
75
|
+
if rule.id.trim().is_empty() || rule.paths.is_empty() {
|
|
76
|
+
return Err(NaomeError::new(
|
|
77
|
+
".naome/repository-structure.json directoryRoleRules require id and paths.",
|
|
78
|
+
));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for rule in &config.layer_rules {
|
|
82
|
+
if rule.id.trim().is_empty() || rule.paths.is_empty() || rule.layer.trim().is_empty() {
|
|
83
|
+
return Err(NaomeError::new(
|
|
84
|
+
".naome/repository-structure.json layerRules require id, paths, and layer.",
|
|
85
|
+
));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
Ok(())
|
|
89
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
use super::model::{RepositoryStructureConfig, StructureLimits};
|
|
2
|
+
|
|
3
|
+
pub fn default_structure_config() -> RepositoryStructureConfig {
|
|
4
|
+
RepositoryStructureConfig {
|
|
5
|
+
schema: "naome.repository-structure.v1".to_string(),
|
|
6
|
+
version: 1,
|
|
7
|
+
status: "ready".to_string(),
|
|
8
|
+
enabled_adapters: Vec::new(),
|
|
9
|
+
source_roots: string_list(DEFAULT_SOURCE_ROOTS),
|
|
10
|
+
test_roots: string_list(DEFAULT_TEST_ROOTS),
|
|
11
|
+
docs_roots: string_list(DEFAULT_DOCS_ROOTS),
|
|
12
|
+
generated_roots: string_list(DEFAULT_GENERATED_ROOTS),
|
|
13
|
+
artifact_roots: string_list(DEFAULT_ARTIFACT_ROOTS),
|
|
14
|
+
module_roots: string_list(DEFAULT_MODULE_ROOTS),
|
|
15
|
+
allowed_root_files: string_list(DEFAULT_ALLOWED_ROOT_FILES),
|
|
16
|
+
directory_role_rules: Vec::new(),
|
|
17
|
+
layer_rules: Vec::new(),
|
|
18
|
+
ignored_paths: string_list(DEFAULT_IGNORED_PATHS),
|
|
19
|
+
disabled_checks: Vec::new(),
|
|
20
|
+
changed_code_policy: default_changed_policy(),
|
|
21
|
+
debt_policy: default_debt_policy(),
|
|
22
|
+
limits: StructureLimits::default(),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn default_changed_policy() -> String {
|
|
27
|
+
"block".to_string()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn default_debt_policy() -> String {
|
|
31
|
+
"report".to_string()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DEFAULT_SOURCE_ROOTS: &[&str] = &[
|
|
35
|
+
"src/**",
|
|
36
|
+
"app/**",
|
|
37
|
+
"apps/*/src/**",
|
|
38
|
+
"packages/*/src/**",
|
|
39
|
+
"crates/*/src/**",
|
|
40
|
+
"**/src/**",
|
|
41
|
+
"lib/**",
|
|
42
|
+
];
|
|
43
|
+
const DEFAULT_TEST_ROOTS: &[&str] = &[
|
|
44
|
+
"test/**",
|
|
45
|
+
"tests/**",
|
|
46
|
+
"__tests__/**",
|
|
47
|
+
"packages/*/test/**",
|
|
48
|
+
"packages/*/tests/**",
|
|
49
|
+
"crates/*/tests/**",
|
|
50
|
+
"**/tests/**",
|
|
51
|
+
"scripts/*.test.js",
|
|
52
|
+
];
|
|
53
|
+
const DEFAULT_DOCS_ROOTS: &[&str] = &["docs/**", "doc/**"];
|
|
54
|
+
const DEFAULT_GENERATED_ROOTS: &[&str] = &[
|
|
55
|
+
"**/generated/**",
|
|
56
|
+
"**/__generated__/**",
|
|
57
|
+
"**/codegen/**",
|
|
58
|
+
"**/*.generated.*",
|
|
59
|
+
];
|
|
60
|
+
const DEFAULT_ARTIFACT_ROOTS: &[&str] = &[
|
|
61
|
+
"dist/**",
|
|
62
|
+
"build/**",
|
|
63
|
+
"coverage/**",
|
|
64
|
+
".next/**",
|
|
65
|
+
"target/**",
|
|
66
|
+
"**/dist/**",
|
|
67
|
+
"**/build/**",
|
|
68
|
+
];
|
|
69
|
+
const DEFAULT_MODULE_ROOTS: &[&str] = &[
|
|
70
|
+
"src/**",
|
|
71
|
+
"app/**",
|
|
72
|
+
"packages/*/src/**",
|
|
73
|
+
"crates/*/src/**",
|
|
74
|
+
"**/src/**",
|
|
75
|
+
];
|
|
76
|
+
const DEFAULT_ALLOWED_ROOT_FILES: &[&str] = &[
|
|
77
|
+
"README.md",
|
|
78
|
+
"LICENSE",
|
|
79
|
+
"NOTICE",
|
|
80
|
+
"CHANGELOG.md",
|
|
81
|
+
"CONTRIBUTING.md",
|
|
82
|
+
"SECURITY.md",
|
|
83
|
+
"AGENTS.md",
|
|
84
|
+
"package.json",
|
|
85
|
+
"Cargo.toml",
|
|
86
|
+
"pyproject.toml",
|
|
87
|
+
"go.mod",
|
|
88
|
+
"go.sum",
|
|
89
|
+
"Makefile",
|
|
90
|
+
".gitignore",
|
|
91
|
+
".naomeignore",
|
|
92
|
+
];
|
|
93
|
+
const DEFAULT_IGNORED_PATHS: &[&str] = &[
|
|
94
|
+
".git/**",
|
|
95
|
+
"node_modules/**",
|
|
96
|
+
"vendor/**",
|
|
97
|
+
"target/**",
|
|
98
|
+
"**/target/**",
|
|
99
|
+
"dist/**",
|
|
100
|
+
"build/**",
|
|
101
|
+
"coverage/**",
|
|
102
|
+
".next/**",
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
fn string_list(values: &[&str]) -> Vec<String> {
|
|
106
|
+
values.iter().map(|value| (*value).to_string()).collect()
|
|
107
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
mod adapters;
|
|
2
|
+
mod checks;
|
|
3
|
+
mod classify;
|
|
4
|
+
mod config;
|
|
5
|
+
mod defaults;
|
|
6
|
+
mod model;
|
|
7
|
+
|
|
8
|
+
use std::collections::HashSet;
|
|
9
|
+
use std::path::Path;
|
|
10
|
+
|
|
11
|
+
use crate::models::NaomeError;
|
|
12
|
+
|
|
13
|
+
use super::scanner::QualityContext;
|
|
14
|
+
use super::types::QualityViolation;
|
|
15
|
+
use checks::run_structure_checks;
|
|
16
|
+
use classify::build_structure_model;
|
|
17
|
+
use config::read_structure_config;
|
|
18
|
+
pub use config::{structure_config_relative_path, write_default_structure_config_if_missing};
|
|
19
|
+
pub use model::{RepositoryStructureConfig, StructurePathExplanation};
|
|
20
|
+
|
|
21
|
+
pub fn run_repository_structure_checks(
|
|
22
|
+
root: &Path,
|
|
23
|
+
context: &QualityContext,
|
|
24
|
+
baseline_fingerprints: &HashSet<String>,
|
|
25
|
+
) -> Result<Vec<QualityViolation>, NaomeError> {
|
|
26
|
+
let model = structure_model(root, context, baseline_fingerprints)?;
|
|
27
|
+
let mut violations = run_structure_checks(&model, context.mode);
|
|
28
|
+
violations.retain(|violation| {
|
|
29
|
+
violation.check_id != "test-source-pairing"
|
|
30
|
+
|| context
|
|
31
|
+
.files
|
|
32
|
+
.iter()
|
|
33
|
+
.find(|file| file.path == violation.path)
|
|
34
|
+
.is_some_and(|file| !file.symbols.is_empty())
|
|
35
|
+
});
|
|
36
|
+
if !context.mode.is_changed() {
|
|
37
|
+
for violation in &mut violations {
|
|
38
|
+
violation.baseline = baseline_fingerprints.contains(&violation.fingerprint);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
Ok(violations)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn explain_repository_structure(
|
|
45
|
+
root: &Path,
|
|
46
|
+
path: impl AsRef<str>,
|
|
47
|
+
) -> Result<StructurePathExplanation, NaomeError> {
|
|
48
|
+
let repo_paths = super::scanner::collect_repo_paths(root)?;
|
|
49
|
+
let changed_paths = crate::git::changed_paths(root)?;
|
|
50
|
+
let config = read_structure_config(root, &repo_paths)?;
|
|
51
|
+
let model = build_structure_model(config, &repo_paths, &changed_paths, &HashSet::new());
|
|
52
|
+
let normalized = path.as_ref().replace('\\', "/");
|
|
53
|
+
model
|
|
54
|
+
.paths
|
|
55
|
+
.into_iter()
|
|
56
|
+
.find(|entry| entry.explanation.path == normalized)
|
|
57
|
+
.map(|entry| entry.explanation)
|
|
58
|
+
.ok_or_else(|| {
|
|
59
|
+
NaomeError::new(format!(
|
|
60
|
+
"{normalized} is not visible to structure analysis."
|
|
61
|
+
))
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn structure_model(
|
|
66
|
+
root: &Path,
|
|
67
|
+
context: &QualityContext,
|
|
68
|
+
baseline_fingerprints: &HashSet<String>,
|
|
69
|
+
) -> Result<model::RepositoryStructureModel, NaomeError> {
|
|
70
|
+
let config = read_structure_config(root, &context.repo_paths)?;
|
|
71
|
+
Ok(build_structure_model(
|
|
72
|
+
config,
|
|
73
|
+
&context.repo_paths,
|
|
74
|
+
&context.changed_paths,
|
|
75
|
+
baseline_fingerprints,
|
|
76
|
+
))
|
|
77
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
2
|
+
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
|
|
5
|
+
use super::defaults::{default_changed_policy, default_debt_policy, default_structure_config};
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
8
|
+
#[serde(rename_all = "camelCase")]
|
|
9
|
+
pub struct StructureLimits {
|
|
10
|
+
pub max_directory_files: usize,
|
|
11
|
+
pub max_path_depth: usize,
|
|
12
|
+
pub max_directory_roles: usize,
|
|
13
|
+
pub max_dumping_ground_files: usize,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl Default for StructureLimits {
|
|
17
|
+
fn default() -> Self {
|
|
18
|
+
Self {
|
|
19
|
+
max_directory_files: 40,
|
|
20
|
+
max_path_depth: 10,
|
|
21
|
+
max_directory_roles: 2,
|
|
22
|
+
max_dumping_ground_files: 12,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
28
|
+
#[serde(rename_all = "camelCase")]
|
|
29
|
+
pub struct DirectoryRoleRule {
|
|
30
|
+
pub id: String,
|
|
31
|
+
pub paths: Vec<String>,
|
|
32
|
+
#[serde(default)]
|
|
33
|
+
pub allowed_roles: Vec<String>,
|
|
34
|
+
#[serde(default)]
|
|
35
|
+
pub max_roles: Option<usize>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
39
|
+
#[serde(rename_all = "camelCase")]
|
|
40
|
+
pub struct LayerRule {
|
|
41
|
+
pub id: String,
|
|
42
|
+
pub paths: Vec<String>,
|
|
43
|
+
pub layer: String,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
47
|
+
#[serde(rename_all = "camelCase")]
|
|
48
|
+
pub struct RepositoryStructureConfig {
|
|
49
|
+
pub schema: String,
|
|
50
|
+
pub version: u32,
|
|
51
|
+
pub status: String,
|
|
52
|
+
#[serde(default)]
|
|
53
|
+
pub enabled_adapters: Vec<String>,
|
|
54
|
+
#[serde(default)]
|
|
55
|
+
pub source_roots: Vec<String>,
|
|
56
|
+
#[serde(default)]
|
|
57
|
+
pub test_roots: Vec<String>,
|
|
58
|
+
#[serde(default)]
|
|
59
|
+
pub docs_roots: Vec<String>,
|
|
60
|
+
#[serde(default)]
|
|
61
|
+
pub generated_roots: Vec<String>,
|
|
62
|
+
#[serde(default)]
|
|
63
|
+
pub artifact_roots: Vec<String>,
|
|
64
|
+
#[serde(default)]
|
|
65
|
+
pub module_roots: Vec<String>,
|
|
66
|
+
#[serde(default)]
|
|
67
|
+
pub allowed_root_files: Vec<String>,
|
|
68
|
+
#[serde(default)]
|
|
69
|
+
pub directory_role_rules: Vec<DirectoryRoleRule>,
|
|
70
|
+
#[serde(default)]
|
|
71
|
+
pub layer_rules: Vec<LayerRule>,
|
|
72
|
+
#[serde(default)]
|
|
73
|
+
pub ignored_paths: Vec<String>,
|
|
74
|
+
#[serde(default)]
|
|
75
|
+
pub disabled_checks: Vec<String>,
|
|
76
|
+
#[serde(default = "default_changed_policy")]
|
|
77
|
+
pub changed_code_policy: String,
|
|
78
|
+
#[serde(default = "default_debt_policy")]
|
|
79
|
+
pub debt_policy: String,
|
|
80
|
+
#[serde(default)]
|
|
81
|
+
pub limits: StructureLimits,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl Default for RepositoryStructureConfig {
|
|
85
|
+
fn default() -> Self {
|
|
86
|
+
default_structure_config()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Clone, Serialize)]
|
|
91
|
+
#[serde(rename_all = "camelCase")]
|
|
92
|
+
pub struct StructurePathExplanation {
|
|
93
|
+
pub path: String,
|
|
94
|
+
pub role: String,
|
|
95
|
+
pub module: Option<String>,
|
|
96
|
+
pub layer: String,
|
|
97
|
+
pub language: Option<String>,
|
|
98
|
+
pub generated: bool,
|
|
99
|
+
pub debt: bool,
|
|
100
|
+
pub changed: bool,
|
|
101
|
+
pub directory: String,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[derive(Debug, Clone)]
|
|
105
|
+
pub struct StructurePath {
|
|
106
|
+
pub explanation: StructurePathExplanation,
|
|
107
|
+
pub segments: Vec<String>,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[derive(Debug, Clone)]
|
|
111
|
+
pub struct StructureDirectory {
|
|
112
|
+
pub path: String,
|
|
113
|
+
pub roles: BTreeSet<String>,
|
|
114
|
+
pub modules: BTreeSet<String>,
|
|
115
|
+
pub file_count: usize,
|
|
116
|
+
pub direct_changed_paths: Vec<String>,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[derive(Debug, Clone)]
|
|
120
|
+
pub struct RepositoryStructureModel {
|
|
121
|
+
pub config: RepositoryStructureConfig,
|
|
122
|
+
pub paths: Vec<StructurePath>,
|
|
123
|
+
pub directories: Vec<StructureDirectory>,
|
|
124
|
+
pub module_paths: BTreeMap<String, Vec<String>>,
|
|
125
|
+
#[allow(dead_code)]
|
|
126
|
+
pub directory_paths: BTreeMap<String, Vec<String>>,
|
|
127
|
+
#[allow(dead_code)]
|
|
128
|
+
pub role_paths: BTreeMap<String, Vec<String>>,
|
|
129
|
+
pub lowercase_paths: BTreeMap<String, Vec<String>>,
|
|
130
|
+
pub changed_paths: BTreeSet<String>,
|
|
131
|
+
}
|
|
@@ -4,15 +4,45 @@ use crate::paths;
|
|
|
4
4
|
|
|
5
5
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
6
6
|
pub enum QualityMode {
|
|
7
|
-
|
|
7
|
+
ChangedFast,
|
|
8
8
|
Report,
|
|
9
|
+
DeepReport,
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
impl QualityMode {
|
|
13
|
+
#[allow(non_upper_case_globals)]
|
|
14
|
+
pub const Changed: Self = Self::ChangedFast;
|
|
15
|
+
|
|
12
16
|
pub fn as_str(self) -> &'static str {
|
|
13
17
|
match self {
|
|
14
|
-
Self::
|
|
18
|
+
Self::ChangedFast => "changed",
|
|
15
19
|
Self::Report => "report",
|
|
20
|
+
Self::DeepReport => "deep-report",
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn is_changed(self) -> bool {
|
|
25
|
+
self == Self::ChangedFast
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn is_deep(self) -> bool {
|
|
29
|
+
self == Self::DeepReport
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
34
|
+
pub enum QualityInitMode {
|
|
35
|
+
SeedOnly,
|
|
36
|
+
Baseline,
|
|
37
|
+
DeepBaseline,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl QualityInitMode {
|
|
41
|
+
pub fn as_str(self) -> &'static str {
|
|
42
|
+
match self {
|
|
43
|
+
Self::SeedOnly => "init",
|
|
44
|
+
Self::Baseline => "baseline",
|
|
45
|
+
Self::DeepBaseline => "deep-baseline",
|
|
16
46
|
}
|
|
17
47
|
}
|
|
18
48
|
}
|
|
@@ -187,9 +217,14 @@ pub struct QualityReport {
|
|
|
187
217
|
#[serde(rename_all = "camelCase")]
|
|
188
218
|
pub struct QualitySummary {
|
|
189
219
|
pub scanned_files: usize,
|
|
220
|
+
pub scanned_path_count: usize,
|
|
190
221
|
pub violation_count: usize,
|
|
191
222
|
pub baseline_violation_count: usize,
|
|
192
223
|
pub blocking_violation_count: usize,
|
|
224
|
+
pub truncated: bool,
|
|
225
|
+
pub reason_codes: Vec<String>,
|
|
226
|
+
pub cache_hits: usize,
|
|
227
|
+
pub cache_misses: usize,
|
|
193
228
|
}
|
|
194
229
|
|
|
195
230
|
#[derive(Debug, Clone, Serialize)]
|
|
@@ -211,10 +246,14 @@ pub struct QualityViolation {
|
|
|
211
246
|
#[serde(rename_all = "camelCase")]
|
|
212
247
|
pub struct QualityInitResult {
|
|
213
248
|
pub schema: String,
|
|
249
|
+
pub mode: String,
|
|
214
250
|
pub config_written: bool,
|
|
251
|
+
pub structure_config_written: bool,
|
|
215
252
|
pub baseline_written: bool,
|
|
253
|
+
pub baseline_pending: bool,
|
|
216
254
|
pub baseline_violations: usize,
|
|
217
255
|
pub config_path: String,
|
|
256
|
+
pub structure_config_path: String,
|
|
218
257
|
pub baseline_path: String,
|
|
219
258
|
}
|
|
220
259
|
|
|
@@ -256,9 +295,11 @@ pub fn default_generated_paths() -> Vec<String> {
|
|
|
256
295
|
const DEFAULT_IGNORED_PATHS: &str = r#"
|
|
257
296
|
.git/**
|
|
258
297
|
.naome/archive/**
|
|
298
|
+
.naome/cache/**
|
|
259
299
|
.naome/task-state.json
|
|
260
300
|
.naome/task-journal.jsonl
|
|
261
301
|
.naome/repository-quality.json
|
|
302
|
+
.naome/repository-structure.json
|
|
262
303
|
.naome/repository-quality-baseline.json
|
|
263
304
|
node_modules/**
|
|
264
305
|
.npm/**
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::harness_health::{validate_harness_health, HarnessHealthOptions};
|
|
4
|
+
use crate::models::NaomeError;
|
|
5
|
+
use crate::quality::{check_repository_quality, QualityMode};
|
|
6
|
+
use crate::route::git_ops::{command_output, git_output};
|
|
7
|
+
use crate::route::quality_gate::QualityCheck;
|
|
8
|
+
use crate::task_state::{validate_task_state, TaskStateMode, TaskStateOptions};
|
|
9
|
+
use crate::verification_contract::validate_verification_contract;
|
|
10
|
+
|
|
11
|
+
use super::builtin_context::{run_context_budget_check, template_root};
|
|
12
|
+
use super::builtin_integrity::packaged_harness_integrity;
|
|
13
|
+
use super::builtin_require::{require_builtin_quality_check, require_builtin_quality_check_any};
|
|
14
|
+
|
|
15
|
+
pub(super) fn run_quality_check(
|
|
16
|
+
root: &Path,
|
|
17
|
+
check_id: &str,
|
|
18
|
+
check: &QualityCheck,
|
|
19
|
+
) -> Result<(), NaomeError> {
|
|
20
|
+
match check_id {
|
|
21
|
+
"diff-check" => {
|
|
22
|
+
require_builtin_quality_check(check_id, check, "git diff --check")?;
|
|
23
|
+
let output = git_output(root, &["diff", "--check"])?;
|
|
24
|
+
|
|
25
|
+
if output.status.success() {
|
|
26
|
+
Ok(())
|
|
27
|
+
} else {
|
|
28
|
+
Err(NaomeError::new(command_output(&output)))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
"naome-harness-health" => {
|
|
32
|
+
require_builtin_quality_check(
|
|
33
|
+
check_id,
|
|
34
|
+
check,
|
|
35
|
+
"node .naome/bin/check-harness-health.js",
|
|
36
|
+
)?;
|
|
37
|
+
run_harness_health_check(root)
|
|
38
|
+
}
|
|
39
|
+
"task-state-check" => {
|
|
40
|
+
require_builtin_quality_check(check_id, check, "npm run check:task-state")?;
|
|
41
|
+
run_template_task_state_check(root)
|
|
42
|
+
}
|
|
43
|
+
"verification-contract-check" => {
|
|
44
|
+
require_builtin_quality_check(
|
|
45
|
+
check_id,
|
|
46
|
+
check,
|
|
47
|
+
"npm run check:verification-contract",
|
|
48
|
+
)?;
|
|
49
|
+
run_template_verification_contract_check(root)
|
|
50
|
+
}
|
|
51
|
+
"context-budget-check" => {
|
|
52
|
+
require_builtin_quality_check(check_id, check, "npm run check:context-budget")?;
|
|
53
|
+
run_context_budget_check(root)
|
|
54
|
+
}
|
|
55
|
+
"repository-quality-check" => {
|
|
56
|
+
require_builtin_quality_check_any(
|
|
57
|
+
check_id,
|
|
58
|
+
check,
|
|
59
|
+
&[
|
|
60
|
+
"naome quality check --changed",
|
|
61
|
+
"node .naome/bin/naome.js quality check --changed",
|
|
62
|
+
"npm run check:repository-quality",
|
|
63
|
+
],
|
|
64
|
+
)?;
|
|
65
|
+
run_repository_quality_check(root)
|
|
66
|
+
}
|
|
67
|
+
_ => Err(NaomeError::new(format!(
|
|
68
|
+
"Quality check {check_id} is not a built-in safe check; NAOME will not execute repository-controlled verification commands."
|
|
69
|
+
))),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn run_repository_quality_check(root: &Path) -> Result<(), NaomeError> {
|
|
74
|
+
let report = check_repository_quality(root, QualityMode::ChangedFast)?;
|
|
75
|
+
if report.ok {
|
|
76
|
+
return Ok(());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let details = report
|
|
80
|
+
.violations
|
|
81
|
+
.iter()
|
|
82
|
+
.take(20)
|
|
83
|
+
.map(|violation| {
|
|
84
|
+
let location = violation
|
|
85
|
+
.line
|
|
86
|
+
.map(|line| format!("{}:{line}", violation.path))
|
|
87
|
+
.unwrap_or_else(|| violation.path.clone());
|
|
88
|
+
format!("{location} {}: {}", violation.check_id, violation.message)
|
|
89
|
+
})
|
|
90
|
+
.collect::<Vec<_>>()
|
|
91
|
+
.join("\n");
|
|
92
|
+
Err(NaomeError::new(format!(
|
|
93
|
+
"repository-quality-check failed with {} violation(s).\n{}",
|
|
94
|
+
report.violations.len(),
|
|
95
|
+
details
|
|
96
|
+
)))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn run_harness_health_check(root: &Path) -> Result<(), NaomeError> {
|
|
100
|
+
let errors = validate_harness_health(
|
|
101
|
+
root,
|
|
102
|
+
HarnessHealthOptions {
|
|
103
|
+
expected_integrity: packaged_harness_integrity()?,
|
|
104
|
+
..HarnessHealthOptions::default()
|
|
105
|
+
},
|
|
106
|
+
)?;
|
|
107
|
+
if errors.is_empty() {
|
|
108
|
+
Ok(())
|
|
109
|
+
} else {
|
|
110
|
+
Err(NaomeError::new(errors.join("\n")))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn run_template_task_state_check(root: &Path) -> Result<(), NaomeError> {
|
|
115
|
+
let template_root = template_root(root);
|
|
116
|
+
let report = validate_task_state(
|
|
117
|
+
&template_root,
|
|
118
|
+
TaskStateOptions {
|
|
119
|
+
mode: TaskStateMode::State,
|
|
120
|
+
harness_health: Some(HarnessHealthOptions {
|
|
121
|
+
expected_integrity: packaged_harness_integrity()?,
|
|
122
|
+
allow_missing_archive: true,
|
|
123
|
+
..HarnessHealthOptions::default()
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
)?;
|
|
127
|
+
if report.errors.is_empty() {
|
|
128
|
+
Ok(())
|
|
129
|
+
} else {
|
|
130
|
+
Err(NaomeError::new(report.errors.join("\n")))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn run_template_verification_contract_check(root: &Path) -> Result<(), NaomeError> {
|
|
135
|
+
let errors = validate_verification_contract(&template_root(root))?;
|
|
136
|
+
if errors.is_empty() {
|
|
137
|
+
Ok(())
|
|
138
|
+
} else {
|
|
139
|
+
Err(NaomeError::new(errors.join("\n")))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
|
|
4
|
+
use crate::models::NaomeError;
|
|
5
|
+
|
|
6
|
+
pub(super) fn run_context_budget_check(root: &Path) -> Result<(), NaomeError> {
|
|
7
|
+
let template_root = template_root(root);
|
|
8
|
+
let mut context_files = vec![
|
|
9
|
+
template_root.join("AGENTS.md"),
|
|
10
|
+
template_root.join(".naomeignore"),
|
|
11
|
+
];
|
|
12
|
+
context_files.extend(markdown_files(&template_root.join("docs").join("naome"))?);
|
|
13
|
+
context_files.sort();
|
|
14
|
+
|
|
15
|
+
let mut errors = Vec::new();
|
|
16
|
+
for path in context_files {
|
|
17
|
+
let content = fs::read_to_string(&path)?;
|
|
18
|
+
let line_count = count_lines(&content);
|
|
19
|
+
if line_count > 200 {
|
|
20
|
+
errors.push(format!(
|
|
21
|
+
"{}: {line_count} lines",
|
|
22
|
+
display_repo_path(root, &path)
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if errors.is_empty() {
|
|
28
|
+
Ok(())
|
|
29
|
+
} else {
|
|
30
|
+
Err(NaomeError::new(format!(
|
|
31
|
+
"NAOME context budget exceeded. Limit: 200 lines per file.\n{}",
|
|
32
|
+
errors.join("\n")
|
|
33
|
+
)))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(super) fn template_root(root: &Path) -> PathBuf {
|
|
38
|
+
root.join("packages")
|
|
39
|
+
.join("naome")
|
|
40
|
+
.join("templates")
|
|
41
|
+
.join("naome-root")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn markdown_files(dir: &Path) -> Result<Vec<PathBuf>, NaomeError> {
|
|
45
|
+
let mut files = Vec::new();
|
|
46
|
+
for entry in fs::read_dir(dir)? {
|
|
47
|
+
let entry = entry?;
|
|
48
|
+
let path = entry.path();
|
|
49
|
+
if path.is_dir() {
|
|
50
|
+
files.extend(markdown_files(&path)?);
|
|
51
|
+
} else if path.is_file() && path.extension().is_some_and(|extension| extension == "md") {
|
|
52
|
+
files.push(path);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
Ok(files)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn count_lines(content: &str) -> usize {
|
|
59
|
+
if content.is_empty() {
|
|
60
|
+
0
|
|
61
|
+
} else if content.ends_with('\n') {
|
|
62
|
+
content.split('\n').count() - 1
|
|
63
|
+
} else {
|
|
64
|
+
content.split('\n').count()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn display_repo_path(root: &Path, path: &Path) -> String {
|
|
69
|
+
path.strip_prefix(root)
|
|
70
|
+
.unwrap_or(path)
|
|
71
|
+
.to_string_lossy()
|
|
72
|
+
.to_string()
|
|
73
|
+
}
|