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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 11 additions & 23 deletions .github/workflows/repowise.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
name: RepoWise Code Health

on:
push:
branches: [master]
pull_request:
types: [opened, synchronize, reopened]
merge_group:
types: [checks_requested]
workflow_dispatch:

concurrency:
group: repowise-${{ github.event.pull_request.number || github.ref }}
group: repowise-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
code-health:
name: RepoWise code health
# A merge group can contain fork code without exposing the originating PR here.
runs-on: ${{ fromJSON((github.event_name == 'merge_group' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)) && '["ubuntu-latest"]' || '["self-hosted","codex","Linux"]') }}
runs-on: [self-hosted, codex, Linux]
timeout-minutes: 30
env:
REPOWISE_VENV_NAME: repowise-venv-${{ github.run_id }}-${{ github.run_attempt }}-code-health
REPOWISE_REPORT_NAME: repowise-report-${{ github.run_id }}-${{ github.run_attempt }}-code-health
REPOWISE_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || '' }}
REPOWISE_EVENT_NAME: ${{ github.event_name }}
steps:
- name: Verify Ubuntu runner
shell: bash
Expand Down Expand Up @@ -66,6 +61,8 @@ jobs:
report_dir="$RUNNER_TEMP/$REPOWISE_REPORT_NAME"
repowise_bin="$RUNNER_TEMP/$REPOWISE_VENV_NAME/bin/repowise"
mkdir -p "$report_dir"
analyzed_sha="$(git rev-parse HEAD)"
printf '%s\n' "$analyzed_sha" > "$report_dir/revision.txt"
cleanup() {
rm -rf .repowise .mcp.json
rm -f .vscode/mcp.json .vscode/extensions.json
Expand All @@ -90,23 +87,14 @@ jobs:
fi
}
advisory_scan dead-code "$repowise_bin" dead-code --safe-only --no-workspace --format json .
if [ "$REPOWISE_EVENT_NAME" = workflow_dispatch ]; then
echo 'security scope: full reachable history (manual dispatch)' >> "$report_dir/advisory-status.txt"
advisory_scan security "$repowise_bin" security scan --history --path . --format json
elif [[ "$REPOWISE_BASE_SHA" =~ ^[0-9a-f]{40}$ ]] \
&& git cat-file -e "$REPOWISE_BASE_SHA^{commit}" \
&& git merge-base --is-ancestor "$REPOWISE_BASE_SHA" HEAD; then
printf 'security scope: %s..%s\n' "$REPOWISE_BASE_SHA" "$(git rev-parse HEAD)" >> "$report_dir/advisory-status.txt"
advisory_scan security "$repowise_bin" security scan --history --since "$REPOWISE_BASE_SHA" --to HEAD --path . --format json
else
echo 'security: incomplete (event base unavailable or not an ancestor)' >> "$report_dir/advisory-status.txt"
echo '::warning::RepoWise security advisory scope could not be verified; no full-history fallback was attempted.'
fi
echo 'security scope: full reachable history (manual dispatch)' >> "$report_dir/advisory-status.txt"
advisory_scan security "$repowise_bin" security scan --history --path . --format json
{
echo '## RepoWise code health'
echo
echo 'Advisory reports are retained as workflow artifacts for this commit.'
echo
printf 'Analyzed revision: `%s`\n\n' "$analyzed_sha"
sed -n '1,100p' "$report_dir/health.md"
echo
echo '### Advisory scan status'
Expand Down
3 changes: 2 additions & 1 deletion docs/development/release-process.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ prepare release -> production PR -> accepted production SHA

For unattended completion, enable repository auto-merge and active required
validation on both production and development targets. This repository requires
GitHub Actions `CI Check` on `master` and `develop`; RepoWise is advisory.
GitHub Actions `CI Check` on `master` and `develop`; RepoWise is optional and
runs only through manual dispatch.
Default `auto` mode enables native auto-merge while a managed PR must wait.
When GitHub already reports the PR ready, Copilot requests a normal merge pinned
to its verified head SHA. This also handles pending advisory checks, without
Expand Down
36 changes: 22 additions & 14 deletions docs/development/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,28 @@ local topology before an architecture review with Graphify, and use the
RepoWise index-only health/dead-code reports to distinguish current complexity
from history-based churn signals:

The required `RepoWise code health` workflow publishes its index and health
reports. Its dead-code and history-security scans have separate time and memory limits;
the Job Summary and artifact status identify an incomplete advisory scan
instead of claiming that it passed.
For PRs, pushes and merge groups, history-security scans cover the native event
base through the checked-out HEAD, including commits that introduced and then
removed a finding. The base must be a valid commit and an ancestor of HEAD;
missing or unrelated evidence produces an incomplete advisory result. The
artifact records both immutable bounds. Manual dispatch scans the complete
reachable history within the same limits; a successful event-range scan is
not evidence of a complete historical audit.
Repository-owned PRs, pushes and merge groups run RepoWise and `CI Check` on
self-hosted Ubuntu `codex` runners. Fork PRs use GitHub-hosted Ubuntu for
these checks. The automatic setup fixture matrix remains hosted on three
`RepoWise Code Health` is an optional manual workflow. PRs, pushes and merge
groups do not launch it, and it must not be a required branch check. To run an
analysis, open **Actions → RepoWise Code Health → Run workflow**, select a
reviewed repository branch, and launch it. The CLI equivalent for development is
`gh workflow run repowise.yml --ref develop`. The workflow already exists on the
default branch, as required for [manual dispatch](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow).

Open the run's Job Summary for the analyzed commit and scan status. Download
the `repowise-code-health` artifact for the index log, Markdown/JSON health
reports, `revision.txt` and `advisory-status.txt`; artifacts are retained for
14 days. History security scans cover the complete reachable history of that
revision. Dead-code and security scans each have a 90-second, 2 GiB limit
inside the 30-minute job budget. An incomplete scan produces a warning and an
incomplete status while preserving completed health reports. A failed health
step fails the run; available files are still uploaded. Inspect the failure
before retrying. A newer manual run for the same ref cancels the older one;
cancellation or runner loss can prevent artifact publication. No manual run
blocks the automatic CI path.

Manual RepoWise runs use self-hosted Ubuntu `codex` runners. `CI Check` uses
that pool for repository-owned PRs and pushes; fork PRs and all merge groups
use disposable GitHub-hosted Ubuntu runners. The automatic setup fixture matrix remains hosted on three
operating systems, with service-runner fixtures available by manual dispatch.
Runner routing is contract-checked, but actual runner availability and remote
results require evidence from the corresponding Actions runs.
Expand Down
2 changes: 1 addition & 1 deletion docs/features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ jobs:
project-ids: '2,3'
```

Durable mutation workflows deliberately do not use a GitHub Actions `concurrency` block: native cancellation retains only one pending run and can discard intermediate issue, comment, or release mutations. The Commit and Pull Request templates are the deliberate exception for their own replaceable branch revisions, but they use distinct groups and cannot cancel one another. Native concurrency also remains appropriate for replaceable read-only workflows such as CI and RepoWise.
Durable mutation workflows deliberately do not use a GitHub Actions `concurrency` block: native cancellation retains only one pending run and can discard intermediate issue, comment, or release mutations. The Commit and Pull Request templates are the deliberate exception for their own replaceable branch revisions, but they use distinct groups and cannot cancel one another. Native concurrency also remains appropriate for replaceable read-only workflows such as CI and the optional, manually dispatched RepoWise analysis.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/issues/deployment-orchestration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Complete this once before starting the first production-first operation:
ancestry relationship;
- activate required validation on every promotion/reconciliation target. This
project requires GitHub Actions `CI Check` on both `master` and `develop`;
RepoWise remains advisory;
RepoWise is optional and runs only through manual dispatch;
- enable repository auto-merge if selecting `auto-merge`, or configure every
required check for `merge_group` if the target requires a merge queue, and
add an exact attestation only for a producer Copilot cannot inspect;
Expand Down
12 changes: 8 additions & 4 deletions docs/security-operations/security/self-hosted-runners.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ package-manager caching. This keeps a successful full validation from timing out
in the post-job archive step; hosted fixture caching is retained. See the
[setup-node cache inputs](https://github.com/actions/setup-node#usage).

This repository runs `CI Check` and `RepoWise code health` on self-hosted
Ubuntu `codex` runners for its own PRs and pushes. Fork PRs and all merge
groups keep the same quality checks on disposable GitHub-hosted Ubuntu
This repository runs `CI Check` on self-hosted Ubuntu `codex` runners for its
own PRs and pushes. Fork PRs and all merge groups run CI on disposable GitHub-hosted Ubuntu
runners. A merge group can contain fork code without exposing its origin to
the runner selector; never route it to a persistent service runner. The trusted-default-branch
PR approval observer also runs on a self-hosted Ubuntu `codex` runner. The
Expand All @@ -48,7 +47,12 @@ self-hosted runners. RepoWise obtains a complete Python 3.12 distribution via
`actions/setup-python`, creates its virtual environment and reports in
run-specific temporary directories, and removes them after artifact upload.
Its runner needs a writable Actions tool cache; the workflow does not install
Ubuntu system packages.
Ubuntu system packages. RepoWise runs only through manual dispatch on
`[self-hosted, codex, Linux]`, using the reviewed repository ref selected by the
maintainer. It is optional and must not be required by branch protections or
rulesets. It no longer consumes this pool on every PR or push. See
[testing and manual analysis](/development/testing) for launch instructions,
revision evidence, scan limits, artifacts, and retry behavior.

The `[self-hosted, codex]` pool routes agent jobs to macOS and Ubuntu. Windows
services MUST omit `codex` on registration; recreate existing registrations to
Expand Down
48 changes: 36 additions & 12 deletions scripts/validate-workflow-contract.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,38 @@ function assertRepoWiseTemporaryCleanup(relativeFile, job) {
throw new Error(`${relativeFile} must select complete Python, isolate RepoWise files, and remove them after artifact upload.`);
}
const reports = (job.steps ?? []).find(step => step?.name === 'Generate RepoWise reports');
if (job.env?.REPOWISE_BASE_SHA !== "${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || '' }}"
|| job.env?.REPOWISE_EVENT_NAME !== '${{ github.event_name }}'
|| !reports?.run?.includes('[[ "$REPOWISE_BASE_SHA" =~ ^[0-9a-f]{40}$ ]]')
|| !reports.run.includes('git cat-file -e "$REPOWISE_BASE_SHA^{commit}"')
|| !reports.run.includes('git merge-base --is-ancestor "$REPOWISE_BASE_SHA" HEAD')
|| !reports.run.includes('--since "$REPOWISE_BASE_SHA" --to HEAD')
|| !reports.run.includes('[ "$REPOWISE_EVENT_NAME" = workflow_dispatch ]')
|| !reports.run.includes('security: incomplete (event base unavailable or not an ancestor)')) {
throw new Error(`${relativeFile} must verify and report event-scoped security history; only manual dispatch may scan full history.`);
if (!reports?.run?.includes('advisory_scan security "$repowise_bin" security scan --history --path . --format json')
|| !reports.run.includes('security scope: full reachable history (manual dispatch)')
|| !reports.run.includes('ulimit -v 2097152; timeout --kill-after=10s 90s')
|| !reports.run.includes('incomplete (exit %s)')
|| !reports.run.includes('health --no-workspace --refactoring-targets --format md')
|| !reports.run.includes('health --no-workspace --refactoring-targets --format json')
|| !reports.run.includes('analyzed_sha="$(git rev-parse HEAD)"')
|| !reports.run.includes('printf \'%s\\n\' "$analyzed_sha" > "$report_dir/revision.txt"')
|| !reports.run.includes('printf \'Analyzed revision: `%s`\\n\\n\' "$analyzed_sha"')
|| !reports.run.includes('>> "$GITHUB_STEP_SUMMARY"')
|| upload.if !== '${{ always() }}'
|| upload.with?.['retention-days'] !== 14) {
throw new Error(`${relativeFile} must preserve bounded manual history scans, health reports, revision evidence, summary and unconditional artifacts.`);
}
}

function assertManualRepoWiseWorkflow(file, workflow) {
const relativeFile = relativeWorkflow(file);
if (relativeFile !== '.github/workflows/repowise.yml') return;
if (Object.keys(workflow.on ?? {}).join(',') !== 'workflow_dispatch') {
throw new Error(`${relativeFile} must run only through workflow_dispatch.`);
}
const job = workflow.jobs?.['code-health'];
const checkout = (job?.steps ?? []).find(step => step?.uses === CHECKOUT_ACTION);
if (job?.['timeout-minutes'] !== 30
|| JSON.stringify(workflow.permissions) !== JSON.stringify({ contents: 'read' })
|| job.permissions !== undefined
|| checkout?.with?.['fetch-depth'] !== 0
|| checkout.with?.ref !== undefined
|| workflow.concurrency?.group !== 'repowise-${{ github.ref }}'
|| workflow.concurrency?.['cancel-in-progress'] !== true) {
throw new Error(`${relativeFile} must retain its manual job budget, read-only permissions, selected revision and per-ref concurrency.`);
}
}

Expand Down Expand Up @@ -451,8 +474,9 @@ function assertRunner(file, workflow) {
} else if (relativeFile === '.github/workflows/copilot_runner_diagnostics.yml') {
if (jobId !== 'linux') throw new Error(`${relativeFile} contains an unsupported Codex diagnostic job.`);
expected = ['self-hosted', 'codex', 'Linux'];
} else if (relativeFile === '.github/workflows/ci_check.yml'
|| relativeFile === '.github/workflows/repowise.yml') {
} else if (relativeFile === '.github/workflows/repowise.yml') {
expected = ['self-hosted', 'codex', 'Linux'];
} else if (relativeFile === '.github/workflows/ci_check.yml') {
expected = [QUALITY_RUNNER_BY_TRUST];
}
const labels = runnerLabels(job['runs-on']);
Expand Down Expand Up @@ -1233,7 +1257,6 @@ function assertMergeQueueWorkflowSupport(file, workflow) {
const relativeFile = relativeWorkflow(file);
const required = new Set([
'.github/workflows/ci_check.yml',
'.github/workflows/repowise.yml',
'.github/workflows/copilot_pull_request_merge_queue.yml',
'setup/workflows/copilot_pull_request_merge_queue.yml',
]);
Expand Down Expand Up @@ -1379,6 +1402,7 @@ function assertIssueBranchConfigurationInputs(file, workflow) {

function validateWorkflow(file, workflow) {
if (!workflow || typeof workflow !== 'object') throw new Error('workflow document is empty.');
assertManualRepoWiseWorkflow(file, workflow);
assertDirectEventTriggers(file, workflow);
assertApprovalObserverWorkflow(file, workflow);
assertPullRequestMergeQueueWorkflow(file, workflow);
Expand Down
Loading
Loading