@lamentis/naome 1.1.1 → 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.
Files changed (126) 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 +44 -4
  6. package/bin/naome.js +54 -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 +36 -0
  11. package/crates/naome-cli/src/install_bridge.rs +83 -0
  12. package/crates/naome-cli/src/main.rs +57 -341
  13. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  14. package/crates/naome-cli/src/quality_commands.rs +141 -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/harness_health/integrity.rs +96 -0
  19. package/crates/naome-core/src/harness_health.rs +14 -126
  20. package/crates/naome-core/src/install_plan.rs +3 -0
  21. package/crates/naome-core/src/intent/classifier.rs +171 -0
  22. package/crates/naome-core/src/intent/envelope.rs +108 -0
  23. package/crates/naome-core/src/intent/legacy.rs +138 -0
  24. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  25. package/crates/naome-core/src/intent/model.rs +71 -0
  26. package/crates/naome-core/src/intent/patterns.rs +170 -0
  27. package/crates/naome-core/src/intent/resolver.rs +162 -0
  28. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  29. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  30. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  31. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  32. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  33. package/crates/naome-core/src/intent/risk.rs +40 -0
  34. package/crates/naome-core/src/intent/segment.rs +170 -0
  35. package/crates/naome-core/src/intent.rs +64 -879
  36. package/crates/naome-core/src/journal.rs +9 -20
  37. package/crates/naome-core/src/lib.rs +13 -0
  38. package/crates/naome-core/src/quality/adapters.rs +178 -0
  39. package/crates/naome-core/src/quality/baseline.rs +75 -0
  40. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  41. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  42. package/crates/naome-core/src/quality/checks.rs +228 -0
  43. package/crates/naome-core/src/quality/cleanup.rs +72 -0
  44. package/crates/naome-core/src/quality/config.rs +109 -0
  45. package/crates/naome-core/src/quality/mod.rs +90 -0
  46. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  47. package/crates/naome-core/src/quality/scanner.rs +367 -0
  48. package/crates/naome-core/src/quality/types.rs +289 -0
  49. package/crates/naome-core/src/route.rs +292 -17
  50. package/crates/naome-core/src/task_state/admission.rs +63 -0
  51. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  52. package/crates/naome-core/src/task_state/api.rs +130 -0
  53. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  54. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  55. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  56. package/crates/naome-core/src/task_state/completion.rs +72 -0
  57. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  58. package/crates/naome-core/src/task_state/diff.rs +95 -0
  59. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  60. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  61. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  62. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  63. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  64. package/crates/naome-core/src/task_state/mod.rs +38 -0
  65. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  66. package/crates/naome-core/src/task_state/progress.rs +123 -0
  67. package/crates/naome-core/src/task_state/proof.rs +139 -0
  68. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  69. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  70. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  71. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  72. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  73. package/crates/naome-core/src/task_state/repair.rs +168 -0
  74. package/crates/naome-core/src/task_state/shape.rs +117 -0
  75. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  76. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  77. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  78. package/crates/naome-core/src/task_state/types.rs +87 -0
  79. package/crates/naome-core/src/task_state/util.rs +137 -0
  80. package/crates/naome-core/src/verification/render.rs +122 -0
  81. package/crates/naome-core/src/verification.rs +176 -58
  82. package/crates/naome-core/src/verification_contract.rs +49 -21
  83. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  84. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  85. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  86. package/crates/naome-core/src/workflow/mod.rs +18 -0
  87. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  88. package/crates/naome-core/src/workflow/output.rs +111 -0
  89. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  90. package/crates/naome-core/src/workflow/phases.rs +169 -0
  91. package/crates/naome-core/src/workflow/policy.rs +156 -0
  92. package/crates/naome-core/src/workflow/processes.rs +91 -0
  93. package/crates/naome-core/src/workflow/types.rs +42 -0
  94. package/crates/naome-core/tests/harness_health.rs +3 -0
  95. package/crates/naome-core/tests/intent.rs +97 -792
  96. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  97. package/crates/naome-core/tests/intent_v2.rs +90 -0
  98. package/crates/naome-core/tests/quality.rs +425 -0
  99. package/crates/naome-core/tests/route.rs +221 -4
  100. package/crates/naome-core/tests/task_state.rs +3 -0
  101. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  102. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  103. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  104. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  105. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  106. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  107. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  108. package/native/darwin-arm64/naome +0 -0
  109. package/native/linux-x64/naome +0 -0
  110. package/package.json +2 -2
  111. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  112. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  113. package/templates/naome-root/.naome/bin/naome.js +34 -63
  114. package/templates/naome-root/.naome/manifest.json +20 -18
  115. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  116. package/templates/naome-root/.naome/repository-quality.json +24 -0
  117. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  118. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  119. package/templates/naome-root/.naome/verification.json +37 -0
  120. package/templates/naome-root/AGENTS.md +3 -0
  121. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  122. package/templates/naome-root/docs/naome/execution.md +25 -21
  123. package/templates/naome-root/docs/naome/index.md +4 -3
  124. package/templates/naome-root/docs/naome/repository-quality.md +43 -0
  125. package/templates/naome-root/docs/naome/testing.md +12 -0
  126. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -1,6 +1,19 @@
1
- use std::path::Path;
1
+ mod classifier;
2
+ mod envelope;
3
+ mod legacy;
4
+ mod legacy_response;
5
+ mod model;
6
+ mod patterns;
7
+ mod resolver;
8
+ mod resolver_active;
9
+ mod resolver_baseline;
10
+ mod resolver_catalog;
11
+ mod resolver_policy;
12
+ mod resolver_shared;
13
+ mod risk;
14
+ mod segment;
2
15
 
3
- use serde::Serialize;
16
+ use std::path::Path;
4
17
 
5
18
  use crate::decision::{evaluate_decision, EvaluationOptions};
6
19
  use crate::models::{Decision, NaomeError};
@@ -9,44 +22,9 @@ use crate::task_state::{
9
22
  validate_task_state, TaskStateMode, TaskStateOptions,
10
23
  };
11
24
 
12
- #[derive(Debug, Clone, Serialize, PartialEq, Eq)]
13
- #[serde(rename_all = "camelCase")]
14
- pub struct IntentDecision {
15
- pub schema: String,
16
- pub repo_state: String,
17
- pub blocked: bool,
18
- pub prompt_intent: String,
19
- pub certainty: String,
20
- pub policy_action: String,
21
- pub allowed: bool,
22
- pub summary: String,
23
- pub next_action: String,
24
- pub user_message: String,
25
- pub human_options: Vec<String>,
26
- pub internal_notes: Vec<String>,
27
- pub reason_codes: Vec<String>,
28
- pub risk_codes: Vec<String>,
29
- pub changed_paths: Vec<String>,
30
- pub required_context: Vec<String>,
31
- pub evidence: PromptEvidence,
32
- }
33
-
34
- #[derive(Debug, Clone, Serialize, PartialEq, Eq)]
35
- #[serde(rename_all = "camelCase")]
36
- pub struct PromptEvidence {
37
- pub references_current_task: bool,
38
- pub requests_new_goal: bool,
39
- pub requests_correction: bool,
40
- pub requests_status: bool,
41
- pub requests_review: bool,
42
- pub requests_repair: bool,
43
- pub requests_commit: bool,
44
- pub requests_no_commit: bool,
45
- pub requests_cancel: bool,
46
- pub requests_completion: bool,
47
- pub has_risky_terms: bool,
48
- pub is_empty: bool,
49
- }
25
+ use classifier::canonical_intent;
26
+ pub use legacy::{format_intent, IntentDecision, PromptEvidence};
27
+ use resolver::{resolve_intent, CompletedTaskReadiness, DirtyDiffReadiness};
50
28
 
51
29
  pub fn evaluate_intent(
52
30
  root: &Path,
@@ -54,861 +32,68 @@ pub fn evaluate_intent(
54
32
  options: EvaluationOptions,
55
33
  ) -> Result<IntentDecision, NaomeError> {
56
34
  let decision = evaluate_decision(root, options)?;
57
- let completed_task_readiness = if decision.state == "completed_task_unbaselined" {
58
- let report = validate_task_state(
59
- root,
60
- TaskStateOptions {
61
- mode: TaskStateMode::State,
62
- harness_health: None,
63
- },
64
- )?;
65
- if report.errors.is_empty() {
66
- CompletedTaskReadiness::Valid
67
- } else if completed_task_harness_refresh_diff(root)?.is_some() {
68
- CompletedTaskReadiness::ValidAfterHarnessRefresh
69
- } else if completed_task_commit_diff(root)?
70
- .is_some_and(|diff| !diff.unrelated_paths.is_empty())
71
- {
72
- CompletedTaskReadiness::ValidWithUnrelatedDirty
73
- } else {
74
- CompletedTaskReadiness::Invalid
75
- }
76
- } else {
77
- CompletedTaskReadiness::Valid
78
- };
79
-
80
- let dirty_diff_readiness = if matches!(
81
- decision.state.as_str(),
82
- "dirty_unowned_diff"
83
- | "task_admission_blocked"
84
- | "harness_repair_unbaselined"
85
- | "install_or_upgrade_unbaselined"
86
- ) {
87
- match harness_refresh_diff(root)? {
88
- Some(diff) if diff.unrelated_paths.is_empty() => DirtyDiffReadiness::HarnessRefreshOnly,
89
- Some(diff) if !diff.unrelated_paths.is_empty() => {
90
- DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty
91
- }
92
- _ => DirtyDiffReadiness::Unclassified,
93
- }
94
- } else {
95
- DirtyDiffReadiness::Unclassified
96
- };
97
-
98
- Ok(resolve_intent(
99
- decision,
100
- prompt,
35
+ let completed_task_readiness = completed_task_readiness(root, &decision)?;
36
+ let dirty_diff_readiness = dirty_diff_readiness(root, &decision)?;
37
+ let canonical = canonical_intent(prompt);
38
+ let resolved = resolve_intent(
39
+ &decision,
40
+ &canonical,
101
41
  completed_task_readiness,
102
42
  dirty_diff_readiness,
103
- ))
104
- }
105
-
106
- pub fn format_intent(intent: &IntentDecision) -> String {
107
- let mut lines = vec![
108
- format!("NAOME intent: {}", intent.prompt_intent),
109
- format!("Repo state: {}", intent.repo_state),
110
- format!("Allowed: {}", intent.allowed),
111
- format!("Policy action: {}", intent.policy_action),
112
- format!("Summary: {}", intent.user_message),
113
- format!("Next action: {}", intent.next_action),
114
- ];
115
-
116
- if !intent.human_options.is_empty() {
117
- lines.push(format!(
118
- "Human options: {}",
119
- intent.human_options.join(", ")
120
- ));
121
- }
122
-
123
- if !intent.reason_codes.is_empty() {
124
- lines.push(format!("Reason codes: {}", intent.reason_codes.join(", ")));
125
- }
126
-
127
- if !intent.risk_codes.is_empty() {
128
- lines.push(format!("Risk codes: {}", intent.risk_codes.join(", ")));
129
- }
130
-
131
- if !intent.changed_paths.is_empty() {
132
- lines.push(format!(
133
- "Changed paths: {}",
134
- intent.changed_paths.join(", ")
135
- ));
136
- }
137
-
138
- lines.push(String::new());
139
- lines.join("\n")
140
- }
141
-
142
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
143
- enum CompletedTaskReadiness {
144
- Valid,
145
- ValidAfterHarnessRefresh,
146
- ValidWithUnrelatedDirty,
147
- Invalid,
148
- }
149
-
150
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
151
- enum DirtyDiffReadiness {
152
- Unclassified,
153
- HarnessRefreshOnly,
154
- HarnessRefreshWithUnrelatedDirty,
43
+ );
44
+ Ok(legacy::legacy_decision(decision, canonical, resolved))
155
45
  }
156
46
 
157
- fn resolve_intent(
158
- decision: Decision,
159
- prompt: &str,
160
- completed_task_readiness: CompletedTaskReadiness,
161
- dirty_diff_readiness: DirtyDiffReadiness,
162
- ) -> IntentDecision {
163
- let evidence = PromptEvidence::from_prompt(prompt);
164
- let prompt_intent = prompt_intent(&evidence);
165
- let mut reason_codes = vec![format!("repo_state:{}", decision.state)];
166
- let mut risk_codes = Vec::new();
167
- if evidence.has_risky_terms {
168
- risk_codes.push("prompt_contains_risky_terms".to_string());
169
- }
170
- if completed_task_readiness == CompletedTaskReadiness::Invalid {
171
- risk_codes.push("completed_task_proof_invalid".to_string());
47
+ fn completed_task_readiness(
48
+ root: &Path,
49
+ decision: &Decision,
50
+ ) -> Result<CompletedTaskReadiness, NaomeError> {
51
+ if decision.state != "completed_task_unbaselined" {
52
+ return Ok(CompletedTaskReadiness::Valid);
172
53
  }
173
54
 
174
- let (policy_action, allowed, summary, next_action) = if evidence.is_empty {
175
- (
176
- "block_ambiguous_intent",
177
- false,
178
- "The prompt is empty, so NAOME cannot infer a safe task transition.",
179
- "Ask for a concrete request before mutating the repository.",
180
- )
181
- } else if prompt_intent == "unsafe" {
182
- reason_codes.push("winning_rule:unsafe_intent_precedence".to_string());
183
- (
184
- "block_unsafe_intent",
185
- false,
186
- "The prompt contains risky terms or asks to bypass guardrails.",
187
- "Ask for a narrower request that does not expose secrets or bypass guardrails.",
188
- )
189
- } else if prompt_intent == "status_question" {
190
- (
191
- "answer_status_only",
192
- true,
193
- "The prompt asks for status or explanation, so no task mutation is needed.",
194
- "Answer from the current NAOME status without editing files.",
195
- )
196
- } else {
197
- match decision.state.as_str() {
198
- "harness_unhealthy" => (
199
- "repair_harness_only",
200
- false,
201
- "The harness is unhealthy, so normal task work is blocked.",
202
- "Repair or review the harness before accepting feature work.",
203
- ),
204
- "first_run_required" => (
205
- "run_first_run_protocol",
206
- false,
207
- "First-run intake must finish before feature work.",
208
- "Run the NAOME first-run protocol.",
209
- ),
210
- "upgrade_required" => (
211
- "run_upgrade_protocol",
212
- false,
213
- "A NAOME upgrade is pending before feature work.",
214
- "Run the NAOME upgrade protocol.",
215
- ),
216
- "ready_for_task" => match prompt_intent.as_str() {
217
- "new_task" => (
218
- "create_new_task",
219
- true,
220
- "The repository is clean and the prompt asks for a new goal.",
221
- "Create a NAOME task for the new request.",
222
- ),
223
- "commit_request" => (
224
- "block_ambiguous_intent",
225
- false,
226
- "The repository is already ready for work; there is no open task diff to baseline.",
227
- "Ask for the next concrete task or inspect status.",
228
- ),
229
- "review_request" => (
230
- "answer_status_only",
231
- true,
232
- "The prompt asks for review, but there is no open task diff.",
233
- "Answer from the current NAOME status without editing files.",
234
- ),
235
- "no_commit_request" => (
236
- "create_new_task_without_auto_baseline",
237
- true,
238
- "The repository is clean, so no commit is needed before the new task.",
239
- "Create a NAOME task for the new request.",
240
- ),
241
- "task_revision" => (
242
- "block_ambiguous_intent",
243
- false,
244
- "The prompt sounds like a correction, but there is no open task diff.",
245
- "Ask whether this is a new task or a status question.",
246
- ),
247
- _ => ambiguous_policy(),
248
- },
249
- "completed_task_unbaselined" => match prompt_intent.as_str() {
250
- "no_commit_request" => (
251
- "block_auto_baseline_due_to_no_commit",
252
- false,
253
- "The prompt explicitly blocks committing, so NAOME will not auto-baseline the completed task.",
254
- "Review, revise, cancel, or manually resolve the completed task diff.",
255
- ),
256
- "review_request" => (
257
- "review_task_diff",
258
- false,
259
- "The prompt explicitly asks to review the completed task diff.",
260
- "Review the completed task diff before any baseline or new task.",
261
- ),
262
- "cancel_request" => (
263
- "cancel_task_changes",
264
- false,
265
- "The prompt explicitly asks to cancel the completed task changes.",
266
- "Cancel or review the completed task diff before any new task.",
267
- ),
268
- "commit_request" => (
269
- "commit_task_baseline",
270
- true,
271
- "The prompt asks to baseline the completed task diff.",
272
- "Run the NAOME commit baseline flow for the completed task.",
273
- ),
274
- "task_revision" => (
275
- "reopen_completed_task_revision",
276
- true,
277
- "The prompt refers to follow-up correction on the unbaselined completed task.",
278
- "Reopen the same task as a revision and mark proof stale before editing.",
279
- ),
280
- "new_task"
281
- if completed_task_readiness == CompletedTaskReadiness::Valid
282
- && !evidence.has_risky_terms =>
283
- {
284
- (
285
- "auto_commit_completed_task_then_create_new_task",
286
- true,
287
- "The completed task is valid and the prompt asks for a distinct new goal.",
288
- "Baseline the completed task first, then create the next task.",
289
- )
290
- },
291
- "new_task"
292
- if completed_task_readiness == CompletedTaskReadiness::ValidAfterHarnessRefresh
293
- && !evidence.has_risky_terms =>
294
- {
295
- (
296
- "auto_commit_harness_refresh_then_completed_task_then_create_new_task",
297
- true,
298
- "The completed task is valid after separating deterministic harness refresh changes.",
299
- "Baseline the harness refresh first, then baseline the completed task and create the next task.",
300
- )
301
- },
302
- "new_task"
303
- if completed_task_readiness == CompletedTaskReadiness::ValidWithUnrelatedDirty
304
- && !evidence.has_risky_terms =>
305
- {
306
- (
307
- "auto_commit_completed_task_then_create_isolated_task_worktree",
308
- true,
309
- "The completed task is valid, and unrelated user edits are present.",
310
- "Baseline only the completed task paths, preserve unrelated user edits, and create an isolated worktree for the next task.",
311
- )
312
- },
313
- "new_task" => (
314
- "block_unsafe_intent",
315
- false,
316
- "The prompt asks for a new task, but the current completed task is not safe to auto-baseline.",
317
- "Review the completed diff and proof before starting new work.",
318
- ),
319
- _ => ambiguous_policy(),
320
- },
321
- "active_task_in_progress" => match prompt_intent.as_str() {
322
- "review_request" => (
323
- "review_current_task_diff",
324
- false,
325
- "The prompt asks to review the active task diff.",
326
- "Review the active task before mutating it further.",
327
- ),
328
- "cancel_request" => (
329
- "cancel_current_task",
330
- false,
331
- "The prompt asks to cancel the active task.",
332
- "Cancel or review the active task before starting another.",
333
- ),
334
- "no_commit_request" => (
335
- "continue_current_task_without_commit",
336
- true,
337
- "A task is active and the prompt blocks committing.",
338
- "Continue the active task without baselining.",
339
- ),
340
- "task_revision" | "task_completion" => (
341
- "continue_current_task",
342
- true,
343
- "A task is active and the prompt appears to continue or finish it.",
344
- "Continue the current task and keep proof current.",
345
- ),
346
- "new_task" => (
347
- "block_ambiguous_intent",
348
- false,
349
- "A task is already active and the prompt appears to ask for a distinct new goal.",
350
- "Complete, revise, or cancel the active task before starting another.",
351
- ),
352
- _ => ambiguous_policy(),
353
- },
354
- "active_task_blocked" => (
355
- "block_unowned_diff",
356
- false,
357
- "The active task is blocked by out-of-scope changes.",
358
- "Resolve the scope blocker before interpreting new work.",
359
- ),
360
- "dirty_unowned_diff" | "task_admission_blocked" => match prompt_intent.as_str() {
361
- "repair_request"
362
- if dirty_diff_readiness == DirtyDiffReadiness::HarnessRefreshOnly
363
- && !evidence.has_risky_terms =>
364
- {
365
- (
366
- "auto_commit_harness_refresh_baseline",
367
- true,
368
- "The repository has only deterministic harness refresh changes.",
369
- "Baseline the harness refresh before feature work.",
370
- )
371
- }
372
- "repair_request"
373
- if dirty_diff_readiness
374
- == DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty
375
- && !evidence.has_risky_terms =>
376
- {
377
- (
378
- "auto_commit_harness_refresh_baseline",
379
- true,
380
- "The repository has deterministic harness refresh changes plus unrelated user edits.",
381
- "Baseline only the harness refresh and leave unrelated user edits untouched.",
382
- )
383
- }
384
- "new_task"
385
- if dirty_diff_readiness == DirtyDiffReadiness::HarnessRefreshOnly
386
- && !evidence.has_risky_terms =>
387
- {
388
- (
389
- "auto_commit_harness_refresh_then_create_new_task",
390
- true,
391
- "The repository has only deterministic harness refresh changes.",
392
- "Baseline the harness refresh first, then create the next task in the same worktree.",
393
- )
394
- }
395
- "new_task"
396
- if dirty_diff_readiness
397
- == DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty
398
- && !evidence.has_risky_terms =>
399
- {
400
- (
401
- "auto_commit_harness_refresh_then_create_isolated_task_worktree",
402
- true,
403
- "The repository has deterministic harness refresh changes plus unrelated user edits.",
404
- "Baseline only the harness refresh first, then create an isolated task worktree without touching user edits.",
405
- )
406
- }
407
- "new_task" if !evidence.has_risky_terms => (
408
- "create_isolated_task_worktree",
409
- true,
410
- "The repository has unrelated user changes, and the prompt asks for a new task.",
411
- "Create an isolated NAOME task worktree so the new task can proceed without touching those user changes.",
412
- ),
413
- "commit_request" if !evidence.has_risky_terms => (
414
- "commit_user_diff_with_quality_gate",
415
- true,
416
- "The prompt asks to commit unowned user changes.",
417
- "Run NAOME's user-diff quality gate, then commit only the current changed paths if every required check passes.",
418
- ),
419
- _ => (
420
- "block_unowned_diff",
421
- false,
422
- "The repository has unowned changes that must be resolved first.",
423
- "NAOME will not commit unowned changes. Review them, or route a new task so NAOME can isolate task work in a separate worktree.",
424
- ),
425
- },
426
- "install_or_upgrade_unbaselined" | "harness_repair_unbaselined" => {
427
- if prompt_intent == "no_commit_request" {
428
- (
429
- "block_auto_baseline_due_to_no_commit",
430
- false,
431
- "The prompt explicitly blocks committing, so NAOME will not auto-baseline setup or repair changes.",
432
- "Review, cancel, or manually resolve the setup diff before feature work.",
433
- )
434
- } else if prompt_intent == "review_request" {
435
- (
436
- "review_diff_first",
437
- false,
438
- "The prompt explicitly asks to review setup, upgrade, or repair changes.",
439
- "Review the NAOME diff before any baseline or new task.",
440
- )
441
- } else if prompt_intent == "cancel_request" {
442
- (
443
- "cancel_upgrade_baseline",
444
- false,
445
- "The prompt explicitly asks to cancel setup, upgrade, or repair changes.",
446
- "Cancel or review the NAOME diff before feature work.",
447
- )
448
- } else if prompt_intent == "commit_request"
449
- && dirty_diff_readiness == DirtyDiffReadiness::HarnessRefreshOnly
450
- {
451
- (
452
- "auto_commit_harness_refresh_baseline",
453
- true,
454
- "The prompt asks to baseline a deterministic harness refresh.",
455
- "Baseline only the harness refresh before feature work.",
456
- )
457
- } else if prompt_intent == "commit_request" {
458
- (
459
- "commit_upgrade_baseline",
460
- true,
461
- "The prompt asks to baseline pending setup, upgrade, or repair changes.",
462
- "Run the appropriate NAOME baseline flow before feature work.",
463
- )
464
- } else if prompt_intent == "repair_request"
465
- && dirty_diff_readiness == DirtyDiffReadiness::HarnessRefreshOnly
466
- {
467
- (
468
- "auto_commit_harness_refresh_baseline",
469
- true,
470
- "The prompt asks to repair or baseline a deterministic harness refresh.",
471
- "Baseline only the harness refresh before feature work.",
472
- )
473
- } else if prompt_intent == "repair_request" {
474
- (
475
- "commit_upgrade_baseline",
476
- true,
477
- "The prompt asks to repair or baseline pending setup, upgrade, or repair changes.",
478
- "Run the appropriate NAOME baseline flow before feature work.",
479
- )
480
- } else if prompt_intent == "new_task"
481
- && dirty_diff_readiness == DirtyDiffReadiness::HarnessRefreshOnly
482
- && !evidence.has_risky_terms
483
- {
484
- (
485
- "auto_commit_harness_refresh_then_create_new_task",
486
- true,
487
- "The prompt asks for a new goal and the only blocker is a deterministic harness refresh.",
488
- "Baseline the harness refresh automatically, rerun admission, then create the new task in the same worktree.",
489
- )
490
- } else if prompt_intent == "new_task" && !evidence.has_risky_terms {
491
- (
492
- "auto_commit_upgrade_baseline_then_create_new_task",
493
- true,
494
- "The prompt asks for a new goal and the only blocker is a completed NAOME setup, upgrade, or repair diff.",
495
- "Baseline the NAOME diff automatically, rerun admission, then create the new task.",
496
- )
497
- } else if prompt_intent == "new_task" {
498
- (
499
- "block_unsafe_intent",
500
- false,
501
- "The prompt asks for a new task, but risky terms make automatic baseline unsafe.",
502
- "Review the setup diff and prompt before starting feature work.",
503
- )
504
- } else {
505
- (
506
- "block_unowned_diff",
507
- false,
508
- "Setup, upgrade, or repair changes must be resolved before task routing.",
509
- "Resolve the harness baseline decision before accepting feature work.",
510
- )
511
- }
512
- }
513
- _ => (
514
- "block_ambiguous_intent",
515
- false,
516
- "NAOME cannot safely map this prompt to a task transition for the current state.",
517
- "Review the machine status and ask for a narrower request.",
518
- ),
519
- }
520
- };
521
-
522
- if evidence.references_current_task {
523
- reason_codes.push("prompt_references_current_task".to_string());
524
- }
525
- if evidence.requests_new_goal {
526
- reason_codes.push("prompt_requests_new_goal".to_string());
527
- }
528
- if evidence.requests_correction {
529
- reason_codes.push("prompt_requests_correction".to_string());
530
- }
531
- if evidence.requests_commit {
532
- reason_codes.push("prompt_requests_commit".to_string());
533
- }
534
- if evidence.requests_no_commit {
535
- reason_codes.push("prompt_blocks_commit".to_string());
536
- }
537
- if evidence.requests_review {
538
- reason_codes.push("prompt_requests_review".to_string());
539
- }
540
- if evidence.requests_repair {
541
- reason_codes.push("prompt_requests_repair".to_string());
542
- }
543
- if evidence.requests_cancel {
544
- reason_codes.push("prompt_requests_cancel".to_string());
55
+ let report = validate_task_state(
56
+ root,
57
+ TaskStateOptions {
58
+ mode: TaskStateMode::State,
59
+ harness_health: None,
60
+ },
61
+ )?;
62
+ if report.errors.is_empty() {
63
+ return Ok(CompletedTaskReadiness::Valid);
545
64
  }
546
65
 
547
- let certainty = if !risk_codes.is_empty() && policy_action == "block_unsafe_intent" {
548
- "unsafe"
549
- } else if policy_action == "block_ambiguous_intent"
550
- || policy_action == "block_auto_baseline_due_to_no_commit"
551
- {
552
- "ambiguous"
553
- } else if evidence.requests_commit
554
- || evidence.requests_status
555
- || evidence.requests_review
556
- || evidence.requests_repair
557
- || evidence.requests_no_commit
558
- || evidence.requests_cancel
559
- {
560
- "exact"
561
- } else {
562
- "likely"
563
- };
564
-
565
- let (user_message, human_options, mut internal_notes) =
566
- response_policy(&decision, &prompt_intent, policy_action, summary);
567
- internal_notes.extend(
568
- reason_codes
569
- .iter()
570
- .filter(|code| code.starts_with("winning_rule:"))
571
- .cloned(),
572
- );
573
-
574
- IntentDecision {
575
- schema: "naome.intent.v1".to_string(),
576
- repo_state: decision.state,
577
- blocked: decision.blocked,
578
- prompt_intent,
579
- certainty: certainty.to_string(),
580
- policy_action: policy_action.to_string(),
581
- allowed,
582
- summary: summary.to_string(),
583
- next_action: next_action.to_string(),
584
- user_message,
585
- human_options,
586
- internal_notes,
587
- reason_codes,
588
- risk_codes,
589
- changed_paths: decision.changed_paths,
590
- required_context: decision.required_context,
591
- evidence,
66
+ if completed_task_harness_refresh_diff(root)?.is_some() {
67
+ return Ok(CompletedTaskReadiness::ValidAfterHarnessRefresh);
592
68
  }
593
- }
594
69
 
595
- impl PromptEvidence {
596
- fn from_prompt(prompt: &str) -> Self {
597
- let normalized = normalize_prompt(prompt);
598
- let is_empty = normalized.trim().is_empty();
599
- let references_current_task = contains_any(
600
- &normalized,
601
- &[
602
- "current task",
603
- "active task",
604
- "same task",
605
- "diesen task",
606
- "diesem task",
607
- "aktuellen task",
608
- "gleichen task",
609
- "vorherigen task",
610
- "diese änderung",
611
- "diesen diff",
612
- "das noch",
613
- ],
614
- );
615
- let uses_deprecated_clear_or_commit = normalized.contains("clear_or_commit_unowned_diff");
616
- let requests_commit = !uses_deprecated_clear_or_commit
617
- && contains_any(
618
- &normalized,
619
- &[
620
- "commit",
621
- "committe",
622
- "comitten",
623
- "committen",
624
- "baseline",
625
- "baselinen",
626
- "commit_task_baseline",
627
- ],
628
- );
629
- let requests_no_commit = contains_any(
630
- &normalized,
631
- &[
632
- "do not commit",
633
- "don't commit",
634
- "dont commit",
635
- "without committing",
636
- "no commit",
637
- "nicht committen",
638
- "nicht comitten",
639
- "nicht baselinen",
640
- "nicht commit",
641
- "kein commit",
642
- ],
643
- );
644
- let requests_review = contains_any(
645
- &normalized,
646
- &[
647
- "review",
648
- "review_task_diff",
649
- "review_diff_first",
650
- "show diff",
651
- "zeige diff",
652
- "zeig diff",
653
- "prüfe",
654
- "pruefe",
655
- "prüfen",
656
- "pruefen",
657
- "anschauen",
658
- "ansehen",
659
- ],
660
- );
661
- let requests_repair = contains_any(
662
- &normalized,
663
- &[
664
- "repair",
665
- "repair all",
666
- "repair_harness",
667
- "repariere",
668
- "reparieren",
669
- "reparier",
670
- "sync",
671
- ],
672
- );
673
- let requests_cancel = contains_any(
674
- &normalized,
675
- &[
676
- "cancel",
677
- "abbrechen",
678
- "verwerfen",
679
- "zurücksetzen",
680
- "cancel_task_changes",
681
- ],
682
- );
683
- let requests_completion = contains_any(
684
- &normalized,
685
- &[
686
- "complete",
687
- "abschließen",
688
- "abschliessen",
689
- "fertigstellen",
690
- "mark complete",
691
- "als fertig",
692
- ],
693
- );
694
- let requests_correction = references_current_task
695
- || contains_any(
696
- &normalized,
697
- &[
698
- "auch noch",
699
- "kleinigkeit",
700
- "korrektur",
701
- "korrigiere",
702
- "korrigier",
703
- "nachbessern",
704
- "nachbesserung",
705
- "ändern",
706
- "aendern",
707
- "ändere",
708
- "fix das",
709
- "fixe das",
710
- "request_task_changes",
711
- "reopen",
712
- "erneut öffnen",
713
- "weiterführen",
714
- "weiter fuehren",
715
- ],
716
- );
717
- let requests_new_goal = contains_any(
718
- &normalized,
719
- &[
720
- "new task",
721
- "neuer task",
722
- "neuen task",
723
- "neue aufgabe",
724
- "nächster task",
725
- "naechster task",
726
- "implementiere",
727
- "implementieren",
728
- "baue",
729
- "bauen",
730
- "füge",
731
- "fuege",
732
- "erstelle",
733
- "erstellen",
734
- "mach bitte",
735
- "add ",
736
- "create ",
737
- ],
738
- );
739
- let requests_status = contains_any(
740
- &normalized,
741
- &[
742
- "status",
743
- "stand",
744
- "was ist",
745
- "warum",
746
- "wieso",
747
- "wie können",
748
- "wie koennen",
749
- "wie sollten",
750
- "erklär",
751
- "erklaer",
752
- "zeige",
753
- "zeig",
754
- "what do you say",
755
- ],
756
- ) && !requests_new_goal
757
- && !requests_correction
758
- && !requests_commit
759
- && !requests_completion
760
- && !requests_repair;
761
- let has_risky_terms = contains_any(
762
- &normalized,
763
- &[
764
- "secret",
765
- "token",
766
- "api key",
767
- "private key",
768
- "passwort",
769
- "credential",
770
- "npm publish",
771
- "git push",
772
- "force push",
773
- "force-push",
774
- "--no-verify",
775
- ],
776
- );
777
-
778
- Self {
779
- references_current_task,
780
- requests_new_goal,
781
- requests_correction,
782
- requests_status,
783
- requests_review,
784
- requests_repair,
785
- requests_commit,
786
- requests_no_commit,
787
- requests_cancel,
788
- requests_completion,
789
- has_risky_terms,
790
- is_empty,
791
- }
70
+ if completed_task_commit_diff(root)?.is_some_and(|diff| !diff.unrelated_paths.is_empty()) {
71
+ return Ok(CompletedTaskReadiness::ValidWithUnrelatedDirty);
792
72
  }
793
- }
794
73
 
795
- fn prompt_intent(evidence: &PromptEvidence) -> String {
796
- let intent = if evidence.is_empty {
797
- "ambiguous"
798
- } else if evidence.has_risky_terms {
799
- "unsafe"
800
- } else if evidence.requests_status {
801
- "status_question"
802
- } else if evidence.requests_repair {
803
- "repair_request"
804
- } else if evidence.requests_cancel {
805
- "cancel_request"
806
- } else if evidence.requests_review {
807
- "review_request"
808
- } else if evidence.requests_no_commit {
809
- "no_commit_request"
810
- } else if evidence.requests_commit {
811
- "commit_request"
812
- } else if evidence.requests_new_goal
813
- && !(evidence.references_current_task && evidence.requests_correction)
814
- {
815
- "new_task"
816
- } else if evidence.requests_correction {
817
- "task_revision"
818
- } else if evidence.requests_completion {
819
- "task_completion"
820
- } else {
821
- "ambiguous"
822
- };
823
-
824
- intent.to_string()
74
+ Ok(CompletedTaskReadiness::Invalid)
825
75
  }
826
76
 
827
- fn response_policy(
77
+ fn dirty_diff_readiness(
78
+ root: &Path,
828
79
  decision: &Decision,
829
- prompt_intent: &str,
830
- policy_action: &str,
831
- summary: &str,
832
- ) -> (String, Vec<String>, Vec<String>) {
833
- let mut internal_notes = vec![
834
- format!("repo_state:{}", decision.state),
835
- format!("prompt_intent:{prompt_intent}"),
836
- format!("policy_action:{policy_action}"),
837
- ];
838
- if !decision.allowed_actions.is_empty() {
839
- internal_notes.push(format!(
840
- "internal_allowed_actions:{}",
841
- decision.allowed_actions.join(",")
842
- ));
80
+ ) -> Result<DirtyDiffReadiness, NaomeError> {
81
+ if !matches!(
82
+ decision.state.as_str(),
83
+ "dirty_unowned_diff"
84
+ | "task_admission_blocked"
85
+ | "harness_repair_unbaselined"
86
+ | "install_or_upgrade_unbaselined"
87
+ ) {
88
+ return Ok(DirtyDiffReadiness::Unclassified);
843
89
  }
844
90
 
845
- let human_options = match policy_action {
846
- "review_task_diff" => decision.allowed_actions.clone(),
847
- "cancel_task_changes" => vec![
848
- "review_task_diff".to_string(),
849
- "cancel_task_changes".to_string(),
850
- ],
851
- "block_auto_baseline_due_to_no_commit" => decision
852
- .allowed_actions
853
- .iter()
854
- .filter(|action| !action.starts_with("commit_"))
855
- .cloned()
856
- .collect(),
857
- "block_unsafe_intent" => vec!["narrow_request".to_string(), "review_status".to_string()],
858
- "block_ambiguous_intent" => decision.allowed_actions.clone(),
859
- "block_unowned_diff" => decision
860
- .allowed_actions
861
- .iter()
862
- .filter(|action| action.as_str() == "review_unowned_diff")
863
- .cloned()
864
- .collect(),
865
- "repair_harness_only" => decision.allowed_actions.clone(),
866
- "review_diff_first" | "cancel_upgrade_baseline" => decision.allowed_actions.clone(),
867
- "review_current_task_diff" | "cancel_current_task" => decision.allowed_actions.clone(),
868
- _ => Vec::new(),
869
- };
870
-
871
- let user_message = match policy_action {
872
- "auto_commit_completed_task_then_create_new_task" => "The completed task is valid. NAOME can baseline it automatically before starting the next separate task.".to_string(),
873
- "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.".to_string(),
874
- "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.".to_string(),
875
- "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.".to_string(),
876
- "auto_commit_harness_refresh_baseline" => "NAOME can baseline deterministic harness refresh changes and leave unrelated user edits untouched.".to_string(),
877
- "create_isolated_task_worktree" => "NAOME can create an isolated task worktree and leave existing user edits untouched.".to_string(),
878
- "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.".to_string(),
879
- "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.".to_string(),
880
- "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.".to_string(),
881
- "create_new_task" | "create_new_task_without_auto_baseline" => "NAOME is ready to create the next task.".to_string(),
882
- "answer_status_only" => summary.to_string(),
883
- "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.".to_string(),
884
- "review_task_diff" | "review_diff_first" | "review_current_task_diff" => "The prompt asks for review, so NAOME will not mutate the repository automatically.".to_string(),
885
- "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.".to_string(),
886
- "block_unsafe_intent" => "NAOME blocked this request because it contains unsafe or guardrail-bypass wording.".to_string(),
887
- "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.".to_string(),
888
- "repair_harness_only" => "NAOME harness health failed, so normal task work is blocked until repair or review.".to_string(),
889
- _ => summary.to_string(),
91
+ let readiness = match harness_refresh_diff(root)? {
92
+ Some(diff) if diff.unrelated_paths.is_empty() => DirtyDiffReadiness::HarnessRefreshOnly,
93
+ Some(diff) if !diff.unrelated_paths.is_empty() => {
94
+ DirtyDiffReadiness::HarnessRefreshWithUnrelatedDirty
95
+ }
96
+ _ => DirtyDiffReadiness::Unclassified,
890
97
  };
891
-
892
- (user_message, human_options, internal_notes)
893
- }
894
-
895
- fn ambiguous_policy() -> (&'static str, bool, &'static str, &'static str) {
896
- (
897
- "block_ambiguous_intent",
898
- false,
899
- "The prompt does not clearly map to a safe NAOME task transition.",
900
- "Ask for a narrower request or inspect status.",
901
- )
902
- }
903
-
904
- fn normalize_prompt(prompt: &str) -> String {
905
- prompt
906
- .to_lowercase()
907
- .split_whitespace()
908
- .collect::<Vec<_>>()
909
- .join(" ")
910
- }
911
-
912
- fn contains_any(value: &str, needles: &[&str]) -> bool {
913
- needles.iter().any(|needle| value.contains(needle))
98
+ Ok(readiness)
914
99
  }