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
6 changes: 6 additions & 0 deletions .github/self-heal-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This file contains the current schedule for the self-healing workflow.
# It is updated automatically by scripts/compute_schedule.mjs based on telemetry.
# If you edit this manually, the automation will respect your changes until it feels a recomputation is necessary (e.g. significant PR velocity changes).
SCHEDULE: "0 0 * * *"
RATIONALE: "Initial bootstrap schedule (Daily at midnight)."
LAST_UPDATED: 1715000000000
72 changes: 72 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Compute Self-Heal Schedule

on:
schedule:
- cron: "0 2 * * 0" # Runs every Sunday at 2 AM
workflow_dispatch:

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

jobs:

compute-schedule:
timeout-minutes: 5

runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
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: Compute new schedule
id: compute
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/compute_schedule.mjs > compute.log 2>&1

if [ -n "$(git status --porcelain)" ]; then
echo "diff=true" >> $GITHUB_OUTPUT
git add .github/workflows/self-heal.yml .github/self-heal-schedule.yml
else
echo "diff=false" >> $GITHUB_OUTPUT
fi

- name: Create Schedule Update PR
if: steps.compute.outputs.diff == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RECENT_PR=$(gh pr list --label self-heal-schedule --state open --json createdAt -q '.[0].createdAt')
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "Recent schedule update PR exists. Skipping creation."
exit 0
fi

BRANCH="selfheal-schedule-$(date +%s)"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git commit -m "Automated schedule update based on telemetry"
git push origin "$BRANCH"

gh pr create \
--title "[Self-Heal Schedule] Update cadence" \
--body "Automated PR to update the self-healing schedule based on repository telemetry." \
--label "automation,self-heal-schedule" \
--base main \
--head "$BRANCH"
160 changes: 160 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Self-Heal Repair Pipeline

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

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

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

jobs:

repair:
timeout-minutes: 15

runs-on: ubuntu-latest
if: >
github.event_name != 'workflow_run' ||
github.event.workflow_run.conclusion == 'failure'
steps:
- name: Guard against loops
if: startsWith(github.ref_name, 'selfheal-')
run: |
echo "Branch starts with 'selfheal-'. Aborting to prevent infinite loops."
exit 0

- name: Checkout repository
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 duplicate Self-Heal PRs
id: check-duplicate
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Only close stale PRs if older than 7 days
DATE=$(date -d "7 days ago" +%Y-%m-%d)
STALE_PRS=$(gh pr list --label self-heal --search "created:<$DATE" --json number -q '.[].number')
for pr in $STALE_PRS; do
gh pr close $pr --comment "Closing stale self-heal PR."
done

# Check for recent open PRs
RECENT_PR=$(gh pr list --label self-heal --state open --json createdAt -q '.[0].createdAt')
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "Recent open self-heal PR found: $RECENT_PR. Skipping execution."
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "skip=false" >> $GITHUB_OUTPUT

- name: Run pre-healthcheck
id: pre
if: steps.check-duplicate.outputs.skip != 'true'
run: node scripts/healthcheck.mjs > pre-check.log 2>&1 || echo "PRE_FAILED=true" >> $GITHUB_ENV
if [ "$PRE_FAILED" = "true" ] && [[ "${{ github.event_name }}" == "schedule" ]]; then
echo "::warning title=Self-Heal Schedule::Pre-healthcheck failed on scheduled run. System may be repeatedly failing."
fi

- name: Run Self-Heal Pipeline
id: repair-pipeline
if: steps.check-duplicate.outputs.skip != 'true'
run: |
node scripts/self_heal.mjs > repair.log 2>&1
echo "REPAIR_EXIT=$?" >> $GITHUB_ENV
continue-on-error: true

- name: Run post-healthcheck
id: post
if: steps.check-duplicate.outputs.skip != 'true'
run: node scripts/healthcheck.mjs > post-check.log 2>&1

- name: Upload Logs
if: steps.check-duplicate.outputs.skip != 'true'
uses: actions/upload-artifact@v4
with:
name: self-heal-logs
path: |
pre-check.log
repair.log
post-check.log

- name: Check gating conditions
id: gates
if: steps.check-duplicate.outputs.skip != 'true' && steps.post.outcome == 'success'
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "diff=true" >> $GITHUB_OUTPUT

# Entropy scan check for secrets in diff
git add -A
if git diff --cached | grep -iE "(api[_-]?key|secret|token|password|auth|credential|access[_-]?key)=[\"']?[a-zA-Z0-9_\-]{16,}['\"]?"; then
echo "Secrets detected in diff. Aborting."
exit 1
fi

# File changes NOT in forbidden files
FORBIDDEN_FILES=".github/workflows/ci.yml .env secrets/ src/**/migrations/"
for file in $FORBIDDEN_FILES; do
if git diff --cached --name-only | grep -q "$file"; then
echo "Forbidden file $file modified. Aborting."
exit 1
fi
done

# We ONLY allow expected paths. Add allowed file checks here if strictly needed
else
echo "diff=false" >> $GITHUB_OUTPUT
fi

- name: Determine PR Title
id: pr-title
if: steps.gates.outputs.diff == 'true'
run: |
TITLE="[Self-Heal Manual] Repair"
if [[ "${{ github.event_name }}" == "schedule" ]]; then
TITLE="[Self-Heal Scheduled] Drift fixes"
elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then
TITLE="[Self-Heal Reactive] CI fix"
fi
echo "title=$TITLE" >> $GITHUB_OUTPUT

- name: Create PR
if: steps.gates.outputs.diff == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="selfheal-$(date +%s)"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git commit -m "Automated self-healing repair"
git push origin "$BRANCH"

gh pr create \
--title "${{ steps.pr-title.outputs.title }}" \
--body "Automated repair generated by the self-healing CI pipeline. Please review the attached artifact logs." \
--label "automation,self-heal" \
--base main \
--head "$BRANCH"
44 changes: 44 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Self-Healing CI Pipeline Setup

This repository has been configured with an automated self-healing CI pipeline designed to address code drift, keep dependencies updated, format code, and sync types automatically.

## Triggers

The automation runs based on three distinct triggers:
1. **Scheduled:** Runs on an adaptive computed cadence based on telemetry.
2. **Reactive:** Triggers automatically when the `ci` workflow fails.
3. **Manual:** Can be triggered manually via the GitHub Actions `workflow_dispatch` UI.

## The 6 Repair Steps

When triggered, the pipeline performs the following idempotent steps:
1. **Clean Install:** Runs `npm ci` to ensure tooling and dependencies are correctly installed.
2. **Format & Lint:** Runs `eslint --fix` and `prettier -w` to resolve formatting and linting errors.
3. **Test Snapshots:** Runs `vitest run -u` to regenerate test snapshots if they've drifted.
4. **Sync Types:** Uses `typesync` to acquire missing type stubs and updates `package.json`.
5. **Update Dependencies:** Runs `npm update` to resolve minor or patch dependency updates safely.
6. **Regenerate Assets:** Placeholder for any potential generic asset regeneration if applicable to this project.

After each step, the script checks the project's health. It will exit immediately if the state is healthy and there is a git diff, ensuring only necessary and safe changes are committed.

## Self-Scheduling Logic

The `compute-schedule.yml` workflow runs periodically to analyze the repository's PR velocity telemetry. Based on this telemetry, it computes the most optimal schedule.

If the schedule changes, the workflow will create a PR to modify `.github/workflows/self-heal.yml` and `.github/self-heal-schedule.yml`.

### Manual Overrides
To manually override the schedule:
1. Modify `.github/self-heal-schedule.yml` with your desired `SCHEDULE` and `RATIONALE`.
2. Update the timestamp in `LAST_UPDATED` (in ms since epoch).
3. The automation will respect your change until it computes a significant velocity shift or the oscillation guard expires.

## Reviewer Checklist

When reviewing a `selfheal-*` PR, please verify:
- [ ] No secrets or keys have been inadvertently committed.
- [ ] The `pre-check.log`, `repair.log`, and `post-check.log` artifacts provide clear explanations for the automated changes.
- [ ] Logic files (source code, tests aside from snapshots) have not been negatively impacted by the formatting/linting changes.
- [ ] Dependency updates appear intentional and safe.

The automation runs as `github-actions[bot]`, and any proposed changes must always undergo human review.
29 changes: 29 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default [
{
ignores: [
"build/**",
"dist/**",
"coverage/**",
"bin/**",
"node_modules/**"
]
},
{
languageOptions: {
globals: {
console: "readonly",
process: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
__dirname: "readonly",
__filename: "readonly",
module: "readonly",
require: "readonly",
exports: "readonly",
Buffer: "readonly"
}
}
}
];
Comment on lines +1 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current ESLint configuration does not enable any rules or extend any recommended configurations. As a result, ESLint will run with zero rules enabled, making npx eslint --fix a no-op and rendering the linting step in the self-healing pipeline ineffective.

To fix this, import @eslint/js and extend js.configs.recommended (or your preferred base configuration). Note that you may also want to add @eslint/js to your devDependencies in package.json to ensure it is explicitly tracked.

import js from "@eslint/js";

export default [
  {
    ignores: [
      "build/**",
      "dist/**",
      "coverage/**",
      "bin/**",
      "node_modules/**"
    ]
  },
  js.configs.recommended,
  {
    languageOptions: {
      globals: {
        console: "readonly",
        process: "readonly",
        setTimeout: "readonly",
        clearTimeout: "readonly",
        setInterval: "readonly",
        clearInterval: "readonly",
        __dirname: "readonly",
        __filename: "readonly",
        module: "readonly",
        require: "readonly",
        exports: "readonly",
        Buffer: "readonly"
      }
    }
  }
];

Loading