@lamentis/naome 1.4.0 → 1.4.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.
Files changed (45) hide show
  1. package/Cargo.lock +2 -2
  2. package/crates/naome-cli/Cargo.toml +1 -1
  3. package/crates/naome-cli/src/main.rs +9 -0
  4. package/crates/naome-cli/src/task_commands/common.rs +32 -0
  5. package/crates/naome-cli/src/task_commands/readiness.rs +40 -0
  6. package/crates/naome-cli/src/task_commands/record.rs +134 -0
  7. package/crates/naome-cli/src/task_commands/repair.rs +30 -0
  8. package/crates/naome-cli/src/task_commands/scope_request.rs +24 -0
  9. package/crates/naome-cli/src/task_commands/timeline.rs +71 -0
  10. package/crates/naome-cli/src/task_commands.rs +69 -1
  11. package/crates/naome-cli/tests/task_cli.rs +58 -0
  12. package/crates/naome-cli/tests/task_cli_agent_controls.rs +217 -0
  13. package/crates/naome-cli/tests/task_cli_control.rs +126 -0
  14. package/crates/naome-cli/tests/task_cli_support/mod.rs +150 -0
  15. package/crates/naome-core/Cargo.toml +1 -1
  16. package/crates/naome-core/src/lib.rs +7 -2
  17. package/crates/naome-core/src/task_state/mod.rs +10 -0
  18. package/crates/naome-core/src/task_state/status/agent_model.rs +76 -0
  19. package/crates/naome-core/src/task_state/status/control/action.rs +87 -0
  20. package/crates/naome-core/src/task_state/status/control/exit_code.rs +32 -0
  21. package/crates/naome-core/src/task_state/status/control/loop_state.rs +70 -0
  22. package/crates/naome-core/src/task_state/status/control/policy.rs +31 -0
  23. package/crates/naome-core/src/task_state/status/control/proof_recording.rs +25 -0
  24. package/crates/naome-core/src/task_state/status/control/recovery.rs +19 -0
  25. package/crates/naome-core/src/task_state/status/control/repair.rs +125 -0
  26. package/crates/naome-core/src/task_state/status/control/shared.rs +25 -0
  27. package/crates/naome-core/src/task_state/status/control.rs +16 -0
  28. package/crates/naome-core/src/task_state/status/git.rs +133 -0
  29. package/crates/naome-core/src/task_state/status/model.rs +150 -0
  30. package/crates/naome-core/src/task_state/status/proof.rs +167 -0
  31. package/crates/naome-core/src/task_state/status/proof_read.rs +150 -0
  32. package/crates/naome-core/src/task_state/status/report.rs +148 -0
  33. package/crates/naome-core/src/task_state/status/report_context.rs +126 -0
  34. package/crates/naome-core/src/task_state/status/report_support.rs +117 -0
  35. package/crates/naome-core/src/task_state/status/scope.rs +111 -0
  36. package/crates/naome-core/src/task_state/status/transition.rs +73 -0
  37. package/crates/naome-core/src/task_state/status.rs +23 -0
  38. package/crates/naome-core/src/task_state/status_output.rs +103 -0
  39. package/crates/naome-core/tests/task_state_support/mod.rs +15 -1
  40. package/crates/naome-core/tests/task_state_support/states.rs +4 -0
  41. package/crates/naome-core/tests/task_status.rs +301 -0
  42. package/crates/naome-core/tests/task_status_git.rs +141 -0
  43. package/native/darwin-arm64/naome +0 -0
  44. package/native/linux-x64/naome +0 -0
  45. package/package.json +1 -1
@@ -0,0 +1,217 @@
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::{fixture_root, init_git, task_state, write_fixture_file};
9
+
10
+ #[test]
11
+ fn status_json_exposes_policy_hints_and_recovery_guidance() {
12
+ let root = fixture_root(task_state());
13
+ init_git(&root);
14
+ write_fixture_file(&root, "README.md", "changed\n");
15
+
16
+ let status = run_json(&root, ["task", "status", "--json"]);
17
+
18
+ assert_eq!(status["policyHints"]["mayEdit"], json!(["README.md"]));
19
+ assert_eq!(status["taskMode"]["kind"], "standard");
20
+ assert_eq!(
21
+ status["policyHints"]["mustRunAfterEdit"],
22
+ json!(["diff-check"])
23
+ );
24
+ assert!(status["policyHints"]["forbiddenActions"]
25
+ .as_array()
26
+ .unwrap()
27
+ .iter()
28
+ .any(|item| item == "Do not bypass the NAOME commit gate."));
29
+ assert!(status["recoveryGuidance"].as_array().unwrap().is_empty());
30
+ }
31
+
32
+ #[test]
33
+ fn repair_dry_run_previews_selected_safe_plan() {
34
+ let root = fixture_root(task_state());
35
+ init_git(&root);
36
+ write_fixture_file(&root, "src/lib.rs", "outside\n");
37
+ write_fixture_file(&root, "docs/outside.md", "outside\n");
38
+
39
+ let status = run_json(&root, ["task", "status", "--json"]);
40
+ let repair_ids = status["repairPlan"]
41
+ .as_array()
42
+ .unwrap()
43
+ .iter()
44
+ .filter_map(|item| item["id"].as_str())
45
+ .collect::<Vec<_>>();
46
+ assert!(repair_ids.contains(&"remove_out_of_scope_change_src_lib_rs"));
47
+ assert!(repair_ids.contains(&"remove_out_of_scope_change_docs_outside_md"));
48
+
49
+ let preview = run_json(
50
+ &root,
51
+ [
52
+ "task",
53
+ "repair",
54
+ "--plan",
55
+ "remove_out_of_scope_change_src_lib_rs",
56
+ "--dry-run",
57
+ "--json",
58
+ ],
59
+ );
60
+
61
+ assert_eq!(preview["schema"], "naome.task.repair-preview.v1");
62
+ assert_eq!(preview["found"], true);
63
+ assert_eq!(preview["wouldExecute"], false);
64
+ assert_eq!(preview["plan"]["kind"], "git_revert_path");
65
+ assert_eq!(preview["plan"]["paths"], json!(["src/lib.rs"]));
66
+ }
67
+
68
+ #[test]
69
+ fn record_proof_from_plan_writes_compact_batch() {
70
+ let root = fixture_root(task_cli_support::task_state_with_active_task(
71
+ task_cli_support::active_task(json!({
72
+ "proofPathSets": {
73
+ "current-task-diff": ["OLD.md"]
74
+ },
75
+ "proofBatches": [{
76
+ "id": "cli-task-proof",
77
+ "checkedAt": "2026-05-04T12:00:00.000Z",
78
+ "evidencePathSet": "current-task-diff",
79
+ "proofs": [{ "checkId": "diff-check", "exitCode": 0 }]
80
+ }]
81
+ })),
82
+ ));
83
+ init_git(&root);
84
+ write_fixture_file(&root, "README.md", "changed\n");
85
+
86
+ let recorded = run_json(
87
+ &root,
88
+ ["task", "record-proof", "--from-proof-plan", "--json"],
89
+ );
90
+
91
+ assert_eq!(recorded["schema"], "naome.task.record-proof.v1");
92
+ assert_eq!(recorded["recorded"], true);
93
+ assert_eq!(recorded["checksRecorded"], json!(["diff-check"]));
94
+ assert_eq!(recorded["pathSetId"], "current-task-diff-2");
95
+ assert_eq!(recorded["proofBatchId"], "cli-task-proof-2");
96
+
97
+ let task_state: Value =
98
+ serde_json::from_str(&fs::read_to_string(root.join(".naome/task-state.json")).unwrap())
99
+ .unwrap();
100
+ assert_eq!(
101
+ task_state["activeTask"]["proofPathSets"]["current-task-diff"],
102
+ json!(["OLD.md"])
103
+ );
104
+ assert_eq!(
105
+ task_state["activeTask"]["proofPathSets"]["current-task-diff-2"],
106
+ json!(["README.md"])
107
+ );
108
+ assert_eq!(
109
+ task_state["activeTask"]["proofBatches"][1]["id"],
110
+ "cli-task-proof-2"
111
+ );
112
+ assert_eq!(
113
+ task_state["activeTask"]["proofBatches"][1]["proofs"][0]["checkId"],
114
+ "diff-check"
115
+ );
116
+ }
117
+
118
+ #[test]
119
+ fn rerun_check_repair_preserves_configured_cwd() {
120
+ let root = fixture_root(task_state());
121
+ init_git(&root);
122
+ let mut verification: Value =
123
+ serde_json::from_str(&fs::read_to_string(root.join(".naome/verification.json")).unwrap())
124
+ .unwrap();
125
+ verification["checks"][0]["cwd"] = json!("checks");
126
+ task_cli_support::write_json(&root, ".naome/verification.json", &verification);
127
+ write_fixture_file(&root, "README.md", "changed\n");
128
+
129
+ let plan = run_json(&root, ["task", "proof-plan", "--json"]);
130
+ let rerun = plan["repairPlan"]
131
+ .as_array()
132
+ .unwrap()
133
+ .iter()
134
+ .find(|item| item["id"] == "rerun_diff-check")
135
+ .unwrap();
136
+
137
+ assert_eq!(rerun["cwd"], "checks");
138
+ assert_eq!(rerun["commands"], json!(["git diff --check"]));
139
+ }
140
+
141
+ #[test]
142
+ fn agent_control_commands_emit_stable_json_contracts() {
143
+ let root = fixture_root(task_state());
144
+ init_git(&root);
145
+ write_fixture_file(&root, "README.md", "changed\n");
146
+
147
+ let scope = run_json(
148
+ &root,
149
+ [
150
+ "task",
151
+ "request-scope",
152
+ "--path",
153
+ "src/lib.rs",
154
+ "--reason",
155
+ "Need to update implementation.",
156
+ "--json",
157
+ ],
158
+ );
159
+ assert_eq!(scope["schema"], "naome.task.scope-request.v1");
160
+ assert_eq!(scope["wouldMutate"], false);
161
+ assert_eq!(scope["requestedPaths"], json!(["src/lib.rs"]));
162
+
163
+ let can_commit = run_json(&root, ["task", "can-commit", "--json"]);
164
+ assert_eq!(can_commit["schema"], "naome.task.commit-readiness.v1");
165
+ assert_eq!(can_commit["allowed"], false);
166
+ assert!(can_commit["requiredBeforeCommit"]
167
+ .as_array()
168
+ .unwrap()
169
+ .iter()
170
+ .any(|item| item.as_str().unwrap().contains("required check")));
171
+
172
+ let timeline = run_json(&root, ["task", "timeline", "--json"]);
173
+ assert_eq!(timeline["schema"], "naome.task.timeline.v1");
174
+ assert!(timeline["events"].as_array().unwrap().len() >= 2);
175
+
176
+ let snapshot = run_json(&root, ["task", "loop-snapshot", "--json"]);
177
+ assert_eq!(snapshot["schema"], "naome.task.loop-snapshot.v1");
178
+ assert_eq!(
179
+ snapshot["status"]["agentLoop"]["state"],
180
+ "blocked_by_missing_proof"
181
+ );
182
+ }
183
+
184
+ #[test]
185
+ fn review_fix_mode_is_structured_not_inferred_from_prompt_text() {
186
+ let root = fixture_root(task_cli_support::task_state_with_active_task(
187
+ task_cli_support::active_task(json!({
188
+ "kind": "review_fix",
189
+ "declaredChangeTypes": ["review-fix"],
190
+ "proofResults": []
191
+ })),
192
+ ));
193
+ init_git(&root);
194
+
195
+ let status = run_json(&root, ["task", "status", "--json"]);
196
+
197
+ assert_eq!(status["taskMode"]["kind"], "review_fix");
198
+ assert_eq!(status["taskMode"]["reviewFix"], true);
199
+ assert!(status["taskMode"]["scopePolicy"]
200
+ .as_str()
201
+ .unwrap()
202
+ .contains("explicit allowedPaths"));
203
+ }
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
+ }
@@ -0,0 +1,126 @@
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, init_git, successful_proof, task_state, task_state_with_active_task,
10
+ write_fixture_file, write_json,
11
+ };
12
+
13
+ #[test]
14
+ fn exit_code_reports_missing_stale_scope_git_and_conflict_states() {
15
+ assert_exit_code(
16
+ task_state(),
17
+ |root| write_fixture_file(root, "README.md", "changed\n"),
18
+ 10,
19
+ );
20
+
21
+ assert_exit_code(
22
+ stale_task_state(),
23
+ |root| {
24
+ write_fixture_file(root, "README.md", "covered\n");
25
+ write_fixture_file(root, "docs/task.md", "stale\n");
26
+ },
27
+ 11,
28
+ );
29
+
30
+ assert_exit_code(
31
+ task_state(),
32
+ |root| write_fixture_file(root, "src/lib.rs", "x\n"),
33
+ 20,
34
+ );
35
+
36
+ assert_exit_code(
37
+ task_state(),
38
+ |root| {
39
+ set_admission_head(root, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
40
+ },
41
+ 30,
42
+ );
43
+
44
+ assert_exit_code(
45
+ task_state(),
46
+ |root| {
47
+ fs::write(
48
+ root.join(".naome/task-state.json"),
49
+ "<<<<<<< HEAD\n{}\n=======\n{}\n>>>>>>> branch\n",
50
+ )
51
+ .unwrap();
52
+ },
53
+ 40,
54
+ );
55
+ }
56
+
57
+ #[test]
58
+ fn can_transition_blocks_missing_proof_and_allows_fresh_proof() {
59
+ let blocked = run_transition(task_state(), |root| {
60
+ write_fixture_file(root, "README.md", "changed\n");
61
+ });
62
+ assert_eq!(blocked["schema"], "naome.task.transition-readiness.v1");
63
+ assert_eq!(blocked["allowed"], false);
64
+ assert!(blocked["blockingFindings"]
65
+ .as_array()
66
+ .unwrap()
67
+ .iter()
68
+ .any(|finding| { finding["id"] == "task.proof.missing_check" }));
69
+
70
+ let allowed = run_transition(fresh_task_state(), |root| {
71
+ write_fixture_file(root, "README.md", "changed\n");
72
+ });
73
+ assert_eq!(allowed["allowed"], true);
74
+ assert_eq!(allowed["agentLoop"]["state"], "ready_to_commit");
75
+ }
76
+
77
+ fn assert_exit_code(state: Value, mutate: impl FnOnce(&std::path::Path), expected: i32) {
78
+ let root = fixture_root(state);
79
+ init_git(&root);
80
+ mutate(&root);
81
+ let output = Command::new(env!("CARGO_BIN_EXE_naome"))
82
+ .args(["task", "status", "--json", "--exit-code"])
83
+ .current_dir(root)
84
+ .output()
85
+ .unwrap();
86
+ assert_eq!(output.status.code(), Some(expected));
87
+ }
88
+
89
+ fn run_transition(state: Value, mutate: impl FnOnce(&std::path::Path)) -> Value {
90
+ let root = fixture_root(state);
91
+ init_git(&root);
92
+ mutate(&root);
93
+ let output = Command::new(env!("CARGO_BIN_EXE_naome"))
94
+ .args(["task", "can-transition", "--to", "complete", "--json"])
95
+ .current_dir(root)
96
+ .output()
97
+ .unwrap();
98
+ assert!(output.status.success());
99
+ serde_json::from_slice(&output.stdout).unwrap()
100
+ }
101
+
102
+ fn stale_task_state() -> Value {
103
+ task_state_with_active_task(active_task(json!({
104
+ "allowedPaths": ["README.md", "docs/**"],
105
+ "proofPathSets": { "old": ["README.md"] },
106
+ "proofBatches": [{
107
+ "id": "old-proof",
108
+ "checkedAt": "2026-05-04T12:00:00.000Z",
109
+ "evidencePathSet": "old",
110
+ "proofs": [{ "checkId": "diff-check", "exitCode": 0 }]
111
+ }]
112
+ })))
113
+ }
114
+
115
+ fn fresh_task_state() -> Value {
116
+ task_state_with_active_task(active_task(json!({
117
+ "proofResults": [successful_proof(json!(["README.md"]))]
118
+ })))
119
+ }
120
+
121
+ fn set_admission_head(root: &std::path::Path, head: &str) {
122
+ let path = root.join(".naome/task-state.json");
123
+ let mut state: Value = serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
124
+ state["activeTask"]["admission"]["gitHead"] = json!(head);
125
+ write_json(root, ".naome/task-state.json", &state);
126
+ }
@@ -0,0 +1,150 @@
1
+ #![allow(dead_code)]
2
+
3
+ use std::fs;
4
+ use std::process::Command;
5
+ use std::sync::atomic::{AtomicU64, Ordering};
6
+ use std::time::{SystemTime, UNIX_EPOCH};
7
+
8
+ use serde_json::{json, Value};
9
+
10
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
11
+
12
+ pub fn task_state() -> Value {
13
+ task_state_with_active_task(active_task(json!({ "proofResults": [] })))
14
+ }
15
+
16
+ pub fn task_state_with_active_task(active_task: Value) -> Value {
17
+ let mut state: Value = serde_json::from_str(include_str!(
18
+ "../../../../templates/naome-root/.naome/task-state.json"
19
+ ))
20
+ .unwrap();
21
+ state["status"] = json!("implementing");
22
+ state["activeTask"] = active_task;
23
+ state["updatedAt"] = json!("2026-05-04T12:00:00.000Z");
24
+ state
25
+ }
26
+
27
+ pub fn active_task(overrides: Value) -> Value {
28
+ let mut task = json!({
29
+ "id": "cli-task",
30
+ "request": "Exercise task CLI.",
31
+ "userPrompt": {
32
+ "receivedAt": "2026-05-04T12:00:00.000Z",
33
+ "text": "Exercise task CLI."
34
+ },
35
+ "admission": {
36
+ "command": "node .naome/bin/check-task-state.js --admission",
37
+ "cwd": ".",
38
+ "exitCode": 0,
39
+ "checkedAt": "2026-05-04T12:00:00.000Z",
40
+ "gitHead": "pending-test-head",
41
+ "changedPaths": []
42
+ },
43
+ "allowedPaths": ["README.md"],
44
+ "declaredChangeTypes": ["docs"],
45
+ "requiredCheckIds": ["diff-check"],
46
+ "humanReview": {
47
+ "required": false,
48
+ "approved": false,
49
+ "reason": null
50
+ }
51
+ });
52
+ for (key, value) in overrides.as_object().unwrap() {
53
+ task.as_object_mut()
54
+ .unwrap()
55
+ .insert(key.clone(), value.clone());
56
+ }
57
+ task
58
+ }
59
+
60
+ pub fn successful_proof(evidence: Value) -> Value {
61
+ json!({
62
+ "checkId": "diff-check",
63
+ "command": "git diff --check",
64
+ "cwd": ".",
65
+ "exitCode": 0,
66
+ "checkedAt": "2026-05-04T12:00:00.000Z",
67
+ "evidence": evidence
68
+ })
69
+ }
70
+
71
+ pub fn fixture_root(task_state: Value) -> std::path::PathBuf {
72
+ let nonce = SystemTime::now()
73
+ .duration_since(UNIX_EPOCH)
74
+ .unwrap()
75
+ .as_nanos();
76
+ let counter = FIXTURE_COUNTER.fetch_add(1, Ordering::Relaxed);
77
+ let root = std::env::temp_dir().join(format!(
78
+ "naome-task-cli-fixture-{}-{nonce}-{counter}",
79
+ std::process::id()
80
+ ));
81
+ fs::create_dir_all(root.join(".naome")).unwrap();
82
+ fs::write(
83
+ root.join(".naomeignore"),
84
+ ".naome/archive/\n.naome/tasks/\n",
85
+ )
86
+ .unwrap();
87
+ write_json(&root, ".naome/task-state.json", &task_state);
88
+ write_json(&root, ".naome/verification.json", &verification());
89
+ write_fixture_file(&root, "README.md", "initial\n");
90
+ root
91
+ }
92
+
93
+ pub fn init_git(root: &std::path::Path) {
94
+ git(root, ["init"]);
95
+ git(root, ["config", "user.email", "naome@example.com"]);
96
+ git(root, ["config", "user.name", "NAOME Test"]);
97
+ git(root, ["add", "."]);
98
+ git(root, ["commit", "-m", "baseline"]);
99
+ let head = git(root, ["rev-parse", "HEAD"]).trim().to_string();
100
+ let path = root.join(".naome/task-state.json");
101
+ let mut state: Value = serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
102
+ state["activeTask"]["admission"]["gitHead"] = json!(head);
103
+ write_json(root, ".naome/task-state.json", &state);
104
+ }
105
+
106
+ pub fn git<const N: usize>(root: &std::path::Path, args: [&str; N]) -> String {
107
+ let output = Command::new("git")
108
+ .args(args)
109
+ .current_dir(root)
110
+ .output()
111
+ .unwrap();
112
+ assert!(
113
+ output.status.success(),
114
+ "{}",
115
+ String::from_utf8_lossy(&output.stderr)
116
+ );
117
+ String::from_utf8_lossy(&output.stdout).to_string()
118
+ }
119
+
120
+ pub fn write_fixture_file(root: &std::path::Path, path: &str, content: &str) {
121
+ let target = root.join(path);
122
+ fs::create_dir_all(target.parent().unwrap()).unwrap();
123
+ fs::write(target, content).unwrap();
124
+ }
125
+
126
+ pub fn write_json(root: &std::path::Path, path: &str, value: &Value) {
127
+ write_fixture_file(
128
+ root,
129
+ path,
130
+ &format!("{}\n", serde_json::to_string_pretty(value).unwrap()),
131
+ );
132
+ }
133
+
134
+ fn verification() -> Value {
135
+ json!({
136
+ "schema": "naome.verification.v1",
137
+ "version": 1,
138
+ "status": "ready",
139
+ "checks": [{
140
+ "id": "diff-check",
141
+ "command": "git diff --check",
142
+ "cwd": ".",
143
+ "purpose": "Detect whitespace and patch formatting issues.",
144
+ "cost": "fast",
145
+ "source": "git",
146
+ "evidence": ["README.md"],
147
+ "lastVerified": null
148
+ }]
149
+ })
150
+ }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.4.0"
3
+ version = "1.4.1"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -68,8 +68,13 @@ pub use task_ledger::{
68
68
  TaskLedgerProjection, TaskLedgerStatus,
69
69
  };
70
70
  pub use task_state::{
71
- completed_task_commit_paths, validate_task_state, TaskStateMode, TaskStateOptions,
72
- TaskStateReport,
71
+ completed_task_commit_paths, format_task_proof_plan, format_task_status, task_proof_plan,
72
+ task_status_exit_code, task_status_report, task_transition_readiness, validate_task_state,
73
+ AgentLoop, NextActionV2, PolicyHints, ProofRecording, ProofRecordingAfterSuccess,
74
+ RecoveryGuidance, RepairPlanItem, TaskFeedback, TaskGitStatus, TaskModeStatus,
75
+ TaskProofPlanReport, TaskProofStatus, TaskRecommendedCommand, TaskScopeStatus, TaskStateMode,
76
+ TaskStateOptions, TaskStateReport, TaskStatusFinding, TaskStatusReportV1,
77
+ TransitionReadinessReport,
73
78
  };
74
79
  pub use verification::seed_builtin_verification_checks;
75
80
  pub use verification_contract::validate_verification_contract;
@@ -22,6 +22,8 @@ mod proof_types;
22
22
  mod push_gate;
23
23
  mod repair;
24
24
  mod shape;
25
+ mod status;
26
+ mod status_output;
25
27
  mod task_diff_api;
26
28
  mod task_records;
27
29
  mod task_references;
@@ -31,6 +33,14 @@ mod util;
31
33
  pub use api::validate_task_state;
32
34
  pub use completed_refresh::completed_task_harness_refresh_diff;
33
35
  pub(crate) use proof_model::{canonical_proof_check_ids, canonical_proofs};
36
+ pub use status::{
37
+ task_proof_plan, task_status_exit_code, task_status_report, task_transition_readiness,
38
+ AgentLoop, NextActionV2, PolicyHints, ProofRecording, ProofRecordingAfterSuccess,
39
+ RecoveryGuidance, RepairPlanItem, TaskFeedback, TaskGitStatus, TaskModeStatus,
40
+ TaskProofPlanReport, TaskProofStatus, TaskRecommendedCommand, TaskScopeStatus,
41
+ TaskStatusFinding, TaskStatusReportV1, TransitionReadinessReport,
42
+ };
43
+ pub use status_output::{format_task_proof_plan, format_task_status};
34
44
  pub use task_diff_api::{
35
45
  completed_task_commit_diff, completed_task_commit_paths, harness_refresh_diff,
36
46
  harness_refresh_with_unrelated_diff,
@@ -0,0 +1,76 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4
+ #[serde(rename_all = "camelCase")]
5
+ pub struct NextActionV2 {
6
+ #[serde(rename = "type")]
7
+ pub action_type: String,
8
+ pub reason: String,
9
+ pub check_ids: Vec<String>,
10
+ pub commands: Vec<String>,
11
+ pub paths: Vec<String>,
12
+ pub safe_to_execute: bool,
13
+ pub requires_user_approval: bool,
14
+ }
15
+
16
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17
+ #[serde(rename_all = "camelCase")]
18
+ pub struct AgentLoop {
19
+ pub state: String,
20
+ pub can_continue_editing: bool,
21
+ pub can_run_checks: bool,
22
+ pub can_record_proof: bool,
23
+ pub can_commit: bool,
24
+ pub must_do_next: Vec<String>,
25
+ pub must_not_do: Vec<String>,
26
+ }
27
+
28
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29
+ #[serde(rename_all = "camelCase")]
30
+ pub struct RepairPlanItem {
31
+ pub id: String,
32
+ pub kind: String,
33
+ pub reason: String,
34
+ pub paths: Vec<String>,
35
+ pub check_ids: Vec<String>,
36
+ pub commands: Vec<String>,
37
+ #[serde(skip_serializing_if = "Option::is_none")]
38
+ pub cwd: Option<String>,
39
+ pub safe_to_execute: bool,
40
+ pub requires_user_approval: bool,
41
+ }
42
+
43
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44
+ #[serde(rename_all = "camelCase")]
45
+ pub struct ProofRecording {
46
+ pub path_set_id: String,
47
+ pub paths: Vec<String>,
48
+ pub proof_batch_id: String,
49
+ pub checks_to_record: Vec<String>,
50
+ pub after_success: ProofRecordingAfterSuccess,
51
+ }
52
+
53
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54
+ #[serde(rename_all = "camelCase")]
55
+ pub struct ProofRecordingAfterSuccess {
56
+ pub record_proof: bool,
57
+ pub instructions: String,
58
+ }
59
+
60
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61
+ #[serde(rename_all = "camelCase")]
62
+ pub struct PolicyHints {
63
+ pub may_edit: Vec<String>,
64
+ pub must_inspect_before_edit: Vec<String>,
65
+ pub must_run_after_edit: Vec<String>,
66
+ pub forbidden_actions: Vec<String>,
67
+ }
68
+
69
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70
+ #[serde(rename_all = "camelCase")]
71
+ pub struct RecoveryGuidance {
72
+ pub id: String,
73
+ pub reason: String,
74
+ pub safe_next_action: String,
75
+ pub requires_user_approval: bool,
76
+ }