diff --git a/.github/workflows/refresh-github-signals.yml b/.github/workflows/refresh-release-delta.yml similarity index 53% rename from .github/workflows/refresh-github-signals.yml rename to .github/workflows/refresh-release-delta.yml index b242d1e1..265864e3 100644 --- a/.github/workflows/refresh-github-signals.yml +++ b/.github/workflows/refresh-release-delta.yml @@ -1,11 +1,11 @@ -name: Refresh GitHub Signals +name: Refresh Release Delta permissions: contents: write on: schedule: - - cron: "0 * * * *" + - cron: "17 * * * *" workflow_dispatch: concurrency: @@ -14,9 +14,9 @@ concurrency: jobs: refresh: - name: Refresh GitHub-backed Decodex signals + name: Refresh Codex release checkpoints runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 30 env: GH_API_TOKEN: ${{ secrets.GITHUB_PAT_Y }} steps: @@ -37,40 +37,18 @@ jobs: working-directory: site run: npm ci - - name: Install Codex CLI - run: npm install -g @openai/codex@0.116.0 - - - name: Prepare Codex auth.json - env: - CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} - run: | - if [ -z "$CODEX_AUTH_JSON" ]; then - echo "CODEX_AUTH_JSON secret is not set." >&2 - exit 1 - fi - - codex_home="$RUNNER_TEMP/codex-home" - mkdir -p "$codex_home" - printf '%s' "$CODEX_AUTH_JSON" > "$codex_home/auth.json" - chmod 600 "$codex_home/auth.json" - echo "CODEX_HOME=$codex_home" >> "$GITHUB_ENV" - - - name: Verify Codex auth - run: codex login status - - - name: Refresh GitHub signal artifacts + - name: Refresh release delta run: | - python3 scripts/github/sync_latest_signals.py \ + python3 scripts/github/build_release_delta.py \ --repo openai/codex \ + --signals-dir site/src/content/signals \ + --out site/src/content/release-deltas/openai-codex-latest.json \ --token-env GH_API_TOKEN - name: Detect content changes id: changes run: | if [ -z "$(git status --porcelain -- \ - artifacts/github/bundles \ - artifacts/github/analysis \ - site/src/content/signals \ site/src/content/release-deltas/openai-codex-latest.json)" ]; then echo "changed=false" >> "$GITHUB_OUTPUT" else @@ -81,15 +59,11 @@ jobs: if: steps.changes.outputs.changed == 'true' run: cargo make decodex-checks - - name: Commit refreshed GitHub artifacts + - name: Commit refreshed release delta if: steps.changes.outputs.changed == 'true' run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - artifacts/github/bundles \ - artifacts/github/analysis \ - site/src/content/signals \ - site/src/content/release-deltas/openai-codex-latest.json - git commit -m "chore(decodex): refresh github signals" + git add site/src/content/release-deltas/openai-codex-latest.json + git commit -m "chore(decodex): refresh release delta" git push origin HEAD:main diff --git a/.github/workflows/refresh-upstream-radar.yml b/.github/workflows/refresh-upstream-radar.yml new file mode 100644 index 00000000..6d8326ca --- /dev/null +++ b/.github/workflows/refresh-upstream-radar.yml @@ -0,0 +1,68 @@ +name: Refresh Upstream Radar + +permissions: + contents: write + +on: + schedule: + - cron: "0 */6 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + refresh: + name: Refresh Codex upstream queue + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + GH_API_TOKEN: ${{ secrets.GITHUB_PAT_Y }} + steps: + - name: Fetch latest code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: main + fetch-depth: 0 + + - name: Set up Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: npm + cache-dependency-path: site/package-lock.json + + - name: Install site dependencies + working-directory: site + run: npm ci + + - name: Refresh upstream review queue + run: | + python3 scripts/github/sync_upstream_radar.py \ + --repo openai/codex \ + --token-env GH_API_TOKEN \ + --search-limit 40 + + - name: Detect content changes + id: changes + run: | + if [ -z "$(git status --porcelain -- \ + artifacts/github/review-queue)" ]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run repo-native validation + if: steps.changes.outputs.changed == 'true' + run: cargo make decodex-checks + + - name: Commit refreshed GitHub artifacts + if: steps.changes.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add artifacts/github/review-queue + git commit -m "chore(decodex): refresh upstream radar" + git push origin HEAD:main diff --git a/Makefile.toml b/Makefile.toml index ae833c6c..ef771cb0 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -61,6 +61,10 @@ dependencies = [ workspace = false script = [ "python3 scripts/github/validate_change_bundle.py artifacts/github/bundles", + "python3 scripts/github/validate_upstream_review.py artifacts/github/review-queue", + "python3 scripts/github/validate_upstream_review.py artifacts/github/reviews", + "python3 scripts/github/test_social_post_contract.py", + "python3 scripts/github/validate_social_post.py artifacts/social/x", "python3 scripts/github/validate_signal_entry.py site/src/content/signals", ] diff --git a/README.md b/README.md index 2727f6bc..29fefeac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Decodex -Repo-native agent orchestration and public Codex signal publishing. +Repo-native agent orchestration, upstream Codex radar, and public publishing. [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Language Checks](https://github.com/hack-ink/decodex/actions/workflows/language.yml/badge.svg?branch=main)](https://github.com/hack-ink/decodex/actions/workflows/language.yml) @@ -21,13 +21,14 @@ Repo-native agent orchestration and public Codex signal publishing. - Local operator listener with a dashboard at `/` and `/dashboard`, WebSocket snapshot/control traffic at `/dashboard/control`, Decodex App snapshot/account APIs under `/api/`, and `GET /livez` for liveness. -- Static Astro site that publishes GitHub-backed Codex change signals. -- Deterministic GitHub signal pipeline for change bundles, release deltas, rendered - signal entries, and content validation. +- Static Astro site that publishes curated Decodex Radar and Publisher output. +- Deterministic GitHub upstream Radar pipeline for review queues, change bundles, + release deltas, rendered signal entries, and content validation. - Repo-local Radar skills for upstream Codex triage, code analysis, release analysis, - signal drafting, and X post drafting. -- Publisher workflow for checked-in upstream impact classification and reviewable X - drafts for `@decodexspace`. + signal drafting, and X publishing. +- Publisher workflow for checked-in upstream reviews, impact classification, curated + public signals, and automated low-frequency X publication records for + `@decodexspace`. - Installable Decodex plugin with reusable agent-facing skills for planning, manual CLI, automation, commit, land, and labels. - Repository documentation split by question type into spec, runbook, reference, and @@ -60,16 +61,18 @@ runtime. - `apps/decodex-app/` owns the native macOS app that manages Decodex Codex accounts through the bundled Rust app helper. - `site/` owns the Astro static site and checked-in public content. -- `scripts/github/` owns deterministic GitHub bundle, release-delta, render, and - validation scripts. -- `artifacts/github/` owns checked-in GitHub bundles and editorial analysis drafts. +- `scripts/github/` owns deterministic upstream review queue, GitHub bundle, + release-delta, render, and validation scripts. +- `artifacts/github/` owns checked-in review queues, upstream reviews, GitHub bundles, + impact records, and editorial analysis drafts. - `artifacts/archive/` owns checked-in recovery manifests for cold Radar batches stored as GitHub Release assets. -- `artifacts/social/` owns checked-in Publisher social draft artifacts. +- `artifacts/social/` owns checked-in Publisher publication records and generated-media + evidence. - `plugins/decodex/` owns the installable Decodex plugin and reusable agent-facing skills. - `dev/skills/` owns repository-development skills for Radar analysis and Publisher - drafting. They are not packaged with the installable Decodex plugin. + publishing. They are not packaged with the installable Decodex plugin. - `docs/` remains the authoritative documentation surface. Runtime authority stays in `apps/decodex/src/`, the registered project contracts under @@ -77,9 +80,10 @@ Runtime authority stays in `apps/decodex/src/`, the registered project contracts Public site authority stays in `site/`, `scripts/github/`, `artifacts/github/`, and the site/content specs. -Historical Radar trace is local by default. `scripts/github/sync_latest_signals.py` -writes `.decodex/radar.sqlite3` so every inspected upstream commit can be tracked -without publishing every low-level or skipped item to the static site or Git history. +Historical Radar trace is local by default. `scripts/github/sync_upstream_radar.py` +writes `.decodex/radar.sqlite3` and refreshes `upstream_review_queue/v1` so every +inspected upstream commit can be tracked before AI review decides whether it deserves +Decodex follow-up, public content, or only ledger trace. ## Runtime platform support @@ -187,28 +191,33 @@ The public site does not own: The static-site boundary is recorded in `docs/decisions/static-public-site.md`. GitHub Pages setup for `https://decodex.space` lives in `docs/runbook/github-pages-deploy.md`. -## GitHub Signal Pipeline +## Upstream Radar Pipeline -The GitHub-first public signal path stays deterministic and reviewable: +The upstream Codex Radar path starts deterministic and becomes editorial only after +Codex automation reviews source evidence: -- `scripts/github/build_change_bundle.py` builds normalized GitHub bundles under - `artifacts/github/bundles/`. +- `scripts/github/sync_upstream_radar.py` records every observed recent upstream + commit, resolves PRs when possible, and refreshes + `artifacts/github/review-queue/openai-codex-latest.json`. - `dev/skills/README.md` routes the repo-local Radar and editorial instructions. They are not part of the installable Decodex plugin distribution. -- `scripts/github/sync_latest_signals.py` discovers recent upstream commits, resolves - them back to PRs when possible, and refreshes content artifacts. +- `scripts/github/build_change_bundle.py` builds normalized GitHub bundles under + `artifacts/github/bundles/` when a queued subject needs full source context. - `scripts/github/backfill_release_range.py` fills release-window gaps before a release or prerelease summary, but daily Radar still starts from the commit stream. +- `docs/spec/upstream-review.md` records the queue and AI review boundary. - `docs/spec/upstream-impact.md` records how upstream Codex changes are classified for public signals and Control Plane follow-up work. - `scripts/github/render_signal_entry.py` renders reviewed analysis drafts into site content. - `scripts/github/validate_signal_entry.py` validates the published signal collection. -- `docs/spec/social-post-draft.md` and - `docs/runbook/social-publishing-workflow.md` govern optional checked-in X drafts - before external publication. -- `.github/workflows/refresh-github-signals.yml` refreshes GitHub-backed signals every - hour from a trusted runner. +- `docs/spec/social-publishing.md` and + `docs/runbook/social-publishing-workflow.md` govern automated low-frequency X + publication for `@decodexspace`. +- `.github/workflows/refresh-upstream-radar.yml` refreshes deterministic upstream + queue metadata every six hours. +- `.github/workflows/refresh-release-delta.yml` refreshes release and prerelease + checkpoint metadata every hour. - `.github/workflows/deploy-pages.yml` publishes the Astro site to GitHub Pages on pushes to `main`. @@ -276,7 +285,7 @@ The tracked workspace currently keeps: validation script surface - `artifacts/github/` as checked-in GitHub bundle and analysis artifacts - `plugins/decodex/` as the canonical installable Decodex plugin source -- `dev/skills/` as repo-development Radar analysis and Publisher drafting skills that +- `dev/skills/` as repo-development Radar analysis and Publisher publishing skills that are not packaged with the installable Decodex plugin - `docs/spec/` as the normative runtime, workflow, site, and content contract lane - `docs/runbook/` as the operator procedures, validation sequences, deployment steps, diff --git a/artifacts/github/README.md b/artifacts/github/README.md index 9c09be53..a675628f 100644 --- a/artifacts/github/README.md +++ b/artifacts/github/README.md @@ -7,7 +7,7 @@ This directory stores checked-in GitHub signal pipeline artifacts. - `impact/` holds optional `upstream_impact/v1` classifications. `bundles/` and `analysis/` are hot raw artifact directories. Keep raw entries in Git for -at most 28 days, then move cold batches to dedicated `radar-archive-*` GitHub Release +at most 21 days, then move cold batches to dedicated `radar-archive-*` GitHub Release assets and keep the recovery manifest under `artifacts/archive/index/`. Executable automation for these artifacts lives under `scripts/github/`. Repo-local diff --git a/artifacts/github/review-queue/.gitkeep b/artifacts/github/review-queue/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/artifacts/github/review-queue/.gitkeep @@ -0,0 +1 @@ + diff --git a/artifacts/github/review-queue/openai-codex-latest.json b/artifacts/github/review-queue/openai-codex-latest.json new file mode 100644 index 00000000..9afaef60 --- /dev/null +++ b/artifacts/github/review-queue/openai-codex-latest.json @@ -0,0 +1,1505 @@ +{ + "counts": { + "critical": 15, + "high": 6, + "low": 1, + "normal": 18, + "published_subjects_seen": 0, + "recent_commits_scanned": 40, + "subjects_queued": 40 + }, + "generated_at": "2026-06-02T02:36:11Z", + "repo": "openai/codex", + "schema": "upstream_review_queue/v1", + "source": { + "default_branch": "main", + "search_limit": 40, + "signals_dir": "site/src/content/signals" + }, + "subjects": [ + { + "attention_flags": [ + "breaking_change", + "new_feature", + "protocol_change" + ], + "changed_file_count": 6, + "commit_shas": [ + "cf52eb5dabb5c288ba54721f0544c6d383e18fa6" + ], + "committed_at": "2026-06-01T16:33:05Z", + "next_step": "ai_review_required", + "pr_number": 25624, + "pr_url": "https://github.com/openai/codex/pull/25624", + "review_priority": "critical", + "review_reason": "Needs AI review for breaking_change, new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/rollout/src/compression_tests.rs", + "codex-rs/rollout/src/metadata.rs", + "codex-rs/rollout/src/recorder_tests.rs", + "codex-rs/rollout/src/state_db.rs", + "codex-rs/rollout/src/state_db_tests.rs", + "codex-rs/state/src/model/thread_metadata.rs" + ], + "source_state": "merged", + "subject_id": "25624", + "subject_kind": "pr", + "surface_hints": [ + "model_provider", + "tests_ci" + ], + "title": "Preserve renamed thread titles during reconciliation", + "url": "https://github.com/openai/codex/pull/25624" + }, + { + "attention_flags": [ + "auth_account", + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 6, + "commit_shas": [ + "e6f6a17e8a220ef5f3b7a0eaf98d6604a0af3a9f", + "7c30dd5d4bf1edac8c8a708145859f8a7c702c05", + "a7e4204463f28df53672472f71be2ba015e0476b", + "d9ca4f91c209b7a1b8177d5d183e82bbc29cc890", + "005c818463ca9b15c779df6f4fb6ef96c1441cbc", + "fa20516f6487c01f4744f3d7d75b1e8e8ef32f1f", + "acf9e8b600cc6ad8864dc2f9bd805b600fa857f2", + "4d749de426ceb5049f70dc84137bf38ddfc2f42a", + "d4ea8333e30f5ac7b62f3e04b437684a77e7fa89", + "d68c50dfc3a043fa7ae48c7ad67d366d48d983fe", + "69c3f6e1ac89e0ffb92d192edecabbf8eb78d7a8" + ], + "committed_at": "2026-06-01T16:35:58Z", + "next_step": "ai_review_required", + "pr_number": 25089, + "pr_url": "https://github.com/openai/codex/pull/25089", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/core/config.schema.json", + "codex-rs/core/src/thread_manager.rs", + "codex-rs/features/src/lib.rs", + "codex-rs/rollout/src/compression.rs", + "codex-rs/rollout/src/compression_tests.rs", + "codex-rs/rollout/src/lib.rs" + ], + "source_state": "merged", + "subject_id": "25089", + "subject_kind": "pr", + "surface_hints": [ + "config_hooks", + "tests_ci" + ], + "title": "Compress cold local rollouts", + "url": "https://github.com/openai/codex/pull/25089" + }, + { + "attention_flags": [ + "breaking_change", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 11, + "commit_shas": [ + "c9bdb5255b2b4b5d365a1be3ec3a94e34a4e0af0" + ], + "committed_at": "2026-06-01T17:57:11Z", + "next_step": "ai_review_required", + "pr_number": 25636, + "pr_url": "https://github.com/openai/codex/pull/25636", + "review_priority": "critical", + "review_reason": "Needs AI review for breaking_change, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/core/src/config/mod.rs", + "codex-rs/core/src/tools/handlers/multi_agents_spec.rs", + "codex-rs/core/src/tools/handlers/multi_agents_spec_tests.rs", + "codex-rs/core/src/tools/handlers/multi_agents_tests.rs", + "codex-rs/core/src/tools/handlers/multi_agents_v2.rs", + "codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs", + "codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs", + "codex-rs/core/src/tools/spec_plan.rs", + "codex-rs/core/src/tools/spec_plan_tests.rs", + "codex-rs/rollout-trace/README.md", + "codex-rs/rollout-trace/src/tool_dispatch.rs" + ], + "source_state": "merged", + "subject_id": "25636", + "subject_kind": "pr", + "surface_hints": [ + "config_hooks", + "docs_examples", + "tests_ci" + ], + "title": "[codex] Rename multi-agent v2 assign_task to followup_task", + "url": "https://github.com/openai/codex/pull/25636" + }, + { + "attention_flags": [ + "breaking_change", + "new_feature", + "protocol_change" + ], + "changed_file_count": 5, + "commit_shas": [ + "b6cfa856ffde6de7256525f1f162e911b60ecf51" + ], + "committed_at": "2026-06-01T18:04:21Z", + "next_step": "ai_review_required", + "pr_number": 25491, + "pr_url": "https://github.com/openai/codex/pull/25491", + "review_priority": "critical", + "review_reason": "Needs AI review for breaking_change, new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/Cargo.lock", + "codex-rs/chatgpt/src/connectors.rs", + "codex-rs/core-plugins/Cargo.toml", + "codex-rs/core-plugins/src/loader.rs", + "codex-rs/core-plugins/src/manager_tests.rs" + ], + "source_state": "merged", + "subject_id": "25491", + "subject_kind": "pr", + "surface_hints": [ + "config_hooks", + "mcp_plugins", + "tests_ci" + ], + "title": "Preserve plugin app manifest order", + "url": "https://github.com/openai/codex/pull/25491" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "new_feature", + "protocol_change", + "release_packaging", + "security_policy" + ], + "changed_file_count": 55, + "commit_shas": [ + "e99b34a73a62caf181c2743accdbcb4800604612" + ], + "committed_at": "2026-06-01T18:45:07Z", + "next_step": "ai_review_required", + "pr_number": 25151, + "pr_url": "https://github.com/openai/codex/pull/25151", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, new_feature, protocol_change, release_packaging, security_policy.", + "sample_paths": [ + ".github/CODEOWNERS", + "codex-rs/Cargo.lock", + "codex-rs/Cargo.toml", + "codex-rs/apply-patch/BUILD.bazel", + "codex-rs/apply-patch/src/lib.rs", + "codex-rs/core/BUILD.bazel", + "codex-rs/core/Cargo.toml", + "codex-rs/core/src/agents_md.rs", + "codex-rs/core/src/client_common.rs", + "codex-rs/core/src/compact.rs", + "codex-rs/core/src/context/permissions_instructions.rs", + "codex-rs/core/src/context/realtime_end_instructions.rs" + ], + "source_state": "merged", + "subject_id": "25151", + "subject_kind": "pr", + "surface_hints": [ + "cli_tui", + "config_hooks", + "sandbox_permissions", + "tests_ci" + ], + "title": "[codex] Consolidate shared prompts in codex-prompts", + "url": "https://github.com/openai/codex/pull/25151" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 14, + "commit_shas": [ + "5ab860f8677eb0a0b4bdf7053af5c724dff8549a", + "7622e229a25c9c93114d3434eb94af9880fe42fa", + "385010e2764ee04fab2e0677369b6d1534145b7f", + "111adfbdb61ed3437ff0140633662aadb5d26dfd", + "2d3b3e85ff37e6f89e801b727bdbe8a04e08b592" + ], + "committed_at": "2026-06-01T18:53:31Z", + "next_step": "ai_review_required", + "pr_number": 25149, + "pr_url": "https://github.com/openai/codex/pull/25149", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/config/src/loader/tests.rs", + "codex-rs/exec-server/src/client.rs", + "codex-rs/exec-server/src/environment_path.rs", + "codex-rs/exec-server/src/fs_helper.rs", + "codex-rs/exec-server/src/lib.rs", + "codex-rs/exec-server/src/local_file_system.rs", + "codex-rs/exec-server/src/protocol.rs", + "codex-rs/exec-server/src/remote_file_system.rs", + "codex-rs/exec-server/src/sandboxed_file_system.rs", + "codex-rs/exec-server/src/server/file_system_handler.rs", + "codex-rs/exec-server/src/server/handler.rs", + "codex-rs/exec-server/src/server/registry.rs" + ], + "source_state": "merged", + "subject_id": "25149", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "cli_tui", + "config_hooks", + "sandbox_permissions", + "tests_ci" + ], + "title": "exec-server: canonicalize bound filesystem paths", + "url": "https://github.com/openai/codex/pull/25149" + }, + { + "attention_flags": [ + "auth_account", + "deprecated_removed", + "new_feature", + "protocol_change" + ], + "changed_file_count": 9, + "commit_shas": [ + "d89349d83c99e01045855e92319b00ca4179969e" + ], + "committed_at": "2026-06-01T20:01:36Z", + "next_step": "ai_review_required", + "pr_number": 24979, + "pr_url": "https://github.com/openai/codex/pull/24979", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, deprecated_removed, new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/core/config.schema.json", + "codex-rs/core/src/session/mod.rs", + "codex-rs/core/src/session/review.rs", + "codex-rs/core/src/session/turn_context.rs", + "codex-rs/core/src/tools/spec_plan_tests.rs", + "codex-rs/features/src/lib.rs", + "codex-rs/tools/src/lib.rs", + "codex-rs/tools/src/tool_config.rs", + "codex-rs/tools/src/tool_config_tests.rs" + ], + "source_state": "merged", + "subject_id": "24979", + "subject_kind": "pr", + "surface_hints": [ + "config_hooks", + "tests_ci" + ], + "title": "feat: gate unified exec zsh fork composition", + "url": "https://github.com/openai/codex/pull/24979" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 69, + "commit_shas": [ + "7b49072e058c4bd8a25a21bbfd7f908c3a3b90d5" + ], + "committed_at": "2026-06-01T21:49:38Z", + "next_step": "ai_review_required", + "pr_number": 25701, + "pr_url": "https://github.com/openai/codex/pull/25701", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/app-server/tests/common/lib.rs", + "codex-rs/app-server/tests/common/test_app_server.rs", + "codex-rs/app-server/tests/suite/auth.rs", + "codex-rs/app-server/tests/suite/conversation_summary.rs", + "codex-rs/app-server/tests/suite/fuzzy_file_search.rs", + "codex-rs/app-server/tests/suite/v2/account.rs", + "codex-rs/app-server/tests/suite/v2/app_list.rs", + "codex-rs/app-server/tests/suite/v2/attestation.rs", + "codex-rs/app-server/tests/suite/v2/client_metadata.rs", + "codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs", + "codex-rs/app-server/tests/suite/v2/command_exec.rs", + "codex-rs/app-server/tests/suite/v2/compaction.rs" + ], + "source_state": "merged", + "subject_id": "25701", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "auth_accounts", + "cli_tui", + "config_hooks", + "mcp_plugins", + "model_provider", + "release_packaging", + "sandbox_permissions", + "tests_ci" + ], + "title": "fix: rename McpServer to TestAppServer", + "url": "https://github.com/openai/codex/pull/25701" + }, + { + "attention_flags": [ + "breaking_change", + "deprecated_removed", + "new_feature", + "protocol_change" + ], + "changed_file_count": 1, + "commit_shas": [ + "aadd9c999b4e0789f7afb2b9b8cc43000bb47e86" + ], + "committed_at": "2026-06-01T22:14:03Z", + "next_step": "ai_review_required", + "pr_number": 25705, + "pr_url": "https://github.com/openai/codex/pull/25705", + "review_priority": "critical", + "review_reason": "Needs AI review for breaking_change, deprecated_removed, new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/app-server/tests/suite/v2/plugin_list.rs" + ], + "source_state": "merged", + "subject_id": "25705", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "mcp_plugins", + "tests_ci" + ], + "title": "Fix stale TestAppServer rename in plugin_list test", + "url": "https://github.com/openai/codex/pull/25705" + }, + { + "attention_flags": [ + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 18, + "commit_shas": [ + "c68a3b56884ddec1ed904fd64b819d12f146f5bb", + "9f51e9ec7db4794b2e720091ce33bac385a79090" + ], + "committed_at": "2026-06-01T22:24:41Z", + "next_step": "ai_review_required", + "pr_number": 25684, + "pr_url": "https://github.com/openai/codex/pull/25684", + "review_priority": "critical", + "review_reason": "Needs AI review for deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/core/src/tools/handlers/dynamic.rs", + "codex-rs/core/src/tools/handlers/extension_tools.rs", + "codex-rs/core/src/tools/handlers/mcp.rs", + "codex-rs/core/src/tools/handlers/multi_agents.rs", + "codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs", + "codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs", + "codex-rs/core/src/tools/handlers/multi_agents/send_input.rs", + "codex-rs/core/src/tools/handlers/multi_agents/spawn.rs", + "codex-rs/core/src/tools/handlers/multi_agents/wait.rs", + "codex-rs/core/src/tools/handlers/tool_search.rs", + "codex-rs/core/src/tools/mod.rs", + "codex-rs/core/src/tools/registry.rs" + ], + "source_state": "merged", + "subject_id": "25684", + "subject_kind": "pr", + "surface_hints": [ + "mcp_plugins", + "tests_ci" + ], + "title": "Move tool search metadata onto ToolExecutor", + "url": "https://github.com/openai/codex/pull/25684" + }, + { + "attention_flags": [ + "breaking_change", + "new_feature", + "protocol_change" + ], + "changed_file_count": 3, + "commit_shas": [ + "7c59422ee267f12cee565a6dd76931b4ad6c8d5c" + ], + "committed_at": "2026-06-01T23:02:06Z", + "next_step": "ai_review_required", + "pr_number": 25661, + "pr_url": "https://github.com/openai/codex/pull/25661", + "review_priority": "critical", + "review_reason": "Needs AI review for breaking_change, new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/app-server/tests/suite/v2/thread_fork.rs", + "codex-rs/rollout/src/compression.rs", + "codex-rs/thread-store/src/local/read_thread.rs" + ], + "source_state": "merged", + "subject_id": "25661", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "tests_ci" + ], + "title": "Reject directory rollout paths for pathless side chats", + "url": "https://github.com/openai/codex/pull/25661" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "deprecated_removed", + "new_feature", + "protocol_change", + "release_packaging", + "security_policy" + ], + "changed_file_count": 14, + "commit_shas": [ + "4334b19c23dfd421d45dfa91352777d2e075f400", + "cdefa6f448aac8a651d98c5b85c284d896242f30", + "af0113519af81d64846fec7b202759fbd8ed2c37", + "2c9ccbe03076dcbefa986049b734d117ab3a9fae", + "b4a1e10ce55584e53e8acce9fed01cc0e1a7d3f4", + "60c59733f2a2658ac5cecee6a1f5524b4e012132", + "a4101509c5036ea978f7999d2b25cab9b1fb43bc", + "9fa22ca31e61fe79b5fef6483a6fb80b93aeffdf", + "4dc6530cfab353e73c0275b8fa3dcc22d4d49065", + "13e783f35b4f790519d306f8b5473b6fa232c38b", + "672045c2f02af79b25821c0dd4471382d1aa6744", + "e910c8c78884e85b6b16e60e36e3a4e70bd668fa", + "595d035fbc9bf4ac4c380ff2148a7ef351805a67", + "e29a8a4de6972c917c0d5044549b5b6eba11877a", + "f935762b23b5ed8fe790b5ee05875c04bad7260a", + "4a86c1bbc5c51a9f7ca7b8c4b5f02d6f1a991582", + "bb82054dd019c138723ed9a6829380f6187ebd53", + "d31ba12f8286a5189d86911255c9fcd483325b8c", + "0a41113f270a7df86c2e00c4998343536d7f7cc8", + "8566da5226ffc36203500e92584a2ad98498a89a", + "d149f354b3de12bd2095f47ecc41f912cc515075", + "a385d1992c72f5834f34c033bf4bbfd87776ab50", + "055df79812c75ba36c30a9e6167eb88ffaca9673", + "978af3556fe91bada4dffecea29faefe541edb1b", + "927f6fa9e7412fc0cf0b24eeb90a3d44ede782ca", + "0674148fb7ed2d98f79bae1a0b209227f4f937a4", + "8d4fb216c409e2a51ee9c60880f28d4d9ef36b11", + "954d119567382a84b952c2506d0851fd24332916", + "8e2d32aa185a969a7db23e2ea864c7093d55c2c4", + "b3dd333fab07ce29283e9ffee63deeac127dd810", + "abb70eef1d7dc474659d3c99ef785e02840eff85", + "971cbc3d86c3176b265ba24a051fe3a4aa5a033d", + "0cd665434c03aea2528664d88c645dc99c0ce602", + "987c62e089a4f513c5f4650343ca3d663b1a173f", + "6c939f9b0c4579f895b07fff311cc7f27c926b2a", + "e7231c16d3bdc795bc58ebcd43b5f84e9455fcfe", + "7a21873b67988a54c9fd159b9585229e0440481a", + "b2ba6db5f2b83b368e8c33fdb48e78b308470172", + "4978f1b9f2fcc1b4b5de1d32fe2c8e594ff00437", + "efec451c18d5e980c1c27b6d53117311889b81f7", + "f3f550f71d57f93bd0be274e462e701c69a401e3", + "c23ee4a337451a5b2e2d67cc02f6d5844380ccda" + ], + "committed_at": "2026-06-01T23:23:59Z", + "next_step": "ai_review_required", + "pr_number": 22668, + "pr_url": "https://github.com/openai/codex/pull/22668", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, deprecated_removed, new_feature, protocol_change, release_packaging, security_policy.", + "sample_paths": [ + "codex-rs/Cargo.lock", + "codex-rs/cli/src/debug_sandbox.rs", + "codex-rs/core/src/tasks/user_shell.rs", + "codex-rs/core/src/tools/runtimes/mod.rs", + "codex-rs/core/src/tools/runtimes/mod_tests.rs", + "codex-rs/network-proxy/Cargo.toml", + "codex-rs/network-proxy/README.md", + "codex-rs/network-proxy/src/certs.rs", + "codex-rs/network-proxy/src/lib.rs", + "codex-rs/network-proxy/src/proxy.rs", + "codex-rs/network-proxy/src/upstream.rs", + "codex-rs/sandboxing/src/lib.rs" + ], + "source_state": "merged", + "subject_id": "22668", + "subject_kind": "pr", + "surface_hints": [ + "cli_tui", + "config_hooks", + "docs_examples", + "sandbox_permissions", + "tests_ci" + ], + "title": "Wire managed MITM CA trust into child env", + "url": "https://github.com/openai/codex/pull/22668" + }, + { + "attention_flags": [ + "auth_account", + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 47, + "commit_shas": [ + "d378d0d13b7038783171e4e8045f211e75190f5c" + ], + "committed_at": "2026-06-01T23:33:42Z", + "next_step": "ai_review_required", + "pr_number": 25712, + "pr_url": "https://github.com/openai/codex/pull/25712", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/Cargo.lock", + "codex-rs/app-server-protocol/src/protocol/v2/thread.rs", + "codex-rs/app-server/src/request_processors.rs", + "codex-rs/app-server/src/request_processors/external_agent_config_processor.rs", + "codex-rs/app-server/src/request_processors/thread_processor.rs", + "codex-rs/app-server/src/request_processors/thread_processor_tests.rs", + "codex-rs/app-server/src/request_processors/turn_processor.rs", + "codex-rs/app-server/tests/suite/conversation_summary.rs", + "codex-rs/app-server/tests/suite/v2/skills_list.rs", + "codex-rs/app-server/tests/suite/v2/thread_read.rs", + "codex-rs/app-server/tests/suite/v2/thread_shell_command.rs", + "codex-rs/app-server/tests/suite/v2/thread_start.rs" + ], + "source_state": "merged", + "subject_id": "25712", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "cli_tui", + "config_hooks", + "sandbox_permissions", + "tests_ci" + ], + "title": "app-server: remove experimental persist_extended_history bool flag", + "url": "https://github.com/openai/codex/pull/25712" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "new_feature", + "protocol_change", + "release_packaging" + ], + "changed_file_count": 14, + "commit_shas": [ + "486805b7cea244022b0856675acd1cb34c7e6488" + ], + "committed_at": "2026-06-01T23:43:52Z", + "next_step": "ai_review_required", + "pr_number": 24621, + "pr_url": "https://github.com/openai/codex/pull/24621", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, new_feature, protocol_change, release_packaging.", + "sample_paths": [ + "codex-rs/Cargo.lock", + "codex-rs/Cargo.toml", + "codex-rs/app-server/Cargo.toml", + "codex-rs/app-server/src/config_manager.rs", + "codex-rs/cloud-config/BUILD.bazel", + "codex-rs/cloud-config/Cargo.toml", + "codex-rs/cloud-config/src/lib.rs", + "codex-rs/cloud-requirements/BUILD.bazel", + "codex-rs/exec/Cargo.toml", + "codex-rs/exec/src/lib.rs", + "codex-rs/tui/Cargo.toml", + "codex-rs/tui/src/lib.rs" + ], + "source_state": "merged", + "subject_id": "24621", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "auth_accounts", + "cli_tui", + "config_hooks" + ], + "title": "Move cloud requirements crate to cloud config", + "url": "https://github.com/openai/codex/pull/24621" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "deprecated_removed", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 15, + "commit_shas": [ + "72523ec6dd98aadaf78c7cfab4059f3fea2720b9", + "c4ffb299f91e8f7f41ff22042cdc5bca8806d82c", + "f0ec5554dab6828490402239300577cbee331f22" + ], + "committed_at": "2026-06-02T01:05:50Z", + "next_step": "ai_review_required", + "pr_number": 25675, + "pr_url": "https://github.com/openai/codex/pull/25675", + "review_priority": "critical", + "review_reason": "Needs AI review for auth_account, breaking_change, deprecated_removed, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/app-server-protocol/src/export.rs", + "codex-rs/app-server-protocol/src/protocol/common.rs", + "codex-rs/app-server-protocol/src/protocol/v2/remote_control.rs", + "codex-rs/app-server-transport/src/transport/remote_control/enroll.rs", + "codex-rs/app-server-transport/src/transport/remote_control/mod.rs", + "codex-rs/app-server-transport/src/transport/remote_control/protocol.rs", + "codex-rs/app-server-transport/src/transport/remote_control/tests.rs", + "codex-rs/app-server-transport/src/transport/remote_control/tests/pairing_tests.rs", + "codex-rs/app-server-transport/src/transport/remote_control/websocket.rs", + "codex-rs/app-server/README.md", + "codex-rs/app-server/src/message_processor.rs", + "codex-rs/app-server/src/request_processors/remote_control_processor.rs" + ], + "source_state": "merged", + "subject_id": "25675", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "docs_examples", + "tests_ci" + ], + "title": "feat(remote-control): add pairing start", + "url": "https://github.com/openai/codex/pull/25675" + }, + { + "attention_flags": [ + "new_feature", + "protocol_change" + ], + "changed_file_count": 1, + "commit_shas": [ + "85ecb33ab79125fbd68c435444eda876d41e1c59", + "0ebf287187ce69a692588bd9eba2adf867031912", + "0b0ab0854c1f536912660312c0a97e4bada3270f" + ], + "committed_at": "2026-06-01T17:13:56Z", + "next_step": "ai_review_required", + "pr_number": 25603, + "pr_url": "https://github.com/openai/codex/pull/25603", + "review_priority": "high", + "review_reason": "Needs AI review for new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/app-server/src/request_processors/thread_processor.rs" + ], + "source_state": "merged", + "subject_id": "25603", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol" + ], + "title": "[codex] Inherit raw events for spawned child listeners", + "url": "https://github.com/openai/codex/pull/25603" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change", + "release_packaging", + "security_policy" + ], + "changed_file_count": 17, + "commit_shas": [ + "8cd8071cd48f6a7e701f56a72685c208a98cfbaa", + "b679d1c4953e3476675c525be02f31ed47c353e9", + "823f674f43c5f2675afe3b2b76c3de8581954fe1", + "046f6a8a710ec07ed7164afd3d42bbf8f655a100" + ], + "committed_at": "2026-06-01T18:50:23Z", + "next_step": "ai_review_required", + "pr_number": 25165, + "pr_url": "https://github.com/openai/codex/pull/25165", + "review_priority": "high", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change, release_packaging, security_policy.", + "sample_paths": [ + ".github/workflows/ci.yml", + "AGENTS.md", + "justfile", + "scripts/check_blob_size.py", + "scripts/codex_package/archive.py", + "scripts/codex_package/cargo.py", + "scripts/codex_package/cli.py", + "scripts/codex_package/layout.py", + "scripts/codex_package/test_cargo.py", + "scripts/codex_package/v8.py", + "scripts/just-shell.py", + "scripts/mock_responses_websocket_server.py" + ], + "source_state": "merged", + "subject_id": "25165", + "subject_kind": "pr", + "surface_hints": [ + "cli_tui", + "config_hooks", + "docs_examples", + "release_packaging", + "tests_ci" + ], + "title": "Check root Python script formatting in CI", + "url": "https://github.com/openai/codex/pull/25165" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 16, + "commit_shas": [ + "d5bd752c9a679c28cf7b8c21eba9ed4e66aaa15c", + "58f0201cf890001207627bc4197d8d070cf91236", + "3064b4ebf3e6aa4863c57d8129f1e4b09e20d9ad", + "028e32d35e51073efcc5cf328b0fb328c9e6f626", + "6948662d49c8ec1c7d7f9db6e227d458eeb3aa4f", + "cf128473a849b8bd4b6b5900c50e100d8896aecc", + "2b4d27d0b68c1392a02a2d7ee844a5cfed87989b", + "27b37fdfa1434572199c0905f9c0f45a30bd758b", + "0f42f7f9880007bdf48913e6d66e069598646ef2" + ], + "committed_at": "2026-06-01T18:51:15Z", + "next_step": "ai_review_required", + "pr_number": 23767, + "pr_url": "https://github.com/openai/codex/pull/23767", + "review_priority": "high", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/app-server/tests/common/models_cache.rs", + "codex-rs/codex-api/tests/models_integration.rs", + "codex-rs/core/src/guardian/review.rs", + "codex-rs/core/src/guardian/tests.rs", + "codex-rs/core/tests/suite/auto_review.rs", + "codex-rs/core/tests/suite/mod.rs", + "codex-rs/core/tests/suite/model_switching.rs", + "codex-rs/core/tests/suite/models_cache_ttl.rs", + "codex-rs/core/tests/suite/personality.rs", + "codex-rs/core/tests/suite/remote_models.rs", + "codex-rs/core/tests/suite/rmcp_client.rs", + "codex-rs/core/tests/suite/spawn_agent_description.rs" + ], + "source_state": "merged", + "subject_id": "23767", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "cli_tui", + "config_hooks", + "mcp_plugins", + "model_provider", + "tests_ci" + ], + "title": "[codex-rs] auto-review model override ", + "url": "https://github.com/openai/codex/pull/23767" + }, + { + "attention_flags": [ + "protocol_change", + "security_policy" + ], + "changed_file_count": 1, + "commit_shas": [ + "15200a375689d41197f4e7b5a7179900203aedf3", + "a0ee76a24d4d78ed35f43fc1fe3ec290a0c0465b" + ], + "committed_at": "2026-06-01T19:55:44Z", + "next_step": "ai_review_required", + "pr_number": 25669, + "pr_url": "https://github.com/openai/codex/pull/25669", + "review_priority": "high", + "review_reason": "Needs AI review for protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs" + ], + "source_state": "merged", + "subject_id": "25669", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "tests_ci" + ], + "title": "fix: deflake zsh-fork approval test", + "url": "https://github.com/openai/codex/pull/25669" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change", + "release_packaging", + "security_policy" + ], + "changed_file_count": 6, + "commit_shas": [ + "cfbb27037b1bb488a0dd39272b149a160732503e" + ], + "committed_at": "2026-06-01T21:27:18Z", + "next_step": "ai_review_required", + "pr_number": 25681, + "pr_url": "https://github.com/openai/codex/pull/25681", + "review_priority": "high", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change, release_packaging, security_policy.", + "sample_paths": [ + "codex-rs/app-server/src/request_processors.rs", + "codex-rs/app-server/src/request_processors/plugins.rs", + "codex-rs/app-server/tests/suite/v2/plugin_list.rs", + "codex-rs/core-plugins/src/loader.rs", + "codex-rs/core-plugins/src/manager.rs", + "codex-rs/core-plugins/src/manager_tests.rs" + ], + "source_state": "merged", + "subject_id": "25681", + "subject_kind": "pr", + "surface_hints": [ + "app_server_protocol", + "mcp_plugins", + "tests_ci" + ], + "title": "fix: Deduplicate installed local and remote curated plugins", + "url": "https://github.com/openai/codex/pull/25681" + }, + { + "attention_flags": [ + "new_feature" + ], + "changed_file_count": 2, + "commit_shas": [ + "47c41424db0b3ea53cd282e06455db41cf2e7f64" + ], + "committed_at": "2026-06-02T00:19:34Z", + "next_step": "ai_review_required", + "pr_number": 25717, + "pr_url": "https://github.com/openai/codex/pull/25717", + "review_priority": "high", + "review_reason": "Needs AI review for new_feature.", + "sample_paths": [ + "codex-rs/core-plugins/src/manager_tests.rs", + "codex-rs/core-plugins/src/manifest.rs" + ], + "source_state": "merged", + "subject_id": "25717", + "subject_kind": "pr", + "surface_hints": [ + "mcp_plugins", + "tests_ci" + ], + "title": "Handle invalid plugin skills manifest field", + "url": "https://github.com/openai/codex/pull/25717" + }, + { + "attention_flags": [ + "new_feature", + "protocol_change", + "rate_limit" + ], + "changed_file_count": 12, + "commit_shas": [ + "46e75a8f935e8244224502764ce1bdc3435f7f10" + ], + "committed_at": "2026-06-01T16:30:20Z", + "next_step": "ai_review_required", + "pr_number": 25504, + "pr_url": "https://github.com/openai/codex/pull/25504", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature, protocol_change, rate_limit.", + "sample_paths": [ + "codex-rs/cli/src/doctor/title.rs", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__status_line_setup__tests__setup_view_snapshot_uses_runtime_preview_values.snap", + "codex-rs/tui/src/bottom_pane/status_line_setup.rs", + "codex-rs/tui/src/bottom_pane/status_line_style.rs", + "codex-rs/tui/src/bottom_pane/status_surface_preview.rs", + "codex-rs/tui/src/bottom_pane/title_setup.rs", + "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap", + "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap", + "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_mixed.snap", + "codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_rate_limits.snap", + "codex-rs/tui/src/chatwidget/status_surfaces.rs", + "codex-rs/tui/src/chatwidget/tests/status_and_layout.rs" + ], + "source_state": "merged", + "subject_id": "25504", + "subject_kind": "pr", + "surface_hints": [ + "cli_tui", + "tests_ci" + ], + "title": "Add reasoning-only status surface item", + "url": "https://github.com/openai/codex/pull/25504" + }, + { + "attention_flags": [ + "deprecated_removed", + "release_packaging" + ], + "changed_file_count": 2, + "commit_shas": [ + "b78d153d5bc7e82dfcc2d8c185341e15d0824252", + "2bf2e7221775bb96c1f3c962faae4fb06d271e64" + ], + "committed_at": "2026-06-01T16:49:55Z", + "next_step": "ai_review_required", + "pr_number": 25490, + "pr_url": "https://github.com/openai/codex/pull/25490", + "review_priority": "normal", + "review_reason": "Needs AI review for deprecated_removed, release_packaging.", + "sample_paths": [ + ".github/workflows/rust-release-windows.yml", + ".github/workflows/rust-release.yml" + ], + "source_state": "merged", + "subject_id": "25490", + "subject_kind": "pr", + "surface_hints": [ + "release_packaging", + "tests_ci" + ], + "title": "Disable SQLite intrinsics for Windows x64 releases", + "url": "https://github.com/openai/codex/pull/25490" + }, + { + "attention_flags": [ + "release_packaging" + ], + "changed_file_count": 1, + "commit_shas": [ + "11411353ff8d626c5a6e572a63b8c763e8c6eccf" + ], + "committed_at": "2026-06-01T17:34:12Z", + "next_step": "ai_review_required", + "pr_number": 25644, + "pr_url": "https://github.com/openai/codex/pull/25644", + "review_priority": "normal", + "review_reason": "Needs AI review for release_packaging.", + "sample_paths": [ + ".github/workflows/rust-release.yml" + ], + "source_state": "merged", + "subject_id": "25644", + "subject_kind": "pr", + "surface_hints": [ + "release_packaging", + "tests_ci" + ], + "title": "[codex] Use git CLI for release Cargo fetches", + "url": "https://github.com/openai/codex/pull/25644" + }, + { + "attention_flags": [ + "protocol_change" + ], + "changed_file_count": 1, + "commit_shas": [ + "b5b23651158af48f0cbdddc2054ad2b75ad2a1ff" + ], + "committed_at": "2026-06-01T17:48:29Z", + "next_step": "ai_review_required", + "pr_number": 25655, + "pr_url": "https://github.com/openai/codex/pull/25655", + "review_priority": "normal", + "review_reason": "Needs AI review for protocol_change.", + "sample_paths": [ + "codex-rs/tools/src/tool_call.rs" + ], + "source_state": "merged", + "subject_id": "25655", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "nit: drop todo", + "url": "https://github.com/openai/codex/pull/25655" + }, + { + "attention_flags": [ + "auth_account", + "new_feature" + ], + "changed_file_count": 1, + "commit_shas": [ + "c92fce4ca101f32e6c3f2b5bb58b4718b9f4b4fc" + ], + "committed_at": "2026-06-01T17:54:52Z", + "next_step": "ai_review_required", + "pr_number": 25654, + "pr_url": "https://github.com/openai/codex/pull/25654", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, new_feature.", + "sample_paths": [ + "codex-rs/rollout/src/compression.rs" + ], + "source_state": "merged", + "subject_id": "25654", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "Parallelize cold rollout compression", + "url": "https://github.com/openai/codex/pull/25654" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 2, + "commit_shas": [ + "1ff530376e8284e18624f701ae9eb6b0fdf4d446", + "d3938c8624b2a9ff09e3725bde20a072ae08a31f", + "55ec07d903a016e3e9966998aef79a7e7d9e3341" + ], + "committed_at": "2026-06-01T17:55:52Z", + "next_step": "ai_review_required", + "pr_number": 25121, + "pr_url": "https://github.com/openai/codex/pull/25121", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/exec-server/src/environment_path.rs", + "codex-rs/exec-server/src/lib.rs" + ], + "source_state": "merged", + "subject_id": "25121", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "exec-server: add environment path refs", + "url": "https://github.com/openai/codex/pull/25121" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change" + ], + "changed_file_count": 3, + "commit_shas": [ + "8f4cc03faf8805dd43ad6ef6402b8e52c9d6515a", + "d5a70cf47789759cd5cab13b2b236803719b0210", + "5810eed0d772062b0daea7cc7c2d617308742ccd", + "24c41147ec2bd938a391792fab15e558a7de5121", + "101855ee0c3413596b80a185330c5f6eb761fde2", + "c28620aaf6c2b4a753dc05aa2916a838a8b8cac9", + "d6ce69481e89b718a5dd841838b99663c287fb06", + "65a021073cacb924048438c84e7dc9a248946091", + "957aca2ef2e62be3fc4bfa69ae3624096708d2d6", + "fce27d47544a07422fb57c5e48532d7a4cc8a853", + "1ed627cf1b8488d926817cf68e2b3d46833ef8e8", + "9c9e30529edbb5626b22ef32b4250be62455057c" + ], + "committed_at": "2026-06-01T18:26:36Z", + "next_step": "ai_review_required", + "pr_number": 24983, + "pr_url": "https://github.com/openai/codex/pull/24983", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change.", + "sample_paths": [ + "justfile", + "scripts/just-shell.py", + "sdk/python/tests/test_artifact_workflow_and_binaries.py" + ], + "source_state": "merged", + "subject_id": "24983", + "subject_kind": "pr", + "surface_hints": [ + "tests_ci" + ], + "title": "[codex] Make justfile recipes Windows-aware", + "url": "https://github.com/openai/codex/pull/24983" + }, + { + "attention_flags": [ + "breaking_change", + "deprecated_removed", + "new_feature" + ], + "changed_file_count": 2, + "commit_shas": [ + "0147db5be86208ddbbd29d7382f5b4743d3d38a7", + "894590a07d27f592699d2216be0db876b954b347" + ], + "committed_at": "2026-06-01T18:46:54Z", + "next_step": "ai_review_required", + "pr_number": 25659, + "pr_url": "https://github.com/openai/codex/pull/25659", + "review_priority": "normal", + "review_reason": "Needs AI review for breaking_change, deprecated_removed, new_feature.", + "sample_paths": [ + "codex-rs/rollout/src/compression.rs", + "codex-rs/rollout/src/compression_tests.rs" + ], + "source_state": "merged", + "subject_id": "25659", + "subject_kind": "pr", + "surface_hints": [ + "tests_ci" + ], + "title": "Throttle repeated rollout compression runs", + "url": "https://github.com/openai/codex/pull/25659" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "protocol_change", + "security_policy" + ], + "changed_file_count": 10, + "commit_shas": [ + "7f11af9129e556d821572039ef5cd15433e5a12e" + ], + "committed_at": "2026-06-01T20:22:28Z", + "next_step": "ai_review_required", + "pr_number": 24980, + "pr_url": "https://github.com/openai/codex/pull/24980", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, new_feature, protocol_change, security_policy.", + "sample_paths": [ + "codex-rs/core/src/tools/handlers/shell_spec.rs", + "codex-rs/core/src/tools/handlers/shell_spec_tests.rs", + "codex-rs/core/src/tools/handlers/unified_exec.rs", + "codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs", + "codex-rs/core/src/tools/handlers/unified_exec_tests.rs", + "codex-rs/core/src/tools/spec_plan.rs", + "codex-rs/core/src/tools/spec_plan_tests.rs", + "codex-rs/core/src/unified_exec/mod.rs", + "codex-rs/core/src/unified_exec/process_manager.rs", + "codex-rs/core/src/unified_exec/process_manager_tests.rs" + ], + "source_state": "merged", + "subject_id": "24980", + "subject_kind": "pr", + "surface_hints": [ + "tests_ci" + ], + "title": "refactor: hide shell override for zsh fork unified exec", + "url": "https://github.com/openai/codex/pull/24980" + }, + { + "attention_flags": [ + "deprecated_removed", + "new_feature" + ], + "changed_file_count": 1, + "commit_shas": [ + "46493839bb624898916a7d080b06743ff747248a" + ], + "committed_at": "2026-06-01T20:26:32Z", + "next_step": "ai_review_required", + "pr_number": 25679, + "pr_url": "https://github.com/openai/codex/pull/25679", + "review_priority": "normal", + "review_reason": "Needs AI review for deprecated_removed, new_feature.", + "sample_paths": [ + "codex-rs/rollout/src/compression.rs" + ], + "source_state": "merged", + "subject_id": "25679", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "Add rollout compression counters", + "url": "https://github.com/openai/codex/pull/25679" + }, + { + "attention_flags": [ + "new_feature" + ], + "changed_file_count": 1, + "commit_shas": [ + "4d08fcd4b5eb0996b4b61d55c9498bdab3fe38ef" + ], + "committed_at": "2026-06-01T20:36:16Z", + "next_step": "ai_review_required", + "pr_number": 25682, + "pr_url": "https://github.com/openai/codex/pull/25682", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature.", + "sample_paths": [ + "AGENTS.md" + ], + "source_state": "merged", + "subject_id": "25682", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "[codex] document out-of-line test module convention", + "url": "https://github.com/openai/codex/pull/25682" + }, + { + "attention_flags": [ + "new_feature" + ], + "changed_file_count": 1, + "commit_shas": [ + "46493839bb624898916a7d080b06743ff747248a", + "3846901efe60c2dd33ff2038a66f6863882fe60d", + "5e2196e4ad0fffd75b169fdc57aa4ee396c7559f" + ], + "committed_at": "2026-06-01T20:54:25Z", + "next_step": "ai_review_required", + "pr_number": 25680, + "pr_url": "https://github.com/openai/codex/pull/25680", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature.", + "sample_paths": [ + "codex-rs/rollout/src/compression.rs" + ], + "source_state": "merged", + "subject_id": "25680", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "Add rollout compression histograms", + "url": "https://github.com/openai/codex/pull/25680" + }, + { + "attention_flags": [ + "new_feature", + "protocol_change", + "release_packaging" + ], + "changed_file_count": 1, + "commit_shas": [ + "aaf5504be01a65c574429427843745314de578b7" + ], + "committed_at": "2026-06-01T21:05:54Z", + "next_step": "ai_review_required", + "pr_number": 25690, + "pr_url": "https://github.com/openai/codex/pull/25690", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature, protocol_change, release_packaging.", + "sample_paths": [ + "AGENTS.md" + ], + "source_state": "merged", + "subject_id": "25690", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "Add Python version compatibility guidance", + "url": "https://github.com/openai/codex/pull/25690" + }, + { + "attention_flags": [ + "new_feature", + "protocol_change" + ], + "changed_file_count": 2, + "commit_shas": [ + "f6ba01688689db0d1ff203ff5ff3cfc3966f8e8b" + ], + "committed_at": "2026-06-01T22:04:11Z", + "next_step": "ai_review_required", + "pr_number": 25702, + "pr_url": "https://github.com/openai/codex/pull/25702", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature, protocol_change.", + "sample_paths": [ + "codex-rs/ext/web-search/src/extension.rs", + "codex-rs/ext/web-search/src/tool.rs" + ], + "source_state": "merged", + "subject_id": "25702", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "[codex] enable parallel standalone web search calls", + "url": "https://github.com/openai/codex/pull/25702" + }, + { + "attention_flags": [ + "new_feature" + ], + "changed_file_count": 7, + "commit_shas": [ + "d2b00249f3698b8f852afc7425e84169a378e576", + "52683c4f5ac03ea09781d04980540f18c1964f18" + ], + "committed_at": "2026-06-01T22:41:22Z", + "next_step": "ai_review_required", + "pr_number": 25625, + "pr_url": "https://github.com/openai/codex/pull/25625", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature.", + "sample_paths": [ + "codex-rs/tui/src/bottom_pane/chat_composer.rs", + "codex-rs/tui/src/bottom_pane/footer.rs", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shortcut_overlay.snap", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shortcut_overlay_queue_submissions.snap", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_collaboration_modes_enabled.snap", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_running.snap", + "codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_shortcuts_shift_and_esc.snap" + ], + "source_state": "merged", + "subject_id": "25625", + "subject_kind": "pr", + "surface_hints": [ + "cli_tui", + "tests_ci" + ], + "title": "fix(tui): clarify footer shortcut overlay hints", + "url": "https://github.com/openai/codex/pull/25625" + }, + { + "attention_flags": [ + "auth_account", + "new_feature", + "release_packaging" + ], + "changed_file_count": 3, + "commit_shas": [ + "ef21fca892ca544cbd4e7a50a043f57ac728df3b", + "1d25eca8c5959c52919688051304579725cc49f6", + "d8a0db636391a82a9b60f5d44ed20334e0102d80", + "f2c0eeefea3b5486a422c621817e52f59c77c577", + "b017bc0c0971224d6c31f0cf7478368e9a3ce8a7", + "4d5173b8fea0a4dc4d26b2e697451657094a682c" + ], + "committed_at": "2026-06-01T22:49:54Z", + "next_step": "ai_review_required", + "pr_number": 25649, + "pr_url": "https://github.com/openai/codex/pull/25649", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, new_feature, release_packaging.", + "sample_paths": [ + ".github/scripts/archive-release-symbols-and-strip-binaries.sh", + ".github/workflows/rust-release-windows.yml", + ".github/workflows/rust-release.yml" + ], + "source_state": "merged", + "subject_id": "25649", + "subject_kind": "pr", + "surface_hints": [ + "release_packaging", + "tests_ci" + ], + "title": "[codex] Publish release symbol artifacts", + "url": "https://github.com/openai/codex/pull/25649" + }, + { + "attention_flags": [ + "new_feature", + "release_packaging" + ], + "changed_file_count": 4, + "commit_shas": [ + "e5655c7581cccc6deeb69c0299fa94670df6227a", + "278e87e3e6f8c508b8bb9695a478dde45fbdbb39", + "000561cd2237dc96c1c8e122bd793013a7cb0a9a", + "699944894ecc7db15a60d58d32b1637e6cd9e26e", + "1b3f0c3668c3ece3deb26f3e0af1b8a5d9fe6338", + "9616ce135e60ddd29713521b8a54cef07163b5f1", + "83919c4e3f76487a825ad8f78a8cb6a6b94eeeca", + "5afb526da9d9efa413c4dd0ad4359b4e17ce19b3", + "d0ef086e2fe4aacb5fc35be6da65fc4214c99721", + "562863c5c26e77defbe1be247979dcf0a9d6391b", + "365ac0ec81b2ddad895aff862b20583af3f3419b", + "cba4ad90f9ed9913e8f1035a7607219caeb47cd2" + ], + "committed_at": "2026-06-02T01:20:25Z", + "next_step": "ai_review_required", + "pr_number": 25683, + "pr_url": "https://github.com/openai/codex/pull/25683", + "review_priority": "normal", + "review_reason": "Needs AI review for new_feature, release_packaging.", + "sample_paths": [ + ".github/workflows/ci.yml", + "justfile", + "scripts/format.py", + "sdk/python/tests/test_artifact_workflow_and_binaries.py" + ], + "source_state": "merged", + "subject_id": "25683", + "subject_kind": "pr", + "surface_hints": [ + "tests_ci" + ], + "title": "[codex] Add comprehensive root formatting check", + "url": "https://github.com/openai/codex/pull/25683" + }, + { + "attention_flags": [ + "auth_account", + "breaking_change", + "new_feature", + "protocol_change" + ], + "changed_file_count": 1, + "commit_shas": [ + "ba8814c248d629d3dea8ccc8acc6ab6a184ec632", + "720a2f45d3f67ab414f5a56a6ebe2db5f267e3f7", + "1fb0583d2056474a02c94c961670de46aeb918ea", + "5db9c53727e1cb2c8d8fd7f53312ef92ec606177" + ], + "committed_at": "2026-06-02T01:41:04Z", + "next_step": "ai_review_required", + "pr_number": 25738, + "pr_url": "https://github.com/openai/codex/pull/25738", + "review_priority": "normal", + "review_reason": "Needs AI review for auth_account, breaking_change, new_feature, protocol_change.", + "sample_paths": [ + "AGENTS.md" + ], + "source_state": "merged", + "subject_id": "25738", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "Move code review rules into AGENTS", + "url": "https://github.com/openai/codex/pull/25738" + }, + { + "attention_flags": [], + "changed_file_count": 1, + "commit_shas": [ + "002c656ee455a9bba813dc60ce93ea4500bfc526" + ], + "committed_at": "2026-06-01T21:01:30Z", + "next_step": "ai_review_required", + "pr_number": 25689, + "pr_url": "https://github.com/openai/codex/pull/25689", + "review_priority": "low", + "review_reason": "Needs AI review because every recent upstream commit is tracked, but deterministic hints found only internal churn.", + "sample_paths": [ + "codex-rs/code-mode/src/description.rs" + ], + "source_state": "merged", + "subject_id": "25689", + "subject_kind": "pr", + "surface_hints": [ + "internal_churn" + ], + "title": "[codex] Generalize deferred nested tool guidance", + "url": "https://github.com/openai/codex/pull/25689" + } + ] +} diff --git a/artifacts/github/reviews/.gitkeep b/artifacts/github/reviews/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/artifacts/github/reviews/.gitkeep @@ -0,0 +1 @@ + diff --git a/artifacts/social/README.md b/artifacts/social/README.md index 01a96e10..e337af26 100644 --- a/artifacts/social/README.md +++ b/artifacts/social/README.md @@ -2,8 +2,9 @@ This directory stores checked-in Publisher artifacts for external social channels. -- `x/` holds `social_post_draft/v1` drafts for X/Twitter publication. +- `x/posts/` holds `social_post/v1` publication, block, skip, and failure records for + X. +- `x/images/` holds generated media evidence when the image is committed. -Drafts are review artifacts. A draft is not approved for external publication until its -`status` is `approved` under the rules in `docs/spec/social-post-draft.md` and -`docs/runbook/social-publishing-workflow.md`. +The governing contract is `docs/spec/social-publishing.md`. The primary publishing +account is `@decodexspace`; the controller account is `@hackink`. diff --git a/artifacts/social/x/images/.gitkeep b/artifacts/social/x/images/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/artifacts/social/x/images/.gitkeep @@ -0,0 +1 @@ + diff --git a/artifacts/social/x/posts/.gitkeep b/artifacts/social/x/posts/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/artifacts/social/x/posts/.gitkeep @@ -0,0 +1 @@ + diff --git a/dev/skills/README.md b/dev/skills/README.md index a99f0443..c6a53769 100644 --- a/dev/skills/README.md +++ b/dev/skills/README.md @@ -11,34 +11,35 @@ with the installable Decodex plugin under `plugins/decodex/`. Use these skills in order when turning upstream Codex activity into Decodex content or follow-up work: -1. `codex-upstream-triage`: choose which upstream commits, PRs, releases, or changelog - entries deserve deeper analysis. +1. `codex-upstream-triage`: read the deterministic upstream review queue or a selected + source window and group commits by PR when possible. 2. `codex-code-analysis`: read the selected upstream code or patch evidence and map it to user-visible, Control Plane, and Publisher implications. 3. `codex-release-analysis`: evaluate release or changelog material against commits, PRs, release-delta artifacts, and already-published Decodex signals. 4. `github-signal`: turn the reviewed GitHub bundle and analysis result into the `analysis_draft` JSON consumed by `scripts/github/render_signal_entry.py`. -5. `x-post-draft`: turn evidence-backed Radar output into a reviewable - `social_post_draft/v1` artifact for `@decodexspace`. +5. `x-post-publisher`: turn evidence-backed Radar output into a low-frequency + `social_post/v1` publication, block, skip, or failure record for `@decodexspace`. 6. `rate-limit-reset-watch`: review today's `@thsottiaux` X posts with AI semantic judgment and refresh the homepage `reset_status/v1` artifact. -Use only the skills needed for the current artifact. Do not create a social draft just +Use only the skills needed for the current artifact. Do not publish a social post just because a signal exists. Default posture: track every upstream Codex commit as a possible evidence unit. Resolve commits back to PRs when possible, decide whether the change matters to Decodex Control Plane or the wider Codex community, and only then promote important, useful, or deprecated behavior into a signal, upstream-impact artifact, follow-up issue, or X -draft. +post. For upstream releases and prereleases, use `codex-release-analysis` as a rollup over the accumulated commit/PR analysis. Codex prerelease notes are often too sparse to explain what changed by themselves. -Only the existing checked-in contracts are durable artifacts today: -`github_change_bundle/v1`, `analysis_draft`, `signal_entry/v1`, `upstream_impact/v1`, -`release_delta/v1`, `social_post_draft/v1`, and `reset_status/v1`. The triage, -code-analysis, release-analysis, and reset-watch skills are reasoning passes unless -their conclusions are promoted into one of those contracts. +Checked-in contracts for this workflow are `upstream_review_queue/v1`, +`upstream_review/v1`, `github_change_bundle/v1`, `analysis_draft`, `signal_entry/v1`, +`upstream_impact/v1`, `release_delta/v1`, `social_post/v1`, and +`reset_status/v1`. The triage, code-analysis, release-analysis, and reset-watch skills +are reasoning passes unless their conclusions are promoted into one of those +contracts. diff --git a/dev/skills/codex-code-analysis/SKILL.md b/dev/skills/codex-code-analysis/SKILL.md index 8514643e..7b1aaa62 100644 --- a/dev/skills/codex-code-analysis/SKILL.md +++ b/dev/skills/codex-code-analysis/SKILL.md @@ -14,20 +14,23 @@ Decodex plugin skill. ## Read Before Analysis - `docs/spec/github-change-bundle.md` +- `docs/spec/upstream-review.md` - `docs/spec/upstream-impact.md` - `docs/spec/signal-entry.md` - `dev/skills/README.md` ## Inputs -- A `github_change_bundle/v1` under `artifacts/github/bundles/`, or enough GitHub PR or - commit evidence to request or create one +- An `upstream_review_queue/v1` subject, a `github_change_bundle/v1` under + `artifacts/github/bundles/`, or enough GitHub PR or commit evidence to request or + create one - Optional release or changelog context - Optional existing Decodex signal, upstream-impact, or release-delta artifacts -This skill does not define a new checked-in artifact. Keep the result in-session -unless it is promoted into an existing `analysis_draft`, `upstream_impact/v1`, or -`social_post_draft/v1` contract. +This skill may produce an `upstream_review/v1` when Codex automation is processing the +continuous review queue. Keep ad hoc manual notes in-session unless they are promoted +into `upstream_review/v1`, `analysis_draft`, `upstream_impact/v1`, or +`social_post/v1`. ## Analysis Loop @@ -89,7 +92,7 @@ Return an analysis note that can feed `github-signal`, `codex-release-analysis`, - Publisher angle, if any - confidence and caveats - recommended next artifact: `none`, `analysis_draft` through `github-signal`, - `upstream_impact/v1`, or `social_post_draft/v1` + `upstream_impact/v1`, or `social_post/v1` Keep the note shorter than the source patch. Explain the behavior path, not every changed file. diff --git a/dev/skills/codex-release-analysis/SKILL.md b/dev/skills/codex-release-analysis/SKILL.md index 367f62a1..4a35da47 100644 --- a/dev/skills/codex-release-analysis/SKILL.md +++ b/dev/skills/codex-release-analysis/SKILL.md @@ -16,6 +16,7 @@ Decodex plugin skill. - `docs/spec/release-delta.md` - `docs/spec/signal-entry.md` - `docs/spec/upstream-impact.md` +- `docs/spec/social-publishing.md` - `docs/runbook/local-github-signal-workflow.md` - `dev/skills/codex-upstream-triage/SKILL.md` - `dev/skills/codex-code-analysis/SKILL.md` @@ -58,8 +59,8 @@ When the target is an OpenAI Codex release or prerelease: code analysis. 5. Group findings by reader value: useful now, important for Decodex Control Plane, deprecated/removed behavior, and watch-only changes. -6. Draft release or prerelease X reporting only after the summary is grounded in those - historical analyses. +6. Publish release or prerelease X reporting only after the summary is grounded in + those historical analyses and passes the daily cap. 7. Refresh `release_delta/v1` after new signals are rendered so the homepage can map the release window to tracked signals. @@ -69,7 +70,7 @@ Use exactly one primary mode: | Mode | Use when | Output | | --- | --- | --- | -| `release_pulse` | The release headline is the story and evidence is thin. | Short awareness note or social draft. | +| `release_pulse` | The release headline is the story and evidence is thin. | Short awareness note or social post. | | `delta_explainer` | Compare commits map to existing signals or clear PRs. | Refresh existing `release_delta/v1` and summarize the evidence. | | `operator_impact` | Release changes app-server, plugins, browser, MCP, permissions, sandbox, hooks, config, auth, or providers. | `upstream_impact/v1` plus possible follow-up issue. | | `watch_note` | The release is interesting but evidence is incomplete. | Watch note with caveats. | @@ -101,4 +102,4 @@ Return: Promote durable conclusions into existing artifacts only: `upstream_impact/v1`, `analysis_draft` plus rendered `signal_entry/v1`, refreshed `release_delta/v1`, or -`social_post_draft/v1`. +`social_post/v1`. diff --git a/dev/skills/codex-upstream-triage/SKILL.md b/dev/skills/codex-upstream-triage/SKILL.md index f74cd920..b18c2d76 100644 --- a/dev/skills/codex-upstream-triage/SKILL.md +++ b/dev/skills/codex-upstream-triage/SKILL.md @@ -1,12 +1,13 @@ --- name: codex-upstream-triage -description: Use when scanning latest upstream OpenAI Codex commits, PRs, releases, or changelog entries to decide which items deserve a GitHub bundle, code analysis, upstream-impact classification, site signal, or social draft. +description: Use when scanning latest upstream OpenAI Codex commits, PRs, releases, or changelog entries to decide which items deserve a GitHub bundle, code analysis, upstream-impact classification, site signal, or social post. --- # Decodex Codex Upstream Triage -Use this skill before deep analysis. Its job is to keep Radar fast and selective: find -candidate upstream Codex changes, group them correctly, and choose the next artifact. +Use this skill before deep analysis. Its job is to route the deterministic upstream +review queue: group upstream Codex commits correctly, identify likely surfaces, and +choose the next artifact without deciding final impact. This is a Decodex repository-development instruction surface, not an installable Decodex plugin skill. @@ -14,6 +15,7 @@ Decodex plugin skill. ## Read Before Triage - `docs/spec/github-change-bundle.md` +- `docs/spec/upstream-review.md` - `docs/spec/upstream-impact.md` - `docs/runbook/local-github-signal-workflow.md` - `dev/skills/codex-code-analysis/SKILL.md` @@ -36,9 +38,9 @@ Use the lightest source that can answer the triage question: 4. Upstream changelog or browser observation when the question is about public product framing. -For normal Radar operation, scan recent upstream commits first and resolve each commit -back to a PR when possible. A commit list is a queue for understanding and -classification, not final evidence. +For normal Radar operation, start from `upstream_review_queue/v1`, scan recent +upstream commits first, and resolve each commit back to a PR when possible. A commit +list is a queue for understanding and classification, not final evidence. For release or prerelease work, compare metadata is a rollup index over the commit/PR history already being analyzed. Do not let a release tag displace the underlying commit @@ -50,11 +52,11 @@ Classify each item as exactly one: | Decision | Meaning | Next step | | --- | --- | --- | -| `skip` | Internal churn, no safe user or Decodex implication. | Record nothing durable. | +| `skip` | AI review confirms internal churn with no safe user or Decodex implication. | Keep ledger trace only. | | `watch` | Interesting but too weak, too hidden, or too broad. | Optional `upstream_impact/v1` with `control_plane_impact = "watch"`. | | `bundle` | Enough GitHub context exists for code analysis. | Build or reuse a `github_change_bundle/v1`. | | `release_review` | Release or changelog framing needs comparison against commits and signals. | Use `codex-release-analysis`. | -| `style_reference` | Useful only as style or audience evidence. | Save no technical artifact; use only as optional style context when a separate source-backed draft exists. | +| `style_reference` | Useful only as style or audience evidence. | Save no technical artifact; use only as optional style context when a separate source-backed publication candidate exists. | ## Grouping Rules @@ -80,8 +82,8 @@ Escalate to `codex-release-analysis` when the source is a release, prerelease, a update, or public changelog. For Codex releases and prereleases, summarize from prior commit/PR analysis whenever possible, then use compare data to find gaps. -Escalate to `x-post-draft` only after there is technical source evidence and a clear -Publisher angle. Style references from X must not start a social draft by themselves. +Escalate to `x-post-publisher` only after there is technical source evidence and a clear +Publisher angle. Style references from X must not start a social post by themselves. ## Output @@ -94,6 +96,7 @@ Return a compact triage note with: - next skill to use - confidence limits -Do not draft `signal_entry/v1` or `social_post_draft/v1` directly from this skill. -Do not treat this note as a durable repository artifact unless a later change adds a -schema, path, and validator for it. +Do not draft `signal_entry/v1` or publish `social_post/v1` directly from this skill. +Do not treat deterministic queue hints as technical claims. The durable review layer is +`upstream_review/v1`; public and Control Plane artifacts are promotions from that +source-backed review. diff --git a/dev/skills/github-signal/SKILL.md b/dev/skills/github-signal/SKILL.md index 4a7f90af..34065a33 100644 --- a/dev/skills/github-signal/SKILL.md +++ b/dev/skills/github-signal/SKILL.md @@ -20,7 +20,7 @@ publication, and draft the analysis JSON that the repo already renders into a fi - `docs/spec/github-change-bundle.md` - `docs/spec/upstream-impact.md` - `docs/spec/signal-entry.md` -- `docs/spec/social-post-draft.md` +- `docs/spec/social-publishing.md` - `docs/runbook/local-github-signal-workflow.md` - `dev/skills/codex-upstream-triage/SKILL.md` - `dev/skills/codex-code-analysis/SKILL.md` @@ -40,8 +40,8 @@ publication, and draft the analysis JSON that the repo already renders into a fi - Use `codex-code-analysis` before this skill when the behavior path or Control Plane impact is not already clear. - Use `codex-release-analysis` before this skill when the source is release-shaped. -- Use `x-post-draft` after this skill only when the rendered signal or upstream-impact - artifact supports a social draft. +- Use `x-post-publisher` after this skill only when the rendered signal or + upstream-impact artifact supports a social post. ## Boundaries diff --git a/dev/skills/x-post-draft/SKILL.md b/dev/skills/x-post-draft/SKILL.md deleted file mode 100644 index 4508aa8a..00000000 --- a/dev/skills/x-post-draft/SKILL.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -name: x-post-draft -description: Use when turning Decodex Radar evidence, upstream-impact classifications, signal entries, release analysis, or verified browser style observations into a checked-in social_post_draft/v1 artifact for X. ---- - -# Decodex X Post Draft - -Use this skill after source evidence exists. Its job is to create reviewable X draft -artifacts, not to publish posts. - -This is a Decodex repository-development instruction surface, not an installable -Decodex plugin skill. - -## Read Before Drafting - -- `docs/spec/social-post-draft.md` -- `docs/spec/upstream-impact.md` -- `docs/runbook/social-publishing-workflow.md` -- `dev/skills/codex-release-analysis/SKILL.md` -- `dev/skills/codex-code-analysis/SKILL.md` - -## Inputs - -- `signal_entry/v1`, `upstream_impact/v1`, release-analysis note, or checked source URLs -- Optional style observations from `@Codex_Changelog`, `@LLMJunky`, or `@decodexspace` -- Target account, normally `decodexspace` - -## Browser Boundary - -Use `@Chrome` only for reading public pages or verifying rendered posts. Do not type -into an X composer, save a web draft, or post externally unless the user explicitly -approves the specific draft and the artifact is already `status = "approved"`. - -Style observations from X are not technical evidence. They can shape format and tone, -but every technical claim must point back to GitHub, changelog, signal, or -upstream-impact evidence. - -## Benchmark Patterns - -Use these as format patterns only: - -| Pattern | Good for | Decodex adaptation | -| --- | --- | --- | -| Release-bot bullet | Fast `release_pulse` posts. | Version or source headline, two or three evidence-backed bullets, source link. | -| Release rollup | `release_rollup` posts after a release or prerelease. | Summarize what prior commit/PR analysis found: useful now, Control Plane impact, deprecations, and watch-only gaps. | -| Human workflow read | `practical_explainer` and `operator_impact`. | Start with the concrete workflow change, then explain why it matters and what caveat remains. | -| Watch note | Interesting but incomplete evidence. | Say what changed, why Radar is watching, and what evidence is still missing. | - -## Draft Modes - -Choose exactly one `mode` from `social_post_draft/v1`: - -- `release_pulse`: short release-aware summary with source link. -- `release_rollup`: release or prerelease summary built from accumulated Radar - analysis. -- `practical_explainer`: concrete user workflow and expected result. -- `operator_impact`: Decodex Control Plane implication. -- `thread`: multi-post explanation when one post hides evidence or caveats. -- `watch_note`: cautious public note for incomplete evidence. - -`@decodexspace` should mostly use `practical_explainer` and `operator_impact`. -Use `release_pulse` only when the release itself is the useful alert. Use -`release_rollup` when the release or prerelease is best explained by historical -commit/PR analysis rather than by upstream release notes. - -## Claim Review - -Before writing the artifact: - -- Map every sentence to evidence. -- Remove claims based only on social posts or engagement. -- Make beta, rollout, platform, and config gates explicit. -- Avoid local paths, credentials, private issue details, or internal runtime state. -- Keep each `text[]` item within the X length limit. - -## Output - -Write or propose `artifacts/social/x/.json` with: - -- `schema = "social_post_draft/v1"` -- `channel = "x"` -- `target_account = "decodexspace"` unless requested otherwise -- `status = "draft"` -- `source_refs`, `evidence_notes`, and `claims` -- `caveats` when confidence is not fully confirmed - -Do not update the artifact to `approved` or `published` from this skill unless the user -explicitly asks for that state change. diff --git a/dev/skills/x-post-publisher/SKILL.md b/dev/skills/x-post-publisher/SKILL.md new file mode 100644 index 00000000..0997e6cb --- /dev/null +++ b/dev/skills/x-post-publisher/SKILL.md @@ -0,0 +1,135 @@ +--- +name: x-post-publisher +description: Use when turning Decodex Radar evidence, upstream-impact classifications, signal entries, release analysis, or verified browser style observations into an automated @decodexspace X post or a checked-in social_post/v1 blocked/skipped/failed record. +--- + +# Decodex X Post Publisher + +Use this skill after source evidence exists. Its job is to decide whether a candidate is +worth publishing, publish low-frequency high-value posts from `@decodexspace` through +Chrome when the account state is safe, and write the `social_post/v1` publication +record. + +This is a Decodex repository-development instruction surface, not an installable +Decodex plugin skill. + +## Read Before Publishing + +- `docs/spec/social-publishing.md` +- `docs/spec/upstream-impact.md` +- `docs/runbook/social-publishing-workflow.md` +- `dev/skills/codex-release-analysis/SKILL.md` +- `dev/skills/codex-code-analysis/SKILL.md` + +## Inputs + +- `signal_entry/v1`, `upstream_impact/v1`, `upstream_review/v1`, release-analysis + note, or checked source URLs +- Optional style observations from `@Codex_Changelog`, `@LLMJunky`, or `@decodexspace` +- Target account: `decodexspace` +- Controller account for attribution and site links: `hackink` + +## Browser Boundary + +Use `@Chrome` for X publication only inside this low-frequency Publisher workflow. +Before composing, verify the logged-in account is `@decodexspace`. If account +verification, Chrome availability, X page structure, media upload, duplicate detection, +or final URL readback is unreliable, do not post. Write `status = "failed"` or +`status = "blocked"` with evidence instead. + +Style observations from X are not technical evidence. They can shape format and tone, +but every technical claim must point back to GitHub, changelog, signal, upstream-review, +or upstream-impact evidence. + +## Benchmark Patterns + +Use these as format patterns only: + +| Pattern | Good for | Decodex adaptation | +| --- | --- | --- | +| Release-bot bullet | Fast `release_pulse` posts. | Version or source headline, two or three evidence-backed bullets, source link. | +| Release rollup | `release_rollup` posts after a release or prerelease. | Summarize what prior commit/PR analysis found: useful now, Control Plane impact, deprecations, and watch-only gaps. | +| Human workflow read | `practical_explainer` and `operator_impact`. | Start with the concrete workflow change, then explain why it matters and what caveat remains. | +| Watch note | Interesting but incomplete evidence. | Say what changed, why Radar is watching, and what evidence is still missing. | + +## Publish Modes + +Choose exactly one `mode` from `social_post/v1`: + +- `release_pulse`: short release-aware summary with source link. +- `release_rollup`: release or prerelease summary built from accumulated Radar + analysis. +- `practical_explainer`: concrete user workflow and expected result. +- `operator_impact`: Decodex Control Plane implication. +- `thread`: multi-post explanation when one post hides evidence or caveats. +- `watch_note`: cautious public note for incomplete evidence. + +`@decodexspace` should mostly use `practical_explainer`, `operator_impact`, and +source-backed `release_rollup`. Use `release_pulse` only when the release itself is the +useful alert. + +## Worthiness Gate + +Publish only when all are true: + +- The post is in English. +- The source evidence is enough for every technical claim. +- The item is `critical` or `high`, or it is a release/prerelease rollup with clear + reader value. +- The post is useful to Codex users, Decodex operators, or builders tracking the Codex + app-server ecosystem. +- The idempotency key has not already been published or blocked for the same source. +- The daily cap has not been reached. + +Skip low-value internal churn. Do not post just because a signal exists. + +## Daily Cap + +The daily cap is 8 X posts for `@decodexspace`, counted by `Asia/Shanghai` calendar day +unless the operator supplies another timezone. + +If publishing the candidate would exceed the cap: + +- do not post +- write `social_post/v1` with `status = "blocked"` +- set `block.reason = "daily_cap_exceeded"` +- preserve candidate text, source refs, priority, worthiness reason, and daily counts +- report the block in the automation result so the operator can analyze why volume + exceeded the cap + +## Image Generation + +Generate an image for each published post unless media is explicitly skipped. Use +`image_template = "decodex_signal_card"` and the exact base prompt in +`docs/spec/social-publishing.md`. Do not rely on the generated image for readable text. +Render any title, PR number, release tag, or mode with deterministic overlay tooling or +keep it in the post body. + +## Claim Review + +Before publishing: + +- Map every sentence to evidence. +- Remove claims based only on social posts or engagement. +- Make beta, rollout, platform, and config gates explicit. +- Avoid local paths, credentials, private issue details, or internal runtime state. +- Keep each `text[]` item within the X length limit. + +## Output + +Write `artifacts/social/x/posts//.json` with: + +- `schema = "social_post/v1"` +- `channel = "x"` +- `target_account = "decodexspace"` +- `controller_account = "hackink"` +- `status = "published"`, `blocked`, `failed`, or `skipped` +- `source_refs`, `evidence_notes`, `claims`, and `decision` +- `publication` when posted +- `block`, `failure`, or `skip` when not posted + +Run: + +```bash +python3 scripts/github/validate_social_post.py artifacts/social/x +``` diff --git a/docs/decisions/codex-upstream-radar-redesign.md b/docs/decisions/codex-upstream-radar-redesign.md new file mode 100644 index 00000000..4a1e4386 --- /dev/null +++ b/docs/decisions/codex-upstream-radar-redesign.md @@ -0,0 +1,39 @@ +# Codex Upstream Radar Redesign + +Status: accepted + +Date: 2026-06-02 + +Question: How should Decodex rebuild upstream Codex tracking so it can support both +public X/site updates and Decodex compatibility work? + +Decision: Replace the signal-first refresh path with an upstream-review pipeline. + +The new pipeline has four layers: + +1. Deterministic GitHub sync records every observed upstream commit, resolves PRs when + possible, assigns routing hints, and writes an `upstream_review_queue/v1` artifact. +2. Codex automation consumes that queue and performs AI source review for each queued + subject. +3. Source-backed reviews promote only valuable outcomes into `upstream_impact/v1`, + `signal_entry/v1`, `social_post/v1`, or Linear follow-up work. +4. Release and prerelease summaries roll up accumulated commit and PR analysis instead + of treating sparse release notes as enough evidence. + +GitHub Actions must stay deterministic. It may refresh GitHub metadata, release deltas, +review queues, and validation results, but it must not install Codex, inject Codex auth, +or make AI editorial judgments. AI analysis belongs in Codex automation where the local +operator already manages model access, account state, and review context. + +Consequences: + +- Title-score skipping is removed from the continuous Radar path. +- Public site signals are no longer the first output of upstream tracking; they are + Publisher promotions from source-backed review. +- Decodex compatibility risks and adoption opportunities can be tracked before they + become public content. +- Prerelease rollups can explain changes with prior commit/PR evidence even when the + upstream prerelease has no release notes. +- Raw bundles and review artifacts remain subject to the 21-day hot-window archive + policy; curated impacts, signals, social publication records, and archive manifests + stay in Git. diff --git a/docs/decisions/index.md b/docs/decisions/index.md index 3e39f870..504a963a 100644 --- a/docs/decisions/index.md +++ b/docs/decisions/index.md @@ -26,8 +26,11 @@ Question this index answers: "why was it designed this way?" - [`radar-control-plane-publisher.md`](./radar-control-plane-publisher.md) records the stable capability names for upstream Codex intelligence, retained-lane orchestration, and public publishing after the repository integration. +- [`codex-upstream-radar-redesign.md`](./codex-upstream-radar-redesign.md) records why + continuous upstream Codex tracking now starts from deterministic review queues and + keeps AI judgment in Codex automation rather than GitHub Actions. - [`radar-artifact-release-archives.md`](./radar-artifact-release-archives.md) records - why old raw Radar bundles and analysis drafts leave Git after 28 days and move to + why old raw Radar bundles and analysis drafts leave Git after 21 days and move to dedicated GitHub Release assets with checked-in manifests. - [`static-public-site.md`](./static-public-site.md) records why the public Decodex site remains static while runtime/operator behavior stays in the CLI and local control diff --git a/docs/decisions/radar-artifact-release-archives.md b/docs/decisions/radar-artifact-release-archives.md index 86b08526..7293bc26 100644 --- a/docs/decisions/radar-artifact-release-archives.md +++ b/docs/decisions/radar-artifact-release-archives.md @@ -7,7 +7,7 @@ Date: 2026-05-13 Question: Where should Decodex keep old raw Radar artifacts after the short Git hot window? -Decision: Keep raw GitHub bundles and editorial analysis drafts in Git for at most 28 +Decision: Keep raw GitHub bundles and editorial analysis drafts in Git for at most 21 days, then archive cold batches as dedicated GitHub Release assets. The repository keeps only a manifest under `artifacts/archive/index/`; compressed archives are not committed to Git. @@ -16,8 +16,8 @@ Rationale: - Continuous Radar should inspect every upstream Codex commit, but the repository should not become a permanent raw-data warehouse. -- Public signal entries, upstream-impact records, and published social drafts are small, - curated, reviewable artifacts and can remain in Git. +- Public signal entries, upstream-impact records, and social publication records are + small, curated artifacts and can remain in Git. - Raw bundles and analysis drafts can be recovered from an archive asset when needed, as long as the manifest records file paths, checksums, source commit, and release URL. - GitHub Release assets are better than checked-in compressed archives because they keep diff --git a/docs/decisions/radar-control-plane-publisher.md b/docs/decisions/radar-control-plane-publisher.md index 2cbc8016..ec1a8405 100644 --- a/docs/decisions/radar-control-plane-publisher.md +++ b/docs/decisions/radar-control-plane-publisher.md @@ -16,7 +16,7 @@ Decision: Treat Decodex as one product with three named capability areas: operator status, review handoff, landing, closeout, and cleanup. - **Publisher**: public static-site and social publishing surfaces. Publisher consumes Radar outputs and produces checked-in signal entries, release-delta content, and - reviewable social post drafts for external publication. + low-frequency social publication records for external publication. The temporary A/B repository labels are discussion aids only. Use the capability names above in new documentation, issue text, schema names, and operator-facing language. @@ -27,8 +27,9 @@ Consequences: Upstream Codex changes that touch app-server, plugins, browser automation, MCP, permission profiles, config, or sandbox behavior should be classified for Control Plane impact before they become engineering work. -- Publisher remains static-first. Public pages and social drafts are generated from - checked-in artifacts and reviewed content, not from a live Decodex daemon. +- Publisher remains static-first. Public pages and social publication records are + generated from checked-in artifacts and reviewed content, not from a live Decodex + daemon. - `@decodexspace` content should not duplicate a release bot. Publisher should turn Radar evidence into practical, evidence-backed user and operator angles. - Control Plane remains the local execution authority. Publisher content may describe diff --git a/docs/index.md b/docs/index.md index 719cff19..37176521 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,10 +49,10 @@ The split below is by question type, not by human-versus-agent audience. -> `docs/research/` - Need reusable agent-facing Decodex usage instructions -> `plugins/decodex/` - Need repo-local Radar skills for upstream Codex triage, code analysis, release - analysis, GitHub signal drafting, or X post drafting -> `dev/skills/` plus + analysis, GitHub signal drafting, or X publishing -> `dev/skills/` plus `docs/runbook/local-github-signal-workflow.md` -- Need upstream Codex impact classification or social post draft contracts -> - `docs/spec/upstream-impact.md` and `docs/spec/social-post-draft.md` +- Need upstream Codex impact classification or social publishing contracts -> + `docs/spec/upstream-impact.md` and `docs/spec/social-publishing.md` - Need the `@decodexspace` social publishing procedure -> `docs/runbook/social-publishing-workflow.md` - Need repository execution defaults or tracker-state policy -> registered project @@ -72,8 +72,8 @@ The split below is by question type, not by human-versus-agent audience. - Keep the public site static by default. `site/` consumes checked-in content and generated JSON; it must not depend on a live Decodex daemon unless a later decision changes that boundary. -- Keep social publishing static-first as well. Drafts must be reviewable checked-in - artifacts before any external posting automation acts on them. +- Keep social publishing static-first as well. Publication, block, skip, and failure + outcomes must be checked-in `social_post/v1` records. - Start each document with a short routing header that says what the document is for, when to read it, and what it does not cover. - Keep links explicit and stable. diff --git a/docs/reference/workspace-layout.md b/docs/reference/workspace-layout.md index 3c93ff22..dd86703d 100644 --- a/docs/reference/workspace-layout.md +++ b/docs/reference/workspace-layout.md @@ -23,8 +23,8 @@ should not be treated as repository source. | `scripts/config/` | Repository automation scripts for config-derived artifacts. | | `artifacts/github/` | Checked-in GitHub change bundles and editorial analysis drafts used by the public signal pipeline. | | `artifacts/archive/` | Checked-in manifests for cold Radar archive batches stored as GitHub Release assets. | -| `artifacts/social/` | Checked-in Publisher social post drafts and publication evidence. | -| `dev/skills/` | Repository-development skills for Radar upstream triage, code analysis, release analysis, GitHub signal drafting, and X post drafting. These are not part of installable plugin distribution. | +| `artifacts/social/` | Checked-in Publisher social publication records, blocked-cap records, and generated-media evidence. | +| `dev/skills/` | Repository-development skills for Radar upstream triage, code analysis, release analysis, GitHub signal drafting, and X publishing. These are not part of installable plugin distribution. | | `plugins/decodex/` | Canonical installable Decodex plugin source and reusable agent-facing skills, including planning, manual CLI, automation, commit, land, and labels. | | `docs/spec/` | Normative runtime, workflow, site, and content contracts. | | `docs/runbook/` | Operator procedures, validation sequences, deployment steps, and content workflows. | @@ -76,26 +76,25 @@ Those runtime and operator surfaces stay in `apps/decodex/` and `docs/spec/`. ## GitHub signal tooling -`scripts/github/` owns deterministic content scripts. Its automated Codex step may -apply the repo-local code-analysis and GitHub-signal instructions under `dev/skills/` -to produce the existing `analysis_draft` JSON consumed by `render_signal_entry.py`. -`sync_latest_signals.py` is the continuous Radar entrypoint: it scans recent upstream -commits, resolves them back to PRs when possible, and reuses the existing bundle, -analysis-draft, render, and validation path. `backfill_release_range.py` fills gaps for -release-window summaries. The broader upstream triage, release-analysis, and X-drafting -skills remain manual Radar/Publisher reasoning surfaces unless a script explicitly -wires them into a checked-in contract. Generated GitHub bundles and analysis drafts live under -`artifacts/github/` and must stay explicit and checked into the repository. - -Raw bundles and analysis drafts are hot artifacts with a 28-day Git retention window. +`scripts/github/` owns deterministic content scripts. `sync_upstream_radar.py` is the +continuous Radar entrypoint: it scans recent upstream commits, resolves them back to +PRs when possible, records local ledger state, and writes an +`upstream_review_queue/v1` artifact for Codex automation. It does not run Codex or +render public signals. `backfill_release_range.py` fills gaps for release-window +summaries when an operator or automation chooses to generate signal content. Generated +GitHub bundles and analysis drafts live under `artifacts/github/` and must stay +explicit and checked into the repository when promoted into Publisher content. + +Raw bundles and analysis drafts are hot artifacts with a 21-day Git retention window. Older raw batches move to dedicated GitHub Release assets, with recovery manifests kept under `artifacts/archive/index/`. `artifacts/github/impact/` may hold `upstream_impact/v1` classifications when an upstream Codex change has public-signal, Control Plane, or Publisher implications. -`artifacts/social/` may hold `social_post_draft/v1` drafts before external publication. -Both remain checked-in review artifacts; neither turns the public site into a live -service. +`artifacts/github/review-queue/` may hold the latest deterministic review queue. +`artifacts/social/` may hold `social_post/v1` published, blocked, failed, or skipped +records for external publication. Both remain checked-in artifacts; neither turns the +public site into a live service. ## Installable Codex surface diff --git a/docs/runbook/index.md b/docs/runbook/index.md index 08069522..8c200dbd 100644 --- a/docs/runbook/index.md +++ b/docs/runbook/index.md @@ -34,10 +34,10 @@ Question this index answers: "which sequence should I execute?" GitHub change bundles, running Codex editorial analysis, validating signal entries, and publishing static site content. - [`radar-artifact-archive.md`](./radar-artifact-archive.md) for moving raw Radar - bundles and analysis drafts out of Git after the 28-day hot window while keeping + bundles and analysis drafts out of Git after the 21-day hot window while keeping release-asset recovery manifests checked in. - [`social-publishing-workflow.md`](./social-publishing-workflow.md) for turning Radar - evidence into reviewed `@decodexspace` X drafts and recording publication evidence. + evidence into low-frequency `@decodexspace` X posts or blocked publication records. - [`recover-review-handoff.md`](./recover-review-handoff.md) for diagnosing and explicitly rebinding retained review lanes blocked by a missing or stale runtime DB handoff diff --git a/docs/runbook/local-github-signal-workflow.md b/docs/runbook/local-github-signal-workflow.md index 7453023d..c7bb7db5 100644 --- a/docs/runbook/local-github-signal-workflow.md +++ b/docs/runbook/local-github-signal-workflow.md @@ -1,6 +1,8 @@ # Local GitHub Signal Workflow -Goal: Define the repeatable workflow for collecting GitHub change bundles, running Codex analysis locally or on a trusted CI runner, validating signal entries, and publishing content to the site. +Goal: Define the repeatable workflow for collecting upstream Codex evidence, running +Codex analysis in automation or local sessions, validating signal entries, and +publishing content to the site. Read this when: - You are preparing the first GitHub-backed Decodex signal entries. @@ -15,6 +17,7 @@ Inputs: Depends on: - `docs/reference/workspace-layout.md` - `docs/spec/github-change-bundle.md` +- `docs/spec/upstream-review.md` - `docs/spec/signal-entry.md` - `docs/spec/release-delta.md` - `docs/spec/radar-ledger.md` @@ -23,22 +26,21 @@ Depends on: Outputs: - A validated signal entry committed to the repo -- Optional upstream-impact and social-draft artifacts when the change affects Control +- Optional upstream-impact and social publication artifacts when the change affects Control Plane or external publishing - A push that allows CI to build and deploy the static site ## Workflow -1. Track upstream Codex commits continuously. Treat each commit as a candidate to - understand, then resolve it back to a PR when possible. The sync writes - `.decodex/radar.sqlite3` by default so skipped and deferred commits remain - traceable without becoming public site entries. -2. Triage upstream activity with `dev/skills/codex-upstream-triage/` when the - candidate is not already chosen by automation or by the operator. -3. Build a normalized GitHub change bundle under `artifacts/github/bundles/` for - selected candidates. -4. Analyze source behavior with `dev/skills/codex-code-analysis/` as an in-session - reasoning pass; do not create a separate checked-in artifact for this pass. +1. Track upstream Codex commits continuously with `scripts/github/sync_upstream_radar.py`. + Treat each commit as an evidence unit, resolve it back to a PR when possible, write + `.decodex/radar.sqlite3`, and refresh `upstream_review_queue/v1`. +2. Let Codex automation consume queued subjects and run + `dev/skills/codex-code-analysis/` for each source-backed review. +3. Build a normalized GitHub change bundle under `artifacts/github/bundles/` when the + automation or operator needs full source context for a candidate. +4. Promote reviewed conclusions into `upstream_impact/v1` when the change may affect + Control Plane, Publisher planning, compatibility, or adoption. 5. Use `dev/skills/codex-release-analysis/` when the source is a release, prerelease, app update, or changelog entry. 6. Run final signal drafting with `dev/skills/github-signal/` and save the @@ -48,10 +50,10 @@ Outputs: 9. Classify upstream impact when the change may affect Control Plane or Publisher. 10. Regenerate the release-delta artifact so the homepage compares release windows using the updated signal set. -11. Draft optional social publishing content only through +11. Publish optional social content or record a skip/block only through [`social-publishing-workflow.md`](./social-publishing-workflow.md). 12. When upstream publishes a release or prerelease, use `codex-release-analysis` to - roll up the accumulated commit/PR analysis into a release summary or X draft. + roll up the accumulated commit/PR analysis into a release summary or X post. 13. Review the rendered content manually in the homepage feed. 14. Push the content update and let CI build and deploy the static site. @@ -129,12 +131,12 @@ Repo-local editorial instruction entrypoint: These entrypoints are for Decodex repository development only. They are incomplete as general user-facing skills and must not be packaged with the installable Decodex plugin. Today only `github_change_bundle/v1`, `analysis_draft`, `signal_entry/v1`, -`upstream_impact/v1`, `release_delta/v1`, and `social_post_draft/v1` are durable +`upstream_impact/v1`, `release_delta/v1`, and `social_post/v1` are durable content contracts for this workflow. Automated sync entrypoint: -- `scripts/github/sync_latest_signals.py` +- `scripts/github/sync_upstream_radar.py` Bootstrap or inspect local historical trace: @@ -170,25 +172,24 @@ For the release-delta artifact: - use release and prerelease publication time as a summary checkpoint over accumulated commit/PR analysis, not as the primary source of truth -For upstream-impact and social-draft artifacts: +For upstream-impact and social publishing artifacts: - classify Control Plane implications before creating engineering follow-up work -- keep social drafts checked in and unposted until approval +- keep social publication, block, skip, and failure records checked in - do not use X engagement as technical evidence ## CI boundary The current Decodex boundary is: -- local Codex run: manual editorial review, batch backfills, and prompt iteration -- deterministic scripts: commit/PR discovery, bundle fetch, Codex analysis execution, - render, and validation -- trusted CI runner: refresh of recent upstream commits plus normal site validation and commit/push of changed content +- GitHub Actions: deterministic upstream commit discovery, PR mapping, review-queue + refresh, release-delta refresh, validation, and commit/push of changed metadata. +- Codex automation: AI source review, compatibility judgment, Publisher judgment, + social publication, and any promotion into signal or follow-up artifacts. +- local operator sessions: manual editorial review, batch backfills, prompt iteration, + and public-content audit. -The hourly GitHub Actions path assumes: +The GitHub Actions paths assume: -- Codex CLI is installed on the runner -- a full `auth.json` payload is injected into `CODEX_HOME` -- `CODEX_AUTH_JSON` is treated as a sensitive secret and never logged - `GITHUB_PAT_Y` is available when you want authenticated GitHub API requests for the routed `y` identity; otherwise the sync falls back to unauthenticated reads for public data - `cargo make decodex-checks` remains the final gate before a content refresh commit diff --git a/docs/runbook/radar-artifact-archive.md b/docs/runbook/radar-artifact-archive.md index 7a024a36..73a785e7 100644 --- a/docs/runbook/radar-artifact-archive.md +++ b/docs/runbook/radar-artifact-archive.md @@ -1,6 +1,6 @@ # Radar Artifact Archive -Goal: Move old raw Radar artifacts out of Git after the 28-day hot window while +Goal: Move old raw Radar artifacts out of Git after the 21-day hot window while keeping public signals and archive recovery evidence available. Read this when: @@ -13,7 +13,7 @@ Governing spec: ## Archive candidates -Archive these after the 28-day hot window: +Archive these after the 21-day hot window: - `artifacts/github/bundles/*.json` - `artifacts/github/analysis/*.analysis.json` @@ -25,14 +25,14 @@ Do not archive these as part of raw cleanup: - `site/src/content/signals/*.json` - the current `site/src/content/release-deltas/openai-codex-latest.json` - `artifacts/github/impact/*.json` with active Control Plane or Publisher relevance -- approved or published `artifacts/social/x/*.json` +- `artifacts/social/x/posts/*.json` - `artifacts/archive/index/*.json` ## Procedure 1. Choose the archive window. - Prefer a calendar month or a release-window name. - - Ensure the selected raw artifacts are outside the 28-day hot window. + - Ensure the selected raw artifacts are outside the 21-day hot window. - For artifacts without embedded collection timestamps, use the paired signal `published_at` or record the operator-selected evidence date in the manifest. diff --git a/docs/runbook/social-publishing-workflow.md b/docs/runbook/social-publishing-workflow.md index ed3154fe..25152903 100644 --- a/docs/runbook/social-publishing-workflow.md +++ b/docs/runbook/social-publishing-workflow.md @@ -1,26 +1,27 @@ # Social Publishing Workflow -Goal: Turn Radar evidence into reviewable `@decodexspace` social drafts without making -the public site or X account depend on a live Decodex daemon. +Goal: Turn Radar evidence into low-frequency `@decodexspace` X posts or checked-in +blocked publication records without making the public site depend on a live Decodex +daemon. Read this when: - You are preparing X posts about Codex releases, PRs, app updates, or usage patterns. -- You need to decide whether a Decodex signal should also produce a social draft. -- You are reviewing a `social_post_draft/v1` before external publication. +- You need to decide whether a Decodex signal should also produce a social post. +- You are auditing a `social_post/v1` record after publication or a daily-cap block. Inputs: -- Source evidence from GitHub, OpenAI developer changelogs, checked-in signal entries, - release-delta artifacts, or verified browser observations. +- Source evidence from GitHub, checked-in signal entries, upstream reviews, + upstream-impact records, release-delta artifacts, or verified browser observations. - The governing schemas: - [`../spec/upstream-impact.md`](../spec/upstream-impact.md) - - [`../spec/social-post-draft.md`](../spec/social-post-draft.md) + - [`../spec/social-publishing.md`](../spec/social-publishing.md) - [`../spec/signal-entry.md`](../spec/signal-entry.md) Depends on: - [`local-github-signal-workflow.md`](./local-github-signal-workflow.md) for the GitHub signal path. -- [`../../dev/skills/x-post-draft/SKILL.md`](../../dev/skills/x-post-draft/SKILL.md) - for the repo-local drafting method. +- [`../../dev/skills/x-post-publisher/SKILL.md`](../../dev/skills/x-post-publisher/SKILL.md) + for the repo-local publishing method. - [`../decisions/radar-control-plane-publisher.md`](../decisions/radar-control-plane-publisher.md) for the Radar, Control Plane, and Publisher boundary. - [`../decisions/static-public-site.md`](../decisions/static-public-site.md) for the @@ -28,8 +29,8 @@ Depends on: Outputs: - An optional `upstream_impact/v1` artifact under `artifacts/github/impact/`. -- An optional `social_post_draft/v1` artifact under `artifacts/social/x/`. -- A published X URL only after explicit approval. +- A `social_post/v1` record under `artifacts/social/x/posts//`. +- Optional generated media under `artifacts/social/x/images/`. ## Style Benchmarks @@ -40,13 +41,13 @@ for technical claims. | --- | --- | --- | | `@Codex_Changelog` | Fast release-aware bullets with a changelog link. | Useful for `release_pulse`, but Decodex should not become a duplicate release bot. | | `@LLMJunky` | Practical user interpretation: how a feature changes real workflows, what is worth trying, and where limits remain. | Prefer this style when Radar evidence can support the claim quickly. | -| `@decodexspace` | Fresh account with no post history yet. | Establish a voice around evidence-backed Codex intelligence and Decodex operator impact. | +| `@decodexspace` | Low-frequency automated publication channel. | Establish a voice around evidence-backed Codex intelligence and Decodex operator impact. | ## Workflow 1. Start from source evidence. - - Prefer a merged PR bundle, release note, OpenAI developer changelog entry, or - already-rendered `signal_entry/v1`. + - Prefer a source-backed `upstream_review/v1`, merged PR bundle, release-delta + compare entry, already-rendered `signal_entry/v1`, or `upstream_impact/v1`. - Do not start from social engagement alone. 2. Classify upstream impact. @@ -55,34 +56,46 @@ for technical claims. - Use `public_signal_decision`, `control_plane_impact`, and `publisher_angle` from [`../spec/upstream-impact.md`](../spec/upstream-impact.md). -3. Decide whether to draft a post. - - Draft when the change has a clear `release_pulse`, `practical_explainer`, - `release_rollup`, `operator_impact`, or `watch_note` angle. - - Skip when the change is internal cleanup, too weakly sourced, too private, or too - vague for a useful reader takeaway. - -4. Create a checked-in draft. - - Use `dev/skills/x-post-draft/SKILL.md`. - - Write `artifacts/social/x/.json`. - - Use `schema = "social_post_draft/v1"`. - - Keep `status = "draft"` until approval. - - Keep `text[]` short enough for X, one item per post in a thread. - -5. Review the claims. - - Every user-facing claim must map to source evidence. - - Confirm the post does not imply shipped Decodex behavior without Control Plane - evidence. - - Confirm beta, rollout, platform, and config caveats are explicit. - -6. Approve or reject. - - Move `status` to `approved` only after human or explicitly routed approval. - - Keep rejected drafts as `status = "rejected"` when the rejection explains a useful - future boundary. - -7. Publish externally. - - Do not post from automation unless the draft is already `approved`. - - After posting, update the artifact to `status = "published"` and set - `published_url`. +3. Decide whether to publish. + - Publish only when the change has a clear `release_pulse`, `practical_explainer`, + `release_rollup`, `operator_impact`, or valuable `watch_note` angle. + - Skip when the change is internal cleanup, too weakly sourced, too private, too + vague, or not useful enough for a reader. + +4. Check idempotency and daily cap. + - Build a stable idempotency key from account, source, mode, and release checkpoint + when applicable. + - Count already-published `@decodexspace` records for the cap day. + - The default cap day uses `Asia/Shanghai`. + - If the candidate would exceed 8 posts, do not post. Write + `status = "blocked"` with `block.reason = "daily_cap_exceeded"`. + +5. Generate media. + - Use the `decodex_signal_card` image template in + [`../spec/social-publishing.md`](../spec/social-publishing.md). + - Do not rely on AI-generated text in the image. + - Attach media unless the record explains why media was skipped. + +6. Publish through Chrome. + - Verify Chrome is logged in as `@decodexspace`. + - Compose the English post or thread. + - Attach generated media when present. + - Fail closed if account verification, duplicate detection, media upload, or final + URL readback is unreliable. + +7. Write the publication record. + - Use `schema = "social_post/v1"`. + - Use `target_account = "decodexspace"` and `controller_account = "hackink"`. + - Set `status = "published"`, `blocked`, `failed`, or `skipped`. + - Preserve source refs, evidence notes, claims, decision data, and publication URLs + when available. + +8. Validate. + - Run: + +```bash +python3 scripts/github/validate_social_post.py artifacts/social/x +``` ## Mode Guidance @@ -122,10 +135,10 @@ Use `watch_note` when: ## Guardrails - Do not send credentials, private issue details, or local runtime paths to X. -- Do not publish unapproved drafts. -- Do not use `@Chrome` or any browser automation to post externally without explicit - user approval for that specific post. -- Do not let social drafting bypass the static site, signal-entry, or upstream-impact - evidence chain. +- Do not publish without a source-backed worthiness decision. +- Do not exceed 8 posts per cap day for `@decodexspace`. +- Do not let Chrome automation keep retrying after a failed or uncertain publish. +- Do not let social publishing bypass the static site, signal-entry, upstream-review, + or upstream-impact evidence chain. - Do not quote third-party posts at length. Record style observations, not copied content. diff --git a/docs/spec/index.md b/docs/spec/index.md index 1618bf57..136d2d15 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -66,14 +66,15 @@ Then keep the body explicit: artifact used by the homepage release-delta module. - [`radar-ledger.md`](./radar-ledger.md) defines the local SQLite ledger that keeps every observed upstream Codex commit traceable without storing all raw history in Git. -- [`radar-artifact-retention.md`](./radar-artifact-retention.md) defines the 28-day Git +- [`upstream-review.md`](./upstream-review.md) defines the deterministic upstream review + queue and AI review boundary for every observed upstream Codex commit or PR. +- [`radar-artifact-retention.md`](./radar-artifact-retention.md) defines the 21-day Git hot window for raw Radar artifacts, the warm curated artifacts that stay in Git, and the GitHub Release archive manifest contract. - [`upstream-impact.md`](./upstream-impact.md) defines how Radar classifies upstream Codex changes for public signals, Control Plane follow-up, and Publisher angles. -- [`social-post-draft.md`](./social-post-draft.md) defines the checked-in social draft - artifact required before `@decodexspace` or another external social account publishes - Decodex content. +- [`social-publishing.md`](./social-publishing.md) defines the checked-in social + publication, block, skip, and failure records for `@decodexspace`. - [`site-contract.md`](./site-contract.md) defines the static-site page budget, homepage obligations, and card rendering contract. - [`reset-status.md`](./reset-status.md) defines the reset-status artifact consumed by diff --git a/docs/spec/radar-artifact-retention.md b/docs/spec/radar-artifact-retention.md index a287d4fb..9c1406df 100644 --- a/docs/spec/radar-artifact-retention.md +++ b/docs/spec/radar-artifact-retention.md @@ -7,8 +7,8 @@ recoverable. Status: normative Read this when: -- You are deciding whether a GitHub bundle, analysis draft, signal entry, upstream - impact note, or social draft should remain checked in. +- You are deciding whether a GitHub bundle, upstream review, analysis draft, signal + entry, upstream impact note, or social publication record should remain checked in. - You are preparing an archive batch for old Radar artifacts. - You are adding automation that prunes or restores `artifacts/github/` material. @@ -29,8 +29,8 @@ Decodex uses three retention classes for Radar and Publisher data. | Class | Storage | Examples | Retention | | --- | --- | --- | --- | -| Hot raw artifacts | Git working tree | `artifacts/github/bundles/*.json`, `artifacts/github/analysis/*.analysis.json` | At most 28 days in Git after collection or publication. | -| Warm curated artifacts | Git working tree | `site/src/content/signals/*.json`, `site/src/content/release-deltas/openai-codex-latest.json`, `artifacts/github/impact/*.json`, approved or published `artifacts/social/x/*.json` | Retained in Git while they are part of the public site, Control Plane review trail, or Publisher record. | +| Hot raw artifacts | Git working tree | `artifacts/github/bundles/*.json`, `artifacts/github/reviews/*.review.json`, `artifacts/github/analysis/*.analysis.json` | At most 21 days in Git after collection or publication. | +| Warm curated artifacts | Git working tree | `site/src/content/signals/*.json`, `site/src/content/release-deltas/openai-codex-latest.json`, `artifacts/github/impact/*.json`, `artifacts/social/x/posts/*.json` | Retained in Git while they are part of the public site, Control Plane review trail, Publisher record, or cap analysis. | | Cold raw archive | GitHub Release assets plus a Git manifest | Archived bundle and analysis batches, optional source snapshots, optional ledger exports | Retained outside the Git tree. Git keeps only the manifest. | The hot raw window is intentionally short. Continuous Radar should keep every upstream @@ -38,16 +38,16 @@ commit traceable, but it must not make the repository a permanent raw-data wareh ## Hot raw artifact rule -Raw GitHub bundles and local editorial analysis drafts must not remain in Git for more -than 28 days after collection or publication unless a human explicitly marks the batch -as still active. +Raw GitHub bundles, upstream review artifacts, and local editorial analysis drafts must +not remain in Git for more than 21 days after collection or publication unless a human +explicitly marks the batch as still active. For existing artifacts that do not carry their own collection timestamp, the retention clock should use the paired `signal_entry/v1.published_at` when available. If no paired signal exists, the archive batch must record the operator-selected evidence date in its manifest. -The 28-day limit applies to the raw supporting material, not to the public signal +The 21-day limit applies to the raw supporting material, not to the public signal entry. A signal entry may outlive its raw bundle when the archive manifest preserves how to recover the original bundle and analysis draft. @@ -57,12 +57,13 @@ Keep these artifacts in Git unless a separate content cleanup explicitly removes - published `signal_entry/v1` files under `site/src/content/signals/` - the current homepage `release_delta/v1` artifact +- the latest `upstream_review_queue/v1` artifact under `artifacts/github/review-queue/` - `upstream_impact/v1` records that affect Decodex Control Plane or Publisher follow-up -- approved or published `social_post_draft/v1` records +- `social_post/v1` records, including daily-cap blocks - archive manifests under `artifacts/archive/index/` -Draft or rejected social artifacts may be archived after the same 28-day hot window -unless they document a still-useful editorial boundary. +Generated social images may be archived after the same 21-day hot window when the +paired `social_post/v1` record keeps enough metadata to recover or regenerate them. ## Cold archive destination @@ -97,7 +98,7 @@ The manifest must contain: | `schema` | string | Must be `radar_archive_manifest/v1`. | | `archive_id` | string | Stable archive identifier. | | `created_at` | string | UTC timestamp for archive creation. | -| `retention_days` | number | Must be `28` unless a later spec changes the policy. | +| `retention_days` | number | New manifests must use `21` unless a later spec changes the policy. Historical manifests may keep the value that governed their original archive batch. | | `source_commit` | string | Repository commit used to select and package files. | | `release_tag` | string | GitHub tag holding the archive assets. | | `release_url` | string | GitHub Release URL when available. | diff --git a/docs/spec/radar-ledger.md b/docs/spec/radar-ledger.md index ba5bd022..591c5c95 100644 --- a/docs/spec/radar-ledger.md +++ b/docs/spec/radar-ledger.md @@ -6,7 +6,7 @@ traceable without putting every raw or low-value artifact into Git. Status: normative Read this when: -- You are changing `scripts/github/sync_latest_signals.py`. +- You are changing `scripts/github/sync_upstream_radar.py`. - You are importing existing GitHub bundles, analysis drafts, or signal entries into historical Radar state. - You need to decide what belongs in local history instead of checked-in public @@ -45,7 +45,7 @@ Required tables: | Table | Purpose | | --- | --- | | `upstream_commit` | One row per observed upstream commit, including SHA, title, URL, commit time, PR number when known, and first/last seen timestamps. | -| `radar_review` | One current review state per commit or PR subject. Status values include `seen`, `skipped`, `watch`, `signal`, `control_plane`, `social`, `deprecated`, and `archived`. | +| `radar_review` | One current review state per commit or PR subject. Status values include `seen`, `skipped`, `watch`, `signal`, `control_plane`, `social`, `deprecated`, and `archived`. The deterministic queue uses `watch` for subjects awaiting AI review. | | `artifact_link` | Links commits or PRs to Git-tracked or archived artifacts, including file path, artifact kind, SHA-256, size, and creation time. | | `source_cache` | Optional source cache index for fetched remote payloads when a future cache is added. | @@ -57,17 +57,17 @@ Use the ledger for: - every recent upstream commit observed by continuous Radar - commits skipped because they are low-signal maintenance -- positive candidates deferred by run budget +- subjects queued for AI review by `upstream_review_queue/v1` - mappings from commits to PRs - links from commits or PRs to bundles, analysis drafts, signals, impact notes, social - drafts, release deltas, archive manifests, or ledger exports + posts, release deltas, archive manifests, or ledger exports Use Git for: - curated public site signals - current release-delta data - upstream-impact records that affect Decodex Control Plane or Publisher follow-up -- approved or published social drafts +- social publication records - cold archive manifests Do not use Git as the permanent store for every raw bundle, raw source cache, skipped @@ -75,13 +75,13 @@ candidate, retry queue, or long low-value analysis. ## Sync behavior -`scripts/github/sync_latest_signals.py` writes the local ledger by default. It records +`scripts/github/sync_upstream_radar.py` writes the local ledger by default. It records every recent commit it inspects, including commits that do not become public signals. Operators may disable ledger writes with: ```sh -python3 scripts/github/sync_latest_signals.py --no-ledger +python3 scripts/github/sync_upstream_radar.py --no-ledger ``` Existing checked-in artifacts can be imported with: diff --git a/docs/spec/social-post-draft.md b/docs/spec/social-post-draft.md deleted file mode 100644 index 1ea16598..00000000 --- a/docs/spec/social-post-draft.md +++ /dev/null @@ -1,102 +0,0 @@ -# Social Post Draft - -Purpose: Define the checked-in draft artifact used before Decodex publishes from the -`@decodexspace` X account or another external social channel. - -Status: normative - -Read this when: -- You are generating, reviewing, or validating social publishing drafts. -- You need to decide what evidence a post must carry before external publication. -- You are extending Publisher beyond static site signal entries. - -Not this document: -- The upstream GitHub bundle schema. Read [`github-change-bundle.md`](./github-change-bundle.md). -- The public site signal-entry schema. Read [`signal-entry.md`](./signal-entry.md). -- The social publishing procedure. Read - [`../runbook/social-publishing-workflow.md`](../runbook/social-publishing-workflow.md). - -Defines: -- The `social_post_draft/v1` artifact shape. -- Allowed post modes for Decodex Publisher. -- Review and publication state rules. - -## Artifact identity - -The canonical schema identifier is: - -- `social_post_draft/v1` - -Recommended checked-in location: - -- `artifacts/social/x/.json` - -## Required fields - -| Field | Type | Notes | -| --- | --- | --- | -| `schema` | string | Must be `social_post_draft/v1`. | -| `slug` | string | Stable URL-safe identifier for the draft. | -| `channel` | string | Must be `x` for X/Twitter drafts. | -| `target_account` | string | Account handle without URL, such as `decodexspace`. | -| `mode` | string | One value from the post-mode table. | -| `status` | string | `draft`, `approved`, `published`, or `rejected`. | -| `audience` | string | Primary reader group. | -| `text` | array | One or more post bodies, one array item per thread post. | -| `source_refs` | object | Links to signal, upstream-impact, release, PR, or changelog evidence. | -| `evidence_notes` | array | Non-empty list of evidence-backed notes that justify the post. | -| `claims` | array | Non-empty list of user-facing claims with evidence references. | - -Optional fields: - -- `published_url`: required when `status = "published"`. -- `approval`: reviewer, timestamp, and notes when `status = "approved"` or - `status = "published"`; optional rejection notes when `status = "rejected"`. -- `caveats`: rollout limits, uncertainty, platform limits, or version gates. -- `media_refs`: checked-in screenshots, videos, or generated assets used by the post. - -## Post modes - -Use exactly one `mode` value: - -| Value | Purpose | -| --- | --- | -| `release_pulse` | Short release-aware summary with a source link. | -| `release_rollup` | Release or prerelease summary built from accumulated signal, upstream-impact, and commit/PR analysis. | -| `practical_explainer` | Concrete user-facing explanation of how to try or reason about a feature. | -| `operator_impact` | Decodex-specific explanation of app-server, plugin, browser, MCP, sandbox, config, or orchestration implications. | -| `thread` | Multi-post explanation when one post would hide important evidence or caveats. | -| `watch_note` | Cautious note for interesting changes that are not ready for a strong recommendation. | - -`release_pulse` should be the minority path for `@decodexspace`; the account should -differ from release-only bots by preferring `practical_explainer`, -`operator_impact`, and evidence-backed `release_rollup` drafts when evidence supports -them. - -## Claim rules - -Each `claims[]` entry must include: - -- `text`: the claim visible or implied in the post. -- `evidence`: source reference key, URL, file path, or artifact path. -- `confidence`: `confirmed`, `likely`, or `weak`. - -Rules: - -- Do not publish a claim without evidence. -- Do not imply Decodex runtime support unless Control Plane evidence exists. -- Do not present a beta, hidden, or rollout-gated capability as generally available. -- Do not use a social post to replace the site signal or upstream-impact artifact. -- Do not quote third-party posts at length. Summarize style or public reaction unless - the quoted text is short and necessary. - -## Status rules - -- `draft`: generated or edited, not approved for external publication. -- `approved`: reviewed by a human or an explicitly routed approval process. -- `published`: externally posted; `published_url` is required. -- `rejected`: intentionally not publishable; keep rejection notes in `approval.notes` - or `caveats`. - -No automation may post a `draft` directly to X. External publication requires -`status = "approved"` immediately before the posting action. diff --git a/docs/spec/social-publishing.md b/docs/spec/social-publishing.md new file mode 100644 index 00000000..bedd4a72 --- /dev/null +++ b/docs/spec/social-publishing.md @@ -0,0 +1,187 @@ +# Social Publishing + +Purpose: Define the checked-in publication record used when Decodex publishes from the +`@decodexspace` X account or blocks a candidate that would exceed policy. + +Status: normative + +Read this when: +- You are generating, validating, or auditing Decodex Publisher output for X. +- You need to decide what evidence a post must carry before external publication. +- You are extending Publisher beyond static site signal entries. + +Not this document: +- The upstream GitHub bundle schema. Read [`github-change-bundle.md`](./github-change-bundle.md). +- The public site signal-entry schema. Read [`signal-entry.md`](./signal-entry.md). +- The social publishing procedure. Read + [`../runbook/social-publishing-workflow.md`](../runbook/social-publishing-workflow.md). + +Defines: +- The `social_post/v1` artifact shape. +- Allowed post modes for Decodex Publisher. +- The automated Chrome publishing boundary. +- The daily cap and blocked-publication ledger rule. +- The generated-image style contract. + +## Artifact Identity + +The canonical schema identifier is: + +- `social_post/v1` + +Recommended checked-in locations: + +- `artifacts/social/x/posts//.json` +- `artifacts/social/x/images/.png` + +`social_post/v1` is a publication record, not a review-only draft. The record is +written whether automation publishes the post, skips it, fails safely, or blocks it +because the daily cap has already been reached. + +## Required Fields + +| Field | Type | Notes | +| --- | --- | --- | +| `schema` | string | Must be `social_post/v1`. | +| `slug` | string | Stable URL-safe identifier for the candidate. | +| `channel` | string | Must be `x`. | +| `target_account` | string | Must be `decodexspace` for the primary Publisher automation. | +| `controller_account` | string | Must be `hackink` for ownership attribution unless a later policy changes it. | +| `mode` | string | One value from the post-mode table. | +| `status` | string | `published`, `blocked`, `failed`, or `skipped`. | +| `audience` | string | Primary reader group. | +| `text` | array | One or more English post bodies, one array item per thread post. | +| `source_refs` | object | Links to signal, upstream-impact, upstream-review, release, PR, or changelog evidence. | +| `evidence_notes` | array | Non-empty list of evidence-backed notes that justify the post or skip decision. | +| `claims` | array | Non-empty list of user-facing claims with evidence references. | +| `decision` | object | AI worthiness, priority, idempotency key, daily counter, and cap decision. | + +Optional fields: + +- `publication`: required when `status = "published"`. +- `block`: required when `status = "blocked"`. +- `failure`: required when `status = "failed"`. +- `skip`: required when `status = "skipped"`. +- `caveats`: rollout limits, uncertainty, platform limits, or version gates. +- `media_refs`: checked-in or locally generated assets used by the post. + +## Post Modes + +Use exactly one `mode` value: + +| Value | Purpose | +| --- | --- | +| `release_pulse` | Short release-aware summary with a source link. | +| `release_rollup` | Release or prerelease summary built from accumulated signal, upstream-impact, and commit/PR analysis. | +| `practical_explainer` | Concrete user-facing explanation of how to try or reason about a feature. | +| `operator_impact` | Decodex-specific explanation of app-server, plugin, browser, MCP, sandbox, config, or orchestration implications. | +| `thread` | Multi-post explanation when one post would hide important evidence or caveats. | +| `watch_note` | Cautious note for interesting changes that are not ready for a strong recommendation. | + +`@decodexspace` should mostly use `practical_explainer`, `operator_impact`, and +evidence-backed `release_rollup`. `release_pulse` is allowed only when the release +itself is the useful alert. + +## Claim Rules + +Each `claims[]` entry must include: + +- `text`: the claim visible or implied in the post. +- `evidence`: source reference key, URL, file path, or artifact path. +- `confidence`: `confirmed`, `likely`, or `weak`. + +Rules: + +- Do not publish a claim without evidence. +- Do not imply Decodex runtime support unless Control Plane evidence exists. +- Do not present a beta, hidden, or rollout-gated capability as generally available. +- Do not use a social post to replace the site signal or upstream-impact artifact. +- Do not quote third-party posts at length. Summarize style or public reaction unless + the quoted text is short and necessary. + +## Decision Object + +`decision` must contain: + +| Field | Type | Notes | +| --- | --- | --- | +| `worthiness` | string | `publish`, `skip`, or `block`. | +| `priority` | string | `critical`, `high`, `normal`, or `low`. | +| `idempotency_key` | string | Stable key derived from account, source, mode, and checkpoint when applicable. | +| `reason` | string | Short source-backed reason for the decision. | +| `daily_limit` | number | Must be `8`. | +| `daily_count_before` | number | Number of posts already published for the target account on the selected day. | +| `daily_count_after` | number | Expected count after the action; unchanged for blocked, failed, or skipped records. | +| `day` | string | Calendar day used for cap accounting, formatted `YYYY-MM-DD`. | +| `timezone` | string | Default is `Asia/Shanghai`. | + +The daily cap is hard. Automation must not publish the ninth `@decodexspace` post in +the same cap day. Instead it must write a `status = "blocked"` record with +`block.reason = "daily_cap_exceeded"`. + +## Blocked Cap Records + +When a candidate is blocked by the daily cap, the record must preserve the review +material needed for post-run analysis: + +- source PR, commit, release, or signal references +- mode and priority +- AI worthiness reason +- candidate text +- generated image prompt or intended media refs, if any +- `daily_count_before` +- `daily_limit` + +The automation report must call out the block so the operator can inspect why the +candidate volume exceeded the cap. + +## Publication Object + +`publication` must contain: + +| Field | Type | Notes | +| --- | --- | --- | +| `posted_at` | string | UTC timestamp. | +| `published_urls` | array | X URLs produced by the post or thread. | +| `publisher` | string | `chrome` or `x_api`. Primary policy is `chrome`. | +| `account_verified` | boolean | Must be true before publishing. | +| `made_with_ai` | boolean | Must be true when a generated image is attached. | +| `image_template` | string | Must be `decodex_signal_card` when media is attached. | + +Chrome publication is allowed only for the low-frequency `@decodexspace` automation +described here. It must use the logged-in `@decodexspace` account, verify the account +before composing, and fail closed when Chrome, login state, X page structure, duplicate +detection, or media upload is unreliable. + +## Generated Image Contract + +Every published X post should include a generated image unless the publication record +explains why media was skipped. + +Use the stable image template id: + +- `decodex_signal_card` + +Use this base prompt for image generation: + +```text +Create a refined abstract signal-card image for Decodex, a software control plane that +tracks Codex upstream changes. Style: precise, flat, premium technical poster; soft +off-white or near-black background depending on theme; thin neon magenta, lime, and +blue signal paths; sparse node graph; subtle grid; no mascots, no people, no logos +except a small Decodex wordmark area if provided by deterministic overlay. Leave clean +negative space for deterministic overlay text. Do not render long text in the image. +``` + +The AI image must not be trusted for text rendering. Render title, PR/tag, mode, and +source labels with deterministic overlay tooling or keep them in the post text. + +## Release Checkpoints + +Release and prerelease publishing is separate from continuous six-hour Radar review. +Release checkpoint automation may poll upstream releases more frequently than the +commit review loop, but it must publish only when a new release or prerelease checkpoint +appears and enough accumulated review evidence exists. + +Rollups must use prior `upstream_review/v1`, `upstream_impact/v1`, `signal_entry/v1`, +and compare evidence. Sparse Codex prerelease bodies are not sufficient proof. diff --git a/docs/spec/upstream-impact.md b/docs/spec/upstream-impact.md index 65954ed4..3fbf64f5 100644 --- a/docs/spec/upstream-impact.md +++ b/docs/spec/upstream-impact.md @@ -1,7 +1,7 @@ # Upstream Impact Purpose: Define how Decodex classifies upstream Codex changes before they become public -signals, Control Plane follow-up work, or social publishing drafts. +signals, Control Plane follow-up work, or social publishing records. Status: normative @@ -13,8 +13,10 @@ Read this when: Not this document: - The GitHub input bundle schema. Read [`github-change-bundle.md`](./github-change-bundle.md). +- The upstream review queue and AI review boundary. Read + [`upstream-review.md`](./upstream-review.md). - The published site signal schema. Read [`signal-entry.md`](./signal-entry.md). -- The social post draft schema. Read [`social-post-draft.md`](./social-post-draft.md). +- The social publishing schema. Read [`social-publishing.md`](./social-publishing.md). - The operator procedure for publishing. Read [`../runbook/social-publishing-workflow.md`](../runbook/social-publishing-workflow.md). @@ -52,7 +54,7 @@ Recommended checked-in location: Optional fields: - `candidate_followups`: bounded engineering or research follow-up suggestions. -- `social_notes`: notes useful to a later `social_post_draft/v1`. +- `social_notes`: notes useful to a later `social_post/v1`. - `caveats`: uncertainty, version gating, platform limits, or rollout limits. ## Control Plane impact ladder @@ -103,8 +105,10 @@ summary. `upstream_impact/v1` is an editorial bridge artifact: - It may consume `github_change_bundle/v1`. +- It should normally consume a source-backed `upstream_review/v1` conclusion when the + change came from continuous Radar. - It may support a `signal_entry/v1`. -- It may support a `social_post_draft/v1`. +- It may support a `social_post/v1`. - It may justify a later Linear issue or implementation brief. It does not replace any of those artifacts. diff --git a/docs/spec/upstream-review.md b/docs/spec/upstream-review.md new file mode 100644 index 00000000..ec7eb22e --- /dev/null +++ b/docs/spec/upstream-review.md @@ -0,0 +1,138 @@ +# Upstream Review + +Purpose: Define how Decodex Radar turns every observed upstream Codex commit into a +reviewable evidence unit before public publishing or Control Plane follow-up. + +Status: normative + +Read this when: +- You are changing continuous upstream Codex tracking. +- You are building automation that asks AI to analyze Codex commits or PRs. +- You need to decide what GitHub Actions may refresh without Codex auth. +- You are deciding whether an upstream change should become a signal, impact artifact, + social post, or Decodex engineering follow-up. + +Not this document: +- The normalized GitHub source bundle schema. Read [`github-change-bundle.md`](./github-change-bundle.md). +- The Control Plane and Publisher impact shape. Read [`upstream-impact.md`](./upstream-impact.md). +- The public signal schema. Read [`signal-entry.md`](./signal-entry.md). +- The raw artifact archive policy. Read [`radar-artifact-retention.md`](./radar-artifact-retention.md). + +Defines: +- The deterministic `upstream_review_queue/v1` artifact. +- The AI-owned `upstream_review/v1` artifact. +- The rule that release and prerelease tags are rollup checkpoints over commit and PR + evidence, not first-class discovery roots. +- The promotion boundary from upstream review into impact, site, social, or Linear work. + +## Core rule + +Radar tracks every recent upstream Codex commit as an evidence unit. Continuous sync +must record every observed commit in the local Radar ledger and group it by pull +request when GitHub exposes a PR mapping. It must not skip commits only because the +commit title looks like maintenance. + +Deterministic sync may assign surface hints and review priority, but only AI review may +decide the actual user impact, Decodex compatibility risk, adoption opportunity, or +community publishing value. + +## Artifact identities + +The deterministic queue schema identifier is: + +- `upstream_review_queue/v1` + +Recommended checked-in location: + +- `artifacts/github/review-queue/openai-codex-latest.json` + +The AI review schema identifier is: + +- `upstream_review/v1` + +Recommended checked-in location: + +- `artifacts/github/reviews/.review.json` + +Review artifacts are hot Radar artifacts unless they are promoted into +`upstream_impact/v1`, `signal_entry/v1`, `social_post/v1`, or a Linear follow-up. +Apply the 21-day hot-window rule from [`radar-artifact-retention.md`](./radar-artifact-retention.md). + +## Queue requirements + +`upstream_review_queue/v1` must contain: + +| Field | Type | Notes | +| --- | --- | --- | +| `schema` | string | Must be `upstream_review_queue/v1`. | +| `repo` | string | Upstream repository, normally `openai/codex`. | +| `generated_at` | string | UTC generation timestamp. | +| `source` | object | Default branch, search limit, and source directory context. | +| `subjects` | array | Unique PR or commit subjects that still need AI review. | +| `counts` | object | Commit scan and priority counts for automation routing. | + +Each `subjects[]` record must contain: + +| Field | Type | Notes | +| --- | --- | --- | +| `subject_kind` | string | `pr` or `commit`. | +| `subject_id` | string | PR number as a string, or a commit SHA. | +| `title` | string | PR title or commit title. | +| `url` | string | GitHub source URL. | +| `source_state` | string | `merged`, `open`, `closed`, or `commit_only`. | +| `commit_shas` | array | One or more observed commit SHAs tied to the subject. | +| `changed_file_count` | number | Number of files in the fetched source bundle. | +| `sample_paths` | array | Bounded changed-path sample for routing only. | +| `surface_hints` | array | Deterministic hints such as `app_server_protocol` or `mcp_plugins`. | +| `attention_flags` | array | Deterministic hints such as `deprecated_removed` or `protocol_change`. | +| `review_priority` | string | `critical`, `high`, `normal`, or `low`. | +| `review_reason` | string | Why AI review is still required. | +| `next_step` | string | Must be `ai_review_required`. | + +The queue is a routing artifact. It must not claim final behavior, compatibility risk, +or public value. + +## AI review requirements + +`upstream_review/v1` must be source-backed and contain: + +- the reviewed `subject_kind`, `subject_id`, and source URLs +- the observed change in one factual sentence +- changed surfaces +- user-visible path, if any +- Decodex Control Plane relevance +- Decodex compatibility risk, if any +- adoption opportunity, if any +- community publishing value, if any +- deprecated, removed, migration, or breaking-change notes, if any +- confidence: `confirmed`, `likely`, or `weak` +- source-backed evidence notes +- next actions, each mapped to `none`, `upstream_impact`, `signal_entry`, + `social_post`, or `linear_followup` + +AI review must read enough source evidence to explain behavior. A PR title, release +title, or deterministic queue hint is not enough for a confirmed claim. + +## Promotion boundary + +Promote an upstream review into: + +- `upstream_impact/v1` when it affects Decodex compatibility, Control Plane adoption, + or Publisher planning. +- `signal_entry/v1` when it is community-ready and has user-visible capability, + behavior, try path, or migration value. +- `social_post/v1` when there is a clear public angle and source links are + available. +- a Linear issue when Decodex should adopt, guard, migrate, or investigate the change. + +Do not promote low-value internal churn into public artifacts. Keep it traceable in the +ledger and use it only as release-rollup background if later evidence makes it relevant. + +## Release and prerelease checkpoints + +Release and prerelease tags are summary checkpoints over accumulated upstream reviews. +They may trigger a gap scan, but they must not replace commit and PR evidence. + +This matters most for Codex prereleases because prerelease bodies may be sparse or empty. +Rollups should combine prior reviews, impact artifacts, public signals, and compare +metadata before producing a public summary or X post. diff --git a/scripts/README.md b/scripts/README.md index 4faf79cc..faea930e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -2,9 +2,10 @@ This directory contains executable repository automation. -- `scripts/github/` owns deterministic GitHub signal, release-delta, render, sync, and - validation scripts. +- `scripts/github/` owns deterministic GitHub upstream review queue, release-delta, + render, sync, and validation scripts. - `scripts/config/` owns config-derived artifact synchronization scripts. Checked-in data produced or consumed by scripts belongs outside this directory. GitHub -signal bundles and analysis drafts live under `artifacts/github/`. +review queues, upstream reviews, bundles, impact records, and analysis drafts live +under `artifacts/github/`. diff --git a/scripts/github/README.md b/scripts/github/README.md index 449f3c5a..4659bd29 100644 --- a/scripts/github/README.md +++ b/scripts/github/README.md @@ -9,24 +9,29 @@ Current scripts: - `backfill_release_range.py` - `radar_ledger.py` - `run_codex_analysis.py` -- `sync_latest_signals.py` +- `sync_upstream_radar.py` - `validate_change_bundle.py` +- `validate_upstream_review.py` +- `validate_social_post.py` - `render_signal_entry.py` - `validate_signal_entry.py` Current checked contracts: - `analysis_draft.schema.json` +- `upstream_review_queue/v1` is validated by `contracts.py` +- `upstream_review.schema.json` - `release_delta/v1` is validated by `contracts.py` - `upstream_impact.schema.json` -- `social_post_draft.schema.json` +- `social_post.schema.json` Contract ownership: - input bundle shape: `docs/spec/github-change-bundle.md` +- upstream review queue and AI review boundary: `docs/spec/upstream-review.md` - output signal shape: `docs/spec/signal-entry.md` - upstream impact shape: `docs/spec/upstream-impact.md` -- social post draft shape: `docs/spec/social-post-draft.md` +- social publication shape: `docs/spec/social-publishing.md` Example flow: @@ -45,16 +50,20 @@ python3 scripts/github/validate_signal_entry.py \ site/src/content/signals/openai-codex-pr-15222.json ``` -Continuous commit sync: +Continuous upstream Radar sync: ```bash -python3 scripts/github/sync_latest_signals.py \ +python3 scripts/github/sync_upstream_radar.py \ --repo openai/codex \ - --search-limit 20 \ - --max-new-prs 3 + --search-limit 40 ``` -The sync writes a local SQLite Radar ledger at `.decodex/radar.sqlite3` by default. +The sync records every observed recent commit in the local SQLite Radar ledger and +writes `artifacts/github/review-queue/openai-codex-latest.json`. It does not install +Codex, make AI judgments, render public signals, or publish social posts. +If only `generated_at` would change, the script leaves the existing queue file intact +to avoid empty hourly commits. + Use `--no-ledger` only for throwaway runs. To bootstrap the ledger from existing checked-in artifacts: @@ -72,17 +81,17 @@ python3 scripts/github/backfill_release_range.py \ --max-prs 3 ``` -These scripts stay deterministic on purpose. Local Codex analysis produces the -editorial draft JSON consumed by `render_signal_entry.py`. Trusted automation may -invoke the Codex analysis step as long as `auth.json` is injected into -`CODEX_HOME` and no credentials are logged or persisted into the repo. +These scripts stay deterministic on purpose. GitHub Actions may refresh upstream +queues, release deltas, and validation. Codex automation owns AI review of queued +subjects and may then promote source-backed conclusions into `upstream_impact/v1`, +`analysis_draft`, rendered `signal_entry/v1`, or `social_post/v1`. Repo-local skills under `dev/skills/` are reasoning instructions for the Codex analysis step and for manual Radar/Publisher work. They do not introduce extra intermediate artifact schemas unless the conclusion is promoted into one of the checked-in contracts listed above. -Raw bundles and analysis drafts are retained in Git for a 28-day hot window. Archive +Raw bundles and analysis drafts are retained in Git for a 21-day hot window. Archive older raw batches as dedicated `radar-archive-*` GitHub Release assets and commit only the recovery manifest under `artifacts/archive/index/`. See `docs/spec/radar-artifact-retention.md` and `docs/runbook/radar-artifact-archive.md`. diff --git a/scripts/github/backfill_release_range.py b/scripts/github/backfill_release_range.py index a9deaad3..4d282524 100644 --- a/scripts/github/backfill_release_range.py +++ b/scripts/github/backfill_release_range.py @@ -19,7 +19,6 @@ from build_change_bundle import build_pr_bundle, github_request, routed_token_env # noqa: E402 from contracts import dump_json, load_json, validate_release_delta, validate_signal # noqa: E402 -from sync_latest_signals import run_script # noqa: E402 PR_URL_RE = re.compile(r"/pull/(\d+)$") @@ -68,6 +67,16 @@ def repo_root() -> Path: return SCRIPT_HOME.parents[1] +def run_script(script: str, *extra: str) -> None: + cmd = [sys.executable, str(SCRIPT_HOME / script), *extra] + completed = subprocess.run(cmd, check=False, text=True, capture_output=True, cwd=repo_root()) + if completed.returncode != 0: + stderr = completed.stderr.strip() + stdout = completed.stdout.strip() + details = stderr or stdout or "unknown error" + raise SystemExit(f"{script} failed: {details}") + + def published_pr_numbers(signals_dir: Path) -> set[int]: published: set[int] = set() for path in sorted(signals_dir.glob("*.json")): diff --git a/scripts/github/contracts.py b/scripts/github/contracts.py index a925b274..844ca5b1 100644 --- a/scripts/github/contracts.py +++ b/scripts/github/contracts.py @@ -13,11 +13,42 @@ BUNDLE_SCHEMA = "github_change_bundle/v1" SIGNAL_SCHEMA = "signal_entry/v1" RELEASE_DELTA_SCHEMA = "release_delta/v1" +UPSTREAM_REVIEW_QUEUE_SCHEMA = "upstream_review_queue/v1" +UPSTREAM_REVIEW_SCHEMA = "upstream_review/v1" +SOCIAL_POST_SCHEMA = "social_post/v1" ANALYSIS_MODES = {"pr_first", "commit_only"} SIGNAL_KINDS = {"capability", "behavior_change", "try_now"} SIGNAL_CONFIDENCE = {"confirmed", "likely", "weak"} SIGNAL_IMPACT = {"low", "medium", "high"} SOURCE_ITEM_KINDS = {"pull_request", "commit"} +UPSTREAM_SUBJECT_KINDS = {"commit", "pr"} +UPSTREAM_REVIEW_PRIORITIES = {"critical", "high", "normal", "low"} +UPSTREAM_REVIEW_NEXT_STEPS = {"ai_review_required"} +UPSTREAM_SOURCE_STATES = {"open", "closed", "merged", "commit_only"} +UPSTREAM_REVIEW_ACTION_TYPES = { + "none", + "upstream_impact", + "signal_entry", + "social_post", + "linear_followup", +} +SOCIAL_POST_MODES = { + "release_pulse", + "release_rollup", + "practical_explainer", + "operator_impact", + "thread", + "watch_note", +} +SOCIAL_POST_STATUSES = {"published", "blocked", "failed", "skipped"} +SOCIAL_POST_PRIORITIES = {"critical", "high", "normal", "low"} +SOCIAL_POST_WORTHINESS = {"publish", "skip", "block"} +SOCIAL_BLOCK_REASONS = { + "daily_cap_exceeded", + "duplicate", + "policy_block", + "insufficient_evidence", +} ISSUE_REF_RE = re.compile(r"(?:^|[^\w])((?:[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)?#\d+)") FLAG_RE = re.compile( r"(? ValidationResult: errors.append("comparisons must include the default stable/prerelease pair") return ValidationResult(ok=not errors, errors=errors) + + +def validate_upstream_review_queue(entry: dict[str, Any]) -> ValidationResult: + errors: list[str] = [] + + if entry.get("schema") != UPSTREAM_REVIEW_QUEUE_SCHEMA: + errors.append(f"schema must be {UPSTREAM_REVIEW_QUEUE_SCHEMA}") + + repo = entry.get("repo") + if not isinstance(repo, str) or "/" not in repo: + errors.append("repo must be owner/name") + + if not isinstance(entry.get("generated_at"), str) or not entry["generated_at"]: + errors.append("generated_at must be a non-empty string") + + source = entry.get("source") + if not isinstance(source, dict): + errors.append("source must be an object") + else: + if not isinstance(source.get("default_branch"), str) or not source["default_branch"]: + errors.append("source.default_branch must be a non-empty string") + if not isinstance(source.get("search_limit"), int) or source["search_limit"] < 1: + errors.append("source.search_limit must be a positive integer") + + subjects = entry.get("subjects") + if not isinstance(subjects, list): + errors.append("subjects must be a list") + subjects = [] + + seen: set[tuple[str, str]] = set() + for index, subject in enumerate(subjects): + if not isinstance(subject, dict): + errors.append(f"subjects[{index}] must be an object") + continue + subject_kind = subject.get("subject_kind") + subject_id = subject.get("subject_id") + if subject_kind not in UPSTREAM_SUBJECT_KINDS: + errors.append(f"subjects[{index}].subject_kind must be one of {sorted(UPSTREAM_SUBJECT_KINDS)}") + if not isinstance(subject_id, str) or not subject_id: + errors.append(f"subjects[{index}].subject_id must be a non-empty string") + if isinstance(subject_kind, str) and isinstance(subject_id, str): + key = (subject_kind, subject_id) + if key in seen: + errors.append(f"subjects[{index}] duplicates {subject_kind}:{subject_id}") + seen.add(key) + + for field in ("title", "url", "review_reason"): + if not isinstance(subject.get(field), str) or not subject[field]: + errors.append(f"subjects[{index}].{field} must be a non-empty string") + if isinstance(subject.get("url"), str) and not subject["url"].startswith("https://"): + errors.append(f"subjects[{index}].url must be an https URL") + if subject.get("source_state") not in UPSTREAM_SOURCE_STATES: + errors.append(f"subjects[{index}].source_state must be one of {sorted(UPSTREAM_SOURCE_STATES)}") + if subject.get("review_priority") not in UPSTREAM_REVIEW_PRIORITIES: + errors.append(f"subjects[{index}].review_priority must be one of {sorted(UPSTREAM_REVIEW_PRIORITIES)}") + if subject.get("next_step") not in UPSTREAM_REVIEW_NEXT_STEPS: + errors.append(f"subjects[{index}].next_step must be one of {sorted(UPSTREAM_REVIEW_NEXT_STEPS)}") + + commit_shas = subject.get("commit_shas") + if ( + not isinstance(commit_shas, list) + or not commit_shas + or not all(isinstance(item, str) and item for item in commit_shas) + ): + errors.append(f"subjects[{index}].commit_shas must be a non-empty list of strings") + + for list_field in ("surface_hints", "attention_flags", "sample_paths"): + values = subject.get(list_field) + if values is not None and ( + not isinstance(values, list) + or not all(isinstance(item, str) and item for item in values) + ): + errors.append(f"subjects[{index}].{list_field} must be a list of non-empty strings") + + changed_file_count = subject.get("changed_file_count") + if not isinstance(changed_file_count, int) or changed_file_count < 0: + errors.append(f"subjects[{index}].changed_file_count must be a non-negative integer") + + counts = entry.get("counts") + if not isinstance(counts, dict): + errors.append("counts must be an object") + else: + queued = counts.get("subjects_queued") + if not isinstance(queued, int) or queued != len(subjects): + errors.append("counts.subjects_queued must equal len(subjects)") + for field in ("recent_commits_scanned", "published_subjects_seen", "critical", "high", "normal", "low"): + value = counts.get(field) + if not isinstance(value, int) or value < 0: + errors.append(f"counts.{field} must be a non-negative integer") + + return ValidationResult(ok=not errors, errors=errors) + + +def validate_upstream_review(entry: dict[str, Any]) -> ValidationResult: + errors: list[str] = [] + + if entry.get("schema") != UPSTREAM_REVIEW_SCHEMA: + errors.append(f"schema must be {UPSTREAM_REVIEW_SCHEMA}") + + for field in ("slug", "repo", "reviewed_at", "observed_change"): + if not isinstance(entry.get(field), str) or not entry[field]: + errors.append(f"{field} must be a non-empty string") + repo = entry.get("repo") + if isinstance(repo, str) and "/" not in repo: + errors.append("repo must be owner/name") + + subject = entry.get("subject") + if not isinstance(subject, dict): + errors.append("subject must be an object") + else: + if subject.get("subject_kind") not in UPSTREAM_SUBJECT_KINDS: + errors.append(f"subject.subject_kind must be one of {sorted(UPSTREAM_SUBJECT_KINDS)}") + if not isinstance(subject.get("subject_id"), str) or not subject["subject_id"]: + errors.append("subject.subject_id must be a non-empty string") + commit_shas = subject.get("commit_shas") + if commit_shas is not None and ( + not isinstance(commit_shas, list) + or not all(isinstance(item, str) and item for item in commit_shas) + ): + errors.append("subject.commit_shas must be a list of non-empty strings when present") + + refs = entry.get("source_refs") + if not isinstance(refs, dict): + errors.append("source_refs must be an object") + else: + items = refs.get("items") + if ( + not isinstance(items, list) + or not items + or not all( + isinstance(item, dict) + and isinstance(item.get("kind"), str) + and isinstance(item.get("title"), str) + and item["title"] + and isinstance(item.get("url"), str) + and item["url"].startswith("https://") + for item in items + ) + ): + errors.append("source_refs.items must be a non-empty list of titled https source entries") + + for list_field in ("changed_surfaces", "evidence"): + values = entry.get(list_field) + if ( + not isinstance(values, list) + or not values + or not all(isinstance(item, str) and item for item in values) + ): + errors.append(f"{list_field} must be a non-empty list of strings") + + for optional_field in ( + "user_visible_path", + "control_plane_relevance", + "compatibility_risk", + "adoption_opportunity", + "community_value", + "deprecated_or_breaking_notes", + "caveats", + ): + value = entry.get(optional_field) + if value is not None and not isinstance(value, str): + errors.append(f"{optional_field} must be a string when present") + + if entry.get("confidence") not in SIGNAL_CONFIDENCE: + errors.append(f"confidence must be one of {sorted(SIGNAL_CONFIDENCE)}") + + next_actions = entry.get("next_actions") + if not isinstance(next_actions, list) or not next_actions: + errors.append("next_actions must be a non-empty list") + else: + for index, action in enumerate(next_actions): + if not isinstance(action, dict): + errors.append(f"next_actions[{index}] must be an object") + continue + if action.get("type") not in UPSTREAM_REVIEW_ACTION_TYPES: + errors.append(f"next_actions[{index}].type must be one of {sorted(UPSTREAM_REVIEW_ACTION_TYPES)}") + if not isinstance(action.get("reason"), str) or not action["reason"]: + errors.append(f"next_actions[{index}].reason must be a non-empty string") + + return ValidationResult(ok=not errors, errors=errors) + + +def validate_social_post(entry: dict[str, Any]) -> ValidationResult: + errors: list[str] = [] + + if entry.get("schema") != SOCIAL_POST_SCHEMA: + errors.append(f"schema must be {SOCIAL_POST_SCHEMA}") + + for field in ("slug", "audience"): + if not isinstance(entry.get(field), str) or not entry[field]: + errors.append(f"{field} must be a non-empty string") + + if entry.get("channel") != "x": + errors.append("channel must be x") + if entry.get("target_account") != "decodexspace": + errors.append("target_account must be decodexspace") + if entry.get("controller_account") != "hackink": + errors.append("controller_account must be hackink") + if entry.get("mode") not in SOCIAL_POST_MODES: + errors.append(f"mode must be one of {sorted(SOCIAL_POST_MODES)}") + if entry.get("status") not in SOCIAL_POST_STATUSES: + errors.append(f"status must be one of {sorted(SOCIAL_POST_STATUSES)}") + + text = entry.get("text") + if ( + not isinstance(text, list) + or not text + or not all(isinstance(item, str) and 0 < len(item) <= 280 for item in text) + ): + errors.append("text must be a non-empty list of X-sized strings") + + refs = entry.get("source_refs") + if not isinstance(refs, dict): + errors.append("source_refs must be an object") + else: + present = [ + name + for name in ("signals", "upstream_impacts", "upstream_reviews", "urls") + if isinstance(refs.get(name), list) and refs[name] + ] + if not present: + errors.append("source_refs must include signals, upstream_impacts, upstream_reviews, or urls") + urls = refs.get("urls", []) + if urls and ( + not isinstance(urls, list) + or not all(isinstance(url, str) and url.startswith("https://") for url in urls) + ): + errors.append("source_refs.urls must be a list of https URLs") + + for list_field in ("evidence_notes", "claims"): + values = entry.get(list_field) + if not isinstance(values, list) or not values: + errors.append(f"{list_field} must be a non-empty list") + + claims = entry.get("claims") + if isinstance(claims, list): + for index, claim in enumerate(claims): + if not isinstance(claim, dict): + errors.append(f"claims[{index}] must be an object") + continue + for field in ("text", "evidence"): + if not isinstance(claim.get(field), str) or not claim[field]: + errors.append(f"claims[{index}].{field} must be a non-empty string") + if claim.get("confidence") not in SIGNAL_CONFIDENCE: + errors.append(f"claims[{index}].confidence must be one of {sorted(SIGNAL_CONFIDENCE)}") + + decision = entry.get("decision") + if not isinstance(decision, dict): + errors.append("decision must be an object") + else: + if decision.get("worthiness") not in SOCIAL_POST_WORTHINESS: + errors.append(f"decision.worthiness must be one of {sorted(SOCIAL_POST_WORTHINESS)}") + if decision.get("priority") not in SOCIAL_POST_PRIORITIES: + errors.append(f"decision.priority must be one of {sorted(SOCIAL_POST_PRIORITIES)}") + for field in ("idempotency_key", "reason", "day", "timezone"): + if not isinstance(decision.get(field), str) or not decision[field]: + errors.append(f"decision.{field} must be a non-empty string") + if decision.get("daily_limit") != 8: + errors.append("decision.daily_limit must be 8") + for field in ("daily_count_before", "daily_count_after"): + value = decision.get(field) + if not isinstance(value, int) or value < 0: + errors.append(f"decision.{field} must be a non-negative integer") + before = decision.get("daily_count_before") + after = decision.get("daily_count_after") + status = entry.get("status") + post_count = len(text) if isinstance(text, list) else 0 + if isinstance(before, int) and isinstance(after, int): + if status == "published" and after != before + post_count: + errors.append("decision.daily_count_after must add the published post count") + if status != "published" and after != before: + errors.append("decision.daily_count_after must remain unchanged unless published") + + status = entry.get("status") + if status == "published": + publication = entry.get("publication") + if not isinstance(publication, dict): + errors.append("publication is required when status is published") + else: + if publication.get("publisher") not in {"chrome", "x_api"}: + errors.append("publication.publisher must be chrome or x_api") + if publication.get("account_verified") is not True: + errors.append("publication.account_verified must be true") + if not isinstance(publication.get("made_with_ai"), bool): + errors.append("publication.made_with_ai must be boolean") + if "image_template" in publication and publication.get("image_template") != "decodex_signal_card": + errors.append("publication.image_template must be decodex_signal_card when present") + urls = publication.get("published_urls") + if ( + not isinstance(urls, list) + or not urls + or not all(isinstance(url, str) and url.startswith("https://") for url in urls) + ): + errors.append("publication.published_urls must be a non-empty list of https URLs") + if not isinstance(publication.get("posted_at"), str) or not publication["posted_at"]: + errors.append("publication.posted_at must be a non-empty string") + elif status == "blocked": + block = entry.get("block") + if not isinstance(block, dict): + errors.append("block is required when status is blocked") + else: + if block.get("reason") not in SOCIAL_BLOCK_REASONS: + errors.append(f"block.reason must be one of {sorted(SOCIAL_BLOCK_REASONS)}") + count_before = decision.get("daily_count_before") if isinstance(decision, dict) else None + if block.get("reason") == "daily_cap_exceeded" and ( + not isinstance(count_before, int) or count_before < 8 + ): + errors.append("daily_cap_exceeded requires decision.daily_count_before >= 8") + if not isinstance(block.get("operator_notice"), str) or not block["operator_notice"]: + errors.append("block.operator_notice must be a non-empty string") + elif status == "failed": + failure = entry.get("failure") + if not isinstance(failure, dict): + errors.append("failure is required when status is failed") + elif status == "skipped" and not isinstance(entry.get("skip"), dict): + errors.append("skip is required when status is skipped") + + for list_field in ("caveats", "media_refs"): + values = entry.get(list_field, []) + if values is not None and ( + not isinstance(values, list) + or not all(isinstance(item, str) and item for item in values) + ): + errors.append(f"{list_field} must be a list of non-empty strings when present") + + return ValidationResult(ok=not errors, errors=errors) diff --git a/scripts/github/radar_ledger.py b/scripts/github/radar_ledger.py index f6c032d1..376af637 100644 --- a/scripts/github/radar_ledger.py +++ b/scripts/github/radar_ledger.py @@ -18,7 +18,7 @@ from contracts import load_json, utc_now_iso, validate_bundle, validate_signal # noqa: E402 -SCHEMA_VERSION = 1 +SCHEMA_VERSION = 2 DEFAULT_LEDGER_PATH = ".decodex/radar.sqlite3" COMMIT_URL_RE = re.compile(r"/commit/([0-9a-f]{7,40})$") PR_URL_RE = re.compile(r"/pull/(\d+)$") @@ -39,7 +39,7 @@ "analysis", "signal", "upstream_impact", - "social_draft", + "social_post", "release_delta", "archive_manifest", "ledger_export", @@ -136,7 +136,7 @@ def initialize(connection: sqlite3.Connection) -> None: 'analysis', 'signal', 'upstream_impact', - 'social_draft', + 'social_post', 'release_delta', 'archive_manifest', 'ledger_export' @@ -164,6 +164,7 @@ def initialize(connection: sqlite3.Connection) -> None: ON radar_review (status, reviewed_at); """ ) + migrate_artifact_link_social_kind(connection) connection.execute( """ INSERT INTO metadata (key, value) @@ -175,6 +176,73 @@ def initialize(connection: sqlite3.Connection) -> None: connection.commit() +def migrate_artifact_link_social_kind(connection: sqlite3.Connection) -> None: + row = connection.execute( + """ + SELECT sql + FROM sqlite_master + WHERE type = 'table' AND name = 'artifact_link' + """ + ).fetchone() + if not row or "social_draft" not in row["sql"]: + return + + connection.executescript( + """ + ALTER TABLE artifact_link RENAME TO artifact_link_old; + + CREATE TABLE artifact_link ( + repo TEXT NOT NULL, + subject_kind TEXT NOT NULL CHECK (subject_kind IN ('commit', 'pr')), + subject_id TEXT NOT NULL, + artifact_kind TEXT NOT NULL CHECK ( + artifact_kind IN ( + 'bundle', + 'analysis', + 'signal', + 'upstream_impact', + 'social_post', + 'release_delta', + 'archive_manifest', + 'ledger_export' + ) + ), + path TEXT NOT NULL, + sha256 TEXT NOT NULL, + size_bytes INTEGER NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (repo, subject_kind, subject_id, artifact_kind, path) + ); + + INSERT OR REPLACE INTO artifact_link ( + repo, + subject_kind, + subject_id, + artifact_kind, + path, + sha256, + size_bytes, + created_at + ) + SELECT + repo, + subject_kind, + subject_id, + CASE artifact_kind + WHEN 'social_draft' THEN 'social_post' + ELSE artifact_kind + END, + path, + sha256, + size_bytes, + created_at + FROM artifact_link_old; + + DROP TABLE artifact_link_old; + """ + ) + + def path_for_storage(path: str | Path) -> str: resolved = Path(path).resolve() cwd = Path.cwd().resolve() diff --git a/scripts/github/social_post.schema.json b/scripts/github/social_post.schema.json new file mode 100644 index 00000000..e2bf41f5 --- /dev/null +++ b/scripts/github/social_post.schema.json @@ -0,0 +1,345 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Decodex social post publication record", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "slug", + "channel", + "target_account", + "controller_account", + "mode", + "status", + "audience", + "text", + "source_refs", + "evidence_notes", + "claims", + "decision" + ], + "properties": { + "schema": { + "const": "social_post/v1" + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "channel": { + "const": "x" + }, + "target_account": { + "const": "decodexspace" + }, + "controller_account": { + "const": "hackink" + }, + "mode": { + "type": "string", + "enum": [ + "release_pulse", + "release_rollup", + "practical_explainer", + "operator_impact", + "thread", + "watch_note" + ] + }, + "status": { + "type": "string", + "enum": ["published", "blocked", "failed", "skipped"] + }, + "audience": { + "type": "string", + "minLength": 1 + }, + "text": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 280 + } + }, + "source_refs": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": ["signals"] + }, + { + "required": ["upstream_impacts"] + }, + { + "required": ["upstream_reviews"] + }, + { + "required": ["urls"] + } + ], + "properties": { + "signals": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "upstream_impacts": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "upstream_reviews": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "urls": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^https://" + } + } + } + }, + "evidence_notes": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "claims": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["text", "evidence", "confidence"], + "properties": { + "text": { + "type": "string", + "minLength": 1 + }, + "evidence": { + "type": "string", + "minLength": 1 + }, + "confidence": { + "type": "string", + "enum": ["confirmed", "likely", "weak"] + } + } + } + }, + "decision": { + "type": "object", + "additionalProperties": false, + "required": [ + "worthiness", + "priority", + "idempotency_key", + "reason", + "daily_limit", + "daily_count_before", + "daily_count_after", + "day", + "timezone" + ], + "properties": { + "worthiness": { + "type": "string", + "enum": ["publish", "skip", "block"] + }, + "priority": { + "type": "string", + "enum": ["critical", "high", "normal", "low"] + }, + "idempotency_key": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "daily_limit": { + "const": 8 + }, + "daily_count_before": { + "type": "integer", + "minimum": 0 + }, + "daily_count_after": { + "type": "integer", + "minimum": 0 + }, + "day": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "timezone": { + "type": "string", + "minLength": 1 + } + } + }, + "publication": { + "type": "object", + "additionalProperties": false, + "required": [ + "posted_at", + "published_urls", + "publisher", + "account_verified", + "made_with_ai" + ], + "properties": { + "posted_at": { + "type": "string", + "minLength": 1 + }, + "published_urls": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^https://" + } + }, + "publisher": { + "type": "string", + "enum": ["chrome", "x_api"] + }, + "account_verified": { + "const": true + }, + "made_with_ai": { + "type": "boolean" + }, + "image_template": { + "const": "decodex_signal_card" + } + } + }, + "block": { + "type": "object", + "additionalProperties": false, + "required": ["reason", "operator_notice"], + "properties": { + "reason": { + "type": "string", + "enum": ["daily_cap_exceeded", "duplicate", "policy_block", "insufficient_evidence"] + }, + "operator_notice": { + "type": "string", + "minLength": 1 + } + } + }, + "failure": { + "type": "object", + "additionalProperties": false, + "required": ["reason", "details"], + "properties": { + "reason": { + "type": "string", + "minLength": 1 + }, + "details": { + "type": "string", + "minLength": 1 + } + } + }, + "skip": { + "type": "object", + "additionalProperties": false, + "required": ["reason"], + "properties": { + "reason": { + "type": "string", + "minLength": 1 + } + } + }, + "caveats": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "media_refs": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "published" + } + } + }, + "then": { + "required": ["publication"] + } + }, + { + "if": { + "properties": { + "status": { + "const": "blocked" + } + } + }, + "then": { + "required": ["block"] + } + }, + { + "if": { + "properties": { + "status": { + "const": "failed" + } + } + }, + "then": { + "required": ["failure"] + } + }, + { + "if": { + "properties": { + "status": { + "const": "skipped" + } + } + }, + "then": { + "required": ["skip"] + } + } + ] +} diff --git a/scripts/github/social_post_draft.schema.json b/scripts/github/social_post_draft.schema.json deleted file mode 100644 index 0ec4aee9..00000000 --- a/scripts/github/social_post_draft.schema.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Decodex social post draft", - "type": "object", - "additionalProperties": false, - "required": [ - "schema", - "slug", - "channel", - "target_account", - "mode", - "status", - "audience", - "text", - "source_refs", - "evidence_notes", - "claims" - ], - "properties": { - "schema": { - "const": "social_post_draft/v1" - }, - "slug": { - "type": "string", - "minLength": 1 - }, - "channel": { - "const": "x" - }, - "target_account": { - "type": "string", - "pattern": "^[A-Za-z0-9_]+$", - "minLength": 1 - }, - "mode": { - "type": "string", - "enum": [ - "release_pulse", - "release_rollup", - "practical_explainer", - "operator_impact", - "thread", - "watch_note" - ] - }, - "status": { - "type": "string", - "enum": ["draft", "approved", "published", "rejected"] - }, - "audience": { - "type": "string", - "minLength": 1 - }, - "text": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 280 - } - }, - "source_refs": { - "type": "object", - "additionalProperties": false, - "anyOf": [ - { - "required": ["signals"] - }, - { - "required": ["upstream_impacts"] - }, - { - "required": ["urls"] - } - ], - "properties": { - "signals": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "upstream_impacts": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "urls": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^https://" - } - } - } - }, - "evidence_notes": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "claims": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["text", "evidence", "confidence"], - "properties": { - "text": { - "type": "string", - "minLength": 1 - }, - "evidence": { - "type": "string", - "minLength": 1 - }, - "confidence": { - "type": "string", - "enum": ["confirmed", "likely", "weak"] - } - } - } - }, - "published_url": { - "type": "string", - "pattern": "^https://" - }, - "approval": { - "type": "object", - "additionalProperties": false, - "required": ["reviewed_by", "reviewed_at", "notes"], - "properties": { - "reviewed_by": { - "type": "string", - "minLength": 1 - }, - "reviewed_at": { - "type": "string", - "minLength": 1 - }, - "notes": { - "type": "string", - "minLength": 1 - } - } - }, - "caveats": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "media_refs": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - } - }, - "allOf": [ - { - "if": { - "properties": { - "status": { - "const": "published" - } - } - }, - "then": { - "required": ["published_url", "approval"] - } - }, - { - "if": { - "properties": { - "status": { - "enum": ["approved", "published"] - } - } - }, - "then": { - "required": ["approval"] - } - }, - { - "if": { - "properties": { - "status": { - "const": "rejected" - } - } - }, - "then": { - "anyOf": [ - { - "required": ["approval"] - }, - { - "required": ["caveats"] - } - ] - } - } - ] -} diff --git a/scripts/github/sync_latest_signals.py b/scripts/github/sync_latest_signals.py deleted file mode 100644 index e1a7738a..00000000 --- a/scripts/github/sync_latest_signals.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python3 -"""Discover recent upstream commits, generate Decodex signals, and refresh release deltas.""" - -from __future__ import annotations - -import argparse -import json -import os -import re -import subprocess -import sys -import urllib.parse -from pathlib import Path -from typing import Any - -SCRIPT_HOME = Path(__file__).resolve().parent -if str(SCRIPT_HOME) not in sys.path: - sys.path.insert(0, str(SCRIPT_HOME)) - -from build_change_bundle import ( # noqa: E402 - build_commit_bundle, - build_pr_bundle, - github_request, - maybe_promote_commit_to_pr, - repo_default_branch, - routed_token_env, -) -from contracts import dump_json, load_json, validate_signal # noqa: E402 -from radar_ledger import ( # noqa: E402 - DEFAULT_LEDGER_PATH, - connect as connect_ledger, - ingest_artifact_set, - record_commit, - record_review, -) - -COMMIT_URL_RE = re.compile(r"/commit/([0-9a-f]{7,40})$") -PR_URL_RE = re.compile(r"/pull/(\d+)$") -POSITIVE_TITLE_TERMS = ( - "feat", - "feature", - "add", - "support", - "change", - "plugin", - "image", - "agent", - "tui", - "config", - "flag", - "preview", - "history", - "sync", - "menu", - "startup", - "remote", - "save", - "behavior", - "command", - "model", -) -NEGATIVE_TITLE_TERMS = ( - "bump ", - "deps", - "dependency", - "bazel", - "ci", - "lint", - "typo", - "docs", - "refactor", - "cleanup", - "chore", - "test ", - "tests ", -) - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo", default="openai/codex", help="GitHub repository in owner/name format.") - parser.add_argument("--signals-dir", default="site/src/content/signals", help="Published signal directory.") - parser.add_argument("--bundles-dir", default="artifacts/github/bundles", help="Bundle directory.") - parser.add_argument("--analysis-dir", default="artifacts/github/analysis", help="Analysis draft directory.") - parser.add_argument( - "--release-delta-out", - default="site/src/content/release-deltas/openai-codex-latest.json", - help="Path to write the release delta artifact.", - ) - parser.add_argument("--search-limit", type=int, default=20, help="How many recent commits to inspect.") - parser.add_argument("--max-new-prs", type=int, default=3, help="Maximum unpublished changes to publish per run.") - parser.add_argument("--token-env", help="Environment variable containing a GitHub token.") - parser.add_argument("--codex-bin", default="codex", help="Codex executable to invoke.") - parser.add_argument("--model", help="Optional Codex model override.") - parser.add_argument( - "--ledger", - default=DEFAULT_LEDGER_PATH, - help="Local SQLite Radar ledger path. Defaults to .decodex/radar.sqlite3.", - ) - parser.add_argument("--no-ledger", action="store_true", help="Disable local Radar ledger writes.") - parser.add_argument( - "--refresh-release-delta", - action="store_true", - help="Refresh the release-delta artifact even when no new signal entry was created.", - ) - return parser.parse_args() - - -def repo_root() -> Path: - return SCRIPT_HOME.parents[1] - - -def published_pr_numbers(signals_dir: Path) -> set[int]: - published: set[int] = set() - for path in sorted(signals_dir.glob("*.json")): - payload = load_json(path) - validation = validate_signal(payload) - if not validation.ok: - raise SystemExit(f"Signal validation failed for {path}:\n- " + "\n- ".join(validation.errors)) - pr_url = payload.get("source_refs", {}).get("pr_url") - if not isinstance(pr_url, str): - continue - match = PR_URL_RE.search(pr_url) - if match: - published.add(int(match.group(1))) - return published - - -def published_commit_shas(signals_dir: Path) -> set[str]: - published: set[str] = set() - for path in sorted(signals_dir.glob("*.json")): - payload = load_json(path) - validation = validate_signal(payload) - if not validation.ok: - raise SystemExit(f"Signal validation failed for {path}:\n- " + "\n- ".join(validation.errors)) - for url in payload.get("source_refs", {}).get("commit_urls", []): - if not isinstance(url, str): - continue - match = COMMIT_URL_RE.search(url) - if match: - published.add(match.group(1)) - return published - - -def recent_commits(repo: str, token: str | None, search_limit: int) -> list[dict[str, Any]]: - default_branch = repo_default_branch(repo, token) - payload, _ = github_request( - f"https://api.github.com/repos/{repo}/commits?sha={urllib.parse.quote(default_branch)}&per_page={search_limit}", - token, - ) - if not isinstance(payload, list): - raise SystemExit("Expected commits list payload from GitHub API") - results: list[dict[str, Any]] = [] - for item in payload: - sha = item.get("sha") - commit = item.get("commit") - url = item.get("html_url") - if not isinstance(sha, str) or not isinstance(commit, dict) or not isinstance(url, str): - continue - message = commit.get("message") - if not isinstance(message, str) or not message: - continue - results.append( - { - "sha": sha, - "title": message.strip().splitlines()[0], - "url": url, - "committed_at": (commit.get("committer") or {}).get("date"), - } - ) - return results - - -def candidate_score(title: str) -> int: - lowered = title.lower() - score = 0 - for term in POSITIVE_TITLE_TERMS: - if term in lowered: - score += 2 - for term in NEGATIVE_TITLE_TERMS: - if term in lowered: - score -= 3 - return score - - -def signal_paths(candidate: dict[str, Any], args: argparse.Namespace) -> tuple[Path, Path, Path]: - pr_number = candidate.get("pr_number") - stem = ( - f"openai-codex-pr-{pr_number}" - if isinstance(pr_number, int) - else f"openai-codex-commit-{candidate['sha'][:12]}" - ) - bundles_dir = Path(args.bundles_dir) - analysis_dir = Path(args.analysis_dir) - signals_dir = Path(args.signals_dir) - return ( - bundles_dir / f"{stem}.json", - analysis_dir / f"{stem}.analysis.json", - signals_dir / f"{stem}.json", - ) - - -def run_script(script: str, *extra: str) -> None: - cmd = [sys.executable, str(SCRIPT_HOME / script), *extra] - completed = subprocess.run(cmd, check=False, text=True, capture_output=True, cwd=repo_root()) - if completed.returncode != 0: - stderr = completed.stderr.strip() - stdout = completed.stdout.strip() - details = stderr or stdout or "unknown error" - raise SystemExit(f"{script} failed: {details}") - - -def refresh_release_delta(args: argparse.Namespace) -> None: - run_script( - "build_release_delta.py", - "--repo", - args.repo, - "--signals-dir", - args.signals_dir, - "--out", - args.release_delta_out, - ) - - -def main() -> None: - args = parse_args() - token_env = args.token_env or routed_token_env() or "GITHUB_TOKEN" - token = os.environ.get(token_env) - root = repo_root() - ledger_path = None if args.no_ledger else Path(args.ledger) - if ledger_path is not None and not ledger_path.is_absolute(): - ledger_path = root / ledger_path - ledger = connect_ledger(ledger_path) if ledger_path is not None else None - signals_dir = (root / args.signals_dir).resolve() - published_prs = published_pr_numbers(signals_dir) - published_shas = published_commit_shas(signals_dir) - commits = recent_commits(args.repo, token, args.search_limit) - candidates: list[dict[str, Any]] = [] - seen_candidate_keys: set[tuple[str, int | str]] = set() - try: - for commit in commits: - pr_number = maybe_promote_commit_to_pr(args.repo, commit["sha"], token) - subject_kind = "pr" if pr_number is not None else "commit" - subject_id = str(pr_number) if pr_number is not None else commit["sha"] - if ledger is not None: - record_commit( - ledger, - repo=args.repo, - sha=commit["sha"], - title=commit["title"], - url=commit["url"], - committed_at=commit.get("committed_at"), - pr_number=pr_number, - ) - if commit["sha"] in published_shas or (pr_number is not None and pr_number in published_prs): - if ledger is not None: - record_review( - ledger, - repo=args.repo, - subject_kind=subject_kind, - subject_id=subject_id, - status="signal", - reason="Already present in published signal collection.", - confidence="confirmed", - ) - continue - candidate_key: tuple[str, int | str] = ( - ("pr", pr_number) if pr_number is not None else ("commit", commit["sha"]) - ) - if candidate_key in seen_candidate_keys: - if ledger is not None: - record_review( - ledger, - repo=args.repo, - subject_kind=subject_kind, - subject_id=subject_id, - status="seen", - reason="Duplicate recent commit for an already considered PR.", - ) - continue - seen_candidate_keys.add(candidate_key) - score = candidate_score(commit["title"]) - if score <= 0: - if ledger is not None: - record_review( - ledger, - repo=args.repo, - subject_kind=subject_kind, - subject_id=subject_id, - status="skipped", - reason=f"Recent commit title scored {score}; no public signal was generated.", - confidence="likely", - ) - continue - candidates.append({**commit, "pr_number": pr_number, "score": score}) - - unpublished = candidates[: args.max_new_prs] - for candidate in candidates[args.max_new_prs :]: - if ledger is None: - continue - pr_number = candidate.get("pr_number") - subject_kind = "pr" if isinstance(pr_number, int) else "commit" - subject_id = str(pr_number) if isinstance(pr_number, int) else candidate["sha"] - record_review( - ledger, - repo=args.repo, - subject_kind=subject_kind, - subject_id=subject_id, - status="watch", - reason="Positive Radar candidate left for a later sync budget.", - confidence="likely", - ) - - created = 0 - for candidate in reversed(unpublished): - bundle_path, analysis_path, signal_path = signal_paths(candidate, args) - notes = [f"Discovered via continuous upstream commit sync: {candidate['url']}"] - pr_number = candidate.get("pr_number") - bundle = ( - build_pr_bundle(args.repo, pr_number, token, notes) - if isinstance(pr_number, int) - else build_commit_bundle(args.repo, candidate["sha"], token, notes) - ) - dump_json(root / bundle_path, bundle) - - run_script( - "run_codex_analysis.py", - "--bundle", - str(root / bundle_path), - "--out", - str(root / analysis_path), - "--repo-root", - str(root), - "--codex-bin", - args.codex_bin, - *(["--model", args.model] if args.model else []), - ) - - run_script( - "render_signal_entry.py", - "--bundle", - str(root / bundle_path), - "--analysis", - str(root / analysis_path), - "--out", - str(root / signal_path), - ) - if ledger is not None: - ingest_artifact_set( - ledger, - bundle_path=root / bundle_path, - analysis_path=root / analysis_path, - signal_path=root / signal_path, - ) - created += 1 - if ledger is not None: - ledger.commit() - finally: - if ledger is not None: - ledger.close() - - run_script("validate_signal_entry.py", str(root / args.signals_dir)) - release_delta_refreshed = ( - created > 0 or args.refresh_release_delta or not (root / args.release_delta_out).exists() - ) - if release_delta_refreshed: - refresh_release_delta(args) - print( - json.dumps( - { - "repo": args.repo, - "published_prs_seen": len(published_prs), - "published_commits_seen": len(published_shas), - "recent_commits_scanned": len(commits), - "unpublished_changes_considered": len(candidates), - "new_signals_created": created, - "release_delta_refreshed": release_delta_refreshed, - "ledger": str(ledger_path) if ledger_path is not None else None, - }, - sort_keys=True, - ) - ) - - -if __name__ == "__main__": - main() diff --git a/scripts/github/sync_upstream_radar.py b/scripts/github/sync_upstream_radar.py new file mode 100644 index 00000000..c1f1c098 --- /dev/null +++ b/scripts/github/sync_upstream_radar.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +"""Sync the deterministic Codex upstream review queue for Decodex Radar.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import urllib.parse +from pathlib import Path +from typing import Any + +from build_change_bundle import ( + build_commit_bundle, + build_pr_bundle, + github_request, + maybe_promote_commit_to_pr, + repo_default_branch, + routed_token_env, +) +from contracts import ( + dump_json, + load_json, + utc_now_iso, + validate_signal, + validate_upstream_review_queue, +) +from radar_ledger import DEFAULT_LEDGER_PATH, connect as connect_ledger, record_commit, record_review + +SCRIPT_HOME = Path(__file__).resolve().parent +COMMIT_URL_RE = re.compile(r"/commit/([0-9a-f]{7,40})$") +PR_URL_RE = re.compile(r"/pull/(\d+)$") + +SURFACE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("app_server_protocol", ("app-server", "app_server", "protocol", "jsonrpc", "json-rpc")), + ("mcp_plugins", ("mcp", "plugin", "tool-search", "tool_search")), + ("browser_chrome", ("browser", "chrome", "webview")), + ("sandbox_permissions", ("sandbox", "permission", "approval", "policy", "denylist", "allowlist")), + ("config_hooks", ("config", "hook", "settings", "toml")), + ("auth_accounts", ("auth", "account", "login", "token")), + ("model_provider", ("model", "provider", "rate-limit", "ratelimit", "quota")), + ("cli_tui", ("cli", "tui", "terminal", "chatwidget")), + ("release_packaging", ("release", "appcast", "sparkle", "version", "install", "package")), + ("docs_examples", ("docs/", "readme", "example")), + ("tests_ci", ("test", "tests", ".github", "ci", "fixture")), +) + +ATTENTION_RULES: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("new_feature", ("feat", "feature", "add ", "adds ", "support", "enable", "implement", "introduce")), + ("deprecated_removed", ("deprecat", "remove", "removed", "delete", "disable", "no longer")), + ("protocol_change", ("protocol", "schema", "api", "json-rpc", "jsonrpc", "notification", "request", "response")), + ("breaking_change", ("breaking", "break ", "rename", "migration", "incompat", "no longer")), + ("security_policy", ("sandbox", "permission", "approval", "full access", "network", "denylist", "allowlist")), + ("rate_limit", ("rate limit", "ratelimit", "quota", "usage limit", "message cap")), + ("auth_account", ("auth", "account", "login", "token")), + ("release_packaging", ("release", "appcast", "sparkle", "beta", "version")), +) + +HIGH_VALUE_SURFACES = { + "app_server_protocol", + "mcp_plugins", + "browser_chrome", + "sandbox_permissions", + "config_hooks", + "auth_accounts", + "model_provider", +} +HIGH_VALUE_FLAGS = { + "deprecated_removed", + "protocol_change", + "breaking_change", + "security_policy", + "rate_limit", + "auth_account", +} + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", default="openai/codex", help="GitHub repository in owner/name format.") + parser.add_argument("--search-limit", type=int, default=40, help="How many recent upstream commits to inspect.") + parser.add_argument("--signals-dir", default="site/src/content/signals", help="Published signal directory.") + parser.add_argument( + "--queue-out", + default="artifacts/github/review-queue/openai-codex-latest.json", + help="Path to write the deterministic upstream_review_queue/v1 artifact.", + ) + parser.add_argument("--token-env", help="Environment variable containing a GitHub token.") + parser.add_argument( + "--ledger", + default=DEFAULT_LEDGER_PATH, + help="Local SQLite Radar ledger path. Defaults to .decodex/radar.sqlite3.", + ) + parser.add_argument("--no-ledger", action="store_true", help="Disable local Radar ledger writes.") + parser.add_argument("--dry-run", action="store_true", help="Print the queue without writing queue-out.") + return parser.parse_args() + + +def repo_root() -> Path: + return SCRIPT_HOME.parents[1] + + +def published_subjects(signals_dir: Path) -> tuple[set[int], set[str]]: + published_prs: set[int] = set() + published_shas: set[str] = set() + for path in sorted(signals_dir.glob("*.json")): + payload = load_json(path) + validation = validate_signal(payload) + if not validation.ok: + raise SystemExit(f"Signal validation failed for {path}:\n- " + "\n- ".join(validation.errors)) + pr_url = payload.get("source_refs", {}).get("pr_url") + if isinstance(pr_url, str): + match = PR_URL_RE.search(pr_url) + if match: + published_prs.add(int(match.group(1))) + for url in payload.get("source_refs", {}).get("commit_urls", []): + if not isinstance(url, str): + continue + match = COMMIT_URL_RE.search(url) + if match: + published_shas.add(match.group(1)) + return published_prs, published_shas + + +def recent_commits(repo: str, token: str | None, search_limit: int) -> tuple[str, list[dict[str, Any]]]: + default_branch = repo_default_branch(repo, token) + payload, _ = github_request( + f"https://api.github.com/repos/{repo}/commits?sha={urllib.parse.quote(default_branch)}&per_page={search_limit}", + token, + ) + if not isinstance(payload, list): + raise SystemExit("Expected commits list payload from GitHub API") + results: list[dict[str, Any]] = [] + for item in payload: + sha = item.get("sha") + commit = item.get("commit") + url = item.get("html_url") + if not isinstance(sha, str) or not isinstance(commit, dict) or not isinstance(url, str): + continue + message = commit.get("message") + if not isinstance(message, str) or not message: + continue + results.append( + { + "sha": sha, + "title": message.strip().splitlines()[0], + "url": url, + "committed_at": (commit.get("committer") or {}).get("date"), + } + ) + return default_branch, results + + +def text_blob(bundle: dict[str, Any]) -> str: + parts: list[str] = [] + primary_pr = bundle.get("primary_pr") + if isinstance(primary_pr, dict): + parts.extend([str(primary_pr.get("title") or ""), str(primary_pr.get("body") or "")]) + for commit in bundle.get("commits", []): + if isinstance(commit, dict): + parts.append(str(commit.get("message") or "")) + for item in bundle.get("files", []): + if isinstance(item, dict): + parts.extend([str(item.get("path") or ""), str(item.get("patch_excerpt") or "")]) + return "\n".join(parts).lower() + + +def detect_surface_hints(bundle: dict[str, Any]) -> list[str]: + paths = [str(item.get("path") or "").lower() for item in bundle.get("files", []) if isinstance(item, dict)] + haystack = "\n".join(paths) + hints = { + surface + for surface, terms in SURFACE_RULES + if any(term in haystack for term in terms) + } + if not hints: + hints.add("internal_churn") + return sorted(hints) + + +def detect_attention_flags(bundle: dict[str, Any]) -> list[str]: + haystack = text_blob(bundle) + return sorted( + flag + for flag, terms in ATTENTION_RULES + if any(term in haystack for term in terms) + ) + + +def priority_for(surface_hints: list[str], attention_flags: list[str]) -> str: + surfaces = set(surface_hints) + flags = set(attention_flags) + if (flags & {"breaking_change", "deprecated_removed"}) and (surfaces & HIGH_VALUE_SURFACES): + return "critical" + if (surfaces & HIGH_VALUE_SURFACES) and (flags & HIGH_VALUE_FLAGS): + return "high" + if surfaces & HIGH_VALUE_SURFACES: + return "high" + if flags & {"new_feature", "protocol_change", "release_packaging"}: + return "normal" + return "low" + + +def review_reason(surface_hints: list[str], attention_flags: list[str]) -> str: + if "internal_churn" in surface_hints and not attention_flags: + return "Needs AI review because every recent upstream commit is tracked, but deterministic hints found only internal churn." + if attention_flags: + return "Needs AI review for " + ", ".join(attention_flags) + "." + return "Needs AI review for surface hints: " + ", ".join(surface_hints) + "." + + +def subject_from_bundle( + *, + bundle: dict[str, Any], + subject_kind: str, + subject_id: str, + seed_commit: dict[str, Any], +) -> dict[str, Any]: + primary_pr = bundle.get("primary_pr") + commits = [item for item in bundle.get("commits", []) if isinstance(item, dict)] + files = [item for item in bundle.get("files", []) if isinstance(item, dict)] + commit_shas = [str(item["sha"]) for item in commits if isinstance(item.get("sha"), str)] + surface_hints = detect_surface_hints(bundle) + attention_flags = detect_attention_flags(bundle) + title = seed_commit["title"] + url = seed_commit["url"] + source_state = "commit_only" + subject: dict[str, Any] = { + "subject_kind": subject_kind, + "subject_id": subject_id, + "title": title, + "url": url, + "source_state": source_state, + "commit_shas": commit_shas or [seed_commit["sha"]], + "committed_at": seed_commit.get("committed_at"), + "changed_file_count": len(files), + "sample_paths": [str(item.get("path")) for item in files[:12] if item.get("path")], + "surface_hints": surface_hints, + "attention_flags": attention_flags, + "review_priority": priority_for(surface_hints, attention_flags), + "review_reason": review_reason(surface_hints, attention_flags), + "next_step": "ai_review_required", + } + if isinstance(primary_pr, dict): + title = str(primary_pr.get("title") or title) + url = str(primary_pr.get("url") or url) + subject.update( + { + "title": title, + "url": url, + "source_state": str(primary_pr.get("state") or "pr_first"), + "pr_number": primary_pr.get("number"), + "pr_url": primary_pr.get("url"), + } + ) + return subject + + +def sort_subjects(subjects: list[dict[str, Any]]) -> list[dict[str, Any]]: + priority_rank = {"critical": 0, "high": 1, "normal": 2, "low": 3} + return sorted( + subjects, + key=lambda item: ( + priority_rank.get(str(item.get("review_priority")), 9), + str(item.get("committed_at") or ""), + str(item.get("subject_kind") or ""), + str(item.get("subject_id") or ""), + ), + ) + + +def build_review_queue(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, int]]: + token_env = args.token_env or routed_token_env() or "GITHUB_TOKEN" + token = os.environ.get(token_env) + root = repo_root() + default_branch, commits = recent_commits(args.repo, token, args.search_limit) + published_prs, published_shas = published_subjects((root / args.signals_dir).resolve()) + ledger_path = None if args.no_ledger else Path(args.ledger) + if ledger_path is not None and not ledger_path.is_absolute(): + ledger_path = root / ledger_path + ledger = connect_ledger(ledger_path) if ledger_path is not None else None + subjects: dict[tuple[str, str], dict[str, Any]] = {} + published_seen = 0 + try: + for commit in commits: + pr_number = maybe_promote_commit_to_pr(args.repo, commit["sha"], token) + subject_kind = "pr" if pr_number is not None else "commit" + subject_id = str(pr_number) if pr_number is not None else commit["sha"] + if ledger is not None: + record_commit( + ledger, + repo=args.repo, + sha=commit["sha"], + title=commit["title"], + url=commit["url"], + committed_at=commit.get("committed_at"), + pr_number=pr_number, + ) + if commit["sha"] in published_shas or (pr_number is not None and pr_number in published_prs): + published_seen += 1 + if ledger is not None: + record_review( + ledger, + repo=args.repo, + subject_kind=subject_kind, + subject_id=subject_id, + status="signal", + reason="Already present in published signal collection.", + confidence="confirmed", + ) + continue + key = (subject_kind, subject_id) + if key in subjects: + current = subjects[key] + if commit["sha"] not in current["commit_shas"]: + current["commit_shas"].append(commit["sha"]) + continue + bundle = ( + build_pr_bundle(args.repo, pr_number, token, ["Built in-memory for deterministic Radar queue hints."]) + if isinstance(pr_number, int) + else build_commit_bundle(args.repo, commit["sha"], token, ["Built in-memory for deterministic Radar queue hints."]) + ) + subjects[key] = subject_from_bundle( + bundle=bundle, + subject_kind=subject_kind, + subject_id=subject_id, + seed_commit=commit, + ) + if ledger is not None: + record_review( + ledger, + repo=args.repo, + subject_kind=subject_kind, + subject_id=subject_id, + status="watch", + reason="Queued for AI upstream review by deterministic Radar sync.", + confidence="likely", + ) + if ledger is not None: + ledger.commit() + finally: + if ledger is not None: + ledger.close() + + ordered_subjects = sort_subjects(list(subjects.values())) + queue = { + "schema": "upstream_review_queue/v1", + "repo": args.repo, + "generated_at": utc_now_iso(), + "source": { + "default_branch": default_branch, + "search_limit": args.search_limit, + "signals_dir": args.signals_dir, + }, + "subjects": ordered_subjects, + "counts": { + "recent_commits_scanned": len(commits), + "published_subjects_seen": published_seen, + "subjects_queued": len(ordered_subjects), + "critical": sum(1 for item in ordered_subjects if item["review_priority"] == "critical"), + "high": sum(1 for item in ordered_subjects if item["review_priority"] == "high"), + "normal": sum(1 for item in ordered_subjects if item["review_priority"] == "normal"), + "low": sum(1 for item in ordered_subjects if item["review_priority"] == "low"), + }, + } + return queue, {"ledger_enabled": 0 if args.no_ledger else 1} + + +def material_queue(value: dict[str, Any]) -> dict[str, Any]: + normalized = json.loads(json.dumps(value, sort_keys=True)) + if isinstance(normalized, dict): + normalized["generated_at"] = "" + return normalized + + +def write_queue_if_changed(path: Path, queue: dict[str, Any]) -> bool: + if path.exists(): + try: + existing = load_json(path) + except json.JSONDecodeError: + existing = None + if isinstance(existing, dict) and material_queue(existing) == material_queue(queue): + return False + dump_json(path, queue) + return True + + +def main() -> None: + args = parse_args() + root = repo_root() + queue, extra_counts = build_review_queue(args) + validation = validate_upstream_review_queue(queue) + if not validation.ok: + raise SystemExit("Upstream review queue validation failed:\n- " + "\n- ".join(validation.errors)) + if args.dry_run: + print(json.dumps(queue, indent=2, sort_keys=True)) + return + out = root / args.queue_out + changed = write_queue_if_changed(out, queue) + print( + json.dumps( + { + "repo": queue["repo"], + **queue["counts"], + **extra_counts, + "changed": changed, + "queue_out": str(out), + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/github/test_social_post_contract.py b/scripts/github/test_social_post_contract.py new file mode 100644 index 00000000..3afa1e90 --- /dev/null +++ b/scripts/github/test_social_post_contract.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Exercise the Decodex social_post/v1 validator.""" + +from __future__ import annotations + +from contracts import validate_social_post + + +def base_record() -> dict[str, object]: + return { + "schema": "social_post/v1", + "slug": "openai-codex-pr-22414", + "channel": "x", + "target_account": "decodexspace", + "controller_account": "hackink", + "mode": "operator_impact", + "status": "published", + "audience": "Codex operators", + "text": ["Remote Codex can now use Unix socket endpoints. Source: https://github.com/openai/codex/pull/22414"], + "source_refs": {"urls": ["https://github.com/openai/codex/pull/22414"]}, + "evidence_notes": ["PR #22414 changes the remote app-server endpoint handling."], + "claims": [ + { + "text": "Remote Codex can use Unix socket endpoints.", + "evidence": "https://github.com/openai/codex/pull/22414", + "confidence": "confirmed", + } + ], + "decision": { + "worthiness": "publish", + "priority": "high", + "idempotency_key": "x:decodexspace:operator_impact:openai-codex-pr-22414", + "reason": "High-value Control Plane transport implication.", + "daily_limit": 8, + "daily_count_before": 2, + "daily_count_after": 3, + "day": "2026-06-02", + "timezone": "Asia/Shanghai", + }, + "publication": { + "posted_at": "2026-06-02T03:00:00Z", + "published_urls": ["https://x.com/decodexspace/status/1"], + "publisher": "chrome", + "account_verified": True, + "made_with_ai": True, + "image_template": "decodex_signal_card", + }, + "media_refs": ["artifacts/social/x/images/openai-codex-pr-22414.png"], + } + + +def assert_valid(record: dict[str, object]) -> None: + validation = validate_social_post(record) + assert validation.ok, validation.errors + + +def assert_invalid(record: dict[str, object], expected: str) -> None: + validation = validate_social_post(record) + assert not validation.ok + assert any(expected in error for error in validation.errors), validation.errors + + +def main() -> None: + published = base_record() + assert_valid(published) + + blocked = base_record() + blocked["status"] = "blocked" + blocked.pop("publication") + blocked["decision"] = { + **blocked["decision"], # type: ignore[arg-type] + "worthiness": "block", + "daily_count_before": 8, + "daily_count_after": 8, + } + blocked["block"] = { + "reason": "daily_cap_exceeded", + "operator_notice": "Candidate blocked because @decodexspace already posted 8 times today.", + } + assert_valid(blocked) + + over_cap_published = base_record() + over_cap_published["decision"] = { + **over_cap_published["decision"], # type: ignore[arg-type] + "daily_limit": 9, + } + assert_invalid(over_cap_published, "daily_limit must be 8") + + print("OK") + + +if __name__ == "__main__": + main() diff --git a/scripts/github/upstream_review.schema.json b/scripts/github/upstream_review.schema.json new file mode 100644 index 00000000..14b178ba --- /dev/null +++ b/scripts/github/upstream_review.schema.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Decodex upstream review", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "slug", + "repo", + "subject", + "source_refs", + "reviewed_at", + "observed_change", + "changed_surfaces", + "confidence", + "evidence", + "next_actions" + ], + "properties": { + "schema": { + "const": "upstream_review/v1" + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "repo": { + "type": "string", + "minLength": 1 + }, + "subject": { + "type": "object", + "additionalProperties": false, + "required": ["subject_kind", "subject_id"], + "properties": { + "subject_kind": { + "type": "string", + "enum": ["commit", "pr"] + }, + "subject_id": { + "type": "string", + "minLength": 1 + }, + "commit_shas": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "source_refs": { + "type": "object", + "additionalProperties": false, + "required": ["items"], + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "title", "url"], + "properties": { + "kind": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "pattern": "^https://" + }, + "meta": { + "type": "string", + "minLength": 1 + } + } + } + } + } + }, + "reviewed_at": { + "type": "string", + "minLength": 1 + }, + "observed_change": { + "type": "string", + "minLength": 1 + }, + "changed_surfaces": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "user_visible_path": { + "type": "string" + }, + "control_plane_relevance": { + "type": "string" + }, + "compatibility_risk": { + "type": "string" + }, + "adoption_opportunity": { + "type": "string" + }, + "community_value": { + "type": "string" + }, + "deprecated_or_breaking_notes": { + "type": "string" + }, + "confidence": { + "type": "string", + "enum": ["confirmed", "likely", "weak"] + }, + "evidence": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "caveats": { + "type": "string" + }, + "next_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "reason"], + "properties": { + "type": { + "type": "string", + "enum": ["none", "upstream_impact", "signal_entry", "social_post", "linear_followup"] + }, + "reason": { + "type": "string", + "minLength": 1 + } + } + } + } + } +} diff --git a/scripts/github/validate_social_post.py b/scripts/github/validate_social_post.py new file mode 100644 index 00000000..096cf368 --- /dev/null +++ b/scripts/github/validate_social_post.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Validate Decodex social post publication-record JSON files.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +SCRIPT_HOME = Path(__file__).resolve().parent +if str(SCRIPT_HOME) not in sys.path: + sys.path.insert(0, str(SCRIPT_HOME)) + +from contracts import SOCIAL_POST_SCHEMA, load_json, validate_social_post # noqa: E402 + + +def iter_json_files(paths: list[str]) -> list[Path]: + files: list[Path] = [] + for raw in paths: + path = Path(raw) + if path.is_dir(): + files.extend(sorted(path.rglob("*.json"))) + else: + files.append(path) + return files + + +def validate_payload(path: Path) -> list[str]: + payload = load_json(path) + if payload.get("schema") != SOCIAL_POST_SCHEMA: + return [f"{path}: schema must be {SOCIAL_POST_SCHEMA}"] + validation = validate_social_post(payload) + return [f"{path}: {error}" for error in validation.errors] + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("paths", nargs="+", help="Social post JSON files or directories.") + args = parser.parse_args() + + errors: list[str] = [] + for path in iter_json_files(args.paths): + errors.extend(validate_payload(path)) + + if errors: + raise SystemExit("Social post validation failed:\n- " + "\n- ".join(errors)) + + print("OK") + + +if __name__ == "__main__": + main() diff --git a/scripts/github/validate_upstream_review.py b/scripts/github/validate_upstream_review.py new file mode 100644 index 00000000..b2766800 --- /dev/null +++ b/scripts/github/validate_upstream_review.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Validate upstream review queue and review JSON files.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +SCRIPT_HOME = Path(__file__).resolve().parent +if str(SCRIPT_HOME) not in sys.path: + sys.path.insert(0, str(SCRIPT_HOME)) + +from contracts import ( # noqa: E402 + UPSTREAM_REVIEW_QUEUE_SCHEMA, + UPSTREAM_REVIEW_SCHEMA, + load_json, + validate_upstream_review, + validate_upstream_review_queue, +) + + +def iter_json_files(paths: list[str]) -> list[Path]: + files: list[Path] = [] + for raw in paths: + path = Path(raw) + if path.is_dir(): + files.extend(sorted(path.glob("*.json"))) + else: + files.append(path) + return files + + +def validate_payload(path: Path) -> list[str]: + payload = load_json(path) + schema = payload.get("schema") + if schema == UPSTREAM_REVIEW_QUEUE_SCHEMA: + validation = validate_upstream_review_queue(payload) + elif schema == UPSTREAM_REVIEW_SCHEMA: + validation = validate_upstream_review(payload) + else: + return [f"{path}: schema must be {UPSTREAM_REVIEW_QUEUE_SCHEMA} or {UPSTREAM_REVIEW_SCHEMA}"] + return [f"{path}: {error}" for error in validation.errors] + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("paths", nargs="+", help="Review queue JSON files or directories.") + args = parser.parse_args() + + errors: list[str] = [] + for path in iter_json_files(args.paths): + errors.extend(validate_payload(path)) + + if errors: + raise SystemExit("Upstream review validation failed:\n- " + "\n- ".join(errors)) + + print("OK") + + +if __name__ == "__main__": + main() diff --git a/site/src/content/release-deltas/openai-codex-latest.json b/site/src/content/release-deltas/openai-codex-latest.json index d621d702..8288366e 100644 --- a/site/src/content/release-deltas/openai-codex-latest.json +++ b/site/src/content/release-deltas/openai-codex-latest.json @@ -1,438 +1,28 @@ { "compare": { - "ahead_by": 103, + "ahead_by": 1, "commit_shas": [ - "d9feaffffbbde12b49f3e6264f63524e7814164f", - "317213fd33fcbc76ae59817f9188033bb3569383", - "cce059467af64f05d0a1521344847d2b558b6a80", - "aadcae9f3c2b850a1bf050038e7d6d3005ba4bf1", - "f86d95a242648f91c2bb2b55ed336d9e4d95762e", - "e6312d44f0732c87180a7067a0425ca9aa5e6047", - "bd8fc9adb93fa5bc0a69b396bd5ac78a5ec14487", - "f9bbbafb68dfea19def4bb0b4a8ae7b594cbece8", - "607b0dd1f06ce8b09db43f2ec3e0582daf21158e", - "5b87bd2845b5313357af85b3bd9b031221082837", - "5733e00c7925a15e42d4383abbd9ecebe6c0f050", - "05ffa0b1d08170409f70037f96e356995d9b5b69", - "47f1d7b40bd674bad24a97f4e418db410bcfd976", - "7c0e54bf592bc12ef5ab14531b9732df4fc3803e", - "0a0d09ad21d60c5fad7c61f0565347280e52f197", - "8bea5d231a1d3a14124cec8725c0c9a98d84eaca", - "872b8b15b38acbcc19457ef96b171819a56206db", - "5f2543b74ef06b960dc1c1c42ac1c8219a8297f4", - "772e03459412015703c7a9e9a01b5049c3e7249c", - "8956a928a13778f2fda835cb58853d9963257717", - "9183503b972c5b7aebd58dd3cc0c69e81c0d4631", - "bbb6bf0a371ed913f5629a4a3602a6ed5e47a46d", - "61142b61693dd89fc0104946ecb23c177a360905", - "5f4d0ec343d807f6932e6bdc5785dc5a127ac409", - "cf941ede15a5528930b62f2d075dd39887773e0c", - "e783341b705721728a8fa422416c10c3a09c7716", - "46e2250bcfc6a789f7561a0243f20b4c98e78dcf", - "7c9731c9af879e2ee4fd4bf92312bbd690a55336", - "2f3a2d7a86cdfe082ed5c6efba0021162a75ffcf", - "24111790f060891f0709aff805b4fd13cfbde1bf", - "dac108f2f1a308af7f80828cbd25bd86b4f6ee4f", - "1b86906fa179f416a61159ba4b17857868c4c8cc", - "80a408e201e3c01cf2943f88906d3fdbcd17065a", - "8f4020846ef6ff17f652bec6136378ff438db1fc", - "faa5d4a5e28330a80b251f571ed3a03fae75fa87", - "0c8d42525effe7c0208fcfe052a5aece8941cc17", - "bd42660cb48155206286ade3ec5030418cf4d3e7", - "95ca27637330722a2cc915499fa9c1204ddfc397", - "408e6218ab7fadc192901ae28520471a4f990671", - "c579da41b16dc88b62d9cb2611f70ccdb7ac2735", - "479491ed892568f70fe9489ec23e97cdd107f501", - "77d9223e9f0aec685ecd5eca3a25326a0ad196de", - "ebe75bb683b3c237aad9f039ab17b187048aa499", - "fca81eeb5bab4cad997622a359d446e6489c445b", - "0c70698e24e37cfdb2578cbeec0d981f656dc734", - "f27cf9db0974d344d78e7e0b47e7c812776b1395", - "53468b97f6eded34f9e593ef6d9d9f0b2ddb303a", - "6d747db7d8ae51a20aaf06529aab6563b0cb4168", - "90c0bec50c9439da7a9359b9eb0ec0e24add32ef", - "789b7e39dc4128de99796a484ff4d7a02252a9d1", - "178c3d30053ba63d118ee93a0dbe2716a12e5769", - "cac53544555af8eb8f2d3862d4b92ae6386ef06f", - "e5d022297d8b085ba68cf54a6a1c5e7f4c049642", - "76845d716b720ca701b2c91fec75431532e66c74", - "5248e3da2b05b70cdc3a3144c343fa742bf37b8d", - "2abdeb34d5b7a0bbdf082ce8be1d5dae6c645ffd", - "d2c3ebac1f8cccf578e6196de349e9f3554382ca", - "436c0df6583ffe9512292f421d9f4a41129c7a70", - "569ff6a1c400bd514ff79f5f1050a684dc3afde3", - "95bfea847d9672de9e94f27db51ab52efd76b346", - "ebd3d534516ad0597ae38cd550fc755eb530e4cb", - "7e15e6db9ea5dab04937f7e3b060b5cf199c60f0", - "672cc1f6696d90c568637f3cd8cac3a97d2c639e", - "8e12c12a07dc874471a20ced458606b324f2f2c5", - "704ad620f625c92d83da1cc85e99bf15a7fb2f31", - "2229c8daf260704697bad8a920e5ca297416f50f", - "15e79f3c2696672d292c62fa7823e98cd155462a", - "69f3183a8ebec55a0813b6d4830fcc09597c4d8b", - "c03eb20d8dacf7bdf237a0688d969d95c67048fe", - "96836e15ed0db0123302debd064c857af0fe8c8b", - "f10ddc3f135fc691548efab8b7c11b4241530146", - "a124ddb854a65a6d8994233c55644c88a4869c4a", - "32b1ae70997eac995a395ab196c0ba62f91eacb0", - "1e65b3e0af32cc6b29bd7bb2e326f50c4d212e93", - "0dbad2a34816ce9aa6cb6872ad828918fad68a7a", - "cf6342b75bd407c63d02bf21e67ed2daf0a66b24", - "b401666ca524334edd0f429b9d39331034d726dc", - "eaf05c9002b1f88659111e2550470530ef46d35d", - "e783dab44c4e71325a45dd8dfef0bca708306fb7", - "90bd445e7f0a2b760c30fe0b25cb7ceee897c224", - "7bddb3083d677bf36a1cd65ecff9fe81c5f0bdf1", - "650676516894a8eb65b5e8b66dc9bcd11361f2e8", - "d0fa2d81d894c38bf4ab14f3d025ce19f8b5cbd8", - "192481d1a148eede2df387112c9476c18ff06675", - "99b98aece6b7fde3656ff1ba53b7a133b86e56db", - "e3f481da98890580fdd9b1d8530cc1c89ee4eb6d", - "54ec99cb545176d23790a757a98f18925dcc3191", - "c7b55cdc46fbd705007227aabeeedbc5dabaa303", - "5fe33443b0cc331f8e886a606c28b278b0c1812f", - "6a4653efc863e42bab2fefd80f4b18cea1082ad2", - "3e2936dd0e27e845223ec7a388730b27864e1d11", - "b4bc02439f7eebcc412d409995886de3501bb66c", - "f1b84fac63eb73efa7acfce466ea39d63e43d060", - "2b90c3706945ccf30f54b410ce30db0bff9b9487", - "3e10e09e2457f9285f2950d6af4a7356a6a34f38", - "aa9e8f026295abaefc320fcf3c56e83f12666a3c", - "a175ddacc0893c2847692743a7ebcd8687a6d768", - "9ab7f4e6acb90e9a98cc753322af823f11e5ba58", - "4859d80ffeec76cc59c95fd274157c6b5560b4d2", - "bb6134c028fd2539b3c0a76c6c3bc6635784ac3e", - "22e84c49d0c1eb749f75de07ec3b3a9ce0edb1cb", - "e15ecc9c354587e98c6edca2a69696d64bc55c98", - "b5386206f3fb5ef5848bdb97f6721067ee4a2e9e" - ], - "pr_numbers": [ - 15977, - 18748, - 19068, - 19896, - 20147, - 20293, - 20305, - 20619, - 20667, - 20718, - 20825, - 21061, - 21290, - 21323, - 21431, - 21443, - 21465, - 21497, - 21550, - 21601, - 21604, - 21616, - 21617, - 21622, - 21628, - 21651, - 21652, - 21662, - 21669, - 21677, - 21687, - 21697, - 21736, - 21737, - 21738, - 21745, - 21747, - 21749, - 21755, - 21757, - 21759, - 21760, - 21763, - 21767, - 21772, - 21776, - 21778, - 21784, - 21787, - 21794, - 21805, - 21810, - 21819, - 21825, - 21831, - 21835, - 21840, - 21843, - 21847, - 21853, - 21860, - 21866, - 21867, - 21870, - 21875, - 21891, - 21893, - 21895, - 21896, - 21905, - 21906, - 21910, - 21943, - 21950, - 21954, - 21981, - 21991, - 22014, - 22021, - 22039, - 22045, - 22052, - 22058, - 22106, - 22110, - 22113, - 22138, - 22140, - 22141, - 22143, - 22147, - 22154, - 22159, - 22163, - 22170, - 22173, - 22178, - 22180, - 22188, - 22192, - 22198, - 22218 + "4eac96bb3e1e35fa99242b44eb87494750dc0f7a" ], + "pr_numbers": [], "status": "diverged", - "total_commits": 103, - "url": "https://github.com/openai/codex/compare/rust-v0.130.0...rust-v0.131.0-alpha.9" + "total_commits": 1, + "url": "https://github.com/openai/codex/compare/rust-v0.136.0...rust-v0.136.0-alpha.2" }, "comparisons": [ { "compare": { - "ahead_by": 103, + "ahead_by": 1, "commit_shas": [ - "d9feaffffbbde12b49f3e6264f63524e7814164f", - "317213fd33fcbc76ae59817f9188033bb3569383", - "cce059467af64f05d0a1521344847d2b558b6a80", - "aadcae9f3c2b850a1bf050038e7d6d3005ba4bf1", - "f86d95a242648f91c2bb2b55ed336d9e4d95762e", - "e6312d44f0732c87180a7067a0425ca9aa5e6047", - "bd8fc9adb93fa5bc0a69b396bd5ac78a5ec14487", - "f9bbbafb68dfea19def4bb0b4a8ae7b594cbece8", - "607b0dd1f06ce8b09db43f2ec3e0582daf21158e", - "5b87bd2845b5313357af85b3bd9b031221082837", - "5733e00c7925a15e42d4383abbd9ecebe6c0f050", - "05ffa0b1d08170409f70037f96e356995d9b5b69", - "47f1d7b40bd674bad24a97f4e418db410bcfd976", - "7c0e54bf592bc12ef5ab14531b9732df4fc3803e", - "0a0d09ad21d60c5fad7c61f0565347280e52f197", - "8bea5d231a1d3a14124cec8725c0c9a98d84eaca", - "872b8b15b38acbcc19457ef96b171819a56206db", - "5f2543b74ef06b960dc1c1c42ac1c8219a8297f4", - "772e03459412015703c7a9e9a01b5049c3e7249c", - "8956a928a13778f2fda835cb58853d9963257717", - "9183503b972c5b7aebd58dd3cc0c69e81c0d4631", - "bbb6bf0a371ed913f5629a4a3602a6ed5e47a46d", - "61142b61693dd89fc0104946ecb23c177a360905", - "5f4d0ec343d807f6932e6bdc5785dc5a127ac409", - "cf941ede15a5528930b62f2d075dd39887773e0c", - "e783341b705721728a8fa422416c10c3a09c7716", - "46e2250bcfc6a789f7561a0243f20b4c98e78dcf", - "7c9731c9af879e2ee4fd4bf92312bbd690a55336", - "2f3a2d7a86cdfe082ed5c6efba0021162a75ffcf", - "24111790f060891f0709aff805b4fd13cfbde1bf", - "dac108f2f1a308af7f80828cbd25bd86b4f6ee4f", - "1b86906fa179f416a61159ba4b17857868c4c8cc", - "80a408e201e3c01cf2943f88906d3fdbcd17065a", - "8f4020846ef6ff17f652bec6136378ff438db1fc", - "faa5d4a5e28330a80b251f571ed3a03fae75fa87", - "0c8d42525effe7c0208fcfe052a5aece8941cc17", - "bd42660cb48155206286ade3ec5030418cf4d3e7", - "95ca27637330722a2cc915499fa9c1204ddfc397", - "408e6218ab7fadc192901ae28520471a4f990671", - "c579da41b16dc88b62d9cb2611f70ccdb7ac2735", - "479491ed892568f70fe9489ec23e97cdd107f501", - "77d9223e9f0aec685ecd5eca3a25326a0ad196de", - "ebe75bb683b3c237aad9f039ab17b187048aa499", - "fca81eeb5bab4cad997622a359d446e6489c445b", - "0c70698e24e37cfdb2578cbeec0d981f656dc734", - "f27cf9db0974d344d78e7e0b47e7c812776b1395", - "53468b97f6eded34f9e593ef6d9d9f0b2ddb303a", - "6d747db7d8ae51a20aaf06529aab6563b0cb4168", - "90c0bec50c9439da7a9359b9eb0ec0e24add32ef", - "789b7e39dc4128de99796a484ff4d7a02252a9d1", - "178c3d30053ba63d118ee93a0dbe2716a12e5769", - "cac53544555af8eb8f2d3862d4b92ae6386ef06f", - "e5d022297d8b085ba68cf54a6a1c5e7f4c049642", - "76845d716b720ca701b2c91fec75431532e66c74", - "5248e3da2b05b70cdc3a3144c343fa742bf37b8d", - "2abdeb34d5b7a0bbdf082ce8be1d5dae6c645ffd", - "d2c3ebac1f8cccf578e6196de349e9f3554382ca", - "436c0df6583ffe9512292f421d9f4a41129c7a70", - "569ff6a1c400bd514ff79f5f1050a684dc3afde3", - "95bfea847d9672de9e94f27db51ab52efd76b346", - "ebd3d534516ad0597ae38cd550fc755eb530e4cb", - "7e15e6db9ea5dab04937f7e3b060b5cf199c60f0", - "672cc1f6696d90c568637f3cd8cac3a97d2c639e", - "8e12c12a07dc874471a20ced458606b324f2f2c5", - "704ad620f625c92d83da1cc85e99bf15a7fb2f31", - "2229c8daf260704697bad8a920e5ca297416f50f", - "15e79f3c2696672d292c62fa7823e98cd155462a", - "69f3183a8ebec55a0813b6d4830fcc09597c4d8b", - "c03eb20d8dacf7bdf237a0688d969d95c67048fe", - "96836e15ed0db0123302debd064c857af0fe8c8b", - "f10ddc3f135fc691548efab8b7c11b4241530146", - "a124ddb854a65a6d8994233c55644c88a4869c4a", - "32b1ae70997eac995a395ab196c0ba62f91eacb0", - "1e65b3e0af32cc6b29bd7bb2e326f50c4d212e93", - "0dbad2a34816ce9aa6cb6872ad828918fad68a7a", - "cf6342b75bd407c63d02bf21e67ed2daf0a66b24", - "b401666ca524334edd0f429b9d39331034d726dc", - "eaf05c9002b1f88659111e2550470530ef46d35d", - "e783dab44c4e71325a45dd8dfef0bca708306fb7", - "90bd445e7f0a2b760c30fe0b25cb7ceee897c224", - "7bddb3083d677bf36a1cd65ecff9fe81c5f0bdf1", - "650676516894a8eb65b5e8b66dc9bcd11361f2e8", - "d0fa2d81d894c38bf4ab14f3d025ce19f8b5cbd8", - "192481d1a148eede2df387112c9476c18ff06675", - "99b98aece6b7fde3656ff1ba53b7a133b86e56db", - "e3f481da98890580fdd9b1d8530cc1c89ee4eb6d", - "54ec99cb545176d23790a757a98f18925dcc3191", - "c7b55cdc46fbd705007227aabeeedbc5dabaa303", - "5fe33443b0cc331f8e886a606c28b278b0c1812f", - "6a4653efc863e42bab2fefd80f4b18cea1082ad2", - "3e2936dd0e27e845223ec7a388730b27864e1d11", - "b4bc02439f7eebcc412d409995886de3501bb66c", - "f1b84fac63eb73efa7acfce466ea39d63e43d060", - "2b90c3706945ccf30f54b410ce30db0bff9b9487", - "3e10e09e2457f9285f2950d6af4a7356a6a34f38", - "aa9e8f026295abaefc320fcf3c56e83f12666a3c", - "a175ddacc0893c2847692743a7ebcd8687a6d768", - "9ab7f4e6acb90e9a98cc753322af823f11e5ba58", - "4859d80ffeec76cc59c95fd274157c6b5560b4d2", - "bb6134c028fd2539b3c0a76c6c3bc6635784ac3e", - "22e84c49d0c1eb749f75de07ec3b3a9ce0edb1cb", - "e15ecc9c354587e98c6edca2a69696d64bc55c98", - "b5386206f3fb5ef5848bdb97f6721067ee4a2e9e" - ], - "pr_numbers": [ - 15977, - 18748, - 19068, - 19896, - 20147, - 20293, - 20305, - 20619, - 20667, - 20718, - 20825, - 21061, - 21290, - 21323, - 21431, - 21443, - 21465, - 21497, - 21550, - 21601, - 21604, - 21616, - 21617, - 21622, - 21628, - 21651, - 21652, - 21662, - 21669, - 21677, - 21687, - 21697, - 21736, - 21737, - 21738, - 21745, - 21747, - 21749, - 21755, - 21757, - 21759, - 21760, - 21763, - 21767, - 21772, - 21776, - 21778, - 21784, - 21787, - 21794, - 21805, - 21810, - 21819, - 21825, - 21831, - 21835, - 21840, - 21843, - 21847, - 21853, - 21860, - 21866, - 21867, - 21870, - 21875, - 21891, - 21893, - 21895, - 21896, - 21905, - 21906, - 21910, - 21943, - 21950, - 21954, - 21981, - 21991, - 22014, - 22021, - 22039, - 22045, - 22052, - 22058, - 22106, - 22110, - 22113, - 22138, - 22140, - 22141, - 22143, - 22147, - 22154, - 22159, - 22163, - 22170, - 22173, - 22178, - 22180, - 22188, - 22192, - 22198, - 22218 + "4eac96bb3e1e35fa99242b44eb87494750dc0f7a" ], + "pr_numbers": [], "status": "diverged", - "total_commits": 103, - "url": "https://github.com/openai/codex/compare/rust-v0.130.0...rust-v0.131.0-alpha.9" + "total_commits": 1, + "url": "https://github.com/openai/codex/compare/rust-v0.136.0...rust-v0.136.0-alpha.2" }, - "prerelease_tag_name": "rust-v0.131.0-alpha.9", - "stable_tag_name": "rust-v0.130.0", + "prerelease_tag_name": "rust-v0.136.0-alpha.2", + "stable_tag_name": "rust-v0.136.0", "tracked_signal_slugs": [] }, { @@ -9800,22 +9390,22 @@ ] } ], - "generated_at": "2026-05-13T07:48:40Z", + "generated_at": "2026-06-02T02:34:53Z", "prerelease": { - "name": "0.131.0-alpha.9", + "name": "0.136.0-alpha.2", "prerelease": true, - "published_at": "2026-05-12T01:20:36Z", - "tag_name": "rust-v0.131.0-alpha.9", - "url": "https://github.com/openai/codex/releases/tag/rust-v0.131.0-alpha.9" + "published_at": "2026-05-31T19:40:07Z", + "tag_name": "rust-v0.136.0-alpha.2", + "url": "https://github.com/openai/codex/releases/tag/rust-v0.136.0-alpha.2" }, "release_options": { "preview": [ { - "name": "0.131.0-alpha.9", + "name": "0.136.0-alpha.2", "prerelease": true, - "published_at": "2026-05-12T01:20:36Z", - "tag_name": "rust-v0.131.0-alpha.9", - "url": "https://github.com/openai/codex/releases/tag/rust-v0.131.0-alpha.9" + "published_at": "2026-05-31T19:40:07Z", + "tag_name": "rust-v0.136.0-alpha.2", + "url": "https://github.com/openai/codex/releases/tag/rust-v0.136.0-alpha.2" }, { "name": "0.119.0-alpha.13", @@ -9876,11 +9466,11 @@ ], "stable": [ { - "name": "0.130.0", + "name": "0.136.0", "prerelease": false, - "published_at": "2026-05-08T23:09:55Z", - "tag_name": "rust-v0.130.0", - "url": "https://github.com/openai/codex/releases/tag/rust-v0.130.0" + "published_at": "2026-06-01T17:49:22Z", + "tag_name": "rust-v0.136.0", + "url": "https://github.com/openai/codex/releases/tag/rust-v0.136.0" }, { "name": "0.118.0", @@ -9908,11 +9498,11 @@ "repo": "openai/codex", "schema": "release_delta/v1", "stable_release": { - "name": "0.130.0", + "name": "0.136.0", "prerelease": false, - "published_at": "2026-05-08T23:09:55Z", - "tag_name": "rust-v0.130.0", - "url": "https://github.com/openai/codex/releases/tag/rust-v0.130.0" + "published_at": "2026-06-01T17:49:22Z", + "tag_name": "rust-v0.136.0", + "url": "https://github.com/openai/codex/releases/tag/rust-v0.136.0" }, "tag_prefix": "rust-v", "tracked_signal_slugs": [] diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 959e254b..db42990f 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -31,7 +31,7 @@ import { const rawFilter = Astro.url.searchParams.get("filter"); const activeFilter: FilterId = isFilterId(rawFilter) ? rawFilter : "all"; const decodexGitHubUrl = "https://github.com/hack-ink/decodex"; -const primaryXUrl = "https://x.com/YvetteCipher"; +const primaryXUrl = "https://x.com/hackink"; const signalBotXUrl = "https://x.com/decodexspace"; const collectionEntries = await getCollection("signals"); const releaseDeltaEntries = await getCollection("releaseDeltas"); @@ -533,7 +533,7 @@ const interactionRuntimeScript = `(function () { Open Decodex radar GitHub - Follow @YvetteCipher + Follow @hackink @@ -690,7 +690,7 @@ const interactionRuntimeScript = `(function () {
Output path

- Commit evidence becomes short X posts, release rollups, and B-side follow-up notes. Bot channel: @decodexspace. + Commit evidence becomes low-frequency X posts, release rollups, and follow-up notes. Bot channel: @decodexspace.