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 3 * * 1,3,5"
rationale: "Initial bootstrap schedule (standard tier)."
last_updated: "2023-01-01T00:00:00.000Z"
83 changes: 83 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Compute Self-Heal Schedule

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

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

jobs:
compute:
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.ref == 'refs/heads/main'

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install dependencies
run: npm ci

- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

- name: Check for open schedule PRs
id: check_duplicate
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RECENT_PR=$(gh pr list --label self-heal-schedule --state open --json createdAt -q '.[0].createdAt' 2>/dev/null || true)
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "duplicate_exists=true" >> $GITHUB_OUTPUT
echo "Found an existing open self-heal-schedule PR. Skipping."
else
echo "duplicate_exists=false" >> $GITHUB_OUTPUT
fi

- name: Compute New Schedule
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: node scripts/compute_schedule.mjs

- name: Verify YAML & Workflow Parseability
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: |
node -e "require('js-yaml').load(require('fs').readFileSync('.github/self-heal-schedule.yml', 'utf8'))"
node -e "require('js-yaml').load(require('fs').readFileSync('.github/workflows/self-heal.yml', 'utf8'))"

- name: Create PR if changed
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -n "$(git status --porcelain)" ]; then
git add .github/self-heal-schedule.yml .github/workflows/self-heal.yml

BRANCH_NAME="selfheal-schedule-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "Auto-update self-heal schedule based on telemetry"
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
git -c core.askpass=true push -u origin "$BRANCH_NAME"

gh pr create \
--title "[Self-Heal Schedule] Update cadence" \
--body "Telemetry detected a change in repository velocity. Updating schedule accordingly." \
--label "automation,self-heal-schedule" \
--base main
else
echo "No schedule changes computed."
fi
135 changes: 135 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Self-Heal Repair Pipeline

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

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

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

jobs:
repair:
runs-on: ubuntu-latest
timeout-minutes: 15
if: |
!startsWith(github.ref_name, 'selfheal-') &&
(github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure'))

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install dependencies
run: npm ci

- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

- name: Close Stale Self-Heal PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DATE=$(date -d "7 days ago" +%Y-%m-%d)
PR_LIST=$(gh pr list --label self-heal --search "created:<$DATE" --json number -q '.[].number' 2>/dev/null || true)
for PR in $PR_LIST; do
gh pr close "$PR" -c "Closing stale self-heal PR."
done

- name: Check for duplicate PRs
id: check_duplicate
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RECENT_PR=$(gh pr list --label self-heal --state open --json createdAt -q '.[0].createdAt' 2>/dev/null || true)
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "duplicate_exists=true" >> $GITHUB_OUTPUT
echo "Found an existing open self-heal PR. Skipping."
else
echo "duplicate_exists=false" >> $GITHUB_OUTPUT
fi

- name: Run Pre-Healthcheck
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: node scripts/healthcheck.mjs > pre-check.log 2>&1 || true

- name: Execute Self-Heal Scripts
id: self_heal
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: node scripts/self_heal.mjs > repair.log 2>&1

- name: Run Post-Healthcheck
id: post_check
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: node scripts/healthcheck.mjs > post-check.log 2>&1

- name: Check for Diff & Secrets
id: verify_diff
if: steps.check_duplicate.outputs.duplicate_exists != 'true'
run: |
if [ -n "$(git status --porcelain)" ]; then
git add package.json package-lock.json src/ tests/ .eslintrc* eslint.config* prettier* *.ts *.js 2>/dev/null || true
if git diff --cached | grep -iE 'api[_-]?key|secret|token|password'; then
echo "Error: Potential secret found in diff. Aborting."
sh -c 'exit 1'
fi
echo "has_diff=true" >> $GITHUB_OUTPUT
else
echo "has_diff=false" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.check_duplicate.outputs.duplicate_exists != 'true' && steps.verify_diff.outputs.has_diff == 'true' && steps.post_check.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="selfheal-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "Auto-repair drift/CI failures"
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
git -c core.askpass=true push -u origin "$BRANCH_NAME"

TITLE="[Self-Heal Reactive] CI fix"
if [ "${{ github.event_name }}" == "schedule" ]; then
TITLE="[Self-Heal Scheduled] Drift fixes"
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TITLE="[Self-Heal Manual] Repair"
fi

gh pr create \
--title "$TITLE" \
--body "Automated repair pipeline triggered by ${{ github.event_name }}. Review attached logs for details." \
--label "automation,self-heal" \
--base main

- name: Upload Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: self-heal-logs
path: |
pre-check.log
repair.log
post-check.log
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ bin/
.cursor

.DS_Store
*.log
45 changes: 45 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Self-Healing Pipeline Setup

This project utilizes an automated self-healing CI pipeline to detect drift, auto-repair common issues, and submit changes for human review via Pull Requests.

## Overview

The automation relies on 3 main triggers:
1. **Scheduled (Proactive)**: Computed based on repository velocity telemetry to prevent bit-rot and keep dependencies/snapshots up to date.
2. **CI Failure (Reactive)**: Triggered whenever the main `ci` workflow fails.
3. **Manual Dispatch**: Available on-demand via the GitHub Actions UI.

## How It Works

The repair pipeline consists of scripts located in `scripts/`:
- `healthcheck.mjs`: Acts as a gate, ensuring tests, linting, and builds pass.
- `self_heal.mjs`: Runs 6 idempotent repair steps:
1. **Rebuild/reinstall**: `npm ci || npm install`
2. **Lint/format auto-fix**: `npx eslint --fix . && npx prettier -w .`
3. **Snapshot updates**: `npx vitest run -u --passWithNoTests`
4. **Type config/analyzer**: `npx tsc --build`
5. **Dependency re-resolve**: `npm update`
6. **Static asset generation**: `npm run build`
- `compute_schedule.mjs`: Analyzes commit velocity to compute an optimal schedule cadence and avoid thrashing.

### Safety Guards

- PR duplication is prevented.
- A branch guard prevents automated loops from triggering themselves.
- A simple heuristic secret scanner checks for introduced tokens before creating a PR.
- Changes must pass the `healthcheck.mjs` before being submitted.

## Reviewer Checklist

When reviewing a PR prefixed with `[Self-Heal ...]`, please check:
- [ ] Ensure the changes only affect formatting, lockfiles, snapshots, or dependencies.
- [ ] Ensure there are no logic changes or inadvertently removed code.
- [ ] Review the artifact logs (`pre-check.log`, `repair.log`, `post-check.log`) attached to the PR.
- [ ] Validate no secrets or sensitive data were pushed.

## Manual Schedule Override

If you wish to override the telemetry-based schedule:
1. Edit `.github/self-heal-schedule.yml` directly.
2. Change the `schedule` to the desired cron value.
3. Optionally disable `.github/workflows/compute-schedule.yml` to prevent it from reverting your override.
36 changes: 36 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.browser,
...globals.jest,
},
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'prefer-const': 'off',
'no-undef': 'off',
'preserve-caught-error': 'off',
'@typescript-eslint/no-unused-expressions': 'off'
Comment on lines +21 to +23

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 rule preserve-caught-error is not a valid ESLint or typescript-eslint rule. Including undefined rules will cause ESLint to throw an error and fail to run. Since it is set to 'off', it should be removed.

      'no-undef': 'off',
      '@typescript-eslint/no-unused-expressions': 'off'

},
ignores: [
'build/**',
'dist/**',
'coverage/**',
'bin/**',
'node_modules/**',
'*.log',
'scripts/build-cli.js',
'examples/**'
],
}
);
Loading