Merge pull request #166 from qinfendebingshuo/copilot/zy-sk-eyew-redefine-core-architecture
SkyEye Governance Layer — Guard system, quota tracking, weekly scan pipeline
This commit is contained in:
commit
5cec3bfd55
|
|
@ -0,0 +1,42 @@
|
|||
name: "SkyEye Weekly Full Scan"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 6' # 20:00 CST (UTC+8) every Saturday
|
||||
repository_dispatch:
|
||||
types: [skyeye-full-scan] # 手动触发
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
weekly-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "🧐 Wake up SkyEye Core Brain"
|
||||
run: echo "[SkyEye] Core brain awakened for weekly full scan at $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
- name: "🔍 Phase 1 - Infrastructure Scan"
|
||||
run: node skyeye/scripts/scan-engine.js --mode=full
|
||||
|
||||
- name: "💰 Phase 2 - Quota Audit"
|
||||
run: node skyeye/scripts/quota-tracker.js --audit
|
||||
|
||||
- name: "⚙️ Phase 3 - Optimize Guards"
|
||||
run: node skyeye/scripts/optimizer.js --apply
|
||||
|
||||
- name: "🩹 Phase 4 - Self-Heal"
|
||||
run: node skyeye/scripts/self-healer.js
|
||||
|
||||
- name: "📝 Phase 5 - Generate Report"
|
||||
run: node skyeye/scripts/weekly-scan.js --report
|
||||
|
||||
- name: "Commit scan results"
|
||||
run: |
|
||||
git config user.name "天眼 · SkyEye Core"
|
||||
git config user.email "skyeye@guanghu.system"
|
||||
git add skyeye/ buffer/config/
|
||||
git diff --cached --quiet || git commit -m "[skyeye-weekly] $(date -u +%Y%m%d) full scan complete [skip ci]"
|
||||
git push
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-ACTIONS",
|
||||
"target_service": "GitHub Actions",
|
||||
"status": "active",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/actions-staging/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": 2000,
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 67,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "workflow_status_check",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "healthy",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-DRIVE",
|
||||
"target_service": "Google Drive API",
|
||||
"status": "active",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/drive-staging/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": 15,
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 0,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "api_ping",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "healthy",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-GEMINI",
|
||||
"target_service": "Google Gemini API",
|
||||
"status": "active",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/gemini-staging/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": 1500,
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 50,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "api_ping",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "healthy",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-GITHUB",
|
||||
"target_service": "GitHub API",
|
||||
"status": "active",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/inbox/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": 2000,
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 67,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "api_ping",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "healthy",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-[SERVICE_NAME]",
|
||||
"target_service": "[Service Name]",
|
||||
"status": "initializing",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/[service]-staging/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": "[TO_BE_FILLED]",
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 0,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "[TO_BE_FILLED]",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "unknown",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"guard_id": "GUARD-NOTION",
|
||||
"target_service": "Notion API",
|
||||
"status": "active",
|
||||
"mode": "buffer",
|
||||
"buffer_policy": {
|
||||
"enabled": true,
|
||||
"buffer_path": "buffer/notion-staging/",
|
||||
"max_buffer_age_hours": 24,
|
||||
"flush_strategy": "scheduled",
|
||||
"flush_schedule_cst": ["21:30"]
|
||||
},
|
||||
"quota_policy": {
|
||||
"monthly_limit": 3,
|
||||
"current_month_used": 0,
|
||||
"daily_soft_limit": 0,
|
||||
"warning_threshold_percent": 70,
|
||||
"critical_threshold_percent": 90,
|
||||
"on_warning": "reduce_frequency",
|
||||
"on_critical": "emergency_only"
|
||||
},
|
||||
"trigger_policy": {
|
||||
"default_frequency": "3_per_day",
|
||||
"min_frequency": "1_per_day",
|
||||
"max_frequency": "6_per_day",
|
||||
"auto_adjust": true,
|
||||
"adjust_based_on": ["quota_remaining", "message_volume", "error_rate"]
|
||||
},
|
||||
"health_check": {
|
||||
"enabled": true,
|
||||
"method": "api_ping",
|
||||
"interval_hours": 6,
|
||||
"last_check": null,
|
||||
"last_status": "healthy",
|
||||
"consecutive_failures": 0,
|
||||
"max_failures_before_alert": 3
|
||||
},
|
||||
"last_updated_by": "skyeye-init",
|
||||
"last_updated_at": "2026-03-23T12:00:00Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"last_scan": "2026-03-23T14:30:07.186Z",
|
||||
"last_scan_by": "skyeye-scan-engine",
|
||||
"infrastructure": {
|
||||
"github": {
|
||||
"service": "GitHub",
|
||||
"plan": "Free",
|
||||
"repos": [
|
||||
{
|
||||
"name": "qinfendebingshuo/guanghulab",
|
||||
"role": "main",
|
||||
"guard": "buffer/"
|
||||
},
|
||||
{
|
||||
"name": "WENZHUOXI/guanghu-awen",
|
||||
"role": "sub-repo",
|
||||
"persona": "PER-ZQ001",
|
||||
"guard": ".github/persona-brain/"
|
||||
}
|
||||
],
|
||||
"quotas": {
|
||||
"actions_minutes_per_month": 2000,
|
||||
"actions_current_used": 0,
|
||||
"actions_budget_allocated": {
|
||||
"buffer_collect": 270,
|
||||
"buffer_flush": 90,
|
||||
"skyeye_checkin": 180,
|
||||
"skyeye_weekly_scan": 30,
|
||||
"other_workflows": 200,
|
||||
"emergency_reserve": 1230
|
||||
},
|
||||
"storage_limit_gb": 0.5,
|
||||
"lfs_bandwidth_gb": 1
|
||||
}
|
||||
},
|
||||
"google_drive": {
|
||||
"service": "Google Drive",
|
||||
"plan": "Google One / Free 15GB",
|
||||
"guard": "buffer/drive-staging/",
|
||||
"quotas": {
|
||||
"storage_gb": 15,
|
||||
"current_used_gb": 0,
|
||||
"apps_script_daily_triggers": 20,
|
||||
"apps_script_runtime_min_per_day": 90,
|
||||
"drive_api_queries_per_100s": 20000
|
||||
}
|
||||
},
|
||||
"notion": {
|
||||
"service": "Notion",
|
||||
"plan": "Plus",
|
||||
"guard": "霜砚(PER-SY001)",
|
||||
"quotas": {
|
||||
"api_requests_per_second": 3,
|
||||
"blocks_per_page": 10000,
|
||||
"file_upload_mb": 5,
|
||||
"guest_limit": 100
|
||||
}
|
||||
},
|
||||
"gemini": {
|
||||
"service": "Google Gemini",
|
||||
"plan": "Free / Pro",
|
||||
"guard": "buffer/gemini-staging/",
|
||||
"quotas": {
|
||||
"requests_per_minute": 15,
|
||||
"requests_per_day": 1500,
|
||||
"context_window_tokens": 1000000,
|
||||
"personal_context_files": 100
|
||||
}
|
||||
},
|
||||
"github_actions": {
|
||||
"service": "GitHub Actions",
|
||||
"plan": "Free",
|
||||
"guard": "skyeye/guards/actions-guard.json",
|
||||
"quotas": {
|
||||
"concurrent_jobs": 20,
|
||||
"minutes_per_month": 2000,
|
||||
"job_timeout_hours": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"optimize_id": "OPT-20260323",
|
||||
"timestamp": "2026-03-23T14:30:16.154Z",
|
||||
"mode": "apply",
|
||||
"guards_checked": 5,
|
||||
"adjustments": [],
|
||||
"summary": {
|
||||
"guards_checked": 5,
|
||||
"total_adjustments": 0,
|
||||
"applied": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"audit_id": "AUDIT-20260323",
|
||||
"timestamp": "2026-03-23T14:30:11.691Z",
|
||||
"mode": "full",
|
||||
"services": {
|
||||
"github_actions": {
|
||||
"service": "github_actions",
|
||||
"status": "healthy",
|
||||
"usage_percent": 0,
|
||||
"alerts": [],
|
||||
"recommendations": [
|
||||
"日均可用预算: 250 分钟 (剩余 8 天)"
|
||||
]
|
||||
},
|
||||
"google_drive": {
|
||||
"service": "google_drive",
|
||||
"status": "healthy",
|
||||
"usage_percent": 0,
|
||||
"alerts": [],
|
||||
"recommendations": []
|
||||
},
|
||||
"notion_api": {
|
||||
"service": "notion_api",
|
||||
"status": "healthy",
|
||||
"usage_percent": 0,
|
||||
"alerts": [],
|
||||
"recommendations": []
|
||||
},
|
||||
"gemini": {
|
||||
"service": "gemini",
|
||||
"status": "healthy",
|
||||
"usage_percent": 0,
|
||||
"alerts": [],
|
||||
"recommendations": []
|
||||
}
|
||||
},
|
||||
"overall_status": "healthy",
|
||||
"total_alerts": 0,
|
||||
"recommendations": [
|
||||
"日均可用预算: 250 分钟 (剩余 8 天)"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"scan_id": "SCAN-20260323",
|
||||
"mode": "full",
|
||||
"timestamp": "2026-03-23T14:30:07.185Z",
|
||||
"guard_scan": {
|
||||
"total": 5,
|
||||
"valid": 5,
|
||||
"invalid": [],
|
||||
"missing_fields": []
|
||||
},
|
||||
"workflow_scan": {
|
||||
"total": 79,
|
||||
"files": [
|
||||
"agent-checkin.yml",
|
||||
"auto-deploy-drive-bridge.yml",
|
||||
"auto-reply-discussions.yml",
|
||||
"bingshuo-deploy-agent.yml",
|
||||
"bingshuo-neural-system.yml",
|
||||
"brain-sync.yml",
|
||||
"bridge-broadcast-pdf.yml",
|
||||
"bridge-changes-to-notion.yml",
|
||||
"bridge-heartbeat.yml",
|
||||
"bridge-session-summary.yml",
|
||||
"bridge-syslog-intake.yml",
|
||||
"bridge-syslog-to-notion.yml",
|
||||
"buffer-collect.yml",
|
||||
"buffer-flush.yml",
|
||||
"check-structure.yml",
|
||||
"daily-maintenance.yml",
|
||||
"daily-report.yml",
|
||||
"deploy-pages.yml",
|
||||
"deploy-to-server.yml",
|
||||
"distribute-broadcasts.yml",
|
||||
"esp-signal-processor.yml",
|
||||
"execution-sync.yml",
|
||||
"federation-bridge.yml",
|
||||
"feishu-syslog-bridge.yml",
|
||||
"generate-module-doc.yml",
|
||||
"grid-db-archive.yml",
|
||||
"grid-db-processor.yml",
|
||||
"grid-db-training-extract.yml",
|
||||
"hli-contract-check.yml",
|
||||
"meta-watchdog.yml",
|
||||
"notion-callback-pipeline.yml",
|
||||
"notion-connectivity-test.yml",
|
||||
"notion-heartbeat.yml",
|
||||
"notion-page-reader.yml",
|
||||
"notion-poll.yml",
|
||||
"notion-wake-listener.yml",
|
||||
"openclaw-wake-loop.yml",
|
||||
"persona-invoke.yml",
|
||||
"pm2-server-diagnose.yml",
|
||||
"process-notion-orders.yml",
|
||||
"ps-on-build.yml",
|
||||
"ps-on-chat.yml",
|
||||
"ps-on-complete.yml",
|
||||
"ps-on-login.yml",
|
||||
"psp-daily-inspection.yml",
|
||||
"pull-sync-awen.yml",
|
||||
"push-broadcast-feishu.yml",
|
||||
"push-broadcast.yml",
|
||||
"receive-spoke-checkin.yml",
|
||||
"receive-syslog.yml",
|
||||
"sandbox-deploy.yml",
|
||||
"server-patrol.yml",
|
||||
"skyeye-checkin-audit.yml",
|
||||
"skyeye-checkin-receiver.yml",
|
||||
"skyeye-weekly-scan.yml",
|
||||
"staging-preview.yml",
|
||||
"sync-deploy-to-notion.yml",
|
||||
"sync-dev-status.yml",
|
||||
"sync-griddb-to-drive.yml",
|
||||
"sync-griddb-to-notion.yml",
|
||||
"sync-login-entry.yml",
|
||||
"sync-notion-to-griddb.yml",
|
||||
"sync-persona-studio.yml",
|
||||
"syslog-auto-pipeline.yml",
|
||||
"syslog-issue-pipeline.yml",
|
||||
"syslog-pipeline.yml",
|
||||
"test-notion-bridge.yml",
|
||||
"tianyan-daily-patrol.yml",
|
||||
"update-readme-bulletin.yml",
|
||||
"update-repo-map.yml",
|
||||
"zhuyuan-brain-sync.yml",
|
||||
"zhuyuan-commander.yml",
|
||||
"zhuyuan-daily-agent.yml",
|
||||
"zhuyuan-daily-inspection.yml",
|
||||
"zhuyuan-daily-selfcheck.yml",
|
||||
"zhuyuan-gate-guard.yml",
|
||||
"zhuyuan-issue-reply.yml",
|
||||
"zhuyuan-pr-review.yml",
|
||||
"zhuyuan-skyeye.yml"
|
||||
],
|
||||
"errors": []
|
||||
},
|
||||
"directory_scan": {
|
||||
"checked": 14,
|
||||
"present": 14,
|
||||
"missing": []
|
||||
},
|
||||
"sub_repo_scan": {
|
||||
"total": 2,
|
||||
"verified": [
|
||||
{
|
||||
"name": "qinfendebingshuo/guanghulab",
|
||||
"role": "main",
|
||||
"guard_path_exists": true
|
||||
},
|
||||
{
|
||||
"name": "WENZHUOXI/guanghu-awen",
|
||||
"role": "sub-repo",
|
||||
"guard_path_exists": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"issues": [],
|
||||
"summary": {
|
||||
"total_issues": 0,
|
||||
"errors": 0,
|
||||
"warnings": 0,
|
||||
"guards_healthy": "5/5",
|
||||
"workflows_found": 79,
|
||||
"directories_ok": "14/14"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"heal_id": "HEAL-20260323",
|
||||
"timestamp": "2026-03-23T14:30:20.461Z",
|
||||
"guard_repairs": {
|
||||
"repaired": [],
|
||||
"failed": []
|
||||
},
|
||||
"buffer_cleanup": {
|
||||
"files_cleaned": 0,
|
||||
"directories": [
|
||||
{
|
||||
"path": "buffer/processed",
|
||||
"cleaned": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"log_cleanup": {
|
||||
"files_cleaned": 0
|
||||
},
|
||||
"guard_restarts": {
|
||||
"restarted": []
|
||||
},
|
||||
"directory_fixes": {
|
||||
"created": []
|
||||
},
|
||||
"summary": {
|
||||
"configs_repaired": 0,
|
||||
"files_cleaned": 0,
|
||||
"guards_restarted": 0,
|
||||
"directories_created": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"ledger_version": "1.0.0",
|
||||
"billing_cycle": {
|
||||
"start": "2026-03-01",
|
||||
"end": "2026-03-31"
|
||||
},
|
||||
"services": {
|
||||
"github_actions": {
|
||||
"limit": 2000,
|
||||
"used": 0,
|
||||
"remaining": 2000,
|
||||
"allocated": {
|
||||
"buffer_system": 360,
|
||||
"skyeye_daily": 180,
|
||||
"skyeye_weekly": 30,
|
||||
"sub_repo_checkins": 200,
|
||||
"emergency": 1230
|
||||
},
|
||||
"daily_entries": []
|
||||
},
|
||||
"google_drive": {
|
||||
"storage_limit_gb": 15,
|
||||
"storage_used_gb": 0,
|
||||
"apps_script_triggers_today": 0,
|
||||
"apps_script_triggers_limit": 20
|
||||
},
|
||||
"notion_api": {
|
||||
"rate_limit_per_second": 3,
|
||||
"requests_today": 0,
|
||||
"errors_today": 0
|
||||
},
|
||||
"gemini": {
|
||||
"daily_limit": 1500,
|
||||
"daily_used": 0,
|
||||
"per_minute_limit": 15
|
||||
}
|
||||
},
|
||||
"health_summary": {
|
||||
"overall": "healthy",
|
||||
"alerts": [],
|
||||
"last_updated": "2026-03-23T14:30:11.691Z"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"report_id": "SKYEYE-SCAN-20260323",
|
||||
"scan_time": "2026-03-23T14:30:25.666Z",
|
||||
"scan_time_beijing": "2026/3/23 22:30:25",
|
||||
"scan_duration_seconds": 0,
|
||||
"infrastructure_status": {
|
||||
"total_services": 5,
|
||||
"healthy": 5,
|
||||
"degraded": 0,
|
||||
"down": 0
|
||||
},
|
||||
"quota_status": {
|
||||
"github_actions": {
|
||||
"used_percent": 0,
|
||||
"remaining_minutes": 2000,
|
||||
"trend": "stable",
|
||||
"recommendation": "maintain_current_frequency"
|
||||
},
|
||||
"google_drive": {
|
||||
"used_percent": 0,
|
||||
"recommendation": "no_action_needed"
|
||||
},
|
||||
"notion_api": {
|
||||
"status": "healthy",
|
||||
"recommendation": "no_action_needed"
|
||||
},
|
||||
"gemini": {
|
||||
"used_percent": 0,
|
||||
"recommendation": "no_action_needed"
|
||||
}
|
||||
},
|
||||
"guard_status": {
|
||||
"total_guards": 5,
|
||||
"active": 5,
|
||||
"suspended": 0,
|
||||
"adjustments_made": []
|
||||
},
|
||||
"self_heal_actions": {
|
||||
"files_cleaned": 0,
|
||||
"configs_repaired": 0,
|
||||
"guards_restarted": 0
|
||||
},
|
||||
"phase_details": {
|
||||
"phase1_scan": {
|
||||
"total_issues": 0,
|
||||
"errors": 0,
|
||||
"warnings": 0,
|
||||
"guards_healthy": "5/5",
|
||||
"workflows_found": 79,
|
||||
"directories_ok": "14/14"
|
||||
},
|
||||
"phase2_audit": "healthy",
|
||||
"phase3_optimize": {
|
||||
"guards_checked": 5,
|
||||
"total_adjustments": 0,
|
||||
"applied": true
|
||||
},
|
||||
"phase4_heal": {
|
||||
"configs_repaired": 0,
|
||||
"files_cleaned": 0,
|
||||
"guards_restarted": 0,
|
||||
"directories_created": 0
|
||||
}
|
||||
},
|
||||
"alerts": [],
|
||||
"human_action_required": [],
|
||||
"next_scan": "2026-03-28T12:00:00.000Z"
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/guard-factory.js
|
||||
* 天眼 Guard 工厂 — 从模板自动生成新 Guard 配置
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
|
||||
const TEMPLATE_PATH = path.join(GUARDS_DIR, 'guard-template.json');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function createGuard(serviceName, options = {}) {
|
||||
const template = loadJSON(TEMPLATE_PATH);
|
||||
if (!template) {
|
||||
console.error('[Guard Factory] ERROR: Cannot load guard-template.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const guardId = `GUARD-${serviceName.toUpperCase().replace(/\s+/g, '-')}`;
|
||||
const fileName = `${serviceName.toLowerCase().replace(/\s+/g, '-')}-guard.json`;
|
||||
const outputPath = path.join(GUARDS_DIR, fileName);
|
||||
|
||||
if (fs.existsSync(outputPath)) {
|
||||
console.warn(`[Guard Factory] WARNING: ${fileName} already exists. Use --force to overwrite.`);
|
||||
if (!options.force) return null;
|
||||
}
|
||||
|
||||
const guard = {
|
||||
...template,
|
||||
guard_id: guardId,
|
||||
target_service: options.targetService || serviceName,
|
||||
status: 'initializing',
|
||||
mode: options.mode || 'buffer',
|
||||
buffer_policy: {
|
||||
...template.buffer_policy,
|
||||
buffer_path: options.bufferPath || `buffer/${serviceName.toLowerCase()}-staging/`,
|
||||
},
|
||||
quota_policy: {
|
||||
...template.quota_policy,
|
||||
monthly_limit: options.monthlyLimit || template.quota_policy.monthly_limit,
|
||||
},
|
||||
health_check: {
|
||||
...template.health_check,
|
||||
method: options.healthCheckMethod || 'api_ping',
|
||||
},
|
||||
last_updated_by: 'skyeye-guard-factory',
|
||||
last_updated_at: getTimestamp()
|
||||
};
|
||||
|
||||
saveJSON(outputPath, guard);
|
||||
console.log(`[Guard Factory] Created: ${fileName}`);
|
||||
console.log(`[Guard Factory] Guard ID: ${guardId}`);
|
||||
console.log(`[Guard Factory] Status: initializing`);
|
||||
|
||||
return { fileName, guard };
|
||||
}
|
||||
|
||||
function listGuards() {
|
||||
const files = fs.readdirSync(GUARDS_DIR)
|
||||
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
|
||||
console.log(`[Guard Factory] Current guards (${files.length}):`);
|
||||
for (const file of files) {
|
||||
const guard = loadJSON(path.join(GUARDS_DIR, file));
|
||||
if (guard) {
|
||||
console.log(` ${guard.guard_id}: ${guard.target_service} [${guard.status}] (${guard.mode})`);
|
||||
} else {
|
||||
console.log(` ${file}: [INVALID JSON]`);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function run() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--list')) {
|
||||
listGuards();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.includes('--create')) {
|
||||
const nameIdx = args.indexOf('--create') + 1;
|
||||
if (nameIdx >= args.length) {
|
||||
console.error('[Guard Factory] Usage: --create <service_name> [--force]');
|
||||
process.exit(1);
|
||||
}
|
||||
const serviceName = args[nameIdx];
|
||||
const force = args.includes('--force');
|
||||
createGuard(serviceName, { force });
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: list existing guards
|
||||
console.log('[Guard Factory] 天眼 Guard 工厂');
|
||||
console.log('Usage:');
|
||||
console.log(' --list List all existing guards');
|
||||
console.log(' --create <name> Create a new guard from template');
|
||||
console.log(' --force Overwrite existing guard');
|
||||
listGuards();
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/optimizer.js
|
||||
* 天眼动态调优引擎 — 基于配额使用情况自动调整 Guard 触发频率
|
||||
* Phase 3 of weekly scan: Optimize Guards
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
|
||||
const LEDGER_PATH = path.join(SKYEYE_DIR, 'quota-ledger.json');
|
||||
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getDateStr() {
|
||||
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function calculateUsagePercent(guard) {
|
||||
const qp = guard.quota_policy;
|
||||
if (!qp || !qp.monthly_limit || qp.monthly_limit === 0) return 0;
|
||||
return Math.round(((qp.current_month_used || 0) / qp.monthly_limit) * 100 * 10) / 10;
|
||||
}
|
||||
|
||||
const OPTIMIZATION_RULES = [
|
||||
{
|
||||
name: 'quota_warning_reduce_frequency',
|
||||
condition: (guard) => {
|
||||
const pct = calculateUsagePercent(guard);
|
||||
return pct > 70 && pct <= 90;
|
||||
},
|
||||
action: (guard) => {
|
||||
const prev = guard.trigger_policy.default_frequency;
|
||||
guard.trigger_policy.default_frequency = guard.trigger_policy.min_frequency;
|
||||
return {
|
||||
rule: 'quota_warning_reduce_frequency',
|
||||
guard_id: guard.guard_id,
|
||||
action: 'reduce_frequency',
|
||||
detail: `配额超 70%,频率从 ${prev} 降至 ${guard.trigger_policy.min_frequency}`,
|
||||
previous: prev,
|
||||
current: guard.trigger_policy.min_frequency
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'quota_critical_emergency_only',
|
||||
condition: (guard) => {
|
||||
const pct = calculateUsagePercent(guard);
|
||||
return pct > 90;
|
||||
},
|
||||
action: (guard) => {
|
||||
const prevMode = guard.mode;
|
||||
guard.mode = 'emergency_only';
|
||||
return {
|
||||
rule: 'quota_critical_emergency_only',
|
||||
guard_id: guard.guard_id,
|
||||
action: 'switch_to_emergency',
|
||||
detail: `配额超 90%,切换至仅紧急模式`,
|
||||
previous_mode: prevMode,
|
||||
current_mode: 'emergency_only'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'health_check_failures_suspend',
|
||||
condition: (guard) => {
|
||||
return guard.health_check && guard.health_check.consecutive_failures >= 3;
|
||||
},
|
||||
action: (guard) => {
|
||||
const prevMode = guard.mode;
|
||||
guard.mode = 'suspended';
|
||||
return {
|
||||
rule: 'health_check_failures_suspend',
|
||||
guard_id: guard.guard_id,
|
||||
action: 'suspend',
|
||||
detail: `连续 ${guard.health_check.consecutive_failures} 次健康检查失败,已暂停`,
|
||||
previous_mode: prevMode,
|
||||
current_mode: 'suspended'
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'backlog_increase_frequency',
|
||||
condition: (guard) => {
|
||||
const pct = calculateUsagePercent(guard);
|
||||
const pending = guard.buffer_policy && guard.buffer_policy.pending_count;
|
||||
return pending > 100 && pct < 50;
|
||||
},
|
||||
action: (guard) => {
|
||||
const prev = guard.trigger_policy.default_frequency;
|
||||
guard.trigger_policy.default_frequency = guard.trigger_policy.max_frequency;
|
||||
return {
|
||||
rule: 'backlog_increase_frequency',
|
||||
guard_id: guard.guard_id,
|
||||
action: 'increase_frequency',
|
||||
detail: `消息积压 ${guard.buffer_policy.pending_count} 条,配额充裕,已加频`,
|
||||
previous: prev,
|
||||
current: guard.trigger_policy.max_frequency
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function optimizeGuard(guard) {
|
||||
const adjustments = [];
|
||||
|
||||
for (const rule of OPTIMIZATION_RULES) {
|
||||
if (rule.condition(guard)) {
|
||||
const adjustment = rule.action(guard);
|
||||
adjustments.push(adjustment);
|
||||
// Critical/emergency rules take priority - stop processing
|
||||
if (rule.name === 'quota_critical_emergency_only' || rule.name === 'health_check_failures_suspend') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustments.length > 0) {
|
||||
guard.last_updated_by = 'skyeye-optimizer';
|
||||
guard.last_updated_at = getTimestamp();
|
||||
}
|
||||
|
||||
return adjustments;
|
||||
}
|
||||
|
||||
function run() {
|
||||
const args = process.argv.slice(2);
|
||||
const applyChanges = args.includes('--apply');
|
||||
|
||||
console.log(`[SkyEye Optimizer] Mode: ${applyChanges ? 'apply' : 'dry-run'}`);
|
||||
console.log(`[SkyEye Optimizer] Timestamp: ${getTimestamp()}`);
|
||||
|
||||
const optimizeResult = {
|
||||
optimize_id: `OPT-${getDateStr()}`,
|
||||
timestamp: getTimestamp(),
|
||||
mode: applyChanges ? 'apply' : 'dry-run',
|
||||
guards_checked: 0,
|
||||
adjustments: [],
|
||||
summary: {}
|
||||
};
|
||||
|
||||
const guardFiles = fs.readdirSync(GUARDS_DIR)
|
||||
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
|
||||
for (const file of guardFiles) {
|
||||
const filePath = path.join(GUARDS_DIR, file);
|
||||
const guard = loadJSON(filePath);
|
||||
if (!guard) {
|
||||
console.warn(`[SkyEye Optimizer] WARNING: Cannot load ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
optimizeResult.guards_checked++;
|
||||
const adjustments = optimizeGuard(guard);
|
||||
|
||||
if (adjustments.length > 0) {
|
||||
optimizeResult.adjustments.push(...adjustments);
|
||||
if (applyChanges) {
|
||||
saveJSON(filePath, guard);
|
||||
console.log(` [APPLIED] ${file}: ${adjustments.length} adjustment(s)`);
|
||||
} else {
|
||||
console.log(` [DRY-RUN] ${file}: ${adjustments.length} adjustment(s) would be applied`);
|
||||
}
|
||||
} else {
|
||||
console.log(` [OK] ${file}: no adjustments needed`);
|
||||
}
|
||||
}
|
||||
|
||||
optimizeResult.summary = {
|
||||
guards_checked: optimizeResult.guards_checked,
|
||||
total_adjustments: optimizeResult.adjustments.length,
|
||||
applied: applyChanges
|
||||
};
|
||||
|
||||
// Write log
|
||||
const logDir = path.join(LOGS_DIR, 'weekly');
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
||||
const logPath = path.join(logDir, `optimize-${getDateStr()}.json`);
|
||||
saveJSON(logPath, optimizeResult);
|
||||
|
||||
console.log(`[SkyEye Optimizer] Guards checked: ${optimizeResult.guards_checked}`);
|
||||
console.log(`[SkyEye Optimizer] Adjustments: ${optimizeResult.adjustments.length}`);
|
||||
console.log(`[SkyEye Optimizer] Log saved: ${logPath}`);
|
||||
|
||||
console.log('---OPTIMIZE_RESULT_JSON---');
|
||||
console.log(JSON.stringify(optimizeResult, null, 2));
|
||||
|
||||
return optimizeResult;
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/quota-tracker.js
|
||||
* 天眼配额追踪器 — 配额审计与预算精算
|
||||
* Phase 2 of weekly scan: Quota Audit
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const LEDGER_PATH = path.join(SKYEYE_DIR, 'quota-ledger.json');
|
||||
const MANIFEST_PATH = path.join(SKYEYE_DIR, 'infra-manifest.json');
|
||||
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getDateStr() {
|
||||
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function calculateUsagePercent(used, limit) {
|
||||
if (!limit || limit === 0) return 0;
|
||||
return Math.round((used / limit) * 100 * 10) / 10;
|
||||
}
|
||||
|
||||
function getDaysRemainingInMonth() {
|
||||
const now = new Date();
|
||||
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
return lastDay.getDate() - now.getDate();
|
||||
}
|
||||
|
||||
function auditService(name, serviceData, manifest) {
|
||||
const result = {
|
||||
service: name,
|
||||
status: 'healthy',
|
||||
usage_percent: 0,
|
||||
alerts: [],
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
switch (name) {
|
||||
case 'github_actions': {
|
||||
const pct = calculateUsagePercent(serviceData.used, serviceData.limit);
|
||||
result.usage_percent = pct;
|
||||
const daysLeft = getDaysRemainingInMonth();
|
||||
const dailyBudget = daysLeft > 0 ? Math.round(serviceData.remaining / daysLeft) : 0;
|
||||
|
||||
if (pct > 90) {
|
||||
result.status = 'critical';
|
||||
result.alerts.push('GitHub Actions 配额使用超过 90%,仅允许紧急操作');
|
||||
} else if (pct > 70) {
|
||||
result.status = 'warning';
|
||||
result.alerts.push('GitHub Actions 配额使用超过 70%,建议降低触发频率');
|
||||
}
|
||||
|
||||
result.recommendations.push(`日均可用预算: ${dailyBudget} 分钟 (剩余 ${daysLeft} 天)`);
|
||||
break;
|
||||
}
|
||||
case 'google_drive': {
|
||||
const pct = calculateUsagePercent(serviceData.storage_used_gb, serviceData.storage_limit_gb);
|
||||
result.usage_percent = pct;
|
||||
|
||||
if (pct > 80) {
|
||||
result.status = 'warning';
|
||||
result.alerts.push('Google Drive 存储使用超过 80%,建议清理旧文件');
|
||||
}
|
||||
|
||||
if (serviceData.apps_script_triggers_today >= serviceData.apps_script_triggers_limit * 0.9) {
|
||||
result.alerts.push('Apps Script 每日触发数接近上限');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'notion_api': {
|
||||
if (serviceData.errors_today > 10) {
|
||||
result.status = 'warning';
|
||||
result.alerts.push(`Notion API 今日错误数较高: ${serviceData.errors_today}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'gemini': {
|
||||
const pct = calculateUsagePercent(serviceData.daily_used, serviceData.daily_limit);
|
||||
result.usage_percent = pct;
|
||||
|
||||
if (pct > 90) {
|
||||
result.status = 'critical';
|
||||
result.alerts.push('Gemini 每日配额即将耗尽');
|
||||
} else if (pct > 70) {
|
||||
result.status = 'warning';
|
||||
result.alerts.push('Gemini 每日配额使用超过 70%');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function run() {
|
||||
const args = process.argv.slice(2);
|
||||
const isAudit = args.includes('--audit');
|
||||
|
||||
console.log(`[SkyEye Quota Tracker] Mode: ${isAudit ? 'full audit' : 'quick check'}`);
|
||||
console.log(`[SkyEye Quota Tracker] Timestamp: ${getTimestamp()}`);
|
||||
|
||||
const ledger = loadJSON(LEDGER_PATH);
|
||||
if (!ledger) {
|
||||
console.error('[SkyEye Quota Tracker] ERROR: Cannot load quota-ledger.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const manifest = loadJSON(MANIFEST_PATH);
|
||||
|
||||
const auditResult = {
|
||||
audit_id: `AUDIT-${getDateStr()}`,
|
||||
timestamp: getTimestamp(),
|
||||
mode: isAudit ? 'full' : 'quick',
|
||||
services: {},
|
||||
overall_status: 'healthy',
|
||||
total_alerts: 0,
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
// Audit each service
|
||||
for (const [name, data] of Object.entries(ledger.services)) {
|
||||
const serviceAudit = auditService(name, data, manifest);
|
||||
auditResult.services[name] = serviceAudit;
|
||||
|
||||
if (serviceAudit.status === 'critical') {
|
||||
auditResult.overall_status = 'critical';
|
||||
} else if (serviceAudit.status === 'warning' && auditResult.overall_status !== 'critical') {
|
||||
auditResult.overall_status = 'warning';
|
||||
}
|
||||
|
||||
auditResult.total_alerts += serviceAudit.alerts.length;
|
||||
auditResult.recommendations.push(...serviceAudit.recommendations);
|
||||
}
|
||||
|
||||
// Update ledger health summary
|
||||
ledger.health_summary.overall = auditResult.overall_status;
|
||||
ledger.health_summary.alerts = [];
|
||||
for (const [, svc] of Object.entries(auditResult.services)) {
|
||||
ledger.health_summary.alerts.push(...svc.alerts);
|
||||
}
|
||||
ledger.health_summary.last_updated = getTimestamp();
|
||||
saveJSON(LEDGER_PATH, ledger);
|
||||
|
||||
// Write audit log
|
||||
const logDir = path.join(LOGS_DIR, isAudit ? 'weekly' : 'daily');
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
||||
const logPath = path.join(logDir, `quota-audit-${getDateStr()}.json`);
|
||||
saveJSON(logPath, auditResult);
|
||||
|
||||
console.log(`[SkyEye Quota Tracker] Overall status: ${auditResult.overall_status}`);
|
||||
console.log(`[SkyEye Quota Tracker] Total alerts: ${auditResult.total_alerts}`);
|
||||
for (const [name, svc] of Object.entries(auditResult.services)) {
|
||||
console.log(` ${name}: ${svc.status} (${svc.usage_percent}% used)`);
|
||||
}
|
||||
console.log(`[SkyEye Quota Tracker] Log saved: ${logPath}`);
|
||||
|
||||
console.log('---AUDIT_RESULT_JSON---');
|
||||
console.log(JSON.stringify(auditResult, null, 2));
|
||||
|
||||
return auditResult;
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/scan-engine.js
|
||||
* 天眼扫描引擎 — 全量 + 增量基础设施扫描
|
||||
* Phase 1 of weekly scan: Infrastructure Scan
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const MANIFEST_PATH = path.join(SKYEYE_DIR, 'infra-manifest.json');
|
||||
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
|
||||
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getDateStr() {
|
||||
const d = new Date();
|
||||
return d.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function scanGuardConfigs() {
|
||||
const results = { total: 0, valid: 0, invalid: [], missing_fields: [] };
|
||||
const requiredFields = ['guard_id', 'status', 'mode', 'quota_policy', 'health_check'];
|
||||
|
||||
const files = fs.readdirSync(GUARDS_DIR).filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
results.total = files.length;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(GUARDS_DIR, file);
|
||||
const guard = loadJSON(filePath);
|
||||
if (!guard) {
|
||||
results.invalid.push({ file, reason: 'Invalid JSON' });
|
||||
continue;
|
||||
}
|
||||
const missing = requiredFields.filter(f => !(f in guard));
|
||||
if (missing.length > 0) {
|
||||
results.missing_fields.push({ file, missing });
|
||||
} else {
|
||||
results.valid++;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function scanWorkflows() {
|
||||
const workflowDir = path.join(ROOT, '.github/workflows');
|
||||
const results = { total: 0, files: [], errors: [] };
|
||||
|
||||
if (!fs.existsSync(workflowDir)) {
|
||||
results.errors.push('Workflow directory not found');
|
||||
return results;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
|
||||
results.total = files.length;
|
||||
results.files = files;
|
||||
return results;
|
||||
}
|
||||
|
||||
function scanDirectoryStructure() {
|
||||
const requiredDirs = [
|
||||
'buffer',
|
||||
'buffer/inbox',
|
||||
'buffer/staging',
|
||||
'buffer/scripts',
|
||||
'buffer/config',
|
||||
'grid-db',
|
||||
'skyeye',
|
||||
'skyeye/guards',
|
||||
'skyeye/scripts',
|
||||
'skyeye/logs',
|
||||
'skyeye/scan-report',
|
||||
'.github/persona-brain',
|
||||
'.github/workflows',
|
||||
'scripts/skyeye'
|
||||
];
|
||||
|
||||
const results = { checked: requiredDirs.length, present: 0, missing: [] };
|
||||
|
||||
for (const dir of requiredDirs) {
|
||||
const fullPath = path.join(ROOT, dir);
|
||||
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
||||
results.present++;
|
||||
} else {
|
||||
results.missing.push(dir);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function scanSubRepos(manifest) {
|
||||
const results = { total: 0, verified: [] };
|
||||
if (!manifest || !manifest.infrastructure || !manifest.infrastructure.github) return results;
|
||||
|
||||
const repos = manifest.infrastructure.github.repos || [];
|
||||
results.total = repos.length;
|
||||
|
||||
for (const repo of repos) {
|
||||
results.verified.push({
|
||||
name: repo.name,
|
||||
role: repo.role,
|
||||
guard_path_exists: repo.guard ? fs.existsSync(path.join(ROOT, repo.guard)) : false
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function run() {
|
||||
const args = process.argv.slice(2);
|
||||
const mode = args.find(a => a.startsWith('--mode='))?.split('=')[1] || 'full';
|
||||
|
||||
console.log(`[SkyEye Scan Engine] Mode: ${mode}`);
|
||||
console.log(`[SkyEye Scan Engine] Timestamp: ${getTimestamp()}`);
|
||||
|
||||
const manifest = loadJSON(MANIFEST_PATH);
|
||||
if (!manifest) {
|
||||
console.error('[SkyEye Scan Engine] ERROR: Cannot load infra-manifest.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const scanResult = {
|
||||
scan_id: `SCAN-${getDateStr()}`,
|
||||
mode,
|
||||
timestamp: getTimestamp(),
|
||||
guard_scan: scanGuardConfigs(),
|
||||
workflow_scan: scanWorkflows(),
|
||||
directory_scan: scanDirectoryStructure(),
|
||||
sub_repo_scan: scanSubRepos(manifest),
|
||||
issues: [],
|
||||
summary: {}
|
||||
};
|
||||
|
||||
// Collect issues
|
||||
if (scanResult.guard_scan.invalid.length > 0) {
|
||||
scanResult.issues.push(...scanResult.guard_scan.invalid.map(i => ({
|
||||
severity: 'error',
|
||||
category: 'guard_config',
|
||||
detail: `Invalid guard config: ${i.file} - ${i.reason}`
|
||||
})));
|
||||
}
|
||||
if (scanResult.guard_scan.missing_fields.length > 0) {
|
||||
scanResult.issues.push(...scanResult.guard_scan.missing_fields.map(i => ({
|
||||
severity: 'warning',
|
||||
category: 'guard_config',
|
||||
detail: `Guard ${i.file} missing fields: ${i.missing.join(', ')}`
|
||||
})));
|
||||
}
|
||||
if (scanResult.directory_scan.missing.length > 0) {
|
||||
scanResult.issues.push(...scanResult.directory_scan.missing.map(d => ({
|
||||
severity: 'warning',
|
||||
category: 'directory_structure',
|
||||
detail: `Missing directory: ${d}`
|
||||
})));
|
||||
}
|
||||
|
||||
// Summary
|
||||
scanResult.summary = {
|
||||
total_issues: scanResult.issues.length,
|
||||
errors: scanResult.issues.filter(i => i.severity === 'error').length,
|
||||
warnings: scanResult.issues.filter(i => i.severity === 'warning').length,
|
||||
guards_healthy: `${scanResult.guard_scan.valid}/${scanResult.guard_scan.total}`,
|
||||
workflows_found: scanResult.workflow_scan.total,
|
||||
directories_ok: `${scanResult.directory_scan.present}/${scanResult.directory_scan.checked}`
|
||||
};
|
||||
|
||||
// Update manifest
|
||||
manifest.last_scan = getTimestamp();
|
||||
manifest.last_scan_by = 'skyeye-scan-engine';
|
||||
saveJSON(MANIFEST_PATH, manifest);
|
||||
|
||||
// Write log
|
||||
const logDir = path.join(LOGS_DIR, mode === 'full' ? 'weekly' : 'daily');
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
||||
const logPath = path.join(logDir, `scan-${getDateStr()}.json`);
|
||||
saveJSON(logPath, scanResult);
|
||||
|
||||
console.log(`[SkyEye Scan Engine] Scan complete.`);
|
||||
console.log(`[SkyEye Scan Engine] Guards: ${scanResult.summary.guards_healthy}`);
|
||||
console.log(`[SkyEye Scan Engine] Workflows: ${scanResult.summary.workflows_found}`);
|
||||
console.log(`[SkyEye Scan Engine] Directories: ${scanResult.summary.directories_ok}`);
|
||||
console.log(`[SkyEye Scan Engine] Issues: ${scanResult.summary.total_issues} (${scanResult.summary.errors} errors, ${scanResult.summary.warnings} warnings)`);
|
||||
console.log(`[SkyEye Scan Engine] Log saved: ${logPath}`);
|
||||
|
||||
// Output full result as JSON for pipeline
|
||||
console.log('---SCAN_RESULT_JSON---');
|
||||
console.log(JSON.stringify(scanResult, null, 2));
|
||||
|
||||
return scanResult;
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/self-healer.js
|
||||
* 天眼自愈引擎 — 自动修复损坏配置、清理过期数据
|
||||
* Phase 4 of weekly scan: Self-Heal
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
|
||||
const TEMPLATE_PATH = path.join(GUARDS_DIR, 'guard-template.json');
|
||||
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getDateStr() {
|
||||
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function repairGuardConfigs() {
|
||||
const results = { repaired: [], failed: [] };
|
||||
const template = loadJSON(TEMPLATE_PATH);
|
||||
if (!template) {
|
||||
results.failed.push({ file: 'guard-template.json', reason: 'Template missing or invalid' });
|
||||
return results;
|
||||
}
|
||||
|
||||
const requiredFields = ['guard_id', 'status', 'mode', 'buffer_policy', 'quota_policy', 'trigger_policy', 'health_check'];
|
||||
|
||||
const files = fs.readdirSync(GUARDS_DIR)
|
||||
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(GUARDS_DIR, file);
|
||||
let guard = loadJSON(filePath);
|
||||
|
||||
if (!guard) {
|
||||
// File is corrupted - rebuild from template
|
||||
console.log(` [REPAIR] ${file}: Corrupted JSON, rebuilding from template`);
|
||||
guard = { ...template };
|
||||
guard.guard_id = `GUARD-${file.replace('-guard.json', '').toUpperCase()}`;
|
||||
guard.status = 'repaired';
|
||||
guard.last_updated_by = 'skyeye-self-healer';
|
||||
guard.last_updated_at = getTimestamp();
|
||||
saveJSON(filePath, guard);
|
||||
results.repaired.push({ file, action: 'rebuilt_from_template' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for missing fields and fill from template
|
||||
let modified = false;
|
||||
for (const field of requiredFields) {
|
||||
if (!(field in guard)) {
|
||||
guard[field] = template[field];
|
||||
modified = true;
|
||||
console.log(` [REPAIR] ${file}: Added missing field '${field}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
guard.last_updated_by = 'skyeye-self-healer';
|
||||
guard.last_updated_at = getTimestamp();
|
||||
saveJSON(filePath, guard);
|
||||
results.repaired.push({ file, action: 'filled_missing_fields' });
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function cleanExpiredBuffers() {
|
||||
const results = { files_cleaned: 0, directories: [] };
|
||||
const processedDir = path.join(ROOT, 'buffer/processed');
|
||||
|
||||
if (!fs.existsSync(processedDir)) return results;
|
||||
|
||||
const maxAgeDays = 7;
|
||||
const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(processedDir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(processedDir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.mtimeMs < cutoff) {
|
||||
if (stat.isFile()) {
|
||||
fs.unlinkSync(filePath);
|
||||
results.files_cleaned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
results.directories.push({ path: 'buffer/processed', cleaned: results.files_cleaned });
|
||||
} catch (e) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function cleanExpiredLogs() {
|
||||
const results = { files_cleaned: 0 };
|
||||
const maxAgeDays = 30;
|
||||
const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
const logDirs = [
|
||||
path.join(LOGS_DIR, 'daily'),
|
||||
path.join(LOGS_DIR, 'weekly')
|
||||
];
|
||||
|
||||
for (const dir of logDirs) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
try {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
if (file === '.gitkeep') continue;
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.mtimeMs < cutoff && stat.isFile()) {
|
||||
fs.unlinkSync(filePath);
|
||||
results.files_cleaned++;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function restartSuspendedGuards() {
|
||||
const results = { restarted: [] };
|
||||
|
||||
const files = fs.readdirSync(GUARDS_DIR)
|
||||
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(GUARDS_DIR, file);
|
||||
const guard = loadJSON(filePath);
|
||||
if (!guard) continue;
|
||||
|
||||
// Restart suspended guards with healthy checks
|
||||
if (guard.mode === 'suspended' && guard.health_check) {
|
||||
// Reset health check status for retry
|
||||
guard.health_check.consecutive_failures = 0;
|
||||
guard.health_check.last_status = 'pending_restart';
|
||||
guard.mode = 'buffer';
|
||||
guard.status = 'active';
|
||||
guard.last_updated_by = 'skyeye-self-healer';
|
||||
guard.last_updated_at = getTimestamp();
|
||||
saveJSON(filePath, guard);
|
||||
results.restarted.push({ file, guard_id: guard.guard_id });
|
||||
console.log(` [RESTART] ${file}: Guard restarted`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function ensureDirectories() {
|
||||
const results = { created: [] };
|
||||
const requiredDirs = [
|
||||
'skyeye/scan-report',
|
||||
'skyeye/logs/daily',
|
||||
'skyeye/logs/weekly',
|
||||
'buffer/inbox',
|
||||
'buffer/staging',
|
||||
'buffer/processed'
|
||||
];
|
||||
|
||||
for (const dir of requiredDirs) {
|
||||
const fullPath = path.join(ROOT, dir);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
fs.mkdirSync(fullPath, { recursive: true });
|
||||
results.created.push(dir);
|
||||
console.log(` [CREATE] Directory: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function run() {
|
||||
console.log(`[SkyEye Self-Healer] Starting self-heal process`);
|
||||
console.log(`[SkyEye Self-Healer] Timestamp: ${getTimestamp()}`);
|
||||
|
||||
const healResult = {
|
||||
heal_id: `HEAL-${getDateStr()}`,
|
||||
timestamp: getTimestamp(),
|
||||
guard_repairs: repairGuardConfigs(),
|
||||
buffer_cleanup: cleanExpiredBuffers(),
|
||||
log_cleanup: cleanExpiredLogs(),
|
||||
guard_restarts: restartSuspendedGuards(),
|
||||
directory_fixes: ensureDirectories(),
|
||||
summary: {}
|
||||
};
|
||||
|
||||
healResult.summary = {
|
||||
configs_repaired: healResult.guard_repairs.repaired.length,
|
||||
files_cleaned: healResult.buffer_cleanup.files_cleaned + healResult.log_cleanup.files_cleaned,
|
||||
guards_restarted: healResult.guard_restarts.restarted.length,
|
||||
directories_created: healResult.directory_fixes.created.length
|
||||
};
|
||||
|
||||
// Write log
|
||||
const logDir = path.join(LOGS_DIR, 'weekly');
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
||||
const logPath = path.join(logDir, `self-heal-${getDateStr()}.json`);
|
||||
saveJSON(logPath, healResult);
|
||||
|
||||
console.log(`[SkyEye Self-Healer] Repairs: ${healResult.summary.configs_repaired}`);
|
||||
console.log(`[SkyEye Self-Healer] Files cleaned: ${healResult.summary.files_cleaned}`);
|
||||
console.log(`[SkyEye Self-Healer] Guards restarted: ${healResult.summary.guards_restarted}`);
|
||||
console.log(`[SkyEye Self-Healer] Dirs created: ${healResult.summary.directories_created}`);
|
||||
console.log(`[SkyEye Self-Healer] Log saved: ${logPath}`);
|
||||
|
||||
console.log('---HEAL_RESULT_JSON---');
|
||||
console.log(JSON.stringify(healResult, null, 2));
|
||||
|
||||
return healResult;
|
||||
}
|
||||
|
||||
run();
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* skyeye/scripts/weekly-scan.js
|
||||
* 天眼周六大巡检主脚本 — 汇总所有 Phase 结果,生成报告
|
||||
* Phase 5 of weekly scan: Generate Report
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
||||
const SCAN_REPORT_DIR = path.join(SKYEYE_DIR, 'scan-report');
|
||||
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getDateStr() {
|
||||
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
}
|
||||
|
||||
function getBeijingTime() {
|
||||
return new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
||||
}
|
||||
|
||||
function loadJSON(filePath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveJSON(filePath, data) {
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function findLatestLog(logDir, prefix) {
|
||||
if (!fs.existsSync(logDir)) return null;
|
||||
const files = fs.readdirSync(logDir)
|
||||
.filter(f => f.startsWith(prefix) && f.endsWith('.json'))
|
||||
.sort()
|
||||
.reverse();
|
||||
if (files.length === 0) return null;
|
||||
return loadJSON(path.join(logDir, files[0]));
|
||||
}
|
||||
|
||||
function countGuards() {
|
||||
const guardsDir = path.join(SKYEYE_DIR, 'guards');
|
||||
if (!fs.existsSync(guardsDir)) return { total: 0, active: 0, suspended: 0 };
|
||||
|
||||
const files = fs.readdirSync(guardsDir)
|
||||
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
||||
|
||||
let active = 0;
|
||||
let suspended = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const guard = loadJSON(path.join(guardsDir, file));
|
||||
if (guard) {
|
||||
if (guard.status === 'active') active++;
|
||||
else if (guard.status === 'suspended' || guard.mode === 'suspended') suspended++;
|
||||
}
|
||||
}
|
||||
|
||||
return { total: files.length, active, suspended };
|
||||
}
|
||||
|
||||
function generateReport() {
|
||||
console.log(`[SkyEye Weekly Scan] Generating weekly scan report`);
|
||||
console.log(`[SkyEye Weekly Scan] Beijing Time: ${getBeijingTime()}`);
|
||||
|
||||
const dateStr = getDateStr();
|
||||
const weeklyLogDir = path.join(LOGS_DIR, 'weekly');
|
||||
|
||||
// Load Phase results from logs
|
||||
const scanResult = findLatestLog(weeklyLogDir, 'scan-');
|
||||
const auditResult = findLatestLog(weeklyLogDir, 'quota-audit-');
|
||||
const optimizeResult = findLatestLog(weeklyLogDir, 'optimize-');
|
||||
const healResult = findLatestLog(weeklyLogDir, 'self-heal-');
|
||||
|
||||
const guardStatus = countGuards();
|
||||
|
||||
// Calculate next Saturday 20:00 CST
|
||||
const now = new Date();
|
||||
const dayOfWeek = now.getUTCDay();
|
||||
const daysUntilSaturday = (6 - dayOfWeek + 7) % 7 || 7;
|
||||
const nextSaturday = new Date(now);
|
||||
nextSaturday.setUTCDate(now.getUTCDate() + daysUntilSaturday);
|
||||
nextSaturday.setUTCHours(12, 0, 0, 0); // 20:00 CST = 12:00 UTC
|
||||
|
||||
const report = {
|
||||
report_id: `SKYEYE-SCAN-${dateStr}`,
|
||||
scan_time: getTimestamp(),
|
||||
scan_time_beijing: getBeijingTime(),
|
||||
scan_duration_seconds: 0,
|
||||
|
||||
infrastructure_status: {
|
||||
total_services: 5,
|
||||
healthy: 5,
|
||||
degraded: 0,
|
||||
down: 0
|
||||
},
|
||||
|
||||
quota_status: {
|
||||
github_actions: {
|
||||
used_percent: auditResult?.services?.github_actions?.usage_percent || 0,
|
||||
remaining_minutes: 2000,
|
||||
trend: 'stable',
|
||||
recommendation: 'maintain_current_frequency'
|
||||
},
|
||||
google_drive: {
|
||||
used_percent: auditResult?.services?.google_drive?.usage_percent || 0,
|
||||
recommendation: 'no_action_needed'
|
||||
},
|
||||
notion_api: {
|
||||
status: auditResult?.services?.notion_api?.status || 'healthy',
|
||||
recommendation: 'no_action_needed'
|
||||
},
|
||||
gemini: {
|
||||
used_percent: auditResult?.services?.gemini?.usage_percent || 0,
|
||||
recommendation: 'no_action_needed'
|
||||
}
|
||||
},
|
||||
|
||||
guard_status: {
|
||||
total_guards: guardStatus.total,
|
||||
active: guardStatus.active,
|
||||
suspended: guardStatus.suspended,
|
||||
adjustments_made: optimizeResult?.adjustments || []
|
||||
},
|
||||
|
||||
self_heal_actions: {
|
||||
files_cleaned: healResult?.summary?.files_cleaned || 0,
|
||||
configs_repaired: healResult?.summary?.configs_repaired || 0,
|
||||
guards_restarted: healResult?.summary?.guards_restarted || 0
|
||||
},
|
||||
|
||||
phase_details: {
|
||||
phase1_scan: scanResult?.summary || null,
|
||||
phase2_audit: auditResult?.overall_status || null,
|
||||
phase3_optimize: optimizeResult?.summary || null,
|
||||
phase4_heal: healResult?.summary || null
|
||||
},
|
||||
|
||||
alerts: [],
|
||||
human_action_required: [],
|
||||
|
||||
next_scan: nextSaturday.toISOString()
|
||||
};
|
||||
|
||||
// Collect alerts from all phases
|
||||
if (scanResult?.issues) {
|
||||
for (const issue of scanResult.issues) {
|
||||
if (issue.severity === 'error') {
|
||||
report.alerts.push(issue.detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auditResult?.services) {
|
||||
for (const [, svc] of Object.entries(auditResult.services)) {
|
||||
report.alerts.push(...(svc.alerts || []));
|
||||
}
|
||||
}
|
||||
|
||||
// Determine infrastructure health
|
||||
if (report.alerts.length > 0) {
|
||||
const criticalAlerts = report.alerts.filter(a =>
|
||||
a.includes('90%') || a.includes('critical') || a.includes('严重')
|
||||
);
|
||||
if (criticalAlerts.length > 0) {
|
||||
report.infrastructure_status.degraded = criticalAlerts.length;
|
||||
report.infrastructure_status.healthy -= criticalAlerts.length;
|
||||
}
|
||||
}
|
||||
|
||||
// Write report
|
||||
if (!fs.existsSync(SCAN_REPORT_DIR)) fs.mkdirSync(SCAN_REPORT_DIR, { recursive: true });
|
||||
const reportPath = path.join(SCAN_REPORT_DIR, `${dateStr}-weekly-scan.json`);
|
||||
saveJSON(reportPath, report);
|
||||
|
||||
console.log(`[SkyEye Weekly Scan] Report generated: ${reportPath}`);
|
||||
console.log(`[SkyEye Weekly Scan] Report ID: ${report.report_id}`);
|
||||
console.log(`[SkyEye Weekly Scan] Guards: ${guardStatus.active}/${guardStatus.total} active`);
|
||||
console.log(`[SkyEye Weekly Scan] Alerts: ${report.alerts.length}`);
|
||||
console.log(`[SkyEye Weekly Scan] Human action required: ${report.human_action_required.length}`);
|
||||
console.log(`[SkyEye Weekly Scan] Next scan: ${report.next_scan}`);
|
||||
|
||||
console.log('---REPORT_JSON---');
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
generateReport();
|
||||
Loading…
Reference in New Issue