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
1 change: 1 addition & 0 deletions .git_history
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2025-08-22T11:58:57-07:00
4 changes: 4 additions & 0 deletions .github/self-heal-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schedule: "0 0 * * *"
rationale: "Initial bootstrap schedule based on defaults. To be updated by telemetry."
last_updated: 0
override: false
99 changes: 99 additions & 0 deletions .github/workflows/compute-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Compute Self-Heal Schedule
on:
schedule:
- cron: "0 0 * * 0" # Runs weekly by default
workflow_dispatch:

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

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

jobs:
compute-schedule:
name: Compute Optimal Self-Heal Schedule
runs-on: ubuntu-latest
if: github.ref_name == github.event.repository.default_branch
timeout-minutes: 5
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: Configure Git Committer
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

- name: Install dependencies
run: npm ci

- name: Compute New Schedule
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/compute_schedule.mjs

- name: Create Schedule Update PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Only proceed if there's a diff
if [ -z "$(git status --porcelain)" ]; then
echo "Schedule is unchanged."
exit 0
fi

# Check for recent open schedule PRs to avoid thrash
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 "A schedule update PR is already open. Skipping creation."
exit 0
fi

# Add schedule configuration files
git add .github/workflows/self-heal.yml .github/self-heal-schedule.yml || true

if [ -z "$(git diff --cached --name-only)" ]; then
echo "No schedule files modified, skipping PR creation."
exit 0
fi

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

# Create PR body
cat << EOF > pr_body.txt
## Automated Schedule Update

This PR updates the self-healing CI schedule based on recent repository telemetry.

### Details
The self-heal pipeline automatically adjusts its schedule to match the repository's activity level.
Review the \`.github/self-heal-schedule.yml\` file for the rationale behind this new schedule.

### Reviewer Checklist
- [ ] Review the updated cron expression and rationale
- [ ] Merge PR when ready
EOF

gh pr create \
--title "[Self-Heal Schedule] Update cadence" \
--body-file pr_body.txt \
--label "automation" \
--label "self-heal-schedule" \
--base "${{ github.event.repository.default_branch || 'main' }}" \
--head "$BRANCH_NAME"
172 changes: 172 additions & 0 deletions .github/workflows/self-heal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
name: Self-Heal Pipeline
on:
workflow_run:
workflows: ["ci"]
types:
- completed
schedule:
- cron: "0 0 * * *" # AUTO-UPDATED
workflow_dispatch:

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

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

jobs:
repair:
name: Self-Heal Repair
runs-on: ubuntu-latest
if: >
!startsWith(github.ref_name, 'selfheal-') &&
(
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'schedule' && github.ref_name == github.event.repository.default_branch) ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure')
)
timeout-minutes: 15
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: Configure Git Committer
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

- name: Install dependencies
run: npm ci

- name: Close stale self-heal PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
STALE_DATE=$(date -d "7 days ago" +%Y-%m-%dT%H:%M:%S%z || date -v-7d +%Y-%m-%dT%H:%M:%S%z)
gh pr list --label "self-heal" --search "created:<$STALE_DATE" --json number -q '.[].number' | while read -r PR_NUMBER; do
echo "Closing stale self-heal PR #$PR_NUMBER"
gh pr close "$PR_NUMBER" -c "Closing stale self-heal PR" || true
done

- 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 "A self-heal PR is already open. Skipping repair run."
exit 0
fi

- name: Run Self-Heal Script
id: repair
run: |
node scripts/self_heal.mjs

- name: Scan for secrets in diff
id: secret_scan
if: steps.repair.outcome == 'success' && steps.repair.conclusion == 'success'
run: |
# Stage changes first for accurate diff
git add -N src/ tests/ "requirements*.txt" package.json go.sum go.mod eslint.config.mjs .prettierrc || true

# Only proceed if there's a diff
if [ -z "$(git status --porcelain)" ]; then
echo "No changes found."
exit 0
fi

if git diff --cached | grep -iE '(api_key|apikey|secret|token|password|passwd|auth_token|access_token).*='; then
echo "Potential secrets found in diff! Aborting."
git reset
exit 1
fi

git reset

- name: Determine PR Title
id: pr_title
if: steps.repair.outcome == 'success' && steps.repair.conclusion == 'success'
run: |
if [ "${{ github.event_name }}" == "schedule" ]; then
echo "title=[Self-Heal Scheduled] Drift fixes" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "workflow_run" ]; then
echo "title=[Self-Heal Reactive] CI fix" >> $GITHUB_OUTPUT
else
echo "title=[Self-Heal Manual] Repair" >> $GITHUB_OUTPUT
fi

- name: Create Self-Heal PR
if: steps.repair.outcome == 'success' && steps.repair.conclusion == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No diff to commit, skipping PR creation."
exit 0
fi

# Only add specific safe paths
for path in src/ tests/ "requirements*.txt" package.json package-lock.json go.sum go.mod eslint.config.mjs .prettierrc; do
git add "$path" 2>/dev/null || true
done

if [ -z "$(git diff --cached --name-only)" ]; then
echo "No allowed files modified, skipping PR creation."
exit 0
fi

BRANCH_NAME="selfheal-$(date +%s)"
git checkout -b "$BRANCH_NAME"
git commit -m "Auto-repair drift"
git push origin "$BRANCH_NAME"

PR_TITLE="${{ steps.pr_title.outputs.title }}"

# Create PR body
cat << EOF > pr_body.txt
## Automated Self-Heal Repair

This PR was automatically generated by the self-healing CI pipeline.

### Trigger Reason
- **Event:** \`${{ github.event_name }}\`

### Logs
Check the Actions artifacts for full healthcheck and repair logs: [Workflow Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})

### Reviewer Checklist
- [ ] Review diff to ensure no unintended logic changes
- [ ] Verify tests pass in CI
- [ ] Merge PR when ready
EOF

gh pr create \
--title "$PR_TITLE" \
--body-file pr_body.txt \
--label "automation" \
--label "self-heal" \
--base "${{ github.event.repository.default_branch || 'main' }}" \
--head "$BRANCH_NAME"

- name: Upload logs as artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: self-heal-logs
path: |
pre-check.log
repair.log
post-check.log
retention-days: 7
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
47 changes: 47 additions & 0 deletions SELF_HEAL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Self-Healing CI Pipeline Setup

This project is equipped with an automated self-healing CI pipeline that detects code drift, resolves common issues automatically, and submits Pull Requests for review.

## Architecture & Workflows

- **`.github/workflows/self-heal.yml`**: The primary pipeline that executes repair steps. It triggers on:
- `schedule`: Periodically checks for drift.
- `workflow_run`: Reactively attempts repairs if the main `ci` workflow fails.
- `workflow_dispatch`: Manual trigger.
- **`.github/workflows/compute-schedule.yml`**: An adaptive scheduling workflow that monitors repository telemetry (commit frequency) and dynamically updates the schedule expression.

## Repair Steps (`scripts/self_heal.mjs`)

The self-heal script runs idempotently through the following sequence:

1. **Rebuild/reinstall**: Runs `npm ci` to ensure tooling and dependencies are intact.
2. **Lint & Format**: Runs `npx eslint --fix .` and `npx prettier -w .` to align code formatting to project standards.
3. **Snapshot Updates**: Runs `npx vitest run -u --passWithNoTests` to update test snapshots if they have drifted from implementation.
4. **Type Stubs**: Evaluates types, omitted when strict idempotency is required or typesync is unconfigured.
5. **Dependency Updates**: Safely updates minor/patch versions using `npm update`.
6. **Static Assets**: Regenerates files if generation scripts are detected.

After each step, `scripts/healthcheck.mjs` runs. If the project state becomes "healthy" (lint, build, tests pass) and there is a diff, the pipeline stops and proposes a PR immediately. If healthy but no files were modified, it continues trying subsequent steps.

## Self-Scheduling Logic

The `scripts/compute_schedule.mjs` determines the optimum check cadence based on recent PR and commit velocity:
- **High Activity**: Checks every 6 hours.
- **Active**: Checks every 12 hours.
- **Standard**: Checks daily.
- **Dormant**: Checks weekly.

### Manual Schedule Override

If you need to fix the schedule and stop the pipeline from recomputing it automatically:
1. Open `.github/self-heal-schedule.yml`.
2. Change `override: false` to `override: true`.
3. Adjust the `schedule` cron string manually.

## Reviewer Checklist

When a `[Self-Heal ...]` PR is opened, please:
1. Review the file diffs to ensure no logic was fundamentally altered.
2. Verify that CI passes on the self-healing branch.
3. Review the provided artifact logs (`pre-check.log`, `repair.log`, `post-check.log`) linked in the PR description if anything seems incorrect.
4. Merge using standard procedures when ready.
37 changes: 37 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import eslintJs from "@eslint/js";
import tsEslint from "typescript-eslint";

export default tsEslint.config(
eslintJs.configs.recommended,
...tsEslint.configs.recommended,
{
ignores: [
"build/**",
"dist/**",
"coverage/**",
"bin/**",
"*.log"
],
},
{
languageOptions: {
globals: {
console: "readonly",
process: "readonly",
__dirname: "readonly",
module: "readonly",
require: "readonly",
exports: "readonly",
Buffer: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
window: "readonly",
document: "readonly",
navigator: "readonly",
fetch: "readonly"
},
},
}
);
Loading