@lamentis/naome 1.1.1 → 1.2.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 (126) 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 +44 -4
  6. package/bin/naome.js +54 -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 +36 -0
  11. package/crates/naome-cli/src/install_bridge.rs +83 -0
  12. package/crates/naome-cli/src/main.rs +57 -341
  13. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  14. package/crates/naome-cli/src/quality_commands.rs +141 -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/harness_health/integrity.rs +96 -0
  19. package/crates/naome-core/src/harness_health.rs +14 -126
  20. package/crates/naome-core/src/install_plan.rs +3 -0
  21. package/crates/naome-core/src/intent/classifier.rs +171 -0
  22. package/crates/naome-core/src/intent/envelope.rs +108 -0
  23. package/crates/naome-core/src/intent/legacy.rs +138 -0
  24. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  25. package/crates/naome-core/src/intent/model.rs +71 -0
  26. package/crates/naome-core/src/intent/patterns.rs +170 -0
  27. package/crates/naome-core/src/intent/resolver.rs +162 -0
  28. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  29. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  30. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  31. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  32. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  33. package/crates/naome-core/src/intent/risk.rs +40 -0
  34. package/crates/naome-core/src/intent/segment.rs +170 -0
  35. package/crates/naome-core/src/intent.rs +64 -879
  36. package/crates/naome-core/src/journal.rs +9 -20
  37. package/crates/naome-core/src/lib.rs +13 -0
  38. package/crates/naome-core/src/quality/adapters.rs +178 -0
  39. package/crates/naome-core/src/quality/baseline.rs +75 -0
  40. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  41. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  42. package/crates/naome-core/src/quality/checks.rs +228 -0
  43. package/crates/naome-core/src/quality/cleanup.rs +72 -0
  44. package/crates/naome-core/src/quality/config.rs +109 -0
  45. package/crates/naome-core/src/quality/mod.rs +90 -0
  46. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  47. package/crates/naome-core/src/quality/scanner.rs +367 -0
  48. package/crates/naome-core/src/quality/types.rs +289 -0
  49. package/crates/naome-core/src/route.rs +292 -17
  50. package/crates/naome-core/src/task_state/admission.rs +63 -0
  51. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  52. package/crates/naome-core/src/task_state/api.rs +130 -0
  53. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  54. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  55. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  56. package/crates/naome-core/src/task_state/completion.rs +72 -0
  57. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  58. package/crates/naome-core/src/task_state/diff.rs +95 -0
  59. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  60. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  61. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  62. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  63. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  64. package/crates/naome-core/src/task_state/mod.rs +38 -0
  65. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  66. package/crates/naome-core/src/task_state/progress.rs +123 -0
  67. package/crates/naome-core/src/task_state/proof.rs +139 -0
  68. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  69. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  70. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  71. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  72. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  73. package/crates/naome-core/src/task_state/repair.rs +168 -0
  74. package/crates/naome-core/src/task_state/shape.rs +117 -0
  75. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  76. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  77. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  78. package/crates/naome-core/src/task_state/types.rs +87 -0
  79. package/crates/naome-core/src/task_state/util.rs +137 -0
  80. package/crates/naome-core/src/verification/render.rs +122 -0
  81. package/crates/naome-core/src/verification.rs +176 -58
  82. package/crates/naome-core/src/verification_contract.rs +49 -21
  83. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  84. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  85. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  86. package/crates/naome-core/src/workflow/mod.rs +18 -0
  87. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  88. package/crates/naome-core/src/workflow/output.rs +111 -0
  89. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  90. package/crates/naome-core/src/workflow/phases.rs +169 -0
  91. package/crates/naome-core/src/workflow/policy.rs +156 -0
  92. package/crates/naome-core/src/workflow/processes.rs +91 -0
  93. package/crates/naome-core/src/workflow/types.rs +42 -0
  94. package/crates/naome-core/tests/harness_health.rs +3 -0
  95. package/crates/naome-core/tests/intent.rs +97 -792
  96. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  97. package/crates/naome-core/tests/intent_v2.rs +90 -0
  98. package/crates/naome-core/tests/quality.rs +425 -0
  99. package/crates/naome-core/tests/route.rs +221 -4
  100. package/crates/naome-core/tests/task_state.rs +3 -0
  101. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  102. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  103. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  104. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  105. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  106. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  107. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  108. package/native/darwin-arm64/naome +0 -0
  109. package/native/linux-x64/naome +0 -0
  110. package/package.json +2 -2
  111. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  112. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  113. package/templates/naome-root/.naome/bin/naome.js +34 -63
  114. package/templates/naome-root/.naome/manifest.json +20 -18
  115. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  116. package/templates/naome-root/.naome/repository-quality.json +24 -0
  117. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  118. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  119. package/templates/naome-root/.naome/verification.json +37 -0
  120. package/templates/naome-root/AGENTS.md +3 -0
  121. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  122. package/templates/naome-root/docs/naome/execution.md +25 -21
  123. package/templates/naome-root/docs/naome/index.md +4 -3
  124. package/templates/naome-root/docs/naome/repository-quality.md +43 -0
  125. package/templates/naome-root/docs/naome/testing.md +12 -0
  126. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -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.0",
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
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@lamentis/naome",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Native-first CLI for the NAOME agent harness.",
5
- "license": "MIT",
5
+ "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "agent",
@@ -1,158 +1,141 @@
1
1
  #!/usr/bin/env node
2
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");
3
+ const fs = require("node:fs");
4
+ const crypto = require("node:crypto");
5
+ const childProcess = require("node:child_process");
6
+ const path = require("node:path");
7
7
 
8
- const nativeBinaryRelativePath = process.platform === "win32" ? ".naome/bin/naome-rust.exe" : ".naome/bin/naome-rust";
8
+ const nativeBinaryPath = process.platform === "win32" ? ".naome/bin/naome-rust.exe" : ".naome/bin/naome-rust";
9
9
  const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
10
10
 
11
11
  const expectedMachineOwnedIntegrity = Object.freeze({
12
- "AGENTS.md": "sha256:84ce882fa6798a144c8c74673d4304fc6bf235b1cc417f7649eea9f7461775de",
13
- ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
14
- ".naome/bin/naome.js": "sha256:7894690ad47700a9a5e7ecb5a94ba42c1a12e2637d2c9f41f0023b1bd8bd22b6",
12
+ ".naome/bin/check-harness-health.js": "sha256:dc4de52b79c69600b9ba47b924e2c2b8de61a2cbfab6d1ccc0f1924d963db657",
15
13
  ".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:d04691b25ed377c2a3aa2c56965d0db276539aeadcfdd2a2ec1be7ff7706dd6f",
14
+ ".naome/bin/naome.js": "sha256:5675f729be6da3cbda1d8b9e0b36821cc98409aae95dff0603ed9161a5eb3b38",
15
+ ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
16
+ ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
17
+ "AGENTS.md": "sha256:9192ea81f90bb19f8043513c49b5da9e9598ee694da8356f345e7ccbca0e28df",
18
+ "docs/naome/agent-workflow.md": "sha256:802eb34cf268fc9c5e7aefc28192efef4bf60302d30b3f78e5a61b876ce8a098",
19
+ "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
19
20
  "docs/naome/first-run.md": "sha256:a1dd0bd17ec9d71955a473cd2c4a615538e89a7d81e8f4e1015a50ab9efe3558",
20
- "docs/naome/agent-workflow.md": "sha256:43ba8a6814068e1b932b12a392de70b39841dc82df0acdaac459d6714c501b9d",
21
- "docs/naome/execution.md": "sha256:ad66611b2878d1ba8fe05ddc4cfe9de7fd41de172a0de8295c36227a2700631a",
22
- "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1",
23
- ".naome/bin/naome-rust": "sha256:cc754ae59477577dfc0130cc21c286beaf3f19e2852278bb8f1e8724277eb44b"
21
+ "docs/naome/index.md": "sha256:7d917f1fa368874653bb458384bd9d0b221529e19644028a3143db3d3c144213",
22
+ "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
24
23
  });
25
24
 
26
- function main() {
27
- const root = findHarnessRoot(process.cwd()) || resolve(__dirname, "..", "..");
28
- const nativeBinary = resolveNativeDecisionBinary(root);
25
+ const root = findRoot(process.cwd()) || path.resolve(__dirname, "..", "..");
26
+ const binary = selectBinary(root);
27
+ const result = childProcess.spawnSync(binary, ["check-harness-health", "--root", root], {
28
+ cwd: root,
29
+ encoding: "utf8",
30
+ env: {
31
+ ...process.env,
32
+ NAOME_EXPECTED_INTEGRITY_JSON: JSON.stringify(expectedMachineOwnedIntegrity)
33
+ },
34
+ stdio: "inherit"
35
+ });
29
36
 
30
- const result = spawnSync(nativeBinary, ["check-harness-health", "--root", root], {
31
- cwd: root,
32
- encoding: "utf8",
33
- env: {
34
- ...process.env,
35
- NAOME_EXPECTED_INTEGRITY_JSON: JSON.stringify(expectedMachineOwnedIntegrity)
36
- },
37
- stdio: "inherit"
38
- });
37
+ process.exit(result.status === null ? 1 : result.status);
39
38
 
40
- process.exit(result.status === null ? 1 : result.status);
41
- }
42
-
43
- function resolveNativeDecisionBinary(root) {
44
- const expectedIntegrity = expectedNativeIntegrity(root);
39
+ function selectBinary(rootDir) {
40
+ const wantedHash = manifestNativeHash(rootDir);
45
41
  const candidates = [
46
42
  process.env.NAOME_NATIVE_BIN,
47
- join(root, nativeBinaryRelativePath),
48
- join(root, "packages", "naome", "target", "release", nativeBinaryName),
49
- join(root, "packages", "naome", "target", "debug", nativeBinaryName)
43
+ path.join(rootDir, nativeBinaryPath),
44
+ path.join(rootDir, "packages", "naome", "target", "release", nativeBinaryName),
45
+ path.join(rootDir, "packages", "naome", "target", "debug", nativeBinaryName)
50
46
  ].filter(Boolean);
51
47
 
52
48
  for (const candidate of candidates) {
53
- if (isUsableNativeBinary(candidate, root, expectedIntegrity)) {
49
+ if (canRun(candidate, rootDir, wantedHash)) {
54
50
  return candidate;
55
51
  }
56
52
  }
57
53
 
58
- if (expectedIntegrity) {
59
- fail(`No runnable NAOME native harness health binary matched ${expectedIntegrity}. Run naome sync again.`);
54
+ if (wantedHash) {
55
+ stop(`No runnable NAOME native harness health binary matched ${wantedHash}. Run naome sync again.`);
60
56
  }
61
57
 
62
- const built = buildSourceNativeBinary(root);
63
- if (built && isUsableNativeBinary(built, root, null)) {
58
+ const built = buildFromSource(rootDir);
59
+ if (built && canRun(built, rootDir, null)) {
64
60
  return built;
65
61
  }
66
62
 
67
- fail("NAOME native harness health binary is missing or incompatible. Run naome sync again.");
63
+ stop("NAOME native harness health binary is missing or incompatible. Run naome sync again.");
68
64
  }
69
65
 
70
- function expectedNativeIntegrity(root) {
71
- if (isIntegrityHash(process.env.NAOME_EXPECTED_NATIVE_INTEGRITY)) {
66
+ function manifestNativeHash(rootDir) {
67
+ if (isSha(process.env.NAOME_EXPECTED_NATIVE_INTEGRITY)) {
72
68
  return process.env.NAOME_EXPECTED_NATIVE_INTEGRITY;
73
69
  }
74
70
 
75
- const manifestPath = join(root, ".naome", "manifest.json");
76
- if (!existsSync(manifestPath)) {
77
- return null;
78
- }
79
-
80
- let manifest;
81
71
  try {
82
- manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
72
+ const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, ".naome", "manifest.json"), "utf8"));
73
+ return isSha(manifest.integrity?.[nativeBinaryPath]) ? manifest.integrity[nativeBinaryPath] : null;
83
74
  } catch {
84
75
  return null;
85
76
  }
86
-
87
- const expected = manifest.integrity?.[nativeBinaryRelativePath];
88
- if (!isIntegrityHash(expected)) {
89
- return null;
90
- }
91
-
92
- return expected;
93
77
  }
94
78
 
95
- function isUsableNativeBinary(candidate, root, expectedIntegrity) {
96
- if (!candidate || !existsSync(candidate)) {
79
+ function canRun(candidate, rootDir, wantedHash) {
80
+ if (!candidate || !fs.existsSync(candidate)) {
97
81
  return false;
98
82
  }
99
83
 
100
- if (expectedIntegrity) {
101
- const actual = `sha256:${createHash("sha256").update(readFileSync(candidate)).digest("hex")}`;
102
- if (actual !== expectedIntegrity) {
103
- return false;
104
- }
84
+ if (wantedHash && fileSha(candidate) !== wantedHash) {
85
+ return false;
105
86
  }
106
87
 
107
- const probe = spawnSync(candidate, [], {
108
- cwd: root,
88
+ const probe = childProcess.spawnSync(candidate, [], {
89
+ cwd: rootDir,
109
90
  encoding: "utf8",
110
91
  stdio: "ignore"
111
92
  });
112
93
  return !probe.error && probe.status !== null;
113
94
  }
114
95
 
115
- function buildSourceNativeBinary(root) {
116
- const manifestPath = join(root, "packages", "naome", "Cargo.toml");
117
- if (!existsSync(manifestPath)) {
96
+ function buildFromSource(rootDir) {
97
+ const manifest = path.join(rootDir, "packages", "naome", "Cargo.toml");
98
+ if (!fs.existsSync(manifest)) {
118
99
  return null;
119
100
  }
120
101
 
121
- const result = spawnSync("cargo", ["build", "--release", "--manifest-path", manifestPath, "-p", "naome-cli"], {
122
- cwd: root,
102
+ const build = childProcess.spawnSync("cargo", ["build", "--release", "--manifest-path", manifest, "-p", "naome-cli"], {
103
+ cwd: rootDir,
123
104
  encoding: "utf8",
124
105
  stdio: "ignore"
125
106
  });
126
- if (result.status !== 0) {
107
+ if (build.status !== 0) {
127
108
  return null;
128
109
  }
129
110
 
130
- const builtPath = join(root, "packages", "naome", "target", "release", nativeBinaryName);
131
- return existsSync(builtPath) ? builtPath : null;
111
+ const binaryPath = path.join(rootDir, "packages", "naome", "target", "release", nativeBinaryName);
112
+ return fs.existsSync(binaryPath) ? binaryPath : null;
132
113
  }
133
114
 
134
- function findHarnessRoot(startPath) {
135
- let current = resolve(startPath);
136
-
137
- while (true) {
138
- if (existsSync(join(current, ".naome", "manifest.json"))) {
115
+ function findRoot(startDir) {
116
+ for (let current = path.resolve(startDir); ;) {
117
+ if (fs.existsSync(path.join(current, ".naome", "manifest.json"))) {
139
118
  return current;
140
119
  }
141
120
 
142
- const parent = dirname(current);
121
+ const parent = path.dirname(current);
143
122
  if (parent === current) {
144
123
  return null;
145
124
  }
146
-
147
125
  current = parent;
148
126
  }
149
127
  }
150
128
 
151
- function isIntegrityHash(value) {
129
+ function isSha(value) {
152
130
  return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
153
131
  }
154
132
 
155
- function fail(message) {
133
+ function fileSha(filePath) {
134
+ const digest = crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
135
+ return `sha256:${digest}`;
136
+ }
137
+
138
+ function stop(message) {
156
139
  console.error("NAOME harness health check failed.");
157
140
  console.error(`- ${message}`);
158
141
  console.error("");
@@ -160,5 +143,3 @@ function fail(message) {
160
143
  console.error("Run naome sync to repair machine-owned files after review.");
161
144
  process.exit(1);
162
145
  }
163
-
164
- main();