@lamentis/naome 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +8 -1
  3. package/bin/naome-node.js +4 -1
  4. package/bin/naome.js +198 -3
  5. package/crates/naome-cli/Cargo.toml +1 -1
  6. package/crates/naome-cli/src/main.rs +110 -13
  7. package/crates/naome-core/Cargo.toml +1 -1
  8. package/crates/naome-core/src/decision.rs +82 -11
  9. package/crates/naome-core/src/git.rs +12 -1
  10. package/crates/naome-core/src/harness_health.rs +3 -1
  11. package/crates/naome-core/src/install_plan.rs +4 -2
  12. package/crates/naome-core/src/intent.rs +914 -0
  13. package/crates/naome-core/src/journal.rs +169 -0
  14. package/crates/naome-core/src/lib.rs +10 -1
  15. package/crates/naome-core/src/models.rs +63 -4
  16. package/crates/naome-core/src/route.rs +1063 -0
  17. package/crates/naome-core/src/task_state.rs +372 -21
  18. package/crates/naome-core/tests/decision.rs +8 -6
  19. package/crates/naome-core/tests/install_plan.rs +9 -1
  20. package/crates/naome-core/tests/intent.rs +826 -0
  21. package/crates/naome-core/tests/route.rs +1159 -0
  22. package/crates/naome-core/tests/task_state.rs +203 -4
  23. package/native/darwin-arm64/naome +0 -0
  24. package/native/linux-x64/naome +0 -0
  25. package/package.json +1 -1
  26. package/templates/naome-root/.naome/bin/check-harness-health.js +7 -6
  27. package/templates/naome-root/.naome/bin/check-task-state.js +7 -6
  28. package/templates/naome-root/.naome/bin/naome.js +143 -13
  29. package/templates/naome-root/.naome/manifest.json +8 -7
  30. package/templates/naome-root/.naome/upgrade-state.json +1 -1
  31. package/templates/naome-root/AGENTS.md +30 -5
  32. package/templates/naome-root/docs/naome/agent-workflow.md +45 -24
  33. package/templates/naome-root/docs/naome/execution.md +55 -51
  34. package/templates/naome-root/docs/naome/index.md +10 -3
@@ -0,0 +1,1159 @@
1
+ use std::fs;
2
+ use std::path::{Path, PathBuf};
3
+ use std::process::Command;
4
+ use std::time::{SystemTime, UNIX_EPOCH};
5
+
6
+ use naome_core::{evaluate_route, explain_route, EvaluationOptions, RouteOptions};
7
+ use serde_json::{json, Value};
8
+
9
+ #[test]
10
+ fn dry_route_reports_auto_baseline_without_mutating() {
11
+ let repo = TestRepo::completed_task_with_diff("route-dry-auto");
12
+ let before = repo.git_stdout(&["rev-parse", "HEAD"]);
13
+
14
+ let route = evaluate_route(
15
+ repo.path(),
16
+ "Start a new task for README polish.",
17
+ RouteOptions {
18
+ execute: false,
19
+ evaluation: EvaluationOptions::offline(),
20
+ },
21
+ )
22
+ .unwrap();
23
+
24
+ assert_eq!(
25
+ route.policy_action,
26
+ "auto_commit_completed_task_then_create_new_task"
27
+ );
28
+ assert!(!route.mutation_performed);
29
+ assert!(!route.can_create_task);
30
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
31
+ assert!(!route.user_message.contains("commit_task_baseline"));
32
+ assert!(route.human_options.is_empty());
33
+ }
34
+
35
+ #[test]
36
+ fn execute_route_auto_baselines_then_admits_next_task_and_writes_local_journal() {
37
+ let repo = TestRepo::completed_task_with_diff("route-execute-auto");
38
+
39
+ let route = evaluate_route(
40
+ repo.path(),
41
+ "Start a new task for README polish.",
42
+ RouteOptions {
43
+ execute: true,
44
+ evaluation: EvaluationOptions::offline(),
45
+ },
46
+ )
47
+ .unwrap();
48
+
49
+ assert_eq!(
50
+ route.policy_action,
51
+ "auto_commit_completed_task_then_create_new_task"
52
+ );
53
+ assert!(route.mutation_performed);
54
+ assert!(route.can_create_task);
55
+ assert_eq!(route.next_decision.state, "ready_for_task");
56
+ assert!(route
57
+ .executed_actions
58
+ .contains(&"commit_task_baseline".to_string()));
59
+ assert!(repo.git_status_short().is_empty());
60
+
61
+ let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
62
+ assert!(journal.contains("\"taskId\":\"readme-task\""));
63
+ assert!(journal.contains("\"outcome\":\"route_auto_baseline\""));
64
+ }
65
+
66
+ #[test]
67
+ fn execute_route_baselines_completed_task_and_creates_worktree_for_unrelated_user_edit() {
68
+ let repo = TestRepo::completed_task_with_unrelated_user_edit("route-preserve-user-edit");
69
+
70
+ let route = evaluate_route(
71
+ repo.path(),
72
+ "Add another line to README as a new task.",
73
+ RouteOptions {
74
+ execute: true,
75
+ evaluation: EvaluationOptions::offline(),
76
+ },
77
+ )
78
+ .unwrap();
79
+
80
+ assert_eq!(
81
+ route.policy_action,
82
+ "auto_commit_completed_task_then_create_isolated_task_worktree"
83
+ );
84
+ assert!(route.mutation_performed);
85
+ assert!(route.can_create_task);
86
+ assert_eq!(route.next_decision.state, "ready_for_task");
87
+ assert!(route.user_message.contains("isolated task worktree"));
88
+ assert!(route.human_options.is_empty());
89
+ assert_ne!(route.task_root, repo.path().to_string_lossy());
90
+ assert!(route.worktree.is_some());
91
+ assert_eq!(repo.git_status_short(), "M USER.md");
92
+ assert_eq!(
93
+ TestRepo::git_status_short_at(Path::new(&route.task_root)),
94
+ ""
95
+ );
96
+
97
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
98
+ assert!(committed_paths.contains("README.md"));
99
+ assert!(!committed_paths.contains("USER.md"));
100
+ }
101
+
102
+ #[test]
103
+ fn execute_route_creates_worktree_for_dirty_repo_new_task() {
104
+ let repo = TestRepo::new("route-dirty-worktree");
105
+ repo.init_git();
106
+ repo.write_file("README.md", "# Baseline\n");
107
+ repo.write_file("USER.md", "user baseline\n");
108
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
109
+ repo.git(&["add", "."]);
110
+ repo.git(&["commit", "-m", "baseline"]);
111
+ repo.write_file("USER.md", "user local edit\n");
112
+
113
+ let route = evaluate_route(
114
+ repo.path(),
115
+ "Add another line to README as a new task.",
116
+ RouteOptions {
117
+ execute: true,
118
+ evaluation: EvaluationOptions::offline(),
119
+ },
120
+ )
121
+ .unwrap();
122
+
123
+ assert_eq!(route.policy_action, "create_isolated_task_worktree");
124
+ assert!(route.mutation_performed);
125
+ assert!(route.can_create_task);
126
+ assert_eq!(route.next_decision.state, "ready_for_task");
127
+ assert!(route.worktree.is_some());
128
+ assert_eq!(repo.git_status_short(), "M USER.md");
129
+ assert_eq!(
130
+ TestRepo::git_status_short_at(Path::new(&route.task_root)),
131
+ ""
132
+ );
133
+ }
134
+
135
+ #[test]
136
+ fn execute_route_does_not_mutate_or_offer_clear_commit_for_dirty_unowned_diff() {
137
+ let repo = TestRepo::new("route-dirty-commit-request");
138
+ repo.init_git();
139
+ repo.write_file("README.md", "# Baseline\n");
140
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
141
+ repo.git(&["add", "."]);
142
+ repo.git(&["commit", "-m", "baseline"]);
143
+ repo.write_file("README.md", "# Manual edit\n");
144
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
145
+ let before_status = repo.git_status_short();
146
+
147
+ let route = evaluate_route(
148
+ repo.path(),
149
+ "clear_or_commit_unowned_diff",
150
+ RouteOptions {
151
+ execute: true,
152
+ evaluation: EvaluationOptions::offline(),
153
+ },
154
+ )
155
+ .unwrap();
156
+
157
+ assert_eq!(route.policy_action, "block_unowned_diff");
158
+ assert!(!route.mutation_performed);
159
+ assert!(!route.can_create_task);
160
+ assert_eq!(route.executed_actions, Vec::<String>::new());
161
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
162
+ assert_eq!(repo.git_status_short(), before_status);
163
+ assert!(!route
164
+ .human_options
165
+ .contains(&"clear_or_commit_unowned_diff".to_string()));
166
+ }
167
+
168
+ #[test]
169
+ fn execute_route_commits_user_diff_after_quality_gate_passes() {
170
+ let repo = TestRepo::new("route-user-diff-quality-pass");
171
+ repo.init_git();
172
+ repo.write_file("README.md", "# Baseline\n");
173
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
174
+ repo.write_readme_quality_verification(Vec::new(), vec!["diff-check"]);
175
+ repo.git(&["add", "."]);
176
+ repo.git(&["commit", "-m", "baseline"]);
177
+ repo.write_file("README.md", "# Manual edit\n");
178
+
179
+ let route = evaluate_route(
180
+ repo.path(),
181
+ "commit my changes",
182
+ RouteOptions {
183
+ execute: true,
184
+ evaluation: EvaluationOptions::offline(),
185
+ },
186
+ )
187
+ .unwrap();
188
+
189
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
190
+ assert!(route.allowed);
191
+ assert!(route.mutation_performed);
192
+ assert_eq!(
193
+ route.executed_actions,
194
+ vec![
195
+ "run_user_diff_quality_gate".to_string(),
196
+ "commit_user_diff".to_string()
197
+ ]
198
+ );
199
+ assert_eq!(repo.git_status_short(), "");
200
+
201
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
202
+ assert!(committed_paths.contains("README.md"));
203
+ assert!(route.user_message.contains("quality gates passed"));
204
+ }
205
+
206
+ #[test]
207
+ fn execute_route_preserves_staged_rename_when_committing_user_diff() {
208
+ let repo = TestRepo::new("route-user-diff-staged-rename");
209
+ repo.init_git();
210
+ repo.write_file("README.md", "# Baseline\n");
211
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
212
+ repo.write_naome_json(
213
+ "verification.json",
214
+ json!({
215
+ "schema": "naome.verification.v1",
216
+ "version": 1,
217
+ "status": "ready",
218
+ "checks": [
219
+ {
220
+ "id": "diff-check",
221
+ "command": "git diff --check",
222
+ "cwd": ".",
223
+ "purpose": "Reject whitespace errors.",
224
+ "cost": "fast",
225
+ "source": "test",
226
+ "evidence": ["README.md", "INTRO.md"],
227
+ "lastVerified": null
228
+ }
229
+ ],
230
+ "changeTypes": [
231
+ {
232
+ "id": "docs",
233
+ "description": "Repository docs.",
234
+ "paths": ["README.md", "INTRO.md"],
235
+ "requiredChecks": ["diff-check"],
236
+ "recommendedChecks": [],
237
+ "humanReview": false
238
+ }
239
+ ],
240
+ "releaseGates": []
241
+ }),
242
+ );
243
+ repo.git(&["add", "."]);
244
+ repo.git(&["commit", "-m", "baseline"]);
245
+ repo.git(&["mv", "README.md", "INTRO.md"]);
246
+
247
+ let route = evaluate_route(
248
+ repo.path(),
249
+ "commit my changes",
250
+ RouteOptions {
251
+ execute: true,
252
+ evaluation: EvaluationOptions::offline(),
253
+ },
254
+ )
255
+ .unwrap();
256
+
257
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
258
+ assert!(route.allowed);
259
+ assert!(route.mutation_performed);
260
+ assert_eq!(repo.git_status_short(), "");
261
+
262
+ let committed_paths = repo.git_stdout(&["show", "--name-status", "--format=", "HEAD"]);
263
+ assert!(committed_paths.contains("README.md"));
264
+ assert!(committed_paths.contains("INTRO.md"));
265
+ }
266
+
267
+ #[test]
268
+ fn execute_route_refuses_user_diff_commit_without_quality_coverage() {
269
+ let repo = TestRepo::new("route-user-diff-no-quality-coverage");
270
+ repo.init_git();
271
+ repo.write_file("README.md", "# Baseline\n");
272
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
273
+ repo.git(&["add", "."]);
274
+ repo.git(&["commit", "-m", "baseline"]);
275
+ repo.write_file("README.md", "# Manual edit\n");
276
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
277
+
278
+ let route = evaluate_route(
279
+ repo.path(),
280
+ "commit my changes",
281
+ RouteOptions {
282
+ execute: true,
283
+ evaluation: EvaluationOptions::offline(),
284
+ },
285
+ )
286
+ .unwrap();
287
+
288
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
289
+ assert!(!route.allowed);
290
+ assert!(!route.mutation_performed);
291
+ assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
292
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
293
+ assert!(repo.git_status_short().contains("README.md"));
294
+ assert!(route.user_message.contains("No quality coverage"));
295
+ }
296
+
297
+ #[test]
298
+ fn execute_route_refuses_user_diff_commit_when_check_mutates_after_diff_check() {
299
+ let repo = TestRepo::new("route-user-diff-mutating-check");
300
+ repo.init_git();
301
+ repo.write_file("README.md", "# Baseline\n");
302
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
303
+ repo.write_readme_quality_verification(
304
+ vec![json!({
305
+ "id": "mutate-readme-after-check",
306
+ "command": "node -e \"require('fs').writeFileSync('README.md', '# Mutated \\\\n')\"",
307
+ "cwd": ".",
308
+ "purpose": "Simulate a mutating check that dirties checked content.",
309
+ "cost": "fast",
310
+ "source": "test",
311
+ "evidence": ["README.md"],
312
+ "lastVerified": null
313
+ })],
314
+ vec!["mutate-readme-after-check"],
315
+ );
316
+ repo.git(&["add", "."]);
317
+ repo.git(&["commit", "-m", "baseline"]);
318
+ repo.write_file("README.md", "# Manual edit\n");
319
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
320
+
321
+ let route = evaluate_route(
322
+ repo.path(),
323
+ "commit my changes",
324
+ RouteOptions {
325
+ execute: true,
326
+ evaluation: EvaluationOptions::offline(),
327
+ },
328
+ )
329
+ .unwrap();
330
+
331
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
332
+ assert!(!route.allowed);
333
+ assert!(!route.mutation_performed);
334
+ assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
335
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
336
+ assert!(repo.git_status_short().contains("README.md"));
337
+ assert!(route.user_message.contains("trailing whitespace"));
338
+ }
339
+
340
+ #[test]
341
+ fn execute_route_refuses_user_diff_commit_when_diff_check_adds_paths() {
342
+ let repo = TestRepo::new("route-user-diff-mutating-diff-check");
343
+ repo.init_git();
344
+ repo.write_file("README.md", "# Baseline\n");
345
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
346
+ repo.write_naome_json(
347
+ "verification.json",
348
+ json!({
349
+ "schema": "naome.verification.v1",
350
+ "version": 1,
351
+ "status": "ready",
352
+ "checks": [
353
+ {
354
+ "id": "diff-check",
355
+ "command": "node -e \"const fs = require('fs'); const marker = '.git/naome-diff-check-marker'; if (fs.existsSync(marker)) { fs.writeFileSync('NEW.md', '# unexpected\\\\n'); } else { fs.writeFileSync(marker, 'x'); }\"",
356
+ "cwd": ".",
357
+ "purpose": "Simulate a mutating diff check.",
358
+ "cost": "fast",
359
+ "source": "test",
360
+ "evidence": ["README.md"],
361
+ "lastVerified": null
362
+ }
363
+ ],
364
+ "changeTypes": [
365
+ {
366
+ "id": "readme",
367
+ "description": "README changes.",
368
+ "paths": ["README.md"],
369
+ "requiredChecks": ["diff-check"],
370
+ "recommendedChecks": [],
371
+ "humanReview": false
372
+ }
373
+ ],
374
+ "releaseGates": []
375
+ }),
376
+ );
377
+ repo.git(&["add", "."]);
378
+ repo.git(&["commit", "-m", "baseline"]);
379
+ repo.write_file("README.md", "# Manual edit\n");
380
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
381
+
382
+ let route = evaluate_route(
383
+ repo.path(),
384
+ "commit my changes",
385
+ RouteOptions {
386
+ execute: true,
387
+ evaluation: EvaluationOptions::offline(),
388
+ },
389
+ )
390
+ .unwrap();
391
+
392
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
393
+ assert!(!route.allowed);
394
+ assert!(!route.mutation_performed);
395
+ assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
396
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
397
+ assert!(repo.git_status_short().contains("NEW.md"));
398
+ assert!(route
399
+ .user_message
400
+ .contains("Quality checks changed the diff path set"));
401
+ }
402
+
403
+ #[test]
404
+ fn execute_route_refuses_user_diff_commit_when_quality_gate_fails() {
405
+ let repo = TestRepo::new("route-user-diff-quality-fail");
406
+ repo.init_git();
407
+ repo.write_file("README.md", "# Baseline\n");
408
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
409
+ repo.write_readme_quality_verification(Vec::new(), vec!["diff-check"]);
410
+ repo.git(&["add", "."]);
411
+ repo.git(&["commit", "-m", "baseline"]);
412
+ repo.write_file("README.md", "# Manual edit \n");
413
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
414
+
415
+ let route = evaluate_route(
416
+ repo.path(),
417
+ "commit my changes",
418
+ RouteOptions {
419
+ execute: true,
420
+ evaluation: EvaluationOptions::offline(),
421
+ },
422
+ )
423
+ .unwrap();
424
+
425
+ assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
426
+ assert!(!route.allowed);
427
+ assert!(!route.mutation_performed);
428
+ assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
429
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
430
+ assert!(repo.git_status_short().contains("README.md"));
431
+ assert_eq!(route.human_options, vec!["review_unowned_diff"]);
432
+ assert!(route.user_message.contains("quality gate failed"));
433
+ }
434
+
435
+ #[test]
436
+ fn execute_route_baselines_harness_refresh_before_dirty_repo_worktree() {
437
+ let repo = TestRepo::new("route-dirty-harness-refresh-worktree");
438
+ repo.init_git();
439
+ repo.write_file("README.md", "# Baseline\n");
440
+ repo.write_file("USER.md", "user baseline\n");
441
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nOld harness.\n");
442
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
443
+ repo.write_naome_json(
444
+ "manifest.json",
445
+ json!({
446
+ "name": "naome",
447
+ "harnessVersion": "1.1.0",
448
+ "profile": "old",
449
+ "machineOwned": ["AGENTS.md"],
450
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
451
+ "integrity": {}
452
+ }),
453
+ );
454
+ repo.git(&["add", "."]);
455
+ repo.git(&["commit", "-m", "baseline"]);
456
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nUpdated by sync.\n");
457
+ repo.write_naome_json(
458
+ "manifest.json",
459
+ json!({
460
+ "name": "naome",
461
+ "harnessVersion": "1.1.0",
462
+ "profile": "standard",
463
+ "machineOwned": ["AGENTS.md"],
464
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
465
+ "integrity": {}
466
+ }),
467
+ );
468
+ repo.write_file("USER.md", "user local edit\n");
469
+
470
+ let route = evaluate_route(
471
+ repo.path(),
472
+ "Add another line to README as a new task.",
473
+ RouteOptions {
474
+ execute: true,
475
+ evaluation: EvaluationOptions::offline(),
476
+ },
477
+ )
478
+ .unwrap();
479
+
480
+ assert_eq!(
481
+ route.policy_action,
482
+ "auto_commit_harness_refresh_then_create_isolated_task_worktree"
483
+ );
484
+ assert_eq!(
485
+ route.executed_actions,
486
+ vec![
487
+ "commit_harness_refresh_baseline".to_string(),
488
+ "create_task_worktree".to_string()
489
+ ]
490
+ );
491
+ assert!(route.can_create_task);
492
+ assert_eq!(route.next_decision.state, "ready_for_task");
493
+ assert!(route.worktree.is_some());
494
+ assert_eq!(repo.git_status_short(), "M USER.md");
495
+ assert_eq!(
496
+ TestRepo::git_status_short_at(Path::new(&route.task_root)),
497
+ ""
498
+ );
499
+
500
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
501
+ assert!(committed_paths.contains("AGENTS.md"));
502
+ assert!(committed_paths.contains(".naome/manifest.json"));
503
+ assert!(!committed_paths.contains("USER.md"));
504
+ }
505
+
506
+ #[test]
507
+ fn execute_route_baselines_pure_harness_refresh_before_new_task_without_worktree() {
508
+ let repo = TestRepo::new("route-pure-harness-refresh-no-worktree");
509
+ repo.init_git();
510
+ repo.write_file("README.md", "# Baseline\n");
511
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nOld harness.\n");
512
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
513
+ repo.write_naome_json(
514
+ "manifest.json",
515
+ json!({
516
+ "name": "naome",
517
+ "harnessVersion": "1.1.0",
518
+ "profile": "old",
519
+ "machineOwned": ["AGENTS.md"],
520
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
521
+ "integrity": {}
522
+ }),
523
+ );
524
+ repo.git(&["add", "."]);
525
+ repo.git(&["commit", "-m", "baseline"]);
526
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
527
+ repo.write_base_naome_state(completed_task_state(&admission_head));
528
+ repo.git(&["add", ".naome/task-state.json"]);
529
+ repo.git(&["commit", "-m", "task state"]);
530
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nUpdated by sync.\n");
531
+ repo.write_naome_json(
532
+ "manifest.json",
533
+ json!({
534
+ "name": "naome",
535
+ "harnessVersion": "1.1.0",
536
+ "profile": "standard",
537
+ "machineOwned": ["AGENTS.md"],
538
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
539
+ "integrity": {}
540
+ }),
541
+ );
542
+
543
+ let route = evaluate_route(
544
+ repo.path(),
545
+ "Add another line to README as a new task.",
546
+ RouteOptions {
547
+ execute: true,
548
+ evaluation: EvaluationOptions::offline(),
549
+ },
550
+ )
551
+ .unwrap();
552
+
553
+ assert_eq!(
554
+ route.policy_action,
555
+ "auto_commit_harness_refresh_then_create_new_task"
556
+ );
557
+ assert_eq!(
558
+ route.executed_actions,
559
+ vec!["commit_harness_refresh_baseline".to_string()]
560
+ );
561
+ assert!(route.mutation_performed);
562
+ assert!(route.can_create_task);
563
+ assert!(route.worktree.is_none());
564
+ assert_eq!(route.task_root, repo.path().to_string_lossy());
565
+ assert_eq!(route.next_decision.state, "ready_for_task");
566
+ assert_eq!(repo.git_status_short(), "");
567
+
568
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
569
+ assert!(committed_paths.contains("AGENTS.md"));
570
+ assert!(committed_paths.contains(".naome/manifest.json"));
571
+ assert!(!committed_paths.contains("README.md"));
572
+ }
573
+
574
+ #[test]
575
+ fn execute_route_repair_request_baselines_harness_refresh_only() {
576
+ let repo = TestRepo::new("route-harness-refresh-repair-only");
577
+ repo.init_git();
578
+ repo.write_file("README.md", "# Baseline\n");
579
+ repo.write_file("USER.md", "user baseline\n");
580
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nOld harness.\n");
581
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
582
+ repo.write_naome_json(
583
+ "manifest.json",
584
+ json!({
585
+ "name": "naome",
586
+ "harnessVersion": "1.1.0",
587
+ "profile": "old",
588
+ "machineOwned": ["AGENTS.md"],
589
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
590
+ "integrity": {}
591
+ }),
592
+ );
593
+ repo.git(&["add", "."]);
594
+ repo.git(&["commit", "-m", "baseline"]);
595
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nUpdated by sync.\n");
596
+ repo.write_naome_json(
597
+ "manifest.json",
598
+ json!({
599
+ "name": "naome",
600
+ "harnessVersion": "1.1.0",
601
+ "profile": "standard",
602
+ "machineOwned": ["AGENTS.md"],
603
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
604
+ "integrity": {}
605
+ }),
606
+ );
607
+ repo.write_file("USER.md", "user local edit\n");
608
+
609
+ let route = evaluate_route(
610
+ repo.path(),
611
+ "please repair all",
612
+ RouteOptions {
613
+ execute: true,
614
+ evaluation: EvaluationOptions::offline(),
615
+ },
616
+ )
617
+ .unwrap();
618
+
619
+ assert_eq!(route.policy_action, "auto_commit_harness_refresh_baseline");
620
+ assert!(route.mutation_performed);
621
+ assert_eq!(
622
+ route.executed_actions,
623
+ vec!["commit_harness_refresh_baseline".to_string()]
624
+ );
625
+ assert_eq!(repo.git_status_short(), "M USER.md");
626
+
627
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
628
+ assert!(committed_paths.contains("AGENTS.md"));
629
+ assert!(committed_paths.contains(".naome/manifest.json"));
630
+ assert!(!committed_paths.contains("USER.md"));
631
+ }
632
+
633
+ #[test]
634
+ fn execute_route_refuses_to_create_more_than_max_isolated_worktrees() {
635
+ let repo = TestRepo::new("route-worktree-limit");
636
+ repo.init_git();
637
+ repo.write_file("README.md", "# Baseline\n");
638
+ repo.write_file("USER.md", "user baseline\n");
639
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
640
+ repo.git(&["add", "."]);
641
+ repo.git(&["commit", "-m", "baseline"]);
642
+ repo.write_file("USER.md", "user local edit\n");
643
+ let common_dir = repo.git_stdout(&["rev-parse", "--git-common-dir"]);
644
+ let worktree_root = repo.path().join(common_dir).join("naome").join("worktrees");
645
+ fs::create_dir_all(&worktree_root).unwrap();
646
+ for index in 0..25 {
647
+ fs::create_dir_all(worktree_root.join(format!("stale-{index}"))).unwrap();
648
+ }
649
+
650
+ let error = evaluate_route(
651
+ repo.path(),
652
+ "Add another line to README as a new task.",
653
+ RouteOptions {
654
+ execute: true,
655
+ evaluation: EvaluationOptions::offline(),
656
+ },
657
+ )
658
+ .unwrap_err();
659
+
660
+ assert!(error
661
+ .to_string()
662
+ .contains("Too many NAOME task worktrees are present"));
663
+ }
664
+
665
+ #[test]
666
+ fn execute_route_preflights_worktree_before_completed_task_baseline() {
667
+ let repo =
668
+ TestRepo::completed_task_with_unrelated_user_edit("route-completed-worktree-preflight");
669
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
670
+ let before_status = repo.git_status_short();
671
+ let common_dir = repo.git_stdout(&["rev-parse", "--git-common-dir"]);
672
+ let worktree_root = repo.path().join(common_dir).join("naome").join("worktrees");
673
+ fs::create_dir_all(&worktree_root).unwrap();
674
+ for index in 0..25 {
675
+ fs::create_dir_all(worktree_root.join(format!("stale-{index}"))).unwrap();
676
+ }
677
+
678
+ let error = evaluate_route(
679
+ repo.path(),
680
+ "Add another line to README as a new task.",
681
+ RouteOptions {
682
+ execute: true,
683
+ evaluation: EvaluationOptions::offline(),
684
+ },
685
+ )
686
+ .unwrap_err();
687
+
688
+ assert!(error
689
+ .to_string()
690
+ .contains("Too many NAOME task worktrees are present"));
691
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
692
+ assert_eq!(repo.git_status_short(), before_status);
693
+ }
694
+
695
+ #[test]
696
+ fn execute_route_uses_preflighted_worktree_name_after_completed_task_baseline() {
697
+ let repo = TestRepo::completed_task_with_unrelated_user_edit("route-worktree-name-preflight");
698
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
699
+ let before_short = &before_head[..12];
700
+
701
+ let route = evaluate_route(
702
+ repo.path(),
703
+ "Add another line to README as a new task.",
704
+ RouteOptions {
705
+ execute: true,
706
+ evaluation: EvaluationOptions::offline(),
707
+ },
708
+ )
709
+ .unwrap();
710
+
711
+ let worktree = route.worktree.expect("route should create a worktree");
712
+ assert!(worktree.branch.contains(before_short));
713
+ assert!(worktree.path.contains(before_short));
714
+ }
715
+
716
+ #[test]
717
+ fn dry_route_plans_harness_refresh_split_before_completed_task_baseline() {
718
+ let repo = TestRepo::completed_task_with_harness_refresh_diff("route-dry-harness-refresh");
719
+ let before = repo.git_stdout(&["rev-parse", "HEAD"]);
720
+
721
+ let route = evaluate_route(
722
+ repo.path(),
723
+ "Start a new task for README polish.",
724
+ RouteOptions {
725
+ execute: false,
726
+ evaluation: EvaluationOptions::offline(),
727
+ },
728
+ )
729
+ .unwrap();
730
+
731
+ assert_eq!(
732
+ route.policy_action,
733
+ "auto_commit_harness_refresh_then_completed_task_then_create_new_task"
734
+ );
735
+ assert!(!route.mutation_performed);
736
+ assert!(!route.can_create_task);
737
+ assert!(route.human_options.is_empty());
738
+ assert!(route.intent.risk_codes.is_empty());
739
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
740
+ }
741
+
742
+ #[test]
743
+ fn execute_route_splits_harness_refresh_then_completed_task_baseline() {
744
+ let repo = TestRepo::completed_task_with_harness_refresh_diff("route-execute-harness-refresh");
745
+
746
+ let route = evaluate_route(
747
+ repo.path(),
748
+ "Start a new task for README polish.",
749
+ RouteOptions {
750
+ execute: true,
751
+ evaluation: EvaluationOptions::offline(),
752
+ },
753
+ )
754
+ .unwrap();
755
+
756
+ assert_eq!(
757
+ route.policy_action,
758
+ "auto_commit_harness_refresh_then_completed_task_then_create_new_task"
759
+ );
760
+ assert!(route.mutation_performed);
761
+ assert!(route.can_create_task);
762
+ assert_eq!(
763
+ route.executed_actions,
764
+ vec![
765
+ "commit_harness_refresh_baseline".to_string(),
766
+ "commit_task_baseline".to_string()
767
+ ]
768
+ );
769
+ assert_eq!(route.next_decision.state, "ready_for_task");
770
+ assert!(repo.git_status_short().is_empty());
771
+
772
+ let log = repo.git_stdout(&["log", "--format=%s", "-2"]);
773
+ assert!(log.contains("chore(naome): baseline completed task"));
774
+ assert!(log.contains("chore(naome): baseline harness refresh"));
775
+
776
+ let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
777
+ assert!(journal.contains("\"taskId\":\"readme-task\""));
778
+ assert!(journal.contains("\"outcome\":\"route_auto_baseline\""));
779
+ }
780
+
781
+ #[test]
782
+ fn execute_route_does_not_mutate_when_prompt_blocks_commit() {
783
+ let repo = TestRepo::completed_task_with_diff("route-no-commit");
784
+ let before = repo.git_stdout(&["rev-parse", "HEAD"]);
785
+
786
+ let route = evaluate_route(
787
+ repo.path(),
788
+ "Do not commit. Start a new task after this.",
789
+ RouteOptions {
790
+ execute: true,
791
+ evaluation: EvaluationOptions::offline(),
792
+ },
793
+ )
794
+ .unwrap();
795
+
796
+ assert_eq!(route.policy_action, "block_auto_baseline_due_to_no_commit");
797
+ assert!(!route.mutation_performed);
798
+ assert!(!route.can_create_task);
799
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
800
+ assert!(repo.git_status_short().contains("README.md"));
801
+ }
802
+
803
+ #[test]
804
+ fn explicit_route_commit_baseline_leaves_unrelated_user_edit_unstaged() {
805
+ let repo = TestRepo::completed_task_with_diff("route-commit-task-scope");
806
+ repo.write_file("USER.md", "user baseline\n");
807
+ repo.git(&["add", "USER.md"]);
808
+ repo.git(&["commit", "-m", "user baseline"]);
809
+ repo.write_file("README.md", "# Changed\n");
810
+ repo.write_file("USER.md", "user local edit\n");
811
+ repo.git(&["add", "USER.md"]);
812
+
813
+ let route = evaluate_route(
814
+ repo.path(),
815
+ "commit_task_baseline",
816
+ RouteOptions {
817
+ execute: true,
818
+ evaluation: EvaluationOptions::offline(),
819
+ },
820
+ )
821
+ .unwrap();
822
+
823
+ assert_eq!(route.policy_action, "commit_task_baseline");
824
+ assert!(route.mutation_performed);
825
+ assert_eq!(repo.git_status_short(), "M USER.md");
826
+
827
+ let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
828
+ assert!(committed_paths.contains("README.md"));
829
+ assert!(!committed_paths.contains("USER.md"));
830
+ }
831
+
832
+ #[test]
833
+ fn execute_route_journals_external_commit_after_completed_task() {
834
+ let repo = TestRepo::completed_task_with_diff("route-external-commit");
835
+ repo.git(&["add", "-A"]);
836
+ repo.git(&["commit", "-m", "manual baseline"]);
837
+ assert!(repo.git_status_short().is_empty());
838
+
839
+ let route = evaluate_route(
840
+ repo.path(),
841
+ "Create a new task for README polish.",
842
+ RouteOptions {
843
+ execute: true,
844
+ evaluation: EvaluationOptions::offline(),
845
+ },
846
+ )
847
+ .unwrap();
848
+
849
+ assert_eq!(route.repo_state_before, "ready_for_task");
850
+ assert!(route.mutation_performed);
851
+ assert!(route.can_create_task);
852
+ assert!(route
853
+ .executed_actions
854
+ .contains(&"journal_external_task_baseline".to_string()));
855
+
856
+ let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
857
+ assert!(journal.contains("\"outcome\":\"external_baseline\""));
858
+ assert!(journal.contains("\"taskId\":\"readme-task\""));
859
+ }
860
+
861
+ #[test]
862
+ fn explain_reports_winning_rule_and_mutation_plan_without_executing() {
863
+ let repo = TestRepo::completed_task_with_diff("route-explain");
864
+ let before = repo.git_stdout(&["rev-parse", "HEAD"]);
865
+
866
+ let explain = explain_route(
867
+ repo.path(),
868
+ "Start a new task for README polish.",
869
+ EvaluationOptions::offline(),
870
+ )
871
+ .unwrap();
872
+
873
+ assert_eq!(
874
+ explain.winning_rule,
875
+ "completed_task_valid_new_task_auto_baseline"
876
+ );
877
+ assert!(explain.would_mutate);
878
+ assert!(explain
879
+ .discarded_candidate_actions
880
+ .contains(&"review_task_diff".to_string()));
881
+ assert!(explain
882
+ .required_context
883
+ .contains(&"docs/naome/execution.md".to_string()));
884
+ assert!(!explain.user_message.contains("commit_task_baseline"));
885
+ assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
886
+ }
887
+
888
+ #[test]
889
+ fn unhealthy_harness_route_blocks_normal_work() {
890
+ let repo = TestRepo::new("route-unhealthy");
891
+ repo.init_git();
892
+ repo.write_file("README.md", "# Baseline\n");
893
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
894
+ repo.write_file(".naome/bin/check-harness-health.js", "process.exit(1);\n");
895
+ repo.git(&["add", "."]);
896
+ repo.git(&["commit", "-m", "baseline"]);
897
+
898
+ let route = evaluate_route(
899
+ repo.path(),
900
+ "Create a new task.",
901
+ RouteOptions {
902
+ execute: true,
903
+ evaluation: EvaluationOptions::online(),
904
+ },
905
+ )
906
+ .unwrap();
907
+
908
+ assert_eq!(route.repo_state_before, "harness_unhealthy");
909
+ assert!(!route.mutation_performed);
910
+ assert!(!route.can_create_task);
911
+ assert!(!route.human_options.is_empty());
912
+ }
913
+
914
+ struct TestRepo {
915
+ root: PathBuf,
916
+ }
917
+
918
+ impl TestRepo {
919
+ fn new(name: &str) -> Self {
920
+ let nonce = SystemTime::now()
921
+ .duration_since(UNIX_EPOCH)
922
+ .unwrap()
923
+ .as_nanos();
924
+ let root = std::env::temp_dir().join(format!("naome-route-{name}-{nonce}"));
925
+ fs::create_dir_all(root.join(".naome")).unwrap();
926
+ Self { root }
927
+ }
928
+
929
+ fn completed_task_with_diff(name: &str) -> Self {
930
+ let repo = Self::new(name);
931
+ repo.init_git();
932
+ repo.write_file("README.md", "# Baseline\n");
933
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
934
+ repo.git(&["add", "."]);
935
+ repo.git(&["commit", "-m", "baseline"]);
936
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
937
+ repo.write_base_naome_state(completed_task_state(&admission_head));
938
+ repo.git(&["add", ".naome/task-state.json"]);
939
+ repo.git(&["commit", "-m", "task state"]);
940
+ repo.write_file("README.md", "# Changed\n");
941
+ repo
942
+ }
943
+
944
+ fn completed_task_with_unrelated_user_edit(name: &str) -> Self {
945
+ let repo = Self::new(name);
946
+ repo.init_git();
947
+ repo.write_file("README.md", "# Baseline\n");
948
+ repo.write_file("USER.md", "user baseline\n");
949
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
950
+ repo.git(&["add", "."]);
951
+ repo.git(&["commit", "-m", "baseline"]);
952
+ let admission_head = repo.git_stdout(&["rev-parse", "HEAD"]);
953
+ repo.write_base_naome_state(completed_task_state(&admission_head));
954
+ repo.git(&["add", ".naome/task-state.json"]);
955
+ repo.git(&["commit", "-m", "task state"]);
956
+ repo.write_file("README.md", "# Changed\n");
957
+ repo.write_file("USER.md", "user local edit\n");
958
+ repo
959
+ }
960
+
961
+ fn completed_task_with_harness_refresh_diff(name: &str) -> Self {
962
+ let repo = Self::completed_task_with_diff(name);
963
+ repo.write_file("AGENTS.md", "# Agent Instructions\n\nUpdated by sync.\n");
964
+ repo.write_file(
965
+ ".naome/manifest.json",
966
+ r#"{
967
+ "name": "naome",
968
+ "harnessVersion": "1.1.0",
969
+ "profile": "standard",
970
+ "machineOwned": ["AGENTS.md"],
971
+ "projectOwned": [".naome/manifest.json", ".naome/task-state.json"],
972
+ "integrity": {}
973
+ }
974
+ "#,
975
+ );
976
+ repo
977
+ }
978
+
979
+ fn path(&self) -> &Path {
980
+ &self.root
981
+ }
982
+
983
+ fn write_base_naome_state(&self, task_state: serde_json::Value) {
984
+ self.write_naome_json(
985
+ "init-state.json",
986
+ json!({ "initialized": true, "intakeStatus": "complete" }),
987
+ );
988
+ self.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
989
+ self.write_naome_json(
990
+ "verification.json",
991
+ json!({
992
+ "schema": "naome.verification.v1",
993
+ "version": 1,
994
+ "status": "ready",
995
+ "checks": [
996
+ {
997
+ "id": "diff-check",
998
+ "command": "git diff --check",
999
+ "cwd": ".",
1000
+ "purpose": "Reject whitespace errors.",
1001
+ "cost": "fast",
1002
+ "source": "test",
1003
+ "evidence": ["README.md"],
1004
+ "lastVerified": null
1005
+ }
1006
+ ],
1007
+ "changeTypes": [],
1008
+ "releaseGates": []
1009
+ }),
1010
+ );
1011
+ self.write_naome_json("task-state.json", task_state);
1012
+ }
1013
+
1014
+ fn write_readme_quality_verification(
1015
+ &self,
1016
+ extra_checks: Vec<Value>,
1017
+ required_checks: Vec<&str>,
1018
+ ) {
1019
+ let mut checks = vec![json!({
1020
+ "id": "diff-check",
1021
+ "command": "git diff --check",
1022
+ "cwd": ".",
1023
+ "purpose": "Reject whitespace errors.",
1024
+ "cost": "fast",
1025
+ "source": "test",
1026
+ "evidence": ["README.md"],
1027
+ "lastVerified": null
1028
+ })];
1029
+ checks.extend(extra_checks);
1030
+
1031
+ self.write_naome_json(
1032
+ "verification.json",
1033
+ json!({
1034
+ "schema": "naome.verification.v1",
1035
+ "version": 1,
1036
+ "status": "ready",
1037
+ "checks": checks,
1038
+ "changeTypes": [
1039
+ {
1040
+ "id": "readme",
1041
+ "description": "README changes.",
1042
+ "paths": ["README.md"],
1043
+ "requiredChecks": required_checks,
1044
+ "recommendedChecks": [],
1045
+ "humanReview": false
1046
+ }
1047
+ ],
1048
+ "releaseGates": []
1049
+ }),
1050
+ );
1051
+ }
1052
+
1053
+ fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
1054
+ let path = self.root.join(".naome").join(file_name);
1055
+ fs::write(
1056
+ path,
1057
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
1058
+ )
1059
+ .unwrap();
1060
+ }
1061
+
1062
+ fn write_file(&self, relative_path: &str, content: &str) {
1063
+ let path = self.root.join(relative_path);
1064
+ if let Some(parent) = path.parent() {
1065
+ fs::create_dir_all(parent).unwrap();
1066
+ }
1067
+ fs::write(path, content).unwrap();
1068
+ }
1069
+
1070
+ fn init_git(&self) {
1071
+ self.git(&["init"]);
1072
+ self.git(&["config", "user.email", "naome@example.com"]);
1073
+ self.git(&["config", "user.name", "NAOME Test"]);
1074
+ }
1075
+
1076
+ fn git(&self, args: &[&str]) {
1077
+ let output = Command::new("git")
1078
+ .args(args)
1079
+ .current_dir(&self.root)
1080
+ .output()
1081
+ .unwrap();
1082
+ assert!(
1083
+ output.status.success(),
1084
+ "git {:?} failed\nstdout:\n{}\nstderr:\n{}",
1085
+ args,
1086
+ String::from_utf8_lossy(&output.stdout),
1087
+ String::from_utf8_lossy(&output.stderr)
1088
+ );
1089
+ }
1090
+
1091
+ fn git_stdout(&self, args: &[&str]) -> String {
1092
+ let output = Command::new("git")
1093
+ .args(args)
1094
+ .current_dir(&self.root)
1095
+ .output()
1096
+ .unwrap();
1097
+ assert!(output.status.success());
1098
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
1099
+ }
1100
+
1101
+ fn git_status_short(&self) -> String {
1102
+ self.git_stdout(&["status", "--short"])
1103
+ }
1104
+
1105
+ fn git_status_short_at(root: &Path) -> String {
1106
+ let output = Command::new("git")
1107
+ .args(["status", "--short"])
1108
+ .current_dir(root)
1109
+ .output()
1110
+ .unwrap();
1111
+ assert!(output.status.success());
1112
+ String::from_utf8_lossy(&output.stdout).trim().to_string()
1113
+ }
1114
+ }
1115
+
1116
+ fn completed_task_state(admission_head: &str) -> serde_json::Value {
1117
+ json!({
1118
+ "schema": "naome.task-state.v1",
1119
+ "version": 1,
1120
+ "status": "complete",
1121
+ "activeTask": {
1122
+ "id": "readme-task",
1123
+ "request": "Change README.",
1124
+ "userPrompt": {
1125
+ "receivedAt": "2026-05-06T00:00:00.000Z",
1126
+ "text": "Change README."
1127
+ },
1128
+ "admission": {
1129
+ "command": "node .naome/bin/check-task-state.js --admission",
1130
+ "cwd": ".",
1131
+ "exitCode": 0,
1132
+ "checkedAt": "2026-05-06T00:00:00.000Z",
1133
+ "gitHead": admission_head,
1134
+ "changedPaths": []
1135
+ },
1136
+ "allowedPaths": ["README.md"],
1137
+ "declaredChangeTypes": ["product-docs"],
1138
+ "requiredCheckIds": ["diff-check"],
1139
+ "proofResults": [
1140
+ {
1141
+ "checkId": "diff-check",
1142
+ "command": "git diff --check",
1143
+ "cwd": ".",
1144
+ "exitCode": 0,
1145
+ "checkedAt": "2026-05-06T00:00:00.000Z",
1146
+ "evidence": ["README.md"]
1147
+ }
1148
+ ],
1149
+ "revisions": [],
1150
+ "humanReview": {
1151
+ "required": false,
1152
+ "approved": false,
1153
+ "reason": null
1154
+ }
1155
+ },
1156
+ "blocker": null,
1157
+ "updatedAt": "2026-05-06T00:00:00.000Z"
1158
+ })
1159
+ }