@lamentis/naome 1.1.2 → 1.2.0
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/Cargo.toml +1 -1
- package/LICENSE +180 -21
- package/README.md +49 -6
- package/bin/naome.js +54 -16
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/check_commands.rs +135 -0
- package/crates/naome-cli/src/cli_args.rs +5 -0
- package/crates/naome-cli/src/dispatcher.rs +36 -0
- package/crates/naome-cli/src/install_bridge.rs +83 -0
- package/crates/naome-cli/src/main.rs +57 -341
- package/crates/naome-cli/src/prompt_commands.rs +68 -0
- package/crates/naome-cli/src/quality_commands.rs +141 -0
- package/crates/naome-cli/src/simple_commands.rs +53 -0
- package/crates/naome-cli/src/workflow_commands.rs +153 -0
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/harness_health/integrity.rs +96 -0
- package/crates/naome-core/src/harness_health.rs +14 -126
- package/crates/naome-core/src/install_plan.rs +3 -0
- package/crates/naome-core/src/intent/classifier.rs +171 -0
- package/crates/naome-core/src/intent/envelope.rs +108 -0
- package/crates/naome-core/src/intent/legacy.rs +138 -0
- package/crates/naome-core/src/intent/legacy_response.rs +76 -0
- package/crates/naome-core/src/intent/model.rs +71 -0
- package/crates/naome-core/src/intent/patterns.rs +170 -0
- package/crates/naome-core/src/intent/resolver.rs +162 -0
- package/crates/naome-core/src/intent/resolver_active.rs +17 -0
- package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
- package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
- package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
- package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
- package/crates/naome-core/src/intent/risk.rs +40 -0
- package/crates/naome-core/src/intent/segment.rs +170 -0
- package/crates/naome-core/src/intent.rs +64 -879
- package/crates/naome-core/src/journal.rs +9 -20
- package/crates/naome-core/src/lib.rs +13 -0
- package/crates/naome-core/src/quality/adapters.rs +178 -0
- package/crates/naome-core/src/quality/baseline.rs +75 -0
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
- package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
- package/crates/naome-core/src/quality/checks.rs +228 -0
- package/crates/naome-core/src/quality/cleanup.rs +72 -0
- package/crates/naome-core/src/quality/config.rs +109 -0
- package/crates/naome-core/src/quality/mod.rs +90 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
- package/crates/naome-core/src/quality/scanner.rs +367 -0
- package/crates/naome-core/src/quality/types.rs +289 -0
- package/crates/naome-core/src/route.rs +62 -0
- package/crates/naome-core/src/task_state/admission.rs +63 -0
- package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
- package/crates/naome-core/src/task_state/api.rs +130 -0
- package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
- package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
- package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
- package/crates/naome-core/src/task_state/completion.rs +72 -0
- package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
- package/crates/naome-core/src/task_state/diff.rs +95 -0
- package/crates/naome-core/src/task_state/evidence.rs +154 -0
- package/crates/naome-core/src/task_state/git_io.rs +86 -0
- package/crates/naome-core/src/task_state/git_parse.rs +86 -0
- package/crates/naome-core/src/task_state/git_refs.rs +37 -0
- package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
- package/crates/naome-core/src/task_state/mod.rs +38 -0
- package/crates/naome-core/src/task_state/process_guard.rs +40 -0
- package/crates/naome-core/src/task_state/progress.rs +123 -0
- package/crates/naome-core/src/task_state/proof.rs +139 -0
- package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
- package/crates/naome-core/src/task_state/proof_model.rs +70 -0
- package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
- package/crates/naome-core/src/task_state/push_gate.rs +49 -0
- package/crates/naome-core/src/task_state/reconcile.rs +7 -0
- package/crates/naome-core/src/task_state/repair.rs +168 -0
- package/crates/naome-core/src/task_state/shape.rs +117 -0
- package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
- package/crates/naome-core/src/task_state/task_records.rs +131 -0
- package/crates/naome-core/src/task_state/task_references.rs +126 -0
- package/crates/naome-core/src/task_state/types.rs +87 -0
- package/crates/naome-core/src/task_state/util.rs +137 -0
- package/crates/naome-core/src/verification/render.rs +122 -0
- package/crates/naome-core/src/verification.rs +176 -58
- package/crates/naome-core/src/verification_contract.rs +49 -21
- package/crates/naome-core/src/workflow/integrity.rs +123 -0
- package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
- package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
- package/crates/naome-core/src/workflow/mod.rs +18 -0
- package/crates/naome-core/src/workflow/mutation.rs +68 -0
- package/crates/naome-core/src/workflow/output.rs +111 -0
- package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
- package/crates/naome-core/src/workflow/phases.rs +169 -0
- package/crates/naome-core/src/workflow/policy.rs +156 -0
- package/crates/naome-core/src/workflow/processes.rs +91 -0
- package/crates/naome-core/src/workflow/types.rs +42 -0
- package/crates/naome-core/tests/harness_health.rs +3 -0
- package/crates/naome-core/tests/intent.rs +97 -792
- package/crates/naome-core/tests/intent_support/mod.rs +133 -0
- package/crates/naome-core/tests/intent_v2.rs +90 -0
- package/crates/naome-core/tests/quality.rs +425 -0
- package/crates/naome-core/tests/route.rs +88 -188
- package/crates/naome-core/tests/task_state.rs +3 -0
- package/crates/naome-core/tests/task_state_compact.rs +110 -0
- package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
- package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
- package/crates/naome-core/tests/workflow_integrity.rs +85 -0
- package/crates/naome-core/tests/workflow_policy.rs +139 -0
- package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +2 -2
- package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
- package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
- package/templates/naome-root/.naome/bin/naome.js +34 -63
- package/templates/naome-root/.naome/manifest.json +20 -18
- package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
- package/templates/naome-root/.naome/repository-quality.json +24 -0
- package/templates/naome-root/.naome/task-contract.schema.json +93 -11
- package/templates/naome-root/.naome/upgrade-state.json +1 -1
- package/templates/naome-root/.naome/verification.json +37 -0
- package/templates/naome-root/AGENTS.md +3 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
- package/templates/naome-root/docs/naome/execution.md +25 -21
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +43 -0
- package/templates/naome-root/docs/naome/testing.md +12 -0
- package/crates/naome-core/src/task_state.rs +0 -2210
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
|
|
3
|
+
use super::envelope::parse_routing_envelope;
|
|
4
|
+
use super::model::{CanonicalIntent, IntentCandidate, IntentKind, PromptSegment, SegmentKind};
|
|
5
|
+
use super::patterns;
|
|
6
|
+
use super::risk::detect_risk;
|
|
7
|
+
use super::segment::{actionable_text, segment_prompt};
|
|
8
|
+
|
|
9
|
+
const DIRECT_WORKFLOW_CONFIDENCE: u8 = 90;
|
|
10
|
+
const NEW_TASK_CONFIDENCE: u8 = 80;
|
|
11
|
+
const REVISION_CONFIDENCE: u8 = 78;
|
|
12
|
+
const GENERIC_WORK_CONFIDENCE: u8 = 50;
|
|
13
|
+
|
|
14
|
+
pub(crate) fn canonical_intent(prompt: &str) -> CanonicalIntent {
|
|
15
|
+
let segments = segment_prompt(prompt);
|
|
16
|
+
let actionable = actionable_text(&segments);
|
|
17
|
+
let mut candidates = classify_candidates(&segments);
|
|
18
|
+
let mut risk = detect_risk(&segments);
|
|
19
|
+
|
|
20
|
+
if let Some(envelope) = parse_routing_envelope(prompt) {
|
|
21
|
+
candidates.clear();
|
|
22
|
+
candidates.extend(envelope.candidates);
|
|
23
|
+
risk.risk_codes.extend(envelope.risk.risk_codes);
|
|
24
|
+
risk.risk_codes.sort();
|
|
25
|
+
risk.risk_codes.dedup();
|
|
26
|
+
risk.has_risky_terms = !risk.risk_codes.is_empty();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
candidates = dedupe_candidates(candidates);
|
|
30
|
+
let references_current_task = has_candidate_kind(&candidates, IntentKind::TaskRevision);
|
|
31
|
+
if risk.has_risky_terms {
|
|
32
|
+
candidates.push(IntentCandidate {
|
|
33
|
+
kind: IntentKind::Unsafe,
|
|
34
|
+
confidence: 100,
|
|
35
|
+
evidence: risk.risk_codes.join(","),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
CanonicalIntent {
|
|
40
|
+
is_empty: actionable.trim().is_empty(),
|
|
41
|
+
references_current_task,
|
|
42
|
+
has_workflow_conflict: has_workflow_conflict(&candidates),
|
|
43
|
+
candidates,
|
|
44
|
+
risk,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub(crate) fn winning_intent(canonical: &CanonicalIntent) -> IntentKind {
|
|
49
|
+
if canonical.is_empty || canonical.has_workflow_conflict {
|
|
50
|
+
return IntentKind::Ambiguous;
|
|
51
|
+
}
|
|
52
|
+
for kind in [
|
|
53
|
+
IntentKind::Unsafe,
|
|
54
|
+
IntentKind::StatusQuestion,
|
|
55
|
+
IntentKind::RepairRequest,
|
|
56
|
+
IntentKind::CancelRequest,
|
|
57
|
+
IntentKind::ReviewRequest,
|
|
58
|
+
IntentKind::NoCommitRequest,
|
|
59
|
+
IntentKind::CommitRequest,
|
|
60
|
+
IntentKind::NewTask,
|
|
61
|
+
IntentKind::TaskRevision,
|
|
62
|
+
IntentKind::TaskCompletion,
|
|
63
|
+
] {
|
|
64
|
+
if has_candidate(canonical, kind) {
|
|
65
|
+
return kind;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
IntentKind::Ambiguous
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub(crate) fn has_candidate(canonical: &CanonicalIntent, kind: IntentKind) -> bool {
|
|
72
|
+
canonical
|
|
73
|
+
.candidates
|
|
74
|
+
.iter()
|
|
75
|
+
.any(|candidate| candidate.kind == kind)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn classify_candidates(segments: &[PromptSegment]) -> Vec<IntentCandidate> {
|
|
79
|
+
let mut candidates = Vec::new();
|
|
80
|
+
for segment in action_segments(segments) {
|
|
81
|
+
classify_direct_workflow(&mut candidates, segment);
|
|
82
|
+
classify_task_work(&mut candidates, &segment.text);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if candidates.is_empty() {
|
|
86
|
+
let actionable = actionable_text(segments);
|
|
87
|
+
if patterns::is_generic_work_request(&actionable) {
|
|
88
|
+
push(
|
|
89
|
+
&mut candidates,
|
|
90
|
+
IntentKind::NewTask,
|
|
91
|
+
GENERIC_WORK_CONFIDENCE,
|
|
92
|
+
"generic_work_request",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
dedupe_candidates(candidates)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn classify_direct_workflow(candidates: &mut Vec<IntentCandidate>, segment: &PromptSegment) {
|
|
101
|
+
let intents = if segment.kind == SegmentKind::CommandLike {
|
|
102
|
+
patterns::command_workflow_intents(&segment.text)
|
|
103
|
+
} else {
|
|
104
|
+
patterns::structured_workflow_intents(&segment.text)
|
|
105
|
+
};
|
|
106
|
+
for intent in intents {
|
|
107
|
+
push(
|
|
108
|
+
candidates,
|
|
109
|
+
intent,
|
|
110
|
+
DIRECT_WORKFLOW_CONFIDENCE,
|
|
111
|
+
&patterns::normalized(&segment.text),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn classify_task_work(candidates: &mut Vec<IntentCandidate>, text: &str) {
|
|
117
|
+
if let Some(intent) = patterns::explicit_task_intent(text) {
|
|
118
|
+
let confidence = if intent == IntentKind::NewTask {
|
|
119
|
+
NEW_TASK_CONFIDENCE
|
|
120
|
+
} else {
|
|
121
|
+
REVISION_CONFIDENCE
|
|
122
|
+
};
|
|
123
|
+
push(candidates, intent, confidence, &patterns::normalized(text));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn has_candidate_kind(candidates: &[IntentCandidate], kind: IntentKind) -> bool {
|
|
128
|
+
candidates.iter().any(|candidate| candidate.kind == kind)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fn has_workflow_conflict(candidates: &[IntentCandidate]) -> bool {
|
|
132
|
+
let workflow = candidates
|
|
133
|
+
.iter()
|
|
134
|
+
.filter(|candidate| candidate.confidence >= DIRECT_WORKFLOW_CONFIDENCE)
|
|
135
|
+
.filter(|candidate| {
|
|
136
|
+
!matches!(
|
|
137
|
+
candidate.kind,
|
|
138
|
+
IntentKind::NewTask | IntentKind::TaskRevision | IntentKind::Unsafe
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
.map(|candidate| candidate.kind)
|
|
142
|
+
.collect::<HashSet<_>>();
|
|
143
|
+
workflow.len() > 1
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn action_segments(segments: &[PromptSegment]) -> impl Iterator<Item = &PromptSegment> {
|
|
147
|
+
segments.iter().filter(|segment| {
|
|
148
|
+
matches!(
|
|
149
|
+
segment.kind,
|
|
150
|
+
super::model::SegmentKind::Prose
|
|
151
|
+
| super::model::SegmentKind::ListItem
|
|
152
|
+
| super::model::SegmentKind::CommandLike
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn push(candidates: &mut Vec<IntentCandidate>, kind: IntentKind, confidence: u8, text: &str) {
|
|
158
|
+
candidates.push(IntentCandidate {
|
|
159
|
+
kind,
|
|
160
|
+
confidence,
|
|
161
|
+
evidence: text.to_string(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn dedupe_candidates(candidates: Vec<IntentCandidate>) -> Vec<IntentCandidate> {
|
|
166
|
+
let mut seen = HashSet::new();
|
|
167
|
+
candidates
|
|
168
|
+
.into_iter()
|
|
169
|
+
.filter(|candidate| seen.insert((candidate.kind, candidate.evidence.clone())))
|
|
170
|
+
.collect()
|
|
171
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
use serde::Deserialize;
|
|
2
|
+
|
|
3
|
+
use super::model::{IntentCandidate, IntentKind, RiskContext};
|
|
4
|
+
|
|
5
|
+
const ENVELOPE_FENCE: &str = "```naome-intent-v2";
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
8
|
+
pub(crate) struct RoutingEnvelope {
|
|
9
|
+
pub candidates: Vec<IntentCandidate>,
|
|
10
|
+
pub risk: RiskContext,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Deserialize)]
|
|
14
|
+
#[serde(rename_all = "camelCase")]
|
|
15
|
+
struct EnvelopeInput {
|
|
16
|
+
schema: Option<String>,
|
|
17
|
+
workflow_action: Option<String>,
|
|
18
|
+
task_intent: Option<String>,
|
|
19
|
+
risk: Option<String>,
|
|
20
|
+
risk_codes: Option<Vec<String>>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub(crate) fn parse_routing_envelope(prompt: &str) -> Option<RoutingEnvelope> {
|
|
24
|
+
let json = envelope_json(prompt)?;
|
|
25
|
+
let input = serde_json::from_str::<EnvelopeInput>(json).ok()?;
|
|
26
|
+
if !matches!(input.schema.as_deref(), Some("naome.intent.v2")) {
|
|
27
|
+
return None;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let mut candidates = Vec::new();
|
|
31
|
+
if let Some(kind) = workflow_action(input.workflow_action.as_deref()) {
|
|
32
|
+
candidates.push(candidate(kind, "envelope.workflowAction"));
|
|
33
|
+
}
|
|
34
|
+
if let Some(kind) = task_intent(input.task_intent.as_deref()) {
|
|
35
|
+
candidates.push(candidate(kind, "envelope.taskIntent"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let risk_codes = envelope_risk_codes(input.risk.as_deref(), input.risk_codes);
|
|
39
|
+
let risk = RiskContext {
|
|
40
|
+
has_risky_terms: !risk_codes.is_empty(),
|
|
41
|
+
risk_codes,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Some(RoutingEnvelope { candidates, risk })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn envelope_json(prompt: &str) -> Option<&str> {
|
|
48
|
+
let start = prompt.find(ENVELOPE_FENCE)?;
|
|
49
|
+
let after_open = &prompt[start + ENVELOPE_FENCE.len()..];
|
|
50
|
+
let after_line = after_open
|
|
51
|
+
.strip_prefix('\n')
|
|
52
|
+
.or_else(|| after_open.strip_prefix("\r\n"))?;
|
|
53
|
+
let end = after_line.find("\n```")?;
|
|
54
|
+
Some(after_line[..end].trim())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn workflow_action(value: Option<&str>) -> Option<IntentKind> {
|
|
58
|
+
match normalized_value(value)?.as_str() {
|
|
59
|
+
"none" | "unknown" => None,
|
|
60
|
+
"commit_request" => Some(IntentKind::CommitRequest),
|
|
61
|
+
"review_request" => Some(IntentKind::ReviewRequest),
|
|
62
|
+
"repair_request" => Some(IntentKind::RepairRequest),
|
|
63
|
+
"cancel_request" => Some(IntentKind::CancelRequest),
|
|
64
|
+
"task_completion" => Some(IntentKind::TaskCompletion),
|
|
65
|
+
"status_question" => Some(IntentKind::StatusQuestion),
|
|
66
|
+
"no_commit_request" => Some(IntentKind::NoCommitRequest),
|
|
67
|
+
_ => None,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn task_intent(value: Option<&str>) -> Option<IntentKind> {
|
|
72
|
+
match normalized_value(value)?.as_str() {
|
|
73
|
+
"none" | "unknown" => None,
|
|
74
|
+
"new_task" => Some(IntentKind::NewTask),
|
|
75
|
+
"task_revision" => Some(IntentKind::TaskRevision),
|
|
76
|
+
_ => None,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn envelope_risk_codes(risk: Option<&str>, risk_codes: Option<Vec<String>>) -> Vec<String> {
|
|
81
|
+
let mut codes = risk_codes.unwrap_or_default();
|
|
82
|
+
match normalized_value(risk).as_deref() {
|
|
83
|
+
Some("none") | Some("unknown") | None => {}
|
|
84
|
+
Some("unsafe") | Some("unsafe_context") => codes.push("envelope_risk:unsafe".to_string()),
|
|
85
|
+
Some("credential_context") => {
|
|
86
|
+
codes.push("envelope_risk:credential_context".to_string());
|
|
87
|
+
}
|
|
88
|
+
Some("bypass_context") => codes.push("envelope_risk:bypass_context".to_string()),
|
|
89
|
+
Some(other) => codes.push(format!("envelope_risk:{other}")),
|
|
90
|
+
}
|
|
91
|
+
codes.sort();
|
|
92
|
+
codes.dedup();
|
|
93
|
+
codes
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn normalized_value(value: Option<&str>) -> Option<String> {
|
|
97
|
+
value
|
|
98
|
+
.map(|value| value.trim().to_ascii_lowercase())
|
|
99
|
+
.filter(|value| !value.is_empty())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn candidate(kind: IntentKind, evidence: &str) -> IntentCandidate {
|
|
103
|
+
IntentCandidate {
|
|
104
|
+
kind,
|
|
105
|
+
confidence: 100,
|
|
106
|
+
evidence: evidence.to_string(),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
use serde::Serialize;
|
|
2
|
+
|
|
3
|
+
use crate::models::Decision;
|
|
4
|
+
|
|
5
|
+
use super::classifier::{has_candidate, winning_intent};
|
|
6
|
+
use super::legacy_response::response_policy;
|
|
7
|
+
use super::model::{CanonicalIntent, IntentKind};
|
|
8
|
+
use super::resolver::ResolvedIntent;
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
11
|
+
#[serde(rename_all = "camelCase")]
|
|
12
|
+
pub struct IntentDecision {
|
|
13
|
+
pub schema: String,
|
|
14
|
+
pub repo_state: String,
|
|
15
|
+
pub blocked: bool,
|
|
16
|
+
pub prompt_intent: String,
|
|
17
|
+
pub certainty: String,
|
|
18
|
+
pub policy_action: String,
|
|
19
|
+
pub allowed: bool,
|
|
20
|
+
pub summary: String,
|
|
21
|
+
pub next_action: String,
|
|
22
|
+
pub user_message: String,
|
|
23
|
+
pub human_options: Vec<String>,
|
|
24
|
+
pub internal_notes: Vec<String>,
|
|
25
|
+
pub reason_codes: Vec<String>,
|
|
26
|
+
pub risk_codes: Vec<String>,
|
|
27
|
+
pub changed_paths: Vec<String>,
|
|
28
|
+
pub required_context: Vec<String>,
|
|
29
|
+
pub evidence: PromptEvidence,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
33
|
+
#[serde(rename_all = "camelCase")]
|
|
34
|
+
pub struct PromptEvidence {
|
|
35
|
+
pub references_current_task: bool,
|
|
36
|
+
pub requests_new_goal: bool,
|
|
37
|
+
pub requests_correction: bool,
|
|
38
|
+
pub requests_status: bool,
|
|
39
|
+
pub requests_review: bool,
|
|
40
|
+
pub requests_repair: bool,
|
|
41
|
+
pub requests_commit: bool,
|
|
42
|
+
pub requests_no_commit: bool,
|
|
43
|
+
pub requests_cancel: bool,
|
|
44
|
+
pub requests_completion: bool,
|
|
45
|
+
pub has_risky_terms: bool,
|
|
46
|
+
pub is_empty: bool,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub(crate) fn legacy_decision(
|
|
50
|
+
decision: Decision,
|
|
51
|
+
canonical: CanonicalIntent,
|
|
52
|
+
resolved: ResolvedIntent,
|
|
53
|
+
) -> IntentDecision {
|
|
54
|
+
let evidence = PromptEvidence::from_canonical(&canonical);
|
|
55
|
+
let (user_message, human_options, mut internal_notes) = response_policy(
|
|
56
|
+
&decision,
|
|
57
|
+
resolved.prompt_intent.as_str(),
|
|
58
|
+
&resolved.policy_action,
|
|
59
|
+
&resolved.summary,
|
|
60
|
+
);
|
|
61
|
+
for code in &resolved.reason_codes {
|
|
62
|
+
if !internal_notes.contains(code) {
|
|
63
|
+
internal_notes.push(code.clone());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
IntentDecision {
|
|
68
|
+
schema: "naome.intent.v1".to_string(),
|
|
69
|
+
repo_state: decision.state,
|
|
70
|
+
blocked: decision.blocked,
|
|
71
|
+
prompt_intent: resolved.prompt_intent.as_str().to_string(),
|
|
72
|
+
certainty: resolved.certainty,
|
|
73
|
+
policy_action: resolved.policy_action,
|
|
74
|
+
allowed: resolved.allowed,
|
|
75
|
+
summary: resolved.summary,
|
|
76
|
+
next_action: resolved.next_action,
|
|
77
|
+
user_message,
|
|
78
|
+
human_options,
|
|
79
|
+
internal_notes,
|
|
80
|
+
reason_codes: resolved.reason_codes,
|
|
81
|
+
risk_codes: resolved.risk_codes,
|
|
82
|
+
changed_paths: decision.changed_paths,
|
|
83
|
+
required_context: decision.required_context,
|
|
84
|
+
evidence,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fn format_intent(intent: &IntentDecision) -> String {
|
|
89
|
+
let mut lines = vec![
|
|
90
|
+
format!("NAOME intent: {}", intent.prompt_intent),
|
|
91
|
+
format!("Repo state: {}", intent.repo_state),
|
|
92
|
+
format!("Allowed: {}", intent.allowed),
|
|
93
|
+
format!("Policy action: {}", intent.policy_action),
|
|
94
|
+
format!("Summary: {}", intent.user_message),
|
|
95
|
+
format!("Next action: {}", intent.next_action),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if !intent.human_options.is_empty() {
|
|
99
|
+
lines.push(format!(
|
|
100
|
+
"Human options: {}",
|
|
101
|
+
intent.human_options.join(", ")
|
|
102
|
+
));
|
|
103
|
+
}
|
|
104
|
+
if !intent.reason_codes.is_empty() {
|
|
105
|
+
lines.push(format!("Reason codes: {}", intent.reason_codes.join(", ")));
|
|
106
|
+
}
|
|
107
|
+
if !intent.risk_codes.is_empty() {
|
|
108
|
+
lines.push(format!("Risk codes: {}", intent.risk_codes.join(", ")));
|
|
109
|
+
}
|
|
110
|
+
if !intent.changed_paths.is_empty() {
|
|
111
|
+
lines.push(format!(
|
|
112
|
+
"Changed paths: {}",
|
|
113
|
+
intent.changed_paths.join(", ")
|
|
114
|
+
));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
lines.push(String::new());
|
|
118
|
+
lines.join("\n")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl PromptEvidence {
|
|
122
|
+
fn from_canonical(canonical: &CanonicalIntent) -> Self {
|
|
123
|
+
Self {
|
|
124
|
+
references_current_task: canonical.references_current_task,
|
|
125
|
+
requests_new_goal: has_candidate(canonical, IntentKind::NewTask),
|
|
126
|
+
requests_correction: has_candidate(canonical, IntentKind::TaskRevision),
|
|
127
|
+
requests_status: winning_intent(canonical) == IntentKind::StatusQuestion,
|
|
128
|
+
requests_review: has_candidate(canonical, IntentKind::ReviewRequest),
|
|
129
|
+
requests_repair: has_candidate(canonical, IntentKind::RepairRequest),
|
|
130
|
+
requests_commit: has_candidate(canonical, IntentKind::CommitRequest),
|
|
131
|
+
requests_no_commit: has_candidate(canonical, IntentKind::NoCommitRequest),
|
|
132
|
+
requests_cancel: has_candidate(canonical, IntentKind::CancelRequest),
|
|
133
|
+
requests_completion: has_candidate(canonical, IntentKind::TaskCompletion),
|
|
134
|
+
has_risky_terms: canonical.risk.has_risky_terms,
|
|
135
|
+
is_empty: canonical.is_empty,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use crate::models::Decision;
|
|
2
|
+
|
|
3
|
+
pub(crate) fn response_policy(
|
|
4
|
+
decision: &Decision,
|
|
5
|
+
prompt_intent: &str,
|
|
6
|
+
policy_action: &str,
|
|
7
|
+
summary: &str,
|
|
8
|
+
) -> (String, Vec<String>, Vec<String>) {
|
|
9
|
+
let internal_notes = response_notes(decision, prompt_intent, policy_action);
|
|
10
|
+
let human_options = response_human_options(decision, policy_action);
|
|
11
|
+
let user_message = match policy_action {
|
|
12
|
+
"auto_commit_completed_task_then_create_new_task" => "The completed task is valid. NAOME can baseline it automatically before starting the next separate task.",
|
|
13
|
+
"auto_commit_completed_task_then_create_isolated_task_worktree" => "The completed task is valid. NAOME can baseline only that task, preserve unrelated user edits, and create an isolated worktree for the next task.",
|
|
14
|
+
"auto_commit_harness_refresh_then_create_new_task" => "NAOME can baseline deterministic harness refresh changes first, then create the next task in the same worktree.",
|
|
15
|
+
"auto_commit_harness_refresh_then_create_isolated_task_worktree" => "NAOME can baseline deterministic harness refresh changes first, then create an isolated task worktree and leave user edits untouched.",
|
|
16
|
+
"auto_commit_harness_refresh_baseline" => "NAOME can baseline deterministic harness refresh changes and leave unrelated user edits untouched.",
|
|
17
|
+
"create_isolated_task_worktree" => "NAOME can create an isolated task worktree and leave existing user edits untouched.",
|
|
18
|
+
"commit_user_diff_with_quality_gate" => "NAOME can commit the current user-owned diff only after the configured quality gate passes. It will stage only the changed paths present before the gate runs.",
|
|
19
|
+
"auto_commit_harness_refresh_then_completed_task_then_create_new_task" => "The completed task is valid after separating deterministic harness refresh changes. NAOME can baseline the refresh first, then baseline the task before starting the next separate task.",
|
|
20
|
+
"auto_commit_upgrade_baseline_then_create_new_task" => "The setup or repair diff is ready. NAOME can baseline it automatically before starting the next task.",
|
|
21
|
+
"create_new_task" | "create_new_task_without_auto_baseline" => "NAOME is ready to create the next task.",
|
|
22
|
+
"answer_status_only" => summary,
|
|
23
|
+
"block_auto_baseline_due_to_no_commit" => "I will not baseline or commit because the prompt explicitly blocks committing. Review, revise, cancel, or clear the current diff first.",
|
|
24
|
+
"review_task_diff" | "review_diff_first" | "review_current_task_diff" => "The prompt asks for review, so NAOME will not mutate the repository automatically.",
|
|
25
|
+
"cancel_task_changes" | "cancel_upgrade_baseline" | "cancel_current_task" => "The prompt asks to cancel changes, so NAOME will not start new work until that decision is handled.",
|
|
26
|
+
"block_unsafe_intent" => "NAOME blocked this request because it contains unsafe or guardrail-bypass wording.",
|
|
27
|
+
"block_unowned_diff" => "NAOME will not commit or clear unowned changes automatically. Review the existing diff, or ask for a separate new task so NAOME can use an isolated worktree and leave those edits untouched.",
|
|
28
|
+
"repair_harness_only" => "NAOME harness health failed, so normal task work is blocked until repair or review.",
|
|
29
|
+
_ => summary,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
(user_message.to_string(), human_options, internal_notes)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn response_notes(decision: &Decision, prompt_intent: &str, policy_action: &str) -> Vec<String> {
|
|
36
|
+
let mut notes = vec![
|
|
37
|
+
format!("repo_state:{}", decision.state),
|
|
38
|
+
format!("prompt_intent:{prompt_intent}"),
|
|
39
|
+
format!("policy_action:{policy_action}"),
|
|
40
|
+
];
|
|
41
|
+
if !decision.allowed_actions.is_empty() {
|
|
42
|
+
notes.push(format!(
|
|
43
|
+
"internal_allowed_actions:{}",
|
|
44
|
+
decision.allowed_actions.join(",")
|
|
45
|
+
));
|
|
46
|
+
}
|
|
47
|
+
notes
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn response_human_options(decision: &Decision, policy_action: &str) -> Vec<String> {
|
|
51
|
+
match policy_action {
|
|
52
|
+
"review_task_diff" => decision.allowed_actions.clone(),
|
|
53
|
+
"cancel_task_changes" => vec![
|
|
54
|
+
"review_task_diff".to_string(),
|
|
55
|
+
"cancel_task_changes".to_string(),
|
|
56
|
+
],
|
|
57
|
+
"block_auto_baseline_due_to_no_commit" => decision
|
|
58
|
+
.allowed_actions
|
|
59
|
+
.iter()
|
|
60
|
+
.filter(|action| !action.starts_with("commit_"))
|
|
61
|
+
.cloned()
|
|
62
|
+
.collect(),
|
|
63
|
+
"block_unsafe_intent" => vec!["narrow_request".to_string(), "review_status".to_string()],
|
|
64
|
+
"block_ambiguous_intent" => decision.allowed_actions.clone(),
|
|
65
|
+
"block_unowned_diff" => decision
|
|
66
|
+
.allowed_actions
|
|
67
|
+
.iter()
|
|
68
|
+
.filter(|action| action.as_str() == "review_unowned_diff")
|
|
69
|
+
.cloned()
|
|
70
|
+
.collect(),
|
|
71
|
+
"repair_harness_only" => decision.allowed_actions.clone(),
|
|
72
|
+
"review_diff_first" | "cancel_upgrade_baseline" => decision.allowed_actions.clone(),
|
|
73
|
+
"review_current_task_diff" | "cancel_current_task" => decision.allowed_actions.clone(),
|
|
74
|
+
_ => Vec::new(),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
2
|
+
pub(crate) enum SegmentKind {
|
|
3
|
+
Prose,
|
|
4
|
+
InlineCode,
|
|
5
|
+
CodeFence,
|
|
6
|
+
QuotedText,
|
|
7
|
+
ListItem,
|
|
8
|
+
FilePath,
|
|
9
|
+
CommandLike,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
13
|
+
pub(crate) struct PromptSegment {
|
|
14
|
+
pub kind: SegmentKind,
|
|
15
|
+
pub text: String,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
19
|
+
pub(crate) enum IntentKind {
|
|
20
|
+
Ambiguous,
|
|
21
|
+
NewTask,
|
|
22
|
+
TaskRevision,
|
|
23
|
+
StatusQuestion,
|
|
24
|
+
ReviewRequest,
|
|
25
|
+
RepairRequest,
|
|
26
|
+
CommitRequest,
|
|
27
|
+
NoCommitRequest,
|
|
28
|
+
CancelRequest,
|
|
29
|
+
TaskCompletion,
|
|
30
|
+
Unsafe,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl IntentKind {
|
|
34
|
+
pub fn as_str(self) -> &'static str {
|
|
35
|
+
match self {
|
|
36
|
+
Self::Ambiguous => "ambiguous",
|
|
37
|
+
Self::NewTask => "new_task",
|
|
38
|
+
Self::TaskRevision => "task_revision",
|
|
39
|
+
Self::StatusQuestion => "status_question",
|
|
40
|
+
Self::ReviewRequest => "review_request",
|
|
41
|
+
Self::RepairRequest => "repair_request",
|
|
42
|
+
Self::CommitRequest => "commit_request",
|
|
43
|
+
Self::NoCommitRequest => "no_commit_request",
|
|
44
|
+
Self::CancelRequest => "cancel_request",
|
|
45
|
+
Self::TaskCompletion => "task_completion",
|
|
46
|
+
Self::Unsafe => "unsafe",
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
52
|
+
pub(crate) struct IntentCandidate {
|
|
53
|
+
pub kind: IntentKind,
|
|
54
|
+
pub confidence: u8,
|
|
55
|
+
pub evidence: String,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
59
|
+
pub(crate) struct RiskContext {
|
|
60
|
+
pub has_risky_terms: bool,
|
|
61
|
+
pub risk_codes: Vec<String>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
65
|
+
pub(crate) struct CanonicalIntent {
|
|
66
|
+
pub is_empty: bool,
|
|
67
|
+
pub references_current_task: bool,
|
|
68
|
+
pub candidates: Vec<IntentCandidate>,
|
|
69
|
+
pub risk: RiskContext,
|
|
70
|
+
pub has_workflow_conflict: bool,
|
|
71
|
+
}
|