@agent-native/core 0.84.18 → 0.84.21

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 (157) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/caller-auth.ts +37 -8
  5. package/corpus/core/src/a2a/client.ts +178 -67
  6. package/corpus/core/src/agent/production-agent.ts +19 -3
  7. package/corpus/core/src/agent/run-store.ts +180 -13
  8. package/corpus/core/src/agent/types.ts +1 -1
  9. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  10. package/corpus/core/src/cli/recap.ts +43 -34
  11. package/corpus/core/src/client/AssistantChat.tsx +49 -0
  12. package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
  13. package/corpus/core/src/client/active-run-state.ts +30 -0
  14. package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
  15. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +41 -3
  16. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +133 -0
  17. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +2 -2
  18. package/corpus/core/src/client/i18n.tsx +10 -5
  19. package/corpus/core/src/client/sse-event-processor.ts +147 -5
  20. package/corpus/core/src/extensions/html-shell.ts +17 -3
  21. package/corpus/core/src/extensions/theme.ts +39 -0
  22. package/corpus/core/src/file-upload/builder.ts +35 -14
  23. package/corpus/core/src/file-upload/types.ts +3 -0
  24. package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
  25. package/corpus/core/src/mcp/builtin-tools.ts +3 -0
  26. package/corpus/core/src/mcp/org-directory.ts +66 -20
  27. package/corpus/core/src/scripts/call-agent.ts +7 -2
  28. package/corpus/templates/analytics/actions/provider-api-request.ts +8 -1
  29. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -1
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +91 -33
  31. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
  32. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
  33. package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
  34. package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
  35. package/corpus/templates/clips/actions/create-recording.ts +7 -2
  36. package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
  37. package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
  38. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
  39. package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
  40. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
  41. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
  42. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
  43. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +65 -1
  44. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
  45. package/corpus/templates/clips/app/routes/record.tsx +48 -8
  46. package/corpus/templates/clips/changelog/2026-07-01-chrome-recording-uploads-now-serialize-chunks-to-avoid-resum.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
  55. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  56. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
  57. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +94 -29
  58. package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
  59. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
  60. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +14 -1
  61. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
  62. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
  63. package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
  64. package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
  65. package/corpus/templates/plan/app/global.css +1 -1
  66. package/dist/a2a/caller-auth.d.ts +1 -0
  67. package/dist/a2a/caller-auth.d.ts.map +1 -1
  68. package/dist/a2a/caller-auth.js +34 -8
  69. package/dist/a2a/caller-auth.js.map +1 -1
  70. package/dist/a2a/client.d.ts +4 -0
  71. package/dist/a2a/client.d.ts.map +1 -1
  72. package/dist/a2a/client.js +148 -58
  73. package/dist/a2a/client.js.map +1 -1
  74. package/dist/agent/production-agent.d.ts.map +1 -1
  75. package/dist/agent/production-agent.js +19 -4
  76. package/dist/agent/production-agent.js.map +1 -1
  77. package/dist/agent/run-store.d.ts +19 -6
  78. package/dist/agent/run-store.d.ts.map +1 -1
  79. package/dist/agent/run-store.js +163 -13
  80. package/dist/agent/run-store.js.map +1 -1
  81. package/dist/agent/types.d.ts +1 -0
  82. package/dist/agent/types.d.ts.map +1 -1
  83. package/dist/agent/types.js.map +1 -1
  84. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  85. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  86. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  87. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  88. package/dist/cli/recap.d.ts +3 -3
  89. package/dist/cli/recap.d.ts.map +1 -1
  90. package/dist/cli/recap.js +41 -34
  91. package/dist/cli/recap.js.map +1 -1
  92. package/dist/client/AssistantChat.d.ts +2 -0
  93. package/dist/client/AssistantChat.d.ts.map +1 -1
  94. package/dist/client/AssistantChat.js +36 -2
  95. package/dist/client/AssistantChat.js.map +1 -1
  96. package/dist/client/PoweredByBadge.d.ts +9 -3
  97. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  98. package/dist/client/PoweredByBadge.js +47 -15
  99. package/dist/client/PoweredByBadge.js.map +1 -1
  100. package/dist/client/active-run-state.d.ts +3 -0
  101. package/dist/client/active-run-state.d.ts.map +1 -1
  102. package/dist/client/active-run-state.js +24 -0
  103. package/dist/client/active-run-state.js.map +1 -1
  104. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  105. package/dist/client/agent-chat-adapter.js +5 -1
  106. package/dist/client/agent-chat-adapter.js.map +1 -1
  107. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  108. package/dist/client/extensions/EmbeddedExtension.js +32 -3
  109. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  110. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  111. package/dist/client/extensions/ExtensionViewer.js +59 -2
  112. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  113. package/dist/client/extensions/ExtensionsSidebarSection.js +2 -2
  114. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  115. package/dist/client/i18n.d.ts +1 -1
  116. package/dist/client/i18n.d.ts.map +1 -1
  117. package/dist/client/i18n.js +6 -2
  118. package/dist/client/i18n.js.map +1 -1
  119. package/dist/client/sse-event-processor.d.ts +2 -0
  120. package/dist/client/sse-event-processor.d.ts.map +1 -1
  121. package/dist/client/sse-event-processor.js +121 -5
  122. package/dist/client/sse-event-processor.js.map +1 -1
  123. package/dist/collab/awareness.d.ts +2 -2
  124. package/dist/collab/awareness.d.ts.map +1 -1
  125. package/dist/collab/routes.d.ts +1 -1
  126. package/dist/extensions/html-shell.d.ts.map +1 -1
  127. package/dist/extensions/html-shell.js +17 -3
  128. package/dist/extensions/html-shell.js.map +1 -1
  129. package/dist/extensions/theme.d.ts +9 -0
  130. package/dist/extensions/theme.d.ts.map +1 -1
  131. package/dist/extensions/theme.js +38 -0
  132. package/dist/extensions/theme.js.map +1 -1
  133. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  134. package/dist/file-upload/builder.d.ts.map +1 -1
  135. package/dist/file-upload/builder.js +22 -8
  136. package/dist/file-upload/builder.js.map +1 -1
  137. package/dist/file-upload/types.d.ts +5 -1
  138. package/dist/file-upload/types.d.ts.map +1 -1
  139. package/dist/file-upload/types.js.map +1 -1
  140. package/dist/integrations/a2a-continuation-processor.js +43 -18
  141. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  142. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  143. package/dist/mcp/builtin-tools.js +3 -0
  144. package/dist/mcp/builtin-tools.js.map +1 -1
  145. package/dist/mcp/org-directory.d.ts.map +1 -1
  146. package/dist/mcp/org-directory.js +58 -20
  147. package/dist/mcp/org-directory.js.map +1 -1
  148. package/dist/notifications/routes.d.ts +3 -3
  149. package/dist/observability/routes.d.ts +6 -6
  150. package/dist/progress/routes.d.ts +1 -1
  151. package/dist/resources/handlers.d.ts +3 -3
  152. package/dist/scripts/call-agent.d.ts.map +1 -1
  153. package/dist/scripts/call-agent.js +2 -2
  154. package/dist/scripts/call-agent.js.map +1 -1
  155. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  156. package/dist/server/transcribe-voice.d.ts +1 -1
  157. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pr-visual-recap-workflow.js","sourceRoot":"","sources":["../../src/cli/pr-visual-recap-workflow.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,4BAA4B,GACvC,qx0CAAqx0C,CAAC","sourcesContent":["/**\n * Bundled copy of .github/workflows/pr-visual-recap.yml used by\n * `agent-native recap setup`. Keep byte-identical to the source workflow.\n *\n * This file is generated from the workflow source; tests assert the exported\n * string stays in sync.\n */\n\nexport const PR_VISUAL_RECAP_WORKFLOW_YML =\n 'name: PR Visual Recap\\n\\n# Visual code review: a coding agent runs the repo\\'s visual-recap skill over the\\n# PR diff, publishes a plan, and upserts one sticky comment with a screenshot.\\n# Plain `pull_request` (NOT `pull_request_target`) so fork code never sees secrets.\\n\\non:\\n pull_request:\\n types: [opened, synchronize, reopened, ready_for_review, closed]\\n\\npermissions:\\n contents: read\\n\\nconcurrency:\\n group: pr-visual-recap-${{ github.event.pull_request.number }}\\n cancel-in-progress: true\\n\\nenv:\\n VISUAL_RECAP_AGENT: ${{ vars.VISUAL_RECAP_AGENT || \\'claude\\' }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\' }}\\n VISUAL_RECAP_SECRET_SCAN: ${{ vars.VISUAL_RECAP_SECRET_SCAN || \\'high-confidence\\' }}\\n\\njobs:\\n gate:\\n name: Gate\\n runs-on: ubuntu-latest\\n timeout-minutes: 10\\n permissions:\\n contents: read\\n issues: write\\n pull-requests: write\\n outputs:\\n run: ${{ steps.decide.outputs.run }}\\n agent: ${{ steps.decide.outputs.agent }}\\n steps:\\n - id: decide\\n uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0\\n env:\\n # Presence-only signals — never expose secret VALUES to the gate.\\n HAS_PLAN: ${{ secrets.PLAN_RECAP_TOKEN != \\'\\' }}\\n HAS_ANTHROPIC: ${{ secrets.ANTHROPIC_API_KEY != \\'\\' }}\\n HAS_OPENAI: ${{ secrets.OPENAI_API_KEY != \\'\\' }}\\n AGENT: ${{ env.VISUAL_RECAP_AGENT }}\\n VISUAL_RECAP_MODEL: ${{ vars.VISUAL_RECAP_MODEL }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ env.VISUAL_RECAP_SKILL_SOURCE }}\\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\\n with:\\n script: |\\n const pr = context.payload.pull_request;\\n const reasons = [];\\n\\n if (!pr) reasons.push(\\'no pull_request payload\\');\\n if (pr && pr.draft) reasons.push(\\'draft PR\\');\\n if (pr && context.payload.action === \\'closed\\' && !pr.merged) {\\n reasons.push(\\'closed without merge\\');\\n }\\n\\n // Fork PRs only receive repo secrets when the org/repo opts into\\n // GitHub\\'s \"Send secrets to workflows from pull requests\" setting\\n // (common in private orgs that use forks heavily). Gate on secret\\n // availability, not fork-ness: run on forks that have the token,\\n // and skip — with an actionable hint — those that don\\'t.\\n const headRepo = pr && pr.head && pr.head.repo && pr.head.repo.full_name;\\n const isFork = !!(pr && headRepo && headRepo !== process.env.GITHUB_REPOSITORY);\\n const isPrivate = !!(context.payload.repository && context.payload.repository.private);\\n const association = (pr && pr.author_association || \\'\\').toUpperCase();\\n const trustedAssociations = [\\'OWNER\\', \\'MEMBER\\', \\'COLLABORATOR\\'];\\n const isTrustedAuthor = trustedAssociations.includes(association);\\n if (isFork && process.env.HAS_PLAN !== \\'true\\') {\\n reasons.push(`fork PR (${headRepo}) without secret access — enable \"Send secrets to workflows from pull requests\" (and write tokens) in the repo/org Actions settings to run recaps on forks`);\\n }\\n\\n const login = (pr && pr.user && pr.user.login || \\'\\').toLowerCase();\\n const botAuthors = [\\'dependabot[bot]\\', \\'dependabot\\', \\'renovate[bot]\\', \\'renovate\\'];\\n if (botAuthors.includes(login)) reasons.push(`bot author (${login})`);\\n if (pr && pr.user && pr.user.type === \\'Bot\\') reasons.push(\\'bot author (type=Bot)\\');\\n\\n if (!isFork && process.env.HAS_PLAN !== \\'true\\') reasons.push(\\'PLAN_RECAP_TOKEN not configured\\');\\n\\n // Normalize + validate the agent so a mis-cased value can\\'t pass the\\n // gate and then match neither agent step below.\\n const agent = (process.env.AGENT || \\'claude\\').toLowerCase();\\n if (agent !== \\'claude\\' && agent !== \\'codex\\') {\\n reasons.push(`unsupported VISUAL_RECAP_AGENT \"${process.env.AGENT}\" (expected \"claude\" or \"codex\")`);\\n } else if (agent === \\'codex\\') {\\n if (process.env.HAS_OPENAI !== \\'true\\') reasons.push(\\'OPENAI_API_KEY not configured (codex backend)\\');\\n } else {\\n if (process.env.HAS_ANTHROPIC !== \\'true\\') reasons.push(\\'ANTHROPIC_API_KEY not configured (claude backend)\\');\\n }\\n\\n // Validate the model before it reaches the agent CLI.\\n const model = process.env.VISUAL_RECAP_MODEL || \\'\\';\\n if (model && !/^[a-zA-Z0-9._-]{1,80}$/.test(model)) {\\n reasons.push(`invalid VISUAL_RECAP_MODEL value (must match [a-zA-Z0-9._-]{1,80})`);\\n }\\n\\n const skillSource = (process.env.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\').toLowerCase();\\n if (![\\'auto\\', \\'latest\\', \\'repo\\'].includes(skillSource)) {\\n reasons.push(\\'invalid VISUAL_RECAP_SKILL_SOURCE value (expected \"auto\", \"latest\", or \"repo\")\\');\\n }\\n const usesRepoSkill = skillSource === \\'repo\\';\\n\\n // Self-modifying guard, evaluated in the trusted gate (runs NO\\n // PR-checked-out code): skip the ENTIRE job if the PR touches the\\n // repo-pinned skill instructions or any agent config the runner\\n // loads, so a PR can\\'t rewrite what the agent loads and exfiltrate\\n // secrets. With the default bundled skill source, visual skill and\\n // recap workflow files are reviewed content, not instructions loaded\\n // by the runner.\\n // Keep this guard for forks and untrusted public-repo PRs. Trusted\\n // same-repo authors may edit AGENTS.md/CLAUDE.md as normal reviewable\\n // content; the risk acceptance should not depend on repo visibility.\\n if (pr && (isFork || (!isPrivate && !isTrustedAuthor))) {\\n try {\\n const files = await github.paginate(github.rest.pulls.listFiles, {\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n pull_number: pr.number,\\n per_page: 100,\\n });\\n const isSensitive = (p) =>\\n (usesRepoSkill && /(^|\\\\/)skills\\\\/visual-(recap|plan|plans)\\\\//.test(p)) ||\\n /(^|\\\\/)\\\\.claude\\\\//.test(p) ||\\n /(^|\\\\/)CLAUDE\\\\.md$/.test(p) ||\\n /(^|\\\\/)AGENTS\\\\.md$/.test(p) ||\\n /(^|\\\\/)\\\\.mcp\\\\.json$/.test(p);\\n const hits = files.map((f) => f.filename).filter(isSensitive);\\n if (hits.length) {\\n reasons.push(`PR modifies recap-control files (${hits.slice(0, 3).join(\\', \\')}${hits.length > 3 ? \\', …\\' : \\'\\'}) — skipping so untrusted PR code never runs with secrets`);\\n }\\n } catch (e) {\\n // Fail closed: if the file list can\\'t be read, skip.\\n reasons.push(`could not list PR files for the self-modifying guard (${e.message}); skipping to be safe`);\\n }\\n }\\n\\n const run = reasons.length === 0;\\n core.setOutput(\\'run\\', run ? \\'true\\' : \\'false\\');\\n core.setOutput(\\'agent\\', agent);\\n if (run) {\\n core.info(`Visual recap will run (${agent}).`);\\n } else {\\n // Surface the skip reason as a run-summary annotation, not just a\\n // buried info log, so it\\'s clear in the Actions UI why we skipped.\\n core.notice(`Visual recap skipped: ${reasons.join(\\'; \\')}`);\\n }\\n\\n // When skipping, upsert a sticky recap comment with a short skip\\n // line so the PR always explains why the recap job did not run.\\n if (!run && pr) {\\n try {\\n const MARKER = \\'<!-- pr-visual-recap -->\\';\\n const { data: comments } = await github.rest.issues.listComments({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n issue_number: pr.number,\\n per_page: 100,\\n });\\n const existing = comments.find(\\n (c) => c.user && c.user.type === \\'Bot\\' && c.body && c.body.includes(MARKER)\\n );\\n const headShort = (process.env.HEAD_SHA || \\'\\').slice(0, 7);\\n const shaRef = headShort ? `\\\\`${headShort}\\\\`` : \\'latest push\\';\\n const primaryReason = reasons.filter(\\n (r) => !r.startsWith(\\'could not list PR files for the self-modifying guard\\')\\n )[0] || reasons[0] || \\'skipped\\';\\n const skipLine = `_Recap skipped for ${shaRef}: ${primaryReason}._`;\\n const baseBody = `${MARKER}\\\\n### Visual recap — skipped\\\\n\\\\nThe visual recap job did not run for this pull request. This is informational only and does **not** block the PR.`;\\n const withoutPrev = (existing && existing.body ? existing.body : baseBody)\\n .split(\\'\\\\n\\')\\n .filter((l) => !/_Recap skipped for .+_$/.test(l.trim()))\\n .join(\\'\\\\n\\')\\n .trimEnd();\\n const updatedBody = `${withoutPrev}\\\\n\\\\n${skipLine}`;\\n if (existing) {\\n await github.rest.issues.updateComment({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n comment_id: existing.id,\\n body: updatedBody,\\n });\\n } else {\\n await github.rest.issues.createComment({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n issue_number: pr.number,\\n body: updatedBody,\\n });\\n }\\n } catch (e) {\\n core.warning(`Could not update recap skip comment: ${e.message}`);\\n }\\n }\\n\\n recap:\\n name: Generate visual recap\\n needs: gate\\n if: needs.gate.outputs.run == \\'true\\'\\n runs-on: ubuntu-latest\\n timeout-minutes: 30\\n permissions:\\n actions: write\\n checks: write\\n contents: read\\n issues: write\\n pull-requests: write\\n env:\\n PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL || \\'https://plan.agent-native.com\\' }}\\n PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }}\\n GH_TOKEN: ${{ github.token }}\\n PR_NUMBER: ${{ github.event.pull_request.number }}\\n PR_STATE: ${{ github.event.pull_request.state }}\\n PR_MERGED: ${{ github.event.pull_request.merged }}\\n PR_MERGED_AT: ${{ github.event.pull_request.merged_at }}\\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\\n VISUAL_RECAP_MODEL: ${{ vars.VISUAL_RECAP_MODEL }}\\n VISUAL_RECAP_REASONING: ${{ vars.VISUAL_RECAP_REASONING }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\' }}\\n VISUAL_RECAP_SECRET_SCAN: ${{ vars.VISUAL_RECAP_SECRET_SCAN || \\'high-confidence\\' }}\\n steps:\\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\\n with:\\n fetch-depth: 0\\n # This job runs an agent over untrusted PR diff; don\\'t leave the token\\n # in .git/config (it uses GH_TOKEN for gh API calls, never git push).\\n persist-credentials: false\\n\\n # Dogfood trusted base-branch source inside this monorepo, else install the\\n # published package once. Never execute PR-head recap CLI code.\\n - name: Resolve recap CLI\\n id: cli\\n env:\\n # Optional: pin the consumer CLI version (e.g. \"1.2.3\"). Defaults to\\n # \"latest\" when unset. Set via repository variable RECAP_CLI_VERSION.\\n RECAP_CLI_VERSION: ${{ vars.RECAP_CLI_VERSION || \\'latest\\' }}\\n run: |\\n if [ \"$GITHUB_REPOSITORY\" = \"BuilderIO/agent-native\" ] && [ -f packages/core/src/cli/index.ts ]; then\\n echo \"local=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"local=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n\\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\\n if: steps.cli.outputs.local == \\'true\\'\\n with:\\n ref: ${{ github.event.pull_request.base.sha }}\\n path: .recap-cli-source\\n fetch-depth: 1\\n persist-credentials: false\\n\\n - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8\\n if: steps.cli.outputs.local == \\'true\\'\\n\\n - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\\n with:\\n node-version: \"22\"\\n cache: ${{ steps.cli.outputs.local == \\'true\\' && \\'pnpm\\' || \\'\\' }}\\n\\n - name: Install trusted workspace recap CLI\\n if: steps.cli.outputs.local == \\'true\\'\\n working-directory: .recap-cli-source\\n run: |\\n set -euo pipefail\\n pnpm install --frozen-lockfile --ignore-scripts\\n echo \"RECAP_CLI=$PWD/node_modules/.bin/tsx $PWD/packages/core/src/cli/index.ts\" >> \"$GITHUB_ENV\"\\n echo \"RECAP_PLAYWRIGHT=$PWD/node_modules/.bin/playwright\" >> \"$GITHUB_ENV\"\\n\\n - name: Install published recap CLI\\n if: steps.cli.outputs.local != \\'true\\'\\n env:\\n RECAP_CLI_VERSION: ${{ vars.RECAP_CLI_VERSION || \\'latest\\' }}\\n run: |\\n set -euo pipefail\\n VERSION=\"$RECAP_CLI_VERSION\"\\n if [ \"$VERSION\" = \"latest\" ]; then\\n VERSION=\"$(npm view @agent-native/core@latest version)\"\\n fi\\n for attempt in 1 2 3; do\\n if npm install --prefix \"$RUNNER_TEMP/recap-cli\" --no-audit --no-fund \"@agent-native/core@$VERSION\"; then\\n break\\n fi\\n if [ \"$attempt\" = \"3\" ]; then exit 1; fi\\n sleep $((attempt * 10))\\n done\\n echo \"RECAP_CLI=$RUNNER_TEMP/recap-cli/node_modules/.bin/agent-native\" >> \"$GITHUB_ENV\"\\n echo \"RECAP_PLAYWRIGHT=$RUNNER_TEMP/recap-cli/node_modules/.bin/playwright\" >> \"$GITHUB_ENV\"\\n\\n - name: Start visual recap check\\n id: recap_check\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n $RECAP_CLI recap check start --sha \"$HEAD_SHA\" --workflow-url \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"\\n\\n - name: Fetch pull request head\\n env:\\n PR_NUMBER_ENV: ${{ github.event.pull_request.number }}\\n run: |\\n set -euo pipefail\\n if git cat-file -e \"${HEAD_SHA}^{commit}\" 2>/dev/null; then\\n git update-ref refs/recap/pr-head \"$HEAD_SHA\"\\n else\\n AUTH_B64=\"$(printf \\'x-access-token:%s\\' \"$GH_TOKEN\" | base64 | tr -d \\'\\\\n\\')\"\\n git -c \"http.https://github.com/.extraheader=AUTHORIZATION: basic $AUTH_B64\" fetch origin \"pull/${PR_NUMBER_ENV}/head:refs/recap/pr-head\"\\n fi\\n FETCHED_SHA=\"$(git rev-parse refs/recap/pr-head)\"\\n if [ \"$FETCHED_SHA\" != \"$HEAD_SHA\" ]; then\\n echo \"FATAL: fetched PR head $FETCHED_SHA != event HEAD_SHA $HEAD_SHA — aborting to avoid recapping the wrong commit\"\\n exit 1\\n fi\\n\\n - name: Collect bounded diff\\n id: diff\\n env:\\n BASE_SHA: ${{ github.event.pull_request.base.sha }}\\n run: |\\n set -euo pipefail\\n $RECAP_CLI recap collect-diff --base \"$BASE_SHA\" --head refs/recap/pr-head --out recap.diff --stat recap.stat\\n\\n - name: Probe plan-app auth\\n id: auth_probe\\n if: steps.diff.outputs.tiny != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n # Hit the plan app\\'s action surface with the publish token. A 401 means\\n # the token is expired/revoked; surface it in the sticky comment so the\\n # repo owner knows to re-mint it instead of seeing a generic failure.\\n HTTP_STATUS=$(node -e \\'\\n const https = require(\"https\");\\n const url = new URL(\"/_agent-native/actions/record-recap-usage\", process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\");\\n const req = https.request(url, { method: \"POST\", headers: { \"authorization\": \"Bearer \" + process.env.PLAN_RECAP_TOKEN, \"content-type\": \"application/json\" }, timeout: 8000 }, (res) => { process.stdout.write(String(res.statusCode)); req.destroy(); });\\n req.on(\"error\", () => process.stdout.write(\"0\"));\\n req.end(JSON.stringify({ planId: \"__probe__\" }));\\n \\' 2>/dev/null || echo \"0\")\\n if [ \"$HTTP_STATUS\" = \"401\" ]; then\\n echo \"auth_failed=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"auth_failed=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n\\n - name: Probe plan-app route health\\n id: route_health\\n if: steps.diff.outputs.tiny != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n # Pre-publish health gate: confirm the plan app\\'s recap action routes\\n # are actually deployed BEFORE the agent runs. A 404 from\\n # create-visual-recap (POST) or get-plan-blocks (GET) means the\\n # plan-app deploy has not propagated yet (the client is ahead of the\\n # deployed server). Say that plainly here instead of letting the agent\\n # run and then fail confusingly at publish time. A 401 or 200 is\\n # healthy — the route exists, it just rejected/accepted the probe.\\n probe_status() {\\n ROUTE=\"$1\" METHOD=\"$2\" node -e \\'\\n const https = require(\"https\");\\n const base = process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\";\\n const url = new URL(process.env.ROUTE, base);\\n if (process.env.METHOD === \"GET\") url.searchParams.set(\"format\", \"reference\");\\n const req = https.request(url, { method: process.env.METHOD, headers: { \"authorization\": \"Bearer \" + (process.env.PLAN_RECAP_TOKEN || \"\"), \"content-type\": \"application/json\" }, timeout: 8000 }, (res) => { process.stdout.write(String(res.statusCode)); req.destroy(); });\\n req.on(\"error\", () => process.stdout.write(\"0\"));\\n req.on(\"timeout\", () => { process.stdout.write(\"0\"); req.destroy(); });\\n if (process.env.METHOD === \"POST\") { req.end(JSON.stringify({ __probe__: true })); } else { req.end(); }\\n \\' 2>/dev/null || echo \"0\"\\n }\\n CREATE_STATUS=\"$(probe_status /_agent-native/actions/create-visual-recap POST)\"\\n BLOCKS_STATUS=\"$(probe_status /_agent-native/actions/get-plan-blocks GET)\"\\n REASON=\"\"\\n if [ \"$CREATE_STATUS\" = \"404\" ] || [ \"$BLOCKS_STATUS\" = \"404\" ]; then\\n REASON=\"Plan app routes return 404 — deploy not yet propagated (create-visual-recap: $CREATE_STATUS, get-plan-blocks: $BLOCKS_STATUS). The plan-app client is ahead of the deployed server; re-run once the deploy finishes propagating.\"\\n echo \"::error::$REASON\"\\n echo \"unhealthy=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"unhealthy=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n {\\n echo \\'reason<<__RECAP_ROUTE_HEALTH_EOF__\\'\\n echo \"$REASON\"\\n echo \\'__RECAP_ROUTE_HEALTH_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n\\n - name: Secret scan\\n id: scan\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\'\\n run: |\\n set -uo pipefail\\n # Fail CLOSED: a scanner error or invalid JSON suppresses the diff so a\\n # credential-bearing diff is never handed to the agent / plan service.\\n if ! SCAN_JSON=\"$($RECAP_CLI recap scan --diff recap.diff --mode \"$VISUAL_RECAP_SECRET_SCAN\")\"; then\\n SCAN_JSON=\\'{\"suppressed\":true,\"reason\":\"secret scan failed to run; failing closed\"}\\'\\n fi\\n {\\n echo \\'json<<__RECAP_SCAN_EOF__\\'\\n echo \"$SCAN_JSON\"\\n echo \\'__RECAP_SCAN_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n SUPPRESSED=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).suppressed?\"true\":\"false\")}catch{process.stdout.write(\"true\")}\\' \"$SCAN_JSON\")\\n echo \"suppressed=$SUPPRESSED\" >> \"$GITHUB_OUTPUT\"\\n\\n - name: Read previous plan id\\n id: prev\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -euo pipefail\\n PLAN_ID=\"$($RECAP_CLI recap comment find-plan-id --repo \"$GITHUB_REPOSITORY\" --issue \"$PR_NUMBER\" --token \"$GH_TOKEN\")\"\\n echo \"plan_id=$PLAN_ID\" >> \"$GITHUB_OUTPUT\"\\n\\n - name: Fetch plan block reference\\n id: block_reference\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n if $RECAP_CLI recap block-reference --app-url \"$PLAN_RECAP_APP_URL\" --out recap-blocks.md; then\\n echo \"ok=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"ok=false\" >> \"$GITHUB_OUTPUT\"\\n {\\n echo \\'summary<<__RECAP_BLOCK_REFERENCE_EOF__\\'\\n echo \"Could not fetch the live plan block reference; the agent will fall back to bundled visual-recap instructions and the publisher will validate the final MDX.\"\\n echo \\'__RECAP_BLOCK_REFERENCE_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n cat > recap-blocks.md <<\\'EOF\\'\\n Live plan block reference unavailable. Follow the bundled visual-recap skill and author conservative MDX; the deterministic publisher will validate the source before posting.\\n EOF\\n fi\\n\\n - name: Build recap prompt\\n id: prompt\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n env:\\n # Pass step outputs via env, NOT ${{ }} interpolation into the run body:\\n # the prev plan id is parsed from a PR comment and could inject shell.\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}\\n run: |\\n set -euo pipefail\\n ARGS=(--diff recap.diff --stat recap.stat --block-reference recap-blocks.md --pr \"$PR_NUMBER\" --repo \"$GITHUB_REPOSITORY\" --head \"$HEAD_SHA\" --app-url \"$PLAN_RECAP_APP_URL\" --skill-source \"$VISUAL_RECAP_SKILL_SOURCE\" --out recap-prompt.md)\\n if [ \"${DIFF_HUGE:-}\" = \"true\" ]; then ARGS+=(--huge); fi\\n if [ \"${IS_FORK:-}\" = \"true\" ]; then ARGS+=(--fork-pr true); fi\\n if [ -n \"${PREV_PLAN_ID:-}\" ]; then ARGS+=(--prev-plan-id \"$PREV_PLAN_ID\"); fi\\n $RECAP_CLI recap build-prompt \"${ARGS[@]}\"\\n\\n - name: Run agent (Claude Code)\\n id: claude\\n if: needs.gate.outputs.agent == \\'claude\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\\n run: |\\n set -uo pipefail\\n CLAUDE_ALLOWED_TOOLS=\"Read,Write,Bash(git diff:*)\"\\n CLAUDE_ARGS=(-p \"$(cat recap-prompt.md)\" --allowedTools \"$CLAUDE_ALLOWED_TOOLS\" --permission-mode dontAsk --output-format json)\\n if [ -n \"${VISUAL_RECAP_MODEL:-}\" ]; then CLAUDE_ARGS+=(--model \"$VISUAL_RECAP_MODEL\"); fi\\n rm -f recap-source.json recap-url.txt recap-url-reason.txt claude-result.json claude-stderr.log\\n run_claude() {\\n set +e\\n npx -y @anthropic-ai/claude-code@2 \"${CLAUDE_ARGS[@]}\" > claude-result.json 2> claude-stderr.log\\n CLAUDE_STATUS=\"$?\"\\n set -e\\n echo \"$CLAUDE_STATUS\" > claude-exit-code.txt\\n }\\n run_claude\\n # A clean agent exit WITHOUT recap-source.json is the strongest\\n # \"retry me\" signal — the deterministic publisher needs that file, and\\n # the agent occasionally finishes a turn without writing it. Retry once.\\n if [ ! -s recap-source.json ]; then\\n echo \"::warning::recap-source.json missing after the agent run; retrying the agent once.\"\\n sleep 5\\n run_claude\\n fi\\n\\n - name: Run agent (Codex)\\n id: codex\\n if: needs.gate.outputs.agent == \\'codex\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\\n run: |\\n set -uo pipefail\\n # `codex login` writes ~/.codex/auth.json (the bare env var is dropped on\\n # the gpt-5.5 wss transport); stdin keeps the key out of process args.\\n printenv OPENAI_API_KEY | npx -y @openai/codex@0 login --with-api-key || true\\n # The runner is itself an ephemeral sandbox; bypass Codex\\'s own sandbox\\n # (bubblewrap can\\'t init here) and approval gate (cancels the MCP write).\\n CODEX_ARGS=(exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check)\\n if [ -n \"${VISUAL_RECAP_MODEL:-}\" ]; then CODEX_ARGS+=(--model \"$VISUAL_RECAP_MODEL\"); fi\\n # Validate reasoning against the enum before embedding it in the TOML override.\\n case \"${VISUAL_RECAP_REASONING:-}\" in\\n none|minimal|low|medium|high|xhigh)\\n CODEX_ARGS+=(-c \"model_reasoning_effort=\\\\\"$VISUAL_RECAP_REASONING\\\\\"\") ;;\\n \"\") ;;\\n *) echo \"Ignoring invalid VISUAL_RECAP_REASONING: $VISUAL_RECAP_REASONING\" ;;\\n esac\\n rm -f recap-source.json recap-url.txt recap-url-reason.txt codex-events.jsonl codex-stderr.log\\n run_codex() {\\n set +e\\n npx -y @openai/codex@0 \"${CODEX_ARGS[@]}\" --json \"$(cat recap-prompt.md)\" 2> codex-stderr.log | tee codex-events.jsonl\\n CODEX_STATUS=\"${PIPESTATUS[0]}\"\\n set -e\\n echo \"$CODEX_STATUS\" > codex-exit-code.txt\\n }\\n run_codex\\n # Retry once if the agent exited without writing recap-source.json\\n # (see the Claude step) — the publisher needs that file.\\n if [ ! -s recap-source.json ]; then\\n echo \"::warning::recap-source.json missing after the agent run; retrying the agent once.\"\\n sleep 5\\n run_codex\\n fi\\n\\n - name: Publish recap source\\n id: publish\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n run: |\\n set -uo pipefail\\n ARGS=(--source recap-source.json --out recap-url.txt --repo \"$GITHUB_REPOSITORY\" --pr \"$PR_NUMBER\" --app-url \"$PLAN_RECAP_APP_URL\" --token \"$PLAN_RECAP_TOKEN\")\\n if [ -n \"${PREV_PLAN_ID:-}\" ]; then ARGS+=(--prev-plan-id \"$PREV_PLAN_ID\"); fi\\n ARGS+=(--source-type pull-request --source-repo \"$GITHUB_REPOSITORY\" --source-pr-number \"$PR_NUMBER\")\\n if [ \"${PR_MERGED:-false}\" = \"true\" ] || [ -n \"${PR_MERGED_AT:-}\" ]; then\\n ARGS+=(--source-pr-state merged)\\n elif [ -n \"${PR_STATE:-}\" ]; then\\n ARGS+=(--source-pr-state \"$PR_STATE\")\\n fi\\n if [ -n \"${PR_MERGED_AT:-}\" ]; then ARGS+=(--source-pr-merged-at \"$PR_MERGED_AT\"); fi\\n $RECAP_CLI recap publish \"${ARGS[@]}\"\\n\\n - name: Read plan URL\\n id: url\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n run: |\\n set -uo pipefail\\n PLAN_URL=\"\"\\n URL_REASON=\"\"\\n if [ -f recap-url.txt ]; then\\n PLAN_URL=\"$(tr -d \\'\\\\r\\\\n\\' < recap-url.txt | tr -d \\' \\')\"\\n elif [ -f recap-url-reason.txt ]; then\\n URL_REASON=\"$(cat recap-url-reason.txt)\"\\n else\\n URL_REASON=\"recap-url.txt was not created.\"\\n fi\\n # recap-url.txt is agent-written -> untrusted. Rebuild a canonical\\n # recap URL from the trusted app base and a strictly validated plan id,\\n # preserving path-prefixed self-hosted mounts.\\n if [ -z \"$URL_REASON\" ]; then\\n URL_RESULT=$(PLAN_URL=\"$PLAN_URL\" node <<\\'NODE\\'\\n const emit = (value) => process.stdout.write(JSON.stringify(value));\\n try {\\n const raw = process.env.PLAN_URL || \"\";\\n if (!raw) {\\n emit({ url: \"\", reason: \"recap-url.txt was empty\" });\\n process.exit(0);\\n }\\n const trusted = new URL(process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\");\\n const parsed = /^https?:\\\\/\\\\//i.test(raw)\\n ? new URL(raw)\\n : new URL(raw, trusted);\\n if (parsed.origin !== trusted.origin) {\\n emit({ url: \"\", reason: `recap-url.txt points at ${parsed.origin}, expected ${trusted.origin}` });\\n process.exit(0);\\n }\\n\\n const base = trusted.pathname.replace(/\\\\/$/, \"\");\\n const paths = [parsed.pathname];\\n if (base && parsed.pathname.startsWith(`${base}/`)) {\\n paths.push(parsed.pathname.slice(base.length) || \"/\");\\n }\\n\\n for (const path of paths) {\\n const match = path.match(/^\\\\/(?:plans|recaps)\\\\/([A-Za-z0-9_-]+)\\\\/?$/);\\n if (match) {\\n emit({ url: `${trusted.origin}${base}/recaps/${match[1]}`, reason: \"\" });\\n process.exit(0);\\n }\\n }\\n emit({ url: \"\", reason: \"recap-url.txt did not contain a valid /plans/<id> or /recaps/<id> URL for the configured plan app\" });\\n } catch {\\n emit({ url: \"\", reason: \"recap-url.txt was not a valid URL or recap path\" });\\n }\\n NODE\\n )\\n CANONICAL_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).url||\"\")}catch{process.stdout.write(\"\")}\\' \"$URL_RESULT\")\\n URL_REASON=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).reason||\"\")}catch{process.stdout.write(\"recap-url.txt URL validation failed\")}\\' \"$URL_RESULT\")\\n else\\n CANONICAL_URL=\"\"\\n fi\\n if [ -n \"$CANONICAL_URL\" ]; then\\n echo \"plan_url=$CANONICAL_URL\" >> \"$GITHUB_OUTPUT\"; echo \"ok=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"plan_url=\" >> \"$GITHUB_OUTPUT\"; echo \"ok=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n {\\n echo \\'reason<<__RECAP_URL_REASON_EOF__\\'\\n echo \"$URL_REASON\"\\n echo \\'__RECAP_URL_REASON_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n\\n - name: Summarize agent failure\\n id: agent_summary\\n if: steps.url.outputs.ok != \\'true\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n RECAP_AGENT: ${{ needs.gate.outputs.agent }}\\n RECAP_BLOCK_REFERENCE_SUMMARY: ${{ steps.block_reference.outputs.summary }}\\n RECAP_PUBLISH_REASON: ${{ steps.publish.outputs.reason }}\\n run: |\\n set -uo pipefail\\n if [ -n \"${RECAP_BLOCK_REFERENCE_SUMMARY:-}\" ]; then\\n {\\n echo \\'summary<<__RECAP_BLOCK_REFERENCE_SUMMARY_EOF__\\'\\n echo \"$RECAP_BLOCK_REFERENCE_SUMMARY\"\\n echo \\'__RECAP_BLOCK_REFERENCE_SUMMARY_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n node -e \\'process.stdout.write(JSON.stringify({ ok: true, summary: process.env.RECAP_BLOCK_REFERENCE_SUMMARY || \"\" }) + \"\\\\n\")\\'\\n exit 0\\n fi\\n if [ -n \"${RECAP_PUBLISH_REASON:-}\" ]; then\\n {\\n echo \\'summary<<__RECAP_PUBLISH_SUMMARY_EOF__\\'\\n echo \"$RECAP_PUBLISH_REASON\"\\n echo \\'__RECAP_PUBLISH_SUMMARY_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n node -e \\'process.stdout.write(JSON.stringify({ ok: true, summary: process.env.RECAP_PUBLISH_REASON || \"\" }) + \"\\\\n\")\\'\\n exit 0\\n fi\\n RESULT=claude-result.json\\n STDERR=claude-stderr.log\\n EXIT_CODE=claude-exit-code.txt\\n if [ \"$RECAP_AGENT\" = \"codex\" ]; then\\n RESULT=codex-events.jsonl\\n STDERR=codex-stderr.log\\n EXIT_CODE=codex-exit-code.txt\\n fi\\n $RECAP_CLI recap agent-summary --agent \"$RECAP_AGENT\" --result-file \"$RESULT\" --stderr-file \"$STDERR\" --exit-code-file \"$EXIT_CODE\" || true\\n\\n - name: Attach usage\\n if: steps.url.outputs.ok == \\'true\\'\\n continue-on-error: true\\n env:\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n # Use the gate-normalized agent so \"Codex\" still selects the right file.\\n RECAP_AGENT: ${{ needs.gate.outputs.agent }}\\n run: |\\n set -uo pipefail\\n RESULT=claude-result.json\\n if [ \"$RECAP_AGENT\" = \"codex\" ]; then RESULT=codex-events.jsonl; fi\\n if [ -f \"$RESULT\" ]; then $RECAP_CLI recap usage --plan-url \"$PLAN_URL\" --agent \"$RECAP_AGENT\" --result-file \"$RESULT\" --model \"${VISUAL_RECAP_MODEL:-}\" --app-url \"$PLAN_RECAP_APP_URL\" --token \"$PLAN_RECAP_TOKEN\" || true; fi\\n\\n - name: Cache Playwright browsers\\n if: steps.url.outputs.ok == \\'true\\'\\n uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3\\n with:\\n path: ~/.cache/ms-playwright\\n key: playwright-1-${{ runner.os }}\\n\\n - name: Screenshot + upload\\n id: shot\\n if: steps.url.outputs.ok == \\'true\\'\\n continue-on-error: true\\n env:\\n # recap-url.txt is untrusted agent output; pass via env, never ${{ }}.\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n run: |\\n set -uo pipefail\\n if [ -n \"${RECAP_PLAYWRIGHT:-}\" ] && [ -x \"$RECAP_PLAYWRIGHT\" ]; then\\n \"$RECAP_PLAYWRIGHT\" install --with-deps chromium || true\\n elif command -v pnpm >/dev/null 2>&1; then\\n pnpm exec playwright install --with-deps chromium 2>/dev/null || npx -y playwright@1 install --with-deps chromium || true\\n else\\n npx -y playwright@1 install --with-deps chromium || true\\n fi\\n IMAGE_CACHE_KEY=\"$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT\"\\n LIGHT_SHOT_JSON=\"$($RECAP_CLI recap shot --url \"$PLAN_URL\" --token \"$PLAN_RECAP_TOKEN\" --app-url \"$PLAN_RECAP_APP_URL\" --out recap.png --theme light --image-cache-key \"$IMAGE_CACHE_KEY\" || echo \\'{}\\')\"\\n DARK_SHOT_JSON=\"$($RECAP_CLI recap shot --url \"$PLAN_URL\" --token \"$PLAN_RECAP_TOKEN\" --app-url \"$PLAN_RECAP_APP_URL\" --out recap-dark.png --theme dark --image-cache-key \"$IMAGE_CACHE_KEY\" || echo \\'{}\\')\"\\n for SHOT_LABEL in light dark; do\\n if [ \"$SHOT_LABEL\" = \"light\" ]; then SHOT_JSON=\"$LIGHT_SHOT_JSON\"; else SHOT_JSON=\"$DARK_SHOT_JSON\"; fi\\n SHOT_LABEL=\"$SHOT_LABEL\" SHOT_JSON=\"$SHOT_JSON\" node -e \\'const label = process.env.SHOT_LABEL || \"shot\"; let parsed = {}; try { parsed = JSON.parse(process.env.SHOT_JSON || \"{}\"); } catch { parsed = { ok: false, reason: \"invalid shot JSON\" }; } const summary = { ok: parsed.ok === true, imageUrl: parsed.imageUrl ? \"[present]\" : \"\", out: typeof parsed.out === \"string\" ? parsed.out : \"\", reason: typeof parsed.reason === \"string\" ? parsed.reason.slice(0, 500) : \"\" }; console.log(`[recap shot] ${label}: ${JSON.stringify(summary)}`);\\'\\n done\\n IMAGE_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).imageUrl||\"\")}catch{process.stdout.write(\"\")}\\' \"$LIGHT_SHOT_JSON\")\\n DARK_IMAGE_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).imageUrl||\"\")}catch{process.stdout.write(\"\")}\\' \"$DARK_SHOT_JSON\")\\n SHOT_STATUS=$(LIGHT_SHOT_JSON=\"$LIGHT_SHOT_JSON\" DARK_SHOT_JSON=\"$DARK_SHOT_JSON\" node <<\\'NODE\\'\\n const parse = (raw) => { try { return JSON.parse(raw || \"{}\"); } catch { return { ok: false, reason: \"invalid shot JSON\" }; } };\\n const shots = [[\"light\", parse(process.env.LIGHT_SHOT_JSON)], [\"dark\", parse(process.env.DARK_SHOT_JSON)]];\\n const hasImage = shots.some(([, shot]) => typeof shot.imageUrl === \"string\" && shot.imageUrl.trim());\\n const reasons = shots.flatMap(([label, shot]) => {\\n if (typeof shot.reason === \"string\" && shot.reason.trim()) return [`${label}: ${shot.reason.trim()}`];\\n if (!(typeof shot.imageUrl === \"string\" && shot.imageUrl.trim())) return [`${label}: no imageUrl returned`];\\n return [];\\n });\\n process.stdout.write(JSON.stringify({ ok: hasImage, reason: hasImage ? \"\" : reasons.join(\"; \").slice(0, 1000) }));\\n NODE\\n )\\n SHOT_OK=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).ok===true?\"true\":\"false\")}catch{process.stdout.write(\"false\")}\\' \"$SHOT_STATUS\")\\n SHOT_REASON=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).reason||\"\")}catch{process.stdout.write(\"invalid shot status JSON\")}\\' \"$SHOT_STATUS\")\\n if [ \"$SHOT_OK\" != \"true\" ]; then\\n echo \"::warning::Visual recap screenshot unavailable; posting screenshot-failed recap comment. $SHOT_REASON\"\\n fi\\n echo \"image_url=$IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"light_image_url=$IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"dark_image_url=$DARK_IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"shot_ok=$SHOT_OK\" >> \"$GITHUB_OUTPUT\"\\n {\\n echo \\'shot_reason<<__RECAP_SHOT_REASON_EOF__\\'\\n echo \"$SHOT_REASON\"\\n echo \\'__RECAP_SHOT_REASON_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n if [ -f recap.png ] || [ -f recap-dark.png ]; then echo \"captured=true\" >> \"$GITHUB_OUTPUT\"; else echo \"captured=false\" >> \"$GITHUB_OUTPUT\"; fi\\n\\n - name: Upload recap screenshot artifact\\n if: steps.shot.outputs.captured == \\'true\\'\\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\\n with:\\n name: pr-visual-recap-${{ github.event.pull_request.number }}\\n path: |\\n recap.png\\n recap-dark.png\\n if-no-files-found: ignore\\n retention-days: 14\\n\\n - name: Upload recap source artifact\\n if: always() && !cancelled()\\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\\n with:\\n # recap-source.json + the agent transcript (claude-result.json /\\n # codex-events.jsonl + stderr) are the only window into WHAT the agent\\n # did when a publish fails (no plan URL) — INCLUDING the case where it\\n # finished without writing recap-source.json at all. The sticky comment\\n # only shows the screenshot, so without these a failed recap is\\n # undebuggable. Uploaded on success + failure; tolerant when absent.\\n name: pr-visual-recap-source-${{ github.event.pull_request.number }}\\n path: |\\n recap-source.json\\n claude-result.json\\n claude-stderr.log\\n codex-events.jsonl\\n codex-stderr.log\\n if-no-files-found: ignore\\n retention-days: 14\\n\\n - name: Upsert sticky comment\\n if: always() && !cancelled()\\n continue-on-error: true\\n env:\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n RECAP_IMAGE_URL: ${{ steps.shot.outputs.image_url }}\\n RECAP_LIGHT_IMAGE_URL: ${{ steps.shot.outputs.light_image_url }}\\n RECAP_DARK_IMAGE_URL: ${{ steps.shot.outputs.dark_image_url }}\\n RECAP_SHOT_OK: ${{ steps.shot.outputs.shot_ok }}\\n RECAP_SHOT_REASON: ${{ steps.shot.outputs.shot_reason }}\\n SUPPRESSED: ${{ steps.scan.outputs.suppressed }}\\n SUPPRESSED_JSON: ${{ steps.scan.outputs.json }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n DIFF_TINY: ${{ steps.diff.outputs.tiny }}\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n RECAP_AUTH_FAILED: ${{ steps.auth_probe.outputs.auth_failed }}\\n RECAP_AGENT_SUMMARY: ${{ steps.agent_summary.outputs.summary }}\\n # Prefer the route-health diagnostic when the plan app routes are not\\n # yet deployed so the comment explains the 404 instead of a generic\\n # \"recap-url.txt was not created\" message.\\n RECAP_URL_REASON: ${{ steps.route_health.outputs.reason || steps.url.outputs.reason }}\\n run: |\\n set -euo pipefail\\n $RECAP_CLI recap comment upsert --repo \"$GITHUB_REPOSITORY\" --issue \"$PR_NUMBER\" --token \"$GH_TOKEN\" --head-sha \"$HEAD_SHA\"\\n\\n - name: Complete visual recap check\\n if: always() && !cancelled() && steps.recap_check.outputs.check_run_id != \\'\\'\\n continue-on-error: true\\n env:\\n # Untrusted/step values via env (NOT ${{ }}-interpolated into the run\\n # body): the agent-written plan URL and the scan JSON could inject shell.\\n CHECK_RUN_ID: ${{ steps.recap_check.outputs.check_run_id }}\\n PLAN_OK: ${{ steps.url.outputs.ok }}\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n SUPPRESSED: ${{ steps.scan.outputs.suppressed }}\\n SUPPRESSED_JSON: ${{ steps.scan.outputs.json }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n DIFF_TINY: ${{ steps.diff.outputs.tiny }}\\n RECAP_AGENT_SUMMARY: ${{ steps.agent_summary.outputs.summary }}\\n RECAP_URL_REASON: ${{ steps.route_health.outputs.reason || steps.url.outputs.reason }}\\n run: |\\n set -uo pipefail\\n $RECAP_CLI recap check complete \\\\\\n --check-run-id \"$CHECK_RUN_ID\" \\\\\\n --plan-ok \"$PLAN_OK\" \\\\\\n --plan-url \"$PLAN_URL\" \\\\\\n --suppressed \"$SUPPRESSED\" \\\\\\n --suppressed-json \"$SUPPRESSED_JSON\" \\\\\\n --huge \"$DIFF_HUGE\" \\\\\\n --tiny \"$DIFF_TINY\" \\\\\\n --failure-summary \"$RECAP_AGENT_SUMMARY\" \\\\\\n --url-reason \"$RECAP_URL_REASON\" \\\\\\n --workflow-url \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"\\n';\n"]}
1
+ {"version":3,"file":"pr-visual-recap-workflow.js","sourceRoot":"","sources":["../../src/cli/pr-visual-recap-workflow.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,4BAA4B,GACvC,2v0CAA2v0C,CAAC","sourcesContent":["/**\n * Bundled copy of .github/workflows/pr-visual-recap.yml used by\n * `agent-native recap setup`. Keep byte-identical to the source workflow.\n *\n * This file is generated from the workflow source; tests assert the exported\n * string stays in sync.\n */\n\nexport const PR_VISUAL_RECAP_WORKFLOW_YML =\n 'name: PR Visual Recap\\n\\n# Visual code review: a coding agent runs the repo\\'s visual-recap skill over the\\n# PR diff, publishes a plan, and upserts one sticky comment with a screenshot.\\n# Plain `pull_request` (NOT `pull_request_target`) so fork code never sees secrets.\\n\\non:\\n pull_request:\\n types: [opened, synchronize, reopened, ready_for_review, closed]\\n\\npermissions:\\n contents: read\\n\\nconcurrency:\\n group: pr-visual-recap-${{ github.event.pull_request.number }}\\n cancel-in-progress: true\\n\\nenv:\\n VISUAL_RECAP_AGENT: ${{ vars.VISUAL_RECAP_AGENT || \\'claude\\' }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\' }}\\n VISUAL_RECAP_SECRET_SCAN: ${{ vars.VISUAL_RECAP_SECRET_SCAN || \\'high-confidence\\' }}\\n\\njobs:\\n gate:\\n name: Gate\\n runs-on: ubuntu-latest\\n timeout-minutes: 10\\n permissions:\\n contents: read\\n issues: write\\n pull-requests: write\\n outputs:\\n run: ${{ steps.decide.outputs.run }}\\n agent: ${{ steps.decide.outputs.agent }}\\n steps:\\n - id: decide\\n uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0\\n env:\\n # Presence-only signals — never expose secret VALUES to the gate.\\n HAS_PLAN: ${{ secrets.PLAN_RECAP_TOKEN != \\'\\' }}\\n HAS_ANTHROPIC: ${{ secrets.ANTHROPIC_API_KEY != \\'\\' }}\\n HAS_OPENAI: ${{ secrets.OPENAI_API_KEY != \\'\\' }}\\n AGENT: ${{ env.VISUAL_RECAP_AGENT }}\\n VISUAL_RECAP_MODEL: ${{ vars.VISUAL_RECAP_MODEL }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ env.VISUAL_RECAP_SKILL_SOURCE }}\\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\\n with:\\n script: |\\n const pr = context.payload.pull_request;\\n const reasons = [];\\n\\n if (!pr) reasons.push(\\'no pull_request payload\\');\\n if (pr && pr.draft) reasons.push(\\'draft PR\\');\\n if (pr && context.payload.action === \\'closed\\' && !pr.merged) {\\n reasons.push(\\'closed without merge\\');\\n }\\n\\n // Fork PRs only receive repo secrets when the org/repo opts into\\n // GitHub\\'s \"Send secrets to workflows from pull requests\" setting\\n // (common in private orgs that use forks heavily). Gate on secret\\n // availability, not fork-ness: run on forks that have the token,\\n // and skip — with an actionable hint — those that don\\'t.\\n const headRepo = pr && pr.head && pr.head.repo && pr.head.repo.full_name;\\n const isFork = !!(pr && headRepo && headRepo !== process.env.GITHUB_REPOSITORY);\\n const isPrivate = !!(context.payload.repository && context.payload.repository.private);\\n const association = (pr && pr.author_association || \\'\\').toUpperCase();\\n const trustedAssociations = [\\'OWNER\\', \\'MEMBER\\', \\'COLLABORATOR\\'];\\n const isTrustedAuthor = trustedAssociations.includes(association);\\n if (isFork && process.env.HAS_PLAN !== \\'true\\') {\\n reasons.push(`fork PR (${headRepo}) without secret access — enable \"Send secrets to workflows from pull requests\" (and write tokens) in the repo/org Actions settings to run recaps on forks`);\\n }\\n\\n const login = (pr && pr.user && pr.user.login || \\'\\').toLowerCase();\\n const botAuthors = [\\'dependabot[bot]\\', \\'dependabot\\', \\'renovate[bot]\\', \\'renovate\\'];\\n if (botAuthors.includes(login)) reasons.push(`bot author (${login})`);\\n if (pr && pr.user && pr.user.type === \\'Bot\\') reasons.push(\\'bot author (type=Bot)\\');\\n\\n if (!isFork && process.env.HAS_PLAN !== \\'true\\') reasons.push(\\'PLAN_RECAP_TOKEN not configured\\');\\n\\n // Normalize + validate the agent so a mis-cased value can\\'t pass the\\n // gate and then match neither agent step below.\\n const agent = (process.env.AGENT || \\'claude\\').toLowerCase();\\n if (agent !== \\'claude\\' && agent !== \\'codex\\') {\\n reasons.push(`unsupported VISUAL_RECAP_AGENT \"${process.env.AGENT}\" (expected \"claude\" or \"codex\")`);\\n } else if (agent === \\'codex\\') {\\n if (process.env.HAS_OPENAI !== \\'true\\') reasons.push(\\'OPENAI_API_KEY not configured (codex backend)\\');\\n } else {\\n if (process.env.HAS_ANTHROPIC !== \\'true\\') reasons.push(\\'ANTHROPIC_API_KEY not configured (claude backend)\\');\\n }\\n\\n // Validate the model before it reaches the agent CLI.\\n const model = process.env.VISUAL_RECAP_MODEL || \\'\\';\\n if (model && !/^[a-zA-Z0-9._-]{1,80}$/.test(model)) {\\n reasons.push(`invalid VISUAL_RECAP_MODEL value (must match [a-zA-Z0-9._-]{1,80})`);\\n }\\n\\n const skillSource = (process.env.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\').toLowerCase();\\n if (![\\'auto\\', \\'latest\\', \\'repo\\'].includes(skillSource)) {\\n reasons.push(\\'invalid VISUAL_RECAP_SKILL_SOURCE value (expected \"auto\", \"latest\", or \"repo\")\\');\\n }\\n const usesRepoSkill = skillSource === \\'repo\\';\\n\\n // Self-modifying guard, evaluated in the trusted gate (runs NO\\n // PR-checked-out code): skip the ENTIRE job if the PR touches the\\n // repo-pinned skill instructions or any agent config the runner\\n // loads, so a PR can\\'t rewrite what the agent loads and exfiltrate\\n // secrets. With the default bundled skill source, visual skill and\\n // recap workflow files are reviewed content, not instructions loaded\\n // by the runner.\\n // Keep this guard for untrusted forks and untrusted public-repo PRs.\\n // Trusted write actors may edit recap-control files as normal\\n // reviewable content; running the recap is useful signal for those\\n // changes.\\n if (pr && !isTrustedAuthor && (isFork || !isPrivate)) {\\n try {\\n const files = await github.paginate(github.rest.pulls.listFiles, {\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n pull_number: pr.number,\\n per_page: 100,\\n });\\n const isSensitive = (p) =>\\n (usesRepoSkill && /(^|\\\\/)skills\\\\/visual-(recap|plan|plans)\\\\//.test(p)) ||\\n p.startsWith(\\'.claude/\\') ||\\n p === \\'CLAUDE.md\\' ||\\n p === \\'AGENTS.md\\' ||\\n p === \\'.mcp.json\\';\\n const hits = files.map((f) => f.filename).filter(isSensitive);\\n if (hits.length) {\\n reasons.push(`PR modifies recap-control files (${hits.slice(0, 3).join(\\', \\')}${hits.length > 3 ? \\', …\\' : \\'\\'}) — skipping so untrusted PR code never runs with secrets`);\\n }\\n } catch (e) {\\n // Fail closed: if the file list can\\'t be read, skip.\\n reasons.push(`could not list PR files for the self-modifying guard (${e.message}); skipping to be safe`);\\n }\\n }\\n\\n const run = reasons.length === 0;\\n core.setOutput(\\'run\\', run ? \\'true\\' : \\'false\\');\\n core.setOutput(\\'agent\\', agent);\\n if (run) {\\n core.info(`Visual recap will run (${agent}).`);\\n } else {\\n // Surface the skip reason as a run-summary annotation, not just a\\n // buried info log, so it\\'s clear in the Actions UI why we skipped.\\n core.notice(`Visual recap skipped: ${reasons.join(\\'; \\')}`);\\n }\\n\\n // When skipping, upsert a sticky recap comment with a short skip\\n // line so the PR always explains why the recap job did not run.\\n if (!run && pr) {\\n try {\\n const MARKER = \\'<!-- pr-visual-recap -->\\';\\n const { data: comments } = await github.rest.issues.listComments({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n issue_number: pr.number,\\n per_page: 100,\\n });\\n const existing = comments.find(\\n (c) => c.user && c.user.type === \\'Bot\\' && c.body && c.body.includes(MARKER)\\n );\\n const headShort = (process.env.HEAD_SHA || \\'\\').slice(0, 7);\\n const shaRef = headShort ? `\\\\`${headShort}\\\\`` : \\'latest push\\';\\n const primaryReason = reasons.filter(\\n (r) => !r.startsWith(\\'could not list PR files for the self-modifying guard\\')\\n )[0] || reasons[0] || \\'skipped\\';\\n const skipLine = `_Recap skipped for ${shaRef}: ${primaryReason}._`;\\n const baseBody = `${MARKER}\\\\n### Visual recap — skipped\\\\n\\\\nThe visual recap job did not run for this pull request. This is informational only and does **not** block the PR.`;\\n const planIdMatch = (existing && existing.body ? existing.body : \\'\\').match(/<!--\\\\s*plan-id:\\\\s*([A-Za-z0-9_-]{1,64})\\\\s*-->/);\\n const planIdMarker = planIdMatch ? `\\\\n\\\\n<!-- plan-id: ${planIdMatch[1]} -->` : \\'\\';\\n const updatedBody = `${baseBody}${planIdMarker}\\\\n\\\\n${skipLine}`;\\n if (existing) {\\n await github.rest.issues.updateComment({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n comment_id: existing.id,\\n body: updatedBody,\\n });\\n } else {\\n await github.rest.issues.createComment({\\n owner: context.repo.owner,\\n repo: context.repo.repo,\\n issue_number: pr.number,\\n body: updatedBody,\\n });\\n }\\n } catch (e) {\\n core.warning(`Could not update recap skip comment: ${e.message}`);\\n }\\n }\\n\\n recap:\\n name: Generate visual recap\\n needs: gate\\n if: needs.gate.outputs.run == \\'true\\'\\n runs-on: ubuntu-latest\\n timeout-minutes: 30\\n permissions:\\n actions: write\\n checks: write\\n contents: read\\n issues: write\\n pull-requests: write\\n env:\\n PLAN_RECAP_APP_URL: ${{ secrets.PLAN_RECAP_APP_URL || \\'https://plan.agent-native.com\\' }}\\n PLAN_RECAP_TOKEN: ${{ secrets.PLAN_RECAP_TOKEN }}\\n GH_TOKEN: ${{ github.token }}\\n PR_NUMBER: ${{ github.event.pull_request.number }}\\n PR_STATE: ${{ github.event.pull_request.state }}\\n PR_MERGED: ${{ github.event.pull_request.merged }}\\n PR_MERGED_AT: ${{ github.event.pull_request.merged_at }}\\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\\n VISUAL_RECAP_MODEL: ${{ vars.VISUAL_RECAP_MODEL }}\\n VISUAL_RECAP_REASONING: ${{ vars.VISUAL_RECAP_REASONING }}\\n VISUAL_RECAP_SKILL_SOURCE: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || \\'auto\\' }}\\n VISUAL_RECAP_SECRET_SCAN: ${{ vars.VISUAL_RECAP_SECRET_SCAN || \\'high-confidence\\' }}\\n steps:\\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\\n with:\\n fetch-depth: 0\\n # This job runs an agent over untrusted PR diff; don\\'t leave the token\\n # in .git/config (it uses GH_TOKEN for gh API calls, never git push).\\n persist-credentials: false\\n\\n # Dogfood trusted base-branch source inside this monorepo, else install the\\n # published package once. Never execute PR-head recap CLI code.\\n - name: Resolve recap CLI\\n id: cli\\n env:\\n # Optional: pin the consumer CLI version (e.g. \"1.2.3\"). Defaults to\\n # \"latest\" when unset. Set via repository variable RECAP_CLI_VERSION.\\n RECAP_CLI_VERSION: ${{ vars.RECAP_CLI_VERSION || \\'latest\\' }}\\n run: |\\n if [ \"$GITHUB_REPOSITORY\" = \"BuilderIO/agent-native\" ] && [ -f packages/core/src/cli/index.ts ]; then\\n echo \"local=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"local=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n\\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\\n if: steps.cli.outputs.local == \\'true\\'\\n with:\\n ref: ${{ github.event.pull_request.base.sha }}\\n path: .recap-cli-source\\n fetch-depth: 1\\n persist-credentials: false\\n\\n - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8\\n if: steps.cli.outputs.local == \\'true\\'\\n\\n - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\\n with:\\n node-version: \"22\"\\n cache: ${{ steps.cli.outputs.local == \\'true\\' && \\'pnpm\\' || \\'\\' }}\\n\\n - name: Install trusted workspace recap CLI\\n if: steps.cli.outputs.local == \\'true\\'\\n working-directory: .recap-cli-source\\n run: |\\n set -euo pipefail\\n pnpm install --frozen-lockfile --ignore-scripts\\n echo \"RECAP_CLI=$PWD/node_modules/.bin/tsx $PWD/packages/core/src/cli/index.ts\" >> \"$GITHUB_ENV\"\\n echo \"RECAP_PLAYWRIGHT=$PWD/node_modules/.bin/playwright\" >> \"$GITHUB_ENV\"\\n\\n - name: Install published recap CLI\\n if: steps.cli.outputs.local != \\'true\\'\\n env:\\n RECAP_CLI_VERSION: ${{ vars.RECAP_CLI_VERSION || \\'latest\\' }}\\n run: |\\n set -euo pipefail\\n VERSION=\"$RECAP_CLI_VERSION\"\\n if [ \"$VERSION\" = \"latest\" ]; then\\n VERSION=\"$(npm view @agent-native/core@latest version)\"\\n fi\\n for attempt in 1 2 3; do\\n if npm install --prefix \"$RUNNER_TEMP/recap-cli\" --no-audit --no-fund \"@agent-native/core@$VERSION\"; then\\n break\\n fi\\n if [ \"$attempt\" = \"3\" ]; then exit 1; fi\\n sleep $((attempt * 10))\\n done\\n echo \"RECAP_CLI=$RUNNER_TEMP/recap-cli/node_modules/.bin/agent-native\" >> \"$GITHUB_ENV\"\\n echo \"RECAP_PLAYWRIGHT=$RUNNER_TEMP/recap-cli/node_modules/.bin/playwright\" >> \"$GITHUB_ENV\"\\n\\n - name: Start visual recap check\\n id: recap_check\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n $RECAP_CLI recap check start --sha \"$HEAD_SHA\" --workflow-url \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"\\n\\n - name: Fetch pull request head\\n env:\\n PR_NUMBER_ENV: ${{ github.event.pull_request.number }}\\n run: |\\n set -euo pipefail\\n if git cat-file -e \"${HEAD_SHA}^{commit}\" 2>/dev/null; then\\n git update-ref refs/recap/pr-head \"$HEAD_SHA\"\\n else\\n AUTH_B64=\"$(printf \\'x-access-token:%s\\' \"$GH_TOKEN\" | base64 | tr -d \\'\\\\n\\')\"\\n git -c \"http.https://github.com/.extraheader=AUTHORIZATION: basic $AUTH_B64\" fetch origin \"pull/${PR_NUMBER_ENV}/head:refs/recap/pr-head\"\\n fi\\n FETCHED_SHA=\"$(git rev-parse refs/recap/pr-head)\"\\n if [ \"$FETCHED_SHA\" != \"$HEAD_SHA\" ]; then\\n echo \"FATAL: fetched PR head $FETCHED_SHA != event HEAD_SHA $HEAD_SHA — aborting to avoid recapping the wrong commit\"\\n exit 1\\n fi\\n\\n - name: Collect bounded diff\\n id: diff\\n env:\\n BASE_SHA: ${{ github.event.pull_request.base.sha }}\\n run: |\\n set -euo pipefail\\n $RECAP_CLI recap collect-diff --base \"$BASE_SHA\" --head refs/recap/pr-head --out recap.diff --stat recap.stat\\n\\n - name: Probe plan-app auth\\n id: auth_probe\\n if: steps.diff.outputs.tiny != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n # Hit the plan app\\'s action surface with the publish token. A 401 means\\n # the token is expired/revoked; surface it in the sticky comment so the\\n # repo owner knows to re-mint it instead of seeing a generic failure.\\n HTTP_STATUS=$(node -e \\'\\n const https = require(\"https\");\\n const url = new URL(\"/_agent-native/actions/record-recap-usage\", process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\");\\n const req = https.request(url, { method: \"POST\", headers: { \"authorization\": \"Bearer \" + process.env.PLAN_RECAP_TOKEN, \"content-type\": \"application/json\" }, timeout: 8000 }, (res) => { process.stdout.write(String(res.statusCode)); req.destroy(); });\\n req.on(\"error\", () => process.stdout.write(\"0\"));\\n req.end(JSON.stringify({ planId: \"__probe__\" }));\\n \\' 2>/dev/null || echo \"0\")\\n if [ \"$HTTP_STATUS\" = \"401\" ]; then\\n echo \"auth_failed=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"auth_failed=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n\\n - name: Probe plan-app route health\\n id: route_health\\n if: steps.diff.outputs.tiny != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n # Pre-publish health gate: confirm the plan app\\'s recap action routes\\n # are actually deployed BEFORE the agent runs. A 404 from\\n # create-visual-recap (POST) or get-plan-blocks (GET) means the\\n # plan-app deploy has not propagated yet (the client is ahead of the\\n # deployed server). Say that plainly here instead of letting the agent\\n # run and then fail confusingly at publish time. A 401 or 200 is\\n # healthy — the route exists, it just rejected/accepted the probe.\\n probe_status() {\\n ROUTE=\"$1\" METHOD=\"$2\" node -e \\'\\n const https = require(\"https\");\\n const base = process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\";\\n const url = new URL(process.env.ROUTE, base);\\n if (process.env.METHOD === \"GET\") url.searchParams.set(\"format\", \"reference\");\\n const req = https.request(url, { method: process.env.METHOD, headers: { \"authorization\": \"Bearer \" + (process.env.PLAN_RECAP_TOKEN || \"\"), \"content-type\": \"application/json\" }, timeout: 8000 }, (res) => { process.stdout.write(String(res.statusCode)); req.destroy(); });\\n req.on(\"error\", () => process.stdout.write(\"0\"));\\n req.on(\"timeout\", () => { process.stdout.write(\"0\"); req.destroy(); });\\n if (process.env.METHOD === \"POST\") { req.end(JSON.stringify({ __probe__: true })); } else { req.end(); }\\n \\' 2>/dev/null || echo \"0\"\\n }\\n CREATE_STATUS=\"$(probe_status /_agent-native/actions/create-visual-recap POST)\"\\n BLOCKS_STATUS=\"$(probe_status /_agent-native/actions/get-plan-blocks GET)\"\\n REASON=\"\"\\n if [ \"$CREATE_STATUS\" = \"404\" ] || [ \"$BLOCKS_STATUS\" = \"404\" ]; then\\n REASON=\"Plan app routes return 404 — deploy not yet propagated (create-visual-recap: $CREATE_STATUS, get-plan-blocks: $BLOCKS_STATUS). The plan-app client is ahead of the deployed server; re-run once the deploy finishes propagating.\"\\n echo \"::error::$REASON\"\\n echo \"unhealthy=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"unhealthy=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n {\\n echo \\'reason<<__RECAP_ROUTE_HEALTH_EOF__\\'\\n echo \"$REASON\"\\n echo \\'__RECAP_ROUTE_HEALTH_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n\\n - name: Secret scan\\n id: scan\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\'\\n run: |\\n set -uo pipefail\\n # Fail CLOSED: a scanner error or invalid JSON suppresses the diff so a\\n # credential-bearing diff is never handed to the agent / plan service.\\n if ! SCAN_JSON=\"$($RECAP_CLI recap scan --diff recap.diff --mode \"$VISUAL_RECAP_SECRET_SCAN\")\"; then\\n SCAN_JSON=\\'{\"suppressed\":true,\"reason\":\"secret scan failed to run; failing closed\"}\\'\\n fi\\n {\\n echo \\'json<<__RECAP_SCAN_EOF__\\'\\n echo \"$SCAN_JSON\"\\n echo \\'__RECAP_SCAN_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n SUPPRESSED=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).suppressed?\"true\":\"false\")}catch{process.stdout.write(\"true\")}\\' \"$SCAN_JSON\")\\n echo \"suppressed=$SUPPRESSED\" >> \"$GITHUB_OUTPUT\"\\n\\n - name: Read previous plan id\\n id: prev\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -euo pipefail\\n PLAN_ID=\"$($RECAP_CLI recap comment find-plan-id --repo \"$GITHUB_REPOSITORY\" --issue \"$PR_NUMBER\" --token \"$GH_TOKEN\")\"\\n echo \"plan_id=$PLAN_ID\" >> \"$GITHUB_OUTPUT\"\\n\\n - name: Fetch plan block reference\\n id: block_reference\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n run: |\\n set -uo pipefail\\n if $RECAP_CLI recap block-reference --app-url \"$PLAN_RECAP_APP_URL\" --out recap-blocks.md; then\\n echo \"ok=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"ok=false\" >> \"$GITHUB_OUTPUT\"\\n {\\n echo \\'summary<<__RECAP_BLOCK_REFERENCE_EOF__\\'\\n echo \"Could not fetch the live plan block reference; the agent will fall back to bundled visual-recap instructions and the publisher will validate the final MDX.\"\\n echo \\'__RECAP_BLOCK_REFERENCE_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n cat > recap-blocks.md <<\\'EOF\\'\\n Live plan block reference unavailable. Follow the bundled visual-recap skill and author conservative MDX; the deterministic publisher will validate the source before posting.\\n EOF\\n fi\\n\\n - name: Build recap prompt\\n id: prompt\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n env:\\n # Pass step outputs via env, NOT ${{ }} interpolation into the run body:\\n # the prev plan id is parsed from a PR comment and could inject shell.\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}\\n run: |\\n set -euo pipefail\\n ARGS=(--diff recap.diff --stat recap.stat --block-reference recap-blocks.md --pr \"$PR_NUMBER\" --repo \"$GITHUB_REPOSITORY\" --head \"$HEAD_SHA\" --app-url \"$PLAN_RECAP_APP_URL\" --skill-source \"$VISUAL_RECAP_SKILL_SOURCE\" --out recap-prompt.md)\\n if [ \"${DIFF_HUGE:-}\" = \"true\" ]; then ARGS+=(--huge); fi\\n if [ \"${IS_FORK:-}\" = \"true\" ]; then ARGS+=(--fork-pr true); fi\\n if [ -n \"${PREV_PLAN_ID:-}\" ]; then ARGS+=(--prev-plan-id \"$PREV_PLAN_ID\"); fi\\n $RECAP_CLI recap build-prompt \"${ARGS[@]}\"\\n\\n - name: Run agent (Claude Code)\\n id: claude\\n if: needs.gate.outputs.agent == \\'claude\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\\n run: |\\n set -uo pipefail\\n CLAUDE_ALLOWED_TOOLS=\"Read,Write,Bash(git diff:*)\"\\n CLAUDE_ARGS=(-p \"$(cat recap-prompt.md)\" --allowedTools \"$CLAUDE_ALLOWED_TOOLS\" --permission-mode dontAsk --output-format json)\\n if [ -n \"${VISUAL_RECAP_MODEL:-}\" ]; then CLAUDE_ARGS+=(--model \"$VISUAL_RECAP_MODEL\"); fi\\n rm -f recap-source.json recap-url.txt recap-url-reason.txt claude-result.json claude-stderr.log\\n run_claude() {\\n set +e\\n npx -y @anthropic-ai/claude-code@2 \"${CLAUDE_ARGS[@]}\" > claude-result.json 2> claude-stderr.log\\n CLAUDE_STATUS=\"$?\"\\n set -e\\n echo \"$CLAUDE_STATUS\" > claude-exit-code.txt\\n }\\n run_claude\\n # A clean agent exit WITHOUT recap-source.json is the strongest\\n # \"retry me\" signal — the deterministic publisher needs that file, and\\n # the agent occasionally finishes a turn without writing it. Retry once.\\n if [ ! -s recap-source.json ]; then\\n echo \"::warning::recap-source.json missing after the agent run; retrying the agent once.\"\\n sleep 5\\n run_claude\\n fi\\n\\n - name: Run agent (Codex)\\n id: codex\\n if: needs.gate.outputs.agent == \\'codex\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\\n run: |\\n set -uo pipefail\\n # `codex login` writes ~/.codex/auth.json (the bare env var is dropped on\\n # the gpt-5.5 wss transport); stdin keeps the key out of process args.\\n printenv OPENAI_API_KEY | npx -y @openai/codex@0 login --with-api-key || true\\n # The runner is itself an ephemeral sandbox; bypass Codex\\'s own sandbox\\n # (bubblewrap can\\'t init here) and approval gate (cancels the MCP write).\\n CODEX_ARGS=(exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check)\\n if [ -n \"${VISUAL_RECAP_MODEL:-}\" ]; then CODEX_ARGS+=(--model \"$VISUAL_RECAP_MODEL\"); fi\\n # Validate reasoning against the enum before embedding it in the TOML override.\\n case \"${VISUAL_RECAP_REASONING:-}\" in\\n none|minimal|low|medium|high|xhigh)\\n CODEX_ARGS+=(-c \"model_reasoning_effort=\\\\\"$VISUAL_RECAP_REASONING\\\\\"\") ;;\\n \"\") ;;\\n *) echo \"Ignoring invalid VISUAL_RECAP_REASONING: $VISUAL_RECAP_REASONING\" ;;\\n esac\\n rm -f recap-source.json recap-url.txt recap-url-reason.txt codex-events.jsonl codex-stderr.log\\n run_codex() {\\n set +e\\n npx -y @openai/codex@0 \"${CODEX_ARGS[@]}\" --json \"$(cat recap-prompt.md)\" 2> codex-stderr.log | tee codex-events.jsonl\\n CODEX_STATUS=\"${PIPESTATUS[0]}\"\\n set -e\\n echo \"$CODEX_STATUS\" > codex-exit-code.txt\\n }\\n run_codex\\n # Retry once if the agent exited without writing recap-source.json\\n # (see the Claude step) — the publisher needs that file.\\n if [ ! -s recap-source.json ]; then\\n echo \"::warning::recap-source.json missing after the agent run; retrying the agent once.\"\\n sleep 5\\n run_codex\\n fi\\n\\n - name: Publish recap source\\n id: publish\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n run: |\\n set -uo pipefail\\n ARGS=(--source recap-source.json --out recap-url.txt --repo \"$GITHUB_REPOSITORY\" --pr \"$PR_NUMBER\" --app-url \"$PLAN_RECAP_APP_URL\" --token \"$PLAN_RECAP_TOKEN\")\\n if [ -n \"${PREV_PLAN_ID:-}\" ]; then ARGS+=(--prev-plan-id \"$PREV_PLAN_ID\"); fi\\n ARGS+=(--source-type pull-request --source-repo \"$GITHUB_REPOSITORY\" --source-pr-number \"$PR_NUMBER\")\\n if [ \"${PR_MERGED:-false}\" = \"true\" ] || [ -n \"${PR_MERGED_AT:-}\" ]; then\\n ARGS+=(--source-pr-state merged)\\n elif [ -n \"${PR_STATE:-}\" ]; then\\n ARGS+=(--source-pr-state \"$PR_STATE\")\\n fi\\n if [ -n \"${PR_MERGED_AT:-}\" ]; then ARGS+=(--source-pr-merged-at \"$PR_MERGED_AT\"); fi\\n $RECAP_CLI recap publish \"${ARGS[@]}\"\\n\\n - name: Read plan URL\\n id: url\\n if: steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n run: |\\n set -uo pipefail\\n PLAN_URL=\"\"\\n URL_REASON=\"\"\\n if [ -f recap-url.txt ]; then\\n PLAN_URL=\"$(tr -d \\'\\\\r\\\\n\\' < recap-url.txt | tr -d \\' \\')\"\\n elif [ -f recap-url-reason.txt ]; then\\n URL_REASON=\"$(cat recap-url-reason.txt)\"\\n else\\n URL_REASON=\"recap-url.txt was not created.\"\\n fi\\n # recap-url.txt is agent-written -> untrusted. Rebuild a canonical\\n # recap URL from the trusted app base and a strictly validated plan id,\\n # preserving path-prefixed self-hosted mounts.\\n if [ -z \"$URL_REASON\" ]; then\\n URL_RESULT=$(PLAN_URL=\"$PLAN_URL\" node <<\\'NODE\\'\\n const emit = (value) => process.stdout.write(JSON.stringify(value));\\n try {\\n const raw = process.env.PLAN_URL || \"\";\\n if (!raw) {\\n emit({ url: \"\", reason: \"recap-url.txt was empty\" });\\n process.exit(0);\\n }\\n const trusted = new URL(process.env.PLAN_RECAP_APP_URL || \"https://plan.agent-native.com\");\\n const parsed = /^https?:\\\\/\\\\//i.test(raw)\\n ? new URL(raw)\\n : new URL(raw, trusted);\\n if (parsed.origin !== trusted.origin) {\\n emit({ url: \"\", reason: `recap-url.txt points at ${parsed.origin}, expected ${trusted.origin}` });\\n process.exit(0);\\n }\\n\\n const base = trusted.pathname.replace(/\\\\/$/, \"\");\\n const paths = [parsed.pathname];\\n if (base && parsed.pathname.startsWith(`${base}/`)) {\\n paths.push(parsed.pathname.slice(base.length) || \"/\");\\n }\\n\\n for (const path of paths) {\\n const match = path.match(/^\\\\/(?:plans|recaps)\\\\/([A-Za-z0-9_-]+)\\\\/?$/);\\n if (match) {\\n emit({ url: `${trusted.origin}${base}/recaps/${match[1]}`, reason: \"\" });\\n process.exit(0);\\n }\\n }\\n emit({ url: \"\", reason: \"recap-url.txt did not contain a valid /plans/<id> or /recaps/<id> URL for the configured plan app\" });\\n } catch {\\n emit({ url: \"\", reason: \"recap-url.txt was not a valid URL or recap path\" });\\n }\\n NODE\\n )\\n CANONICAL_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).url||\"\")}catch{process.stdout.write(\"\")}\\' \"$URL_RESULT\")\\n URL_REASON=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).reason||\"\")}catch{process.stdout.write(\"recap-url.txt URL validation failed\")}\\' \"$URL_RESULT\")\\n else\\n CANONICAL_URL=\"\"\\n fi\\n if [ -n \"$CANONICAL_URL\" ]; then\\n echo \"plan_url=$CANONICAL_URL\" >> \"$GITHUB_OUTPUT\"; echo \"ok=true\" >> \"$GITHUB_OUTPUT\"\\n else\\n echo \"plan_url=\" >> \"$GITHUB_OUTPUT\"; echo \"ok=false\" >> \"$GITHUB_OUTPUT\"\\n fi\\n {\\n echo \\'reason<<__RECAP_URL_REASON_EOF__\\'\\n echo \"$URL_REASON\"\\n echo \\'__RECAP_URL_REASON_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n\\n - name: Summarize agent failure\\n id: agent_summary\\n if: steps.url.outputs.ok != \\'true\\' && steps.diff.outputs.tiny != \\'true\\' && steps.route_health.outputs.unhealthy != \\'true\\' && steps.scan.outputs.suppressed != \\'true\\'\\n continue-on-error: true\\n env:\\n RECAP_AGENT: ${{ needs.gate.outputs.agent }}\\n RECAP_BLOCK_REFERENCE_SUMMARY: ${{ steps.block_reference.outputs.summary }}\\n RECAP_PUBLISH_REASON: ${{ steps.publish.outputs.reason }}\\n run: |\\n set -uo pipefail\\n if [ -n \"${RECAP_BLOCK_REFERENCE_SUMMARY:-}\" ]; then\\n {\\n echo \\'summary<<__RECAP_BLOCK_REFERENCE_SUMMARY_EOF__\\'\\n echo \"$RECAP_BLOCK_REFERENCE_SUMMARY\"\\n echo \\'__RECAP_BLOCK_REFERENCE_SUMMARY_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n node -e \\'process.stdout.write(JSON.stringify({ ok: true, summary: process.env.RECAP_BLOCK_REFERENCE_SUMMARY || \"\" }) + \"\\\\n\")\\'\\n exit 0\\n fi\\n if [ -n \"${RECAP_PUBLISH_REASON:-}\" ]; then\\n {\\n echo \\'summary<<__RECAP_PUBLISH_SUMMARY_EOF__\\'\\n echo \"$RECAP_PUBLISH_REASON\"\\n echo \\'__RECAP_PUBLISH_SUMMARY_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n node -e \\'process.stdout.write(JSON.stringify({ ok: true, summary: process.env.RECAP_PUBLISH_REASON || \"\" }) + \"\\\\n\")\\'\\n exit 0\\n fi\\n RESULT=claude-result.json\\n STDERR=claude-stderr.log\\n EXIT_CODE=claude-exit-code.txt\\n if [ \"$RECAP_AGENT\" = \"codex\" ]; then\\n RESULT=codex-events.jsonl\\n STDERR=codex-stderr.log\\n EXIT_CODE=codex-exit-code.txt\\n fi\\n $RECAP_CLI recap agent-summary --agent \"$RECAP_AGENT\" --result-file \"$RESULT\" --stderr-file \"$STDERR\" --exit-code-file \"$EXIT_CODE\" || true\\n\\n - name: Attach usage\\n if: steps.url.outputs.ok == \\'true\\'\\n continue-on-error: true\\n env:\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n # Use the gate-normalized agent so \"Codex\" still selects the right file.\\n RECAP_AGENT: ${{ needs.gate.outputs.agent }}\\n run: |\\n set -uo pipefail\\n RESULT=claude-result.json\\n if [ \"$RECAP_AGENT\" = \"codex\" ]; then RESULT=codex-events.jsonl; fi\\n if [ -f \"$RESULT\" ]; then $RECAP_CLI recap usage --plan-url \"$PLAN_URL\" --agent \"$RECAP_AGENT\" --result-file \"$RESULT\" --model \"${VISUAL_RECAP_MODEL:-}\" --app-url \"$PLAN_RECAP_APP_URL\" --token \"$PLAN_RECAP_TOKEN\" || true; fi\\n\\n - name: Cache Playwright browsers\\n if: steps.url.outputs.ok == \\'true\\'\\n uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3\\n with:\\n path: ~/.cache/ms-playwright\\n key: playwright-1-${{ runner.os }}\\n\\n - name: Screenshot + upload\\n id: shot\\n if: steps.url.outputs.ok == \\'true\\'\\n continue-on-error: true\\n env:\\n # recap-url.txt is untrusted agent output; pass via env, never ${{ }}.\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n run: |\\n set -uo pipefail\\n if [ -n \"${RECAP_PLAYWRIGHT:-}\" ] && [ -x \"$RECAP_PLAYWRIGHT\" ]; then\\n \"$RECAP_PLAYWRIGHT\" install --with-deps chromium || true\\n elif command -v pnpm >/dev/null 2>&1; then\\n pnpm exec playwright install --with-deps chromium 2>/dev/null || npx -y playwright@1 install --with-deps chromium || true\\n else\\n npx -y playwright@1 install --with-deps chromium || true\\n fi\\n IMAGE_CACHE_KEY=\"$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT\"\\n LIGHT_SHOT_JSON=\"$($RECAP_CLI recap shot --url \"$PLAN_URL\" --token \"$PLAN_RECAP_TOKEN\" --app-url \"$PLAN_RECAP_APP_URL\" --out recap.png --theme light --image-cache-key \"$IMAGE_CACHE_KEY\" || echo \\'{}\\')\"\\n DARK_SHOT_JSON=\"$($RECAP_CLI recap shot --url \"$PLAN_URL\" --token \"$PLAN_RECAP_TOKEN\" --app-url \"$PLAN_RECAP_APP_URL\" --out recap-dark.png --theme dark --image-cache-key \"$IMAGE_CACHE_KEY\" || echo \\'{}\\')\"\\n for SHOT_LABEL in light dark; do\\n if [ \"$SHOT_LABEL\" = \"light\" ]; then SHOT_JSON=\"$LIGHT_SHOT_JSON\"; else SHOT_JSON=\"$DARK_SHOT_JSON\"; fi\\n SHOT_LABEL=\"$SHOT_LABEL\" SHOT_JSON=\"$SHOT_JSON\" node -e \\'const label = process.env.SHOT_LABEL || \"shot\"; let parsed = {}; try { parsed = JSON.parse(process.env.SHOT_JSON || \"{}\"); } catch { parsed = { ok: false, reason: \"invalid shot JSON\" }; } const summary = { ok: parsed.ok === true, imageUrl: parsed.imageUrl ? \"[present]\" : \"\", out: typeof parsed.out === \"string\" ? parsed.out : \"\", reason: typeof parsed.reason === \"string\" ? parsed.reason.slice(0, 500) : \"\" }; console.log(`[recap shot] ${label}: ${JSON.stringify(summary)}`);\\'\\n done\\n IMAGE_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).imageUrl||\"\")}catch{process.stdout.write(\"\")}\\' \"$LIGHT_SHOT_JSON\")\\n DARK_IMAGE_URL=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).imageUrl||\"\")}catch{process.stdout.write(\"\")}\\' \"$DARK_SHOT_JSON\")\\n SHOT_STATUS=$(LIGHT_SHOT_JSON=\"$LIGHT_SHOT_JSON\" DARK_SHOT_JSON=\"$DARK_SHOT_JSON\" node <<\\'NODE\\'\\n const parse = (raw) => { try { return JSON.parse(raw || \"{}\"); } catch { return { ok: false, reason: \"invalid shot JSON\" }; } };\\n const shots = [[\"light\", parse(process.env.LIGHT_SHOT_JSON)], [\"dark\", parse(process.env.DARK_SHOT_JSON)]];\\n const hasImage = shots.some(([, shot]) => typeof shot.imageUrl === \"string\" && shot.imageUrl.trim());\\n const reasons = shots.flatMap(([label, shot]) => {\\n if (typeof shot.reason === \"string\" && shot.reason.trim()) return [`${label}: ${shot.reason.trim()}`];\\n if (!(typeof shot.imageUrl === \"string\" && shot.imageUrl.trim())) return [`${label}: no imageUrl returned`];\\n return [];\\n });\\n process.stdout.write(JSON.stringify({ ok: hasImage, reason: hasImage ? \"\" : reasons.join(\"; \").slice(0, 1000) }));\\n NODE\\n )\\n SHOT_OK=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).ok===true?\"true\":\"false\")}catch{process.stdout.write(\"false\")}\\' \"$SHOT_STATUS\")\\n SHOT_REASON=$(node -e \\'try{process.stdout.write(JSON.parse(process.argv[1]).reason||\"\")}catch{process.stdout.write(\"invalid shot status JSON\")}\\' \"$SHOT_STATUS\")\\n if [ \"$SHOT_OK\" != \"true\" ]; then\\n echo \"::warning::Visual recap screenshot unavailable; posting screenshot-failed recap comment. $SHOT_REASON\"\\n fi\\n echo \"image_url=$IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"light_image_url=$IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"dark_image_url=$DARK_IMAGE_URL\" >> \"$GITHUB_OUTPUT\"\\n echo \"shot_ok=$SHOT_OK\" >> \"$GITHUB_OUTPUT\"\\n {\\n echo \\'shot_reason<<__RECAP_SHOT_REASON_EOF__\\'\\n echo \"$SHOT_REASON\"\\n echo \\'__RECAP_SHOT_REASON_EOF__\\'\\n } >> \"$GITHUB_OUTPUT\"\\n if [ -f recap.png ] || [ -f recap-dark.png ]; then echo \"captured=true\" >> \"$GITHUB_OUTPUT\"; else echo \"captured=false\" >> \"$GITHUB_OUTPUT\"; fi\\n\\n - name: Upload recap screenshot artifact\\n if: steps.shot.outputs.captured == \\'true\\'\\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\\n with:\\n name: pr-visual-recap-${{ github.event.pull_request.number }}\\n path: |\\n recap.png\\n recap-dark.png\\n if-no-files-found: ignore\\n retention-days: 14\\n\\n - name: Upload recap source artifact\\n if: always() && !cancelled()\\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\\n with:\\n # recap-source.json + the agent transcript (claude-result.json /\\n # codex-events.jsonl + stderr) are the only window into WHAT the agent\\n # did when a publish fails (no plan URL) — INCLUDING the case where it\\n # finished without writing recap-source.json at all. The sticky comment\\n # only shows the screenshot, so without these a failed recap is\\n # undebuggable. Uploaded on success + failure; tolerant when absent.\\n name: pr-visual-recap-source-${{ github.event.pull_request.number }}\\n path: |\\n recap-source.json\\n claude-result.json\\n claude-stderr.log\\n codex-events.jsonl\\n codex-stderr.log\\n if-no-files-found: ignore\\n retention-days: 14\\n\\n - name: Upsert sticky comment\\n if: always() && !cancelled()\\n continue-on-error: true\\n env:\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n RECAP_IMAGE_URL: ${{ steps.shot.outputs.image_url }}\\n RECAP_LIGHT_IMAGE_URL: ${{ steps.shot.outputs.light_image_url }}\\n RECAP_DARK_IMAGE_URL: ${{ steps.shot.outputs.dark_image_url }}\\n RECAP_SHOT_OK: ${{ steps.shot.outputs.shot_ok }}\\n RECAP_SHOT_REASON: ${{ steps.shot.outputs.shot_reason }}\\n SUPPRESSED: ${{ steps.scan.outputs.suppressed }}\\n SUPPRESSED_JSON: ${{ steps.scan.outputs.json }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n DIFF_TINY: ${{ steps.diff.outputs.tiny }}\\n PREV_PLAN_ID: ${{ steps.prev.outputs.plan_id }}\\n RECAP_AUTH_FAILED: ${{ steps.auth_probe.outputs.auth_failed }}\\n RECAP_AGENT_SUMMARY: ${{ steps.agent_summary.outputs.summary }}\\n # Prefer the route-health diagnostic when the plan app routes are not\\n # yet deployed so the comment explains the 404 instead of a generic\\n # \"recap-url.txt was not created\" message.\\n RECAP_URL_REASON: ${{ steps.route_health.outputs.reason || steps.url.outputs.reason }}\\n run: |\\n set -euo pipefail\\n $RECAP_CLI recap comment upsert --repo \"$GITHUB_REPOSITORY\" --issue \"$PR_NUMBER\" --token \"$GH_TOKEN\" --head-sha \"$HEAD_SHA\"\\n\\n - name: Complete visual recap check\\n if: always() && !cancelled() && steps.recap_check.outputs.check_run_id != \\'\\'\\n continue-on-error: true\\n env:\\n # Untrusted/step values via env (NOT ${{ }}-interpolated into the run\\n # body): the agent-written plan URL and the scan JSON could inject shell.\\n CHECK_RUN_ID: ${{ steps.recap_check.outputs.check_run_id }}\\n PLAN_OK: ${{ steps.url.outputs.ok }}\\n PLAN_URL: ${{ steps.url.outputs.plan_url }}\\n SUPPRESSED: ${{ steps.scan.outputs.suppressed }}\\n SUPPRESSED_JSON: ${{ steps.scan.outputs.json }}\\n DIFF_HUGE: ${{ steps.diff.outputs.huge }}\\n DIFF_TINY: ${{ steps.diff.outputs.tiny }}\\n RECAP_AGENT_SUMMARY: ${{ steps.agent_summary.outputs.summary }}\\n RECAP_URL_REASON: ${{ steps.route_health.outputs.reason || steps.url.outputs.reason }}\\n run: |\\n set -uo pipefail\\n $RECAP_CLI recap check complete \\\\\\n --check-run-id \"$CHECK_RUN_ID\" \\\\\\n --plan-ok \"$PLAN_OK\" \\\\\\n --plan-url \"$PLAN_URL\" \\\\\\n --suppressed \"$SUPPRESSED\" \\\\\\n --suppressed-json \"$SUPPRESSED_JSON\" \\\\\\n --huge \"$DIFF_HUGE\" \\\\\\n --tiny \"$DIFF_TINY\" \\\\\\n --failure-summary \"$RECAP_AGENT_SUMMARY\" \\\\\\n --url-reason \"$RECAP_URL_REASON\" \\\\\\n --workflow-url \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"\\n';\n"]}
@@ -8,9 +8,9 @@
8
8
  * thin, deterministic glue around that:
9
9
  *
10
10
  * gate The security boundary: decide whether the recap runs at all
11
- * (skipping drafts, forks, bots, missing secrets, an invalid
12
- * agent/model, and PRs that touch recap-control files) and which
13
- * normalized backend agent to use.
11
+ * (skipping drafts, forks without secret access, bots, missing
12
+ * secrets, an invalid agent/model, and untrusted PRs that touch
13
+ * recap-control files) and which normalized backend agent to use.
14
14
  * collect-diff Collect the bounded base...head diff (excluding lockfiles,
15
15
  * build output, snapshots), cap it at ~600KB, and classify the
16
16
  * huge/tiny flags.
@@ -1 +1 @@
1
- {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"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"}
package/dist/cli/recap.js CHANGED
@@ -8,9 +8,9 @@
8
8
  * thin, deterministic glue around that:
9
9
  *
10
10
  * gate The security boundary: decide whether the recap runs at all
11
- * (skipping drafts, forks, bots, missing secrets, an invalid
12
- * agent/model, and PRs that touch recap-control files) and which
13
- * normalized backend agent to use.
11
+ * (skipping drafts, forks without secret access, bots, missing
12
+ * secrets, an invalid agent/model, and untrusted PRs that touch
13
+ * recap-control files) and which normalized backend agent to use.
14
14
  * collect-diff Collect the bounded base...head diff (excluding lockfiles,
15
15
  * build output, snapshots), cap it at ~600KB, and classify the
16
16
  * huge/tiny flags.
@@ -2227,7 +2227,14 @@ importPlaywright = defaultImportPlaywright) {
2227
2227
  });
2228
2228
  }
2229
2229
  const page = await context.newPage();
2230
- await page.goto(captureUrl, { waitUntil: "networkidle", timeout: 45_000 });
2230
+ await page.goto(captureUrl, {
2231
+ waitUntil: "domcontentloaded",
2232
+ timeout: 45_000,
2233
+ });
2234
+ await page.waitForLoadState("load", { timeout: 15_000 }).catch(() => {
2235
+ // The selectors below are the real readiness signal for screenshots.
2236
+ // Some recap pages keep long-lived/background requests open.
2237
+ });
2231
2238
  const selectors = [
2232
2239
  "[data-plan-document]",
2233
2240
  "[data-plan-block]",
@@ -2423,11 +2430,11 @@ function recoverRecapFailureEnv(env = process.env) {
2423
2430
  return recovered;
2424
2431
  }
2425
2432
  /**
2426
- * Files that, if a PR touches them, would let that PR rewrite repo-pinned skill
2427
- * instructions or agent config the trusted recap job loads. The workflow runs
2428
- * the recap CLI from trusted base-branch source (or an installed package), so
2429
- * normal package code such as `packages/core/**` and recap workflow YAML can be
2430
- * recapped without executing PR-modified CLI code.
2433
+ * Files that, if an untrusted PR touches them, would let that PR rewrite
2434
+ * repo-pinned skill instructions or root agent config the trusted recap job
2435
+ * loads. The workflow runs the recap CLI from trusted base-branch source (or an
2436
+ * installed package), so normal package code, template-local AGENTS.md files,
2437
+ * and recap workflow YAML can be recapped without executing PR-modified CLI code.
2431
2438
  */
2432
2439
  function normalizeRecapSkillSourceMode(value) {
2433
2440
  return (value || "auto").toLowerCase();
@@ -2437,10 +2444,10 @@ function isRepoPinnedRecapSkillSource(value) {
2437
2444
  }
2438
2445
  export function isRecapSensitivePath(p, options = {}) {
2439
2446
  const skillSource = options.skillSource;
2440
- if (/(^|\/)\.claude\//.test(p) ||
2441
- /(^|\/)CLAUDE\.md$/.test(p) ||
2442
- /(^|\/)AGENTS\.md$/.test(p) ||
2443
- /(^|\/)\.mcp\.json$/.test(p)) {
2447
+ if (p.startsWith(".claude/") ||
2448
+ p === "CLAUDE.md" ||
2449
+ p === "AGENTS.md" ||
2450
+ p === ".mcp.json") {
2444
2451
  return true;
2445
2452
  }
2446
2453
  if (isRepoPinnedRecapSkillSource(skillSource) &&
@@ -2521,15 +2528,17 @@ export function evaluateRecapGate(input) {
2521
2528
  if (skillSource && !["auto", "latest", "repo"].includes(skillSource)) {
2522
2529
  reasons.push('invalid VISUAL_RECAP_SKILL_SOURCE value (expected "auto", "latest", or "repo")');
2523
2530
  }
2524
- // Self-modifying guard: if this PR changes the visual-recap/visual-plan skill
2525
- // when CI is explicitly pinned to repo-local skill instructions, or any agent
2526
- // config the runner would load (.claude/**, CLAUDE.md, AGENTS.md, .mcp.json),
2527
- // skip the ENTIRE job — not just the agent — so a PR can never rewrite what
2528
- // the agent loads (skill, hooks, settings) and exfiltrate the publish/API
2529
- // secrets. In the default auto/latest modes the recap prompt comes from the
2530
- // trusted bundled skill, so visual skill and recap workflow files are ordinary
2531
- // reviewed content and may be recapped.
2532
- const shouldApplySensitivePathGuard = Boolean(pr) && (isFork || (!isPrivate && !isTrustedAuthor));
2531
+ // Self-modifying guard: if an untrusted PR changes the visual-recap/visual-plan
2532
+ // skill when CI is explicitly pinned to repo-local skill instructions, or root
2533
+ // agent config the runner would load (.claude/**, CLAUDE.md, AGENTS.md,
2534
+ // .mcp.json), skip the ENTIRE job — not just the agent — so a PR can never
2535
+ // rewrite what the agent loads (skill, hooks, settings) and exfiltrate the
2536
+ // publish/API secrets. In the default auto/latest modes the recap prompt comes
2537
+ // from the trusted bundled skill, so visual skill and recap workflow files are
2538
+ // ordinary reviewed content and may be recapped. Trusted write actors may edit
2539
+ // recap-control files as reviewable content; running the recap is useful signal
2540
+ // for those changes.
2541
+ const shouldApplySensitivePathGuard = Boolean(pr) && !isTrustedAuthor && (isFork || !isPrivate);
2533
2542
  const hits = shouldApplySensitivePathGuard
2534
2543
  ? input.changedFiles.filter((p) => isRecapSensitivePath(p, { skillSource }))
2535
2544
  : [];
@@ -2710,13 +2719,11 @@ export function buildGateSkipCommentBody() {
2710
2719
  * Pure so it can be unit-tested.
2711
2720
  */
2712
2721
  export function appendGateSkipLine(existingBody, skipLine) {
2713
- // Remove any previous gate-skip line (pattern: `_Recap skipped for ...._`)
2714
- const withoutPrev = existingBody
2715
- .split("\n")
2716
- .filter((l) => !/_Recap skipped for .+_$/.test(l.trim()))
2717
- .join("\n")
2718
- .trimEnd();
2719
- return `${withoutPrev}\n\n${skipLine}`;
2722
+ const planIdMatch = existingBody.match(/<!--\s*plan-id:\s*([A-Za-z0-9_-]{1,64})\s*-->/);
2723
+ const planIdMarker = planIdMatch
2724
+ ? `\n\n<!-- plan-id: ${planIdMatch[1]} -->`
2725
+ : "";
2726
+ return `${buildGateSkipCommentBody()}${planIdMarker}\n\n${skipLine}`;
2720
2727
  }
2721
2728
  /* -------------------------------------------------------------------------- */
2722
2729
  /* Check run — the "Visual Recap" GitHub check (was two inline github-script */
@@ -3231,11 +3238,11 @@ Usage:
3231
3238
  environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI === 'true', AGENT,
3232
3239
  VISUAL_RECAP_MODEL), the repo from $GITHUB_REPOSITORY, and the PR's changed
3233
3240
  files from the GitHub REST API (paged, with GH_TOKEN/GITHUB_TOKEN). Skips
3234
- drafts, forks, bot authors, the missing-secret case, an invalid agent/model,
3235
- and any PR that touches recap-control files (repo-pinned skill instructions,
3236
- .claude/**, CLAUDE.md, AGENTS.md, .mcp.json) — failing CLOSED on any
3237
- file-list error. Writes run=<true|false> and agent=<claude|codex> to
3238
- $GITHUB_OUTPUT.
3241
+ drafts, forks without secret access, bot authors, the missing-secret case, an
3242
+ invalid agent/model, and any untrusted PR that touches recap-control files
3243
+ (repo-pinned skill instructions, .claude/**, root CLAUDE.md, root AGENTS.md,
3244
+ root .mcp.json) — failing CLOSED on any file-list error. Writes
3245
+ run=<true|false> and agent=<claude|codex> to $GITHUB_OUTPUT.
3239
3246
  npx @agent-native/core@latest recap agent-summary
3240
3247
  Read the captured Claude/Codex result file and write a sanitized one-line
3241
3248
  summary to stdout and $GITHUB_OUTPUT (summary). Used only when no plan URL