claude-dev-env 1.60.0 → 1.62.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.
- package/CLAUDE.md +12 -0
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +1 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +17 -4
- package/audit-rubrics/prompts/category-f-silent-failures.md +1 -0
- package/bin/install.mjs +1 -1
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/code_rules_annotations_length.py +189 -10
- package/hooks/blocking/code_rules_dead_config_field.py +321 -0
- package/hooks/blocking/code_rules_enforcer.py +14 -0
- package/hooks/blocking/code_rules_orphan_css_class.py +196 -0
- package/hooks/blocking/config/verified_commit_constants.py +15 -2
- package/hooks/blocking/destructive_command_blocker.py +483 -61
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +240 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +432 -0
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_orphan_css_class.py +196 -0
- package/hooks/blocking/test_destructive_command_blocker.py +213 -0
- package/hooks/blocking/test_verification_verdict_store.py +212 -0
- package/hooks/blocking/test_verified_commit_gate.py +159 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +74 -95
- package/hooks/blocking/verification_verdict_store.py +240 -0
- package/hooks/blocking/verified_commit_gate.py +31 -9
- package/hooks/blocking/verifier_verdict_minter.py +46 -124
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
- package/hooks/hooks_constants/dead_config_field_constants.py +39 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +15 -0
- package/hooks/hooks_constants/orphan_css_class_constants.py +40 -0
- package/hooks/validation/mypy_validator.py +59 -7
- package/hooks/validation/test_mypy_validator.py +94 -0
- package/package.json +1 -1
- package/rules/orphan-css-class.md +23 -0
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +5 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +202 -13
- package/skills/autoconverge/workflow/converge.mjs +392 -51
- package/skills/autoconverge/workflow/test_render_report.py +55 -0
- package/skills/doc-gist/SKILL.md +3 -2
- package/skills/doc-gist/references/examples/21-decision-signoff.html +546 -0
- package/skills/doc-gist/references/examples/README.md +2 -2
- package/skills/task-build/SKILL.md +31 -0
|
@@ -6,6 +6,7 @@ path the verified_commit_gate hook runs.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib.util
|
|
9
|
+
import json
|
|
9
10
|
import pathlib
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
@@ -25,6 +26,8 @@ store_spec.loader.exec_module(store_module)
|
|
|
25
26
|
is_verification_exempt_diff = store_module.is_verification_exempt_diff
|
|
26
27
|
resolve_merge_base = store_module.resolve_merge_base
|
|
27
28
|
branch_surface_manifest = store_module.branch_surface_manifest
|
|
29
|
+
manifest_sha256 = store_module.manifest_sha256
|
|
30
|
+
workflow_verdict_covers_surface = store_module.workflow_verdict_covers_surface
|
|
28
31
|
|
|
29
32
|
constants_spec = importlib.util.spec_from_file_location(
|
|
30
33
|
"verified_commit_constants",
|
|
@@ -276,3 +279,212 @@ def test_production_change_is_gated_on_nonstandard_default_branch(
|
|
|
276
279
|
encoding="utf-8",
|
|
277
280
|
)
|
|
278
281
|
assert _exemption_for(work_dir) is False
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
MATCHING_MANIFEST_SHA256 = "a" * 64
|
|
285
|
+
OTHER_MANIFEST_SHA256 = "b" * 64
|
|
286
|
+
VERIFIER_AGENT_TYPE = "code-verifier"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _verdict_transcript_text(is_all_pass: bool, bound_manifest_sha256: str) -> str:
|
|
290
|
+
verdict_record = {
|
|
291
|
+
"all_pass": is_all_pass,
|
|
292
|
+
"findings": [],
|
|
293
|
+
"manifest_sha256": bound_manifest_sha256,
|
|
294
|
+
}
|
|
295
|
+
assistant_text = (
|
|
296
|
+
"Verification complete.\n\n```verdict\n"
|
|
297
|
+
+ json.dumps(verdict_record)
|
|
298
|
+
+ "\n```\n"
|
|
299
|
+
)
|
|
300
|
+
assistant_entry = {
|
|
301
|
+
"type": "assistant",
|
|
302
|
+
"message": {"content": [{"type": "text", "text": assistant_text}]},
|
|
303
|
+
}
|
|
304
|
+
return json.dumps(assistant_entry) + "\n"
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _write_agent_transcript(
|
|
308
|
+
subagents_dir: pathlib.Path,
|
|
309
|
+
agent_id: str,
|
|
310
|
+
agent_type: str,
|
|
311
|
+
transcript_text: str,
|
|
312
|
+
should_write_sidecar: bool,
|
|
313
|
+
) -> None:
|
|
314
|
+
workflow_dir = subagents_dir / "workflows" / "wf_x"
|
|
315
|
+
workflow_dir.mkdir(parents=True, exist_ok=True)
|
|
316
|
+
(workflow_dir / f"agent-{agent_id}.jsonl").write_text(
|
|
317
|
+
transcript_text, encoding="utf-8"
|
|
318
|
+
)
|
|
319
|
+
if should_write_sidecar:
|
|
320
|
+
(workflow_dir / f"agent-{agent_id}.meta.json").write_text(
|
|
321
|
+
json.dumps({"agentType": agent_type}), encoding="utf-8"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _session_transcript_path(tmp_path: pathlib.Path, session_id: str) -> pathlib.Path:
|
|
326
|
+
session_root = tmp_path / "projects" / "demo"
|
|
327
|
+
session_root.mkdir(parents=True)
|
|
328
|
+
transcript_path = session_root / f"{session_id}.jsonl"
|
|
329
|
+
transcript_path.write_text("", encoding="utf-8")
|
|
330
|
+
return transcript_path
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_workflow_verdict_covers_surface_true_for_matching_passing_verifier(
|
|
334
|
+
tmp_path: pathlib.Path,
|
|
335
|
+
) -> None:
|
|
336
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
337
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
338
|
+
_write_agent_transcript(
|
|
339
|
+
subagents_dir,
|
|
340
|
+
"01",
|
|
341
|
+
VERIFIER_AGENT_TYPE,
|
|
342
|
+
_verdict_transcript_text(True, MATCHING_MANIFEST_SHA256),
|
|
343
|
+
should_write_sidecar=True,
|
|
344
|
+
)
|
|
345
|
+
assert (
|
|
346
|
+
workflow_verdict_covers_surface(
|
|
347
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
348
|
+
)
|
|
349
|
+
is True
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def test_workflow_verdict_covers_surface_false_for_nonmatching_hash(
|
|
354
|
+
tmp_path: pathlib.Path,
|
|
355
|
+
) -> None:
|
|
356
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
357
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
358
|
+
_write_agent_transcript(
|
|
359
|
+
subagents_dir,
|
|
360
|
+
"01",
|
|
361
|
+
VERIFIER_AGENT_TYPE,
|
|
362
|
+
_verdict_transcript_text(True, OTHER_MANIFEST_SHA256),
|
|
363
|
+
should_write_sidecar=True,
|
|
364
|
+
)
|
|
365
|
+
assert (
|
|
366
|
+
workflow_verdict_covers_surface(
|
|
367
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
368
|
+
)
|
|
369
|
+
is False
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_workflow_verdict_covers_surface_false_for_all_pass_false(
|
|
374
|
+
tmp_path: pathlib.Path,
|
|
375
|
+
) -> None:
|
|
376
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
377
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
378
|
+
_write_agent_transcript(
|
|
379
|
+
subagents_dir,
|
|
380
|
+
"01",
|
|
381
|
+
VERIFIER_AGENT_TYPE,
|
|
382
|
+
_verdict_transcript_text(False, MATCHING_MANIFEST_SHA256),
|
|
383
|
+
should_write_sidecar=True,
|
|
384
|
+
)
|
|
385
|
+
assert (
|
|
386
|
+
workflow_verdict_covers_surface(
|
|
387
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
388
|
+
)
|
|
389
|
+
is False
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_workflow_verdict_covers_surface_false_for_non_verifier_sidecar(
|
|
394
|
+
tmp_path: pathlib.Path,
|
|
395
|
+
) -> None:
|
|
396
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
397
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
398
|
+
_write_agent_transcript(
|
|
399
|
+
subagents_dir,
|
|
400
|
+
"01",
|
|
401
|
+
"clean-coder",
|
|
402
|
+
_verdict_transcript_text(True, MATCHING_MANIFEST_SHA256),
|
|
403
|
+
should_write_sidecar=True,
|
|
404
|
+
)
|
|
405
|
+
assert (
|
|
406
|
+
workflow_verdict_covers_surface(
|
|
407
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
408
|
+
)
|
|
409
|
+
is False
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def test_workflow_verdict_covers_surface_false_for_missing_sidecar(
|
|
414
|
+
tmp_path: pathlib.Path,
|
|
415
|
+
) -> None:
|
|
416
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
417
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
418
|
+
_write_agent_transcript(
|
|
419
|
+
subagents_dir,
|
|
420
|
+
"01",
|
|
421
|
+
VERIFIER_AGENT_TYPE,
|
|
422
|
+
_verdict_transcript_text(True, MATCHING_MANIFEST_SHA256),
|
|
423
|
+
should_write_sidecar=False,
|
|
424
|
+
)
|
|
425
|
+
assert (
|
|
426
|
+
workflow_verdict_covers_surface(
|
|
427
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
428
|
+
)
|
|
429
|
+
is False
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def test_workflow_verdict_covers_surface_false_for_missing_subagents_dir(
|
|
434
|
+
tmp_path: pathlib.Path,
|
|
435
|
+
) -> None:
|
|
436
|
+
transcript_path = _session_transcript_path(tmp_path, "sess1")
|
|
437
|
+
assert (
|
|
438
|
+
workflow_verdict_covers_surface(
|
|
439
|
+
str(transcript_path), MATCHING_MANIFEST_SHA256
|
|
440
|
+
)
|
|
441
|
+
is False
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def test_workflow_verdict_covers_surface_true_when_transcript_is_under_subagents(
|
|
446
|
+
tmp_path: pathlib.Path,
|
|
447
|
+
) -> None:
|
|
448
|
+
subagents_dir = tmp_path / "projects" / "demo" / "sess1" / "subagents"
|
|
449
|
+
_write_agent_transcript(
|
|
450
|
+
subagents_dir,
|
|
451
|
+
"01",
|
|
452
|
+
VERIFIER_AGENT_TYPE,
|
|
453
|
+
_verdict_transcript_text(True, MATCHING_MANIFEST_SHA256),
|
|
454
|
+
should_write_sidecar=True,
|
|
455
|
+
)
|
|
456
|
+
caller_transcript_path = (
|
|
457
|
+
subagents_dir / "workflows" / "wf_x" / "agent-00.jsonl"
|
|
458
|
+
)
|
|
459
|
+
caller_transcript_path.write_text("", encoding="utf-8")
|
|
460
|
+
assert (
|
|
461
|
+
workflow_verdict_covers_surface(
|
|
462
|
+
str(caller_transcript_path), MATCHING_MANIFEST_SHA256
|
|
463
|
+
)
|
|
464
|
+
is True
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_manifest_hash_cli_prints_live_surface_hash(tmp_path: pathlib.Path) -> None:
|
|
469
|
+
work_dir = _make_repo_with_origin(tmp_path)
|
|
470
|
+
(work_dir / "src" / "app.py").write_text(
|
|
471
|
+
"def add(left: int, right: int) -> int:\n return left - right\n",
|
|
472
|
+
encoding="utf-8",
|
|
473
|
+
)
|
|
474
|
+
merge_base_sha = resolve_merge_base(str(work_dir))
|
|
475
|
+
assert merge_base_sha is not None
|
|
476
|
+
surface_manifest_text = branch_surface_manifest(str(work_dir), merge_base_sha)
|
|
477
|
+
assert surface_manifest_text is not None
|
|
478
|
+
expected_hash = manifest_sha256(surface_manifest_text)
|
|
479
|
+
completed_process = subprocess.run(
|
|
480
|
+
[
|
|
481
|
+
sys.executable,
|
|
482
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
483
|
+
"--manifest-hash",
|
|
484
|
+
str(work_dir),
|
|
485
|
+
],
|
|
486
|
+
check=True,
|
|
487
|
+
capture_output=True,
|
|
488
|
+
text=True,
|
|
489
|
+
)
|
|
490
|
+
assert completed_process.stdout.strip() == expected_hash
|
|
@@ -6,8 +6,11 @@ decide what to gate.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib.util
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
9
11
|
import os
|
|
10
12
|
import pathlib
|
|
13
|
+
import subprocess
|
|
11
14
|
import sys
|
|
12
15
|
|
|
13
16
|
import pytest
|
|
@@ -25,6 +28,22 @@ assert gate_spec.loader is not None
|
|
|
25
28
|
gate_module = importlib.util.module_from_spec(gate_spec)
|
|
26
29
|
gate_spec.loader.exec_module(gate_module)
|
|
27
30
|
gated_repo_directories = gate_module.gated_repo_directories
|
|
31
|
+
deny_reason_for_directory = gate_module.deny_reason_for_directory
|
|
32
|
+
gate_main = gate_module.main
|
|
33
|
+
|
|
34
|
+
store_spec = importlib.util.spec_from_file_location(
|
|
35
|
+
"verification_verdict_store",
|
|
36
|
+
_HOOK_DIR / "verification_verdict_store.py",
|
|
37
|
+
)
|
|
38
|
+
assert store_spec is not None
|
|
39
|
+
assert store_spec.loader is not None
|
|
40
|
+
store_module = importlib.util.module_from_spec(store_spec)
|
|
41
|
+
store_spec.loader.exec_module(store_module)
|
|
42
|
+
resolve_merge_base = store_module.resolve_merge_base
|
|
43
|
+
branch_surface_manifest = store_module.branch_surface_manifest
|
|
44
|
+
manifest_sha256 = store_module.manifest_sha256
|
|
45
|
+
|
|
46
|
+
PRODUCTION_SOURCE = "def add(left: int, right: int) -> int:\n return left + right\n"
|
|
28
47
|
|
|
29
48
|
|
|
30
49
|
def test_plain_git_commit_is_gated() -> None:
|
|
@@ -366,3 +385,143 @@ def test_git_verb_inside_gh_comment_body_is_not_gated() -> None:
|
|
|
366
385
|
assert gated_repo_directories(
|
|
367
386
|
'gh pr comment -b "please git commit your work"', "/d"
|
|
368
387
|
) == []
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _run_git(repo_dir: pathlib.Path, *git_arguments: str) -> None:
|
|
391
|
+
subprocess.run(
|
|
392
|
+
["git", "-C", str(repo_dir), *git_arguments],
|
|
393
|
+
check=True,
|
|
394
|
+
capture_output=True,
|
|
395
|
+
text=True,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _make_gated_repo(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
400
|
+
origin_dir = tmp_path / "origin.git"
|
|
401
|
+
work_dir = tmp_path / "work"
|
|
402
|
+
work_dir.mkdir()
|
|
403
|
+
subprocess.run(
|
|
404
|
+
["git", "init", "--bare", "--initial-branch=main", str(origin_dir)],
|
|
405
|
+
check=True,
|
|
406
|
+
capture_output=True,
|
|
407
|
+
text=True,
|
|
408
|
+
)
|
|
409
|
+
_run_git(work_dir, "init", "--initial-branch=main")
|
|
410
|
+
_run_git(work_dir, "config", "user.email", "tests@example.com")
|
|
411
|
+
_run_git(work_dir, "config", "user.name", "Gate Tests")
|
|
412
|
+
(work_dir / "app.py").write_text(PRODUCTION_SOURCE, encoding="utf-8")
|
|
413
|
+
_run_git(work_dir, "add", "-A")
|
|
414
|
+
_run_git(work_dir, "commit", "-m", "base")
|
|
415
|
+
_run_git(work_dir, "remote", "add", "origin", str(origin_dir))
|
|
416
|
+
_run_git(work_dir, "push", "-u", "origin", "main")
|
|
417
|
+
(work_dir / "app.py").write_text(
|
|
418
|
+
"def add(left: int, right: int) -> int:\n return left - right\n",
|
|
419
|
+
encoding="utf-8",
|
|
420
|
+
)
|
|
421
|
+
return work_dir
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _live_surface_hash(work_dir: pathlib.Path) -> str:
|
|
425
|
+
merge_base_sha = resolve_merge_base(str(work_dir))
|
|
426
|
+
assert merge_base_sha is not None
|
|
427
|
+
surface_manifest_text = branch_surface_manifest(str(work_dir), merge_base_sha)
|
|
428
|
+
assert surface_manifest_text is not None
|
|
429
|
+
return manifest_sha256(surface_manifest_text)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _write_workflow_verdict(
|
|
433
|
+
transcript_path: pathlib.Path, bound_manifest_sha256: str
|
|
434
|
+
) -> None:
|
|
435
|
+
subagents_dir = transcript_path.with_suffix("") / "subagents"
|
|
436
|
+
workflow_dir = subagents_dir / "workflows" / "wf_x"
|
|
437
|
+
workflow_dir.mkdir(parents=True)
|
|
438
|
+
verdict_record = {
|
|
439
|
+
"all_pass": True,
|
|
440
|
+
"findings": [],
|
|
441
|
+
"manifest_sha256": bound_manifest_sha256,
|
|
442
|
+
}
|
|
443
|
+
assistant_text = (
|
|
444
|
+
"Verification complete.\n\n```verdict\n"
|
|
445
|
+
+ json.dumps(verdict_record)
|
|
446
|
+
+ "\n```\n"
|
|
447
|
+
)
|
|
448
|
+
assistant_entry = {
|
|
449
|
+
"type": "assistant",
|
|
450
|
+
"message": {"content": [{"type": "text", "text": assistant_text}]},
|
|
451
|
+
}
|
|
452
|
+
(workflow_dir / "agent-01.jsonl").write_text(
|
|
453
|
+
json.dumps(assistant_entry) + "\n", encoding="utf-8"
|
|
454
|
+
)
|
|
455
|
+
(workflow_dir / "agent-01.meta.json").write_text(
|
|
456
|
+
json.dumps({"agentType": "code-verifier"}), encoding="utf-8"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _isolate_home(monkeypatch: pytest.MonkeyPatch, fake_home: pathlib.Path) -> None:
|
|
461
|
+
home_text = str(fake_home)
|
|
462
|
+
monkeypatch.setenv("HOME", home_text)
|
|
463
|
+
monkeypatch.setenv("USERPROFILE", home_text)
|
|
464
|
+
monkeypatch.delenv("HOMEDRIVE", raising=False)
|
|
465
|
+
monkeypatch.delenv("HOMEPATH", raising=False)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_workflow_verdict_allows_commit_without_a_minted_verdict_file(
|
|
469
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
470
|
+
) -> None:
|
|
471
|
+
fake_home = tmp_path / "home"
|
|
472
|
+
fake_home.mkdir()
|
|
473
|
+
_isolate_home(monkeypatch, fake_home)
|
|
474
|
+
work_dir = _make_gated_repo(tmp_path)
|
|
475
|
+
live_surface_hash = _live_surface_hash(work_dir)
|
|
476
|
+
transcript_path = tmp_path / "projects" / "demo" / "sess1.jsonl"
|
|
477
|
+
transcript_path.parent.mkdir(parents=True)
|
|
478
|
+
transcript_path.write_text("", encoding="utf-8")
|
|
479
|
+
_write_workflow_verdict(transcript_path, live_surface_hash)
|
|
480
|
+
assert (
|
|
481
|
+
deny_reason_for_directory(str(work_dir), str(transcript_path)) is None
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_no_verdict_of_either_kind_denies_the_commit(
|
|
486
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
487
|
+
) -> None:
|
|
488
|
+
fake_home = tmp_path / "home"
|
|
489
|
+
fake_home.mkdir()
|
|
490
|
+
_isolate_home(monkeypatch, fake_home)
|
|
491
|
+
work_dir = _make_gated_repo(tmp_path)
|
|
492
|
+
transcript_path = tmp_path / "projects" / "demo" / "sess1.jsonl"
|
|
493
|
+
transcript_path.parent.mkdir(parents=True)
|
|
494
|
+
transcript_path.write_text("", encoding="utf-8")
|
|
495
|
+
deny_reason = deny_reason_for_directory(str(work_dir), str(transcript_path))
|
|
496
|
+
assert deny_reason is not None
|
|
497
|
+
assert "VERIFIED_COMMIT_GATE" in deny_reason
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _run_gate_main(
|
|
501
|
+
monkeypatch: pytest.MonkeyPatch, command_text: str, work_dir: pathlib.Path
|
|
502
|
+
) -> None:
|
|
503
|
+
payload_text = json.dumps(
|
|
504
|
+
{
|
|
505
|
+
"tool_name": "Bash",
|
|
506
|
+
"tool_input": {"command": command_text},
|
|
507
|
+
"cwd": str(work_dir),
|
|
508
|
+
"transcript_path": "",
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO(payload_text))
|
|
512
|
+
gate_main()
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def test_verification_bypass_marker_allows_an_otherwise_gated_commit(
|
|
516
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
517
|
+
capsys: pytest.CaptureFixture[str],
|
|
518
|
+
tmp_path: pathlib.Path,
|
|
519
|
+
) -> None:
|
|
520
|
+
fake_home = tmp_path / "home"
|
|
521
|
+
fake_home.mkdir()
|
|
522
|
+
_isolate_home(monkeypatch, fake_home)
|
|
523
|
+
work_dir = _make_gated_repo(tmp_path)
|
|
524
|
+
_run_gate_main(monkeypatch, "git commit -m x", work_dir)
|
|
525
|
+
assert "VERIFIED_COMMIT_GATE" in capsys.readouterr().out
|
|
526
|
+
_run_gate_main(monkeypatch, "git commit -m x # verify-skip", work_dir)
|
|
527
|
+
assert capsys.readouterr().out == ""
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
"""Tests for the agent-type gate in verifier_verdict_minter.
|
|
2
2
|
|
|
3
|
-
The minter mints a verdict only for a code-verifier stop event. The
|
|
4
|
-
SubagentStop payload names the stopping subagent
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
minter
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
can mint a passing verdict.
|
|
3
|
+
The minter mints a verdict only for a code-verifier stop event. The
|
|
4
|
+
SubagentStop payload names the stopping subagent's own transcript
|
|
5
|
+
(``agent_transcript_path``), which sits beside a harness-written
|
|
6
|
+
``agent-<id>.meta.json`` sidecar naming the spawning ``agentType``. These
|
|
7
|
+
tests build that sidecar and assert the minter gates on the resolved type and
|
|
8
|
+
on the shared MINTING_AGENT_TYPE constant, so a rename in config propagates to
|
|
9
|
+
the minter without a second edit. A malformed or non-string sidecar resolves
|
|
10
|
+
nothing, and an absent sidecar mints nothing — the main session writes neither
|
|
11
|
+
the transcript nor the sidecar, so it cannot forge a passing verdict. A
|
|
12
|
+
further test holds the shipped settings.json to the minter docstring's
|
|
13
|
+
anti-forgery claim: the main session is denied writes to the verdict
|
|
14
|
+
directory, so only this hook can mint a passing verdict.
|
|
16
15
|
"""
|
|
17
16
|
|
|
18
17
|
import importlib.util
|
|
@@ -21,8 +20,6 @@ import pathlib
|
|
|
21
20
|
import subprocess
|
|
22
21
|
import sys
|
|
23
22
|
|
|
24
|
-
import pytest
|
|
25
|
-
|
|
26
23
|
_HOOK_DIR = pathlib.Path(__file__).parent
|
|
27
24
|
if str(_HOOK_DIR) not in sys.path:
|
|
28
25
|
sys.path.insert(0, str(_HOOK_DIR))
|
|
@@ -51,100 +48,83 @@ constants_spec.loader.exec_module(constants_module)
|
|
|
51
48
|
MINTING_AGENT_TYPE = constants_module.MINTING_AGENT_TYPE
|
|
52
49
|
|
|
53
50
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"type": "tool_use",
|
|
61
|
-
"name": "Task",
|
|
62
|
-
"input": {"subagent_type": agent_type, "description": "Verify"},
|
|
63
|
-
"agentId": agent_id,
|
|
64
|
-
"agentType": agent_type,
|
|
65
|
-
"content": [{"type": "text", "text": "verification complete"}],
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
transcript_file.write_text(json.dumps(spawn_record) + "\n", encoding="utf-8")
|
|
51
|
+
def _write_sidecar(agent_transcript_file: pathlib.Path, agent_type: str) -> None:
|
|
52
|
+
sidecar_file = agent_transcript_file.with_name(f"{agent_transcript_file.stem}.meta.json")
|
|
53
|
+
sidecar_file.write_text(
|
|
54
|
+
json.dumps({"agentType": agent_type, "description": "Verify"}) + "\n",
|
|
55
|
+
encoding="utf-8",
|
|
56
|
+
)
|
|
71
57
|
|
|
72
58
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
def test_resolves_subagent_type_from_sidecar(tmp_path: pathlib.Path) -> None:
|
|
60
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
61
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
62
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
63
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
77
64
|
assert resolved_subagent_type(payload) == MINTING_AGENT_TYPE
|
|
78
65
|
|
|
79
66
|
|
|
80
|
-
def
|
|
81
|
-
tmp_path
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
_write_parent_transcript(transcript_file, "agent-7", MINTING_AGENT_TYPE)
|
|
85
|
-
payload = {"agent_id": "different-agent", "transcript_path": str(transcript_file)}
|
|
67
|
+
def test_resolves_none_when_sidecar_absent(tmp_path: pathlib.Path) -> None:
|
|
68
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
69
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
70
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
86
71
|
assert resolved_subagent_type(payload) is None
|
|
87
72
|
|
|
88
73
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
transcript_file = tmp_path / "parent.jsonl"
|
|
93
|
-
transcript_file.write_text("", encoding="utf-8")
|
|
74
|
+
def test_resolves_none_when_agent_transcript_path_empty() -> None:
|
|
75
|
+
assert resolved_subagent_type({"agent_transcript_path": ""}) is None
|
|
76
|
+
assert resolved_subagent_type({}) is None
|
|
94
77
|
|
|
95
|
-
def write_record_on_first_sleep(_seconds: float) -> None:
|
|
96
|
-
if transcript_file.read_text(encoding="utf-8"):
|
|
97
|
-
return
|
|
98
|
-
_write_parent_transcript(transcript_file, "agent-7", MINTING_AGENT_TYPE)
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
79
|
+
def test_resolves_none_when_sidecar_names_no_string_type(tmp_path: pathlib.Path) -> None:
|
|
80
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
81
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
82
|
+
sidecar_file = agent_transcript.with_name("agent-7.meta.json")
|
|
83
|
+
sidecar_file.write_text(json.dumps({"agentType": 123}), encoding="utf-8")
|
|
84
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
85
|
+
assert resolved_subagent_type(payload) is None
|
|
103
86
|
|
|
104
87
|
|
|
105
|
-
def
|
|
106
|
-
tmp_path
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
88
|
+
def test_unparseable_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
|
|
89
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
90
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
91
|
+
sidecar_file = agent_transcript.with_name("agent-7.meta.json")
|
|
92
|
+
sidecar_file.write_text("{not valid json", encoding="utf-8")
|
|
93
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
94
|
+
assert resolved_subagent_type(payload) is None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_invalid_utf8_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
|
|
98
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
99
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
100
|
+
sidecar_file = agent_transcript.with_name("agent-7.meta.json")
|
|
101
|
+
sidecar_file.write_bytes(b'{"agentType": "\xff\xfe bad"}')
|
|
102
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
103
|
+
assert resolved_subagent_type(payload) is None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_non_object_json_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
|
|
107
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
108
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
109
|
+
sidecar_file = agent_transcript.with_name("agent-7.meta.json")
|
|
110
|
+
sidecar_file.write_text(json.dumps(["agentType", "code-verifier"]), encoding="utf-8")
|
|
111
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
122
112
|
assert resolved_subagent_type(payload) is None
|
|
123
113
|
|
|
124
114
|
|
|
125
115
|
def test_non_verifier_agent_type_mints_nothing(tmp_path: pathlib.Path) -> None:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"transcript_path": str(transcript_file),
|
|
131
|
-
"agent_transcript_path": "",
|
|
132
|
-
"cwd": ".",
|
|
133
|
-
}
|
|
116
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
117
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
118
|
+
_write_sidecar(agent_transcript, "general-purpose")
|
|
119
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
134
120
|
assert mint_for_payload(payload) is None
|
|
135
121
|
|
|
136
122
|
|
|
137
|
-
def
|
|
138
|
-
tmp_path
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
payload = {
|
|
143
|
-
"agent_id": "agent-7",
|
|
144
|
-
"transcript_path": str(transcript_file),
|
|
145
|
-
"agent_transcript_path": "",
|
|
146
|
-
"cwd": ".",
|
|
147
|
-
}
|
|
123
|
+
def test_verifier_type_without_a_verdict_mints_nothing(tmp_path: pathlib.Path) -> None:
|
|
124
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
125
|
+
agent_transcript.write_text("", encoding="utf-8")
|
|
126
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
127
|
+
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
148
128
|
assert mint_for_payload(payload) is None
|
|
149
129
|
|
|
150
130
|
|
|
@@ -165,9 +145,7 @@ def test_clean_verifier_verdict_mints_a_verdict_file(tmp_path: pathlib.Path) ->
|
|
|
165
145
|
repo_root = tmp_path / "repo"
|
|
166
146
|
repo_root.mkdir()
|
|
167
147
|
_init_repo_with_upstream_and_edit(repo_root)
|
|
168
|
-
|
|
169
|
-
_write_parent_transcript(transcript_file, "agent-7", MINTING_AGENT_TYPE)
|
|
170
|
-
agent_transcript = tmp_path / "agent.jsonl"
|
|
148
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
171
149
|
agent_transcript.write_text(
|
|
172
150
|
json.dumps(
|
|
173
151
|
{
|
|
@@ -185,17 +163,18 @@ def test_clean_verifier_verdict_mints_a_verdict_file(tmp_path: pathlib.Path) ->
|
|
|
185
163
|
+ "\n",
|
|
186
164
|
encoding="utf-8",
|
|
187
165
|
)
|
|
166
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
188
167
|
payload = {
|
|
189
|
-
"agent_id": "agent-7",
|
|
190
|
-
"transcript_path": str(transcript_file),
|
|
191
168
|
"agent_transcript_path": str(agent_transcript),
|
|
192
169
|
"cwd": str(repo_root),
|
|
170
|
+
"agent_id": "a02b9583eedc74093",
|
|
193
171
|
}
|
|
194
172
|
verdict_path = mint_for_payload(payload)
|
|
195
173
|
try:
|
|
196
174
|
assert verdict_path is not None
|
|
197
175
|
verdict_record = json.loads(verdict_path.read_text(encoding="utf-8"))
|
|
198
176
|
assert verdict_record["all_pass"] is True
|
|
177
|
+
assert verdict_record["minted_from_agent_id"] == "a02b9583eedc74093"
|
|
199
178
|
finally:
|
|
200
179
|
if verdict_path is not None and verdict_path.exists():
|
|
201
180
|
verdict_path.unlink()
|