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 * * 0'
rationale: 'Low velocity (<=5 commits/week) -> runs weekly'
last_updated: 1780793191727
76 changes: 76 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Compute Self-Heal Schedule

on:
schedule:
- cron: "0 0 * * 0" # Runs weekly to evaluate if schedule needs to be updated
workflow_dispatch:

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

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

jobs:
compute-schedule:
runs-on: ubuntu-latest
timeout-minutes: 5
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'

- name: Install JS-YAML
run: npm ci

- name: Check for recent Open Schedule PRs
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 "Open selfheal schedule PR exists. Skipping to avoid thrash."
exit 0
fi

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

- name: Compute New Schedule
run: |
node scripts/compute_schedule.mjs

- name: Create Schedule Update PR
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for path in .github/workflows/self-heal.yml .github/self-heal-schedule.yml; do
git add "$path" 2>/dev/null || true
done

if git diff --cached --quiet; then
echo "Schedule unchanged, no PR needed."
exit 0
fi

BRANCH_NAME="selfheal-schedule-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "chore: update self-heal schedule based on telemetry"
git push origin "$BRANCH_NAME"

PR_TITLE="[Self-Heal Schedule] Update cadence"
PR_BODY="Automated self-heal schedule update based on recent commit telemetry."

gh pr create --title "$PR_TITLE" --body "$PR_BODY" --label "automation,self-heal-schedule" --head "$BRANCH_NAME" --base main
145 changes: 145 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Self-Heal Repair

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

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

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

jobs:
repair:
# Trigger gate: run if scheduled, manual, or if CI failed
if: >-
(github.event_name == 'schedule') ||
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure')
runs-on: ubuntu-latest
timeout-minutes: 15
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'

- name: Loop Prevention Guard
run: |
if [[ "${{ github.ref_name }}" == selfheal-* ]]; then
echo "Skipping self-heal run on a self-heal branch."
exit 0
fi

- name: Close stale Self-Heal PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
STALE_DATE=$(date -d "7 days ago" -I)
gh pr list --label self-heal --search "created:<$STALE_DATE" --json number --jq '.[].number' | xargs -I {} gh pr close {} --comment "Closing stale self-heal PR." || true

- name: Check for recent Open Self-Heal PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RECENT_PR=$(gh pr list --label self-heal --state open --json createdAt -q '.[0].createdAt')
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "Open selfheal PR exists. Skipping to avoid duplicates."
exit 0
fi

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

- name: Pre-Healthcheck
id: pre_healthcheck
run: |
node scripts/healthcheck.mjs > pre-check.log 2>&1 || true

- name: Run Self-Heal Pipeline
id: repair
run: |
node scripts/self_heal.mjs > repair.log 2>&1
# Explicitly do NOT use || true here. A failure means no fix or healthcheck failure.

- name: Post-Healthcheck
id: post_healthcheck
if: always()
run: |
node scripts/healthcheck.mjs > post-check.log 2>&1 || true

- name: Upload Logs
uses: actions/upload-artifact@v4
if: always()
with:
name: self-heal-logs
path: |
pre-check.log
repair.log
post-check.log

- name: Stage Allowed Files
if: success()
run: |
for path in src/ tests/ package.json package-lock.json eslint.config.mjs; do
git add "$path" 2>/dev/null || true
done
# Ensure generated logs are not staged
git reset HEAD pre-check.log repair.log post-check.log 2>/dev/null || true

- name: Check for Entropy Patterns (Secrets)
if: success()
run: |
# Simple check to avoid committing high entropy strings
if git diff --cached | grep -iE 'api[_-]?key|token|secret|password'; then
echo "Detected potential secrets in diff. Aborting."
exit 1
fi

- name: Create Self-Heal PR
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if git diff --cached --quiet; then
echo "No changes to commit."
exit 0
fi

BRANCH_NAME="selfheal-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "chore: automated self-heal repair"
git push origin "$BRANCH_NAME"

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

PR_BODY="Automated self-heal repair generated by Jules/Claude Code.
Check the uploaded artifacts for detailed pre-check, repair, and post-check logs.

Trigger: ${{ github.event_name }}

Review the changes carefully. The pipeline guarantees healthchecks pass after these repairs."

gh pr create --title "$PR_TITLE" --body "$PR_BODY" --label "automation,self-heal" --head "$BRANCH_NAME" --base main
48 changes: 48 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Self-Heal Coding Agent Setup

This repository is configured with a self-adapting, self-healing automated repair pipeline.

## Overview

The setup includes:
- **Scripts:** Idempotent, deterministic repair actions (`scripts/self_heal.mjs`), a holistic test via (`scripts/healthcheck.mjs`), and a dynamic scheduler (`scripts/compute_schedule.mjs`).
- **Workflows:** GitHub Actions configured in `.github/workflows/self-heal.yml` to trigger the repair pipeline, and `.github/workflows/compute-schedule.yml` to adjust its frequency.
- **Config:** `.github/self-heal-schedule.yml` persists the current schedule metadata.

## The Repair Pipeline
When triggered, `scripts/self_heal.mjs` applies repairs in this exact order:
1. Rebuild / reinstall (`npm install`)
2. Lint / format auto-fix (`eslint --fix` & `prettier --write`)
3. Snapshot updates (`vitest run -u`)
4. Type stubs acquisition (`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 it passes and an actual diff was produced, the process exits successfully, generating a PR for your review. If no repairs were required, no PR is made.

## Self-Scheduling

The system analyzes this project's git commit velocity to automatically adjust the self-healing frequency.

- **High Activity:** Evaluates multiple times a day.
- **Dormant:** Evaluates once a week.

This schedule is managed by the `Compute Self-Heal Schedule` workflow, preventing unnecessary runs in low-activity periods and ensuring high responsiveness during active development.

## Triggers

1. **Scheduled (Proactive):** The computed schedule defined in `.github/workflows/self-heal.yml` triggers routine drift checks.
2. **Reactive:** Automatically attempts repair if the main `ci` workflow fails.
3. **Manual Dispatch:** Can be manually started from the Actions tab.

## Reviewer Checklist

When reviewing a PR generated by this bot (`[Self-Heal Scheduled]...` or `[Self-Heal Reactive]...`), verify that:
- [ ] You have inspected the Pre-check and Post-check artifacts linked in the PR.
- [ ] No regressions or unexpected logic changes have occurred (as the bot restricts itself to formatters, dependency updates, and snapshots).
- [ ] The generated fix aligns with project intent.

## Overriding the Schedule
To override the automatically computed schedule:
1. Update `.github/self-heal-schedule.yml` with your desired `schedule` cron expression and `rationale`.
2. Push your changes. The `compute_schedule.mjs` script will honor this modification until enough time/telemetry passes to naturally adapt again.
28 changes: 28 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';

export default tseslint.config(
{
ignores: ['build/**', 'dist/**', 'coverage/**', 'bin/**', 'node_modules/**']
},
eslint.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.browser,
...globals.jest,
...globals.vitest
}
},
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'prefer-const': 'warn',
'preserve-caught-error': 'warn'
Comment on lines +24 to +25

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 core ESLint rule. Configuring a non-existent rule without a plugin prefix will cause ESLint to throw a fatal error and fail to run, which will break the healthcheck and self-heal scripts.

      'prefer-const': 'warn'

}
}
);
Loading