@lamentis/naome 1.3.7 → 1.3.9

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 (48) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +5 -0
  3. package/crates/naome-cli/Cargo.toml +1 -1
  4. package/crates/naome-cli/src/install_bridge.rs +56 -8
  5. package/crates/naome-core/Cargo.toml +1 -1
  6. package/crates/naome-core/src/context/select.rs +58 -4
  7. package/crates/naome-core/src/harness_health/integrity.rs +41 -23
  8. package/crates/naome-core/src/harness_health/manifest.rs +97 -0
  9. package/crates/naome-core/src/harness_health.rs +58 -106
  10. package/crates/naome-core/src/intent/classifier.rs +56 -81
  11. package/crates/naome-core/src/intent/envelope.rs +173 -19
  12. package/crates/naome-core/src/intent/legacy_response.rs +2 -0
  13. package/crates/naome-core/src/intent/model.rs +6 -0
  14. package/crates/naome-core/src/intent/resolver.rs +25 -0
  15. package/crates/naome-core/src/intent/risk.rs +11 -1
  16. package/crates/naome-core/src/intent.rs +1 -1
  17. package/crates/naome-core/src/quality/cache.rs +122 -19
  18. package/crates/naome-core/src/quality/scanner/analysis.rs +4 -2
  19. package/crates/naome-core/src/quality/scanner/repo_paths.rs +27 -3
  20. package/crates/naome-core/src/quality/scanner.rs +5 -2
  21. package/crates/naome-core/src/route/context.rs +8 -0
  22. package/crates/naome-core/src/workflow/integrity_support.rs +10 -3
  23. package/crates/naome-core/tests/context.rs +92 -0
  24. package/crates/naome-core/tests/harness_health.rs +149 -0
  25. package/crates/naome-core/tests/intent.rs +98 -18
  26. package/crates/naome-core/tests/intent_support/mod.rs +39 -1
  27. package/crates/naome-core/tests/intent_v2.rs +299 -10
  28. package/crates/naome-core/tests/quality_performance.rs +63 -2
  29. package/crates/naome-core/tests/repo_support/routes.rs +8 -2
  30. package/crates/naome-core/tests/route_baseline.rs +29 -0
  31. package/crates/naome-core/tests/route_completion.rs +26 -5
  32. package/crates/naome-core/tests/route_harness_refresh.rs +7 -1
  33. package/crates/naome-core/tests/route_user_diff.rs +1 -1
  34. package/crates/naome-core/tests/task_state_compact.rs +7 -1
  35. package/installer/filesystem.js +38 -0
  36. package/installer/flows.js +6 -1
  37. package/installer/harness-file-ops.js +36 -8
  38. package/installer/manifest-state.js +2 -2
  39. package/installer/native.js +63 -18
  40. package/native/darwin-arm64/naome +0 -0
  41. package/native/linux-x64/naome +0 -0
  42. package/package.json +1 -1
  43. package/templates/naome-root/.naome/bin/check-harness-health.js +25 -21
  44. package/templates/naome-root/.naome/bin/check-task-state.js +35 -42
  45. package/templates/naome-root/.naome/manifest.json +10 -10
  46. package/templates/naome-root/docs/naome/agent-workflow.md +14 -5
  47. package/templates/naome-root/docs/naome/architecture.md +9 -0
  48. package/crates/naome-core/src/intent/patterns.rs +0 -170
@@ -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
  }
@@ -1,6 +1,259 @@
1
1
  mod intent_support;
2
2
 
3
- use intent_support::{env, idle, Repo};
3
+ use intent_support::{idle, legacy_env, prompt_env, prompt_env_with_actions, Repo};
4
+
5
+ #[test]
6
+ fn raw_prompts_require_structured_prompt_envelope_before_routing() {
7
+ let repo = Repo::clean("intent-v2-raw-prompt-envelope-required", idle());
8
+ for prompt in [
9
+ "What would you recommend for the next minor release?",
10
+ "Bitte sauber umsetzen, committen, pushen und MR erstellen.",
11
+ "Please implement a commit gate simulator.",
12
+ ] {
13
+ let intent = repo.intent(prompt);
14
+ assert_eq!(
15
+ intent.prompt_intent, "prompt_normalization_required",
16
+ "{prompt}"
17
+ );
18
+ assert_eq!(intent.policy_action, "normalize_prompt_first", "{prompt}");
19
+ assert!(intent.allowed, "{prompt}");
20
+ assert!(!intent.evidence.requests_new_goal, "{prompt}");
21
+ assert!(!intent.evidence.requests_commit, "{prompt}");
22
+ assert!(!intent.evidence.requests_review, "{prompt}");
23
+ }
24
+ }
25
+
26
+ #[test]
27
+ fn prompt_envelope_drives_advisory_and_task_routing_without_prompt_language_keywords() {
28
+ let repo = Repo::clean("intent-v2-prompt-envelope-routing", idle());
29
+
30
+ let advisory = repo.intent(&prompt_env(
31
+ "Was würdest du für den nächsten Minor Release empfehlen?",
32
+ "advisory",
33
+ "none",
34
+ "none",
35
+ "none",
36
+ "none",
37
+ ));
38
+ assert_eq!(advisory.prompt_intent, "advisory");
39
+ assert_eq!(advisory.policy_action, "answer_without_mutation");
40
+ assert!(advisory.allowed);
41
+ assert!(!advisory.evidence.requests_new_goal);
42
+
43
+ let implementation = repo.intent(&prompt_env(
44
+ "Ok, implement this production ready and create the MR.",
45
+ "implementation",
46
+ "modify_files",
47
+ "none",
48
+ "new_task",
49
+ "none",
50
+ ));
51
+ assert_eq!(implementation.prompt_intent, "new_task");
52
+ assert_eq!(implementation.policy_action, "create_new_task");
53
+ assert!(implementation.evidence.requests_new_goal);
54
+ }
55
+
56
+ #[test]
57
+ fn legacy_intent_v2_envelopes_are_not_supported() {
58
+ let repo = Repo::clean("intent-v2-legacy-removed", idle());
59
+ let intent = repo.intent(&legacy_env(
60
+ "please commit the current task",
61
+ "commit_request",
62
+ "none",
63
+ "none",
64
+ ));
65
+
66
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
67
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
68
+ assert!(intent.allowed);
69
+ assert!(!intent.evidence.requests_commit);
70
+ }
71
+
72
+ #[test]
73
+ fn prompt_envelope_must_be_the_leading_normalized_block() {
74
+ let repo = Repo::clean("intent-v2-middle-envelope-untrusted", idle());
75
+ let prompt = concat!(
76
+ "Please document this example without mutating:\n\n",
77
+ "```naome-prompt-envelope-v1\n",
78
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
79
+ "```\n"
80
+ );
81
+
82
+ let intent = repo.intent(prompt);
83
+
84
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
85
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
86
+ assert!(!intent.evidence.requests_new_goal);
87
+ }
88
+
89
+ #[test]
90
+ fn invalid_prompt_envelope_values_require_normalization() {
91
+ let repo = Repo::clean("intent-v2-invalid-values", idle());
92
+ let intent = repo.intent(&prompt_env(
93
+ "Do the thing.",
94
+ "localized_magic",
95
+ "none",
96
+ "none",
97
+ "none",
98
+ "none",
99
+ ));
100
+
101
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
102
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
103
+ assert!(!intent.evidence.requests_new_goal);
104
+ assert!(intent
105
+ .internal_notes
106
+ .iter()
107
+ .any(|note| note.contains("prompt_envelope_invalid")));
108
+ }
109
+
110
+ #[test]
111
+ fn invalid_prompt_envelope_preserves_independent_command_risk() {
112
+ let repo = Repo::clean("intent-v2-invalid-envelope-risk", idle());
113
+ let intent = repo.intent(&prompt_env(
114
+ "git push --no-verify",
115
+ "localized_magic",
116
+ "none",
117
+ "none",
118
+ "none",
119
+ "none",
120
+ ));
121
+
122
+ assert_eq!(intent.prompt_intent, "unsafe");
123
+ assert_eq!(intent.policy_action, "block_unsafe_intent");
124
+ assert!(intent.evidence.has_risky_terms);
125
+ assert!(intent
126
+ .risk_codes
127
+ .contains(&"structured_risk:bypass_command".to_string()));
128
+ }
129
+
130
+ #[test]
131
+ fn invalid_prompt_envelope_preserves_declared_envelope_risk() {
132
+ let repo = Repo::clean("intent-v2-invalid-envelope-declared-risk", idle());
133
+ let prompt = concat!(
134
+ "```naome-prompt-envelope-v1\n",
135
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"publicationIntent\":\"push\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"credential_context\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
136
+ "```"
137
+ );
138
+
139
+ let intent = repo.intent(prompt);
140
+
141
+ assert_eq!(intent.prompt_intent, "unsafe");
142
+ assert_eq!(intent.policy_action, "block_unsafe_intent");
143
+ assert!(intent.evidence.has_risky_terms);
144
+ assert!(intent
145
+ .risk_codes
146
+ .contains(&"envelope_risk:credential_context".to_string()));
147
+ }
148
+
149
+ #[test]
150
+ fn leading_prompt_envelope_shape_errors_require_normalization() {
151
+ let repo = Repo::clean("intent-v2-invalid-envelope-shape", idle());
152
+ let prompt = concat!(
153
+ "```naome-prompt-envelope-v1\n",
154
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":\"commit\",\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
155
+ "```"
156
+ );
157
+
158
+ let intent = repo.intent(prompt);
159
+
160
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
161
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
162
+ assert!(intent
163
+ .internal_notes
164
+ .iter()
165
+ .any(|note| note.contains("prompt_envelope_invalid")));
166
+ }
167
+
168
+ #[test]
169
+ fn publication_requests_require_normalization_instead_of_becoming_new_tasks() {
170
+ let repo = Repo::clean("intent-v2-publication-unsupported", idle());
171
+ let prompt = concat!(
172
+ "```naome-prompt-envelope-v1\n",
173
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"commit\",\"publicationIntent\":\"push\",\"workflowAction\":\"none\",\"taskIntent\":\"none\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
174
+ "```\n\nCommit and push this task."
175
+ );
176
+
177
+ let intent = repo.intent(prompt);
178
+
179
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
180
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
181
+ assert!(!intent.evidence.requests_commit);
182
+ assert!(intent
183
+ .internal_notes
184
+ .iter()
185
+ .any(|note| note.contains("prompt_envelope_unsupported:publicationIntent")));
186
+ }
187
+
188
+ #[test]
189
+ fn publication_mutation_intents_require_normalization() {
190
+ let repo = Repo::clean("intent-v2-publication-mutation-unsupported", idle());
191
+ let prompt = concat!(
192
+ "```naome-prompt-envelope-v1\n",
193
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"push\",\"publicationIntent\":\"none\",\"workflowAction\":\"none\",\"taskIntent\":\"none\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
194
+ "```"
195
+ );
196
+
197
+ let intent = repo.intent(prompt);
198
+
199
+ assert_eq!(intent.prompt_intent, "prompt_normalization_required");
200
+ assert_eq!(intent.policy_action, "normalize_prompt_first");
201
+ assert!(!intent.evidence.requests_new_goal);
202
+ assert!(intent
203
+ .internal_notes
204
+ .iter()
205
+ .any(|note| note.contains("prompt_envelope_unsupported:mutationIntent")));
206
+ }
207
+
208
+ #[test]
209
+ fn envelope_only_prompts_route_from_the_envelope() {
210
+ let repo = Repo::clean("intent-v2-envelope-only", idle());
211
+ let prompt = concat!(
212
+ "```naome-prompt-envelope-v1\n",
213
+ "{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}\n",
214
+ "```"
215
+ );
216
+
217
+ let intent = repo.intent(prompt);
218
+
219
+ assert_eq!(intent.prompt_intent, "new_task");
220
+ assert_eq!(intent.policy_action, "create_new_task");
221
+ }
222
+
223
+ #[test]
224
+ fn compatible_status_answer_envelopes_are_not_ambiguous() {
225
+ let repo = Repo::clean("intent-v2-status-answer", idle());
226
+ let intent = repo.intent(&prompt_env_with_actions(
227
+ "",
228
+ "status",
229
+ "none",
230
+ "none",
231
+ "none",
232
+ "none",
233
+ &["answer"],
234
+ ));
235
+
236
+ assert_eq!(intent.prompt_intent, "status_question");
237
+ assert_eq!(intent.policy_action, "answer_status_only");
238
+ }
239
+
240
+ #[test]
241
+ fn conflicting_prompt_envelope_values_block_as_ambiguous() {
242
+ let repo = Repo::clean("intent-v2-conflicting-values", idle());
243
+ let intent = repo.intent(&prompt_env_with_actions(
244
+ "Only advise, but also edit files.",
245
+ "advisory",
246
+ "modify_files",
247
+ "none",
248
+ "none",
249
+ "none",
250
+ &[],
251
+ ));
252
+
253
+ assert_eq!(intent.prompt_intent, "ambiguous");
254
+ assert_eq!(intent.policy_action, "block_ambiguous_intent");
255
+ assert!(!intent.allowed);
256
+ }
4
257
 
5
258
  #[test]
6
259
  fn feature_descriptions_with_workflow_terms_route_as_new_tasks() {
@@ -11,7 +264,14 @@ fn feature_descriptions_with_workflow_terms_route_as_new_tasks() {
11
264
  "Implement a review findings feature for pull request analysis.",
12
265
  "Create a baseline flow visualization in the UI.",
13
266
  ] {
14
- let intent = repo.intent(prompt);
267
+ let intent = repo.intent(&prompt_env(
268
+ prompt,
269
+ "implementation",
270
+ "modify_files",
271
+ "none",
272
+ "new_task",
273
+ "none",
274
+ ));
15
275
  assert_eq!(intent.prompt_intent, "new_task", "{prompt}");
16
276
  assert_eq!(intent.policy_action, "create_new_task", "{prompt}");
17
277
  assert!(!intent.evidence.requests_commit, "{prompt}");
@@ -23,8 +283,10 @@ fn feature_descriptions_with_workflow_terms_route_as_new_tasks() {
23
283
  #[test]
24
284
  fn enveloped_workflow_requests_keep_their_intent() {
25
285
  let repo = Repo::clean("intent-v2-direct-workflow", idle());
26
- let commit = repo.intent(&env(
286
+ let commit = repo.intent(&prompt_env(
27
287
  "please commit the current task",
288
+ "implementation",
289
+ "commit",
28
290
  "commit_request",
29
291
  "none",
30
292
  "none",
@@ -32,16 +294,26 @@ fn enveloped_workflow_requests_keep_their_intent() {
32
294
  assert_eq!(commit.prompt_intent, "commit_request");
33
295
  assert!(commit.evidence.requests_commit);
34
296
 
35
- let review = repo.intent(&env(
297
+ let review = repo.intent(&prompt_env_with_actions(
36
298
  "review the current diff",
299
+ "implementation",
300
+ "none",
37
301
  "review_request",
38
302
  "none",
39
303
  "none",
304
+ &["review"],
40
305
  ));
41
306
  assert_eq!(review.prompt_intent, "review_request");
42
307
  assert!(review.evidence.requests_review);
43
308
 
44
- let status = repo.intent(&env("show status", "status_question", "none", "none"));
309
+ let status = repo.intent(&prompt_env(
310
+ "show status",
311
+ "status",
312
+ "none",
313
+ "status_question",
314
+ "none",
315
+ "none",
316
+ ));
45
317
  assert_eq!(status.prompt_intent, "status_question");
46
318
  assert_eq!(status.policy_action, "answer_status_only");
47
319
  }
@@ -49,8 +321,10 @@ fn enveloped_workflow_requests_keep_their_intent() {
49
321
  #[test]
50
322
  fn envelope_is_canonical_over_legacy_prompt_words() {
51
323
  let repo = Repo::clean("intent-v2-envelope-canonical", idle());
52
- let intent = repo.intent(&env(
324
+ let intent = repo.intent(&prompt_env(
53
325
  "please commit the current task",
326
+ "implementation",
327
+ "modify_files",
54
328
  "none",
55
329
  "new_task",
56
330
  "none",
@@ -62,12 +336,25 @@ fn envelope_is_canonical_over_legacy_prompt_words() {
62
336
  #[test]
63
337
  fn token_boundaries_and_code_segments_avoid_false_workflow_actions() {
64
338
  let repo = Repo::clean("intent-v2-segments", idle());
65
- let async_intent = repo.intent("Add async sync-state naming examples to the documentation.");
339
+ let async_intent = repo.intent(&prompt_env(
340
+ "Add async sync-state naming examples to the documentation.",
341
+ "implementation",
342
+ "modify_files",
343
+ "none",
344
+ "new_task",
345
+ "none",
346
+ ));
66
347
  assert_eq!(async_intent.prompt_intent, "new_task");
67
348
  assert!(!async_intent.evidence.requests_repair);
68
349
 
69
- let code_intent =
70
- repo.intent("Add docs with examples like `naome commit` and ```sh\ngit push\n```.");
350
+ let code_intent = repo.intent(&prompt_env(
351
+ "Add docs with examples like `naome commit` and ```sh\ngit push\n```.",
352
+ "implementation",
353
+ "modify_files",
354
+ "none",
355
+ "new_task",
356
+ "none",
357
+ ));
71
358
  assert_eq!(code_intent.prompt_intent, "new_task");
72
359
  assert!(!code_intent.evidence.requests_commit);
73
360
  assert!(!code_intent.evidence.has_risky_terms);
@@ -76,8 +363,10 @@ fn token_boundaries_and_code_segments_avoid_false_workflow_actions() {
76
363
  #[test]
77
364
  fn explicit_credential_context_still_routes_as_unsafe() {
78
365
  let repo = Repo::clean("intent-v2-risk", idle());
79
- let intent = repo.intent(&env(
366
+ let intent = repo.intent(&prompt_env(
80
367
  "Commit the current token and API key so the deployment can use the secret.",
368
+ "implementation",
369
+ "commit",
81
370
  "commit_request",
82
371
  "none",
83
372
  "credential_context",