@riddledc/riddle-proof 0.8.36 → 0.8.38

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.
Files changed (36) hide show
  1. package/README.md +21 -0
  2. package/dist/advanced/index.d.cts +1 -1
  3. package/dist/advanced/index.d.ts +1 -1
  4. package/dist/advanced/proof-run-engine.d.cts +1 -1
  5. package/dist/advanced/proof-run-engine.d.ts +1 -1
  6. package/dist/chunk-6KYXX4OE.js +209 -0
  7. package/dist/{chunk-TWTEUS7R.js → chunk-DI2XNGEZ.js} +126 -1
  8. package/dist/{chunk-E25K5PDM.js → chunk-U44KBAPH.js} +168 -7
  9. package/dist/cli/index.js +3 -2
  10. package/dist/cli.cjs +488 -3
  11. package/dist/cli.js +3 -2
  12. package/dist/index.cjs +336 -1
  13. package/dist/index.d.cts +2 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +26 -18
  16. package/dist/pr-comment.cjs +235 -0
  17. package/dist/pr-comment.d.cts +41 -0
  18. package/dist/pr-comment.d.ts +41 -0
  19. package/dist/pr-comment.js +11 -0
  20. package/dist/{proof-run-engine-DYUu2mqY.d.cts → proof-run-engine-4dM37pEx.d.cts} +3 -3
  21. package/dist/{proof-run-engine-BmNYuOJ7.d.ts → proof-run-engine-BqaeqAze.d.ts} +3 -3
  22. package/dist/proof-run-engine.d.cts +1 -1
  23. package/dist/proof-run-engine.d.ts +1 -1
  24. package/dist/riddle-client.cjs +126 -1
  25. package/dist/riddle-client.d.cts +21 -1
  26. package/dist/riddle-client.d.ts +21 -1
  27. package/dist/riddle-client.js +1 -1
  28. package/dist/runtime/index.cjs +126 -1
  29. package/dist/runtime/index.d.cts +1 -1
  30. package/dist/runtime/index.d.ts +1 -1
  31. package/dist/runtime/index.js +1 -1
  32. package/dist/runtime/riddle-client.cjs +126 -1
  33. package/dist/runtime/riddle-client.d.cts +1 -1
  34. package/dist/runtime/riddle-client.d.ts +1 -1
  35. package/dist/runtime/riddle-client.js +1 -1
  36. package/package.json +7 -2
package/README.md CHANGED
@@ -968,6 +968,27 @@ way to a ready PR after proof and CI; `leave_draft: true` is only an explicit
968
968
  debug or user-request escape hatch. The notification adapter is where a host
969
969
  updates Discord, OpenClaw, GitHub, or another integration.
970
970
 
971
+ ## PR Proof Comments
972
+
973
+ Use `pr-comment` when a project already has a PR and a Riddle Proof output
974
+ directory, but is not using the full ship loop. It reads
975
+ `riddle-run-response.json` and `result.json`, builds a managed GitHub comment,
976
+ embeds the primary hosted screenshot when available, links structured artifacts,
977
+ summarizes check counts, and updates the existing Riddle Proof comment on
978
+ reruns.
979
+
980
+ ```sh
981
+ riddle-proof-loop pr-comment \
982
+ --proof-dir test-results/riddle-proof-docs \
983
+ --pr 228 \
984
+ --repo davisdiehl/riddle-site
985
+ ```
986
+
987
+ Use `--dry-run` to print the exact Markdown without touching GitHub, or
988
+ `--body-file proof-comment.md` to save the generated body for another tool. The
989
+ markdown builder is also exported from `@riddledc/riddle-proof/pr-comment` for
990
+ host integrations that want to post comments themselves.
991
+
971
992
  ## Runtime Scratch Space
972
993
 
973
994
  The packaged proof-run setup uses isolated git worktrees for before and after
@@ -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-DYUu2mqY.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-BmNYuOJ7.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-DYUu2mqY.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-BmNYuOJ7.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';
@@ -0,0 +1,209 @@
1
+ // src/pr-comment.ts
2
+ var RIDDLE_PROOF_PR_COMMENT_MARKER = "<!-- riddle-proof:pr-comment:v1 -->";
3
+ function asRecord(value) {
4
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5
+ }
6
+ function asArray(value) {
7
+ return Array.isArray(value) ? value : [];
8
+ }
9
+ function stringValue(value) {
10
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
11
+ }
12
+ function numberValue(value) {
13
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
14
+ }
15
+ function booleanValue(value) {
16
+ return typeof value === "boolean" ? value : void 0;
17
+ }
18
+ function artifactKind(name, url) {
19
+ const target = `${name} ${url}`.toLowerCase();
20
+ if (/\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(target)) return "image";
21
+ if (/\.(json|har|txt|md|html|log)(\?|#|$)/.test(target)) return "data";
22
+ return "artifact";
23
+ }
24
+ function artifactDisplayName(value, fallback) {
25
+ const raw = stringValue(value);
26
+ if (raw) return raw;
27
+ return fallback;
28
+ }
29
+ function collectArtifacts(runResponse) {
30
+ const proofResult = asRecord(runResponse.proofResult);
31
+ const outputs = asArray(proofResult.outputs);
32
+ const artifacts = [];
33
+ const seen = /* @__PURE__ */ new Set();
34
+ for (const [index, item] of outputs.entries()) {
35
+ const artifact = asRecord(item);
36
+ const url = stringValue(artifact.url);
37
+ if (!url || seen.has(url)) continue;
38
+ seen.add(url);
39
+ const name = artifactDisplayName(artifact.name, `artifact-${index + 1}`);
40
+ artifacts.push({
41
+ name,
42
+ url,
43
+ kind: artifactKind(name, url),
44
+ size_bytes: numberValue(artifact.size)
45
+ });
46
+ }
47
+ return artifacts;
48
+ }
49
+ function pageSummaries(result) {
50
+ const pages = [];
51
+ for (const page of asArray(result.pages)) {
52
+ const record = asRecord(page);
53
+ const route = stringValue(record.route) || stringValue(record.url) || "page";
54
+ const checks = asRecord(record.checks);
55
+ let passed = 0;
56
+ let failed = 0;
57
+ for (const value of Object.values(checks)) {
58
+ if (value === true) passed += 1;
59
+ if (value === false) failed += 1;
60
+ }
61
+ pages.push({ route, passed, failed });
62
+ }
63
+ return pages;
64
+ }
65
+ function summarizeExplicitChecks(value) {
66
+ let passed = 0;
67
+ let failed = 0;
68
+ const visit = (current, inChecks = false) => {
69
+ if (current === true && inChecks) {
70
+ passed += 1;
71
+ return;
72
+ }
73
+ if (current === false && inChecks) {
74
+ failed += 1;
75
+ return;
76
+ }
77
+ if (Array.isArray(current)) {
78
+ for (const item of current) visit(item, inChecks);
79
+ return;
80
+ }
81
+ if (current && typeof current === "object") {
82
+ for (const [key, item] of Object.entries(current)) {
83
+ visit(item, inChecks || key === "checks");
84
+ }
85
+ }
86
+ };
87
+ visit(value);
88
+ return { passed, failed };
89
+ }
90
+ function selectPrimaryImage(artifacts) {
91
+ const images = artifacts.filter((artifact) => artifact.kind === "image");
92
+ return images.find((artifact) => /after|proof|screenshot/i.test(artifact.name)) || images[0];
93
+ }
94
+ function summarizeRiddleProofPrComment(input) {
95
+ const runResponse = asRecord(input.runResponse);
96
+ const result = asRecord(input.result);
97
+ const proofResult = asRecord(runResponse.proofResult);
98
+ const preview = asRecord(runResponse.preview);
99
+ const artifacts = collectArtifacts(runResponse);
100
+ const pages = pageSummaries(result);
101
+ const checkSource = { ...result };
102
+ delete checkSource.ok;
103
+ const nestedChecks = summarizeExplicitChecks(checkSource);
104
+ const ok = booleanValue(result.ok) ?? booleanValue(runResponse.ok) ?? null;
105
+ return {
106
+ ok,
107
+ status: stringValue(proofResult.status),
108
+ job_id: stringValue(proofResult.job_id),
109
+ duration_ms: numberValue(proofResult.duration_ms),
110
+ proof_url: stringValue(runResponse.proofUrl),
111
+ preview_id: stringValue(preview.id),
112
+ preview_url: stringValue(preview.preview_url) || stringValue(preview.url),
113
+ preview_publish_recovered: booleanValue(preview.publish_recovered),
114
+ preview_publish_error: stringValue(preview.publish_error),
115
+ passed_checks: nestedChecks.passed,
116
+ failed_checks: nestedChecks.failed,
117
+ pages,
118
+ artifacts,
119
+ primary_image: selectPrimaryImage(artifacts)
120
+ };
121
+ }
122
+ function formatDuration(ms) {
123
+ if (typeof ms !== "number" || !Number.isFinite(ms)) return "";
124
+ const seconds = Math.max(0, Math.round(ms / 1e3));
125
+ const minutes = Math.floor(seconds / 60);
126
+ const remainder = seconds % 60;
127
+ return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
128
+ }
129
+ function markdownLink(label, url) {
130
+ return `[${label.replace(/\]/g, "\\]")}](${url})`;
131
+ }
132
+ function resultLabel(summary) {
133
+ if (summary.ok === true) return "passed";
134
+ if (summary.ok === false) return "failed";
135
+ return summary.status || "recorded";
136
+ }
137
+ function artifactRank(artifact) {
138
+ const name = artifact.name.toLowerCase();
139
+ if (name === "proof.json") return 0;
140
+ if (name === "result.json") return 1;
141
+ if (name.includes("proof") && name.endsWith(".json") && !name.includes("layout")) return 2;
142
+ if (name === "console.json") return 3;
143
+ if (artifact.kind === "data") return 10;
144
+ if (artifact.kind === "image") return 20;
145
+ return 30;
146
+ }
147
+ function buildRiddleProofPrCommentMarkdown(input) {
148
+ const summary = summarizeRiddleProofPrComment(input);
149
+ const title = input.title?.trim() || "Riddle Proof Evidence";
150
+ const lines = [
151
+ RIDDLE_PROOF_PR_COMMENT_MARKER,
152
+ `## ${title}`,
153
+ "",
154
+ `**Result:** ${resultLabel(summary)}`
155
+ ];
156
+ if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
157
+ if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
158
+ if (summary.status) lines.push(`**Riddle job status:** ${summary.status}`);
159
+ if (summary.job_id) lines.push(`**Riddle job:** \`${summary.job_id}\``);
160
+ if (summary.duration_ms) lines.push(`**Duration:** ${formatDuration(summary.duration_ms)}`);
161
+ if (summary.proof_url) lines.push(`**Proof URL:** ${markdownLink(summary.proof_url, summary.proof_url)}`);
162
+ if (summary.preview_id || summary.preview_url) {
163
+ const previewLabel = summary.preview_id ? `\`${summary.preview_id}\`` : "preview";
164
+ lines.push(`**Preview:** ${summary.preview_url ? markdownLink(previewLabel, summary.preview_url) : previewLabel}`);
165
+ }
166
+ if (summary.preview_publish_recovered) {
167
+ const detail = summary.preview_publish_error ? `: ${summary.preview_publish_error}` : "";
168
+ lines.push(`**Preview publish recovery:** recovered after publish error${detail}`);
169
+ }
170
+ lines.push(`**Checks:** ${summary.passed_checks} passed / ${summary.failed_checks} failed`);
171
+ lines.push("");
172
+ if (summary.primary_image) {
173
+ lines.push("### Screenshot");
174
+ lines.push(`![${summary.primary_image.name}](${summary.primary_image.url})`);
175
+ lines.push("");
176
+ }
177
+ if (summary.pages.length) {
178
+ lines.push("### Page Checks");
179
+ for (const page of summary.pages.slice(0, 12)) {
180
+ lines.push(`- \`${page.route}\`: ${page.passed} passed / ${page.failed} failed`);
181
+ }
182
+ if (summary.pages.length > 12) lines.push(`- ${summary.pages.length - 12} more page(s) omitted`);
183
+ lines.push("");
184
+ }
185
+ const linkedArtifacts = summary.artifacts.filter((artifact) => artifact.url !== summary.primary_image?.url).sort((left, right) => artifactRank(left) - artifactRank(right) || left.name.localeCompare(right.name)).slice(0, 20);
186
+ if (linkedArtifacts.length) {
187
+ lines.push("### Artifacts");
188
+ for (const artifact of linkedArtifacts) {
189
+ lines.push(`- ${markdownLink(artifact.name, artifact.url)}`);
190
+ }
191
+ if (summary.artifacts.length - (summary.primary_image ? 1 : 0) > linkedArtifacts.length) {
192
+ lines.push(`- ${summary.artifacts.length - (summary.primary_image ? 1 : 0) - linkedArtifacts.length} more artifact(s) omitted`);
193
+ }
194
+ lines.push("");
195
+ }
196
+ if (input.source?.trim()) {
197
+ lines.push(`_Source: ${input.source.trim()}_`);
198
+ } else {
199
+ lines.push("_Updated by `riddle-proof-loop pr-comment`._");
200
+ }
201
+ return `${lines.join("\n").trim()}
202
+ `;
203
+ }
204
+
205
+ export {
206
+ RIDDLE_PROOF_PR_COMMENT_MARKER,
207
+ summarizeRiddleProofPrComment,
208
+ buildRiddleProofPrCommentMarkdown
209
+ };
@@ -81,10 +81,68 @@ function previewDeployResultFromRecord(input) {
81
81
  function canRecoverPreviewPublish(error) {
82
82
  return error instanceof RiddleApiError && PREVIEW_PUBLISH_RECOVERY_STATUSES.has(error.status);
83
83
  }
84
+ function summarizePreviewDirectory(directory) {
85
+ const stack = [directory];
86
+ let fileCount = 0;
87
+ let totalBytes = 0;
88
+ while (stack.length) {
89
+ const current = stack.pop();
90
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
91
+ const fullPath = path.join(current, entry.name);
92
+ if (entry.isDirectory()) {
93
+ stack.push(fullPath);
94
+ continue;
95
+ }
96
+ if (!entry.isFile()) continue;
97
+ const stat = statSync(fullPath);
98
+ fileCount += 1;
99
+ totalBytes += stat.size;
100
+ }
101
+ }
102
+ return { file_count: fileCount, total_bytes: totalBytes };
103
+ }
104
+ function previewProgressEmitter(config, base) {
105
+ return async (snapshot) => {
106
+ if (!config.onPreviewProgress) return;
107
+ await config.onPreviewProgress({
108
+ label: base.label,
109
+ framework: base.framework,
110
+ directory: base.directory,
111
+ elapsed_ms: Math.max(0, Date.now() - base.startedAt),
112
+ warnings: base.warnings?.length ? base.warnings : void 0,
113
+ ...snapshot
114
+ });
115
+ };
116
+ }
84
117
  async function waitForPublishedPreview(config, input) {
118
+ await input.emitProgress?.({
119
+ stage: "publish_recovering",
120
+ id: input.id,
121
+ file_count: input.localSummary?.file_count,
122
+ total_bytes: input.localSummary?.total_bytes,
123
+ publish_error: input.publishError.message,
124
+ message: `publish returned ${input.publishError.status}; polling preview status`
125
+ });
85
126
  for (let attempt = 1; attempt <= PREVIEW_PUBLISH_RECOVERY_ATTEMPTS; attempt += 1) {
86
127
  const status = await riddleRequestJson(config, `/v1/preview/${input.id}`);
128
+ await input.emitProgress?.({
129
+ stage: "checking_status",
130
+ id: input.id,
131
+ attempt,
132
+ attempts: PREVIEW_PUBLISH_RECOVERY_ATTEMPTS,
133
+ status: typeof status.status === "string" ? status.status : null,
134
+ preview_url: typeof status.preview_url === "string" ? status.preview_url : void 0,
135
+ file_count: typeof status.file_count === "number" ? status.file_count : input.localSummary?.file_count,
136
+ total_bytes: typeof status.total_bytes === "number" ? status.total_bytes : input.localSummary?.total_bytes
137
+ });
87
138
  if (String(status.status || "") === "ready" && String(status.preview_url || "").trim()) {
139
+ await input.emitProgress?.({
140
+ stage: "ready",
141
+ id: input.id,
142
+ preview_url: String(status.preview_url),
143
+ file_count: typeof status.file_count === "number" ? status.file_count : input.localSummary?.file_count,
144
+ total_bytes: typeof status.total_bytes === "number" ? status.total_bytes : input.localSummary?.total_bytes
145
+ });
88
146
  return previewDeployResultFromRecord({
89
147
  record: status,
90
148
  id: input.id,
@@ -106,7 +164,17 @@ async function deployRiddlePreview(config, directory, label, framework = "static
106
164
  if (!directory?.trim()) throw new Error("directory is required");
107
165
  if (!label?.trim()) throw new Error("label is required");
108
166
  if (framework !== "spa" && framework !== "static") throw new Error("framework must be spa or static");
167
+ const startedAt = Date.now();
109
168
  const warnings = collectRiddlePreviewDeployWarnings(directory, framework);
169
+ const emitProgress = previewProgressEmitter(config, { label, framework, directory, startedAt, warnings });
170
+ await emitProgress({ stage: "validating", message: "checking preview input directory" });
171
+ const localSummary = summarizePreviewDirectory(directory);
172
+ await emitProgress({
173
+ stage: "creating",
174
+ file_count: localSummary.file_count,
175
+ total_bytes: localSummary.total_bytes,
176
+ message: "creating preview upload target"
177
+ });
110
178
  const created = await riddleRequestJson(config, "/v1/preview", {
111
179
  method: "POST",
112
180
  body: JSON.stringify({ framework, label })
@@ -114,10 +182,42 @@ async function deployRiddlePreview(config, directory, label, framework = "static
114
182
  const id = String(created.id || "");
115
183
  const uploadUrl = String(created.upload_url || "");
116
184
  if (!id || !uploadUrl) throw new Error("Riddle preview create response was missing id or upload_url.");
185
+ await emitProgress({
186
+ stage: "created",
187
+ id,
188
+ file_count: localSummary.file_count,
189
+ total_bytes: localSummary.total_bytes,
190
+ message: "preview upload target created"
191
+ });
117
192
  const scratch = mkdtempSync(path.join(tmpdir(), "riddle-preview-upload-"));
118
193
  const tarball = path.join(scratch, `${id}.tar.gz`);
194
+ let tarballBytes = 0;
119
195
  try {
196
+ await emitProgress({
197
+ stage: "archiving",
198
+ id,
199
+ file_count: localSummary.file_count,
200
+ total_bytes: localSummary.total_bytes,
201
+ message: "creating preview archive"
202
+ });
120
203
  execFileSync("tar", ["czf", tarball, "-C", directory, "."], { stdio: "pipe" });
204
+ tarballBytes = statSync(tarball).size;
205
+ await emitProgress({
206
+ stage: "archived",
207
+ id,
208
+ file_count: localSummary.file_count,
209
+ total_bytes: localSummary.total_bytes,
210
+ tarball_bytes: tarballBytes,
211
+ message: "preview archive created"
212
+ });
213
+ await emitProgress({
214
+ stage: "uploading",
215
+ id,
216
+ file_count: localSummary.file_count,
217
+ total_bytes: localSummary.total_bytes,
218
+ tarball_bytes: tarballBytes,
219
+ message: "uploading preview archive"
220
+ });
121
221
  const upload = await fetchFor(config)(uploadUrl, {
122
222
  method: "PUT",
123
223
  headers: { "Content-Type": "application/gzip" },
@@ -126,14 +226,37 @@ async function deployRiddlePreview(config, directory, label, framework = "static
126
226
  if (!upload.ok) {
127
227
  throw new RiddleApiError(uploadUrl, upload.status, await upload.text());
128
228
  }
229
+ await emitProgress({
230
+ stage: "uploaded",
231
+ id,
232
+ file_count: localSummary.file_count,
233
+ total_bytes: localSummary.total_bytes,
234
+ tarball_bytes: tarballBytes,
235
+ message: "preview archive uploaded"
236
+ });
129
237
  } finally {
130
238
  rmSync(scratch, { recursive: true, force: true });
131
239
  }
132
240
  const expiresAt = typeof created.expires_at === "string" ? created.expires_at : void 0;
133
241
  try {
242
+ await emitProgress({
243
+ stage: "publishing",
244
+ id,
245
+ file_count: localSummary.file_count,
246
+ total_bytes: localSummary.total_bytes,
247
+ tarball_bytes: tarballBytes || void 0,
248
+ message: "publishing preview"
249
+ });
134
250
  const published = await riddleRequestJson(config, `/v1/preview/${id}/publish`, {
135
251
  method: "POST"
136
252
  });
253
+ await emitProgress({
254
+ stage: "ready",
255
+ id,
256
+ preview_url: String(published.preview_url || ""),
257
+ file_count: typeof published.file_count === "number" ? published.file_count : localSummary.file_count,
258
+ total_bytes: typeof published.total_bytes === "number" ? published.total_bytes : localSummary.total_bytes
259
+ });
137
260
  return previewDeployResultFromRecord({ record: published, id, label, framework, expiresAt, warnings });
138
261
  } catch (error) {
139
262
  if (!canRecoverPreviewPublish(error)) throw error;
@@ -143,7 +266,9 @@ async function deployRiddlePreview(config, directory, label, framework = "static
143
266
  framework,
144
267
  expiresAt,
145
268
  publishError: error,
146
- warnings
269
+ warnings,
270
+ localSummary,
271
+ emitProgress
147
272
  });
148
273
  }
149
274
  }