@lamentis/naome 1.4.1 → 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.
Files changed (52) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +17 -122
  3. package/crates/naome-cli/Cargo.toml +1 -1
  4. package/crates/naome-cli/src/main.rs +14 -5
  5. package/crates/naome-cli/src/task_commands/agent_snapshot.rs +173 -0
  6. package/crates/naome-cli/src/task_commands/can_edit.rs +64 -0
  7. package/crates/naome-cli/src/task_commands/check_run/output.rs +34 -0
  8. package/crates/naome-cli/src/task_commands/check_run/receipts.rs +163 -0
  9. package/crates/naome-cli/src/task_commands/check_run/verification.rs +165 -0
  10. package/crates/naome-cli/src/task_commands/check_run.rs +196 -0
  11. package/crates/naome-cli/src/task_commands/commit_preflight.rs +89 -0
  12. package/crates/naome-cli/src/task_commands/common.rs +39 -1
  13. package/crates/naome-cli/src/task_commands/compact_proof.rs +69 -0
  14. package/crates/naome-cli/src/task_commands/complete.rs +43 -0
  15. package/crates/naome-cli/src/task_commands/loop_control.rs +73 -0
  16. package/crates/naome-cli/src/task_commands/path_policy.rs +57 -0
  17. package/crates/naome-cli/src/task_commands/planner/checks.rs +166 -0
  18. package/crates/naome-cli/src/task_commands/planner/impact.rs +35 -0
  19. package/crates/naome-cli/src/task_commands/planner/mod.rs +24 -0
  20. package/crates/naome-cli/src/task_commands/preflight.rs +208 -0
  21. package/crates/naome-cli/src/task_commands/readiness.rs +14 -10
  22. package/crates/naome-cli/src/task_commands/record.rs +176 -37
  23. package/crates/naome-cli/src/task_commands/repair.rs +58 -11
  24. package/crates/naome-cli/src/task_commands/scope_suggestions.rs +109 -0
  25. package/crates/naome-cli/src/task_commands.rs +26 -3
  26. package/crates/naome-cli/tests/task_cli_agent_controls.rs +9 -16
  27. package/crates/naome-cli/tests/task_cli_fast_flow.rs +290 -0
  28. package/crates/naome-cli/tests/task_cli_loop.rs +383 -0
  29. package/crates/naome-cli/tests/task_cli_loop_edit.rs +144 -0
  30. package/crates/naome-cli/tests/task_cli_support/mod.rs +28 -0
  31. package/crates/naome-core/Cargo.toml +1 -1
  32. package/crates/naome-core/src/lib.rs +7 -7
  33. package/crates/naome-core/src/task_state/evidence_fingerprint.rs +47 -0
  34. package/crates/naome-core/src/task_state/mod.rs +2 -0
  35. package/crates/naome-core/src/task_state/status/control/repair.rs +2 -2
  36. package/crates/naome-core/src/task_state/status/model.rs +2 -0
  37. package/crates/naome-core/src/task_state/status/proof.rs +59 -9
  38. package/crates/naome-core/src/task_state/status/proof_read.rs +14 -0
  39. package/crates/naome-core/src/task_state/status/report_context.rs +23 -1
  40. package/crates/naome-core/src/task_state/status/transition.rs +29 -1
  41. package/crates/naome-core/tests/task_status.rs +122 -0
  42. package/installer/context.js +1 -1
  43. package/installer/harness-verification.js +2 -6
  44. package/installer/manifest-state.js +2 -2
  45. package/installer/native.js +3 -31
  46. package/native/darwin-arm64/naome +0 -0
  47. package/native/linux-x64/naome +0 -0
  48. package/package.json +1 -1
  49. package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
  50. package/templates/naome-root/.naome/bin/check-task-state.js +4 -39
  51. package/templates/naome-root/.naome/bin/naome.js +2 -30
  52. package/templates/naome-root/.naome/manifest.json +2 -2
@@ -1,48 +1,65 @@
1
- use std::fs;
2
1
  use std::path::Path;
3
2
 
4
3
  use naome_core::task_proof_plan;
5
4
  use serde_json::{json, Value};
6
5
 
7
- use super::common::{print_json, read_task_state};
6
+ use super::check_run::{evidence_fingerprint, successful_receipts, CheckRunReceipt};
7
+ use super::common::{agent_session, print_json_with_session, read_task_state, write_task_state};
8
8
 
9
9
  pub(super) fn record_proof(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
10
10
  if !args.iter().any(|arg| arg == "--from-proof-plan") {
11
11
  return Err("naome task record-proof requires --from-proof-plan".into());
12
12
  }
13
- let plan = task_proof_plan(root)?;
13
+ let session = agent_session(args)?;
14
14
  let dry_run = args.iter().any(|arg| arg == "--dry-run");
15
- let recorded = !dry_run && !plan.proof_recording.checks_to_record.is_empty();
15
+ print_json_with_session(
16
+ record_proof_payload(root, dry_run, session.as_deref())?,
17
+ session.as_deref(),
18
+ )
19
+ }
20
+
21
+ pub(super) fn record_proof_value(
22
+ root: &Path,
23
+ session: Option<&str>,
24
+ ) -> Result<Value, Box<dyn std::error::Error>> {
25
+ record_proof_payload(root, false, session)
26
+ }
27
+
28
+ fn record_proof_payload(
29
+ root: &Path,
30
+ dry_run: bool,
31
+ session: Option<&str>,
32
+ ) -> Result<Value, Box<dyn std::error::Error>> {
33
+ let plan = task_proof_plan(root)?;
34
+ let paths = plan.proof_recording.paths.clone();
35
+ let check_ids = plan.proof_recording.checks_to_record.clone();
36
+ let (can_record, findings, receipts) = recordable_receipts(root, &check_ids, &paths)?;
37
+ let recorded = !dry_run && can_record && !check_ids.is_empty();
16
38
  let recorded_ids = if recorded {
17
- write_proof_batch(root, &plan.proof_recording)?
39
+ write_proof_batch(
40
+ root,
41
+ &plan.proof_recording.path_set_id,
42
+ &plan.proof_recording.proof_batch_id,
43
+ &paths,
44
+ &receipts,
45
+ )?
18
46
  } else {
19
47
  RecordedProof {
20
48
  path_set_id: plan.proof_recording.path_set_id.clone(),
21
49
  proof_batch_id: plan.proof_recording.proof_batch_id.clone(),
22
50
  }
23
51
  };
24
- if dry_run && !plan.proof_recording.checks_to_record.is_empty() {
25
- print_json(json!({
52
+ Ok(json!({
26
53
  "schema": "naome.task.record-proof.v1",
27
54
  "dryRun": dry_run,
28
55
  "recorded": recorded,
29
56
  "pathSetId": recorded_ids.path_set_id,
30
- "paths": plan.proof_recording.paths,
57
+ "paths": paths,
31
58
  "proofBatchId": recorded_ids.proof_batch_id,
32
- "checksRecorded": plan.proof_recording.checks_to_record,
33
- "agentInstruction": "Record proof only after the listed checks exited 0 for the current task diff."
34
- }))?;
35
- return Ok(());
36
- }
37
- print_json(json!({
38
- "schema": "naome.task.record-proof.v1",
39
- "dryRun": dry_run,
40
- "recorded": recorded,
41
- "pathSetId": recorded_ids.path_set_id,
42
- "paths": plan.proof_recording.paths,
43
- "proofBatchId": recorded_ids.proof_batch_id,
44
- "checksRecorded": plan.proof_recording.checks_to_record,
45
- "agentInstruction": "Record proof only after the listed checks exited 0 for the current task diff."
59
+ "checksRecorded": if recorded || dry_run && can_record { check_ids } else { Vec::<String>::new() },
60
+ "findings": findings,
61
+ "agentInstruction": if recorded { "Proof recorded from recent successful safe check evidence." } else { "Run task run-check --record-proof or task loop --execute-safe before recording proof." },
62
+ "agentSession": session
46
63
  }))
47
64
  }
48
65
 
@@ -51,17 +68,48 @@ struct RecordedProof {
51
68
  proof_batch_id: String,
52
69
  }
53
70
 
71
+ pub(super) fn record_receipts_for_checks(
72
+ root: &Path,
73
+ check_ids: &[String],
74
+ paths: &[String],
75
+ session: Option<&str>,
76
+ ) -> Result<bool, Box<dyn std::error::Error>> {
77
+ let (can_record, _findings, receipts) = recordable_receipts(root, check_ids, paths)?;
78
+ if !can_record || receipts.is_empty() {
79
+ return Ok(false);
80
+ }
81
+ let task_id = read_task_state(root)?
82
+ .get("activeTask")
83
+ .and_then(|task| task.get("id"))
84
+ .and_then(Value::as_str)
85
+ .unwrap_or("task")
86
+ .to_string();
87
+ let batch_id = format!("{task_id}-proof");
88
+ let receipts = receipts
89
+ .into_iter()
90
+ .map(|mut receipt| {
91
+ if receipt.agent_session.is_none() {
92
+ receipt.agent_session = session.map(ToString::to_string);
93
+ }
94
+ receipt
95
+ })
96
+ .collect::<Vec<_>>();
97
+ write_proof_batch(root, "current-task-diff", &batch_id, paths, &receipts)?;
98
+ Ok(true)
99
+ }
100
+
54
101
  fn write_proof_batch(
55
102
  root: &Path,
56
- recording: &naome_core::ProofRecording,
103
+ path_set_base: &str,
104
+ proof_batch_base: &str,
105
+ paths: &[String],
106
+ receipts: &[CheckRunReceipt],
57
107
  ) -> Result<RecordedProof, Box<dyn std::error::Error>> {
58
- let path = root.join(".naome/task-state.json");
59
108
  let mut state = read_task_state(root)?;
60
- let checked_at = state
61
- .get("updatedAt")
62
- .and_then(Value::as_str)
63
- .unwrap_or("1970-01-01T00:00:00.000Z")
64
- .to_string();
109
+ let checked_at = receipts
110
+ .first()
111
+ .map(|receipt| receipt.checked_at.clone())
112
+ .unwrap_or_else(|| "1970-01-01T00:00:00.000Z".to_string());
65
113
  let active = state
66
114
  .get_mut("activeTask")
67
115
  .and_then(Value::as_object_mut)
@@ -71,21 +119,21 @@ fn write_proof_batch(
71
119
  .or_insert_with(|| json!({}))
72
120
  .as_object_mut()
73
121
  .ok_or("activeTask.proofPathSets must be an object")?;
74
- let path_set_id = unique_path_set_id(path_sets, &recording.path_set_id);
75
- path_sets.insert(path_set_id.clone(), json!(recording.paths));
122
+ let path_set_id = unique_path_set_id(path_sets, path_set_base);
123
+ path_sets.insert(path_set_id.clone(), json!(paths));
76
124
  let batches = active
77
125
  .entry("proofBatches")
78
126
  .or_insert_with(|| json!([]))
79
127
  .as_array_mut()
80
128
  .ok_or("activeTask.proofBatches must be an array")?;
81
- let proof_batch_id = unique_proof_batch_id(batches, &recording.proof_batch_id);
129
+ let proof_batch_id = unique_proof_batch_id(batches, proof_batch_base);
82
130
  batches.push(proof_batch(
83
- recording,
131
+ receipts,
84
132
  &checked_at,
85
133
  &path_set_id,
86
134
  &proof_batch_id,
87
135
  ));
88
- fs::write(path, format!("{}\n", serde_json::to_string_pretty(&state)?))?;
136
+ write_task_state(root, &state)?;
89
137
  Ok(RecordedProof {
90
138
  path_set_id,
91
139
  proof_batch_id,
@@ -93,7 +141,7 @@ fn write_proof_batch(
93
141
  }
94
142
 
95
143
  fn proof_batch(
96
- recording: &naome_core::ProofRecording,
144
+ receipts: &[CheckRunReceipt],
97
145
  checked_at: &str,
98
146
  path_set_id: &str,
99
147
  proof_batch_id: &str,
@@ -102,12 +150,103 @@ fn proof_batch(
102
150
  "id": proof_batch_id,
103
151
  "checkedAt": checked_at,
104
152
  "evidencePathSet": path_set_id,
105
- "proofs": recording.checks_to_record.iter().map(|check_id| {
106
- json!({ "checkId": check_id, "exitCode": 0 })
153
+ "proofs": receipts.iter().map(|receipt| {
154
+ json!({
155
+ "checkId": receipt.check_id,
156
+ "command": receipt.command,
157
+ "cwd": receipt.cwd,
158
+ "exitCode": receipt.exit_code,
159
+ "checkedAt": receipt.checked_at,
160
+ "evidenceFingerprint": receipt.evidence_fingerprint,
161
+ "agentSession": receipt.agent_session
162
+ })
107
163
  }).collect::<Vec<_>>()
108
164
  })
109
165
  }
110
166
 
167
+ fn recordable_receipts(
168
+ root: &Path,
169
+ check_ids: &[String],
170
+ paths: &[String],
171
+ ) -> Result<(bool, Vec<Value>, Vec<CheckRunReceipt>), Box<dyn std::error::Error>> {
172
+ let receipts = successful_receipts(root)?;
173
+ let current_fingerprint = evidence_fingerprint(root, paths)?;
174
+ let task_id = read_task_state(root)?
175
+ .get("activeTask")
176
+ .and_then(|task| task.get("id"))
177
+ .and_then(Value::as_str)
178
+ .map(ToString::to_string);
179
+ let mut selected = Vec::new();
180
+ let mut findings = Vec::new();
181
+ for check_id in check_ids {
182
+ let expected = super::check_run::read_verification_check(root, check_id)?;
183
+ let matching_check = receipts
184
+ .iter()
185
+ .rev()
186
+ .find(|receipt| receipt.check_id == *check_id);
187
+ let Some(receipt) = matching_check else {
188
+ findings.push(no_recent_success(check_id));
189
+ continue;
190
+ };
191
+ if receipt.task_id != task_id {
192
+ findings.push(json!({
193
+ "id": "task.proof.receipt_task_mismatch",
194
+ "severity": "error",
195
+ "message": format!("Recent receipt for {check_id} was produced for a different task."),
196
+ "path": null,
197
+ "suggestedFix": "Rerun the check for the active task before recording proof.",
198
+ "agentInstruction": "Do not record proof from another task."
199
+ }));
200
+ continue;
201
+ }
202
+ if !expected
203
+ .as_ref()
204
+ .is_some_and(|check| receipt.command == check.command && receipt.cwd == check.cwd)
205
+ {
206
+ findings.push(no_recent_success(check_id));
207
+ continue;
208
+ }
209
+ if paths != receipt.evidence_paths {
210
+ findings.push(json!({
211
+ "id": "task.proof.receipt_path_mismatch",
212
+ "severity": "error",
213
+ "message": format!("Recent receipt for {check_id} covers a different path set."),
214
+ "path": null,
215
+ "suggestedFix": "Rerun the check against the current task-owned changed paths.",
216
+ "agentInstruction": "Do not record proof when receipt paths differ from the current task diff."
217
+ }));
218
+ continue;
219
+ }
220
+ if receipt.evidence_fingerprint != current_fingerprint {
221
+ findings.push(json!({
222
+ "id": "task.proof.stale_receipt_content",
223
+ "severity": "error",
224
+ "message": format!("Recent receipt for {check_id} does not match current file contents."),
225
+ "path": null,
226
+ "suggestedFix": "Rerun the check after the latest edits.",
227
+ "agentInstruction": "Do not record proof from stale check output."
228
+ }));
229
+ continue;
230
+ }
231
+ match matching_check.cloned() {
232
+ Some(receipt) => selected.push(receipt),
233
+ None => findings.push(no_recent_success(check_id)),
234
+ }
235
+ }
236
+ Ok((findings.is_empty(), findings, selected))
237
+ }
238
+
239
+ fn no_recent_success(check_id: &str) -> Value {
240
+ json!({
241
+ "id": "task.proof.no_recent_success",
242
+ "severity": "error",
243
+ "message": format!("No recent successful safe check evidence covers current task paths for {check_id}."),
244
+ "path": null,
245
+ "suggestedFix": "Run naome task run-check --check <id> --record-proof --json or naome task loop --execute-safe --json.",
246
+ "agentInstruction": "Do not record proof until NAOME has just executed the check successfully."
247
+ })
248
+ }
249
+
111
250
  fn unique_path_set_id(path_sets: &serde_json::Map<String, Value>, base: &str) -> String {
112
251
  unique_id(base, |candidate| path_sets.contains_key(candidate))
113
252
  }
@@ -3,14 +3,20 @@ use std::path::Path;
3
3
  use naome_core::task_status_report;
4
4
  use serde_json::json;
5
5
 
6
- use super::common::{print_json, value_after};
6
+ use super::common::{agent_session, print_json_with_session, value_after};
7
7
 
8
8
  pub(super) fn repair_preview(
9
9
  root: &Path,
10
10
  args: &[String],
11
11
  ) -> Result<(), Box<dyn std::error::Error>> {
12
- if !args.iter().any(|arg| arg == "--dry-run") {
13
- return Err("naome task repair requires --dry-run in v1.4.1".into());
12
+ let session = agent_session(args)?;
13
+ let dry_run = args.iter().any(|arg| arg == "--dry-run");
14
+ let execute_safe = args.iter().any(|arg| arg == "--execute-safe");
15
+ if dry_run && execute_safe {
16
+ return Err("naome task repair accepts only one of --dry-run or --execute-safe".into());
17
+ }
18
+ if !dry_run && !execute_safe {
19
+ return Err("naome task repair requires --dry-run or --execute-safe".into());
14
20
  }
15
21
  let plan_id = value_after(args, "--plan").ok_or("naome task repair requires --plan <id>")?;
16
22
  let status = task_status_report(root)?;
@@ -19,12 +25,53 @@ pub(super) fn repair_preview(
19
25
  .iter()
20
26
  .find(|item| item.id == plan_id)
21
27
  .cloned();
22
- print_json(json!({
23
- "schema": "naome.task.repair-preview.v1",
24
- "planId": plan_id,
25
- "found": plan.is_some(),
26
- "wouldExecute": false,
27
- "plan": plan,
28
- "agentInstruction": "Review this dry-run output and execute only safe commands explicitly allowed by NAOME."
29
- }))
28
+ let mut steps = Vec::new();
29
+ let mut executed = false;
30
+ let mut requires_user_approval = false;
31
+ if execute_safe {
32
+ if let Some(plan) = &plan {
33
+ if !can_execute_safe(plan) {
34
+ requires_user_approval = true;
35
+ } else {
36
+ match plan.kind.as_str() {
37
+ "rerun_check" if plan.check_ids.len() == 1 => {
38
+ steps.push(super::check_run::run_check_by_id(
39
+ root,
40
+ &plan.check_ids[0],
41
+ false,
42
+ session.as_deref(),
43
+ )?);
44
+ executed = true;
45
+ }
46
+ "record_proof" => {
47
+ steps.push(super::record::record_proof_value(root, session.as_deref())?);
48
+ executed = true;
49
+ }
50
+ _ => {
51
+ requires_user_approval = true;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ print_json_with_session(
58
+ json!({
59
+ "schema": if dry_run { "naome.task.repair-preview.v1" } else { "naome.task.repair-execute.v1" },
60
+ "planId": plan_id,
61
+ "found": plan.is_some(),
62
+ "wouldExecute": dry_run && plan.as_ref().is_some_and(can_execute_safe),
63
+ "executed": executed,
64
+ "requiresUserApproval": requires_user_approval || plan.as_ref().is_some_and(|item| item.requires_user_approval),
65
+ "plan": plan,
66
+ "steps": steps,
67
+ "agentInstruction": if executed { "Executed only NAOME safe check/proof repair steps." } else { "Review this output; unsafe repair plans require human approval." }
68
+ }),
69
+ session.as_deref(),
70
+ )
71
+ }
72
+
73
+ fn can_execute_safe(item: &naome_core::RepairPlanItem) -> bool {
74
+ item.safe_to_execute
75
+ && !item.requires_user_approval
76
+ && matches!(item.kind.as_str(), "rerun_check" | "record_proof")
30
77
  }
@@ -0,0 +1,109 @@
1
+ use std::path::Path;
2
+ use std::process::Command;
3
+
4
+ use serde_json::json;
5
+
6
+ use super::common::{agent_session, print_json_with_session};
7
+ use super::path_policy::is_ignored;
8
+ use super::planner;
9
+
10
+ pub(super) fn scope_suggestions(
11
+ root: &Path,
12
+ args: &[String],
13
+ ) -> Result<(), Box<dyn std::error::Error>> {
14
+ let session = agent_session(args)?;
15
+ if !args.iter().any(|arg| arg == "--from-changed") {
16
+ return Err("naome task scope-suggestions requires --from-changed".into());
17
+ }
18
+ let mut suggestions = rename_suggestions(root)
19
+ .into_iter()
20
+ .chain(loader_suggestions(root))
21
+ .filter(|suggestion| {
22
+ !suggestion
23
+ .get("path")
24
+ .and_then(serde_json::Value::as_str)
25
+ .is_some_and(|path| is_ignored(root, path))
26
+ })
27
+ .collect::<Vec<_>>();
28
+ suggestions.sort_by(|left, right| {
29
+ left.get("path")
30
+ .and_then(serde_json::Value::as_str)
31
+ .unwrap_or("")
32
+ .cmp(
33
+ right
34
+ .get("path")
35
+ .and_then(serde_json::Value::as_str)
36
+ .unwrap_or(""),
37
+ )
38
+ });
39
+ print_json_with_session(
40
+ json!({
41
+ "schema": "naome.task.scope-suggestions.v1",
42
+ "suggestions": suggestions,
43
+ "agentInstruction": "Suggestions are read-only hints; task scope mutation still requires explicit approval."
44
+ }),
45
+ session.as_deref(),
46
+ )
47
+ }
48
+
49
+ fn rename_suggestions(root: &Path) -> Vec<serde_json::Value> {
50
+ let Ok(output) = Command::new("git")
51
+ .args(["status", "--porcelain=v1", "-z"])
52
+ .current_dir(root)
53
+ .output()
54
+ else {
55
+ return Vec::new();
56
+ };
57
+ if !output.status.success() {
58
+ return Vec::new();
59
+ }
60
+ let mut suggestions = Vec::new();
61
+ let entries = output.stdout.split(|byte| *byte == 0).collect::<Vec<_>>();
62
+ let mut index = 0;
63
+ while index < entries.len() {
64
+ let entry = entries[index];
65
+ index += 1;
66
+ if entry.len() < 4 {
67
+ continue;
68
+ }
69
+ let status = String::from_utf8_lossy(&entry[..2]);
70
+ if status.contains('R') && index < entries.len() {
71
+ let old = String::from_utf8_lossy(entries[index]).replace('\\', "/");
72
+ index += 1;
73
+ let new = String::from_utf8_lossy(&entry[3..]).replace('\\', "/");
74
+ for path in [old, new] {
75
+ suggestions.push(json!({
76
+ "path": path,
77
+ "reason": "paired path for renamed file",
78
+ "confidence": 0.95,
79
+ "requiresUserApproval": true
80
+ }));
81
+ }
82
+ }
83
+ }
84
+ suggestions
85
+ }
86
+
87
+ fn loader_suggestions(root: &Path) -> Vec<serde_json::Value> {
88
+ planner::changed_paths(root)
89
+ .into_iter()
90
+ .filter(|path| path.ends_with(".test.js") || path.ends_with("_test.rs"))
91
+ .filter_map(|path| {
92
+ let loader = if path.starts_with("scripts/") {
93
+ Some("scripts/naome-installer-support.js".to_string())
94
+ } else if path.contains("/tests/") {
95
+ path.split("/tests/")
96
+ .next()
97
+ .map(|prefix| format!("{prefix}/tests/support/mod.rs"))
98
+ } else {
99
+ None
100
+ }?;
101
+ Some(json!({
102
+ "path": loader,
103
+ "reason": "fixture support helper for changed test",
104
+ "confidence": 0.7,
105
+ "requiresUserApproval": true
106
+ }))
107
+ })
108
+ .collect()
109
+ }
@@ -1,10 +1,21 @@
1
1
  use std::path::Path;
2
2
 
3
+ mod agent_snapshot;
4
+ mod can_edit;
5
+ mod check_run;
6
+ mod commit_preflight;
3
7
  mod common;
8
+ mod compact_proof;
9
+ mod complete;
10
+ mod loop_control;
11
+ mod path_policy;
12
+ mod planner;
13
+ mod preflight;
4
14
  mod readiness;
5
15
  mod record;
6
16
  mod repair;
7
17
  mod scope_request;
18
+ mod scope_suggestions;
8
19
  mod timeline;
9
20
 
10
21
  use naome_core::{
@@ -19,9 +30,18 @@ pub fn run_task_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std:
19
30
  Some("migrate-ledger") => migrate_ledger(root, args),
20
31
  Some("status") => task_status(root, args),
21
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),
35
+ Some("can-edit") => can_edit::can_edit(root, args),
36
+ Some("run-check") => check_run::run_check_command(root, args),
22
37
  Some("can-transition") => can_transition(root, args),
23
38
  Some("repair") => repair::repair_preview(root, args),
24
39
  Some("record-proof") => record::record_proof(root, args),
40
+ Some("compact-proof") => compact_proof::compact_proof(root, args),
41
+ Some("complete") => complete::complete_task(root, args),
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),
25
45
  Some("request-scope") => scope_request::request_scope(root, args),
26
46
  Some("can-commit") => readiness::can_commit(root, args),
27
47
  Some("timeline") => timeline::timeline(root, args),
@@ -31,9 +51,10 @@ pub fn run_task_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std:
31
51
  }
32
52
 
33
53
  fn task_status(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
54
+ let session = common::agent_session(args)?;
34
55
  let report = task_status_report(root)?;
35
56
  if args.iter().any(|arg| arg == "--json") {
36
- println!("{}", serde_json::to_string_pretty(&report)?);
57
+ common::print_json_with_session(serde_json::to_value(&report)?, session.as_deref())?;
37
58
  } else {
38
59
  print!("{}", format_task_status(&report));
39
60
  }
@@ -42,9 +63,10 @@ fn task_status(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::E
42
63
  }
43
64
 
44
65
  fn proof_plan(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
66
+ let session = common::agent_session(args)?;
45
67
  let report = task_proof_plan(root)?;
46
68
  if args.iter().any(|arg| arg == "--json") {
47
- println!("{}", serde_json::to_string_pretty(&report)?);
69
+ common::print_json_with_session(serde_json::to_value(&report)?, session.as_deref())?;
48
70
  } else {
49
71
  print!("{}", format_task_proof_plan(&report));
50
72
  }
@@ -53,6 +75,7 @@ fn proof_plan(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Er
53
75
  }
54
76
 
55
77
  fn can_transition(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
78
+ let session = common::agent_session(args)?;
56
79
  let Some(target) = args
57
80
  .windows(2)
58
81
  .find(|window| window[0] == "--to")
@@ -62,7 +85,7 @@ fn can_transition(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error
62
85
  };
63
86
  let report = task_transition_readiness(root, target)?;
64
87
  if args.iter().any(|arg| arg == "--json") {
65
- println!("{}", serde_json::to_string_pretty(&report)?);
88
+ common::print_json_with_session(serde_json::to_value(&report)?, session.as_deref())?;
66
89
  } else {
67
90
  println!(
68
91
  "NAOME task transition {target}: {}",
@@ -1,11 +1,10 @@
1
1
  use std::fs;
2
- use std::process::Command;
3
2
 
4
3
  use serde_json::{json, Value};
5
4
 
6
5
  mod task_cli_support;
7
6
 
8
- use task_cli_support::{fixture_root, init_git, task_state, write_fixture_file};
7
+ use task_cli_support::{fixture_root, init_git, run_json, task_state, write_fixture_file};
9
8
 
10
9
  #[test]
11
10
  fn status_json_exposes_policy_hints_and_recovery_guidance() {
@@ -83,6 +82,14 @@ fn record_proof_from_plan_writes_compact_batch() {
83
82
  init_git(&root);
84
83
  write_fixture_file(&root, "README.md", "changed\n");
85
84
 
85
+ let check = run_json(
86
+ &root,
87
+ ["task", "run-check", "--check", "diff-check", "--json"],
88
+ );
89
+ assert_eq!(check["schema"], "naome.task.run-check.v1");
90
+ assert_eq!(check["executed"], true);
91
+ assert_eq!(check["recordedProof"], false);
92
+
86
93
  let recorded = run_json(
87
94
  &root,
88
95
  ["task", "record-proof", "--from-proof-plan", "--json"],
@@ -201,17 +208,3 @@ fn review_fix_mode_is_structured_not_inferred_from_prompt_text() {
201
208
  .unwrap()
202
209
  .contains("explicit allowedPaths"));
203
210
  }
204
-
205
- fn run_json<const N: usize>(root: &std::path::Path, args: [&str; N]) -> Value {
206
- let output = Command::new(env!("CARGO_BIN_EXE_naome"))
207
- .args(args)
208
- .current_dir(root)
209
- .output()
210
- .unwrap();
211
- assert!(
212
- output.status.success(),
213
- "{}",
214
- String::from_utf8_lossy(&output.stderr)
215
- );
216
- serde_json::from_slice(&output.stdout).unwrap()
217
- }