@aion0/forge 0.4.4 → 0.4.6

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 CHANGED
@@ -1,28 +1,12 @@
1
- # Forge v0.4.4
1
+ # Forge v0.4.6
2
2
 
3
3
  Released: 2026-03-21
4
4
 
5
- ## Changes since v0.4.3
6
-
7
- ### Features
8
- - feat: add scheduled pipeline execution for project bindings
9
- - feat: pipeline import/delete + editor cancel confirmation
10
-
11
- ### Bug Fixes
12
- - fix: embed editor in right panel, button nesting, fetchData error handling
13
-
14
- ### Refactoring
15
- - refactor: generic project-pipeline bindings replace hardcoded issue scanner
16
- - refactor: pipeline UI — workflow list in sidebar, project dropdown, per-workflow history
17
- - refactor: merge issue-auto-fix + pr-review into single issue-fix-and-review pipeline
18
-
19
- ### Documentation
20
- - feat: pipeline import/delete + editor cancel confirmation
5
+ ## Changes since v0.4.5
21
6
 
22
7
  ### Other
23
- - fix issue
24
- - fix issue
25
- - fix issues for no any issue on github
8
+ - improve help features
9
+ - imporve help features
26
10
 
27
11
 
28
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.3...v0.4.4
12
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.5...v0.4.6
@@ -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 className="w-56 border-r border-[var(--border)] flex flex-col shrink-0">
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">
@@ -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 className="w-56 border-r border-[var(--border)] flex flex-col shrink-0">
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 */}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  const PipelineEditor = lazy(() => import('./PipelineEditor'));
6
7
 
@@ -64,6 +65,7 @@ const STATUS_COLOR: Record<string, string> = {
64
65
  };
65
66
 
66
67
  export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: string) => void }) {
68
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 256, minWidth: 140, maxWidth: 480 });
67
69
  const [pipelines, setPipelines] = useState<Pipeline[]>([]);
68
70
  const [workflows, setWorkflows] = useState<Workflow[]>([]);
69
71
  const [selectedPipeline, setSelectedPipeline] = useState<Pipeline | null>(null);
@@ -158,7 +160,7 @@ export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: str
158
160
  return (
159
161
  <div className="flex-1 flex min-h-0">
160
162
  {/* Left — Workflow list */}
161
- <aside className="w-64 border-r border-[var(--border)] flex flex-col shrink-0">
163
+ <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
162
164
  <div className="px-3 py-2 border-b border-[var(--border)] flex items-center gap-1.5">
163
165
  <span className="text-[11px] font-semibold text-[var(--text-primary)] flex-1">Workflows</span>
164
166
  <button
@@ -369,6 +371,12 @@ export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: str
369
371
  </div>
370
372
  </aside>
371
373
 
374
+ {/* Sidebar resize handle */}
375
+ <div
376
+ onMouseDown={onSidebarDragStart}
377
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
378
+ />
379
+
372
380
  {/* Right — Pipeline detail / Editor */}
373
381
  <main className="flex-1 flex flex-col min-w-0 overflow-hidden">
374
382
  {showEditor ? (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState, useEffect, useCallback, memo } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  // ─── Syntax highlighting ─────────────────────────────────
6
7
  const KEYWORDS = new Set([
@@ -49,6 +50,7 @@ interface GitInfo {
49
50
  }
50
51
 
51
52
  export default memo(function ProjectDetail({ projectPath, projectName, hasGit }: { projectPath: string; projectName: string; hasGit: boolean }) {
53
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 208, minWidth: 120, maxWidth: 400 });
52
54
  const [gitInfo, setGitInfo] = useState<GitInfo | null>(null);
53
55
  const [loading, setLoading] = useState(false);
54
56
  const [commitMsg, setCommitMsg] = useState('');
@@ -490,12 +492,18 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
490
492
  {/* Code content area */}
491
493
  {projectTab === 'code' && <div className="flex-1 flex min-h-0 overflow-hidden">
492
494
  {/* File tree */}
493
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto p-1 shrink-0">
495
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto p-1 shrink-0">
494
496
  {fileTree.map((node: any) => (
495
497
  <FileTreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFile} />
496
498
  ))}
497
499
  </div>
498
500
 
501
+ {/* Sidebar resize handle */}
502
+ <div
503
+ onMouseDown={onSidebarDragStart}
504
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
505
+ />
506
+
499
507
  {/* File content */}
500
508
  <div className="flex-1 min-w-0 overflow-auto bg-[var(--bg-primary)]" style={{ width: 0 }}>
501
509
  {/* Diff view */}
@@ -543,7 +551,7 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
543
551
  {projectTab === 'skills' && (
544
552
  <div className="flex-1 flex min-h-0 overflow-hidden">
545
553
  {/* Left: skill/command tree */}
546
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto p-1 shrink-0">
554
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto p-1 shrink-0">
547
555
  {projectSkills.length === 0 ? (
548
556
  <p className="text-[9px] text-[var(--text-secondary)] p-2">No skills or commands installed</p>
549
557
  ) : (
@@ -591,6 +599,12 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
591
599
  )}
592
600
  </div>
593
601
 
602
+ {/* Sidebar resize handle */}
603
+ <div
604
+ onMouseDown={onSidebarDragStart}
605
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
606
+ />
607
+
594
608
  {/* Right: file content / editor */}
595
609
  <div className="flex-1 min-w-0 flex flex-col overflow-hidden bg-[var(--bg-primary)]">
596
610
  {skillActivePath ? (
@@ -658,7 +672,7 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
658
672
  {projectTab === 'claudemd' && (
659
673
  <div className="flex-1 flex min-h-0 overflow-hidden">
660
674
  {/* Left: templates list */}
661
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto shrink-0 flex flex-col">
675
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto shrink-0 flex flex-col">
662
676
  <button
663
677
  onClick={() => { setClaudeSelectedTemplate(null); setClaudeEditing(false); }}
664
678
  className={`w-full px-2 py-1.5 border-b border-[var(--border)] text-[10px] text-left flex items-center gap-1 ${
@@ -703,6 +717,12 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
703
717
  </div>
704
718
  </div>
705
719
 
720
+ {/* Sidebar resize handle */}
721
+ <div
722
+ onMouseDown={onSidebarDragStart}
723
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
724
+ />
725
+
706
726
  {/* Right: CLAUDE.md content or template preview */}
707
727
  <div className="flex-1 min-w-0 flex flex-col overflow-hidden bg-[var(--bg-primary)]">
708
728
  {/* Header bar */}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
  import MarkdownContent from './MarkdownContent';
5
6
 
6
7
  interface SessionEntry {
@@ -42,6 +43,7 @@ export default function SessionView({
42
43
  projects: { name: string; path: string; language: string | null }[];
43
44
  onOpenInTerminal?: (sessionId: string, projectPath: string) => void;
44
45
  }) {
46
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 288, minWidth: 160, maxWidth: 480 });
45
47
  // Tree data: project → sessions
46
48
  const [sessionTree, setSessionTree] = useState<Record<string, ClaudeSessionInfo[]>>({});
47
49
  const [expandedProjects, setExpandedProjects] = useState<Set<string>>(new Set());
@@ -285,7 +287,7 @@ export default function SessionView({
285
287
  return (
286
288
  <div className="flex h-full">
287
289
  {/* Left: tree view */}
288
- <div className="w-72 border-r border-[var(--border)] flex flex-col shrink-0">
290
+ <div style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
289
291
  {/* Header */}
290
292
  <div className="flex items-center justify-between p-2 border-b border-[var(--border)]">
291
293
  <span className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase">Sessions</span>
@@ -451,6 +453,12 @@ export default function SessionView({
451
453
  </div>
452
454
  </div>
453
455
 
456
+ {/* Sidebar resize handle */}
457
+ <div
458
+ onMouseDown={onSidebarDragStart}
459
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
460
+ />
461
+
454
462
  {/* Right: session content */}
455
463
  <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
456
464
  {activeSession && (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  type ItemType = 'skill' | 'command';
6
7
 
@@ -28,6 +29,7 @@ interface ProjectInfo {
28
29
  }
29
30
 
30
31
  export default function SkillsPanel({ projectFilter }: { projectFilter?: string }) {
32
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 140, maxWidth: 400 });
31
33
  const [skills, setSkills] = useState<Skill[]>([]);
32
34
  const [projects, setProjects] = useState<ProjectInfo[]>([]);
33
35
  const [syncing, setSyncing] = useState(false);
@@ -282,7 +284,7 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
282
284
  ) : (
283
285
  <div className="flex-1 flex min-h-0">
284
286
  {/* Left: skill list */}
285
- <div className="w-56 border-r border-[var(--border)] overflow-y-auto shrink-0">
287
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto shrink-0">
286
288
  {/* Registry items */}
287
289
  {filtered.map(skill => {
288
290
  const isInstalled = skill.installedGlobal || skill.installedProjects.length > 0;
@@ -401,6 +403,12 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
401
403
  )}
402
404
  </div>
403
405
 
406
+ {/* Sidebar resize handle */}
407
+ <div
408
+ onMouseDown={onSidebarDragStart}
409
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
410
+ />
411
+
404
412
  {/* Right: detail panel */}
405
413
  <div className="flex-1 flex flex-col min-w-0">
406
414
  {expandedSkill ? (() => {
@@ -0,0 +1,52 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+
3
+ interface UseSidebarResizeOptions {
4
+ defaultWidth?: number;
5
+ minWidth?: number;
6
+ maxWidth?: number;
7
+ }
8
+
9
+ /**
10
+ * Provides drag-to-resize behaviour for a vertical split panel sidebar.
11
+ *
12
+ * Usage:
13
+ * const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224 });
14
+ *
15
+ * <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">…</aside>
16
+ * <div onMouseDown={onSidebarDragStart} className="w-1 cursor-col-resize shrink-0 bg-[var(--border)] hover:bg-[var(--accent)]/50" />
17
+ * <main className="flex-1 min-w-0">…</main>
18
+ */
19
+ export function useSidebarResize({
20
+ defaultWidth = 224,
21
+ minWidth = 120,
22
+ maxWidth = 480,
23
+ }: UseSidebarResizeOptions = {}) {
24
+ const [sidebarWidth, setSidebarWidth] = useState(defaultWidth);
25
+ // Track the in-progress drag without causing re-renders in the move handler
26
+ const dragRef = useRef<{ startX: number; startW: number } | null>(null);
27
+ // Keep a mutable copy so the stable onSidebarDragStart callback always reads the latest width
28
+ const widthRef = useRef(defaultWidth);
29
+
30
+ const onSidebarDragStart = useCallback((e: React.MouseEvent) => {
31
+ e.preventDefault();
32
+ dragRef.current = { startX: e.clientX, startW: widthRef.current };
33
+
34
+ const onMove = (ev: MouseEvent) => {
35
+ if (!dragRef.current) return;
36
+ const next = Math.max(minWidth, Math.min(maxWidth, dragRef.current.startW + ev.clientX - dragRef.current.startX));
37
+ widthRef.current = next;
38
+ setSidebarWidth(next);
39
+ };
40
+
41
+ const onUp = () => {
42
+ dragRef.current = null;
43
+ window.removeEventListener('mousemove', onMove);
44
+ window.removeEventListener('mouseup', onUp);
45
+ };
46
+
47
+ window.addEventListener('mousemove', onMove);
48
+ window.addEventListener('mouseup', onUp);
49
+ }, [minWidth, maxWidth]);
50
+
51
+ return { sidebarWidth, onSidebarDragStart };
52
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What Are Pipelines?
4
4
 
5
- Pipelines chain multiple tasks into a DAG (directed acyclic graph). Each step can depend on previous steps, pass outputs forward, and run in parallel.
5
+ Pipelines chain multiple tasks into a DAG (directed acyclic graph). Each step can depend on previous steps, pass outputs forward, and run in parallel. Pipelines are defined as YAML workflow files.
6
6
 
7
7
  ## YAML Workflow Format
8
8
 
@@ -10,9 +10,10 @@ Pipelines chain multiple tasks into a DAG (directed acyclic graph). Each step ca
10
10
  name: my-workflow
11
11
  description: "What this workflow does"
12
12
  input:
13
- feature: "Feature description"
13
+ feature: "Feature description" # required input fields
14
+ priority: "Priority level (optional)"
14
15
  vars:
15
- project: my-app
16
+ project: my-app # default variables
16
17
  nodes:
17
18
  design:
18
19
  project: "{{vars.project}}"
@@ -23,7 +24,10 @@ nodes:
23
24
  implement:
24
25
  project: "{{vars.project}}"
25
26
  depends_on: [design]
26
- prompt: "Implement: {{nodes.design.outputs.spec}}"
27
+ prompt: "Implement based on: {{nodes.design.outputs.spec}}"
28
+ outputs:
29
+ - name: diff
30
+ extract: git_diff
27
31
  review:
28
32
  project: "{{vars.project}}"
29
33
  depends_on: [implement]
@@ -32,30 +36,252 @@ nodes:
32
36
 
33
37
  ## Node Options
34
38
 
35
- | Field | Description |
36
- |-------|-------------|
37
- | `project` | Project name (supports `{{vars.xxx}}` templates) |
38
- | `prompt` | Claude Code prompt or shell command |
39
- | `mode` | `claude` (default) or `shell` |
40
- | `branch` | Auto-checkout branch before running |
41
- | `depends_on` | List of node IDs that must complete first |
42
- | `outputs` | Extract results: `result`, `git_diff`, or `stdout` |
43
- | `routes` | Conditional routing to next nodes |
39
+ | Field | Description | Default |
40
+ |-------|-------------|---------|
41
+ | `project` | Project name (supports templates) | required |
42
+ | `prompt` | Claude Code prompt or shell command | required |
43
+ | `mode` | `claude` (AI agent) or `shell` (raw command) | `claude` |
44
+ | `branch` | Auto-checkout branch before running (supports templates) | none |
45
+ | `depends_on` | List of node IDs that must complete first | `[]` |
46
+ | `outputs` | Extract results (see Output Extraction) | `[]` |
47
+ | `routes` | Conditional routing to next nodes (see Routing) | `[]` |
48
+ | `max_iterations` | Max loop iterations for routed nodes | `3` |
49
+
50
+ ## Node Modes
51
+
52
+ ### `claude` (default)
53
+ Runs the prompt via Claude Code (`claude -p`). The AI agent reads the codebase, makes changes, and returns a result.
54
+
55
+ ### `shell`
56
+ Runs the prompt as a raw shell command (`bash -c "..."`). Useful for git operations, CLI tools, API calls, etc.
57
+
58
+ ```yaml
59
+ nodes:
60
+ setup:
61
+ mode: shell
62
+ project: my-app
63
+ prompt: |
64
+ git checkout main && git pull && echo "READY"
65
+ outputs:
66
+ - name: info
67
+ extract: stdout
68
+ ```
69
+
70
+ **Shell escaping**: Template values in shell mode are automatically escaped (single quotes `'` → `'\''`) to prevent injection.
44
71
 
45
72
  ## Template Variables
46
73
 
47
- - `{{input.xxx}}` pipeline input values
48
- - `{{vars.xxx}}` — workflow variables
49
- - `{{nodes.xxx.outputs.yyy}}` — outputs from previous nodes
74
+ Templates use `{{...}}` syntax and are resolved before execution:
75
+
76
+ - `{{input.xxx}}` — pipeline input values provided at trigger time
77
+ - `{{vars.xxx}}` — workflow-level variables defined in YAML
78
+ - `{{nodes.<node_id>.outputs.<output_name>}}` — outputs from completed nodes
79
+
80
+ Node IDs can contain hyphens (e.g., `{{nodes.fetch-issue.outputs.data}}`).
81
+
82
+ ### Examples
83
+
84
+ ```yaml
85
+ prompt: "Fix issue #{{input.issue_id}} in {{input.project}}"
86
+ prompt: "Based on: {{nodes.design.outputs.spec}}"
87
+ prompt: |
88
+ REPO={{nodes.setup.outputs.repo}} && \
89
+ gh pr create --title "Fix #{{input.issue_id}}" -R "$REPO"
90
+ ```
91
+
92
+ ## Output Extraction
93
+
94
+ Each node can extract outputs for downstream nodes:
95
+
96
+ | Extract Type | Description |
97
+ |-------------|-------------|
98
+ | `result` | Claude's final response text |
99
+ | `stdout` | Shell command stdout (same as result for shell mode) |
100
+ | `git_diff` | Git diff of changes made during the task |
101
+
102
+ ```yaml
103
+ outputs:
104
+ - name: summary
105
+ extract: result
106
+ - name: changes
107
+ extract: git_diff
108
+ ```
109
+
110
+ ## Skip Convention (`__SKIP__`)
111
+
112
+ If a shell node outputs `__SKIP__` in its stdout and exits with code 0, the node is marked as `skipped` instead of `done`. All downstream dependent nodes are also skipped. The pipeline completes successfully (not failed).
113
+
114
+ ```yaml
115
+ nodes:
116
+ check:
117
+ mode: shell
118
+ project: my-app
119
+ prompt: |
120
+ if [ -z "{{input.issue_id}}" ]; then
121
+ echo "__SKIP__ No issue_id provided"
122
+ exit 0
123
+ fi
124
+ echo "Processing issue {{input.issue_id}}"
125
+ ```
126
+
127
+ Use this for optional steps that should gracefully skip when preconditions aren't met.
128
+
129
+ ## Conditional Routing
130
+
131
+ Nodes can route to different next steps based on output content:
132
+
133
+ ```yaml
134
+ nodes:
135
+ analyze:
136
+ project: my-app
137
+ prompt: "Analyze the issue. Reply SIMPLE or COMPLEX."
138
+ outputs:
139
+ - name: complexity
140
+ extract: result
141
+ routes:
142
+ - condition: "{{outputs.complexity contains 'SIMPLE'}}"
143
+ next: quick-fix
144
+ - condition: default
145
+ next: deep-fix
146
+ quick-fix:
147
+ depends_on: [analyze]
148
+ project: my-app
149
+ prompt: "Apply a quick fix"
150
+ deep-fix:
151
+ depends_on: [analyze]
152
+ project: my-app
153
+ prompt: "Do a thorough analysis and fix"
154
+ ```
155
+
156
+ ### Route Conditions
157
+
158
+ - `{{outputs.<name> contains '<keyword>'}}` — check if output contains a keyword
159
+ - `default` — fallback route (always matches)
160
+
161
+ ### Loops
162
+
163
+ If a route points back to the same node, it creates a loop (up to `max_iterations`):
164
+
165
+ ```yaml
166
+ nodes:
167
+ fix-and-test:
168
+ project: my-app
169
+ prompt: "Fix the failing test, then run tests."
170
+ max_iterations: 5
171
+ outputs:
172
+ - name: test_result
173
+ extract: result
174
+ routes:
175
+ - condition: "{{outputs.test_result contains 'PASS'}}"
176
+ next: done
177
+ - condition: default
178
+ next: fix-and-test # loop back to retry
179
+ done:
180
+ depends_on: [fix-and-test]
181
+ mode: shell
182
+ project: my-app
183
+ prompt: "echo 'All tests passing!'"
184
+ ```
185
+
186
+ ## Branch Auto-checkout
187
+
188
+ Nodes can auto-checkout a git branch before execution:
189
+
190
+ ```yaml
191
+ nodes:
192
+ work:
193
+ project: my-app
194
+ branch: "feature/{{input.feature_name}}"
195
+ prompt: "Implement the feature"
196
+ ```
197
+
198
+ ## Parallel Execution
199
+
200
+ Nodes without dependency relationships run in parallel:
201
+
202
+ ```yaml
203
+ nodes:
204
+ frontend:
205
+ project: my-app
206
+ prompt: "Build frontend component"
207
+ backend:
208
+ project: my-app
209
+ prompt: "Build API endpoint"
210
+ integration:
211
+ depends_on: [frontend, backend] # waits for both
212
+ project: my-app
213
+ prompt: "Integration test"
214
+ ```
215
+
216
+ `frontend` and `backend` run simultaneously; `integration` starts when both finish.
50
217
 
51
218
  ## Built-in Workflows
52
219
 
53
- ### issue-fix-and-review
54
- Complete issue resolution pipeline: fetch issue → fix code create PRreview code → notify.
220
+ ### issue-auto-fix
221
+ Complete issue resolution: fetch GitHub issue → fix code on new branchcreate PR.
222
+
223
+ **Input**: `issue_id`, `project`, `base_branch` (optional), `extra_context` (optional)
224
+
225
+ **Steps**: setup → fetch-issue → fix-code → push-and-pr → notify
226
+
227
+ **Prerequisites**: `gh` CLI installed and authenticated (`gh auth login`), project has GitHub remote.
228
+
229
+ ### pr-review
230
+ Review a pull request: fetch PR diff → AI review → report.
231
+
232
+ **Input**: `pr_number`, `project`
233
+
234
+ **Steps**: setup → fetch-pr → review → post-review
55
235
 
56
- Steps: setup fetch-issue → fix-code → push-and-pr → review → cleanup
236
+ ## Project Pipeline Bindings
57
237
 
58
- Input: `issue_id`, `project`, `base_branch` (optional), `extra_context` (optional)
238
+ Projects can bind workflows for easy access and scheduled execution.
239
+
240
+ ### Binding a Workflow to a Project
241
+
242
+ 1. Go to **Projects → select project → Pipelines tab**
243
+ 2. Click **+ Add** to attach a workflow
244
+ 3. Configure:
245
+ - **Enabled**: toggle on/off
246
+ - **Schedule**: Manual only, or periodic (15min to 24h intervals)
247
+ 4. Click **Run** to manually trigger
248
+
249
+ ### Scheduled Execution
250
+
251
+ When a schedule is set (e.g., "Every 30 min"):
252
+ - The scheduler checks all bindings every 60 seconds
253
+ - If the interval has elapsed since last run, the pipeline triggers automatically
254
+ - Running pipelines are not re-triggered (prevents overlap)
255
+ - `Last run` and `Next run` times are shown in the UI
256
+
257
+ Schedule options: Manual only, 15min, 30min, 1h, 2h, 6h, 12h, 24h.
258
+
259
+ ### API
260
+
261
+ ```bash
262
+ # List bindings + runs + workflows for a project
263
+ curl "http://localhost:3000/api/project-pipelines?project=/path/to/project"
264
+
265
+ # Add binding
266
+ curl -X POST http://localhost:3000/api/project-pipelines \
267
+ -H 'Content-Type: application/json' \
268
+ -d '{"action":"add","projectPath":"/path","projectName":"my-app","workflowName":"issue-auto-fix"}'
269
+
270
+ # Update binding (enable/disable, change config/schedule)
271
+ curl -X POST http://localhost:3000/api/project-pipelines \
272
+ -H 'Content-Type: application/json' \
273
+ -d '{"action":"update","projectPath":"/path","workflowName":"issue-auto-fix","config":{"interval":30}}'
274
+
275
+ # Trigger pipeline manually
276
+ curl -X POST http://localhost:3000/api/project-pipelines \
277
+ -H 'Content-Type: application/json' \
278
+ -d '{"action":"trigger","projectPath":"/path","projectName":"my-app","workflowName":"issue-auto-fix","input":{"issue_id":"42"}}'
279
+
280
+ # Remove binding
281
+ curl -X POST http://localhost:3000/api/project-pipelines \
282
+ -H 'Content-Type: application/json' \
283
+ -d '{"action":"remove","projectPath":"/path","workflowName":"issue-auto-fix"}'
284
+ ```
59
285
 
60
286
  ## CLI
61
287
 
@@ -66,7 +292,7 @@ forge run my-workflow # execute a workflow
66
292
 
67
293
  ## Import a Workflow
68
294
 
69
- 1. In Pipelines tab, click **Import**
295
+ 1. In **Pipelines** tab, click **Import**
70
296
  2. Paste YAML workflow content
71
297
  3. Click **Save Workflow**
72
298
 
@@ -79,10 +305,25 @@ To create a workflow via Help AI: ask "Create a pipeline that does X" — the AI
79
305
  ```bash
80
306
  curl -X POST http://localhost:3000/api/pipelines \
81
307
  -H 'Content-Type: application/json' \
82
- -d '{"action": "save-workflow", "yaml": "name: my-flow\nnodes:\n step1:\n project: my-project\n prompt: do something"}'
308
+ -d '{"action": "save-workflow", "yaml": "<yaml content>"}'
83
309
  ```
84
310
 
311
+ ## Pipeline Model
312
+
313
+ In **Settings → Pipeline Model**, you can select which Claude model runs pipeline tasks. Set to `default` to use the same model as regular tasks.
314
+
85
315
  ## Storage
86
316
 
87
317
  - Workflow YAML: `~/.forge/data/flows/`
88
318
  - Execution state: `~/.forge/data/pipelines/`
319
+ - Binding config & run history: SQLite database (`~/.forge/data/forge.db`)
320
+
321
+ ## Tips for Writing Workflows
322
+
323
+ 1. **Start with shell nodes** for setup (git checkout, environment checks)
324
+ 2. **Use `__SKIP__`** for optional steps with precondition checks
325
+ 3. **Extract outputs** to pass data between nodes
326
+ 4. **Use routes** for conditional logic (simple/complex paths, retry loops)
327
+ 5. **Keep prompts focused** — each node should do one thing well
328
+ 6. **Test manually first** before setting up schedules
329
+ 7. **Use `depends_on`** to control execution order; nodes without dependencies run in parallel
@@ -2,50 +2,50 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- Automatically scan GitHub Issues, fix code, create PRs, and review — all hands-free.
5
+ Automatically scan GitHub Issues, fix code, create PRs — all hands-free. Uses the built-in `issue-auto-fix` pipeline workflow.
6
6
 
7
7
  ## Prerequisites
8
8
 
9
9
  - `gh` CLI installed and authenticated: `gh auth login`
10
10
  - Project has a GitHub remote
11
11
 
12
- ## Setup
12
+ ## Setup (via Project Pipeline Binding)
13
13
 
14
- 1. Go to **Projects → select project → Issues tab**
15
- 2. Enable **Issue Auto-fix**
16
- 3. Configure:
17
- - **Scan Interval**: minutes between scans (0 = manual only)
18
- - **Base Branch**: leave empty for auto-detect (main/master)
19
- - **Labels Filter**: comma-separated labels (empty = all issues)
20
- 4. Click **Scan Now** to test
14
+ 1. Go to **Projects → select project → Pipelines tab**
15
+ 2. Click **+ Add** and select `issue-auto-fix`
16
+ 3. Enable the binding
17
+ 4. Set a **Schedule** (e.g., Every 30 min) for automatic scanning, or leave as "Manual only"
18
+ 5. Click **Run** to manually trigger with an `issue_id`
21
19
 
22
20
  ## Flow
23
21
 
24
22
  ```
25
- Scan → Fetch Issue → Fix Code (new branch) → Push Create PR → Auto Review → Notify
23
+ Setup → Fetch Issue → Fix Code (new branch) → Push & Create PR → Notify
26
24
  ```
27
25
 
28
- 1. **Scan**: `gh issue list` finds open issues matching labels
29
- 2. **Fix**: Claude Code analyzes issue and fixes code on `fix/<id>-<description>` branch
30
- 3. **PR**: Pushes branch and creates Pull Request
31
- 4. **Review**: AI reviews the code changes in the same pipeline
32
- 5. **Notify**: Results sent via Telegram (if configured)
26
+ 1. **Setup**: Checks for clean working directory, detects repo and base branch
27
+ 2. **Fetch Issue**: `gh issue view` fetches issue data (skips if no issue_id)
28
+ 3. **Fix Code**: Claude analyzes issue and fixes code on `fix/<id>-<description>` branch
29
+ 4. **Push & PR**: Pushes branch and creates Pull Request via `gh pr create`
30
+ 5. **Notify**: Switches back to original branch, reports PR URL
33
31
 
34
- ## Manual Trigger
32
+ ## Input Fields
35
33
 
36
- Enter an issue number in "Manual Trigger" section and click "Fix Issue".
37
-
38
- ## Retry
39
-
40
- Failed fixes show a "Retry" button. Click to provide additional context (e.g. "rebase from main first") and re-run.
34
+ | Input | Description | Required |
35
+ |-------|-------------|----------|
36
+ | `issue_id` | GitHub issue number | Yes (skips if empty) |
37
+ | `project` | Project name | Yes |
38
+ | `base_branch` | Base branch for fix | No (auto-detect) |
39
+ | `extra_context` | Additional instructions | No |
41
40
 
42
41
  ## Safety
43
42
 
44
43
  - Checks for uncommitted changes before starting (aborts if dirty)
45
- - Always works on new branches (never modifies main)
44
+ - Always works on new branches (never modifies main/master)
45
+ - Cleans up old fix branches for the same issue
46
46
  - Switches back to original branch after completion
47
- - Existing PRs are updated, not duplicated
47
+ - Uses `--force-with-lease` for safe push
48
48
 
49
- ## Processed Issues
49
+ ## Legacy Issue Scanner
50
50
 
51
- History shows all processed issues with status (processing/done/failed), PR number, and pipeline ID. Click pipeline ID to view details.
51
+ The old issue scanner (`Projects Issues tab`) is still functional for existing configurations. It uses `issue_autofix_config` DB table for per-project scan settings. New projects should use the pipeline binding approach above.
@@ -0,0 +1,41 @@
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
+ ## Available documentation
14
+
15
+ | File | Topic |
16
+ |------|-------|
17
+ | `00-overview.md` | Installation, startup, data paths, architecture |
18
+ | `01-settings.md` | All settings fields and configuration |
19
+ | `02-telegram.md` | Telegram bot setup and commands |
20
+ | `03-tunnel.md` | Remote access via Cloudflare tunnel |
21
+ | `04-tasks.md` | Background task system |
22
+ | `05-pipelines.md` | Pipeline/workflow engine — YAML format, nodes, templates, routing, scheduling, project bindings |
23
+ | `06-skills.md` | Skills marketplace and installation |
24
+ | `07-projects.md` | Project management |
25
+ | `08-rules.md` | CLAUDE.md templates and rule injection |
26
+ | `09-issue-autofix.md` | GitHub issue auto-fix pipeline |
27
+ | `10-troubleshooting.md` | Common issues and solutions |
28
+
29
+ ## Matching questions to docs
30
+
31
+ - Pipeline/workflow/DAG/YAML → `05-pipelines.md`
32
+ - Issue/PR/auto-fix → `09-issue-autofix.md` + `05-pipelines.md`
33
+ - Telegram/notification → `02-telegram.md`
34
+ - Tunnel/remote/cloudflare → `03-tunnel.md`
35
+ - Task/background/queue → `04-tasks.md`
36
+ - Settings/config → `01-settings.md`
37
+ - Install/start/update → `00-overview.md`
38
+ - Error/bug/crash → `10-troubleshooting.md`
39
+ - Skill/marketplace → `06-skills.md`
40
+ - Project/favorite → `07-projects.md`
41
+ - Rules/CLAUDE.md/template → `08-rules.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {