@lamentis/naome 1.0.1 → 1.1.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.
Files changed (34) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +8 -1
  3. package/bin/naome-node.js +121 -4
  4. package/bin/naome.js +198 -3
  5. package/crates/naome-cli/Cargo.toml +1 -1
  6. package/crates/naome-cli/src/main.rs +110 -13
  7. package/crates/naome-core/Cargo.toml +1 -1
  8. package/crates/naome-core/src/decision.rs +82 -11
  9. package/crates/naome-core/src/git.rs +12 -1
  10. package/crates/naome-core/src/harness_health.rs +3 -1
  11. package/crates/naome-core/src/install_plan.rs +8 -3
  12. package/crates/naome-core/src/intent.rs +914 -0
  13. package/crates/naome-core/src/journal.rs +169 -0
  14. package/crates/naome-core/src/lib.rs +10 -1
  15. package/crates/naome-core/src/models.rs +63 -4
  16. package/crates/naome-core/src/route.rs +1000 -0
  17. package/crates/naome-core/src/task_state.rs +326 -21
  18. package/crates/naome-core/tests/decision.rs +8 -6
  19. package/crates/naome-core/tests/install_plan.rs +12 -3
  20. package/crates/naome-core/tests/intent.rs +826 -0
  21. package/crates/naome-core/tests/route.rs +1108 -0
  22. package/crates/naome-core/tests/task_state.rs +63 -4
  23. package/native/darwin-arm64/naome +0 -0
  24. package/native/linux-x64/naome +0 -0
  25. package/package.json +1 -1
  26. package/templates/naome-root/.naome/bin/check-harness-health.js +7 -6
  27. package/templates/naome-root/.naome/bin/check-task-state.js +7 -6
  28. package/templates/naome-root/.naome/bin/naome.js +143 -13
  29. package/templates/naome-root/.naome/manifest.json +8 -7
  30. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  31. package/templates/naome-root/AGENTS.md +30 -5
  32. package/templates/naome-root/docs/naome/agent-workflow.md +45 -24
  33. package/templates/naome-root/docs/naome/execution.md +55 -51
  34. package/templates/naome-root/docs/naome/index.md +10 -3
@@ -1,5 +1,5 @@
1
- use std::fs;
2
1
  use std::ffi::OsString;
2
+ use std::fs;
3
3
  use std::path::Path;
4
4
  use std::process::Command;
5
5
 
@@ -8,6 +8,7 @@ use serde_json::Value;
8
8
  use crate::git;
9
9
  use crate::models::{CheckDecision, Decision, NaomeError, TaskDecision};
10
10
  use crate::paths;
11
+ use crate::task_state::harness_refresh_diff;
11
12
 
12
13
  #[derive(Debug, Clone, Copy)]
13
14
  pub struct EvaluationOptions {
@@ -111,18 +112,38 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
111
112
  vec!["create_task"],
112
113
  "Task admission is clear; create the next task before feature work.",
113
114
  )
114
- } else {
115
+ } else if has_completed_task_owned_paths(task.as_ref(), &changed_paths) {
115
116
  Decision::new(
116
117
  "completed_task_unbaselined",
117
118
  true,
118
- "A completed NAOME task still has an unbaselined diff.",
119
+ "A completed NAOME task is verified and waiting for the next routing decision.",
119
120
  vec![
120
121
  "commit_task_baseline",
121
122
  "review_task_diff",
122
123
  "request_task_changes",
123
124
  "cancel_task_changes",
124
125
  ],
125
- "Choose how to resolve the completed task diff before accepting new feature work.",
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.",
126
147
  )
127
148
  };
128
149
  decision.changed_paths = changed_paths;
@@ -131,6 +152,8 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
131
152
  decision.required_context = vec![
132
153
  "docs/naome/execution.md".to_string(),
133
154
  ".naome/task-state.json".to_string(),
155
+ "docs/naome/agent-workflow.md".to_string(),
156
+ "docs/naome/testing.md".to_string(),
134
157
  ];
135
158
  return Ok(decision);
136
159
  }
@@ -175,6 +198,7 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
175
198
  decision.required_context = vec![
176
199
  "docs/naome/execution.md".to_string(),
177
200
  ".naome/task-state.json".to_string(),
201
+ "docs/naome/testing.md".to_string(),
178
202
  ];
179
203
  return Ok(decision);
180
204
  }
@@ -226,6 +250,12 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
226
250
  decision.harness_health = harness_health;
227
251
  decision.task_admission = task_admission;
228
252
  decision.task = task;
253
+ decision.required_context = vec![
254
+ "docs/naome/agent-workflow.md".to_string(),
255
+ "docs/naome/testing.md".to_string(),
256
+ ".naome/verification.json".to_string(),
257
+ "docs/naome/architecture.md".to_string(),
258
+ ];
229
259
  Ok(decision)
230
260
  }
231
261
 
@@ -238,15 +268,20 @@ pub fn format_decision(decision: &Decision, mode: &str) -> String {
238
268
  let mut lines = vec![
239
269
  format!("{title}: {}", decision.state),
240
270
  format!("Blocked: {}", decision.blocked),
241
- format!("Summary: {}", decision.summary),
271
+ format!("Summary: {}", decision.user_message),
242
272
  format!("Next action: {}", decision.next_action),
243
273
  ];
244
274
 
245
- if !decision.allowed_actions.is_empty() {
275
+ if should_show_allowed_actions(decision) {
246
276
  lines.push(format!(
247
277
  "Allowed actions: {}",
248
278
  decision.allowed_actions.join(", ")
249
279
  ));
280
+ } else if is_intent_routed_baseline_state(&decision.state) {
281
+ lines.push(
282
+ "Intent routing: run `naome intent --prompt-file <path>` before presenting human choices."
283
+ .to_string(),
284
+ );
250
285
  }
251
286
 
252
287
  if !decision.changed_paths.is_empty() {
@@ -267,6 +302,19 @@ pub fn format_decision(decision: &Decision, mode: &str) -> String {
267
302
  lines.join("\n")
268
303
  }
269
304
 
305
+ fn should_show_allowed_actions(decision: &Decision) -> bool {
306
+ !decision.allowed_actions.is_empty() && !is_intent_routed_baseline_state(&decision.state)
307
+ }
308
+
309
+ fn is_intent_routed_baseline_state(state: &str) -> bool {
310
+ matches!(
311
+ state,
312
+ "completed_task_unbaselined"
313
+ | "install_or_upgrade_unbaselined"
314
+ | "harness_repair_unbaselined"
315
+ )
316
+ }
317
+
270
318
  fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decision, NaomeError> {
271
319
  let manifest = read_json(root, ".naome/manifest.json").unwrap_or(Value::Null);
272
320
  let machine_owned = string_array_at(&manifest, "machineOwned");
@@ -284,7 +332,21 @@ fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decisio
284
332
  .iter()
285
333
  .all(|path| paths::matches_any(path, &known_harness_paths));
286
334
 
287
- let mut decision = if all_machine_owned {
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 {
288
350
  Decision::new(
289
351
  "harness_repair_unbaselined",
290
352
  true,
@@ -313,8 +375,8 @@ fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decisio
313
375
  "dirty_unowned_diff",
314
376
  true,
315
377
  "The repository has changes not owned by an active NAOME task.",
316
- vec!["review_unowned_diff", "clear_or_commit_unowned_diff"],
317
- "Review, commit, or clear the unowned diff before accepting new feature work.",
378
+ vec!["review_unowned_diff"],
379
+ "Review the unowned diff, or route a new task so NAOME can isolate task work without touching it.",
318
380
  )
319
381
  };
320
382
 
@@ -345,6 +407,16 @@ fn is_control_state_path(path: &str) -> bool {
345
407
  path == ".naome/task-state.json"
346
408
  }
347
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
+
348
420
  fn run_node_check(root: &Path, script: &str, args: &[&str]) -> Result<CheckDecision, NaomeError> {
349
421
  let mut command_args = vec![script.to_string()];
350
422
  command_args.extend(args.iter().map(ToString::to_string));
@@ -405,7 +477,7 @@ fn string_array_at(value: &Value, key: &str) -> Vec<String> {
405
477
  }
406
478
 
407
479
  fn extract_known_actions(output: &str) -> Vec<&'static str> {
408
- const KNOWN: [&str; 12] = [
480
+ const KNOWN: [&str; 11] = [
409
481
  "commit_task_baseline",
410
482
  "review_task_diff",
411
483
  "request_task_changes",
@@ -416,7 +488,6 @@ fn extract_known_actions(output: &str) -> Vec<&'static str> {
416
488
  "run_first_run_protocol",
417
489
  "run_upgrade_protocol",
418
490
  "review_unowned_diff",
419
- "clear_or_commit_unowned_diff",
420
491
  "create_task",
421
492
  ];
422
493
 
@@ -38,7 +38,18 @@ pub fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
38
38
  }
39
39
 
40
40
  if status.contains('R') || status.contains('C') {
41
- let _ = entries.next();
41
+ let previous_path = entries.next();
42
+ if status.contains('R') {
43
+ if let Some(previous_path_bytes) = previous_path {
44
+ let previous_path =
45
+ String::from_utf8_lossy(previous_path_bytes).replace('\\', "/");
46
+ if !previous_path.is_empty()
47
+ && !paths::matches_any(&previous_path, &ignored_patterns)
48
+ {
49
+ paths.push(previous_path);
50
+ }
51
+ }
52
+ }
42
53
  }
43
54
  }
44
55
 
@@ -496,7 +496,9 @@ fn sha256_bytes(content: &[u8]) -> String {
496
496
  }
497
497
 
498
498
  fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
499
- if relative_path == HEALTH_CHECKER_RELATIVE_PATH || relative_path == TASK_STATE_CHECKER_RELATIVE_PATH {
499
+ if relative_path == HEALTH_CHECKER_RELATIVE_PATH
500
+ || relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
501
+ {
500
502
  return replace_expected_integrity_block(content);
501
503
  }
502
504
 
@@ -41,10 +41,11 @@ pub const LOCAL_ONLY_MACHINE_OWNED_PATHS: &[&str] = &[
41
41
  "docs/naome/upgrade.md",
42
42
  ];
43
43
 
44
- const LOCAL_NATIVE_BINARY_PATHS: &[&str] = &[
44
+ pub const LOCAL_NATIVE_BINARY_PATHS: &[&str] = &[
45
45
  ".naome/bin/naome-rust",
46
46
  ".naome/bin/naome-rust.exe",
47
47
  ".naome/archive",
48
+ ".naome/task-journal.jsonl",
48
49
  ];
49
50
 
50
51
  #[derive(Debug, Clone, Serialize, PartialEq, Eq)]
@@ -56,16 +57,19 @@ pub struct InstallPlan {
56
57
  pub project_owned: Vec<&'static str>,
57
58
  pub local_only_machine_owned: Vec<&'static str>,
58
59
  pub gitignore_entries: Vec<&'static str>,
60
+ pub git_exclude_entries: Vec<&'static str>,
59
61
  pub git_untrack_paths: Vec<&'static str>,
60
62
  }
61
63
 
62
64
  pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
63
- let mut gitignore_entries = vec![
65
+ let gitignore_entries = vec![".naome/task-journal.jsonl"];
66
+ let mut git_exclude_entries = vec![
64
67
  "# NAOME local machine-owned harness files.",
65
68
  ".naome/archive/",
66
69
  ".naome/bin/naome-rust*",
70
+ ".naome/task-journal.jsonl",
67
71
  ];
68
- gitignore_entries.extend_from_slice(LOCAL_ONLY_MACHINE_OWNED_PATHS);
72
+ git_exclude_entries.extend_from_slice(LOCAL_ONLY_MACHINE_OWNED_PATHS);
69
73
 
70
74
  let mut git_untrack_paths = LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec();
71
75
  git_untrack_paths.extend_from_slice(LOCAL_NATIVE_BINARY_PATHS);
@@ -77,6 +81,7 @@ pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
77
81
  project_owned: PROJECT_OWNED_PATHS.to_vec(),
78
82
  local_only_machine_owned: LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec(),
79
83
  gitignore_entries,
84
+ git_exclude_entries,
80
85
  git_untrack_paths,
81
86
  }
82
87
  }