diffpanel 0.0.1
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/dist/index.d.ts +408 -0
- package/dist/index.js +394 -0
- package/dist/index.js.map +1 -0
- package/package.json +28 -0
- package/src/format.ts +58 -0
- package/src/hash.ts +15 -0
- package/src/index.ts +6 -0
- package/src/schemas.ts +167 -0
- package/src/title.test.ts +28 -0
- package/src/title.ts +20 -0
- package/src/validation.test.ts +121 -0
- package/src/validation.ts +164 -0
- package/tsconfig.json +9 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// src/format.ts
|
|
2
|
+
var PREVIEW_LIMIT = 24e3;
|
|
3
|
+
function formatGenerationInput(manifest) {
|
|
4
|
+
const lines = [
|
|
5
|
+
"# Diffpanel review generation input",
|
|
6
|
+
"",
|
|
7
|
+
`Run ID: ${manifest.runId}`,
|
|
8
|
+
`Repository: ${manifest.repositoryName}`,
|
|
9
|
+
`Root: ${manifest.repositoryRoot}`,
|
|
10
|
+
`Scope: ${JSON.stringify(manifest.scope)}`,
|
|
11
|
+
`Snapshot: ${manifest.snapshotHash}`,
|
|
12
|
+
`Files: ${manifest.files.length}`,
|
|
13
|
+
`Review items: ${manifest.files.reduce((total, file) => total + file.items.length, 0)}`,
|
|
14
|
+
`Skipped paths: ${manifest.skipped.length} (not covered by review item assignments)`,
|
|
15
|
+
"",
|
|
16
|
+
"Every item ID below must appear in exactly one chapter itemRefs array.",
|
|
17
|
+
"Use repository inspection when the preview is insufficient.",
|
|
18
|
+
""
|
|
19
|
+
];
|
|
20
|
+
if (manifest.requirements?.diagramAssessment) {
|
|
21
|
+
lines.push(
|
|
22
|
+
"## Required visual assessment",
|
|
23
|
+
"Include diagramAssessment: { kind: 'architectural' | 'other', reasoning: string, overviewOmissionReason?: string, chapterDiagramOmissionReason?: string } in the review JSON.",
|
|
24
|
+
"Assess whether this change alters ownership, dependencies, composition, or execution flow. For architectural restructuring, provide a before/after overview and focused chapter diagrams where useful.",
|
|
25
|
+
"Provide prologue.diagram with diagramItemRefs, or a concrete diagramAssessment.overviewOmissionReason. Architectural reviews also need focused chapter diagrams with evidence, or chapterDiagramOmissionReason.",
|
|
26
|
+
"Every diagram needs supporting immutable item refs. Use real newlines in Mermaid, not literal backslash-n text. Do not claim diagrams prove behavior.",
|
|
27
|
+
"Metadata-only file items (including pure moves) must be assigned exactly once, just like hunks. Skipped paths are not captured coverage.",
|
|
28
|
+
""
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
for (const file of manifest.files) {
|
|
32
|
+
lines.push(
|
|
33
|
+
`=== File: ${file.filePath} | status: ${file.status} | +${file.additions} -${file.deletions} ===`,
|
|
34
|
+
...file.oldPath ? [`Previous path: ${file.oldPath}`] : []
|
|
35
|
+
);
|
|
36
|
+
for (const item of file.items) {
|
|
37
|
+
lines.push(
|
|
38
|
+
`--- Item: ${item.id} | kind: ${item.kind} | old: ${item.oldStart ?? "-"},${item.oldLines ?? "-"} | new: ${item.newStart ?? "-"},${item.newLines ?? "-"} ---`
|
|
39
|
+
);
|
|
40
|
+
const patch = item.patch.length > PREVIEW_LIMIT ? `${item.patch.slice(0, PREVIEW_LIMIT)}
|
|
41
|
+
[preview truncated]` : item.patch;
|
|
42
|
+
lines.push(patch, "");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (manifest.skipped.length > 0) {
|
|
46
|
+
lines.push("=== Skipped entries ===");
|
|
47
|
+
for (const entry of manifest.skipped) lines.push(`${entry.filePath}: ${entry.reason}`);
|
|
48
|
+
lines.push("");
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/hash.ts
|
|
54
|
+
import { createHash, randomUUID } from "crypto";
|
|
55
|
+
function sha256(value) {
|
|
56
|
+
return createHash("sha256").update(value).digest("hex");
|
|
57
|
+
}
|
|
58
|
+
function createRunId(now = /* @__PURE__ */ new Date()) {
|
|
59
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
60
|
+
return `${stamp}-${randomUUID().slice(0, 8)}`;
|
|
61
|
+
}
|
|
62
|
+
function stableId(prefix, ...parts) {
|
|
63
|
+
return `${prefix}_${sha256(parts.map((part) => part ?? "").join("\0")).slice(0, 16)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/schemas.ts
|
|
67
|
+
import { z } from "zod";
|
|
68
|
+
var reviewScopeSchema = z.discriminatedUnion("type", [
|
|
69
|
+
z.object({ type: z.literal("worktree"), baseRef: z.string(), baseSha: z.string() }),
|
|
70
|
+
z.object({ type: z.literal("staged"), baseRef: z.string(), baseSha: z.string() }),
|
|
71
|
+
z.object({
|
|
72
|
+
type: z.literal("range"),
|
|
73
|
+
expression: z.string(),
|
|
74
|
+
baseRef: z.string(),
|
|
75
|
+
compareRef: z.string(),
|
|
76
|
+
baseSha: z.string(),
|
|
77
|
+
compareSha: z.string(),
|
|
78
|
+
mergeBase: z.boolean()
|
|
79
|
+
}),
|
|
80
|
+
z.object({
|
|
81
|
+
type: z.literal("repository"),
|
|
82
|
+
ref: z.string(),
|
|
83
|
+
sha: z.string(),
|
|
84
|
+
maxFiles: z.number().int().positive()
|
|
85
|
+
})
|
|
86
|
+
]);
|
|
87
|
+
var fileStatusSchema = z.enum([
|
|
88
|
+
"added",
|
|
89
|
+
"modified",
|
|
90
|
+
"deleted",
|
|
91
|
+
"renamed",
|
|
92
|
+
"copied",
|
|
93
|
+
"unmerged",
|
|
94
|
+
"snapshot",
|
|
95
|
+
"unknown"
|
|
96
|
+
]);
|
|
97
|
+
var reviewItemSchema = z.object({
|
|
98
|
+
id: z.string().min(1),
|
|
99
|
+
kind: z.enum(["hunk", "file"]),
|
|
100
|
+
filePath: z.string().min(1),
|
|
101
|
+
oldPath: z.string().nullable().default(null),
|
|
102
|
+
status: fileStatusSchema,
|
|
103
|
+
ordinal: z.number().int().nonnegative(),
|
|
104
|
+
oldStart: z.number().int().nonnegative().nullable(),
|
|
105
|
+
oldLines: z.number().int().nonnegative().nullable(),
|
|
106
|
+
newStart: z.number().int().nonnegative().nullable(),
|
|
107
|
+
newLines: z.number().int().nonnegative().nullable(),
|
|
108
|
+
patch: z.string(),
|
|
109
|
+
contentHash: z.string().min(1)
|
|
110
|
+
});
|
|
111
|
+
var reviewFileSchema = z.object({
|
|
112
|
+
id: z.string().min(1),
|
|
113
|
+
filePath: z.string().min(1),
|
|
114
|
+
oldPath: z.string().nullable().default(null),
|
|
115
|
+
status: fileStatusSchema,
|
|
116
|
+
beforeBlob: z.string().nullable(),
|
|
117
|
+
afterBlob: z.string().nullable(),
|
|
118
|
+
additions: z.number().int().nonnegative(),
|
|
119
|
+
deletions: z.number().int().nonnegative(),
|
|
120
|
+
language: z.string().nullable(),
|
|
121
|
+
size: z.number().int().nonnegative(),
|
|
122
|
+
items: z.array(reviewItemSchema)
|
|
123
|
+
});
|
|
124
|
+
var skippedEntrySchema = z.object({
|
|
125
|
+
filePath: z.string(),
|
|
126
|
+
reason: z.string()
|
|
127
|
+
});
|
|
128
|
+
var reviewManifestSchema = z.object({
|
|
129
|
+
schemaVersion: z.literal(1),
|
|
130
|
+
runId: z.string().min(1),
|
|
131
|
+
repositoryId: z.string().min(1),
|
|
132
|
+
repositoryRoot: z.string().min(1),
|
|
133
|
+
repositoryName: z.string().min(1),
|
|
134
|
+
createdAt: z.string().datetime(),
|
|
135
|
+
snapshotHash: z.string().min(1),
|
|
136
|
+
scope: reviewScopeSchema,
|
|
137
|
+
files: z.array(reviewFileSchema),
|
|
138
|
+
skipped: z.array(skippedEntrySchema),
|
|
139
|
+
requirements: z.object({ diagramAssessment: z.literal(true) }).optional()
|
|
140
|
+
});
|
|
141
|
+
var lineReferenceSchema = z.object({
|
|
142
|
+
filePath: z.string().min(1),
|
|
143
|
+
side: z.enum(["before", "after"]),
|
|
144
|
+
startLine: z.number().int().positive(),
|
|
145
|
+
endLine: z.number().int().positive()
|
|
146
|
+
}).refine((value) => value.endLine >= value.startLine, {
|
|
147
|
+
message: "endLine must be greater than or equal to startLine",
|
|
148
|
+
path: ["endLine"]
|
|
149
|
+
});
|
|
150
|
+
var keyChangeSchema = z.object({
|
|
151
|
+
content: z.string().min(1),
|
|
152
|
+
lineRefs: z.array(lineReferenceSchema).min(1)
|
|
153
|
+
});
|
|
154
|
+
var chapterSchema = z.object({
|
|
155
|
+
id: z.string().min(1),
|
|
156
|
+
parentId: z.string().min(1).nullable().default(null),
|
|
157
|
+
order: z.number().int().positive(),
|
|
158
|
+
title: z.string().min(1).max(100),
|
|
159
|
+
summary: z.string().min(1),
|
|
160
|
+
diagram: z.string().max(2e4).nullable().optional(),
|
|
161
|
+
diagramItemRefs: z.array(z.string().min(1)).optional(),
|
|
162
|
+
itemRefs: z.array(z.string().min(1)).default([]),
|
|
163
|
+
keyChanges: z.array(keyChangeSchema).default([])
|
|
164
|
+
});
|
|
165
|
+
var focusAreaSchema = z.object({
|
|
166
|
+
type: z.enum([
|
|
167
|
+
"security",
|
|
168
|
+
"breaking-change",
|
|
169
|
+
"high-complexity",
|
|
170
|
+
"data-integrity",
|
|
171
|
+
"new-pattern",
|
|
172
|
+
"architecture",
|
|
173
|
+
"performance",
|
|
174
|
+
"testing-gap"
|
|
175
|
+
]),
|
|
176
|
+
severity: z.enum(["critical", "high", "medium", "info"]),
|
|
177
|
+
title: z.string().min(1),
|
|
178
|
+
description: z.string().min(1),
|
|
179
|
+
locations: z.array(z.string().min(1)).min(1)
|
|
180
|
+
});
|
|
181
|
+
var prologueSchema = z.object({
|
|
182
|
+
motivation: z.string().nullable(),
|
|
183
|
+
outcome: z.string().nullable(),
|
|
184
|
+
diagram: z.string().nullable(),
|
|
185
|
+
diagramItemRefs: z.array(z.string().min(1)).optional(),
|
|
186
|
+
keyChanges: z.array(z.object({
|
|
187
|
+
summary: z.string().min(1),
|
|
188
|
+
description: z.string().min(1)
|
|
189
|
+
})).min(1).max(8),
|
|
190
|
+
focusAreas: z.array(focusAreaSchema).min(1).max(8),
|
|
191
|
+
complexity: z.object({
|
|
192
|
+
level: z.enum(["low", "medium", "high", "very-high"]),
|
|
193
|
+
reasoning: z.string().min(1)
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
var REVIEW_TITLE_MAX_LENGTH = 80;
|
|
197
|
+
var reviewTitleSchema = z.string().trim().min(1).max(REVIEW_TITLE_MAX_LENGTH);
|
|
198
|
+
var generatedReviewSchema = z.object({
|
|
199
|
+
schemaVersion: z.literal(1),
|
|
200
|
+
runId: z.string().min(1),
|
|
201
|
+
generator: z.string().min(1).optional(),
|
|
202
|
+
title: reviewTitleSchema.optional(),
|
|
203
|
+
diagramAssessment: z.object({
|
|
204
|
+
kind: z.enum(["architectural", "other"]),
|
|
205
|
+
reasoning: z.string().trim().min(1),
|
|
206
|
+
overviewOmissionReason: z.string().trim().min(1).optional(),
|
|
207
|
+
chapterDiagramOmissionReason: z.string().trim().min(1).optional()
|
|
208
|
+
}).optional(),
|
|
209
|
+
chapters: z.array(chapterSchema).min(1),
|
|
210
|
+
prologue: prologueSchema
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// src/title.ts
|
|
214
|
+
function parseReviewTitle(value) {
|
|
215
|
+
const result = reviewTitleSchema.safeParse(value);
|
|
216
|
+
if (!result.success) {
|
|
217
|
+
throw new Error(`Review title must be 1 to ${REVIEW_TITLE_MAX_LENGTH} characters.`);
|
|
218
|
+
}
|
|
219
|
+
return result.data;
|
|
220
|
+
}
|
|
221
|
+
function titleForScope(scope) {
|
|
222
|
+
if (scope.type === "worktree") return "Working tree";
|
|
223
|
+
if (scope.type === "staged") return "Staged changes";
|
|
224
|
+
if (scope.type === "range") return scope.expression;
|
|
225
|
+
return `Repository at ${scope.ref}`;
|
|
226
|
+
}
|
|
227
|
+
function displayReviewTitle(title, scope) {
|
|
228
|
+
return title?.trim() || titleForScope(scope);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/validation.ts
|
|
232
|
+
function validateGeneratedReview(manifestInput, reviewInput) {
|
|
233
|
+
const manifestResult = reviewManifestSchema.safeParse(manifestInput);
|
|
234
|
+
const reviewResult = generatedReviewSchema.safeParse(reviewInput);
|
|
235
|
+
const errors = [];
|
|
236
|
+
if (!manifestResult.success) {
|
|
237
|
+
errors.push(...manifestResult.error.issues.map(
|
|
238
|
+
(issue) => `manifest.${issue.path.join(".")}: ${issue.message}`
|
|
239
|
+
));
|
|
240
|
+
}
|
|
241
|
+
if (!reviewResult.success) {
|
|
242
|
+
errors.push(...reviewResult.error.issues.map(
|
|
243
|
+
(issue) => `review.${issue.path.join(".")}: ${issue.message}`
|
|
244
|
+
));
|
|
245
|
+
}
|
|
246
|
+
if (!manifestResult.success || !reviewResult.success) {
|
|
247
|
+
return {
|
|
248
|
+
valid: false,
|
|
249
|
+
errors,
|
|
250
|
+
missingItemRefs: [],
|
|
251
|
+
duplicateItemRefs: [],
|
|
252
|
+
extraItemRefs: []
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const manifest = manifestResult.data;
|
|
256
|
+
const review = reviewResult.data;
|
|
257
|
+
const visualAssessment = review.diagramAssessment;
|
|
258
|
+
const enforceVisuals = !!manifest.requirements?.diagramAssessment || !!visualAssessment;
|
|
259
|
+
const hasOverview = !!review.prologue.diagram?.trim();
|
|
260
|
+
const hasChapterDiagrams = review.chapters.some((chapter) => !!chapter.diagram?.trim());
|
|
261
|
+
if (enforceVisuals) {
|
|
262
|
+
if (!visualAssessment) errors.push("review requires diagramAssessment for this snapshot");
|
|
263
|
+
else {
|
|
264
|
+
if (!hasOverview && !visualAssessment.overviewOmissionReason) errors.push("provide an overview diagram or overviewOmissionReason");
|
|
265
|
+
if (hasOverview && visualAssessment.overviewOmissionReason) errors.push("overview diagram and overviewOmissionReason are mutually exclusive");
|
|
266
|
+
if (visualAssessment.kind === "architectural" && !hasChapterDiagrams && !visualAssessment.chapterDiagramOmissionReason) {
|
|
267
|
+
errors.push("architectural reviews require focused chapter diagrams or chapterDiagramOmissionReason");
|
|
268
|
+
}
|
|
269
|
+
if (hasChapterDiagrams && visualAssessment.chapterDiagramOmissionReason) errors.push("chapter diagrams and chapterDiagramOmissionReason are mutually exclusive");
|
|
270
|
+
}
|
|
271
|
+
if (hasOverview && !review.prologue.diagramItemRefs?.length) errors.push("prologue diagram requires evidence item refs");
|
|
272
|
+
}
|
|
273
|
+
if (manifest.runId !== review.runId) {
|
|
274
|
+
errors.push(`review.runId ${review.runId} does not match manifest runId ${manifest.runId}`);
|
|
275
|
+
}
|
|
276
|
+
const chapterIds = /* @__PURE__ */ new Set();
|
|
277
|
+
const chapterOrders = /* @__PURE__ */ new Set();
|
|
278
|
+
for (const chapter of review.chapters) {
|
|
279
|
+
if (chapterIds.has(chapter.id)) errors.push(`duplicate chapter id: ${chapter.id}`);
|
|
280
|
+
if (chapterOrders.has(chapter.order)) errors.push(`duplicate chapter order: ${chapter.order}`);
|
|
281
|
+
chapterIds.add(chapter.id);
|
|
282
|
+
chapterOrders.add(chapter.order);
|
|
283
|
+
}
|
|
284
|
+
for (const chapter of review.chapters) {
|
|
285
|
+
if (chapter.parentId === chapter.id) errors.push(`chapter ${chapter.id} cannot be its own parent`);
|
|
286
|
+
if (chapter.parentId && !chapterIds.has(chapter.parentId)) {
|
|
287
|
+
errors.push(`chapter ${chapter.id} has unknown parent ${chapter.parentId}`);
|
|
288
|
+
}
|
|
289
|
+
const ancestry = /* @__PURE__ */ new Set([chapter.id]);
|
|
290
|
+
let parentId = chapter.parentId;
|
|
291
|
+
while (parentId) {
|
|
292
|
+
if (ancestry.has(parentId)) {
|
|
293
|
+
errors.push(`chapter hierarchy contains a cycle at ${parentId}`);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
ancestry.add(parentId);
|
|
297
|
+
parentId = review.chapters.find((candidate) => candidate.id === parentId)?.parentId ?? null;
|
|
298
|
+
}
|
|
299
|
+
const hasChildren = review.chapters.some((candidate) => candidate.parentId === chapter.id);
|
|
300
|
+
if (chapter.itemRefs.length === 0 && !hasChildren) {
|
|
301
|
+
errors.push(`chapter ${chapter.id} has no review items or child chapters`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const expected = new Set(manifest.files.flatMap((file) => file.items.map((item) => item.id)));
|
|
305
|
+
const checkDiagramRefs = (label, refs, allowed) => {
|
|
306
|
+
const seen = /* @__PURE__ */ new Set();
|
|
307
|
+
for (const ref of refs ?? []) {
|
|
308
|
+
if (!allowed.has(ref)) errors.push(`${label} diagram references out-of-scope item ${ref}`);
|
|
309
|
+
if (seen.has(ref)) errors.push(`${label} diagram repeats item ${ref}`);
|
|
310
|
+
seen.add(ref);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
checkDiagramRefs("prologue", review.prologue.diagramItemRefs, expected);
|
|
314
|
+
for (const chapter of review.chapters) {
|
|
315
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
316
|
+
const visited = /* @__PURE__ */ new Set();
|
|
317
|
+
const collect = (id) => {
|
|
318
|
+
if (visited.has(id)) return;
|
|
319
|
+
visited.add(id);
|
|
320
|
+
for (const candidate of review.chapters) {
|
|
321
|
+
if (candidate.id === id) {
|
|
322
|
+
for (const ref of candidate.itemRefs) if (expected.has(ref)) allowed.add(ref);
|
|
323
|
+
}
|
|
324
|
+
if (candidate.parentId === id) collect(candidate.id);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
collect(chapter.id);
|
|
328
|
+
checkDiagramRefs(`chapter ${chapter.id}`, chapter.diagramItemRefs, allowed);
|
|
329
|
+
if (chapter.diagram && !chapter.diagramItemRefs?.length) {
|
|
330
|
+
errors.push(`chapter ${chapter.id} diagram requires evidence item refs`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const observed = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const itemRef of review.chapters.flatMap((chapter) => chapter.itemRefs)) {
|
|
335
|
+
observed.set(itemRef, (observed.get(itemRef) ?? 0) + 1);
|
|
336
|
+
}
|
|
337
|
+
const missingItemRefs = [...expected].filter((id) => !observed.has(id)).sort();
|
|
338
|
+
const duplicateItemRefs = [...observed.entries()].filter(([, count]) => count > 1).map(([id]) => id).sort();
|
|
339
|
+
const extraItemRefs = [...observed.keys()].filter((id) => !expected.has(id)).sort();
|
|
340
|
+
if (missingItemRefs.length > 0) errors.push(`missing item refs: ${missingItemRefs.join(", ")}`);
|
|
341
|
+
if (duplicateItemRefs.length > 0) errors.push(`duplicate item refs: ${duplicateItemRefs.join(", ")}`);
|
|
342
|
+
if (extraItemRefs.length > 0) errors.push(`unknown item refs: ${extraItemRefs.join(", ")}`);
|
|
343
|
+
const files = new Set(manifest.files.map((file) => file.filePath));
|
|
344
|
+
for (const chapter of review.chapters) {
|
|
345
|
+
for (const change of chapter.keyChanges) {
|
|
346
|
+
for (const ref of change.lineRefs) {
|
|
347
|
+
if (!files.has(ref.filePath)) {
|
|
348
|
+
errors.push(`chapter ${chapter.id} references unknown file ${ref.filePath}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
valid: errors.length === 0,
|
|
355
|
+
errors,
|
|
356
|
+
missingItemRefs,
|
|
357
|
+
duplicateItemRefs,
|
|
358
|
+
extraItemRefs
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function assertGeneratedReview(manifest, reviewInput) {
|
|
362
|
+
const result = validateGeneratedReview(manifest, reviewInput);
|
|
363
|
+
if (!result.valid) {
|
|
364
|
+
throw new Error(`Generated review validation failed:
|
|
365
|
+
${result.errors.join("\n")}`);
|
|
366
|
+
}
|
|
367
|
+
return generatedReviewSchema.parse(reviewInput);
|
|
368
|
+
}
|
|
369
|
+
export {
|
|
370
|
+
REVIEW_TITLE_MAX_LENGTH,
|
|
371
|
+
assertGeneratedReview,
|
|
372
|
+
chapterSchema,
|
|
373
|
+
createRunId,
|
|
374
|
+
displayReviewTitle,
|
|
375
|
+
fileStatusSchema,
|
|
376
|
+
focusAreaSchema,
|
|
377
|
+
formatGenerationInput,
|
|
378
|
+
generatedReviewSchema,
|
|
379
|
+
keyChangeSchema,
|
|
380
|
+
lineReferenceSchema,
|
|
381
|
+
parseReviewTitle,
|
|
382
|
+
prologueSchema,
|
|
383
|
+
reviewFileSchema,
|
|
384
|
+
reviewItemSchema,
|
|
385
|
+
reviewManifestSchema,
|
|
386
|
+
reviewScopeSchema,
|
|
387
|
+
reviewTitleSchema,
|
|
388
|
+
sha256,
|
|
389
|
+
skippedEntrySchema,
|
|
390
|
+
stableId,
|
|
391
|
+
titleForScope,
|
|
392
|
+
validateGeneratedReview
|
|
393
|
+
};
|
|
394
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/format.ts","../src/hash.ts","../src/schemas.ts","../src/title.ts","../src/validation.ts"],"sourcesContent":["import type { ReviewManifest } from \"./schemas.js\";\n\nconst PREVIEW_LIMIT = 24_000;\n\nexport function formatGenerationInput(manifest: ReviewManifest): string {\n const lines: string[] = [\n \"# Diffpanel review generation input\",\n \"\",\n `Run ID: ${manifest.runId}`,\n `Repository: ${manifest.repositoryName}`,\n `Root: ${manifest.repositoryRoot}`,\n `Scope: ${JSON.stringify(manifest.scope)}`,\n `Snapshot: ${manifest.snapshotHash}`,\n `Files: ${manifest.files.length}`,\n `Review items: ${manifest.files.reduce((total, file) => total + file.items.length, 0)}`,\n `Skipped paths: ${manifest.skipped.length} (not covered by review item assignments)`,\n \"\",\n \"Every item ID below must appear in exactly one chapter itemRefs array.\",\n \"Use repository inspection when the preview is insufficient.\",\n \"\",\n ];\n\n if (manifest.requirements?.diagramAssessment) {\n lines.push(\n \"## Required visual assessment\",\n \"Include diagramAssessment: { kind: 'architectural' | 'other', reasoning: string, overviewOmissionReason?: string, chapterDiagramOmissionReason?: string } in the review JSON.\",\n \"Assess whether this change alters ownership, dependencies, composition, or execution flow. For architectural restructuring, provide a before/after overview and focused chapter diagrams where useful.\",\n \"Provide prologue.diagram with diagramItemRefs, or a concrete diagramAssessment.overviewOmissionReason. Architectural reviews also need focused chapter diagrams with evidence, or chapterDiagramOmissionReason.\",\n \"Every diagram needs supporting immutable item refs. Use real newlines in Mermaid, not literal backslash-n text. Do not claim diagrams prove behavior.\",\n \"Metadata-only file items (including pure moves) must be assigned exactly once, just like hunks. Skipped paths are not captured coverage.\",\n \"\",\n );\n }\n\n for (const file of manifest.files) {\n lines.push(\n `=== File: ${file.filePath} | status: ${file.status} | +${file.additions} -${file.deletions} ===`,\n ...(file.oldPath ? [`Previous path: ${file.oldPath}`] : []),\n );\n for (const item of file.items) {\n lines.push(\n `--- Item: ${item.id} | kind: ${item.kind} | old: ${item.oldStart ?? \"-\"},${item.oldLines ?? \"-\"} | new: ${item.newStart ?? \"-\"},${item.newLines ?? \"-\"} ---`,\n );\n const patch = item.patch.length > PREVIEW_LIMIT\n ? `${item.patch.slice(0, PREVIEW_LIMIT)}\\n[preview truncated]`\n : item.patch;\n lines.push(patch, \"\");\n }\n }\n\n if (manifest.skipped.length > 0) {\n lines.push(\"=== Skipped entries ===\");\n for (const entry of manifest.skipped) lines.push(`${entry.filePath}: ${entry.reason}`);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","import { createHash, randomUUID } from \"node:crypto\";\n\nexport function sha256(value: string | Buffer): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function createRunId(now = new Date()): string {\n const stamp = now.toISOString().replace(/[-:]/g, \"\").replace(/\\.\\d{3}Z$/, \"Z\");\n return `${stamp}-${randomUUID().slice(0, 8)}`;\n}\n\nexport function stableId(prefix: string, ...parts: Array<string | number | null>): string {\n return `${prefix}_${sha256(parts.map((part) => part ?? \"\").join(\"\\0\")).slice(0, 16)}`;\n}\n\n","import { z } from \"zod\";\n\nexport const reviewScopeSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"worktree\"), baseRef: z.string(), baseSha: z.string() }),\n z.object({ type: z.literal(\"staged\"), baseRef: z.string(), baseSha: z.string() }),\n z.object({\n type: z.literal(\"range\"),\n expression: z.string(),\n baseRef: z.string(),\n compareRef: z.string(),\n baseSha: z.string(),\n compareSha: z.string(),\n mergeBase: z.boolean(),\n }),\n z.object({\n type: z.literal(\"repository\"),\n ref: z.string(),\n sha: z.string(),\n maxFiles: z.number().int().positive(),\n }),\n]);\n\nexport const fileStatusSchema = z.enum([\n \"added\",\n \"modified\",\n \"deleted\",\n \"renamed\",\n \"copied\",\n \"unmerged\",\n \"snapshot\",\n \"unknown\",\n]);\n\nexport const reviewItemSchema = z.object({\n id: z.string().min(1),\n kind: z.enum([\"hunk\", \"file\"]),\n filePath: z.string().min(1),\n oldPath: z.string().nullable().default(null),\n status: fileStatusSchema,\n ordinal: z.number().int().nonnegative(),\n oldStart: z.number().int().nonnegative().nullable(),\n oldLines: z.number().int().nonnegative().nullable(),\n newStart: z.number().int().nonnegative().nullable(),\n newLines: z.number().int().nonnegative().nullable(),\n patch: z.string(),\n contentHash: z.string().min(1),\n});\n\nexport const reviewFileSchema = z.object({\n id: z.string().min(1),\n filePath: z.string().min(1),\n oldPath: z.string().nullable().default(null),\n status: fileStatusSchema,\n beforeBlob: z.string().nullable(),\n afterBlob: z.string().nullable(),\n additions: z.number().int().nonnegative(),\n deletions: z.number().int().nonnegative(),\n language: z.string().nullable(),\n size: z.number().int().nonnegative(),\n items: z.array(reviewItemSchema),\n});\n\nexport const skippedEntrySchema = z.object({\n filePath: z.string(),\n reason: z.string(),\n});\n\nexport const reviewManifestSchema = z.object({\n schemaVersion: z.literal(1),\n runId: z.string().min(1),\n repositoryId: z.string().min(1),\n repositoryRoot: z.string().min(1),\n repositoryName: z.string().min(1),\n createdAt: z.string().datetime(),\n snapshotHash: z.string().min(1),\n scope: reviewScopeSchema,\n files: z.array(reviewFileSchema),\n skipped: z.array(skippedEntrySchema),\n requirements: z.object({ diagramAssessment: z.literal(true) }).optional(),\n});\n\nexport const lineReferenceSchema = z.object({\n filePath: z.string().min(1),\n side: z.enum([\"before\", \"after\"]),\n startLine: z.number().int().positive(),\n endLine: z.number().int().positive(),\n}).refine((value) => value.endLine >= value.startLine, {\n message: \"endLine must be greater than or equal to startLine\",\n path: [\"endLine\"],\n});\n\nexport const keyChangeSchema = z.object({\n content: z.string().min(1),\n lineRefs: z.array(lineReferenceSchema).min(1),\n});\n\nexport const chapterSchema = z.object({\n id: z.string().min(1),\n parentId: z.string().min(1).nullable().default(null),\n order: z.number().int().positive(),\n title: z.string().min(1).max(100),\n summary: z.string().min(1),\n diagram: z.string().max(20_000).nullable().optional(),\n diagramItemRefs: z.array(z.string().min(1)).optional(),\n itemRefs: z.array(z.string().min(1)).default([]),\n keyChanges: z.array(keyChangeSchema).default([]),\n});\n\nexport const focusAreaSchema = z.object({\n type: z.enum([\n \"security\",\n \"breaking-change\",\n \"high-complexity\",\n \"data-integrity\",\n \"new-pattern\",\n \"architecture\",\n \"performance\",\n \"testing-gap\",\n ]),\n severity: z.enum([\"critical\", \"high\", \"medium\", \"info\"]),\n title: z.string().min(1),\n description: z.string().min(1),\n locations: z.array(z.string().min(1)).min(1),\n});\n\nexport const prologueSchema = z.object({\n motivation: z.string().nullable(),\n outcome: z.string().nullable(),\n diagram: z.string().nullable(),\n diagramItemRefs: z.array(z.string().min(1)).optional(),\n keyChanges: z.array(z.object({\n summary: z.string().min(1),\n description: z.string().min(1),\n })).min(1).max(8),\n focusAreas: z.array(focusAreaSchema).min(1).max(8),\n complexity: z.object({\n level: z.enum([\"low\", \"medium\", \"high\", \"very-high\"]),\n reasoning: z.string().min(1),\n }),\n});\n\nexport const REVIEW_TITLE_MAX_LENGTH = 80;\n\nexport const reviewTitleSchema = z.string().trim().min(1).max(REVIEW_TITLE_MAX_LENGTH);\n\nexport const generatedReviewSchema = z.object({\n schemaVersion: z.literal(1),\n runId: z.string().min(1),\n generator: z.string().min(1).optional(),\n title: reviewTitleSchema.optional(),\n diagramAssessment: z.object({\n kind: z.enum([\"architectural\", \"other\"]),\n reasoning: z.string().trim().min(1),\n overviewOmissionReason: z.string().trim().min(1).optional(),\n chapterDiagramOmissionReason: z.string().trim().min(1).optional(),\n }).optional(),\n chapters: z.array(chapterSchema).min(1),\n prologue: prologueSchema,\n});\n\nexport type ReviewScope = z.infer<typeof reviewScopeSchema>;\nexport type ReviewItem = z.infer<typeof reviewItemSchema>;\nexport type ReviewFile = z.infer<typeof reviewFileSchema>;\nexport type ReviewManifest = z.infer<typeof reviewManifestSchema>;\nexport type Chapter = z.infer<typeof chapterSchema>;\nexport type GeneratedReview = z.infer<typeof generatedReviewSchema>;\nexport type Prologue = z.infer<typeof prologueSchema>;\n","import { REVIEW_TITLE_MAX_LENGTH, reviewTitleSchema, type ReviewScope } from \"./schemas.js\";\n\nexport function parseReviewTitle(value: string): string {\n const result = reviewTitleSchema.safeParse(value);\n if (!result.success) {\n throw new Error(`Review title must be 1 to ${REVIEW_TITLE_MAX_LENGTH} characters.`);\n }\n return result.data;\n}\n\nexport function titleForScope(scope: ReviewScope): string {\n if (scope.type === \"worktree\") return \"Working tree\";\n if (scope.type === \"staged\") return \"Staged changes\";\n if (scope.type === \"range\") return scope.expression;\n return `Repository at ${scope.ref}`;\n}\n\nexport function displayReviewTitle(title: string | null | undefined, scope: ReviewScope): string {\n return title?.trim() || titleForScope(scope);\n}\n","import type { GeneratedReview, ReviewManifest } from \"./schemas.js\";\nimport { generatedReviewSchema, reviewManifestSchema } from \"./schemas.js\";\n\nexport interface ReviewValidationResult {\n valid: boolean;\n errors: string[];\n missingItemRefs: string[];\n duplicateItemRefs: string[];\n extraItemRefs: string[];\n}\n\nexport function validateGeneratedReview(\n manifestInput: unknown,\n reviewInput: unknown,\n): ReviewValidationResult {\n const manifestResult = reviewManifestSchema.safeParse(manifestInput);\n const reviewResult = generatedReviewSchema.safeParse(reviewInput);\n const errors: string[] = [];\n\n if (!manifestResult.success) {\n errors.push(...manifestResult.error.issues.map((issue) =>\n `manifest.${issue.path.join(\".\")}: ${issue.message}`,\n ));\n }\n if (!reviewResult.success) {\n errors.push(...reviewResult.error.issues.map((issue) =>\n `review.${issue.path.join(\".\")}: ${issue.message}`,\n ));\n }\n\n if (!manifestResult.success || !reviewResult.success) {\n return {\n valid: false,\n errors,\n missingItemRefs: [],\n duplicateItemRefs: [],\n extraItemRefs: [],\n };\n }\n\n const manifest = manifestResult.data;\n const review = reviewResult.data;\n const visualAssessment = review.diagramAssessment;\n const enforceVisuals = !!manifest.requirements?.diagramAssessment || !!visualAssessment;\n const hasOverview = !!review.prologue.diagram?.trim();\n const hasChapterDiagrams = review.chapters.some((chapter) => !!chapter.diagram?.trim());\n if (enforceVisuals) {\n if (!visualAssessment) errors.push(\"review requires diagramAssessment for this snapshot\");\n else {\n if (!hasOverview && !visualAssessment.overviewOmissionReason) errors.push(\"provide an overview diagram or overviewOmissionReason\");\n if (hasOverview && visualAssessment.overviewOmissionReason) errors.push(\"overview diagram and overviewOmissionReason are mutually exclusive\");\n if (visualAssessment.kind === \"architectural\" && !hasChapterDiagrams && !visualAssessment.chapterDiagramOmissionReason) {\n errors.push(\"architectural reviews require focused chapter diagrams or chapterDiagramOmissionReason\");\n }\n if (hasChapterDiagrams && visualAssessment.chapterDiagramOmissionReason) errors.push(\"chapter diagrams and chapterDiagramOmissionReason are mutually exclusive\");\n }\n if (hasOverview && !review.prologue.diagramItemRefs?.length) errors.push(\"prologue diagram requires evidence item refs\");\n }\n if (manifest.runId !== review.runId) {\n errors.push(`review.runId ${review.runId} does not match manifest runId ${manifest.runId}`);\n }\n\n const chapterIds = new Set<string>();\n const chapterOrders = new Set<number>();\n for (const chapter of review.chapters) {\n if (chapterIds.has(chapter.id)) errors.push(`duplicate chapter id: ${chapter.id}`);\n if (chapterOrders.has(chapter.order)) errors.push(`duplicate chapter order: ${chapter.order}`);\n chapterIds.add(chapter.id);\n chapterOrders.add(chapter.order);\n }\n for (const chapter of review.chapters) {\n if (chapter.parentId === chapter.id) errors.push(`chapter ${chapter.id} cannot be its own parent`);\n if (chapter.parentId && !chapterIds.has(chapter.parentId)) {\n errors.push(`chapter ${chapter.id} has unknown parent ${chapter.parentId}`);\n }\n const ancestry = new Set([chapter.id]);\n let parentId = chapter.parentId;\n while (parentId) {\n if (ancestry.has(parentId)) {\n errors.push(`chapter hierarchy contains a cycle at ${parentId}`);\n break;\n }\n ancestry.add(parentId);\n parentId = review.chapters.find((candidate) => candidate.id === parentId)?.parentId ?? null;\n }\n const hasChildren = review.chapters.some((candidate) => candidate.parentId === chapter.id);\n if (chapter.itemRefs.length === 0 && !hasChildren) {\n errors.push(`chapter ${chapter.id} has no review items or child chapters`);\n }\n }\n\n const expected = new Set(manifest.files.flatMap((file) => file.items.map((item) => item.id)));\n const checkDiagramRefs = (label: string, refs: string[] | undefined, allowed: Set<string>): void => {\n const seen = new Set<string>();\n for (const ref of refs ?? []) {\n if (!allowed.has(ref)) errors.push(`${label} diagram references out-of-scope item ${ref}`);\n if (seen.has(ref)) errors.push(`${label} diagram repeats item ${ref}`);\n seen.add(ref);\n }\n };\n checkDiagramRefs(\"prologue\", review.prologue.diagramItemRefs, expected);\n for (const chapter of review.chapters) {\n const allowed = new Set<string>();\n const visited = new Set<string>();\n const collect = (id: string): void => {\n if (visited.has(id)) return;\n visited.add(id);\n for (const candidate of review.chapters) {\n if (candidate.id === id) for (const ref of candidate.itemRefs) if (expected.has(ref)) allowed.add(ref);\n if (candidate.parentId === id) collect(candidate.id);\n }\n };\n collect(chapter.id);\n checkDiagramRefs(`chapter ${chapter.id}`, chapter.diagramItemRefs, allowed);\n if (chapter.diagram && !chapter.diagramItemRefs?.length) {\n errors.push(`chapter ${chapter.id} diagram requires evidence item refs`);\n }\n }\n const observed = new Map<string, number>();\n for (const itemRef of review.chapters.flatMap((chapter) => chapter.itemRefs)) {\n observed.set(itemRef, (observed.get(itemRef) ?? 0) + 1);\n }\n\n const missingItemRefs = [...expected].filter((id) => !observed.has(id)).sort();\n const duplicateItemRefs = [...observed.entries()]\n .filter(([, count]) => count > 1)\n .map(([id]) => id)\n .sort();\n const extraItemRefs = [...observed.keys()].filter((id) => !expected.has(id)).sort();\n\n if (missingItemRefs.length > 0) errors.push(`missing item refs: ${missingItemRefs.join(\", \")}`);\n if (duplicateItemRefs.length > 0) errors.push(`duplicate item refs: ${duplicateItemRefs.join(\", \")}`);\n if (extraItemRefs.length > 0) errors.push(`unknown item refs: ${extraItemRefs.join(\", \")}`);\n\n const files = new Set(manifest.files.map((file) => file.filePath));\n for (const chapter of review.chapters) {\n for (const change of chapter.keyChanges) {\n for (const ref of change.lineRefs) {\n if (!files.has(ref.filePath)) {\n errors.push(`chapter ${chapter.id} references unknown file ${ref.filePath}`);\n }\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n missingItemRefs,\n duplicateItemRefs,\n extraItemRefs,\n };\n}\n\nexport function assertGeneratedReview(\n manifest: ReviewManifest,\n reviewInput: unknown,\n): GeneratedReview {\n const result = validateGeneratedReview(manifest, reviewInput);\n if (!result.valid) {\n throw new Error(`Generated review validation failed:\\n${result.errors.join(\"\\n\")}`);\n }\n return generatedReviewSchema.parse(reviewInput);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diffpanel",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format esm --dts --sourcemap",
|
|
13
|
+
"clean": "rm -rf dist coverage",
|
|
14
|
+
"lint": "tsc --noEmit",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"zod": "^4.1.5"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.15.30",
|
|
23
|
+
"tsup": "^8.5.0",
|
|
24
|
+
"typescript": "^5.8.3",
|
|
25
|
+
"vitest": "^3.2.4"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReviewManifest } from "./schemas.js";
|
|
2
|
+
|
|
3
|
+
const PREVIEW_LIMIT = 24_000;
|
|
4
|
+
|
|
5
|
+
export function formatGenerationInput(manifest: ReviewManifest): string {
|
|
6
|
+
const lines: string[] = [
|
|
7
|
+
"# Diffpanel review generation input",
|
|
8
|
+
"",
|
|
9
|
+
`Run ID: ${manifest.runId}`,
|
|
10
|
+
`Repository: ${manifest.repositoryName}`,
|
|
11
|
+
`Root: ${manifest.repositoryRoot}`,
|
|
12
|
+
`Scope: ${JSON.stringify(manifest.scope)}`,
|
|
13
|
+
`Snapshot: ${manifest.snapshotHash}`,
|
|
14
|
+
`Files: ${manifest.files.length}`,
|
|
15
|
+
`Review items: ${manifest.files.reduce((total, file) => total + file.items.length, 0)}`,
|
|
16
|
+
`Skipped paths: ${manifest.skipped.length} (not covered by review item assignments)`,
|
|
17
|
+
"",
|
|
18
|
+
"Every item ID below must appear in exactly one chapter itemRefs array.",
|
|
19
|
+
"Use repository inspection when the preview is insufficient.",
|
|
20
|
+
"",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
if (manifest.requirements?.diagramAssessment) {
|
|
24
|
+
lines.push(
|
|
25
|
+
"## Required visual assessment",
|
|
26
|
+
"Include diagramAssessment: { kind: 'architectural' | 'other', reasoning: string, overviewOmissionReason?: string, chapterDiagramOmissionReason?: string } in the review JSON.",
|
|
27
|
+
"Assess whether this change alters ownership, dependencies, composition, or execution flow. For architectural restructuring, provide a before/after overview and focused chapter diagrams where useful.",
|
|
28
|
+
"Provide prologue.diagram with diagramItemRefs, or a concrete diagramAssessment.overviewOmissionReason. Architectural reviews also need focused chapter diagrams with evidence, or chapterDiagramOmissionReason.",
|
|
29
|
+
"Every diagram needs supporting immutable item refs. Use real newlines in Mermaid, not literal backslash-n text. Do not claim diagrams prove behavior.",
|
|
30
|
+
"Metadata-only file items (including pure moves) must be assigned exactly once, just like hunks. Skipped paths are not captured coverage.",
|
|
31
|
+
"",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const file of manifest.files) {
|
|
36
|
+
lines.push(
|
|
37
|
+
`=== File: ${file.filePath} | status: ${file.status} | +${file.additions} -${file.deletions} ===`,
|
|
38
|
+
...(file.oldPath ? [`Previous path: ${file.oldPath}`] : []),
|
|
39
|
+
);
|
|
40
|
+
for (const item of file.items) {
|
|
41
|
+
lines.push(
|
|
42
|
+
`--- Item: ${item.id} | kind: ${item.kind} | old: ${item.oldStart ?? "-"},${item.oldLines ?? "-"} | new: ${item.newStart ?? "-"},${item.newLines ?? "-"} ---`,
|
|
43
|
+
);
|
|
44
|
+
const patch = item.patch.length > PREVIEW_LIMIT
|
|
45
|
+
? `${item.patch.slice(0, PREVIEW_LIMIT)}\n[preview truncated]`
|
|
46
|
+
: item.patch;
|
|
47
|
+
lines.push(patch, "");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (manifest.skipped.length > 0) {
|
|
52
|
+
lines.push("=== Skipped entries ===");
|
|
53
|
+
for (const entry of manifest.skipped) lines.push(`${entry.filePath}: ${entry.reason}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
package/src/hash.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function sha256(value: string | Buffer): string {
|
|
4
|
+
return createHash("sha256").update(value).digest("hex");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createRunId(now = new Date()): string {
|
|
8
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
9
|
+
return `${stamp}-${randomUUID().slice(0, 8)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function stableId(prefix: string, ...parts: Array<string | number | null>): string {
|
|
13
|
+
return `${prefix}_${sha256(parts.map((part) => part ?? "").join("\0")).slice(0, 16)}`;
|
|
14
|
+
}
|
|
15
|
+
|