@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
|
@@ -0,0 +1,154 @@
|
|
|
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::deleted_paths::read_historical_deleted_paths;
|
|
9
|
+
use super::git_io::read_git_changed_entries;
|
|
10
|
+
use super::types::{ALLOWED_EVIDENCE_STATUS, CONTROL_STATE_PATH};
|
|
11
|
+
use super::util::{
|
|
12
|
+
is_non_empty_string, matches_any_pattern, normalize_path, require_string, string_array,
|
|
13
|
+
};
|
|
14
|
+
pub(super) fn validate_evidence_array(
|
|
15
|
+
evidence: Option<&Value>,
|
|
16
|
+
field_name: &str,
|
|
17
|
+
errors: &mut Vec<String>,
|
|
18
|
+
) {
|
|
19
|
+
let Some(evidence) = evidence.and_then(Value::as_array) else {
|
|
20
|
+
errors.push(format!("{field_name} must be an evidence array."));
|
|
21
|
+
return;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (index, entry) in evidence.iter().enumerate() {
|
|
25
|
+
let prefix = format!("{field_name}[{index}]");
|
|
26
|
+
if entry.as_str().is_some_and(is_non_empty_string) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let Some(object) = entry.as_object() else {
|
|
31
|
+
errors.push(format!(
|
|
32
|
+
"{prefix} must be a non-empty string path or an evidence object."
|
|
33
|
+
));
|
|
34
|
+
continue;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
require_string(object.get("path"), &format!("{prefix}.path"), errors);
|
|
38
|
+
|
|
39
|
+
if let Some(status) = object.get("status").and_then(Value::as_str) {
|
|
40
|
+
if !ALLOWED_EVIDENCE_STATUS.contains(&status) {
|
|
41
|
+
errors.push(format!(
|
|
42
|
+
"{prefix}.status must be one of: {}.",
|
|
43
|
+
ALLOWED_EVIDENCE_STATUS.join(", ")
|
|
44
|
+
));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if object.contains_key("fromPath")
|
|
49
|
+
&& !object
|
|
50
|
+
.get("fromPath")
|
|
51
|
+
.and_then(Value::as_str)
|
|
52
|
+
.is_some_and(is_non_empty_string)
|
|
53
|
+
{
|
|
54
|
+
errors.push(format!(
|
|
55
|
+
"{prefix}.fromPath must be a non-empty string when present."
|
|
56
|
+
));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(super) fn validate_control_state_patterns(
|
|
62
|
+
patterns: Option<&Value>,
|
|
63
|
+
field_name: &str,
|
|
64
|
+
errors: &mut Vec<String>,
|
|
65
|
+
) {
|
|
66
|
+
let Some(patterns) = string_array(patterns) else {
|
|
67
|
+
return;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for pattern in patterns {
|
|
71
|
+
if matches_any_pattern(CONTROL_STATE_PATH, std::slice::from_ref(&pattern)) {
|
|
72
|
+
errors.push(format!(
|
|
73
|
+
"{field_name} cannot include NAOME control state: {pattern}"
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub(super) fn validate_control_state_paths(
|
|
80
|
+
paths: Option<&Value>,
|
|
81
|
+
field_name: &str,
|
|
82
|
+
errors: &mut Vec<String>,
|
|
83
|
+
) {
|
|
84
|
+
let Some(paths) = paths.and_then(Value::as_array) else {
|
|
85
|
+
return;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for entry in paths {
|
|
89
|
+
let Some(path) = evidence_entry_path(entry) else {
|
|
90
|
+
continue;
|
|
91
|
+
};
|
|
92
|
+
if normalize_path(&path) == CONTROL_STATE_PATH {
|
|
93
|
+
errors.push(format!(
|
|
94
|
+
"{field_name} cannot include NAOME control state: {path}"
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub(super) fn validate_evidence_paths(
|
|
101
|
+
evidence: Option<&Value>,
|
|
102
|
+
field_name: &str,
|
|
103
|
+
root: &Path,
|
|
104
|
+
errors: &mut Vec<String>,
|
|
105
|
+
active_task: &Value,
|
|
106
|
+
) -> Result<(), NaomeError> {
|
|
107
|
+
let Some(evidence) = evidence.and_then(Value::as_array) else {
|
|
108
|
+
return Ok(());
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let mut deleted_paths: HashSet<String> = read_git_changed_entries(root)?
|
|
112
|
+
.into_iter()
|
|
113
|
+
.filter(|entry| entry.status == "deleted")
|
|
114
|
+
.map(|entry| entry.path)
|
|
115
|
+
.collect();
|
|
116
|
+
for path in read_historical_deleted_paths(active_task, root)? {
|
|
117
|
+
deleted_paths.insert(path);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for entry in evidence {
|
|
121
|
+
let Some(evidence_path) = evidence_entry_path(entry) else {
|
|
122
|
+
continue;
|
|
123
|
+
};
|
|
124
|
+
let normalized_path = normalize_path(&evidence_path);
|
|
125
|
+
if Path::new(&evidence_path).is_absolute()
|
|
126
|
+
|| normalized_path.split('/').any(|part| part == "..")
|
|
127
|
+
{
|
|
128
|
+
errors.push(format!("{field_name} unsafe path: {evidence_path}"));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if !root.join(&normalized_path).exists() && !deleted_paths.contains(&normalized_path) {
|
|
133
|
+
errors.push(format!(
|
|
134
|
+
"{field_name} path does not exist or is not deleted in git diff: {evidence_path}"
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Ok(())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub(super) fn evidence_entry_path(entry: &Value) -> Option<String> {
|
|
143
|
+
entry
|
|
144
|
+
.as_str()
|
|
145
|
+
.filter(|value| is_non_empty_string(value))
|
|
146
|
+
.map(ToString::to_string)
|
|
147
|
+
.or_else(|| {
|
|
148
|
+
entry
|
|
149
|
+
.get("path")
|
|
150
|
+
.and_then(Value::as_str)
|
|
151
|
+
.filter(|value| is_non_empty_string(value))
|
|
152
|
+
.map(ToString::to_string)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
pub(super) use super::git_parse::{parse_name_status_output, split_nul, upsert_changed_entry};
|
|
8
|
+
pub(super) use super::git_refs::{command_output, git_commit_exists, read_git_head, run_git};
|
|
9
|
+
use super::types::ChangedEntry;
|
|
10
|
+
use super::util::normalize_path;
|
|
11
|
+
pub(super) fn read_git_changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
|
|
12
|
+
Ok(read_git_changed_entries(root)?
|
|
13
|
+
.into_iter()
|
|
14
|
+
.map(|entry| entry.path)
|
|
15
|
+
.collect())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub(super) fn read_git_staged_changed_entries(
|
|
19
|
+
root: &Path,
|
|
20
|
+
) -> Result<Vec<ChangedEntry>, NaomeError> {
|
|
21
|
+
let output = Command::new("git")
|
|
22
|
+
.args(["diff", "--name-status", "--cached", "-z"])
|
|
23
|
+
.current_dir(root)
|
|
24
|
+
.output()?;
|
|
25
|
+
if !output.status.success() {
|
|
26
|
+
return Err(NaomeError::new(format!(
|
|
27
|
+
"git diff --name-status --cached -z failed: {}",
|
|
28
|
+
command_output(&output)
|
|
29
|
+
)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Ok(parse_name_status_output(&output.stdout))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub(super) fn read_git_changed_entries(root: &Path) -> Result<Vec<ChangedEntry>, NaomeError> {
|
|
36
|
+
let git_check = run_git(root, ["rev-parse", "--is-inside-work-tree"])?;
|
|
37
|
+
if !git_check.status.success() {
|
|
38
|
+
return Err(NaomeError::new(
|
|
39
|
+
"complete task validation requires a git work tree.",
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let mut entries: HashMap<String, ChangedEntry> = HashMap::new();
|
|
44
|
+
for args in [
|
|
45
|
+
vec!["diff", "--name-status", "-z"],
|
|
46
|
+
vec!["diff", "--name-status", "--cached", "-z"],
|
|
47
|
+
] {
|
|
48
|
+
let output = Command::new("git").args(&args).current_dir(root).output()?;
|
|
49
|
+
if !output.status.success() {
|
|
50
|
+
return Err(NaomeError::new(format!(
|
|
51
|
+
"git {} failed: {}",
|
|
52
|
+
args.join(" "),
|
|
53
|
+
command_output(&output)
|
|
54
|
+
)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for entry in parse_name_status_output(&output.stdout) {
|
|
58
|
+
upsert_changed_entry(&mut entries, entry);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let untracked = run_git(root, ["ls-files", "--others", "--exclude-standard", "-z"])?;
|
|
63
|
+
if !untracked.status.success() {
|
|
64
|
+
return Err(NaomeError::new(format!(
|
|
65
|
+
"git ls-files --others --exclude-standard -z failed: {}",
|
|
66
|
+
command_output(&untracked)
|
|
67
|
+
)));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for token in split_nul(&untracked.stdout) {
|
|
71
|
+
let path = normalize_path(token.trim());
|
|
72
|
+
if !path.is_empty() {
|
|
73
|
+
upsert_changed_entry(
|
|
74
|
+
&mut entries,
|
|
75
|
+
ChangedEntry {
|
|
76
|
+
path,
|
|
77
|
+
status: "added".to_string(),
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let mut entries: Vec<ChangedEntry> = entries.into_values().collect();
|
|
84
|
+
entries.sort_by(|left, right| left.path.cmp(&right.path));
|
|
85
|
+
Ok(entries)
|
|
86
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use super::types::ChangedEntry;
|
|
4
|
+
use super::util::normalize_path;
|
|
5
|
+
pub(super) fn parse_name_status_output(output: &[u8]) -> Vec<ChangedEntry> {
|
|
6
|
+
let tokens = split_nul(output);
|
|
7
|
+
let mut entries = Vec::new();
|
|
8
|
+
let mut index = 0;
|
|
9
|
+
|
|
10
|
+
while index < tokens.len() {
|
|
11
|
+
let raw_status = &tokens[index];
|
|
12
|
+
index += 1;
|
|
13
|
+
let status_code = raw_status.chars().next().unwrap_or('M');
|
|
14
|
+
|
|
15
|
+
if status_code == 'R' || status_code == 'C' {
|
|
16
|
+
let from_path = normalize_path(tokens.get(index).map(String::as_str).unwrap_or(""));
|
|
17
|
+
index += 1;
|
|
18
|
+
let to_path = normalize_path(tokens.get(index).map(String::as_str).unwrap_or(""));
|
|
19
|
+
index += 1;
|
|
20
|
+
if !from_path.is_empty() {
|
|
21
|
+
entries.push(ChangedEntry {
|
|
22
|
+
path: from_path,
|
|
23
|
+
status: "deleted".to_string(),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if !to_path.is_empty() {
|
|
27
|
+
entries.push(ChangedEntry {
|
|
28
|
+
path: to_path,
|
|
29
|
+
status: "renamed".to_string(),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let path = normalize_path(tokens.get(index).map(String::as_str).unwrap_or(""));
|
|
36
|
+
index += 1;
|
|
37
|
+
if path.is_empty() {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
entries.push(ChangedEntry {
|
|
42
|
+
path,
|
|
43
|
+
status: git_status_code_to_evidence_status(status_code).to_string(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
entries
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub(super) fn split_nul(output: &[u8]) -> Vec<String> {
|
|
51
|
+
output
|
|
52
|
+
.split(|byte| *byte == 0)
|
|
53
|
+
.filter(|token| !token.is_empty())
|
|
54
|
+
.map(|token| String::from_utf8_lossy(token).to_string())
|
|
55
|
+
.collect()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub(super) fn git_status_code_to_evidence_status(status_code: char) -> &'static str {
|
|
59
|
+
match status_code {
|
|
60
|
+
'A' => "added",
|
|
61
|
+
'D' => "deleted",
|
|
62
|
+
_ => "modified",
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub(super) fn upsert_changed_entry(
|
|
67
|
+
entries: &mut HashMap<String, ChangedEntry>,
|
|
68
|
+
entry: ChangedEntry,
|
|
69
|
+
) {
|
|
70
|
+
let should_replace = entries
|
|
71
|
+
.get(&entry.path)
|
|
72
|
+
.map(|existing| status_rank(&entry.status) > status_rank(&existing.status))
|
|
73
|
+
.unwrap_or(true);
|
|
74
|
+
if should_replace {
|
|
75
|
+
entries.insert(entry.path.clone(), entry);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub(super) fn status_rank(status: &str) -> u8 {
|
|
80
|
+
match status {
|
|
81
|
+
"deleted" => 4,
|
|
82
|
+
"renamed" => 3,
|
|
83
|
+
"added" => 2,
|
|
84
|
+
_ => 1,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
use std::process::Command;
|
|
3
|
+
|
|
4
|
+
use crate::models::NaomeError;
|
|
5
|
+
|
|
6
|
+
pub(super) fn read_git_head(root: &Path) -> Result<Option<String>, NaomeError> {
|
|
7
|
+
let output = run_git(root, ["rev-parse", "HEAD"])?;
|
|
8
|
+
if !output.status.success() {
|
|
9
|
+
return Ok(None);
|
|
10
|
+
}
|
|
11
|
+
Ok(Some(
|
|
12
|
+
String::from_utf8_lossy(&output.stdout).trim().to_string(),
|
|
13
|
+
))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub(super) fn git_commit_exists(root: &Path, commit: &str) -> Result<bool, NaomeError> {
|
|
17
|
+
Ok(
|
|
18
|
+
run_git(root, ["cat-file", "-e", &format!("{commit}^{{commit}}")])?
|
|
19
|
+
.status
|
|
20
|
+
.success(),
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub(super) fn run_git<const N: usize>(
|
|
25
|
+
root: &Path,
|
|
26
|
+
args: [&str; N],
|
|
27
|
+
) -> Result<std::process::Output, NaomeError> {
|
|
28
|
+
Ok(Command::new("git").args(args).current_dir(root).output()?)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub(super) fn command_output(output: &std::process::Output) -> String {
|
|
32
|
+
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
|
33
|
+
if !stderr.is_empty() {
|
|
34
|
+
return stderr;
|
|
35
|
+
}
|
|
36
|
+
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::NaomeError;
|
|
6
|
+
|
|
7
|
+
use super::diff::validate_human_review_blocker_paths;
|
|
8
|
+
use super::proof::validate_proof_evidence_covers_changed_paths;
|
|
9
|
+
use super::shape::{validate_active_task, validate_active_task_references, validate_blocker};
|
|
10
|
+
|
|
11
|
+
pub(super) fn validate_human_review_state(
|
|
12
|
+
task_state: &Value,
|
|
13
|
+
root: &Path,
|
|
14
|
+
errors: &mut Vec<String>,
|
|
15
|
+
) -> Result<(), NaomeError> {
|
|
16
|
+
validate_active_task(task_state.get("activeTask"), errors);
|
|
17
|
+
validate_active_task_references(
|
|
18
|
+
task_state.get("activeTask"),
|
|
19
|
+
root,
|
|
20
|
+
errors,
|
|
21
|
+
Some("needs_human_review"),
|
|
22
|
+
)?;
|
|
23
|
+
validate_blocker(task_state.get("blocker"), errors);
|
|
24
|
+
validate_human_review_blocker_paths(
|
|
25
|
+
task_state.get("activeTask"),
|
|
26
|
+
task_state.get("blocker"),
|
|
27
|
+
root,
|
|
28
|
+
errors,
|
|
29
|
+
)?;
|
|
30
|
+
validate_proof_evidence_covers_changed_paths(task_state.get("activeTask"), root, errors)
|
|
31
|
+
}
|
|
@@ -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
|
+
}
|