@aion0/forge 0.4.3 → 0.4.5
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/RELEASE_NOTES.md +4 -4
- package/app/api/issue-scanner/route.ts +2 -2
- package/app/api/pipelines/route.ts +14 -0
- package/app/api/project-pipelines/route.ts +68 -0
- package/components/CodeViewer.tsx +11 -1
- package/components/Dashboard.tsx +10 -12
- package/components/DocsViewer.tsx +11 -1
- package/components/PipelineEditor.tsx +3 -1
- package/components/PipelineView.tsx +262 -129
- package/components/ProjectDetail.tsx +186 -233
- package/components/SessionView.tsx +9 -1
- package/components/SkillsPanel.tsx +9 -1
- package/hooks/useSidebarResize.ts +52 -0
- package/lib/help-docs/05-pipelines.md +22 -7
- package/lib/help-docs/09-issue-autofix.md +1 -1
- package/lib/init.ts +7 -1
- package/lib/issue-scanner.ts +2 -2
- package/lib/pipeline-scheduler.ts +239 -0
- package/lib/pipeline.ts +43 -87
- package/package.json +1 -1
- package/src/core/db/database.ts +24 -0
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Forge v0.4.
|
|
1
|
+
# Forge v0.4.5
|
|
2
2
|
|
|
3
3
|
Released: 2026-03-21
|
|
4
4
|
|
|
5
|
-
## Changes since v0.4.
|
|
5
|
+
## Changes since v0.4.4
|
|
6
6
|
|
|
7
7
|
### Bug Fixes
|
|
8
|
-
- fix:
|
|
8
|
+
- fix: add drag-to-resize for left sidebars across all split-panel pages (#14)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.
|
|
11
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.4...v0.4.5
|
|
@@ -61,7 +61,7 @@ export async function POST(req: Request) {
|
|
|
61
61
|
const config = getConfig(body.projectPath);
|
|
62
62
|
const projectName = config?.projectName || body.projectName;
|
|
63
63
|
try {
|
|
64
|
-
const pipeline = startPipeline('issue-
|
|
64
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
65
65
|
issue_id: String(body.issueId),
|
|
66
66
|
project: projectName,
|
|
67
67
|
base_branch: config?.baseBranch || body.baseBranch || 'auto-detect',
|
|
@@ -93,7 +93,7 @@ export async function POST(req: Request) {
|
|
|
93
93
|
// Reset the processed record first, then re-create with new pipeline
|
|
94
94
|
resetProcessedIssue(body.projectPath, body.issueId);
|
|
95
95
|
try {
|
|
96
|
-
const pipeline = startPipeline('issue-
|
|
96
|
+
const pipeline = startPipeline('issue-fix-and-review', {
|
|
97
97
|
issue_id: String(body.issueId),
|
|
98
98
|
project: projectName,
|
|
99
99
|
base_branch: config?.baseBranch || 'auto-detect',
|
|
@@ -61,6 +61,20 @@ export async function POST(req: Request) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// Delete workflow
|
|
65
|
+
if (body.action === 'delete-workflow' && body.name) {
|
|
66
|
+
const { existsSync: ex, unlinkSync: ul } = await import('node:fs');
|
|
67
|
+
const filePath = join(FLOWS_DIR, `${body.name}.yaml`);
|
|
68
|
+
const altPath = join(FLOWS_DIR, `${body.name}.yml`);
|
|
69
|
+
const path = ex(filePath) ? filePath : ex(altPath) ? altPath : null;
|
|
70
|
+
if (!path) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
71
|
+
// Check if built-in
|
|
72
|
+
const w = listWorkflows().find(w => w.name === body.name);
|
|
73
|
+
if (w?.builtin) return NextResponse.json({ error: 'Cannot delete built-in workflow' }, { status: 400 });
|
|
74
|
+
ul(path);
|
|
75
|
+
return NextResponse.json({ ok: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
// Start pipeline
|
|
65
79
|
const { workflow, input } = body;
|
|
66
80
|
if (!workflow) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getBindings,
|
|
4
|
+
addBinding,
|
|
5
|
+
removeBinding,
|
|
6
|
+
updateBinding,
|
|
7
|
+
getRuns,
|
|
8
|
+
deleteRun,
|
|
9
|
+
triggerPipeline,
|
|
10
|
+
getNextRunTime,
|
|
11
|
+
} from '@/lib/pipeline-scheduler';
|
|
12
|
+
import { listWorkflows } from '@/lib/pipeline';
|
|
13
|
+
|
|
14
|
+
// GET /api/project-pipelines?project=PATH
|
|
15
|
+
export async function GET(req: Request) {
|
|
16
|
+
const { searchParams } = new URL(req.url);
|
|
17
|
+
const projectPath = searchParams.get('project');
|
|
18
|
+
if (!projectPath) return NextResponse.json({ error: 'project required' }, { status: 400 });
|
|
19
|
+
|
|
20
|
+
const bindings = getBindings(projectPath).map(b => ({
|
|
21
|
+
...b,
|
|
22
|
+
nextRunAt: getNextRunTime(b),
|
|
23
|
+
}));
|
|
24
|
+
const runs = getRuns(projectPath);
|
|
25
|
+
const workflows = listWorkflows().map(w => ({ name: w.name, description: w.description, builtin: w.builtin }));
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({ bindings, runs, workflows });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// POST /api/project-pipelines
|
|
31
|
+
export async function POST(req: Request) {
|
|
32
|
+
const body = await req.json();
|
|
33
|
+
|
|
34
|
+
if (body.action === 'add') {
|
|
35
|
+
const { projectPath, projectName, workflowName, config } = body;
|
|
36
|
+
if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
|
|
37
|
+
addBinding(projectPath, projectName || projectPath.split('/').pop(), workflowName, config);
|
|
38
|
+
return NextResponse.json({ ok: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (body.action === 'remove') {
|
|
42
|
+
removeBinding(body.projectPath, body.workflowName);
|
|
43
|
+
return NextResponse.json({ ok: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (body.action === 'update') {
|
|
47
|
+
updateBinding(body.projectPath, body.workflowName, { enabled: body.enabled, config: body.config });
|
|
48
|
+
return NextResponse.json({ ok: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (body.action === 'trigger') {
|
|
52
|
+
const { projectPath, projectName, workflowName, input } = body;
|
|
53
|
+
if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
|
|
54
|
+
try {
|
|
55
|
+
const result = triggerPipeline(projectPath, projectName || projectPath.split('/').pop(), workflowName, input);
|
|
56
|
+
return NextResponse.json({ ok: true, ...result });
|
|
57
|
+
} catch (e: any) {
|
|
58
|
+
return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (body.action === 'delete-run') {
|
|
63
|
+
deleteRun(body.id);
|
|
64
|
+
return NextResponse.json({ ok: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
68
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
|
|
4
4
|
import type { WebTerminalHandle, WebTerminalProps } from './WebTerminal';
|
|
5
|
+
import { useSidebarResize } from '@/hooks/useSidebarResize';
|
|
5
6
|
|
|
6
7
|
const WebTerminal = lazy(() => import('./WebTerminal'));
|
|
7
8
|
|
|
@@ -196,6 +197,7 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
|
|
|
196
197
|
const [activeSession, setActiveSession] = useState<string | null>(null);
|
|
197
198
|
const [taskNotification, setTaskNotification] = useState<{ id: string; status: string; prompt: string; sessionId?: string } | null>(null);
|
|
198
199
|
const dragRef = useRef<{ startY: number; startH: number } | null>(null);
|
|
200
|
+
const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 120, maxWidth: 480 });
|
|
199
201
|
const lastDirRef = useRef<string | null>(null);
|
|
200
202
|
const lastTaskCheckRef = useRef<string>('');
|
|
201
203
|
|
|
@@ -437,7 +439,7 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
|
|
|
437
439
|
{codeOpen && <div className="flex-1 flex min-h-0 min-w-0 overflow-hidden">
|
|
438
440
|
{/* Sidebar */}
|
|
439
441
|
{sidebarOpen && (
|
|
440
|
-
<aside
|
|
442
|
+
<aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
|
|
441
443
|
{/* Directory name + git */}
|
|
442
444
|
<div className="px-3 py-2 border-b border-[var(--border)]">
|
|
443
445
|
<div className="flex items-center gap-2">
|
|
@@ -611,6 +613,14 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
|
|
|
611
613
|
</aside>
|
|
612
614
|
)}
|
|
613
615
|
|
|
616
|
+
{/* Sidebar resize handle */}
|
|
617
|
+
{sidebarOpen && (
|
|
618
|
+
<div
|
|
619
|
+
onMouseDown={onSidebarDragStart}
|
|
620
|
+
className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
|
|
621
|
+
/>
|
|
622
|
+
)}
|
|
623
|
+
|
|
614
624
|
{/* Code viewer */}
|
|
615
625
|
<main className="flex-1 flex flex-col min-w-0 overflow-hidden" style={{ width: 0 }}>
|
|
616
626
|
<div className="px-3 py-1.5 border-b border-[var(--border)] shrink-0 flex items-center gap-2">
|
package/components/Dashboard.tsx
CHANGED
|
@@ -137,18 +137,16 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
137
137
|
}, []);
|
|
138
138
|
|
|
139
139
|
const fetchData = useCallback(async () => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
setUsage(statusData.usage);
|
|
151
|
-
setProjects(projectsData);
|
|
140
|
+
try {
|
|
141
|
+
const [tasksRes, statusRes, projectsRes] = await Promise.all([
|
|
142
|
+
fetch('/api/tasks'),
|
|
143
|
+
fetch('/api/status'),
|
|
144
|
+
fetch('/api/projects'),
|
|
145
|
+
]);
|
|
146
|
+
if (tasksRes.ok) setTasks(await tasksRes.json());
|
|
147
|
+
if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
|
|
148
|
+
if (projectsRes.ok) setProjects(await projectsRes.json());
|
|
149
|
+
} catch {}
|
|
152
150
|
}, []);
|
|
153
151
|
|
|
154
152
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
|
|
4
|
+
import { useSidebarResize } from '@/hooks/useSidebarResize';
|
|
4
5
|
import MarkdownContent from './MarkdownContent';
|
|
5
6
|
import TabBar from './TabBar';
|
|
6
7
|
|
|
@@ -100,6 +101,7 @@ export default function DocsViewer() {
|
|
|
100
101
|
const [editContent, setEditContent] = useState('');
|
|
101
102
|
const [saving, setSaving] = useState(false);
|
|
102
103
|
const dragRef = useRef<{ startY: number; startH: number } | null>(null);
|
|
104
|
+
const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 120, maxWidth: 480 });
|
|
103
105
|
|
|
104
106
|
// Doc tabs
|
|
105
107
|
const [docTabs, setDocTabs] = useState<DocTab[]>([]);
|
|
@@ -324,7 +326,7 @@ export default function DocsViewer() {
|
|
|
324
326
|
<div className="flex-1 flex min-h-0">
|
|
325
327
|
{/* Collapsible sidebar — file tree */}
|
|
326
328
|
{sidebarOpen && (
|
|
327
|
-
<aside
|
|
329
|
+
<aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
|
|
328
330
|
{/* Root selector */}
|
|
329
331
|
{roots.length > 0 && (
|
|
330
332
|
<div className="p-2 border-b border-[var(--border)]">
|
|
@@ -390,6 +392,14 @@ export default function DocsViewer() {
|
|
|
390
392
|
</aside>
|
|
391
393
|
)}
|
|
392
394
|
|
|
395
|
+
{/* Sidebar resize handle */}
|
|
396
|
+
{sidebarOpen && (
|
|
397
|
+
<div
|
|
398
|
+
onMouseDown={onSidebarDragStart}
|
|
399
|
+
className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
|
|
400
|
+
/>
|
|
401
|
+
)}
|
|
402
|
+
|
|
393
403
|
{/* Main content */}
|
|
394
404
|
<main className="flex-1 flex flex-col min-w-0">
|
|
395
405
|
{/* Doc tab bar */}
|
|
@@ -360,7 +360,9 @@ export default function PipelineEditor({ onSave, onClose, initialYaml }: {
|
|
|
360
360
|
Save
|
|
361
361
|
</button>
|
|
362
362
|
<button
|
|
363
|
-
onClick={
|
|
363
|
+
onClick={() => {
|
|
364
|
+
if (confirm('Discard unsaved changes?')) onClose();
|
|
365
|
+
}}
|
|
364
366
|
className="text-xs px-3 py-1 text-gray-400 hover:text-white"
|
|
365
367
|
>
|
|
366
368
|
Cancel
|