@lamentis/naome 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/Cargo.lock +199 -0
  2. package/Cargo.toml +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +6 -0
  5. package/bin/naome-node.js +1424 -0
  6. package/bin/naome.js +129 -0
  7. package/crates/naome-cli/Cargo.toml +14 -0
  8. package/crates/naome-cli/src/main.rs +341 -0
  9. package/crates/naome-core/Cargo.toml +11 -0
  10. package/crates/naome-core/src/decision.rs +432 -0
  11. package/crates/naome-core/src/git.rs +70 -0
  12. package/crates/naome-core/src/harness_health.rs +557 -0
  13. package/crates/naome-core/src/install_plan.rs +82 -0
  14. package/crates/naome-core/src/lib.rs +17 -0
  15. package/crates/naome-core/src/models.rs +99 -0
  16. package/crates/naome-core/src/paths.rs +72 -0
  17. package/crates/naome-core/src/task_state.rs +1859 -0
  18. package/crates/naome-core/src/verification.rs +217 -0
  19. package/crates/naome-core/src/verification_contract.rs +406 -0
  20. package/crates/naome-core/tests/decision.rs +297 -0
  21. package/crates/naome-core/tests/harness_health.rs +232 -0
  22. package/crates/naome-core/tests/install_plan.rs +35 -0
  23. package/crates/naome-core/tests/task_state.rs +588 -0
  24. package/crates/naome-core/tests/verification.rs +165 -0
  25. package/crates/naome-core/tests/verification_contract.rs +181 -0
  26. package/native/darwin-arm64/naome +0 -0
  27. package/package.json +44 -0
  28. package/templates/naome-root/.naome/bin/check-harness-health.js +163 -0
  29. package/templates/naome-root/.naome/bin/check-task-state.js +180 -0
  30. package/templates/naome-root/.naome/bin/naome.js +306 -0
  31. package/templates/naome-root/.naome/init-state.json +13 -0
  32. package/templates/naome-root/.naome/manifest.json +45 -0
  33. package/templates/naome-root/.naome/package.json +3 -0
  34. package/templates/naome-root/.naome/task-contract.schema.json +174 -0
  35. package/templates/naome-root/.naome/task-state.json +8 -0
  36. package/templates/naome-root/.naome/upgrade-state.json +7 -0
  37. package/templates/naome-root/.naome/verification.json +45 -0
  38. package/templates/naome-root/.naomeignore +4 -0
  39. package/templates/naome-root/AGENTS.md +77 -0
  40. package/templates/naome-root/docs/naome/agent-workflow.md +82 -0
  41. package/templates/naome-root/docs/naome/architecture.md +37 -0
  42. package/templates/naome-root/docs/naome/decisions.md +18 -0
  43. package/templates/naome-root/docs/naome/execution.md +192 -0
  44. package/templates/naome-root/docs/naome/first-run.md +135 -0
  45. package/templates/naome-root/docs/naome/index.md +67 -0
  46. package/templates/naome-root/docs/naome/repo-profile.md +51 -0
  47. package/templates/naome-root/docs/naome/security.md +60 -0
  48. package/templates/naome-root/docs/naome/testing.md +51 -0
  49. package/templates/naome-root/docs/naome/upgrade.md +20 -0
@@ -0,0 +1,165 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::time::{SystemTime, UNIX_EPOCH};
4
+
5
+ use naome_core::seed_builtin_verification_checks;
6
+ use serde_json::json;
7
+
8
+ #[test]
9
+ fn seeds_builtin_checks_without_removing_repo_specific_checks() {
10
+ let repo = TestRepo::new("verification-seed");
11
+ repo.write_naome_json(
12
+ "verification.json",
13
+ json!({
14
+ "schema": "naome.verification.v1",
15
+ "version": 1,
16
+ "status": "ready",
17
+ "lastUpdated": "2026-05-05",
18
+ "checks": [
19
+ {
20
+ "id": "repo-docs",
21
+ "command": "npm run docs:test",
22
+ "cwd": ".",
23
+ "purpose": "Validate repository documentation.",
24
+ "cost": "fast",
25
+ "source": "repository intake",
26
+ "evidence": ["package.json"],
27
+ "lastVerified": null
28
+ }
29
+ ],
30
+ "changeTypes": [
31
+ {
32
+ "id": "repo-docs",
33
+ "description": "Repository documentation changes.",
34
+ "paths": ["docs/**"],
35
+ "requiredChecks": ["repo-docs"],
36
+ "recommendedChecks": [],
37
+ "humanReview": false
38
+ }
39
+ ],
40
+ "releaseGates": [
41
+ {
42
+ "checkId": "repo-docs",
43
+ "requiredWhen": "Before merging repository documentation changes."
44
+ }
45
+ ]
46
+ }),
47
+ );
48
+
49
+ let changed = seed_builtin_verification_checks(repo.path()).unwrap();
50
+
51
+ assert!(changed);
52
+ let verification = repo.read_naome_json("verification.json");
53
+ assert_eq!(
54
+ check_command(&verification, "diff-check"),
55
+ Some("git diff --check")
56
+ );
57
+ assert_eq!(
58
+ check_command(&verification, "naome-harness-health"),
59
+ Some("node .naome/bin/check-harness-health.js")
60
+ );
61
+ assert_eq!(
62
+ check_command(&verification, "naome-task-state"),
63
+ Some("node .naome/bin/check-task-state.js")
64
+ );
65
+ assert_eq!(
66
+ check_command(&verification, "repo-docs"),
67
+ Some("npm run docs:test")
68
+ );
69
+
70
+ let changed_again = seed_builtin_verification_checks(repo.path()).unwrap();
71
+ assert!(!changed_again);
72
+ }
73
+
74
+ #[test]
75
+ fn seeding_builtin_checks_preserves_existing_verification_layout() {
76
+ let repo = TestRepo::new("verification-layout");
77
+ repo.write_naome_file(
78
+ "verification.json",
79
+ r#"{
80
+ "schema": "naome.verification.v1",
81
+ "version": 1,
82
+ "status": "ready",
83
+ "lastUpdated": "2026-05-06",
84
+ "checks": [
85
+ {
86
+ "id": "repo-docs",
87
+ "command": "npm run docs:test",
88
+ "cwd": ".",
89
+ "purpose": "Validate repository documentation.",
90
+ "cost": "fast",
91
+ "source": "repository intake",
92
+ "evidence": [
93
+ "package.json"
94
+ ],
95
+ "lastVerified": null
96
+ }
97
+ ],
98
+ "changeTypes": [],
99
+ "releaseGates": []
100
+ }
101
+ "#,
102
+ );
103
+
104
+ let changed = seed_builtin_verification_checks(repo.path()).unwrap();
105
+
106
+ assert!(changed);
107
+ let content = repo.read_naome_file("verification.json");
108
+ assert!(content.find("\"schema\"").unwrap() < content.find("\"checks\"").unwrap());
109
+ assert!(content.find("\"checks\"").unwrap() < content.find("\"changeTypes\"").unwrap());
110
+ assert!(
111
+ content.find("\"id\": \"repo-docs\"").unwrap()
112
+ < content.find("\"command\": \"npm run docs:test\"").unwrap()
113
+ );
114
+ }
115
+
116
+ fn check_command<'a>(verification: &'a serde_json::Value, check_id: &str) -> Option<&'a str> {
117
+ verification
118
+ .get("checks")?
119
+ .as_array()?
120
+ .iter()
121
+ .find(|check| check.get("id").and_then(serde_json::Value::as_str) == Some(check_id))?
122
+ .get("command")?
123
+ .as_str()
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
+ }
@@ -0,0 +1,181 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::time::{SystemTime, UNIX_EPOCH};
4
+
5
+ use naome_core::validate_verification_contract;
6
+ use serde_json::json;
7
+
8
+ #[test]
9
+ fn accepts_valid_contract_and_testing_map() {
10
+ let repo = TestRepo::new("verification-contract-valid");
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
+ );
49
+
50
+ let errors = validate_verification_contract(repo.path()).unwrap();
51
+
52
+ assert!(errors.is_empty(), "{errors:#?}");
53
+ }
54
+
55
+ #[test]
56
+ fn rejects_missing_check_references_and_ready_placeholders() {
57
+ let repo = TestRepo::new("verification-contract-invalid");
58
+ repo.write_testing_markdown(default_testing_markdown());
59
+ repo.write_naome_json(
60
+ "verification.json",
61
+ json!({
62
+ "schema": "naome.verification.v1",
63
+ "version": 1,
64
+ "status": "ready",
65
+ "lastUpdated": "2026-05-05",
66
+ "checks": [
67
+ {
68
+ "id": "example-check",
69
+ "command": "replace with real command",
70
+ "cwd": ".",
71
+ "purpose": "Replace with what this proves.",
72
+ "cost": "fast",
73
+ "source": "replace with evidence source",
74
+ "evidence": [],
75
+ "lastVerified": null
76
+ }
77
+ ],
78
+ "changeTypes": [
79
+ {
80
+ "id": "docs",
81
+ "description": "Documentation changes.",
82
+ "paths": ["docs/**"],
83
+ "requiredChecks": ["missing-check"],
84
+ "recommendedChecks": [],
85
+ "humanReview": false
86
+ }
87
+ ],
88
+ "releaseGates": []
89
+ }),
90
+ );
91
+
92
+ let errors = validate_verification_contract(repo.path()).unwrap();
93
+ let joined = errors.join("\n");
94
+
95
+ assert!(joined.contains("unknown check id: missing-check"));
96
+ assert!(joined.contains("must not contain example placeholders"));
97
+ }
98
+
99
+ #[test]
100
+ fn rejects_checks_without_explicit_last_verified_value() {
101
+ let repo = TestRepo::new("verification-contract-last-verified");
102
+ repo.write_testing_markdown(default_testing_markdown());
103
+ repo.write_naome_json(
104
+ "verification.json",
105
+ json!({
106
+ "schema": "naome.verification.v1",
107
+ "version": 1,
108
+ "status": "uninitialized",
109
+ "lastUpdated": null,
110
+ "checks": [
111
+ {
112
+ "id": "diff-check",
113
+ "command": "git diff --check",
114
+ "cwd": ".",
115
+ "purpose": "Reject whitespace errors.",
116
+ "cost": "fast",
117
+ "source": "git",
118
+ "evidence": []
119
+ }
120
+ ],
121
+ "changeTypes": [],
122
+ "releaseGates": []
123
+ }),
124
+ );
125
+
126
+ let errors = validate_verification_contract(repo.path()).unwrap();
127
+ let joined = errors.join("\n");
128
+
129
+ assert!(joined.contains("checks[0].lastVerified must be YYYY-MM-DD or null."));
130
+ }
131
+
132
+ fn default_testing_markdown() -> &'static str {
133
+ "# Testing And Verification\n\n\
134
+ ## Verification Map\n\n\
135
+ | Change type | Required proof | Command | Notes |\n|---|---|---|---|\n\n\
136
+ ## Known Checks\n\n\
137
+ | Check id | Command | Cwd | Cost | Last verified |\n|---|---|---|---|---|\n\n\
138
+ ## Change Type Rules\n\n\
139
+ | Change type | Paths | Required checks |\n|---|---|---|\n\n\
140
+ ## Release Gates\n\n\
141
+ | Check id | Required when |\n|---|---|\n\n\
142
+ ## Evidence\n\n\
143
+ - Test fixture.\n"
144
+ }
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
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@lamentis/naome",
3
+ "version": "1.0.0",
4
+ "description": "Native-first CLI for the NAOME agent harness.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "agent",
9
+ "agents",
10
+ "ai",
11
+ "harness",
12
+ "repository",
13
+ "codex",
14
+ "claude"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Lamentis-O/naome.git",
19
+ "directory": "packages/naome"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/Lamentis-O/naome/issues"
23
+ },
24
+ "homepage": "https://github.com/Lamentis-O/naome#readme",
25
+ "bin": {
26
+ "naome": "bin/naome.js"
27
+ },
28
+ "files": [
29
+ "bin/naome.js",
30
+ "bin/naome-node.js",
31
+ "Cargo.lock",
32
+ "Cargo.toml",
33
+ "crates",
34
+ "native",
35
+ "templates",
36
+ "README.md"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { existsSync, readFileSync } = require("node:fs");
4
+ const { createHash } = require("node:crypto");
5
+ const { dirname, join, resolve } = require("node:path");
6
+ const { spawnSync } = require("node:child_process");
7
+
8
+ const nativeBinaryRelativePath = process.platform === "win32" ? ".naome/bin/naome-rust.exe" : ".naome/bin/naome-rust";
9
+ const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
10
+
11
+ const expectedMachineOwnedIntegrity = Object.freeze({
12
+ "AGENTS.md": "sha256:ebe589ce85b43d4aa23014d8a26a49bd33dc52ef1bba68e4f63a7e86640be06a",
13
+ ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
14
+ ".naome/bin/naome.js": "sha256:1b63729dc55730a826193d35cf78e755a991b356ac81b45f4aa3fcf24c8152fa",
15
+ ".naome/bin/check-task-state.js": "sha256:43c02868072d0d13499aefba2e9a5ec9517d59539fd19ff0f11e3e4623a51b44",
16
+ ".naome/bin/check-harness-health.js": "sha256:dce2a380022dd63d86bd762869debafbc635ab3d7e8fae985e2d429d8ee437ad",
17
+ ".naome/task-contract.schema.json": "sha256:c806416d4944eaed6dc48b3760fd0dd5b9b5a7c9ab895697fe36b54c41c1332f",
18
+ "docs/naome/index.md": "sha256:75c4cdf9edcd46c83a619cbfc551e96cae35bcff694d955ebab4624125ae7f12",
19
+ "docs/naome/first-run.md": "sha256:a1dd0bd17ec9d71955a473cd2c4a615538e89a7d81e8f4e1015a50ab9efe3558",
20
+ "docs/naome/agent-workflow.md": "sha256:5e5b26e7896f95a9154abcd133f08b33f84fddc679757ed15c043a91e319fb4e",
21
+ "docs/naome/execution.md": "sha256:3d67fd9922a66a66f79f920adbb24dcd47eefdfde835a5351a27ffed932dcfea",
22
+ "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
23
+ });
24
+
25
+ function main() {
26
+ const root = findHarnessRoot(process.cwd()) || resolve(__dirname, "..", "..");
27
+ const nativeBinary = resolveNativeDecisionBinary(root);
28
+
29
+ const result = spawnSync(nativeBinary, ["check-harness-health", "--root", root], {
30
+ cwd: root,
31
+ encoding: "utf8",
32
+ env: {
33
+ ...process.env,
34
+ NAOME_EXPECTED_INTEGRITY_JSON: JSON.stringify(expectedMachineOwnedIntegrity)
35
+ },
36
+ stdio: "inherit"
37
+ });
38
+
39
+ process.exit(result.status === null ? 1 : result.status);
40
+ }
41
+
42
+ function resolveNativeDecisionBinary(root) {
43
+ const expectedIntegrity = expectedNativeIntegrity(root);
44
+ const candidates = [
45
+ process.env.NAOME_NATIVE_BIN,
46
+ join(root, nativeBinaryRelativePath),
47
+ join(root, "packages", "naome", "target", "release", nativeBinaryName),
48
+ join(root, "packages", "naome", "target", "debug", nativeBinaryName)
49
+ ].filter(Boolean);
50
+
51
+ for (const candidate of candidates) {
52
+ if (isUsableNativeBinary(candidate, root, expectedIntegrity)) {
53
+ return candidate;
54
+ }
55
+ }
56
+
57
+ if (expectedIntegrity) {
58
+ fail(`No runnable NAOME native harness health binary matched ${expectedIntegrity}. Run naome sync again.`);
59
+ }
60
+
61
+ const built = buildSourceNativeBinary(root);
62
+ if (built && isUsableNativeBinary(built, root, null)) {
63
+ return built;
64
+ }
65
+
66
+ fail("NAOME native harness health binary is missing or incompatible. Run naome sync again.");
67
+ }
68
+
69
+ function expectedNativeIntegrity(root) {
70
+ if (isIntegrityHash(process.env.NAOME_EXPECTED_NATIVE_INTEGRITY)) {
71
+ return process.env.NAOME_EXPECTED_NATIVE_INTEGRITY;
72
+ }
73
+
74
+ const manifestPath = join(root, ".naome", "manifest.json");
75
+ if (!existsSync(manifestPath)) {
76
+ return null;
77
+ }
78
+
79
+ let manifest;
80
+ try {
81
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
82
+ } catch {
83
+ return null;
84
+ }
85
+
86
+ const expected = manifest.integrity?.[nativeBinaryRelativePath];
87
+ if (!isIntegrityHash(expected)) {
88
+ return null;
89
+ }
90
+
91
+ return expected;
92
+ }
93
+
94
+ function isUsableNativeBinary(candidate, root, expectedIntegrity) {
95
+ if (!candidate || !existsSync(candidate)) {
96
+ return false;
97
+ }
98
+
99
+ if (expectedIntegrity) {
100
+ const actual = `sha256:${createHash("sha256").update(readFileSync(candidate)).digest("hex")}`;
101
+ if (actual !== expectedIntegrity) {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ const probe = spawnSync(candidate, [], {
107
+ cwd: root,
108
+ encoding: "utf8",
109
+ stdio: "ignore"
110
+ });
111
+ return !probe.error && probe.status !== null;
112
+ }
113
+
114
+ function buildSourceNativeBinary(root) {
115
+ const manifestPath = join(root, "packages", "naome", "Cargo.toml");
116
+ if (!existsSync(manifestPath)) {
117
+ return null;
118
+ }
119
+
120
+ const result = spawnSync("cargo", ["build", "--release", "--manifest-path", manifestPath, "-p", "naome-cli"], {
121
+ cwd: root,
122
+ encoding: "utf8",
123
+ stdio: "ignore"
124
+ });
125
+ if (result.status !== 0) {
126
+ return null;
127
+ }
128
+
129
+ const builtPath = join(root, "packages", "naome", "target", "release", nativeBinaryName);
130
+ return existsSync(builtPath) ? builtPath : null;
131
+ }
132
+
133
+ function findHarnessRoot(startPath) {
134
+ let current = resolve(startPath);
135
+
136
+ while (true) {
137
+ if (existsSync(join(current, ".naome", "manifest.json"))) {
138
+ return current;
139
+ }
140
+
141
+ const parent = dirname(current);
142
+ if (parent === current) {
143
+ return null;
144
+ }
145
+
146
+ current = parent;
147
+ }
148
+ }
149
+
150
+ function isIntegrityHash(value) {
151
+ return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
152
+ }
153
+
154
+ function fail(message) {
155
+ console.error("NAOME harness health check failed.");
156
+ console.error(`- ${message}`);
157
+ console.error("");
158
+ console.error("Repair options: repair_harness, review_harness_diff, cancel_repair_baseline");
159
+ console.error("Run naome sync to repair machine-owned files after review.");
160
+ process.exit(1);
161
+ }
162
+
163
+ main();