@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
@@ -6,6 +6,7 @@ use std::process::Command;
6
6
  use serde_json::Value;
7
7
 
8
8
  use crate::harness_health::{validate_harness_health, HarnessHealthOptions};
9
+ use crate::install_plan::MACHINE_OWNED_PATHS;
9
10
  use crate::models::NaomeError;
10
11
 
11
12
  const CONTROL_STATE_PATH: &str = ".naome/task-state.json";
@@ -65,6 +66,30 @@ struct ChangedEntry {
65
66
  status: String,
66
67
  }
67
68
 
69
+ #[derive(Debug, Clone, PartialEq, Eq)]
70
+ pub struct CompletedTaskHarnessRefreshDiff {
71
+ pub harness_paths: Vec<String>,
72
+ pub task_paths: Vec<String>,
73
+ }
74
+
75
+ #[derive(Debug, Clone, PartialEq, Eq)]
76
+ pub struct HarnessRefreshWithUnrelatedDiff {
77
+ pub harness_paths: Vec<String>,
78
+ pub unrelated_paths: Vec<String>,
79
+ }
80
+
81
+ #[derive(Debug, Clone, PartialEq, Eq)]
82
+ pub struct HarnessRefreshDiff {
83
+ pub harness_paths: Vec<String>,
84
+ pub unrelated_paths: Vec<String>,
85
+ }
86
+
87
+ #[derive(Debug, Clone, PartialEq, Eq)]
88
+ pub struct CompletedTaskCommitDiff {
89
+ pub task_paths: Vec<String>,
90
+ pub unrelated_paths: Vec<String>,
91
+ }
92
+
68
93
  pub fn validate_task_state(
69
94
  root: &Path,
70
95
  options: TaskStateOptions,
@@ -175,6 +200,128 @@ pub fn validate_task_state(
175
200
  Ok(report)
176
201
  }
177
202
 
203
+ pub fn completed_task_commit_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
204
+ Ok(completed_task_commit_diff(root)?
205
+ .map(|diff| diff.task_paths)
206
+ .unwrap_or_default())
207
+ }
208
+
209
+ pub fn completed_task_commit_diff(
210
+ root: &Path,
211
+ ) -> Result<Option<CompletedTaskCommitDiff>, NaomeError> {
212
+ let mut read_errors = Vec::new();
213
+ let Some(task_state) = read_json(root, ".naome/task-state.json", &mut read_errors)? else {
214
+ return Ok(None);
215
+ };
216
+ if !read_errors.is_empty()
217
+ || task_state.get("status").and_then(Value::as_str) != Some("complete")
218
+ {
219
+ return Ok(None);
220
+ }
221
+ let Some(active_task) = task_state.get("activeTask") else {
222
+ return Ok(None);
223
+ };
224
+
225
+ let allowed_paths = string_array(active_task.get("allowedPaths")).unwrap_or_default();
226
+ let mut task_entries = Vec::new();
227
+ let mut unrelated_paths = Vec::new();
228
+ for entry in read_git_changed_entries(root)? {
229
+ if entry.path == CONTROL_STATE_PATH || matches_any_pattern(&entry.path, &allowed_paths) {
230
+ task_entries.push(entry);
231
+ } else {
232
+ unrelated_paths.push(entry.path);
233
+ }
234
+ }
235
+
236
+ if task_entries.is_empty() {
237
+ return Ok(None);
238
+ }
239
+
240
+ let mut errors = Vec::new();
241
+ validate_task_state_shape(&task_state, &mut errors);
242
+ validate_active_task(Some(active_task), &mut errors);
243
+ validate_pending_upgrade(&task_state, root, &mut errors)?;
244
+ validate_active_task_references(Some(active_task), root, &mut errors, Some("complete"))?;
245
+ if !task_state.get("blocker").is_some_and(Value::is_null) {
246
+ errors.push("complete task state must have blocker set to null.".to_string());
247
+ }
248
+ let check_ids = read_verification_check_ids(root, &mut errors)?;
249
+ validate_required_check_ids(active_task, &check_ids, &mut errors);
250
+ validate_complete_task_against_entries(
251
+ active_task,
252
+ root,
253
+ &check_ids,
254
+ &task_entries,
255
+ &mut errors,
256
+ )?;
257
+ if !errors.is_empty() {
258
+ return Ok(None);
259
+ }
260
+
261
+ let mut task_paths: Vec<String> = task_entries
262
+ .into_iter()
263
+ .map(|entry| entry.path)
264
+ .collect::<HashSet<_>>()
265
+ .into_iter()
266
+ .collect();
267
+ task_paths.sort();
268
+ unrelated_paths.sort();
269
+
270
+ Ok(Some(CompletedTaskCommitDiff {
271
+ task_paths,
272
+ unrelated_paths,
273
+ }))
274
+ }
275
+
276
+ pub fn harness_refresh_diff(root: &Path) -> Result<Option<HarnessRefreshDiff>, NaomeError> {
277
+ let changed_paths = read_git_changed_paths(root)?;
278
+ let has_repair_signal = changed_paths
279
+ .iter()
280
+ .any(|path| is_packaged_machine_owned_path(path) || is_repair_archive_path(path));
281
+ if !has_repair_signal {
282
+ return Ok(None);
283
+ }
284
+
285
+ let mut harness_paths = Vec::new();
286
+ let mut unrelated_paths = Vec::new();
287
+
288
+ for path in changed_paths {
289
+ if is_safe_harness_refresh_path(&path) {
290
+ harness_paths.push(path);
291
+ } else {
292
+ unrelated_paths.push(path);
293
+ }
294
+ }
295
+
296
+ if harness_paths.is_empty() {
297
+ return Ok(None);
298
+ }
299
+
300
+ harness_paths.sort();
301
+ unrelated_paths.sort();
302
+
303
+ Ok(Some(HarnessRefreshDiff {
304
+ harness_paths,
305
+ unrelated_paths,
306
+ }))
307
+ }
308
+
309
+ pub fn harness_refresh_with_unrelated_diff(
310
+ root: &Path,
311
+ ) -> Result<Option<HarnessRefreshWithUnrelatedDiff>, NaomeError> {
312
+ let Some(diff) = harness_refresh_diff(root)? else {
313
+ return Ok(None);
314
+ };
315
+ if diff.unrelated_paths.is_empty() {
316
+ return Ok(None);
317
+ }
318
+
319
+ Ok(Some(HarnessRefreshWithUnrelatedDiff {
320
+ harness_paths: diff.harness_paths,
321
+ unrelated_paths: diff.unrelated_paths,
322
+ }))
323
+ }
324
+
178
325
  fn validate_harness_health_gate(
179
326
  root: &Path,
180
327
  options: &TaskStateOptions,
@@ -677,7 +824,23 @@ fn validate_proof_evidence_covers_changed_paths(
677
824
  return Ok(());
678
825
  }
679
826
 
680
- let changed_paths = read_task_diff(active_task, root)?;
827
+ let entries = read_git_changed_entries(root)?;
828
+ validate_proof_evidence_covers_changed_entries(active_task, &entries, errors)
829
+ }
830
+
831
+ fn validate_proof_evidence_covers_changed_entries(
832
+ active_task: &Value,
833
+ entries: &[ChangedEntry],
834
+ errors: &mut Vec<String>,
835
+ ) -> Result<(), NaomeError> {
836
+ let Some(proofs) = active_task.get("proofResults").and_then(Value::as_array) else {
837
+ return Ok(());
838
+ };
839
+ if proofs.is_empty() {
840
+ return Ok(());
841
+ }
842
+
843
+ let changed_paths = task_diff_from_entries(active_task, entries);
681
844
  let evidence_paths: HashSet<String> = proofs
682
845
  .iter()
683
846
  .flat_map(|proof| {
@@ -945,7 +1108,16 @@ fn validate_changed_paths(
945
1108
  root: &Path,
946
1109
  errors: &mut Vec<String>,
947
1110
  ) -> Result<(), NaomeError> {
948
- let diff = read_task_diff(active_task, root)?;
1111
+ let entries = read_git_changed_entries(root)?;
1112
+ validate_changed_entries(active_task, &entries, errors)
1113
+ }
1114
+
1115
+ fn validate_changed_entries(
1116
+ active_task: &Value,
1117
+ entries: &[ChangedEntry],
1118
+ errors: &mut Vec<String>,
1119
+ ) -> Result<(), NaomeError> {
1120
+ let diff = task_diff_from_entries(active_task, entries);
949
1121
  if !diff.outside_paths.is_empty() {
950
1122
  errors.push(format!(
951
1123
  "Changed files outside allowedPaths: {}",
@@ -1000,10 +1172,14 @@ struct TaskDiff {
1000
1172
 
1001
1173
  fn read_task_diff(active_task: &Value, root: &Path) -> Result<TaskDiff, NaomeError> {
1002
1174
  let entries = read_git_changed_entries(root)?;
1175
+ Ok(task_diff_from_entries(active_task, &entries))
1176
+ }
1177
+
1178
+ fn task_diff_from_entries(active_task: &Value, entries: &[ChangedEntry]) -> TaskDiff {
1003
1179
  let allowed_paths = string_array(active_task.get("allowedPaths")).unwrap_or_default();
1004
1180
  let diff_paths: Vec<String> = entries
1005
- .into_iter()
1006
- .map(|entry| entry.path)
1181
+ .iter()
1182
+ .map(|entry| entry.path.clone())
1007
1183
  .filter(|path| path != CONTROL_STATE_PATH)
1008
1184
  .collect();
1009
1185
  let outside_paths = diff_paths
@@ -1012,10 +1188,10 @@ fn read_task_diff(active_task: &Value, root: &Path) -> Result<TaskDiff, NaomeErr
1012
1188
  .cloned()
1013
1189
  .collect();
1014
1190
 
1015
- Ok(TaskDiff {
1191
+ TaskDiff {
1016
1192
  diff_paths,
1017
1193
  outside_paths,
1018
- })
1194
+ }
1019
1195
  }
1020
1196
 
1021
1197
  fn validate_complete_task(
@@ -1051,9 +1227,8 @@ fn validate_complete_task(
1051
1227
 
1052
1228
  let check_ids = read_verification_check_ids(root, errors)?;
1053
1229
  validate_required_check_ids(active_task, &check_ids, errors);
1054
- validate_proof_results(active_task, &check_ids, root, errors)?;
1055
- validate_changed_paths(active_task, root, errors)?;
1056
- validate_proof_evidence_covers_changed_paths(Some(active_task), root, errors)?;
1230
+ let entries = read_git_changed_entries(root)?;
1231
+ validate_complete_task_against_entries(active_task, root, &check_ids, &entries, errors)?;
1057
1232
 
1058
1233
  if errors.len() == error_start {
1059
1234
  add_completed_task_diff_notice(root, notices)?;
@@ -1062,6 +1237,19 @@ fn validate_complete_task(
1062
1237
  Ok(())
1063
1238
  }
1064
1239
 
1240
+ fn validate_complete_task_against_entries(
1241
+ active_task: &Value,
1242
+ root: &Path,
1243
+ check_ids: &HashSet<String>,
1244
+ entries: &[ChangedEntry],
1245
+ errors: &mut Vec<String>,
1246
+ ) -> Result<(), NaomeError> {
1247
+ validate_proof_results(active_task, check_ids, root, errors)?;
1248
+ validate_changed_entries(active_task, entries, errors)?;
1249
+ validate_proof_evidence_covers_changed_entries(active_task, entries, errors)?;
1250
+ Ok(())
1251
+ }
1252
+
1065
1253
  fn validate_progress(
1066
1254
  task_state: &Value,
1067
1255
  root: &Path,
@@ -1268,7 +1456,11 @@ fn validate_commit_gate(
1268
1456
  errors: &mut Vec<String>,
1269
1457
  notices: &mut Vec<String>,
1270
1458
  ) -> Result<(), NaomeError> {
1271
- let changed_paths = read_git_changed_paths(root)?;
1459
+ let staged_entries = read_git_staged_changed_entries(root)?;
1460
+ let changed_paths: Vec<String> = staged_entries
1461
+ .iter()
1462
+ .map(|entry| entry.path.clone())
1463
+ .collect();
1272
1464
  if changed_paths.is_empty() {
1273
1465
  return Ok(());
1274
1466
  }
@@ -1277,16 +1469,35 @@ fn validate_commit_gate(
1277
1469
  .get("status")
1278
1470
  .and_then(Value::as_str)
1279
1471
  .unwrap_or("invalid");
1472
+ if status == "complete" && completed_task_harness_refresh_diff(root)?.is_some() {
1473
+ if is_safe_harness_refresh_diff(&changed_paths) {
1474
+ return Ok(());
1475
+ }
1476
+ }
1477
+
1280
1478
  if status == "complete" {
1281
1479
  validate_active_task(task_state.get("activeTask"), errors);
1282
1480
  validate_active_task_references(task_state.get("activeTask"), root, errors, Some(status))?;
1283
- validate_complete_task(
1284
- task_state.get("activeTask"),
1285
- task_state.get("blocker"),
1286
- root,
1287
- errors,
1288
- notices,
1289
- )?;
1481
+ if !task_state.get("blocker").is_some_and(Value::is_null) {
1482
+ errors.push("complete task state must have blocker set to null.".to_string());
1483
+ }
1484
+ if let Some(active_task) = task_state.get("activeTask") {
1485
+ let check_ids = read_verification_check_ids(root, errors)?;
1486
+ validate_required_check_ids(active_task, &check_ids, errors);
1487
+ validate_complete_task_against_entries(
1488
+ active_task,
1489
+ root,
1490
+ &check_ids,
1491
+ &staged_entries,
1492
+ errors,
1493
+ )?;
1494
+ if errors.is_empty() {
1495
+ notices.push(format!(
1496
+ "Commit gate accepted task-owned staged paths: {}.",
1497
+ changed_paths.join(", ")
1498
+ ));
1499
+ }
1500
+ }
1290
1501
  return Ok(());
1291
1502
  }
1292
1503
 
@@ -1341,15 +1552,15 @@ fn format_dirty_diff_admission_blocker(
1341
1552
  );
1342
1553
 
1343
1554
  if is_harness_repair_diff(root, changed_paths)? {
1344
- return Ok(format!("{prefix} These look like completed Harness Repair changes. Ask the user to choose exactly one: commit_repair_baseline, review_repair_diff, cancel_repair_baseline. Do not commit without explicit user selection."));
1555
+ return Ok(format!("{prefix} These look like completed Harness Repair changes. Run NAOME intent for the next natural-language request before deciding whether to baseline, review, or cancel the repair diff."));
1345
1556
  }
1346
1557
 
1347
1558
  if is_completed_task_diff(task_state, changed_paths) {
1348
- return Ok(format!("{prefix} These look like completed task changes. Ask the user to choose exactly one: commit_task_baseline, review_task_diff, request_task_changes, cancel_task_changes. Do not commit without explicit user selection."));
1559
+ return Ok(format!("{prefix} These look like completed task changes. Run NAOME intent for the next natural-language request; deterministic policy can baseline a valid completed task before creating the next task."));
1349
1560
  }
1350
1561
 
1351
1562
  if is_naome_baseline_diff(changed_paths) {
1352
- return Ok(format!("{prefix} These look like completed NAOME install or upgrade changes. Ask the user to choose exactly one: commit_upgrade_baseline, review_diff_first, cancel_upgrade_baseline. Do not commit without explicit user selection."));
1563
+ return Ok(format!("{prefix} These look like completed NAOME install or upgrade changes. Run NAOME intent for the next natural-language request; deterministic policy can baseline setup before creating the next task."));
1353
1564
  }
1354
1565
 
1355
1566
  Ok(format!("{prefix} Ask the user to choose exactly one: review_task_diff, request_task_changes, cancel_task_changes. Do not start new feature work or commit without explicit user selection."))
@@ -1379,6 +1590,21 @@ fn is_repair_support_path(path: &str) -> bool {
1379
1590
  || is_repair_archive_path(path)
1380
1591
  }
1381
1592
 
1593
+ fn is_packaged_machine_owned_path(path: &str) -> bool {
1594
+ MACHINE_OWNED_PATHS.iter().any(|owned| *owned == path)
1595
+ }
1596
+
1597
+ fn is_safe_harness_refresh_path(path: &str) -> bool {
1598
+ is_packaged_machine_owned_path(path) || is_repair_support_path(path)
1599
+ }
1600
+
1601
+ fn is_safe_harness_refresh_diff(changed_paths: &[String]) -> bool {
1602
+ !changed_paths.is_empty()
1603
+ && changed_paths
1604
+ .iter()
1605
+ .all(|path| is_safe_harness_refresh_path(path))
1606
+ }
1607
+
1382
1608
  fn is_repair_archive_path(path: &str) -> bool {
1383
1609
  path.starts_with(".naome/archive/repair-")
1384
1610
  }
@@ -1500,10 +1726,74 @@ fn add_completed_task_diff_notice(
1500
1726
  return Ok(());
1501
1727
  }
1502
1728
 
1503
- notices.push(format!("Next task admission is blocked until the completed task diff is resolved. Changed paths: {}. Ask the user to choose exactly one: commit_task_baseline, review_task_diff, request_task_changes, cancel_task_changes. Do not start new feature work until one option is resolved.", changed_paths.join(", ")));
1729
+ notices.push(format!("Task is complete and verified. Changed paths: {}. NAOME intent can baseline it automatically before the next distinct task; only surface human choices when intent blocks or the user explicitly asks to review, revise, cancel, or commit.", changed_paths.join(", ")));
1504
1730
  Ok(())
1505
1731
  }
1506
1732
 
1733
+ pub fn completed_task_harness_refresh_diff(
1734
+ root: &Path,
1735
+ ) -> Result<Option<CompletedTaskHarnessRefreshDiff>, NaomeError> {
1736
+ let mut read_errors = Vec::new();
1737
+ let Some(task_state) = read_json(root, ".naome/task-state.json", &mut read_errors)? else {
1738
+ return Ok(None);
1739
+ };
1740
+ if !read_errors.is_empty() {
1741
+ return Ok(None);
1742
+ }
1743
+ if task_state.get("status").and_then(Value::as_str) != Some("complete") {
1744
+ return Ok(None);
1745
+ }
1746
+ let Some(active_task) = task_state.get("activeTask") else {
1747
+ return Ok(None);
1748
+ };
1749
+
1750
+ let allowed_paths = string_array(active_task.get("allowedPaths")).unwrap_or_default();
1751
+ let mut harness_paths = Vec::new();
1752
+ let mut task_paths = Vec::new();
1753
+ let mut other_paths = Vec::new();
1754
+
1755
+ for path in read_git_changed_paths(root)? {
1756
+ if path == CONTROL_STATE_PATH {
1757
+ continue;
1758
+ }
1759
+ if matches_any_pattern(&path, &allowed_paths) {
1760
+ task_paths.push(path);
1761
+ } else if is_safe_harness_refresh_path(&path) {
1762
+ harness_paths.push(path);
1763
+ } else {
1764
+ other_paths.push(path);
1765
+ }
1766
+ }
1767
+
1768
+ if task_paths.is_empty() || harness_paths.is_empty() || !other_paths.is_empty() {
1769
+ return Ok(None);
1770
+ }
1771
+
1772
+ let report = validate_task_state(
1773
+ root,
1774
+ TaskStateOptions {
1775
+ mode: TaskStateMode::State,
1776
+ harness_health: None,
1777
+ },
1778
+ )?;
1779
+ let allowed_scope_error = format!(
1780
+ "Changed files outside allowedPaths: {}",
1781
+ harness_paths.join(", ")
1782
+ );
1783
+ if report
1784
+ .errors
1785
+ .iter()
1786
+ .all(|error| error == &allowed_scope_error)
1787
+ {
1788
+ Ok(Some(CompletedTaskHarnessRefreshDiff {
1789
+ harness_paths,
1790
+ task_paths,
1791
+ }))
1792
+ } else {
1793
+ Ok(None)
1794
+ }
1795
+ }
1796
+
1507
1797
  fn read_git_changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
1508
1798
  Ok(read_git_changed_entries(root)?
1509
1799
  .into_iter()
@@ -1511,6 +1801,21 @@ fn read_git_changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
1511
1801
  .collect())
1512
1802
  }
1513
1803
 
1804
+ fn read_git_staged_changed_entries(root: &Path) -> Result<Vec<ChangedEntry>, NaomeError> {
1805
+ let output = Command::new("git")
1806
+ .args(["diff", "--name-status", "--cached", "-z"])
1807
+ .current_dir(root)
1808
+ .output()?;
1809
+ if !output.status.success() {
1810
+ return Err(NaomeError::new(format!(
1811
+ "git diff --name-status --cached -z failed: {}",
1812
+ command_output(&output)
1813
+ )));
1814
+ }
1815
+
1816
+ Ok(parse_name_status_output(&output.stdout))
1817
+ }
1818
+
1514
1819
  fn read_git_changed_entries(root: &Path) -> Result<Vec<ChangedEntry>, NaomeError> {
1515
1820
  let git_check = run_git(root, ["rev-parse", "--is-inside-work-tree"])?;
1516
1821
  if !git_check.status.success() {
@@ -1,10 +1,10 @@
1
1
  use std::fs;
2
- use std::sync::Mutex;
3
2
  use std::path::{Path, PathBuf};
4
3
  use std::process::Command;
4
+ use std::sync::Mutex;
5
5
  use std::time::{SystemTime, UNIX_EPOCH};
6
6
 
7
- use naome_core::{evaluate_decision, EvaluationOptions};
7
+ use naome_core::{evaluate_decision, format_decision, EvaluationOptions};
8
8
  use serde_json::json;
9
9
 
10
10
  static ENV_LOCK: Mutex<()> = Mutex::new(());
@@ -62,6 +62,11 @@ fn completed_task_diff_returns_baseline_choices() {
62
62
  .allowed_actions
63
63
  .contains(&"request_task_changes".to_string()));
64
64
  assert_eq!(decision.changed_paths, vec!["README.md"]);
65
+
66
+ let human_output = format_decision(&decision, "next");
67
+ assert!(human_output.contains("Intent routing"));
68
+ assert!(!human_output.contains("Allowed actions:"));
69
+ assert!(!human_output.contains("commit_task_baseline"));
65
70
  }
66
71
 
67
72
  #[test]
@@ -82,10 +87,7 @@ fn idle_unowned_diff_blocks_new_feature_work() {
82
87
  let decision = evaluate_decision(repo.path(), EvaluationOptions::offline()).unwrap();
83
88
 
84
89
  assert_eq!(decision.state, "dirty_unowned_diff");
85
- assert_eq!(
86
- decision.allowed_actions,
87
- vec!["review_unowned_diff", "clear_or_commit_unowned_diff"]
88
- );
90
+ assert_eq!(decision.allowed_actions, vec!["review_unowned_diff"]);
89
91
  assert_eq!(decision.changed_paths, vec!["README.md"]);
90
92
  }
91
93
 
@@ -21,15 +21,24 @@ fn install_plan_marks_machine_docs_and_bins_local_only() {
21
21
  }
22
22
 
23
23
  #[test]
24
- fn install_plan_includes_gitignore_and_untrack_policy() {
24
+ fn install_plan_includes_git_exclude_and_untrack_policy() {
25
25
  let plan = install_plan("1.0.0");
26
26
 
27
- assert!(plan.gitignore_entries.contains(&".naome/archive/"));
28
- assert!(plan.gitignore_entries.contains(&".naome/bin/naome-rust*"));
29
27
  assert!(plan
30
28
  .gitignore_entries
29
+ .contains(&".naome/task-journal.jsonl"));
30
+ assert!(plan.git_exclude_entries.contains(&".naome/archive/"));
31
+ assert!(plan.git_exclude_entries.contains(&".naome/bin/naome-rust*"));
32
+ assert!(plan
33
+ .git_exclude_entries
34
+ .contains(&".naome/task-journal.jsonl"));
35
+ assert!(plan
36
+ .git_exclude_entries
31
37
  .contains(&".naome/bin/check-harness-health.js"));
32
38
  assert!(plan.git_untrack_paths.contains(&".naome/archive"));
33
39
  assert!(plan.git_untrack_paths.contains(&".naome/bin/naome-rust"));
40
+ assert!(plan
41
+ .git_untrack_paths
42
+ .contains(&".naome/task-journal.jsonl"));
34
43
  assert!(plan.git_untrack_paths.contains(&"docs/naome/first-run.md"));
35
44
  }