@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
@@ -1,51 +1,32 @@
1
- use std::fs;
2
- use std::path::{Path, PathBuf};
3
- use std::time::{SystemTime, UNIX_EPOCH};
4
-
5
1
  use naome_core::validate_verification_contract;
6
2
  use serde_json::json;
7
3
 
4
+ mod repo_support;
5
+
6
+ use repo_support::{change_type, diff_check, verification_value, TestRepo};
7
+
8
8
  #[test]
9
9
  fn accepts_valid_contract_and_testing_map() {
10
10
  let repo = TestRepo::new("verification-contract-valid");
11
11
  repo.write_testing_markdown(default_testing_markdown());
12
- repo.write_naome_json(
13
- "verification.json",
14
- json!({
15
- "schema": "naome.verification.v1",
16
- "version": 1,
17
- "status": "uninitialized",
18
- "lastUpdated": null,
19
- "checks": [
20
- {
21
- "id": "diff-check",
22
- "command": "git diff --check",
23
- "cwd": ".",
24
- "purpose": "Reject whitespace errors.",
25
- "cost": "fast",
26
- "source": "git",
27
- "evidence": [],
28
- "lastVerified": null
29
- }
30
- ],
31
- "changeTypes": [
32
- {
33
- "id": "docs",
34
- "description": "Documentation changes.",
35
- "paths": ["docs/**"],
36
- "requiredChecks": ["diff-check"],
37
- "recommendedChecks": [],
38
- "humanReview": false
39
- }
40
- ],
41
- "releaseGates": [
42
- {
43
- "checkId": "diff-check",
44
- "requiredWhen": "Before release when docs changed."
45
- }
46
- ]
47
- }),
48
- );
12
+ repo.write_naome_json("verification.json", {
13
+ let mut value = verification_value(
14
+ "uninitialized",
15
+ vec![diff_check(vec![])],
16
+ vec![change_type(
17
+ "docs",
18
+ "Documentation changes.",
19
+ vec!["docs/**"],
20
+ vec!["diff-check"],
21
+ )],
22
+ );
23
+ value["lastUpdated"] = json!(null);
24
+ value["releaseGates"] = json!([{
25
+ "checkId": "diff-check",
26
+ "requiredWhen": "Before release when docs changed."
27
+ }]);
28
+ value
29
+ });
49
30
 
50
31
  let errors = validate_verification_contract(repo.path()).unwrap();
51
32
 
@@ -142,40 +123,3 @@ fn default_testing_markdown() -> &'static str {
142
123
  ## Evidence\n\n\
143
124
  - Test fixture.\n"
144
125
  }
145
-
146
- struct TestRepo {
147
- root: PathBuf,
148
- }
149
-
150
- impl TestRepo {
151
- fn new(name: &str) -> Self {
152
- let nonce = SystemTime::now()
153
- .duration_since(UNIX_EPOCH)
154
- .unwrap()
155
- .as_nanos();
156
- let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
157
- fs::create_dir_all(root.join(".naome")).unwrap();
158
- fs::create_dir_all(root.join("docs").join("naome")).unwrap();
159
- Self { root }
160
- }
161
-
162
- fn path(&self) -> &Path {
163
- &self.root
164
- }
165
-
166
- fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
167
- fs::write(
168
- self.root.join(".naome").join(file_name),
169
- format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
170
- )
171
- .unwrap();
172
- }
173
-
174
- fn write_testing_markdown(&self, content: &str) {
175
- fs::write(
176
- self.root.join("docs").join("naome").join("testing.md"),
177
- content,
178
- )
179
- .unwrap();
180
- }
181
- }
@@ -0,0 +1,85 @@
1
+ mod workflow_support;
2
+
3
+ use std::fs;
4
+
5
+ use naome_core::{
6
+ refresh_integrity, summarize_command_output, verification_phase_plan, CommandCheckResult,
7
+ };
8
+ use serde_json::json;
9
+ use workflow_support::{check, phase, WorkflowFixture};
10
+
11
+ #[test]
12
+ fn refresh_integrity_is_idempotent_and_repairs_manifest_hash() {
13
+ let repo = WorkflowFixture::new("refresh-integrity");
14
+ repo.install_machine_owned_harness();
15
+ let wrong = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
16
+ repo.write_manifest_with_integrity(wrong);
17
+
18
+ let first = refresh_integrity(repo.path()).unwrap();
19
+ let after_first = fs::read_to_string(repo.path().join(".naome/manifest.json")).unwrap();
20
+ let second = refresh_integrity(repo.path()).unwrap();
21
+ let after_second = fs::read_to_string(repo.path().join(".naome/manifest.json")).unwrap();
22
+
23
+ assert!(first.updated);
24
+ assert!(!second.updated);
25
+ assert_eq!(after_first, after_second);
26
+ assert!(!after_first.contains(wrong));
27
+ }
28
+
29
+ #[test]
30
+ fn failed_early_phase_withholds_expensive_recommendations() {
31
+ let repo = WorkflowFixture::new("verification-phases");
32
+ repo.write_verification(json!({
33
+ "schema": "naome.verification.v1",
34
+ "version": 1,
35
+ "status": "ready",
36
+ "lastUpdated": "2026-05-07",
37
+ "checks": [
38
+ check("naome-harness-health", "node .naome/bin/check-harness-health.js", "fast"),
39
+ check("repository-quality-check", "node .naome/bin/naome.js quality check --changed", "fast"),
40
+ check("package-dry-run", "npm run pack:dry-run", "medium")
41
+ ],
42
+ "phases": [
43
+ phase("shape-health", 10, ["naome-harness-health"]),
44
+ phase("quality", 20, ["repository-quality-check"]),
45
+ phase("package-release", 50, ["package-dry-run"])
46
+ ],
47
+ "changeTypes": [],
48
+ "releaseGates": []
49
+ }));
50
+
51
+ let plan = verification_phase_plan(
52
+ repo.path(),
53
+ &[CommandCheckResult {
54
+ check_id: "naome-harness-health".to_string(),
55
+ exit_code: 1,
56
+ }],
57
+ )
58
+ .unwrap();
59
+
60
+ assert_eq!(plan.recommended_check_ids, vec!["naome-harness-health"]);
61
+ assert!(plan
62
+ .withheld_check_ids
63
+ .contains(&"package-dry-run".to_string()));
64
+ }
65
+
66
+ #[test]
67
+ fn long_command_output_is_stably_summarized() {
68
+ let output = (0..80)
69
+ .map(|index| {
70
+ if index == 42 {
71
+ "error: src/main.rs failed to compile".to_string()
72
+ } else {
73
+ format!("line {index}")
74
+ }
75
+ })
76
+ .collect::<Vec<_>>()
77
+ .join("\n");
78
+
79
+ let summary = summarize_command_output("cargo test", ".", 101, &output, 8);
80
+
81
+ assert!(summary.truncated);
82
+ assert_eq!(summary.exit_code, 101);
83
+ assert!(summary.relevant_lines[0].contains("error: src/main.rs"));
84
+ assert!(summary.affected_paths.contains(&"src/main.rs".to_string()));
85
+ }
@@ -0,0 +1,139 @@
1
+ mod workflow_support;
2
+
3
+ use std::collections::HashMap;
4
+
5
+ use naome_core::{
6
+ classify_mutations, safe_rg_args, tracked_process_report, validate_read_boundaries,
7
+ validate_search_command, validate_task_state, ReadActivity, TaskStateMode, TaskStateOptions,
8
+ };
9
+ use serde_json::json;
10
+ use workflow_support::WorkflowFixture;
11
+
12
+ #[test]
13
+ fn read_boundary_violation_is_detected_from_activity() {
14
+ let repo = WorkflowFixture::new("read-boundary");
15
+ repo.write(".naomeignore", ".naome/archive/\n.local-cache/\n");
16
+
17
+ let findings = validate_read_boundaries(
18
+ repo.path(),
19
+ &[ReadActivity {
20
+ command: "rg --hidden panic .".to_string(),
21
+ paths: vec![".naome/archive/old-task.json".to_string()],
22
+ }],
23
+ )
24
+ .unwrap();
25
+
26
+ assert_eq!(findings[0].check_id, "read-boundary");
27
+ assert!(findings[0]
28
+ .paths
29
+ .contains(&".naome/archive/old-task.json".to_string()));
30
+ }
31
+
32
+ #[test]
33
+ fn safe_search_profile_excludes_default_boundaries() {
34
+ let repo = WorkflowFixture::new("safe-search-profile");
35
+ repo.write(".naomeignore", ".naome/archive/\n.local-cache/\n");
36
+
37
+ let args = safe_rg_args(repo.path()).unwrap().join(" ");
38
+
39
+ assert!(args.contains("!.git/**"));
40
+ assert!(args.contains("!.naome/archive/**"));
41
+ assert!(args.contains("!node_modules/**"));
42
+ assert!(args.contains("!.local-cache/**"));
43
+ }
44
+
45
+ #[test]
46
+ fn unsafe_hidden_search_is_workflow_finding() {
47
+ let repo = WorkflowFixture::new("unsafe-search");
48
+
49
+ let findings = validate_search_command(repo.path(), "rg --hidden TODO .").unwrap();
50
+
51
+ assert!(findings
52
+ .iter()
53
+ .any(|finding| finding.check_id == "unsafe-search-command"));
54
+ }
55
+
56
+ #[test]
57
+ fn scope_drift_is_reported_during_progress_gate() {
58
+ let repo = WorkflowFixture::new("scope-drift");
59
+ repo.init_git();
60
+ repo.write_task_state("implementing", &["src/owned.rs"], &["diff-check"]);
61
+ repo.write("src/outside.rs", "pub fn outside() {}\n");
62
+
63
+ let report = validate_task_state(
64
+ repo.path(),
65
+ TaskStateOptions {
66
+ mode: TaskStateMode::Progress,
67
+ ..TaskStateOptions::default()
68
+ },
69
+ )
70
+ .unwrap();
71
+ let joined = report.errors.join("\n");
72
+
73
+ assert!(joined.contains("Changed files outside allowedPaths"));
74
+ assert!(joined.contains("src/outside.rs"));
75
+ assert!(joined.contains("request_scope_change"));
76
+ }
77
+
78
+ #[test]
79
+ fn tracked_running_process_blocks_completion_until_documented() {
80
+ let repo = WorkflowFixture::new("process-tracking");
81
+ repo.write(
82
+ ".naome/processes.json",
83
+ &format!(
84
+ "{}\n",
85
+ serde_json::to_string_pretty(&json!({
86
+ "schema": "naome.processes.v1",
87
+ "processes": [{
88
+ "pid": std::process::id(),
89
+ "command": "npm run dev",
90
+ "cwd": ".",
91
+ "startedAt": "2026-05-07T12:00:00.000Z",
92
+ "status": "running",
93
+ "allowAfterCompletion": false
94
+ }]
95
+ }))
96
+ .unwrap()
97
+ ),
98
+ );
99
+
100
+ let report = tracked_process_report(repo.path()).unwrap();
101
+
102
+ assert_eq!(report.active.len(), 1);
103
+ assert!(report.active[0].command.contains("npm run dev"));
104
+ }
105
+
106
+ #[test]
107
+ fn mutation_classifier_recognizes_core_classes() {
108
+ let repo = WorkflowFixture::new("mutation-classes");
109
+
110
+ let classes = classify_mutations(
111
+ repo.path(),
112
+ &[
113
+ "src/lib.rs".to_string(),
114
+ ".naome/manifest.json".to_string(),
115
+ ".naome/archive/repair/AGENTS.md".to_string(),
116
+ "coverage/report.json".to_string(),
117
+ "packages/naome/native/darwin-arm64/naome".to_string(),
118
+ "notes/manual.md".to_string(),
119
+ ],
120
+ )
121
+ .unwrap();
122
+ let by_path = classes
123
+ .iter()
124
+ .map(|entry| (entry.path.as_str(), entry.mutation_class.as_str()))
125
+ .collect::<HashMap<_, _>>();
126
+
127
+ assert_eq!(by_path["src/lib.rs"], "source change");
128
+ assert_eq!(by_path[".naome/manifest.json"], "generated refresh");
129
+ assert_eq!(
130
+ by_path[".naome/archive/repair/AGENTS.md"],
131
+ "local-only repair"
132
+ );
133
+ assert_eq!(by_path["coverage/report.json"], "test artifact");
134
+ assert_eq!(
135
+ by_path["packages/naome/native/darwin-arm64/naome"],
136
+ "release artifact"
137
+ );
138
+ assert_eq!(by_path["notes/manual.md"], "user edit");
139
+ }
@@ -0,0 +1,194 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::process::Command;
4
+ use std::sync::atomic::{AtomicU64, Ordering};
5
+ use std::time::{SystemTime, UNIX_EPOCH};
6
+
7
+ use serde_json::json;
8
+
9
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
10
+
11
+ pub struct WorkflowFixture {
12
+ root: PathBuf,
13
+ }
14
+
15
+ impl WorkflowFixture {
16
+ pub fn new(name: &str) -> Self {
17
+ let nonce = SystemTime::now()
18
+ .duration_since(UNIX_EPOCH)
19
+ .unwrap()
20
+ .as_nanos();
21
+ let root = std::env::temp_dir().join(format!(
22
+ "naome-workflow-{name}-{}-{}-{nonce}",
23
+ std::process::id(),
24
+ FIXTURE_COUNTER.fetch_add(1, Ordering::SeqCst)
25
+ ));
26
+ fs::create_dir_all(root.join(".naome")).unwrap();
27
+ fs::create_dir_all(root.join("docs/naome")).unwrap();
28
+ let fixture = Self { root };
29
+ fixture.write(".naomeignore", ".naome/archive/\n");
30
+ fixture.write(
31
+ ".naome/init-state.json",
32
+ "{\"initialized\":true,\"intakeStatus\":\"complete\"}\n",
33
+ );
34
+ fixture.write(".naome/upgrade-state.json", "{\"status\":\"complete\"}\n");
35
+ fixture.write_verification(default_verification());
36
+ fixture
37
+ }
38
+
39
+ pub fn path(&self) -> &Path {
40
+ &self.root
41
+ }
42
+
43
+ pub fn init_git(&self) {
44
+ run_git(&self.root, &["init"]);
45
+ run_git(&self.root, &["config", "user.email", "test@example.com"]);
46
+ run_git(&self.root, &["config", "user.name", "Test User"]);
47
+ self.write("README.md", "# Baseline\n");
48
+ run_git(&self.root, &["add", "."]);
49
+ run_git(&self.root, &["commit", "-m", "baseline"]);
50
+ }
51
+
52
+ pub fn install_machine_owned_harness(&self) {
53
+ for path in ["AGENTS.md", ".naome/bin/naome.js", "docs/naome/index.md"] {
54
+ self.write(path, &format!("# {path}\n"));
55
+ }
56
+ }
57
+
58
+ pub fn write_manifest_with_integrity(&self, integrity: &str) {
59
+ self.write(
60
+ ".naome/manifest.json",
61
+ &pretty(json!({
62
+ "name": "naome",
63
+ "harnessVersion": "1.2.1",
64
+ "profile": "standard",
65
+ "installedAt": null,
66
+ "machineOwned": ["AGENTS.md", ".naome/bin/naome.js", "docs/naome/index.md"],
67
+ "projectOwned": [".naome/manifest.json"],
68
+ "integrity": {
69
+ "AGENTS.md": integrity,
70
+ ".naome/bin/naome.js": integrity,
71
+ "docs/naome/index.md": integrity
72
+ }
73
+ })),
74
+ );
75
+ }
76
+
77
+ pub fn write_task_state(&self, status: &str, allowed_paths: &[&str], checks: &[&str]) {
78
+ let active_task = active_task_state(
79
+ allowed_paths,
80
+ checks,
81
+ git_text(&self.root, &["rev-parse", "HEAD"]),
82
+ );
83
+ self.write(
84
+ ".naome/task-state.json",
85
+ &pretty(json!({
86
+ "schema": "naome.task-state.v2",
87
+ "version": 2,
88
+ "status": status,
89
+ "activeTask": active_task,
90
+ "blocker": null,
91
+ "updatedAt": "2026-05-07T12:00:00.000Z"
92
+ })),
93
+ );
94
+ }
95
+
96
+ pub fn write_verification(&self, value: serde_json::Value) {
97
+ self.write(".naome/verification.json", &pretty(value));
98
+ }
99
+
100
+ pub fn write(&self, relative_path: &str, content: &str) {
101
+ let path = self.root.join(relative_path);
102
+ if let Some(parent) = path.parent() {
103
+ fs::create_dir_all(parent).unwrap();
104
+ }
105
+ fs::write(path, content).unwrap();
106
+ }
107
+ }
108
+
109
+ impl Drop for WorkflowFixture {
110
+ fn drop(&mut self) {
111
+ let _ = fs::remove_dir_all(&self.root);
112
+ }
113
+ }
114
+
115
+ pub fn default_verification() -> serde_json::Value {
116
+ json!({
117
+ "schema": "naome.verification.v1",
118
+ "version": 1,
119
+ "status": "ready",
120
+ "lastUpdated": "2026-05-07",
121
+ "checks": [check("diff-check", "git diff --check", "fast")],
122
+ "changeTypes": [],
123
+ "releaseGates": []
124
+ })
125
+ }
126
+
127
+ pub fn check(id: &str, command: &str, cost: &str) -> serde_json::Value {
128
+ json!({
129
+ "id": id,
130
+ "command": command,
131
+ "cwd": ".",
132
+ "purpose": "Test check.",
133
+ "cost": cost,
134
+ "source": "test",
135
+ "evidence": [],
136
+ "lastVerified": null
137
+ })
138
+ }
139
+
140
+ pub fn phase<const N: usize>(id: &str, order: usize, check_ids: [&str; N]) -> serde_json::Value {
141
+ json!({ "id": id, "order": order, "checkIds": check_ids.to_vec() })
142
+ }
143
+
144
+ fn active_task_state(
145
+ allowed_paths: &[&str],
146
+ checks: &[&str],
147
+ git_head: String,
148
+ ) -> serde_json::Value {
149
+ json!({
150
+ "id": "workflow-test",
151
+ "request": "Test workflow gates.",
152
+ "userPrompt": { "receivedAt": "2026-05-07T12:00:00.000Z", "text": "Test workflow gates." },
153
+ "admission": admission_record(git_head),
154
+ "allowedPaths": allowed_paths,
155
+ "declaredChangeTypes": ["source"],
156
+ "requiredCheckIds": checks,
157
+ "humanReview": { "required": false, "approved": false, "reason": null },
158
+ "proofResults": []
159
+ })
160
+ }
161
+
162
+ fn admission_record(git_head: String) -> serde_json::Value {
163
+ json!({
164
+ "command": "node .naome/bin/check-task-state.js --admission",
165
+ "cwd": ".",
166
+ "exitCode": 0,
167
+ "checkedAt": "2026-05-07T12:00:00.000Z",
168
+ "gitHead": git_head,
169
+ "changedPaths": []
170
+ })
171
+ }
172
+
173
+ fn pretty(value: serde_json::Value) -> String {
174
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap())
175
+ }
176
+
177
+ fn run_git(root: &Path, args: &[&str]) {
178
+ let output = Command::new("git")
179
+ .args(args)
180
+ .current_dir(root)
181
+ .output()
182
+ .unwrap();
183
+ assert!(output.status.success(), "git {} failed", args.join(" "));
184
+ }
185
+
186
+ fn git_text(root: &Path, args: &[&str]) -> String {
187
+ let output = Command::new("git")
188
+ .args(args)
189
+ .current_dir(root)
190
+ .output()
191
+ .unwrap();
192
+ assert!(output.status.success());
193
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
194
+ }
@@ -0,0 +1,90 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { copyFileSync, existsSync, readFileSync, renameSync } from "node:fs";
4
+ import { join, relative } from "node:path";
5
+
6
+ import {
7
+ assertArchiveDirectoryAvailableForTakeover,
8
+ hasSymlinkInTargetPath,
9
+ nextArchivePath,
10
+ removeSkipped,
11
+ } from "./filesystem.js";
12
+ import { printCancelled, printError, printSection } from "./output.js";
13
+
14
+ export async function confirmAgentsTakeover(ctx) {
15
+ if (!hasAgentsTakeoverCandidate(ctx)) {
16
+ return;
17
+ }
18
+
19
+ if (hasSymlinkInTargetPath(ctx, "AGENTS.md")) {
20
+ printError(ctx, "NAOME cannot take over AGENTS.md because it is a symlink.");
21
+ console.error("Replace the symlink with a regular file before installing NAOME.");
22
+ process.exit(1);
23
+ }
24
+
25
+ if (!process.stdin.isTTY || process.env.CI === "true") {
26
+ printError(ctx, "AGENTS.md already exists.");
27
+ console.error("NAOME must replace AGENTS.md to become the active harness entrypoint.");
28
+ console.error("Run this installer in an interactive terminal and confirm the takeover.");
29
+ process.exit(1);
30
+ }
31
+
32
+ printSection(ctx, "AGENTS.md takeover required");
33
+ console.log(`${ctx.color.dim("existing")} AGENTS.md`);
34
+ console.log(`${ctx.color.dim("archive ")} .naome/archive/AGENTS.pre-naome.md`);
35
+ console.log(`${ctx.color.dim("replace ")} AGENTS.md with the NAOME entrypoint`);
36
+ console.log(`${ctx.color.dim("ignore ")} .naome/archive/ via .naomeignore`);
37
+ console.log("");
38
+
39
+ const rl = createInterface({ input, output });
40
+ const answer = await rl.question(`${ctx.color.yellow("?")} Replace AGENTS.md and continue installation? (y/N) `);
41
+ rl.close();
42
+
43
+ if (answer.trim().toLowerCase() !== "y") {
44
+ printCancelled(ctx, "NAOME was not installed. AGENTS.md was left unchanged.");
45
+ process.exit(1);
46
+ }
47
+
48
+ assertArchiveDirectoryAvailableForTakeover(ctx);
49
+ }
50
+
51
+ export function takeoverExistingAgents(ctx) {
52
+ const agentsPath = join(ctx.targetRoot, "AGENTS.md");
53
+ if (!existsSync(agentsPath) || hasSymlinkInTargetPath(ctx, "AGENTS.md")) {
54
+ return;
55
+ }
56
+
57
+ const templateAgentsPath = join(ctx.templateRoot, "AGENTS.md");
58
+ const templateAgents = readFileSync(templateAgentsPath, "utf8");
59
+ const existingAgents = readFileSync(agentsPath, "utf8");
60
+
61
+ if (existingAgents === templateAgents) {
62
+ return;
63
+ }
64
+
65
+ const archivePath = nextArchivePath(ctx, "AGENTS.pre-naome.md");
66
+ if (!archivePath) {
67
+ printError(ctx, "AGENTS.md already exists, but NAOME could not archive it safely.");
68
+ console.error("AGENTS.md was left unchanged.");
69
+ process.exit(1);
70
+ }
71
+
72
+ renameSync(agentsPath, archivePath);
73
+ copyFileSync(templateAgentsPath, agentsPath);
74
+ removeSkipped(ctx, "AGENTS.md");
75
+ ctx.installed.push("AGENTS.md");
76
+ ctx.archived.push({ from: "AGENTS.md", to: relative(ctx.targetRoot, archivePath) });
77
+ }
78
+
79
+ function hasAgentsTakeoverCandidate(ctx) {
80
+ const agentsPath = join(ctx.targetRoot, "AGENTS.md");
81
+ if (!existsSync(agentsPath)) {
82
+ return false;
83
+ }
84
+
85
+ if (hasSymlinkInTargetPath(ctx, "AGENTS.md")) {
86
+ return true;
87
+ }
88
+
89
+ return readFileSync(agentsPath, "utf8") !== readFileSync(join(ctx.templateRoot, "AGENTS.md"), "utf8");
90
+ }
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const firstRunPrompt = `Run the NAOME first-run protocol for this repository.
6
+
7
+ Start by reading .naomeignore, then docs/naome/index.md, then .naome/init-state.json.
8
+ Then read .naome/upgrade-state.json, .naome/task-state.json, and docs/naome/execution.md.
9
+ Run node .naome/bin/check-harness-health.js before feature work.
10
+ If initialized is false, follow docs/naome/first-run.md.
11
+ Do not begin feature work until NAOME intake is complete.
12
+ After intake, save the user's next natural-language request to a temporary prompt file, then run node .naome/bin/naome.js route --prompt-file <path> --execute --json.
13
+ Follow userMessage, humanOptions, requiredContext, and canCreateTask from the route output before creating the requested task.`;
14
+
15
+ export function createInstallerContext() {
16
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
17
+ const targetRoot = process.cwd();
18
+ const useColor = process.stdout.isTTY && process.env.NO_COLOR !== "1";
19
+ const format = (value, code) => useColor ? `\u001b[${code}m${value}\u001b[0m` : value;
20
+
21
+ return {
22
+ packageRoot,
23
+ templateRoot: join(packageRoot, "templates", "naome-root"),
24
+ targetRoot,
25
+ packageVersion: JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")).version,
26
+ installed: [],
27
+ archived: [],
28
+ updated: [],
29
+ skipped: [],
30
+ unsafeSkipped: [],
31
+ verboseOutput: process.argv.includes("--verbose"),
32
+ summaryTitle: "NAOME intake harness installed",
33
+ nextStepPrompt: firstRunPrompt,
34
+ installPlan: null,
35
+ machineOwnedPaths: [],
36
+ projectOwnedPaths: [],
37
+ localOnlyMachineOwnedPaths: [],
38
+ localOnlyGitIgnoreEntries: [],
39
+ localOnlyGitExcludeEntries: [],
40
+ localOnlyGitUntrackPaths: [],
41
+ healthCheckerRelativePath: ".naome/bin/check-harness-health.js",
42
+ taskStateCheckerRelativePath: ".naome/bin/check-task-state.js",
43
+ naomeCommandRelativePath: ".naome/bin/naome.js",
44
+ nativeBinaryName: process.platform === "win32" ? "naome.exe" : "naome",
45
+ nativeBinaryRelativePath:
46
+ process.platform === "win32" ? ".naome/bin/naome-rust.exe" : ".naome/bin/naome-rust",
47
+ minimumSupportedUpgradeVersion: "0.6.1",
48
+ integrityBlockPattern: /^const expectedMachineOwnedIntegrity = Object\.freeze\(\{[\s\S]*?\n\}\);\n/m,
49
+ normalizedIntegrityBlock:
50
+ "const expectedMachineOwnedIntegrity = Object.freeze({\n __generated__: \"sha256:generated\"\n});\n",
51
+ nativeIntegrityPattern:
52
+ /^const expectedNativeBinaryIntegrity = "(?:sha256:[a-f0-9]{64}|sha256:generated)";\n/m,
53
+ normalizedNativeIntegrity: 'const expectedNativeBinaryIntegrity = "sha256:generated";\n',
54
+ executableMachineOwnedPaths: new Set([
55
+ ".naome/bin/naome.js",
56
+ ".naome/bin/check-task-state.js",
57
+ ".naome/bin/check-harness-health.js",
58
+ ]),
59
+ color: {
60
+ bold: (value) => format(value, "1"),
61
+ dim: (value) => format(value, "2"),
62
+ green: (value) => format(value, "32"),
63
+ yellow: (value) => format(value, "33"),
64
+ red: (value) => format(value, "31"),
65
+ },
66
+ };
67
+ }