@shepai/cli 1.45.0 → 1.47.0

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 (90) hide show
  1. package/dist/src/presentation/web/app/actions/create-feature.d.ts +2 -0
  2. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  3. package/dist/src/presentation/web/app/actions/create-feature.js +3 -1
  4. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts +11 -0
  5. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts.map +1 -0
  6. package/dist/src/presentation/web/app/actions/get-workflow-defaults.js +15 -0
  7. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  8. package/dist/src/presentation/web/app/page.js +18 -10
  9. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +5 -1
  10. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  11. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +25 -7
  12. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +17 -0
  13. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -1
  14. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +54 -2
  15. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  16. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +15 -1
  17. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  18. package/dist/src/presentation/web/lib/layout-with-dagre.js +3 -0
  19. package/dist/tsconfig.build.tsbuildinfo +1 -1
  20. package/package.json +1 -1
  21. package/web/.next/BUILD_ID +1 -1
  22. package/web/.next/build-manifest.json +2 -2
  23. package/web/.next/cache/.previewinfo +1 -1
  24. package/web/.next/cache/.rscinfo +1 -1
  25. package/web/.next/cache/.tsbuildinfo +1 -1
  26. package/web/.next/cache/config.json +3 -3
  27. package/web/.next/fallback-build-manifest.json +2 -2
  28. package/web/.next/prerender-manifest.json +3 -3
  29. package/web/.next/required-server-files.js +1 -1
  30. package/web/.next/required-server-files.json +1 -1
  31. package/web/.next/server/app/_global-error.html +2 -2
  32. package/web/.next/server/app/_global-error.rsc +1 -1
  33. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  34. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  36. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  37. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/web/.next/server/app/page/server-reference-manifest.json +37 -22
  39. package/web/.next/server/app/page.js +1 -1
  40. package/web/.next/server/app/page.js.nft.json +1 -1
  41. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  43. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  44. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  45. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +9 -0
  46. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js.map +1 -0
  47. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  48. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  49. package/web/.next/server/pages/500.html +2 -2
  50. package/web/.next/server/server-reference-manifest.js +1 -1
  51. package/web/.next/server/server-reference-manifest.json +38 -23
  52. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  53. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  54. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  55. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  56. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  57. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  58. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +37 -22
  64. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +1 -1
  65. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  66. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  68. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  69. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +9 -0
  70. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  71. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  72. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  73. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +38 -23
  74. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +5 -1
  75. package/web/.next/standalone/src/presentation/web/app/actions/get-workflow-defaults.ts +28 -0
  76. package/web/.next/standalone/src/presentation/web/app/page.tsx +19 -10
  77. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx +74 -1
  78. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +55 -5
  79. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +31 -0
  80. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +3 -0
  81. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  82. package/web/.next/static/chunks/{22f9e39aab128063.js → 779c99975c85389e.js} +4 -4
  83. package/web/.next/trace +1 -1
  84. package/web/.next/trace-build +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js.map +0 -1
  87. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  88. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_buildManifest.js +0 -0
  89. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_clientMiddlewareManifest.json +0 -0
  90. /package/web/.next/static/{z34pr57Jw4sBfoDeMMuGn → wQ3vPV9I8JeacVNyp4HFg}/_ssgManifest.js +0 -0
@@ -0,0 +1,28 @@
1
+ 'use server';
2
+
3
+ import { getSettings } from '@shepai/core/infrastructure/services/settings.service';
4
+
5
+ export interface WorkflowDefaults {
6
+ approvalGates: {
7
+ allowPrd: boolean;
8
+ allowPlan: boolean;
9
+ allowMerge: boolean;
10
+ };
11
+ push: boolean;
12
+ openPr: boolean;
13
+ }
14
+
15
+ export async function getWorkflowDefaults(): Promise<WorkflowDefaults> {
16
+ const settings = getSettings();
17
+ const { workflow } = settings;
18
+
19
+ return {
20
+ approvalGates: {
21
+ allowPrd: workflow.approvalGateDefaults.allowPrd,
22
+ allowPlan: workflow.approvalGateDefaults.allowPlan,
23
+ allowMerge: workflow.approvalGateDefaults.allowMerge,
24
+ },
25
+ push: workflow.approvalGateDefaults.pushOnImplementationComplete,
26
+ openPr: workflow.openPrOnImplementationComplete,
27
+ };
28
+ }
@@ -119,19 +119,28 @@ export default async function HomePage() {
119
119
  nodesep: 15,
120
120
  });
121
121
 
122
- // Position "+ Add Repository" vertically centered with the bottom-most feature node,
123
- // mirroring how Dagre centers repo nodes with their connected features.
122
+ // Position "+ Add Repository" below the last repo node (no-features case) or
123
+ // vertically centered with a bottom feature node (features case).
124
124
  const repoNodes = laid.nodes.filter((n) => n.type === 'repositoryNode');
125
125
  const featureNodes = laid.nodes.filter((n) => n.type === 'featureNode');
126
- const sortedFeatures = [...featureNodes].sort((a, b) => a.position.y - b.position.y);
127
- // Pick the feature node just below center (one past the midpoint index)
128
- const centerIdx = Math.floor(sortedFeatures.length / 2);
129
- const targetFeature = sortedFeatures[centerIdx + 1] ?? sortedFeatures[sortedFeatures.length - 1];
130
126
  const repoX = repoNodes[0]?.position.x ?? 0;
131
- // Center the add-repo node (h=50) with the target feature node (h=140)
132
- const addRepoPosition = targetFeature
133
- ? { x: repoX, y: targetFeature.position.y + 70 - 25 }
134
- : { x: repoX, y: (repoNodes[0]?.position.y ?? 0) + 150 };
127
+
128
+ let addRepoPosition: { x: number; y: number };
129
+ if (featureNodes.length > 0) {
130
+ // Mirror how dagre centers repo nodes with their connected features
131
+ const sortedFeatures = [...featureNodes].sort((a, b) => a.position.y - b.position.y);
132
+ const centerIdx = Math.floor(sortedFeatures.length / 2);
133
+ const targetFeature =
134
+ sortedFeatures[centerIdx + 1] ?? sortedFeatures[sortedFeatures.length - 1];
135
+ // Center the add-repo node (h=50) with the target feature node (h=140)
136
+ addRepoPosition = { x: repoX, y: targetFeature.position.y + 70 - 25 };
137
+ } else {
138
+ // No features — place below the bottom-most repo node with a gap
139
+ const lastRepoY = repoNodes.length > 0 ? Math.max(...repoNodes.map((n) => n.position.y)) : 0;
140
+ const repoHeight = 50;
141
+ const gap = 200;
142
+ addRepoPosition = { x: repoX, y: lastRepoY + repoHeight + gap };
143
+ }
135
144
 
136
145
  laid.nodes.push({
137
146
  id: 'add-repository',
@@ -3,6 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
3
3
  import { within, userEvent, fn } from '@storybook/test';
4
4
  import { FeatureCreateDrawer } from './feature-create-drawer';
5
5
  import type { FeatureCreatePayload } from './feature-create-drawer';
6
+ import type { WorkflowDefaults } from '@/app/actions/get-workflow-defaults';
6
7
  import { Button } from '@/components/ui/button';
7
8
 
8
9
  /* ---------------------------------------------------------------------------
@@ -81,7 +82,13 @@ const logClose = fn().mockName('onClose');
81
82
  * ------------------------------------------------------------------------- */
82
83
 
83
84
  /** Starts closed — click button to open. Actions are logged. */
84
- function CreateDrawerTrigger({ label = 'Open Create Feature' }: { label?: string }) {
85
+ function CreateDrawerTrigger({
86
+ label = 'Open Create Feature',
87
+ workflowDefaults,
88
+ }: {
89
+ label?: string;
90
+ workflowDefaults?: WorkflowDefaults;
91
+ }) {
85
92
  const [open, setOpen] = useState(false);
86
93
 
87
94
  return (
@@ -100,6 +107,7 @@ function CreateDrawerTrigger({ label = 'Open Create Feature' }: { label?: string
100
107
  setOpen(false);
101
108
  }}
102
109
  repositoryPath="/Users/dev/my-repo"
110
+ workflowDefaults={workflowDefaults}
103
111
  />
104
112
  </div>
105
113
  );
@@ -222,6 +230,71 @@ export const MergeOnly: Story = {
222
230
  },
223
231
  };
224
232
 
233
+ /* ---------------------------------------------------------------------------
234
+ * Git checkbox stories
235
+ * ------------------------------------------------------------------------- */
236
+
237
+ /**
238
+ * Push checkbox checked — the "Push" option is enabled so the branch will be
239
+ * pushed to remote after implementation. "Create PR" remains unchecked.
240
+ */
241
+ export const PushOnly: Story = {
242
+ render: () => <CreateDrawerTrigger label="Open (Push Only)" />,
243
+ play: async ({ canvasElement }) => {
244
+ const canvas = within(canvasElement);
245
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Push Only)' }));
246
+
247
+ const body = within(canvasElement.ownerDocument.body);
248
+ const pushCheckbox = await body.findByLabelText('Push');
249
+ await userEvent.click(pushCheckbox);
250
+ },
251
+ };
252
+
253
+ /**
254
+ * Create PR checked — checking "Create PR" auto-checks and disables "Push"
255
+ * because a PR cannot be created without pushing. Both checkboxes appear
256
+ * checked, but "Push" is non-interactive (disabled).
257
+ */
258
+ export const PrChecked: Story = {
259
+ render: () => <CreateDrawerTrigger label="Open (PR Checked)" />,
260
+ play: async ({ canvasElement }) => {
261
+ const canvas = within(canvasElement);
262
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (PR Checked)' }));
263
+
264
+ const body = within(canvasElement.ownerDocument.body);
265
+ const prCheckbox = await body.findByLabelText('Create PR');
266
+ await userEvent.click(prCheckbox);
267
+ },
268
+ };
269
+
270
+ /* ---------------------------------------------------------------------------
271
+ * Workflow defaults stories
272
+ * ------------------------------------------------------------------------- */
273
+
274
+ const SAMPLE_WORKFLOW_DEFAULTS: WorkflowDefaults = {
275
+ approvalGates: { allowPrd: true, allowPlan: true, allowMerge: false },
276
+ push: true,
277
+ openPr: true,
278
+ };
279
+
280
+ /**
281
+ * Drawer pre-populated from workflow settings — PRD and Plan approval gates
282
+ * are checked, Push and Create PR are enabled. These values come from
283
+ * `shep settings workflow` and are read at mount time via `getWorkflowDefaults()`.
284
+ */
285
+ export const WithWorkflowDefaults: Story = {
286
+ render: () => (
287
+ <CreateDrawerTrigger
288
+ label="Open (Workflow Defaults)"
289
+ workflowDefaults={SAMPLE_WORKFLOW_DEFAULTS}
290
+ />
291
+ ),
292
+ play: async ({ canvasElement }) => {
293
+ const canvas = within(canvasElement);
294
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Workflow Defaults)' }));
295
+ },
296
+ };
297
+
225
298
  /* ---------------------------------------------------------------------------
226
299
  * Interactive story — full paths shown in submitted data panel
227
300
  * ------------------------------------------------------------------------- */
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useCallback } from 'react';
3
+ import { useState, useCallback, useEffect } from 'react';
4
4
  import type { LucideIcon } from 'lucide-react';
5
5
  import {
6
6
  XIcon,
@@ -25,9 +25,11 @@ import { Input } from '@/components/ui/input';
25
25
  import { Textarea } from '@/components/ui/textarea';
26
26
  import { Label } from '@/components/ui/label';
27
27
  import { CheckboxGroup } from '@/components/ui/checkbox-group';
28
+ import { CheckboxGroupItem } from '@/components/ui/checkbox-group-item';
28
29
  import { Separator } from '@/components/ui/separator';
29
30
  import { Badge } from '@/components/ui/badge';
30
31
  import type { FileAttachment } from '@shepai/core/infrastructure/services/file-dialog.service';
32
+ import type { WorkflowDefaults } from '@/app/actions/get-workflow-defaults';
31
33
  import { pickFiles } from './pick-files';
32
34
 
33
35
  export type { FileAttachment } from '@shepai/core/infrastructure/services/file-dialog.service';
@@ -42,6 +44,8 @@ export interface FeatureCreatePayload {
42
44
  allowPlan: boolean;
43
45
  allowMerge: boolean;
44
46
  };
47
+ push: boolean;
48
+ openPr: boolean;
45
49
  }
46
50
 
47
51
  const AUTO_APPROVE_OPTIONS = [
@@ -62,6 +66,7 @@ export interface FeatureCreateDrawerProps {
62
66
  onSubmit: (data: FeatureCreatePayload) => void;
63
67
  repositoryPath: string;
64
68
  isSubmitting?: boolean;
69
+ workflowDefaults?: WorkflowDefaults;
65
70
  }
66
71
 
67
72
  export function FeatureCreateDrawer({
@@ -70,18 +75,36 @@ export function FeatureCreateDrawer({
70
75
  onSubmit,
71
76
  repositoryPath,
72
77
  isSubmitting = false,
78
+ workflowDefaults,
73
79
  }: FeatureCreateDrawerProps) {
80
+ const defaultGates = workflowDefaults?.approvalGates ?? EMPTY_GATES;
81
+ const defaultPush = workflowDefaults?.push ?? false;
82
+ const defaultOpenPr = workflowDefaults?.openPr ?? false;
83
+
74
84
  const [name, setName] = useState('');
75
85
  const [description, setDescription] = useState('');
76
86
  const [attachments, setAttachments] = useState<FileAttachment[]>([]);
77
- const [approvalGates, setApprovalGates] = useState<Record<string, boolean>>({ ...EMPTY_GATES });
87
+ const [approvalGates, setApprovalGates] = useState<Record<string, boolean>>({ ...defaultGates });
88
+ const [push, setPush] = useState(defaultPush);
89
+ const [openPr, setOpenPr] = useState(defaultOpenPr);
90
+
91
+ // Sync state when workflowDefaults load asynchronously
92
+ useEffect(() => {
93
+ if (workflowDefaults) {
94
+ setApprovalGates({ ...workflowDefaults.approvalGates });
95
+ setPush(workflowDefaults.push);
96
+ setOpenPr(workflowDefaults.openPr);
97
+ }
98
+ }, [workflowDefaults]);
78
99
 
79
100
  const resetForm = useCallback(() => {
80
101
  setName('');
81
102
  setDescription('');
82
103
  setAttachments([]);
83
- setApprovalGates({ ...EMPTY_GATES });
84
- }, []);
104
+ setApprovalGates({ ...defaultGates });
105
+ setPush(defaultPush);
106
+ setOpenPr(defaultOpenPr);
107
+ }, [defaultGates, defaultPush, defaultOpenPr]);
85
108
 
86
109
  const handleOpenChange = useCallback(
87
110
  (nextOpen: boolean) => {
@@ -108,9 +131,11 @@ export function FeatureCreateDrawer({
108
131
  allowPlan: approvalGates.allowPlan ?? false,
109
132
  allowMerge: approvalGates.allowMerge ?? false,
110
133
  },
134
+ push: push || openPr,
135
+ openPr,
111
136
  });
112
137
  },
113
- [name, description, attachments, approvalGates, repositoryPath, onSubmit]
138
+ [name, description, attachments, approvalGates, repositoryPath, onSubmit, push, openPr]
114
139
  );
115
140
 
116
141
  const handleAddFiles = useCallback(async () => {
@@ -211,6 +236,31 @@ export function FeatureCreateDrawer({
211
236
  />
212
237
  </div>
213
238
 
239
+ {/* Git options */}
240
+ <div className="flex flex-col gap-1.5">
241
+ <Label className="text-muted-foreground text-xs font-semibold tracking-wider">
242
+ GIT
243
+ </Label>
244
+ <div className="flex flex-col gap-2">
245
+ <CheckboxGroupItem
246
+ id="push"
247
+ label="Push"
248
+ description="Push branch to remote after implementation."
249
+ checked={push || openPr}
250
+ onCheckedChange={setPush}
251
+ disabled={openPr || isSubmitting}
252
+ />
253
+ <CheckboxGroupItem
254
+ id="open-pr"
255
+ label="Create PR"
256
+ description="Open a pull request after pushing."
257
+ checked={openPr}
258
+ onCheckedChange={setOpenPr}
259
+ disabled={isSubmitting}
260
+ />
261
+ </div>
262
+ </div>
263
+
214
264
  {/* Attachments */}
215
265
  <div className="flex flex-col gap-2">
216
266
  <div className="flex items-center justify-between">
@@ -6,6 +6,8 @@ import { toast } from 'sonner';
6
6
  import { approveFeature } from '@/app/actions/approve-feature';
7
7
  import { getFeatureArtifact } from '@/app/actions/get-feature-artifact';
8
8
  import { getResearchArtifact } from '@/app/actions/get-research-artifact';
9
+ import { getWorkflowDefaults } from '@/app/actions/get-workflow-defaults';
10
+ import type { WorkflowDefaults } from '@/app/actions/get-workflow-defaults';
9
11
  import type { TechDecisionsReviewData } from '@/components/common/tech-decisions-review';
10
12
  import { FeaturesCanvas } from '@/components/features/features-canvas';
11
13
  import type { CanvasNodeType } from '@/components/features/features-canvas';
@@ -44,6 +46,16 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
44
46
  closeCreateDrawer,
45
47
  } = useControlCenterState(initialNodes, initialEdges);
46
48
 
49
+ // Workflow defaults for the create-feature drawer
50
+ const [workflowDefaults, setWorkflowDefaults] = useState<WorkflowDefaults | undefined>();
51
+ useEffect(() => {
52
+ getWorkflowDefaults()
53
+ .then(setWorkflowDefaults)
54
+ .catch(() => {
55
+ // Settings unavailable — drawer falls back to all-false defaults
56
+ });
57
+ }, []);
58
+
47
59
  // PRD questionnaire drawer state
48
60
  const [prdSelections, setPrdSelections] = useState<Record<string, string>>({});
49
61
  const [isPrdProcessing, setIsPrdProcessing] = useState(false);
@@ -185,6 +197,8 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
185
197
  };
186
198
  }, [techDecisionsFeatureId]);
187
199
 
200
+ const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
201
+
188
202
  // Listen for global "open create drawer" events from the sidebar
189
203
  useEffect(() => {
190
204
  const handler = () => handleAddFeature();
@@ -192,6 +206,22 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
192
206
  return () => window.removeEventListener('shep:open-create-drawer', handler);
193
207
  }, [handleAddFeature]);
194
208
 
209
+ if (!hasRepositories) {
210
+ return (
211
+ <>
212
+ <NotificationPermissionBanner />
213
+ <ControlCenterEmptyState onRepositorySelect={handleAddRepository} />
214
+ <FeatureCreateDrawer
215
+ open={isCreateDrawerOpen}
216
+ onClose={closeCreateDrawer}
217
+ onSubmit={handleCreateFeatureSubmit}
218
+ repositoryPath={pendingRepositoryPath}
219
+ workflowDefaults={workflowDefaults}
220
+ />
221
+ </>
222
+ );
223
+ }
224
+
195
225
  return (
196
226
  <>
197
227
  <NotificationPermissionBanner />
@@ -257,6 +287,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
257
287
  onClose={closeCreateDrawer}
258
288
  onSubmit={handleCreateFeatureSubmit}
259
289
  repositoryPath={pendingRepositoryPath}
290
+ workflowDefaults={workflowDefaults}
260
291
  />
261
292
  </>
262
293
  );
@@ -124,6 +124,9 @@ export function layoutWithDagre<N extends Node>(
124
124
  minX = Math.min(minX, n.position.x);
125
125
  }
126
126
 
127
+ // When no graph nodes exist (all disconnected), default to origin
128
+ if (minX === Infinity) minX = 0;
129
+
127
130
  for (let i = 0; i < disconnectedNodes.length; i++) {
128
131
  const node = disconnectedNodes[i];
129
132
  const size = getNodeSize(node, nodeSize);
@@ -15,7 +15,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
15
15
  const hostname = process.env.HOSTNAME || '0.0.0.0'
16
16
 
17
17
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
18
- const nextConfig = {"env":{"NEXT_PUBLIC_SHEP_VERSION":"1.44.2","NEXT_PUBLIC_SHEP_PACKAGE_NAME":"@shepai/cli","NEXT_PUBLIC_SHEP_DESCRIPTION":"Autonomous AI Native SDLC Platform - Automate the development cycle from idea to deploy"},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":true,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/cli/cli","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"dynamicOnHover":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":false,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":false,"lockDistDir":true,"isolatedDevBuild":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":false,"optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","turbopack":{"root":"/home/runner/work/cli/cli"},"serverExternalPackages":["tsyringe","reflect-metadata","better-sqlite3"],"distDirRoot":".next"}
18
+ const nextConfig = {"env":{"NEXT_PUBLIC_SHEP_VERSION":"1.46.0","NEXT_PUBLIC_SHEP_PACKAGE_NAME":"@shepai/cli","NEXT_PUBLIC_SHEP_DESCRIPTION":"Autonomous AI Native SDLC Platform - Automate the development cycle from idea to deploy"},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":true,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/cli/cli","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"dynamicOnHover":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":false,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":false,"lockDistDir":true,"isolatedDevBuild":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":false,"optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","turbopack":{"root":"/home/runner/work/cli/cli"},"serverExternalPackages":["tsyringe","reflect-metadata","better-sqlite3"],"distDirRoot":".next"}
19
19
 
20
20
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
21
21