@lamentis/naome 1.1.1 → 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-node.js +44 -4
- 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 +292 -17
- 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 +221 -4
- 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
|
@@ -8,15 +8,18 @@ use serde_json::Value;
|
|
|
8
8
|
|
|
9
9
|
use crate::decision::{evaluate_decision, EvaluationOptions};
|
|
10
10
|
use crate::git;
|
|
11
|
+
use crate::harness_health::{validate_harness_health, HarnessHealthOptions};
|
|
11
12
|
use crate::install_plan::{LOCAL_NATIVE_BINARY_PATHS, LOCAL_ONLY_MACHINE_OWNED_PATHS};
|
|
12
13
|
use crate::intent::{evaluate_intent, IntentDecision};
|
|
13
14
|
use crate::journal::{append_task_journal, TaskJournalEntry};
|
|
14
15
|
use crate::models::{Decision, NaomeError};
|
|
15
16
|
use crate::paths;
|
|
17
|
+
use crate::quality::{check_repository_quality, QualityMode};
|
|
16
18
|
use crate::task_state::{
|
|
17
19
|
completed_task_commit_paths, completed_task_harness_refresh_diff, harness_refresh_diff,
|
|
18
|
-
harness_refresh_with_unrelated_diff,
|
|
20
|
+
harness_refresh_with_unrelated_diff, validate_task_state, TaskStateMode, TaskStateOptions,
|
|
19
21
|
};
|
|
22
|
+
use crate::verification_contract::validate_verification_contract;
|
|
20
23
|
|
|
21
24
|
const MAX_NAOME_TASK_WORKTREES: usize = 25;
|
|
22
25
|
|
|
@@ -603,7 +606,7 @@ fn run_user_diff_quality_gate(
|
|
|
603
606
|
"Quality check {check_id} is referenced but not defined."
|
|
604
607
|
)));
|
|
605
608
|
};
|
|
606
|
-
run_quality_check(root, check)?;
|
|
609
|
+
run_quality_check(root, check_id, check)?;
|
|
607
610
|
}
|
|
608
611
|
|
|
609
612
|
let current_paths = git::changed_paths(root)?;
|
|
@@ -618,7 +621,7 @@ fn run_user_diff_quality_gate(
|
|
|
618
621
|
|
|
619
622
|
validate_changed_text_whitespace(root, changed_paths)?;
|
|
620
623
|
if let Some(check) = checks.get("diff-check") {
|
|
621
|
-
run_quality_check(root, check)?;
|
|
624
|
+
run_quality_check(root, "diff-check", check)?;
|
|
622
625
|
}
|
|
623
626
|
let current_paths = git::changed_paths(root)?;
|
|
624
627
|
let current_set = sorted_path_set(¤t_paths);
|
|
@@ -826,27 +829,299 @@ fn push_unique_string(values: &mut Vec<String>, value: &str) {
|
|
|
826
829
|
}
|
|
827
830
|
}
|
|
828
831
|
|
|
829
|
-
fn run_quality_check(root: &Path, check: &QualityCheck) -> Result<(), NaomeError> {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
832
|
+
fn run_quality_check(root: &Path, check_id: &str, check: &QualityCheck) -> Result<(), NaomeError> {
|
|
833
|
+
match check_id {
|
|
834
|
+
"installer-tests" => require_builtin_quality_check(
|
|
835
|
+
check_id,
|
|
836
|
+
check,
|
|
837
|
+
"npm run test:naome-installer",
|
|
838
|
+
),
|
|
839
|
+
"rust-build" => require_builtin_quality_check(check_id, check, "npm run build:rust"),
|
|
840
|
+
"decision-engine-tests" => {
|
|
841
|
+
require_builtin_quality_check(check_id, check, "npm run test:decision-engine")
|
|
842
|
+
}
|
|
843
|
+
"package-dry-run" => require_builtin_quality_check(check_id, check, "npm run pack:dry-run"),
|
|
844
|
+
"diff-check" => {
|
|
845
|
+
require_builtin_quality_check(check_id, check, "git diff --check")?;
|
|
846
|
+
let output = Command::new("git")
|
|
847
|
+
.args(["diff", "--check"])
|
|
848
|
+
.current_dir(root)
|
|
849
|
+
.output()?;
|
|
850
|
+
|
|
851
|
+
if output.status.success() {
|
|
852
|
+
Ok(())
|
|
853
|
+
} else {
|
|
854
|
+
Err(NaomeError::new(command_output(&output)))
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
"naome-harness-health" => {
|
|
858
|
+
require_builtin_quality_check(
|
|
859
|
+
check_id,
|
|
860
|
+
check,
|
|
861
|
+
"node .naome/bin/check-harness-health.js",
|
|
862
|
+
)?;
|
|
863
|
+
run_harness_health_check(root)
|
|
864
|
+
}
|
|
865
|
+
"dogfood-health" => {
|
|
866
|
+
require_builtin_quality_check(check_id, check, "npm run dogfood:health")?;
|
|
867
|
+
run_harness_health_check(root)
|
|
868
|
+
}
|
|
869
|
+
"task-state-check" => {
|
|
870
|
+
require_builtin_quality_check(check_id, check, "npm run check:task-state")?;
|
|
871
|
+
run_template_task_state_check(root)
|
|
872
|
+
}
|
|
873
|
+
"verification-contract-check" => {
|
|
874
|
+
require_builtin_quality_check(
|
|
875
|
+
check_id,
|
|
876
|
+
check,
|
|
877
|
+
"npm run check:verification-contract",
|
|
878
|
+
)?;
|
|
879
|
+
run_template_verification_contract_check(root)
|
|
880
|
+
}
|
|
881
|
+
"context-budget-check" => {
|
|
882
|
+
require_builtin_quality_check(check_id, check, "npm run check:context-budget")?;
|
|
883
|
+
run_context_budget_check(root)
|
|
884
|
+
}
|
|
885
|
+
"repository-quality-check" => {
|
|
886
|
+
require_builtin_quality_check_any(
|
|
887
|
+
check_id,
|
|
888
|
+
check,
|
|
889
|
+
&[
|
|
890
|
+
"naome quality check --changed",
|
|
891
|
+
"node .naome/bin/naome.js quality check --changed",
|
|
892
|
+
"npm run check:repository-quality",
|
|
893
|
+
],
|
|
894
|
+
)?;
|
|
895
|
+
run_repository_quality_check(root)
|
|
896
|
+
}
|
|
897
|
+
_ => Err(NaomeError::new(format!(
|
|
898
|
+
"Quality check {check_id} is not a built-in safe check; NAOME will not execute repository-controlled verification commands."
|
|
899
|
+
))),
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
fn require_builtin_quality_check_any(
|
|
904
|
+
check_id: &str,
|
|
905
|
+
check: &QualityCheck,
|
|
906
|
+
expected_commands: &[&str],
|
|
907
|
+
) -> Result<(), NaomeError> {
|
|
908
|
+
if check.cwd == "."
|
|
909
|
+
&& expected_commands
|
|
910
|
+
.iter()
|
|
911
|
+
.any(|expected_command| check.command == *expected_command)
|
|
912
|
+
{
|
|
913
|
+
return Ok(());
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
Err(NaomeError::new(format!(
|
|
917
|
+
"Quality check {check_id} has an unsafe command or cwd; expected one of [{}] with cwd `.`.",
|
|
918
|
+
expected_commands
|
|
919
|
+
.iter()
|
|
920
|
+
.map(|command| format!("`{command}`"))
|
|
921
|
+
.collect::<Vec<_>>()
|
|
922
|
+
.join(", ")
|
|
923
|
+
)))
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
fn require_builtin_quality_check(
|
|
927
|
+
check_id: &str,
|
|
928
|
+
check: &QualityCheck,
|
|
929
|
+
expected_command: &str,
|
|
930
|
+
) -> Result<(), NaomeError> {
|
|
931
|
+
if check.cwd == "." && check.command == expected_command {
|
|
932
|
+
return Ok(());
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
Err(NaomeError::new(format!(
|
|
936
|
+
"Quality check {check_id} has an unsafe command or cwd; expected command `{expected_command}` with cwd `.`."
|
|
937
|
+
)))
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
fn run_repository_quality_check(root: &Path) -> Result<(), NaomeError> {
|
|
941
|
+
let report = check_repository_quality(root, QualityMode::Changed)?;
|
|
942
|
+
if report.ok {
|
|
943
|
+
return Ok(());
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
let details = report
|
|
947
|
+
.violations
|
|
948
|
+
.iter()
|
|
949
|
+
.take(20)
|
|
950
|
+
.map(|violation| {
|
|
951
|
+
let location = violation
|
|
952
|
+
.line
|
|
953
|
+
.map(|line| format!("{}:{line}", violation.path))
|
|
954
|
+
.unwrap_or_else(|| violation.path.clone());
|
|
955
|
+
format!("{location} {}: {}", violation.check_id, violation.message)
|
|
956
|
+
})
|
|
957
|
+
.collect::<Vec<_>>()
|
|
958
|
+
.join("\n");
|
|
959
|
+
Err(NaomeError::new(format!(
|
|
960
|
+
"repository-quality-check failed with {} violation(s).\n{}",
|
|
961
|
+
report.violations.len(),
|
|
962
|
+
details
|
|
963
|
+
)))
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
fn run_harness_health_check(root: &Path) -> Result<(), NaomeError> {
|
|
967
|
+
let errors = validate_harness_health(
|
|
968
|
+
root,
|
|
969
|
+
HarnessHealthOptions {
|
|
970
|
+
expected_integrity: packaged_harness_integrity()?,
|
|
971
|
+
..HarnessHealthOptions::default()
|
|
972
|
+
},
|
|
973
|
+
)?;
|
|
974
|
+
if errors.is_empty() {
|
|
975
|
+
Ok(())
|
|
836
976
|
} else {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
.output()?
|
|
841
|
-
};
|
|
977
|
+
Err(NaomeError::new(errors.join("\n")))
|
|
978
|
+
}
|
|
979
|
+
}
|
|
842
980
|
|
|
843
|
-
|
|
981
|
+
fn packaged_harness_integrity() -> Result<std::collections::HashMap<String, String>, NaomeError> {
|
|
982
|
+
const CHECKER: &str =
|
|
983
|
+
include_str!("../../../templates/naome-root/.naome/bin/check-harness-health.js");
|
|
984
|
+
let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({";
|
|
985
|
+
let start = CHECKER
|
|
986
|
+
.find(start_marker)
|
|
987
|
+
.ok_or_else(|| NaomeError::new("Packaged harness integrity block is missing."))?;
|
|
988
|
+
let body_start = start + start_marker.len();
|
|
989
|
+
let end = CHECKER[body_start..]
|
|
990
|
+
.find("\n});")
|
|
991
|
+
.map(|offset| body_start + offset)
|
|
992
|
+
.ok_or_else(|| NaomeError::new("Packaged harness integrity block is incomplete."))?;
|
|
993
|
+
|
|
994
|
+
let mut integrity = std::collections::HashMap::new();
|
|
995
|
+
for line in CHECKER[body_start..end].lines() {
|
|
996
|
+
let line = line.trim().trim_end_matches(',').trim();
|
|
997
|
+
if line.is_empty() {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
let Some((path, hash)) = line.split_once(':') else {
|
|
1001
|
+
return Err(NaomeError::new(format!(
|
|
1002
|
+
"Packaged harness integrity entry is invalid: {line}"
|
|
1003
|
+
)));
|
|
1004
|
+
};
|
|
1005
|
+
let path: String = serde_json::from_str(path.trim()).map_err(|error| {
|
|
1006
|
+
NaomeError::new(format!(
|
|
1007
|
+
"Packaged harness integrity path is invalid: {error}"
|
|
1008
|
+
))
|
|
1009
|
+
})?;
|
|
1010
|
+
let hash: String = serde_json::from_str(hash.trim()).map_err(|error| {
|
|
1011
|
+
NaomeError::new(format!(
|
|
1012
|
+
"Packaged harness integrity hash is invalid: {error}"
|
|
1013
|
+
))
|
|
1014
|
+
})?;
|
|
1015
|
+
integrity.insert(path, hash);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if integrity.is_empty() {
|
|
1019
|
+
return Err(NaomeError::new(
|
|
1020
|
+
"Packaged harness integrity block is empty.",
|
|
1021
|
+
));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
Ok(integrity)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
fn run_template_task_state_check(root: &Path) -> Result<(), NaomeError> {
|
|
1028
|
+
let template_root = template_root(root);
|
|
1029
|
+
let report = validate_task_state(
|
|
1030
|
+
&template_root,
|
|
1031
|
+
TaskStateOptions {
|
|
1032
|
+
mode: TaskStateMode::State,
|
|
1033
|
+
harness_health: Some(HarnessHealthOptions {
|
|
1034
|
+
expected_integrity: packaged_harness_integrity()?,
|
|
1035
|
+
allow_missing_archive: true,
|
|
1036
|
+
..HarnessHealthOptions::default()
|
|
1037
|
+
}),
|
|
1038
|
+
},
|
|
1039
|
+
)?;
|
|
1040
|
+
if report.errors.is_empty() {
|
|
844
1041
|
Ok(())
|
|
845
1042
|
} else {
|
|
846
|
-
Err(NaomeError::new(
|
|
1043
|
+
Err(NaomeError::new(report.errors.join("\n")))
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
fn run_template_verification_contract_check(root: &Path) -> Result<(), NaomeError> {
|
|
1048
|
+
let errors = validate_verification_contract(&template_root(root))?;
|
|
1049
|
+
if errors.is_empty() {
|
|
1050
|
+
Ok(())
|
|
1051
|
+
} else {
|
|
1052
|
+
Err(NaomeError::new(errors.join("\n")))
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
fn run_context_budget_check(root: &Path) -> Result<(), NaomeError> {
|
|
1057
|
+
let template_root = template_root(root);
|
|
1058
|
+
let mut context_files = vec![
|
|
1059
|
+
template_root.join("AGENTS.md"),
|
|
1060
|
+
template_root.join(".naomeignore"),
|
|
1061
|
+
];
|
|
1062
|
+
context_files.extend(markdown_files(&template_root.join("docs").join("naome"))?);
|
|
1063
|
+
context_files.sort();
|
|
1064
|
+
|
|
1065
|
+
let mut errors = Vec::new();
|
|
1066
|
+
for path in context_files {
|
|
1067
|
+
let content = fs::read_to_string(&path)?;
|
|
1068
|
+
let line_count = count_lines(&content);
|
|
1069
|
+
if line_count > 200 {
|
|
1070
|
+
errors.push(format!(
|
|
1071
|
+
"{}: {line_count} lines",
|
|
1072
|
+
display_repo_path(root, &path)
|
|
1073
|
+
));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if errors.is_empty() {
|
|
1078
|
+
Ok(())
|
|
1079
|
+
} else {
|
|
1080
|
+
Err(NaomeError::new(format!(
|
|
1081
|
+
"NAOME context budget exceeded. Limit: 200 lines per file.\n{}",
|
|
1082
|
+
errors.join("\n")
|
|
1083
|
+
)))
|
|
847
1084
|
}
|
|
848
1085
|
}
|
|
849
1086
|
|
|
1087
|
+
fn template_root(root: &Path) -> PathBuf {
|
|
1088
|
+
root.join("packages")
|
|
1089
|
+
.join("naome")
|
|
1090
|
+
.join("templates")
|
|
1091
|
+
.join("naome-root")
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
fn markdown_files(dir: &Path) -> Result<Vec<PathBuf>, NaomeError> {
|
|
1095
|
+
let mut files = Vec::new();
|
|
1096
|
+
for entry in fs::read_dir(dir)? {
|
|
1097
|
+
let entry = entry?;
|
|
1098
|
+
let path = entry.path();
|
|
1099
|
+
if path.is_dir() {
|
|
1100
|
+
files.extend(markdown_files(&path)?);
|
|
1101
|
+
} else if path.is_file() && path.extension().is_some_and(|extension| extension == "md") {
|
|
1102
|
+
files.push(path);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
Ok(files)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
fn count_lines(content: &str) -> usize {
|
|
1109
|
+
if content.is_empty() {
|
|
1110
|
+
0
|
|
1111
|
+
} else if content.ends_with('\n') {
|
|
1112
|
+
content.split('\n').count() - 1
|
|
1113
|
+
} else {
|
|
1114
|
+
content.split('\n').count()
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
fn display_repo_path(root: &Path, path: &Path) -> String {
|
|
1119
|
+
path.strip_prefix(root)
|
|
1120
|
+
.unwrap_or(path)
|
|
1121
|
+
.to_string_lossy()
|
|
1122
|
+
.to_string()
|
|
1123
|
+
}
|
|
1124
|
+
|
|
850
1125
|
fn git_commit(root: &Path, message: &str) -> Result<(), NaomeError> {
|
|
851
1126
|
let output = Command::new("git")
|
|
852
1127
|
.args(["commit", "-m", message])
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::completion::validate_complete_task;
|
|
8
|
+
use super::human_review_state::validate_human_review_state;
|
|
9
|
+
use super::progress::{checked_status, validate_clean_git_diff};
|
|
10
|
+
use super::shape::{
|
|
11
|
+
format_blocker, validate_active_task, validate_active_task_references, validate_blocker,
|
|
12
|
+
validate_idle_state,
|
|
13
|
+
};
|
|
14
|
+
pub(super) fn validate_admission(
|
|
15
|
+
task_state: &Value,
|
|
16
|
+
root: &Path,
|
|
17
|
+
errors: &mut Vec<String>,
|
|
18
|
+
) -> Result<(), NaomeError> {
|
|
19
|
+
let status = checked_status(task_state, root, errors)?;
|
|
20
|
+
match status {
|
|
21
|
+
"idle" => validate_idle_state(task_state, errors),
|
|
22
|
+
"complete" => {
|
|
23
|
+
validate_active_task(task_state.get("activeTask"), errors);
|
|
24
|
+
validate_active_task_references(
|
|
25
|
+
task_state.get("activeTask"),
|
|
26
|
+
root,
|
|
27
|
+
errors,
|
|
28
|
+
Some(status),
|
|
29
|
+
)?;
|
|
30
|
+
validate_complete_task(
|
|
31
|
+
task_state.get("activeTask"),
|
|
32
|
+
task_state.get("blocker"),
|
|
33
|
+
root,
|
|
34
|
+
errors,
|
|
35
|
+
&mut Vec::new(),
|
|
36
|
+
)?;
|
|
37
|
+
}
|
|
38
|
+
"needs_human_review" => {
|
|
39
|
+
let start = errors.len();
|
|
40
|
+
validate_human_review_state(task_state, root, errors)?;
|
|
41
|
+
if errors.len() > start {
|
|
42
|
+
errors.push("Task admission is blocked because needs_human_review state is invalid; fix blocker paths and proof evidence before asking for a human decision.".to_string());
|
|
43
|
+
} else {
|
|
44
|
+
errors.push(format_blocker(
|
|
45
|
+
"Task admission is blocked",
|
|
46
|
+
task_state.get("blocker"),
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
"blocked" => {
|
|
51
|
+
validate_blocker(task_state.get("blocker"), errors);
|
|
52
|
+
errors.push(format_blocker(
|
|
53
|
+
"Task admission is blocked",
|
|
54
|
+
task_state.get("blocker"),
|
|
55
|
+
));
|
|
56
|
+
}
|
|
57
|
+
other => errors.push(format!(
|
|
58
|
+
"Task admission is blocked because task state is {other}."
|
|
59
|
+
)),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
validate_clean_git_diff(task_state, root, errors)
|
|
63
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::git_io::git_commit_exists;
|
|
8
|
+
use super::util::{is_iso_datetime, require_string, require_string_array_allow_empty};
|
|
9
|
+
|
|
10
|
+
pub(super) fn validate_admission_proof(
|
|
11
|
+
admission: Option<&Value>,
|
|
12
|
+
root: &Path,
|
|
13
|
+
errors: &mut Vec<String>,
|
|
14
|
+
) -> Result<(), NaomeError> {
|
|
15
|
+
let Some(object) = admission.and_then(Value::as_object) else {
|
|
16
|
+
errors.push(
|
|
17
|
+
"activeTask.admission must be an object recorded from a passed admission check."
|
|
18
|
+
.to_string(),
|
|
19
|
+
);
|
|
20
|
+
return Ok(());
|
|
21
|
+
};
|
|
22
|
+
let prefix = "activeTask.admission";
|
|
23
|
+
|
|
24
|
+
require_string(object.get("command"), &format!("{prefix}.command"), errors);
|
|
25
|
+
require_string(object.get("cwd"), &format!("{prefix}.cwd"), errors);
|
|
26
|
+
require_string_array_allow_empty(
|
|
27
|
+
object.get("changedPaths"),
|
|
28
|
+
&format!("{prefix}.changedPaths"),
|
|
29
|
+
errors,
|
|
30
|
+
);
|
|
31
|
+
require_string(object.get("gitHead"), &format!("{prefix}.gitHead"), errors);
|
|
32
|
+
|
|
33
|
+
if object.get("command").and_then(Value::as_str)
|
|
34
|
+
!= Some("node .naome/bin/check-task-state.js --admission")
|
|
35
|
+
{
|
|
36
|
+
errors.push(format!(
|
|
37
|
+
"{prefix}.command must be node .naome/bin/check-task-state.js --admission."
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if object.get("cwd").and_then(Value::as_str) != Some(".") {
|
|
42
|
+
errors.push(format!("{prefix}.cwd must be \".\"."));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
match object.get("exitCode").and_then(Value::as_i64) {
|
|
46
|
+
Some(0) => {}
|
|
47
|
+
Some(_) => errors.push(format!("{prefix}.exitCode must be 0.")),
|
|
48
|
+
None => errors.push(format!("{prefix}.exitCode must be an integer.")),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if !object
|
|
52
|
+
.get("checkedAt")
|
|
53
|
+
.and_then(Value::as_str)
|
|
54
|
+
.is_some_and(is_iso_datetime)
|
|
55
|
+
{
|
|
56
|
+
errors.push(format!("{prefix}.checkedAt must be an ISO timestamp."));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if let Some(changed_paths) = object.get("changedPaths").and_then(Value::as_array) {
|
|
60
|
+
if !changed_paths.is_empty() {
|
|
61
|
+
errors.push(format!("{prefix}.changedPaths must be empty because task admission requires a clean git diff."));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if let Some(git_head) = object.get("gitHead").and_then(Value::as_str) {
|
|
66
|
+
if !git_head.trim().is_empty() && !git_commit_exists(root, git_head)? {
|
|
67
|
+
errors.push(format!("{prefix}.gitHead must be an existing git commit."));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Ok(())
|
|
72
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::completion::{
|
|
8
|
+
validate_admission, validate_commit_gate, validate_complete_task, validate_progress,
|
|
9
|
+
};
|
|
10
|
+
use super::diff::validate_human_review_blocker_paths;
|
|
11
|
+
use super::proof::validate_proof_evidence_covers_changed_paths;
|
|
12
|
+
use super::reconcile::validate_push_gate;
|
|
13
|
+
use super::shape::{
|
|
14
|
+
format_blocker, validate_active_task, validate_active_task_references, validate_blocker,
|
|
15
|
+
validate_idle_state, validate_pending_upgrade, validate_task_state_shape,
|
|
16
|
+
};
|
|
17
|
+
use super::task_diff_api::validate_harness_health_gate;
|
|
18
|
+
use super::types::*;
|
|
19
|
+
use super::util::read_json;
|
|
20
|
+
pub fn validate_task_state(
|
|
21
|
+
root: &Path,
|
|
22
|
+
options: TaskStateOptions,
|
|
23
|
+
) -> Result<TaskStateReport, NaomeError> {
|
|
24
|
+
let mut report = TaskStateReport {
|
|
25
|
+
errors: Vec::new(),
|
|
26
|
+
notices: Vec::new(),
|
|
27
|
+
};
|
|
28
|
+
let Some(task_state) = read_json(root, ".naome/task-state.json", &mut report.errors)? else {
|
|
29
|
+
return Ok(report);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
validate_task_state_shape(&task_state, &mut report.errors);
|
|
33
|
+
let status = task_state
|
|
34
|
+
.get("status")
|
|
35
|
+
.and_then(Value::as_str)
|
|
36
|
+
.unwrap_or("invalid");
|
|
37
|
+
if !ALLOWED_STATUS.contains(&status) {
|
|
38
|
+
return Ok(report);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
validate_harness_health_gate(root, &options, &mut report.errors)?;
|
|
42
|
+
if !report.errors.is_empty() {
|
|
43
|
+
return Ok(report);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if validate_requested_mode(&task_state, root, options.mode, &mut report)? {
|
|
47
|
+
return Ok(report);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if status == "idle" {
|
|
51
|
+
validate_idle_state(&task_state, &mut report.errors);
|
|
52
|
+
return Ok(report);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let active_error_start = report.errors.len();
|
|
56
|
+
validate_active_task(task_state.get("activeTask"), &mut report.errors);
|
|
57
|
+
validate_pending_upgrade(&task_state, root, &mut report.errors)?;
|
|
58
|
+
validate_active_task_references(
|
|
59
|
+
task_state.get("activeTask"),
|
|
60
|
+
root,
|
|
61
|
+
&mut report.errors,
|
|
62
|
+
Some(status),
|
|
63
|
+
)?;
|
|
64
|
+
|
|
65
|
+
if status == "needs_human_review" {
|
|
66
|
+
validate_blocker(task_state.get("blocker"), &mut report.errors);
|
|
67
|
+
validate_human_review_blocker_paths(
|
|
68
|
+
task_state.get("activeTask"),
|
|
69
|
+
task_state.get("blocker"),
|
|
70
|
+
root,
|
|
71
|
+
&mut report.errors,
|
|
72
|
+
)?;
|
|
73
|
+
validate_proof_evidence_covers_changed_paths(
|
|
74
|
+
task_state.get("activeTask"),
|
|
75
|
+
root,
|
|
76
|
+
&mut report.errors,
|
|
77
|
+
)?;
|
|
78
|
+
if report.errors.len() > active_error_start {
|
|
79
|
+
report.errors.push("needs_human_review task state is invalid; fix blocker paths and proof evidence before asking for a human decision.".to_string());
|
|
80
|
+
} else {
|
|
81
|
+
report.errors.push(format_blocker(
|
|
82
|
+
"Human review required",
|
|
83
|
+
task_state.get("blocker"),
|
|
84
|
+
));
|
|
85
|
+
}
|
|
86
|
+
return Ok(report);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if status == "blocked" {
|
|
90
|
+
validate_blocker(task_state.get("blocker"), &mut report.errors);
|
|
91
|
+
report
|
|
92
|
+
.errors
|
|
93
|
+
.push(format_blocker("Task is blocked", task_state.get("blocker")));
|
|
94
|
+
return Ok(report);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if BLOCKING_STATUS.contains(&status) {
|
|
98
|
+
report.errors.push(format!(
|
|
99
|
+
"Task is still {status}; new work must wait until the active task is complete or resolved."
|
|
100
|
+
));
|
|
101
|
+
return Ok(report);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
validate_complete_task(
|
|
105
|
+
task_state.get("activeTask"),
|
|
106
|
+
task_state.get("blocker"),
|
|
107
|
+
root,
|
|
108
|
+
&mut report.errors,
|
|
109
|
+
&mut report.notices,
|
|
110
|
+
)?;
|
|
111
|
+
Ok(report)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn validate_requested_mode(
|
|
115
|
+
task_state: &Value,
|
|
116
|
+
root: &Path,
|
|
117
|
+
mode: TaskStateMode,
|
|
118
|
+
report: &mut TaskStateReport,
|
|
119
|
+
) -> Result<bool, NaomeError> {
|
|
120
|
+
match mode {
|
|
121
|
+
TaskStateMode::Admission => validate_admission(task_state, root, &mut report.errors)?,
|
|
122
|
+
TaskStateMode::Progress => validate_progress(task_state, root, &mut report.errors)?,
|
|
123
|
+
TaskStateMode::CommitGate => {
|
|
124
|
+
validate_commit_gate(task_state, root, &mut report.errors, &mut report.notices)?;
|
|
125
|
+
}
|
|
126
|
+
TaskStateMode::PushGate => validate_push_gate(task_state, &mut report.errors),
|
|
127
|
+
TaskStateMode::State => return Ok(false),
|
|
128
|
+
}
|
|
129
|
+
Ok(true)
|
|
130
|
+
}
|