@lamentis/naome 1.1.2 → 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 (125) 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.js +54 -16
  6. package/crates/naome-cli/Cargo.toml +1 -1
  7. package/crates/naome-cli/src/check_commands.rs +135 -0
  8. package/crates/naome-cli/src/cli_args.rs +5 -0
  9. package/crates/naome-cli/src/dispatcher.rs +36 -0
  10. package/crates/naome-cli/src/install_bridge.rs +83 -0
  11. package/crates/naome-cli/src/main.rs +57 -341
  12. package/crates/naome-cli/src/prompt_commands.rs +68 -0
  13. package/crates/naome-cli/src/quality_commands.rs +141 -0
  14. package/crates/naome-cli/src/simple_commands.rs +53 -0
  15. package/crates/naome-cli/src/workflow_commands.rs +153 -0
  16. package/crates/naome-core/Cargo.toml +1 -1
  17. package/crates/naome-core/src/harness_health/integrity.rs +96 -0
  18. package/crates/naome-core/src/harness_health.rs +14 -126
  19. package/crates/naome-core/src/install_plan.rs +3 -0
  20. package/crates/naome-core/src/intent/classifier.rs +171 -0
  21. package/crates/naome-core/src/intent/envelope.rs +108 -0
  22. package/crates/naome-core/src/intent/legacy.rs +138 -0
  23. package/crates/naome-core/src/intent/legacy_response.rs +76 -0
  24. package/crates/naome-core/src/intent/model.rs +71 -0
  25. package/crates/naome-core/src/intent/patterns.rs +170 -0
  26. package/crates/naome-core/src/intent/resolver.rs +162 -0
  27. package/crates/naome-core/src/intent/resolver_active.rs +17 -0
  28. package/crates/naome-core/src/intent/resolver_baseline.rs +55 -0
  29. package/crates/naome-core/src/intent/resolver_catalog.rs +167 -0
  30. package/crates/naome-core/src/intent/resolver_policy.rs +72 -0
  31. package/crates/naome-core/src/intent/resolver_shared.rs +55 -0
  32. package/crates/naome-core/src/intent/risk.rs +40 -0
  33. package/crates/naome-core/src/intent/segment.rs +170 -0
  34. package/crates/naome-core/src/intent.rs +64 -879
  35. package/crates/naome-core/src/journal.rs +9 -20
  36. package/crates/naome-core/src/lib.rs +13 -0
  37. package/crates/naome-core/src/quality/adapters.rs +178 -0
  38. package/crates/naome-core/src/quality/baseline.rs +75 -0
  39. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +175 -0
  40. package/crates/naome-core/src/quality/checks/near_duplicates.rs +130 -0
  41. package/crates/naome-core/src/quality/checks.rs +228 -0
  42. package/crates/naome-core/src/quality/cleanup.rs +72 -0
  43. package/crates/naome-core/src/quality/config.rs +109 -0
  44. package/crates/naome-core/src/quality/mod.rs +90 -0
  45. package/crates/naome-core/src/quality/scanner/repo_paths.rs +103 -0
  46. package/crates/naome-core/src/quality/scanner.rs +367 -0
  47. package/crates/naome-core/src/quality/types.rs +289 -0
  48. package/crates/naome-core/src/route.rs +62 -0
  49. package/crates/naome-core/src/task_state/admission.rs +63 -0
  50. package/crates/naome-core/src/task_state/admission_proof.rs +72 -0
  51. package/crates/naome-core/src/task_state/api.rs +130 -0
  52. package/crates/naome-core/src/task_state/commit_gate.rs +138 -0
  53. package/crates/naome-core/src/task_state/compact_proof.rs +160 -0
  54. package/crates/naome-core/src/task_state/completed_refresh.rs +89 -0
  55. package/crates/naome-core/src/task_state/completion.rs +72 -0
  56. package/crates/naome-core/src/task_state/deleted_paths.rs +47 -0
  57. package/crates/naome-core/src/task_state/diff.rs +95 -0
  58. package/crates/naome-core/src/task_state/evidence.rs +154 -0
  59. package/crates/naome-core/src/task_state/git_io.rs +86 -0
  60. package/crates/naome-core/src/task_state/git_parse.rs +86 -0
  61. package/crates/naome-core/src/task_state/git_refs.rs +37 -0
  62. package/crates/naome-core/src/task_state/human_review_state.rs +31 -0
  63. package/crates/naome-core/src/task_state/mod.rs +38 -0
  64. package/crates/naome-core/src/task_state/process_guard.rs +40 -0
  65. package/crates/naome-core/src/task_state/progress.rs +123 -0
  66. package/crates/naome-core/src/task_state/proof.rs +139 -0
  67. package/crates/naome-core/src/task_state/proof_entry.rs +66 -0
  68. package/crates/naome-core/src/task_state/proof_model.rs +70 -0
  69. package/crates/naome-core/src/task_state/proof_sources.rs +76 -0
  70. package/crates/naome-core/src/task_state/push_gate.rs +49 -0
  71. package/crates/naome-core/src/task_state/reconcile.rs +7 -0
  72. package/crates/naome-core/src/task_state/repair.rs +168 -0
  73. package/crates/naome-core/src/task_state/shape.rs +117 -0
  74. package/crates/naome-core/src/task_state/task_diff_api.rs +170 -0
  75. package/crates/naome-core/src/task_state/task_records.rs +131 -0
  76. package/crates/naome-core/src/task_state/task_references.rs +126 -0
  77. package/crates/naome-core/src/task_state/types.rs +87 -0
  78. package/crates/naome-core/src/task_state/util.rs +137 -0
  79. package/crates/naome-core/src/verification/render.rs +122 -0
  80. package/crates/naome-core/src/verification.rs +176 -58
  81. package/crates/naome-core/src/verification_contract.rs +49 -21
  82. package/crates/naome-core/src/workflow/integrity.rs +123 -0
  83. package/crates/naome-core/src/workflow/integrity_normalize.rs +7 -0
  84. package/crates/naome-core/src/workflow/integrity_support.rs +110 -0
  85. package/crates/naome-core/src/workflow/mod.rs +18 -0
  86. package/crates/naome-core/src/workflow/mutation.rs +68 -0
  87. package/crates/naome-core/src/workflow/output.rs +111 -0
  88. package/crates/naome-core/src/workflow/phase_inference.rs +73 -0
  89. package/crates/naome-core/src/workflow/phases.rs +169 -0
  90. package/crates/naome-core/src/workflow/policy.rs +156 -0
  91. package/crates/naome-core/src/workflow/processes.rs +91 -0
  92. package/crates/naome-core/src/workflow/types.rs +42 -0
  93. package/crates/naome-core/tests/harness_health.rs +3 -0
  94. package/crates/naome-core/tests/intent.rs +97 -792
  95. package/crates/naome-core/tests/intent_support/mod.rs +133 -0
  96. package/crates/naome-core/tests/intent_v2.rs +90 -0
  97. package/crates/naome-core/tests/quality.rs +425 -0
  98. package/crates/naome-core/tests/route.rs +88 -188
  99. package/crates/naome-core/tests/task_state.rs +3 -0
  100. package/crates/naome-core/tests/task_state_compact.rs +110 -0
  101. package/crates/naome-core/tests/task_state_compact_support/mod.rs +5 -0
  102. package/crates/naome-core/tests/task_state_compact_support/repo.rs +130 -0
  103. package/crates/naome-core/tests/task_state_compact_support/states.rs +151 -0
  104. package/crates/naome-core/tests/workflow_integrity.rs +85 -0
  105. package/crates/naome-core/tests/workflow_policy.rs +139 -0
  106. package/crates/naome-core/tests/workflow_support/mod.rs +194 -0
  107. package/native/darwin-arm64/naome +0 -0
  108. package/native/linux-x64/naome +0 -0
  109. package/package.json +2 -2
  110. package/templates/naome-root/.naome/bin/check-harness-health.js +66 -85
  111. package/templates/naome-root/.naome/bin/check-task-state.js +9 -10
  112. package/templates/naome-root/.naome/bin/naome.js +34 -63
  113. package/templates/naome-root/.naome/manifest.json +20 -18
  114. package/templates/naome-root/.naome/repository-quality-baseline.json +5 -0
  115. package/templates/naome-root/.naome/repository-quality.json +24 -0
  116. package/templates/naome-root/.naome/task-contract.schema.json +93 -11
  117. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  118. package/templates/naome-root/.naome/verification.json +37 -0
  119. package/templates/naome-root/AGENTS.md +3 -0
  120. package/templates/naome-root/docs/naome/agent-workflow.md +25 -12
  121. package/templates/naome-root/docs/naome/execution.md +25 -21
  122. package/templates/naome-root/docs/naome/index.md +4 -3
  123. package/templates/naome-root/docs/naome/repository-quality.md +43 -0
  124. package/templates/naome-root/docs/naome/testing.md +12 -0
  125. package/crates/naome-core/src/task_state.rs +0 -2210
@@ -203,6 +203,94 @@ 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
+
206
294
  #[test]
207
295
  fn execute_route_commits_product_diff_with_declared_safe_quality_checks() {
208
296
  let repo = TestRepo::new("route-product-diff-quality-pass");
@@ -482,79 +570,6 @@ fn execute_route_refuses_user_diff_commit_when_quality_gate_fails() {
482
570
  assert!(route.user_message.contains("quality gate failed"));
483
571
  }
484
572
 
485
- #[test]
486
- fn execute_route_refuses_user_diff_commit_when_dogfood_health_finds_integrity_mismatch() {
487
- let repo = TestRepo::from_template("route-user-diff-dogfood-health-integrity");
488
- repo.init_git();
489
- repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
490
- repo.write_dogfood_readme_quality_verification();
491
- repo.write_file(
492
- ".naome/bin/check-task-state.js",
493
- "#!/usr/bin/env node\nconsole.log('already tampered');\n",
494
- );
495
- repo.git(&["add", "."]);
496
- repo.git(&["commit", "-m", "baseline"]);
497
- repo.write_file("README.md", "# Local edit\n");
498
- let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
499
-
500
- let route = evaluate_route(
501
- repo.path(),
502
- "commit my changes",
503
- RouteOptions {
504
- execute: true,
505
- evaluation: EvaluationOptions::offline(),
506
- },
507
- )
508
- .unwrap();
509
-
510
- assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
511
- assert!(!route.allowed);
512
- assert!(!route.mutation_performed);
513
- assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
514
- assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
515
- assert!(repo.git_status_short().contains("README.md"));
516
- assert!(route.user_message.contains("quality gate failed"));
517
- assert!(route.user_message.contains("integrity mismatch"));
518
- }
519
-
520
- #[test]
521
- fn execute_route_refuses_user_diff_commit_when_task_state_check_finds_template_integrity_mismatch()
522
- {
523
- let repo = TestRepo::new("route-user-diff-task-state-integrity");
524
- repo.init_git();
525
- repo.write_file("README.md", "# Baseline\n");
526
- repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
527
- repo.write_task_state_readme_quality_verification();
528
- repo.copy_packaged_template_to_route_template_root();
529
- repo.write_file(
530
- "packages/naome/templates/naome-root/.naome/bin/check-task-state.js",
531
- "#!/usr/bin/env node\nconsole.log('already tampered');\n",
532
- );
533
- repo.git(&["add", "."]);
534
- repo.git(&["commit", "-m", "baseline"]);
535
- repo.write_file("README.md", "# Local edit\n");
536
- let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
537
-
538
- let route = evaluate_route(
539
- repo.path(),
540
- "commit my changes",
541
- RouteOptions {
542
- execute: true,
543
- evaluation: EvaluationOptions::offline(),
544
- },
545
- )
546
- .unwrap();
547
-
548
- assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
549
- assert!(!route.allowed);
550
- assert!(!route.mutation_performed);
551
- assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
552
- assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
553
- assert!(repo.git_status_short().contains("README.md"));
554
- assert!(route.user_message.contains("quality gate failed"));
555
- assert!(route.user_message.contains("integrity mismatch"));
556
- }
557
-
558
573
  #[test]
559
574
  fn execute_route_baselines_harness_refresh_before_dirty_repo_worktree() {
560
575
  let repo = TestRepo::new("route-dirty-harness-refresh-worktree");
@@ -1049,27 +1064,6 @@ impl TestRepo {
1049
1064
  Self { root }
1050
1065
  }
1051
1066
 
1052
- fn from_template(name: &str) -> Self {
1053
- let repo = Self::new(name);
1054
- let template_root = packaged_template_root();
1055
- copy_dir(&template_root, repo.path());
1056
- fs::create_dir_all(repo.path().join(".naome/archive")).unwrap();
1057
- repo
1058
- }
1059
-
1060
- fn copy_packaged_template_to_route_template_root(&self) {
1061
- copy_dir(&packaged_template_root(), &self.route_template_root());
1062
- fs::create_dir_all(self.route_template_root().join(".naome/archive")).unwrap();
1063
- }
1064
-
1065
- fn route_template_root(&self) -> PathBuf {
1066
- self.root
1067
- .join("packages")
1068
- .join("naome")
1069
- .join("templates")
1070
- .join("naome-root")
1071
- }
1072
-
1073
1067
  fn completed_task_with_diff(name: &str) -> Self {
1074
1068
  let repo = Self::new(name);
1075
1069
  repo.init_git();
@@ -1273,74 +1267,6 @@ impl TestRepo {
1273
1267
  );
1274
1268
  }
1275
1269
 
1276
- fn write_dogfood_readme_quality_verification(&self) {
1277
- self.write_naome_json(
1278
- "verification.json",
1279
- json!({
1280
- "schema": "naome.verification.v1",
1281
- "version": 1,
1282
- "status": "ready",
1283
- "checks": [
1284
- {
1285
- "id": "dogfood-health",
1286
- "command": "npm run dogfood:health",
1287
- "cwd": ".",
1288
- "purpose": "Validate installed harness integrity.",
1289
- "cost": "fast",
1290
- "source": "test",
1291
- "evidence": [".naome/bin/check-task-state.js"],
1292
- "lastVerified": null
1293
- }
1294
- ],
1295
- "changeTypes": [
1296
- {
1297
- "id": "installed-self-hosted-harness",
1298
- "description": "Installed harness files.",
1299
- "paths": ["README.md"],
1300
- "requiredChecks": ["dogfood-health"],
1301
- "recommendedChecks": [],
1302
- "humanReview": false
1303
- }
1304
- ],
1305
- "releaseGates": []
1306
- }),
1307
- );
1308
- }
1309
-
1310
- fn write_task_state_readme_quality_verification(&self) {
1311
- self.write_naome_json(
1312
- "verification.json",
1313
- json!({
1314
- "schema": "naome.verification.v1",
1315
- "version": 1,
1316
- "status": "ready",
1317
- "checks": [
1318
- {
1319
- "id": "task-state-check",
1320
- "command": "npm run check:task-state",
1321
- "cwd": ".",
1322
- "purpose": "Validate template task-state and harness integrity.",
1323
- "cost": "fast",
1324
- "source": "test",
1325
- "evidence": ["packages/naome/templates/naome-root/.naome/bin/check-task-state.js"],
1326
- "lastVerified": null
1327
- }
1328
- ],
1329
- "changeTypes": [
1330
- {
1331
- "id": "installed-self-hosted-harness",
1332
- "description": "Installed harness files.",
1333
- "paths": ["README.md"],
1334
- "requiredChecks": ["task-state-check"],
1335
- "recommendedChecks": [],
1336
- "humanReview": false
1337
- }
1338
- ],
1339
- "releaseGates": []
1340
- }),
1341
- );
1342
- }
1343
-
1344
1270
  fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
1345
1271
  let path = self.root.join(".naome").join(file_name);
1346
1272
  fs::write(
@@ -1404,32 +1330,6 @@ impl TestRepo {
1404
1330
  }
1405
1331
  }
1406
1332
 
1407
- fn packaged_template_root() -> PathBuf {
1408
- PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1409
- .join("..")
1410
- .join("..")
1411
- .join("templates")
1412
- .join("naome-root")
1413
- }
1414
-
1415
- fn copy_dir(from: &Path, to: &Path) {
1416
- fs::create_dir_all(to).unwrap();
1417
- for entry in fs::read_dir(from).unwrap() {
1418
- let entry = entry.unwrap();
1419
- let from_path = entry.path();
1420
- let to_path = to.join(entry.file_name());
1421
- let file_type = entry.file_type().unwrap();
1422
- if file_type.is_dir() {
1423
- copy_dir(&from_path, &to_path);
1424
- } else if file_type.is_file() {
1425
- if let Some(parent) = to_path.parent() {
1426
- fs::create_dir_all(parent).unwrap();
1427
- }
1428
- fs::copy(&from_path, &to_path).unwrap();
1429
- }
1430
- }
1431
- }
1432
-
1433
1333
  fn completed_task_state(admission_head: &str) -> serde_json::Value {
1434
1334
  json!({
1435
1335
  "schema": "naome.task-state.v1",
@@ -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
+ }