@lamentis/naome 1.2.0 → 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 (113) hide show
  1. package/Cargo.lock +2 -2
  2. package/bin/naome-node.js +2 -1579
  3. package/bin/naome.js +19 -5
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/dispatcher.rs +2 -1
  6. package/crates/naome-cli/src/main.rs +3 -0
  7. package/crates/naome-cli/src/quality_commands.rs +90 -2
  8. package/crates/naome-core/Cargo.toml +1 -1
  9. package/crates/naome-core/src/decision/checks.rs +64 -0
  10. package/crates/naome-core/src/decision/idle.rs +67 -0
  11. package/crates/naome-core/src/decision/json.rs +36 -0
  12. package/crates/naome-core/src/decision/states.rs +165 -0
  13. package/crates/naome-core/src/decision.rs +131 -353
  14. package/crates/naome-core/src/install_plan.rs +2 -0
  15. package/crates/naome-core/src/lib.rs +5 -3
  16. package/crates/naome-core/src/paths.rs +3 -1
  17. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  18. package/crates/naome-core/src/quality/adapters.rs +20 -67
  19. package/crates/naome-core/src/quality/cleanup.rs +13 -1
  20. package/crates/naome-core/src/quality/config.rs +8 -15
  21. package/crates/naome-core/src/quality/config_support.rs +24 -0
  22. package/crates/naome-core/src/quality/mod.rs +18 -0
  23. package/crates/naome-core/src/quality/scanner.rs +20 -8
  24. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  25. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  26. package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
  27. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  28. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  29. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  30. package/crates/naome-core/src/quality/structure/classify.rs +94 -0
  31. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  32. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  33. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  34. package/crates/naome-core/src/quality/structure/model.rs +124 -0
  35. package/crates/naome-core/src/quality/types.rs +3 -0
  36. package/crates/naome-core/src/route/builtin_checks.rs +155 -0
  37. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  38. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  39. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  40. package/crates/naome-core/src/route/context.rs +180 -0
  41. package/crates/naome-core/src/route/execution.rs +96 -0
  42. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  43. package/crates/naome-core/src/route/execution_support.rs +57 -0
  44. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  45. package/crates/naome-core/src/route/git_ops.rs +72 -0
  46. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  47. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  48. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  49. package/crates/naome-core/src/route/worktree.rs +75 -0
  50. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  51. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  52. package/crates/naome-core/src/route.rs +44 -1217
  53. package/crates/naome-core/src/verification.rs +1 -0
  54. package/crates/naome-core/tests/decision.rs +24 -118
  55. package/crates/naome-core/tests/harness_health.rs +2 -0
  56. package/crates/naome-core/tests/quality.rs +12 -118
  57. package/crates/naome-core/tests/quality_structure.rs +116 -0
  58. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  59. package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
  60. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  61. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  62. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  63. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  64. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  65. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  66. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  67. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  68. package/crates/naome-core/tests/route.rs +1 -1376
  69. package/crates/naome-core/tests/route_baseline.rs +86 -0
  70. package/crates/naome-core/tests/route_completion.rs +141 -0
  71. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  72. package/crates/naome-core/tests/route_user_diff.rs +198 -0
  73. package/crates/naome-core/tests/route_worktree.rs +54 -0
  74. package/crates/naome-core/tests/task_state.rs +60 -432
  75. package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
  76. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  77. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  78. package/crates/naome-core/tests/verification.rs +4 -45
  79. package/crates/naome-core/tests/verification_contract.rs +22 -78
  80. package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
  81. package/installer/agents.js +90 -0
  82. package/installer/context.js +67 -0
  83. package/installer/filesystem.js +166 -0
  84. package/installer/flows.js +84 -0
  85. package/installer/git-boundary.js +170 -0
  86. package/installer/git-hook-content.js +36 -0
  87. package/installer/git-hooks.js +134 -0
  88. package/installer/git-local.js +2 -0
  89. package/installer/git-shared.js +35 -0
  90. package/installer/harness-file-ops.js +140 -0
  91. package/installer/harness-files.js +56 -0
  92. package/installer/harness-verification.js +123 -0
  93. package/installer/install-plan.js +66 -0
  94. package/installer/main.js +25 -0
  95. package/installer/manifest-state.js +167 -0
  96. package/installer/native-build.js +24 -0
  97. package/installer/native-format.js +6 -0
  98. package/installer/native.js +162 -0
  99. package/installer/output.js +131 -0
  100. package/installer/version.js +32 -0
  101. package/native/darwin-arm64/naome +0 -0
  102. package/native/linux-x64/naome +0 -0
  103. package/package.json +2 -1
  104. package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
  105. package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
  106. package/templates/naome-root/.naome/bin/naome.js +25 -21
  107. package/templates/naome-root/.naome/manifest.json +4 -2
  108. package/templates/naome-root/.naome/repository-structure.json +90 -0
  109. package/templates/naome-root/.naome/verification.json +1 -0
  110. package/templates/naome-root/docs/naome/index.md +4 -3
  111. package/templates/naome-root/docs/naome/repository-quality.md +3 -0
  112. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  113. package/templates/naome-root/docs/naome/testing.md +2 -1
@@ -0,0 +1,163 @@
1
+ mod states;
2
+
3
+ use std::collections::HashMap;
4
+ use std::fs;
5
+ use std::path::{Path, PathBuf};
6
+ use std::process::Command;
7
+ use std::sync::atomic::{AtomicU64, Ordering};
8
+ use std::time::{SystemTime, UNIX_EPOCH};
9
+
10
+ use naome_core::{HarnessHealthOptions, MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
11
+ use serde_json::{json, Value};
12
+ use sha2::{Digest, Sha256};
13
+ pub use states::{
14
+ active_task, complete_task_state, idle_task_state, successful_admission, successful_proof,
15
+ };
16
+
17
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
18
+
19
+ pub struct TaskFixture {
20
+ root: PathBuf,
21
+ }
22
+
23
+ impl TaskFixture {
24
+ pub fn new(task_state: Value) -> Self {
25
+ let nonce = SystemTime::now()
26
+ .duration_since(UNIX_EPOCH)
27
+ .unwrap()
28
+ .as_nanos();
29
+ let counter = FIXTURE_COUNTER.fetch_add(1, Ordering::Relaxed);
30
+ let root = std::env::temp_dir().join(format!(
31
+ "naome-task-state-rust-{}-{nonce}-{counter}",
32
+ std::process::id()
33
+ ));
34
+ fs::create_dir_all(root.join(".naome")).unwrap();
35
+ write_json(&root, ".naome/task-state.json", task_state);
36
+ write_json(&root, ".naome/init-state.json", init_state());
37
+ write_json(&root, ".naome/upgrade-state.json", upgrade_state());
38
+ write_json(&root, ".naome/verification.json", verification_state());
39
+ Self { root }
40
+ }
41
+
42
+ pub fn path(&self) -> &Path {
43
+ &self.root
44
+ }
45
+
46
+ pub fn write(&self, relative_path: &str, content: &str) {
47
+ let path = self.root.join(relative_path);
48
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
49
+ fs::write(path, content).unwrap();
50
+ }
51
+
52
+ pub fn write_json(&self, relative_path: &str, value: Value) {
53
+ write_json(&self.root, relative_path, value);
54
+ }
55
+
56
+ pub fn install_healthy_harness(&self) -> HashMap<String, String> {
57
+ let mut integrity = HashMap::new();
58
+ for relative_path in MACHINE_OWNED_PATHS {
59
+ let content = machine_content(relative_path);
60
+ self.write(relative_path, &content);
61
+ let digest = Sha256::digest(content.as_bytes());
62
+ integrity.insert((*relative_path).to_string(), format!("sha256:{digest:x}"));
63
+ }
64
+ for relative_path in PROJECT_OWNED_PATHS {
65
+ let path = self.root.join(relative_path);
66
+ if *relative_path == ".naome/manifest.json" || path.exists() {
67
+ continue;
68
+ }
69
+ let content = if *relative_path == ".naomeignore" {
70
+ ".naome/archive/\n".to_string()
71
+ } else {
72
+ format!("# {relative_path}\n")
73
+ };
74
+ self.write(relative_path, &content);
75
+ }
76
+ fs::create_dir_all(self.root.join(".naome/archive")).unwrap();
77
+ self.write_json(".naome/manifest.json", manifest_state(integrity.clone()));
78
+ integrity
79
+ }
80
+
81
+ pub fn init_git(&self) {
82
+ self.git(["init"]);
83
+ self.git(["config", "user.email", "naome@example.com"]);
84
+ self.git(["config", "user.name", "NAOME Test"]);
85
+ self.git(["add", "."]);
86
+ self.git(["commit", "-m", "baseline"]);
87
+ self.refresh_admission_head();
88
+ }
89
+
90
+ pub fn git<const N: usize>(&self, args: [&str; N]) -> String {
91
+ let output = Command::new("git")
92
+ .args(args)
93
+ .current_dir(&self.root)
94
+ .output()
95
+ .unwrap();
96
+ assert!(
97
+ output.status.success(),
98
+ "{}",
99
+ String::from_utf8_lossy(&output.stderr)
100
+ );
101
+ String::from_utf8_lossy(&output.stdout).to_string()
102
+ }
103
+
104
+ fn refresh_admission_head(&self) {
105
+ let path = self.root.join(".naome/task-state.json");
106
+ let mut task_state: Value =
107
+ serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
108
+ let Some(admission) = task_state
109
+ .get_mut("activeTask")
110
+ .and_then(|task| task.get_mut("admission"))
111
+ else {
112
+ return;
113
+ };
114
+ admission["gitHead"] = Value::String(self.git(["rev-parse", "HEAD"]).trim().to_string());
115
+ fs::write(
116
+ path,
117
+ format!("{}\n", serde_json::to_string_pretty(&task_state).unwrap()),
118
+ )
119
+ .unwrap();
120
+ }
121
+ }
122
+
123
+ pub fn strict_health_options(expected_integrity: HashMap<String, String>) -> HarnessHealthOptions {
124
+ HarnessHealthOptions {
125
+ expected_integrity,
126
+ allow_missing_integrity: false,
127
+ allow_missing_archive: false,
128
+ }
129
+ }
130
+
131
+ pub fn write_json(root: &Path, relative_path: &str, value: Value) {
132
+ let path = root.join(relative_path);
133
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
134
+ fs::write(
135
+ path,
136
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
137
+ )
138
+ .unwrap();
139
+ }
140
+
141
+ fn machine_content(relative_path: &str) -> String {
142
+ match relative_path {
143
+ "AGENTS.md" => "# Agent Instructions\nRun node .naome/bin/check-task-state.js --admission before task work.\n".to_string(),
144
+ ".naome/package.json" => format!("{}\n", json!({ "type": "commonjs" })),
145
+ _ => format!("# {relative_path}\n"),
146
+ }
147
+ }
148
+
149
+ fn init_state() -> Value {
150
+ json!({ "initialized": true, "intakeStatus": "complete", "initializedAt": "2026-05-04T12:00:00.000Z", "initializedBy": "naome-test", "blockedReason": null, "requiredDocs": [] })
151
+ }
152
+
153
+ fn upgrade_state() -> Value {
154
+ json!({ "status": "complete", "fromVersion": null, "toVersion": "0.6.1", "pending": [], "completed": [] })
155
+ }
156
+
157
+ fn verification_state() -> Value {
158
+ json!({ "schema": "naome.verification.v1", "version": 1, "status": "ready", "lastUpdated": "2026-05-04", "checks": [{ "id": "diff-check", "command": "git diff --check", "cwd": ".", "purpose": "Detect whitespace and patch formatting issues.", "cost": "fast", "source": "git", "evidence": ["README.md"], "lastVerified": "2026-05-04" }], "changeTypes": [], "releaseGates": [] })
159
+ }
160
+
161
+ fn manifest_state(integrity: HashMap<String, String>) -> Value {
162
+ json!({ "name": "naome", "harnessVersion": "0.6.1", "profile": "standard", "installedAt": "2026-05-05T00:00:00.000Z", "machineOwned": MACHINE_OWNED_PATHS, "projectOwned": PROJECT_OWNED_PATHS, "integrity": integrity })
163
+ }
@@ -0,0 +1,84 @@
1
+ use serde_json::{json, Value};
2
+
3
+ pub fn idle_task_state() -> Value {
4
+ json!({
5
+ "schema": "naome.task-state.v1",
6
+ "version": 1,
7
+ "status": "idle",
8
+ "activeTask": null,
9
+ "blocker": null,
10
+ "updatedAt": null
11
+ })
12
+ }
13
+
14
+ pub fn complete_task_state(overrides: Value) -> Value {
15
+ json!({
16
+ "schema": "naome.task-state.v1",
17
+ "version": 1,
18
+ "status": "complete",
19
+ "activeTask": active_task(overrides),
20
+ "blocker": null,
21
+ "updatedAt": "2026-05-04T12:00:00.000Z"
22
+ })
23
+ }
24
+
25
+ pub fn active_task(overrides: Value) -> Value {
26
+ let mut task = json!({
27
+ "id": "readme-test",
28
+ "request": "Update the README.",
29
+ "userPrompt": {
30
+ "receivedAt": "2026-05-04T12:00:00.000Z",
31
+ "text": "Update the README."
32
+ },
33
+ "admission": successful_admission(json!({})),
34
+ "allowedPaths": ["README.md"],
35
+ "declaredChangeTypes": ["docs"],
36
+ "requiredCheckIds": ["diff-check"],
37
+ "proofResults": [],
38
+ "humanReview": {
39
+ "required": false,
40
+ "approved": false,
41
+ "reason": null
42
+ }
43
+ });
44
+ merge_object(&mut task, overrides);
45
+ task
46
+ }
47
+
48
+ pub fn successful_admission(overrides: Value) -> Value {
49
+ let mut admission = json!({
50
+ "command": "node .naome/bin/check-task-state.js --admission",
51
+ "cwd": ".",
52
+ "exitCode": 0,
53
+ "checkedAt": "2026-05-04T12:00:00.000Z",
54
+ "gitHead": "pending-test-head",
55
+ "changedPaths": []
56
+ });
57
+ merge_object(&mut admission, overrides);
58
+ admission
59
+ }
60
+
61
+ pub fn successful_proof(overrides: Value) -> Value {
62
+ let mut proof = json!({
63
+ "checkId": "diff-check",
64
+ "command": "git diff --check",
65
+ "cwd": ".",
66
+ "exitCode": 0,
67
+ "checkedAt": "2026-05-04T12:00:00.000Z",
68
+ "evidence": ["README.md"]
69
+ });
70
+ merge_object(&mut proof, overrides);
71
+ proof
72
+ }
73
+
74
+ fn merge_object(target: &mut Value, overrides: Value) {
75
+ let Some(target_object) = target.as_object_mut() else {
76
+ return;
77
+ };
78
+ let Some(overrides_object) = overrides.as_object() else {
79
+ return;
80
+ };
81
+ for (key, value) in overrides_object {
82
+ target_object.insert(key.clone(), value.clone());
83
+ }
84
+ }
@@ -1,10 +1,10 @@
1
- use std::fs;
2
- use std::path::{Path, PathBuf};
3
- use std::time::{SystemTime, UNIX_EPOCH};
4
-
5
1
  use naome_core::seed_builtin_verification_checks;
6
2
  use serde_json::json;
7
3
 
4
+ mod repo_support;
5
+
6
+ use repo_support::TestRepo;
7
+
8
8
  #[test]
9
9
  fn seeds_builtin_checks_without_removing_repo_specific_checks() {
10
10
  let repo = TestRepo::new("verification-seed");
@@ -122,44 +122,3 @@ fn check_command<'a>(verification: &'a serde_json::Value, check_id: &str) -> Opt
122
122
  .get("command")?
123
123
  .as_str()
124
124
  }
125
-
126
- struct TestRepo {
127
- root: PathBuf,
128
- }
129
-
130
- impl TestRepo {
131
- fn new(name: &str) -> Self {
132
- let nonce = SystemTime::now()
133
- .duration_since(UNIX_EPOCH)
134
- .unwrap()
135
- .as_nanos();
136
- let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
137
- fs::create_dir_all(root.join(".naome")).unwrap();
138
- Self { root }
139
- }
140
-
141
- fn path(&self) -> &Path {
142
- &self.root
143
- }
144
-
145
- fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
146
- fs::write(
147
- self.root.join(".naome").join(file_name),
148
- format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
149
- )
150
- .unwrap();
151
- }
152
-
153
- fn write_naome_file(&self, file_name: &str, content: &str) {
154
- fs::write(self.root.join(".naome").join(file_name), content).unwrap();
155
- }
156
-
157
- fn read_naome_json(&self, file_name: &str) -> serde_json::Value {
158
- serde_json::from_str(&fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap())
159
- .unwrap()
160
- }
161
-
162
- fn read_naome_file(&self, file_name: &str) -> String {
163
- fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap()
164
- }
165
- }
@@ -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
- }
@@ -60,7 +60,7 @@ impl WorkflowFixture {
60
60
  ".naome/manifest.json",
61
61
  &pretty(json!({
62
62
  "name": "naome",
63
- "harnessVersion": "1.2.0",
63
+ "harnessVersion": "1.2.1",
64
64
  "profile": "standard",
65
65
  "installedAt": null,
66
66
  "machineOwned": ["AGENTS.md", ".naome/bin/naome.js", "docs/naome/index.md"],
@@ -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
+ }