Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .github/self-heal-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
schedule: "0 0 * * *"
rationale: "Bootstrap schedule (daily) set up during initial self-heal automation configuration."
last_updated: "2024-05-23T00:00:00Z"
94 changes: 94 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Compute Schedule

on:
schedule:
- cron: "0 0 * * 0" # Runs weekly
workflow_dispatch:

concurrency:
group: schedule-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write
actions: read

jobs:
compute-schedule:
if: github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install dependencies
run: npm ci

- name: Check for open schedule PR
id: duplicate_check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
open_prs=$(gh pr list --label "self-heal-schedule" --state open --json number -q 'length')
if [ "$open_prs" -gt 0 ]; then
echo "Open schedule PR exists. Skipping."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: Compute New Schedule
if: steps.duplicate_check.outputs.skip == 'false'
id: compute
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/compute_schedule.mjs || true

- name: Create Pull Request
if: steps.duplicate_check.outputs.skip == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "Schedule unchanged. Exiting."
exit 0
fi

git add .github/workflows/self-heal.yml .github/self-heal-schedule.yml

if [ -z "$(git diff --staged --name-only)" ]; then
echo "No allowed files modified. Exiting."
exit 0
fi

TITLE="[Self-Heal Schedule] Update cadence"
BRANCH="selfheal-schedule-$(date +%s)"

git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

git checkout -b "$BRANCH"
git commit -m "$TITLE"
git push origin "$BRANCH"

gh pr create \
--title "$TITLE" \
--body "Triggered by schedule recomputation.

The self-heal pipeline has computed a new optimal cadence based on recent telemetry.

This PR was auto-generated by the Compute Schedule workflow.
- Reviewer checklist applies (see SELF_HEAL_SETUP.md)." \
--label "self-heal-schedule" \
--label "automation"
137 changes: 137 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Self-Heal CI

on:
schedule:
- cron: "0 0 * * *" # AUTO-UPDATED
workflow_dispatch:
workflow_run:
workflows: ["ci"]
types:
- completed

concurrency:
group: selfheal-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write
actions: read

jobs:
repair:
if: >
!startsWith(github.ref_name, 'selfheal-') &&
(github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'failure') &&
(github.event_name != 'schedule' || github.ref_name == github.event.repository.default_branch)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Close stale selfheal PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
stale_prs=$(gh pr list --label "self-heal" --state open --json number,createdAt -q '.[] | select(.createdAt < (now - 604800 | todate)) | .number')
for pr in $stale_prs; do
gh pr close "$pr" -c "Closing stale self-heal PR"
done

- name: Check for recent open selfheal PRs
id: duplicate_check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
open_prs=$(gh pr list --label "self-heal" --state open --json number -q 'length')
if [ "$open_prs" -gt 0 ]; then
echo "Open self-heal PR exists. Skipping."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: Run Self-Heal Pipeline
if: steps.duplicate_check.outputs.skip == 'false'
id: heal
run: |
node scripts/self_heal.mjs || true

- name: Scan for secrets and Validate diff
if: steps.duplicate_check.outputs.skip == 'false'
id: validate
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No diff found. Exiting."
echo "create_pr=false" >> $GITHUB_OUTPUT
exit 0
fi

# Stage specific allowed files only
for path in src tests scripts package.json package-lock.json tsconfig.json; do
git add "$path" 2>/dev/null || true
done

# Ensure forbidden files are not staged
git reset .github/workflows/ci.yml .env 2>/dev/null || true

if [ -z "$(git diff --staged --name-only)" ]; then
echo "No allowed files modified. Exiting."
echo "create_pr=false" >> $GITHUB_OUTPUT
exit 0
fi

# Entropy / secret scan
if git diff --staged | grep -iE "(api_key|secret|token|password) *[=:] *['\"][a-zA-Z0-9]{20,}['\"]"; then
echo "Potential secret found in diff. Aborting."
git reset
echo "create_pr=false" >> $GITHUB_OUTPUT
exit 1
fi

echo "create_pr=true" >> $GITHUB_OUTPUT

- name: Create Pull Request
if: steps.duplicate_check.outputs.skip == 'false' && steps.validate.outputs.create_pr == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ github.event_name }}" == "schedule" ]; then
TITLE="[Self-Heal Scheduled] Drift fixes"
REASON="Triggered by scheduled drift detection."
elif [ "${{ github.event_name }}" == "workflow_run" ]; then
TITLE="[Self-Heal Reactive] CI fix"
REASON="Triggered by CI failure on default branch."
else
TITLE="[Self-Heal Manual] Repair"
REASON="Triggered manually via workflow_dispatch."
fi

BRANCH="selfheal-$(date +%s)"

git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

git checkout -b "$BRANCH"
git commit -m "$TITLE"
git push origin "$BRANCH"

gh pr create \
--title "$TITLE" \
--body "$REASON

This PR was auto-generated by the Self-Heal CI pipeline.
- Checks have been run locally to ensure successful fix.
- Reviewer checklist applies (see SELF_HEAL_SETUP.md).
- Link to chat: [Chat Link]" \
--label "self-heal" \
--label "automation"
37 changes: 37 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Self-Heal CI Setup

This project uses an automated self-healing CI pipeline configured via GitHub Actions.

## Overview
The pipeline detects code drift and formatting issues automatically and tries to repair them.
It creates an auto-generated Pull Request containing the repairs, which must be reviewed by a human before merging.

## Triggers
1. **Scheduled (Proactive):** Runs on a dynamically computed schedule to catch drift (e.g., dependency updates, formatting).
2. **CI Failure (Reactive):** Runs automatically when a PR fails the main CI workflow.
3. **Manual Dispatch:** Can be run at any time via the GitHub Actions UI.

## Self-Scheduling Logic
The schedule for the proactive trigger is periodically recomputed based on telemetry (like PR merge frequency and CI failure rates).
This is handled by the `compute-schedule.yml` workflow, which will open a PR if the computed schedule changes.

## Repair Pipeline
The pipeline runs an idempotent 6-step repair process (`scripts/self_heal.mjs`):
1. **Rebuild/reinstall:** `npm ci`
2. **Lint/format auto-fix:** `npx eslint --fix . && npx prettier -w .`
3. **Snapshot updates:** `npx vitest run -u --passWithNoTests`
4. **Type stubs:** `npx typesync`
5. **Dependency re-resolve:** `npm update`
6. **Static asset regeneration:** `npm run build`

After each step, a healthcheck (`scripts/healthcheck.mjs`) is run. If the healthcheck passes and there is a diff, a PR is generated.

## Reviewer Checklist
When reviewing a self-heal PR, ensure:
- [ ] No unintended source code logic changes were introduced.
- [ ] Snapshots reflect correct behavior, not just masking a bug.
- [ ] Formatting changes are consistent with project standards.
- [ ] The build passes locally.

## Manual Overrides
To override the dynamically computed schedule, you can manually update the `cron` string in `.github/workflows/self-heal.yml` (ensure the `# AUTO-UPDATED` marker is preserved) and update the rationale in `.github/self-heal-schedule.yml`.
Loading