@agent-native/core 0.51.6 → 0.51.8

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 (35) hide show
  1. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  2. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  3. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  4. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  5. package/dist/cli/skills.d.ts +13 -0
  6. package/dist/cli/skills.d.ts.map +1 -1
  7. package/dist/cli/skills.js +255 -24
  8. package/dist/cli/skills.js.map +1 -1
  9. package/dist/client/assistant-ui-recovery.d.ts.map +1 -1
  10. package/dist/client/assistant-ui-recovery.js +15 -12
  11. package/dist/client/assistant-ui-recovery.js.map +1 -1
  12. package/dist/provider-api/corpus-jobs-store.d.ts +95 -0
  13. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -0
  14. package/dist/provider-api/corpus-jobs-store.js +394 -0
  15. package/dist/provider-api/corpus-jobs-store.js.map +1 -0
  16. package/dist/provider-api/corpus-jobs.d.ts +146 -0
  17. package/dist/provider-api/corpus-jobs.d.ts.map +1 -0
  18. package/dist/provider-api/corpus-jobs.js +1192 -0
  19. package/dist/provider-api/corpus-jobs.js.map +1 -0
  20. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  21. package/dist/server/agent-chat-plugin.js +9 -2
  22. package/dist/server/agent-chat-plugin.js.map +1 -1
  23. package/docs/content/template-analytics.md +0 -8
  24. package/docs/content/template-assets.md +0 -6
  25. package/docs/content/template-brain.md +0 -8
  26. package/docs/content/template-calendar.md +0 -8
  27. package/docs/content/template-clips.md +0 -8
  28. package/docs/content/template-content.md +0 -14
  29. package/docs/content/template-design.md +0 -6
  30. package/docs/content/template-forms.md +0 -10
  31. package/docs/content/template-mail.md +0 -8
  32. package/docs/content/template-plan.md +180 -0
  33. package/docs/content/template-slides.md +0 -8
  34. package/docs/content/template-videos.md +0 -8
  35. package/package.json +3 -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,o8oCAAo8oC,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]\\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\\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 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 if (pr) {\\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 core.info(run ? `Visual recap will run (${agent}).` : `Visual recap skipped: ${reasons.join(\\'; \\')}`);\\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 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: 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 \"$HEAD_SHA\" --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\\'\\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 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.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.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.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\\n - name: Run agent (Codex)\\n id: codex\\n if: needs.gate.outputs.agent == \\'codex\\' && steps.diff.outputs.tiny != \\'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\\n - name: Publish recap source\\n id: publish\\n if: steps.diff.outputs.tiny != \\'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 $RECAP_CLI recap publish \"${ARGS[@]}\"\\n\\n - name: Read plan URL\\n id: url\\n if: steps.diff.outputs.tiny != \\'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.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 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 || 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 || 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 if [ -z \"$IMAGE_URL\" ] && [ -z \"$DARK_IMAGE_URL\" ]; then\\n echo \"::warning::Visual recap screenshot unavailable; posting link-only recap comment.\"\\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 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 # The agent-authored recap-source.json is the only window into WHAT the\\n # agent emitted when a publish fails (no plan URL). The sticky comment\\n # only shows the screenshot, so without this artifact a failed recap is\\n # undebuggable. Uploaded on success and failure; tolerant when absent.\\n name: pr-visual-recap-source-${{ github.event.pull_request.number }}\\n path: recap-source.json\\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 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,m2sCAAm2sC,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]\\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\\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 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 all public-repo PRs: a fork or a\\n // public same-repo author could rewrite loaded instruction files\\n // (AGENTS.md/CLAUDE.md/.claude/.mcp.json) and exfiltrate the\\n // secret-backed agent run. Skip it ONLY for private-repo same-repo\\n // PRs, where the author is a trusted org member — a deliberate owner\\n // risk acceptance so legit instruction edits don\\'t false-skip recaps.\\n if (pr && (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 /(^|\\\\/)\\\\.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 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: 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 \"$HEAD_SHA\" --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\\'\\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 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.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.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.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.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.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 $RECAP_CLI recap publish \"${ARGS[@]}\"\\n\\n - name: Read plan URL\\n id: url\\n if: steps.diff.outputs.tiny != \\'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.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 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 || 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 || 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 if [ -z \"$IMAGE_URL\" ] && [ -z \"$DARK_IMAGE_URL\" ]; then\\n echo \"::warning::Visual recap screenshot unavailable; posting link-only recap comment.\"\\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 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 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"]}
@@ -51,6 +51,7 @@ export declare const BUILT_IN_APP_SKILLS: {
51
51
  };
52
52
  export declare const AGENT_NATIVE_SKILL_METADATA_FILE = "agent-native-skill.json";
53
53
  type SkillsCommand = "list" | "add" | "status" | "update" | "help";
54
+ type PlanInstallMode = "hosted" | "local-files" | "self-hosted";
54
55
  export interface ParsedSkillsArgs {
55
56
  command: SkillsCommand;
56
57
  target?: string;
@@ -78,6 +79,12 @@ export interface ParsedSkillsArgs {
78
79
  * an ngrok tunnel, a local dev origin, or a self-hosted deployment.
79
80
  */
80
81
  mcpUrl?: string;
82
+ /**
83
+ * Storage/backend mode for the Plans skills. Hosted is the existing default;
84
+ * local-files installs instructions that default to DB-free MDX + local
85
+ * preview and skips MCP registration/auth.
86
+ */
87
+ planMode?: PlanInstallMode;
81
88
  /**
82
89
  * When installing the visual-plan skill, also write the PR Visual Recap
83
90
  * GitHub Action workflow into `.github/workflows/` so PRs get automatic
@@ -136,6 +143,7 @@ export interface SkillsAddResult {
136
143
  githubActionPath?: string;
137
144
  githubActionExisted?: boolean;
138
145
  githubActionSuggestedCommand?: string;
146
+ planMode?: PlanInstallMode;
139
147
  }
140
148
  interface RunCommandOptions {
141
149
  stdio?: "inherit" | "stderr" | "silent";
@@ -148,6 +156,8 @@ interface RunSkillsOptions {
148
156
  promptSkills?: (context: SkillsTargetPromptContext) => Promise<string[] | null>;
149
157
  promptGithubAction?: (context: SkillsGithubActionPromptContext) => Promise<boolean | null>;
150
158
  promptScope?: (context: SkillsScopePromptContext) => Promise<"project" | "user" | null>;
159
+ promptPlanMode?: (context: SkillsPlanModePromptContext) => Promise<PlanInstallMode | null>;
160
+ promptPlanMcpUrl?: () => Promise<string | null>;
151
161
  runCommand?: (cmd: string, args: string[], options?: RunCommandOptions) => Promise<number>;
152
162
  /**
153
163
  * Injectable connect/auth entrypoint (defaults to the real `agent-native
@@ -186,6 +196,9 @@ interface SkillsGithubActionPromptContext {
186
196
  interface SkillsScopePromptContext {
187
197
  initialScope: "project" | "user";
188
198
  }
199
+ interface SkillsPlanModePromptContext {
200
+ initialMode: PlanInstallMode;
201
+ }
189
202
  export declare function parseSkillsArgs(argv: string[]): ParsedSkillsArgs;
190
203
  export declare function addAgentNativeSkill(parsed: ParsedSkillsArgs, options?: RunSkillsOptions): Promise<SkillsAddResult>;
191
204
  export declare function runSkills(argv: string[], options?: RunSkillsOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/cli/skills.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/cli/skills.ts"],"names":[],"mappings":"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"}