@lamentis/naome 1.2.0 → 1.2.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.
Files changed (113) hide show
  1. package/Cargo.lock +2 -2
  2. package/bin/naome-node.js +2 -1579
  3. package/bin/naome.js +19 -5
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/dispatcher.rs +2 -1
  6. package/crates/naome-cli/src/main.rs +3 -0
  7. package/crates/naome-cli/src/quality_commands.rs +90 -2
  8. package/crates/naome-core/Cargo.toml +1 -1
  9. package/crates/naome-core/src/decision/checks.rs +64 -0
  10. package/crates/naome-core/src/decision/idle.rs +67 -0
  11. package/crates/naome-core/src/decision/json.rs +36 -0
  12. package/crates/naome-core/src/decision/states.rs +165 -0
  13. package/crates/naome-core/src/decision.rs +131 -353
  14. package/crates/naome-core/src/install_plan.rs +2 -0
  15. package/crates/naome-core/src/lib.rs +5 -3
  16. package/crates/naome-core/src/paths.rs +3 -1
  17. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  18. package/crates/naome-core/src/quality/adapters.rs +20 -67
  19. package/crates/naome-core/src/quality/cleanup.rs +13 -1
  20. package/crates/naome-core/src/quality/config.rs +8 -15
  21. package/crates/naome-core/src/quality/config_support.rs +24 -0
  22. package/crates/naome-core/src/quality/mod.rs +18 -0
  23. package/crates/naome-core/src/quality/scanner.rs +20 -8
  24. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  25. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  26. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  27. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  28. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  29. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  30. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  31. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  32. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  33. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  34. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  35. package/crates/naome-core/src/quality/types.rs +3 -0
  36. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  37. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  38. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  39. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  40. package/crates/naome-core/src/route/context.rs +180 -0
  41. package/crates/naome-core/src/route/execution.rs +96 -0
  42. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  43. package/crates/naome-core/src/route/execution_support.rs +57 -0
  44. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  45. package/crates/naome-core/src/route/git_ops.rs +72 -0
  46. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  47. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  48. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  49. package/crates/naome-core/src/route/worktree.rs +75 -0
  50. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  51. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  52. package/crates/naome-core/src/route.rs +44 -1217
  53. package/crates/naome-core/src/verification.rs +1 -0
  54. package/crates/naome-core/tests/decision.rs +24 -118
  55. package/crates/naome-core/tests/harness_health.rs +2 -0
  56. package/crates/naome-core/tests/quality.rs +12 -118
  57. package/crates/naome-core/tests/quality_structure.rs +116 -0
  58. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  59. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  60. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  61. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  62. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  63. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  64. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  65. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  66. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  67. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  68. package/crates/naome-core/tests/route.rs +1 -1376
  69. package/crates/naome-core/tests/route_baseline.rs +86 -0
  70. package/crates/naome-core/tests/route_completion.rs +141 -0
  71. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  72. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  73. package/crates/naome-core/tests/route_worktree.rs +54 -0
  74. package/crates/naome-core/tests/task_state.rs +60 -432
  75. package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
  76. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  77. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  78. package/crates/naome-core/tests/verification.rs +4 -45
  79. package/crates/naome-core/tests/verification_contract.rs +22 -78
  80. package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
  81. package/installer/agents.js +90 -0
  82. package/installer/context.js +67 -0
  83. package/installer/filesystem.js +166 -0
  84. package/installer/flows.js +84 -0
  85. package/installer/git-boundary.js +170 -0
  86. package/installer/git-hook-content.js +36 -0
  87. package/installer/git-hooks.js +134 -0
  88. package/installer/git-local.js +2 -0
  89. package/installer/git-shared.js +35 -0
  90. package/installer/harness-file-ops.js +140 -0
  91. package/installer/harness-files.js +56 -0
  92. package/installer/harness-verification.js +123 -0
  93. package/installer/install-plan.js +66 -0
  94. package/installer/main.js +25 -0
  95. package/installer/manifest-state.js +167 -0
  96. package/installer/native-build.js +24 -0
  97. package/installer/native-format.js +6 -0
  98. package/installer/native.js +162 -0
  99. package/installer/output.js +131 -0
  100. package/installer/version.js +32 -0
  101. package/native/darwin-arm64/naome +0 -0
  102. package/native/linux-x64/naome +0 -0
  103. package/package.json +2 -1
  104. package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
  105. package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
  106. package/templates/naome-root/.naome/bin/naome.js +25 -21
  107. package/templates/naome-root/.naome/manifest.json +4 -2
  108. package/templates/naome-root/.naome/repository-structure.json +90 -0
  109. package/templates/naome-root/.naome/verification.json +1 -0
  110. package/templates/naome-root/docs/naome/index.md +4 -3
  111. package/templates/naome-root/docs/naome/repository-quality.md +3 -0
  112. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  113. package/templates/naome-root/docs/naome/testing.md +2 -1
@@ -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::{QualityMode, 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 == QualityMode::Report {
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,124 @@
1
+ use std::collections::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
+ }
@@ -212,9 +212,11 @@ pub struct QualityViolation {
212
212
  pub struct QualityInitResult {
213
213
  pub schema: String,
214
214
  pub config_written: bool,
215
+ pub structure_config_written: bool,
215
216
  pub baseline_written: bool,
216
217
  pub baseline_violations: usize,
217
218
  pub config_path: String,
219
+ pub structure_config_path: String,
218
220
  pub baseline_path: String,
219
221
  }
220
222
 
@@ -259,6 +261,7 @@ const DEFAULT_IGNORED_PATHS: &str = r#"
259
261
  .naome/task-state.json
260
262
  .naome/task-journal.jsonl
261
263
  .naome/repository-quality.json
264
+ .naome/repository-structure.json
262
265
  .naome/repository-quality-baseline.json
263
266
  node_modules/**
264
267
  .npm/**
@@ -0,0 +1,155 @@
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
+ "installer-tests" => require_builtin_quality_check(
22
+ check_id,
23
+ check,
24
+ "npm run test:naome-installer",
25
+ ),
26
+ "rust-build" => require_builtin_quality_check(check_id, check, "npm run build:rust"),
27
+ "decision-engine-tests" => {
28
+ require_builtin_quality_check(check_id, check, "npm run test:decision-engine")
29
+ }
30
+ "package-dry-run" => require_builtin_quality_check(check_id, check, "npm run pack:dry-run"),
31
+ "diff-check" => {
32
+ require_builtin_quality_check(check_id, check, "git diff --check")?;
33
+ let output = git_output(root, &["diff", "--check"])?;
34
+
35
+ if output.status.success() {
36
+ Ok(())
37
+ } else {
38
+ Err(NaomeError::new(command_output(&output)))
39
+ }
40
+ }
41
+ "naome-harness-health" => {
42
+ require_builtin_quality_check(
43
+ check_id,
44
+ check,
45
+ "node .naome/bin/check-harness-health.js",
46
+ )?;
47
+ run_harness_health_check(root)
48
+ }
49
+ "dogfood-health" => {
50
+ require_builtin_quality_check(check_id, check, "npm run dogfood:health")?;
51
+ run_harness_health_check(root)
52
+ }
53
+ "task-state-check" => {
54
+ require_builtin_quality_check(check_id, check, "npm run check:task-state")?;
55
+ run_template_task_state_check(root)
56
+ }
57
+ "verification-contract-check" => {
58
+ require_builtin_quality_check(
59
+ check_id,
60
+ check,
61
+ "npm run check:verification-contract",
62
+ )?;
63
+ run_template_verification_contract_check(root)
64
+ }
65
+ "context-budget-check" => {
66
+ require_builtin_quality_check(check_id, check, "npm run check:context-budget")?;
67
+ run_context_budget_check(root)
68
+ }
69
+ "repository-quality-check" => {
70
+ require_builtin_quality_check_any(
71
+ check_id,
72
+ check,
73
+ &[
74
+ "naome quality check --changed",
75
+ "node .naome/bin/naome.js quality check --changed",
76
+ "npm run check:repository-quality",
77
+ ],
78
+ )?;
79
+ run_repository_quality_check(root)
80
+ }
81
+ _ => Err(NaomeError::new(format!(
82
+ "Quality check {check_id} is not a built-in safe check; NAOME will not execute repository-controlled verification commands."
83
+ ))),
84
+ }
85
+ }
86
+
87
+ fn run_repository_quality_check(root: &Path) -> Result<(), NaomeError> {
88
+ let report = check_repository_quality(root, QualityMode::Changed)?;
89
+ if report.ok {
90
+ return Ok(());
91
+ }
92
+
93
+ let details = report
94
+ .violations
95
+ .iter()
96
+ .take(20)
97
+ .map(|violation| {
98
+ let location = violation
99
+ .line
100
+ .map(|line| format!("{}:{line}", violation.path))
101
+ .unwrap_or_else(|| violation.path.clone());
102
+ format!("{location} {}: {}", violation.check_id, violation.message)
103
+ })
104
+ .collect::<Vec<_>>()
105
+ .join("\n");
106
+ Err(NaomeError::new(format!(
107
+ "repository-quality-check failed with {} violation(s).\n{}",
108
+ report.violations.len(),
109
+ details
110
+ )))
111
+ }
112
+
113
+ fn run_harness_health_check(root: &Path) -> Result<(), NaomeError> {
114
+ let errors = validate_harness_health(
115
+ root,
116
+ HarnessHealthOptions {
117
+ expected_integrity: packaged_harness_integrity()?,
118
+ ..HarnessHealthOptions::default()
119
+ },
120
+ )?;
121
+ if errors.is_empty() {
122
+ Ok(())
123
+ } else {
124
+ Err(NaomeError::new(errors.join("\n")))
125
+ }
126
+ }
127
+
128
+ fn run_template_task_state_check(root: &Path) -> Result<(), NaomeError> {
129
+ let template_root = template_root(root);
130
+ let report = validate_task_state(
131
+ &template_root,
132
+ TaskStateOptions {
133
+ mode: TaskStateMode::State,
134
+ harness_health: Some(HarnessHealthOptions {
135
+ expected_integrity: packaged_harness_integrity()?,
136
+ allow_missing_archive: true,
137
+ ..HarnessHealthOptions::default()
138
+ }),
139
+ },
140
+ )?;
141
+ if report.errors.is_empty() {
142
+ Ok(())
143
+ } else {
144
+ Err(NaomeError::new(report.errors.join("\n")))
145
+ }
146
+ }
147
+
148
+ fn run_template_verification_contract_check(root: &Path) -> Result<(), NaomeError> {
149
+ let errors = validate_verification_contract(&template_root(root))?;
150
+ if errors.is_empty() {
151
+ Ok(())
152
+ } else {
153
+ Err(NaomeError::new(errors.join("\n")))
154
+ }
155
+ }
@@ -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
+ }
@@ -0,0 +1,49 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::models::NaomeError;
4
+
5
+ pub(super) fn packaged_harness_integrity() -> Result<HashMap<String, String>, NaomeError> {
6
+ const CHECKER: &str =
7
+ include_str!("../../../../templates/naome-root/.naome/bin/check-harness-health.js");
8
+ let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({";
9
+ let start = CHECKER
10
+ .find(start_marker)
11
+ .ok_or_else(|| NaomeError::new("Packaged harness integrity block is missing."))?;
12
+ let body_start = start + start_marker.len();
13
+ let end = CHECKER[body_start..]
14
+ .find("\n});")
15
+ .map(|offset| body_start + offset)
16
+ .ok_or_else(|| NaomeError::new("Packaged harness integrity block is incomplete."))?;
17
+
18
+ let mut integrity = HashMap::new();
19
+ for line in CHECKER[body_start..end].lines() {
20
+ let line = line.trim().trim_end_matches(',').trim();
21
+ if line.is_empty() {
22
+ continue;
23
+ }
24
+ let Some((path, hash)) = line.split_once(':') else {
25
+ return Err(NaomeError::new(format!(
26
+ "Packaged harness integrity entry is invalid: {line}"
27
+ )));
28
+ };
29
+ let path: String = serde_json::from_str(path.trim()).map_err(|error| {
30
+ NaomeError::new(format!(
31
+ "Packaged harness integrity path is invalid: {error}"
32
+ ))
33
+ })?;
34
+ let hash: String = serde_json::from_str(hash.trim()).map_err(|error| {
35
+ NaomeError::new(format!(
36
+ "Packaged harness integrity hash is invalid: {error}"
37
+ ))
38
+ })?;
39
+ integrity.insert(path, hash);
40
+ }
41
+
42
+ if integrity.is_empty() {
43
+ return Err(NaomeError::new(
44
+ "Packaged harness integrity block is empty.",
45
+ ));
46
+ }
47
+
48
+ Ok(integrity)
49
+ }
@@ -0,0 +1,40 @@
1
+ use crate::models::NaomeError;
2
+
3
+ use super::quality_gate::QualityCheck;
4
+
5
+ pub(super) fn require_builtin_quality_check_any(
6
+ check_id: &str,
7
+ check: &QualityCheck,
8
+ expected_commands: &[&str],
9
+ ) -> Result<(), NaomeError> {
10
+ if check.cwd == "."
11
+ && expected_commands
12
+ .iter()
13
+ .any(|expected_command| check.command == *expected_command)
14
+ {
15
+ return Ok(());
16
+ }
17
+
18
+ Err(NaomeError::new(format!(
19
+ "Quality check {check_id} has an unsafe command or cwd; expected one of [{}] with cwd `.`.",
20
+ expected_commands
21
+ .iter()
22
+ .map(|command| format!("`{command}`"))
23
+ .collect::<Vec<_>>()
24
+ .join(", ")
25
+ )))
26
+ }
27
+
28
+ pub(super) fn require_builtin_quality_check(
29
+ check_id: &str,
30
+ check: &QualityCheck,
31
+ expected_command: &str,
32
+ ) -> Result<(), NaomeError> {
33
+ if check.cwd == "." && check.command == expected_command {
34
+ return Ok(());
35
+ }
36
+
37
+ Err(NaomeError::new(format!(
38
+ "Quality check {check_id} has an unsafe command or cwd; expected command `{expected_command}` with cwd `.`."
39
+ )))
40
+ }