@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
@@ -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",
@@ -1,3 +1,5 @@
1
+ #![allow(dead_code)]
2
+
1
3
  use std::fs;
2
4
  use std::path::{Path, PathBuf};
3
5
  use std::process::Command;
@@ -126,31 +128,10 @@ impl StructureFixture {
126
128
  #[allow(dead_code)]
127
129
  impl StructureFixture {
128
130
  pub fn write_structure_config(&self, overrides: serde_json::Value) {
129
- let mut config = json!({
130
- "schema": "naome.repository-structure.v1",
131
- "version": 1,
132
- "status": "ready",
133
- "enabledAdapters": [],
134
- "sourceRoots": ["src/**"],
135
- "testRoots": ["tests/**", "test/**"],
136
- "docsRoots": ["docs/**"],
137
- "generatedRoots": ["**/generated/**"],
138
- "artifactRoots": ["dist/**", "build/**"],
139
- "moduleRoots": ["src/**"],
140
- "allowedRootFiles": ["README.md", "LICENSE", "package.json", "Cargo.toml"],
141
- "directoryRoleRules": [],
142
- "layerRules": [],
143
- "ignoredPaths": [],
144
- "disabledChecks": [],
145
- "changedCodePolicy": "block",
146
- "debtPolicy": "report",
147
- "limits": {
148
- "maxDirectoryFiles": 40,
149
- "maxPathDepth": 10,
150
- "maxDirectoryRoles": 2,
151
- "maxDumpingGroundFiles": 8
152
- }
153
- });
131
+ let mut config: serde_json::Value = serde_json::from_str(include_str!(
132
+ "../../../../templates/naome-root/.naome/repository-structure.json"
133
+ ))
134
+ .unwrap();
154
135
  merge(&mut config, overrides);
155
136
  self.write(
156
137
  ".naome/repository-structure.json",
@@ -14,10 +14,8 @@ pub use routes::{
14
14
  try_route_readme_task,
15
15
  };
16
16
  pub use verification_values::{
17
- change_type, check_missing_last_verified_fixture, diff_check,
17
+ change_type, check_missing_last_verified_fixture, diff_check, minimal_task_state,
18
18
  placeholder_verification_contract_fixture, quality_check, repo_docs_verification_fixture,
19
- repository_quality_config_source,
20
- repository_quality_config_value, repository_semantic_check,
21
- semantic_repository_quality_fixture_source,
22
- verification_value,
19
+ repository_quality_config_source, repository_quality_config_value, repository_semantic_check,
20
+ semantic_repository_quality_fixture_source, verification_value,
23
21
  };
@@ -3,6 +3,7 @@ use std::fs;
3
3
  use serde_json::json;
4
4
 
5
5
  use super::repo::TestRepo;
6
+ use super::verification_values::minimal_task_state;
6
7
 
7
8
  impl TestRepo {
8
9
  pub fn product_quality_repo(name: &str) -> Self {
@@ -69,15 +70,7 @@ impl TestRepo {
69
70
  pub fn write_implementing_task_state(&self, allowed_path: &str, check_id: &str) {
70
71
  self.write_naome_json(
71
72
  "task-state.json",
72
- json!({
73
- "status": "implementing",
74
- "activeTask": {
75
- "id": "rust-task",
76
- "allowedPaths": [allowed_path],
77
- "requiredCheckIds": [check_id],
78
- "proofResults": []
79
- }
80
- }),
73
+ minimal_task_state("implementing", "rust-task", allowed_path, check_id),
81
74
  );
82
75
  }
83
76
 
@@ -6,10 +6,16 @@ use super::TestRepo;
6
6
 
7
7
  const NEW_README_TASK_PROMPT: &str = "Add another line to README as a new task.";
8
8
 
9
+ pub fn prompt_env(prompt: &str, workflow: &str, task: &str, mutation_intent: &str) -> String {
10
+ format!(
11
+ "```naome-prompt-envelope-v1\n{{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"{mutation_intent}\",\"workflowAction\":\"{workflow}\",\"taskIntent\":\"{task}\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}}\n```\n\n{prompt}"
12
+ )
13
+ }
14
+
9
15
  pub fn route_commit_request(repo: &TestRepo) -> RouteDecision {
10
16
  evaluate_route(
11
17
  repo.path(),
12
- "commit my changes",
18
+ &prompt_env("commit my changes", "commit_request", "none", "commit"),
13
19
  RouteOptions {
14
20
  execute: true,
15
21
  evaluation: EvaluationOptions::offline(),
@@ -29,7 +35,7 @@ pub fn try_route_new_task(
29
35
  ) -> Result<RouteDecision, NaomeError> {
30
36
  evaluate_route(
31
37
  repo.path(),
32
- prompt,
38
+ &prompt_env(prompt, "none", "new_task", "modify_files"),
33
39
  RouteOptions {
34
40
  execute,
35
41
  evaluation: EvaluationOptions::offline(),
@@ -15,14 +15,7 @@ impl TestRepo {
15
15
  self.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
16
16
  self.write_naome_json(
17
17
  "verification.json",
18
- json!({
19
- "schema": "naome.verification.v1",
20
- "version": 1,
21
- "status": "ready",
22
- "checks": [diff_check(vec!["README.md"])],
23
- "changeTypes": [],
24
- "releaseGates": []
25
- }),
18
+ verification_value("ready", vec![diff_check(vec!["README.md"])], vec![]),
26
19
  );
27
20
  self.write_naome_json("task-state.json", task_state);
28
21
  }
@@ -74,28 +74,30 @@ pub fn verification_value(
74
74
  })
75
75
  }
76
76
 
77
- pub fn repository_quality_config_value() -> serde_json::Value {
77
+ pub fn minimal_task_state(
78
+ status: &str,
79
+ task_id: &str,
80
+ allowed_path: &str,
81
+ check_id: &str,
82
+ ) -> serde_json::Value {
78
83
  json!({
79
- "schema": "naome.repository-quality.v1",
80
- "version": 1,
81
- "status": "ready",
82
- "limits": {
83
- "maxFileLines": 450,
84
- "maxNewFileLines": 300,
85
- "maxDiffAddedLines": 180,
86
- "maxFunctionLines": 100,
87
- "maxTopLevelSymbols": 30,
88
- "duplicateBlockLines": 10,
89
- "nearDuplicateSimilarity": 0.9
90
- },
91
- "enabledAdapters": [],
92
- "disabledChecks": [],
93
- "ignoredPaths": [],
94
- "generatedPaths": [],
95
- "pathRules": []
84
+ "status": status,
85
+ "activeTask": {
86
+ "id": task_id,
87
+ "allowedPaths": [allowed_path],
88
+ "requiredCheckIds": [check_id],
89
+ "proofResults": []
90
+ }
96
91
  })
97
92
  }
98
93
 
94
+ pub fn repository_quality_config_value() -> serde_json::Value {
95
+ serde_json::from_str(include_str!(
96
+ "../../../../templates/naome-root/.naome/repository-quality.json"
97
+ ))
98
+ .unwrap()
99
+ }
100
+
99
101
  pub fn repository_quality_config_source() -> String {
100
102
  format!(
101
103
  "{}\n",
@@ -3,11 +3,40 @@ use std::fs;
3
3
 
4
4
  mod repo_support;
5
5
 
6
+ use naome_core::{evaluate_route, EvaluationOptions, RouteOptions};
6
7
  use repo_support::{
7
8
  assert_commit_paths, assert_isolated_worktree_ready, route_new_task, route_readme_task,
8
9
  TestRepo,
9
10
  };
10
11
 
12
+ #[test]
13
+ fn execute_route_with_raw_prompt_requests_normalization_without_mutating() {
14
+ let repo = TestRepo::new("route-raw-prompt-normalize-first");
15
+ repo.init_git();
16
+ repo.write_file("README.md", "# Baseline\n");
17
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
18
+ repo.git(&["add", "."]);
19
+ repo.git(&["commit", "-m", "baseline"]);
20
+ let before = repo.git_stdout(&["rev-parse", "HEAD"]);
21
+
22
+ let route = evaluate_route(
23
+ repo.path(),
24
+ "Please implement, commit, push, and create the MR.",
25
+ RouteOptions {
26
+ execute: true,
27
+ evaluation: EvaluationOptions::offline(),
28
+ },
29
+ )
30
+ .unwrap();
31
+
32
+ assert_eq!(route.prompt_intent, "prompt_normalization_required");
33
+ assert_eq!(route.policy_action, "normalize_prompt_first");
34
+ assert!(!route.mutation_performed);
35
+ assert!(!route.can_create_task);
36
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
37
+ assert!(repo.git_status_short().is_empty());
38
+ }
39
+
11
40
  #[test]
12
41
  fn dry_route_reports_auto_baseline_without_mutating() {
13
42
  let repo = TestRepo::completed_task_with_diff("route-dry-auto");
@@ -7,6 +7,12 @@ mod repo_support;
7
7
 
8
8
  use repo_support::TestRepo;
9
9
 
10
+ fn route_prompt(prompt: &str, workflow: &str, task: &str, mutation_intent: &str) -> String {
11
+ format!(
12
+ "```naome-prompt-envelope-v1\n{{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"{mutation_intent}\",\"workflowAction\":\"{workflow}\",\"taskIntent\":\"{task}\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}}\n```\n\n{prompt}"
13
+ )
14
+ }
15
+
10
16
  #[test]
11
17
  fn execute_route_does_not_mutate_when_prompt_blocks_commit() {
12
18
  let repo = TestRepo::completed_task_with_diff("route-no-commit");
@@ -14,7 +20,12 @@ fn execute_route_does_not_mutate_when_prompt_blocks_commit() {
14
20
 
15
21
  let route = evaluate_route(
16
22
  repo.path(),
17
- "Do not commit. Start a new task after this.",
23
+ &route_prompt(
24
+ "Do not commit. Start a new task after this.",
25
+ "no_commit_request",
26
+ "new_task",
27
+ "none",
28
+ ),
18
29
  RouteOptions {
19
30
  execute: true,
20
31
  evaluation: EvaluationOptions::offline(),
@@ -41,7 +52,7 @@ fn explicit_route_commit_baseline_leaves_unrelated_user_edit_unstaged() {
41
52
 
42
53
  let route = evaluate_route(
43
54
  repo.path(),
44
- "commit_task_baseline",
55
+ &route_prompt("commit_task_baseline", "commit_request", "none", "commit"),
45
56
  RouteOptions {
46
57
  execute: true,
47
58
  evaluation: EvaluationOptions::offline(),
@@ -67,7 +78,12 @@ fn execute_route_journals_external_commit_after_completed_task() {
67
78
 
68
79
  let route = evaluate_route(
69
80
  repo.path(),
70
- "Create a new task for README polish.",
81
+ &route_prompt(
82
+ "Create a new task for README polish.",
83
+ "none",
84
+ "new_task",
85
+ "modify_files",
86
+ ),
71
87
  RouteOptions {
72
88
  execute: true,
73
89
  evaluation: EvaluationOptions::offline(),
@@ -94,7 +110,12 @@ fn explain_reports_winning_rule_and_mutation_plan_without_executing() {
94
110
 
95
111
  let explain = explain_route(
96
112
  repo.path(),
97
- "Start a new task for README polish.",
113
+ &route_prompt(
114
+ "Start a new task for README polish.",
115
+ "none",
116
+ "new_task",
117
+ "modify_files",
118
+ ),
98
119
  EvaluationOptions::offline(),
99
120
  )
100
121
  .unwrap();
@@ -126,7 +147,7 @@ fn unhealthy_harness_route_blocks_normal_work() {
126
147
 
127
148
  let route = evaluate_route(
128
149
  repo.path(),
129
- "Create a new task.",
150
+ &route_prompt("Create a new task.", "none", "new_task", "modify_files"),
130
151
  RouteOptions {
131
152
  execute: true,
132
153
  evaluation: EvaluationOptions::online(),