@lamentis/naome 1.1.2 → 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.
- package/Cargo.lock +2 -2
- package/Cargo.toml +1 -1
- package/LICENSE +180 -21
- package/README.md +49 -6
- package/bin/naome-node.js +2 -1579
- package/bin/naome.js +68 -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 +37 -0
- package/crates/naome-cli/src/install_bridge.rs +83 -0
- package/crates/naome-cli/src/main.rs +60 -341
- package/crates/naome-cli/src/prompt_commands.rs +68 -0
- package/crates/naome-cli/src/quality_commands.rs +229 -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/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/harness_health/integrity.rs +96 -0
- package/crates/naome-core/src/harness_health.rs +14 -126
- package/crates/naome-core/src/install_plan.rs +5 -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 +15 -0
- 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 +131 -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 +84 -0
- package/crates/naome-core/src/quality/config.rs +102 -0
- package/crates/naome-core/src/quality/config_support.rs +24 -0
- package/crates/naome-core/src/quality/mod.rs +108 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
- package/crates/naome-core/src/quality/scanner.rs +379 -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 +144 -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 +94 -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 +124 -0
- package/crates/naome-core/src/quality/types.rs +292 -0
- package/crates/naome-core/src/route/builtin_checks.rs +155 -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 -1155
- 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 +177 -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/decision.rs +24 -118
- package/crates/naome-core/tests/harness_health.rs +5 -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 +319 -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 +125 -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 -1476
- 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 +198 -0
- package/crates/naome-core/tests/route_worktree.rs +54 -0
- package/crates/naome-core/tests/task_state.rs +60 -429
- 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/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_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/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 +170 -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 +3 -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 +51 -76
- package/templates/naome-root/.naome/manifest.json +22 -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/repository-structure.json +90 -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 +38 -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 +5 -3
- package/templates/naome-root/docs/naome/repository-quality.md +46 -0
- package/templates/naome-root/docs/naome/repository-structure.md +51 -0
- package/templates/naome-root/docs/naome/testing.md +13 -0
- package/crates/naome-core/src/task_state.rs +0 -2210
|
@@ -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
|
+
};
|