@lamentis/naome 1.1.2 → 1.2.1

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 (204) hide show
  1. package/Cargo.lock +2 -2
  2. package/Cargo.toml +1 -1
  3. package/LICENSE +180 -21
  4. package/README.md +49 -6
  5. package/bin/naome-node.js +2 -1579
  6. package/bin/naome.js +68 -16
  7. package/crates/naome-cli/Cargo.toml +1 -1
  8. package/crates/naome-cli/src/check_commands.rs +135 -0
  9. package/crates/naome-cli/src/cli_args.rs +5 -0
  10. package/crates/naome-cli/src/dispatcher.rs +37 -0
  11. package/crates/naome-cli/src/install_bridge.rs +83 -0
  12. package/crates/naome-cli/src/main.rs +60 -341
  13. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  14. package/crates/naome-cli/src/quality_commands.rs +229 -0
  15. package/crates/naome-cli/src/simple_commands.rs +53 -0
  16. package/crates/naome-cli/src/workflow_commands.rs +153 -0
  17. package/crates/naome-core/Cargo.toml +1 -1
  18. package/crates/naome-core/src/decision/checks.rs +64 -0
  19. package/crates/naome-core/src/decision/idle.rs +67 -0
  20. package/crates/naome-core/src/decision/json.rs +36 -0
  21. package/crates/naome-core/src/decision/states.rs +165 -0
  22. package/crates/naome-core/src/decision.rs +131 -353
  23. package/crates/naome-core/src/harness_health/integrity.rs +96 -0
  24. package/crates/naome-core/src/harness_health.rs +14 -126
  25. package/crates/naome-core/src/install_plan.rs +5 -0
  26. package/crates/naome-core/src/intent/classifier.rs +171 -0
  27. package/crates/naome-core/src/intent/envelope.rs +108 -0
  28. package/crates/naome-core/src/intent/legacy.rs +138 -0
  29. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  30. package/crates/naome-core/src/intent/model.rs +71 -0
  31. package/crates/naome-core/src/intent/patterns.rs +170 -0
  32. package/crates/naome-core/src/intent/resolver.rs +162 -0
  33. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  34. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  35. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  36. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  37. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  38. package/crates/naome-core/src/intent/risk.rs +40 -0
  39. package/crates/naome-core/src/intent/segment.rs +170 -0
  40. package/crates/naome-core/src/intent.rs +64 -879
  41. package/crates/naome-core/src/journal.rs +9 -20
  42. package/crates/naome-core/src/lib.rs +15 -0
  43. package/crates/naome-core/src/paths.rs +3 -1
  44. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  45. package/crates/naome-core/src/quality/adapters.rs +131 -0
  46. package/crates/naome-core/src/quality/baseline.rs +75 -0
  47. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  48. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  49. package/crates/naome-core/src/quality/checks.rs +228 -0
  50. package/crates/naome-core/src/quality/cleanup.rs +84 -0
  51. package/crates/naome-core/src/quality/config.rs +102 -0
  52. package/crates/naome-core/src/quality/config_support.rs +24 -0
  53. package/crates/naome-core/src/quality/mod.rs +108 -0
  54. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  55. package/crates/naome-core/src/quality/scanner.rs +379 -0
  56. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  57. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  58. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  59. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  60. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  61. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  62. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  63. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  64. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  65. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  66. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  67. package/crates/naome-core/src/quality/types.rs +292 -0
  68. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  69. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  70. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  71. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  72. package/crates/naome-core/src/route/context.rs +180 -0
  73. package/crates/naome-core/src/route/execution.rs +96 -0
  74. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  75. package/crates/naome-core/src/route/execution_support.rs +57 -0
  76. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  77. package/crates/naome-core/src/route/git_ops.rs +72 -0
  78. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  79. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  80. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  81. package/crates/naome-core/src/route/worktree.rs +75 -0
  82. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  83. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  84. package/crates/naome-core/src/route.rs +44 -1155
  85. package/crates/naome-core/src/task_state/admission.rs +63 -0
  86. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  87. package/crates/naome-core/src/task_state/api.rs +130 -0
  88. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  89. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  90. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  91. package/crates/naome-core/src/task_state/completion.rs +72 -0
  92. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  93. package/crates/naome-core/src/task_state/diff.rs +95 -0
  94. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  95. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  96. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  97. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  98. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  99. package/crates/naome-core/src/task_state/mod.rs +38 -0
  100. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  101. package/crates/naome-core/src/task_state/progress.rs +123 -0
  102. package/crates/naome-core/src/task_state/proof.rs +139 -0
  103. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  104. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  105. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  106. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  107. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  108. package/crates/naome-core/src/task_state/repair.rs +168 -0
  109. package/crates/naome-core/src/task_state/shape.rs +117 -0
  110. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  111. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  112. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  113. package/crates/naome-core/src/task_state/types.rs +87 -0
  114. package/crates/naome-core/src/task_state/util.rs +137 -0
  115. package/crates/naome-core/src/verification/render.rs +122 -0
  116. package/crates/naome-core/src/verification.rs +177 -58
  117. package/crates/naome-core/src/verification_contract.rs +49 -21
  118. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  119. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  120. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  121. package/crates/naome-core/src/workflow/mod.rs +18 -0
  122. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  123. package/crates/naome-core/src/workflow/output.rs +111 -0
  124. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  125. package/crates/naome-core/src/workflow/phases.rs +169 -0
  126. package/crates/naome-core/src/workflow/policy.rs +156 -0
  127. package/crates/naome-core/src/workflow/processes.rs +91 -0
  128. package/crates/naome-core/src/workflow/types.rs +42 -0
  129. package/crates/naome-core/tests/decision.rs +24 -118
  130. package/crates/naome-core/tests/harness_health.rs +5 -0
  131. package/crates/naome-core/tests/intent.rs +97 -792
  132. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  133. package/crates/naome-core/tests/intent_v2.rs +90 -0
  134. package/crates/naome-core/tests/quality.rs +319 -0
  135. package/crates/naome-core/tests/quality_structure.rs +116 -0
  136. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  137. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  138. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  139. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  140. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  141. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  142. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  143. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  144. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  145. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  146. package/crates/naome-core/tests/route.rs +1 -1476
  147. package/crates/naome-core/tests/route_baseline.rs +86 -0
  148. package/crates/naome-core/tests/route_completion.rs +141 -0
  149. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  150. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  151. package/crates/naome-core/tests/route_worktree.rs +54 -0
  152. package/crates/naome-core/tests/task_state.rs +60 -429
  153. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  154. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  155. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  156. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  157. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  158. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  159. package/crates/naome-core/tests/verification.rs +4 -45
  160. package/crates/naome-core/tests/verification_contract.rs +22 -78
  161. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  162. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  163. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  164. package/installer/agents.js +90 -0
  165. package/installer/context.js +67 -0
  166. package/installer/filesystem.js +166 -0
  167. package/installer/flows.js +84 -0
  168. package/installer/git-boundary.js +170 -0
  169. package/installer/git-hook-content.js +36 -0
  170. package/installer/git-hooks.js +134 -0
  171. package/installer/git-local.js +2 -0
  172. package/installer/git-shared.js +35 -0
  173. package/installer/harness-file-ops.js +140 -0
  174. package/installer/harness-files.js +56 -0
  175. package/installer/harness-verification.js +123 -0
  176. package/installer/install-plan.js +66 -0
  177. package/installer/main.js +25 -0
  178. package/installer/manifest-state.js +167 -0
  179. package/installer/native-build.js +24 -0
  180. package/installer/native-format.js +6 -0
  181. package/installer/native.js +162 -0
  182. package/installer/output.js +131 -0
  183. package/installer/version.js +32 -0
  184. package/native/darwin-arm64/naome +0 -0
  185. package/native/linux-x64/naome +0 -0
  186. package/package.json +3 -2
  187. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  188. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  189. package/templates/naome-root/.naome/bin/naome.js +51 -76
  190. package/templates/naome-root/.naome/manifest.json +22 -18
  191. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  192. package/templates/naome-root/.naome/repository-quality.json +24 -0
  193. package/templates/naome-root/.naome/repository-structure.json +90 -0
  194. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  195. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  196. package/templates/naome-root/.naome/verification.json +38 -0
  197. package/templates/naome-root/AGENTS.md +3 -0
  198. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  199. package/templates/naome-root/docs/naome/execution.md +25 -21
  200. package/templates/naome-root/docs/naome/index.md +5 -3
  201. package/templates/naome-root/docs/naome/repository-quality.md +46 -0
  202. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  203. package/templates/naome-root/docs/naome/testing.md +13 -0
  204. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -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
+ }
@@ -0,0 +1,170 @@
1
+ use super::model::IntentKind;
2
+
3
+ pub(crate) fn normalized(text: &str) -> String {
4
+ text.to_lowercase()
5
+ .chars()
6
+ .map(|ch| if ch.is_alphanumeric() { ch } else { ' ' })
7
+ .collect::<String>()
8
+ .split_whitespace()
9
+ .collect::<Vec<_>>()
10
+ .join(" ")
11
+ }
12
+
13
+ pub(crate) fn lexical_tokens(text: &str) -> Vec<String> {
14
+ text.split_whitespace()
15
+ .map(|token| {
16
+ token
17
+ .trim_matches(|ch: char| !ch.is_alphanumeric() && ch != '_' && ch != '-')
18
+ .to_lowercase()
19
+ })
20
+ .filter(|token| !token.is_empty())
21
+ .collect()
22
+ }
23
+
24
+ pub(crate) fn structured_workflow_intents(text: &str) -> Vec<IntentKind> {
25
+ let tokens = lexical_tokens(text);
26
+ let mut intents = Vec::new();
27
+
28
+ if let Some(intent) = structured_action(&tokens) {
29
+ intents.push(intent);
30
+ }
31
+ if let Some(intent) = legacy_v1_short_action_request(&tokens) {
32
+ intents.push(intent);
33
+ }
34
+ if legacy_v1_no_commit(&tokens) {
35
+ intents.push(IntentKind::NoCommitRequest);
36
+ }
37
+ if has_structured_token(&tokens, &["no_commit", "without_commit"]) {
38
+ intents.push(IntentKind::NoCommitRequest);
39
+ }
40
+
41
+ intents.sort_by_key(|kind| kind.as_str());
42
+ intents.dedup();
43
+ intents
44
+ }
45
+
46
+ pub(crate) fn command_workflow_intents(text: &str) -> Vec<IntentKind> {
47
+ let tokens = lexical_tokens(text);
48
+ let mut intents = Vec::new();
49
+ if let Some(intent) = cli_action(&tokens) {
50
+ intents.push(intent);
51
+ }
52
+ intents
53
+ }
54
+
55
+ pub(crate) fn explicit_task_intent(text: &str) -> Option<IntentKind> {
56
+ let tokens = lexical_tokens(text);
57
+ if has_structured_token(&tokens, &["task_revision", "current_task", "active_task"]) {
58
+ return Some(IntentKind::TaskRevision);
59
+ }
60
+ if has_structured_token(&tokens, &["new_task", "create_new_task", "separate_task"]) {
61
+ return Some(IntentKind::NewTask);
62
+ }
63
+ None
64
+ }
65
+
66
+ pub(crate) fn is_generic_work_request(text: &str) -> bool {
67
+ !lexical_tokens(text).is_empty()
68
+ }
69
+
70
+ fn structured_action(tokens: &[String]) -> Option<IntentKind> {
71
+ policy_action(tokens)
72
+ }
73
+
74
+ fn policy_action(tokens: &[String]) -> Option<IntentKind> {
75
+ for token in tokens {
76
+ let intent = match token.as_str() {
77
+ "commit_task_baseline" | "commit_user_diff" | "commit_upgrade_baseline" => {
78
+ IntentKind::CommitRequest
79
+ }
80
+ "review_task_diff" | "review_current_task_diff" | "review_unowned_diff" => {
81
+ IntentKind::ReviewRequest
82
+ }
83
+ "repair_harness" | "repair_harness_only" => IntentKind::RepairRequest,
84
+ "cancel_task_changes" | "cancel_current_task" => IntentKind::CancelRequest,
85
+ "continue_current_task" | "task_complete" | "complete_task" => {
86
+ IntentKind::TaskCompletion
87
+ }
88
+ "answer_status_only" => IntentKind::StatusQuestion,
89
+ "clear_or_commit_unowned_diff" => IntentKind::Ambiguous,
90
+ _ => continue,
91
+ };
92
+ return Some(intent);
93
+ }
94
+ None
95
+ }
96
+
97
+ fn cli_action(tokens: &[String]) -> Option<IntentKind> {
98
+ match (
99
+ tokens.first().map(String::as_str),
100
+ tokens.get(1).map(String::as_str),
101
+ ) {
102
+ (Some("naome"), Some("commit")) => Some(IntentKind::CommitRequest),
103
+ (Some("naome"), Some("status")) => Some(IntentKind::StatusQuestion),
104
+ (Some("naome"), Some("sync" | "update" | "install")) => Some(IntentKind::RepairRequest),
105
+ (Some("git"), Some("commit")) => Some(IntentKind::CommitRequest),
106
+ _ => None,
107
+ }
108
+ }
109
+
110
+ // Kept only so pre-v2 route callers keep their existing conservative behavior.
111
+ // A naome-intent-v2 envelope clears these candidates before policy resolution.
112
+ fn legacy_v1_short_action_request(tokens: &[String]) -> Option<IntentKind> {
113
+ let action_index = tokens
114
+ .iter()
115
+ .position(|token| legacy_v1_action_intent(token).is_some())?;
116
+ if action_index > 1 || tokens.len() > 6 {
117
+ return None;
118
+ }
119
+
120
+ let intent = legacy_v1_action_intent(&tokens[action_index])?;
121
+ if intent == IntentKind::StatusQuestion || tokens.len() <= 3 {
122
+ return Some(intent);
123
+ }
124
+ if tokens.len() <= 2 || legacy_v1_has_action_scope(tokens) {
125
+ return Some(intent);
126
+ }
127
+ None
128
+ }
129
+
130
+ fn legacy_v1_no_commit(tokens: &[String]) -> bool {
131
+ tokens
132
+ .windows(3)
133
+ .any(|window| window[0] == "do" && window[1] == "not" && window[2] == "commit")
134
+ || tokens
135
+ .windows(2)
136
+ .any(|window| matches!(window[0].as_str(), "no" | "without") && window[1] == "commit")
137
+ }
138
+
139
+ fn legacy_v1_action_intent(token: &str) -> Option<IntentKind> {
140
+ match token {
141
+ "commit" | "baseline" => Some(IntentKind::CommitRequest),
142
+ "review" => Some(IntentKind::ReviewRequest),
143
+ "repair" | "sync" => Some(IntentKind::RepairRequest),
144
+ "cancel" => Some(IntentKind::CancelRequest),
145
+ "complete" | "completion" => Some(IntentKind::TaskCompletion),
146
+ "status" => Some(IntentKind::StatusQuestion),
147
+ _ => None,
148
+ }
149
+ }
150
+
151
+ fn legacy_v1_has_action_scope(tokens: &[String]) -> bool {
152
+ has_structured_token(
153
+ tokens,
154
+ &[
155
+ "task",
156
+ "diff",
157
+ "harness",
158
+ "baseline",
159
+ "state",
160
+ "current_task",
161
+ "active_task",
162
+ ],
163
+ )
164
+ }
165
+
166
+ fn has_structured_token(tokens: &[String], accepted: &[&str]) -> bool {
167
+ tokens
168
+ .iter()
169
+ .any(|token| accepted.iter().any(|accepted| token == accepted))
170
+ }
@@ -0,0 +1,162 @@
1
+ use crate::models::Decision;
2
+
3
+ use super::classifier::winning_intent;
4
+ use super::model::{CanonicalIntent, IntentKind};
5
+ use super::resolver_policy::state_policy;
6
+ use super::resolver_shared::{ambiguous_conflict, ambiguous_empty, unsafe_policy, Policy};
7
+
8
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9
+ pub(crate) enum CompletedTaskReadiness {
10
+ Valid,
11
+ ValidAfterHarnessRefresh,
12
+ ValidWithUnrelatedDirty,
13
+ Invalid,
14
+ }
15
+
16
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
17
+ pub(crate) enum DirtyDiffReadiness {
18
+ Unclassified,
19
+ HarnessRefreshOnly,
20
+ HarnessRefreshWithUnrelatedDirty,
21
+ }
22
+
23
+ #[derive(Debug, Clone, PartialEq, Eq)]
24
+ pub(crate) struct ResolvedIntent {
25
+ pub prompt_intent: IntentKind,
26
+ pub certainty: String,
27
+ pub policy_action: String,
28
+ pub allowed: bool,
29
+ pub summary: String,
30
+ pub next_action: String,
31
+ pub reason_codes: Vec<String>,
32
+ pub risk_codes: Vec<String>,
33
+ }
34
+
35
+ pub(crate) fn resolve_intent(
36
+ decision: &Decision,
37
+ canonical: &CanonicalIntent,
38
+ completed_task_readiness: CompletedTaskReadiness,
39
+ dirty_diff_readiness: DirtyDiffReadiness,
40
+ ) -> ResolvedIntent {
41
+ let prompt_intent = winning_intent(canonical);
42
+ let mut reason_codes = reason_codes(decision, canonical, prompt_intent);
43
+ let mut risk_codes = canonical.risk.risk_codes.clone();
44
+ if canonical.risk.has_risky_terms
45
+ && !risk_codes
46
+ .iter()
47
+ .any(|code| code == "prompt_contains_risky_terms")
48
+ {
49
+ risk_codes.push("prompt_contains_risky_terms".to_string());
50
+ }
51
+ if completed_task_readiness == CompletedTaskReadiness::Invalid {
52
+ risk_codes.push("completed_task_proof_invalid".to_string());
53
+ }
54
+
55
+ let policy = resolve_policy(
56
+ decision,
57
+ canonical,
58
+ prompt_intent,
59
+ completed_task_readiness,
60
+ dirty_diff_readiness,
61
+ &mut reason_codes,
62
+ );
63
+
64
+ ResolvedIntent {
65
+ prompt_intent,
66
+ certainty: certainty(prompt_intent, &policy.0, &risk_codes),
67
+ policy_action: policy.0.to_string(),
68
+ allowed: policy.1,
69
+ summary: policy.2.to_string(),
70
+ next_action: policy.3.to_string(),
71
+ reason_codes,
72
+ risk_codes,
73
+ }
74
+ }
75
+
76
+ fn resolve_policy(
77
+ decision: &Decision,
78
+ canonical: &CanonicalIntent,
79
+ prompt_intent: IntentKind,
80
+ completed_task_readiness: CompletedTaskReadiness,
81
+ dirty_diff_readiness: DirtyDiffReadiness,
82
+ reason_codes: &mut Vec<String>,
83
+ ) -> Policy {
84
+ if canonical.is_empty {
85
+ ambiguous_empty()
86
+ } else if canonical.has_workflow_conflict {
87
+ reason_codes.push("winning_rule:workflow_intent_conflict".to_string());
88
+ ambiguous_conflict()
89
+ } else if prompt_intent == IntentKind::Unsafe {
90
+ reason_codes.push("winning_rule:unsafe_intent_precedence".to_string());
91
+ unsafe_policy()
92
+ } else if prompt_intent == IntentKind::StatusQuestion {
93
+ (
94
+ "answer_status_only",
95
+ true,
96
+ "The prompt asks for status or explanation, so no task mutation is needed.",
97
+ "Answer from the current NAOME status without editing files.",
98
+ )
99
+ } else {
100
+ state_policy(
101
+ decision,
102
+ prompt_intent,
103
+ completed_task_readiness,
104
+ dirty_diff_readiness,
105
+ canonical.risk.has_risky_terms,
106
+ )
107
+ }
108
+ }
109
+
110
+ fn reason_codes(
111
+ decision: &Decision,
112
+ canonical: &CanonicalIntent,
113
+ prompt_intent: IntentKind,
114
+ ) -> Vec<String> {
115
+ let mut codes = vec![format!("repo_state:{}", decision.state)];
116
+ if canonical.references_current_task {
117
+ codes.push("prompt_references_current_task".to_string());
118
+ }
119
+ for (kind, code) in [
120
+ (IntentKind::NewTask, "prompt_requests_new_goal"),
121
+ (IntentKind::TaskRevision, "prompt_requests_correction"),
122
+ (IntentKind::CommitRequest, "prompt_requests_commit"),
123
+ (IntentKind::NoCommitRequest, "prompt_blocks_commit"),
124
+ (IntentKind::ReviewRequest, "prompt_requests_review"),
125
+ (IntentKind::RepairRequest, "prompt_requests_repair"),
126
+ (IntentKind::CancelRequest, "prompt_requests_cancel"),
127
+ ] {
128
+ if prompt_intent == kind
129
+ || canonical
130
+ .candidates
131
+ .iter()
132
+ .any(|candidate| candidate.kind == kind)
133
+ {
134
+ codes.push(code.to_string());
135
+ }
136
+ }
137
+ codes
138
+ }
139
+
140
+ fn certainty(intent: IntentKind, policy_action: &str, risk_codes: &[String]) -> String {
141
+ if !risk_codes.is_empty() && policy_action == "block_unsafe_intent" {
142
+ "unsafe"
143
+ } else if policy_action == "block_ambiguous_intent"
144
+ || policy_action == "block_auto_baseline_due_to_no_commit"
145
+ || intent == IntentKind::Ambiguous
146
+ {
147
+ "ambiguous"
148
+ } else if matches!(
149
+ intent,
150
+ IntentKind::CommitRequest
151
+ | IntentKind::StatusQuestion
152
+ | IntentKind::ReviewRequest
153
+ | IntentKind::RepairRequest
154
+ | IntentKind::NoCommitRequest
155
+ | IntentKind::CancelRequest
156
+ ) {
157
+ "exact"
158
+ } else {
159
+ "likely"
160
+ }
161
+ .to_string()
162
+ }
@@ -0,0 +1,17 @@
1
+ use super::model::IntentKind;
2
+ use super::resolver_catalog::{
3
+ POLICY_ACTIVE_CANCEL, POLICY_ACTIVE_CONTINUE, POLICY_ACTIVE_NEW_BLOCK, POLICY_ACTIVE_NO_COMMIT,
4
+ POLICY_ACTIVE_REVIEW,
5
+ };
6
+ use super::resolver_shared::{ambiguous_policy, Policy};
7
+
8
+ pub(crate) fn active_policy(intent: IntentKind) -> Policy {
9
+ match intent {
10
+ IntentKind::ReviewRequest => POLICY_ACTIVE_REVIEW,
11
+ IntentKind::CancelRequest => POLICY_ACTIVE_CANCEL,
12
+ IntentKind::NoCommitRequest => POLICY_ACTIVE_NO_COMMIT,
13
+ IntentKind::TaskRevision | IntentKind::TaskCompletion => POLICY_ACTIVE_CONTINUE,
14
+ IntentKind::NewTask => POLICY_ACTIVE_NEW_BLOCK,
15
+ _ => ambiguous_policy(),
16
+ }
17
+ }
@@ -0,0 +1,55 @@
1
+ use super::model::IntentKind;
2
+ use super::resolver::DirtyDiffReadiness;
3
+ use super::resolver_catalog::{
4
+ POLICY_BASELINE_BLOCK, POLICY_BASELINE_CANCEL, POLICY_BASELINE_COMMIT_UPGRADE,
5
+ POLICY_BASELINE_NEW_REFRESH, POLICY_BASELINE_NEW_UPGRADE, POLICY_BASELINE_NO_COMMIT,
6
+ POLICY_BASELINE_REVIEW, POLICY_DIRTY_BLOCK, POLICY_DIRTY_COMMIT_USER,
7
+ POLICY_DIRTY_NEW_ISOLATED, POLICY_DIRTY_NEW_REFRESH, POLICY_DIRTY_NEW_WORKTREE,
8
+ };
9
+ use super::resolver_shared::{harness_refresh_policy, unsafe_policy, Policy};
10
+
11
+ pub(crate) fn dirty_policy(intent: IntentKind, dirty: DirtyDiffReadiness, risky: bool) -> Policy {
12
+ match (intent, dirty, risky) {
13
+ (IntentKind::RepairRequest, DirtyDiffReadiness::HarnessRefreshOnly, false) => {
14
+ harness_refresh_policy()
15
+ }
16
+ (
17
+ IntentKind::RepairRequest,
18
+ DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty,
19
+ false,
20
+ ) => harness_refresh_policy(),
21
+ (IntentKind::NewTask, DirtyDiffReadiness::HarnessRefreshOnly, false) => {
22
+ POLICY_DIRTY_NEW_REFRESH
23
+ }
24
+ (IntentKind::NewTask, DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty, false) => {
25
+ POLICY_DIRTY_NEW_ISOLATED
26
+ }
27
+ (IntentKind::NewTask, _, false) => POLICY_DIRTY_NEW_WORKTREE,
28
+ (IntentKind::CommitRequest, _, false) => POLICY_DIRTY_COMMIT_USER,
29
+ _ => POLICY_DIRTY_BLOCK,
30
+ }
31
+ }
32
+
33
+ pub(crate) fn baseline_policy(
34
+ intent: IntentKind,
35
+ dirty: DirtyDiffReadiness,
36
+ risky: bool,
37
+ ) -> Policy {
38
+ match intent {
39
+ IntentKind::NoCommitRequest => POLICY_BASELINE_NO_COMMIT,
40
+ IntentKind::ReviewRequest => POLICY_BASELINE_REVIEW,
41
+ IntentKind::CancelRequest => POLICY_BASELINE_CANCEL,
42
+ IntentKind::CommitRequest | IntentKind::RepairRequest
43
+ if dirty == DirtyDiffReadiness::HarnessRefreshOnly =>
44
+ {
45
+ harness_refresh_policy()
46
+ }
47
+ IntentKind::CommitRequest | IntentKind::RepairRequest => POLICY_BASELINE_COMMIT_UPGRADE,
48
+ IntentKind::NewTask if dirty == DirtyDiffReadiness::HarnessRefreshOnly && !risky => {
49
+ POLICY_BASELINE_NEW_REFRESH
50
+ }
51
+ IntentKind::NewTask if !risky => POLICY_BASELINE_NEW_UPGRADE,
52
+ IntentKind::NewTask => unsafe_policy(),
53
+ _ => POLICY_BASELINE_BLOCK,
54
+ }
55
+ }