newpr 1.0.21 → 1.0.22
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/package.json +1 -1
- package/src/stack/delta.ts +38 -3
- package/src/stack/execute.ts +20 -6
- package/src/stack/feasibility.ts +6 -1
- package/src/stack/plan.ts +63 -5
- package/src/stack/publish.ts +111 -21
- package/src/stack/types.ts +2 -0
- package/src/web/client/App.tsx +2 -4
- package/src/web/client/components/AnalyticsConsent.tsx +1 -98
- package/src/web/client/components/AppShell.tsx +5 -5
- package/src/web/client/components/StackDagView.tsx +317 -0
- package/src/web/client/components/StackGroupCard.tsx +15 -1
- package/src/web/client/lib/analytics.ts +6 -4
- package/src/web/client/panels/StackPanel.tsx +6 -15
- package/src/web/server/routes.ts +20 -2
- package/src/web/server/stack-manager.ts +3 -0
- package/src/web/styles/built.css +1 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { ChevronRight, GitBranch, ExternalLink, Plus, Minus, GitMerge } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import type { StackGroupStats } from "../../../stack/types.ts";
|
|
4
|
+
|
|
5
|
+
const TYPE_COLORS: Record<string, { dot: string; badge: string; text: string }> = {
|
|
6
|
+
feature: { dot: "bg-blue-500", badge: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
|
|
7
|
+
refactor: { dot: "bg-purple-500", badge: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
|
|
8
|
+
bugfix: { dot: "bg-red-500", badge: "bg-red-500/10", text: "text-red-600 dark:text-red-400" },
|
|
9
|
+
chore: { dot: "bg-neutral-400", badge: "bg-neutral-500/10", text: "text-neutral-500" },
|
|
10
|
+
docs: { dot: "bg-teal-500", badge: "bg-teal-500/10", text: "text-teal-600 dark:text-teal-400" },
|
|
11
|
+
test: { dot: "bg-yellow-500", badge: "bg-yellow-500/10", text: "text-yellow-600 dark:text-yellow-400" },
|
|
12
|
+
config: { dot: "bg-orange-500", badge: "bg-orange-500/10", text: "text-orange-600 dark:text-orange-400" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function formatStat(n: number): string {
|
|
16
|
+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
17
|
+
return String(n);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DagGroup {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
files: string[];
|
|
26
|
+
deps: string[];
|
|
27
|
+
order: number;
|
|
28
|
+
stats?: StackGroupStats;
|
|
29
|
+
pr_title?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface DagCommit {
|
|
33
|
+
group_id: string;
|
|
34
|
+
commit_sha: string;
|
|
35
|
+
branch_name: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DagPr {
|
|
39
|
+
group_id: string;
|
|
40
|
+
number: number;
|
|
41
|
+
url: string;
|
|
42
|
+
title: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface DagNode {
|
|
46
|
+
group: DagGroup;
|
|
47
|
+
level: number;
|
|
48
|
+
isLastAtLevel: boolean;
|
|
49
|
+
parentIds: string[];
|
|
50
|
+
childIds: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildDagNodes(groups: DagGroup[]): DagNode[] {
|
|
54
|
+
const byId = new Map(groups.map((g) => [g.id, g]));
|
|
55
|
+
|
|
56
|
+
const levels = new Map<string, number>();
|
|
57
|
+
const inDegree = new Map(groups.map((g) => [g.id, 0]));
|
|
58
|
+
for (const g of groups) {
|
|
59
|
+
for (const dep of (g.deps ?? [])) {
|
|
60
|
+
if (byId.has(dep)) inDegree.set(g.id, (inDegree.get(g.id) ?? 0) + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const queue = groups.filter((g) => (inDegree.get(g.id) ?? 0) === 0).map((g) => g.id);
|
|
65
|
+
for (const id of queue) levels.set(id, 0);
|
|
66
|
+
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
const id = queue.shift()!;
|
|
69
|
+
const level = levels.get(id) ?? 0;
|
|
70
|
+
for (const g of groups) {
|
|
71
|
+
if ((g.deps ?? []).includes(id)) {
|
|
72
|
+
const newLevel = Math.max(levels.get(g.id) ?? 0, level + 1);
|
|
73
|
+
levels.set(g.id, newLevel);
|
|
74
|
+
const remaining = (inDegree.get(g.id) ?? 1) - 1;
|
|
75
|
+
inDegree.set(g.id, remaining);
|
|
76
|
+
if (remaining === 0) queue.push(g.id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const levelCount = new Map<number, number>();
|
|
82
|
+
const levelSeen = new Map<number, number>();
|
|
83
|
+
for (const [, l] of levels) levelCount.set(l, (levelCount.get(l) ?? 0) + 1);
|
|
84
|
+
|
|
85
|
+
const childrenOf = new Map<string, string[]>();
|
|
86
|
+
for (const g of groups) {
|
|
87
|
+
for (const dep of (g.deps ?? [])) {
|
|
88
|
+
const arr = childrenOf.get(dep) ?? [];
|
|
89
|
+
arr.push(g.id);
|
|
90
|
+
childrenOf.set(dep, arr);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const sorted = [...groups].sort((a, b) => {
|
|
95
|
+
const la = levels.get(a.id) ?? 0;
|
|
96
|
+
const lb = levels.get(b.id) ?? 0;
|
|
97
|
+
if (la !== lb) return la - lb;
|
|
98
|
+
return a.order - b.order;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return sorted.map((g) => {
|
|
102
|
+
const level = levels.get(g.id) ?? 0;
|
|
103
|
+
const seenAtLevel = levelSeen.get(level) ?? 0;
|
|
104
|
+
const countAtLevel = levelCount.get(level) ?? 1;
|
|
105
|
+
levelSeen.set(level, seenAtLevel + 1);
|
|
106
|
+
return {
|
|
107
|
+
group: g,
|
|
108
|
+
level,
|
|
109
|
+
isLastAtLevel: seenAtLevel === countAtLevel - 1,
|
|
110
|
+
parentIds: (g.deps ?? []).filter((d) => byId.has(d)),
|
|
111
|
+
childIds: childrenOf.get(g.id) ?? [],
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
function DagNodeCard({
|
|
118
|
+
node,
|
|
119
|
+
commit,
|
|
120
|
+
pr,
|
|
121
|
+
allGroups,
|
|
122
|
+
}: {
|
|
123
|
+
node: DagNode;
|
|
124
|
+
commit?: DagCommit;
|
|
125
|
+
pr?: DagPr;
|
|
126
|
+
allGroups: DagGroup[];
|
|
127
|
+
}) {
|
|
128
|
+
const [expanded, setExpanded] = useState(false);
|
|
129
|
+
const { group, level } = node;
|
|
130
|
+
const stats = group.stats;
|
|
131
|
+
const colors = TYPE_COLORS[group.type] ?? TYPE_COLORS.chore!;
|
|
132
|
+
|
|
133
|
+
const depNames = (group.deps ?? []).map((depId) => {
|
|
134
|
+
const found = allGroups.find((g) => g.id === depId);
|
|
135
|
+
return found?.pr_title ?? found?.name ?? depId;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const isParallel = node.parentIds.length === 0
|
|
139
|
+
? false
|
|
140
|
+
: allGroups.filter((g) => {
|
|
141
|
+
const gDeps = g.deps ?? [];
|
|
142
|
+
return node.parentIds.every((p) => gDeps.includes(p)) && g.id !== group.id;
|
|
143
|
+
}).length > 0;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="relative" style={{ marginLeft: `${level * 20}px` }}>
|
|
147
|
+
{level > 0 && (
|
|
148
|
+
<div
|
|
149
|
+
className="absolute top-[18px] h-px bg-border/30"
|
|
150
|
+
style={{ left: `-${20 - 9}px`, width: `${20 - 9}px` }}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
<div className="group/card relative">
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
onClick={() => setExpanded(!expanded)}
|
|
158
|
+
className="w-full flex items-start gap-2 px-2.5 py-2 text-left hover:bg-accent/20 transition-colors rounded-md"
|
|
159
|
+
>
|
|
160
|
+
<div className="flex-shrink-0 mt-[5px] flex flex-col items-center gap-[3px]">
|
|
161
|
+
<div className={`h-2 w-2 rounded-full ${colors.dot} ring-2 ring-background`} />
|
|
162
|
+
{node.childIds.length > 1 && (
|
|
163
|
+
<GitMerge className="h-2.5 w-2.5 text-muted-foreground/25 mt-0.5" />
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="flex-1 min-w-0">
|
|
168
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
169
|
+
<span className={`text-[9px] font-medium px-1.5 py-px rounded ${colors.badge} ${colors.text} shrink-0`}>
|
|
170
|
+
{group.type}
|
|
171
|
+
</span>
|
|
172
|
+
{isParallel && (
|
|
173
|
+
<span className="text-[9px] text-muted-foreground/25 shrink-0">∥</span>
|
|
174
|
+
)}
|
|
175
|
+
<span className="text-[11.5px] font-medium text-foreground/90 truncate">
|
|
176
|
+
{group.pr_title ?? group.name}
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{depNames.length > 0 && (
|
|
181
|
+
<div className="flex items-center gap-1 mt-0.5 flex-wrap">
|
|
182
|
+
<span className="text-[9px] text-muted-foreground/20">after</span>
|
|
183
|
+
{depNames.map((name, i) => (
|
|
184
|
+
<span key={i} className="text-[9px] text-muted-foreground/25 font-mono truncate max-w-[120px]">
|
|
185
|
+
{name}
|
|
186
|
+
</span>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div className="flex-shrink-0 flex items-center gap-2 mt-[3px]">
|
|
193
|
+
{stats ? (
|
|
194
|
+
<span className="flex items-center gap-1.5">
|
|
195
|
+
<span className="text-[10px] text-green-600/60 dark:text-green-400/60 tabular-nums">
|
|
196
|
+
+{formatStat(stats.additions)}
|
|
197
|
+
</span>
|
|
198
|
+
<span className="text-[10px] text-red-500/60 tabular-nums">
|
|
199
|
+
−{formatStat(stats.deletions)}
|
|
200
|
+
</span>
|
|
201
|
+
</span>
|
|
202
|
+
) : (
|
|
203
|
+
<span className="text-[9px] text-muted-foreground/20 tabular-nums">{group.files.length}f</span>
|
|
204
|
+
)}
|
|
205
|
+
<ChevronRight className={`h-3 w-3 text-muted-foreground/20 transition-transform duration-150 ${expanded ? "rotate-90" : ""}`} />
|
|
206
|
+
</div>
|
|
207
|
+
</button>
|
|
208
|
+
|
|
209
|
+
{expanded && (
|
|
210
|
+
<div className="ml-4 pl-3 pb-3 pt-1 space-y-2 border-l border-border/30">
|
|
211
|
+
{group.description && (
|
|
212
|
+
<p className="text-[10.5px] text-muted-foreground/45 leading-[1.55]">{group.description}</p>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{stats && (
|
|
216
|
+
<div className="flex items-center gap-3 text-[9.5px] text-muted-foreground/30 tabular-nums">
|
|
217
|
+
<span>{group.files.length} files</span>
|
|
218
|
+
<span className="flex items-center gap-0.5">
|
|
219
|
+
<Plus className="h-2 w-2 text-green-600/50 dark:text-green-400/50" />
|
|
220
|
+
{stats.additions.toLocaleString()}
|
|
221
|
+
</span>
|
|
222
|
+
<span className="flex items-center gap-0.5">
|
|
223
|
+
<Minus className="h-2 w-2 text-red-500/50" />
|
|
224
|
+
{stats.deletions.toLocaleString()}
|
|
225
|
+
</span>
|
|
226
|
+
{(stats.files_added > 0 || stats.files_modified > 0 || stats.files_deleted > 0) && (
|
|
227
|
+
<span>
|
|
228
|
+
{stats.files_added > 0 && `${stats.files_added}A `}
|
|
229
|
+
{stats.files_modified > 0 && `${stats.files_modified}M `}
|
|
230
|
+
{stats.files_deleted > 0 && `${stats.files_deleted}D`}
|
|
231
|
+
</span>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{commit && (
|
|
237
|
+
<div className="flex items-center gap-1.5 text-[9.5px] text-muted-foreground/25">
|
|
238
|
+
<GitBranch className="h-2.5 w-2.5 shrink-0" />
|
|
239
|
+
<span className="font-mono truncate">{commit.branch_name}</span>
|
|
240
|
+
<span className="text-muted-foreground/15">·</span>
|
|
241
|
+
<span className="font-mono shrink-0">{commit.commit_sha.slice(0, 7)}</span>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{pr && (
|
|
246
|
+
<a
|
|
247
|
+
href={pr.url}
|
|
248
|
+
target="_blank"
|
|
249
|
+
rel="noopener noreferrer"
|
|
250
|
+
className="inline-flex items-center gap-1 text-[9.5px] text-foreground/40 hover:text-foreground/70 transition-colors"
|
|
251
|
+
>
|
|
252
|
+
<ExternalLink className="h-2.5 w-2.5 shrink-0" />
|
|
253
|
+
<span className="tabular-nums">#{pr.number}</span>
|
|
254
|
+
<span className="truncate max-w-[180px]">{pr.title}</span>
|
|
255
|
+
</a>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
<div className="space-y-0 pt-0.5">
|
|
259
|
+
{group.files.map((file) => (
|
|
260
|
+
<div key={file} className="text-[9px] font-mono text-muted-foreground/20 py-[1px] truncate">
|
|
261
|
+
{file}
|
|
262
|
+
</div>
|
|
263
|
+
))}
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function StackDagView({
|
|
273
|
+
groups,
|
|
274
|
+
groupCommits,
|
|
275
|
+
publishedPrs,
|
|
276
|
+
}: {
|
|
277
|
+
groups: DagGroup[];
|
|
278
|
+
groupCommits?: DagCommit[];
|
|
279
|
+
publishedPrs?: DagPr[];
|
|
280
|
+
}) {
|
|
281
|
+
const nodes = buildDagNodes(groups);
|
|
282
|
+
const isLinear = nodes.every((n) => n.level === n.group.order);
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className="relative">
|
|
286
|
+
{!isLinear && (
|
|
287
|
+
<div className="flex items-center gap-1.5 mb-2 px-1">
|
|
288
|
+
<GitMerge className="h-3 w-3 text-muted-foreground/25" />
|
|
289
|
+
<span className="text-[9px] text-muted-foreground/25 uppercase tracking-wider">DAG</span>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
|
|
293
|
+
<div className="relative space-y-0.5">
|
|
294
|
+
{nodes.map((node) => {
|
|
295
|
+
const commit = groupCommits?.find((c) => c.group_id === node.group.id);
|
|
296
|
+
const pr = publishedPrs?.find((p) => p.group_id === node.group.id);
|
|
297
|
+
return (
|
|
298
|
+
<DagNodeCard
|
|
299
|
+
key={node.group.id}
|
|
300
|
+
node={node}
|
|
301
|
+
commit={commit}
|
|
302
|
+
pr={pr}
|
|
303
|
+
allGroups={groups}
|
|
304
|
+
/>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
307
|
+
|
|
308
|
+
{nodes.length > 0 && (
|
|
309
|
+
<div
|
|
310
|
+
className="absolute top-[14px] bottom-[14px] w-px bg-border/15 pointer-events-none"
|
|
311
|
+
style={{ left: "8px" }}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
@@ -45,6 +45,7 @@ interface StackGroupCardProps {
|
|
|
45
45
|
description: string;
|
|
46
46
|
files: string[];
|
|
47
47
|
order: number;
|
|
48
|
+
deps?: string[];
|
|
48
49
|
stats?: StackGroupStats;
|
|
49
50
|
pr_title?: string;
|
|
50
51
|
};
|
|
@@ -57,15 +58,28 @@ interface StackGroupCardProps {
|
|
|
57
58
|
url: string;
|
|
58
59
|
title: string;
|
|
59
60
|
};
|
|
61
|
+
allGroups?: Array<{ id: string; name: string; pr_title?: string }>;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
export function StackGroupCard({ group, commit, pr }: StackGroupCardProps) {
|
|
64
|
+
export function StackGroupCard({ group, commit, pr, allGroups }: StackGroupCardProps) {
|
|
63
65
|
const [expanded, setExpanded] = useState(false);
|
|
64
66
|
const stats = group.stats;
|
|
65
67
|
const colors = TYPE_COLORS[group.type] ?? TYPE_COLORS.chore!;
|
|
68
|
+
const hasDeps = (group.deps ?? []).length > 0;
|
|
69
|
+
const depNames = (group.deps ?? []).map((depId) => {
|
|
70
|
+
const found = allGroups?.find((g) => g.id === depId);
|
|
71
|
+
return found?.pr_title ?? found?.name ?? depId;
|
|
72
|
+
});
|
|
66
73
|
|
|
67
74
|
return (
|
|
68
75
|
<div className="group/card">
|
|
76
|
+
{hasDeps && (
|
|
77
|
+
<div className="ml-[26px] px-3 pb-0.5 flex items-center gap-1.5 flex-wrap">
|
|
78
|
+
{depNames.map((name, i) => (
|
|
79
|
+
<span key={i} className="text-[9px] text-muted-foreground/25 font-mono leading-none">↑ {name}</span>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
69
83
|
<button
|
|
70
84
|
type="button"
|
|
71
85
|
onClick={() => setExpanded(!expanded)}
|
|
@@ -8,12 +8,12 @@ declare global {
|
|
|
8
8
|
const GA_ID = "G-L3SL6T6JQ1";
|
|
9
9
|
const CONSENT_KEY = "newpr-analytics-consent";
|
|
10
10
|
|
|
11
|
-
export type ConsentState = "granted" | "denied"
|
|
11
|
+
export type ConsentState = "granted" | "denied";
|
|
12
12
|
|
|
13
13
|
export function getConsent(): ConsentState {
|
|
14
14
|
const stored = localStorage.getItem(CONSENT_KEY);
|
|
15
|
-
if (stored === "
|
|
16
|
-
return "
|
|
15
|
+
if (stored === "denied") return "denied";
|
|
16
|
+
return "granted";
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function setConsent(state: "granted" | "denied"): void {
|
|
@@ -49,7 +49,9 @@ function disableGA(): void {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function initAnalytics(): void {
|
|
52
|
-
if (getConsent() === "
|
|
52
|
+
if (getConsent() === "denied") {
|
|
53
|
+
disableGA();
|
|
54
|
+
} else {
|
|
53
55
|
loadGA();
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Loader2, Play, Upload, RotateCcw, CheckCircle2, AlertTriangle, Circle, GitPullRequestArrow, ArrowRight, Layers, FileText, RefreshCw, XCircle, Trash2 } from "lucide-react";
|
|
2
2
|
import { useStack } from "../hooks/useStack.ts";
|
|
3
3
|
import { FeasibilityAlert } from "../components/FeasibilityAlert.tsx";
|
|
4
|
-
import {
|
|
4
|
+
import { StackDagView } from "../components/StackDagView.tsx";
|
|
5
5
|
import { StackWarnings } from "../components/StackWarnings.tsx";
|
|
6
6
|
|
|
7
7
|
type StackPhase = "idle" | "partitioning" | "planning" | "executing" | "publishing" | "done" | "error";
|
|
@@ -228,20 +228,11 @@ export function StackPanel({ sessionId, onTrackAnalysis }: StackPanelProps) {
|
|
|
228
228
|
</span>
|
|
229
229
|
)}
|
|
230
230
|
</div>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<StackGroupCard
|
|
237
|
-
key={group.id}
|
|
238
|
-
group={group}
|
|
239
|
-
commit={commit}
|
|
240
|
-
pr={pr}
|
|
241
|
-
/>
|
|
242
|
-
);
|
|
243
|
-
})}
|
|
244
|
-
</div>
|
|
231
|
+
<StackDagView
|
|
232
|
+
groups={stack.plan.groups}
|
|
233
|
+
groupCommits={stack.execResult?.group_commits}
|
|
234
|
+
publishedPrs={stack.publishResult?.prs}
|
|
235
|
+
/>
|
|
245
236
|
</div>
|
|
246
237
|
)}
|
|
247
238
|
|
package/src/web/server/routes.ts
CHANGED
|
@@ -1927,7 +1927,16 @@ Before posting an inline comment, ALWAYS call \`get_file_diff\` first to find th
|
|
|
1927
1927
|
base_branch: state.context.base_branch,
|
|
1928
1928
|
owner: state.context.owner,
|
|
1929
1929
|
repo: state.context.repo,
|
|
1930
|
-
plan_groups: state.plan?.groups
|
|
1930
|
+
plan_groups: state.plan?.groups?.map((g) => ({
|
|
1931
|
+
id: g.id,
|
|
1932
|
+
name: g.name,
|
|
1933
|
+
description: g.description,
|
|
1934
|
+
files: g.files,
|
|
1935
|
+
order: g.order,
|
|
1936
|
+
type: g.type,
|
|
1937
|
+
pr_title: g.pr_title,
|
|
1938
|
+
deps: g.deps,
|
|
1939
|
+
})),
|
|
1931
1940
|
llm_client: llmClient,
|
|
1932
1941
|
language: config.language,
|
|
1933
1942
|
publish_preview: state.publishPreview,
|
|
@@ -2070,7 +2079,16 @@ Before posting an inline comment, ALWAYS call \`get_file_diff\` first to find th
|
|
|
2070
2079
|
base_branch: state.context.base_branch,
|
|
2071
2080
|
owner: state.context.owner,
|
|
2072
2081
|
repo: state.context.repo,
|
|
2073
|
-
plan_groups: state.plan?.groups
|
|
2082
|
+
plan_groups: state.plan?.groups?.map((g) => ({
|
|
2083
|
+
id: g.id,
|
|
2084
|
+
name: g.name,
|
|
2085
|
+
description: g.description,
|
|
2086
|
+
files: g.files,
|
|
2087
|
+
order: g.order,
|
|
2088
|
+
type: g.type,
|
|
2089
|
+
pr_title: g.pr_title,
|
|
2090
|
+
deps: g.deps,
|
|
2091
|
+
})),
|
|
2074
2092
|
llm_client: llmClient,
|
|
2075
2093
|
language: config.language,
|
|
2076
2094
|
});
|
|
@@ -629,14 +629,17 @@ async function runStackPipeline(
|
|
|
629
629
|
ownership,
|
|
630
630
|
group_order: feasibility.ordered_group_ids!,
|
|
631
631
|
groups: currentGroups,
|
|
632
|
+
dependency_edges: feasibility.dependency_edges,
|
|
632
633
|
});
|
|
633
634
|
|
|
634
635
|
emit(session, "planning", "Computing group stats...");
|
|
636
|
+
const planDagParents = new Map(plan.groups.map((g) => [g.id, g.deps ?? []]));
|
|
635
637
|
const groupStats = await computeGroupStats(
|
|
636
638
|
repoPath,
|
|
637
639
|
baseSha,
|
|
638
640
|
feasibility.ordered_group_ids!,
|
|
639
641
|
plan.expected_trees,
|
|
642
|
+
planDagParents,
|
|
640
643
|
);
|
|
641
644
|
for (const group of plan.groups) {
|
|
642
645
|
const s = groupStats.get(group.id);
|