@riddledc/riddle-proof 0.5.27 → 0.5.29

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.27",
3
+ "version": "0.5.29",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -42,6 +42,7 @@ with open(args_file) as f:
42
42
 
43
43
  mode = (s.get('mode') or '').strip().lower()
44
44
  reference = s.get('reference', 'both')
45
+ requested_reference = reference
45
46
  reference_note = ''
46
47
  verification_mode = (s.get('verification_mode') or 'proof').strip() or 'proof'
47
48
  s['verification_mode'] = verification_mode
@@ -68,6 +69,15 @@ if discord_url_match:
68
69
  if reference not in ('prod', 'before', 'both'):
69
70
  raise SystemExit('Invalid reference: ' + reference + '. Must be prod, before, or both.')
70
71
  s['reference'] = reference
72
+ prod_url_present = bool((s.get('prod_url') or '').strip())
73
+ s['reference_resolution'] = {
74
+ 'requested_reference': requested_reference,
75
+ 'effective_reference': reference,
76
+ 'prod_reference_requested': requested_reference in ('prod', 'both'),
77
+ 'prod_url_present': prod_url_present,
78
+ 'prod_reference_skipped': False,
79
+ 'prod_reference_skip_reason': '',
80
+ }
71
81
 
72
82
  # Infer a reasonable commit title during setup instead of forcing the caller to
73
83
  # fill boilerplate that can be derived from the requested change.
@@ -77,11 +87,17 @@ if not (s.get('commit_message') or '').strip():
77
87
  # Setup should not block on a missing production URL. If prod comparison was
78
88
  # requested but prod_url is not known yet, continue with a before-only setup so
79
89
  # the repo homework can happen first.
80
- if reference in ('prod', 'both') and not (s.get('prod_url') or '').strip():
90
+ if reference in ('prod', 'both') and not prod_url_present:
81
91
  s['requested_reference'] = reference
82
92
  reference = 'before'
83
93
  s['reference'] = reference
84
94
  reference_note = 'prod_url not provided; setup will continue with reference=before until prod is known.'
95
+ s['reference_resolution'].update({
96
+ 'effective_reference': reference,
97
+ 'prod_reference_skipped': True,
98
+ 'prod_reference_skip_reason': 'prod_url_not_provided',
99
+ 'note': reference_note,
100
+ })
85
101
 
86
102
  # Parse optional assertions JSON
87
103
  parsed_assertions = None
@@ -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 ''
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  ROOT = Path(__file__).resolve().parents[1]
12
12
  LIB = ROOT / 'lib'
13
13
  UTIL_PATH = LIB / 'util.py'
14
+ PREFLIGHT_PATH = LIB / 'preflight.py'
14
15
  RECON_PATH = LIB / 'recon.py'
15
16
  VERIFY_PATH = LIB / 'verify.py'
16
17
  AUTHOR_PATH = LIB / 'author.py'
@@ -593,6 +594,49 @@ def temporary_env(**updates):
593
594
  os.environ[key] = value
594
595
 
595
596
 
597
+ def run_preflight_records_prod_reference_skip_reason():
598
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-preflight-reference-'))
599
+ args_path = tempdir / 'args.json'
600
+ state_path = tempdir / 'state.json'
601
+ repo_dir = tempdir / 'repo'
602
+ try:
603
+ make_project(repo_dir, "export const routes = [{ path: '/pricing', element: <Pricing /> }];\n")
604
+ args_path.write_text(json.dumps({
605
+ 'repo': 'example/repo',
606
+ 'repo_dir': str(repo_dir),
607
+ 'mode': 'static',
608
+ 'reference': 'both',
609
+ 'prod_url': '',
610
+ 'change_request': 'Make the pricing CTA clearer',
611
+ 'commit_message': 'Make the pricing CTA clearer',
612
+ 'success_criteria': 'Pricing CTA is visible.',
613
+ 'verification_mode': 'text',
614
+ 'build_command': BUILD_SCRIPT,
615
+ 'build_output': 'build',
616
+ 'allow_static_preview_fallback': True,
617
+ 'server_path': '/pricing',
618
+ }, indent=2))
619
+ with temporary_env(
620
+ RIDDLE_PROOF_ARGS_FILE=str(args_path),
621
+ RIDDLE_PROOF_STATE_FILE=str(state_path),
622
+ ):
623
+ load_module('util_preflight_reference_skip', UTIL_PATH)
624
+ load_module('preflight_reference_skip', PREFLIGHT_PATH)
625
+ after_preflight = json.loads(state_path.read_text())
626
+ assert after_preflight['requested_reference'] == 'both'
627
+ assert after_preflight['reference'] == 'before'
628
+ assert after_preflight['reference_resolution']['requested_reference'] == 'both'
629
+ assert after_preflight['reference_resolution']['effective_reference'] == 'before'
630
+ assert after_preflight['reference_resolution']['prod_reference_skipped'] is True
631
+ assert after_preflight['reference_resolution']['prod_reference_skip_reason'] == 'prod_url_not_provided'
632
+ return {
633
+ 'ok': True,
634
+ 'reference_resolution': after_preflight['reference_resolution'],
635
+ }
636
+ finally:
637
+ shutil.rmtree(tempdir, ignore_errors=True)
638
+
639
+
596
640
  def base_state(tempdir: Path, *, reference='before', prod_url=''):
597
641
  before_dir = tempdir / 'before'
598
642
  after_dir = tempdir / 'after'
@@ -1646,6 +1690,42 @@ def run_ship_filters_tool_noise_when_staging():
1646
1690
  shutil.rmtree(tempdir, ignore_errors=True)
1647
1691
 
1648
1692
 
1693
+ def run_ship_preserves_literal_backticks_in_args():
1694
+ sys.modules.pop('util', None)
1695
+ source = SHIP_PATH.read_text()
1696
+ helpers_source = source.split('\ns = load_state()', 1)[0]
1697
+ namespace = {'__file__': str(SHIP_PATH)}
1698
+ exec(compile(helpers_source, str(SHIP_PATH), 'exec'), namespace)
1699
+
1700
+ title = 'Change button from `Start Run` to `Launch Run`'
1701
+ body = 'Proof request keeps `Start Run` and `Launch Run` literal.'
1702
+ args = namespace['gh_pr_create_args'](title, body, 'agent/test-backticks')
1703
+ assert args[0:3] == ['gh', 'pr', 'create'], args
1704
+ assert args[args.index('--title') + 1] == title, args
1705
+ assert args[args.index('--body') + 1] == body, args
1706
+
1707
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-ship-backticks-'))
1708
+ try:
1709
+ sp.run(['git', 'init', '-b', 'main'], cwd=tempdir, check=True, capture_output=True, text=True)
1710
+ sp.run(['git', 'config', 'user.email', 'test@example.com'], cwd=tempdir, check=True)
1711
+ sp.run(['git', 'config', 'user.name', 'Test User'], cwd=tempdir, check=True)
1712
+ (tempdir / 'tracked.txt').write_text('literal backticks\n')
1713
+ sp.run(['git', 'add', 'tracked.txt'], cwd=tempdir, check=True)
1714
+
1715
+ namespace['git_checked'](['commit', '-m', title], str(tempdir))
1716
+ subject = sp.run(
1717
+ ['git', 'log', '-1', '--pretty=%s'],
1718
+ cwd=tempdir,
1719
+ check=True,
1720
+ capture_output=True,
1721
+ text=True,
1722
+ ).stdout.strip()
1723
+ assert subject == title, subject
1724
+ return {'ok': True, 'subject': subject}
1725
+ finally:
1726
+ shutil.rmtree(tempdir, ignore_errors=True)
1727
+
1728
+
1649
1729
  def run_ship_resolves_real_pr_branch():
1650
1730
  sys.modules.pop('util', None)
1651
1731
  source = SHIP_PATH.read_text()
@@ -1704,6 +1784,7 @@ def run_ship_resolves_real_pr_branch():
1704
1784
 
1705
1785
  if __name__ == '__main__':
1706
1786
  payload = {
1787
+ 'preflight_reference_skip_reason': run_preflight_records_prod_reference_skip_reason(),
1707
1788
  'capture_artifact_enrichment': run_capture_artifact_enrichment(),
1708
1789
  'capture_diagnostics_redaction': run_capture_diagnostics_redact_sensitive_values(),
1709
1790
  'apply_auth_context': run_apply_auth_context_passes_supported_auth_payloads(),
@@ -1728,6 +1809,7 @@ if __name__ == '__main__':
1728
1809
  'ship_structured_after_evidence': run_ship_accepts_structured_after_evidence(),
1729
1810
  'ship_discord_thread_target': run_ship_discord_thread_target(),
1730
1811
  'ship_filters_tool_noise_when_staging': run_ship_filters_tool_noise_when_staging(),
1812
+ 'ship_preserves_literal_backticks_in_args': run_ship_preserves_literal_backticks_in_args(),
1731
1813
  'ship_resolves_real_pr_branch': run_ship_resolves_real_pr_branch(),
1732
1814
  }
1733
1815
  print(json.dumps(payload, indent=2))