diff --git a/.github/agents/registry.yml b/.github/agents/registry.yml index 6cc23eb91..6e5f1b2a8 100644 --- a/.github/agents/registry.yml +++ b/.github/agents/registry.yml @@ -21,7 +21,7 @@ model_profile_trial_contract: artifact_schema: workflows.model-profile-trial-result/v2 identity_authority: workflows-read-only-trial-artifact/v2 collector_identity_authority: github-actions-api/workflows-read-only-trial-artifact/v2 - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 cli_version: 0.144.1 runtime_fallback_allowed: false auxiliary_evaluator_allowed: false @@ -52,7 +52,7 @@ execution_profiles: model: gpt-5.6-sol fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -63,7 +63,7 @@ execution_profiles: model: gpt-5.6-terra fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -74,7 +74,7 @@ execution_profiles: model: gpt-5.6-luna fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -137,7 +137,7 @@ agents: branch_prefix: cursor/issue- capacity: window: daily - limit: 1 # TODO: confirm owner-supplied public plan limit + limit: 10 # SET THIS to your actual Cursor plan quota ui_mentions_allowed: false # Reuses stranske-automation-bot for branch pushes/attribution until a # dedicated stranske-cursor-bot service account is provisioned. @@ -152,7 +152,7 @@ agents: capabilities: pr_keepalive: true pr_autofix: true # wired into agents-autofix-loop.yml (autofix-cursor job) - belt: false # belt routing deferred to a later phase + belt: true # dispatched by agents-81 run-cursor verifier_checkbox: false # verification stays on the existing judge (config/llm_slots.json) gemini: diff --git a/.github/sync-manifest.yml b/.github/sync-manifest.yml index 786eba03c..a9fad3327 100644 --- a/.github/sync-manifest.yml +++ b/.github/sync-manifest.yml @@ -35,6 +35,7 @@ workflows: sync_mode: create_only # Don't overwrite existing consumer Gate files; fresh repos can still receive this file, so follow #2158 before first seeding. overwrite_repos: - stranske/Template + - iamkayleb/bukay skip_repos: - repo: stranske/Manager-Database reason: "Maintains a fully custom Gate workflow; never overwrite (replaces the hard-coded custom_gate_repos list in maint-68)." @@ -73,6 +74,9 @@ workflows: - source: .github/workflows/agents-72-codex-belt-worker.yml description: "Codex belt worker - executes agent on issues with full prompt and context" + - source: .github/workflows/health-codex-auth-check.yml + description: "Twice-daily CODEX_AUTH_JSON expiry check - opens an auth-expiring issue before the token lapses" + - source: .github/workflows/agents-72-codex-belt-worker-dispatch.yml description: "Codex belt worker dispatch wrapper - allows workflow_dispatch for the worker" diff --git a/.github/workflows/agents-71-codex-belt-dispatcher.yml b/.github/workflows/agents-71-codex-belt-dispatcher.yml index 025a512a9..f5e2c9271 100644 --- a/.github/workflows/agents-71-codex-belt-dispatcher.yml +++ b/.github/workflows/agents-71-codex-belt-dispatcher.yml @@ -20,6 +20,13 @@ on: required: false default: false type: boolean + base_branch: + description: >- + Optional base branch for the agent branch. Overrides the issue's + `` marker and the repository default. + required: false + default: '' + type: string orchestrator_skill_pack: description: >- Optional reference-pack name override for exported Orchestrator skill context on @@ -76,6 +83,13 @@ on: required: false default: false type: boolean + base_branch: + description: >- + Optional base branch for the agent branch. Overrides the issue's + `` marker and the repository default. + required: false + default: '' + type: string orchestrator_skill_pack: description: >- Optional reference-pack name override for exported Orchestrator skill context on @@ -292,12 +306,42 @@ jobs: ]); const { data: repoInfo } = await withRetry((client) => client.rest.repos.get({ owner, repo })); - const base = repoInfo.default_branch; - if (!base) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Base resolution mirrors reusable-agents-issue-bridge.yml: an + // explicit input wins, then the issue's `` + // marker, then the repository default. Without the marker every + // agent branch is cut from the default branch, which silently + // defeats any per-lane workflow (evaluation lanes, release trains, + // long-lived feature bases). + let base = String(process.env.INPUT_BASE_BRANCH || '').trim(); + let baseSource = base ? 'input' : ''; + if (!base) { + try { + const { data: issueData } = await withRetry((client) => + client.rest.issues.get({ owner, repo, issue_number: issueNumber })); + const marker = String(issueData.body || '') + .match(//); + if (marker) { base = marker[1].trim(); baseSource = 'issue-marker'; } + } catch (error) { + core.warning(`Could not read issue body for a base-branch marker: ${error.message}`); + } + } + if (!base) { base = defaultBranch; baseSource = 'default'; } + if (base !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ owner, repo, branch: base })); + } catch (error) { + core.warning(`Base branch '${base}' not found; falling back to '${defaultBranch}'.`); + base = defaultBranch; baseSource = 'default-fallback'; + } + } + core.info(`Base branch: ${base} (source: ${baseSource})`); + let branchPrefix = 'codex/issue-'; try { const { getAgentConfig } = require('./.github/scripts/agent_registry.js'); diff --git a/.github/workflows/agents-auto-pilot.yml b/.github/workflows/agents-auto-pilot.yml index cc24e38ad..01259d1ed 100644 --- a/.github/workflows/agents-auto-pilot.yml +++ b/.github/workflows/agents-auto-pilot.yml @@ -1,6 +1,14 @@ # See docs/ci/AGENTS_POLICY.md for guardrails and override process. name: Agents Auto-Pilot +# Correlating a run to its issue needs the issue number in the run title; +# without it `gh run list` shows only the workflow name and no per-issue +# history can be reconstructed. +run-name: >- + Agents Auto-Pilot + #${{ github.event.issue.number || github.event.pull_request.number || + inputs.issue_number }} + # End-to-end automation: Issue → Format → Optimize → Apply → Agent → Keepalive → Merge # Triggered by: # 1. agents:auto-pilot label (initial trigger) @@ -2880,12 +2888,53 @@ jobs: owner: context.repo.owner, repo: context.repo.repo })); - const baseBranch = repoInfo.default_branch; - if (!baseBranch) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Resolve the pull request base the same way the issue bridge and the + // belt dispatcher do: the issue's `` marker + // wins over the repository default. Auto-pilot opens the pull request + // itself, so without this the marker is honoured when the branch is + // cut and then ignored when the PR is opened — the PR lands on the + // default branch and any per-lane workflow silently collapses. + let baseBranch = defaultBranch; + let baseSource = 'default'; + try { + const { data: baseIssue } = await withRetry((client) => + client.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + })); + const marker = String(baseIssue.body || '') + .match(//); + if (marker) { + const candidate = marker[1].trim(); + if (candidate && candidate !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: candidate, + })); + baseBranch = candidate; + baseSource = 'issue-marker'; + } catch (branchError) { + core.warning( + `Base branch '${candidate}' from the issue marker was not found; ` + + `falling back to '${defaultBranch}'.`); + baseSource = 'default-fallback'; + } + } + } + } catch (markerError) { + core.warning(`Could not read issue #${issueNumber} for a base-branch marker: ${markerError.message}`); + } + core.info(`Base branch: ${baseBranch} (source: ${baseSource})`); + // If a PR already exists for this branch, stop create-pr loop try { const headRef = `${context.repo.owner}:${branchName}`; @@ -3754,12 +3803,53 @@ jobs: owner: context.repo.owner, repo: context.repo.repo })); - const baseBranch = repoInfo.default_branch; - if (!baseBranch) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Resolve the pull request base the same way the issue bridge and the + // belt dispatcher do: the issue's `` marker + // wins over the repository default. Auto-pilot opens the pull request + // itself, so without this the marker is honoured when the branch is + // cut and then ignored when the PR is opened — the PR lands on the + // default branch and any per-lane workflow silently collapses. + let baseBranch = defaultBranch; + let baseSource = 'default'; + try { + const { data: baseIssue } = await withRetry((client) => + client.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + })); + const marker = String(baseIssue.body || '') + .match(//); + if (marker) { + const candidate = marker[1].trim(); + if (candidate && candidate !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: candidate, + })); + baseBranch = candidate; + baseSource = 'issue-marker'; + } catch (branchError) { + core.warning( + `Base branch '${candidate}' from the issue marker was not found; ` + + `falling back to '${defaultBranch}'.`); + baseSource = 'default-fallback'; + } + } + } + } catch (markerError) { + core.warning(`Could not read issue #${issueNumber} for a base-branch marker: ${markerError.message}`); + } + core.info(`Base branch: ${baseBranch} (source: ${baseSource})`); + const scriptsPath = process.env.WORKFLOWS_SCRIPTS_PATH || process.env.GITHUB_WORKSPACE; const { redispatchForceStep } = require( `${scriptsPath}/.github/scripts/auto_pilot_transitions.js` diff --git a/.github/workflows/agents-issue-optimizer.yml b/.github/workflows/agents-issue-optimizer.yml index 0883f3fec..bf46c7016 100644 --- a/.github/workflows/agents-issue-optimizer.yml +++ b/.github/workflows/agents-issue-optimizer.yml @@ -5,7 +5,13 @@ name: Agents Issue Optimizer # nothing and reported 0 for an issue it was re-running every minute. Pin the issue # number into the run name so both trigger types are correlatable. run-name: >- - Agents Issue Optimizer #${{ github.event.issue.number || github.event.inputs.issue_number }} + Agents Issue Optimizer + ${{ (github.event_name == 'workflow_dispatch' || + github.event.label.name == 'agents:format' || + github.event.label.name == 'agents:optimize' || + github.event.label.name == 'agents:apply-suggestions') + && '[work]' || '[noop]' }} + #${{ github.event.issue.number || github.event.inputs.issue_number }} on: issues: @@ -210,6 +216,14 @@ jobs: # `gh run list` defaults to 20 runs, which a tight loop exhausts inside the # window; ask for enough history to actually see the recursion. + # + # Count only runs that could do work. This workflow triggers on EVERY + # `labeled` event, but only agents:format / agents:optimize / + # agents:apply-suggestions (and workflow_dispatch) reach the optimizer; + # every other label spawns a run that exits at the trigger check. Those + # no-ops used to count here, so applying three labels to a new issue + # burned the whole budget before any real work started and the guard + # tripped on legitimate bulk seeding. run-name marks them [noop]. # shellcheck disable=SC2016 count=$(gh run list \ --workflow=agents-issue-optimizer.yml \ @@ -218,7 +232,8 @@ jobs: | jq --arg cutoff "$one_hour_ago" \ --arg issue "#$ISSUE_NUMBER" \ '[.[] | select(.createdAt > $cutoff - and (.displayTitle | endswith($issue))) + and (.displayTitle | endswith($issue)) + and (.displayTitle | contains("[noop]") | not)) ] | length') echo "Optimizer runs for issue #$ISSUE_NUMBER in last hour: $count" diff --git a/.github/workflows/agents-model-profile-trial.yml b/.github/workflows/agents-model-profile-trial.yml index 49f09a92a..563bef27b 100644 --- a/.github/workflows/agents-model-profile-trial.yml +++ b/.github/workflows/agents-model-profile-trial.yml @@ -49,7 +49,7 @@ permissions: jobs: trial: - uses: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + uses: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 permissions: contents: read with: @@ -61,6 +61,6 @@ jobs: packet_hash: ${{ inputs.packet_hash }} launch_ordinal: ${{ fromJSON(inputs.launch_ordinal) }} expected_source_sha: ${{ inputs.expected_source_sha }} - runner_sha: 822e323eefba3edb640e0bd9c922caec6fffee65 + runner_sha: e85edadb246e41d172a0c79fad147752d1df9ea9 secrets: CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} diff --git a/.github/workflows/agents-verify-to-new-pr.yml b/.github/workflows/agents-verify-to-new-pr.yml index d0682a3f4..25c1daf1a 100644 --- a/.github/workflows/agents-verify-to-new-pr.yml +++ b/.github/workflows/agents-verify-to-new-pr.yml @@ -29,6 +29,9 @@ jobs: create-new-pr: if: github.event.label.name == 'verify:create-new-pr' runs-on: ubuntu-latest + env: + WORKFLOWS_APP_ID: ${{ secrets.WORKFLOWS_APP_ID || '' }} + WORKFLOWS_APP_PRIVATE_KEY: ${{ secrets.WORKFLOWS_APP_PRIVATE_KEY || '' }} steps: - name: Check PR is merged id: check-merged @@ -45,21 +48,44 @@ jobs: core.setOutput('pr_number', pr.number); core.setOutput('pr_title', pr.title); + - name: Mint GitHub App token + id: app_token + if: >- + steps.check-merged.outputs.merged == 'true' && + env.WORKFLOWS_APP_ID != '' && env.WORKFLOWS_APP_PRIVATE_KEY != '' + continue-on-error: true + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ env.WORKFLOWS_APP_ID }} + private-key: ${{ env.WORKFLOWS_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + - name: Select GitHub token id: select-token if: steps.check-merged.outputs.merged == 'true' env: + APP_TOKEN: ${{ steps.app_token.outputs.token }} OWNER_PR_PAT: ${{ secrets.OWNER_PR_PAT }} SERVICE_BOT_PAT: ${{ secrets.SERVICE_BOT_PAT }} GITHUB_TOKEN: ${{ github.token }} run: | - if [ -n "$OWNER_PR_PAT" ]; then + # Prefer an App installation token. Issues and labels created with + # GITHUB_TOKEN raise no events for other workflows (GitHub's loop + # guard), so a follow-up issue created that way is never picked up by + # auto-pilot and waits until a human re-applies its label by hand. + if [ -n "$APP_TOKEN" ]; then + echo "token=$APP_TOKEN" >> "$GITHUB_OUTPUT" + echo "source=workflows-app" >> "$GITHUB_OUTPUT" + elif [ -n "$OWNER_PR_PAT" ]; then echo "token=$OWNER_PR_PAT" >> "$GITHUB_OUTPUT" echo "source=owner-pat" >> "$GITHUB_OUTPUT" elif [ -n "$SERVICE_BOT_PAT" ]; then echo "token=$SERVICE_BOT_PAT" >> "$GITHUB_OUTPUT" echo "source=service-bot" >> "$GITHUB_OUTPUT" else + echo "::warning::No App token or PAT; using GITHUB_TOKEN." + echo "::warning::The follow-up issue will NOT trigger auto-pilot." + echo "::warning::Set WORKFLOWS_APP_ID + _PRIVATE_KEY, or SERVICE_BOT_PAT." echo "token=$GITHUB_TOKEN" >> "$GITHUB_OUTPUT" echo "source=github-token" >> "$GITHUB_OUTPUT" fi @@ -211,6 +237,23 @@ jobs: fs.writeFileSync('original_issue.txt', originalIssueBody); + // --- Base-branch propagation --- + // Carry the merged PR's base forward when it is NOT the default + // branch (evaluation lanes such as eval/claude). Without this the + // follow-up PR targets the default branch and the fix escapes the + // lane it belongs to. The bridge reads this marker back. + const prBaseRef = context.payload.pull_request.base?.ref || ''; + const repoDefaultBranch = + context.payload.repository?.default_branch || ''; + const nonDefaultBase = + prBaseRef && prBaseRef !== repoDefaultBranch ? prBaseRef : ''; + if (nonDefaultBase) { + core.info( + `PR base '${nonDefaultBase}' is non-default; follow-up will target it.`, + ); + } + core.setOutput('pr_base_ref', nonDefaultBase); + // --- Chain depth tracking (P0) --- // Extract follow-up-depth from original issue body or PR body // Format: @@ -572,6 +615,7 @@ jobs: ORIGINAL_ISSUE_TITLE: ${{ steps.collect.outputs.original_issue_title }} PR_NUMBER: ${{ steps.check-merged.outputs.pr_number }} FOLLOW_UP_DEPTH: ${{ steps.chain-check.outputs.next_depth }} + PR_BASE_REF: ${{ steps.collect.outputs.pr_base_ref }} run: | # Generate using Python script python scripts/langchain/followup_issue_generator.py \ @@ -586,6 +630,11 @@ jobs: # Inject chain depth marker into generated body depth="${FOLLOW_UP_DEPTH:-1}" marker="" + # Preserve a non-default base (evaluation lanes) so the follow-up PR + # targets the same branch the originating PR merged into. + if [ -n "${PR_BASE_REF:-}" ]; then + marker="$(printf '%s\n%s' "$marker" "")" + fi jq --arg marker "$marker" \ '.body = $marker + "\n" + .body' \ followup_issue.json > followup_issue_tmp.json @@ -613,11 +662,13 @@ jobs: env: FOLLOW_UP_DEPTH: ${{ steps.chain-check.outputs.next_depth }} EXTRACTED_VERDICT: ${{ steps.extract-verdict.outputs.verdict }} + PR_BASE_REF: ${{ steps.collect.outputs.pr_base_ref }} with: github-token: ${{ steps.select-token.outputs.token }} script: | // Fallback to structured extraction if Python script fails const fs = require('fs'); + const prBaseRef = process.env.PR_BASE_REF || ''; const prNumber = context.payload.pull_request.number; const prUrl = context.payload.pull_request.html_url; const depth = process.env.FOLLOW_UP_DEPTH || '1'; @@ -738,6 +789,7 @@ jobs: const issueBody = [ ``, + ...(prBaseRef ? [``] : []), '## Why', '', `PR #${prNumber} was verified with verdict **${verdict}**. ` + diff --git a/.github/workflows/maint-68-sync-consumer-repos.yml b/.github/workflows/maint-68-sync-consumer-repos.yml index 15a1c1c6b..128a5df4d 100644 --- a/.github/workflows/maint-68-sync-consumer-repos.yml +++ b/.github/workflows/maint-68-sync-consumer-repos.yml @@ -80,6 +80,7 @@ env: stranske/learning-management-system stranske/Fine-Art-Archive stranske/Orchestrator + iamkayleb/bukay concurrency: group: sync-consumer-repos-${{ github.repository }}-${{ github.ref }} diff --git a/.github/workflows/reusable-10-ci-python.yml b/.github/workflows/reusable-10-ci-python.yml index a576dfe19..15a37e168 100644 --- a/.github/workflows/reusable-10-ci-python.yml +++ b/.github/workflows/reusable-10-ci-python.yml @@ -402,7 +402,7 @@ jobs: - name: Checkout Workflows repo for actions uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows # @main-only policy (issue #2346): `@main` is the single supported pin, # so checking out the helper layer at `main` is intentional and correct # — callers ride `@main`, hence main IS the pinned ref. The @@ -721,7 +721,7 @@ jobs: - name: Checkout Workflows helper uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows # @main-only policy (issue #2346): `@main` is the single supported pin, # so checking out the helper layer at `main` is intentional and correct # — callers ride `@main`, hence main IS the pinned ref. The @@ -1585,7 +1585,7 @@ jobs: if: ${{ inputs.cache }} uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows # @main-only policy (issue #2346): `@main` is the single supported pin, # so checking out the helper layer at `main` is intentional and correct # — callers ride `@main`, hence main IS the pinned ref. The @@ -2532,7 +2532,7 @@ jobs: }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows # @main-only policy (issue #2346): `@main` is the single supported pin, # so checking out the helper layer at `main` is intentional and correct # - callers ride `@main`, hence main IS the pinned ref. The @@ -2638,7 +2638,7 @@ jobs: - name: Checkout Workflows repo for actions uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows # @main-only policy (issue #2346): `@main` is the single supported pin, # so checking out the helper layer at `main` is intentional and correct # — callers ride `@main`, hence main IS the pinned ref. The diff --git a/.github/workflows/reusable-16-agents.yml b/.github/workflows/reusable-16-agents.yml index 617e58d0e..f4ea186d9 100644 --- a/.github/workflows/reusable-16-agents.yml +++ b/.github/workflows/reusable-16-agents.yml @@ -1018,7 +1018,7 @@ jobs: - name: Checkout resolver action uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_workflow_ref.outputs.ref }} sparse-checkout: | .github/actions/resolve-default-branch @@ -1043,7 +1043,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | scripts diff --git a/.github/workflows/reusable-18-autofix.yml b/.github/workflows/reusable-18-autofix.yml index c0757bddb..5e99e9967 100644 --- a/.github/workflows/reusable-18-autofix.yml +++ b/.github/workflows/reusable-18-autofix.yml @@ -253,7 +253,11 @@ jobs: set -euo pipefail workflow_repo="${WORKFLOW_REF%%/.github/workflows/*}" ref="main" - if [ "$workflow_repo" = "stranske/Workflows" ]; then + # Owner-agnostic on purpose: this asks "did the run start inside the + # control-plane repo itself?", so only the repository NAME is the signal. + # Comparing against a literal `stranske/Workflows` made the branch dead in + # a fork, silently degrading a self-test's pinned ref to `main`. + if [ "${workflow_repo##*/}" = "Workflows" ]; then ref="${WORKFLOW_REF##*@}" fi if [ -z "$ref" ] || [ "$ref" = "$WORKFLOW_REF" ]; then @@ -264,7 +268,7 @@ jobs: - name: Checkout resolver action uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_workflow_ref.outputs.ref }} sparse-checkout: | .github/actions/resolve-default-branch @@ -284,7 +288,7 @@ jobs: - name: Checkout Workflows scripts (for autofix utilities) uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | scripts @@ -1469,7 +1473,7 @@ jobs: if: steps.guard.outputs.skip != 'true' uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | .github/actions/build-pr-comment diff --git a/.github/workflows/reusable-19-dependency-repair-contract.yml b/.github/workflows/reusable-19-dependency-repair-contract.yml index 7904ae751..8fcffb1ea 100644 --- a/.github/workflows/reusable-19-dependency-repair-contract.yml +++ b/.github/workflows/reusable-19-dependency-repair-contract.yml @@ -43,7 +43,7 @@ jobs: - name: Checkout trusted contract implementation uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: main sparse-checkout: | .github/scripts/dependency-repair-contract.js diff --git a/.github/workflows/reusable-20-pr-meta.yml b/.github/workflows/reusable-20-pr-meta.yml index e6d388930..679da9f9e 100644 --- a/.github/workflows/reusable-20-pr-meta.yml +++ b/.github/workflows/reusable-20-pr-meta.yml @@ -1,6 +1,6 @@ # Reusable PR Meta workflow for consumer repos # Provides keepalive detection and dispatch functionality using dual checkout pattern -# Scripts are fetched from stranske/Workflows - no local scripts needed in consumer repo +# Scripts are fetched from iamkayleb/Workflows - no local scripts needed in consumer repo name: Reusable 20 PR Meta on: @@ -149,7 +149,7 @@ jobs: - name: Checkout resolver action uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_workflow_ref.outputs.ref }} sparse-checkout: | .github/actions/resolve-default-branch @@ -175,7 +175,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | scripts @@ -291,7 +291,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: main sparse-checkout: | .github/scripts @@ -368,7 +368,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: main sparse-checkout: | .github/scripts @@ -525,7 +525,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: main sparse-checkout: | .github/scripts diff --git a/.github/workflows/reusable-agents-issue-bridge.yml b/.github/workflows/reusable-agents-issue-bridge.yml index 6ac523aea..0e0602a3d 100644 --- a/.github/workflows/reusable-agents-issue-bridge.yml +++ b/.github/workflows/reusable-agents-issue-bridge.yml @@ -50,6 +50,14 @@ on: required: false type: string default: "false" + base_branch: + description: >- + Optional base branch for the automation PR. Overrides the + `` issue marker and the repository default. + Used for evaluation lanes that must not target the default branch. + required: false + type: string + default: "" secrets: service_bot_pat: description: "PAT for service bot operations" @@ -279,6 +287,10 @@ jobs: - name: Resolve base and head refs id: refs uses: actions/github-script@v9 + env: + # Passed via env (not inline interpolation) so a caller-supplied + # value can never be injected into the script body. + INPUT_BASE_BRANCH: ${{ inputs.base_branch }} with: script: | const { owner, repo } = context.repo; @@ -300,13 +312,64 @@ jobs: repo, }), ); - const base = data.default_branch; - if (!base) { + const defaultBranch = data.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } const issue = Number('${{ steps.ctx.outputs.issue }}'); + // Base-branch resolution, highest priority first: + // 1. explicit `base_branch` input (caller override) + // 2. `` marker in the issue body + // 3. repository default branch + // Evaluation lanes rely on (1)/(2) so agent PRs land on + // eval/ instead of the default branch. + let base = String(process.env.INPUT_BASE_BRANCH || '').trim(); + let baseSource = base ? 'input' : ''; + + if (!base && issue) { + try { + const { data: issueData } = await withRetry(() => + api.rest.issues.get({ owner, repo, issue_number: issue }), + ); + const marker = String(issueData.body || '').match( + //, + ); + if (marker) { + base = marker[1].trim(); + baseSource = 'issue-marker'; + } + } catch (error) { + core.warning( + `Could not read issue #${issue} for a base-branch marker: ${error.message}`, + ); + } + } + + if (!base) { + base = defaultBranch; + baseSource = 'default'; + } + + // Never target a branch that does not exist: a stale or mistyped + // marker would otherwise wedge every dispatch for that issue. + if (base !== defaultBranch) { + try { + await withRetry(() => + api.rest.repos.getBranch({ owner, repo, branch: base }), + ); + } catch (error) { + core.warning( + `Base branch '${base}' (from ${baseSource}) not found; ` + + `falling back to '${defaultBranch}'.`, + ); + base = defaultBranch; + baseSource = 'default-fallback'; + } + } + core.info(`Resolved PR base: ${base} (source: ${baseSource})`); + let branchPrefix = 'codex/issue-'; if (agentKey) { try { diff --git a/.github/workflows/reusable-agents-pr-health.yml b/.github/workflows/reusable-agents-pr-health.yml index 65c28c87a..37128e58a 100644 --- a/.github/workflows/reusable-agents-pr-health.yml +++ b/.github/workflows/reusable-agents-pr-health.yml @@ -216,7 +216,7 @@ jobs: - name: Checkout Workflows helpers uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows sparse-checkout: | .github/actions/setup-api-client .github/scripts/error_classifier.js @@ -593,7 +593,7 @@ jobs: - name: Checkout Workflows helpers uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows sparse-checkout: | .github/scripts/error_classifier.js .github/scripts/github-api-with-retry.js @@ -807,7 +807,7 @@ jobs: - name: Checkout Workflows helpers uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows sparse-checkout: | .github/scripts/error_classifier.js .github/scripts/github-api-with-retry.js diff --git a/.github/workflows/reusable-agents-verifier.yml b/.github/workflows/reusable-agents-verifier.yml index 3d5c0d3de..b1f94d296 100644 --- a/.github/workflows/reusable-agents-verifier.yml +++ b/.github/workflows/reusable-agents-verifier.yml @@ -118,7 +118,7 @@ jobs: uses: actions/checkout@v7 with: token: ${{ steps.app_token.outputs.token || github.token }} - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | .github/actions/setup-api-client diff --git a/.github/workflows/reusable-backplane-conformance.yml b/.github/workflows/reusable-backplane-conformance.yml index 25e928dbf..81926210f 100644 --- a/.github/workflows/reusable-backplane-conformance.yml +++ b/.github/workflows/reusable-backplane-conformance.yml @@ -12,7 +12,7 @@ name: Reusable Backplane Conformance # evidence-object/v1); a consumer is never failed for not emitting a run.json. # # The reusable workflow reads the CANONICAL registry + schemas + validator from -# stranske/Workflows@ (default @main) -- never from the +# iamkayleb/Workflows@ (default @main) -- never from the # participant's copy -- so there is exactly one registry. on: @@ -94,7 +94,7 @@ jobs: - name: Checkout Workflows contract uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.contract_ref }} path: .backplane-contract diff --git a/.github/workflows/reusable-claude-run.yml b/.github/workflows/reusable-claude-run.yml index 73486908d..60dcde7c5 100644 --- a/.github/workflows/reusable-claude-run.yml +++ b/.github/workflows/reusable-claude-run.yml @@ -288,7 +288,7 @@ jobs: - name: Checkout Workflows run-base action uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.workflows_ref }} path: .workflows-actions sparse-checkout: | diff --git a/.github/workflows/reusable-codex-run.yml b/.github/workflows/reusable-codex-run.yml index fd36375b2..a393f9815 100644 --- a/.github/workflows/reusable-codex-run.yml +++ b/.github/workflows/reusable-codex-run.yml @@ -341,7 +341,7 @@ jobs: - name: Checkout Workflows run-base action uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.workflows_ref }} path: .workflows-actions sparse-checkout: | diff --git a/.github/workflows/reusable-cursor-run.yml b/.github/workflows/reusable-cursor-run.yml index 513eab677..bb2ef0e59 100644 --- a/.github/workflows/reusable-cursor-run.yml +++ b/.github/workflows/reusable-cursor-run.yml @@ -309,7 +309,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.workflows_ref }} path: .workflows-lib sparse-checkout: | diff --git a/.github/workflows/reusable-gemini-run.yml b/.github/workflows/reusable-gemini-run.yml index b6c8ca707..7023d03c5 100644 --- a/.github/workflows/reusable-gemini-run.yml +++ b/.github/workflows/reusable-gemini-run.yml @@ -317,7 +317,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@v7 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.workflows_ref }} path: .workflows-lib sparse-checkout: | diff --git a/.github/workflows/reusable-model-profile-trial.yml b/.github/workflows/reusable-model-profile-trial.yml index 5f6dd8709..3df564048 100644 --- a/.github/workflows/reusable-model-profile-trial.yml +++ b/.github/workflows/reusable-model-profile-trial.yml @@ -61,13 +61,13 @@ jobs: env: PINNED_RUNNER_SHA: ${{ inputs.runner_sha }} PINNED_RUNNER_REF: >- - stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@${{ inputs.runner_sha }} + iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@${{ inputs.runner_sha }} ZERO_SOURCE_MANIFEST: sha256:0000000000000000000000000000000000000000000000000000000000000000 steps: - name: Checkout immutable trial runner uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.runner_sha }} path: runner-src persist-credentials: false @@ -91,7 +91,7 @@ jobs: exit 1 fi remote_main="$( - git ls-remote https://github.com/stranske/Workflows.git refs/heads/main | + git ls-remote https://github.com/iamkayleb/Workflows.git refs/heads/main | awk 'NR == 1 { print $1 }' )" if [ -z "$remote_main" ] || [ "$remote_main" != "$EXPECTED_SOURCE_SHA" ]; then @@ -102,7 +102,7 @@ jobs: - name: Checkout exact trial source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ inputs.expected_source_sha }} path: target-src persist-credentials: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71a7ae097..c667f88aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: # Python linting and formatting - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.2 # Synced with autofix-versions.env + rev: v0.16.4 # Synced with autofix-versions.env hooks: - id: ruff args: ["--fix"] diff --git a/agents/auto-pilot-755.md b/agents/auto-pilot-755.md deleted file mode 100644 index 341779a2d..000000000 --- a/agents/auto-pilot-755.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/auto-pilot-763.md b/agents/auto-pilot-763.md deleted file mode 100644 index fdb46dfb5..000000000 --- a/agents/auto-pilot-763.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/auto-pilot-768.md b/agents/auto-pilot-768.md deleted file mode 100644 index d743ee523..000000000 --- a/agents/auto-pilot-768.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/auto-pilot-771.md b/agents/auto-pilot-771.md deleted file mode 100644 index 8e4adfa4f..000000000 --- a/agents/auto-pilot-771.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/auto-pilot-785.md b/agents/auto-pilot-785.md deleted file mode 100644 index 9b35c58f9..000000000 --- a/agents/auto-pilot-785.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/auto-pilot-821.md b/agents/auto-pilot-821.md deleted file mode 100644 index b6bd8780c..000000000 --- a/agents/auto-pilot-821.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-10.md b/agents/codex-10.md deleted file mode 100644 index 3bd38eda6..000000000 --- a/agents/codex-10.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1001.md b/agents/codex-1001.md deleted file mode 100644 index c2cfb5042..000000000 --- a/agents/codex-1001.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1003.md b/agents/codex-1003.md deleted file mode 100644 index f8423cc30..000000000 --- a/agents/codex-1003.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1005.md b/agents/codex-1005.md deleted file mode 100644 index eb5af58bc..000000000 --- a/agents/codex-1005.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1006.md b/agents/codex-1006.md deleted file mode 100644 index a0311d73a..000000000 --- a/agents/codex-1006.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-101.md b/agents/codex-101.md deleted file mode 100644 index c38e04466..000000000 --- a/agents/codex-101.md +++ /dev/null @@ -1,5 +0,0 @@ - -# Trigger keepalive test 2025-12-24T14:43:06+00:00 - -# Test Codex CLI with ChatGPT auth 2025-12-24T16:57:45+00:00 -# Test with Codex CLI install fix 2025-12-24T17:16:00+00:00 diff --git a/agents/codex-1011.md b/agents/codex-1011.md deleted file mode 100644 index 2e35c793b..000000000 --- a/agents/codex-1011.md +++ /dev/null @@ -1,66 +0,0 @@ -# Issue 1011: Add rebalancer coverage and sanitize blank ranks - -**Source**: https://github.com/stranske/Trend_Model_Project/issues/1011 - -## Why - -The Rebalancer helper in the multi-period workflow lacks sufficient unit test coverage for critical entry, exit, and weighting paths. Additionally, blank or whitespace-only fund names can flow through the ranking system unchanged, leading to empty string selections that cause unexpected downstream behavior. Empty multi-period demo exports also need placeholder rows to prevent parsing errors. - -## Scope - -- Add comprehensive unit tests for the multi-period Rebalancer helper covering entry triggers, exit triggers, and weighting strategies -- Normalize blank or whitespace-only column labels in rank_selection before processing to avoid empty fund name selections -- Ensure empty multi-period period exports include placeholder rows with appropriate messages -- Update lockfile dependencies (hypothesis 6.138.16→6.138.17, xlsxwriter 3.2.8→3.2.9) - -## Non-Goals - -- Changing the core Rebalancer algorithm or trigger logic -- Refactoring existing multi-period workflow structure -- Adding new weighting strategies beyond existing score_prop_bayes -- Modifying the export file format structure - -## Tasks - -- [x] Add unit tests for Rebalancer consecutive soft strikes (exit path) -- [x] Add unit tests for Rebalancer hard exit threshold override -- [x] Add unit tests for hard entry candidates filling capacity first -- [x] Add unit tests for eligible candidates accumulating strikes -- [x] Add unit tests for score-proportional weighting behavior -- [x] Add unit tests for score-proportional fallback to equal weights -- [x] Add unit tests for empty holdings edge case -- [x] Sanitize blank/whitespace column names in rank_select_funds before processing -- [x] Add _ensure_periods_placeholder helper for empty period exports -- [x] Apply placeholder logic to phase1_multi and multi_period exports in empty demo -- [x] Update requirements.lock with hypothesis 6.138.17 and xlsxwriter 3.2.9 -- [x] Verify all tests pass with ./scripts/run_tests.sh - -## Acceptance Criteria - -- [x] All unit tests in test_multi_period_rebalancer.py pass -- [x] Rebalancer correctly removes funds after consecutive soft strikes -- [x] Rebalancer immediately drops funds below hard exit threshold -- [x] Hard entry candidates consume capacity before auto entries -- [x] Eligible candidates join after accumulating required strikes -- [x] Score-proportional weighting produces normalized weights favoring higher scores -- [x] Score-proportional weighting falls back to equal weights when zscore column is missing -- [x] Empty holdings return empty Series without errors -- [x] rank_select_funds strips whitespace from column names and renames blank columns to Unnamed_N -- [x] Empty multi-period exports include placeholder rows with descriptive messages -- [x] All existing tests continue to pass after changes -- [x] Requirements lockfile reflects updated dependency versions - -## Implementation Notes - -Files modified: -- `tests/test_multi_period_rebalancer.py` - New comprehensive unit test suite (130 lines) -- `src/trend_analysis/core/rank_selection.py` - Column name sanitization logic in rank_select_funds -- `scripts/run_multi_demo.py` - Added _ensure_periods_placeholder helper and applied to empty exports -- `requirements.lock` - Updated hypothesis and xlsxwriter versions - -The column sanitization in rank_select_funds ensures uniqueness after stripping by appending numeric suffixes when needed. The placeholder helper populates CSV, JSON, and TXT period exports with {"period": "N/A", "note": ""} when empty. - -Testing command: `./scripts/run_tests.sh` - -**Status**: ✅ Completed -**ChatGPT Task**: https://chatgpt.com/codex/tasks/task_e_68c8c29578f08331969f58373bf6896a diff --git a/agents/codex-1015.md b/agents/codex-1015.md deleted file mode 100644 index 002befa18..000000000 --- a/agents/codex-1015.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1021.md b/agents/codex-1021.md deleted file mode 100644 index 7ca2bda9f..000000000 --- a/agents/codex-1021.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1025.md b/agents/codex-1025.md deleted file mode 100644 index e486510ee..000000000 --- a/agents/codex-1025.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1026.md b/agents/codex-1026.md deleted file mode 100644 index c6f862b6a..000000000 --- a/agents/codex-1026.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1038.md b/agents/codex-1038.md deleted file mode 100644 index 2499286cc..000000000 --- a/agents/codex-1038.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1039.md b/agents/codex-1039.md deleted file mode 100644 index aa3b59e0d..000000000 --- a/agents/codex-1039.md +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/agents/codex-1063.md b/agents/codex-1063.md deleted file mode 100644 index d4b432c59..000000000 --- a/agents/codex-1063.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-11.md b/agents/codex-11.md deleted file mode 100644 index ab374e167..000000000 --- a/agents/codex-11.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-12.md b/agents/codex-12.md deleted file mode 100644 index eeb3113dd..000000000 --- a/agents/codex-12.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-123.md b/agents/codex-123.md deleted file mode 100644 index 3c65ee8dd..000000000 --- a/agents/codex-123.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1267.md b/agents/codex-1267.md deleted file mode 100644 index b8364c037..000000000 --- a/agents/codex-1267.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1296.md b/agents/codex-1296.md deleted file mode 100644 index 224c2354d..000000000 --- a/agents/codex-1296.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-13.md b/agents/codex-13.md deleted file mode 100644 index d2004b66c..000000000 --- a/agents/codex-13.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1322.md b/agents/codex-1322.md deleted file mode 100644 index cc912d996..000000000 --- a/agents/codex-1322.md +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/agents/codex-1331.md b/agents/codex-1331.md deleted file mode 100644 index d2a3cfd19..000000000 --- a/agents/codex-1331.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1342.md b/agents/codex-1342.md deleted file mode 100644 index a3747dbcd..000000000 --- a/agents/codex-1342.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1353.md b/agents/codex-1353.md deleted file mode 100644 index 7bd63d480..000000000 --- a/agents/codex-1353.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1355.md b/agents/codex-1355.md deleted file mode 100644 index 21c90b90f..000000000 --- a/agents/codex-1355.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1365.md b/agents/codex-1365.md deleted file mode 100644 index 08b835d4d..000000000 --- a/agents/codex-1365.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1366.md b/agents/codex-1366.md deleted file mode 100644 index 10df3e2e2..000000000 --- a/agents/codex-1366.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1371.md b/agents/codex-1371.md deleted file mode 100644 index 74f692f6c..000000000 --- a/agents/codex-1371.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1385.md b/agents/codex-1385.md deleted file mode 100644 index f0e95aec3..000000000 --- a/agents/codex-1385.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1395.md b/agents/codex-1395.md deleted file mode 100644 index 89e7100ad..000000000 --- a/agents/codex-1395.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-14.md b/agents/codex-14.md deleted file mode 100644 index 7847c03d7..000000000 --- a/agents/codex-14.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1404.md b/agents/codex-1404.md deleted file mode 100644 index 6f66e1748..000000000 --- a/agents/codex-1404.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1406.md b/agents/codex-1406.md deleted file mode 100644 index b0b5fb112..000000000 --- a/agents/codex-1406.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1407.md b/agents/codex-1407.md deleted file mode 100644 index 218ff6637..000000000 --- a/agents/codex-1407.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1416.md b/agents/codex-1416.md deleted file mode 100644 index 3ceb8437a..000000000 --- a/agents/codex-1416.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-1425.md b/agents/codex-1425.md deleted file mode 100644 index fd0208bd5..000000000 --- a/agents/codex-1425.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-144.md b/agents/codex-144.md deleted file mode 100644 index 668c9cc10..000000000 --- a/agents/codex-144.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-15.md b/agents/codex-15.md deleted file mode 100644 index e9c95e321..000000000 --- a/agents/codex-15.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-153.md b/agents/codex-153.md deleted file mode 100644 index f226439a1..000000000 --- a/agents/codex-153.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-157.md b/agents/codex-157.md deleted file mode 100644 index 35f6cb5c6..000000000 --- a/agents/codex-157.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-16.md b/agents/codex-16.md deleted file mode 100644 index a608de089..000000000 --- a/agents/codex-16.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-160.md b/agents/codex-160.md deleted file mode 100644 index c76310f0e..000000000 --- a/agents/codex-160.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-164.md b/agents/codex-164.md deleted file mode 100644 index 408c743f2..000000000 --- a/agents/codex-164.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-167.md b/agents/codex-167.md deleted file mode 100644 index 0a14b7024..000000000 --- a/agents/codex-167.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-17.md b/agents/codex-17.md deleted file mode 100644 index 0ae41736c..000000000 --- a/agents/codex-17.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-170.md b/agents/codex-170.md deleted file mode 100644 index 73eccd8b3..000000000 --- a/agents/codex-170.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-2.md b/agents/codex-2.md deleted file mode 100644 index ce6f5204c..000000000 --- a/agents/codex-2.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-213.md b/agents/codex-213.md deleted file mode 100644 index aa9069319..000000000 --- a/agents/codex-213.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-23.md b/agents/codex-23.md deleted file mode 100644 index e81c2e0e3..000000000 --- a/agents/codex-23.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-271.md b/agents/codex-271.md deleted file mode 100644 index 1a8fd2c43..000000000 --- a/agents/codex-271.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-287.md b/agents/codex-287.md deleted file mode 100644 index 4dfdaca5c..000000000 --- a/agents/codex-287.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-288.md b/agents/codex-288.md deleted file mode 100644 index 6c3fb74a0..000000000 --- a/agents/codex-288.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-297.md b/agents/codex-297.md deleted file mode 100644 index 0a0a3251c..000000000 --- a/agents/codex-297.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-299.md b/agents/codex-299.md deleted file mode 100644 index b59b82b5f..000000000 --- a/agents/codex-299.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-302.md b/agents/codex-302.md deleted file mode 100644 index 83da77f4d..000000000 --- a/agents/codex-302.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-356.md b/agents/codex-356.md deleted file mode 100644 index a941b8976..000000000 --- a/agents/codex-356.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-359.md b/agents/codex-359.md deleted file mode 100644 index 3129eaab1..000000000 --- a/agents/codex-359.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-362.md b/agents/codex-362.md deleted file mode 100644 index 9bab13e41..000000000 --- a/agents/codex-362.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-365.md b/agents/codex-365.md deleted file mode 100644 index 2df543cc2..000000000 --- a/agents/codex-365.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-368.md b/agents/codex-368.md deleted file mode 100644 index 8e5a53196..000000000 --- a/agents/codex-368.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-371.md b/agents/codex-371.md deleted file mode 100644 index 33b4a5ed1..000000000 --- a/agents/codex-371.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-377.md b/agents/codex-377.md deleted file mode 100644 index 9d8a03585..000000000 --- a/agents/codex-377.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-380.md b/agents/codex-380.md deleted file mode 100644 index c0446edc9..000000000 --- a/agents/codex-380.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-387.md b/agents/codex-387.md deleted file mode 100644 index 083ff9f5c..000000000 --- a/agents/codex-387.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-392.md b/agents/codex-392.md deleted file mode 100644 index 700236e7f..000000000 --- a/agents/codex-392.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-397.md b/agents/codex-397.md deleted file mode 100644 index e111db33a..000000000 --- a/agents/codex-397.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-399.md b/agents/codex-399.md deleted file mode 100644 index e66cbd18d..000000000 --- a/agents/codex-399.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-4.md b/agents/codex-4.md deleted file mode 100644 index a1c08ef27..000000000 --- a/agents/codex-4.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-405.md b/agents/codex-405.md deleted file mode 100644 index 005f3eee3..000000000 --- a/agents/codex-405.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-439.md b/agents/codex-439.md deleted file mode 100644 index 0aa6b51a5..000000000 --- a/agents/codex-439.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-453.md b/agents/codex-453.md deleted file mode 100644 index 6f1270ce0..000000000 --- a/agents/codex-453.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-454.md b/agents/codex-454.md deleted file mode 100644 index 07c336f6a..000000000 --- a/agents/codex-454.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-455.md b/agents/codex-455.md deleted file mode 100644 index faf570b08..000000000 --- a/agents/codex-455.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-456.md b/agents/codex-456.md deleted file mode 100644 index a6496e555..000000000 --- a/agents/codex-456.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-457.md b/agents/codex-457.md deleted file mode 100644 index 3b8220174..000000000 --- a/agents/codex-457.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-477.md b/agents/codex-477.md deleted file mode 100644 index 2ca34ee29..000000000 --- a/agents/codex-477.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-478.md b/agents/codex-478.md deleted file mode 100644 index e5d13c4c1..000000000 --- a/agents/codex-478.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-480.md b/agents/codex-480.md deleted file mode 100644 index 5b477d03b..000000000 --- a/agents/codex-480.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-481.md b/agents/codex-481.md deleted file mode 100644 index db39c0761..000000000 --- a/agents/codex-481.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-482.md b/agents/codex-482.md deleted file mode 100644 index 7b028d735..000000000 --- a/agents/codex-482.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-483.md b/agents/codex-483.md deleted file mode 100644 index df8589d4a..000000000 --- a/agents/codex-483.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-518.md b/agents/codex-518.md deleted file mode 100644 index c1f757f5e..000000000 --- a/agents/codex-518.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-540.md b/agents/codex-540.md deleted file mode 100644 index f623f883e..000000000 --- a/agents/codex-540.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-545.md b/agents/codex-545.md deleted file mode 100644 index 223c675b4..000000000 --- a/agents/codex-545.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-567.md b/agents/codex-567.md deleted file mode 100644 index 55d642a4c..000000000 --- a/agents/codex-567.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-581.md b/agents/codex-581.md deleted file mode 100644 index 8f1a08cdb..000000000 --- a/agents/codex-581.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-582.md b/agents/codex-582.md deleted file mode 100644 index 7f9af6f7c..000000000 --- a/agents/codex-582.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-690.md b/agents/codex-690.md deleted file mode 100644 index db791e0dd..000000000 --- a/agents/codex-690.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-692.md b/agents/codex-692.md deleted file mode 100644 index 212362919..000000000 --- a/agents/codex-692.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-693.md b/agents/codex-693.md deleted file mode 100644 index fe9b356e2..000000000 --- a/agents/codex-693.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-719.md b/agents/codex-719.md deleted file mode 100644 index 16a3ac036..000000000 --- a/agents/codex-719.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-723.md b/agents/codex-723.md deleted file mode 100644 index 72061212c..000000000 --- a/agents/codex-723.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-725.md b/agents/codex-725.md deleted file mode 100644 index 0243369be..000000000 --- a/agents/codex-725.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-729.md b/agents/codex-729.md deleted file mode 100644 index 2924fffd5..000000000 --- a/agents/codex-729.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-73.md b/agents/codex-73.md deleted file mode 100644 index 0749583e0..000000000 --- a/agents/codex-73.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-74.md b/agents/codex-74.md deleted file mode 100644 index ad9046a89..000000000 --- a/agents/codex-74.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-75.md b/agents/codex-75.md deleted file mode 100644 index 1d683792d..000000000 --- a/agents/codex-75.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-76.md b/agents/codex-76.md deleted file mode 100644 index a7bb5c990..000000000 --- a/agents/codex-76.md +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/agents/codex-77.md b/agents/codex-77.md deleted file mode 100644 index 182a27acb..000000000 --- a/agents/codex-77.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-78.md b/agents/codex-78.md deleted file mode 100644 index d6d39899e..000000000 --- a/agents/codex-78.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-79.md b/agents/codex-79.md deleted file mode 100644 index 1b847734c..000000000 --- a/agents/codex-79.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-8.md b/agents/codex-8.md deleted file mode 100644 index f9215a45d..000000000 --- a/agents/codex-8.md +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/agents/codex-875.md b/agents/codex-875.md deleted file mode 100644 index 3d2ee565a..000000000 --- a/agents/codex-875.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-893.md b/agents/codex-893.md deleted file mode 100644 index 90bcb4356..000000000 --- a/agents/codex-893.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-895.md b/agents/codex-895.md deleted file mode 100644 index 29dfd25a5..000000000 --- a/agents/codex-895.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-9.md b/agents/codex-9.md deleted file mode 100644 index 3fafe21b7..000000000 --- a/agents/codex-9.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-92.md b/agents/codex-92.md deleted file mode 100644 index fc886374f..000000000 --- a/agents/codex-92.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-922.md b/agents/codex-922.md deleted file mode 100644 index 29a63355e..000000000 --- a/agents/codex-922.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-93.md b/agents/codex-93.md deleted file mode 100644 index 7b554e0ae..000000000 --- a/agents/codex-93.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-935.md b/agents/codex-935.md deleted file mode 100644 index ee1f0addb..000000000 --- a/agents/codex-935.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-936.md b/agents/codex-936.md deleted file mode 100644 index c055295bc..000000000 --- a/agents/codex-936.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-942.md b/agents/codex-942.md deleted file mode 100644 index 3b2902501..000000000 --- a/agents/codex-942.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-970.md b/agents/codex-970.md deleted file mode 100644 index 6dc8cc2bd..000000000 --- a/agents/codex-970.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-982.md b/agents/codex-982.md deleted file mode 100644 index 34add8512..000000000 --- a/agents/codex-982.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-983.md b/agents/codex-983.md deleted file mode 100644 index f6d1f93f0..000000000 --- a/agents/codex-983.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-988.md b/agents/codex-988.md deleted file mode 100644 index 8a8754136..000000000 --- a/agents/codex-988.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-989.md b/agents/codex-989.md deleted file mode 100644 index 81bd9345f..000000000 --- a/agents/codex-989.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-992.md b/agents/codex-992.md deleted file mode 100644 index 24ebcb818..000000000 --- a/agents/codex-992.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/codex-994.md b/agents/codex-994.md deleted file mode 100644 index e122e3758..000000000 --- a/agents/codex-994.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/agents/format-580.md b/agents/format-580.md deleted file mode 100644 index 134dd6147..000000000 --- a/agents/format-580.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/config/consumer_sync_canaries.json b/config/consumer_sync_canaries.json index 2378ca4be..621edb582 100644 --- a/config/consumer_sync_canaries.json +++ b/config/consumer_sync_canaries.json @@ -1,18 +1,29 @@ { "schema": "workflows.consumer-sync-canaries/v1", - "version": 1, + "version": 3, "canaries": [ { - "repo": "stranske/Travel-Plan-Permission", - "capabilities": ["standard", "custom-gate"] + "repo": "iamkayleb/bukay", + "capabilities": [ + "node-tooling", + "python-consumer", + "lock-heavy" + ] }, { - "repo": "stranske/trip-planner", - "capabilities": ["lock-heavy", "node-tooling"] + "repo": "stranske/Travel-Plan-Permission", + "capabilities": [ + "standard", + "custom-gate" + ] }, { "repo": "stranske/Portable-Alpha-Extension-Model", - "capabilities": ["python-consumer", "codex-review", "legacy-precommit"] + "capabilities": [ + "python-consumer", + "codex-review", + "legacy-precommit" + ] } ] } diff --git a/config/langsmith_fleet_allowlist.json b/config/langsmith_fleet_allowlist.json index bc385c3cb..9a53fb18b 100644 --- a/config/langsmith_fleet_allowlist.json +++ b/config/langsmith_fleet_allowlist.json @@ -24,6 +24,12 @@ "status": "not-applicable", "reason": "Local agent-orchestration tool repo; receives consumer sync for agent machinery but has no substantive LangSmith-traced runtime surface (offload-only Brain runs).", "registry_activation_condition": "Register when Orchestrator gains an LLM-backed runtime or agent-observability surface that should participate in fleet LangSmith metrics." + }, + { + "repo": "iamkayleb/bukay", + "status": "not-applicable", + "reason": "Newly registered consumer; receives fleet updates and runs the agent workflows but has no conformant langsmith-fleet.ndjson producer yet, so registry membership would add a zero-signal artifact expectation.", + "registry_activation_condition": "Register once bukay uploads a conformant langsmith-fleet.ndjson artifact from a trusted workflow path." } ] } diff --git a/config/model_eval_corpus_staging.json b/config/model_eval_corpus_staging.json index c3871ccf4..2619ca1a8 100644 --- a/config/model_eval_corpus_staging.json +++ b/config/model_eval_corpus_staging.json @@ -298,6 +298,384 @@ "category": "clean-pass", "provenance": "harvested", "harvested_at": "2026-08-17" + }, + { + "case_id": "workflows-2833", + "repo": "stranske/Workflows", + "pr": 2833, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "workflows-2832", + "repo": "stranske/Workflows", + "pr": 2832, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "workflows-2831", + "repo": "stranske/Workflows", + "pr": 2831, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "travel-plan-permission-1350", + "repo": "stranske/Travel-Plan-Permission", + "pr": 1350, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "travel-plan-permission-1349", + "repo": "stranske/Travel-Plan-Permission", + "pr": 1349, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "trend_model_project-5757", + "repo": "stranske/Trend_Model_Project", + "pr": 5757, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "portable-alpha-extension-model-2163", + "repo": "stranske/Portable-Alpha-Extension-Model", + "pr": 2163, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "portable-alpha-extension-model-2162", + "repo": "stranske/Portable-Alpha-Extension-Model", + "pr": 2162, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "counter_risk-887", + "repo": "stranske/Counter_Risk", + "pr": 887, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "counter_risk-886", + "repo": "stranske/Counter_Risk", + "pr": 886, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "manager-database-1482", + "repo": "stranske/Manager-Database", + "pr": 1482, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "inv-man-intake-853", + "repo": "stranske/Inv-Man-Intake", + "pr": 853, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "inv-man-intake-844", + "repo": "stranske/Inv-Man-Intake", + "pr": 844, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-780", + "repo": "stranske/Pension-Data", + "pr": 780, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-779", + "repo": "stranske/Pension-Data", + "pr": 779, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-777", + "repo": "stranske/Pension-Data", + "pr": 777, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-776", + "repo": "stranske/Pension-Data", + "pr": 776, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-775", + "repo": "stranske/Pension-Data", + "pr": 775, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-774", + "repo": "stranske/Pension-Data", + "pr": 774, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "pension-data-773", + "repo": "stranske/Pension-Data", + "pr": 773, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "ready-474", + "repo": "stranske/Ready", + "pr": 474, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "ready-473", + "repo": "stranske/Ready", + "pr": 473, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "trip-planner-1582", + "repo": "stranske/trip-planner", + "pr": 1582, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "trip-planner-1581", + "repo": "stranske/trip-planner", + "pr": 1581, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "learning-management-system-476", + "repo": "stranske/learning-management-system", + "pr": 476, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "learning-management-system-475", + "repo": "stranske/learning-management-system", + "pr": 475, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "fine-art-archive-344", + "repo": "stranske/Fine-Art-Archive", + "pr": 344, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "fine-art-archive-343", + "repo": "stranske/Fine-Art-Archive", + "pr": 343, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "fine-art-archive-342", + "repo": "stranske/Fine-Art-Archive", + "pr": 342, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "fine-art-archive-341", + "repo": "stranske/Fine-Art-Archive", + "pr": 341, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-08-24" + }, + { + "case_id": "workflows-2993", + "repo": "stranske/Workflows", + "pr": 2993, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2988", + "repo": "stranske/Workflows", + "pr": 2988, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2987", + "repo": "stranske/Workflows", + "pr": 2987, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2986", + "repo": "stranske/Workflows", + "pr": 2986, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2985", + "repo": "stranske/Workflows", + "pr": 2985, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2984", + "repo": "stranske/Workflows", + "pr": 2984, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "workflows-2981", + "repo": "stranske/Workflows", + "pr": 2981, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "trend_model_project-5799", + "repo": "stranske/Trend_Model_Project", + "pr": 5799, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "fine-art-archive-474", + "repo": "stranske/Fine-Art-Archive", + "pr": 474, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "fine-art-archive-473", + "repo": "stranske/Fine-Art-Archive", + "pr": 473, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "fine-art-archive-472", + "repo": "stranske/Fine-Art-Archive", + "pr": 472, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" + }, + { + "case_id": "fine-art-archive-470", + "repo": "stranske/Fine-Art-Archive", + "pr": 470, + "expected_verdict": "PASS", + "category": "clean-pass", + "provenance": "harvested", + "harvested_at": "2026-09-07" } ] } diff --git a/config/template-drift-allowlist.txt b/config/template-drift-allowlist.txt index 250401002..070cd9998 100644 --- a/config/template-drift-allowlist.txt +++ b/config/template-drift-allowlist.txt @@ -44,22 +44,22 @@ main = .github/workflows/agents-63-issue-intake.yml template = templates/consumer-repo/.github/workflows/agents-issue-intake.yml main_sha256 = 200757cbb8d1801434ac3acb9e517a90ea37aa06e57ce925e8ac4480d829fc7f -template_sha256 = 9176b7cffc68dba50fa7ff9c6a2386383c237433ac5a656053eccdd202628c6d -reason = Intentional divergence re-reviewed 2026-08-16: root and consumer intake surfaces both remove the operator draft toggle and always hand off ready-for-review automation PRs; root retains its richer failure summary while the consumer remains a pinned, minimal bridge contract. +template_sha256 = 9674a8cbba932c5af668fb50f8d90362df4510447efb0b376b9433dece046a9a +reason = Intentional divergence re-reviewed 2026-08-16: root and consumer intake surfaces both remove the operator draft toggle and always hand off ready-for-review automation PRs; root retains its richer failure summary while the consumer remains a pinned, minimal bridge contract. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.2] main = .github/workflows/agents-71-codex-belt-dispatcher.yml template = templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml main_sha256 = 0e9a4c7e7b120985fd9684a9e4f89049601b2bbc0b9da9bee5510d3ec1c3c175 -template_sha256 = 6a7b6e203f6ac2837bc09dc2dc6620faa37b72cb78068af1bb77779e246cb1dd -reason = Intentional divergence re-baselined 2026-08-16: workflow_dispatch inputs (force_issue, agent_key) are now passed to the github-script step via step-level env: and read through process.env instead of being interpolated into the script body, removing a script-injection surface that caused GitHub to block the workflow as possibly malicious in consumer repos. Applied identically to both surfaces; consumer action pinning and Codex-specific wording preserved. +template_sha256 = a797257d9e1145c41615ab12872d2d040edf663cdc72b16e99a96c7e03b59c81 +reason = Intentional divergence re-baselined 2026-08-16: workflow_dispatch inputs (force_issue, agent_key) are now passed to the github-script step via step-level env: and read through process.env instead of being interpolated into the script body, removing a script-injection surface that caused GitHub to block the workflow as possibly malicious in consumer repos. Applied identically to both surfaces; consumer action pinning and Codex-specific wording preserved. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.3] main = .github/workflows/agents-72-codex-belt-worker.yml template = templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml main_sha256 = 905e1a6487c44f0705b4c83294fa5cc9e2693cfdf2927994f6dc28912516b758 -template_sha256 = f8ce0688837030094e20af4aee01d25274dbffcb3eeec3fa5b34da16318f3993 -reason = Existing reviewed baseline drift re-baselined 2026-06-20: exported Orchestrator skill inputs were added to both root and consumer worker workflow_call surfaces while preserving consumer action pinning and guarded merge wording. +template_sha256 = 4b4ed9f19b25bfd7fa7afb75967f5fc3374cf30af755700ae87290530b1619aa +reason = Existing reviewed baseline drift re-baselined 2026-06-20: exported Orchestrator skill inputs were added to both root and consumer worker workflow_call surfaces while preserving consumer action pinning and guarded merge wording. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.4] main = .github/workflows/agents-73-codex-belt-conveyor.yml @@ -107,15 +107,15 @@ reason = Intentional divergence (re-baselined 2026-06-14): consumer template SHA main = .github/workflows/agents-guard.yml template = templates/consumer-repo/.github/workflows/agents-guard.yml main_sha256 = eb5a10b5246ab1aa94afa481ed40e6a330a44be935f581827f6a49527b73e19f -template_sha256 = a8bc6224681c3fbb3314a393135188bca1b1362439cfd509b81eceb1acc0e8cd -reason = Intentional divergence re-baselined 2026-06-30: root and consumer guard workflows differ for pinned consumer actions/App-token setup; stranske/Workflows digest pins were refreshed together in root and consumer guard surfaces after Renovate moved the Workflows digest to ebef44a. +template_sha256 = e56f95bb658f17060c8aa6985c8e3b8e4fc402df5571fe3279a785fd42b21745 +reason = Intentional divergence re-baselined 2026-06-30: root and consumer guard workflows differ for pinned consumer actions/App-token setup; stranske/Workflows digest pins were refreshed together in root and consumer guard surfaces after Renovate moved the Workflows digest to ebef44a. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.11] main = .github/workflows/agents-issue-optimizer.yml template = templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml main_sha256 = 32a4dd4b3b2c744fe11abf0cfe60e47464fe4ca63836af2560ac5002f3912441 -template_sha256 = 145e47655f8153d50970ea51e99aa833e74698fce915a2c124e1463f1b194e85 -reason = Intentional divergence re-baselined 2026-08-11: root remains in-tree (scripts/langchain + .github/scripts/issue_format.py); consumer vendors those via Workflows sparse-checkout under workflows-scripts/. Shared behavioral contract includes live format eligibility checks, format-lease release when those checks skip work, checkout-aware path validation, explicit guard retry dispatch, and quoted lowercase identifier acceptance. Do not align wholesale — that would strip consumer action pins/token setup. +template_sha256 = ab3a44f201da0bd1dc21bb4d00f0ff54fd8892e1250a72eff2d32116fe5f6f4d +reason = Intentional divergence re-baselined 2026-08-11: root remains in-tree (scripts/langchain + .github/scripts/issue_format.py); consumer vendors those via Workflows sparse-checkout under workflows-scripts/. Shared behavioral contract includes live format eligibility checks, format-lease release when those checks skip work, checkout-aware path validation, explicit guard retry dispatch, and quoted lowercase identifier acceptance. Do not align wholesale — that would strip consumer action pins/token setup. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.12] main = .github/workflows/agents-keepalive-loop-reporter.yml @@ -135,8 +135,8 @@ reason = Intentional divergence re-reviewed 2026-08-13: root and consumer sweeps main = .github/workflows/agents-verifier.yml template = templates/consumer-repo/.github/workflows/agents-verifier.yml main_sha256 = 5e1cb04f45d27ccd30395eb804203677db130c7f1bcabf8054d80cb22c189e97 -template_sha256 = 9c803d40ce8b26f4450a60d6f821eb2cadbd59c42b47259d3394ad1d932fee0f -reason = Existing reviewed baseline drift; align the template or update this fingerprint deliberately. +template_sha256 = e288ad90d1b243af7b60f850f143f23a13ae4d874a7a632d927a6acefbbac01b +reason = Existing reviewed baseline drift; align the template or update this fingerprint deliberately. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. [pair.15] main = .github/workflows/agents-weekly-metrics.yml @@ -149,5 +149,12 @@ reason = Intentional divergence (re-baselined 2026-07-14): consumer template SHA main = .github/workflows/agents-auto-pilot.yml template = templates/consumer-repo/.github/workflows/agents-auto-pilot.yml main_sha256 = 7d3c491c9f45d1666a08e6b69c3aea487568b1eeeb9bcbe48dac3f5d3de69b93 -template_sha256 = 9a3d67230d348407a4cc7c98ff2d8709a77b065c2fa6b5401922518ceef808e1 -reason = Intentional divergence reviewed 2026-08-21: the Workflows-local auto-pilot retains Workflows-only PR-meta and keepalive fallbacks, while the consumer template dispatches only the consolidated Agents 80 and Agents 81 entry points. The sync manifest delivers the consumer-specific template so retired Workflows-local targets cannot be restored in consumer repositories. +template_sha256 = 877d440d20eb31964a0c387d5213cce65ed1bbd7df44a5e7bc1061fae367f5e8 +reason = Intentional divergence reviewed 2026-08-21: the Workflows-local auto-pilot retains Workflows-only PR-meta and keepalive fallbacks, while the consumer template dispatches only the consolidated Agents 80 and Agents 81 entry points. The sync manifest delivers the consumer-specific template so retired Workflows-local targets cannot be restored in consumer repositories. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. +[pair.17] +main = .github/workflows/agents-verify-to-new-pr.yml +template = templates/consumer-repo/.github/workflows/agents-verify-to-new-pr.yml +main_sha256 = 68d891295a386a2a0b6f07226eba358263ad5b3be9444fa2c4207264b70ea6b2 +template_sha256 = 02c0d9b92a15fcb97944cfd3c6b68ee79d645e05d7abcedad802f3e7b5015630 +reason = Consumer template resolves the control plane from iamkayleb/Workflows (this fork) while the root workflow keeps its own wiring. Template fingerprint re-baselined 2026-08-22: the consumer templates now resolve reusable workflows, composite actions, and script sparse-checkouts from iamkayleb/Workflows (this fork) instead of stranske/Workflows, so every template copy's normalized content changed. The paired root agent workflows were not repointed (only the reusables' vendored helper layer was), so the owner divergence in these pairs is the intended contract. + diff --git a/docs/WORKFLOW_GUIDE.md b/docs/WORKFLOW_GUIDE.md index 77af69e11..4c41b38d7 100644 --- a/docs/WORKFLOW_GUIDE.md +++ b/docs/WORKFLOW_GUIDE.md @@ -113,7 +113,7 @@ _Inline Gate helper_ - **`agents-bot-comment-handler.yml`** — Runs the existing inline-comment application logic. In addition to the prior label/Gate/manual triggers it now auto-runs when trusted bot review comments land, so inline suggestions on non-agent PRs are applied without manual relabeling. Override the trusted logins via `BOT_COMMENT_LOGINS` when repositories add more bots. - **`agents-guard.yml`** (aka Health 45 Agents Guard) — PR workflow that validates agent-related labels and permissions for both `pull_request` and `pull_request_target`. It now relies entirely on the shared API client/token balancer, so there’s no bespoke App-token mint ahead of safety checks. - **`pr-46-dependency-repair-contract.yml`** — Thin PR check that calls `reusable-19-dependency-repair-contract.yml` for dependency-bot and marked promotion PRs. It rejects unclassified agent commits on bot branches while permitting later repair commits on an agent-owned promotion branch after verifying that its first commit reproduces the selected bot delta. See [`docs/ops/DEPENDENCY_REPAIR_PROMOTION.md`](ops/DEPENDENCY_REPAIR_PROMOTION.md). -- **`agents-issue-optimizer.yml`** — Powers the analyzer/apply/format stages for legacy agent issues. It fires on the `agents:*` optimizer labels or via manual dispatch, re-reads live issue eligibility before format work, skips held, exempt, closed, bot-authored, and `agents:auto-pilot` work, releases the `agents:format` lease when that recheck makes formatting ineligible, validates formatted path evidence against the current checkout, enforces recursion guards via the GitHub CLI, and runs the LangChain optimizer before posting suggestions or applying changes. It now uses only the shared API client (no manual App-token mint) for GH CLI/auth flows. +- **`agents-issue-optimizer.yml`** — Powers the analyzer/apply/format stages for legacy agent issues. It fires on the `agents:*` optimizer labels or via manual dispatch, re-reads live issue eligibility before format work, skips held, exempt, closed, bot-authored, and `agents:auto-pilot` work, releases the `agents:format` lease when that recheck makes formatting ineligible, validates formatted path evidence against the current checkout, enforces a per-issue recursion guard via the GitHub CLI (counting only runs that could reach the optimizer — `run-name` marks non-triggering label events `[noop]` so bulk labelling cannot exhaust the budget), and runs the LangChain optimizer before posting suggestions or applying changes. It now uses only the shared API client (no manual App-token mint) for GH CLI/auth flows. - **`agents-80-pr-event-hub.yml`** — Consumer-template consolidated PR event hub that fans out keepalive metadata, bot-comment handling, and verification follow-ups after a single PR context fetch. - **`agents-81-gate-followups.yml`** — Consumer-template consolidated Gate follow-up hub that coordinates keepalive, autofix, and post-CI recovery. - **`agents-pr-meta-v4.yml`** — Workflows-repo PR metadata/keepalive front door: listens to issue comments, structural PR updates (open, synchronize, and reopen), and Gate completions to detect `@agent` activations, enforce gate/run-cap rules, dispatch the orchestrator, and write dispatch summaries. It deliberately ignores PR-body edit events because it writes that body itself; observing its own edits can create an unbounded metadata-check loop. It leaves release-please PR bodies untouched so release-please can parse merged release PRs and publish tags/releases. This remains a Workflows-local service workflow; the current consumer default is the `agents-80-pr-event-hub.yml` / `agents-81-gate-followups.yml` pair distributed from `templates/consumer-repo/`. diff --git a/docs/ops/CONSUMER_REPO_MAINTENANCE.md b/docs/ops/CONSUMER_REPO_MAINTENANCE.md index d84a124d3..71b7fa1bc 100644 --- a/docs/ops/CONSUMER_REPO_MAINTENANCE.md +++ b/docs/ops/CONSUMER_REPO_MAINTENANCE.md @@ -561,6 +561,58 @@ of following the first-party default. If a reusable workflow fix must ship immediately, trigger: - `Maint 68 Sync Consumer Repos` only if template files changed +#### Control-plane owner in the consumer templates + +The consumer templates resolve reusable workflows, composite actions, and script +sparse-checkouts from **`iamkayleb/Workflows`** — this fork — not from +`stranske/Workflows`. `iamkayleb/bukay` was already wired that way by hand, so +syncing upstream-owned templates into it would have silently reverted 38 refs +back to upstream on the first delivery. + +The reusables' **vendored helper layer** is repointed too, and that part is easy +to miss. A reusable runs in the *caller's* context: before this change, a consumer +calling `iamkayleb/Workflows/.../reusable-10-ci-python.yml@main` got fork reusable +logic and then a `repository: stranske/Workflows` checkout that vendored its helper +scripts from upstream. All 27 of those checkouts across the 13 reusables now name +this fork, so a script change here actually reaches consumers. + +Four consequences worth knowing before you touch a template or reusable workflow: + +- **The paired root agent workflows were deliberately not repointed.** Only the + reusables' helper layer was, plus `agents-model-profile-trial.yml` (its caller, + registry `runner_ref`, and the runner's own immutability assertion are one unit + and must name the same commit). The owner divergence between a root agent + workflow and its template twin is the intended contract, and those pairs are + re-baselined in `config/template-drift-allowlist.txt`. Health 74 canonicalizes + the `@` pin but *preserves the action path*, so an owner change registers + as drift and needs a deliberate re-baseline. +- **Pinned refs must exist in this fork.** The upstream SHAs for + `generated-delivery-seal`, `setup-api-client`, and + `reusable-model-profile-trial.yml` are not in this fork's history; they are + pinned to `e85edad` here. Bumping a pin means picking a commit that exists in + `iamkayleb/Workflows`, not copying an upstream SHA. +- **Discovery predicates and owner comparisons must be owner-agnostic.** Anything + that matches a literal `stranske/Workflows` becomes a silent no-op after a + repoint — it does not fail, it just stops seeing anything. Match on the + repository *name* (`*/Workflows`) instead. Three cases have already bitten: + `test_workflow_llm_installs` dropped three template workflows from coverage, + `test_reusable_ci_no_hardcoded_ref_main` fell to zero inspected checkouts (its + `checked >= 4` floor is the only reason anyone noticed), and + `reusable-18-autofix.yml` had a dead `if` branch that silently degraded a + SHA-pinned call to `main`. Keep a count floor on any such discovery loop. +- **Still pointing upstream, on purpose, for now.** 14 refs across 8 root agent + workflows (`agents-keepalive-loop.yml`, `agents-autofix-loop.yml`, + `agents-guard.yml`, `agents-verifier.yml`, `agents-bot-comment-handler.yml`, + `pr-00-gate.yml`, `pr-46-dependency-repair-contract.yml`) still call upstream + reusables and actions, so this fork's own CI exercises upstream code. That is a + fork-self-CI concern only — it does not affect consumers, which reach the fork + through the templates. `maint-69/70/71-auto-fix-integration` also target + `stranske/Workflows-Integration-Tests`, which this fork cannot write to. + +Prose references to `stranske/Workflows` in `README.md`, `docs/USAGE.md`, and +`docs/INTEGRATION_GUIDE.md` were left alone; they document the upstream project, +not this fork's delivery wiring. + ### Sync PR Branch Cleanup `maint-71-merge-sync-prs.yml` owns routine cleanup for `sync/workflows-*` diff --git a/renovate-presets/consumer-managed-paths.json b/renovate-presets/consumer-managed-paths.json index e32b783d8..7987c1037 100644 --- a/renovate-presets/consumer-managed-paths.json +++ b/renovate-presets/consumer-managed-paths.json @@ -5,6 +5,7 @@ { "description": "Maint 68 overwrites these 214 manifest-managed paths in every registered consumer; Renovate edits there are reverted on the next sync.", "matchRepositories": [ + "iamkayleb/bukay", "stranske/Collab-Admin", "stranske/Counter_Risk", "stranske/Fine-Art-Archive", @@ -238,6 +239,22 @@ ], "enabled": false }, + { + "description": "iamkayleb/bukay additionally has 7 manifest-managed path(s) that at least one other consumer owns via skip_repos, create_only, or an overwrite_repos opt-in.", + "matchRepositories": [ + "iamkayleb/bukay" + ], + "matchFileNames": [ + ".github/scripts/node_modules/balanced-match/**", + ".github/scripts/node_modules/brace-expansion/**", + ".github/scripts/node_modules/minimatch/**", + ".github/scripts/package.json", + ".github/workflows/pr-00-gate.yml", + "AGENTS.md", + "CLAUDE.md" + ], + "enabled": false + }, { "description": "stranske/Collab-Admin, stranske/Counter_Risk, stranske/Fine-Art-Archive, stranske/Inv-Man-Intake, stranske/Manager-Database, stranske/Pension-Data, stranske/Portable-Alpha-Extension-Model, stranske/Ready, stranske/Travel-Plan-Permission, stranske/learning-management-system additionally has 6 manifest-managed path(s) that at least one other consumer owns via skip_repos, create_only, or an overwrite_repos opt-in.", "matchRepositories": [ diff --git a/scripts/langsmith_fleet.py b/scripts/langsmith_fleet.py index ee54671ae..976193082 100644 --- a/scripts/langsmith_fleet.py +++ b/scripts/langsmith_fleet.py @@ -44,6 +44,7 @@ "stranske/learning-management-system", "stranske/Fine-Art-Archive", "stranske/Orchestrator", + "iamkayleb/bukay", } REQUIRED_ACTIVE_REPO_ISSUES = { "stranske/trip-planner": 1208, diff --git a/templates/consumer-repo/.github/agents/registry.yml b/templates/consumer-repo/.github/agents/registry.yml index 6cc23eb91..6e5f1b2a8 100644 --- a/templates/consumer-repo/.github/agents/registry.yml +++ b/templates/consumer-repo/.github/agents/registry.yml @@ -21,7 +21,7 @@ model_profile_trial_contract: artifact_schema: workflows.model-profile-trial-result/v2 identity_authority: workflows-read-only-trial-artifact/v2 collector_identity_authority: github-actions-api/workflows-read-only-trial-artifact/v2 - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 cli_version: 0.144.1 runtime_fallback_allowed: false auxiliary_evaluator_allowed: false @@ -52,7 +52,7 @@ execution_profiles: model: gpt-5.6-sol fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -63,7 +63,7 @@ execution_profiles: model: gpt-5.6-terra fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -74,7 +74,7 @@ execution_profiles: model: gpt-5.6-luna fallback_model: gpt-5.5 runner: reusable-model-profile-trial - runner_ref: stranske/Workflows/.github/workflows/reusable-model-profile-trial.yml@822e323eefba3edb640e0bd9c922caec6fffee65 + runner_ref: iamkayleb/Workflows/.github/workflows/reusable-model-profile-trial.yml@e85edadb246e41d172a0c79fad147752d1df9ea9 capacity_pool: codex-standard safety: read-only lifecycle: trial @@ -137,7 +137,7 @@ agents: branch_prefix: cursor/issue- capacity: window: daily - limit: 1 # TODO: confirm owner-supplied public plan limit + limit: 10 # SET THIS to your actual Cursor plan quota ui_mentions_allowed: false # Reuses stranske-automation-bot for branch pushes/attribution until a # dedicated stranske-cursor-bot service account is provisioned. @@ -152,7 +152,7 @@ agents: capabilities: pr_keepalive: true pr_autofix: true # wired into agents-autofix-loop.yml (autofix-cursor job) - belt: false # belt routing deferred to a later phase + belt: true # dispatched by agents-81 run-cursor verifier_checkbox: false # verification stays on the existing judge (config/llm_slots.json) gemini: diff --git a/templates/consumer-repo/.github/renovate.json b/templates/consumer-repo/.github/renovate.json index f63e2fb99..24ba43cee 100644 --- a/templates/consumer-repo/.github/renovate.json +++ b/templates/consumer-repo/.github/renovate.json @@ -1,4 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>stranske/Workflows//renovate-presets/fleet"] + "extends": ["github>iamkayleb/Workflows//renovate-presets/fleet"] } diff --git a/templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml b/templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml index b9dfd0444..6d3c0067a 100644 --- a/templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml +++ b/templates/consumer-repo/.github/workflows/agents-71-codex-belt-dispatcher.yml @@ -20,6 +20,13 @@ on: required: false default: false type: boolean + base_branch: + description: >- + Optional base branch for the agent branch. Overrides the issue's + `` marker and the repository default. + required: false + default: '' + type: string orchestrator_skill_pack: description: >- Optional reference-pack name override for exported Orchestrator skill context on @@ -76,6 +83,13 @@ on: required: false default: false type: boolean + base_branch: + description: >- + Optional base branch for the agent branch. Overrides the issue's + `` marker and the repository default. + required: false + default: '' + type: string orchestrator_skill_pack: description: >- Optional reference-pack name override for exported Orchestrator skill context on @@ -96,7 +110,15 @@ permissions: actions: write concurrency: - group: codex-belt-dispatcher + # Keyed by the forced issue so per-issue dispatches run in parallel. A fixed + # group serialises the whole belt: with several issues dispatched at once, + # GitHub keeps one running plus one waiting and CANCELS the rest while they + # are still pending, which surfaces as a failed run with no jobs and no log. + # Auto-select runs (no force_issue) still share one lane, so the "pick the + # next issue" path cannot race itself into double-assignment. + group: >- + belt-dispatcher-${{ inputs.agent_key || 'codex' }}-${{ + inputs.force_issue || 'auto-select' }} cancel-in-progress: false jobs: @@ -211,7 +233,7 @@ jobs: - name: Checkout (for retry helpers) uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | .github/actions/setup-api-client @@ -292,12 +314,42 @@ jobs: ]); const { data: repoInfo } = await withRetry((client) => client.rest.repos.get({ owner, repo })); - const base = repoInfo.default_branch; - if (!base) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Base resolution mirrors reusable-agents-issue-bridge.yml: an + // explicit input wins, then the issue's `` + // marker, then the repository default. Without the marker every + // agent branch is cut from the default branch, which silently + // defeats any per-lane workflow (evaluation lanes, release trains, + // long-lived feature bases). + let base = String(process.env.INPUT_BASE_BRANCH || '').trim(); + let baseSource = base ? 'input' : ''; + if (!base) { + try { + const { data: issueData } = await withRetry((client) => + client.rest.issues.get({ owner, repo, issue_number: issueNumber })); + const marker = String(issueData.body || '') + .match(//); + if (marker) { base = marker[1].trim(); baseSource = 'issue-marker'; } + } catch (error) { + core.warning(`Could not read issue body for a base-branch marker: ${error.message}`); + } + } + if (!base) { base = defaultBranch; baseSource = 'default'; } + if (base !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ owner, repo, branch: base })); + } catch (error) { + core.warning(`Base branch '${base}' not found; falling back to '${defaultBranch}'.`); + base = defaultBranch; baseSource = 'default-fallback'; + } + } + core.info(`Base branch: ${base} (source: ${baseSource})`); + let branchPrefix = 'codex/issue-'; try { const { getAgentConfig } = require('./.github/scripts/agent_registry.js'); diff --git a/templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml b/templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml index 80cc3d449..724f1af6b 100644 --- a/templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml +++ b/templates/consumer-repo/.github/workflows/agents-72-codex-belt-worker.yml @@ -321,7 +321,7 @@ jobs: - name: Checkout Workflows scripts uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | .github/actions/setup-api-client @@ -592,7 +592,7 @@ jobs: if: ${{ steps.parallel.outputs.allowed == 'true' && (inputs.keepalive != true || steps.keepalive_gate.outputs.action != 'skip') }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} token: ${{ env.GH_BELT_TOKEN }} fetch-depth: 1 diff --git a/templates/consumer-repo/.github/workflows/agents-80-pr-event-hub.yml b/templates/consumer-repo/.github/workflows/agents-80-pr-event-hub.yml index 1a3ce1e85..b97c6beb0 100644 --- a/templates/consumer-repo/.github/workflows/agents-80-pr-event-hub.yml +++ b/templates/consumer-repo/.github/workflows/agents-80-pr-event-hub.yml @@ -194,7 +194,7 @@ jobs: needs.resolve.outputs.pr_number != '' && (needs.resolve.outputs.run_pr_meta == 'true' || needs.resolve.outputs.run_bot_comments == 'true') - uses: stranske/Workflows/.github/workflows/reusable-pr-context.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-pr-context.yml@main with: pr_number: ${{ fromJSON(needs.resolve.outputs.pr_number) }} secrets: inherit @@ -206,7 +206,7 @@ jobs: needs.resolve.outputs.should_run == 'true' && needs.resolve.outputs.pr_number != '' && needs.resolve.outputs.run_pr_meta == 'true' - uses: stranske/Workflows/.github/workflows/reusable-20-pr-meta.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-20-pr-meta.yml@main with: pr_number: ${{ fromJSON(needs.resolve.outputs.pr_number) }} comment_id: ${{ needs.resolve.outputs.comment_id }} @@ -230,7 +230,7 @@ jobs: (needs.resolve.outputs.gate_conclusion == 'success' && needs.pr_context.outputs.has_agent_label == 'true') ) - uses: stranske/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main with: pr_number: ${{ fromJSON(needs.resolve.outputs.pr_number) }} dry_run: ${{ needs.resolve.outputs.dry_run == 'true' }} @@ -317,7 +317,7 @@ jobs: if: steps.check-merged.outputs.merged == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows token: ${{ secrets.SERVICE_BOT_PAT || github.token }} sparse-checkout: | config diff --git a/templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml b/templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml index ca885518b..003623985 100644 --- a/templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml +++ b/templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml @@ -667,7 +667,7 @@ jobs: if: >- needs.evaluate.outputs.agent_type == 'codex' && needs.evaluate.outputs.dispatch_should_run == 'true' - uses: stranske/Workflows/.github/workflows/reusable-codex-run.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-codex-run.yml@main secrets: CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} # Use dedicated KEEPALIVE_APP for isolated rate limit pool (5000/hr) @@ -684,6 +684,15 @@ jobs: needs.evaluate.outputs.action != 'conflict' }} prompt_file: ${{ needs.evaluate.outputs.prompt_file }} mode: keepalive + # Codex sandboxes with bubblewrap under `workspace-write`, and bwrap cannot + # create a loopback interface on a GitHub-hosted runner: + # bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted + # Every shell command then fails before it runs, so Codex correctly refuses + # the task and reports "Blocked" having executed 0 commands and written 0 + # files — which reads as the agent producing nothing. The runner is an + # ephemeral, single-tenant VM that is already the isolation boundary, so + # dropping Codex's inner sandbox costs nothing here. + sandbox: danger-full-access pr_number: ${{ needs.evaluate.outputs.pr_number }} pr_ref: ${{ needs.evaluate.outputs.pr_ref }} appendix: ${{ needs.evaluate.outputs.task_appendix }} @@ -701,7 +710,33 @@ jobs: (needs.evaluate.outputs.action == 'run' || needs.evaluate.outputs.action == 'fix' || needs.evaluate.outputs.action == 'conflict') - uses: stranske/Workflows/.github/workflows/reusable-claude-run.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-claude-run.yml@main + secrets: inherit + with: + skip: >- + ${{ needs.evaluate.outputs.action != 'run' && + needs.evaluate.outputs.action != 'fix' && + needs.evaluate.outputs.action != 'conflict' }} + prompt_file: ${{ needs.evaluate.outputs.prompt_file }} + mode: keepalive + pr_number: ${{ needs.evaluate.outputs.pr_number }} + pr_ref: ${{ needs.evaluate.outputs.pr_ref }} + appendix: ${{ needs.evaluate.outputs.task_appendix }} + iteration: ${{ needs.evaluate.outputs.iteration }} + + run-cursor: + name: Keepalive next task (Cursor) + needs: + - evaluate + - preflight + - mark-running + if: | + needs.evaluate.outputs.agent_type == 'cursor' && + needs.evaluate.outputs.dispatch_should_run == 'true' && + (needs.evaluate.outputs.action == 'run' || + needs.evaluate.outputs.action == 'fix' || + needs.evaluate.outputs.action == 'conflict') + uses: iamkayleb/Workflows/.github/workflows/reusable-cursor-run.yml@main secrets: inherit with: skip: >- @@ -721,6 +756,7 @@ jobs: - evaluate - run-codex - run-claude + - run-cursor if: >- always() && needs.evaluate.outputs.dispatch_should_run == 'true' && @@ -780,6 +816,7 @@ jobs: - preflight - run-codex - run-claude + - run-cursor # Run always if PR exists, handle skipped agent jobs gracefully if: | always() && @@ -1621,10 +1658,12 @@ jobs: needs.prepare.outputs.dispatch_should_run == 'true' && needs.prepare.outputs.agent_type == 'codex' name: Run Codex autofix - uses: stranske/Workflows/.github/workflows/reusable-codex-run.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-codex-run.yml@main with: prompt_file: .github/codex/prompts/autofix_from_ci_failure.md mode: autofix + # Same bubblewrap failure as run-codex; see the note there. + sandbox: danger-full-access pr_number: ${{ needs.prepare.outputs.pr_number }} pr_ref: ${{ needs.prepare.outputs.head_ref }} appendix: ${{ needs.prepare.outputs.appendix }} @@ -1640,7 +1679,7 @@ jobs: needs.prepare.outputs.dispatch_should_run == 'true' && needs.prepare.outputs.agent_type == 'claude' name: Run Claude autofix - uses: stranske/Workflows/.github/workflows/reusable-claude-run.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-claude-run.yml@main with: prompt_file: .github/codex/prompts/autofix_from_ci_failure.md mode: autofix @@ -1653,6 +1692,25 @@ jobs: WORKFLOWS_APP_ID: ${{ secrets.WORKFLOWS_APP_ID }} WORKFLOWS_APP_PRIVATE_KEY: ${{ secrets.WORKFLOWS_APP_PRIVATE_KEY }} + autofix-cursor: + needs: prepare + if: >- + needs.prepare.outputs.should_run == 'true' && + needs.prepare.outputs.dispatch_should_run == 'true' && + needs.prepare.outputs.agent_type == 'cursor' + name: Run Cursor autofix + uses: iamkayleb/Workflows/.github/workflows/reusable-cursor-run.yml@main + with: + prompt_file: .github/codex/prompts/autofix_from_ci_failure.md + mode: autofix + pr_number: ${{ needs.prepare.outputs.pr_number }} + pr_ref: ${{ needs.prepare.outputs.head_ref }} + appendix: ${{ needs.prepare.outputs.appendix }} + secrets: + CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} + WORKFLOWS_APP_ID: ${{ secrets.WORKFLOWS_APP_ID }} + WORKFLOWS_APP_PRIVATE_KEY: ${{ secrets.WORKFLOWS_APP_PRIVATE_KEY }} + needs-human: needs: prepare if: needs.prepare.outputs.stop_reason == 'max_attempts' @@ -1727,6 +1785,7 @@ jobs: - prepare - autofix - autofix-claude + - autofix-cursor if: >- ${{ always() && diff --git a/templates/consumer-repo/.github/workflows/agents-auto-label.yml b/templates/consumer-repo/.github/workflows/agents-auto-label.yml index b671c8c4e..337b675be 100644 --- a/templates/consumer-repo/.github/workflows/agents-auto-label.yml +++ b/templates/consumer-repo/.github/workflows/agents-auto-label.yml @@ -35,6 +35,7 @@ jobs: !contains(github.event.issue.labels.*.name, 'agents:autofix') && !contains(github.event.issue.labels.*.name, 'agent:codex') && !contains(github.event.issue.labels.*.name, 'agent:claude') && + !contains(github.event.issue.labels.*.name, 'agent:cursor') && !contains(github.event.issue.labels.*.name, 'agent:auto') && !contains(join(github.event.issue.labels.*.name, ','), 'campaign:') && !contains(github.event.issue.labels.*.name, 'automated') diff --git a/templates/consumer-repo/.github/workflows/agents-auto-pilot.yml b/templates/consumer-repo/.github/workflows/agents-auto-pilot.yml index e620eec1a..f5c347096 100644 --- a/templates/consumer-repo/.github/workflows/agents-auto-pilot.yml +++ b/templates/consumer-repo/.github/workflows/agents-auto-pilot.yml @@ -1,6 +1,14 @@ # See docs/ci/AGENTS_POLICY.md for guardrails and override process. name: Agents Auto-Pilot +# Correlating a run to its issue needs the issue number in the run title; +# without it `gh run list` shows only the workflow name and no per-issue +# history can be reconstructed. +run-name: >- + Agents Auto-Pilot + #${{ github.event.issue.number || github.event.pull_request.number || + inputs.issue_number }} + # End-to-end automation: Issue → Format → Optimize → Apply → Agent → Keepalive → Merge # Triggered by: # 1. agents:auto-pilot label (initial trigger) @@ -184,7 +192,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ steps.app_token.outputs.token || github.token }} - repository: stranske/Workflows + repository: iamkayleb/Workflows ref: ${{ steps.workflows_ref.outputs.ref }} sparse-checkout: | .github/actions/setup-api-client @@ -2880,12 +2888,53 @@ jobs: owner: context.repo.owner, repo: context.repo.repo })); - const baseBranch = repoInfo.default_branch; - if (!baseBranch) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Resolve the pull request base the same way the issue bridge and the + // belt dispatcher do: the issue's `` marker + // wins over the repository default. Auto-pilot opens the pull request + // itself, so without this the marker is honoured when the branch is + // cut and then ignored when the PR is opened — the PR lands on the + // default branch and any per-lane workflow silently collapses. + let baseBranch = defaultBranch; + let baseSource = 'default'; + try { + const { data: baseIssue } = await withRetry((client) => + client.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + })); + const marker = String(baseIssue.body || '') + .match(//); + if (marker) { + const candidate = marker[1].trim(); + if (candidate && candidate !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: candidate, + })); + baseBranch = candidate; + baseSource = 'issue-marker'; + } catch (branchError) { + core.warning( + `Base branch '${candidate}' from the issue marker was not found; ` + + `falling back to '${defaultBranch}'.`); + baseSource = 'default-fallback'; + } + } + } + } catch (markerError) { + core.warning(`Could not read issue #${issueNumber} for a base-branch marker: ${markerError.message}`); + } + core.info(`Base branch: ${baseBranch} (source: ${baseSource})`); + // If a PR already exists for this branch, stop create-pr loop try { const headRef = `${context.repo.owner}:${branchName}`; @@ -3751,12 +3800,53 @@ jobs: owner: context.repo.owner, repo: context.repo.repo })); - const baseBranch = repoInfo.default_branch; - if (!baseBranch) { + const defaultBranch = repoInfo.default_branch; + if (!defaultBranch) { core.setFailed('Repository default branch not available'); return; } + // Resolve the pull request base the same way the issue bridge and the + // belt dispatcher do: the issue's `` marker + // wins over the repository default. Auto-pilot opens the pull request + // itself, so without this the marker is honoured when the branch is + // cut and then ignored when the PR is opened — the PR lands on the + // default branch and any per-lane workflow silently collapses. + let baseBranch = defaultBranch; + let baseSource = 'default'; + try { + const { data: baseIssue } = await withRetry((client) => + client.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + })); + const marker = String(baseIssue.body || '') + .match(//); + if (marker) { + const candidate = marker[1].trim(); + if (candidate && candidate !== defaultBranch) { + try { + await withRetry((client) => client.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: candidate, + })); + baseBranch = candidate; + baseSource = 'issue-marker'; + } catch (branchError) { + core.warning( + `Base branch '${candidate}' from the issue marker was not found; ` + + `falling back to '${defaultBranch}'.`); + baseSource = 'default-fallback'; + } + } + } + } catch (markerError) { + core.warning(`Could not read issue #${issueNumber} for a base-branch marker: ${markerError.message}`); + } + core.info(`Base branch: ${baseBranch} (source: ${baseSource})`); + const scriptsPath = process.env.WORKFLOWS_SCRIPTS_PATH || process.env.GITHUB_WORKSPACE; const { redispatchForceStep } = require( `${scriptsPath}/.github/scripts/auto_pilot_transitions.js` diff --git a/templates/consumer-repo/.github/workflows/agents-capability-check.yml b/templates/consumer-repo/.github/workflows/agents-capability-check.yml index 2e5a3fd59..93b85c389 100644 --- a/templates/consumer-repo/.github/workflows/agents-capability-check.yml +++ b/templates/consumer-repo/.github/workflows/agents-capability-check.yml @@ -21,7 +21,7 @@ jobs: capability-check: runs-on: ubuntu-latest # Trigger when an agent assignment label is added (pre-agent gate) - if: contains(fromJSON('["agent:codex","agent:claude","agent:auto"]'), github.event.label.name) + if: contains(fromJSON('["agent:codex","agent:claude","agent:cursor","agent:auto"]'), github.event.label.name) steps: - name: Checkout repository diff --git a/templates/consumer-repo/.github/workflows/agents-guard.yml b/templates/consumer-repo/.github/workflows/agents-guard.yml index 038e8ec27..5afa47402 100644 --- a/templates/consumer-repo/.github/workflows/agents-guard.yml +++ b/templates/consumer-repo/.github/workflows/agents-guard.yml @@ -111,7 +111,7 @@ jobs: github.event_name == 'pull_request_target' && steps.eligibility.outputs.should-run == 'true' && steps.api_client_base.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@ebef44a616c1da319b1dc96658978fd8f621a00c" # v1 + uses: "iamkayleb/Workflows/.github/actions/setup-api-client@e85edadb246e41d172a0c79fad147752d1df9ea9" # v1 with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} @@ -180,7 +180,7 @@ jobs: steps.eligibility.outputs.should-run == 'true' && github.event_name == 'pull_request' && steps.api_client_head.outputs.available != 'true' - uses: "stranske/Workflows/.github/actions/setup-api-client@ebef44a616c1da319b1dc96658978fd8f621a00c" # v1 + uses: "iamkayleb/Workflows/.github/actions/setup-api-client@e85edadb246e41d172a0c79fad147752d1df9ea9" # v1 with: secrets: ${{ toJSON(secrets) }} github_token: ${{ github.token }} diff --git a/templates/consumer-repo/.github/workflows/agents-issue-intake.yml b/templates/consumer-repo/.github/workflows/agents-issue-intake.yml index 6fc3bbcf2..00181bfcc 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-intake.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-intake.yml @@ -176,7 +176,7 @@ jobs: if: | needs.route.outputs.should_run_bridge == 'true' && needs.check_labels.outputs.should_run == 'true' - uses: stranske/Workflows/.github/workflows/reusable-agents-issue-bridge.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-agents-issue-bridge.yml@main with: agent: ${{ needs.check_labels.outputs.agent }} issue_number: ${{ needs.check_labels.outputs.issue_number }} @@ -204,7 +204,7 @@ jobs: id-token: write models: read pull-requests: write - uses: stranske/Workflows/.github/workflows/agents-63-issue-intake.yml@main + uses: iamkayleb/Workflows/.github/workflows/agents-63-issue-intake.yml@main with: intake_mode: "chatgpt_sync" source: ${{ inputs.topic_files }} diff --git a/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml b/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml index eec484a4c..76c2c9d00 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml @@ -5,7 +5,13 @@ name: Agents Issue Optimizer # nothing and reported 0 for an issue it was re-running every minute. Pin the issue # number into the run name so both trigger types are correlatable. run-name: >- - Agents Issue Optimizer #${{ github.event.issue.number || github.event.inputs.issue_number }} + Agents Issue Optimizer + ${{ (github.event_name == 'workflow_dispatch' || + github.event.label.name == 'agents:format' || + github.event.label.name == 'agents:optimize' || + github.event.label.name == 'agents:apply-suggestions') + && '[work]' || '[noop]' }} + #${{ github.event.issue.number || github.event.inputs.issue_number }} on: issues: @@ -198,7 +204,7 @@ jobs: if: steps.check.outputs.should_run == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows path: workflows-scripts sparse-checkout: | .github/scripts/issue_format.py @@ -241,6 +247,14 @@ jobs: # `gh run list` defaults to 20 runs, which a tight loop exhausts inside the # window; ask for enough history to actually see the recursion. + # + # Count only runs that could do work. This workflow triggers on EVERY + # `labeled` event, but only agents:format / agents:optimize / + # agents:apply-suggestions (and workflow_dispatch) reach the optimizer; + # every other label spawns a run that exits at the trigger check. Those + # no-ops used to count here, so applying three labels to a new issue + # burned the whole budget before any real work started and the guard + # tripped on legitimate bulk seeding. run-name marks them [noop]. # shellcheck disable=SC2016 count=$(gh run list \ --workflow=agents-issue-optimizer.yml \ @@ -249,7 +263,8 @@ jobs: | jq --arg cutoff "$one_hour_ago" \ --arg issue "#$ISSUE_NUMBER" \ '[.[] | select(.createdAt > $cutoff - and (.displayTitle | endswith($issue))) + and (.displayTitle | endswith($issue)) + and (.displayTitle | contains("[noop]") | not)) ] | length') echo "Optimizer runs for issue #$ISSUE_NUMBER in last hour: $count" diff --git a/templates/consumer-repo/.github/workflows/agents-pr-health.yml b/templates/consumer-repo/.github/workflows/agents-pr-health.yml index b4e8eb6ec..5c2fe37a4 100644 --- a/templates/consumer-repo/.github/workflows/agents-pr-health.yml +++ b/templates/consumer-repo/.github/workflows/agents-pr-health.yml @@ -52,7 +52,7 @@ concurrency: jobs: health: - uses: stranske/Workflows/.github/workflows/reusable-agents-pr-health.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-agents-pr-health.yml@main with: dry_run: ${{ inputs.dry_run && 'true' || 'false' }} max_prs: ${{ inputs.max_prs || '10' }} diff --git a/templates/consumer-repo/.github/workflows/agents-verifier.yml b/templates/consumer-repo/.github/workflows/agents-verifier.yml index 45a64a449..f5ae3ad60 100644 --- a/templates/consumer-repo/.github/workflows/agents-verifier.yml +++ b/templates/consumer-repo/.github/workflows/agents-verifier.yml @@ -300,7 +300,7 @@ jobs: if: >- needs.check.outputs.should_run == 'true' && needs.check.outputs.fingerprint_should_run == 'true' - uses: stranske/Workflows/.github/workflows/reusable-agents-verifier.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-agents-verifier.yml@main with: # CI workflows to wait for before running verifier ci_workflows: '["ci.yml", "pr-00-gate.yml"]' diff --git a/templates/consumer-repo/.github/workflows/agents-verify-to-new-pr.yml b/templates/consumer-repo/.github/workflows/agents-verify-to-new-pr.yml index d0682a3f4..a0918af0f 100644 --- a/templates/consumer-repo/.github/workflows/agents-verify-to-new-pr.yml +++ b/templates/consumer-repo/.github/workflows/agents-verify-to-new-pr.yml @@ -29,6 +29,9 @@ jobs: create-new-pr: if: github.event.label.name == 'verify:create-new-pr' runs-on: ubuntu-latest + env: + WORKFLOWS_APP_ID: ${{ secrets.WORKFLOWS_APP_ID || '' }} + WORKFLOWS_APP_PRIVATE_KEY: ${{ secrets.WORKFLOWS_APP_PRIVATE_KEY || '' }} steps: - name: Check PR is merged id: check-merged @@ -45,21 +48,44 @@ jobs: core.setOutput('pr_number', pr.number); core.setOutput('pr_title', pr.title); + - name: Mint GitHub App token + id: app_token + if: >- + steps.check-merged.outputs.merged == 'true' && + env.WORKFLOWS_APP_ID != '' && env.WORKFLOWS_APP_PRIVATE_KEY != '' + continue-on-error: true + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ env.WORKFLOWS_APP_ID }} + private-key: ${{ env.WORKFLOWS_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + - name: Select GitHub token id: select-token if: steps.check-merged.outputs.merged == 'true' env: + APP_TOKEN: ${{ steps.app_token.outputs.token }} OWNER_PR_PAT: ${{ secrets.OWNER_PR_PAT }} SERVICE_BOT_PAT: ${{ secrets.SERVICE_BOT_PAT }} GITHUB_TOKEN: ${{ github.token }} run: | - if [ -n "$OWNER_PR_PAT" ]; then + # Prefer an App installation token. Issues and labels created with + # GITHUB_TOKEN raise no events for other workflows (GitHub's loop + # guard), so a follow-up issue created that way is never picked up by + # auto-pilot and waits until a human re-applies its label by hand. + if [ -n "$APP_TOKEN" ]; then + echo "token=$APP_TOKEN" >> "$GITHUB_OUTPUT" + echo "source=workflows-app" >> "$GITHUB_OUTPUT" + elif [ -n "$OWNER_PR_PAT" ]; then echo "token=$OWNER_PR_PAT" >> "$GITHUB_OUTPUT" echo "source=owner-pat" >> "$GITHUB_OUTPUT" elif [ -n "$SERVICE_BOT_PAT" ]; then echo "token=$SERVICE_BOT_PAT" >> "$GITHUB_OUTPUT" echo "source=service-bot" >> "$GITHUB_OUTPUT" else + echo "::warning::No App token or PAT; using GITHUB_TOKEN." + echo "::warning::The follow-up issue will NOT trigger auto-pilot." + echo "::warning::Set WORKFLOWS_APP_ID + _PRIVATE_KEY, or SERVICE_BOT_PAT." echo "token=$GITHUB_TOKEN" >> "$GITHUB_OUTPUT" echo "source=github-token" >> "$GITHUB_OUTPUT" fi @@ -68,7 +94,7 @@ jobs: if: steps.check-merged.outputs.merged == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: stranske/Workflows + repository: iamkayleb/Workflows token: ${{ steps.select-token.outputs.token }} sparse-checkout: | .github/actions/setup-api-client diff --git a/templates/consumer-repo/.github/workflows/autofix.yml b/templates/consumer-repo/.github/workflows/autofix.yml index 47c2d3bf9..f343d5998 100644 --- a/templates/consumer-repo/.github/workflows/autofix.yml +++ b/templates/consumer-repo/.github/workflows/autofix.yml @@ -519,7 +519,7 @@ jobs: if: >- needs.resolve.outputs.should_run == 'true' && needs.resolve.outputs.dispatch_should_run == 'true' - uses: stranske/Workflows/.github/workflows/reusable-18-autofix.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-18-autofix.yml@main with: pr_number: ${{ fromJson(needs.resolve.outputs.pr_number) }} pr_head_ref: ${{ needs.resolve.outputs.pr_head_ref }} diff --git a/templates/consumer-repo/.github/workflows/backplane-conformance.yml b/templates/consumer-repo/.github/workflows/backplane-conformance.yml index 3898ec1e0..7e2e21c98 100644 --- a/templates/consumer-repo/.github/workflows/backplane-conformance.yml +++ b/templates/consumer-repo/.github/workflows/backplane-conformance.yml @@ -52,7 +52,7 @@ jobs: conformance: needs: emit-reference-run - uses: stranske/Workflows/.github/workflows/reusable-backplane-conformance.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-backplane-conformance.yml@main with: run_json_path: artifacts/reference/run.json manifest_path: artifacts/reference/manifest.json diff --git a/templates/consumer-repo/.github/workflows/ci.yml b/templates/consumer-repo/.github/workflows/ci.yml index 2715b8691..4f74073b1 100644 --- a/templates/consumer-repo/.github/workflows/ci.yml +++ b/templates/consumer-repo/.github/workflows/ci.yml @@ -31,7 +31,7 @@ on: jobs: python: name: Python CI - uses: stranske/Workflows/.github/workflows/reusable-10-ci-python.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-10-ci-python.yml@main with: python-versions: '["3.12", "3.13"]' typecheck: true diff --git a/templates/consumer-repo/.github/workflows/cross-repo-smoke.yml b/templates/consumer-repo/.github/workflows/cross-repo-smoke.yml index 9742880ed..672f7d9f2 100644 --- a/templates/consumer-repo/.github/workflows/cross-repo-smoke.yml +++ b/templates/consumer-repo/.github/workflows/cross-repo-smoke.yml @@ -32,7 +32,7 @@ jobs: vars.CROSS_REPO_SMOKE_DEPENDENCY_REPO != '' && vars.CROSS_REPO_SMOKE_RUN_COMMAND != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) - uses: stranske/Workflows/.github/workflows/reusable-13-cross-repo-smoke.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-13-cross-repo-smoke.yml@main with: dependency_repo: ${{ vars.CROSS_REPO_SMOKE_DEPENDENCY_REPO }} dependency_ref: ${{ vars.CROSS_REPO_SMOKE_DEPENDENCY_REF || 'main' }} diff --git a/templates/consumer-repo/.github/workflows/health-codex-auth-check.yml b/templates/consumer-repo/.github/workflows/health-codex-auth-check.yml new file mode 100644 index 000000000..be94e8f7e --- /dev/null +++ b/templates/consumer-repo/.github/workflows/health-codex-auth-check.yml @@ -0,0 +1,305 @@ +# Scheduled check for Codex auth token expiration +# Creates an issue when token is close to expiring to prompt manual refresh +name: Health 46 Codex Auth Check + +on: + schedule: + # Run twice daily at 8am and 8pm UTC + - cron: '0 8,20 * * *' + workflow_dispatch: + inputs: + force_check: + description: 'Force check even if issue exists' + type: boolean + default: false + +permissions: + contents: read + issues: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-expiration: + runs-on: ubuntu-latest + steps: + - name: Checkout retry helpers + uses: actions/checkout@v7 + with: + sparse-checkout: | + .github/actions/setup-api-client + .github/scripts/error_classifier.js + .github/scripts/github-api-with-retry.js + .github/scripts/token_load_balancer.js + sparse-checkout-cone-mode: false + + - name: Setup API client + uses: ./.github/actions/setup-api-client + with: + secrets: ${{ toJSON(secrets) }} + github_token: ${{ github.token }} + + + + - name: Check for existing open issue + id: existing + uses: actions/github-script@v9 + with: + github-token: ${{ github.token }} + script: | + const fs = require('fs'); + const retryHelperPath = './.github/scripts/github-api-with-retry.js'; + const retryHelpers = fs.existsSync(retryHelperPath) + ? require(retryHelperPath) + : { + withRetry: (fn) => fn(), + paginateWithRetry: (githubInstance, method, params) => + githubInstance.paginate(method, params), + }; + const { withRetry } = retryHelpers; + + const issues = await withRetry(() => github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'auth-expiring', + per_page: 1 + })); + const exists = issues.data.length > 0; + console.log(`Existing auth-expiring issue: ${exists}`); + if (exists) { + console.log(`Issue #${issues.data[0].number}: ${issues.data[0].title}`); + } + return exists; + result-encoding: string + + - name: Check token expiration + id: check + env: + CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} + run: | + set -euo pipefail + + if [ -z "$CODEX_AUTH_JSON" ]; then + echo "status=missing" >> "$GITHUB_OUTPUT" + echo "::error::CODEX_AUTH_JSON secret is not set" + exit 0 + fi + + # Write auth.json temporarily + mkdir -p ~/.codex + echo "$CODEX_AUTH_JSON" > ~/.codex/auth.json + + # Check expiration with Python + python3 << 'PYEOF' + import json, base64, datetime, sys, os + + auth_path = os.path.expanduser("~/.codex/auth.json") + gh_output = os.environ.get("GITHUB_OUTPUT", "/dev/null") + + try: + with open(auth_path) as f: + auth = json.load(f) + token = auth.get("tokens", {}).get("access_token", "") + if not token: + with open(gh_output, "a") as out: + out.write("status=invalid\n") + print("No access token found") + sys.exit(0) + + parts = token.split(".") + if len(parts) != 3: + with open(gh_output, "a") as out: + out.write("status=invalid\n") + print("Invalid JWT format") + sys.exit(0) + + payload = parts[1] + "=" * (4 - len(parts[1]) % 4) + data = json.loads(base64.urlsafe_b64decode(payload)) + exp_time = datetime.datetime.fromtimestamp(data["exp"], tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.timezone.utc) + diff = exp_time - now + days_left = diff.days + hours_left = diff.total_seconds() / 3600 + + print(f"Token expires: {exp_time.isoformat()}") + print(f"Days until expiration: {days_left}") + print(f"Hours until expiration: {hours_left:.1f}") + + with open(gh_output, "a") as out: + out.write(f"expires_at={exp_time.strftime('%Y-%m-%d %H:%M UTC')}\n") + out.write(f"days_left={days_left}\n") + out.write(f"hours_left={hours_left:.0f}\n") + + if days_left < 0: + out.write("status=expired\n") + elif days_left < 2: + out.write("status=expiring-soon\n") + elif days_left < 5: + out.write("status=expiring\n") + else: + out.write("status=ok\n") + + except Exception as e: + print(f"Error checking token: {e}") + with open(gh_output, "a") as out: + out.write("status=error\n") + PYEOF + + # Cleanup + rm -f ~/.codex/auth.json + + - name: Create expiration warning issue + if: >- + steps.existing.outputs.result != 'true' && + (steps.check.outputs.status == 'expiring-soon' || + steps.check.outputs.status == 'expired') + uses: actions/github-script@v9 + with: + github-token: ${{ github.token }} + script: | + const fs = require('fs'); + const retryHelperPath = './.github/scripts/github-api-with-retry.js'; + const retryHelpers = fs.existsSync(retryHelperPath) + ? require(retryHelperPath) + : { + withRetry: (fn) => fn(), + paginateWithRetry: (githubInstance, method, params) => + githubInstance.paginate(method, params), + }; + const { withRetry } = retryHelpers; + const status = '${{ steps.check.outputs.status }}'; + const expiresAt = '${{ steps.check.outputs.expires_at }}'; + const hoursLeft = '${{ steps.check.outputs.hours_left }}'; + const daysLeft = '${{ steps.check.outputs.days_left }}'; + + const isExpired = status === 'expired'; + const title = isExpired + ? '🚨 CODEX_AUTH_JSON has expired - CI agents broken' + : `⚠️ CODEX_AUTH_JSON expires in ${hoursLeft} hours`; + + const body = `## Codex Authentication Token ${isExpired ? 'Expired' : 'Expiring Soon'} + + ${isExpired + ? '**The token has already expired.** CI workflows using Codex are currently broken.' + : `**Token expires:** ${expiresAt} (${hoursLeft} hours / ${daysLeft} days remaining)` + } + + ### Action Required + + Run the device authentication flow to refresh the token: + + \`\`\`bash + # 1. Authenticate with device code flow + codex login --device-auth + + # 2. Follow the prompts: + # - Go to https://auth.openai.com/codex/device + # - Enter the code displayed in terminal + # - Complete authentication + + # 3. Copy the new auth.json content + cat ~/.codex/auth.json + \`\`\` + + ### Update GitHub Secret + + 1. Go to Repository Secrets: + + https://github.com/${context.repo.owner}/${context.repo.repo}/settings/secrets/actions + 2. Click on \`CODEX_AUTH_JSON\` → **Update** + 3. Paste the JSON content from step 3 above + 4. Click **Update secret** + + ### Affected Workflows + + - \`agents-keepalive-loop.yml\` - Codex agent task processing + - \`agents-autofix-loop.yml\` - Automated CI failure fixes + - \`agents-verifier.yml\` - PR verification + - \`reusable-codex-run.yml\` - Reusable Codex runner + + ### Documentation + + Documentation link: + + /${context.repo.owner}/${context.repo.repo}/blob/main/docs/ci/CHATGPT_SUBSCRIPTION_CI.md + + See the linked doc for full details on ChatGPT subscription authentication in CI. + + --- + *This issue was automatically created by the \`health-codex-auth-check\` workflow.* + `; + + await withRetry(() => + github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: ['auth-expiring', 'ci', 'priority:high'], + }) + ); + + console.log(`Created issue: ${title}`); + + - name: Summary + env: + STATUS: ${{ steps.check.outputs.status }} + EXISTING: ${{ steps.existing.outputs.result }} + EXPIRES_AT: ${{ steps.check.outputs.expires_at }} + DAYS_LEFT: ${{ steps.check.outputs.days_left }} + HOURS_LEFT: ${{ steps.check.outputs.hours_left }} + run: | + { + echo "## Codex Auth Check Results" + echo "" + + if [ -z "$STATUS" ]; then + echo "⏭️ **Skipped** - Check was not run" + elif [ "$STATUS" = "ok" ]; then + echo "✅ **Token OK** - Expires $EXPIRES_AT ($DAYS_LEFT days)" + elif [ "$STATUS" = "expiring" ]; then + echo "ℹ️ **Token expiring** - Expires $EXPIRES_AT ($DAYS_LEFT days)" + elif [ "$STATUS" = "expiring-soon" ]; then + echo "⚠️ **Token expiring soon** - Expires $EXPIRES_AT ($HOURS_LEFT hours)" + echo "" + if [ "$EXISTING" = "true" ]; then + echo "🎫 Existing auth-expiring issue remains open" + else + echo "🎫 Issue created to prompt token refresh" + fi + elif [ "$STATUS" = "expired" ]; then + echo "🚨 **Token EXPIRED** - Was due $EXPIRES_AT" + echo "" + if [ "$EXISTING" = "true" ]; then + echo "🎫 Existing auth-expiring issue remains open" + else + echo "🎫 Issue created - CI agents are broken" + fi + elif [ "$STATUS" = "missing" ]; then + echo "❌ **Secret missing** - CODEX_AUTH_JSON is not set" + elif [ "$STATUS" = "invalid" ]; then + echo "❌ **Secret invalid** - CODEX_AUTH_JSON has no usable access token" + elif [ "$STATUS" = "error" ]; then + echo "❌ **Auth check failed** - CODEX_AUTH_JSON could not be decoded" + else + echo "⚠️ **Unknown status**: $STATUS" + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Fail on unusable auth + if: always() + env: + STATUS: ${{ steps.check.outputs.status }} + run: | + case "$STATUS" in + missing|invalid|error) + echo "::error::CODEX_AUTH_JSON is ${STATUS}; Codex workflows cannot authenticate." + exit 1 + ;; + *) + exit 0 + ;; + esac diff --git a/templates/consumer-repo/.github/workflows/pr-00-gate.yml b/templates/consumer-repo/.github/workflows/pr-00-gate.yml index 8f5f0defe..87a4cc75d 100644 --- a/templates/consumer-repo/.github/workflows/pr-00-gate.yml +++ b/templates/consumer-repo/.github/workflows/pr-00-gate.yml @@ -171,7 +171,7 @@ jobs: contents: read steps: - name: Require an exact-head delivery seal - uses: stranske/Workflows/.github/actions/generated-delivery-seal@632eb20586f8403219d101e8a982b62efeb94104 + uses: iamkayleb/Workflows/.github/actions/generated-delivery-seal@e85edadb246e41d172a0c79fad147752d1df9ea9 python-ci: name: python ci @@ -184,7 +184,7 @@ jobs: needs.detect.outputs.is_python_code == 'true' && needs.detect.outputs.run_core == 'true' }} - uses: stranske/Workflows/.github/workflows/reusable-10-ci-python.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-10-ci-python.yml@main secrets: inherit permissions: contents: read @@ -406,7 +406,7 @@ jobs: fromJSON(needs.detect.outputs.run_core || 'true') && fromJSON(needs.detect.outputs.docker_changed || 'false') }} - uses: stranske/Workflows/.github/workflows/reusable-12-ci-docker.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-12-ci-docker.yml@main ledger-validation: name: ledger validation diff --git a/templates/consumer-repo/.github/workflows/pr-46-dependency-repair-contract.yml b/templates/consumer-repo/.github/workflows/pr-46-dependency-repair-contract.yml index a7ad4ca22..0fcb3c638 100644 --- a/templates/consumer-repo/.github/workflows/pr-46-dependency-repair-contract.yml +++ b/templates/consumer-repo/.github/workflows/pr-46-dependency-repair-contract.yml @@ -31,7 +31,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'dependency:repair-promotion') && (contains(github.event.pull_request.labels.*.name, 'workflow:source-dependabot') || contains(github.event.pull_request.labels.*.name, 'workflow_source_dependabot'))) - uses: stranske/Workflows/.github/workflows/reusable-19-dependency-repair-contract.yml@main + uses: iamkayleb/Workflows/.github/workflows/reusable-19-dependency-repair-contract.yml@main # `contract` is gated on the two trust labels, and a skipped job reports # success. Without this guard a same-repository promotion marker whose labels diff --git a/tests/scripts/test_generate_consumer_renovate_ownership.py b/tests/scripts/test_generate_consumer_renovate_ownership.py index fedf317d6..bf84f2f51 100644 --- a/tests/scripts/test_generate_consumer_renovate_ownership.py +++ b/tests/scripts/test_generate_consumer_renovate_ownership.py @@ -125,18 +125,50 @@ def test_manifest_managed_workflows_are_disabled_for_consumers(): ) +def _manifest_overwrite_repos(path: str) -> set[str]: + """Repos that opted a create_only path back into Maint 68 ownership.""" + manifest = yaml.safe_load(MANIFEST.read_text(encoding="utf-8")) or {} + for section in manifest.values(): + if not isinstance(section, list): + continue + for item in section: + if isinstance(item, dict) and item.get("target", item.get("source")) == path: + return set(item.get("overwrite_repos") or []) + raise AssertionError(f"{path} is not manifested") + + def test_create_only_targets_stay_visible_to_consumer_renovate(): - """`.github/workflows/ci.yml` is create_only, so consumers own their copy.""" + """create_only paths stay consumer-owned unless that repo opted into overwrite. + + The opt-out set is derived from each entry's `overwrite_repos` in the live + manifest rather than hardcoded here: a repo that opts in (stranske/Template for + byte-alignment, iamkayleb/bukay so the Gate receives the delivery-seal job) is + genuinely Maint-68-owned for that path, so its own Renovate must stay disabled + there. Hardcoding the list made this test fail the next time a repo opted in. + """ preset = committed_preset() - consumers = [ - repo for repo in registered_consumers() if repo not in {SOURCE_REPO, "stranske/Template"} - ] - for repo in consumers: - effective = disabled_paths(preset, repo) - assert ".github/workflows/ci.yml" not in effective - assert ".github/workflows/pr-00-gate.yml" not in effective - assert ".github/renovate.json" not in effective + for path in ( + ".github/workflows/ci.yml", + ".github/workflows/pr-00-gate.yml", + ".github/renovate.json", + ): + opted_in = _manifest_overwrite_repos(path) + for repo in registered_consumers(): + if repo == SOURCE_REPO or repo in opted_in: + continue + assert path not in disabled_paths(preset, repo), ( + f"{path} is create_only for {repo}; its own Renovate must still see it" + ) + + +def test_gate_overwrite_optin_is_managed_for_that_repo(): + """iamkayleb/bukay opted pr-00-gate.yml into overwrite, so Maint 68 owns it there.""" + effective = disabled_paths(committed_preset(), "iamkayleb/bukay") + + assert ".github/workflows/pr-00-gate.yml" in effective + # ci.yml stayed create_only for bukay, so the repo keeps owning its Node CI. + assert ".github/workflows/ci.yml" not in effective def test_overwrite_repos_opt_back_into_managed_paths(): diff --git a/tests/scripts/test_langsmith_fleet.py b/tests/scripts/test_langsmith_fleet.py index 660ce0387..a7adb631f 100644 --- a/tests/scripts/test_langsmith_fleet.py +++ b/tests/scripts/test_langsmith_fleet.py @@ -205,7 +205,7 @@ def test_markdown_summary_renders_mixed_valid_invalid_missing_rows() -> None: assert "- Invalid: 1" in markdown assert "- Missing: 1" in markdown assert "- Direct evidence: 3" in markdown - assert "- Not applicable: 4" in markdown + assert "- Not applicable: 5" in markdown # Per-repo status rows, one per status flavor. assert ( diff --git a/tests/scripts/test_metrics_dashboard_generator.py b/tests/scripts/test_metrics_dashboard_generator.py index 7ea44ce33..d7ce01777 100644 --- a/tests/scripts/test_metrics_dashboard_generator.py +++ b/tests/scripts/test_metrics_dashboard_generator.py @@ -257,7 +257,7 @@ def test_build_dashboard_from_path_includes_langsmith_fleet_status(tmp_path: Pat assert "- Stale: 0" in dashboard assert "- Invalid: 0" in dashboard assert "- Direct evidence: 3" in dashboard - assert "- Not applicable: 4" in dashboard + assert "- Not applicable: 5" in dashboard assert ( "| stranske/trip-planner | planner-runtime | artifact | " "stranske/trip-planner#1208 | valid |" in dashboard @@ -337,7 +337,7 @@ def summarize_with_fixed_now(*args: object, **kwargs: object) -> dict[str, objec assert "## LangSmith Fleet Artifact Status" in content assert "- Valid: 8" in content assert "- Direct evidence: 3" in content - assert "- Not applicable: 4" in content + assert "- Not applicable: 5" in content def test_build_dashboard_from_path_mixed_fleet_status(tmp_path: Path) -> None: @@ -366,7 +366,7 @@ def test_build_dashboard_from_path_mixed_fleet_status(tmp_path: Path) -> None: assert "- Invalid: 1" in dashboard assert "- Missing: 6" in dashboard assert "- Direct evidence: 3" in dashboard - assert "- Not applicable: 4" in dashboard + assert "- Not applicable: 5" in dashboard assert ( "| stranske/Workflows | agent-automation | artifact | " "stranske/Workflows#2150 | valid |" in dashboard diff --git a/tests/workflows/test_bot_comment_handler.py b/tests/workflows/test_bot_comment_handler.py index a4deaf367..59d7f67ad 100644 --- a/tests/workflows/test_bot_comment_handler.py +++ b/tests/workflows/test_bot_comment_handler.py @@ -180,8 +180,13 @@ def test_bot_comment_handler_callers_pass_app_client_id() -> None: reusable_jobs = [ job for job in workflow.get("jobs", {}).values() - if job.get("uses") - == "stranske/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main" + # Owner-agnostic: the root caller resolves the reusable from the + # upstream control plane while the consumer template resolves it from + # this fork. Which reusable is called, and with which secrets, is what + # this test is about — not who hosts it. + if str(job.get("uses") or "").endswith( + "/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main" + ) ] assert reusable_jobs, f"{caller_path} must call reusable-bot-comment-handler" @@ -322,7 +327,7 @@ def test_template_event_hub_uses_reusable_bot_comment_handler_defaults() -> None assert ( bot_comments_job.get("uses") - == "stranske/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main" + == "iamkayleb/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main" ) assert "ignored_paths" not in inputs diff --git a/tests/workflows/test_consumer_sync_create_only_evidence.py b/tests/workflows/test_consumer_sync_create_only_evidence.py index 72c5f51c8..3950d791d 100644 --- a/tests/workflows/test_consumer_sync_create_only_evidence.py +++ b/tests/workflows/test_consumer_sync_create_only_evidence.py @@ -32,11 +32,15 @@ def test_consumer_create_only_files_are_manifested() -> None: assert source in entries assert entries[source]["sync_mode"] == "create_only" - for source in ( - ".github/workflows/pr-00-gate.yml", - ".github/workflows/ci.yml", - ): - assert entries[source]["overwrite_repos"] == ["stranske/Template"] + # Gate overwrite is per-repo: stranske/Template stays byte-aligned with the + # canonical template, and iamkayleb/bukay opted in so the delivery-seal job + # reaches its Gate (a create_only Gate would never receive that job, and + # Maint 71 will not merge a delivery whose head is unsealed). + assert entries[".github/workflows/pr-00-gate.yml"]["overwrite_repos"] == [ + "stranske/Template", + "iamkayleb/bukay", + ] + assert entries[".github/workflows/ci.yml"]["overwrite_repos"] == ["stranske/Template"] # .github/dependabot.yml was intentionally dropped from the template and the # manifest in #2401 (P3b of the Renovate fleet migration): create_only sync @@ -50,7 +54,7 @@ def test_consumer_renovate_template_extends_fleet_preset() -> None: (REPO_ROOT / "templates/consumer-repo/.github/renovate.json").read_text(encoding="utf-8") ) - assert template["extends"] == ["github>stranske/Workflows//renovate-presets/fleet"] + assert template["extends"] == ["github>iamkayleb/Workflows//renovate-presets/fleet"] def test_fine_art_archive_jsonschema_renovate_exception_is_repo_scoped() -> None: diff --git a/tests/workflows/test_github_api_retry_standard.py b/tests/workflows/test_github_api_retry_standard.py index 1ef5a3f3a..920728004 100644 --- a/tests/workflows/test_github_api_retry_standard.py +++ b/tests/workflows/test_github_api_retry_standard.py @@ -159,7 +159,9 @@ def test_agents_verifier_callers_pass_checked_pr_number() -> None: for relative_path in caller_paths: workflow = _load_workflow(REPO_ROOT / relative_path) verifier_job = workflow["jobs"]["verifier"] - assert verifier_job["uses"] == ( - "stranske/Workflows/.github/workflows/reusable-agents-verifier.yml@main" + # Owner-agnostic: root resolves the reusable upstream, the consumer + # template resolves it from this fork. + assert str(verifier_job["uses"]).endswith( + "/Workflows/.github/workflows/reusable-agents-verifier.yml@main" ) assert verifier_job["with"]["pr_number"] == "${{ needs.check.outputs.pr_number }}" diff --git a/tests/workflows/test_langsmith_metrics_dashboard.py b/tests/workflows/test_langsmith_metrics_dashboard.py index 8d7824a85..7ab621013 100644 --- a/tests/workflows/test_langsmith_metrics_dashboard.py +++ b/tests/workflows/test_langsmith_metrics_dashboard.py @@ -43,6 +43,7 @@ def test_every_maintained_consumer_has_an_observability_state() -> None: "stranske/Ready", "stranske/Collab-Admin", "stranske/Orchestrator", + "iamkayleb/bukay", } assert {entry["status"] for entry in allowlist["repos"]} == {"not-applicable"} diff --git a/tests/workflows/test_model_profile_trial_workflows.py b/tests/workflows/test_model_profile_trial_workflows.py index f2bad5027..c818d7ffd 100644 --- a/tests/workflows/test_model_profile_trial_workflows.py +++ b/tests/workflows/test_model_profile_trial_workflows.py @@ -42,7 +42,7 @@ def test_dispatch_shim_is_single_arm_and_calls_only_pinned_reusable_runner(): assert list(workflow["jobs"]) == ["trial"] runner_ref = workflow["jobs"]["trial"]["uses"] assert re.fullmatch( - r"stranske/Workflows/\.github/workflows/" r"reusable-model-profile-trial\.yml@[0-9a-f]{40}", + r"iamkayleb/Workflows/\.github/workflows/" r"reusable-model-profile-trial\.yml@[0-9a-f]{40}", runner_ref, ) runner_sha = workflow["jobs"]["trial"]["with"]["runner_sha"] @@ -65,7 +65,7 @@ def test_reusable_runner_is_read_only_exact_cli_and_has_no_write_lane(): assert "--ignore-user-config" in source assert "persist-credentials: false" in source assert "expected_source_sha must equal current remote main before auth" in source - assert "git ls-remote https://github.com/stranske/Workflows.git refs/heads/main" in source + assert "git ls-remote https://github.com/iamkayleb/Workflows.git refs/heads/main" in source assert "target-src/scripts/" not in source assert "provider_resolved" not in source forbidden = ( @@ -109,7 +109,7 @@ def test_runner_uses_separate_pinned_helper_checkout_and_full_action_shas(): ] assert len(checkouts) == 2 assert checkouts[0]["with"] == { - "repository": "stranske/Workflows", + "repository": "iamkayleb/Workflows", "ref": "${{ inputs.runner_sha }}", "path": "runner-src", "persist-credentials": False, diff --git a/tests/workflows/test_renovate_fleet_policy.py b/tests/workflows/test_renovate_fleet_policy.py index 8e108f2d2..4c2686fb6 100644 --- a/tests/workflows/test_renovate_fleet_policy.py +++ b/tests/workflows/test_renovate_fleet_policy.py @@ -85,7 +85,10 @@ def test_lock_file_maintenance_has_the_same_explicit_weekly_cadence() -> None: def test_workflows_and_consumer_entrypoints_share_the_bounded_fleet_policy() -> None: - expected_preset = "github>stranske/Workflows//renovate-presets/fleet" + # Owner-agnostic: both entrypoints must extend exactly one fleet preset from a + # repository named Workflows. Root points at the upstream control plane; the + # consumer template points at this fork. + expected_preset_suffix = "/Workflows//renovate-presets/fleet" entrypoints = ( REPO_ROOT / "renovate.json", REPO_ROOT / "templates" / "consumer-repo" / ".github" / "renovate.json", @@ -93,7 +96,9 @@ def test_workflows_and_consumer_entrypoints_share_the_bounded_fleet_policy() -> for entrypoint in entrypoints: config = json.loads(entrypoint.read_text(encoding="utf-8")) - assert config["extends"] == [expected_preset] + assert len(config["extends"]) == 1 + assert config["extends"][0].startswith("github>") + assert config["extends"][0].endswith(expected_preset_suffix) preset = _preset() assert preset["prConcurrentLimit"] == preset["branchConcurrentLimit"] == 3 diff --git a/tests/workflows/test_reusable_ci_no_hardcoded_ref_main.py b/tests/workflows/test_reusable_ci_no_hardcoded_ref_main.py index b861857ae..5356af6ac 100644 --- a/tests/workflows/test_reusable_ci_no_hardcoded_ref_main.py +++ b/tests/workflows/test_reusable_ci_no_hardcoded_ref_main.py @@ -88,7 +88,7 @@ def test_workflows_ref_input_declared() -> None: def test_workflows_checkouts_use_workflows_ref_input() -> None: - """Every ``stranske/Workflows`` helper checkout resolves via the input.""" + """Every Workflows helper checkout resolves via the input.""" data = _load_workflow() jobs = data.get("jobs", {}) checked = 0 @@ -98,7 +98,11 @@ def test_workflows_checkouts_use_workflows_ref_input() -> None: if not uses.startswith("actions/checkout"): continue with_block = step.get("with", {}) or {} - if with_block.get("repository") != "stranske/Workflows": + # Owner-agnostic: match the repository NAME, not the owner. This guard + # is about the ref, and pinning the owner here turned it into a silent + # no-op the moment the helper layer was repointed at a fork — the + # `checked >= 4` floor below is what caught it. + if not str(with_block.get("repository") or "").endswith("/Workflows"): continue ref = str(with_block.get("ref", "")) checked += 1 diff --git a/tests/workflows/test_reusable_run_shared_base.py b/tests/workflows/test_reusable_run_shared_base.py index 8fbe68e6f..5c9fd0c05 100644 --- a/tests/workflows/test_reusable_run_shared_base.py +++ b/tests/workflows/test_reusable_run_shared_base.py @@ -26,6 +26,7 @@ from __future__ import annotations import os +import re import subprocess from pathlib import Path @@ -210,7 +211,9 @@ def test_extracted_setup_steps_not_duplicated_in_runners(workflow_rel: str) -> N # Runtime auth selection and the Workflows scripts checkout remain shared in # the composite; the target checkout is intentionally caller-owned. - assert "repository: stranske/Workflows" in src + # Owner-agnostic: the helper layer is vendored from whichever owner hosts the + # control plane, so assert on the repository name. + assert re.search(r"repository: \S+/Workflows\b", src) assert sum(_uses_base(step) == "actions/create-github-app-token" for step in steps) == 1, ( f"{workflow_rel}: only the run-base bootstrap checkout may mint an App " "token in the runner; shared runtime auth still belongs in " diff --git a/tests/workflows/test_sync_manifest_delivery.py b/tests/workflows/test_sync_manifest_delivery.py index f662f4971..f5abacabd 100644 --- a/tests/workflows/test_sync_manifest_delivery.py +++ b/tests/workflows/test_sync_manifest_delivery.py @@ -660,7 +660,15 @@ def test_gate_and_shared_mergers_hold_mutable_stable_deliveries() -> None: "@632eb20586f8403219d101e8a982b62efeb94104" ) assert action_ref in gate - assert action_ref in template_gate + # The consumer template resolves the seal from this fork, so it carries the + # same commit-pinned action under iamkayleb/Workflows. The pin is what matters: + # the seal must be evaluated from an immutable ref outside the consumer's own + # (sync-mutable) checkout, whichever owner hosts the control plane. + template_action_ref = ( + "iamkayleb/Workflows/.github/actions/generated-delivery-seal" + "@e85edadb246e41d172a0c79fad147752d1df9ea9" + ) + assert template_action_ref in template_gate assert ( "uses: ./.github/actions/path-classifier" not in gate.split("generated-delivery-seal:", 1)[1].split("\n python-ci:", 1)[0] diff --git a/tests/workflows/test_workflow_llm_installs.py b/tests/workflows/test_workflow_llm_installs.py index bc7b07fa7..6505beaf0 100644 --- a/tests/workflows/test_workflow_llm_installs.py +++ b/tests/workflows/test_workflow_llm_installs.py @@ -118,7 +118,12 @@ def _workflows_library_checkout_steps(workflow: dict) -> list[dict]: steps = [] for step in _iter_steps(workflow): with_block = step.get("with") or {} - if with_block.get("repository") == "stranske/Workflows" and "sparse-checkout" in with_block: + # Owner-agnostic on purpose: the consumer template resolves the control + # plane from whichever owner hosts this fork, so discovery must key on the + # repository NAME. Pinning the owner here silently dropped three template + # workflows out of coverage when the templates were repointed at the fork. + repository = str(with_block.get("repository") or "") + if repository.endswith("/Workflows") and "sparse-checkout" in with_block: steps.append(step) return steps