@lamentis/naome 1.1.2 → 1.2.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/Cargo.toml +1 -1
- package/LICENSE +180 -21
- package/README.md +49 -6
- package/bin/naome.js +54 -16
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/check_commands.rs +135 -0
- package/crates/naome-cli/src/cli_args.rs +5 -0
- package/crates/naome-cli/src/dispatcher.rs +36 -0
- package/crates/naome-cli/src/install_bridge.rs +83 -0
- package/crates/naome-cli/src/main.rs +57 -341
- package/crates/naome-cli/src/prompt_commands.rs +68 -0
- package/crates/naome-cli/src/quality_commands.rs +141 -0
- package/crates/naome-cli/src/simple_commands.rs +53 -0
- package/crates/naome-cli/src/workflow_commands.rs +153 -0
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/harness_health/integrity.rs +96 -0
- package/crates/naome-core/src/harness_health.rs +14 -126
- package/crates/naome-core/src/install_plan.rs +3 -0
- package/crates/naome-core/src/intent/classifier.rs +171 -0
- package/crates/naome-core/src/intent/envelope.rs +108 -0
- package/crates/naome-core/src/intent/legacy.rs +138 -0
- package/crates/naome-core/src/intent/legacy_response.rs +76 -0
- package/crates/naome-core/src/intent/model.rs +71 -0
- package/crates/naome-core/src/intent/patterns.rs +170 -0
- package/crates/naome-core/src/intent/resolver.rs +162 -0
- package/crates/naome-core/src/intent/resolver_active.rs +17 -0
- package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
- package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
- package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
- package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
- package/crates/naome-core/src/intent/risk.rs +40 -0
- package/crates/naome-core/src/intent/segment.rs +170 -0
- package/crates/naome-core/src/intent.rs +64 -879
- package/crates/naome-core/src/journal.rs +9 -20
- package/crates/naome-core/src/lib.rs +13 -0
- package/crates/naome-core/src/quality/adapters.rs +178 -0
- package/crates/naome-core/src/quality/baseline.rs +75 -0
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
- package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
- package/crates/naome-core/src/quality/checks.rs +228 -0
- package/crates/naome-core/src/quality/cleanup.rs +72 -0
- package/crates/naome-core/src/quality/config.rs +109 -0
- package/crates/naome-core/src/quality/mod.rs +90 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
- package/crates/naome-core/src/quality/scanner.rs +367 -0
- package/crates/naome-core/src/quality/types.rs +289 -0
- package/crates/naome-core/src/route.rs +62 -0
- package/crates/naome-core/src/task_state/admission.rs +63 -0
- package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
- package/crates/naome-core/src/task_state/api.rs +130 -0
- package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
- package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
- package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
- package/crates/naome-core/src/task_state/completion.rs +72 -0
- package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
- package/crates/naome-core/src/task_state/diff.rs +95 -0
- package/crates/naome-core/src/task_state/evidence.rs +154 -0
- package/crates/naome-core/src/task_state/git_io.rs +86 -0
- package/crates/naome-core/src/task_state/git_parse.rs +86 -0
- package/crates/naome-core/src/task_state/git_refs.rs +37 -0
- package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
- package/crates/naome-core/src/task_state/mod.rs +38 -0
- package/crates/naome-core/src/task_state/process_guard.rs +40 -0
- package/crates/naome-core/src/task_state/progress.rs +123 -0
- package/crates/naome-core/src/task_state/proof.rs +139 -0
- package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
- package/crates/naome-core/src/task_state/proof_model.rs +70 -0
- package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
- package/crates/naome-core/src/task_state/push_gate.rs +49 -0
- package/crates/naome-core/src/task_state/reconcile.rs +7 -0
- package/crates/naome-core/src/task_state/repair.rs +168 -0
- package/crates/naome-core/src/task_state/shape.rs +117 -0
- package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
- package/crates/naome-core/src/task_state/task_records.rs +131 -0
- package/crates/naome-core/src/task_state/task_references.rs +126 -0
- package/crates/naome-core/src/task_state/types.rs +87 -0
- package/crates/naome-core/src/task_state/util.rs +137 -0
- package/crates/naome-core/src/verification/render.rs +122 -0
- package/crates/naome-core/src/verification.rs +176 -58
- package/crates/naome-core/src/verification_contract.rs +49 -21
- package/crates/naome-core/src/workflow/integrity.rs +123 -0
- package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
- package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
- package/crates/naome-core/src/workflow/mod.rs +18 -0
- package/crates/naome-core/src/workflow/mutation.rs +68 -0
- package/crates/naome-core/src/workflow/output.rs +111 -0
- package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
- package/crates/naome-core/src/workflow/phases.rs +169 -0
- package/crates/naome-core/src/workflow/policy.rs +156 -0
- package/crates/naome-core/src/workflow/processes.rs +91 -0
- package/crates/naome-core/src/workflow/types.rs +42 -0
- package/crates/naome-core/tests/harness_health.rs +3 -0
- package/crates/naome-core/tests/intent.rs +97 -792
- package/crates/naome-core/tests/intent_support/mod.rs +133 -0
- package/crates/naome-core/tests/intent_v2.rs +90 -0
- package/crates/naome-core/tests/quality.rs +425 -0
- package/crates/naome-core/tests/route.rs +88 -188
- package/crates/naome-core/tests/task_state.rs +3 -0
- package/crates/naome-core/tests/task_state_compact.rs +110 -0
- package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
- package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
- package/crates/naome-core/tests/workflow_integrity.rs +85 -0
- package/crates/naome-core/tests/workflow_policy.rs +139 -0
- package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +2 -2
- package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
- package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
- package/templates/naome-root/.naome/bin/naome.js +34 -63
- package/templates/naome-root/.naome/manifest.json +20 -18
- package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
- package/templates/naome-root/.naome/repository-quality.json +24 -0
- package/templates/naome-root/.naome/task-contract.schema.json +93 -11
- package/templates/naome-root/.naome/upgrade-state.json +1 -1
- package/templates/naome-root/.naome/verification.json +37 -0
- package/templates/naome-root/AGENTS.md +3 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
- package/templates/naome-root/docs/naome/execution.md +25 -21
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +43 -0
- package/templates/naome-root/docs/naome/testing.md +12 -0
- package/crates/naome-core/src/task_state.rs +0 -2210
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use naome_core::{
|
|
4
|
+
check_repository_quality, init_repository_quality, plan_quality_cleanup, route_quality_cleanup,
|
|
5
|
+
QualityMode,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use crate::cli_args::option_value;
|
|
9
|
+
|
|
10
|
+
pub fn run_quality_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
11
|
+
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
12
|
+
return Err("naome quality requires init, check, or report.".into());
|
|
13
|
+
};
|
|
14
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
15
|
+
|
|
16
|
+
match subcommand {
|
|
17
|
+
"init" => {
|
|
18
|
+
let result = init_repository_quality(root)?;
|
|
19
|
+
if json {
|
|
20
|
+
println!("{}", serde_json::to_string_pretty(&result)?);
|
|
21
|
+
} else {
|
|
22
|
+
println!("NAOME repository quality initialized.");
|
|
23
|
+
println!("Baseline violations: {}", result.baseline_violations);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
"check" => run_quality_check(root, args, json)?,
|
|
27
|
+
"report" => run_quality_report(root, json)?,
|
|
28
|
+
_ => return Err(format!("unknown naome quality command: {subcommand}").into()),
|
|
29
|
+
}
|
|
30
|
+
Ok(())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn run_cleanup_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
34
|
+
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
35
|
+
return Err("naome cleanup requires plan or route.".into());
|
|
36
|
+
};
|
|
37
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
38
|
+
|
|
39
|
+
match subcommand {
|
|
40
|
+
"plan" => run_cleanup_plan(root, json)?,
|
|
41
|
+
"route" => run_cleanup_route(root, args, json)?,
|
|
42
|
+
_ => return Err(format!("unknown naome cleanup command: {subcommand}").into()),
|
|
43
|
+
}
|
|
44
|
+
Ok(())
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn run_quality_check(
|
|
48
|
+
root: &Path,
|
|
49
|
+
args: &[String],
|
|
50
|
+
json: bool,
|
|
51
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
52
|
+
if !args.iter().any(|arg| arg == "--changed") {
|
|
53
|
+
return Err("naome quality check requires --changed.".into());
|
|
54
|
+
}
|
|
55
|
+
let report = check_repository_quality(root, QualityMode::Changed)?;
|
|
56
|
+
if json {
|
|
57
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
58
|
+
} else if report.ok {
|
|
59
|
+
println!("NAOME repository quality OK.");
|
|
60
|
+
} else {
|
|
61
|
+
eprintln!(
|
|
62
|
+
"NAOME repository quality failed with {} violation(s).",
|
|
63
|
+
report.violations.len()
|
|
64
|
+
);
|
|
65
|
+
for violation in report.violations.iter().take(20) {
|
|
66
|
+
print_quality_violation(violation);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if !report.ok {
|
|
70
|
+
std::process::exit(1);
|
|
71
|
+
}
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn run_quality_report(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
76
|
+
let report = check_repository_quality(root, QualityMode::Report)?;
|
|
77
|
+
if json {
|
|
78
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
79
|
+
} else if report.violations.is_empty() {
|
|
80
|
+
println!("NAOME repository quality report: no debt found.");
|
|
81
|
+
} else {
|
|
82
|
+
println!(
|
|
83
|
+
"NAOME repository quality report: {} violation(s) across {} scanned file(s).",
|
|
84
|
+
report.violations.len(),
|
|
85
|
+
report.summary.scanned_files
|
|
86
|
+
);
|
|
87
|
+
for violation in report.violations.iter().take(20) {
|
|
88
|
+
print_quality_violation(violation);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
Ok(())
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn run_cleanup_plan(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
95
|
+
let plan = plan_quality_cleanup(root)?;
|
|
96
|
+
if json {
|
|
97
|
+
println!("{}", serde_json::to_string_pretty(&plan)?);
|
|
98
|
+
} else if plan.tasks.is_empty() {
|
|
99
|
+
println!("NAOME cleanup plan: no repository-quality debt found.");
|
|
100
|
+
} else {
|
|
101
|
+
println!("NAOME cleanup plan:");
|
|
102
|
+
for task in plan.tasks.iter().take(20) {
|
|
103
|
+
println!(
|
|
104
|
+
"- {}: {} violation(s) [{}]",
|
|
105
|
+
task.path,
|
|
106
|
+
task.violation_count,
|
|
107
|
+
task.check_ids.join(", ")
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
Ok(())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn run_cleanup_route(
|
|
115
|
+
root: &Path,
|
|
116
|
+
args: &[String],
|
|
117
|
+
json: bool,
|
|
118
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
119
|
+
let Some(path) = option_value(args, "--path") else {
|
|
120
|
+
return Err("naome cleanup route requires --path <path>.".into());
|
|
121
|
+
};
|
|
122
|
+
let route = route_quality_cleanup(root, path)?;
|
|
123
|
+
if json {
|
|
124
|
+
println!("{}", serde_json::to_string_pretty(&route)?);
|
|
125
|
+
} else {
|
|
126
|
+
println!("NAOME cleanup route for {}", route.path);
|
|
127
|
+
println!("{}", route.agent_instructions);
|
|
128
|
+
if !route.related_paths.is_empty() {
|
|
129
|
+
println!("Related paths: {}", route.related_paths.join(", "));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
Ok(())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn print_quality_violation(violation: &naome_core::QualityViolation) {
|
|
136
|
+
let location = violation
|
|
137
|
+
.line
|
|
138
|
+
.map(|line| format!("{}:{line}", violation.path))
|
|
139
|
+
.unwrap_or_else(|| violation.path.clone());
|
|
140
|
+
eprintln!("- {location} {}: {}", violation.check_id, violation.message);
|
|
141
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use naome_core::{install_plan, seed_builtin_verification_checks};
|
|
4
|
+
|
|
5
|
+
use crate::cli_args::option_value;
|
|
6
|
+
|
|
7
|
+
pub fn print_install_plan(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
8
|
+
let harness_version = option_value(args, "--harness-version")
|
|
9
|
+
.or_else(|| option_value(args, "--version"))
|
|
10
|
+
.unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
|
|
11
|
+
println!(
|
|
12
|
+
"{}",
|
|
13
|
+
serde_json::to_string_pretty(&install_plan(harness_version))?
|
|
14
|
+
);
|
|
15
|
+
Ok(())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn seed_verification(root: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
|
19
|
+
if seed_builtin_verification_checks(root)? {
|
|
20
|
+
println!("NAOME verification checks updated.");
|
|
21
|
+
} else {
|
|
22
|
+
println!("NAOME verification checks already present.");
|
|
23
|
+
}
|
|
24
|
+
Ok(())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn run_journal_task(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
28
|
+
let outcome =
|
|
29
|
+
option_value(args, "--outcome").unwrap_or_else(|| "naome_commit_baseline".to_string());
|
|
30
|
+
let commit_before = option_value(args, "--commit-before");
|
|
31
|
+
let commit_after = option_value(args, "--commit-after");
|
|
32
|
+
let entry = naome_core::append_task_journal(root, &outcome, commit_before, commit_after)?;
|
|
33
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
34
|
+
println!("{}", serde_json::to_string_pretty(&entry)?);
|
|
35
|
+
} else if entry.is_some() {
|
|
36
|
+
println!("NAOME task journal updated.");
|
|
37
|
+
} else {
|
|
38
|
+
println!("NAOME task journal had no active task to record.");
|
|
39
|
+
}
|
|
40
|
+
Ok(())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub fn run_commit_paths(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
44
|
+
let paths = naome_core::completed_task_commit_paths(root)?;
|
|
45
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
46
|
+
println!("{}", serde_json::to_string_pretty(&paths)?);
|
|
47
|
+
} else {
|
|
48
|
+
for path in paths {
|
|
49
|
+
println!("{path}");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use naome_core::{
|
|
4
|
+
classify_mutations, refresh_integrity, safe_rg_args, tracked_process_report,
|
|
5
|
+
validate_search_command, verification_phase_plan,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use crate::cli_args::option_value;
|
|
9
|
+
|
|
10
|
+
pub fn run_refresh_integrity(
|
|
11
|
+
root: &Path,
|
|
12
|
+
args: &[String],
|
|
13
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
14
|
+
let report = refresh_integrity(root)?;
|
|
15
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
16
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
17
|
+
} else if report.updated {
|
|
18
|
+
println!(
|
|
19
|
+
"NAOME integrity refreshed: {}.",
|
|
20
|
+
report.changed_paths.join(", ")
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
println!("NAOME integrity already current.");
|
|
24
|
+
}
|
|
25
|
+
Ok(())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn run_workflow_command(
|
|
29
|
+
root: &Path,
|
|
30
|
+
args: &[String],
|
|
31
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
32
|
+
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
33
|
+
return Err("naome workflow requires search-profile, check-search, phases, processes, or mutations.".into());
|
|
34
|
+
};
|
|
35
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
36
|
+
|
|
37
|
+
match subcommand {
|
|
38
|
+
"search-profile" => {
|
|
39
|
+
let args = safe_rg_args(root)?;
|
|
40
|
+
if json {
|
|
41
|
+
println!("{}", serde_json::to_string_pretty(&args)?);
|
|
42
|
+
} else {
|
|
43
|
+
println!(
|
|
44
|
+
"{}",
|
|
45
|
+
args.iter()
|
|
46
|
+
.map(|arg| shell_quote(arg))
|
|
47
|
+
.collect::<Vec<_>>()
|
|
48
|
+
.join(" ")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
"check-search" => run_check_search(root, args, json)?,
|
|
53
|
+
"phases" => {
|
|
54
|
+
let plan = verification_phase_plan(root, &[])?;
|
|
55
|
+
if json {
|
|
56
|
+
println!("{}", serde_json::to_string_pretty(&plan)?);
|
|
57
|
+
} else {
|
|
58
|
+
println!(
|
|
59
|
+
"Recommended checks: {}",
|
|
60
|
+
empty_label(&plan.recommended_check_ids)
|
|
61
|
+
);
|
|
62
|
+
if !plan.withheld_check_ids.is_empty() {
|
|
63
|
+
println!("Withheld checks: {}", plan.withheld_check_ids.join(", "));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
"processes" => {
|
|
68
|
+
let report = tracked_process_report(root)?;
|
|
69
|
+
if json {
|
|
70
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
71
|
+
} else if report.active.is_empty() {
|
|
72
|
+
println!("NAOME tracked processes OK.");
|
|
73
|
+
} else {
|
|
74
|
+
println!("Active tracked processes:");
|
|
75
|
+
for process in report.active {
|
|
76
|
+
println!("- {} [{}]", process.command, process.cwd);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
"mutations" => run_mutations(root, args, json)?,
|
|
81
|
+
_ => return Err(format!("unknown naome workflow command: {subcommand}").into()),
|
|
82
|
+
}
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn run_check_search(
|
|
87
|
+
root: &Path,
|
|
88
|
+
args: &[String],
|
|
89
|
+
json: bool,
|
|
90
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
91
|
+
let Some(command) = option_value(args, "--command") else {
|
|
92
|
+
return Err("naome workflow check-search requires --command <command>.".into());
|
|
93
|
+
};
|
|
94
|
+
let findings = validate_search_command(root, &command)?;
|
|
95
|
+
if !json && findings.is_empty() {
|
|
96
|
+
println!("NAOME search command OK.");
|
|
97
|
+
}
|
|
98
|
+
if json {
|
|
99
|
+
println!("{}", serde_json::to_string_pretty(&findings)?);
|
|
100
|
+
} else {
|
|
101
|
+
for finding in &findings {
|
|
102
|
+
println!("- {}: {}", finding.check_id, finding.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if !findings.is_empty() {
|
|
106
|
+
std::process::exit(1);
|
|
107
|
+
}
|
|
108
|
+
Ok(())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn run_mutations(
|
|
112
|
+
root: &Path,
|
|
113
|
+
args: &[String],
|
|
114
|
+
json: bool,
|
|
115
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
116
|
+
let paths = args
|
|
117
|
+
.iter()
|
|
118
|
+
.skip_while(|arg| arg.as_str() != "--path")
|
|
119
|
+
.skip(1)
|
|
120
|
+
.filter(|arg| !arg.starts_with("--"))
|
|
121
|
+
.cloned()
|
|
122
|
+
.collect::<Vec<_>>();
|
|
123
|
+
if paths.is_empty() {
|
|
124
|
+
return Err("naome workflow mutations requires --path <path> [path...]".into());
|
|
125
|
+
}
|
|
126
|
+
let classes = classify_mutations(root, &paths)?;
|
|
127
|
+
if json {
|
|
128
|
+
println!("{}", serde_json::to_string_pretty(&classes)?);
|
|
129
|
+
} else {
|
|
130
|
+
for entry in classes {
|
|
131
|
+
println!("{}: {}", entry.path, entry.mutation_class);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
Ok(())
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn empty_label(values: &[String]) -> String {
|
|
138
|
+
if values.is_empty() {
|
|
139
|
+
"none".to_string()
|
|
140
|
+
} else {
|
|
141
|
+
values.join(", ")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn shell_quote(value: &str) -> String {
|
|
146
|
+
if value
|
|
147
|
+
.chars()
|
|
148
|
+
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | '/' | ':'))
|
|
149
|
+
{
|
|
150
|
+
return value.to_string();
|
|
151
|
+
}
|
|
152
|
+
format!("'{}'", value.replace('\'', "'\\''"))
|
|
153
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
use sha2::{Digest, Sha256};
|
|
2
|
+
|
|
3
|
+
const HEALTH_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-harness-health.js";
|
|
4
|
+
const TASK_STATE_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-task-state.js";
|
|
5
|
+
|
|
6
|
+
pub(crate) const NAOME_COMMAND_RELATIVE_PATH: &str = ".naome/bin/naome.js";
|
|
7
|
+
|
|
8
|
+
#[cfg(windows)]
|
|
9
|
+
pub(crate) const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust.exe";
|
|
10
|
+
#[cfg(not(windows))]
|
|
11
|
+
pub(crate) const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust";
|
|
12
|
+
|
|
13
|
+
pub(crate) fn machine_integrity_hash(relative_path: &str, content: &[u8]) -> String {
|
|
14
|
+
format!("sha256:{}", machine_sha256(relative_path, content))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub(crate) fn sha256_bytes(content: &[u8]) -> String {
|
|
18
|
+
let mut hasher = Sha256::new();
|
|
19
|
+
hasher.update(content);
|
|
20
|
+
format!("{:x}", hasher.finalize())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub(crate) fn is_integrity_hash(value: &str) -> bool {
|
|
24
|
+
value.len() == "sha256:".len() + 64
|
|
25
|
+
&& value.starts_with("sha256:")
|
|
26
|
+
&& value["sha256:".len()..]
|
|
27
|
+
.chars()
|
|
28
|
+
.all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub(crate) fn native_integrity_from_naome_command(content: &str) -> Option<String> {
|
|
32
|
+
let prefix = "const expectedNativeBinaryIntegrity = \"";
|
|
33
|
+
let start = content.find(prefix)? + prefix.len();
|
|
34
|
+
let rest = &content[start..];
|
|
35
|
+
let end = rest.find("\";")?;
|
|
36
|
+
let value = &rest[..end];
|
|
37
|
+
is_integrity_hash(value).then(|| value.to_string())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
|
|
41
|
+
let normalized = normalize_machine_owned_content(relative_path, content);
|
|
42
|
+
sha256_bytes(&normalized)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
|
|
46
|
+
if relative_path == HEALTH_CHECKER_RELATIVE_PATH
|
|
47
|
+
|| relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
|
|
48
|
+
{
|
|
49
|
+
return replace_expected_integrity_block(content);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if relative_path == NAOME_COMMAND_RELATIVE_PATH {
|
|
53
|
+
return replace_native_integrity_line(content);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
content.to_vec()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
|
|
60
|
+
let text = String::from_utf8_lossy(content);
|
|
61
|
+
let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({\n";
|
|
62
|
+
let normalized =
|
|
63
|
+
"const expectedMachineOwnedIntegrity = Object.freeze({\n __generated__: \"sha256:generated\"\n});\n";
|
|
64
|
+
let Some(start) = text.find(start_marker) else {
|
|
65
|
+
return content.to_vec();
|
|
66
|
+
};
|
|
67
|
+
let after_start = start + start_marker.len();
|
|
68
|
+
let Some(relative_end) = text[after_start..].find("\n});\n") else {
|
|
69
|
+
return content.to_vec();
|
|
70
|
+
};
|
|
71
|
+
let end = after_start + relative_end + "\n});\n".len();
|
|
72
|
+
|
|
73
|
+
let mut next = String::with_capacity(text.len());
|
|
74
|
+
next.push_str(&text[..start]);
|
|
75
|
+
next.push_str(normalized);
|
|
76
|
+
next.push_str(&text[end..]);
|
|
77
|
+
next.into_bytes()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn replace_native_integrity_line(content: &[u8]) -> Vec<u8> {
|
|
81
|
+
let text = String::from_utf8_lossy(content);
|
|
82
|
+
let prefix = "const expectedNativeBinaryIntegrity = \"";
|
|
83
|
+
let Some(start) = text.find(prefix) else {
|
|
84
|
+
return content.to_vec();
|
|
85
|
+
};
|
|
86
|
+
let Some(relative_end) = text[start..].find(";\n") else {
|
|
87
|
+
return content.to_vec();
|
|
88
|
+
};
|
|
89
|
+
let end = start + relative_end + ";\n".len();
|
|
90
|
+
|
|
91
|
+
let mut next = String::with_capacity(text.len());
|
|
92
|
+
next.push_str(&text[..start]);
|
|
93
|
+
next.push_str("const expectedNativeBinaryIntegrity = \"sha256:generated\";\n");
|
|
94
|
+
next.push_str(&text[end..]);
|
|
95
|
+
next.into_bytes()
|
|
96
|
+
}
|
|
@@ -1,49 +1,19 @@
|
|
|
1
|
+
mod integrity;
|
|
2
|
+
|
|
1
3
|
use std::collections::{HashMap, HashSet};
|
|
2
4
|
use std::fs;
|
|
3
5
|
use std::path::{Component, Path};
|
|
4
6
|
|
|
5
7
|
use serde_json::Value;
|
|
6
|
-
use sha2::{Digest, Sha256};
|
|
7
8
|
|
|
9
|
+
pub(crate) use self::integrity::machine_integrity_hash;
|
|
10
|
+
use self::integrity::{
|
|
11
|
+
is_integrity_hash, native_integrity_from_naome_command, sha256_bytes,
|
|
12
|
+
NAOME_COMMAND_RELATIVE_PATH, NATIVE_BINARY_RELATIVE_PATH,
|
|
13
|
+
};
|
|
14
|
+
use crate::install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
8
15
|
use crate::models::NaomeError;
|
|
9
16
|
|
|
10
|
-
const HEALTH_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-harness-health.js";
|
|
11
|
-
const TASK_STATE_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-task-state.js";
|
|
12
|
-
const NAOME_COMMAND_RELATIVE_PATH: &str = ".naome/bin/naome.js";
|
|
13
|
-
|
|
14
|
-
#[cfg(windows)]
|
|
15
|
-
const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust.exe";
|
|
16
|
-
#[cfg(not(windows))]
|
|
17
|
-
const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust";
|
|
18
|
-
|
|
19
|
-
const EXPECTED_MACHINE_OWNED_PATHS: &[&str] = &[
|
|
20
|
-
"AGENTS.md",
|
|
21
|
-
".naome/package.json",
|
|
22
|
-
".naome/bin/naome.js",
|
|
23
|
-
".naome/bin/check-task-state.js",
|
|
24
|
-
".naome/bin/check-harness-health.js",
|
|
25
|
-
".naome/task-contract.schema.json",
|
|
26
|
-
"docs/naome/index.md",
|
|
27
|
-
"docs/naome/first-run.md",
|
|
28
|
-
"docs/naome/agent-workflow.md",
|
|
29
|
-
"docs/naome/execution.md",
|
|
30
|
-
"docs/naome/upgrade.md",
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const EXPECTED_PROJECT_OWNED_PATHS: &[&str] = &[
|
|
34
|
-
".naomeignore",
|
|
35
|
-
".naome/init-state.json",
|
|
36
|
-
".naome/manifest.json",
|
|
37
|
-
".naome/task-state.json",
|
|
38
|
-
".naome/upgrade-state.json",
|
|
39
|
-
".naome/verification.json",
|
|
40
|
-
"docs/naome/architecture.md",
|
|
41
|
-
"docs/naome/decisions.md",
|
|
42
|
-
"docs/naome/repo-profile.md",
|
|
43
|
-
"docs/naome/security.md",
|
|
44
|
-
"docs/naome/testing.md",
|
|
45
|
-
];
|
|
46
|
-
|
|
47
17
|
#[derive(Debug, Clone, Default)]
|
|
48
18
|
pub struct HarnessHealthOptions {
|
|
49
19
|
pub expected_integrity: HashMap<String, String>,
|
|
@@ -57,11 +27,11 @@ pub fn validate_harness_health(
|
|
|
57
27
|
) -> Result<Vec<String>, NaomeError> {
|
|
58
28
|
let mut errors = Vec::new();
|
|
59
29
|
|
|
60
|
-
for relative_path in
|
|
30
|
+
for relative_path in MACHINE_OWNED_PATHS {
|
|
61
31
|
validate_regular_file(root, relative_path, &mut errors)?;
|
|
62
32
|
}
|
|
63
33
|
|
|
64
|
-
for relative_path in
|
|
34
|
+
for relative_path in PROJECT_OWNED_PATHS {
|
|
65
35
|
validate_regular_file(root, relative_path, &mut errors)?;
|
|
66
36
|
}
|
|
67
37
|
|
|
@@ -130,13 +100,13 @@ fn validate_manifest_ownership(manifest: &Value, errors: &mut Vec<String>) {
|
|
|
130
100
|
|
|
131
101
|
validate_contains_all(
|
|
132
102
|
&machine_owned,
|
|
133
|
-
|
|
103
|
+
MACHINE_OWNED_PATHS,
|
|
134
104
|
".naome/manifest.json machineOwned",
|
|
135
105
|
errors,
|
|
136
106
|
);
|
|
137
107
|
validate_contains_all(
|
|
138
108
|
&project_owned,
|
|
139
|
-
|
|
109
|
+
PROJECT_OWNED_PATHS,
|
|
140
110
|
".naome/manifest.json projectOwned",
|
|
141
111
|
errors,
|
|
142
112
|
);
|
|
@@ -152,7 +122,7 @@ fn validate_manifest_integrity(
|
|
|
152
122
|
return Ok(());
|
|
153
123
|
};
|
|
154
124
|
|
|
155
|
-
for relative_path in
|
|
125
|
+
for relative_path in MACHINE_OWNED_PATHS {
|
|
156
126
|
let packaged_expected = options.expected_integrity.get(*relative_path);
|
|
157
127
|
let manifest_expected = integrity.get(*relative_path).and_then(Value::as_str);
|
|
158
128
|
|
|
@@ -190,7 +160,7 @@ fn validate_manifest_integrity(
|
|
|
190
160
|
}
|
|
191
161
|
|
|
192
162
|
let content = fs::read(root.join(relative_path))?;
|
|
193
|
-
let actual =
|
|
163
|
+
let actual = machine_integrity_hash(relative_path, &content);
|
|
194
164
|
if actual != *packaged_expected {
|
|
195
165
|
errors.push(format!(
|
|
196
166
|
"{relative_path} integrity mismatch. Expected {packaged_expected}, got {actual}."
|
|
@@ -475,85 +445,3 @@ fn is_version(value: &str) -> bool {
|
|
|
475
445
|
.iter()
|
|
476
446
|
.all(|part| !part.is_empty() && part.chars().all(|ch| ch.is_ascii_digit()))
|
|
477
447
|
}
|
|
478
|
-
|
|
479
|
-
fn is_integrity_hash(value: &str) -> bool {
|
|
480
|
-
value.len() == "sha256:".len() + 64
|
|
481
|
-
&& value.starts_with("sha256:")
|
|
482
|
-
&& value["sha256:".len()..]
|
|
483
|
-
.chars()
|
|
484
|
-
.all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
|
|
488
|
-
let normalized = normalize_machine_owned_content(relative_path, content);
|
|
489
|
-
sha256_bytes(&normalized)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
fn sha256_bytes(content: &[u8]) -> String {
|
|
493
|
-
let mut hasher = Sha256::new();
|
|
494
|
-
hasher.update(content);
|
|
495
|
-
format!("{:x}", hasher.finalize())
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
|
|
499
|
-
if relative_path == HEALTH_CHECKER_RELATIVE_PATH
|
|
500
|
-
|| relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
|
|
501
|
-
{
|
|
502
|
-
return replace_expected_integrity_block(content);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if relative_path == NAOME_COMMAND_RELATIVE_PATH {
|
|
506
|
-
return replace_native_integrity_line(content);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
content.to_vec()
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
|
|
513
|
-
let text = String::from_utf8_lossy(content);
|
|
514
|
-
let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({\n";
|
|
515
|
-
let normalized =
|
|
516
|
-
"const expectedMachineOwnedIntegrity = Object.freeze({\n __generated__: \"sha256:generated\"\n});\n";
|
|
517
|
-
let Some(start) = text.find(start_marker) else {
|
|
518
|
-
return content.to_vec();
|
|
519
|
-
};
|
|
520
|
-
let after_start = start + start_marker.len();
|
|
521
|
-
let Some(relative_end) = text[after_start..].find("\n});\n") else {
|
|
522
|
-
return content.to_vec();
|
|
523
|
-
};
|
|
524
|
-
let end = after_start + relative_end + "\n});\n".len();
|
|
525
|
-
|
|
526
|
-
let mut next = String::with_capacity(text.len());
|
|
527
|
-
next.push_str(&text[..start]);
|
|
528
|
-
next.push_str(normalized);
|
|
529
|
-
next.push_str(&text[end..]);
|
|
530
|
-
next.into_bytes()
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
fn replace_native_integrity_line(content: &[u8]) -> Vec<u8> {
|
|
534
|
-
let text = String::from_utf8_lossy(content);
|
|
535
|
-
let prefix = "const expectedNativeBinaryIntegrity = \"";
|
|
536
|
-
let Some(start) = text.find(prefix) else {
|
|
537
|
-
return content.to_vec();
|
|
538
|
-
};
|
|
539
|
-
let Some(relative_end) = text[start..].find(";\n") else {
|
|
540
|
-
return content.to_vec();
|
|
541
|
-
};
|
|
542
|
-
let end = start + relative_end + ";\n".len();
|
|
543
|
-
let replacement = "const expectedNativeBinaryIntegrity = \"sha256:generated\";\n";
|
|
544
|
-
|
|
545
|
-
let mut next = String::with_capacity(text.len());
|
|
546
|
-
next.push_str(&text[..start]);
|
|
547
|
-
next.push_str(replacement);
|
|
548
|
-
next.push_str(&text[end..]);
|
|
549
|
-
next.into_bytes()
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
fn native_integrity_from_naome_command(content: &str) -> Option<String> {
|
|
553
|
-
let prefix = "const expectedNativeBinaryIntegrity = \"";
|
|
554
|
-
let start = content.find(prefix)? + prefix.len();
|
|
555
|
-
let rest = &content[start..];
|
|
556
|
-
let end = rest.find("\";")?;
|
|
557
|
-
let value = &rest[..end];
|
|
558
|
-
is_integrity_hash(value).then(|| value.to_string())
|
|
559
|
-
}
|
|
@@ -21,9 +21,12 @@ pub const PROJECT_OWNED_PATHS: &[&str] = &[
|
|
|
21
21
|
".naome/task-state.json",
|
|
22
22
|
".naome/upgrade-state.json",
|
|
23
23
|
".naome/verification.json",
|
|
24
|
+
".naome/repository-quality.json",
|
|
25
|
+
".naome/repository-quality-baseline.json",
|
|
24
26
|
"docs/naome/architecture.md",
|
|
25
27
|
"docs/naome/decisions.md",
|
|
26
28
|
"docs/naome/repo-profile.md",
|
|
29
|
+
"docs/naome/repository-quality.md",
|
|
27
30
|
"docs/naome/security.md",
|
|
28
31
|
"docs/naome/testing.md",
|
|
29
32
|
];
|