@lamentis/naome 1.3.7 → 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.
- package/Cargo.lock +2 -2
- package/README.md +5 -0
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/context/select.rs +58 -4
- package/crates/naome-core/src/intent/classifier.rs +56 -81
- package/crates/naome-core/src/intent/envelope.rs +173 -19
- package/crates/naome-core/src/intent/legacy_response.rs +2 -0
- package/crates/naome-core/src/intent/model.rs +6 -0
- package/crates/naome-core/src/intent/resolver.rs +25 -0
- package/crates/naome-core/src/intent/risk.rs +11 -1
- package/crates/naome-core/src/intent.rs +1 -1
- package/crates/naome-core/src/route/context.rs +8 -0
- package/crates/naome-core/tests/context.rs +92 -0
- package/crates/naome-core/tests/intent.rs +98 -18
- package/crates/naome-core/tests/intent_support/mod.rs +39 -1
- package/crates/naome-core/tests/intent_v2.rs +299 -10
- package/crates/naome-core/tests/repo_support/routes.rs +8 -2
- package/crates/naome-core/tests/route_baseline.rs +29 -0
- package/crates/naome-core/tests/route_completion.rs +26 -5
- package/crates/naome-core/tests/route_harness_refresh.rs +7 -1
- package/crates/naome-core/tests/route_user_diff.rs +1 -1
- package/crates/naome-core/tests/task_state_compact.rs +7 -1
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/manifest.json +2 -2
- package/templates/naome-root/docs/naome/agent-workflow.md +14 -5
- package/templates/naome-root/docs/naome/architecture.md +9 -0
- 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::{
|
|
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(
|
|
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(&
|
|
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(&
|
|
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(&
|
|
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(&
|
|
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(
|
|
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
|
-
|
|
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(&
|
|
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",
|
|
@@ -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(),
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(),
|
|
@@ -9,6 +9,12 @@ use repo_support::{
|
|
|
9
9
|
TestRepo,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
fn repair_prompt(prompt: &str) -> String {
|
|
13
|
+
format!(
|
|
14
|
+
"```naome-prompt-envelope-v1\n{{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"fix\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"repair_request\",\"taskIntent\":\"none\",\"risk\":\"none\",\"requestedActions\":[\"repair\"],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}}\n```\n\n{prompt}"
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
#[test]
|
|
13
19
|
fn execute_route_baselines_harness_refresh_before_dirty_repo_worktree() {
|
|
14
20
|
let repo = TestRepo::dirty_harness_refresh_repo("route-dirty-harness-refresh-worktree", true);
|
|
@@ -66,7 +72,7 @@ fn execute_route_repair_request_baselines_harness_refresh_only() {
|
|
|
66
72
|
|
|
67
73
|
let route = evaluate_route(
|
|
68
74
|
repo.path(),
|
|
69
|
-
"please repair all",
|
|
75
|
+
&repair_prompt("please repair all"),
|
|
70
76
|
RouteOptions {
|
|
71
77
|
execute: true,
|
|
72
78
|
evaluation: EvaluationOptions::offline(),
|
|
@@ -27,7 +27,7 @@ fn execute_route_does_not_mutate_or_offer_clear_commit_for_dirty_unowned_diff()
|
|
|
27
27
|
)
|
|
28
28
|
.unwrap();
|
|
29
29
|
|
|
30
|
-
assert_eq!(route.policy_action, "
|
|
30
|
+
assert_eq!(route.policy_action, "normalize_prompt_first");
|
|
31
31
|
assert!(!route.mutation_performed);
|
|
32
32
|
assert!(!route.can_create_task);
|
|
33
33
|
assert_eq!(route.executed_actions, Vec::<String>::new());
|
|
@@ -6,6 +6,12 @@ use task_state_compact_support::{
|
|
|
6
6
|
compact_state, large_compact_state, large_expanded_state, legacy_state, MiniRepo,
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
fn new_task_prompt(prompt: &str) -> String {
|
|
10
|
+
format!(
|
|
11
|
+
"```naome-prompt-envelope-v1\n{{\"schema\":\"naome.prompt-envelope.v1\",\"requestKind\":\"implementation\",\"mutationIntent\":\"modify_files\",\"workflowAction\":\"none\",\"taskIntent\":\"new_task\",\"risk\":\"none\",\"requestedActions\":[],\"referencedPaths\":[],\"constraints\":[],\"uncertainties\":[]}}\n```\n\n{prompt}"
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
#[test]
|
|
10
16
|
fn legacy_v1_proof_results_remain_valid() {
|
|
11
17
|
let repo = MiniRepo::new();
|
|
@@ -30,7 +36,7 @@ fn compact_path_sets_and_batches_cover_completion_commit_and_route() {
|
|
|
30
36
|
|
|
31
37
|
let route = evaluate_route(
|
|
32
38
|
repo.path(),
|
|
33
|
-
"new task",
|
|
39
|
+
&new_task_prompt("new task"),
|
|
34
40
|
RouteOptions {
|
|
35
41
|
execute: false,
|
|
36
42
|
evaluation: EvaluationOptions::offline(),
|
|
Binary file
|
package/native/linux-x64/naome
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"harnessVersion": "1.
|
|
2
|
+
"harnessVersion": "1.3.8",
|
|
3
3
|
"installedAt": null,
|
|
4
4
|
"integrity": {
|
|
5
5
|
".naome/bin/check-harness-health.js": "sha256:dc4de52b79c69600b9ba47b924e2c2b8de61a2cbfab6d1ccc0f1924d963db657",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
9
9
|
".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
|
|
10
10
|
"AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
|
|
11
|
-
"docs/naome/agent-workflow.md": "sha256:
|
|
11
|
+
"docs/naome/agent-workflow.md": "sha256:0be1c29adfbcd3fd73c4f904080ffc67237692fe413871a30243538c4db38ac7",
|
|
12
12
|
"docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
|
|
13
13
|
"docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
|
|
14
14
|
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
@@ -11,11 +11,20 @@ Use this workflow after first-run intake is complete.
|
|
|
11
11
|
`canCreateTask` fields instead of inventing routing or final-response text.
|
|
12
12
|
If route returns a `taskRoot` different from the current directory, continue
|
|
13
13
|
the task from that path and leave the original worktree untouched.
|
|
14
|
-
When preparing the route prompt file,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
When preparing the route prompt file, first normalize the user's raw prompt
|
|
15
|
+
into a fenced `naome-prompt-envelope-v1` JSON envelope. The envelope is the
|
|
16
|
+
routing input; the raw user text below it is audit text. Use canonical NAOME
|
|
17
|
+
schema fields such as `requestKind`, `mutationIntent`,
|
|
18
|
+
`publicationIntent`, `requestedActions`, `referencedPaths`, `constraints`,
|
|
19
|
+
`uncertainties`, `workflowAction`, `taskIntent`, and `risk`. Do not derive
|
|
20
|
+
workflow actions from natural-language keywords, localized words, or the
|
|
21
|
+
prompt's prose language. If a prompt file has no envelope, route returns a
|
|
22
|
+
non-mutating `normalize_prompt_first` decision and the agent must add the
|
|
23
|
+
deterministic envelope before retrying. `naome-intent-v2` is not supported.
|
|
24
|
+
Unknown envelope values require normalization, and conflicting envelope
|
|
25
|
+
fields block as ambiguous instead of mutating. Put concrete files in
|
|
26
|
+
`referencedPaths`; prompt context selection treats those paths as the
|
|
27
|
+
primary source of path intent.
|
|
19
28
|
3. Use `node .naome/bin/naome.js explain --prompt-file <path> --json` only when
|
|
20
29
|
debugging why a policy won.
|
|
21
30
|
4. Run `node .naome/bin/naome.js status --json` when reporting state without
|
|
@@ -18,6 +18,15 @@ Status: Uninitialized
|
|
|
18
18
|
- Per-check proof files are local run evidence. Prefer compact proof batches in
|
|
19
19
|
the committed task-state projection when many release checks share the same
|
|
20
20
|
evidence paths.
|
|
21
|
+
- Prompt routing uses fenced `naome-prompt-envelope-v1` JSON envelopes as the
|
|
22
|
+
deterministic routing input. Raw natural-language prompts are audit text and
|
|
23
|
+
must not be treated as workflow authority until an agent normalizes them into
|
|
24
|
+
canonical fields such as `requestKind`, `mutationIntent`,
|
|
25
|
+
`publicationIntent`, `requestedActions`, `workflowAction`, `taskIntent`, and
|
|
26
|
+
`risk`. Legacy `naome-intent-v2` envelopes are not supported. Unknown
|
|
27
|
+
envelope values require prompt normalization, conflicting fields block as
|
|
28
|
+
ambiguous, and `referencedPaths` is the primary context-selection source for
|
|
29
|
+
prompt-mentioned files.
|
|
21
30
|
|
|
22
31
|
## Assumed Boundaries
|
|
23
32
|
|