Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/refresh-release-delta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

- name: Refresh release delta
run: |
python3 scripts/github/build_release_delta.py \
cargo run -p decodex --bin decodex -- radar refresh-release-delta \
--repo openai/codex \
--signals-dir site/src/content/signals \
--out site/src/content/release-deltas/openai-codex-latest.json \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/refresh-upstream-radar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

- name: Refresh upstream review queue
run: |
python3 scripts/github/sync_upstream_radar.py \
cargo run -p decodex --bin decodex -- radar refresh-upstream-queue \
--repo openai/codex \
--token-env GH_API_TOKEN \
--search-limit 40
Expand Down
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ 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 upstream review queue, GitHub bundle,
release-delta, render, and validation scripts.
- `apps/decodex/src/radar.rs` owns Rust Radar queue, release-delta, and validation
commands.
- `scripts/github/` owns deterministic GitHub bundle, render, validation, backfill,
ledger import, and analysis-support 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
Expand All @@ -80,7 +82,7 @@ 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_upstream_radar.py`
Historical Radar trace is local by default. `decodex radar refresh-upstream-queue`
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.
Expand All @@ -107,6 +109,8 @@ cargo run -p decodex --bin decodex -- project list
cargo run -p decodex --bin decodex -- status
cargo run -p decodex --bin decodex -- diagnose --json
cargo run -p decodex --bin decodex -- maintenance prune --dry-run
cargo run -p decodex --bin decodex -- radar refresh-upstream-queue
cargo run -p decodex --bin decodex -- radar refresh-release-delta
cargo run -p decodex --bin decodex -- radar validate
cargo run -p decodex --bin decodex -- run --dry-run
cargo run -p decodex --bin decodex -- serve --listen-address 127.0.0.1:8912
Expand Down Expand Up @@ -198,7 +202,7 @@ Pages setup for `https://decodex.space` lives in `docs/runbook/github-pages-depl
The upstream Codex Radar path starts deterministic and becomes editorial only after
Codex automation reviews source evidence:

- `scripts/github/sync_upstream_radar.py` records every observed recent upstream
- `decodex radar refresh-upstream-queue` 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
Expand All @@ -212,11 +216,12 @@ Codex automation reviews source evidence:
public signals and Control Plane follow-up work.
- `decodex radar render-signal` renders reviewed analysis drafts into site content.
- `scripts/github/validate_signal_entry.py` validates the published signal collection.
- `decodex radar bundle validate`, `decodex radar ledger ...`, `decodex radar
- `decodex radar refresh-upstream-queue`, `decodex radar refresh-release-delta`,
`decodex radar bundle validate`, `decodex radar ledger ...`, `decodex radar
render-signal`, `decodex radar backfill-release-range`, and `decodex radar
validate` provide the Rust-owned command surface for bundle validation, local ledger
maintenance, signal rendering, release-window backfill, and checked Radar artifact
validation.
validate` provide the Rust-owned command surface for deterministic queue refresh,
release-delta refresh, bundle validation, local ledger maintenance, signal
rendering, release-window backfill, and checked Radar artifact validation.
- `docs/spec/social-publishing.md` and
`docs/runbook/social-publishing-workflow.md` govern automated low-frequency X
publication for `@decodexspace`.
Expand Down
205 changes: 203 additions & 2 deletions apps/decodex/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use crate::{
self, RadarBackfillReleaseRangeRequest, RadarBundleBuildRequest,
RadarBundleValidateRequest, RadarLedgerArtifactLinkRequest, RadarLedgerBootstrapRequest,
RadarLedgerIngestExistingRequest, RadarLedgerIngestRequest, RadarLedgerSummaryRequest,
RadarRenderSignalRequest, RadarValidateRequest,
RadarRefreshQueueRequest, RadarRefreshReleaseDeltaRequest, RadarRenderSignalRequest,
RadarValidateRequest,
},
recovery::{self, ReviewHandoffDiagnoseRequest, ReviewHandoffRebindRequest},
runtime,
Expand Down Expand Up @@ -601,6 +602,8 @@ impl RadarCommand {
match &self.command {
RadarSubcommand::Bundle(args) => args.run(),
RadarSubcommand::Ledger(args) => args.run(),
RadarSubcommand::RefreshUpstreamQueue(args) => args.run(),
RadarSubcommand::RefreshReleaseDelta(args) => args.run(),
RadarSubcommand::Validate(args) => args.run(),
RadarSubcommand::RenderSignal(args) => args.run(),
RadarSubcommand::BackfillReleaseRange(args) => args.run(),
Expand Down Expand Up @@ -772,6 +775,131 @@ impl RadarBundleCommand {
}
}

#[derive(Debug, Args)]
struct RadarRefreshUpstreamQueueCommand {
/// GitHub repository in owner/name format.
#[arg(long, default_value = "openai/codex")]
repo: String,
/// How many recent upstream commits to inspect.
#[arg(long, default_value_t = 40)]
search_limit: usize,
/// Published signal directory used to suppress already-published subjects.
#[arg(long, default_value = "site/src/content/signals")]
signals_dir: PathBuf,
/// Path to write the deterministic upstream_review_queue/v1 artifact.
#[arg(long, default_value = "artifacts/github/review-queue/openai-codex-latest.json")]
queue_out: PathBuf,
/// Environment variable containing a GitHub token.
#[arg(long)]
token_env: Option<String>,
/// Local SQLite Radar ledger path.
#[arg(long, default_value = ".decodex/radar.sqlite3")]
ledger: PathBuf,
/// Disable local Radar ledger writes.
#[arg(long)]
no_ledger: bool,
/// Print the queue without writing queue-out.
#[arg(long)]
dry_run: bool,
}
impl RadarRefreshUpstreamQueueCommand {
fn run(&self) -> Result<()> {
let report = radar::refresh_queue(&RadarRefreshQueueRequest {
repo: self.repo.clone(),
search_limit: self.search_limit,
signals_dir: self.signals_dir.clone(),
queue_out: self.queue_out.clone(),
token_env: self.token_env.clone(),
ledger: self.ledger.clone(),
no_ledger: self.no_ledger,
dry_run: self.dry_run,
})?;

if !self.dry_run {
println!(
"{}",
serde_json::to_string(&serde_json::json!({
"repo": self.repo,
"recent_commits_scanned": report.recent_commits_scanned,
"published_subjects_seen": report.published_subjects_seen,
"subjects_queued": report.subjects_queued,
"ledger_enabled": if report.ledger_enabled { 1 } else { 0 },
"changed": report.changed,
"queue_out": report.queue_out.display().to_string(),
}))?
);
}

Ok(())
}
}

#[derive(Debug, Args)]
struct RadarRefreshReleaseDeltaCommand {
/// GitHub repository in owner/name format.
#[arg(long, default_value = "openai/codex")]
repo: String,
/// Directory containing published signal-entry JSON files.
#[arg(long, default_value = "site/src/content/signals")]
signals_dir: PathBuf,
/// Path to write the release-delta JSON artifact.
#[arg(long, default_value = "site/src/content/release-deltas/openai-codex-latest.json")]
out: PathBuf,
/// Release tag prefix to scope the tracked channel.
#[arg(long, default_value = "rust-v")]
tag_prefix: String,
/// Environment variable containing a GitHub token.
#[arg(long)]
token_env: Option<String>,
/// Maximum recent stable releases to include. Use 0 for all releases at or above the floor.
#[arg(long, default_value_t = 0)]
stable_limit: usize,
/// Maximum recent prereleases to include. Use 0 for all supported prereleases.
#[arg(long, default_value_t = 0)]
preview_limit: usize,
/// Maximum signal-bearing compare entries. Use 0 for all valid pairs.
#[arg(long, default_value_t = 24)]
pair_limit: usize,
/// Minimum stable tag to include in the comparator option set.
#[arg(long, default_value = "rust-v0.116.0")]
min_stable_tag: String,
/// Print the release delta without writing out.
#[arg(long)]
dry_run: bool,
}
impl RadarRefreshReleaseDeltaCommand {
fn run(&self) -> Result<()> {
let report = radar::refresh_release_delta(&RadarRefreshReleaseDeltaRequest {
repo: self.repo.clone(),
signals_dir: self.signals_dir.clone(),
out: self.out.clone(),
tag_prefix: self.tag_prefix.clone(),
token_env: self.token_env.clone(),
stable_limit: self.stable_limit,
preview_limit: self.preview_limit,
pair_limit: self.pair_limit,
min_stable_tag: self.min_stable_tag.clone(),
dry_run: self.dry_run,
})?;

if !self.dry_run {
println!(
"{}",
serde_json::to_string(&serde_json::json!({
"repo": self.repo,
"stable_tag_name": report.stable_tag_name,
"prerelease_tag_name": report.prerelease_tag_name,
"comparisons": report.comparisons,
"changed": report.changed,
"out": report.out.display().to_string(),
}))?
);
}

Ok(())
}
}

#[derive(Debug, Args)]
struct RadarBundleBuildCommand {
/// GitHub repository in owner/name format.
Expand Down Expand Up @@ -1072,7 +1200,7 @@ enum Command {
Maintenance(MaintenanceCommand),
/// Manage the global Decodex Codex account pool.
Account(AccountCommand),
/// Inspect and validate Decodex Radar artifacts.
/// Refresh and validate Decodex Radar artifacts.
Radar(RadarCommand),
/// Validate the local app-server integration boundary.
Probe(ProbeCommand),
Expand Down Expand Up @@ -1140,6 +1268,10 @@ enum RadarSubcommand {
Bundle(RadarBundleCommand),
/// Maintain the local Radar SQLite ledger.
Ledger(RadarLedgerCommand),
/// Refresh the deterministic upstream Radar review queue.
RefreshUpstreamQueue(RadarRefreshUpstreamQueueCommand),
/// Refresh the stable-versus-prerelease release-delta artifact.
RefreshReleaseDelta(RadarRefreshReleaseDeltaCommand),
/// Validate checked-in Radar artifact JSON contracts.
Validate(RadarValidateCommand),
/// Render a signal_entry/v1 artifact from a bundle plus Codex analysis draft.
Expand Down Expand Up @@ -1208,6 +1340,7 @@ mod tests {
RadarBundleBuildCommand, RadarBundleCommand, RadarBundleSubcommand,
RadarBundleValidateCommand, RadarCommand, RadarLedgerCommand,
RadarLedgerIngestExistingCommand, RadarLedgerSubcommand, RadarLedgerSummaryCommand,
RadarRefreshReleaseDeltaCommand, RadarRefreshUpstreamQueueCommand,
RadarRenderSignalCommand, RadarSubcommand, RadarValidateCommand, RecoverCommand,
RecoverSubcommand, ReviewHandoffDiagnoseCommand, ReviewHandoffRebindCommand,
ReviewHandoffRecoveryCommand, ReviewHandoffRecoverySubcommand, RunCommand, ServeCommand,
Expand Down Expand Up @@ -1570,6 +1703,74 @@ mod tests {
));
}

#[test]
fn parses_radar_refresh_upstream_queue() {
let cli = Cli::parse_from([
"decodex",
"radar",
"refresh-upstream-queue",
"--repo",
"openai/codex",
"--token-env",
"GH_API_TOKEN",
"--search-limit",
"40",
]);

assert!(matches!(
cli.command,
Command::Radar(RadarCommand {
command: RadarSubcommand::RefreshUpstreamQueue(
RadarRefreshUpstreamQueueCommand {
repo,
token_env: Some(token_env),
search_limit: 40,
queue_out,
..
}
),
}) if repo == "openai/codex"
&& token_env == "GH_API_TOKEN"
&& queue_out == Path::new("artifacts/github/review-queue/openai-codex-latest.json")
));
}

#[test]
fn parses_radar_refresh_release_delta() {
let cli = Cli::parse_from([
"decodex",
"radar",
"refresh-release-delta",
"--repo",
"openai/codex",
"--signals-dir",
"site/src/content/signals",
"--out",
"site/src/content/release-deltas/openai-codex-latest.json",
"--token-env",
"GH_API_TOKEN",
]);

assert!(matches!(
cli.command,
Command::Radar(RadarCommand {
command: RadarSubcommand::RefreshReleaseDelta(
RadarRefreshReleaseDeltaCommand {
repo,
token_env: Some(token_env),
signals_dir,
out,
pair_limit: 24,
..
}
),
}) if repo == "openai/codex"
&& token_env == "GH_API_TOKEN"
&& signals_dir == Path::new("site/src/content/signals")
&& out == Path::new("site/src/content/release-deltas/openai-codex-latest.json")
));
}

#[test]
fn rejects_serve_interval_argument() {
let error = Cli::try_parse_from(["decodex", "serve", "--interval", "30s"])
Expand Down
Loading