@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,38 @@
|
|
|
1
|
+
mod admission;
|
|
2
|
+
mod admission_proof;
|
|
3
|
+
mod api;
|
|
4
|
+
mod commit_gate;
|
|
5
|
+
mod compact_proof;
|
|
6
|
+
mod completed_refresh;
|
|
7
|
+
mod completion;
|
|
8
|
+
mod deleted_paths;
|
|
9
|
+
mod diff;
|
|
10
|
+
mod evidence;
|
|
11
|
+
mod git_io;
|
|
12
|
+
mod git_parse;
|
|
13
|
+
mod git_refs;
|
|
14
|
+
mod human_review_state;
|
|
15
|
+
mod process_guard;
|
|
16
|
+
mod progress;
|
|
17
|
+
mod proof;
|
|
18
|
+
mod proof_entry;
|
|
19
|
+
mod proof_model;
|
|
20
|
+
mod proof_sources;
|
|
21
|
+
mod push_gate;
|
|
22
|
+
mod reconcile;
|
|
23
|
+
mod repair;
|
|
24
|
+
mod shape;
|
|
25
|
+
mod task_diff_api;
|
|
26
|
+
mod task_records;
|
|
27
|
+
mod task_references;
|
|
28
|
+
mod types;
|
|
29
|
+
mod util;
|
|
30
|
+
|
|
31
|
+
pub use api::validate_task_state;
|
|
32
|
+
pub use completed_refresh::completed_task_harness_refresh_diff;
|
|
33
|
+
pub(crate) use proof_model::canonical_proof_check_ids;
|
|
34
|
+
pub use task_diff_api::{
|
|
35
|
+
completed_task_commit_diff, completed_task_commit_paths, harness_refresh_diff,
|
|
36
|
+
harness_refresh_with_unrelated_diff,
|
|
37
|
+
};
|
|
38
|
+
pub use types::{TaskStateMode, TaskStateOptions, TaskStateReport};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::models::NaomeError;
|
|
4
|
+
use crate::workflow::tracked_process_report;
|
|
5
|
+
|
|
6
|
+
pub(super) enum ProcessGate {
|
|
7
|
+
Completion,
|
|
8
|
+
Commit,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub(super) fn validate_no_active_processes(
|
|
12
|
+
root: &Path,
|
|
13
|
+
errors: &mut Vec<String>,
|
|
14
|
+
gate: ProcessGate,
|
|
15
|
+
) -> Result<(), NaomeError> {
|
|
16
|
+
let report = tracked_process_report(root)?;
|
|
17
|
+
if report.active.is_empty() {
|
|
18
|
+
return Ok(());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let commands = report
|
|
22
|
+
.active
|
|
23
|
+
.iter()
|
|
24
|
+
.map(|process| process.command.clone())
|
|
25
|
+
.collect::<Vec<_>>();
|
|
26
|
+
errors.push(process_error_message(gate, &commands));
|
|
27
|
+
Ok(())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn process_error_message(gate: ProcessGate, commands: &[String]) -> String {
|
|
31
|
+
let command_list = commands.join(", ");
|
|
32
|
+
match gate {
|
|
33
|
+
ProcessGate::Completion => format!(
|
|
34
|
+
"Task has active tracked processes: {command_list}. Stop them or document allowAfterCompletion before completion. Human options: stop_tracked_processes, document_long_running_process."
|
|
35
|
+
),
|
|
36
|
+
ProcessGate::Commit => format!(
|
|
37
|
+
"NAOME commit gate blocked active tracked processes: {command_list}. Stop them or mark allowAfterCompletion before committing."
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::diff::validate_changed_paths;
|
|
8
|
+
use super::git_io::read_git_changed_paths;
|
|
9
|
+
use super::human_review_state::validate_human_review_state;
|
|
10
|
+
use super::reconcile::format_dirty_diff_admission_blocker;
|
|
11
|
+
use super::shape::{
|
|
12
|
+
format_blocker, validate_active_task, validate_active_task_references, validate_blocker,
|
|
13
|
+
validate_pending_upgrade,
|
|
14
|
+
};
|
|
15
|
+
use super::util::{joined_strings, read_json};
|
|
16
|
+
pub(super) fn validate_progress(
|
|
17
|
+
task_state: &Value,
|
|
18
|
+
root: &Path,
|
|
19
|
+
errors: &mut Vec<String>,
|
|
20
|
+
) -> Result<(), NaomeError> {
|
|
21
|
+
let status = checked_status(task_state, root, errors)?;
|
|
22
|
+
|
|
23
|
+
match status {
|
|
24
|
+
"planning" | "implementing" | "revising" | "verifying" => {
|
|
25
|
+
validate_active_task(task_state.get("activeTask"), errors);
|
|
26
|
+
validate_pending_upgrade(task_state, root, errors)?;
|
|
27
|
+
validate_active_task_references(
|
|
28
|
+
task_state.get("activeTask"),
|
|
29
|
+
root,
|
|
30
|
+
errors,
|
|
31
|
+
Some(status),
|
|
32
|
+
)?;
|
|
33
|
+
if let Some(active_task) = task_state.get("activeTask") {
|
|
34
|
+
validate_changed_paths(active_task, root, errors)?;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
"needs_human_review" => {
|
|
38
|
+
validate_human_review_state(task_state, root, errors)?;
|
|
39
|
+
errors.push(format_blocker(
|
|
40
|
+
"Human review required",
|
|
41
|
+
task_state.get("blocker"),
|
|
42
|
+
));
|
|
43
|
+
}
|
|
44
|
+
"blocked" => {
|
|
45
|
+
validate_active_task(task_state.get("activeTask"), errors);
|
|
46
|
+
validate_blocker(task_state.get("blocker"), errors);
|
|
47
|
+
errors.push(format_blocker("Task is blocked", task_state.get("blocker")));
|
|
48
|
+
}
|
|
49
|
+
"complete" => errors.push(
|
|
50
|
+
"Task is complete; use node .naome/bin/check-task-state.js for completion validation."
|
|
51
|
+
.to_string(),
|
|
52
|
+
),
|
|
53
|
+
"idle" => errors.push(
|
|
54
|
+
"No active task is in progress; use --admission before starting feature work."
|
|
55
|
+
.to_string(),
|
|
56
|
+
),
|
|
57
|
+
_ => {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Ok(())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(super) fn checked_status<'a>(
|
|
64
|
+
task_state: &'a Value,
|
|
65
|
+
root: &Path,
|
|
66
|
+
errors: &mut Vec<String>,
|
|
67
|
+
) -> Result<&'a str, NaomeError> {
|
|
68
|
+
validate_init_complete(root, errors)?;
|
|
69
|
+
validate_upgrade_complete(root, errors)?;
|
|
70
|
+
Ok(task_state
|
|
71
|
+
.get("status")
|
|
72
|
+
.and_then(Value::as_str)
|
|
73
|
+
.unwrap_or("invalid"))
|
|
74
|
+
}
|
|
75
|
+
pub(super) fn validate_upgrade_complete(
|
|
76
|
+
root: &Path,
|
|
77
|
+
errors: &mut Vec<String>,
|
|
78
|
+
) -> Result<(), NaomeError> {
|
|
79
|
+
let Some(upgrade_state) = read_json(root, ".naome/upgrade-state.json", errors)? else {
|
|
80
|
+
return Ok(());
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if upgrade_state.get("status").and_then(Value::as_str) != Some("complete") {
|
|
84
|
+
let pending = joined_strings(upgrade_state.get("pending"), "unknown");
|
|
85
|
+
errors.push(format!("NAOME upgrade is not complete. Finish docs/naome/upgrade.md before feature work. Pending: {pending}"));
|
|
86
|
+
}
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pub(super) fn validate_init_complete(
|
|
91
|
+
root: &Path,
|
|
92
|
+
errors: &mut Vec<String>,
|
|
93
|
+
) -> Result<(), NaomeError> {
|
|
94
|
+
let Some(init_state) = read_json(root, ".naome/init-state.json", errors)? else {
|
|
95
|
+
return Ok(());
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if init_state.get("initialized").and_then(Value::as_bool) != Some(true)
|
|
99
|
+
|| init_state.get("intakeStatus").and_then(Value::as_str) != Some("complete")
|
|
100
|
+
{
|
|
101
|
+
errors.push(
|
|
102
|
+
"NAOME intake is not complete. Finish docs/naome/first-run.md before feature work."
|
|
103
|
+
.to_string(),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
Ok(())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub(super) fn validate_clean_git_diff(
|
|
110
|
+
task_state: &Value,
|
|
111
|
+
root: &Path,
|
|
112
|
+
errors: &mut Vec<String>,
|
|
113
|
+
) -> Result<(), NaomeError> {
|
|
114
|
+
let changed_paths = read_git_changed_paths(root)?;
|
|
115
|
+
if !changed_paths.is_empty() {
|
|
116
|
+
errors.push(format_dirty_diff_admission_blocker(
|
|
117
|
+
task_state,
|
|
118
|
+
root,
|
|
119
|
+
&changed_paths,
|
|
120
|
+
)?);
|
|
121
|
+
}
|
|
122
|
+
Ok(())
|
|
123
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
|
|
6
|
+
use crate::models::NaomeError;
|
|
7
|
+
|
|
8
|
+
use super::diff::task_diff_from_entries;
|
|
9
|
+
pub(super) use super::evidence::{
|
|
10
|
+
evidence_entry_path, validate_control_state_paths, validate_control_state_patterns,
|
|
11
|
+
validate_evidence_array, validate_evidence_paths,
|
|
12
|
+
};
|
|
13
|
+
use super::git_io::read_git_changed_entries;
|
|
14
|
+
use super::proof_entry::validate_proof_result;
|
|
15
|
+
use super::proof_model::canonical_proofs;
|
|
16
|
+
use super::types::ChangedEntry;
|
|
17
|
+
use super::util::{is_iso_datetime, matches_any_pattern, normalize_path, string_array};
|
|
18
|
+
pub(super) fn validate_proof_result_entries(
|
|
19
|
+
active_task: &Value,
|
|
20
|
+
check_ids: &HashSet<String>,
|
|
21
|
+
root: &Path,
|
|
22
|
+
errors: &mut Vec<String>,
|
|
23
|
+
) -> Result<(), NaomeError> {
|
|
24
|
+
if let Some(proofs) = active_task.get("proofResults").and_then(Value::as_array) {
|
|
25
|
+
for (index, proof) in proofs.iter().enumerate() {
|
|
26
|
+
validate_proof_result(proof, index, check_ids, root, errors, active_task)?;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for proof in canonical_proofs(active_task, root, errors)? {
|
|
30
|
+
if proof.command.trim().is_empty() {
|
|
31
|
+
errors.push(format!(
|
|
32
|
+
"activeTask proof command is empty: {}",
|
|
33
|
+
proof.check_id
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
if proof.cwd.trim().is_empty() {
|
|
37
|
+
errors.push(format!("activeTask proof cwd is empty: {}", proof.check_id));
|
|
38
|
+
}
|
|
39
|
+
if !is_iso_datetime(&proof.checked_at) {
|
|
40
|
+
errors.push(format!(
|
|
41
|
+
"activeTask proof checkedAt must be an ISO timestamp: {}",
|
|
42
|
+
proof.check_id
|
|
43
|
+
));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Ok(())
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub(super) fn validate_proof_results(
|
|
51
|
+
active_task: &Value,
|
|
52
|
+
check_ids: &HashSet<String>,
|
|
53
|
+
root: &Path,
|
|
54
|
+
errors: &mut Vec<String>,
|
|
55
|
+
) -> Result<(), NaomeError> {
|
|
56
|
+
let Some(required_check_ids) = active_task
|
|
57
|
+
.get("requiredCheckIds")
|
|
58
|
+
.and_then(Value::as_array)
|
|
59
|
+
else {
|
|
60
|
+
return Ok(());
|
|
61
|
+
};
|
|
62
|
+
let proofs = canonical_proofs(active_task, root, errors)?;
|
|
63
|
+
|
|
64
|
+
for check_id in required_check_ids {
|
|
65
|
+
let Some(check_id) = check_id.as_str().filter(|value| !value.trim().is_empty()) else {
|
|
66
|
+
continue;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
match proofs.iter().find(|proof| proof.check_id == check_id) {
|
|
70
|
+
Some(proof) if proof.exit_code == 0 => {}
|
|
71
|
+
Some(_) => errors.push(format!(
|
|
72
|
+
"activeTask.proofResults failed proof result: {check_id}"
|
|
73
|
+
)),
|
|
74
|
+
None => errors.push(format!(
|
|
75
|
+
"activeTask.proofResults missing proof result: {check_id}"
|
|
76
|
+
)),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
validate_proof_result_entries(active_task, check_ids, root, errors)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub(super) fn validate_proof_evidence_covers_changed_paths(
|
|
84
|
+
active_task: Option<&Value>,
|
|
85
|
+
root: &Path,
|
|
86
|
+
errors: &mut Vec<String>,
|
|
87
|
+
) -> Result<(), NaomeError> {
|
|
88
|
+
let Some(active_task) = active_task else {
|
|
89
|
+
return Ok(());
|
|
90
|
+
};
|
|
91
|
+
let mut proof_errors = Vec::new();
|
|
92
|
+
let proofs = canonical_proofs(active_task, root, &mut proof_errors)?;
|
|
93
|
+
if proofs.is_empty() {
|
|
94
|
+
return Ok(());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let entries = read_git_changed_entries(root)?;
|
|
98
|
+
validate_proof_evidence_covers_changed_entries(active_task, root, &entries, errors)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub(super) fn validate_proof_evidence_covers_changed_entries(
|
|
102
|
+
active_task: &Value,
|
|
103
|
+
root: &Path,
|
|
104
|
+
entries: &[ChangedEntry],
|
|
105
|
+
errors: &mut Vec<String>,
|
|
106
|
+
) -> Result<(), NaomeError> {
|
|
107
|
+
let mut proof_errors = Vec::new();
|
|
108
|
+
let proofs = canonical_proofs(active_task, root, &mut proof_errors)?;
|
|
109
|
+
if proofs.is_empty() {
|
|
110
|
+
return Ok(());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let changed_paths = task_diff_from_entries(active_task, entries);
|
|
114
|
+
let evidence_paths: HashSet<String> = proofs
|
|
115
|
+
.iter()
|
|
116
|
+
.flat_map(|proof| proof.evidence.clone())
|
|
117
|
+
.filter_map(|entry| evidence_entry_path(&entry).map(normalize_path))
|
|
118
|
+
.collect();
|
|
119
|
+
|
|
120
|
+
let allowed_paths = string_array(active_task.get("allowedPaths")).unwrap_or_default();
|
|
121
|
+
let changed_allowed_paths: Vec<String> = changed_paths
|
|
122
|
+
.diff_paths
|
|
123
|
+
.into_iter()
|
|
124
|
+
.filter(|path| matches_any_pattern(path, &allowed_paths))
|
|
125
|
+
.collect();
|
|
126
|
+
let missing_paths: Vec<String> = changed_allowed_paths
|
|
127
|
+
.into_iter()
|
|
128
|
+
.filter(|path| !evidence_paths.contains(path))
|
|
129
|
+
.collect();
|
|
130
|
+
|
|
131
|
+
if !missing_paths.is_empty() {
|
|
132
|
+
errors.push(format!(
|
|
133
|
+
"activeTask.proofResults evidence missing changed allowed paths: {}",
|
|
134
|
+
missing_paths.join(", ")
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Ok(())
|
|
139
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
|
|
6
|
+
use crate::models::NaomeError;
|
|
7
|
+
|
|
8
|
+
use super::proof::{
|
|
9
|
+
validate_control_state_paths, validate_evidence_array, validate_evidence_paths,
|
|
10
|
+
};
|
|
11
|
+
use super::util::{is_iso_datetime, require_string};
|
|
12
|
+
|
|
13
|
+
pub(super) fn validate_proof_result(
|
|
14
|
+
proof: &Value,
|
|
15
|
+
index: usize,
|
|
16
|
+
check_ids: &HashSet<String>,
|
|
17
|
+
root: &Path,
|
|
18
|
+
errors: &mut Vec<String>,
|
|
19
|
+
active_task: &Value,
|
|
20
|
+
) -> Result<(), NaomeError> {
|
|
21
|
+
let prefix = format!("activeTask.proofResults[{index}]");
|
|
22
|
+
let Some(object) = proof.as_object() else {
|
|
23
|
+
errors.push(format!("{prefix} must be an object."));
|
|
24
|
+
return Ok(());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
require_string(object.get("checkId"), &format!("{prefix}.checkId"), errors);
|
|
28
|
+
require_string(object.get("command"), &format!("{prefix}.command"), errors);
|
|
29
|
+
require_string(object.get("cwd"), &format!("{prefix}.cwd"), errors);
|
|
30
|
+
validate_evidence_array(
|
|
31
|
+
object.get("evidence"),
|
|
32
|
+
&format!("{prefix}.evidence"),
|
|
33
|
+
errors,
|
|
34
|
+
);
|
|
35
|
+
validate_control_state_paths(
|
|
36
|
+
object.get("evidence"),
|
|
37
|
+
&format!("{prefix}.evidence"),
|
|
38
|
+
errors,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if object.get("exitCode").and_then(Value::as_i64).is_none() {
|
|
42
|
+
errors.push(format!("{prefix}.exitCode must be an integer."));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if !object
|
|
46
|
+
.get("checkedAt")
|
|
47
|
+
.and_then(Value::as_str)
|
|
48
|
+
.is_some_and(is_iso_datetime)
|
|
49
|
+
{
|
|
50
|
+
errors.push(format!("{prefix}.checkedAt must be an ISO timestamp."));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if let Some(check_id) = object.get("checkId").and_then(Value::as_str) {
|
|
54
|
+
if !check_id.trim().is_empty() && !check_ids.contains(check_id) {
|
|
55
|
+
errors.push(format!("{prefix}.checkId unknown check id: {check_id}"));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
validate_evidence_paths(
|
|
60
|
+
object.get("evidence"),
|
|
61
|
+
&format!("{prefix}.evidence"),
|
|
62
|
+
root,
|
|
63
|
+
errors,
|
|
64
|
+
active_task,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::compact_proof::compact_proofs;
|
|
8
|
+
use super::proof_sources::{check_id_from_proof, read_verification_defaults};
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone)]
|
|
11
|
+
pub(super) struct CanonicalProof {
|
|
12
|
+
pub(super) check_id: String,
|
|
13
|
+
pub(super) command: String,
|
|
14
|
+
pub(super) cwd: String,
|
|
15
|
+
pub(super) exit_code: i64,
|
|
16
|
+
pub(super) checked_at: String,
|
|
17
|
+
pub(super) evidence: Vec<Value>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Clone)]
|
|
21
|
+
pub(super) struct VerificationDefaults {
|
|
22
|
+
pub(super) command: String,
|
|
23
|
+
pub(super) cwd: String,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub(super) fn canonical_proofs(
|
|
27
|
+
active_task: &Value,
|
|
28
|
+
root: &Path,
|
|
29
|
+
errors: &mut Vec<String>,
|
|
30
|
+
) -> Result<Vec<CanonicalProof>, NaomeError> {
|
|
31
|
+
let defaults = read_verification_defaults(root, errors)?;
|
|
32
|
+
let mut proofs = legacy_proofs(active_task);
|
|
33
|
+
proofs.extend(compact_proofs(active_task, root, errors, &defaults)?);
|
|
34
|
+
Ok(proofs)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(crate) fn canonical_proof_check_ids(active_task: &Value) -> Vec<String> {
|
|
38
|
+
let mut check_ids = Vec::new();
|
|
39
|
+
if let Some(proofs) = active_task.get("proofResults").and_then(Value::as_array) {
|
|
40
|
+
check_ids.extend(proofs.iter().filter_map(check_id_from_proof));
|
|
41
|
+
}
|
|
42
|
+
if let Some(batches) = active_task.get("proofBatches").and_then(Value::as_array) {
|
|
43
|
+
for batch in batches {
|
|
44
|
+
if let Some(proofs) = batch.get("proofs").and_then(Value::as_array) {
|
|
45
|
+
check_ids.extend(proofs.iter().filter_map(check_id_from_proof));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
check_ids
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn legacy_proofs(active_task: &Value) -> Vec<CanonicalProof> {
|
|
53
|
+
active_task
|
|
54
|
+
.get("proofResults")
|
|
55
|
+
.and_then(Value::as_array)
|
|
56
|
+
.into_iter()
|
|
57
|
+
.flatten()
|
|
58
|
+
.filter_map(|proof| {
|
|
59
|
+
let evidence = proof.get("evidence")?.as_array()?.clone();
|
|
60
|
+
Some(CanonicalProof {
|
|
61
|
+
check_id: proof.get("checkId")?.as_str()?.to_string(),
|
|
62
|
+
command: proof.get("command")?.as_str()?.to_string(),
|
|
63
|
+
cwd: proof.get("cwd")?.as_str()?.to_string(),
|
|
64
|
+
exit_code: proof.get("exitCode")?.as_i64()?,
|
|
65
|
+
checked_at: proof.get("checkedAt")?.as_str()?.to_string(),
|
|
66
|
+
evidence,
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
.collect()
|
|
70
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
|
|
6
|
+
use crate::models::NaomeError;
|
|
7
|
+
|
|
8
|
+
use super::proof::{
|
|
9
|
+
validate_control_state_paths, validate_evidence_array, validate_evidence_paths,
|
|
10
|
+
};
|
|
11
|
+
use super::proof_model::VerificationDefaults;
|
|
12
|
+
use super::util::read_json;
|
|
13
|
+
|
|
14
|
+
pub(super) fn read_path_sets(
|
|
15
|
+
active_task: &Value,
|
|
16
|
+
root: &Path,
|
|
17
|
+
errors: &mut Vec<String>,
|
|
18
|
+
) -> Result<HashMap<String, Vec<Value>>, NaomeError> {
|
|
19
|
+
let mut sets = HashMap::new();
|
|
20
|
+
let Some(path_sets) = active_task.get("proofPathSets") else {
|
|
21
|
+
return Ok(sets);
|
|
22
|
+
};
|
|
23
|
+
let Some(path_sets) = path_sets.as_object() else {
|
|
24
|
+
errors.push("activeTask.proofPathSets must be an object when present.".to_string());
|
|
25
|
+
return Ok(sets);
|
|
26
|
+
};
|
|
27
|
+
for (name, value) in path_sets {
|
|
28
|
+
let prefix = format!("activeTask.proofPathSets.{name}");
|
|
29
|
+
validate_evidence_array(Some(value), &prefix, errors);
|
|
30
|
+
validate_control_state_paths(Some(value), &prefix, errors);
|
|
31
|
+
validate_evidence_paths(Some(value), &prefix, root, errors, active_task)?;
|
|
32
|
+
if let Some(paths) = value.as_array() {
|
|
33
|
+
sets.insert(name.clone(), paths.clone());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
Ok(sets)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub(super) fn read_verification_defaults(
|
|
40
|
+
root: &Path,
|
|
41
|
+
errors: &mut Vec<String>,
|
|
42
|
+
) -> Result<HashMap<String, VerificationDefaults>, NaomeError> {
|
|
43
|
+
let Some(verification) = read_json(root, ".naome/verification.json", errors)? else {
|
|
44
|
+
return Ok(HashMap::new());
|
|
45
|
+
};
|
|
46
|
+
let mut defaults = HashMap::new();
|
|
47
|
+
for check in verification
|
|
48
|
+
.get("checks")
|
|
49
|
+
.and_then(Value::as_array)
|
|
50
|
+
.into_iter()
|
|
51
|
+
.flatten()
|
|
52
|
+
{
|
|
53
|
+
let (Some(id), Some(command), Some(cwd)) = (
|
|
54
|
+
check.get("id").and_then(Value::as_str),
|
|
55
|
+
check.get("command").and_then(Value::as_str),
|
|
56
|
+
check.get("cwd").and_then(Value::as_str),
|
|
57
|
+
) else {
|
|
58
|
+
continue;
|
|
59
|
+
};
|
|
60
|
+
defaults.insert(
|
|
61
|
+
id.to_string(),
|
|
62
|
+
VerificationDefaults {
|
|
63
|
+
command: command.to_string(),
|
|
64
|
+
cwd: cwd.to_string(),
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
Ok(defaults)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub(super) fn check_id_from_proof(proof: &Value) -> Option<String> {
|
|
72
|
+
proof
|
|
73
|
+
.get("checkId")
|
|
74
|
+
.and_then(Value::as_str)
|
|
75
|
+
.map(ToString::to_string)
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::repair::{is_completed_task_diff, is_harness_repair_diff, is_naome_baseline_diff};
|
|
8
|
+
use super::shape::validate_blocker;
|
|
9
|
+
use super::types::BLOCKING_STATUS;
|
|
10
|
+
pub(super) fn validate_push_gate(task_state: &Value, errors: &mut Vec<String>) {
|
|
11
|
+
let status = task_state
|
|
12
|
+
.get("status")
|
|
13
|
+
.and_then(Value::as_str)
|
|
14
|
+
.unwrap_or("invalid");
|
|
15
|
+
if !BLOCKING_STATUS.contains(&status) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if status == "blocked" || status == "needs_human_review" {
|
|
20
|
+
validate_blocker(task_state.get("blocker"), errors);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
errors.push(format!("NAOME push gate blocked because task state is {status}. Resolve the active task before pushing. Human options: continue_current_task, request_task_changes, mark_task_blocked, cancel_task_state."));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub(super) fn format_dirty_diff_admission_blocker(
|
|
27
|
+
task_state: &Value,
|
|
28
|
+
root: &Path,
|
|
29
|
+
changed_paths: &[String],
|
|
30
|
+
) -> Result<String, NaomeError> {
|
|
31
|
+
let prefix = format!(
|
|
32
|
+
"Task admission requires a clean git diff. Changed paths: {}.",
|
|
33
|
+
changed_paths.join(", ")
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if is_harness_repair_diff(root, changed_paths)? {
|
|
37
|
+
return Ok(format!("{prefix} These look like completed Harness Repair changes. Run NAOME intent for the next natural-language request before deciding whether to baseline, review, or cancel the repair diff."));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if is_completed_task_diff(task_state, changed_paths) {
|
|
41
|
+
return Ok(format!("{prefix} These look like completed task changes. Run NAOME intent for the next natural-language request; deterministic policy can baseline a valid completed task before creating the next task."));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if is_naome_baseline_diff(changed_paths) {
|
|
45
|
+
return Ok(format!("{prefix} These look like completed NAOME install or upgrade changes. Run NAOME intent for the next natural-language request; deterministic policy can baseline setup before creating the next task."));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Ok(format!("{prefix} Ask the user to choose exactly one: review_task_diff, request_task_changes, cancel_task_changes. Do not start new feature work or commit without explicit user selection."))
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pub(super) use super::completed_refresh::add_completed_task_diff_notice;
|
|
2
|
+
pub(super) use super::push_gate::{format_dirty_diff_admission_blocker, validate_push_gate};
|
|
3
|
+
pub(super) use super::repair::{
|
|
4
|
+
is_deterministic_harness_refresh_diff, is_harness_repair_diff,
|
|
5
|
+
is_install_or_upgrade_baseline_diff, is_packaged_machine_owned_path, is_repair_archive_path,
|
|
6
|
+
is_safe_harness_refresh_path,
|
|
7
|
+
};
|