@hallucination-studio/harness-engine 1.0.0-beta.10.9ff10d9

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.
@@ -0,0 +1,1188 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ import time
9
+ from pathlib import Path
10
+
11
+ SKILL_DIR = Path(__file__).resolve().parents[1]
12
+ MANAGER = SKILL_DIR / "scripts" / "manage_harness.py"
13
+ CASES_PATH = Path(__file__).with_name("cases.json")
14
+
15
+
16
+ def load_case_metadata():
17
+ if not CASES_PATH.exists():
18
+ return {}
19
+ return {item["id"]: item for item in json.loads(CASES_PATH.read_text())}
20
+
21
+
22
+ def run_manager(*args, expect_success=True):
23
+ result = subprocess.run(
24
+ [sys.executable, str(MANAGER), *args],
25
+ text=True,
26
+ capture_output=True,
27
+ check=False,
28
+ )
29
+ if expect_success and result.returncode != 0:
30
+ raise AssertionError(result.stderr or result.stdout)
31
+ if not expect_success and result.returncode == 0:
32
+ raise AssertionError("Command succeeded unexpectedly")
33
+ if result.stdout.strip():
34
+ return json.loads(result.stdout)
35
+ return {}
36
+
37
+
38
+ def write_answers(path, project_name="demo"):
39
+ answers = {
40
+ "project_name": project_name,
41
+ "project_summary": "A developer tooling project used to install and maintain Codex harness docs.",
42
+ "primary_users": "Codex users and maintainers",
43
+ "deployment_targets": "npm package and local repositories",
44
+ "product_domain": "developer tooling",
45
+ "reliability_targets": "Repeatable local commands and safe init behavior",
46
+ "security_constraints": "Do not write secrets or overwrite user-owned docs without consent",
47
+ "frontend_stack_notes": "Frontend changes require browser validation when a UI is detected",
48
+ "quality_focus": "installer behavior, generated docs, plan closure, and knowledge capture",
49
+ "frontend_scope": "No frontend unless one is detected by analysis",
50
+ }
51
+ path.write_text(json.dumps(answers, indent=2) + "\n")
52
+
53
+
54
+ def assert_exists(repo, relative_path):
55
+ path = repo / relative_path
56
+ if not path.exists():
57
+ raise AssertionError(f"Expected {relative_path} to exist")
58
+
59
+
60
+ def assert_contains(repo, relative_path, needle):
61
+ text = (repo / relative_path).read_text()
62
+ if needle not in text:
63
+ raise AssertionError(f"Expected {relative_path} to contain {needle!r}")
64
+
65
+
66
+ def quality_note_args(
67
+ product="Product behavior was validated by the eval case.",
68
+ ux="User/operator workflow evidence was validated by the eval case.",
69
+ architecture="Architecture and plan state were validated by the eval case.",
70
+ reliability="Repeatable validation evidence was produced by the eval case.",
71
+ security="Security and data-handling assumptions were checked by the eval case.",
72
+ ):
73
+ return [
74
+ "--product-note",
75
+ product,
76
+ "--ux-note",
77
+ ux,
78
+ "--architecture-note",
79
+ architecture,
80
+ "--reliability-note",
81
+ reliability,
82
+ "--security-note",
83
+ security,
84
+ ]
85
+
86
+
87
+ def test_empty_repo_init(tmp_root):
88
+ repo = tmp_root / "empty-repo"
89
+ repo.mkdir()
90
+ answers = tmp_root / "answers.json"
91
+ write_answers(answers)
92
+
93
+ analysis = run_manager("analyze", "--repo", str(repo))
94
+ if analysis["recommended_action"] != "init":
95
+ raise AssertionError("Empty repo should recommend init")
96
+ if not analysis["missing_exec_plan_state"]:
97
+ raise AssertionError("Analysis should report missing exec-plan state")
98
+ if not analysis["missing_sops"]:
99
+ raise AssertionError("Analysis should report missing SOPs")
100
+ nested_output = tmp_root / "nested" / "generated" / "analysis.json"
101
+ run_manager("analyze", "--repo", str(repo), "--output", str(nested_output))
102
+ if not nested_output.exists():
103
+ raise AssertionError("analyze --output should create missing parent directories")
104
+
105
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
106
+ for relative_path in [
107
+ "AGENTS.md",
108
+ "ARCHITECTURE.md",
109
+ "docs/PLANS.md",
110
+ "docs/QUALITY_SCORE.md",
111
+ "docs/exec-plans/workstreams.md",
112
+ "docs/exec-plans/active/_template.md",
113
+ "docs/exec-plans/completed/README.md",
114
+ "docs/sops/encode-unseen-knowledge.md",
115
+ "docs/sops/evidence-first-eval-loop.md",
116
+ ]:
117
+ assert_exists(repo, relative_path)
118
+ assert_contains(repo, "AGENTS.md", "docs/exec-plans/active/")
119
+ assert_contains(repo, "AGENTS.md", "docs/exec-plans/workstreams.md")
120
+ assert_contains(repo, "AGENTS.md", "docs/sops/")
121
+ assert_contains(repo, "AGENTS.md", ".codex/skills/harness-engine/scripts/manage_harness.py check")
122
+ assert_contains(repo, "AGENTS.md", "## Issue Workflows")
123
+ assert_contains(repo, "AGENTS.md", "Product contract or acceptance drift")
124
+ assert_contains(repo, "AGENTS.md", "Backend, API, runtime behavior, background jobs, or integrations")
125
+ assert_contains(repo, "AGENTS.md", "Architecture boundaries, layering, data flow, or dependency direction")
126
+ assert_contains(repo, "AGENTS.md", "Data, state, migrations, cache, queues, or file formats")
127
+ assert_contains(repo, "AGENTS.md", "Security, privacy, auth, authorization, secrets, or sensitive data")
128
+ assert_contains(repo, "AGENTS.md", "Performance, capacity, timeout, resource use, or availability")
129
+ assert_contains(repo, "AGENTS.md", "Convert the issue into assertions, tests, smoke checks, or a regression case")
130
+ assert_contains(repo, "AGENTS.md", "Log confirmed defects or missing evidence with `defect-log`")
131
+ assert_contains(repo, "docs/QUALITY_SCORE.md", "Evidence Requirements")
132
+ assert_contains(repo, "docs/QUALITY_SCORE.md", "Treat LLM or human judgment as a summary over evidence")
133
+ assert_contains(repo, "docs/QUALITY_SCORE.md", "Backend and runtime scores must cite")
134
+ assert_contains(repo, "docs/QUALITY_SCORE.md", "Architecture scores must cite")
135
+ assert_contains(repo, "docs/QUALITY_SCORE.md", "Security scores must cite")
136
+ assert_contains(repo, "docs/FRONTEND.md", "Evidence For Meaningful UI Work")
137
+ assert_contains(repo, "docs/FRONTEND.md", "Define and verify layout invariants")
138
+ assert_contains(repo, "docs/FRONTEND.md", "preserve the primary task area")
139
+ assert_contains(repo, "docs/sops/evidence-first-eval-loop.md", "Report per-case results")
140
+ assert_contains(repo, "docs/sops/evidence-first-eval-loop.md", "Read the Issue Workflows in `AGENTS.md`")
141
+
142
+
143
+ def test_frontend_analysis(tmp_root):
144
+ repo = tmp_root / "frontend-repo"
145
+ repo.mkdir()
146
+ (repo / "package.json").write_text(
147
+ json.dumps(
148
+ {
149
+ "dependencies": {
150
+ "react": "^19.0.0",
151
+ "vite": "^6.0.0",
152
+ }
153
+ },
154
+ indent=2,
155
+ )
156
+ + "\n"
157
+ )
158
+ (repo / "src").mkdir()
159
+ (repo / "src" / "App.tsx").write_text("export default function App() { return null; }\n")
160
+
161
+ analysis = run_manager("analyze", "--repo", str(repo))
162
+ question_ids = {item["id"] for item in analysis["human_confirmations"]}
163
+ if not analysis["has_frontend"]:
164
+ raise AssertionError("Frontend repo should be detected")
165
+ if "frontend_stack_notes" not in question_ids:
166
+ raise AssertionError("Frontend repo should ask frontend confirmation questions")
167
+ if "React" not in analysis["frameworks"]:
168
+ raise AssertionError("React should be detected")
169
+ if "docs/sops/evidence-first-eval-loop.md" not in analysis["missing_sops"]:
170
+ raise AssertionError("Analysis should include the evidence-first eval SOP")
171
+
172
+
173
+ def test_init_reconciles_existing_harness(tmp_root):
174
+ repo = tmp_root / "reconcile-repo"
175
+ repo.mkdir()
176
+ answers = tmp_root / "reconcile-answers.json"
177
+ write_answers(answers, project_name="reconcile-demo")
178
+ init_result = run_manager("init", "--repo", str(repo), "--answers", str(answers))
179
+ if init_result["mode"] != "init" or "AGENTS.md" not in init_result["created"]:
180
+ raise AssertionError("init should report created managed files")
181
+
182
+ existing_analysis = run_manager("analyze", "--repo", str(repo))
183
+ if existing_analysis["recommended_action"] != "init" or existing_analysis["harness_state"] != "existing":
184
+ raise AssertionError("existing harnesses should still route through init reconciliation")
185
+
186
+ target = repo / "docs" / "sops" / "evidence-first-eval-loop.md"
187
+ target.unlink()
188
+ (repo / "AGENTS.md").write_text("<!-- harness-engine:managed -->\n# stale managed router\n")
189
+ reconcile_result = run_manager("init", "--repo", str(repo), "--answers", str(answers))
190
+ if reconcile_result["mode"] != "init" or reconcile_result["operation"] != "reconciled":
191
+ raise AssertionError("init should reconcile an existing managed harness")
192
+ if "docs/sops/evidence-first-eval-loop.md" not in reconcile_result["created"]:
193
+ raise AssertionError("init reconcile should create missing managed files introduced by newer templates")
194
+ if "AGENTS.md" not in reconcile_result["refreshed"]:
195
+ raise AssertionError("init reconcile should refresh existing managed files")
196
+ assert_contains(repo, "AGENTS.md", "## Issue Workflows")
197
+ assert_exists(repo, "docs/sops/evidence-first-eval-loop.md")
198
+
199
+
200
+ def test_closed_loop_plan(tmp_root):
201
+ repo = tmp_root / "loop-repo"
202
+ repo.mkdir()
203
+ (repo / "snake.sh").write_text("#!/usr/bin/env bash\nprintf 'snake\\n'\n")
204
+ (repo / ".codex" / "skills" / "demo" / "scripts").mkdir(parents=True)
205
+ (repo / ".codex" / "skills" / "demo" / "scripts" / "tool.py").write_text("print('ignore me')\n")
206
+ answers = tmp_root / "loop-answers.json"
207
+ write_answers(answers, project_name="loop-demo")
208
+ analysis = run_manager("analyze", "--repo", str(repo))
209
+ if "Shell" not in analysis["languages"]:
210
+ raise AssertionError("Shell should be detected from target project files")
211
+ if "Python" in analysis["languages"]:
212
+ raise AssertionError(".codex skill files should not affect target project language detection")
213
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
214
+
215
+ plan_result = run_manager(
216
+ "plan-start",
217
+ "--repo",
218
+ str(repo),
219
+ "--slug",
220
+ "knowledge-loop",
221
+ "--goal",
222
+ "Validate durable knowledge closure",
223
+ )
224
+ plan_path = Path(plan_result["plan"])
225
+ relative_plan = str(plan_path.resolve().relative_to(repo.resolve()))
226
+ fact = "Install mode must distinguish local and global skill destinations"
227
+ run_manager(
228
+ "knowledge-log",
229
+ "--repo",
230
+ str(repo),
231
+ "--plan",
232
+ relative_plan,
233
+ "--fact",
234
+ fact,
235
+ "--destination",
236
+ "docs/PRODUCT_SENSE.md",
237
+ )
238
+ run_manager(
239
+ "plan-close",
240
+ "--repo",
241
+ str(repo),
242
+ "--plan",
243
+ relative_plan,
244
+ "--summary",
245
+ "done",
246
+ expect_success=False,
247
+ )
248
+ run_manager(
249
+ "knowledge-mark-written",
250
+ "--repo",
251
+ str(repo),
252
+ "--plan",
253
+ relative_plan,
254
+ "--fact",
255
+ fact,
256
+ "--destination",
257
+ "docs/PRODUCT_SENSE.md",
258
+ expect_success=False,
259
+ )
260
+ run_manager(
261
+ "knowledge-mark-written",
262
+ "--repo",
263
+ str(repo),
264
+ "--plan",
265
+ relative_plan,
266
+ "--fact",
267
+ fact,
268
+ "--destination",
269
+ "docs/PRODUCT_SENSE.md",
270
+ "--append",
271
+ )
272
+ assert_contains(repo, "docs/PRODUCT_SENSE.md", fact)
273
+ run_manager(
274
+ "plan-close",
275
+ "--repo",
276
+ str(repo),
277
+ "--plan",
278
+ relative_plan,
279
+ "--summary",
280
+ "done",
281
+ expect_success=False,
282
+ )
283
+ failing_score = run_manager(
284
+ "quality-score",
285
+ "--repo",
286
+ str(repo),
287
+ "--plan",
288
+ relative_plan,
289
+ "--product-correctness",
290
+ "9",
291
+ "--ux-operator-clarity",
292
+ "8",
293
+ "--architecture-maintainability",
294
+ "7",
295
+ "--reliability-observability",
296
+ "8",
297
+ "--security-data-handling",
298
+ "8",
299
+ "--architecture-note",
300
+ "Plan closure needs a deterministic quality gate before handoff",
301
+ *quality_note_args(
302
+ architecture="Plan closure needs a deterministic quality gate before handoff",
303
+ ),
304
+ expect_success=False,
305
+ )
306
+ if failing_score["status"] != "fail":
307
+ raise AssertionError("Low dimension score should fail the quality gate")
308
+ plan_text_after_fail = plan_path.read_text()
309
+ if "## Rework Required" not in plan_text_after_fail:
310
+ raise AssertionError("Failing quality score should keep a rework section")
311
+ if "Improve Architecture and maintainability" not in plan_text_after_fail:
312
+ raise AssertionError("Failing quality score should name the low dimension")
313
+ check_after_fail = run_manager("check", "--repo", str(repo), expect_success=False)
314
+ if check_after_fail["status"] != "fail":
315
+ raise AssertionError("Harness check should fail while an active plan has a failed quality gate")
316
+ passing_score = run_manager(
317
+ "quality-score",
318
+ "--repo",
319
+ str(repo),
320
+ "--plan",
321
+ relative_plan,
322
+ "--product-correctness",
323
+ "9",
324
+ "--ux-operator-clarity",
325
+ "8",
326
+ "--architecture-maintainability",
327
+ "8",
328
+ "--reliability-observability",
329
+ "8",
330
+ "--security-data-handling",
331
+ "8",
332
+ *quality_note_args(
333
+ product="Requested behavior is complete",
334
+ architecture="Plan closure now has a deterministic quality gate",
335
+ ),
336
+ )
337
+ if passing_score["status"] != "pass":
338
+ raise AssertionError("Scores at or above the minimum should pass")
339
+ close_result = run_manager(
340
+ "plan-close",
341
+ "--repo",
342
+ str(repo),
343
+ "--plan",
344
+ relative_plan,
345
+ "--summary",
346
+ "Closed after writing durable knowledge.",
347
+ )
348
+ if close_result["status"] != "closed":
349
+ raise AssertionError("Plan should close after knowledge is marked written")
350
+ if plan_path.exists():
351
+ raise AssertionError("Active plan should be moved after close")
352
+ assert_exists(repo, "docs/exec-plans/completed/" + plan_path.name)
353
+ check_result = run_manager("check", "--repo", str(repo))
354
+ if check_result["status"] != "pass":
355
+ raise AssertionError("Harness check should pass after plan closure")
356
+
357
+ formatted_plan = create_formatted_plan(repo)
358
+ formatted_relative_plan = str(formatted_plan.resolve().relative_to(repo.resolve()))
359
+ formatted_fact = "snake.sh is the single runtime entrypoint and owns terminal control directly with stty and tput"
360
+ with (repo / "ARCHITECTURE.md").open("a") as handle:
361
+ handle.write("\n`snake.sh` is the single runtime entrypoint and owns terminal control directly with `stty` and `tput`.\n")
362
+ run_manager(
363
+ "knowledge-mark-written",
364
+ "--repo",
365
+ str(repo),
366
+ "--plan",
367
+ formatted_relative_plan,
368
+ "--fact",
369
+ formatted_fact,
370
+ "--destination",
371
+ "ARCHITECTURE.md",
372
+ )
373
+
374
+ id_plan_result = run_manager(
375
+ "plan-start",
376
+ "--repo",
377
+ str(repo),
378
+ "--slug",
379
+ "id-knowledge-loop",
380
+ "--goal",
381
+ "Validate id-based durable knowledge closure",
382
+ )
383
+ id_plan_path = Path(id_plan_result["plan"])
384
+ id_relative_plan = str(id_plan_path.resolve().relative_to(repo.resolve()))
385
+ id_fact = "Runtime input is owned by the terminal runner and core game logic remains independent of terminal packages"
386
+ log_result = run_manager(
387
+ "knowledge-log",
388
+ "--repo",
389
+ str(repo),
390
+ "--plan",
391
+ id_relative_plan,
392
+ "--fact",
393
+ id_fact,
394
+ "--destination",
395
+ "ARCHITECTURE.md",
396
+ )
397
+ with (repo / "ARCHITECTURE.md").open("a") as handle:
398
+ handle.write(
399
+ "\nThe `main` package owns keyboard input and rendering, while `game` contains pure state transitions.\n"
400
+ )
401
+ evidence_file = tmp_root / "evidence.txt"
402
+ evidence_file.write_text("main package owns keyboard input and rendering\n")
403
+ run_manager(
404
+ "knowledge-mark-written",
405
+ "--repo",
406
+ str(repo),
407
+ "--plan",
408
+ id_relative_plan,
409
+ "--id",
410
+ log_result["id"],
411
+ "--evidence-file",
412
+ str(evidence_file),
413
+ )
414
+ run_manager(
415
+ "quality-score",
416
+ "--repo",
417
+ str(repo),
418
+ "--plan",
419
+ id_relative_plan,
420
+ "--product-correctness",
421
+ "8",
422
+ "--ux-operator-clarity",
423
+ "8",
424
+ "--architecture-maintainability",
425
+ "8",
426
+ "--reliability-observability",
427
+ "8",
428
+ "--security-data-handling",
429
+ "8",
430
+ *quality_note_args(
431
+ architecture="Id-based evidence closure was validated against ARCHITECTURE.md",
432
+ ),
433
+ )
434
+ plan_text = id_plan_path.read_text()
435
+ if id_fact in (repo / "ARCHITECTURE.md").read_text():
436
+ raise AssertionError("Id/evidence closure should not require appending the exact fact to the destination")
437
+ if "| evidence: main package owns keyboard input and rendering" not in plan_text:
438
+ raise AssertionError("Closed knowledge item should record the verification evidence")
439
+ run_manager(
440
+ "plan-close",
441
+ "--repo",
442
+ str(repo),
443
+ "--plan",
444
+ id_relative_plan,
445
+ "--summary",
446
+ "Closed with id-based evidence.",
447
+ )
448
+
449
+
450
+ def create_formatted_plan(repo):
451
+ plan_path = repo / "docs" / "exec-plans" / "active" / "formatted-plan.md"
452
+ plan_path.write_text(
453
+ """# Execution Plan: Formatted Plan
454
+
455
+ ## Quality Gate
456
+
457
+ Status: pass
458
+ Minimum score: 8.0
459
+ Average score: 8.0
460
+ Last scored: 2026-06-11T00:00:00Z
461
+
462
+ | Dimension | Score | Notes |
463
+ | --- | ---: | --- |
464
+ | Product correctness | 8.0 | ok |
465
+ | UX and operator clarity | 8.0 | ok |
466
+ | Architecture and maintainability | 8.0 | ok |
467
+ | Reliability and observability | 8.0 | ok |
468
+ | Security and data handling | 8.0 | ok |
469
+
470
+ ## Durable Knowledge To Capture
471
+
472
+ - [ ] `snake.sh` is the single runtime entrypoint and owns terminal control directly with `stty` and `tput`. -> `ARCHITECTURE.md`
473
+ """
474
+ )
475
+ return plan_path
476
+
477
+
478
+ def test_preserve_unmanaged_docs(tmp_root):
479
+ repo = tmp_root / "partial-repo"
480
+ repo.mkdir()
481
+ (repo / "AGENTS.md").write_text("# Existing user router\n\nKeep this custom content.\n")
482
+ answers = tmp_root / "partial-answers.json"
483
+ write_answers(answers)
484
+
485
+ result = run_manager("init", "--repo", str(repo), "--answers", str(answers))
486
+ if "AGENTS.md" not in result["skipped"]:
487
+ raise AssertionError("Unmanaged AGENTS.md should be skipped")
488
+ assert_contains(repo, "AGENTS.md", "Keep this custom content.")
489
+ assert_exists(repo, "docs/PLANS.md")
490
+
491
+
492
+ def test_phase_continuity_workstream(tmp_root):
493
+ repo = tmp_root / "phase-repo"
494
+ repo.mkdir()
495
+ answers = tmp_root / "phase-answers.json"
496
+ write_answers(answers, project_name="phase-demo")
497
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
498
+
499
+ plan_result = run_manager(
500
+ "plan-start",
501
+ "--repo",
502
+ str(repo),
503
+ "--slug",
504
+ "local-workbench-phase-1",
505
+ "--goal",
506
+ "Complete Local Workbench Phase 1",
507
+ )
508
+ plan_path = Path(plan_result["plan"])
509
+ relative_plan = str(plan_path.resolve().relative_to(repo.resolve()))
510
+ run_manager(
511
+ "quality-score",
512
+ "--repo",
513
+ str(repo),
514
+ "--plan",
515
+ relative_plan,
516
+ "--product-correctness",
517
+ "8",
518
+ "--ux-operator-clarity",
519
+ "8",
520
+ "--architecture-maintainability",
521
+ "8",
522
+ "--reliability-observability",
523
+ "8",
524
+ "--security-data-handling",
525
+ "8",
526
+ *quality_note_args(
527
+ product="Phase 1 plan state was validated.",
528
+ architecture="Workstream continuity was validated.",
529
+ ),
530
+ )
531
+ close_without_continuity = run_manager(
532
+ "plan-close",
533
+ "--repo",
534
+ str(repo),
535
+ "--plan",
536
+ relative_plan,
537
+ "--summary",
538
+ "Phase 1 done",
539
+ expect_success=False,
540
+ )
541
+ if close_without_continuity:
542
+ raise AssertionError("plan-close should not produce JSON when phase continuity blocks closure")
543
+ check_without_continuity = run_manager("check", "--repo", str(repo), expect_success=False)
544
+ issue_codes = {issue["code"] for issue in check_without_continuity["issues"]}
545
+ if "phase-mode-not-declared" not in issue_codes:
546
+ raise AssertionError("check should flag phased plans that do not declare continuation")
547
+
548
+ run_manager(
549
+ "phase-set",
550
+ "--repo",
551
+ str(repo),
552
+ "--plan",
553
+ relative_plan,
554
+ "--mode",
555
+ "multi-phase",
556
+ "--workstream",
557
+ "local-workbench",
558
+ "--current-phase",
559
+ "1",
560
+ "--next-phase",
561
+ "2",
562
+ "--continuation",
563
+ "docs/exec-plans/workstreams.md#local-workbench",
564
+ "--next-action",
565
+ "Create Phase 2 plan for command adapters",
566
+ "--resume-notes",
567
+ "Read completed Phase 1 plan and ARCHITECTURE.md before continuing",
568
+ )
569
+ close_without_workstream = run_manager(
570
+ "plan-close",
571
+ "--repo",
572
+ str(repo),
573
+ "--plan",
574
+ relative_plan,
575
+ "--summary",
576
+ "Phase 1 done",
577
+ expect_success=False,
578
+ )
579
+ if close_without_workstream:
580
+ raise AssertionError("plan-close should not allow a workstreams continuation without a ledger entry")
581
+ run_manager(
582
+ "workstream-upsert",
583
+ "--repo",
584
+ str(repo),
585
+ "--id",
586
+ "local-workbench",
587
+ "--status",
588
+ "active",
589
+ "--current-plan",
590
+ relative_plan,
591
+ "--next-action",
592
+ "Create Phase 2 plan for command adapters",
593
+ "--goal",
594
+ "Refactor local workbench into a maintainable terminal workflow",
595
+ "--resume-notes",
596
+ "Read completed Phase 1 plan and ARCHITECTURE.md before continuing",
597
+ )
598
+ assert_contains(repo, "docs/exec-plans/workstreams.md", "local-workbench")
599
+ assert_contains(repo, "docs/exec-plans/workstreams.md", "Create Phase 2 plan for command adapters")
600
+ close_result = run_manager(
601
+ "plan-close",
602
+ "--repo",
603
+ str(repo),
604
+ "--plan",
605
+ relative_plan,
606
+ "--summary",
607
+ "Phase 1 done; Phase 2 recovery is recorded in workstreams.",
608
+ )
609
+ if close_result["status"] != "closed":
610
+ raise AssertionError("Phased plan should close after continuity and workstream recovery are recorded")
611
+ completed_relative_plan = "docs/exec-plans/completed/" + plan_path.name
612
+ workstreams_text = (repo / "docs/exec-plans/workstreams.md").read_text()
613
+ if completed_relative_plan not in workstreams_text:
614
+ raise AssertionError("plan-close should update workstream ledger to the completed plan path")
615
+ if relative_plan in workstreams_text:
616
+ raise AssertionError("workstream ledger should not keep stale active plan references after plan-close")
617
+ broken = workstreams_text.replace(completed_relative_plan, relative_plan)
618
+ (repo / "docs/exec-plans/workstreams.md").write_text(broken)
619
+ broken_check = run_manager("check", "--repo", str(repo), expect_success=False)
620
+ broken_codes = {issue["code"] for issue in broken_check["issues"]}
621
+ if "missing-workstream-plan-reference" not in broken_codes:
622
+ raise AssertionError("check should fail when workstream ledger points to a missing plan")
623
+
624
+
625
+ def test_plan_path_canonicalization(tmp_root):
626
+ repo = tmp_root / "canonical-repo"
627
+ repo.mkdir()
628
+ answers = tmp_root / "canonical-answers.json"
629
+ write_answers(answers, project_name="canonical-demo")
630
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
631
+
632
+ plan_result = run_manager(
633
+ "plan-start",
634
+ "--repo",
635
+ str(repo),
636
+ "--slug",
637
+ "canonical-close",
638
+ "--goal",
639
+ "Close a plan when repo and plan paths use different filesystem spellings",
640
+ )
641
+ plan_path = Path(plan_result["plan"])
642
+ relative_plan = str(plan_path.resolve().relative_to(repo.resolve()))
643
+ run_manager(
644
+ "quality-score",
645
+ "--repo",
646
+ str(repo),
647
+ "--plan",
648
+ str(plan_path),
649
+ "--product-correctness",
650
+ "8",
651
+ "--ux-operator-clarity",
652
+ "8",
653
+ "--architecture-maintainability",
654
+ "8",
655
+ "--reliability-observability",
656
+ "8",
657
+ "--security-data-handling",
658
+ "8",
659
+ *quality_note_args(
660
+ architecture="Canonical plan path normalization was validated.",
661
+ ),
662
+ )
663
+ run_manager(
664
+ "workstream-upsert",
665
+ "--repo",
666
+ str(repo),
667
+ "--id",
668
+ "canonical-close",
669
+ "--status",
670
+ "active",
671
+ "--current-plan",
672
+ relative_plan,
673
+ "--next-action",
674
+ "Close after canonical path validation",
675
+ "--goal",
676
+ "Verify plan-close updates workstreams with normalized relative paths",
677
+ "--resume-notes",
678
+ "No special resume notes",
679
+ )
680
+
681
+ repo_arg = os.path.realpath(repo)
682
+ plan_arg = str(plan_path)
683
+ if repo_arg == str(repo) and plan_arg == str(plan_path.resolve()):
684
+ repo_arg = str(repo)
685
+ plan_arg = str(plan_path.resolve())
686
+
687
+ close_result = run_manager(
688
+ "plan-close",
689
+ "--repo",
690
+ repo_arg,
691
+ "--plan",
692
+ plan_arg,
693
+ "--summary",
694
+ "Closed with canonicalized plan path.",
695
+ )
696
+ if close_result["status"] != "closed":
697
+ raise AssertionError("plan-close should accept absolute plan paths inside the repo")
698
+ completed_relative_plan = "docs/exec-plans/completed/" + plan_path.name
699
+ workstreams_text = (repo / "docs/exec-plans/workstreams.md").read_text()
700
+ if completed_relative_plan not in workstreams_text:
701
+ raise AssertionError("canonicalized plan-close should update last completed plan")
702
+ if relative_plan in workstreams_text:
703
+ raise AssertionError("canonicalized plan-close should remove stale current plan references")
704
+ check_result = run_manager("check", "--repo", str(repo))
705
+ if check_result["status"] != "pass":
706
+ raise AssertionError("canonicalized plan-close should leave harness check passing")
707
+
708
+
709
+ def test_defect_recovery_loop(tmp_root):
710
+ repo = tmp_root / "defect-repo"
711
+ repo.mkdir()
712
+ answers = tmp_root / "defect-answers.json"
713
+ write_answers(answers, project_name="defect-demo")
714
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
715
+
716
+ plan_result = run_manager(
717
+ "plan-start",
718
+ "--repo",
719
+ str(repo),
720
+ "--slug",
721
+ "snake-tail-collision",
722
+ "--goal",
723
+ "Validate defect recovery when Snake tail-cell collision behavior fails",
724
+ )
725
+ plan_path = Path(plan_result["plan"])
726
+ relative_plan = str(plan_path.resolve().relative_to(repo.resolve()))
727
+ defect_summary = (
728
+ "Snake marks game over when the head moves into the current tail cell during a non-eating tick"
729
+ )
730
+ defect_result = run_manager(
731
+ "defect-log",
732
+ "--repo",
733
+ str(repo),
734
+ "--plan",
735
+ relative_plan,
736
+ "--severity",
737
+ "P1",
738
+ "--summary",
739
+ defect_summary,
740
+ "--evidence",
741
+ "go test ./internal/game -run TestCanMoveIntoVacatedTailCell failed",
742
+ expect_success=False,
743
+ )
744
+ defect_id = defect_result["id"]
745
+ plan_text = plan_path.read_text()
746
+ if "## Defects To Resolve" not in plan_text or defect_id not in plan_text:
747
+ raise AssertionError("defect-log should record the open defect in the plan")
748
+ if "Status: fail" not in plan_text:
749
+ raise AssertionError("defect-log should force the quality gate to fail")
750
+ if "Resolve all open defects" not in plan_text:
751
+ raise AssertionError("defect-log should turn the bug into rework input")
752
+
753
+ score_with_open_defect = run_manager(
754
+ "quality-score",
755
+ "--repo",
756
+ str(repo),
757
+ "--plan",
758
+ relative_plan,
759
+ "--product-correctness",
760
+ "10",
761
+ "--ux-operator-clarity",
762
+ "10",
763
+ "--architecture-maintainability",
764
+ "10",
765
+ "--reliability-observability",
766
+ "10",
767
+ "--security-data-handling",
768
+ "10",
769
+ *quality_note_args(
770
+ product="Open Snake defect remains unresolved.",
771
+ reliability="Open defect must block scoring despite high numeric values.",
772
+ ),
773
+ expect_success=False,
774
+ )
775
+ if score_with_open_defect["status"] != "fail" or defect_id not in score_with_open_defect["open_defects"]:
776
+ raise AssertionError("quality-score should fail while any defect is open")
777
+ check_with_open_defect = run_manager("check", "--repo", str(repo), expect_success=False)
778
+ issue_codes = {issue["code"] for issue in check_with_open_defect["issues"]}
779
+ if "open-defect" not in issue_codes:
780
+ raise AssertionError("check should surface unresolved defects")
781
+ close_with_open_defect = run_manager(
782
+ "plan-close",
783
+ "--repo",
784
+ str(repo),
785
+ "--plan",
786
+ relative_plan,
787
+ "--summary",
788
+ "Should not close with open defects",
789
+ expect_success=False,
790
+ )
791
+ if close_with_open_defect:
792
+ raise AssertionError("plan-close should not close while defects are open")
793
+
794
+ run_manager(
795
+ "defect-resolve",
796
+ "--repo",
797
+ str(repo),
798
+ "--plan",
799
+ relative_plan,
800
+ "--id",
801
+ defect_id,
802
+ "--fix-evidence",
803
+ "go test ./internal/game -run TestCanMoveIntoVacatedTailCell passed",
804
+ )
805
+ plan_text_after_resolve = plan_path.read_text()
806
+ if f"- [x] [bug:{defect_id}]" not in plan_text_after_resolve:
807
+ raise AssertionError("defect-resolve should close the defect checkbox")
808
+ if "Defects resolved. Re-run validation and `quality-score` before closing." not in plan_text_after_resolve:
809
+ raise AssertionError("defect-resolve should require a fresh quality score")
810
+
811
+ passing_score = run_manager(
812
+ "quality-score",
813
+ "--repo",
814
+ str(repo),
815
+ "--plan",
816
+ relative_plan,
817
+ "--product-correctness",
818
+ "9",
819
+ "--ux-operator-clarity",
820
+ "8",
821
+ "--architecture-maintainability",
822
+ "8",
823
+ "--reliability-observability",
824
+ "9",
825
+ "--security-data-handling",
826
+ "10",
827
+ *quality_note_args(
828
+ product="Snake tail-cell defect was resolved with passing test evidence.",
829
+ reliability="Defect recovery was validated with fresh passing evidence.",
830
+ ),
831
+ )
832
+ if passing_score["status"] != "pass":
833
+ raise AssertionError("quality-score should pass after defects are resolved")
834
+ close_result = run_manager(
835
+ "plan-close",
836
+ "--repo",
837
+ str(repo),
838
+ "--plan",
839
+ relative_plan,
840
+ "--summary",
841
+ "Closed after defect recovery and fresh quality score.",
842
+ )
843
+ if close_result["status"] != "closed":
844
+ raise AssertionError("plan-close should close after defect recovery")
845
+ completed_plan = repo / "docs" / "exec-plans" / "completed" / plan_path.name
846
+ completed_text = completed_plan.read_text()
847
+ if "- [x] Add durable facts here as they emerge" in completed_text:
848
+ raise AssertionError("plan-close should not mark the default knowledge placeholder as completed")
849
+
850
+
851
+ def test_quality_score_requires_notes(tmp_root):
852
+ repo = tmp_root / "quality-notes-repo"
853
+ repo.mkdir()
854
+ answers = tmp_root / "quality-notes-answers.json"
855
+ write_answers(answers, project_name="quality-notes-demo")
856
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
857
+
858
+ plan_result = run_manager(
859
+ "plan-start",
860
+ "--repo",
861
+ str(repo),
862
+ "--slug",
863
+ "quality-notes",
864
+ "--goal",
865
+ "Validate quality-score evidence notes are required",
866
+ )
867
+ relative_plan = str(Path(plan_result["plan"]).resolve().relative_to(repo.resolve()))
868
+ missing_notes = run_manager(
869
+ "quality-score",
870
+ "--repo",
871
+ str(repo),
872
+ "--plan",
873
+ relative_plan,
874
+ "--product-correctness",
875
+ "9",
876
+ "--ux-operator-clarity",
877
+ "9",
878
+ "--architecture-maintainability",
879
+ "9",
880
+ "--reliability-observability",
881
+ "9",
882
+ "--security-data-handling",
883
+ "9",
884
+ expect_success=False,
885
+ )
886
+ if missing_notes["reason"] != "missing-quality-notes":
887
+ raise AssertionError("quality-score should fail with a missing-quality-notes reason")
888
+ if len(missing_notes["missing_notes"]) != 5:
889
+ raise AssertionError("quality-score should name every dimension missing an evidence note")
890
+ arguments = {item["argument"] for item in missing_notes["missing_notes"]}
891
+ if "--product-note" not in arguments or "--security-note" not in arguments:
892
+ raise AssertionError("quality-score should name the missing note arguments")
893
+
894
+ passing_score = run_manager(
895
+ "quality-score",
896
+ "--repo",
897
+ str(repo),
898
+ "--plan",
899
+ relative_plan,
900
+ "--product-correctness",
901
+ "9",
902
+ "--ux-operator-clarity",
903
+ "9",
904
+ "--architecture-maintainability",
905
+ "9",
906
+ "--reliability-observability",
907
+ "9",
908
+ "--security-data-handling",
909
+ "9",
910
+ *quality_note_args(
911
+ product="Product assertions were checked.",
912
+ ux="User workflow evidence was checked.",
913
+ architecture="Architecture evidence was checked.",
914
+ reliability="Validation command evidence was checked.",
915
+ security="Security evidence was checked.",
916
+ ),
917
+ )
918
+ if passing_score["status"] != "pass":
919
+ raise AssertionError("quality-score should pass when all evidence notes are present")
920
+ plan_text = Path(plan_result["plan"]).read_text()
921
+ if "No note provided" in plan_text:
922
+ raise AssertionError("quality-score should not write placeholder notes when evidence is required")
923
+
924
+
925
+ def test_knowledge_evidence_verbatim(tmp_root):
926
+ repo = tmp_root / "knowledge-evidence-repo"
927
+ repo.mkdir()
928
+ answers = tmp_root / "knowledge-evidence-answers.json"
929
+ write_answers(answers, project_name="knowledge-evidence-demo")
930
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
931
+
932
+ plan_result = run_manager(
933
+ "plan-start",
934
+ "--repo",
935
+ str(repo),
936
+ "--slug",
937
+ "knowledge-evidence",
938
+ "--goal",
939
+ "Validate durable knowledge evidence must be exact destination text",
940
+ )
941
+ plan_path = Path(plan_result["plan"])
942
+ relative_plan = str(plan_path.resolve().relative_to(repo.resolve()))
943
+ fact = "Snake non-growth movement may enter the current tail cell because the tail leaves during the same tick"
944
+ log_result = run_manager(
945
+ "knowledge-log",
946
+ "--repo",
947
+ str(repo),
948
+ "--plan",
949
+ relative_plan,
950
+ "--fact",
951
+ fact,
952
+ "--destination",
953
+ "docs/product-specs/snake.md",
954
+ )
955
+ destination = repo / "docs" / "product-specs" / "snake.md"
956
+ destination.parent.mkdir(parents=True, exist_ok=True)
957
+ exact_evidence = "On a non-eating tick, moving into the current tail cell is legal because the tail leaves during the same tick."
958
+ destination.write_text(f"# Snake Rules\n\n- {exact_evidence}\n")
959
+
960
+ paraphrase_result = run_manager(
961
+ "knowledge-mark-written",
962
+ "--repo",
963
+ str(repo),
964
+ "--plan",
965
+ relative_plan,
966
+ "--id",
967
+ log_result["id"],
968
+ "--evidence",
969
+ "docs/product-specs/snake.md now states the tail-vacating rule.",
970
+ expect_success=False,
971
+ )
972
+ if paraphrase_result:
973
+ raise AssertionError("Paraphrased knowledge evidence should not succeed")
974
+ plan_text_after_failure = plan_path.read_text()
975
+ if f"- [x] [id:{log_result['id']}]" in plan_text_after_failure:
976
+ raise AssertionError("Failed knowledge evidence should not close the knowledge item")
977
+
978
+ evidence_file = tmp_root / "snake-evidence.txt"
979
+ evidence_file.write_text(exact_evidence + "\n")
980
+ run_manager(
981
+ "knowledge-mark-written",
982
+ "--repo",
983
+ str(repo),
984
+ "--plan",
985
+ relative_plan,
986
+ "--id",
987
+ log_result["id"],
988
+ "--evidence-file",
989
+ str(evidence_file),
990
+ )
991
+ plan_text = plan_path.read_text()
992
+ if f"- [x] [id:{log_result['id']}]" not in plan_text:
993
+ raise AssertionError("Exact destination evidence should close the knowledge item")
994
+ if f"| evidence: {exact_evidence}" not in plan_text:
995
+ raise AssertionError("Closed knowledge item should record the exact verification evidence")
996
+
997
+
998
+ def test_evidence_prune_generated_artifacts(tmp_root):
999
+ repo = tmp_root / "prune-repo"
1000
+ repo.mkdir()
1001
+ answers = tmp_root / "prune-answers.json"
1002
+ write_answers(answers, project_name="prune-demo")
1003
+ run_manager("init", "--repo", str(repo), "--answers", str(answers))
1004
+
1005
+ generated = repo / "docs" / "generated"
1006
+ stale = generated / "old-layout.json"
1007
+ referenced = generated / "kept-layout.json"
1008
+ recent = generated / "recent-layout.json"
1009
+ managed = generated / "managed-starter.md"
1010
+ stale.write_text('{"old": true}\n')
1011
+ referenced.write_text('{"referenced": true}\n')
1012
+ recent.write_text('{"recent": true}\n')
1013
+ managed.write_text("<!-- harness-engine:managed -->\n# Starter\n")
1014
+ old_time = time.time() - (30 * 24 * 60 * 60)
1015
+ for path in [stale, referenced, managed]:
1016
+ os.utime(path, (old_time, old_time))
1017
+ (repo / "docs" / "PLANS.md").write_text(
1018
+ (repo / "docs" / "PLANS.md").read_text()
1019
+ + "\nKeep evidence at docs/generated/kept-layout.json for the closed mobile layout plan.\n"
1020
+ )
1021
+
1022
+ dry_run = run_manager("evidence-prune", "--repo", str(repo), "--older-than-days", "14")
1023
+ candidate_paths = {item["path"] for item in dry_run["candidates"]}
1024
+ if dry_run["mode"] != "dry-run" or dry_run["removed"]:
1025
+ raise AssertionError("evidence-prune should dry-run by default")
1026
+ if "docs/generated/old-layout.json" not in candidate_paths:
1027
+ raise AssertionError("stale unreferenced generated evidence should be a prune candidate")
1028
+ if "docs/generated/kept-layout.json" in candidate_paths:
1029
+ raise AssertionError("referenced generated evidence should not be a prune candidate")
1030
+ if "docs/generated/recent-layout.json" in candidate_paths:
1031
+ raise AssertionError("recent generated evidence should not be a prune candidate")
1032
+ if "docs/generated/managed-starter.md" in candidate_paths:
1033
+ raise AssertionError("managed starter files should not be prune candidates")
1034
+ if not stale.exists():
1035
+ raise AssertionError("dry-run should not delete candidates")
1036
+
1037
+ applied = run_manager(
1038
+ "evidence-prune",
1039
+ "--repo",
1040
+ str(repo),
1041
+ "--older-than-days",
1042
+ "14",
1043
+ "--apply",
1044
+ )
1045
+ if "docs/generated/old-layout.json" not in applied["removed"]:
1046
+ raise AssertionError("apply should remove stale unreferenced generated evidence")
1047
+ if stale.exists() or not referenced.exists() or not recent.exists() or not managed.exists():
1048
+ raise AssertionError("apply should delete only stale unreferenced evidence")
1049
+
1050
+
1051
+ def test_eval_report_shape(tmp_root):
1052
+ case_metadata = load_case_metadata()
1053
+ report = build_report(
1054
+ [
1055
+ {
1056
+ "id": "empty-repo-init",
1057
+ "status": "pass",
1058
+ "description": case_metadata["empty-repo-init"]["description"],
1059
+ "score": 1.0,
1060
+ "duration_seconds": 0.01,
1061
+ "findings": [],
1062
+ "recommended_actions": [],
1063
+ },
1064
+ {
1065
+ "id": "frontend-analysis",
1066
+ "status": "fail",
1067
+ "description": case_metadata["frontend-analysis"]["description"],
1068
+ "score": 0.0,
1069
+ "duration_seconds": 0.02,
1070
+ "findings": ["Frontend repo should ask frontend confirmation questions"],
1071
+ "recommended_actions": ["Fix frontend-analysis before release."],
1072
+ },
1073
+ ]
1074
+ )
1075
+ if report["schema_version"] != "harness-eval-report.v1":
1076
+ raise AssertionError("Eval report should expose a stable schema version")
1077
+ if report["status"] != "fail" or report["score"] != 50:
1078
+ raise AssertionError("Eval report should expose aggregate status and score")
1079
+ if report["metrics"]["case_pass_rate"] != 0.5:
1080
+ raise AssertionError("Eval report should expose detailed aggregate metrics")
1081
+ if "case_results" not in report or len(report["case_results"]) != 2:
1082
+ raise AssertionError("Eval report should expose per-case results")
1083
+ failed_case = report["case_results"][1]
1084
+ if not failed_case["findings"] or not failed_case["recommended_actions"]:
1085
+ raise AssertionError("Failed eval cases should expose findings and recommended actions")
1086
+ if "Review `case_results`" not in report["user_message"]:
1087
+ raise AssertionError("Eval report should include a user-facing failure message")
1088
+
1089
+
1090
+ EVALS = [
1091
+ ("empty-repo-init", test_empty_repo_init),
1092
+ ("frontend-analysis", test_frontend_analysis),
1093
+ ("init-reconciles-existing-harness", test_init_reconciles_existing_harness),
1094
+ ("closed-loop-plan", test_closed_loop_plan),
1095
+ ("phase-continuity-workstream", test_phase_continuity_workstream),
1096
+ ("plan-path-canonicalization", test_plan_path_canonicalization),
1097
+ ("defect-recovery-loop", test_defect_recovery_loop),
1098
+ ("quality-score-requires-notes", test_quality_score_requires_notes),
1099
+ ("knowledge-evidence-verbatim", test_knowledge_evidence_verbatim),
1100
+ ("evidence-prune-generated-artifacts", test_evidence_prune_generated_artifacts),
1101
+ ("eval-report-shape", test_eval_report_shape),
1102
+ ("preserve-unmanaged-docs", test_preserve_unmanaged_docs),
1103
+ ]
1104
+
1105
+
1106
+ def build_report(results):
1107
+ passed = sum(1 for result in results if result["status"] == "pass")
1108
+ total = len(results)
1109
+ failed_results = [result for result in results if result["status"] == "fail"]
1110
+ return {
1111
+ "schema_version": "harness-eval-report.v1",
1112
+ "status": "pass" if passed == total else "fail",
1113
+ "score": round((passed / total) * 100) if total else 0,
1114
+ "summary": {
1115
+ "passed": passed,
1116
+ "failed": total - passed,
1117
+ "total": total,
1118
+ "message": (
1119
+ f"All {total} harness eval cases passed."
1120
+ if passed == total
1121
+ else f"{total - passed} of {total} harness eval cases failed."
1122
+ ),
1123
+ },
1124
+ "metrics": {
1125
+ "case_pass_rate": round(passed / total, 4) if total else 0,
1126
+ "case_fail_rate": round((total - passed) / total, 4) if total else 0,
1127
+ "failed_case_count": total - passed,
1128
+ },
1129
+ "case_results": results,
1130
+ "user_message": (
1131
+ "Harness evals passed. No release-blocking eval findings were detected."
1132
+ if passed == total
1133
+ else "Harness evals failed. Review `case_results` and fix the listed findings before handoff or release."
1134
+ ),
1135
+ "recommended_actions": [
1136
+ action
1137
+ for result in failed_results
1138
+ for action in result["recommended_actions"]
1139
+ ],
1140
+ }
1141
+
1142
+
1143
+ def main():
1144
+ results = []
1145
+ case_metadata = load_case_metadata()
1146
+ with tempfile.TemporaryDirectory() as tmp:
1147
+ tmp_root = Path(tmp)
1148
+ for eval_id, test_func in EVALS:
1149
+ started = time.monotonic()
1150
+ metadata = case_metadata.get(eval_id, {})
1151
+ try:
1152
+ test_func(tmp_root)
1153
+ results.append(
1154
+ {
1155
+ "id": eval_id,
1156
+ "status": "pass",
1157
+ "description": metadata.get("description", ""),
1158
+ "score": 1.0,
1159
+ "duration_seconds": round(time.monotonic() - started, 3),
1160
+ "findings": [],
1161
+ "recommended_actions": [],
1162
+ }
1163
+ )
1164
+ except Exception as error:
1165
+ message = str(error)
1166
+ results.append(
1167
+ {
1168
+ "id": eval_id,
1169
+ "status": "fail",
1170
+ "description": metadata.get("description", ""),
1171
+ "score": 0.0,
1172
+ "duration_seconds": round(time.monotonic() - started, 3),
1173
+ "findings": [message],
1174
+ "recommended_actions": [
1175
+ f"Reproduce `{eval_id}` locally with python3 skills/harness-engine/evals/run_evals.py.",
1176
+ "Treat the failing assertion as the next implementation input before release.",
1177
+ ],
1178
+ }
1179
+ )
1180
+
1181
+ report = build_report(results)
1182
+ print(json.dumps(report, indent=2) + "\n")
1183
+ if report["status"] != "pass":
1184
+ sys.exit(1)
1185
+
1186
+
1187
+ if __name__ == "__main__":
1188
+ main()