forked from makenotion/notion-mcp-server
-
Notifications
You must be signed in to change notification settings - Fork 0
[Self-Heal] Add self-scheduling auto-repair workflow #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
badMade
wants to merge
1
commit into
main
Choose a base branch
from
feat/self-heal-automation-11992912434856171913
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| schedule: "0 0 * * *" | ||
| rationale: "Bootstrap schedule" | ||
| lastUpdated: 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| name: Compute Self-Heal Schedule | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: "0 0 * * 0" # Runs weekly to evaluate the self-heal schedule | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| actions: read | ||
|
|
||
| jobs: | ||
| compute-schedule: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
| cache: 'npm' | ||
|
|
||
| - name: Check for duplicate Schedule Update PRs | ||
| id: check_pr | ||
| 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 "An open schedule update PR already exists. Skipping run." | ||
| echo "skip=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "skip=false" >> $GITHUB_OUTPUT | ||
| fi | ||
|
|
||
| - name: Install dependencies | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| run: npm ci | ||
|
|
||
| - name: Compute New Schedule | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| id: compute | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| node scripts/compute_schedule.mjs | ||
|
|
||
| - name: Update Workflow with sed | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| run: | | ||
| # Read the new schedule | ||
| NEW_CRON=$(grep -oP 'schedule: "\K[^"]+' .github/self-heal-schedule.yml || echo "") | ||
| if [ ! -z "$NEW_CRON" ]; then | ||
| # Replace the schedule in self-heal.yml specifically targeting the # AUTO-UPDATED marker line | ||
| sed -i "s|.*# AUTO-UPDATED| - cron: \"$NEW_CRON\" # AUTO-UPDATED|" .github/workflows/self-heal.yml | ||
| fi | ||
|
|
||
| - name: Setup Git Identity | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| run: | | ||
| git config --global user.name "github-actions[bot]" | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Create Pull Request | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| if git diff --quiet; then | ||
| echo "No schedule changes needed." | ||
| exit 0 | ||
| fi | ||
|
|
||
| BRANCH_NAME="selfheal-schedule-$(date +%s)" | ||
| git checkout -b $BRANCH_NAME | ||
|
|
||
| # Only stage allowed files | ||
| 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 "No allowed files were changed." | ||
| exit 0 | ||
| fi | ||
|
|
||
| git commit -m "Update self-heal cadence" | ||
| git push -u origin $BRANCH_NAME | ||
|
|
||
| gh pr create \ | ||
| --title "[Self-Heal Schedule] Update cadence" \ | ||
| --body "Auto-generated PR to update the self-healing schedule based on recent repository telemetry. | ||
|
|
||
| _Generated by Compute Schedule Workflow_" \ | ||
| --label "self-heal-schedule" \ | ||
| --label "automation" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| name: Self-Heal Repair Pipeline | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: "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: | ||
| # Run if manual dispatch OR scheduled OR (ci workflow failed) | ||
| if: >- | ||
| !startsWith(github.ref_name, 'selfheal-') && | ||
| ( | ||
| github.event_name == 'workflow_dispatch' || | ||
| github.event_name == 'schedule' || | ||
| (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure') | ||
| ) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
| cache: 'npm' | ||
|
|
||
| - name: Check for duplicate Self-Heal PRs | ||
| id: check_pr | ||
| 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 "An open self-heal PR already exists. Skipping run." | ||
| echo "skip=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "skip=false" >> $GITHUB_OUTPUT | ||
| fi | ||
|
|
||
| - name: Close Stale PRs | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| DATE=$(date -d "7 days ago" +%Y-%m-%d) | ||
| STALE_PRS=$(gh pr list --label "self-heal" --state open --search "created:<$DATE" --json number -q '.[].number') | ||
| for PR in $STALE_PRS; do | ||
| gh pr close $PR --comment "Closing stale self-heal PR" | ||
| done | ||
|
|
||
| - name: Pre-Repair Healthcheck | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| run: node scripts/healthcheck.mjs > pre-check.log 2>&1 || echo "Pre-check failed, as expected" | ||
|
|
||
| - name: Run Self-Heal Pipeline | ||
| id: selfheal | ||
| if: steps.check_pr.outputs.skip == 'false' | ||
| run: | | ||
| if node scripts/self_heal.mjs; then | ||
| echo "success=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "success=false" >> $GITHUB_OUTPUT | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Post-Repair Healthcheck | ||
| id: post | ||
| if: steps.selfheal.outputs.success == 'true' | ||
| 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 | ||
| id: check_diff | ||
| if: steps.post.outcome == 'success' | ||
| run: | | ||
| if git diff --quiet; then | ||
| echo "No changes made." | ||
| echo "diff=false" >> $GITHUB_OUTPUT | ||
| else | ||
| # Filter files strictly to allowed directories/files, rejecting .env, ci.yml, secrets | ||
| ALLOWED_FILES=$(git diff --name-only | grep -E '^(src/|tests/|package\.json|package-lock\.json|scripts/|eslint\.config\.mjs)') | ||
| FORBIDDEN_FILES=$(git diff --name-only | grep -E '^(\.github/workflows/ci\.yml|\.env|secrets/)') | ||
|
|
||
| if [ ! -z "$FORBIDDEN_FILES" ]; then | ||
| echo "Changes detected in forbidden files. Aborting." | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ -z "$ALLOWED_FILES" ]; then | ||
| echo "No allowed files were changed." | ||
| echo "diff=false" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "diff=true" >> $GITHUB_OUTPUT | ||
|
|
||
| for path in $ALLOWED_FILES; do | ||
| git add "$path" 2>/dev/null || true | ||
| done | ||
|
|
||
| if git diff --cached -E -i '(password|secret|key|token)[=:]' ; then | ||
| echo "Potential secrets detected in diff. Aborting PR creation." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Meaningful diff check (fail if it's only whitespace and healthcheck was green) | ||
| # Not fully perfect but basic safeguard | ||
| if git diff --cached --quiet; then | ||
| echo "No meaningful changes made after adding allowed files." | ||
| echo "diff=false" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| fi | ||
| fi | ||
|
|
||
| - name: Setup Git Identity | ||
| if: steps.check_diff.outputs.diff == 'true' | ||
| run: | | ||
| git config --global user.name "github-actions[bot]" | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Create Pull Request | ||
| if: steps.check_diff.outputs.diff == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| BRANCH_NAME="selfheal-$(date +%s)" | ||
| git checkout -b $BRANCH_NAME | ||
| git commit -m "Auto-repair via Self-Heal Pipeline" | ||
| git push -u origin $BRANCH_NAME | ||
|
|
||
| if [ "${{ github.event_name }}" == "schedule" ]; then | ||
| TITLE="[Self-Heal Scheduled] Drift fixes" | ||
| TRIGGER="Scheduled" | ||
| elif [ "${{ github.event_name }}" == "workflow_run" ]; then | ||
| TITLE="[Self-Heal Reactive] CI fix" | ||
| TRIGGER="Reactive (CI Failure)" | ||
| else | ||
| TITLE="[Self-Heal Manual] Repair" | ||
| TRIGGER="Manual Dispatch" | ||
| fi | ||
|
|
||
| DRIFT_SUMMARY=$(git log -1 --stat) | ||
| SCHEDULE_CONFIG=$(cat .github/self-heal-schedule.yml || echo "Unknown schedule") | ||
|
|
||
| gh pr create \ | ||
| --title "$TITLE" \ | ||
| --body "Auto-generated repair PR. | ||
|
|
||
| **Trigger:** $TRIGGER | ||
|
|
||
| **Drift Summary:** | ||
| \`\`\` | ||
| $DRIFT_SUMMARY | ||
| \`\`\` | ||
|
|
||
| **Current Schedule Rationale:** | ||
| \`\`\`yaml | ||
| $SCHEDULE_CONFIG | ||
| \`\`\` | ||
|
|
||
| Please review the attached logs in the Artifacts section. | ||
|
|
||
| _Generated by the Self-Heal Pipeline requested via Claude Code / Jules_" \ | ||
| --label "self-heal" \ | ||
| --label "automation" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Self-Heal Automation Setup | ||
|
|
||
| This repository uses an adaptive, automated self-healing pipeline to maintain code health, detect drift, and perform necessary repairs via pull requests. | ||
|
|
||
| ## How It Works | ||
|
|
||
| The system is powered by two main GitHub Actions workflows and a suite of scripts. | ||
|
|
||
| 1. **Triggers:** | ||
| * **Proactive (Scheduled):** Periodically runs to catch codebase drift (e.g., outdated lockfiles, new linting rules). | ||
| * **Reactive (CI Failure):** Runs automatically when the primary `ci` workflow fails on the default branch. | ||
| * **Manual:** Can be manually triggered via `workflow_dispatch`. | ||
|
|
||
| 2. **Idempotent Repair Pipeline (`scripts/self_heal.mjs`):** | ||
| When triggered, the pipeline attempts a 6-step repair process: | ||
| 1. Rebuild/Reinstall dependencies. | ||
| 2. Lint and format auto-fix. | ||
| 3. Snapshot regeneration for tests. | ||
| 4. Type stubs update (`typesync`). | ||
| 5. Dependency re-resolve. | ||
| 6. Static asset regeneration (builds). | ||
|
|
||
| After each step, a **Healthcheck** (`scripts/healthcheck.mjs`) validates the state. If the build passes and diffs are generated, it halts and succeeds. | ||
|
|
||
| 3. **Adaptive Scheduling (`scripts/compute_schedule.mjs`):** | ||
| A secondary workflow (`compute-schedule.yml`) runs weekly to monitor repository telemetry (like PR velocity). It recalculates the optimal frequency for the scheduled trigger to avoid unnecessary CI usage during dormant periods. | ||
|
|
||
| ## Manual Overrides | ||
|
|
||
| If you need to manually change the self-heal schedule and prevent the adaptive logic from overriding it: | ||
| 1. Edit `.github/self-heal-schedule.yml`. | ||
| 2. Ensure the `lastUpdated` timestamp is set to a future epoch time or a very recent time to delay the next re-computation. | ||
| 3. The `# AUTO-UPDATED` marker in `.github/workflows/self-heal.yml` must remain intact for future automation. | ||
|
|
||
| ## Reviewer Checklist for Self-Heal PRs | ||
| When reviewing a PR generated by this automation: | ||
| - [ ] Verify the diff does not include logical source code changes (only formatting, snapshots, deps). | ||
| - [ ] Review the artifact logs (`repair.log`, `pre-check.log`, `post-check.log`) attached to the workflow run. | ||
| - [ ] Ensure the PR resolves the immediate issue or drift before merging. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import js from "@eslint/js"; | ||
| import tseslint from "typescript-eslint"; | ||
| import globals from "globals"; | ||
|
|
||
| export default tseslint.config( | ||
| { | ||
| ignores: ["build/**", "dist/**", "coverage/**", "bin/**", "node_modules/**"] | ||
| }, | ||
| js.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": ["warn", { "argsIgnorePattern": "^_" }], | ||
| "@typescript-eslint/ban-ts-comment": "off", | ||
| "prefer-const": "off", | ||
| "preserve-caught-error": "off" | ||
| } | ||
| } | ||
| ); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rule
preserve-caught-erroris not a standard ESLint or TypeScript ESLint rule. Including a non-existent rule in the configuration will cause ESLint to throw an error and crash, which in turn will fail the healthcheck script. You should remove this line.