@riddledc/riddle-proof 0.8.32 → 0.8.34

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.
@@ -1,5 +1,5 @@
1
1
  export { b as runner } from '../runner-4LJ5z0D-.cjs';
2
2
  export { l as engineHarness } from '../engine-harness-LBfqbFSe.cjs';
3
3
  export { p as proofRunCore } from '../proof-run-core-B1GeqkR8.cjs';
4
- export { p as proofRunEngine } from '../proof-run-engine-DeHxtGnW.cjs';
4
+ export { p as proofRunEngine } from '../proof-run-engine-4dM37pEx.cjs';
5
5
  import '../types.cjs';
@@ -1,5 +1,5 @@
1
1
  export { b as runner } from '../runner-BdQpOkZD.js';
2
2
  export { l as engineHarness } from '../engine-harness-CMACHP6A.js';
3
3
  export { p as proofRunCore } from '../proof-run-core-B1GeqkR8.js';
4
- export { p as proofRunEngine } from '../proof-run-engine-DYfmd8d7.js';
4
+ export { p as proofRunEngine } from '../proof-run-engine-BqaeqAze.js';
5
5
  import '../types.js';
@@ -1,2 +1,2 @@
1
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-DeHxtGnW.cjs';
1
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-4dM37pEx.cjs';
2
2
  import '../proof-run-core-B1GeqkR8.cjs';
@@ -1,2 +1,2 @@
1
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-DYfmd8d7.js';
1
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from '../proof-run-engine-BqaeqAze.js';
2
2
  import '../proof-run-core-B1GeqkR8.js';
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "recon" | "author" | "ship" | "implement" | "verify" | "setup";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -1,2 +1,2 @@
1
1
  import './proof-run-core-B1GeqkR8.cjs';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-DeHxtGnW.cjs';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-4dM37pEx.cjs';
@@ -1,2 +1,2 @@
1
1
  import './proof-run-core-B1GeqkR8.js';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-DYfmd8d7.js';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-BqaeqAze.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.8.32",
3
+ "version": "0.8.34",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -320,7 +320,20 @@ def local_artifact_sources_fingerprint(local_sources):
320
320
  def public_proof_artifacts(state):
321
321
  published = state.get('proof_artifact_publication') or {}
322
322
  artifacts = published.get('artifacts') if isinstance(published, dict) else []
323
- public = [artifact for artifact in artifacts or [] if isinstance(artifact, dict) and (artifact.get('raw_url') or artifact.get('html_url'))]
323
+ attachments = state.get('proof_image_attachments') or {}
324
+ attachment_artifacts = attachments.get('artifacts') if isinstance(attachments, dict) else []
325
+ public = [
326
+ {
327
+ **artifact,
328
+ 'raw_url': artifact.get('attachment_url'),
329
+ 'html_url': artifact.get('attachment_url'),
330
+ 'embeddable': True,
331
+ 'attachment': True,
332
+ }
333
+ for artifact in attachment_artifacts or []
334
+ if isinstance(artifact, dict) and artifact.get('attachment_url')
335
+ ]
336
+ public.extend([artifact for artifact in artifacts or [] if isinstance(artifact, dict) and (artifact.get('raw_url') or artifact.get('html_url'))])
324
337
  for artifact in collect_proof_artifact_sources(state):
325
338
  url = str(artifact.get('url') or '').strip()
326
339
  if is_http_url(url):
@@ -328,6 +341,7 @@ def public_proof_artifacts(state):
328
341
  **artifact,
329
342
  'raw_url': url,
330
343
  'html_url': url,
344
+ 'embeddable': True,
331
345
  'published': False,
332
346
  })
333
347
  deduped = []
@@ -346,6 +360,8 @@ def first_public_artifact_url(state, role, kind=None):
346
360
  continue
347
361
  if kind and artifact.get('kind') != kind:
348
362
  continue
363
+ if artifact.get('kind') == 'image' and artifact.get('embeddable') is False:
364
+ continue
349
365
  return artifact.get('raw_url') or artifact.get('html_url') or artifact.get('url') or ''
350
366
  return ''
351
367
 
@@ -362,6 +378,92 @@ def resolve_github_repo_name(repo_dir):
362
378
  return ''
363
379
 
364
380
 
381
+ def resolve_github_repo_private(repo_dir):
382
+ result = sp.run(['gh', 'repo', 'view', '--json', 'isPrivate', '--jq', '.isPrivate'],
383
+ cwd=repo_dir, capture_output=True, text=True, timeout=30)
384
+ if result.returncode == 0:
385
+ text = result.stdout.strip().lower()
386
+ if text in ('true', 'false'):
387
+ return text == 'true'
388
+ return None
389
+
390
+
391
+ def github_file_url(repo_name, ref, path_value, mode='blob'):
392
+ safe_path = urllib.parse.quote(str(path_value or '').lstrip('/'), safe='/._-')
393
+ return 'https://github.com/' + repo_name + '/' + mode + '/' + ref + '/' + safe_path
394
+
395
+
396
+ def parse_github_image_markdown(line):
397
+ match = re.search(r'!\[[^\]]*\]\((https://github\.com/user-attachments/assets/[^)\s]+)\)', str(line or '').strip())
398
+ return match.group(1) if match else ''
399
+
400
+
401
+ def upload_local_images_to_github_attachments(state, repo_dir):
402
+ if truthy(os.environ.get('RIDDLE_PROOF_DISABLE_GITHUB_IMAGE_ATTACHMENTS')):
403
+ state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'disabled'}
404
+ save_state(state)
405
+ return state['proof_image_attachments']
406
+
407
+ image_sources = [
408
+ artifact for artifact in local_proof_artifact_sources(state)
409
+ if artifact.get('kind') == 'image' and artifact.get('path')
410
+ ]
411
+ if not image_sources:
412
+ state['proof_image_attachments'] = {'ok': True, 'skipped': True, 'reason': 'no local image artifacts'}
413
+ save_state(state)
414
+ return state['proof_image_attachments']
415
+
416
+ repo_name = resolve_github_repo_name(repo_dir)
417
+ if not repo_name:
418
+ state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'repo name unavailable'}
419
+ save_state(state)
420
+ return state['proof_image_attachments']
421
+
422
+ timeout_seconds = int(os.environ.get('RIDDLE_PROOF_GH_IMAGE_TIMEOUT_SECONDS') or '15')
423
+ command = ['gh', 'image'] + [artifact.get('path') for artifact in image_sources] + ['--repo', repo_name]
424
+ try:
425
+ result = sp.run(command, cwd=repo_dir, capture_output=True, text=True, timeout=timeout_seconds)
426
+ except sp.TimeoutExpired:
427
+ state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'gh image timed out'}
428
+ save_state(state)
429
+ return state['proof_image_attachments']
430
+
431
+ if result.returncode != 0:
432
+ state['proof_image_attachments'] = {
433
+ 'ok': False,
434
+ 'skipped': True,
435
+ 'reason': 'gh image failed',
436
+ 'stderr': (result.stderr or result.stdout or '')[:500],
437
+ }
438
+ save_state(state)
439
+ return state['proof_image_attachments']
440
+
441
+ urls = [parse_github_image_markdown(line) for line in result.stdout.splitlines()]
442
+ urls = [url for url in urls if url]
443
+ if not urls:
444
+ state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'gh image returned no attachment URLs'}
445
+ save_state(state)
446
+ return state['proof_image_attachments']
447
+
448
+ artifacts = []
449
+ for index, artifact in enumerate(image_sources):
450
+ if index >= len(urls):
451
+ break
452
+ artifacts.append({
453
+ **artifact,
454
+ 'attachment_url': urls[index],
455
+ 'attachment': True,
456
+ 'embeddable': True,
457
+ })
458
+ state['proof_image_attachments'] = {
459
+ 'ok': True,
460
+ 'repo': repo_name,
461
+ 'artifacts': artifacts,
462
+ }
463
+ save_state(state)
464
+ return state['proof_image_attachments']
465
+
466
+
365
467
  def write_artifact_readme(path_value, state, artifacts):
366
468
  lines = [
367
469
  '# Riddle Proof Artifacts',
@@ -403,6 +505,7 @@ def publish_local_proof_artifacts_to_github(state, repo_dir, pr_num):
403
505
  repo_name = resolve_github_repo_name(repo_dir)
404
506
  if not repo_name:
405
507
  raise SystemExit('Could not resolve GitHub repository name for proof artifact publication.')
508
+ repo_private = resolve_github_repo_private(repo_dir)
406
509
 
407
510
  run_id = safe_slug(state.get('run_id') or str(int(time.time())), 'run')
408
511
  pr_slug = safe_slug('pr-' + str(pr_num or 'unknown'), 'pr')
@@ -467,16 +570,18 @@ def publish_local_proof_artifacts_to_github(state, repo_dir, pr_num):
467
570
  for artifact in published:
468
571
  if artifact.get('published'):
469
572
  published_path = artifact.get('published_path')
470
- artifact['raw_url'] = 'https://raw.githubusercontent.com/' + repo_name + '/' + commit + '/' + published_path
471
- artifact['html_url'] = 'https://github.com/' + repo_name + '/blob/' + commit + '/' + published_path
573
+ artifact['raw_url'] = github_file_url(repo_name, commit, published_path, 'raw')
574
+ artifact['html_url'] = github_file_url(repo_name, commit, published_path, 'blob')
575
+ artifact['embeddable'] = False if repo_private is True and artifact.get('kind') == 'image' else True
472
576
  publication = {
473
577
  'ok': True,
474
578
  'branch': artifact_branch,
475
579
  'commit': commit,
476
580
  'repo': repo_name,
477
- 'html_url': 'https://github.com/' + repo_name + '/tree/' + commit + '/' + artifact_dir_name,
478
- 'manifest_url': 'https://github.com/' + repo_name + '/blob/' + commit + '/' + artifact_dir_name + '/proof-artifacts.json',
479
- 'readme_url': 'https://github.com/' + repo_name + '/blob/' + commit + '/' + artifact_dir_name + '/README.md',
581
+ 'repo_private': repo_private,
582
+ 'html_url': github_file_url(repo_name, commit, artifact_dir_name, 'tree'),
583
+ 'manifest_url': github_file_url(repo_name, commit, artifact_dir_name + '/proof-artifacts.json', 'blob'),
584
+ 'readme_url': github_file_url(repo_name, commit, artifact_dir_name + '/README.md', 'blob'),
480
585
  'source_fingerprint': source_fingerprint,
481
586
  'artifacts': published,
482
587
  }
@@ -1075,6 +1180,12 @@ if publication.get('ok') and not publication.get('skipped'):
1075
1180
  elif publication.get('skipped'):
1076
1181
  print('Proof artifact publication skipped: ' + publication.get('reason', 'unknown'))
1077
1182
 
1183
+ image_attachments = upload_local_images_to_github_attachments(s, repo_dir)
1184
+ if image_attachments.get('ok') and not image_attachments.get('skipped'):
1185
+ print('Proof images attached: ' + str(len(image_attachments.get('artifacts') or [])))
1186
+ elif image_attachments.get('skipped'):
1187
+ print('Proof image attachment skipped: ' + image_attachments.get('reason', 'unknown'))
1188
+
1078
1189
  # Post proof comment on PR
1079
1190
  body = '## Riddle Proof — Proof of Fix\n\n'
1080
1191
  body += '**Goal:** ' + s.get('change_request', '') + '\n\n'
@@ -1094,15 +1205,34 @@ if publication.get('ok') and not publication.get('skipped'):
1094
1205
  before_image = first_public_artifact_url(s, 'before', 'image')
1095
1206
  prod_image = first_public_artifact_url(s, 'prod', 'image')
1096
1207
  after_image = first_public_artifact_url(s, 'after', 'image')
1208
+ linked_image_artifacts = [
1209
+ artifact for artifact in public_artifacts
1210
+ if artifact.get('kind') == 'image'
1211
+ and artifact.get('embeddable') is False
1212
+ and (artifact.get('html_url') or artifact.get('raw_url'))
1213
+ ]
1097
1214
  if before_image:
1098
1215
  body += '### Before\n![before](' + before_image + ')\n\n'
1099
1216
  if prod_image:
1100
1217
  body += '### Prod\n![prod](' + prod_image + ')\n\n'
1101
1218
  if after_image:
1102
1219
  body += '### After\n![after](' + after_image + ')\n\n'
1220
+ elif linked_image_artifacts:
1221
+ body += '### After evidence\nA screenshot artifact was captured, but GitHub attachment upload was unavailable; linked image artifacts are listed below.\n\n'
1103
1222
  else:
1104
1223
  body += '### After evidence\nNo after screenshot was captured for this verification mode; structured evidence is summarized below.\n\n'
1105
1224
 
1225
+ if linked_image_artifacts:
1226
+ body += '### Image artifacts\n'
1227
+ for artifact in linked_image_artifacts[:12]:
1228
+ label = artifact.get('name') or artifact.get('filename') or 'image'
1229
+ url = artifact.get('html_url') or artifact.get('raw_url')
1230
+ body += '- [' + str(label) + '](' + str(url) + ')'
1231
+ if artifact.get('role'):
1232
+ body += ' (' + str(artifact.get('role')) + ')'
1233
+ body += '\n'
1234
+ body += '\n'
1235
+
1106
1236
  data_artifacts = [
1107
1237
  artifact for artifact in public_artifacts
1108
1238
  if artifact.get('kind') != 'image' and (artifact.get('html_url') or artifact.get('raw_url'))
@@ -27,7 +27,15 @@ import sys
27
27
 
28
28
  args = sys.argv[1:]
29
29
  if args[:3] == ["repo", "view", "--json"]:
30
- print("example/test-repo")
30
+ if "isPrivate" in args:
31
+ print("true")
32
+ else:
33
+ print("example/test-repo")
34
+ raise SystemExit(0)
35
+ if args and args[0] == "image":
36
+ image_paths = [arg for arg in args[1:] if not arg.startswith("--") and arg != "example/test-repo"]
37
+ for index, image_path in enumerate(image_paths):
38
+ print(f"![{os.path.basename(image_path)}](https://github.com/user-attachments/assets/00000000-0000-4000-8000-{index:012d})")
31
39
  raise SystemExit(0)
32
40
  if args[:2] == ["pr", "list"]:
33
41
  print("")
@@ -163,13 +171,16 @@ def main():
163
171
  assert publication.get("ok") is True, "proof artifact publication should be recorded"
164
172
  assert publication.get("artifacts"), "published artifact list should be recorded"
165
173
  assert updated.get("ship_report", {}).get("after_artifact_url", "").startswith(
166
- "https://raw.githubusercontent.com/example/test-repo/"
167
- ), "ship report should expose a public after artifact URL"
174
+ "https://github.com/user-attachments/assets/"
175
+ ), "ship report should expose a GitHub-hosted attachment URL for the after artifact"
168
176
 
169
177
  comment = comment_body_path.read_text(encoding="utf-8")
170
178
  assert "file://" not in comment, "PR proof comment must not expose local file URLs"
171
- assert "![after](https://raw.githubusercontent.com/example/test-repo/" in comment, (
172
- "PR proof comment should embed the GitHub-hosted after screenshot"
179
+ assert "raw.githubusercontent.com" not in comment, (
180
+ "PR proof comment must not depend on unauthenticated raw GitHub URLs"
181
+ )
182
+ assert "![after](https://github.com/user-attachments/assets/" in comment, (
183
+ "PR proof comment should embed the GitHub user-attachments screenshot when available"
173
184
  )
174
185
  assert "[proof.json](https://github.com/example/test-repo/blob/" in comment, (
175
186
  "PR proof comment should link the structured proof JSON"