@lamentis/naome 1.4.2 → 1.4.3

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.
@@ -1,14 +1,21 @@
1
1
  use std::path::Path;
2
2
 
3
+ mod agent_snapshot;
3
4
  mod can_edit;
4
5
  mod check_run;
6
+ mod commit_preflight;
5
7
  mod common;
8
+ mod compact_proof;
6
9
  mod complete;
7
10
  mod loop_control;
11
+ mod path_policy;
12
+ mod planner;
13
+ mod preflight;
8
14
  mod readiness;
9
15
  mod record;
10
16
  mod repair;
11
17
  mod scope_request;
18
+ mod scope_suggestions;
12
19
  mod timeline;
13
20
 
14
21
  use naome_core::{
@@ -23,13 +30,18 @@ pub fn run_task_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std:
23
30
  Some("migrate-ledger") => migrate_ledger(root, args),
24
31
  Some("status") => task_status(root, args),
25
32
  Some("proof-plan") => proof_plan(root, args),
33
+ Some("agent-snapshot") => agent_snapshot::agent_snapshot(root, args),
34
+ Some("preflight") => preflight::preflight(root, args),
26
35
  Some("can-edit") => can_edit::can_edit(root, args),
27
36
  Some("run-check") => check_run::run_check_command(root, args),
28
37
  Some("can-transition") => can_transition(root, args),
29
38
  Some("repair") => repair::repair_preview(root, args),
30
39
  Some("record-proof") => record::record_proof(root, args),
40
+ Some("compact-proof") => compact_proof::compact_proof(root, args),
31
41
  Some("complete") => complete::complete_task(root, args),
32
42
  Some("loop") => loop_control::task_loop(root, args),
43
+ Some("commit-preflight") => commit_preflight::commit_preflight(root, args),
44
+ Some("scope-suggestions") => scope_suggestions::scope_suggestions(root, args),
33
45
  Some("request-scope") => scope_request::request_scope(root, args),
34
46
  Some("can-commit") => readiness::can_commit(root, args),
35
47
  Some("timeline") => timeline::timeline(root, args),
@@ -0,0 +1,290 @@
1
+ use std::fs;
2
+ use std::process::Command;
3
+
4
+ use serde_json::{json, Value};
5
+
6
+ mod task_cli_support;
7
+
8
+ use task_cli_support::{
9
+ active_task, fixture_root, git, init_git, run_json, task_state, task_state_with_active_task,
10
+ write_fixture_file, write_json,
11
+ };
12
+
13
+ #[test]
14
+ fn agent_snapshot_reports_missing_proof_and_safe_commands() {
15
+ let root = fixture_root(task_state());
16
+ init_git(&root);
17
+ write_fixture_file(&root, "README.md", "changed\n");
18
+
19
+ let snapshot = run_json(&root, ["task", "agent-snapshot", "--json"]);
20
+
21
+ assert_eq!(snapshot["schema"], "naome.task.agent-snapshot.v1");
22
+ assert_eq!(snapshot["proof"]["missingChecks"], json!(["diff-check"]));
23
+ assert_eq!(snapshot["nextAction"]["type"], "run_checks");
24
+ assert_eq!(snapshot["checks"]["safeToRun"][0]["checkId"], "diff-check");
25
+ }
26
+
27
+ #[test]
28
+ fn preflight_reports_path_policy_and_check_plan() {
29
+ let root = fixture_root(task_state_with_active_task(active_task(json!({
30
+ "allowedPaths": ["scripts/*.js"],
31
+ "proofResults": []
32
+ }))));
33
+ fs::write(
34
+ root.join(".naomeignore"),
35
+ ".naome/archive/\n.naome/tasks/\ndist/\n",
36
+ )
37
+ .unwrap();
38
+ init_git(&root);
39
+
40
+ let allowed = run_json(
41
+ &root,
42
+ ["task", "preflight", "--path", "scripts/check.js", "--json"],
43
+ );
44
+ assert_eq!(allowed["paths"][0]["editable"], true);
45
+ assert_eq!(allowed["paths"][0]["risk"], "medium");
46
+
47
+ let ignored = run_json(
48
+ &root,
49
+ ["task", "preflight", "--path", "dist/bundle.js", "--json"],
50
+ );
51
+ assert_eq!(ignored["paths"][0]["editable"], false);
52
+ assert_eq!(ignored["findings"][0]["id"], "task.preflight.ignored_path");
53
+
54
+ let traversal = run_json(
55
+ &root,
56
+ ["task", "preflight", "--path", "..\\outside.js", "--json"],
57
+ );
58
+ assert_eq!(traversal["paths"][0]["editable"], false);
59
+ assert_eq!(traversal["findings"][0]["id"], "task.preflight.unsafe_path");
60
+
61
+ let control = run_json(
62
+ &root,
63
+ ["task", "preflight", "--path", ".naomeignore", "--json"],
64
+ );
65
+ assert_eq!(control["paths"][0]["editable"], false);
66
+ assert_eq!(control["findings"][0]["id"], "task.preflight.control_path");
67
+ }
68
+
69
+ #[test]
70
+ fn preflight_blocks_when_no_target_paths_are_selected() {
71
+ let root = fixture_root(task_state());
72
+ init_git(&root);
73
+
74
+ let preflight = run_json(&root, ["task", "preflight", "--json"]);
75
+
76
+ assert_eq!(preflight["schema"], "naome.task.preflight.v1");
77
+ assert_eq!(
78
+ preflight["findings"][0]["id"],
79
+ "task.preflight.missing_target_paths"
80
+ );
81
+ assert_eq!(preflight["nextAction"]["type"], "blocked");
82
+ assert_eq!(preflight["nextAction"]["safeToExecute"], false);
83
+ }
84
+
85
+ #[test]
86
+ fn preflight_from_changed_allows_clean_changed_set() {
87
+ let root = fixture_root(task_state());
88
+ init_git(&root);
89
+ git(&root, ["add", ".naome/task-state.json"]);
90
+ git(&root, ["commit", "-m", "record admission head"]);
91
+
92
+ let preflight = run_json(&root, ["task", "preflight", "--from-changed", "--json"]);
93
+
94
+ assert_eq!(preflight["schema"], "naome.task.preflight.v1");
95
+ assert!(preflight["findings"].as_array().unwrap().is_empty());
96
+ assert_eq!(preflight["paths"], json!([]));
97
+ assert_eq!(preflight["nextAction"]["type"], "edit");
98
+ }
99
+
100
+ #[test]
101
+ fn commit_preflight_blocks_missing_proof() {
102
+ let root = fixture_root(task_state());
103
+ init_git(&root);
104
+ write_fixture_file(&root, "README.md", "changed\n");
105
+
106
+ let preflight = run_json(&root, ["task", "commit-preflight", "--json"]);
107
+
108
+ assert_eq!(preflight["schema"], "naome.task.commit-preflight.v1");
109
+ assert_eq!(preflight["wouldPass"], false);
110
+ assert_eq!(
111
+ preflight["blockingFindings"][0]["id"],
112
+ "task.proof.missing_check"
113
+ );
114
+ assert_eq!(preflight["nextAction"]["type"], "rerun_checks");
115
+ assert_eq!(preflight["nextAction"]["checkIds"], json!(["diff-check"]));
116
+ }
117
+
118
+ #[test]
119
+ fn agent_snapshot_prefers_commit_ready_over_more_editing() {
120
+ let root = fixture_root(task_state());
121
+ init_git(&root);
122
+ write_fixture_file(&root, "README.md", "changed\n");
123
+
124
+ let checked = run_json(
125
+ &root,
126
+ [
127
+ "task",
128
+ "run-check",
129
+ "--check",
130
+ "diff-check",
131
+ "--record-proof",
132
+ "--json",
133
+ ],
134
+ );
135
+ assert_eq!(checked["recordedProof"], true);
136
+
137
+ let snapshot = run_json(&root, ["task", "agent-snapshot", "--json"]);
138
+
139
+ assert_eq!(snapshot["commit"]["canCommit"], true);
140
+ assert_eq!(snapshot["nextAction"]["type"], "commit_ready");
141
+ assert_eq!(snapshot["nextAction"]["safeToExecute"], false);
142
+ }
143
+
144
+ #[test]
145
+ fn commit_preflight_surfaces_transition_blocker_action_and_exit_code() {
146
+ let root = fixture_root(task_state_with_active_task(active_task(json!({
147
+ "requiredCheckIds": [],
148
+ "humanReview": {
149
+ "required": true,
150
+ "approved": false,
151
+ "reason": "Release owner approval required."
152
+ },
153
+ "proofResults": []
154
+ }))));
155
+ init_git(&root);
156
+ write_fixture_file(&root, "README.md", "changed\n");
157
+
158
+ let preflight = run_json(&root, ["task", "commit-preflight", "--json"]);
159
+
160
+ assert_eq!(preflight["wouldPass"], false);
161
+ assert_eq!(
162
+ preflight["blockingFindings"][0]["id"],
163
+ "task.transition.human_review_required"
164
+ );
165
+ assert_eq!(preflight["nextAction"]["type"], "blocked");
166
+ assert_eq!(preflight["nextAction"]["safeToExecute"], false);
167
+
168
+ let output = Command::new(env!("CARGO_BIN_EXE_naome"))
169
+ .args(["task", "commit-preflight", "--json", "--exit-code"])
170
+ .current_dir(root)
171
+ .output()
172
+ .unwrap();
173
+
174
+ assert_eq!(output.status.code(), Some(1));
175
+ }
176
+
177
+ #[test]
178
+ fn compact_proof_dry_run_and_write_remove_verbose_fields() {
179
+ let root = fixture_root(task_state());
180
+ let path = root.join(".naome/task-state.json");
181
+ let mut state: Value = serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
182
+ state["activeTask"]["proofPathSets"] = json!({ "current": ["README.md"] });
183
+ state["activeTask"]["proofBatches"] = json!([{
184
+ "id": "batch",
185
+ "checkedAt": "2026-05-14T00:00:00.000Z",
186
+ "evidencePathSet": "current",
187
+ "proofs": [{
188
+ "checkId": "diff-check",
189
+ "command": "git diff --check",
190
+ "cwd": ".",
191
+ "exitCode": 0,
192
+ "evidenceFingerprint": "fnv64:test",
193
+ "stdoutSummary": "large",
194
+ "stderrSummary": "large",
195
+ "durationMs": 42
196
+ }]
197
+ }]);
198
+ write_json(&root, ".naome/task-state.json", &state);
199
+
200
+ let dry = run_json(&root, ["task", "compact-proof", "--dry-run", "--json"]);
201
+ assert_eq!(dry["dryRun"], true);
202
+ assert_eq!(dry["removedVerboseFields"], 3);
203
+ let unchanged = fs::read_to_string(&path).unwrap();
204
+ assert!(unchanged.contains("stdoutSummary"));
205
+
206
+ let compacted = run_json(&root, ["task", "compact-proof", "--json"]);
207
+ assert_eq!(compacted["compacted"], true);
208
+ let changed = fs::read_to_string(&path).unwrap();
209
+ assert!(!changed.contains("stdoutSummary"));
210
+ assert!(changed.contains("evidenceFingerprint"));
211
+ }
212
+
213
+ #[test]
214
+ fn record_proof_rejects_task_and_path_mismatched_receipts() {
215
+ let root = fixture_root(task_state_with_active_task(active_task(json!({
216
+ "allowedPaths": ["README.md", "docs/**"],
217
+ "proofResults": []
218
+ }))));
219
+ init_git(&root);
220
+ write_fixture_file(&root, "README.md", "changed\n");
221
+
222
+ let checked = run_json(
223
+ &root,
224
+ ["task", "run-check", "--check", "diff-check", "--json"],
225
+ );
226
+ assert_eq!(checked["exitCode"], 0);
227
+
228
+ let mut state: Value =
229
+ serde_json::from_str(&fs::read_to_string(root.join(".naome/task-state.json")).unwrap())
230
+ .unwrap();
231
+ state["activeTask"]["id"] = json!("different-task");
232
+ write_json(&root, ".naome/task-state.json", &state);
233
+ let task_mismatch = run_json(
234
+ &root,
235
+ ["task", "record-proof", "--from-proof-plan", "--json"],
236
+ );
237
+ assert_eq!(
238
+ task_mismatch["findings"][0]["id"],
239
+ "task.proof.receipt_task_mismatch"
240
+ );
241
+
242
+ state["activeTask"]["id"] = json!("cli-task");
243
+ write_json(&root, ".naome/task-state.json", &state);
244
+ write_fixture_file(&root, "docs/new.md", "new\n");
245
+ let path_mismatch = run_json(
246
+ &root,
247
+ ["task", "record-proof", "--from-proof-plan", "--json"],
248
+ );
249
+ assert_eq!(
250
+ path_mismatch["findings"][0]["id"],
251
+ "task.proof.receipt_path_mismatch"
252
+ );
253
+ }
254
+
255
+ #[test]
256
+ fn scope_suggestions_reports_test_support_helpers() {
257
+ let root = fixture_root(task_state_with_active_task(active_task(json!({
258
+ "allowedPaths": ["scripts/**"],
259
+ "proofResults": []
260
+ }))));
261
+ init_git(&root);
262
+ write_fixture_file(&root, "scripts/new-flow.test.js", "test('x', () => {});\n");
263
+
264
+ let suggestions = run_json(
265
+ &root,
266
+ ["task", "scope-suggestions", "--from-changed", "--json"],
267
+ );
268
+
269
+ assert_eq!(suggestions["schema"], "naome.task.scope-suggestions.v1");
270
+ assert!(suggestions["suggestions"]
271
+ .as_array()
272
+ .unwrap()
273
+ .iter()
274
+ .any(|suggestion| suggestion["path"] == "scripts/naome-installer-support.js"));
275
+ }
276
+
277
+ #[test]
278
+ fn commit_preflight_exit_code_reports_blocker() {
279
+ let root = fixture_root(task_state());
280
+ init_git(&root);
281
+ write_fixture_file(&root, "README.md", "changed\n");
282
+
283
+ let output = Command::new(env!("CARGO_BIN_EXE_naome"))
284
+ .args(["task", "commit-preflight", "--json", "--exit-code"])
285
+ .current_dir(root)
286
+ .output()
287
+ .unwrap();
288
+
289
+ assert_eq!(output.status.code(), Some(10));
290
+ }
@@ -96,7 +96,7 @@ fn record_proof_rejects_receipts_from_older_same_path_content() {
96
96
  assert_eq!(recorded["recorded"], false);
97
97
  assert_eq!(
98
98
  recorded["findings"][0]["id"],
99
- "task.proof.no_recent_success"
99
+ "task.proof.stale_receipt_content"
100
100
  );
101
101
  }
102
102
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.4.2"
3
+ version = "1.4.3"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamentis/naome",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "Native-first CLI for the NAOME agent harness.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",