@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.
- package/Cargo.lock +2 -2
- package/README.md +9 -3
- 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/git.rs +1 -26
- package/crates/naome-core/src/information_architecture.rs +15 -26
- package/crates/naome-core/src/install_plan.rs +2 -0
- 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/lib.rs +1 -0
- package/crates/naome-core/src/paths.rs +27 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +2 -47
- package/crates/naome-core/src/repo_paths.rs +76 -0
- package/crates/naome-core/src/repository_model/path_scan.rs +2 -23
- package/crates/naome-core/src/route/context.rs +8 -0
- package/crates/naome-core/src/task_ledger/write.rs +6 -0
- package/crates/naome-core/src/task_ledger.rs +50 -2
- package/crates/naome-core/src/task_state/util.rs +21 -17
- package/crates/naome-core/tests/context.rs +92 -0
- package/crates/naome-core/tests/decision.rs +2 -10
- package/crates/naome-core/tests/information_architecture.rs +7 -10
- package/crates/naome-core/tests/install_plan.rs +2 -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/quality_structure_support/mod.rs +6 -25
- package/crates/naome-core/tests/repo_support/mod.rs +3 -5
- package/crates/naome-core/tests/repo_support/repo_helpers.rs +2 -9
- package/crates/naome-core/tests/repo_support/routes.rs +8 -2
- package/crates/naome-core/tests/repo_support/verification.rs +1 -8
- package/crates/naome-core/tests/repo_support/verification_values.rs +20 -18
- 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_ledger.rs +69 -1
- package/crates/naome-core/tests/task_state_compact.rs +7 -1
- package/crates/naome-core/tests/task_state_compact_support/states.rs +10 -8
- package/crates/naome-core/tests/task_state_support/states.rs +8 -8
- package/crates/naome-core/tests/workflow_support/mod.rs +2 -0
- 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 +3 -3
- package/templates/naome-root/.naomeignore +1 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +22 -15
- package/templates/naome-root/docs/naome/architecture.md +17 -8
- package/templates/naome-root/docs/naome/task-ledger.md +20 -17
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
69
|
+
errors.push(format!("{field_name} must be a {label}."));
|
|
67
70
|
return;
|
|
68
71
|
};
|
|
69
72
|
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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, "
|
|
25
|
-
assert_eq!(proof.commit_policy, "
|
|
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, "
|
|
33
|
-
assert_eq!(task.commit_policy, "
|
|
34
|
-
assert_eq!(task.restore_policy, "
|
|
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
|
|
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,
|
|
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(
|
|
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(&
|
|
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(&
|
|
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(
|
|
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(&
|
|
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(&
|
|
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(&
|
|
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(&
|
|
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
|
-
|
|
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(&
|
|
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
|
-
"
|
|
101
|
-
"
|
|
102
|
-
|
|
171
|
+
"prompt_normalization_required",
|
|
172
|
+
"normalize_prompt_first",
|
|
173
|
+
true,
|
|
103
174
|
);
|
|
104
175
|
|
|
105
|
-
let risky = Repo::clean("risky", idle()).intent(&
|
|
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
|
-
"
|
|
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(
|
|
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
|
|
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
|
}
|