@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,146 @@
1
+ use std::path::Path;
2
+
3
+ use crate::models::NaomeError;
4
+ use crate::task_state::{
5
+ completed_task_harness_refresh_diff, harness_refresh_diff, harness_refresh_with_unrelated_diff,
6
+ };
7
+
8
+ use super::execution::RouteExecution;
9
+ use super::execution_support::{commit_completed_task, commit_harness_paths, set_created_worktree};
10
+ use super::git_ops::{git_add_all, git_commit, git_head};
11
+ use super::worktree::{
12
+ create_isolated_task_worktree_with_name_head, preflight_isolated_task_worktree,
13
+ task_worktree_name_head,
14
+ };
15
+
16
+ pub(super) fn baseline_completed_task(
17
+ root: &Path,
18
+ journal_action: &str,
19
+ message: &str,
20
+ execution: &mut RouteExecution,
21
+ ) -> Result<(), NaomeError> {
22
+ commit_completed_task(root, git_head(root)?, journal_action, execution)?;
23
+ execution.user_message = message.to_string();
24
+ Ok(())
25
+ }
26
+
27
+ pub(super) fn baseline_completed_task_then_worktree(
28
+ root: &Path,
29
+ prompt: &str,
30
+ execution: &mut RouteExecution,
31
+ ) -> Result<(), NaomeError> {
32
+ let name_head = task_worktree_name_head(root)?;
33
+ preflight_isolated_task_worktree(root, prompt, &name_head)?;
34
+ commit_completed_task(
35
+ root,
36
+ Some(name_head.clone()),
37
+ "route_auto_baseline",
38
+ execution,
39
+ )?;
40
+ let created = create_isolated_task_worktree_with_name_head(root, prompt, &name_head)?;
41
+ set_created_worktree(
42
+ root,
43
+ created,
44
+ "NAOME baselined the completed task and created an isolated task worktree",
45
+ execution,
46
+ );
47
+ Ok(())
48
+ }
49
+
50
+ pub(super) fn baseline_harness_refresh_then_completed_task(
51
+ root: &Path,
52
+ execution: &mut RouteExecution,
53
+ ) -> Result<(), NaomeError> {
54
+ let Some(split) = completed_task_harness_refresh_diff(root)? else {
55
+ return Err(NaomeError::new(
56
+ "Unable to split harness refresh paths from completed task diff.",
57
+ ));
58
+ };
59
+ commit_harness_paths(root, &split.harness_paths, execution)?;
60
+ baseline_completed_task(
61
+ root,
62
+ "route_auto_baseline",
63
+ "NAOME baselined the harness refresh and completed task, then admitted the next task.",
64
+ execution,
65
+ )
66
+ }
67
+
68
+ pub(super) fn baseline_harness_refresh_then_worktree(
69
+ root: &Path,
70
+ prompt: &str,
71
+ execution: &mut RouteExecution,
72
+ ) -> Result<(), NaomeError> {
73
+ let name_head = task_worktree_name_head(root)?;
74
+ preflight_isolated_task_worktree(root, prompt, &name_head)?;
75
+ let Some(split) = harness_refresh_with_unrelated_diff(root)? else {
76
+ return Err(NaomeError::new(
77
+ "Unable to split harness refresh paths from unrelated dirty paths.",
78
+ ));
79
+ };
80
+ commit_harness_paths(root, &split.harness_paths, execution)?;
81
+ let created = create_isolated_task_worktree_with_name_head(root, prompt, &name_head)?;
82
+ set_created_worktree(
83
+ root,
84
+ created,
85
+ "NAOME baselined the harness refresh and created an isolated task worktree",
86
+ execution,
87
+ );
88
+ Ok(())
89
+ }
90
+
91
+ pub(super) fn baseline_pure_harness_refresh(
92
+ root: &Path,
93
+ execution: &mut RouteExecution,
94
+ ) -> Result<(), NaomeError> {
95
+ let Some(diff) = harness_refresh_diff(root)? else {
96
+ return Err(NaomeError::new(
97
+ "Unable to find deterministic harness refresh paths.",
98
+ ));
99
+ };
100
+ if !diff.unrelated_paths.is_empty() {
101
+ return Err(NaomeError::new(
102
+ "Harness refresh baseline expected no unrelated dirty paths.",
103
+ ));
104
+ }
105
+ commit_harness_paths(root, &diff.harness_paths, execution)?;
106
+ execution.user_message =
107
+ "NAOME baselined the harness refresh and is ready to create the next task in the same worktree."
108
+ .to_string();
109
+ Ok(())
110
+ }
111
+
112
+ pub(super) fn baseline_harness_refresh(
113
+ root: &Path,
114
+ execution: &mut RouteExecution,
115
+ ) -> Result<(), NaomeError> {
116
+ let Some(split) = harness_refresh_diff(root)? else {
117
+ return Err(NaomeError::new(
118
+ "Unable to find deterministic harness refresh paths.",
119
+ ));
120
+ };
121
+ commit_harness_paths(root, &split.harness_paths, execution)?;
122
+ execution.user_message =
123
+ "NAOME baselined the deterministic harness refresh and left unrelated user edits untouched."
124
+ .to_string();
125
+ Ok(())
126
+ }
127
+
128
+ pub(super) fn baseline_setup(
129
+ root: &Path,
130
+ execution: &mut RouteExecution,
131
+ ) -> Result<(), NaomeError> {
132
+ commit_upgrade_baseline(root, execution)?;
133
+ execution.user_message =
134
+ "NAOME baselined the setup changes and is ready to create the next task.".to_string();
135
+ Ok(())
136
+ }
137
+
138
+ pub(super) fn commit_upgrade_baseline(
139
+ root: &Path,
140
+ execution: &mut RouteExecution,
141
+ ) -> Result<(), NaomeError> {
142
+ git_add_all(root)?;
143
+ git_commit(root, "chore(naome): baseline setup")?;
144
+ execution.mark_action("commit_upgrade_baseline");
145
+ Ok(())
146
+ }
@@ -0,0 +1,57 @@
1
+ use std::path::{Path, PathBuf};
2
+
3
+ use crate::journal::append_task_journal;
4
+ use crate::models::{Decision, NaomeError};
5
+
6
+ use super::execution::RouteExecution;
7
+ use super::git_ops::{git_add_completed_task_paths, git_commit, git_head, git_stage_only_paths};
8
+ use super::RouteWorktree;
9
+
10
+ pub(super) fn initial_decision_completed(decision: &Decision) -> bool {
11
+ decision
12
+ .task
13
+ .as_ref()
14
+ .map(|task| task.status.clone())
15
+ .as_deref()
16
+ .is_some_and(|status| status == "complete")
17
+ }
18
+
19
+ pub(super) fn commit_completed_task(
20
+ root: &Path,
21
+ before: Option<String>,
22
+ journal_action: &str,
23
+ execution: &mut RouteExecution,
24
+ ) -> Result<(), NaomeError> {
25
+ git_add_completed_task_paths(root)?;
26
+ git_commit(root, "chore(naome): baseline completed task")?;
27
+ execution.journal_entry = append_task_journal(root, journal_action, before, git_head(root)?)?;
28
+ execution.mark_action("commit_task_baseline");
29
+ Ok(())
30
+ }
31
+
32
+ pub(super) fn commit_harness_paths(
33
+ root: &Path,
34
+ paths: &[String],
35
+ execution: &mut RouteExecution,
36
+ ) -> Result<(), NaomeError> {
37
+ git_stage_only_paths(root, paths)?;
38
+ git_commit(root, "chore(naome): baseline harness refresh")?;
39
+ execution.mark_action("commit_harness_refresh_baseline");
40
+ Ok(())
41
+ }
42
+
43
+ pub(super) fn set_created_worktree(
44
+ root: &Path,
45
+ created: RouteWorktree,
46
+ prefix: &str,
47
+ execution: &mut RouteExecution,
48
+ ) {
49
+ execution.task_root = PathBuf::from(&created.path);
50
+ execution.mark_action("create_task_worktree");
51
+ execution.user_message = format!(
52
+ "{prefix} at {}. Continue the new task there; unrelated user edits remain untouched in the original worktree.",
53
+ created.path
54
+ );
55
+ debug_assert_eq!(created.source_root, root.to_string_lossy().to_string());
56
+ execution.worktree = Some(created);
57
+ }
@@ -0,0 +1,71 @@
1
+ use std::path::Path;
2
+
3
+ use crate::git;
4
+ use crate::journal::append_task_journal;
5
+ use crate::models::{Decision, NaomeError};
6
+
7
+ use super::execution::RouteExecution;
8
+ use super::execution_support::{initial_decision_completed, set_created_worktree};
9
+ use super::git_ops::{git_commit, git_head, git_stage_only_paths};
10
+ use super::quality_gate::run_user_diff_quality_gate;
11
+ use super::worktree::create_isolated_task_worktree;
12
+
13
+ pub(super) fn create_task_worktree(
14
+ root: &Path,
15
+ prompt: &str,
16
+ execution: &mut RouteExecution,
17
+ ) -> Result<(), NaomeError> {
18
+ let created = create_isolated_task_worktree(root, prompt)?;
19
+ set_created_worktree(
20
+ root,
21
+ created,
22
+ "NAOME created an isolated task worktree",
23
+ execution,
24
+ );
25
+ Ok(())
26
+ }
27
+
28
+ pub(super) fn commit_user_diff(
29
+ root: &Path,
30
+ execution: &mut RouteExecution,
31
+ ) -> Result<(), NaomeError> {
32
+ let paths = git::changed_paths(root)?;
33
+ execution
34
+ .executed_actions
35
+ .push("run_user_diff_quality_gate".to_string());
36
+ match run_user_diff_quality_gate(root, &paths) {
37
+ Ok(check_ids) => {
38
+ git_stage_only_paths(root, &paths)?;
39
+ git_commit(root, "chore(user): baseline verified user changes")?;
40
+ execution.mark_action("commit_user_diff");
41
+ execution.user_message = format!(
42
+ "NAOME quality gates passed ({}) and committed only the current user-owned changed paths.",
43
+ check_ids.join(", ")
44
+ );
45
+ }
46
+ Err(error) => {
47
+ execution.route_allowed = false;
48
+ execution.human_options = vec!["review_unowned_diff".to_string()];
49
+ execution.user_message =
50
+ format!("NAOME user-diff quality gate failed, so no commit was created. {error}");
51
+ }
52
+ }
53
+ Ok(())
54
+ }
55
+
56
+ pub(super) fn journal_external_baseline_if_needed(
57
+ root: &Path,
58
+ initial_decision: &Decision,
59
+ repo_state_before: &str,
60
+ execution: &mut RouteExecution,
61
+ ) -> Result<(), NaomeError> {
62
+ if repo_state_before != "ready_for_task" || !initial_decision_completed(initial_decision) {
63
+ return Ok(());
64
+ }
65
+ execution.journal_entry =
66
+ append_task_journal(root, "external_baseline", None, git_head(root)?)?;
67
+ if execution.journal_entry.is_some() {
68
+ execution.mark_action("journal_external_task_baseline");
69
+ }
70
+ Ok(())
71
+ }
@@ -0,0 +1,72 @@
1
+ use std::path::Path;
2
+ use std::process::{Command, Output};
3
+
4
+ use crate::models::NaomeError;
5
+ use crate::task_state::completed_task_commit_paths;
6
+
7
+ pub(super) fn git_head(root: &Path) -> Result<Option<String>, NaomeError> {
8
+ let output = git_output(root, &["rev-parse", "HEAD"])?;
9
+ if !output.status.success() {
10
+ return Ok(None);
11
+ }
12
+ Ok(Some(
13
+ String::from_utf8_lossy(&output.stdout).trim().to_string(),
14
+ ))
15
+ }
16
+
17
+ pub(super) fn git_add_all(root: &Path) -> Result<(), NaomeError> {
18
+ ensure_git_success(git_output(root, &["add", "-A"])?)
19
+ }
20
+
21
+ pub(super) fn git_add_completed_task_paths(root: &Path) -> Result<(), NaomeError> {
22
+ let paths = completed_task_commit_paths(root)?;
23
+ if paths.is_empty() {
24
+ return Err(NaomeError::new(
25
+ "No task-owned paths are available to commit.",
26
+ ));
27
+ }
28
+ git_stage_only_paths(root, &paths)
29
+ }
30
+
31
+ pub(super) fn git_stage_only_paths(root: &Path, paths: &[String]) -> Result<(), NaomeError> {
32
+ ensure_git_success(git_output(root, &["reset", "-q", "--", "."])?)?;
33
+ git_add_paths(root, paths)
34
+ }
35
+
36
+ fn git_add_paths(root: &Path, paths: &[String]) -> Result<(), NaomeError> {
37
+ if paths.is_empty() {
38
+ return Ok(());
39
+ }
40
+ let mut args = vec!["add", "--"];
41
+ args.extend(paths.iter().map(String::as_str));
42
+ let output = Command::new("git").args(args).current_dir(root).output()?;
43
+ if output.status.success() {
44
+ Ok(())
45
+ } else {
46
+ Err(NaomeError::new(command_output(&output)))
47
+ }
48
+ }
49
+
50
+ pub(super) fn git_commit(root: &Path, message: &str) -> Result<(), NaomeError> {
51
+ ensure_git_success(git_output(root, &["commit", "-m", message])?)
52
+ }
53
+
54
+ pub(super) fn git_output(root: &Path, args: &[&str]) -> Result<Output, NaomeError> {
55
+ Ok(Command::new("git").args(args).current_dir(root).output()?)
56
+ }
57
+
58
+ pub(super) fn ensure_git_success(output: Output) -> Result<(), NaomeError> {
59
+ if output.status.success() {
60
+ Ok(())
61
+ } else {
62
+ Err(NaomeError::new(command_output(&output)))
63
+ }
64
+ }
65
+
66
+ pub(super) fn command_output(output: &std::process::Output) -> String {
67
+ let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
68
+ if !stderr.is_empty() {
69
+ return stderr;
70
+ }
71
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
72
+ }
@@ -0,0 +1,73 @@
1
+ use std::path::Path;
2
+
3
+ use crate::git;
4
+ use crate::models::NaomeError;
5
+
6
+ use super::builtin_checks::run_quality_check;
7
+ pub(super) use super::quality_gate_config::QualityCheck;
8
+ use super::quality_gate_config::{user_diff_check_ids, verification_checks};
9
+ use super::quality_gate_snapshot::{
10
+ changed_path_snapshot, read_head_verification, sorted_path_set,
11
+ validate_changed_text_whitespace,
12
+ };
13
+
14
+ pub(super) fn run_user_diff_quality_gate(
15
+ root: &Path,
16
+ changed_paths: &[String],
17
+ ) -> Result<Vec<String>, NaomeError> {
18
+ if changed_paths.is_empty() {
19
+ return Err(NaomeError::new("No changed paths are available to commit."));
20
+ }
21
+ let verification = read_head_verification(root)?;
22
+ let checks = verification_checks(&verification);
23
+ let check_ids = user_diff_check_ids(&verification, changed_paths, &checks)?;
24
+
25
+ let initial_paths = sorted_path_set(changed_paths);
26
+ let mut previous_snapshot = changed_path_snapshot(root, changed_paths)?;
27
+ for _ in 0..3 {
28
+ validate_changed_text_whitespace(root, changed_paths)?;
29
+
30
+ for check_id in &check_ids {
31
+ let Some(check) = checks.get(check_id.as_str()) else {
32
+ return Err(NaomeError::new(format!(
33
+ "Quality check {check_id} is referenced but not defined."
34
+ )));
35
+ };
36
+ run_quality_check(root, check_id, check)?;
37
+ }
38
+
39
+ let current_paths = git::changed_paths(root)?;
40
+ let current_set = sorted_path_set(&current_paths);
41
+ if current_set != initial_paths {
42
+ return Err(NaomeError::new(format!(
43
+ "Quality checks changed the diff path set from [{}] to [{}].",
44
+ initial_paths.iter().cloned().collect::<Vec<_>>().join(", "),
45
+ current_set.iter().cloned().collect::<Vec<_>>().join(", ")
46
+ )));
47
+ }
48
+
49
+ validate_changed_text_whitespace(root, changed_paths)?;
50
+ if let Some(check) = checks.get("diff-check") {
51
+ run_quality_check(root, "diff-check", check)?;
52
+ }
53
+ let current_paths = git::changed_paths(root)?;
54
+ let current_set = sorted_path_set(&current_paths);
55
+ if current_set != initial_paths {
56
+ return Err(NaomeError::new(format!(
57
+ "Quality checks changed the diff path set from [{}] to [{}].",
58
+ initial_paths.iter().cloned().collect::<Vec<_>>().join(", "),
59
+ current_set.iter().cloned().collect::<Vec<_>>().join(", ")
60
+ )));
61
+ }
62
+
63
+ let next_snapshot = changed_path_snapshot(root, changed_paths)?;
64
+ if next_snapshot == previous_snapshot {
65
+ return Ok(check_ids);
66
+ }
67
+ previous_snapshot = next_snapshot;
68
+ }
69
+
70
+ Err(NaomeError::new(
71
+ "Quality checks did not stabilize the user-owned diff after three runs.",
72
+ ))
73
+ }
@@ -0,0 +1,126 @@
1
+ use std::collections::HashMap;
2
+
3
+ use serde_json::Value;
4
+
5
+ use crate::models::NaomeError;
6
+ use crate::paths;
7
+
8
+ #[derive(Debug, Clone)]
9
+ pub(super) struct QualityCheck {
10
+ pub(super) command: String,
11
+ pub(super) cwd: String,
12
+ }
13
+
14
+ pub(super) fn verification_checks(verification: &Value) -> HashMap<&str, QualityCheck> {
15
+ let mut checks = HashMap::new();
16
+ let Some(items) = verification.get("checks").and_then(Value::as_array) else {
17
+ return checks;
18
+ };
19
+
20
+ for item in items {
21
+ let Some(id) = item.get("id").and_then(Value::as_str) else {
22
+ continue;
23
+ };
24
+ let Some(command) = item.get("command").and_then(Value::as_str) else {
25
+ continue;
26
+ };
27
+ let cwd = item
28
+ .get("cwd")
29
+ .and_then(Value::as_str)
30
+ .unwrap_or(".")
31
+ .to_string();
32
+ checks.insert(
33
+ id,
34
+ QualityCheck {
35
+ command: command.to_string(),
36
+ cwd,
37
+ },
38
+ );
39
+ }
40
+
41
+ checks
42
+ }
43
+
44
+ pub(super) fn user_diff_check_ids(
45
+ verification: &Value,
46
+ changed_paths: &[String],
47
+ checks: &HashMap<&str, QualityCheck>,
48
+ ) -> Result<Vec<String>, NaomeError> {
49
+ let mut ids = Vec::new();
50
+ if checks.contains_key("diff-check") {
51
+ push_unique_string(&mut ids, "diff-check");
52
+ }
53
+ if checks.contains_key("naome-harness-health") {
54
+ push_unique_string(&mut ids, "naome-harness-health");
55
+ }
56
+
57
+ let Some(change_types) = verification.get("changeTypes").and_then(Value::as_array) else {
58
+ return Err(no_quality_coverage());
59
+ };
60
+ if change_types.is_empty() {
61
+ return Err(no_quality_coverage());
62
+ }
63
+
64
+ let mut uncovered_paths = Vec::new();
65
+ for change_type in change_types {
66
+ let patterns = string_array(change_type.get("paths"));
67
+ if patterns.is_empty()
68
+ || !changed_paths
69
+ .iter()
70
+ .any(|path| paths::matches_any(path, &patterns))
71
+ {
72
+ continue;
73
+ }
74
+
75
+ for check_id in string_array(change_type.get("requiredChecks")) {
76
+ push_unique_string(&mut ids, &check_id);
77
+ }
78
+ }
79
+
80
+ for path in changed_paths {
81
+ let covered = change_types.iter().any(|change_type| {
82
+ let patterns = string_array(change_type.get("paths"));
83
+ !patterns.is_empty() && paths::matches_any(path, &patterns)
84
+ });
85
+ if !covered {
86
+ uncovered_paths.push(path.clone());
87
+ }
88
+ }
89
+
90
+ if !uncovered_paths.is_empty() {
91
+ return Err(NaomeError::new(format!(
92
+ "No quality coverage is configured for changed path(s): {}.",
93
+ uncovered_paths.join(", ")
94
+ )));
95
+ }
96
+ if ids.is_empty() {
97
+ return Err(NaomeError::new(
98
+ "No quality checks are configured for these changed paths.",
99
+ ));
100
+ }
101
+
102
+ Ok(ids)
103
+ }
104
+
105
+ fn no_quality_coverage() -> NaomeError {
106
+ NaomeError::new("No quality coverage is configured for user-owned changed paths.")
107
+ }
108
+
109
+ fn string_array(value: Option<&Value>) -> Vec<String> {
110
+ value
111
+ .and_then(Value::as_array)
112
+ .map(|items| {
113
+ items
114
+ .iter()
115
+ .filter_map(Value::as_str)
116
+ .map(ToString::to_string)
117
+ .collect()
118
+ })
119
+ .unwrap_or_default()
120
+ }
121
+
122
+ fn push_unique_string(values: &mut Vec<String>, value: &str) {
123
+ if !values.iter().any(|item| item == value) {
124
+ values.push(value.to_string());
125
+ }
126
+ }
@@ -0,0 +1,69 @@
1
+ use std::collections::BTreeSet;
2
+ use std::fs;
3
+ use std::path::Path;
4
+ use std::process::Command;
5
+
6
+ use serde_json::Value;
7
+
8
+ use crate::models::NaomeError;
9
+
10
+ pub(super) fn validate_changed_text_whitespace(
11
+ root: &Path,
12
+ changed_paths: &[String],
13
+ ) -> Result<(), NaomeError> {
14
+ for relative_path in changed_paths {
15
+ let path = root.join(relative_path);
16
+ if !path.is_file() {
17
+ continue;
18
+ }
19
+ let Ok(content) = fs::read_to_string(&path) else {
20
+ continue;
21
+ };
22
+ for (index, line) in content.lines().enumerate() {
23
+ if line.ends_with(' ') || line.ends_with('\t') {
24
+ return Err(NaomeError::new(format!(
25
+ "{relative_path}:{} has trailing whitespace.",
26
+ index + 1
27
+ )));
28
+ }
29
+ }
30
+ }
31
+ Ok(())
32
+ }
33
+
34
+ pub(super) fn read_head_verification(root: &Path) -> Result<Value, NaomeError> {
35
+ let output = Command::new("git")
36
+ .args(["show", "HEAD:.naome/verification.json"])
37
+ .current_dir(root)
38
+ .output()?;
39
+
40
+ if !output.status.success() {
41
+ return Err(NaomeError::new(
42
+ "No committed NAOME verification profile is available for user-diff quality gating.",
43
+ ));
44
+ }
45
+
46
+ Ok(serde_json::from_slice(&output.stdout)?)
47
+ }
48
+
49
+ pub(super) fn sorted_path_set(paths: &[String]) -> BTreeSet<String> {
50
+ paths.iter().cloned().collect()
51
+ }
52
+
53
+ pub(super) fn changed_path_snapshot(
54
+ root: &Path,
55
+ changed_paths: &[String],
56
+ ) -> Result<Vec<(String, Option<Vec<u8>>)>, NaomeError> {
57
+ let mut snapshot = Vec::new();
58
+ for relative_path in changed_paths {
59
+ let path = root.join(relative_path);
60
+ let content = if path.is_file() {
61
+ Some(fs::read(&path)?)
62
+ } else {
63
+ None
64
+ };
65
+ snapshot.push((relative_path.clone(), content));
66
+ }
67
+ snapshot.sort_by(|left, right| left.0.cmp(&right.0));
68
+ Ok(snapshot)
69
+ }
@@ -0,0 +1,75 @@
1
+ use std::path::Path;
2
+ use std::process::Command;
3
+
4
+ use crate::models::NaomeError;
5
+ use crate::route::{
6
+ git_ops::{command_output, git_head},
7
+ RouteWorktree,
8
+ };
9
+
10
+ use super::worktree_files::copy_local_harness_files;
11
+ use super::worktree_plan::{available_worktree_candidates, worktree_plan};
12
+
13
+ pub(super) fn create_isolated_task_worktree(
14
+ root: &Path,
15
+ prompt: &str,
16
+ ) -> Result<RouteWorktree, NaomeError> {
17
+ let name_head = task_worktree_name_head(root)?;
18
+ create_isolated_task_worktree_with_name_head(root, prompt, &name_head)
19
+ }
20
+
21
+ pub(super) fn create_isolated_task_worktree_with_name_head(
22
+ root: &Path,
23
+ prompt: &str,
24
+ name_head: &str,
25
+ ) -> Result<RouteWorktree, NaomeError> {
26
+ let base_head = git_head(root)?.ok_or_else(|| {
27
+ NaomeError::new("Cannot create task worktree because the repository has no HEAD.")
28
+ })?;
29
+ let plan = worktree_plan(root, prompt, name_head)?;
30
+ for candidate in available_worktree_candidates(root, &plan)? {
31
+ let path_text = candidate.path.to_string_lossy().to_string();
32
+ let output = Command::new("git")
33
+ .args([
34
+ "worktree",
35
+ "add",
36
+ "-b",
37
+ &candidate.branch,
38
+ &path_text,
39
+ "HEAD",
40
+ ])
41
+ .current_dir(root)
42
+ .output()?;
43
+ if !output.status.success() {
44
+ return Err(NaomeError::new(command_output(&output)));
45
+ }
46
+
47
+ copy_local_harness_files(root, &candidate.path)?;
48
+
49
+ return Ok(RouteWorktree {
50
+ path: path_text,
51
+ branch: candidate.branch,
52
+ base_head,
53
+ source_root: root.to_string_lossy().to_string(),
54
+ });
55
+ }
56
+
57
+ Err(NaomeError::new(
58
+ "Cannot create a unique NAOME task worktree after 99 attempts.",
59
+ ))
60
+ }
61
+
62
+ pub(super) fn preflight_isolated_task_worktree(
63
+ root: &Path,
64
+ prompt: &str,
65
+ name_head: &str,
66
+ ) -> Result<(), NaomeError> {
67
+ let plan = worktree_plan(root, prompt, name_head)?;
68
+ available_worktree_candidates(root, &plan).map(|_| ())
69
+ }
70
+
71
+ pub(super) fn task_worktree_name_head(root: &Path) -> Result<String, NaomeError> {
72
+ git_head(root)?.ok_or_else(|| {
73
+ NaomeError::new("Cannot create task worktree because the repository has no HEAD.")
74
+ })
75
+ }