@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 +5 -21
- package/components/CodeViewer.tsx +11 -1
- package/components/DocsViewer.tsx +11 -1
- package/components/PipelineView.tsx +9 -1
- package/components/ProjectDetail.tsx +23 -3
- 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 +263 -22
- package/lib/help-docs/09-issue-autofix.md +25 -25
- package/lib/help-docs/CLAUDE.md +41 -0
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,28 +1,12 @@
|
|
|
1
|
-
# Forge v0.4.
|
|
1
|
+
# Forge v0.4.6
|
|
2
2
|
|
|
3
3
|
Released: 2026-03-21
|
|
4
4
|
|
|
5
|
-
## Changes since v0.4.
|
|
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
|
-
-
|
|
24
|
-
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
38
|
-
| `prompt` | Claude Code prompt or shell command |
|
|
39
|
-
| `mode` | `claude` (
|
|
40
|
-
| `branch` | Auto-checkout branch before running |
|
|
41
|
-
| `depends_on` | List of node IDs that must complete first |
|
|
42
|
-
| `outputs` | Extract results
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
- `{{
|
|
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
|
|
54
|
-
Complete issue resolution
|
|
220
|
+
### issue-auto-fix
|
|
221
|
+
Complete issue resolution: fetch GitHub issue → fix code on new branch → create 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
|
-
|
|
236
|
+
## Project Pipeline Bindings
|
|
57
237
|
|
|
58
|
-
|
|
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": "
|
|
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
|
|
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 →
|
|
15
|
-
2.
|
|
16
|
-
3.
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
+
Setup → Fetch Issue → Fix Code (new branch) → Push & Create PR → Notify
|
|
26
24
|
```
|
|
27
25
|
|
|
28
|
-
1. **
|
|
29
|
-
2. **
|
|
30
|
-
3. **
|
|
31
|
-
4. **
|
|
32
|
-
5. **Notify**:
|
|
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
|
-
##
|
|
32
|
+
## Input Fields
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
-
|
|
47
|
+
- Uses `--force-with-lease` for safe push
|
|
48
48
|
|
|
49
|
-
##
|
|
49
|
+
## Legacy Issue Scanner
|
|
50
50
|
|
|
51
|
-
|
|
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`
|