@aion0/forge 0.5.26 → 0.5.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
- package/RELEASE_NOTES.md +10 -29
- package/app/api/terminal-bell/route.ts +6 -2
- package/app/api/terminal-cwd/route.ts +7 -4
- package/components/CodeViewer.tsx +3 -31
- package/components/Dashboard.tsx +34 -20
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
You are a help assistant for **Forge** — a self-hosted Vibe Coding platform.
|
|
2
|
+
|
|
3
|
+
Your job is to answer user questions about Forge features, configuration, and troubleshooting.
|
|
4
|
+
|
|
5
|
+
## How to answer
|
|
6
|
+
|
|
7
|
+
1. Read the relevant documentation file(s) from this directory before answering
|
|
8
|
+
2. Base your answers on the documentation content, not assumptions
|
|
9
|
+
3. If the answer isn't in the docs, say so honestly
|
|
10
|
+
4. Give concise, actionable answers with code examples when helpful
|
|
11
|
+
5. When generating files (YAML workflows, configs, scripts, etc.), **always save the file directly** to the appropriate directory rather than printing it. For pipeline workflows, save to `~/.forge/data/flows/<name>.yaml`. Tell the user the file path so they can find it. The terminal does not support copy/paste.
|
|
12
|
+
|
|
13
|
+
## API Authentication
|
|
14
|
+
|
|
15
|
+
When you need to call Forge APIs (e.g., workspace, settings) that return "unauthorized", ask the user for their admin password and authenticate:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Step 1: Get API token (ask user for their admin password)
|
|
19
|
+
TOKEN=$(curl -s -X POST http://localhost:8403/api/auth/verify \
|
|
20
|
+
-H "Content-Type: application/json" \
|
|
21
|
+
-d '{"password":"USER_PASSWORD"}' | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))")
|
|
22
|
+
|
|
23
|
+
# Step 2: Use token in subsequent requests
|
|
24
|
+
curl -s -H "X-Forge-Token: $TOKEN" http://localhost:8403/api/workspace/...
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The token is valid for 24 hours. Store it in a variable and reuse for all API calls in this session. Only ask for the password once.
|
|
28
|
+
|
|
29
|
+
## Available documentation
|
|
30
|
+
|
|
31
|
+
| File | Topic |
|
|
32
|
+
|------|-------|
|
|
33
|
+
| `00-overview.md` | Installation, startup, data paths, architecture |
|
|
34
|
+
| `01-settings.md` | All settings fields and configuration |
|
|
35
|
+
| `02-telegram.md` | Telegram bot setup and commands |
|
|
36
|
+
| `03-tunnel.md` | Remote access via Cloudflare tunnel |
|
|
37
|
+
| `04-tasks.md` | Background task system |
|
|
38
|
+
| `05-pipelines.md` | Pipeline/workflow engine — YAML format, nodes, templates, routing, scheduling, project bindings |
|
|
39
|
+
| `06-skills.md` | Skills marketplace and installation |
|
|
40
|
+
| `07-projects.md` | Project management |
|
|
41
|
+
| `08-rules.md` | CLAUDE.md templates and rule injection |
|
|
42
|
+
| `09-issue-autofix.md` | GitHub issue auto-fix pipeline |
|
|
43
|
+
| `10-troubleshooting.md` | Common issues and solutions |
|
|
44
|
+
| `11-workspace.md` | Workspace (Forge Smiths) — multi-agent orchestration, daemon, message bus, profiles |
|
|
45
|
+
|
|
46
|
+
## Matching questions to docs
|
|
47
|
+
|
|
48
|
+
- Pipeline/workflow/DAG/YAML → `05-pipelines.md`
|
|
49
|
+
- Issue/PR/auto-fix → `09-issue-autofix.md` + `05-pipelines.md`
|
|
50
|
+
- Telegram/notification → `02-telegram.md`
|
|
51
|
+
- Tunnel/remote/cloudflare → `03-tunnel.md`
|
|
52
|
+
- Task/background/queue → `04-tasks.md`
|
|
53
|
+
- Settings/config/agent/profile/provider → `01-settings.md`
|
|
54
|
+
- Install/start/update → `00-overview.md`
|
|
55
|
+
- Error/bug/crash → `10-troubleshooting.md`
|
|
56
|
+
- Skill/marketplace → `06-skills.md`
|
|
57
|
+
- Project/favorite/terminal → `07-projects.md`
|
|
58
|
+
- Rules/CLAUDE.md/template → `08-rules.md`
|
|
59
|
+
- Workspace/smith/daemon/multi-agent/bus/message/ticket → `11-workspace.md`
|
|
60
|
+
- Watch/monitor/detect/file changes/autonomous → `11-workspace.md`
|
|
61
|
+
- Agent profile/env/model/cliType → `01-settings.md` + `11-workspace.md`
|
|
62
|
+
- Agent log/logs/history/clear logs → `11-workspace.md`
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side initialization — called once on first API request per worker.
|
|
3
|
+
* When FORGE_EXTERNAL_SERVICES=1 (set by forge-server), telegram/terminal/tunnel
|
|
4
|
+
* are managed externally — only task runner starts here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ensureRunnerStarted } from './task-manager';
|
|
8
|
+
import { startTelegramBot, stopTelegramBot } from './telegram-bot';
|
|
9
|
+
import { startWatcherLoop } from './session-watcher';
|
|
10
|
+
import { getAdminPassword } from './password';
|
|
11
|
+
import { loadSettings, saveSettings } from './settings';
|
|
12
|
+
import { startTunnel } from './cloudflared';
|
|
13
|
+
import { isEncrypted, SECRET_FIELDS } from './crypto';
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
const initKey = Symbol.for('mw-initialized');
|
|
18
|
+
const gInit = globalThis as any;
|
|
19
|
+
|
|
20
|
+
/** Migrate plaintext secrets to encrypted on first run */
|
|
21
|
+
function migrateSecrets() {
|
|
22
|
+
try {
|
|
23
|
+
const { existsSync, readFileSync } = require('node:fs');
|
|
24
|
+
const YAML = require('yaml');
|
|
25
|
+
const { getDataDir: _gdd } = require('./dirs');
|
|
26
|
+
const dataDir = _gdd();
|
|
27
|
+
const file = join(dataDir, 'settings.yaml');
|
|
28
|
+
if (!existsSync(file)) return;
|
|
29
|
+
const raw = YAML.parse(readFileSync(file, 'utf-8')) || {};
|
|
30
|
+
let needsSave = false;
|
|
31
|
+
for (const field of SECRET_FIELDS) {
|
|
32
|
+
if (raw[field] && typeof raw[field] === 'string' && !isEncrypted(raw[field])) {
|
|
33
|
+
needsSave = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (needsSave) {
|
|
38
|
+
// loadSettings returns decrypted, saveSettings encrypts
|
|
39
|
+
const settings = loadSettings();
|
|
40
|
+
saveSettings(settings);
|
|
41
|
+
console.log('[init] Migrated plaintext secrets to encrypted storage');
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error('[init] Secret migration error:', e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Auto-detect agent binaries */
|
|
49
|
+
function autoDetectAgents() {
|
|
50
|
+
try {
|
|
51
|
+
const settings = loadSettings();
|
|
52
|
+
// Backward compat: detect claude if not configured
|
|
53
|
+
if (!settings.claudePath) {
|
|
54
|
+
const { execSync } = require('node:child_process');
|
|
55
|
+
try {
|
|
56
|
+
const path = execSync('which claude', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
57
|
+
if (path) {
|
|
58
|
+
settings.claudePath = path;
|
|
59
|
+
saveSettings(settings);
|
|
60
|
+
console.log(`[init] Auto-detected claude: ${path}`);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
// Detect all agents
|
|
65
|
+
const { autoDetectAgents: detect } = require('./agents');
|
|
66
|
+
detect();
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function ensureInitialized() {
|
|
71
|
+
if (gInit[initKey]) return;
|
|
72
|
+
gInit[initKey] = true;
|
|
73
|
+
|
|
74
|
+
// Add timestamps to all console output
|
|
75
|
+
try { const { initLogger } = require('./logger'); initLogger(); } catch {}
|
|
76
|
+
|
|
77
|
+
// Migrate old data layout (~/.forge/* → ~/.forge/data/*) on first run
|
|
78
|
+
try { const { migrateDataDir } = require('./dirs'); migrateDataDir(); } catch {}
|
|
79
|
+
|
|
80
|
+
// Migrate plaintext secrets on startup
|
|
81
|
+
migrateSecrets();
|
|
82
|
+
|
|
83
|
+
// Cleanup old notifications
|
|
84
|
+
try {
|
|
85
|
+
const { cleanupNotifications } = require('./notifications');
|
|
86
|
+
cleanupNotifications();
|
|
87
|
+
} catch {}
|
|
88
|
+
|
|
89
|
+
// Auto-detect claude path if not configured
|
|
90
|
+
autoDetectAgents();
|
|
91
|
+
|
|
92
|
+
// Install/update forge skills to ~/.claude/skills/ on every startup
|
|
93
|
+
try {
|
|
94
|
+
const { installForgeSkills } = require('./workspace/skill-installer');
|
|
95
|
+
installForgeSkills('', '', '', Number(process.env.PORT) || 8403);
|
|
96
|
+
console.log('[init] Forge skills installed/updated');
|
|
97
|
+
} catch {}
|
|
98
|
+
|
|
99
|
+
// Sync help docs + CLAUDE.md to data dir on startup
|
|
100
|
+
try {
|
|
101
|
+
const { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } = require('node:fs');
|
|
102
|
+
const { join: joinPath } = require('node:path');
|
|
103
|
+
const { getConfigDir, getDataDir } = require('./dirs');
|
|
104
|
+
const helpDir = joinPath(getConfigDir(), 'help');
|
|
105
|
+
const sourceDir = joinPath(process.cwd(), 'lib', 'help-docs');
|
|
106
|
+
if (existsSync(sourceDir)) {
|
|
107
|
+
if (!existsSync(helpDir)) mkdirSync(helpDir, { recursive: true });
|
|
108
|
+
for (const f of readdirSync(sourceDir)) {
|
|
109
|
+
if (f.endsWith('.md')) writeFileSync(joinPath(helpDir, f), readFileSync(joinPath(sourceDir, f)));
|
|
110
|
+
}
|
|
111
|
+
const claudeMd = joinPath(helpDir, 'CLAUDE.md');
|
|
112
|
+
if (existsSync(claudeMd)) writeFileSync(joinPath(getDataDir(), 'CLAUDE.md'), readFileSync(claudeMd));
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
|
|
116
|
+
// Sync skills registry (async, non-blocking) — on startup + every 30 min
|
|
117
|
+
try {
|
|
118
|
+
const { syncSkills } = require('./skills');
|
|
119
|
+
syncSkills().catch(() => {});
|
|
120
|
+
setInterval(() => { syncSkills().catch(() => {}); }, 60 * 60 * 1000);
|
|
121
|
+
} catch {}
|
|
122
|
+
|
|
123
|
+
// Usage scanner — scan JSONL files for token usage on startup + every hour
|
|
124
|
+
try {
|
|
125
|
+
const { scanUsage } = require('./usage-scanner');
|
|
126
|
+
scanUsage();
|
|
127
|
+
setInterval(() => { try { scanUsage(); } catch {} }, 60 * 60 * 1000);
|
|
128
|
+
} catch {}
|
|
129
|
+
|
|
130
|
+
// Task runner is safe in every worker (DB-level coordination)
|
|
131
|
+
ensureRunnerStarted();
|
|
132
|
+
|
|
133
|
+
// Session watcher is safe (file-based, idempotent)
|
|
134
|
+
startWatcherLoop();
|
|
135
|
+
|
|
136
|
+
// Pipeline scheduler — periodic execution + issue scanning for project-bound workflows
|
|
137
|
+
try {
|
|
138
|
+
const { startScheduler } = require('./pipeline-scheduler');
|
|
139
|
+
startScheduler();
|
|
140
|
+
} catch {}
|
|
141
|
+
|
|
142
|
+
// If services are managed externally (forge-server), skip
|
|
143
|
+
if (process.env.FORGE_EXTERNAL_SERVICES === '1') {
|
|
144
|
+
// Password display
|
|
145
|
+
const admin = getAdminPassword();
|
|
146
|
+
if (admin) {
|
|
147
|
+
console.log(`[init] Admin password: configured`);
|
|
148
|
+
} else {
|
|
149
|
+
console.log('[init] No admin password set — configure in Settings');
|
|
150
|
+
};
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Standalone mode (pnpm dev without forge-server) — start everything here
|
|
155
|
+
const admin2 = getAdminPassword();
|
|
156
|
+
if (admin2) {
|
|
157
|
+
console.log(`[init] Admin password: configured`);
|
|
158
|
+
} else {
|
|
159
|
+
console.log('[init] No admin password set — configure in Settings');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
startTelegramBot(); // registers task event listener only
|
|
163
|
+
startTerminalProcess();
|
|
164
|
+
startTelegramProcess(); // spawns telegram-standalone
|
|
165
|
+
startWorkspaceProcess(); // spawns workspace-standalone
|
|
166
|
+
|
|
167
|
+
const settings = loadSettings();
|
|
168
|
+
if (settings.tunnelAutoStart) {
|
|
169
|
+
startTunnel().then(result => {
|
|
170
|
+
if (result.url) console.log(`[init] Tunnel started: ${result.url}`);
|
|
171
|
+
else if (result.error) console.log(`[init] Tunnel failed: ${result.error}`);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('[init] Background services started');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Restart Telegram bot (e.g. after settings change) */
|
|
179
|
+
export function restartTelegramBot() {
|
|
180
|
+
stopTelegramBot();
|
|
181
|
+
startTelegramBot();
|
|
182
|
+
// Kill existing telegram process and restart if configured
|
|
183
|
+
if (telegramChild) {
|
|
184
|
+
try { telegramChild.kill('SIGTERM'); } catch {}
|
|
185
|
+
telegramChild = null;
|
|
186
|
+
}
|
|
187
|
+
startTelegramProcess();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let telegramChild: ReturnType<typeof spawn> | null = null;
|
|
191
|
+
|
|
192
|
+
function startTelegramProcess() {
|
|
193
|
+
if (telegramChild) return;
|
|
194
|
+
const settings = loadSettings();
|
|
195
|
+
if (!settings.telegramBotToken || !settings.telegramChatId) return;
|
|
196
|
+
|
|
197
|
+
const script = join(process.cwd(), 'lib', 'telegram-standalone.ts');
|
|
198
|
+
telegramChild = spawn('npx', ['tsx', script], {
|
|
199
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
200
|
+
env: { ...process.env, PORT: String(process.env.PORT || 8403) },
|
|
201
|
+
detached: false,
|
|
202
|
+
});
|
|
203
|
+
telegramChild.on('exit', () => { telegramChild = null; });
|
|
204
|
+
console.log('[telegram] Started standalone (pid:', telegramChild.pid, ')');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let terminalChild: ReturnType<typeof spawn> | null = null;
|
|
208
|
+
|
|
209
|
+
function startTerminalProcess() {
|
|
210
|
+
if (terminalChild) return;
|
|
211
|
+
|
|
212
|
+
const termPort = Number(process.env.TERMINAL_PORT) || 8404;
|
|
213
|
+
|
|
214
|
+
const net = require('node:net');
|
|
215
|
+
const tester = net.createServer();
|
|
216
|
+
tester.once('error', () => {
|
|
217
|
+
console.log(`[terminal] Port ${termPort} already in use, reusing existing`);
|
|
218
|
+
});
|
|
219
|
+
tester.once('listening', () => {
|
|
220
|
+
tester.close();
|
|
221
|
+
const script = join(process.cwd(), 'lib', 'terminal-standalone.ts');
|
|
222
|
+
terminalChild = spawn('npx', ['tsx', script], {
|
|
223
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
224
|
+
env: { ...Object.fromEntries(Object.entries(process.env).filter(([k]) => k !== 'CLAUDECODE')) } as NodeJS.ProcessEnv,
|
|
225
|
+
detached: false,
|
|
226
|
+
});
|
|
227
|
+
terminalChild.on('exit', () => { terminalChild = null; });
|
|
228
|
+
console.log('[terminal] Started standalone server (pid:', terminalChild.pid, ')');
|
|
229
|
+
});
|
|
230
|
+
tester.listen(termPort);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let workspaceChild: ReturnType<typeof spawn> | null = null;
|
|
234
|
+
|
|
235
|
+
function startWorkspaceProcess() {
|
|
236
|
+
if (workspaceChild) return;
|
|
237
|
+
|
|
238
|
+
const wsPort = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
239
|
+
|
|
240
|
+
const net = require('node:net');
|
|
241
|
+
const tester = net.createServer();
|
|
242
|
+
tester.once('error', () => {
|
|
243
|
+
// Port in use — kill stale process and retry after 2s
|
|
244
|
+
console.log(`[workspace] Port ${wsPort} in use, killing stale process...`);
|
|
245
|
+
try { require('node:child_process').execSync(`lsof -ti:${wsPort} | xargs kill -9 2>/dev/null`, { timeout: 3000 }); } catch {}
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
if (!workspaceChild) launchWorkspaceDaemon();
|
|
248
|
+
}, 2000);
|
|
249
|
+
});
|
|
250
|
+
tester.once('listening', () => {
|
|
251
|
+
tester.close();
|
|
252
|
+
launchWorkspaceDaemon();
|
|
253
|
+
});
|
|
254
|
+
tester.listen(wsPort);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function launchWorkspaceDaemon() {
|
|
258
|
+
const script = join(process.cwd(), 'lib', 'workspace-standalone.ts');
|
|
259
|
+
workspaceChild = spawn('npx', ['tsx', script], {
|
|
260
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
261
|
+
env: { ...process.env },
|
|
262
|
+
detached: false,
|
|
263
|
+
});
|
|
264
|
+
workspaceChild.on('exit', () => { workspaceChild = null; });
|
|
265
|
+
console.log('[workspace] Started daemon (pid:', workspaceChild.pid, ')');
|
|
266
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue Scanner — periodically scans GitHub issues for configured projects
|
|
3
|
+
* and triggers issue-fix-and-review pipeline for new issues.
|
|
4
|
+
*
|
|
5
|
+
* Per-project config stored in DB:
|
|
6
|
+
* - enabled: boolean
|
|
7
|
+
* - interval: minutes (0 = manual only)
|
|
8
|
+
* - labels: string[] (only process issues with these labels, empty = all)
|
|
9
|
+
* - baseBranch: string (default: auto-detect)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { getDb } from '@/src/core/db/database';
|
|
14
|
+
import { getDbPath } from '@/src/config';
|
|
15
|
+
import { startPipeline } from './pipeline';
|
|
16
|
+
import { loadSettings } from './settings';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
|
|
19
|
+
function db() { return getDb(getDbPath()); }
|
|
20
|
+
|
|
21
|
+
export interface IssueAutofixConfig {
|
|
22
|
+
projectPath: string;
|
|
23
|
+
projectName: string;
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
interval: number; // minutes, 0 = manual
|
|
26
|
+
labels: string[]; // filter labels
|
|
27
|
+
baseBranch: string; // default: '' (auto-detect)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── DB setup ────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function migrateTable() {
|
|
33
|
+
try { db().exec('ALTER TABLE issue_autofix_config ADD COLUMN last_scan_at TEXT'); } catch {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function ensureTable() {
|
|
37
|
+
db().exec(`
|
|
38
|
+
CREATE TABLE IF NOT EXISTS issue_autofix_config (
|
|
39
|
+
project_path TEXT PRIMARY KEY,
|
|
40
|
+
project_name TEXT NOT NULL,
|
|
41
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
interval_min INTEGER NOT NULL DEFAULT 30,
|
|
43
|
+
labels TEXT NOT NULL DEFAULT '[]',
|
|
44
|
+
base_branch TEXT NOT NULL DEFAULT '',
|
|
45
|
+
last_scan_at TEXT
|
|
46
|
+
);
|
|
47
|
+
CREATE TABLE IF NOT EXISTS issue_autofix_processed (
|
|
48
|
+
project_path TEXT NOT NULL,
|
|
49
|
+
issue_number INTEGER NOT NULL,
|
|
50
|
+
pipeline_id TEXT,
|
|
51
|
+
pr_number INTEGER,
|
|
52
|
+
status TEXT NOT NULL DEFAULT 'processing',
|
|
53
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
54
|
+
PRIMARY KEY (project_path, issue_number)
|
|
55
|
+
);
|
|
56
|
+
`);
|
|
57
|
+
migrateTable();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Config CRUD ─────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export function getConfig(projectPath: string): IssueAutofixConfig | null {
|
|
63
|
+
ensureTable();
|
|
64
|
+
const row = db().prepare('SELECT * FROM issue_autofix_config WHERE project_path = ?').get(projectPath) as any;
|
|
65
|
+
if (!row) return null;
|
|
66
|
+
return {
|
|
67
|
+
projectPath: row.project_path,
|
|
68
|
+
projectName: row.project_name,
|
|
69
|
+
enabled: !!row.enabled,
|
|
70
|
+
interval: row.interval_min,
|
|
71
|
+
labels: JSON.parse(row.labels || '[]'),
|
|
72
|
+
baseBranch: row.base_branch || '',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function saveConfig(config: IssueAutofixConfig): void {
|
|
77
|
+
ensureTable();
|
|
78
|
+
db().prepare(`
|
|
79
|
+
INSERT OR REPLACE INTO issue_autofix_config (project_path, project_name, enabled, interval_min, labels, base_branch)
|
|
80
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
81
|
+
`).run(config.projectPath, config.projectName, config.enabled ? 1 : 0, config.interval, JSON.stringify(config.labels), config.baseBranch);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function listConfigs(): IssueAutofixConfig[] {
|
|
85
|
+
ensureTable();
|
|
86
|
+
return (db().prepare('SELECT * FROM issue_autofix_config WHERE enabled = 1').all() as any[]).map(row => ({
|
|
87
|
+
projectPath: row.project_path,
|
|
88
|
+
projectName: row.project_name,
|
|
89
|
+
enabled: !!row.enabled,
|
|
90
|
+
interval: row.interval_min,
|
|
91
|
+
labels: JSON.parse(row.labels || '[]'),
|
|
92
|
+
baseBranch: row.base_branch || '',
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Issue scanning ──────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function getRepoFromProject(projectPath: string): string | null {
|
|
99
|
+
try {
|
|
100
|
+
return execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {
|
|
101
|
+
cwd: projectPath, encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
102
|
+
}).trim() || null;
|
|
103
|
+
} catch {
|
|
104
|
+
try {
|
|
105
|
+
const url = execSync('git remote get-url origin', {
|
|
106
|
+
cwd: projectPath, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
}).trim();
|
|
108
|
+
return url.replace(/.*github\.com[:/]/, '').replace(/\.git$/, '') || null;
|
|
109
|
+
} catch { return null; }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fetchOpenIssues(projectPath: string, labels: string[]): { number: number; title: string; error?: string }[] {
|
|
114
|
+
const repo = getRepoFromProject(projectPath);
|
|
115
|
+
if (!repo) return [{ number: -1, title: '', error: 'Could not detect GitHub repo. Run: gh auth login' }];
|
|
116
|
+
try {
|
|
117
|
+
const labelFilter = labels.length > 0 ? ` --label "${labels.join(',')}"` : '';
|
|
118
|
+
const out = execSync(`gh issue list --state open --json number,title${labelFilter} -R ${repo}`, {
|
|
119
|
+
cwd: projectPath, encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
|
+
});
|
|
121
|
+
return JSON.parse(out) || [];
|
|
122
|
+
} catch (e: any) {
|
|
123
|
+
const msg = e.stderr?.toString() || e.message || 'gh CLI failed';
|
|
124
|
+
return [{ number: -1, title: '', error: msg.includes('auth') ? 'GitHub CLI not authenticated. Run: gh auth login' : msg }];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isProcessed(projectPath: string, issueNumber: number): boolean {
|
|
129
|
+
const row = db().prepare('SELECT 1 FROM issue_autofix_processed WHERE project_path = ? AND issue_number = ?')
|
|
130
|
+
.get(projectPath, issueNumber);
|
|
131
|
+
return !!row;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function markProcessed(projectPath: string, issueNumber: number, pipelineId: string): void {
|
|
135
|
+
db().prepare(`
|
|
136
|
+
INSERT OR REPLACE INTO issue_autofix_processed (project_path, issue_number, pipeline_id, status)
|
|
137
|
+
VALUES (?, ?, ?, 'processing')
|
|
138
|
+
`).run(projectPath, issueNumber, pipelineId);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function updateProcessedStatus(projectPath: string, issueNumber: number, status: string, prNumber?: number): void {
|
|
142
|
+
if (prNumber) {
|
|
143
|
+
db().prepare('UPDATE issue_autofix_processed SET status = ?, pr_number = ? WHERE project_path = ? AND issue_number = ?')
|
|
144
|
+
.run(status, prNumber, projectPath, issueNumber);
|
|
145
|
+
} else {
|
|
146
|
+
db().prepare('UPDATE issue_autofix_processed SET status = ? WHERE project_path = ? AND issue_number = ?')
|
|
147
|
+
.run(status, projectPath, issueNumber);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Reset a processed issue so it can be re-scanned or retried */
|
|
152
|
+
export function resetProcessedIssue(projectPath: string, issueNumber: number): void {
|
|
153
|
+
db().prepare('DELETE FROM issue_autofix_processed WHERE project_path = ? AND issue_number = ?')
|
|
154
|
+
.run(projectPath, issueNumber);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getProcessedIssues(projectPath: string): { issueNumber: number; pipelineId: string; prNumber: number | null; status: string; createdAt: string }[] {
|
|
158
|
+
ensureTable();
|
|
159
|
+
return (db().prepare('SELECT * FROM issue_autofix_processed WHERE project_path = ? ORDER BY created_at DESC').all(projectPath) as any[]).map(r => ({
|
|
160
|
+
issueNumber: r.issue_number,
|
|
161
|
+
pipelineId: r.pipeline_id,
|
|
162
|
+
prNumber: r.pr_number,
|
|
163
|
+
status: r.status,
|
|
164
|
+
createdAt: r.created_at,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Scan & trigger ──────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
function saveLastScan(projectPath: string) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
scannerState.lastScan.set(projectPath, now);
|
|
173
|
+
try { db().prepare('UPDATE issue_autofix_config SET last_scan_at = ? WHERE project_path = ?').run(new Date(now).toISOString(), projectPath); } catch {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function loadLastScan(projectPath: string): number | null {
|
|
177
|
+
try {
|
|
178
|
+
const row = db().prepare('SELECT last_scan_at FROM issue_autofix_config WHERE project_path = ?').get(projectPath) as any;
|
|
179
|
+
return row?.last_scan_at ? new Date(row.last_scan_at).getTime() : null;
|
|
180
|
+
} catch { return null; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function scanAndTrigger(config: IssueAutofixConfig): { triggered: number; issues: number[]; total: number; error?: string } {
|
|
184
|
+
saveLastScan(config.projectPath);
|
|
185
|
+
const issues = fetchOpenIssues(config.projectPath, config.labels);
|
|
186
|
+
|
|
187
|
+
// Check for errors
|
|
188
|
+
if (issues.length === 1 && (issues[0] as any).error) {
|
|
189
|
+
return { triggered: 0, issues: [], total: 0, error: (issues[0] as any).error };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const triggered: number[] = [];
|
|
193
|
+
|
|
194
|
+
for (const issue of issues) {
|
|
195
|
+
if (issue.number < 0) continue;
|
|
196
|
+
if (isProcessed(config.projectPath, issue.number)) continue;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
200
|
+
issue_id: String(issue.number),
|
|
201
|
+
project: config.projectName,
|
|
202
|
+
base_branch: config.baseBranch || 'auto-detect',
|
|
203
|
+
});
|
|
204
|
+
markProcessed(config.projectPath, issue.number, pipeline.id);
|
|
205
|
+
triggered.push(issue.number);
|
|
206
|
+
console.log(`[issue-scanner] Triggered fix for #${issue.number} "${issue.title}" in ${config.projectName} (pipeline: ${pipeline.id})`);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error(`[issue-scanner] Failed to trigger for #${issue.number}:`, e);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { triggered: triggered.length, issues: triggered, total: issues.length };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─── Periodic scanner ────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
// Use global symbol to prevent multiple workers from starting duplicate scanners
|
|
218
|
+
const scannerKey = Symbol.for('forge-issue-scanner');
|
|
219
|
+
const gAny = globalThis as any;
|
|
220
|
+
if (!gAny[scannerKey]) gAny[scannerKey] = { timers: new Map<string, NodeJS.Timeout>(), started: false, lastScan: new Map<string, number>() };
|
|
221
|
+
const scannerState = gAny[scannerKey] as { timers: Map<string, NodeJS.Timeout>; started: boolean; lastScan: Map<string, number> };
|
|
222
|
+
|
|
223
|
+
export function startScanner() {
|
|
224
|
+
if (scannerState.started) return;
|
|
225
|
+
scannerState.started = true;
|
|
226
|
+
ensureTable();
|
|
227
|
+
const configs = listConfigs();
|
|
228
|
+
for (const config of configs) {
|
|
229
|
+
if (config.interval > 0 && !scannerState.timers.has(config.projectPath)) {
|
|
230
|
+
const projectPath = config.projectPath;
|
|
231
|
+
const intervalMs = config.interval * 60 * 1000;
|
|
232
|
+
|
|
233
|
+
// Restore lastScan from DB
|
|
234
|
+
const dbLastScan = loadLastScan(projectPath);
|
|
235
|
+
if (dbLastScan) {
|
|
236
|
+
scannerState.lastScan.set(projectPath, dbLastScan);
|
|
237
|
+
} else {
|
|
238
|
+
scannerState.lastScan.set(projectPath, Date.now());
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Calculate delay: if time since lastScan > interval, run soon; otherwise wait remaining
|
|
242
|
+
const elapsed = Date.now() - (scannerState.lastScan.get(projectPath) || 0);
|
|
243
|
+
const firstDelay = Math.max(0, intervalMs - elapsed);
|
|
244
|
+
|
|
245
|
+
const startInterval = () => {
|
|
246
|
+
const timer = setInterval(() => {
|
|
247
|
+
const latestConfig = getConfig(projectPath);
|
|
248
|
+
if (!latestConfig || !latestConfig.enabled) return;
|
|
249
|
+
try { scanAndTrigger(latestConfig); } catch {}
|
|
250
|
+
}, intervalMs);
|
|
251
|
+
scannerState.timers.set(projectPath, timer);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// First run after remaining delay, then regular interval
|
|
255
|
+
if (firstDelay > 0) {
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
const latestConfig = getConfig(projectPath);
|
|
258
|
+
if (latestConfig?.enabled) { try { scanAndTrigger(latestConfig); } catch {} }
|
|
259
|
+
startInterval();
|
|
260
|
+
}, firstDelay);
|
|
261
|
+
} else {
|
|
262
|
+
// Overdue — run immediately then start interval
|
|
263
|
+
const latestConfig = getConfig(projectPath);
|
|
264
|
+
if (latestConfig?.enabled) { try { scanAndTrigger(latestConfig); } catch {} }
|
|
265
|
+
startInterval();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const nextTime = new Date((scannerState.lastScan.get(projectPath) || Date.now()) + intervalMs);
|
|
269
|
+
console.log(`[issue-scanner] Started scanner for ${config.projectName} (every ${config.interval}min, next: ${nextTime.toLocaleTimeString()})`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function getNextScanTime(projectPath: string): { lastScan: string | null; nextScan: string | null } {
|
|
275
|
+
const last = scannerState.lastScan.get(projectPath);
|
|
276
|
+
const config = getConfig(projectPath);
|
|
277
|
+
if (!last || !config || !config.enabled || config.interval <= 0) return { lastScan: null, nextScan: null };
|
|
278
|
+
const next = last + config.interval * 60 * 1000;
|
|
279
|
+
return {
|
|
280
|
+
lastScan: new Date(last).toISOString(),
|
|
281
|
+
nextScan: new Date(next).toISOString(),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function restartScanner() {
|
|
286
|
+
for (const timer of scannerState.timers.values()) clearInterval(timer);
|
|
287
|
+
scannerState.timers.clear();
|
|
288
|
+
scannerState.started = false;
|
|
289
|
+
startScanner();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function stopScanner(projectPath: string) {
|
|
293
|
+
const timer = scannerState.timers.get(projectPath);
|
|
294
|
+
if (timer) {
|
|
295
|
+
clearInterval(timer);
|
|
296
|
+
scannerState.timers.delete(projectPath);
|
|
297
|
+
}
|
|
298
|
+
}
|