@lamentis/naome 1.2.0 → 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/bin/naome-node.js +2 -1579
- package/bin/naome.js +19 -5
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/dispatcher.rs +2 -1
- package/crates/naome-cli/src/main.rs +3 -0
- package/crates/naome-cli/src/quality_commands.rs +90 -2
- 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/install_plan.rs +2 -0
- package/crates/naome-core/src/lib.rs +5 -3
- 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 +20 -67
- package/crates/naome-core/src/quality/cleanup.rs +13 -1
- package/crates/naome-core/src/quality/config.rs +8 -15
- package/crates/naome-core/src/quality/config_support.rs +24 -0
- package/crates/naome-core/src/quality/mod.rs +18 -0
- package/crates/naome-core/src/quality/scanner.rs +20 -8
- 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 +3 -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 -1217
- package/crates/naome-core/src/verification.rs +1 -0
- package/crates/naome-core/tests/decision.rs +24 -118
- package/crates/naome-core/tests/harness_health.rs +2 -0
- package/crates/naome-core/tests/quality.rs +12 -118
- 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 -1376
- 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 -432
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
- 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_support/mod.rs +1 -1
- 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 +2 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
- package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
- package/templates/naome-root/.naome/bin/naome.js +25 -21
- package/templates/naome-root/.naome/manifest.json +4 -2
- package/templates/naome-root/.naome/repository-structure.json +90 -0
- package/templates/naome-root/.naome/verification.json +1 -0
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +3 -0
- package/templates/naome-root/docs/naome/repository-structure.md +51 -0
- package/templates/naome-root/docs/naome/testing.md +2 -1
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
use std::ffi::OsString;
|
|
2
|
-
use std::fs;
|
|
3
1
|
use std::path::Path;
|
|
4
|
-
use std::process::Command;
|
|
5
|
-
|
|
6
|
-
use serde_json::Value;
|
|
7
2
|
|
|
8
3
|
use crate::git;
|
|
9
4
|
use crate::models::{CheckDecision, Decision, NaomeError, TaskDecision};
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
|
|
6
|
+
mod checks;
|
|
7
|
+
mod idle;
|
|
8
|
+
mod json;
|
|
9
|
+
mod states;
|
|
10
|
+
|
|
11
|
+
use self::checks::{extract_known_actions, run_node_check};
|
|
12
|
+
use self::json::{json_bool, json_string, read_json};
|
|
13
|
+
use self::states::{
|
|
14
|
+
active_task_decision, classify_idle_diff, completed_task_decision, task_decision,
|
|
15
|
+
};
|
|
12
16
|
|
|
13
17
|
#[derive(Debug, Clone, Copy)]
|
|
14
18
|
pub struct EvaluationOptions {
|
|
@@ -31,175 +35,29 @@ impl EvaluationOptions {
|
|
|
31
35
|
|
|
32
36
|
pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Decision, NaomeError> {
|
|
33
37
|
let changed_paths = git::changed_paths(root)?;
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
Some(run_node_check(
|
|
37
|
-
root,
|
|
38
|
-
".naome/bin/check-harness-health.js",
|
|
39
|
-
&[],
|
|
40
|
-
)?)
|
|
41
|
-
} else {
|
|
42
|
-
None
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
if let Some(check) = &harness_health {
|
|
46
|
-
if !check.ok {
|
|
47
|
-
let mut decision = Decision::new(
|
|
48
|
-
"harness_unhealthy",
|
|
49
|
-
true,
|
|
50
|
-
"NAOME harness health failed.",
|
|
51
|
-
vec!["repair_harness", "review_harness_health"],
|
|
52
|
-
"Repair the machine-owned harness before feature work.",
|
|
53
|
-
);
|
|
54
|
-
decision.changed_paths = changed_paths;
|
|
55
|
-
decision.harness_health = harness_health;
|
|
56
|
-
decision.required_context = vec![
|
|
57
|
-
".naomeignore".to_string(),
|
|
58
|
-
"docs/naome/index.md".to_string(),
|
|
59
|
-
".naome/manifest.json".to_string(),
|
|
60
|
-
];
|
|
61
|
-
return Ok(decision);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let init_state = read_json(root, ".naome/init-state.json")?;
|
|
66
|
-
let upgrade_state = read_json(root, ".naome/upgrade-state.json")?;
|
|
67
|
-
let task_state = read_json(root, ".naome/task-state.json")?;
|
|
68
|
-
|
|
69
|
-
if json_string(&upgrade_state, "status").as_deref() == Some("needs_agent_upgrade") {
|
|
70
|
-
let mut decision = Decision::new(
|
|
71
|
-
"upgrade_required",
|
|
72
|
-
true,
|
|
73
|
-
"NAOME upgrade requires agent follow-up.",
|
|
74
|
-
vec!["run_upgrade_protocol"],
|
|
75
|
-
"Run the NAOME upgrade protocol before feature work.",
|
|
76
|
-
);
|
|
77
|
-
decision.changed_paths = changed_paths;
|
|
78
|
-
decision.harness_health = harness_health;
|
|
79
|
-
decision.required_context = vec![
|
|
80
|
-
"docs/naome/upgrade.md".to_string(),
|
|
81
|
-
".naome/upgrade-state.json".to_string(),
|
|
82
|
-
];
|
|
38
|
+
let harness_health = run_harness_health(root, options)?;
|
|
39
|
+
if let Some(decision) = harness_health_decision(&changed_paths, &harness_health) {
|
|
83
40
|
return Ok(decision);
|
|
84
41
|
}
|
|
85
42
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"first_run_required",
|
|
89
|
-
true,
|
|
90
|
-
"NAOME first-run intake has not been completed.",
|
|
91
|
-
vec!["run_first_run_protocol"],
|
|
92
|
-
"Run the NAOME first-run protocol before feature work.",
|
|
93
|
-
);
|
|
94
|
-
decision.changed_paths = changed_paths;
|
|
95
|
-
decision.harness_health = harness_health;
|
|
96
|
-
decision.required_context = vec![
|
|
97
|
-
"docs/naome/first-run.md".to_string(),
|
|
98
|
-
".naome/init-state.json".to_string(),
|
|
99
|
-
];
|
|
43
|
+
let task_state = read_json(root, ".naome/task-state.json")?;
|
|
44
|
+
if let Some(decision) = setup_block_decision(root, &changed_paths, &harness_health)? {
|
|
100
45
|
return Ok(decision);
|
|
101
46
|
}
|
|
102
47
|
|
|
103
48
|
let task_status = json_string(&task_state, "status").unwrap_or_else(|| "invalid".to_string());
|
|
104
49
|
let task = task_decision(&task_state, &task_status);
|
|
105
|
-
|
|
106
50
|
if task_status == "complete" {
|
|
107
|
-
let mut decision =
|
|
108
|
-
Decision::new(
|
|
109
|
-
"ready_for_task",
|
|
110
|
-
false,
|
|
111
|
-
"The last completed NAOME task has no open diff.",
|
|
112
|
-
vec!["create_task"],
|
|
113
|
-
"Task admission is clear; create the next task before feature work.",
|
|
114
|
-
)
|
|
115
|
-
} else if has_completed_task_owned_paths(task.as_ref(), &changed_paths) {
|
|
116
|
-
Decision::new(
|
|
117
|
-
"completed_task_unbaselined",
|
|
118
|
-
true,
|
|
119
|
-
"A completed NAOME task is verified and waiting for the next routing decision.",
|
|
120
|
-
vec![
|
|
121
|
-
"commit_task_baseline",
|
|
122
|
-
"review_task_diff",
|
|
123
|
-
"request_task_changes",
|
|
124
|
-
"cancel_task_changes",
|
|
125
|
-
],
|
|
126
|
-
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a valid completed task automatically.",
|
|
127
|
-
)
|
|
128
|
-
} else if harness_refresh_diff(root)?.is_some_and(|diff| diff.unrelated_paths.is_empty()) {
|
|
129
|
-
Decision::new(
|
|
130
|
-
"harness_repair_unbaselined",
|
|
131
|
-
true,
|
|
132
|
-
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
133
|
-
vec![
|
|
134
|
-
"commit_upgrade_baseline",
|
|
135
|
-
"review_diff_first",
|
|
136
|
-
"cancel_upgrade_baseline",
|
|
137
|
-
],
|
|
138
|
-
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
139
|
-
)
|
|
140
|
-
} else {
|
|
141
|
-
Decision::new(
|
|
142
|
-
"dirty_unowned_diff",
|
|
143
|
-
true,
|
|
144
|
-
"The completed NAOME task has been baselined, but unrelated user changes remain.",
|
|
145
|
-
vec!["review_unowned_diff"],
|
|
146
|
-
"Review the unrelated diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
147
|
-
)
|
|
148
|
-
};
|
|
149
|
-
decision.changed_paths = changed_paths;
|
|
51
|
+
let mut decision = completed_task_decision(root, changed_paths, task.as_ref())?;
|
|
150
52
|
decision.harness_health = harness_health;
|
|
151
53
|
decision.task = task;
|
|
152
|
-
decision.required_context = vec![
|
|
153
|
-
"docs/naome/execution.md".to_string(),
|
|
154
|
-
".naome/task-state.json".to_string(),
|
|
155
|
-
"docs/naome/agent-workflow.md".to_string(),
|
|
156
|
-
"docs/naome/testing.md".to_string(),
|
|
157
|
-
];
|
|
158
54
|
return Ok(decision);
|
|
159
55
|
}
|
|
160
56
|
|
|
161
57
|
if task_status != "idle" {
|
|
162
|
-
let
|
|
163
|
-
.as_ref()
|
|
164
|
-
.map(|task| task.allowed_paths.clone())
|
|
165
|
-
.unwrap_or_default();
|
|
166
|
-
let out_of_scope: Vec<String> = changed_paths
|
|
167
|
-
.iter()
|
|
168
|
-
.filter(|path| {
|
|
169
|
-
!is_control_state_path(path) && !paths::matches_any(path, &allowed_paths)
|
|
170
|
-
})
|
|
171
|
-
.cloned()
|
|
172
|
-
.collect();
|
|
173
|
-
|
|
174
|
-
let mut decision = if out_of_scope.is_empty() {
|
|
175
|
-
Decision::new(
|
|
176
|
-
"active_task_in_progress",
|
|
177
|
-
false,
|
|
178
|
-
"A NAOME task is active and the current diff is inside its declared scope.",
|
|
179
|
-
vec!["continue_task", "request_task_changes", "complete_task"],
|
|
180
|
-
"Continue the active task and keep proof current.",
|
|
181
|
-
)
|
|
182
|
-
} else {
|
|
183
|
-
Decision::new(
|
|
184
|
-
"active_task_blocked",
|
|
185
|
-
true,
|
|
186
|
-
"A NAOME task is active, but the current diff includes paths outside its declared scope.",
|
|
187
|
-
vec!["revise_task_scope", "revert_out_of_scope_diff", "request_human_review"],
|
|
188
|
-
"Resolve out-of-scope changes before completing this task.",
|
|
189
|
-
)
|
|
190
|
-
};
|
|
191
|
-
decision.changed_paths = if out_of_scope.is_empty() {
|
|
192
|
-
changed_paths
|
|
193
|
-
} else {
|
|
194
|
-
out_of_scope
|
|
195
|
-
};
|
|
58
|
+
let mut decision = active_task_decision(changed_paths, task.as_ref());
|
|
196
59
|
decision.harness_health = harness_health;
|
|
197
60
|
decision.task = task;
|
|
198
|
-
decision.required_context = vec![
|
|
199
|
-
"docs/naome/execution.md".to_string(),
|
|
200
|
-
".naome/task-state.json".to_string(),
|
|
201
|
-
"docs/naome/testing.md".to_string(),
|
|
202
|
-
];
|
|
203
61
|
return Ok(decision);
|
|
204
62
|
}
|
|
205
63
|
|
|
@@ -210,15 +68,91 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
210
68
|
return Ok(decision);
|
|
211
69
|
}
|
|
212
70
|
|
|
213
|
-
|
|
214
|
-
|
|
71
|
+
ready_or_admission_blocked_decision(root, options, harness_health, task)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn run_harness_health(
|
|
75
|
+
root: &Path,
|
|
76
|
+
options: EvaluationOptions,
|
|
77
|
+
) -> Result<Option<CheckDecision>, NaomeError> {
|
|
78
|
+
if options.run_external_checks {
|
|
79
|
+
Ok(Some(run_node_check(
|
|
215
80
|
root,
|
|
216
|
-
".naome/bin/check-
|
|
217
|
-
&[
|
|
218
|
-
)?)
|
|
81
|
+
".naome/bin/check-harness-health.js",
|
|
82
|
+
&[],
|
|
83
|
+
)?))
|
|
219
84
|
} else {
|
|
220
|
-
None
|
|
221
|
-
}
|
|
85
|
+
Ok(None)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn harness_health_decision(
|
|
90
|
+
changed_paths: &[String],
|
|
91
|
+
harness_health: &Option<CheckDecision>,
|
|
92
|
+
) -> Option<Decision> {
|
|
93
|
+
let check = harness_health.as_ref()?;
|
|
94
|
+
if check.ok {
|
|
95
|
+
return None;
|
|
96
|
+
}
|
|
97
|
+
let mut decision = Decision::new(
|
|
98
|
+
"harness_unhealthy",
|
|
99
|
+
true,
|
|
100
|
+
"NAOME harness health failed.",
|
|
101
|
+
vec!["repair_harness", "review_harness_health"],
|
|
102
|
+
"Repair the machine-owned harness before feature work.",
|
|
103
|
+
);
|
|
104
|
+
decision.changed_paths = changed_paths.to_vec();
|
|
105
|
+
decision.harness_health = harness_health.clone();
|
|
106
|
+
decision.required_context = vec![
|
|
107
|
+
".naomeignore".to_string(),
|
|
108
|
+
"docs/naome/index.md".to_string(),
|
|
109
|
+
".naome/manifest.json".to_string(),
|
|
110
|
+
];
|
|
111
|
+
Some(decision)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn setup_block_decision(
|
|
115
|
+
root: &Path,
|
|
116
|
+
changed_paths: &[String],
|
|
117
|
+
harness_health: &Option<CheckDecision>,
|
|
118
|
+
) -> Result<Option<Decision>, NaomeError> {
|
|
119
|
+
let init_state = read_json(root, ".naome/init-state.json")?;
|
|
120
|
+
let upgrade_state = read_json(root, ".naome/upgrade-state.json")?;
|
|
121
|
+
|
|
122
|
+
if json_string(&upgrade_state, "status").as_deref() == Some("needs_agent_upgrade") {
|
|
123
|
+
return Ok(Some(static_block_decision(
|
|
124
|
+
"upgrade_required",
|
|
125
|
+
"NAOME upgrade requires agent follow-up.",
|
|
126
|
+
vec!["run_upgrade_protocol"],
|
|
127
|
+
"Run the NAOME upgrade protocol before feature work.",
|
|
128
|
+
&["docs/naome/upgrade.md", ".naome/upgrade-state.json"],
|
|
129
|
+
changed_paths,
|
|
130
|
+
harness_health,
|
|
131
|
+
)));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if json_bool(&init_state, "initialized") != Some(true) {
|
|
135
|
+
return Ok(Some(static_block_decision(
|
|
136
|
+
"first_run_required",
|
|
137
|
+
"NAOME first-run intake has not been completed.",
|
|
138
|
+
vec!["run_first_run_protocol"],
|
|
139
|
+
"Run the NAOME first-run protocol before feature work.",
|
|
140
|
+
&["docs/naome/first-run.md", ".naome/init-state.json"],
|
|
141
|
+
changed_paths,
|
|
142
|
+
harness_health,
|
|
143
|
+
)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Ok(None)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn ready_or_admission_blocked_decision(
|
|
150
|
+
root: &Path,
|
|
151
|
+
options: EvaluationOptions,
|
|
152
|
+
harness_health: Option<CheckDecision>,
|
|
153
|
+
task: Option<TaskDecision>,
|
|
154
|
+
) -> Result<Decision, NaomeError> {
|
|
155
|
+
let task_admission = run_task_admission(root, options)?;
|
|
222
156
|
|
|
223
157
|
if let Some(check) = &task_admission {
|
|
224
158
|
if !check.ok {
|
|
@@ -259,6 +193,37 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
259
193
|
Ok(decision)
|
|
260
194
|
}
|
|
261
195
|
|
|
196
|
+
fn run_task_admission(
|
|
197
|
+
root: &Path,
|
|
198
|
+
options: EvaluationOptions,
|
|
199
|
+
) -> Result<Option<CheckDecision>, NaomeError> {
|
|
200
|
+
if options.run_external_checks {
|
|
201
|
+
Ok(Some(run_node_check(
|
|
202
|
+
root,
|
|
203
|
+
".naome/bin/check-task-state.js",
|
|
204
|
+
&["--admission"],
|
|
205
|
+
)?))
|
|
206
|
+
} else {
|
|
207
|
+
Ok(None)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fn static_block_decision(
|
|
212
|
+
state: &str,
|
|
213
|
+
summary: &str,
|
|
214
|
+
actions: Vec<&str>,
|
|
215
|
+
next_action: &str,
|
|
216
|
+
required_context: &[&str],
|
|
217
|
+
changed_paths: &[String],
|
|
218
|
+
harness_health: &Option<CheckDecision>,
|
|
219
|
+
) -> Decision {
|
|
220
|
+
let mut decision = Decision::new(state, true, summary, actions, next_action);
|
|
221
|
+
decision.changed_paths = changed_paths.to_vec();
|
|
222
|
+
decision.harness_health = harness_health.clone();
|
|
223
|
+
decision.required_context = required_context.iter().map(ToString::to_string).collect();
|
|
224
|
+
decision
|
|
225
|
+
}
|
|
226
|
+
|
|
262
227
|
pub fn format_decision(decision: &Decision, mode: &str) -> String {
|
|
263
228
|
let title = if mode == "next" {
|
|
264
229
|
"NAOME next"
|
|
@@ -314,190 +279,3 @@ fn is_intent_routed_baseline_state(state: &str) -> bool {
|
|
|
314
279
|
| "harness_repair_unbaselined"
|
|
315
280
|
)
|
|
316
281
|
}
|
|
317
|
-
|
|
318
|
-
fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decision, NaomeError> {
|
|
319
|
-
let manifest = read_json(root, ".naome/manifest.json").unwrap_or(Value::Null);
|
|
320
|
-
let machine_owned = string_array_at(&manifest, "machineOwned");
|
|
321
|
-
let project_owned = string_array_at(&manifest, "projectOwned");
|
|
322
|
-
|
|
323
|
-
let mut known_harness_paths = machine_owned.clone();
|
|
324
|
-
known_harness_paths.extend(project_owned);
|
|
325
|
-
|
|
326
|
-
let all_machine_owned = !machine_owned.is_empty()
|
|
327
|
-
&& changed_paths
|
|
328
|
-
.iter()
|
|
329
|
-
.all(|path| paths::matches_any(path, &machine_owned));
|
|
330
|
-
let all_harness_owned = !known_harness_paths.is_empty()
|
|
331
|
-
&& changed_paths
|
|
332
|
-
.iter()
|
|
333
|
-
.all(|path| paths::matches_any(path, &known_harness_paths));
|
|
334
|
-
|
|
335
|
-
let mut decision = if harness_refresh_diff(root)?
|
|
336
|
-
.is_some_and(|diff| diff.unrelated_paths.is_empty())
|
|
337
|
-
{
|
|
338
|
-
Decision::new(
|
|
339
|
-
"harness_repair_unbaselined",
|
|
340
|
-
true,
|
|
341
|
-
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
342
|
-
vec![
|
|
343
|
-
"commit_upgrade_baseline",
|
|
344
|
-
"review_diff_first",
|
|
345
|
-
"cancel_upgrade_baseline",
|
|
346
|
-
],
|
|
347
|
-
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
348
|
-
)
|
|
349
|
-
} else if all_machine_owned {
|
|
350
|
-
Decision::new(
|
|
351
|
-
"harness_repair_unbaselined",
|
|
352
|
-
true,
|
|
353
|
-
"Machine-owned NAOME harness files changed outside an active task.",
|
|
354
|
-
vec![
|
|
355
|
-
"commit_upgrade_baseline",
|
|
356
|
-
"review_diff_first",
|
|
357
|
-
"cancel_upgrade_baseline",
|
|
358
|
-
],
|
|
359
|
-
"Review and baseline the harness repair or cancel it before feature work.",
|
|
360
|
-
)
|
|
361
|
-
} else if all_harness_owned {
|
|
362
|
-
Decision::new(
|
|
363
|
-
"install_or_upgrade_unbaselined",
|
|
364
|
-
true,
|
|
365
|
-
"NAOME setup or upgrade files changed outside an active task.",
|
|
366
|
-
vec![
|
|
367
|
-
"commit_upgrade_baseline",
|
|
368
|
-
"review_diff_first",
|
|
369
|
-
"cancel_upgrade_baseline",
|
|
370
|
-
],
|
|
371
|
-
"Resolve the setup or upgrade diff before feature work.",
|
|
372
|
-
)
|
|
373
|
-
} else {
|
|
374
|
-
Decision::new(
|
|
375
|
-
"dirty_unowned_diff",
|
|
376
|
-
true,
|
|
377
|
-
"The repository has changes not owned by an active NAOME task.",
|
|
378
|
-
vec!["review_unowned_diff"],
|
|
379
|
-
"Review the unowned diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
380
|
-
)
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
decision.changed_paths = changed_paths;
|
|
384
|
-
decision.required_context = vec![
|
|
385
|
-
"docs/naome/execution.md".to_string(),
|
|
386
|
-
".naome/task-state.json".to_string(),
|
|
387
|
-
];
|
|
388
|
-
Ok(decision)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
fn task_decision(task_state: &Value, status: &str) -> Option<TaskDecision> {
|
|
392
|
-
let active_task = task_state.get("activeTask")?;
|
|
393
|
-
if active_task.is_null() {
|
|
394
|
-
return None;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
Some(TaskDecision {
|
|
398
|
-
id: json_string(active_task, "id"),
|
|
399
|
-
status: status.to_string(),
|
|
400
|
-
request: json_string(active_task, "request"),
|
|
401
|
-
allowed_paths: string_array_at(active_task, "allowedPaths"),
|
|
402
|
-
required_check_ids: string_array_at(active_task, "requiredCheckIds"),
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
fn is_control_state_path(path: &str) -> bool {
|
|
407
|
-
path == ".naome/task-state.json"
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
fn has_completed_task_owned_paths(task: Option<&TaskDecision>, changed_paths: &[String]) -> bool {
|
|
411
|
-
let Some(task) = task else {
|
|
412
|
-
return false;
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
changed_paths
|
|
416
|
-
.iter()
|
|
417
|
-
.any(|path| is_control_state_path(path) || paths::matches_any(path, &task.allowed_paths))
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
fn run_node_check(root: &Path, script: &str, args: &[&str]) -> Result<CheckDecision, NaomeError> {
|
|
421
|
-
let mut command_args = vec![script.to_string()];
|
|
422
|
-
command_args.extend(args.iter().map(ToString::to_string));
|
|
423
|
-
let node_bin = std::env::var_os("NAOME_NODE_BIN").unwrap_or_else(|| OsString::from("node"));
|
|
424
|
-
let output = Command::new(&node_bin)
|
|
425
|
-
.args(&command_args)
|
|
426
|
-
.current_dir(root)
|
|
427
|
-
.output()?;
|
|
428
|
-
let mut combined = String::new();
|
|
429
|
-
combined.push_str(&String::from_utf8_lossy(&output.stdout));
|
|
430
|
-
combined.push_str(&String::from_utf8_lossy(&output.stderr));
|
|
431
|
-
|
|
432
|
-
Ok(CheckDecision {
|
|
433
|
-
command: format!(
|
|
434
|
-
"{} {}{}",
|
|
435
|
-
node_bin.to_string_lossy(),
|
|
436
|
-
script,
|
|
437
|
-
if args.is_empty() {
|
|
438
|
-
String::new()
|
|
439
|
-
} else {
|
|
440
|
-
format!(" {}", args.join(" "))
|
|
441
|
-
}
|
|
442
|
-
),
|
|
443
|
-
exit_code: output.status.code(),
|
|
444
|
-
ok: output.status.success(),
|
|
445
|
-
output: combined.trim().to_string(),
|
|
446
|
-
})
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
fn read_json(root: &Path, relative_path: &str) -> Result<Value, NaomeError> {
|
|
450
|
-
let content = fs::read_to_string(root.join(relative_path))?;
|
|
451
|
-
Ok(serde_json::from_str(&content)?)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
fn json_bool(value: &Value, key: &str) -> Option<bool> {
|
|
455
|
-
value.get(key).and_then(Value::as_bool)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
fn json_string(value: &Value, key: &str) -> Option<String> {
|
|
459
|
-
value
|
|
460
|
-
.get(key)
|
|
461
|
-
.and_then(Value::as_str)
|
|
462
|
-
.map(ToString::to_string)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
fn string_array_at(value: &Value, key: &str) -> Vec<String> {
|
|
466
|
-
value
|
|
467
|
-
.get(key)
|
|
468
|
-
.and_then(Value::as_array)
|
|
469
|
-
.map(|values| {
|
|
470
|
-
values
|
|
471
|
-
.iter()
|
|
472
|
-
.filter_map(Value::as_str)
|
|
473
|
-
.map(ToString::to_string)
|
|
474
|
-
.collect()
|
|
475
|
-
})
|
|
476
|
-
.unwrap_or_default()
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
fn extract_known_actions(output: &str) -> Vec<&'static str> {
|
|
480
|
-
const KNOWN: [&str; 11] = [
|
|
481
|
-
"commit_task_baseline",
|
|
482
|
-
"review_task_diff",
|
|
483
|
-
"request_task_changes",
|
|
484
|
-
"cancel_task_changes",
|
|
485
|
-
"commit_upgrade_baseline",
|
|
486
|
-
"review_diff_first",
|
|
487
|
-
"cancel_upgrade_baseline",
|
|
488
|
-
"run_first_run_protocol",
|
|
489
|
-
"run_upgrade_protocol",
|
|
490
|
-
"review_unowned_diff",
|
|
491
|
-
"create_task",
|
|
492
|
-
];
|
|
493
|
-
|
|
494
|
-
let actions: Vec<&'static str> = KNOWN
|
|
495
|
-
.into_iter()
|
|
496
|
-
.filter(|action| output.contains(action))
|
|
497
|
-
.collect();
|
|
498
|
-
if actions.is_empty() {
|
|
499
|
-
vec!["review_task_admission"]
|
|
500
|
-
} else {
|
|
501
|
-
actions
|
|
502
|
-
}
|
|
503
|
-
}
|
|
@@ -22,11 +22,13 @@ pub const PROJECT_OWNED_PATHS: &[&str] = &[
|
|
|
22
22
|
".naome/upgrade-state.json",
|
|
23
23
|
".naome/verification.json",
|
|
24
24
|
".naome/repository-quality.json",
|
|
25
|
+
".naome/repository-structure.json",
|
|
25
26
|
".naome/repository-quality-baseline.json",
|
|
26
27
|
"docs/naome/architecture.md",
|
|
27
28
|
"docs/naome/decisions.md",
|
|
28
29
|
"docs/naome/repo-profile.md",
|
|
29
30
|
"docs/naome/repository-quality.md",
|
|
31
|
+
"docs/naome/repository-structure.md",
|
|
30
32
|
"docs/naome/security.md",
|
|
31
33
|
"docs/naome/testing.md",
|
|
32
34
|
];
|
|
@@ -16,13 +16,15 @@ mod workflow;
|
|
|
16
16
|
pub use decision::{evaluate_decision, format_decision, EvaluationOptions};
|
|
17
17
|
pub use harness_health::{validate_harness_health, HarnessHealthOptions};
|
|
18
18
|
pub use install_plan::{install_plan, InstallPlan};
|
|
19
|
+
pub use install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
19
20
|
pub use intent::{evaluate_intent, format_intent, IntentDecision, PromptEvidence};
|
|
20
21
|
pub use journal::{append_task_journal, TaskJournalEntry};
|
|
21
22
|
pub use models::{Decision, NaomeError};
|
|
22
23
|
pub use quality::{
|
|
23
|
-
check_repository_quality,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
check_repository_quality, explain_repository_structure, init_repository_quality,
|
|
25
|
+
plan_quality_cleanup, route_quality_cleanup, QualityCleanupPlan, QualityCleanupRoute,
|
|
26
|
+
QualityCleanupTask, QualityInitResult, QualityMode, QualityReport, QualitySummary,
|
|
27
|
+
QualityViolation, RepositoryQualityConfig, RepositoryStructureConfig, StructurePathExplanation,
|
|
26
28
|
};
|
|
27
29
|
pub use route::{evaluate_route, explain_route, ExplainDecision, RouteDecision, RouteOptions};
|
|
28
30
|
pub use task_state::{
|
|
@@ -13,7 +13,9 @@ fn matches_pattern(path: &str, pattern: &str) -> bool {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if let Some(prefix) = normalized_pattern.strip_suffix("/**") {
|
|
16
|
-
|
|
16
|
+
if !prefix.contains('*') {
|
|
17
|
+
return normalized_path == prefix || normalized_path.starts_with(&format!("{prefix}/"));
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
if !normalized_pattern.contains('*') {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
|
|
3
|
+
use crate::models::NaomeError;
|
|
4
|
+
|
|
5
|
+
pub(crate) struct RepoSignals<'a> {
|
|
6
|
+
paths: &'a [String],
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl<'a> RepoSignals<'a> {
|
|
10
|
+
pub(crate) fn new(paths: &'a [String]) -> Self {
|
|
11
|
+
Self { paths }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub(crate) fn has_manifest(&self, expected: &str) -> bool {
|
|
15
|
+
let nested_suffix = format!("/{expected}");
|
|
16
|
+
self.paths
|
|
17
|
+
.iter()
|
|
18
|
+
.any(|path| path == expected || path.ends_with(&nested_suffix))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub(crate) fn has_extension(&self, extensions: &[&str]) -> bool {
|
|
22
|
+
self.paths
|
|
23
|
+
.iter()
|
|
24
|
+
.any(|path| extensions.iter().any(|extension| path.ends_with(extension)))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub(crate) trait AdapterDescriptor {
|
|
29
|
+
fn id(&self) -> &'static str;
|
|
30
|
+
fn detects(&self, signals: &RepoSignals<'_>) -> bool;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub(crate) fn detected_ids<T: AdapterDescriptor>(paths: &[String], registry: &[T]) -> Vec<String> {
|
|
34
|
+
let signals = RepoSignals::new(paths);
|
|
35
|
+
registry
|
|
36
|
+
.iter()
|
|
37
|
+
.filter(|adapter| adapter.detects(&signals))
|
|
38
|
+
.map(|adapter| adapter.id().to_string())
|
|
39
|
+
.collect()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub(crate) fn find_adapter_by_id<'a, T: AdapterDescriptor>(
|
|
43
|
+
registry: &'a [T],
|
|
44
|
+
id: &str,
|
|
45
|
+
config_path: &str,
|
|
46
|
+
) -> Result<&'a T, NaomeError> {
|
|
47
|
+
registry
|
|
48
|
+
.iter()
|
|
49
|
+
.find(|adapter| adapter.id() == id)
|
|
50
|
+
.ok_or_else(|| {
|
|
51
|
+
NaomeError::new(format!(
|
|
52
|
+
"{config_path} enabledAdapters contains unknown adapter '{id}'."
|
|
53
|
+
))
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(crate) fn validate_ids<T: AdapterDescriptor>(
|
|
58
|
+
ids: &[String],
|
|
59
|
+
registry: &[T],
|
|
60
|
+
config_path: &str,
|
|
61
|
+
) -> Result<(), NaomeError> {
|
|
62
|
+
let mut seen = HashSet::new();
|
|
63
|
+
for adapter_id in ids {
|
|
64
|
+
if !seen.insert(adapter_id) {
|
|
65
|
+
return Err(NaomeError::new(format!(
|
|
66
|
+
"{config_path} enabledAdapters contains duplicate adapter '{adapter_id}'."
|
|
67
|
+
)));
|
|
68
|
+
}
|
|
69
|
+
find_adapter_by_id(registry, adapter_id, config_path)?;
|
|
70
|
+
}
|
|
71
|
+
Ok(())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub(crate) fn extend_unique(target: &mut Vec<String>, values: &[&str]) {
|
|
75
|
+
for value in values {
|
|
76
|
+
if !target.iter().any(|existing| existing == value) {
|
|
77
|
+
target.push((*value).to_string());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub(crate) fn detects_rust_project(signals: &RepoSignals<'_>) -> bool {
|
|
83
|
+
signals.has_manifest("Cargo.toml") || signals.has_extension(&[".rs"])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub(crate) fn detects_javascript_typescript_project(signals: &RepoSignals<'_>) -> bool {
|
|
87
|
+
signals.has_manifest("package.json")
|
|
88
|
+
|| signals.has_extension(&[".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"])
|
|
89
|
+
}
|