@lamentis/naome 1.3.6 → 1.3.8

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 (55) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +9 -3
  3. package/crates/naome-cli/Cargo.toml +1 -1
  4. package/crates/naome-core/Cargo.toml +1 -1
  5. package/crates/naome-core/src/context/select.rs +58 -4
  6. package/crates/naome-core/src/git.rs +1 -26
  7. package/crates/naome-core/src/information_architecture.rs +15 -26
  8. package/crates/naome-core/src/install_plan.rs +2 -0
  9. package/crates/naome-core/src/intent/classifier.rs +56 -81
  10. package/crates/naome-core/src/intent/envelope.rs +173 -19
  11. package/crates/naome-core/src/intent/legacy_response.rs +2 -0
  12. package/crates/naome-core/src/intent/model.rs +6 -0
  13. package/crates/naome-core/src/intent/resolver.rs +25 -0
  14. package/crates/naome-core/src/intent/risk.rs +11 -1
  15. package/crates/naome-core/src/intent.rs +1 -1
  16. package/crates/naome-core/src/lib.rs +1 -0
  17. package/crates/naome-core/src/paths.rs +27 -0
  18. package/crates/naome-core/src/quality/scanner/repo_paths.rs +2 -47
  19. package/crates/naome-core/src/repo_paths.rs +76 -0
  20. package/crates/naome-core/src/repository_model/path_scan.rs +2 -23
  21. package/crates/naome-core/src/route/context.rs +8 -0
  22. package/crates/naome-core/src/task_ledger/write.rs +6 -0
  23. package/crates/naome-core/src/task_ledger.rs +50 -2
  24. package/crates/naome-core/src/task_state/util.rs +21 -17
  25. package/crates/naome-core/tests/context.rs +92 -0
  26. package/crates/naome-core/tests/decision.rs +2 -10
  27. package/crates/naome-core/tests/information_architecture.rs +7 -10
  28. package/crates/naome-core/tests/install_plan.rs +2 -0
  29. package/crates/naome-core/tests/intent.rs +98 -18
  30. package/crates/naome-core/tests/intent_support/mod.rs +39 -1
  31. package/crates/naome-core/tests/intent_v2.rs +299 -10
  32. package/crates/naome-core/tests/quality_structure_support/mod.rs +6 -25
  33. package/crates/naome-core/tests/repo_support/mod.rs +3 -5
  34. package/crates/naome-core/tests/repo_support/repo_helpers.rs +2 -9
  35. package/crates/naome-core/tests/repo_support/routes.rs +8 -2
  36. package/crates/naome-core/tests/repo_support/verification.rs +1 -8
  37. package/crates/naome-core/tests/repo_support/verification_values.rs +20 -18
  38. package/crates/naome-core/tests/route_baseline.rs +29 -0
  39. package/crates/naome-core/tests/route_completion.rs +26 -5
  40. package/crates/naome-core/tests/route_harness_refresh.rs +7 -1
  41. package/crates/naome-core/tests/route_user_diff.rs +1 -1
  42. package/crates/naome-core/tests/task_ledger.rs +69 -1
  43. package/crates/naome-core/tests/task_state_compact.rs +7 -1
  44. package/crates/naome-core/tests/task_state_compact_support/states.rs +10 -8
  45. package/crates/naome-core/tests/task_state_support/states.rs +8 -8
  46. package/crates/naome-core/tests/workflow_support/mod.rs +2 -0
  47. package/native/darwin-arm64/naome +0 -0
  48. package/native/linux-x64/naome +0 -0
  49. package/package.json +1 -1
  50. package/templates/naome-root/.naome/manifest.json +3 -3
  51. package/templates/naome-root/.naomeignore +1 -0
  52. package/templates/naome-root/docs/naome/agent-workflow.md +22 -15
  53. package/templates/naome-root/docs/naome/architecture.md +17 -8
  54. package/templates/naome-root/docs/naome/task-ledger.md +20 -17
  55. package/crates/naome-core/src/intent/patterns.rs +0 -170
@@ -10,6 +10,7 @@ use std::path::Path;
10
10
  use serde_json::Value;
11
11
 
12
12
  use crate::models::NaomeError;
13
+ use crate::paths;
13
14
 
14
15
  pub use model::{TaskLedgerProjection, TaskLedgerStatus};
15
16
 
@@ -21,10 +22,21 @@ pub fn migrate_task_state_to_ledger(
21
22
  }
22
23
 
23
24
  pub fn read_task_state_projection(root: &Path) -> Result<Option<Value>, NaomeError> {
24
- if let Some(projection) = render::render_from_ledger(root)? {
25
+ let legacy = read::read_legacy_task_state(root)?;
26
+ if legacy.is_some() && local_runtime_ledger_is_ignored(root) {
27
+ return Ok(legacy);
28
+ }
29
+ let rendered = render::render_from_ledger(root)?;
30
+ if let (Some(projection), Some(legacy_state)) = (&rendered, &legacy) {
31
+ if legacy_completed_projection_covers_local_runtime_ledger(&projection.state, legacy_state)
32
+ {
33
+ return Ok(Some(legacy_state.clone()));
34
+ }
35
+ }
36
+ if let Some(projection) = rendered {
25
37
  return Ok(Some(projection.state));
26
38
  }
27
- read::read_legacy_task_state(root)
39
+ Ok(legacy)
28
40
  }
29
41
 
30
42
  pub fn render_task_state_from_ledger(
@@ -46,3 +58,39 @@ pub(crate) fn validate_task_state_projection_is_current(
46
58
  ) -> Result<(), NaomeError> {
47
59
  write::validate_task_state_projection_is_current(root, errors)
48
60
  }
61
+
62
+ pub(crate) fn legacy_completed_projection_covers_local_runtime_ledger(
63
+ rendered: &Value,
64
+ legacy: &Value,
65
+ ) -> bool {
66
+ if state_status(legacy) != Some("complete") || state_status(rendered) != Some("complete") {
67
+ return false;
68
+ }
69
+
70
+ let Some(legacy_task) = legacy.get("activeTask") else {
71
+ return false;
72
+ };
73
+ let Some(rendered_task) = rendered.get("activeTask") else {
74
+ return false;
75
+ };
76
+
77
+ has_compact_proof_evidence(legacy_task) && !has_compact_proof_evidence(rendered_task)
78
+ }
79
+
80
+ pub(crate) fn local_runtime_ledger_is_ignored(root: &Path) -> bool {
81
+ let ignored_patterns = paths::naomeignore_patterns(root);
82
+ paths::matches_any(".naome/tasks/active.json", &ignored_patterns)
83
+ }
84
+
85
+ fn state_status(state: &Value) -> Option<&str> {
86
+ state.get("status").and_then(Value::as_str)
87
+ }
88
+
89
+ fn has_compact_proof_evidence(active_task: &Value) -> bool {
90
+ ["proofResults", "proofBatches"].iter().any(|field| {
91
+ active_task
92
+ .get(*field)
93
+ .and_then(Value::as_array)
94
+ .is_some_and(|entries| !entries.is_empty())
95
+ })
96
+ }
@@ -43,18 +43,7 @@ pub(super) fn require_string_array(
43
43
  field_name: &str,
44
44
  errors: &mut Vec<String>,
45
45
  ) {
46
- let Some(values) = value.and_then(Value::as_array) else {
47
- errors.push(format!("{field_name} must be a non-empty string array."));
48
- return;
49
- };
50
-
51
- if values.is_empty()
52
- || values
53
- .iter()
54
- .any(|entry| !entry.as_str().is_some_and(is_non_empty_string))
55
- {
56
- errors.push(format!("{field_name} must be a non-empty string array."));
57
- }
46
+ require_string_array_shape(value, field_name, errors, false);
58
47
  }
59
48
 
60
49
  pub(super) fn require_string_array_allow_empty(
@@ -62,16 +51,31 @@ pub(super) fn require_string_array_allow_empty(
62
51
  field_name: &str,
63
52
  errors: &mut Vec<String>,
64
53
  ) {
54
+ require_string_array_shape(value, field_name, errors, true);
55
+ }
56
+
57
+ fn require_string_array_shape(
58
+ value: Option<&Value>,
59
+ field_name: &str,
60
+ errors: &mut Vec<String>,
61
+ allow_empty: bool,
62
+ ) {
63
+ let label = if allow_empty {
64
+ "string array"
65
+ } else {
66
+ "non-empty string array"
67
+ };
65
68
  let Some(values) = value.and_then(Value::as_array) else {
66
- errors.push(format!("{field_name} must be a string array."));
69
+ errors.push(format!("{field_name} must be a {label}."));
67
70
  return;
68
71
  };
69
72
 
70
- if values
73
+ let invalid_empty = !allow_empty && values.is_empty();
74
+ let invalid_entry = values
71
75
  .iter()
72
- .any(|entry| !entry.as_str().is_some_and(is_non_empty_string))
73
- {
74
- errors.push(format!("{field_name} must be a string array."));
76
+ .any(|entry| !entry.as_str().is_some_and(is_non_empty_string));
77
+ if invalid_empty || invalid_entry {
78
+ errors.push(format!("{field_name} must be a {label}."));
75
79
  }
76
80
  }
77
81
 
@@ -66,6 +66,91 @@ fn prompt_context_uses_file_mentions_without_broad_markdown_context() {
66
66
  .any(|item| item.path == "docs/naome/repository-quality.md"));
67
67
  }
68
68
 
69
+ #[test]
70
+ fn prompt_context_ignores_nonexistent_concept_tokens_that_look_path_like() {
71
+ let repo = context_repo("context-prompt-concept-terms");
72
+ repo.write_file("docs/naome/repository-quality.md", "# Quality\n");
73
+ repo.commit_all("baseline");
74
+
75
+ let selection = select_context_for_prompt(
76
+ repo.path(),
77
+ "Advisory/planning-only prompts and German/English examples must not become paths.",
78
+ )
79
+ .unwrap();
80
+
81
+ assert_eq!(selection.mode, "prompt");
82
+ assert_eq!(
83
+ selection.required_context[0].path,
84
+ ".naome/verification.json"
85
+ );
86
+ assert!(!selection
87
+ .required_context
88
+ .iter()
89
+ .any(|item| item.path == "Advisory/planning-only" || item.path == "German/English"));
90
+ }
91
+
92
+ #[test]
93
+ fn prompt_context_prefers_envelope_referenced_paths_over_raw_prompt_tokens() {
94
+ let repo = context_repo("context-prompt-envelope-paths");
95
+ repo.write_file("packages/app/src/lib.rs", "pub fn app() {}\n");
96
+ repo.write_file("packages/app/src/other.rs", "pub fn other() {}\n");
97
+ repo.commit_all("baseline");
98
+
99
+ let selection = select_context_for_prompt(
100
+ repo.path(),
101
+ "```naome-prompt-envelope-v1\n{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[\"packages/app/src/lib.rs\"],\"constraints\":[],\"uncertainties\":[]}\n```\n\nPlease mention packages/app/src/other.rs in prose but use the envelope path.",
102
+ )
103
+ .unwrap();
104
+
105
+ assert_eq!(selection.mode, "prompt");
106
+ assert_eq!(
107
+ selection.required_context[0].path,
108
+ "packages/app/src/lib.rs"
109
+ );
110
+ assert!(!selection
111
+ .required_context
112
+ .iter()
113
+ .any(|item| item.path == "packages/app/src/other.rs"));
114
+ }
115
+
116
+ #[test]
117
+ fn prompt_context_keeps_envelope_paths_for_nested_creation_targets() {
118
+ let repo = app_context_repo("context-prompt-envelope-new-paths");
119
+
120
+ let selection = select_context_for_prompt(
121
+ repo.path(),
122
+ "```naome-prompt-envelope-v1\n{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"create_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[\"packages/app/src/new/mod.rs\"],\"constraints\":[],\"uncertainties\":[]}\n```\n\nCreate the new module.",
123
+ )
124
+ .unwrap();
125
+
126
+ assert_eq!(selection.mode, "prompt");
127
+ assert_eq!(
128
+ selection.required_context[0].path,
129
+ "packages/app/src/new/mod.rs"
130
+ );
131
+ }
132
+
133
+ #[test]
134
+ fn prompt_context_rejects_envelope_paths_outside_repository() {
135
+ let repo = app_context_repo("context-prompt-envelope-safe-paths");
136
+
137
+ let selection = select_context_for_prompt(
138
+ repo.path(),
139
+ "```naome-prompt-envelope-v1\n{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[\"../notes.md\",\"/tmp/file.rs\",\"packages/app/src/lib.rs\"],\"constraints\":[],\"uncertainties\":[]}\n```",
140
+ )
141
+ .unwrap();
142
+
143
+ assert_eq!(selection.mode, "prompt");
144
+ assert_eq!(
145
+ selection.required_context[0].path,
146
+ "packages/app/src/lib.rs"
147
+ );
148
+ assert!(!selection
149
+ .required_context
150
+ .iter()
151
+ .any(|item| item.path == "../notes.md" || item.path == "/tmp/file.rs"));
152
+ }
153
+
69
154
  #[test]
70
155
  fn context_selection_reports_over_budget_when_many_paths_change() {
71
156
  let repo = context_repo("context-budget");
@@ -97,3 +182,10 @@ fn context_repo(name: &str) -> TestRepo {
97
182
  );
98
183
  repo
99
184
  }
185
+
186
+ fn app_context_repo(name: &str) -> TestRepo {
187
+ let repo = context_repo(name);
188
+ repo.write_file("packages/app/src/lib.rs", "pub fn app() {}\n");
189
+ repo.commit_all("baseline");
190
+ repo
191
+ }
@@ -7,7 +7,7 @@ use serde_json::json;
7
7
 
8
8
  mod repo_support;
9
9
 
10
- use repo_support::TestRepo;
10
+ use repo_support::{minimal_task_state, TestRepo};
11
11
 
12
12
  static ENV_LOCK: Mutex<()> = Mutex::new(());
13
13
 
@@ -193,13 +193,5 @@ fn online_checks_use_explicit_node_binary_from_environment() {
193
193
  }
194
194
 
195
195
  fn completed_without_proof_state() -> serde_json::Value {
196
- json!({
197
- "status": "complete",
198
- "activeTask": {
199
- "id": "readme-task",
200
- "allowedPaths": ["README.md"],
201
- "requiredCheckIds": ["diff-check"],
202
- "proofResults": []
203
- }
204
- })
196
+ minimal_task_state("complete", "readme-task", "README.md", "diff-check")
205
197
  }
@@ -21,17 +21,14 @@ fn classifies_naome_information_by_restore_source() {
21
21
  assert_eq!(local.restore_policy, "recreate_on_demand");
22
22
 
23
23
  let proof = classify_information_path(".naome/tasks/readme-task/proofs/diff-check.json");
24
- assert_eq!(proof.class, "run_evidence");
25
- assert_eq!(proof.commit_policy, "prefer_compact_release_evidence");
26
- assert_eq!(
27
- proof.restore_policy,
28
- "restore_from_ci_or_task_ledger_when_audited"
29
- );
24
+ assert_eq!(proof.class, "local_runtime_state");
25
+ assert_eq!(proof.commit_policy, "never_commit");
26
+ assert_eq!(proof.restore_policy, "recreate_on_demand");
30
27
 
31
28
  let task = classify_information_path(".naome/tasks/readme-task/task.json");
32
- assert_eq!(task.class, "durable_task_ledger");
33
- assert_eq!(task.commit_policy, "commit");
34
- assert_eq!(task.restore_policy, "restore_from_repository");
29
+ assert_eq!(task.class, "local_runtime_state");
30
+ assert_eq!(task.commit_policy, "never_commit");
31
+ assert_eq!(task.restore_policy, "recreate_on_demand");
35
32
  }
36
33
 
37
34
  #[test]
@@ -54,5 +51,5 @@ fn reports_information_architecture_contract_for_agents() {
54
51
  assert!(report
55
52
  .rules
56
53
  .iter()
57
- .any(|rule| rule.path_pattern == ".naome/tasks/*/proofs/*.json"));
54
+ .any(|rule| rule.path_pattern == ".naome/tasks/**" && rule.class == "local_runtime_state"));
58
55
  }
@@ -30,6 +30,7 @@ fn install_plan_includes_git_exclude_and_untrack_policy() {
30
30
  .contains(&".naome/task-journal.jsonl"));
31
31
  assert!(plan.git_exclude_entries.contains(&".naome/archive/"));
32
32
  assert!(plan.git_exclude_entries.contains(&".naome/cache/"));
33
+ assert!(plan.git_exclude_entries.contains(&".naome/tasks/"));
33
34
  assert!(plan.git_exclude_entries.contains(&".naome/bin/naome-rust*"));
34
35
  assert!(plan
35
36
  .git_exclude_entries
@@ -39,6 +40,7 @@ fn install_plan_includes_git_exclude_and_untrack_policy() {
39
40
  .contains(&".naome/bin/check-harness-health.js"));
40
41
  assert!(plan.git_untrack_paths.contains(&".naome/archive"));
41
42
  assert!(plan.git_untrack_paths.contains(&".naome/cache"));
43
+ assert!(plan.git_untrack_paths.contains(&".naome/tasks"));
42
44
  assert!(plan.git_untrack_paths.contains(&".naome/bin/naome-rust"));
43
45
  assert!(plan
44
46
  .git_untrack_paths
@@ -2,13 +2,20 @@ mod intent_support;
2
2
 
3
3
  use naome_core::IntentDecision;
4
4
 
5
- use intent_support::{completed_state, env, idle, Repo};
5
+ use intent_support::{completed_state, idle, prompt_env, prompt_env_with_actions, Repo};
6
6
 
7
7
  #[test]
8
8
  fn repo_state_and_envelope_resolve_task_policy() {
9
9
  let clean = Repo::clean("clean", idle());
10
10
  assert_intent(
11
- &clean.intent("Please implement a small new feature."),
11
+ &clean.intent(&prompt_env(
12
+ "Please implement a small new feature.",
13
+ "implementation",
14
+ "modify_files",
15
+ "none",
16
+ "new_task",
17
+ "none",
18
+ )),
12
19
  "ready_for_task",
13
20
  "new_task",
14
21
  "create_new_task",
@@ -18,7 +25,14 @@ fn repo_state_and_envelope_resolve_task_policy() {
18
25
  let mut active_state = completed_state("HEAD", true);
19
26
  active_state["status"] = serde_json::json!("implementing");
20
27
  let active = Repo::clean("active", active_state);
21
- let revision = active.intent(&env("follow up", "none", "task_revision", "none"));
28
+ let revision = active.intent(&prompt_env(
29
+ "follow up",
30
+ "revision",
31
+ "modify_files",
32
+ "none",
33
+ "task_revision",
34
+ "none",
35
+ ));
22
36
  assert_intent(
23
37
  &revision,
24
38
  "active_task_in_progress",
@@ -26,7 +40,14 @@ fn repo_state_and_envelope_resolve_task_policy() {
26
40
  "continue_current_task",
27
41
  true,
28
42
  );
29
- let distinct = active.intent(&env("separate work", "none", "new_task", "none"));
43
+ let distinct = active.intent(&prompt_env(
44
+ "separate work",
45
+ "implementation",
46
+ "modify_files",
47
+ "none",
48
+ "new_task",
49
+ "none",
50
+ ));
30
51
  assert_intent(
31
52
  &distinct,
32
53
  "active_task_in_progress",
@@ -36,7 +57,14 @@ fn repo_state_and_envelope_resolve_task_policy() {
36
57
  );
37
58
 
38
59
  let completed = Repo::completed("completed", true);
39
- let next = completed.intent("After that, please implement a new task.");
60
+ let next = completed.intent(&prompt_env(
61
+ "After that, please implement a new task.",
62
+ "implementation",
63
+ "modify_files",
64
+ "none",
65
+ "new_task",
66
+ "none",
67
+ ));
40
68
  assert_intent(
41
69
  &next,
42
70
  "completed_task_unbaselined",
@@ -46,25 +74,55 @@ fn repo_state_and_envelope_resolve_task_policy() {
46
74
  );
47
75
  assert_eq!(
48
76
  completed
49
- .intent(&env("review", "review_request", "new_task", "none"))
77
+ .intent(&prompt_env_with_actions(
78
+ "review",
79
+ "implementation",
80
+ "none",
81
+ "review_request",
82
+ "none",
83
+ "none",
84
+ &["review"],
85
+ ))
50
86
  .policy_action,
51
87
  "review_task_diff"
52
88
  );
53
89
  assert_eq!(
54
90
  completed
55
- .intent(&env("cancel", "cancel_request", "new_task", "none"))
91
+ .intent(&prompt_env_with_actions(
92
+ "cancel",
93
+ "implementation",
94
+ "none",
95
+ "cancel_request",
96
+ "none",
97
+ "none",
98
+ &["cancel"],
99
+ ))
56
100
  .policy_action,
57
101
  "cancel_task_changes"
58
102
  );
59
103
  assert_eq!(
60
104
  completed
61
- .intent(&env("no commit", "no_commit_request", "new_task", "none"))
105
+ .intent(&prompt_env(
106
+ "no commit",
107
+ "implementation",
108
+ "none",
109
+ "no_commit_request",
110
+ "new_task",
111
+ "none",
112
+ ))
62
113
  .policy_action,
63
114
  "block_auto_baseline_due_to_no_commit"
64
115
  );
65
116
  assert_eq!(
66
117
  completed
67
- .intent(&env("revise", "none", "task_revision", "none"))
118
+ .intent(&prompt_env(
119
+ "revise",
120
+ "revision",
121
+ "modify_files",
122
+ "none",
123
+ "task_revision",
124
+ "none",
125
+ ))
68
126
  .policy_action,
69
127
  "reopen_completed_task_revision"
70
128
  );
@@ -72,8 +130,14 @@ fn repo_state_and_envelope_resolve_task_policy() {
72
130
 
73
131
  #[test]
74
132
  fn workflow_and_risk_intents_are_structured_not_language_keywords() {
75
- let status =
76
- Repo::clean("status", idle()).intent(&env("anything", "status_question", "none", "none"));
133
+ let status = Repo::clean("status", idle()).intent(&prompt_env(
134
+ "anything",
135
+ "status",
136
+ "none",
137
+ "status_question",
138
+ "none",
139
+ "none",
140
+ ));
77
141
  assert_intent(
78
142
  &status,
79
143
  "ready_for_task",
@@ -84,7 +148,14 @@ fn workflow_and_risk_intents_are_structured_not_language_keywords() {
84
148
 
85
149
  let dirty = Repo::clean("dirty", idle());
86
150
  dirty.write("README.md", "# Manual edit\n");
87
- let commit = dirty.intent(&env("okay commit it", "commit_request", "none", "none"));
151
+ let commit = dirty.intent(&prompt_env(
152
+ "okay commit it",
153
+ "implementation",
154
+ "commit",
155
+ "commit_request",
156
+ "none",
157
+ "none",
158
+ ));
88
159
  assert_intent(
89
160
  &commit,
90
161
  "dirty_unowned_diff",
@@ -97,15 +168,17 @@ fn workflow_and_risk_intents_are_structured_not_language_keywords() {
97
168
  assert_intent(
98
169
  &deprecated,
99
170
  "dirty_unowned_diff",
100
- "ambiguous",
101
- "block_unowned_diff",
102
- false,
171
+ "prompt_normalization_required",
172
+ "normalize_prompt_first",
173
+ true,
103
174
  );
104
175
 
105
- let risky = Repo::clean("risky", idle()).intent(&env(
176
+ let risky = Repo::clean("risky", idle()).intent(&prompt_env(
106
177
  "include this API key",
178
+ "implementation",
179
+ "commit",
107
180
  "commit_request",
108
- "new_task",
181
+ "none",
109
182
  "credential_context",
110
183
  ));
111
184
  assert_eq!(risky.prompt_intent, "unsafe");
@@ -116,7 +189,14 @@ fn workflow_and_risk_intents_are_structured_not_language_keywords() {
116
189
  #[test]
117
190
  fn invalid_completed_task_proof_blocks_new_task_baseline() {
118
191
  let repo = Repo::completed("invalid", false);
119
- let intent = repo.intent("After that, please implement a new task.");
192
+ let intent = repo.intent(&prompt_env(
193
+ "After that, please implement a new task.",
194
+ "implementation",
195
+ "modify_files",
196
+ "none",
197
+ "new_task",
198
+ "none",
199
+ ));
120
200
  assert_eq!(intent.policy_action, "block_unsafe_intent");
121
201
  assert!(intent
122
202
  .risk_codes
@@ -11,12 +11,50 @@ use serde_json::{json, Value};
11
11
 
12
12
  static REPO_COUNTER: AtomicU64 = AtomicU64::new(0);
13
13
 
14
- pub fn env(prompt: &str, workflow: &str, task: &str, risk: &str) -> String {
14
+ pub fn legacy_env(prompt: &str, workflow: &str, task: &str, risk: &str) -> String {
15
15
  format!(
16
16
  "```naome-intent-v2\n{{\"schema\":\"naome.intent.v2\",\"workflowAction\":\"{workflow}\",\"taskIntent\":\"{task}\",\"risk\":\"{risk}\"}}\n```\n\n{prompt}"
17
17
  )
18
18
  }
19
19
 
20
+ pub fn prompt_env(
21
+ prompt: &str,
22
+ request_kind: &str,
23
+ mutation_intent: &str,
24
+ workflow: &str,
25
+ task: &str,
26
+ risk: &str,
27
+ ) -> String {
28
+ prompt_env_with_actions(
29
+ prompt,
30
+ request_kind,
31
+ mutation_intent,
32
+ workflow,
33
+ task,
34
+ risk,
35
+ &[],
36
+ )
37
+ }
38
+
39
+ pub fn prompt_env_with_actions(
40
+ prompt: &str,
41
+ request_kind: &str,
42
+ mutation_intent: &str,
43
+ workflow: &str,
44
+ task: &str,
45
+ risk: &str,
46
+ actions: &[&str],
47
+ ) -> String {
48
+ let requested_actions = actions
49
+ .iter()
50
+ .map(|action| format!("\"{action}\""))
51
+ .collect::<Vec<_>>()
52
+ .join(",");
53
+ format!(
54
+ "```naome-prompt-envelope-v1\n{{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"{request_kind}\",\"mutationIntent\":\"{mutation_intent}\",\"workflowAction\":\"{workflow}\",\"taskIntent\":\"{task}\",\"risk\":\"{risk}\",\"requestedActions\":[{requested_actions}],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}}\n```\n\n{prompt}"
55
+ )
56
+ }
57
+
20
58
  pub fn idle() -> Value {
21
59
  json!({ "status": "idle", "activeTask": null })
22
60
  }