@riddledc/riddle-proof 0.5.26 → 0.5.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.5.26",
3
+ "version": "0.5.28",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -82,6 +82,24 @@ def git_stdout(args, repo_dir, timeout=30):
82
82
  return result.stdout.strip()
83
83
 
84
84
 
85
+ def git_checked(args, repo_dir, timeout=120):
86
+ result = sp.run(['git'] + args, cwd=repo_dir, capture_output=True, text=True, timeout=timeout)
87
+ if result.returncode != 0:
88
+ raise SystemExit('git ' + ' '.join(args) + ' failed: ' + (result.stderr or result.stdout)[:300])
89
+ return result
90
+
91
+
92
+ def gh_pr_create_args(title, body, branch):
93
+ return [
94
+ 'gh', 'pr', 'create',
95
+ '--draft',
96
+ '--title', str(title or '').strip() or 'Riddle Proof change',
97
+ '--body', str(body or ''),
98
+ '--base', 'main',
99
+ '--head', str(branch or ''),
100
+ ]
101
+
102
+
85
103
  def remote_branch_head(repo_dir, branch):
86
104
  result = sp.run(['git', 'ls-remote', 'origin', 'refs/heads/' + branch], cwd=repo_dir, capture_output=True, text=True, timeout=60)
87
105
  if result.returncode != 0:
@@ -623,11 +641,11 @@ if lines:
623
641
  print('Only ship-noise paths changed; skipping commit.')
624
642
  if s.get('pr_url'):
625
643
  if staged_paths:
626
- git('git commit --amend --no-edit', after_dir)
644
+ git_checked(['commit', '--amend', '--no-edit'], after_dir)
627
645
  push, push_info = push_existing_pr_branch(after_dir, branch, push_target)
628
646
  else:
629
647
  if staged_paths:
630
- git('git commit -m ' + json.dumps(s['commit_message']), after_dir)
648
+ git_checked(['commit', '-m', s['commit_message']], after_dir)
631
649
  push = sp.run(
632
650
  ['git', 'push', 'origin', push_target],
633
651
  cwd=after_dir,
@@ -657,8 +675,8 @@ else:
657
675
 
658
676
  # Create PR if needed
659
677
  if not s.get('pr_url'):
660
- q = sp.run('gh pr list --head "' + branch + '" --json url,number -q ".[0]"',
661
- shell=True, cwd=repo_dir, capture_output=True, text=True)
678
+ q = sp.run(['gh', 'pr', 'list', '--head', branch, '--json', 'url,number', '-q', '.[0]'],
679
+ cwd=repo_dir, capture_output=True, text=True)
662
680
  pr_url = ''
663
681
  if q.stdout.strip():
664
682
  try:
@@ -667,10 +685,10 @@ if not s.get('pr_url'):
667
685
  except:
668
686
  pass
669
687
  if not pr_url:
670
- c = sp.run('gh pr create --draft --title ' + json.dumps(s['commit_message']) +
671
- ' --body ' + json.dumps(s['change_request']) + ' --base main' +
672
- ' --head ' + branch,
673
- shell=True, cwd=repo_dir, capture_output=True, text=True)
688
+ c = sp.run(gh_pr_create_args(s.get('commit_message', ''), s.get('change_request', ''), branch),
689
+ cwd=repo_dir, capture_output=True, text=True)
690
+ if c.returncode != 0:
691
+ raise SystemExit('Failed to create PR: ' + (c.stderr or c.stdout)[:500])
674
692
  pr_url = c.stdout.strip().splitlines()[-1].strip() if c.returncode == 0 else ''
675
693
  s['pr_url'] = pr_url
676
694
  s['pr_number'] = pr_url.rstrip('/').split('/')[-1] if pr_url else ''
@@ -112,8 +112,12 @@ def select_capture_hint(state):
112
112
  if str(item).strip()
113
113
  ]
114
114
  matched_tokens = [token for token in current_tokens if token in hint_tokens]
115
+ mode_matches = bool(current_mode and hint_mode == current_mode)
116
+ root_hint = server_path == '/'
117
+ if not matched_tokens and mode_matches and not root_hint:
118
+ continue
115
119
  score = len(matched_tokens) * 3
116
- if current_mode and hint_mode == current_mode:
120
+ if mode_matches:
117
121
  score += 2
118
122
  if score <= 0:
119
123
  continue
@@ -957,6 +957,73 @@ def run_recon_prefers_hint_root_over_single_route_literal():
957
957
  shutil.rmtree(tempdir, ignore_errors=True)
958
958
 
959
959
 
960
+ def run_capture_hint_rejects_route_specific_mode_only_match():
961
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-hint-selection-'))
962
+ try:
963
+ util = load_module('util_hint_selection', UTIL_PATH)
964
+ repo_key = str(tempdir / 'repo')
965
+ state = {
966
+ 'repo': repo_key,
967
+ 'verification_mode': 'text',
968
+ 'change_request': 'Change the Tic Tac Toe reset button label to Reset Board.',
969
+ 'success_criteria': 'The Tic Tac Toe page shows Reset Board on /games/tic-tac-toe.',
970
+ }
971
+ _, cache_path = util.load_capture_hint_cache(state)
972
+ cache_file = Path(cache_path)
973
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
974
+ cache_file.write_text(json.dumps({
975
+ 'version': util.CAPTURE_HINT_CACHE_VERSION,
976
+ 'hints': [
977
+ {
978
+ 'saved_at': '2026-04-25T00:00:00Z',
979
+ 'verification_mode': 'text',
980
+ 'request_tokens': ['sequencer', 'monkberry', 'drum'],
981
+ 'server_path': '/games/drum-sequencer',
982
+ 'wait_for_selector': '.drum-sequencer h1',
983
+ 'observed_path': '/games/drum-sequencer',
984
+ }
985
+ ],
986
+ }))
987
+
988
+ selected = util.select_capture_hint(state)
989
+ applied = util.apply_capture_hint(state)
990
+
991
+ assert selected is None, selected
992
+ assert applied is None, applied
993
+ assert 'server_path' not in state, state
994
+
995
+ cache_file.write_text(json.dumps({
996
+ 'version': util.CAPTURE_HINT_CACHE_VERSION,
997
+ 'hints': [
998
+ {
999
+ 'saved_at': '2026-04-25T00:00:00Z',
1000
+ 'verification_mode': 'text',
1001
+ 'request_tokens': ['homepage', 'hero'],
1002
+ 'server_path': '/',
1003
+ 'wait_for_selector': '',
1004
+ 'observed_path': '/',
1005
+ }
1006
+ ],
1007
+ }))
1008
+
1009
+ root_state = {
1010
+ 'repo': repo_key,
1011
+ 'verification_mode': 'text',
1012
+ 'change_request': 'Make a tiny harmless copy tweak.',
1013
+ 'success_criteria': 'The changed copy is visible.',
1014
+ }
1015
+ root_applied = util.apply_capture_hint(root_state)
1016
+ assert root_applied is not None, root_applied
1017
+ assert root_state['server_path'] == '/', root_state
1018
+ assert root_state['server_path_source'] == 'hint_cache', root_state
1019
+
1020
+ return {'ok': True, 'route_specific_selected': selected, 'root_server_path': root_state['server_path']}
1021
+ finally:
1022
+ if 'cache_file' in locals():
1023
+ cache_file.unlink(missing_ok=True)
1024
+ shutil.rmtree(tempdir, ignore_errors=True)
1025
+
1026
+
960
1027
  def run_author_applies_supervisor_packet():
961
1028
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-supervisor-apply-'))
962
1029
  state_path = tempdir / 'state.json'
@@ -1579,6 +1646,42 @@ def run_ship_filters_tool_noise_when_staging():
1579
1646
  shutil.rmtree(tempdir, ignore_errors=True)
1580
1647
 
1581
1648
 
1649
+ def run_ship_preserves_literal_backticks_in_args():
1650
+ sys.modules.pop('util', None)
1651
+ source = SHIP_PATH.read_text()
1652
+ helpers_source = source.split('\ns = load_state()', 1)[0]
1653
+ namespace = {'__file__': str(SHIP_PATH)}
1654
+ exec(compile(helpers_source, str(SHIP_PATH), 'exec'), namespace)
1655
+
1656
+ title = 'Change button from `Start Run` to `Launch Run`'
1657
+ body = 'Proof request keeps `Start Run` and `Launch Run` literal.'
1658
+ args = namespace['gh_pr_create_args'](title, body, 'agent/test-backticks')
1659
+ assert args[0:3] == ['gh', 'pr', 'create'], args
1660
+ assert args[args.index('--title') + 1] == title, args
1661
+ assert args[args.index('--body') + 1] == body, args
1662
+
1663
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-ship-backticks-'))
1664
+ try:
1665
+ sp.run(['git', 'init', '-b', 'main'], cwd=tempdir, check=True, capture_output=True, text=True)
1666
+ sp.run(['git', 'config', 'user.email', 'test@example.com'], cwd=tempdir, check=True)
1667
+ sp.run(['git', 'config', 'user.name', 'Test User'], cwd=tempdir, check=True)
1668
+ (tempdir / 'tracked.txt').write_text('literal backticks\n')
1669
+ sp.run(['git', 'add', 'tracked.txt'], cwd=tempdir, check=True)
1670
+
1671
+ namespace['git_checked'](['commit', '-m', title], str(tempdir))
1672
+ subject = sp.run(
1673
+ ['git', 'log', '-1', '--pretty=%s'],
1674
+ cwd=tempdir,
1675
+ check=True,
1676
+ capture_output=True,
1677
+ text=True,
1678
+ ).stdout.strip()
1679
+ assert subject == title, subject
1680
+ return {'ok': True, 'subject': subject}
1681
+ finally:
1682
+ shutil.rmtree(tempdir, ignore_errors=True)
1683
+
1684
+
1582
1685
  def run_ship_resolves_real_pr_branch():
1583
1686
  sys.modules.pop('util', None)
1584
1687
  source = SHIP_PATH.read_text()
@@ -1648,6 +1751,7 @@ if __name__ == '__main__':
1648
1751
  'recon_preserves_query_route': run_recon_preserves_query_route(),
1649
1752
  'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),
1650
1753
  'recon_hint_root_preference': run_recon_prefers_hint_root_over_single_route_literal(),
1754
+ 'capture_hint_rejects_route_specific_mode_only_match': run_capture_hint_rejects_route_specific_mode_only_match(),
1651
1755
  'author_applies_supervisor_packet': run_author_applies_supervisor_packet(),
1652
1756
  'verify_requests_supervisor_assessment': run_verify_requests_supervisor_assessment(),
1653
1757
  'verify_structured_evidence_without_screenshot': run_verify_structured_evidence_without_screenshot(),
@@ -1660,6 +1764,7 @@ if __name__ == '__main__':
1660
1764
  'ship_structured_after_evidence': run_ship_accepts_structured_after_evidence(),
1661
1765
  'ship_discord_thread_target': run_ship_discord_thread_target(),
1662
1766
  'ship_filters_tool_noise_when_staging': run_ship_filters_tool_noise_when_staging(),
1767
+ 'ship_preserves_literal_backticks_in_args': run_ship_preserves_literal_backticks_in_args(),
1663
1768
  'ship_resolves_real_pr_branch': run_ship_resolves_real_pr_branch(),
1664
1769
  }
1665
1770
  print(json.dumps(payload, indent=2))