@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
@@ -203,6 +203,138 @@ fn execute_route_commits_user_diff_after_quality_gate_passes() {
203
203
  assert!(route.user_message.contains("quality gates passed"));
204
204
  }
205
205
 
206
+ #[test]
207
+ fn execute_route_runs_repository_quality_check_without_shelling_to_repo_command() {
208
+ let repo = TestRepo::new("route-repository-quality-check");
209
+ repo.init_git();
210
+ repo.write_file(
211
+ ".naome/repository-quality.json",
212
+ r#"{
213
+ "schema": "naome.repository-quality.v1",
214
+ "version": 1,
215
+ "status": "ready",
216
+ "limits": {
217
+ "maxFileLines": 5,
218
+ "maxNewFileLines": 5,
219
+ "maxDiffAddedLines": 200,
220
+ "maxFunctionLines": 40,
221
+ "maxTopLevelSymbols": 30,
222
+ "duplicateBlockLines": 4,
223
+ "nearDuplicateSimilarity": 0.9
224
+ },
225
+ "disabledChecks": [],
226
+ "ignoredPaths": [],
227
+ "generatedPaths": []
228
+ }
229
+ "#,
230
+ );
231
+ repo.write_file(
232
+ "src/large.js",
233
+ "export function legacy() {\n one();\n two();\n three();\n four();\n}\n",
234
+ );
235
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
236
+ repo.write_naome_json(
237
+ "verification.json",
238
+ json!({
239
+ "schema": "naome.verification.v1",
240
+ "version": 1,
241
+ "status": "ready",
242
+ "checks": [
243
+ {
244
+ "id": "repository-quality-check",
245
+ "command": "naome quality check --changed",
246
+ "cwd": ".",
247
+ "purpose": "Validate changed files against repository quality rules.",
248
+ "cost": "fast",
249
+ "source": "NAOME built-in",
250
+ "evidence": ["src/**"],
251
+ "lastVerified": null
252
+ }
253
+ ],
254
+ "changeTypes": [
255
+ {
256
+ "id": "source",
257
+ "description": "Source changes.",
258
+ "paths": ["src/**"],
259
+ "requiredChecks": ["repository-quality-check"],
260
+ "recommendedChecks": [],
261
+ "humanReview": false
262
+ }
263
+ ],
264
+ "releaseGates": []
265
+ }),
266
+ );
267
+ repo.git(&["add", "."]);
268
+ repo.git(&["commit", "-m", "baseline"]);
269
+ repo.write_file(
270
+ "src/large.js",
271
+ "export function legacy() {\n one();\n two();\n three();\n four();\n five();\n}\n",
272
+ );
273
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
274
+
275
+ let route = evaluate_route(
276
+ repo.path(),
277
+ "commit my changes",
278
+ RouteOptions {
279
+ execute: true,
280
+ evaluation: EvaluationOptions::offline(),
281
+ },
282
+ )
283
+ .unwrap();
284
+
285
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
286
+ assert!(!route.allowed);
287
+ assert!(!route.mutation_performed);
288
+ assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
289
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
290
+ assert!(route.user_message.contains("repository-quality-check"));
291
+ assert!(route.user_message.contains("file-length"));
292
+ }
293
+
294
+ #[test]
295
+ fn execute_route_commits_product_diff_with_declared_safe_quality_checks() {
296
+ let repo = TestRepo::new("route-product-diff-quality-pass");
297
+ repo.init_git();
298
+ repo.write_file(
299
+ "packages/naome/crates/naome-core/src/route.rs",
300
+ "pub fn baseline() {}\n",
301
+ );
302
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
303
+ repo.write_product_quality_verification();
304
+ repo.git(&["add", "."]);
305
+ repo.git(&["commit", "-m", "baseline"]);
306
+ repo.write_file(
307
+ "packages/naome/crates/naome-core/src/route.rs",
308
+ "pub fn changed() {}\n",
309
+ );
310
+
311
+ let route = evaluate_route(
312
+ repo.path(),
313
+ "commit my changes",
314
+ RouteOptions {
315
+ execute: true,
316
+ evaluation: EvaluationOptions::offline(),
317
+ },
318
+ )
319
+ .unwrap();
320
+
321
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
322
+ assert!(route.allowed);
323
+ assert!(route.mutation_performed);
324
+ assert_eq!(
325
+ route.executed_actions,
326
+ vec![
327
+ "run_user_diff_quality_gate".to_string(),
328
+ "commit_user_diff".to_string()
329
+ ]
330
+ );
331
+ assert_eq!(repo.git_status_short(), "");
332
+
333
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
334
+ assert!(committed_paths.contains("packages/naome/crates/naome-core/src/route.rs"));
335
+ assert!(route.user_message.contains("quality gates passed"));
336
+ }
337
+
206
338
  #[test]
207
339
  fn execute_route_preserves_staged_rename_when_committing_user_diff() {
208
340
  let repo = TestRepo::new("route-user-diff-staged-rename");
@@ -334,11 +466,17 @@ fn execute_route_refuses_user_diff_commit_when_check_mutates_after_diff_check()
334
466
  assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
335
467
  assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
336
468
  assert!(repo.git_status_short().contains("README.md"));
337
- assert!(route.user_message.contains("trailing whitespace"));
469
+ assert_eq!(
470
+ fs::read_to_string(repo.path().join("README.md")).unwrap(),
471
+ "# Manual edit\n"
472
+ );
473
+ assert!(route
474
+ .user_message
475
+ .contains("will not execute repository-controlled verification commands"));
338
476
  }
339
477
 
340
478
  #[test]
341
- fn execute_route_refuses_user_diff_commit_when_diff_check_adds_paths() {
479
+ fn execute_route_refuses_user_diff_commit_when_diff_check_command_is_unsafe() {
342
480
  let repo = TestRepo::new("route-user-diff-mutating-diff-check");
343
481
  repo.init_git();
344
482
  repo.write_file("README.md", "# Baseline\n");
@@ -394,10 +532,10 @@ fn execute_route_refuses_user_diff_commit_when_diff_check_adds_paths() {
394
532
  assert!(!route.mutation_performed);
395
533
  assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
396
534
  assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
397
- assert!(repo.git_status_short().contains("NEW.md"));
535
+ assert!(!repo.path().join("NEW.md").exists());
398
536
  assert!(route
399
537
  .user_message
400
- .contains("Quality checks changed the diff path set"));
538
+ .contains("Quality check diff-check has an unsafe command or cwd"));
401
539
  }
402
540
 
403
541
  #[test]
@@ -1050,6 +1188,85 @@ impl TestRepo {
1050
1188
  );
1051
1189
  }
1052
1190
 
1191
+ fn write_product_quality_verification(&self) {
1192
+ self.write_naome_json(
1193
+ "verification.json",
1194
+ json!({
1195
+ "schema": "naome.verification.v1",
1196
+ "version": 1,
1197
+ "status": "ready",
1198
+ "checks": [
1199
+ {
1200
+ "id": "installer-tests",
1201
+ "command": "npm run test:naome-installer",
1202
+ "cwd": ".",
1203
+ "purpose": "Validate installer behavior.",
1204
+ "cost": "medium",
1205
+ "source": "test",
1206
+ "evidence": ["scripts/naome-installer.test.js"],
1207
+ "lastVerified": null
1208
+ },
1209
+ {
1210
+ "id": "rust-build",
1211
+ "command": "npm run build:rust",
1212
+ "cwd": ".",
1213
+ "purpose": "Build native CLI.",
1214
+ "cost": "medium",
1215
+ "source": "test",
1216
+ "evidence": ["packages/naome/Cargo.toml"],
1217
+ "lastVerified": null
1218
+ },
1219
+ {
1220
+ "id": "decision-engine-tests",
1221
+ "command": "npm run test:decision-engine",
1222
+ "cwd": ".",
1223
+ "purpose": "Validate route decisions.",
1224
+ "cost": "fast",
1225
+ "source": "test",
1226
+ "evidence": ["packages/naome/crates/naome-core/tests/route.rs"],
1227
+ "lastVerified": null
1228
+ },
1229
+ {
1230
+ "id": "package-dry-run",
1231
+ "command": "npm run pack:dry-run",
1232
+ "cwd": ".",
1233
+ "purpose": "Validate package metadata.",
1234
+ "cost": "medium",
1235
+ "source": "test",
1236
+ "evidence": ["packages/naome/package.json"],
1237
+ "lastVerified": null
1238
+ },
1239
+ {
1240
+ "id": "diff-check",
1241
+ "command": "git diff --check",
1242
+ "cwd": ".",
1243
+ "purpose": "Reject whitespace errors.",
1244
+ "cost": "fast",
1245
+ "source": "test",
1246
+ "evidence": ["packages/naome/crates/naome-core/src/route.rs"],
1247
+ "lastVerified": null
1248
+ }
1249
+ ],
1250
+ "changeTypes": [
1251
+ {
1252
+ "id": "product-installer-or-template",
1253
+ "description": "NAOME product changes.",
1254
+ "paths": ["packages/naome/**", "scripts/**"],
1255
+ "requiredChecks": [
1256
+ "installer-tests",
1257
+ "rust-build",
1258
+ "decision-engine-tests",
1259
+ "package-dry-run"
1260
+ ],
1261
+ "recommendedChecks": [],
1262
+ "humanReview": false
1263
+ }
1264
+ ],
1265
+ "releaseGates": []
1266
+ }),
1267
+ );
1268
+ }
1269
+
1053
1270
  fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
1054
1271
  let path = self.root.join(".naome").join(file_name);
1055
1272
  fs::write(
@@ -32,9 +32,12 @@ const PROJECT_OWNED_PATHS: &[&str] = &[
32
32
  ".naome/task-state.json",
33
33
  ".naome/upgrade-state.json",
34
34
  ".naome/verification.json",
35
+ ".naome/repository-quality.json",
36
+ ".naome/repository-quality-baseline.json",
35
37
  "docs/naome/architecture.md",
36
38
  "docs/naome/decisions.md",
37
39
  "docs/naome/repo-profile.md",
40
+ "docs/naome/repository-quality.md",
38
41
  "docs/naome/security.md",
39
42
  "docs/naome/testing.md",
40
43
  ];
@@ -0,0 +1,110 @@
1
+ mod task_state_compact_support;
2
+
3
+ use naome_core::{evaluate_route, EvaluationOptions, RouteOptions, TaskStateMode};
4
+ use serde_json::{json, Value};
5
+ use task_state_compact_support::{
6
+ compact_state, large_compact_state, large_expanded_state, legacy_state, MiniRepo,
7
+ };
8
+
9
+ #[test]
10
+ fn legacy_v1_proof_results_remain_valid() {
11
+ let repo = MiniRepo::new();
12
+ let head = repo.seed();
13
+ repo.change_readme();
14
+ repo.write_task_state(legacy_state(&head));
15
+
16
+ let report = repo.validate(TaskStateMode::State);
17
+
18
+ assert_eq!(report.errors, Vec::<String>::new());
19
+ }
20
+
21
+ #[test]
22
+ fn compact_path_sets_and_batches_cover_completion_commit_and_route() {
23
+ let repo = MiniRepo::new();
24
+ let head = repo.seed();
25
+ repo.change_readme();
26
+ repo.write_task_state(compact_state(&head));
27
+
28
+ let completion = repo.validate(TaskStateMode::State);
29
+ assert_eq!(completion.errors, Vec::<String>::new());
30
+
31
+ let route = evaluate_route(
32
+ repo.path(),
33
+ "new task",
34
+ RouteOptions {
35
+ execute: false,
36
+ evaluation: EvaluationOptions::offline(),
37
+ },
38
+ )
39
+ .unwrap();
40
+ assert_eq!(
41
+ route.policy_action,
42
+ "auto_commit_completed_task_then_create_new_task"
43
+ );
44
+
45
+ repo.git(["add", "README.md", ".naome/task-state.json"]);
46
+ let commit_gate = repo.validate(TaskStateMode::CommitGate);
47
+ assert_eq!(commit_gate.errors, Vec::<String>::new());
48
+ }
49
+
50
+ #[test]
51
+ fn compact_missing_required_proof_is_reported() {
52
+ let repo = MiniRepo::new();
53
+ let head = repo.seed();
54
+ repo.change_readme();
55
+ let mut state = compact_state(&head);
56
+ state["activeTask"]["proofBatches"][0]["proofs"] = json!([
57
+ {
58
+ "checkId": "alpha",
59
+ "exitCode": 0
60
+ }
61
+ ]);
62
+ repo.write_task_state(state);
63
+
64
+ let report = repo.validate(TaskStateMode::State);
65
+
66
+ assert!(
67
+ report
68
+ .errors
69
+ .iter()
70
+ .any(|error| error.contains("missing proof result: beta")),
71
+ "{:?}",
72
+ report.errors
73
+ );
74
+ }
75
+
76
+ #[test]
77
+ fn compact_missing_evidence_is_reported() {
78
+ let repo = MiniRepo::new();
79
+ let head = repo.seed();
80
+ repo.change_readme();
81
+ let mut state = compact_state(&head);
82
+ state["activeTask"]["proofBatches"][0]["evidencePathSet"] = Value::Null;
83
+ state["activeTask"]["proofBatches"][0]["proofs"][0]["evidencePathSet"] = Value::Null;
84
+ repo.write_task_state(state);
85
+
86
+ let report = repo.validate(TaskStateMode::State);
87
+
88
+ assert!(
89
+ report
90
+ .errors
91
+ .iter()
92
+ .any(|error| error.contains("evidence must be an evidence array or path-set reference")),
93
+ "{:?}",
94
+ report.errors
95
+ );
96
+ }
97
+
98
+ #[test]
99
+ fn compact_writer_payload_is_smaller_than_expanded_equivalent() {
100
+ let compact = large_compact_state("abcdef");
101
+ let expanded = large_expanded_state("abcdef");
102
+
103
+ let compact_len = serde_json::to_string(&compact).unwrap().len();
104
+ let expanded_len = serde_json::to_string(&expanded).unwrap().len();
105
+
106
+ assert!(
107
+ compact_len * 100 / expanded_len < 75,
108
+ "compact={compact_len} expanded={expanded_len}"
109
+ );
110
+ }
@@ -0,0 +1,5 @@
1
+ pub mod repo;
2
+ pub mod states;
3
+
4
+ pub use repo::MiniRepo;
5
+ pub use states::{compact_state, large_compact_state, large_expanded_state, legacy_state};
@@ -0,0 +1,130 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::process::Command;
4
+ use std::sync::atomic::{AtomicUsize, Ordering};
5
+ use std::time::{SystemTime, UNIX_EPOCH};
6
+
7
+ use naome_core::{validate_task_state, TaskStateMode, TaskStateOptions};
8
+ use serde_json::{json, Value};
9
+
10
+ static REPO_COUNTER: AtomicUsize = AtomicUsize::new(0);
11
+
12
+ pub struct MiniRepo {
13
+ root: PathBuf,
14
+ }
15
+
16
+ impl MiniRepo {
17
+ pub fn new() -> Self {
18
+ let stamp = SystemTime::now()
19
+ .duration_since(UNIX_EPOCH)
20
+ .unwrap()
21
+ .as_nanos();
22
+ let counter = REPO_COUNTER.fetch_add(1, Ordering::SeqCst);
23
+ let root = std::env::temp_dir().join(format!("naome-compact-proof-{stamp}-{counter}"));
24
+ fs::create_dir_all(root.join(".naome")).unwrap();
25
+ let repo = Self { root };
26
+ repo.git(["init"]);
27
+ repo.git(["config", "user.email", "compact@example.test"]);
28
+ repo.git(["config", "user.name", "Compact Proof"]);
29
+ repo
30
+ }
31
+
32
+ pub fn path(&self) -> &Path {
33
+ &self.root
34
+ }
35
+
36
+ pub fn seed(&self) -> String {
37
+ fs::write(self.root.join(".naomeignore"), "").unwrap();
38
+ fs::write(self.root.join("README.md"), "before\n").unwrap();
39
+ fs::write(
40
+ self.root.join(".naome/init-state.json"),
41
+ json_text(json!({
42
+ "schema": "naome.init-state.v1",
43
+ "version": 1,
44
+ "initialized": true,
45
+ "intakeStatus": "complete"
46
+ })),
47
+ )
48
+ .unwrap();
49
+ fs::write(
50
+ self.root.join(".naome/upgrade-state.json"),
51
+ json_text(json!({
52
+ "schema": "naome.upgrade-state.v1",
53
+ "version": 1,
54
+ "status": "complete",
55
+ "fromVersion": "1.1.2",
56
+ "toVersion": "1.2.0",
57
+ "pending": [],
58
+ "completed": []
59
+ })),
60
+ )
61
+ .unwrap();
62
+ fs::write(
63
+ self.root.join(".naome/verification.json"),
64
+ json_text(verification_contract()),
65
+ )
66
+ .unwrap();
67
+ self.git(["add", "."]);
68
+ self.git(["commit", "-m", "seed"]);
69
+ self.git_stdout(["rev-parse", "HEAD"])
70
+ }
71
+
72
+ pub fn change_readme(&self) {
73
+ fs::write(self.root.join("README.md"), "after\n").unwrap();
74
+ }
75
+
76
+ pub fn write_task_state(&self, state: Value) {
77
+ fs::write(self.root.join(".naome/task-state.json"), json_text(state)).unwrap();
78
+ }
79
+
80
+ pub fn validate(&self, mode: TaskStateMode) -> naome_core::TaskStateReport {
81
+ validate_task_state(
82
+ &self.root,
83
+ TaskStateOptions {
84
+ mode,
85
+ harness_health: None,
86
+ },
87
+ )
88
+ .unwrap()
89
+ }
90
+
91
+ pub fn git<const N: usize>(&self, args: [&str; N]) {
92
+ let output = Command::new("git")
93
+ .args(args)
94
+ .current_dir(&self.root)
95
+ .output()
96
+ .unwrap();
97
+ assert!(
98
+ output.status.success(),
99
+ "{}",
100
+ String::from_utf8_lossy(&output.stderr)
101
+ );
102
+ }
103
+
104
+ fn git_stdout<const N: usize>(&self, args: [&str; N]) -> String {
105
+ let output = Command::new("git")
106
+ .args(args)
107
+ .current_dir(&self.root)
108
+ .output()
109
+ .unwrap();
110
+ assert!(output.status.success());
111
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
112
+ }
113
+ }
114
+
115
+ fn verification_contract() -> Value {
116
+ json!({
117
+ "schema": "naome.verification.v1",
118
+ "version": 1,
119
+ "status": "ready",
120
+ "checks": [
121
+ { "id": "alpha", "command": "npm run alpha", "cwd": ".", "evidence": ["README.md"] },
122
+ { "id": "beta", "command": "npm run beta", "cwd": ".", "evidence": ["README.md"] }
123
+ ],
124
+ "changeTypes": []
125
+ })
126
+ }
127
+
128
+ fn json_text(value: Value) -> String {
129
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap())
130
+ }
@@ -0,0 +1,151 @@
1
+ use serde_json::{json, Value};
2
+
3
+ const T0: &str = "2026-05-07T00:00:00.000Z";
4
+
5
+ pub fn compact_state(admission_head: &str) -> Value {
6
+ base_state(
7
+ admission_head,
8
+ json!({
9
+ "proofPathSets": {
10
+ "changed-readme": ["README.md"]
11
+ },
12
+ "proofBatches": [
13
+ {
14
+ "checkedAt": T0,
15
+ "evidencePathSet": "changed-readme",
16
+ "proofs": [
17
+ {
18
+ "checkId": "alpha",
19
+ "exitCode": 0
20
+ },
21
+ {
22
+ "checkId": "beta",
23
+ "command": "npm run beta -- --changed README.md",
24
+ "cwd": "tools",
25
+ "exitCode": 0
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ }),
31
+ )
32
+ }
33
+
34
+ pub fn legacy_state(admission_head: &str) -> Value {
35
+ let mut state = base_state(
36
+ admission_head,
37
+ json!({
38
+ "proofResults": [
39
+ proof("alpha", "npm run alpha", "."),
40
+ proof("beta", "npm run beta", ".")
41
+ ]
42
+ }),
43
+ );
44
+ state["schema"] = json!("naome.task-state.v1");
45
+ state["version"] = json!(1);
46
+ state
47
+ }
48
+
49
+ pub fn large_compact_state(admission_head: &str) -> Value {
50
+ let evidence: Vec<Value> = (0..12)
51
+ .map(|index| json!(format!("src/module_{index}.rs")))
52
+ .collect();
53
+ let proofs: Vec<Value> = (0..8)
54
+ .map(|index| {
55
+ json!({
56
+ "checkId": format!("check-{index}"),
57
+ "exitCode": 0
58
+ })
59
+ })
60
+ .collect();
61
+ base_state(
62
+ admission_head,
63
+ json!({
64
+ "proofPathSets": {
65
+ "large-change": evidence
66
+ },
67
+ "proofBatches": [
68
+ {
69
+ "checkedAt": T0,
70
+ "command": "cargo test",
71
+ "cwd": ".",
72
+ "evidencePathSet": "large-change",
73
+ "proofs": proofs
74
+ }
75
+ ]
76
+ }),
77
+ )
78
+ }
79
+
80
+ pub fn large_expanded_state(admission_head: &str) -> Value {
81
+ let evidence: Vec<Value> = (0..12)
82
+ .map(|index| json!(format!("src/module_{index}.rs")))
83
+ .collect();
84
+ let proofs: Vec<Value> = (0..8)
85
+ .map(|index| {
86
+ json!({
87
+ "checkId": format!("check-{index}"),
88
+ "command": "cargo test",
89
+ "cwd": ".",
90
+ "exitCode": 0,
91
+ "checkedAt": T0,
92
+ "evidence": evidence
93
+ })
94
+ })
95
+ .collect();
96
+ base_state(
97
+ admission_head,
98
+ json!({
99
+ "proofResults": proofs
100
+ }),
101
+ )
102
+ }
103
+
104
+ fn base_state(admission_head: &str, proof_payload: Value) -> Value {
105
+ let mut task = json!({
106
+ "id": "compact-proof",
107
+ "request": "Update README.",
108
+ "userPrompt": {
109
+ "receivedAt": T0,
110
+ "text": "Update README."
111
+ },
112
+ "admission": {
113
+ "command": "node .naome/bin/check-task-state.js --admission",
114
+ "cwd": ".",
115
+ "exitCode": 0,
116
+ "checkedAt": T0,
117
+ "gitHead": admission_head,
118
+ "changedPaths": []
119
+ },
120
+ "allowedPaths": ["README.md"],
121
+ "declaredChangeTypes": ["docs"],
122
+ "requiredCheckIds": ["alpha", "beta"],
123
+ "humanReview": {
124
+ "required": false,
125
+ "approved": false,
126
+ "reason": null
127
+ }
128
+ });
129
+ task.as_object_mut()
130
+ .unwrap()
131
+ .extend(proof_payload.as_object().unwrap().clone());
132
+ json!({
133
+ "schema": "naome.task-state.v2",
134
+ "version": 2,
135
+ "status": "complete",
136
+ "activeTask": task,
137
+ "blocker": null,
138
+ "updatedAt": T0
139
+ })
140
+ }
141
+
142
+ fn proof(check_id: &str, command: &str, cwd: &str) -> Value {
143
+ json!({
144
+ "checkId": check_id,
145
+ "command": command,
146
+ "cwd": cwd,
147
+ "exitCode": 0,
148
+ "checkedAt": T0,
149
+ "evidence": ["README.md"]
150
+ })
151
+ }