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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(wc:*)",
"Bash(ls:*)"
]
}
}

8 changes: 5 additions & 3 deletions .github/agents/registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ agents:
verifier_checkbox: true

claude:
display_name: Claude
runner_workflow: .github/workflows/reusable-claude-run.yml
required_secrets:
- CLAUDE_CODE_OAUTH_TOKEN # preferred: long-lived token from `claude setup-token`
Expand All @@ -35,15 +36,16 @@ agents:
branch_prefix: claude/issue-
ui_mentions_allowed: false
automation_logins:
- stranske-automation-bot
- kayleb-automation-bot
readiness_candidates:
- stranske-automation-bot
- kayleb-automation-bot
preflight:
assign_user: stranske-automation-bot
assign_user: kayleb-automation-bot
command_phrase: ''
enabled: true
capabilities:
pr_keepalive: true
pr_autofix: true
belt: true
verifier_checkbox: true

9 changes: 9 additions & 0 deletions .github/sync-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ scripts:
- source: tools/coverage_trend.py
description: "Generates coverage trend summaries - required by reusable CI workflow"

- source: tools/__init__.py
description: "Package init - required for tools.post_ci_summary import"

- source: tools/post_ci_summary.py
description: "Builds consolidated post-CI summary - required by pr-00-gate.yml summary job"

- source: tools/ci_failure_triage.py
description: "CI failure triage helper - required by post_ci_summary.py"

# Scripts directory - CI helpers used by reusable-10-ci-python.yml
- source: scripts/coverage_history_append.py
description: "Appends coverage data to history file - required by reusable CI workflow"
Expand Down
110 changes: 102 additions & 8 deletions .github/workflows/agents-capability-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,109 @@ name: Capability Check

# Pre-flight check before agent assignment to identify blockers
# Uses capability_check.py to detect issues agents cannot complete

#
# DYNAMIC: Triggers on ANY agent label from registry.yml
# No need to modify this workflow when adding new agents.

on:
issues:
types: [labeled]

permissions:
contents: read
issues: write
models: read

concurrency:
group: >-
agents-capability-check-${{ github.repository }}-
${{ github.event.issue.number || github.run_id }}
cancel-in-progress: false

jobs:
# First job: Check if the label is an agent label from registry
check-agent-label:
runs-on: ubuntu-latest
outputs:
is_agent_label: ${{ steps.check.outputs.is_agent_label }}
agent_id: ${{ steps.check.outputs.agent_id }}
agent_name: ${{ steps.check.outputs.agent_name }}
agent_label: ${{ steps.check.outputs.agent_label }}
steps:
- name: Checkout for registry
uses: actions/checkout@v6
with:
sparse-checkout: |
.github/agents/registry.yml
.github/scripts/agent-router.js
sparse-checkout-cone-mode: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm install js-yaml

- name: Check if label is an agent label
id: check
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const yaml = require('js-yaml');

const labelName = context.payload.label?.name || '';
console.log(`Label added: ${labelName}`);

// Load registry
let registry;
try {
const content = fs.readFileSync('.github/agents/registry.yml', 'utf8');
registry = yaml.load(content);
} catch (error) {
console.log(`Could not load registry: ${error.message}`);
// Fallback: check for known agent: prefix
if (labelName.startsWith('agent:')) {
core.setOutput('is_agent_label', 'true');
core.setOutput('agent_id', labelName.replace('agent:', ''));
core.setOutput('agent_name', labelName.replace('agent:', ''));
core.setOutput('agent_label', labelName);
return;
}
core.setOutput('is_agent_label', 'false');
return;
}

// Check if label matches any agent in registry
for (const [agentId, config] of Object.entries(registry.agents || {})) {
if (config.label === labelName) {
console.log(`Matched agent: ${agentId} (${config.name})`);
core.setOutput('is_agent_label', 'true');
core.setOutput('agent_id', agentId);
core.setOutput('agent_name', config.name || agentId);
core.setOutput('agent_label', labelName);
return;
}
}

// Also support generic agent: prefix for future agents
if (labelName.startsWith('agent:')) {
const agentId = labelName.replace('agent:', '');
console.log(`Unknown agent label, but has agent: prefix: ${agentId}`);
core.setOutput('is_agent_label', 'true');
core.setOutput('agent_id', agentId);
core.setOutput('agent_name', agentId);
core.setOutput('agent_label', labelName);
return;
}

console.log('Not an agent label');
core.setOutput('is_agent_label', 'false');

capability-check:
needs: check-agent-label
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)
Expand Down Expand Up @@ -122,6 +207,9 @@ jobs:
- name: Add needs-human label if blocked
if: steps.check.outputs.recommendation == 'BLOCKED'
uses: actions/github-script@v8
env:
AGENT_LABEL: ${{ needs.check-agent-label.outputs.agent_label }}
AGENT_NAME: ${{ needs.check-agent-label.outputs.agent_name }}
with:
script: |
const fs = require('fs');
Expand Down Expand Up @@ -176,6 +264,9 @@ jobs:
env:
RESULT_JSON: ${{ steps.check.outputs.result_json }}
RECOMMENDATION: ${{ steps.check.outputs.recommendation }}
AGENT_ID: ${{ needs.check-agent-label.outputs.agent_id }}
AGENT_NAME: ${{ needs.check-agent-label.outputs.agent_name }}
AGENT_LABEL: ${{ needs.check-agent-label.outputs.agent_label }}
with:
script: |
const fs = require('fs');
Expand All @@ -199,18 +290,21 @@ jobs:

const result = JSON.parse(process.env.RESULT_JSON || '{}');
const recommendation = process.env.RECOMMENDATION || 'UNKNOWN';

const agentName = process.env.AGENT_NAME || 'Agent';
const agentLabel = process.env.AGENT_LABEL || 'agent:unknown';

let emoji = '✅';
let status = 'Agent can proceed';
let status = `${agentName} can proceed`;
if (recommendation === 'BLOCKED') {
emoji = '🚫';
status = 'Agent cannot complete this issue';
status = `${agentName} cannot complete this issue`;
} else if (recommendation === 'REVIEW_NEEDED') {
emoji = '⚠️';
status = 'Some tasks may need human assistance';
}

let body = `### ${emoji} Capability Check: ${status}\n\n`;
body += `**Agent:** ${agentName} (\`${agentLabel}\`)\n`;
body += `**Recommendation:** ${recommendation}\n\n`;

if (result.actionable_tasks && result.actionable_tasks.length > 0) {
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/agents-keepalive-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ 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: >-
Expand Down Expand Up @@ -975,7 +975,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { autoReconcileTasks } = require('./.github/scripts/keepalive_loop.js');

const prNumber = Number('${{ needs.evaluate.outputs.pr_number }}') || 0;
const beforeSha = '${{ needs.evaluate.outputs.head_sha }}'; // SHA before agent ran
const headSha =
Expand Down Expand Up @@ -1104,7 +1104,7 @@ jobs:
tasks_unchecked: Number('${{ needs.evaluate.outputs.tasks_unchecked }}') || 0,
keepalive_enabled: '${{ needs.evaluate.outputs.keepalive_enabled }}',
autofix_enabled: '${{ needs.evaluate.outputs.autofix_enabled }}',
agent_type: '${{ needs.evaluate.outputs.agent_type }}',
agent_type: agentType,
trace: '${{ needs.evaluate.outputs.trace }}',
// Agent run result - check which agent ran
run_result: runResult,
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/maint-68-sync-consumer-repos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ env:
stranske/Portable-Alpha-Extension-Model
stranske/Trend_Model_Project
stranske/Collab-Admin

iamkayleb/Workflows-Integrations-Tests
concurrency:
group: sync-consumer-repos-${{ github.repository }}-${{ github.ref }}
cancel-in-progress: true
Expand Down Expand Up @@ -298,7 +298,9 @@ jobs:
matrix: ${{ fromJson(needs.prepare.outputs.repos) }}
env:
# Job-level alias for repo token (uses OWNER_PR_PAT or SERVICE_BOT_PAT)
REPO_TOKEN: ${{ secrets.OWNER_PR_PAT || secrets.SERVICE_BOT_PAT }}
# Falls back to github.token so clone/read ops succeed even without PATs;
# cross-repo PR creation still requires a PAT with repo scope.
REPO_TOKEN: ${{ secrets.OWNER_PR_PAT || secrets.SERVICE_BOT_PAT || github.token }}
steps:
- name: Checkout Workflows
uses: actions/checkout@v6
Expand Down Expand Up @@ -692,6 +694,16 @@ jobs:
f.write(f"- {s}\n")
SYNC_SCRIPT

- name: Verify cross-repo token
if: steps.sync.outputs.has_changes == 'true' && inputs.dry_run != true
run: |
if [ -z "${{ secrets.OWNER_PR_PAT }}" ] && [ -z "${{ secrets.SERVICE_BOT_PAT }}" ]; then
echo "::error::No cross-repo PAT available (OWNER_PR_PAT or SERVICE_BOT_PAT)."
echo "::error::Clone succeeded with GITHUB_TOKEN but PR creation requires a PAT with repo scope."
echo "::error::Configure OWNER_PR_PAT or SERVICE_BOT_PAT in repository/org secrets."
exit 1
fi

- name: Check for required labels
if: steps.sync.outputs.has_changes == 'true'
env:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr-00-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,10 @@ jobs:
const { upsertAnchoredComment } = require('./.github/scripts/comment-dedupe.js');

const commentPath = path.resolve('gate-summary.md');
if (!fs.existsSync(commentPath)) {
core.warning('gate-summary.md not found; skipping PR comment.');
return;
}
const body = fs.readFileSync(commentPath, 'utf8');
await upsertAnchoredComment({
github,
Expand Down
Loading