@kmgeon/taskflow 0.1.3

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.
Files changed (158) hide show
  1. package/README.md +374 -0
  2. package/bin/task-mcp.mjs +19 -0
  3. package/bin/task.mjs +19 -0
  4. package/docs/clean-code.md +29 -0
  5. package/docs/git.md +36 -0
  6. package/docs/guideline.md +25 -0
  7. package/docs/security.md +32 -0
  8. package/docs/step-by-step.md +29 -0
  9. package/docs/superpowers/specs/2026-03-21-cli-advisor-design.md +383 -0
  10. package/docs/superpowers/specs/2026-03-21-init-redesign-design.md +429 -0
  11. package/docs/superpowers/specs/2026-03-21-skill-architecture-design.md +362 -0
  12. package/docs/superpowers/specs/2026-03-23-t-create-task-run-design.md +40 -0
  13. package/docs/superpowers/specs/2026-03-23-task-run-design.md +44 -0
  14. package/docs/tdd.md +41 -0
  15. package/package.json +114 -0
  16. package/src/app/(protected)/dashboard/page.tsx +7 -0
  17. package/src/app/(protected)/layout.tsx +10 -0
  18. package/src/app/api/[[...hono]]/route.ts +13 -0
  19. package/src/app/example/page.tsx +11 -0
  20. package/src/app/favicon.ico +0 -0
  21. package/src/app/globals.css +168 -0
  22. package/src/app/layout.tsx +35 -0
  23. package/src/app/page.tsx +5 -0
  24. package/src/app/providers.tsx +57 -0
  25. package/src/backend/config/index.ts +36 -0
  26. package/src/backend/hono/app.ts +32 -0
  27. package/src/backend/hono/context.ts +38 -0
  28. package/src/backend/http/response.ts +64 -0
  29. package/src/backend/middleware/context.ts +23 -0
  30. package/src/backend/middleware/error.ts +31 -0
  31. package/src/backend/middleware/supabase.ts +23 -0
  32. package/src/backend/supabase/client.ts +17 -0
  33. package/src/cli/commands/__tests__/task-commands.test.ts +170 -0
  34. package/src/cli/commands/advisor.ts +45 -0
  35. package/src/cli/commands/ask.ts +50 -0
  36. package/src/cli/commands/board.ts +72 -0
  37. package/src/cli/commands/init.ts +184 -0
  38. package/src/cli/commands/list.ts +138 -0
  39. package/src/cli/commands/run.ts +143 -0
  40. package/src/cli/commands/set-status.ts +50 -0
  41. package/src/cli/commands/show.ts +28 -0
  42. package/src/cli/commands/tree.ts +72 -0
  43. package/src/cli/index.ts +38 -0
  44. package/src/cli/lib/__tests__/formatter.test.ts +123 -0
  45. package/src/cli/lib/error-boundary.test.ts +135 -0
  46. package/src/cli/lib/error-boundary.ts +70 -0
  47. package/src/cli/lib/formatter.ts +764 -0
  48. package/src/cli/lib/trd.ts +33 -0
  49. package/src/cli/lib/validate.test.ts +89 -0
  50. package/src/cli/lib/validate.ts +43 -0
  51. package/src/cli/prompts/task-run.md +25 -0
  52. package/src/components/layout/AppLayout.tsx +15 -0
  53. package/src/components/layout/Sidebar.tsx +124 -0
  54. package/src/components/ui/accordion.tsx +58 -0
  55. package/src/components/ui/avatar.tsx +50 -0
  56. package/src/components/ui/badge.tsx +36 -0
  57. package/src/components/ui/button.tsx +56 -0
  58. package/src/components/ui/card.tsx +79 -0
  59. package/src/components/ui/checkbox.tsx +30 -0
  60. package/src/components/ui/dialog.tsx +122 -0
  61. package/src/components/ui/dropdown-menu.tsx +200 -0
  62. package/src/components/ui/file-upload.tsx +50 -0
  63. package/src/components/ui/form.tsx +179 -0
  64. package/src/components/ui/input.tsx +25 -0
  65. package/src/components/ui/label.tsx +26 -0
  66. package/src/components/ui/scroll-area.tsx +48 -0
  67. package/src/components/ui/select.tsx +160 -0
  68. package/src/components/ui/separator.tsx +31 -0
  69. package/src/components/ui/sheet.tsx +140 -0
  70. package/src/components/ui/textarea.tsx +22 -0
  71. package/src/components/ui/toast.tsx +129 -0
  72. package/src/components/ui/toaster.tsx +35 -0
  73. package/src/core/ai/claude-client.ts +79 -0
  74. package/src/core/claude-runner/flag-builder.ts +57 -0
  75. package/src/core/claude-runner/index.ts +2 -0
  76. package/src/core/claude-runner/spawner.ts +86 -0
  77. package/src/core/prd/__tests__/auto-analyzer.test.ts +35 -0
  78. package/src/core/prd/__tests__/generator.test.ts +26 -0
  79. package/src/core/prd/__tests__/scanner.test.ts +35 -0
  80. package/src/core/prd/auto-analyzer.ts +9 -0
  81. package/src/core/prd/generator.ts +8 -0
  82. package/src/core/prd/scanner.ts +117 -0
  83. package/src/core/project/__tests__/claude-setup.test.ts +133 -0
  84. package/src/core/project/__tests__/config.test.ts +30 -0
  85. package/src/core/project/__tests__/init.test.ts +37 -0
  86. package/src/core/project/__tests__/skill-setup.test.ts +62 -0
  87. package/src/core/project/claude-setup.ts +224 -0
  88. package/src/core/project/config.ts +34 -0
  89. package/src/core/project/docs-setup.ts +26 -0
  90. package/src/core/project/docs-templates.ts +205 -0
  91. package/src/core/project/init.ts +40 -0
  92. package/src/core/project/skill-setup.ts +32 -0
  93. package/src/core/project/skill-templates.ts +277 -0
  94. package/src/core/task/index.ts +16 -0
  95. package/src/core/types.ts +58 -0
  96. package/src/features/example/backend/error.ts +9 -0
  97. package/src/features/example/backend/route.ts +52 -0
  98. package/src/features/example/backend/schema.ts +25 -0
  99. package/src/features/example/backend/service.ts +73 -0
  100. package/src/features/example/components/example-status.test.tsx +97 -0
  101. package/src/features/example/components/example-status.tsx +160 -0
  102. package/src/features/example/hooks/useExampleQuery.ts +23 -0
  103. package/src/features/example/lib/dto.test.ts +57 -0
  104. package/src/features/example/lib/dto.ts +5 -0
  105. package/src/features/kanban/backend/__tests__/sse-broadcaster.test.ts +137 -0
  106. package/src/features/kanban/backend/__tests__/sse-event-format.test.ts +55 -0
  107. package/src/features/kanban/backend/route.ts +55 -0
  108. package/src/features/kanban/backend/sse-broadcaster.ts +142 -0
  109. package/src/features/kanban/backend/sse-route.ts +43 -0
  110. package/src/features/kanban/components/KanbanBoard.tsx +105 -0
  111. package/src/features/kanban/components/KanbanColumn.tsx +51 -0
  112. package/src/features/kanban/components/KanbanError.tsx +29 -0
  113. package/src/features/kanban/components/KanbanSkeleton.tsx +46 -0
  114. package/src/features/kanban/components/ProgressCard.tsx +42 -0
  115. package/src/features/kanban/components/TaskCard.tsx +76 -0
  116. package/src/features/kanban/components/__tests__/kanban-components.test.tsx +86 -0
  117. package/src/features/kanban/hooks/useTaskSse.ts +66 -0
  118. package/src/features/kanban/hooks/useTasksQuery.ts +52 -0
  119. package/src/features/kanban/lib/__tests__/kanban-utils.test.ts +97 -0
  120. package/src/features/kanban/lib/kanban-utils.ts +37 -0
  121. package/src/features/taskflow/constants.ts +54 -0
  122. package/src/features/taskflow/index.ts +27 -0
  123. package/src/features/taskflow/lib/__tests__/filter.test.ts +89 -0
  124. package/src/features/taskflow/lib/__tests__/graph.test.ts +247 -0
  125. package/src/features/taskflow/lib/__tests__/repository.test.ts +233 -0
  126. package/src/features/taskflow/lib/__tests__/serializer.test.ts +98 -0
  127. package/src/features/taskflow/lib/advisor/__tests__/advisor-integration.test.ts +98 -0
  128. package/src/features/taskflow/lib/advisor/ai-advisor.test.ts +40 -0
  129. package/src/features/taskflow/lib/advisor/ai-advisor.ts +20 -0
  130. package/src/features/taskflow/lib/advisor/context-builder.test.ts +73 -0
  131. package/src/features/taskflow/lib/advisor/context-builder.ts +151 -0
  132. package/src/features/taskflow/lib/advisor/db.test.ts +106 -0
  133. package/src/features/taskflow/lib/advisor/db.ts +185 -0
  134. package/src/features/taskflow/lib/advisor/local-summary.test.ts +53 -0
  135. package/src/features/taskflow/lib/advisor/local-summary.ts +72 -0
  136. package/src/features/taskflow/lib/advisor/prompts.ts +86 -0
  137. package/src/features/taskflow/lib/filter.ts +54 -0
  138. package/src/features/taskflow/lib/fs-utils.ts +50 -0
  139. package/src/features/taskflow/lib/graph.ts +148 -0
  140. package/src/features/taskflow/lib/index-builder.ts +42 -0
  141. package/src/features/taskflow/lib/repository.ts +168 -0
  142. package/src/features/taskflow/lib/serializer.ts +62 -0
  143. package/src/features/taskflow/lib/watcher.ts +40 -0
  144. package/src/features/taskflow/types.ts +71 -0
  145. package/src/hooks/use-toast.ts +194 -0
  146. package/src/lib/remote/api-client.ts +40 -0
  147. package/src/lib/supabase/client.ts +8 -0
  148. package/src/lib/supabase/server.ts +46 -0
  149. package/src/lib/supabase/types.ts +3 -0
  150. package/src/lib/utils.ts +6 -0
  151. package/src/mcp/index.ts +7 -0
  152. package/src/mcp/server.ts +21 -0
  153. package/src/mcp/tools/brainstorm.ts +48 -0
  154. package/src/mcp/tools/prd.ts +71 -0
  155. package/src/mcp/tools/project.ts +39 -0
  156. package/src/mcp/tools/task-status.ts +40 -0
  157. package/src/mcp/tools/task.ts +82 -0
  158. package/src/mcp/util.ts +6 -0
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as SelectPrimitive from '@radix-ui/react-select';
5
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ const Select = SelectPrimitive.Root;
10
+
11
+ const SelectGroup = SelectPrimitive.Group;
12
+
13
+ const SelectValue = SelectPrimitive.Value;
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
23
+ className,
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ));
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ 'flex cursor-default items-center justify-center py-1',
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ));
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ 'flex cursor-default items-center justify-center py-1',
60
+ className,
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ));
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName;
69
+
70
+ const SelectContent = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.Content>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
73
+ >(({ className, children, position = 'popper', ...props }, ref) => (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Content
76
+ ref={ref}
77
+ className={cn(
78
+ 'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
79
+ position === 'popper' &&
80
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
81
+ className,
82
+ )}
83
+ position={position}
84
+ {...props}
85
+ >
86
+ <SelectScrollUpButton />
87
+ <SelectPrimitive.Viewport
88
+ className={cn(
89
+ 'p-1',
90
+ position === 'popper' &&
91
+ 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
92
+ )}
93
+ >
94
+ {children}
95
+ </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
97
+ </SelectPrimitive.Content>
98
+ </SelectPrimitive.Portal>
99
+ ));
100
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
101
+
102
+ const SelectLabel = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Label>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105
+ >(({ className, ...props }, ref) => (
106
+ <SelectPrimitive.Label
107
+ ref={ref}
108
+ className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
109
+ {...props}
110
+ />
111
+ ));
112
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
113
+
114
+ const SelectItem = React.forwardRef<
115
+ React.ElementRef<typeof SelectPrimitive.Item>,
116
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <SelectPrimitive.Item
119
+ ref={ref}
120
+ className={cn(
121
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
122
+ className,
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <SelectPrimitive.ItemIndicator>
128
+ <Check className="h-4 w-4" />
129
+ </SelectPrimitive.ItemIndicator>
130
+ </span>
131
+
132
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
133
+ </SelectPrimitive.Item>
134
+ ));
135
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
136
+
137
+ const SelectSeparator = React.forwardRef<
138
+ React.ElementRef<typeof SelectPrimitive.Separator>,
139
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
140
+ >(({ className, ...props }, ref) => (
141
+ <SelectPrimitive.Separator
142
+ ref={ref}
143
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
144
+ {...props}
145
+ />
146
+ ));
147
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
148
+
149
+ export {
150
+ Select,
151
+ SelectGroup,
152
+ SelectValue,
153
+ SelectTrigger,
154
+ SelectContent,
155
+ SelectLabel,
156
+ SelectItem,
157
+ SelectSeparator,
158
+ SelectScrollUpButton,
159
+ SelectScrollDownButton,
160
+ };
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(
12
+ (
13
+ { className, orientation = "horizontal", decorative = true, ...props },
14
+ ref
15
+ ) => (
16
+ <SeparatorPrimitive.Root
17
+ ref={ref}
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "shrink-0 bg-border",
22
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ )
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName
30
+
31
+ export { Separator }
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as SheetPrimitive from '@radix-ui/react-dialog';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+ import { X } from 'lucide-react';
7
+
8
+ import { cn } from '@/lib/utils';
9
+
10
+ const Sheet = SheetPrimitive.Root;
11
+
12
+ const SheetTrigger = SheetPrimitive.Trigger;
13
+
14
+ const SheetClose = SheetPrimitive.Close;
15
+
16
+ const SheetPortal = SheetPrimitive.Portal;
17
+
18
+ const SheetOverlay = React.forwardRef<
19
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
20
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
21
+ >(({ className, ...props }, ref) => (
22
+ <SheetPrimitive.Overlay
23
+ className={cn(
24
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
25
+ className,
26
+ )}
27
+ {...props}
28
+ ref={ref}
29
+ />
30
+ ));
31
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
32
+
33
+ const sheetVariants = cva(
34
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
35
+ {
36
+ variants: {
37
+ side: {
38
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
39
+ bottom:
40
+ 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
41
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
42
+ right:
43
+ 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ side: 'right',
48
+ },
49
+ },
50
+ );
51
+
52
+ interface SheetContentProps
53
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
54
+ VariantProps<typeof sheetVariants> {}
55
+
56
+ const SheetContent = React.forwardRef<
57
+ React.ElementRef<typeof SheetPrimitive.Content>,
58
+ SheetContentProps
59
+ >(({ side = 'right', className, children, ...props }, ref) => (
60
+ <SheetPortal>
61
+ <SheetOverlay />
62
+ <SheetPrimitive.Content
63
+ ref={ref}
64
+ className={cn(sheetVariants({ side }), className)}
65
+ {...props}
66
+ >
67
+ {children}
68
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
69
+ <X className="h-4 w-4" />
70
+ <span className="sr-only">Close</span>
71
+ </SheetPrimitive.Close>
72
+ </SheetPrimitive.Content>
73
+ </SheetPortal>
74
+ ));
75
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
76
+
77
+ const SheetHeader = ({
78
+ className,
79
+ ...props
80
+ }: React.HTMLAttributes<HTMLDivElement>) => (
81
+ <div
82
+ className={cn(
83
+ 'flex flex-col space-y-2 text-center sm:text-left',
84
+ className,
85
+ )}
86
+ {...props}
87
+ />
88
+ );
89
+ SheetHeader.displayName = 'SheetHeader';
90
+
91
+ const SheetFooter = ({
92
+ className,
93
+ ...props
94
+ }: React.HTMLAttributes<HTMLDivElement>) => (
95
+ <div
96
+ className={cn(
97
+ 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
98
+ className,
99
+ )}
100
+ {...props}
101
+ />
102
+ );
103
+ SheetFooter.displayName = 'SheetFooter';
104
+
105
+ const SheetTitle = React.forwardRef<
106
+ React.ElementRef<typeof SheetPrimitive.Title>,
107
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
108
+ >(({ className, ...props }, ref) => (
109
+ <SheetPrimitive.Title
110
+ ref={ref}
111
+ className={cn('text-lg font-semibold text-foreground', className)}
112
+ {...props}
113
+ />
114
+ ));
115
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
116
+
117
+ const SheetDescription = React.forwardRef<
118
+ React.ElementRef<typeof SheetPrimitive.Description>,
119
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
120
+ >(({ className, ...props }, ref) => (
121
+ <SheetPrimitive.Description
122
+ ref={ref}
123
+ className={cn('text-sm text-muted-foreground', className)}
124
+ {...props}
125
+ />
126
+ ));
127
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
128
+
129
+ export {
130
+ Sheet,
131
+ SheetPortal,
132
+ SheetOverlay,
133
+ SheetTrigger,
134
+ SheetClose,
135
+ SheetContent,
136
+ SheetHeader,
137
+ SheetFooter,
138
+ SheetTitle,
139
+ SheetDescription,
140
+ };
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Textarea = React.forwardRef<
6
+ HTMLTextAreaElement,
7
+ React.ComponentProps<'textarea'>
8
+ >(({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
13
+ className,
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ });
20
+ Textarea.displayName = 'Textarea';
21
+
22
+ export { Textarea };
@@ -0,0 +1,129 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as ToastPrimitives from '@radix-ui/react-toast';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+ import { X } from 'lucide-react';
7
+
8
+ import { cn } from '@/lib/utils';
9
+
10
+ const ToastProvider = ToastPrimitives.Provider;
11
+
12
+ const ToastViewport = React.forwardRef<
13
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
14
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
15
+ >(({ className, ...props }, ref) => (
16
+ <ToastPrimitives.Viewport
17
+ ref={ref}
18
+ className={cn(
19
+ 'fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ ));
25
+ ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
26
+
27
+ const toastVariants = cva(
28
+ 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
29
+ {
30
+ variants: {
31
+ variant: {
32
+ default: 'border bg-background text-foreground',
33
+ destructive:
34
+ 'destructive group border-destructive bg-destructive text-destructive-foreground',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: 'default',
39
+ },
40
+ },
41
+ );
42
+
43
+ const Toast = React.forwardRef<
44
+ React.ElementRef<typeof ToastPrimitives.Root>,
45
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
46
+ VariantProps<typeof toastVariants>
47
+ >(({ className, variant, ...props }, ref) => {
48
+ return (
49
+ <ToastPrimitives.Root
50
+ ref={ref}
51
+ className={cn(toastVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ );
55
+ });
56
+ Toast.displayName = ToastPrimitives.Root.displayName;
57
+
58
+ const ToastAction = React.forwardRef<
59
+ React.ElementRef<typeof ToastPrimitives.Action>,
60
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
61
+ >(({ className, ...props }, ref) => (
62
+ <ToastPrimitives.Action
63
+ ref={ref}
64
+ className={cn(
65
+ 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground focus:group-[.destructive]:ring-destructive',
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ ));
71
+ ToastAction.displayName = ToastPrimitives.Action.displayName;
72
+
73
+ const ToastClose = React.forwardRef<
74
+ React.ElementRef<typeof ToastPrimitives.Close>,
75
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
76
+ >(({ className, ...props }, ref) => (
77
+ <ToastPrimitives.Close
78
+ ref={ref}
79
+ className={cn(
80
+ 'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-hidden focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 hover:group-[.destructive]:text-red-50 focus:group-[.destructive]:ring-red-400 focus:group-[.destructive]:ring-offset-red-600',
81
+ className,
82
+ )}
83
+ toast-close=""
84
+ {...props}
85
+ >
86
+ <X className="h-4 w-4" />
87
+ </ToastPrimitives.Close>
88
+ ));
89
+ ToastClose.displayName = ToastPrimitives.Close.displayName;
90
+
91
+ const ToastTitle = React.forwardRef<
92
+ React.ElementRef<typeof ToastPrimitives.Title>,
93
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
94
+ >(({ className, ...props }, ref) => (
95
+ <ToastPrimitives.Title
96
+ ref={ref}
97
+ className={cn('text-sm font-semibold', className)}
98
+ {...props}
99
+ />
100
+ ));
101
+ ToastTitle.displayName = ToastPrimitives.Title.displayName;
102
+
103
+ const ToastDescription = React.forwardRef<
104
+ React.ElementRef<typeof ToastPrimitives.Description>,
105
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
106
+ >(({ className, ...props }, ref) => (
107
+ <ToastPrimitives.Description
108
+ ref={ref}
109
+ className={cn('text-sm opacity-90', className)}
110
+ {...props}
111
+ />
112
+ ));
113
+ ToastDescription.displayName = ToastPrimitives.Description.displayName;
114
+
115
+ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
116
+
117
+ type ToastActionElement = React.ReactElement<typeof ToastAction>;
118
+
119
+ export {
120
+ type ToastProps,
121
+ type ToastActionElement,
122
+ ToastProvider,
123
+ ToastViewport,
124
+ Toast,
125
+ ToastTitle,
126
+ ToastDescription,
127
+ ToastClose,
128
+ ToastAction,
129
+ };
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { useToast } from '@/hooks/use-toast';
4
+ import {
5
+ Toast,
6
+ ToastClose,
7
+ ToastDescription,
8
+ ToastProvider,
9
+ ToastTitle,
10
+ ToastViewport,
11
+ } from '@/components/ui/toast';
12
+
13
+ export function Toaster() {
14
+ const { toasts } = useToast();
15
+
16
+ return (
17
+ <ToastProvider>
18
+ {toasts.map(function ({ id, title, description, action, ...props }) {
19
+ return (
20
+ <Toast key={id} {...props}>
21
+ <div className="grid gap-1">
22
+ {title && <ToastTitle>{title}</ToastTitle>}
23
+ {description && (
24
+ <ToastDescription>{description}</ToastDescription>
25
+ )}
26
+ </div>
27
+ {action}
28
+ <ToastClose />
29
+ </Toast>
30
+ );
31
+ })}
32
+ <ToastViewport />
33
+ </ToastProvider>
34
+ );
35
+ }
@@ -0,0 +1,79 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
3
+ import chalk from "chalk";
4
+
5
+ export interface PrdBrainstormOptions {
6
+ projectRoot: string;
7
+ systemPrompt: string;
8
+ projectContext?: string;
9
+ }
10
+
11
+ export interface PrdBrainstormResult {
12
+ markdown: string;
13
+ projectName: string;
14
+ }
15
+
16
+ /**
17
+ * Run interactive PRD brainstorm using Claude Agent SDK.
18
+ * Claude will ask questions one at a time and generate a PRD.
19
+ * The conversation happens in the terminal via the SDK's built-in I/O.
20
+ */
21
+ export async function runPrdBrainstorm(
22
+ options: PrdBrainstormOptions,
23
+ ): Promise<PrdBrainstormResult | null> {
24
+ const contextSection = options.projectContext
25
+ ? `\n\n## 프로젝트 컨텍스트\n\n${options.projectContext}`
26
+ : "";
27
+
28
+ const prompt = `PRD를 작성해주세요. 사용자와 대화하며 요구사항을 수집하고, 완료되면 PRD 마크다운을 생성해주세요.${contextSection}`;
29
+
30
+ let prdMarkdown = "";
31
+
32
+ const conversation = query({
33
+ prompt,
34
+ options: {
35
+ systemPrompt: options.systemPrompt,
36
+ cwd: options.projectRoot,
37
+ maxTurns: 30,
38
+ model: "claude-sonnet-4-6",
39
+ permissionMode: "default",
40
+ },
41
+ });
42
+
43
+ for await (const message of conversation) {
44
+ if (message.type === "assistant") {
45
+ // SDKAssistantMessage: message field is BetaMessage
46
+ // BetaMessage.content is an array of content blocks
47
+ const betaMessage = (message as Extract<SDKMessage, { type: "assistant" }>).message;
48
+ if (betaMessage?.content) {
49
+ for (const block of betaMessage.content) {
50
+ if (block.type === "text") {
51
+ prdMarkdown = block.text;
52
+ process.stdout.write(chalk.blue(block.text));
53
+ }
54
+ }
55
+ process.stdout.write("\n");
56
+ }
57
+ } else if (message.type === "result") {
58
+ const resultMsg = message as Extract<SDKMessage, { type: "result" }>;
59
+ if (resultMsg.subtype === "success") {
60
+ // SDKResultSuccess has a `result` field
61
+ const successMsg = resultMsg as Extract<
62
+ Extract<SDKMessage, { type: "result" }>,
63
+ { subtype: "success" }
64
+ >;
65
+ if (successMsg.result) {
66
+ prdMarkdown = successMsg.result;
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ if (!prdMarkdown) return null;
73
+
74
+ // Extract project name from PRD markdown heading
75
+ const nameMatch = prdMarkdown.match(/^#\s+(.+?)(?:\s*—|\s*-|\n)/m);
76
+ const projectName = nameMatch ? nameMatch[1].trim() : "project";
77
+
78
+ return { markdown: prdMarkdown, projectName };
79
+ }
@@ -0,0 +1,57 @@
1
+ export interface FlagBuilderInput {
2
+ /** --system-prompt-file 경로 (기존 시스템 프롬프트 전체 교체) */
3
+ systemPromptFilePath?: string;
4
+ /** --append-system-prompt-file 경로 (기존 시스템 프롬프트에 추가) */
5
+ appendSystemPromptFilePath?: string;
6
+ /** --model 값 */
7
+ model?: string;
8
+ /** --mcp-config 경로 */
9
+ mcpConfigFilePath?: string;
10
+ /** --permission-mode 값 */
11
+ permissionMode?: string;
12
+ /** --max-turns 값 */
13
+ maxTurns?: number;
14
+ /** 마지막에 붙을 프롬프트 텍스트 (없으면 interactive mode) */
15
+ prompt?: string;
16
+ /** 추가 CLI 플래그 (예: ['-p']) */
17
+ passthroughFlags?: string[];
18
+ }
19
+
20
+ export function buildFlags(input: FlagBuilderInput): string[] {
21
+ const flags: string[] = [];
22
+
23
+ if (input.systemPromptFilePath) {
24
+ flags.push("--system-prompt-file", input.systemPromptFilePath);
25
+ }
26
+
27
+ if (input.appendSystemPromptFilePath) {
28
+ flags.push("--append-system-prompt-file", input.appendSystemPromptFilePath);
29
+ }
30
+
31
+ if (input.model) {
32
+ flags.push("--model", input.model);
33
+ }
34
+
35
+ if (input.mcpConfigFilePath) {
36
+ flags.push("--mcp-config", input.mcpConfigFilePath);
37
+ }
38
+
39
+ if (input.permissionMode) {
40
+ flags.push("--permission-mode", input.permissionMode);
41
+ }
42
+
43
+ if (input.maxTurns !== undefined) {
44
+ flags.push("--max-turns", String(input.maxTurns));
45
+ }
46
+
47
+ if (input.passthroughFlags && input.passthroughFlags.length > 0) {
48
+ flags.push(...input.passthroughFlags);
49
+ }
50
+
51
+ // prompt는 마지막에 (없으면 interactive mode)
52
+ if (input.prompt) {
53
+ flags.push(input.prompt);
54
+ }
55
+
56
+ return flags;
57
+ }
@@ -0,0 +1,2 @@
1
+ export { spawnClaude, spawnClaudeWithCapture, type SpawnResult, type SpawnCaptureOptions } from "./spawner.js";
2
+ export { buildFlags, type FlagBuilderInput } from "./flag-builder.js";