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 @@
SELFHEAL_SCHEDULE: '0 0 * * 0'
RATIONALE: 'Dormant repository, running weekly'
LAST_UPDATED: '2026-06-06T00:54:18.270Z'
58 changes: 58 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Compute Self-Heal Schedule

on:
schedule:
- cron: '0 0 * * 0' # Weekly default baseline
workflow_dispatch:

jobs:
compute-schedule:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
actions: read
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install Tools
run: |
npm ci

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

- name: Create PR (if schedule changed)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "Schedule unchanged. Exiting."
exit 0
fi

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

BRANCH_NAME="selfheal-schedule-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git add .github/self-heal-schedule.yml .github/workflows/self-heal.yml
git commit -m "Update self-heal schedule cadence"
git push origin "$BRANCH_NAME"

gh pr create --title "[Self-Heal Schedule] Update cadence" \
--body "Automated PR adjusting the self-healing schedule based on recent repository activity metrics." \
--label "automation,self-heal-schedule" \
--base main
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 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

jobs:
repair:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
actions: read
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: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Install Tools
run: |
npm ci

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

- name: Guard against Duplicate PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RECENT_PR=$(gh pr list --label self-heal --state open --json createdAt --jq '.[0].createdAt')
if [ ! -z "$RECENT_PR" ] && [ "$RECENT_PR" != "null" ]; then
echo "A self-heal PR is already open. Exiting to prevent duplication."
exit 0
fi

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

- name: Run Idempotent Repair Pipeline
id: repair
run: |
node scripts/self_heal.mjs > repair.log 2>&1

- name: Post-Repair Healthcheck
if: steps.repair.outcome == 'success'
run: |
node scripts/healthcheck.mjs > post-check.log 2>&1

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

- name: Check Diff & Entropy
if: steps.repair.outcome == 'success'
id: diff
run: |
git status --porcelain
if [ -z "$(git status --porcelain)" ]; then
echo "No diff found. Exiting."
exit 0
fi

# Stage specific allowed paths
for path in src/ tests/ package.json package-lock.json eslint.config.mjs; do
git add "$path" 2>/dev/null || true
done

if grep -iE '(key|token|secret|password|bearer).*[0-9a-zA-Z]{16,}' $(git diff --name-only --cached); then
echo "Potential secrets detected in diff. Aborting."
exit 1
fi

- name: Create PR
if: steps.repair.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

# Ensure there are actually staged files before committing
if git diff --cached --quiet; then
echo "No allowed files staged. Exiting."
exit 0
fi

BRANCH_NAME="selfheal-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "Auto-repair via self-healing pipeline"
git push 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 self-healing PR. Please review the attached logs in GitHub Actions before merging." \
--label "automation,self-heal" \
--base main
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
29 changes: 29 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Self-Healing Pipeline Setup

This project utilizes an automated self-healing CI pipeline to detect and fix common drift or CI failures automatically.

## How It Works

The pipeline is triggered via three methods:
1. **Scheduled Runs:** Runs periodically to detect project drift (e.g. out-of-date snapshots, new lint rules).
2. **Reactive Runs:** Triggers on failure of the `ci` workflow run.
3. **Manual Dispatch:** Can be run at any time via GitHub Actions UI.

## The Repair Pipeline
The pipeline is designed to be idempotent and executes the following steps in order, exiting early if it reaches a healthy state and successfully produces a fixing diff:
1. Rebuild/reinstall (clears and installs fresh dependencies).
2. Lint auto-fix (runs linters and formatters).
3. Snapshot updates (regenerates test snapshots).
4. Type stubs (ensures dependency typings are resolved).
5. Dependency resolution (updates lockfiles cautiously).
6. Static asset generation (rebuilds the output).

## Dynamic Scheduling
The schedule is computed dynamically using repository telemetry (e.g., number of commits). It runs on an adaptive cadence so that high-activity periods get frequent checks, while low-activity periods save CI resources.
If you need to manually override the schedule, edit `.github/self-heal-schedule.yml` and `.github/workflows/self-heal.yml` to match. The pipeline will respect manual overrides as long as they adhere to the standard schedule format.

## Reviewer Guidelines
When reviewing a PR prefixed with `[Self-Heal...]`:
1. Check the associated GitHub Actions run for the pre and post log artifacts to understand what was fixed.
2. Confirm the diff only touches relevant files (e.g., source code, test snapshots, config files) and has not modified sensitive configs unprompted.
3. Once approved, merge the PR as usual.
33 changes: 33 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import eslintJs from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from "globals";

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Disabling core TypeScript and ESLint rules globally (such as @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, and prefer-const) reduces the effectiveness of static analysis. It is highly recommended to keep these rules enabled (or set them to warn) to maintain code quality and type safety, and only disable them via inline comments where absolutely necessary.

Suggested change
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-const": "off"
}
rules: {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"prefer-const": "error"
}

}
);
Loading