@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,96 @@
1
+ use sha2::{Digest, Sha256};
2
+
3
+ const HEALTH_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-harness-health.js";
4
+ const TASK_STATE_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-task-state.js";
5
+
6
+ pub(crate) const NAOME_COMMAND_RELATIVE_PATH: &str = ".naome/bin/naome.js";
7
+
8
+ #[cfg(windows)]
9
+ pub(crate) const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust.exe";
10
+ #[cfg(not(windows))]
11
+ pub(crate) const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust";
12
+
13
+ pub(crate) fn machine_integrity_hash(relative_path: &str, content: &[u8]) -> String {
14
+ format!("sha256:{}", machine_sha256(relative_path, content))
15
+ }
16
+
17
+ pub(crate) fn sha256_bytes(content: &[u8]) -> String {
18
+ let mut hasher = Sha256::new();
19
+ hasher.update(content);
20
+ format!("{:x}", hasher.finalize())
21
+ }
22
+
23
+ pub(crate) fn is_integrity_hash(value: &str) -> bool {
24
+ value.len() == "sha256:".len() + 64
25
+ && value.starts_with("sha256:")
26
+ && value["sha256:".len()..]
27
+ .chars()
28
+ .all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
29
+ }
30
+
31
+ pub(crate) fn native_integrity_from_naome_command(content: &str) -> Option<String> {
32
+ let prefix = "const expectedNativeBinaryIntegrity = \"";
33
+ let start = content.find(prefix)? + prefix.len();
34
+ let rest = &content[start..];
35
+ let end = rest.find("\";")?;
36
+ let value = &rest[..end];
37
+ is_integrity_hash(value).then(|| value.to_string())
38
+ }
39
+
40
+ fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
41
+ let normalized = normalize_machine_owned_content(relative_path, content);
42
+ sha256_bytes(&normalized)
43
+ }
44
+
45
+ fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
46
+ if relative_path == HEALTH_CHECKER_RELATIVE_PATH
47
+ || relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
48
+ {
49
+ return replace_expected_integrity_block(content);
50
+ }
51
+
52
+ if relative_path == NAOME_COMMAND_RELATIVE_PATH {
53
+ return replace_native_integrity_line(content);
54
+ }
55
+
56
+ content.to_vec()
57
+ }
58
+
59
+ fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
60
+ let text = String::from_utf8_lossy(content);
61
+ let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({\n";
62
+ let normalized =
63
+ "const expectedMachineOwnedIntegrity = Object.freeze({\n __generated__: \"sha256:generated\"\n});\n";
64
+ let Some(start) = text.find(start_marker) else {
65
+ return content.to_vec();
66
+ };
67
+ let after_start = start + start_marker.len();
68
+ let Some(relative_end) = text[after_start..].find("\n});\n") else {
69
+ return content.to_vec();
70
+ };
71
+ let end = after_start + relative_end + "\n});\n".len();
72
+
73
+ let mut next = String::with_capacity(text.len());
74
+ next.push_str(&text[..start]);
75
+ next.push_str(normalized);
76
+ next.push_str(&text[end..]);
77
+ next.into_bytes()
78
+ }
79
+
80
+ fn replace_native_integrity_line(content: &[u8]) -> Vec<u8> {
81
+ let text = String::from_utf8_lossy(content);
82
+ let prefix = "const expectedNativeBinaryIntegrity = \"";
83
+ let Some(start) = text.find(prefix) else {
84
+ return content.to_vec();
85
+ };
86
+ let Some(relative_end) = text[start..].find(";\n") else {
87
+ return content.to_vec();
88
+ };
89
+ let end = start + relative_end + ";\n".len();
90
+
91
+ let mut next = String::with_capacity(text.len());
92
+ next.push_str(&text[..start]);
93
+ next.push_str("const expectedNativeBinaryIntegrity = \"sha256:generated\";\n");
94
+ next.push_str(&text[end..]);
95
+ next.into_bytes()
96
+ }
@@ -1,49 +1,19 @@
1
+ mod integrity;
2
+
1
3
  use std::collections::{HashMap, HashSet};
2
4
  use std::fs;
3
5
  use std::path::{Component, Path};
4
6
 
5
7
  use serde_json::Value;
6
- use sha2::{Digest, Sha256};
7
8
 
9
+ pub(crate) use self::integrity::machine_integrity_hash;
10
+ use self::integrity::{
11
+ is_integrity_hash, native_integrity_from_naome_command, sha256_bytes,
12
+ NAOME_COMMAND_RELATIVE_PATH, NATIVE_BINARY_RELATIVE_PATH,
13
+ };
14
+ use crate::install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
8
15
  use crate::models::NaomeError;
9
16
 
10
- const HEALTH_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-harness-health.js";
11
- const TASK_STATE_CHECKER_RELATIVE_PATH: &str = ".naome/bin/check-task-state.js";
12
- const NAOME_COMMAND_RELATIVE_PATH: &str = ".naome/bin/naome.js";
13
-
14
- #[cfg(windows)]
15
- const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust.exe";
16
- #[cfg(not(windows))]
17
- const NATIVE_BINARY_RELATIVE_PATH: &str = ".naome/bin/naome-rust";
18
-
19
- const EXPECTED_MACHINE_OWNED_PATHS: &[&str] = &[
20
- "AGENTS.md",
21
- ".naome/package.json",
22
- ".naome/bin/naome.js",
23
- ".naome/bin/check-task-state.js",
24
- ".naome/bin/check-harness-health.js",
25
- ".naome/task-contract.schema.json",
26
- "docs/naome/index.md",
27
- "docs/naome/first-run.md",
28
- "docs/naome/agent-workflow.md",
29
- "docs/naome/execution.md",
30
- "docs/naome/upgrade.md",
31
- ];
32
-
33
- const EXPECTED_PROJECT_OWNED_PATHS: &[&str] = &[
34
- ".naomeignore",
35
- ".naome/init-state.json",
36
- ".naome/manifest.json",
37
- ".naome/task-state.json",
38
- ".naome/upgrade-state.json",
39
- ".naome/verification.json",
40
- "docs/naome/architecture.md",
41
- "docs/naome/decisions.md",
42
- "docs/naome/repo-profile.md",
43
- "docs/naome/security.md",
44
- "docs/naome/testing.md",
45
- ];
46
-
47
17
  #[derive(Debug, Clone, Default)]
48
18
  pub struct HarnessHealthOptions {
49
19
  pub expected_integrity: HashMap<String, String>,
@@ -57,11 +27,11 @@ pub fn validate_harness_health(
57
27
  ) -> Result<Vec<String>, NaomeError> {
58
28
  let mut errors = Vec::new();
59
29
 
60
- for relative_path in EXPECTED_MACHINE_OWNED_PATHS {
30
+ for relative_path in MACHINE_OWNED_PATHS {
61
31
  validate_regular_file(root, relative_path, &mut errors)?;
62
32
  }
63
33
 
64
- for relative_path in EXPECTED_PROJECT_OWNED_PATHS {
34
+ for relative_path in PROJECT_OWNED_PATHS {
65
35
  validate_regular_file(root, relative_path, &mut errors)?;
66
36
  }
67
37
 
@@ -130,13 +100,13 @@ fn validate_manifest_ownership(manifest: &Value, errors: &mut Vec<String>) {
130
100
 
131
101
  validate_contains_all(
132
102
  &machine_owned,
133
- EXPECTED_MACHINE_OWNED_PATHS,
103
+ MACHINE_OWNED_PATHS,
134
104
  ".naome/manifest.json machineOwned",
135
105
  errors,
136
106
  );
137
107
  validate_contains_all(
138
108
  &project_owned,
139
- EXPECTED_PROJECT_OWNED_PATHS,
109
+ PROJECT_OWNED_PATHS,
140
110
  ".naome/manifest.json projectOwned",
141
111
  errors,
142
112
  );
@@ -152,7 +122,7 @@ fn validate_manifest_integrity(
152
122
  return Ok(());
153
123
  };
154
124
 
155
- for relative_path in EXPECTED_MACHINE_OWNED_PATHS {
125
+ for relative_path in MACHINE_OWNED_PATHS {
156
126
  let packaged_expected = options.expected_integrity.get(*relative_path);
157
127
  let manifest_expected = integrity.get(*relative_path).and_then(Value::as_str);
158
128
 
@@ -190,7 +160,7 @@ fn validate_manifest_integrity(
190
160
  }
191
161
 
192
162
  let content = fs::read(root.join(relative_path))?;
193
- let actual = format!("sha256:{}", machine_sha256(relative_path, &content));
163
+ let actual = machine_integrity_hash(relative_path, &content);
194
164
  if actual != *packaged_expected {
195
165
  errors.push(format!(
196
166
  "{relative_path} integrity mismatch. Expected {packaged_expected}, got {actual}."
@@ -475,85 +445,3 @@ fn is_version(value: &str) -> bool {
475
445
  .iter()
476
446
  .all(|part| !part.is_empty() && part.chars().all(|ch| ch.is_ascii_digit()))
477
447
  }
478
-
479
- fn is_integrity_hash(value: &str) -> bool {
480
- value.len() == "sha256:".len() + 64
481
- && value.starts_with("sha256:")
482
- && value["sha256:".len()..]
483
- .chars()
484
- .all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
485
- }
486
-
487
- fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
488
- let normalized = normalize_machine_owned_content(relative_path, content);
489
- sha256_bytes(&normalized)
490
- }
491
-
492
- fn sha256_bytes(content: &[u8]) -> String {
493
- let mut hasher = Sha256::new();
494
- hasher.update(content);
495
- format!("{:x}", hasher.finalize())
496
- }
497
-
498
- fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
499
- if relative_path == HEALTH_CHECKER_RELATIVE_PATH
500
- || relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
501
- {
502
- return replace_expected_integrity_block(content);
503
- }
504
-
505
- if relative_path == NAOME_COMMAND_RELATIVE_PATH {
506
- return replace_native_integrity_line(content);
507
- }
508
-
509
- content.to_vec()
510
- }
511
-
512
- fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
513
- let text = String::from_utf8_lossy(content);
514
- let start_marker = "const expectedMachineOwnedIntegrity = Object.freeze({\n";
515
- let normalized =
516
- "const expectedMachineOwnedIntegrity = Object.freeze({\n __generated__: \"sha256:generated\"\n});\n";
517
- let Some(start) = text.find(start_marker) else {
518
- return content.to_vec();
519
- };
520
- let after_start = start + start_marker.len();
521
- let Some(relative_end) = text[after_start..].find("\n});\n") else {
522
- return content.to_vec();
523
- };
524
- let end = after_start + relative_end + "\n});\n".len();
525
-
526
- let mut next = String::with_capacity(text.len());
527
- next.push_str(&text[..start]);
528
- next.push_str(normalized);
529
- next.push_str(&text[end..]);
530
- next.into_bytes()
531
- }
532
-
533
- fn replace_native_integrity_line(content: &[u8]) -> Vec<u8> {
534
- let text = String::from_utf8_lossy(content);
535
- let prefix = "const expectedNativeBinaryIntegrity = \"";
536
- let Some(start) = text.find(prefix) else {
537
- return content.to_vec();
538
- };
539
- let Some(relative_end) = text[start..].find(";\n") else {
540
- return content.to_vec();
541
- };
542
- let end = start + relative_end + ";\n".len();
543
- let replacement = "const expectedNativeBinaryIntegrity = \"sha256:generated\";\n";
544
-
545
- let mut next = String::with_capacity(text.len());
546
- next.push_str(&text[..start]);
547
- next.push_str(replacement);
548
- next.push_str(&text[end..]);
549
- next.into_bytes()
550
- }
551
-
552
- fn native_integrity_from_naome_command(content: &str) -> Option<String> {
553
- let prefix = "const expectedNativeBinaryIntegrity = \"";
554
- let start = content.find(prefix)? + prefix.len();
555
- let rest = &content[start..];
556
- let end = rest.find("\";")?;
557
- let value = &rest[..end];
558
- is_integrity_hash(value).then(|| value.to_string())
559
- }
@@ -21,9 +21,14 @@ pub const PROJECT_OWNED_PATHS: &[&str] = &[
21
21
  ".naome/task-state.json",
22
22
  ".naome/upgrade-state.json",
23
23
  ".naome/verification.json",
24
+ ".naome/repository-quality.json",
25
+ ".naome/repository-structure.json",
26
+ ".naome/repository-quality-baseline.json",
24
27
  "docs/naome/architecture.md",
25
28
  "docs/naome/decisions.md",
26
29
  "docs/naome/repo-profile.md",
30
+ "docs/naome/repository-quality.md",
31
+ "docs/naome/repository-structure.md",
27
32
  "docs/naome/security.md",
28
33
  "docs/naome/testing.md",
29
34
  ];
@@ -0,0 +1,171 @@
1
+ use std::collections::HashSet;
2
+
3
+ use super::envelope::parse_routing_envelope;
4
+ use super::model::{CanonicalIntent, IntentCandidate, IntentKind, PromptSegment, SegmentKind};
5
+ use super::patterns;
6
+ use super::risk::detect_risk;
7
+ use super::segment::{actionable_text, segment_prompt};
8
+
9
+ const DIRECT_WORKFLOW_CONFIDENCE: u8 = 90;
10
+ const NEW_TASK_CONFIDENCE: u8 = 80;
11
+ const REVISION_CONFIDENCE: u8 = 78;
12
+ const GENERIC_WORK_CONFIDENCE: u8 = 50;
13
+
14
+ pub(crate) fn canonical_intent(prompt: &str) -> CanonicalIntent {
15
+ let segments = segment_prompt(prompt);
16
+ let actionable = actionable_text(&segments);
17
+ let mut candidates = classify_candidates(&segments);
18
+ let mut risk = detect_risk(&segments);
19
+
20
+ if let Some(envelope) = parse_routing_envelope(prompt) {
21
+ candidates.clear();
22
+ candidates.extend(envelope.candidates);
23
+ risk.risk_codes.extend(envelope.risk.risk_codes);
24
+ risk.risk_codes.sort();
25
+ risk.risk_codes.dedup();
26
+ risk.has_risky_terms = !risk.risk_codes.is_empty();
27
+ }
28
+
29
+ candidates = dedupe_candidates(candidates);
30
+ let references_current_task = has_candidate_kind(&candidates, IntentKind::TaskRevision);
31
+ if risk.has_risky_terms {
32
+ candidates.push(IntentCandidate {
33
+ kind: IntentKind::Unsafe,
34
+ confidence: 100,
35
+ evidence: risk.risk_codes.join(","),
36
+ });
37
+ }
38
+
39
+ CanonicalIntent {
40
+ is_empty: actionable.trim().is_empty(),
41
+ references_current_task,
42
+ has_workflow_conflict: has_workflow_conflict(&candidates),
43
+ candidates,
44
+ risk,
45
+ }
46
+ }
47
+
48
+ pub(crate) fn winning_intent(canonical: &CanonicalIntent) -> IntentKind {
49
+ if canonical.is_empty || canonical.has_workflow_conflict {
50
+ return IntentKind::Ambiguous;
51
+ }
52
+ for kind in [
53
+ IntentKind::Unsafe,
54
+ IntentKind::StatusQuestion,
55
+ IntentKind::RepairRequest,
56
+ IntentKind::CancelRequest,
57
+ IntentKind::ReviewRequest,
58
+ IntentKind::NoCommitRequest,
59
+ IntentKind::CommitRequest,
60
+ IntentKind::NewTask,
61
+ IntentKind::TaskRevision,
62
+ IntentKind::TaskCompletion,
63
+ ] {
64
+ if has_candidate(canonical, kind) {
65
+ return kind;
66
+ }
67
+ }
68
+ IntentKind::Ambiguous
69
+ }
70
+
71
+ pub(crate) fn has_candidate(canonical: &CanonicalIntent, kind: IntentKind) -> bool {
72
+ canonical
73
+ .candidates
74
+ .iter()
75
+ .any(|candidate| candidate.kind == kind)
76
+ }
77
+
78
+ fn classify_candidates(segments: &[PromptSegment]) -> Vec<IntentCandidate> {
79
+ let mut candidates = Vec::new();
80
+ for segment in action_segments(segments) {
81
+ classify_direct_workflow(&mut candidates, segment);
82
+ classify_task_work(&mut candidates, &segment.text);
83
+ }
84
+
85
+ if candidates.is_empty() {
86
+ let actionable = actionable_text(segments);
87
+ if patterns::is_generic_work_request(&actionable) {
88
+ push(
89
+ &mut candidates,
90
+ IntentKind::NewTask,
91
+ GENERIC_WORK_CONFIDENCE,
92
+ "generic_work_request",
93
+ );
94
+ }
95
+ }
96
+
97
+ dedupe_candidates(candidates)
98
+ }
99
+
100
+ fn classify_direct_workflow(candidates: &mut Vec<IntentCandidate>, segment: &PromptSegment) {
101
+ let intents = if segment.kind == SegmentKind::CommandLike {
102
+ patterns::command_workflow_intents(&segment.text)
103
+ } else {
104
+ patterns::structured_workflow_intents(&segment.text)
105
+ };
106
+ for intent in intents {
107
+ push(
108
+ candidates,
109
+ intent,
110
+ DIRECT_WORKFLOW_CONFIDENCE,
111
+ &patterns::normalized(&segment.text),
112
+ );
113
+ }
114
+ }
115
+
116
+ fn classify_task_work(candidates: &mut Vec<IntentCandidate>, text: &str) {
117
+ if let Some(intent) = patterns::explicit_task_intent(text) {
118
+ let confidence = if intent == IntentKind::NewTask {
119
+ NEW_TASK_CONFIDENCE
120
+ } else {
121
+ REVISION_CONFIDENCE
122
+ };
123
+ push(candidates, intent, confidence, &patterns::normalized(text));
124
+ }
125
+ }
126
+
127
+ fn has_candidate_kind(candidates: &[IntentCandidate], kind: IntentKind) -> bool {
128
+ candidates.iter().any(|candidate| candidate.kind == kind)
129
+ }
130
+
131
+ fn has_workflow_conflict(candidates: &[IntentCandidate]) -> bool {
132
+ let workflow = candidates
133
+ .iter()
134
+ .filter(|candidate| candidate.confidence >= DIRECT_WORKFLOW_CONFIDENCE)
135
+ .filter(|candidate| {
136
+ !matches!(
137
+ candidate.kind,
138
+ IntentKind::NewTask | IntentKind::TaskRevision | IntentKind::Unsafe
139
+ )
140
+ })
141
+ .map(|candidate| candidate.kind)
142
+ .collect::<HashSet<_>>();
143
+ workflow.len() > 1
144
+ }
145
+
146
+ fn action_segments(segments: &[PromptSegment]) -> impl Iterator<Item = &PromptSegment> {
147
+ segments.iter().filter(|segment| {
148
+ matches!(
149
+ segment.kind,
150
+ super::model::SegmentKind::Prose
151
+ | super::model::SegmentKind::ListItem
152
+ | super::model::SegmentKind::CommandLike
153
+ )
154
+ })
155
+ }
156
+
157
+ fn push(candidates: &mut Vec<IntentCandidate>, kind: IntentKind, confidence: u8, text: &str) {
158
+ candidates.push(IntentCandidate {
159
+ kind,
160
+ confidence,
161
+ evidence: text.to_string(),
162
+ });
163
+ }
164
+
165
+ fn dedupe_candidates(candidates: Vec<IntentCandidate>) -> Vec<IntentCandidate> {
166
+ let mut seen = HashSet::new();
167
+ candidates
168
+ .into_iter()
169
+ .filter(|candidate| seen.insert((candidate.kind, candidate.evidence.clone())))
170
+ .collect()
171
+ }
@@ -0,0 +1,108 @@
1
+ use serde::Deserialize;
2
+
3
+ use super::model::{IntentCandidate, IntentKind, RiskContext};
4
+
5
+ const ENVELOPE_FENCE: &str = "```naome-intent-v2";
6
+
7
+ #[derive(Debug, Clone, PartialEq, Eq)]
8
+ pub(crate) struct RoutingEnvelope {
9
+ pub candidates: Vec<IntentCandidate>,
10
+ pub risk: RiskContext,
11
+ }
12
+
13
+ #[derive(Debug, Deserialize)]
14
+ #[serde(rename_all = "camelCase")]
15
+ struct EnvelopeInput {
16
+ schema: Option<String>,
17
+ workflow_action: Option<String>,
18
+ task_intent: Option<String>,
19
+ risk: Option<String>,
20
+ risk_codes: Option<Vec<String>>,
21
+ }
22
+
23
+ pub(crate) fn parse_routing_envelope(prompt: &str) -> Option<RoutingEnvelope> {
24
+ let json = envelope_json(prompt)?;
25
+ let input = serde_json::from_str::<EnvelopeInput>(json).ok()?;
26
+ if !matches!(input.schema.as_deref(), Some("naome.intent.v2")) {
27
+ return None;
28
+ }
29
+
30
+ let mut candidates = Vec::new();
31
+ if let Some(kind) = workflow_action(input.workflow_action.as_deref()) {
32
+ candidates.push(candidate(kind, "envelope.workflowAction"));
33
+ }
34
+ if let Some(kind) = task_intent(input.task_intent.as_deref()) {
35
+ candidates.push(candidate(kind, "envelope.taskIntent"));
36
+ }
37
+
38
+ let risk_codes = envelope_risk_codes(input.risk.as_deref(), input.risk_codes);
39
+ let risk = RiskContext {
40
+ has_risky_terms: !risk_codes.is_empty(),
41
+ risk_codes,
42
+ };
43
+
44
+ Some(RoutingEnvelope { candidates, risk })
45
+ }
46
+
47
+ fn envelope_json(prompt: &str) -> Option<&str> {
48
+ let start = prompt.find(ENVELOPE_FENCE)?;
49
+ let after_open = &prompt[start + ENVELOPE_FENCE.len()..];
50
+ let after_line = after_open
51
+ .strip_prefix('\n')
52
+ .or_else(|| after_open.strip_prefix("\r\n"))?;
53
+ let end = after_line.find("\n```")?;
54
+ Some(after_line[..end].trim())
55
+ }
56
+
57
+ fn workflow_action(value: Option<&str>) -> Option<IntentKind> {
58
+ match normalized_value(value)?.as_str() {
59
+ "none" | "unknown" => None,
60
+ "commit_request" => Some(IntentKind::CommitRequest),
61
+ "review_request" => Some(IntentKind::ReviewRequest),
62
+ "repair_request" => Some(IntentKind::RepairRequest),
63
+ "cancel_request" => Some(IntentKind::CancelRequest),
64
+ "task_completion" => Some(IntentKind::TaskCompletion),
65
+ "status_question" => Some(IntentKind::StatusQuestion),
66
+ "no_commit_request" => Some(IntentKind::NoCommitRequest),
67
+ _ => None,
68
+ }
69
+ }
70
+
71
+ fn task_intent(value: Option<&str>) -> Option<IntentKind> {
72
+ match normalized_value(value)?.as_str() {
73
+ "none" | "unknown" => None,
74
+ "new_task" => Some(IntentKind::NewTask),
75
+ "task_revision" => Some(IntentKind::TaskRevision),
76
+ _ => None,
77
+ }
78
+ }
79
+
80
+ fn envelope_risk_codes(risk: Option<&str>, risk_codes: Option<Vec<String>>) -> Vec<String> {
81
+ let mut codes = risk_codes.unwrap_or_default();
82
+ match normalized_value(risk).as_deref() {
83
+ Some("none") | Some("unknown") | None => {}
84
+ Some("unsafe") | Some("unsafe_context") => codes.push("envelope_risk:unsafe".to_string()),
85
+ Some("credential_context") => {
86
+ codes.push("envelope_risk:credential_context".to_string());
87
+ }
88
+ Some("bypass_context") => codes.push("envelope_risk:bypass_context".to_string()),
89
+ Some(other) => codes.push(format!("envelope_risk:{other}")),
90
+ }
91
+ codes.sort();
92
+ codes.dedup();
93
+ codes
94
+ }
95
+
96
+ fn normalized_value(value: Option<&str>) -> Option<String> {
97
+ value
98
+ .map(|value| value.trim().to_ascii_lowercase())
99
+ .filter(|value| !value.is_empty())
100
+ }
101
+
102
+ fn candidate(kind: IntentKind, evidence: &str) -> IntentCandidate {
103
+ IntentCandidate {
104
+ kind,
105
+ confidence: 100,
106
+ evidence: evidence.to_string(),
107
+ }
108
+ }