@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
package/Cargo.lock CHANGED
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
76
76
 
77
77
  [[package]]
78
78
  name = "naome-cli"
79
- version = "1.3.6"
79
+ version = "1.3.8"
80
80
  dependencies = [
81
81
  "naome-core",
82
82
  "serde_json",
@@ -84,7 +84,7 @@ dependencies = [
84
84
 
85
85
  [[package]]
86
86
  name = "naome-core"
87
- version = "1.3.6"
87
+ version = "1.3.8"
88
88
  dependencies = [
89
89
  "serde",
90
90
  "serde_json",
package/README.md CHANGED
@@ -41,6 +41,11 @@ For agent-driven work, route the user's request through the harness:
41
41
  naome route --prompt-file /path/to/prompt.txt --execute --json
42
42
  ```
43
43
 
44
+ Prompt files should start with a fenced `naome-prompt-envelope-v1` JSON
45
+ envelope that normalizes the raw user text into canonical routing fields. A raw
46
+ natural-language prompt routes to a non-mutating normalization decision instead
47
+ of becoming a task by keyword inference.
48
+
44
49
  ## Why NAOME?
45
50
 
46
51
  - Keeps agents inside explicit task scope.
@@ -84,8 +89,9 @@ naome commit -m "type(scope): summary"
84
89
 
85
90
  `naome sync` installs or repairs the local harness files. It does not run a
86
91
  hidden full-repository quality scan. It also migrates any active legacy
87
- task-state into the task ledger automatically. If quality policy is newly
88
- seeded, run `naome quality init --baseline` deliberately; use `--deep` or
92
+ task-state into the local task ledger automatically and untracks local
93
+ `.naome/tasks/` runtime folders from Git. If quality policy is newly seeded,
94
+ run `naome quality init --baseline` deliberately; use `--deep` or
89
95
  `--deep-baseline` only when you want expensive repository-wide checks.
90
96
 
91
97
  ## Repository Docs
@@ -111,7 +117,7 @@ The main local policy files are:
111
117
  quality policy.
112
118
  - `.naome/repository-structure.json` for path role, module, and directory
113
119
  structure policy.
114
- - `.naome/task-state.json` for active task state and proof.
120
+ - `.naome/task-state.json` for the compact committed active task projection.
115
121
 
116
122
  Product defaults stay generic. Repository-specific policy belongs in the local
117
123
  `.naome/` config files.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-cli"
3
- version = "1.3.6"
3
+ version = "1.3.8"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.3.6"
3
+ version = "1.3.8"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -1,5 +1,8 @@
1
- use std::path::Path;
1
+ use std::path::{Component, Path};
2
2
 
3
+ use serde::Deserialize;
4
+
5
+ use crate::intent::prompt_envelope_json;
3
6
  use crate::{git, models::NaomeError};
4
7
 
5
8
  use super::helpers::{capsule_for_paths, context_item, is_source_path, normalize_paths};
@@ -7,6 +10,13 @@ use super::types::{ContextBudgetLedger, ContextItem, ContextSelection};
7
10
 
8
11
  const MAX_CONTEXT_FILES: usize = 12;
9
12
 
13
+ #[derive(Debug, Deserialize)]
14
+ #[serde(rename_all = "camelCase")]
15
+ struct PromptEnvelopeContext {
16
+ schema: Option<String>,
17
+ referenced_paths: Option<Vec<String>>,
18
+ }
19
+
10
20
  pub fn select_context_for_changed_paths(root: &Path) -> Result<ContextSelection, NaomeError> {
11
21
  let paths = git::changed_paths(root)?;
12
22
  Ok(selection_for_paths(root, "changed", paths))
@@ -19,7 +29,9 @@ pub fn select_context_for_prompt(
19
29
  Ok(selection_for_paths(
20
30
  root,
21
31
  "prompt",
22
- mentioned_paths(prompt.as_ref()),
32
+ envelope_referenced_paths(prompt.as_ref())
33
+ .filter(|paths| !paths.is_empty())
34
+ .unwrap_or_else(|| mentioned_paths(root, prompt.as_ref())),
23
35
  ))
24
36
  }
25
37
 
@@ -119,7 +131,7 @@ fn forbidden_context() -> Vec<String> {
119
131
  .collect()
120
132
  }
121
133
 
122
- fn mentioned_paths(prompt: &str) -> Vec<String> {
134
+ fn mentioned_paths(root: &Path, prompt: &str) -> Vec<String> {
123
135
  prompt
124
136
  .split_whitespace()
125
137
  .map(|token| {
@@ -127,8 +139,50 @@ fn mentioned_paths(prompt: &str) -> Vec<String> {
127
139
  matches!(ch, '"' | '\'' | '`' | ',' | ';' | ':' | ')' | '(')
128
140
  })
129
141
  })
130
- .filter(|token| token.contains('/') || is_source_path(token) || token.ends_with(".md"))
131
142
  .filter(|token| !token.starts_with('-') && !token.starts_with("http"))
132
143
  .map(|token| token.trim_start_matches("./").replace('\\', "/"))
144
+ .filter(|path| path_looks_contextual(path))
145
+ .filter(|path| path_exists_or_has_repo_parent(root, path))
133
146
  .collect()
134
147
  }
148
+
149
+ fn envelope_referenced_paths(prompt: &str) -> Option<Vec<String>> {
150
+ let input =
151
+ serde_json::from_str::<PromptEnvelopeContext>(prompt_envelope_json(prompt)?).ok()?;
152
+ if input.schema.as_deref() != Some("naome.prompt-envelope.v1") {
153
+ return None;
154
+ }
155
+ Some(
156
+ input
157
+ .referenced_paths
158
+ .unwrap_or_default()
159
+ .into_iter()
160
+ .map(|path| path.trim_start_matches("./").replace('\\', "/"))
161
+ .filter(|path| is_repo_relative_path(path))
162
+ .filter(|path| path_looks_contextual(path))
163
+ .collect(),
164
+ )
165
+ }
166
+
167
+ fn is_repo_relative_path(path: &str) -> bool {
168
+ let candidate = Path::new(path);
169
+ !path.contains(':')
170
+ && !candidate.is_absolute()
171
+ && !candidate
172
+ .components()
173
+ .any(|component| matches!(component, Component::ParentDir))
174
+ }
175
+
176
+ fn path_looks_contextual(path: &str) -> bool {
177
+ path.contains('/') || is_source_path(path) || path.ends_with(".md")
178
+ }
179
+
180
+ fn path_exists_or_has_repo_parent(root: &Path, path: &str) -> bool {
181
+ let candidate = root.join(path);
182
+ if candidate.exists() {
183
+ return true;
184
+ }
185
+ candidate
186
+ .parent()
187
+ .is_some_and(|parent| parent != root && parent.exists())
188
+ }
@@ -1,4 +1,3 @@
1
- use std::fs;
2
1
  use std::path::Path;
3
2
  use std::process::Command;
4
3
 
@@ -6,7 +5,7 @@ use crate::models::NaomeError;
6
5
  use crate::paths;
7
6
 
8
7
  pub fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
9
- let ignored_patterns = read_naomeignore_patterns(root);
8
+ let ignored_patterns = paths::naomeignore_patterns(root);
10
9
  let output = Command::new("git")
11
10
  .args(["status", "--porcelain=v1", "-z", "-uall"])
12
11
  .current_dir(root)
@@ -57,27 +56,3 @@ pub fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
57
56
  paths.dedup();
58
57
  Ok(paths)
59
58
  }
60
-
61
- fn read_naomeignore_patterns(root: &Path) -> Vec<String> {
62
- let Ok(content) = fs::read_to_string(root.join(".naomeignore")) else {
63
- return Vec::new();
64
- };
65
-
66
- let mut patterns = content
67
- .lines()
68
- .map(str::trim)
69
- .filter(|line| !line.is_empty() && !line.starts_with('#') && !line.starts_with('!'))
70
- .map(normalize_ignore_pattern)
71
- .collect::<Vec<_>>();
72
- patterns.push(".naome/cache/**".to_string());
73
- patterns
74
- }
75
-
76
- fn normalize_ignore_pattern(pattern: &str) -> String {
77
- let normalized = pattern.trim_start_matches("./").replace('\\', "/");
78
- if normalized.ends_with('/') {
79
- format!("{normalized}**")
80
- } else {
81
- normalized
82
- }
83
- }
@@ -36,12 +36,6 @@ const CLASSES: &[(&str, &str, &str, &str)] = &[
36
36
  "commit",
37
37
  "restore_from_repository_or_package",
38
38
  ),
39
- (
40
- "durable_task_ledger",
41
- "Committed task intent, scope, and lifecycle records.",
42
- "commit",
43
- "restore_from_repository",
44
- ),
45
39
  (
46
40
  "generated_projection",
47
41
  "Compatibility views generated from durable state.",
@@ -67,23 +61,18 @@ const CLASSES: &[(&str, &str, &str, &str)] = &[
67
61
  "restore_from_repository",
68
62
  ),
69
63
  ];
70
- struct RuleSeed {
71
- path_pattern: &'static str,
72
- class: &'static str,
73
- }
74
-
75
- const RULES: &[RuleSeed] = &[
76
- RuleSeed { path_pattern: ".naome/tmp/**", class: "local_runtime_state" },
77
- RuleSeed { path_pattern: ".naome/task-state.json", class: "generated_projection" },
78
- RuleSeed { path_pattern: ".naome/tasks/active.json", class: "durable_task_ledger" },
79
- RuleSeed { path_pattern: ".naome/tasks/*/task.json", class: "durable_task_ledger" },
80
- RuleSeed { path_pattern: ".naome/tasks/*/events.jsonl", class: "durable_task_ledger" },
81
- RuleSeed { path_pattern: ".naome/tasks/*/proofs/*.json", class: "run_evidence" },
82
- RuleSeed { path_pattern: ".naome/manifest.json", class: "durable_project_state" },
83
- RuleSeed { path_pattern: ".naome/verification.json", class: "durable_project_state" },
84
- RuleSeed { path_pattern: ".naome/repository-quality*.json", class: "durable_project_state" },
85
- RuleSeed { path_pattern: "docs/naome/**", class: "durable_project_state" },
86
- RuleSeed { path_pattern: "packages/naome/templates/naome-root/**", class: "durable_project_state" },
64
+ const RULES: &[(&str, &str)] = &[
65
+ (".naome/tmp/**", "local_runtime_state"),
66
+ (".naome/task-state.json", "generated_projection"),
67
+ (".naome/tasks/**", "local_runtime_state"),
68
+ (".naome/manifest.json", "durable_project_state"),
69
+ (".naome/verification.json", "durable_project_state"),
70
+ (".naome/repository-quality*.json", "durable_project_state"),
71
+ ("docs/naome/**", "durable_project_state"),
72
+ (
73
+ "packages/naome/templates/naome-root/**",
74
+ "durable_project_state",
75
+ ),
87
76
  ];
88
77
  pub fn information_architecture_report() -> InformationArchitectureReport {
89
78
  InformationArchitectureReport {
@@ -95,7 +84,7 @@ pub fn information_architecture_report() -> InformationArchitectureReport {
95
84
  .collect(),
96
85
  rules: RULES
97
86
  .iter()
98
- .map(|rule| rule_from_class(rule.path_pattern, rule.class))
87
+ .map(|(path_pattern, class)| rule_from_class(path_pattern, class))
99
88
  .collect(),
100
89
  }
101
90
  }
@@ -104,8 +93,8 @@ pub fn classify_information_path(path: &str) -> InformationPathClassification {
104
93
  let path = normalize_path(path);
105
94
  let class_id = RULES
106
95
  .iter()
107
- .find(|rule| matches_information_rule(&path, rule.path_pattern))
108
- .map(|rule| rule.class)
96
+ .find(|(pattern, _)| matches_information_rule(&path, pattern))
97
+ .map(|(_, class)| *class)
109
98
  .unwrap_or("product_source");
110
99
  let class = class_by_id(class_id);
111
100
  InformationPathClassification {
@@ -81,11 +81,13 @@ pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
81
81
  ".naome/bin/naome-rust*",
82
82
  ".naome/cache/",
83
83
  ".naome/task-journal.jsonl",
84
+ ".naome/tasks/",
84
85
  ];
85
86
  git_exclude_entries.extend_from_slice(LOCAL_ONLY_MACHINE_OWNED_PATHS);
86
87
 
87
88
  let mut git_untrack_paths = LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec();
88
89
  git_untrack_paths.extend_from_slice(LOCAL_NATIVE_BINARY_PATHS);
90
+ git_untrack_paths.push(".naome/tasks");
89
91
 
90
92
  InstallPlan {
91
93
  schema: "naome.install-plan.v1",
@@ -1,29 +1,42 @@
1
1
  use std::collections::HashSet;
2
2
 
3
3
  use super::envelope::parse_routing_envelope;
4
- use super::model::{CanonicalIntent, IntentCandidate, IntentKind, PromptSegment, SegmentKind};
5
- use super::patterns;
4
+ use super::model::{CanonicalIntent, IntentCandidate, IntentKind};
6
5
  use super::risk::detect_risk;
7
6
  use super::segment::{actionable_text, segment_prompt};
8
7
 
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
8
  pub(crate) fn canonical_intent(prompt: &str) -> CanonicalIntent {
15
9
  let segments = segment_prompt(prompt);
16
10
  let actionable = actionable_text(&segments);
17
- let mut candidates = classify_candidates(&segments);
11
+ let mut has_routing_envelope = false;
12
+ let mut validation_errors = Vec::new();
13
+ let mut candidates = Vec::new();
18
14
  let mut risk = detect_risk(&segments);
19
15
 
20
16
  if let Some(envelope) = parse_routing_envelope(prompt) {
17
+ has_routing_envelope = true;
21
18
  candidates.clear();
22
- candidates.extend(envelope.candidates);
19
+ validation_errors = envelope.validation_errors;
23
20
  risk.risk_codes.extend(envelope.risk.risk_codes);
24
21
  risk.risk_codes.sort();
25
22
  risk.risk_codes.dedup();
26
23
  risk.has_risky_terms = !risk.risk_codes.is_empty();
24
+ if validation_errors.is_empty() {
25
+ candidates.extend(envelope.candidates);
26
+ } else {
27
+ candidates.push(IntentCandidate {
28
+ kind: IntentKind::PromptNormalizationRequired,
29
+ confidence: 100,
30
+ evidence: "invalid_prompt_envelope".to_string(),
31
+ });
32
+ }
33
+ } else if !actionable.trim().is_empty() {
34
+ candidates.clear();
35
+ candidates.push(IntentCandidate {
36
+ kind: IntentKind::PromptNormalizationRequired,
37
+ confidence: 100,
38
+ evidence: "missing_prompt_envelope".to_string(),
39
+ });
27
40
  }
28
41
 
29
42
  candidates = dedupe_candidates(candidates);
@@ -37,11 +50,13 @@ pub(crate) fn canonical_intent(prompt: &str) -> CanonicalIntent {
37
50
  }
38
51
 
39
52
  CanonicalIntent {
40
- is_empty: actionable.trim().is_empty(),
53
+ is_empty: actionable.trim().is_empty() && !has_routing_envelope,
54
+ has_routing_envelope,
41
55
  references_current_task,
42
56
  has_workflow_conflict: has_workflow_conflict(&candidates),
43
57
  candidates,
44
58
  risk,
59
+ validation_errors,
45
60
  }
46
61
  }
47
62
 
@@ -51,14 +66,16 @@ pub(crate) fn winning_intent(canonical: &CanonicalIntent) -> IntentKind {
51
66
  }
52
67
  for kind in [
53
68
  IntentKind::Unsafe,
69
+ IntentKind::PromptNormalizationRequired,
54
70
  IntentKind::StatusQuestion,
71
+ IntentKind::Advisory,
55
72
  IntentKind::RepairRequest,
56
73
  IntentKind::CancelRequest,
57
74
  IntentKind::ReviewRequest,
58
75
  IntentKind::NoCommitRequest,
59
76
  IntentKind::CommitRequest,
60
- IntentKind::NewTask,
61
77
  IntentKind::TaskRevision,
78
+ IntentKind::NewTask,
62
79
  IntentKind::TaskCompletion,
63
80
  ] {
64
81
  if has_candidate(canonical, kind) {
@@ -75,67 +92,44 @@ pub(crate) fn has_candidate(canonical: &CanonicalIntent, kind: IntentKind) -> bo
75
92
  .any(|candidate| candidate.kind == kind)
76
93
  }
77
94
 
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
95
  fn has_candidate_kind(candidates: &[IntentCandidate], kind: IntentKind) -> bool {
128
96
  candidates.iter().any(|candidate| candidate.kind == kind)
129
97
  }
130
98
 
131
99
  fn has_workflow_conflict(candidates: &[IntentCandidate]) -> bool {
100
+ let kinds = candidates
101
+ .iter()
102
+ .map(|candidate| candidate.kind)
103
+ .collect::<HashSet<_>>();
104
+ let mutating_kinds = [
105
+ IntentKind::NewTask,
106
+ IntentKind::TaskRevision,
107
+ IntentKind::CommitRequest,
108
+ IntentKind::RepairRequest,
109
+ IntentKind::ReviewRequest,
110
+ IntentKind::CancelRequest,
111
+ IntentKind::TaskCompletion,
112
+ ];
113
+ if (kinds.contains(&IntentKind::Advisory) || kinds.contains(&IntentKind::StatusQuestion))
114
+ && mutating_kinds.iter().any(|kind| kinds.contains(kind))
115
+ {
116
+ return true;
117
+ }
118
+ if kinds.contains(&IntentKind::CommitRequest) && kinds.contains(&IntentKind::NoCommitRequest) {
119
+ return true;
120
+ }
121
+
132
122
  let workflow = candidates
133
123
  .iter()
134
- .filter(|candidate| candidate.confidence >= DIRECT_WORKFLOW_CONFIDENCE)
124
+ .filter(|candidate| candidate.confidence >= 90)
135
125
  .filter(|candidate| {
136
126
  !matches!(
137
127
  candidate.kind,
138
- IntentKind::NewTask | IntentKind::TaskRevision | IntentKind::Unsafe
128
+ IntentKind::NewTask
129
+ | IntentKind::TaskRevision
130
+ | IntentKind::Unsafe
131
+ | IntentKind::Advisory
132
+ | IntentKind::StatusQuestion
139
133
  )
140
134
  })
141
135
  .map(|candidate| candidate.kind)
@@ -143,25 +137,6 @@ fn has_workflow_conflict(candidates: &[IntentCandidate]) -> bool {
143
137
  workflow.len() > 1
144
138
  }
145
139
 
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
140
  fn dedupe_candidates(candidates: Vec<IntentCandidate>) -> Vec<IntentCandidate> {
166
141
  let mut seen = HashSet::new();
167
142
  candidates