@usepipr/runtime 0.1.2 → 0.2.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.
@@ -1,134 +1,55 @@
1
- import { i as piReadOnlyToolNames, r as piProviderProfileSchema, t as parsePiProviderInvocation } from "./contract-BsL98-bI.mjs";
2
1
  import { c as resolveReadAtRefRequest, l as unavailableReadAtRefResult, n as boundedLineSlice, r as parseManifestPath, u as createDiffRangeIndex } from "./runtime-tools-core-CW1xenzy.mjs";
3
2
  import { createRequire } from "node:module";
4
3
  import { chmod, cp, lstat, mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
5
4
  import path from "node:path";
6
5
  import { z } from "zod";
7
- import { buildPiprPlan, embeddedSdkDeclaration, isBuiltinReadOnlyTool, isPiprConfigFactory, readSdkDeclarationSourceWithChunk, renderPromptValue, reviewOutputSchemaId } from "@usepipr/sdk/internal";
8
- import { parseReviewResult, reviewFindingSchema, reviewResultSchema, reviewSchemaExample } from "@usepipr/sdk/review";
6
+ import { parseReviewResult, reviewFindingSchema, reviewResultSchema, reviewSchemaExample } from "@usepipr/sdk";
7
+ import { buildPiprPlan, commandPatternParts, embeddedSdkDeclaration, isBuiltinReadOnlyTool, isCommandCaptureToken, isCommandRestCaptureToken, isOptionalCommandPatternPart, isPiprConfigFactory, readSdkDeclarationSourceWithChunk, renderPromptValue, reviewOutputSchemaId, tokenizeCommandPattern, unsupportedCommandRestCaptureError } from "@usepipr/sdk/internal";
9
8
  import os from "node:os";
10
9
  import { fileURLToPath, pathToFileURL } from "node:url";
11
- import { Buffer as Buffer$1 } from "node:buffer";
12
10
  import { compact, isPlainObject, uniq, uniqBy } from "lodash-es";
13
- import { spawn } from "node:child_process";
14
- import pino from "pino";
15
11
  import picomatch from "picomatch";
12
+ import { Buffer as Buffer$1 } from "node:buffer";
13
+ import { spawn } from "node:child_process";
16
14
  import { Octokit } from "@octokit/rest";
17
15
  import { existsSync, mkdirSync } from "node:fs";
18
- //#region src/commands/grammar.ts
19
- const piprCommandPrefix = "@pipr";
20
- function firstNonEmptyLine(value) {
21
- return value.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
22
- }
23
- function isPiprCommandLine(line) {
24
- return line === piprCommandPrefix || line.startsWith(`${piprCommandPrefix} `);
25
- }
26
- function parseCommandPattern(pattern, line) {
27
- const patternParts = patternPartsFor(pattern);
28
- const validationError = unsupportedRestCaptureError(patternParts);
29
- if (validationError) return {
30
- ok: false,
31
- error: validationError
32
- };
33
- const lineTokens = tokenize(line);
34
- const captures = {};
35
- let index = 0;
36
- for (const part of patternParts) {
37
- if (isOptionalPart(part)) {
38
- const nextIndex = parseOptionalPatternPart(part.slice(1, -1), lineTokens, index, captures);
39
- if (nextIndex !== void 0) index = nextIndex;
40
- continue;
41
- }
42
- if (isRestCaptureToken(part)) {
43
- const value = lineTokens.slice(index).join(" ");
44
- if (!value) return {
45
- ok: false,
46
- error: `Expected '${part}'`
47
- };
48
- captures[part.slice(1, -4)] = value;
49
- index = lineTokens.length;
50
- continue;
51
- }
52
- const nextIndex = parsePatternToken(part, lineTokens, index, captures);
53
- if (nextIndex === void 0) return {
54
- ok: false,
55
- error: `Expected '${part}'`
56
- };
57
- index = nextIndex;
58
- }
59
- if (index !== lineTokens.length) return {
60
- ok: false,
61
- error: `Unexpected argument '${lineTokens[index]}'`
62
- };
63
- return {
64
- ok: true,
65
- value: captures
66
- };
67
- }
68
- function commandPatternPrefixMatches(pattern, line) {
69
- const patternTokens = patternPartsFor(pattern);
70
- if (unsupportedRestCaptureError(patternTokens)) return false;
71
- const lineTokens = tokenize(line);
72
- let index = 0;
73
- for (const token of patternTokens) {
74
- if (isCaptureToken(token) || isOptionalPart(token)) return true;
75
- if (lineTokens[index] !== token) return false;
76
- index += 1;
77
- }
78
- return true;
79
- }
80
- function parseOptionalPatternPart(pattern, lineTokens, startIndex, captures) {
81
- const patternTokens = tokenize(pattern);
82
- if (patternTokens.length === 0 || lineTokens[startIndex] !== patternTokens[0]) return;
83
- const snapshot = { ...captures };
84
- let index = startIndex;
85
- for (const token of patternTokens) {
86
- const nextIndex = parsePatternToken(token, lineTokens, index, captures);
87
- if (nextIndex === void 0) {
88
- for (const key of Object.keys(captures)) delete captures[key];
89
- Object.assign(captures, snapshot);
90
- return;
91
- }
92
- index = nextIndex;
93
- }
94
- return index;
95
- }
96
- function parsePatternToken(patternToken, lineTokens, index, captures) {
97
- if (isCaptureToken(patternToken)) {
98
- const value = lineTokens[index];
99
- if (!value) return;
100
- captures[patternToken.slice(1, -1)] = value;
101
- return index + 1;
102
- }
103
- return lineTokens[index] === patternToken ? index + 1 : void 0;
16
+ //#region src/config/config-deps.ts
17
+ const runtimeProvidedPackages = new Set(["@usepipr/sdk", "@types/bun"]);
18
+ async function installConfigDependencies(configDir, options = {}) {
19
+ const packageJsonPath = path.join(configDir, "package.json");
20
+ if (!await fileExists$1(packageJsonPath)) return;
21
+ if (shouldSkipConfigInstall(JSON.parse(await Bun.file(packageJsonPath).text()))) return;
22
+ const bunLockPath = path.join(configDir, "bun.lock");
23
+ if (options.frozen && !await fileExists$1(bunLockPath)) throw new Error(`${configDir}: bun.lock is required when .pipr/package.json declares dependencies. Run \`bun install\` in .pipr/ and commit bun.lock.`);
24
+ await assertBunAvailable();
25
+ const args = ["install", "--ignore-scripts"];
26
+ if (options.frozen) args.push("--frozen-lockfile");
27
+ const proc = Bun.spawn(["bun", ...args], {
28
+ cwd: configDir,
29
+ env: process.env,
30
+ stdout: "pipe",
31
+ stderr: "pipe"
32
+ });
33
+ const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
34
+ if (exitCode !== 0) throw new Error(`${configDir}: bun install failed (exit ${exitCode}).` + (stderr.trim().length > 0 ? `\n${stderr.trim()}` : ""));
104
35
  }
105
- function patternPartsFor(pattern) {
106
- return pattern.match(/\[[^\]]+\]|[^\s]+/g) ?? [];
36
+ function shouldSkipConfigInstall(manifest) {
37
+ const packages = [...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {})];
38
+ return packages.length === 0 || packages.every((name) => runtimeProvidedPackages.has(name));
107
39
  }
108
- function unsupportedRestCaptureError(parts) {
109
- for (const [index, part] of parts.entries()) {
110
- if (isOptionalPart(part)) {
111
- const optionalRest = tokenize(part.slice(1, -1)).find(isRestCaptureToken);
112
- if (optionalRest) return finalRequiredRestCaptureError(optionalRest);
113
- continue;
114
- }
115
- if (isRestCaptureToken(part) && index !== parts.length - 1) return finalRequiredRestCaptureError(part);
40
+ async function assertBunAvailable() {
41
+ try {
42
+ if (await Bun.spawn(["bun", "--version"], {
43
+ env: process.env,
44
+ stdout: "pipe",
45
+ stderr: "pipe"
46
+ }).exited !== 0) throw new Error("bun is required on PATH to install .pipr/package.json dependencies. Install Bun from https://bun.sh");
47
+ } catch {
48
+ throw new Error("bun is required on PATH to install .pipr/package.json dependencies. Install Bun from https://bun.sh");
116
49
  }
117
50
  }
118
- function finalRequiredRestCaptureError(token) {
119
- return `Rest capture '${token}' must be the final required command pattern token`;
120
- }
121
- function isOptionalPart(value) {
122
- return value.startsWith("[") && value.endsWith("]");
123
- }
124
- function isCaptureToken(value) {
125
- return /^<[a-z0-9-]+(\.\.\.)?>$/.test(value);
126
- }
127
- function isRestCaptureToken(value) {
128
- return /^<[a-z0-9-]+\.\.\.>$/.test(value);
129
- }
130
- function tokenize(value) {
131
- return value.trim().split(/\s+/).filter(Boolean);
51
+ async function fileExists$1(filePath) {
52
+ return await Bun.file(filePath).exists();
132
53
  }
133
54
  //#endregion
134
55
  //#region src/config/paths.ts
@@ -153,46 +74,86 @@ function toGitPath(filePath) {
153
74
  return filePath === "." ? "." : filePath.split(path.sep).join("/");
154
75
  }
155
76
  //#endregion
156
- //#region src/review/contract.ts
157
- const prReviewSchemaId = reviewOutputSchemaId;
158
- const prReviewSchema = reviewResultSchema;
159
- z.toJSONSchema(prReviewSchema);
160
- function parsePrReview(value) {
161
- return prReviewSchema.parse(parseReviewResult(value));
77
+ //#region src/pi/contract.ts
78
+ const piThinkingLevels = [
79
+ "off",
80
+ "minimal",
81
+ "low",
82
+ "medium",
83
+ "high",
84
+ "xhigh"
85
+ ];
86
+ const piBuiltinToolNames = [
87
+ "read",
88
+ "bash",
89
+ "edit",
90
+ "write",
91
+ "grep",
92
+ "find",
93
+ "ls"
94
+ ];
95
+ const piReadOnlyToolNames = [
96
+ "read",
97
+ "grep",
98
+ "find",
99
+ "ls"
100
+ ];
101
+ const piRequiredCliFlags = [
102
+ "--provider",
103
+ "--model",
104
+ "--system-prompt",
105
+ "--mode",
106
+ "--print",
107
+ "--no-session",
108
+ "--session-dir",
109
+ "--tools",
110
+ "--extension",
111
+ "--no-context-files",
112
+ "--no-approve",
113
+ "--no-extensions",
114
+ "--no-skills",
115
+ "--no-prompt-templates",
116
+ "--no-themes",
117
+ "--thinking"
118
+ ];
119
+ const nonEmptyStringSchema$1 = z.string().min(1);
120
+ const piProviderIdSchema = z.string().regex(/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/);
121
+ const piApiKeyEnvNameSchema = z.string().regex(/^[A-Z_][A-Z0-9_]*$/);
122
+ const piThinkingLevelSchema = z.enum(piThinkingLevels);
123
+ const piReadOnlyToolNamesSchema = z.tuple([
124
+ z.literal("read"),
125
+ z.literal("grep"),
126
+ z.literal("find"),
127
+ z.literal("ls")
128
+ ]);
129
+ const piProviderProfileSchema = z.strictObject({
130
+ id: piProviderIdSchema,
131
+ provider: nonEmptyStringSchema$1,
132
+ model: nonEmptyStringSchema$1,
133
+ apiKeyEnv: piApiKeyEnvNameSchema,
134
+ thinking: piThinkingLevelSchema.optional()
135
+ });
136
+ const piProviderInvocationSchema = z.strictObject({
137
+ provider: nonEmptyStringSchema$1,
138
+ model: nonEmptyStringSchema$1,
139
+ apiKeyEnv: piApiKeyEnvNameSchema,
140
+ thinking: piThinkingLevelSchema,
141
+ tools: piReadOnlyToolNamesSchema
142
+ });
143
+ function parsePiProviderInvocation(value) {
144
+ return piProviderInvocationSchema.parse(value);
162
145
  }
146
+ //#endregion
147
+ //#region src/review/contract.ts
148
+ const reviewResultSchemaId = reviewOutputSchemaId;
149
+ z.toJSONSchema(reviewResultSchema);
163
150
  function reviewSchemaExample$1() {
164
- return parsePrReview(reviewSchemaExample());
151
+ return parseReviewResult(reviewSchemaExample());
165
152
  }
166
153
  //#endregion
167
154
  //#region src/types.ts
168
155
  const nonEmptyStringSchema = z.string().min(1);
169
156
  const providerConfigSchema = piProviderProfileSchema;
170
- const pathGlobPatternSchema = z.string().min(1).superRefine((pattern, context) => {
171
- if (pattern.includes("\0")) context.addIssue({
172
- code: "custom",
173
- message: "must not contain NUL bytes"
174
- });
175
- if (pattern.includes("\\")) context.addIssue({
176
- code: "custom",
177
- message: "must use POSIX '/' separators"
178
- });
179
- if (pattern.startsWith("/") || /^[A-Za-z]:\//.test(pattern)) context.addIssue({
180
- code: "custom",
181
- message: "must be repo-relative"
182
- });
183
- if (pattern.startsWith("!")) context.addIssue({
184
- code: "custom",
185
- message: "must use paths.exclude instead of negation"
186
- });
187
- if (pattern.split("/").includes("..")) context.addIssue({
188
- code: "custom",
189
- message: "must not contain '..' segments"
190
- });
191
- });
192
- z.strictObject({
193
- include: z.array(pathGlobPatternSchema).min(1).optional(),
194
- exclude: z.array(pathGlobPatternSchema).min(1).optional()
195
- });
196
157
  const diffManifestLimitsConfigSchema = z.strictObject({
197
158
  fullMaxBytes: z.number().int().positive().optional(),
198
159
  fullMaxEstimatedTokens: z.number().int().positive().optional(),
@@ -330,7 +291,7 @@ const droppedFindingSchema = z.strictObject({
330
291
  reason: nonEmptyStringSchema
331
292
  });
332
293
  const validatedReviewSchema = z.strictObject({
333
- review: prReviewSchema,
294
+ review: reviewResultSchema,
334
295
  validFindings: z.array(reviewFindingSchema),
335
296
  droppedFindings: z.array(droppedFindingSchema)
336
297
  });
@@ -368,64 +329,64 @@ function embeddedSdkAssets() {
368
329
  };
369
330
  }
370
331
  //#endregion
371
- //#region src/config/type-support.ts
372
- const starterTsconfig = `{
373
- "compilerOptions": {
374
- "strict": true,
375
- "noEmit": true,
376
- "target": "ES2022",
377
- "module": "ESNext",
378
- "moduleResolution": "Bundler"
379
- },
380
- "include": ["./**/*.ts"]
332
+ //#region src/config/sdk-module.ts
333
+ function resolvedSdkModulePath() {
334
+ try {
335
+ return fileURLToPath(import.meta.resolve("@usepipr/sdk"));
336
+ } catch {
337
+ return;
338
+ }
381
339
  }
382
- `;
383
- async function generatedTypeSupportFiles(relativeConfigDir, options = {}) {
384
- const files = [];
385
- if (options.tsconfig !== false) files.push({
386
- relativePath: path.join(relativeConfigDir, "tsconfig.json"),
387
- contents: starterTsconfig
388
- });
389
- files.push({
390
- relativePath: path.join(relativeConfigDir, "types", "pipr-sdk.d.ts"),
391
- contents: await sdkDeclaration()
392
- });
393
- return files;
340
+ function resolvedSdkPackageRoot() {
341
+ return sdkPackageRootFromResolvedModule(resolvedSdkModulePath());
394
342
  }
395
- async function writeGeneratedTypeSupport(configDir, options = {}) {
396
- for (const file of await generatedTypeSupportFiles("", options)) {
397
- const target = path.join(configDir, file.relativePath);
398
- await mkdir(path.dirname(target), { recursive: true });
399
- await Bun.write(target, file.contents);
400
- }
343
+ function sdkPackageRootFromResolvedModule(modulePath) {
344
+ if (!modulePath) return;
345
+ const moduleDir = path.dirname(modulePath);
346
+ return path.basename(moduleDir) === "src" || path.basename(moduleDir) === "dist" ? path.dirname(moduleDir) : moduleDir;
347
+ }
348
+ function sdkModuleStubSource(modulePath, embeddedModule) {
349
+ if (modulePath) return `export * from ${JSON.stringify(pathToFileURL(modulePath).href)};\n`;
350
+ if (embeddedModule) return embeddedModule;
351
+ throw new Error("Unable to locate @usepipr/sdk runtime module");
352
+ }
353
+ //#endregion
354
+ //#region src/config/sdk-stub.ts
355
+ const sdkDeclarationModules = [{
356
+ moduleName: "@usepipr/sdk",
357
+ fileName: "index.d.mts"
358
+ }];
359
+ async function installTypedSdkStub(configDir) {
360
+ const sdkRoot = path.join(configDir, "node_modules", "@usepipr", "sdk");
361
+ await rm(sdkRoot, {
362
+ recursive: true,
363
+ force: true
364
+ });
365
+ await mkdir(sdkRoot, { recursive: true });
366
+ await Bun.write(path.join(sdkRoot, "package.json"), JSON.stringify({
367
+ type: "module",
368
+ types: "./index.d.ts",
369
+ exports: { ".": {
370
+ types: "./index.d.ts",
371
+ default: "./index.mjs"
372
+ } }
373
+ }));
374
+ await Bun.write(path.join(sdkRoot, "index.mjs"), sdkModuleStubSource(resolvedSdkModulePath(), embeddedSdkAssets().module));
375
+ await Bun.write(path.join(sdkRoot, "index.d.ts"), await sdkStubDeclaration());
401
376
  }
402
- async function sdkDeclaration() {
377
+ async function sdkStubDeclaration() {
403
378
  const embedded = embeddedSdkAssets().declaration;
404
379
  if (embedded?.includes("declare module \"@usepipr/sdk\"")) {
405
380
  assertStandaloneSdkDeclaration(embedded);
406
- return embedded;
381
+ return embedded.endsWith("\n") ? embedded : `${embedded}\n`;
407
382
  }
408
383
  const declaration = embeddedSdkDeclaration(await rawSdkDeclarations());
409
384
  assertStandaloneSdkDeclaration(declaration);
410
- return declaration;
385
+ return declaration.endsWith("\n") ? declaration : `${declaration}\n`;
411
386
  }
412
387
  function assertStandaloneSdkDeclaration(declaration) {
413
388
  if (declaration.includes("from \"zod\"") || declaration.includes("z.ZodType")) throw new Error("generated SDK declaration must be standalone and must not import zod");
414
389
  }
415
- const sdkDeclarationModules = [
416
- {
417
- moduleName: "@usepipr/sdk",
418
- fileName: "index.d.mts"
419
- },
420
- {
421
- moduleName: "@usepipr/sdk/review",
422
- fileName: "review.d.mts"
423
- },
424
- {
425
- moduleName: "@usepipr/sdk/tools",
426
- fileName: "tools.d.mts"
427
- }
428
- ];
429
390
  async function rawSdkDeclarations() {
430
391
  const declarations = await Promise.all(sdkDeclarationModules.map(async (module) => {
431
392
  const declarationPath = await sdkDeclarationPath(module.fileName);
@@ -440,13 +401,27 @@ async function rawSdkDeclarations() {
440
401
  moduleName: sdkDeclarationModules[0].moduleName,
441
402
  source: embedded
442
403
  }];
443
- throw new Error("Unable to locate @usepipr/sdk declaration file. Build @usepipr/sdk before pipr init.");
404
+ throw new Error("Unable to locate @usepipr/sdk declaration file. Build @usepipr/sdk before loading config.");
444
405
  }
445
406
  async function sdkDeclarationPath(fileName) {
446
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
447
- const candidates = [path.resolve(moduleDir, "../../sdk/dist", fileName), path.resolve(moduleDir, "../../../sdk/dist", fileName)];
448
- for (const candidate of candidates) if (await Bun.file(candidate).exists()) return candidate;
407
+ const sdkRoot = resolvedSdkPackageRoot();
408
+ if (!sdkRoot) return;
409
+ const candidate = path.join(sdkRoot, "dist", fileName);
410
+ return await Bun.file(candidate).exists() ? candidate : void 0;
411
+ }
412
+ //#endregion
413
+ //#region src/config/starter-tsconfig.ts
414
+ const starterTsconfig = `{
415
+ "compilerOptions": {
416
+ "strict": true,
417
+ "noEmit": true,
418
+ "target": "ES2022",
419
+ "module": "ESNext",
420
+ "moduleResolution": "Bundler"
421
+ },
422
+ "include": ["./**/*.ts"]
449
423
  }
424
+ `;
450
425
  //#endregion
451
426
  //#region src/config/ts-loader.ts
452
427
  async function loadTypescriptConfig(options) {
@@ -457,13 +432,8 @@ async function loadTypescriptConfig(options) {
457
432
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-config-"));
458
433
  try {
459
434
  const tempConfigDir = path.join(tempRoot, relativeConfigDir);
460
- await cp(projectDir, tempConfigDir, {
461
- recursive: true,
462
- errorOnExist: false,
463
- force: true,
464
- filter: (source) => !isIgnoredConfigCopyPath(source, projectDir)
465
- });
466
- await installSdkStub(tempConfigDir);
435
+ await copyConfigDirectory(projectDir, tempConfigDir);
436
+ await prepareConfigDirectory(tempConfigDir, { frozen: true });
467
437
  const factory = (await import(`${pathToFileURL(path.join(tempConfigDir, "config.ts")).href}?pipr=${Date.now()}`)).default;
468
438
  if (!isPiprConfigFactory(factory)) throw new Error(`${sourceConfigPath}: default export must be created by definePipr()`);
469
439
  return {
@@ -478,27 +448,15 @@ async function loadTypescriptConfig(options) {
478
448
  });
479
449
  }
480
450
  }
481
- async function installSdkStub(configDir) {
482
- const sdkRoot = path.join(configDir, "node_modules", "@usepipr", "sdk");
483
- await mkdir(sdkRoot, { recursive: true });
484
- await Bun.write(path.join(sdkRoot, "package.json"), JSON.stringify({
485
- type: "module",
486
- exports: {
487
- ".": "./index.mjs",
488
- "./review": "./review.mjs",
489
- "./tools": "./tools.mjs"
490
- }
491
- }));
492
- await Bun.write(path.join(sdkRoot, "index.mjs"), await sdkStubSource());
493
- await Bun.write(path.join(sdkRoot, "review.mjs"), "export * from \"./index.mjs\";\n");
494
- await Bun.write(path.join(sdkRoot, "tools.mjs"), "export * from \"./index.mjs\";\n");
451
+ async function prepareConfigDirectory(configDir, options = {}) {
452
+ await installConfigDependencies(configDir, options);
453
+ await installTypedSdkStub(configDir);
495
454
  }
496
455
  async function typecheckTypescriptConfig(rootDir, relativeConfigDir) {
497
456
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-config-check-"));
498
457
  try {
499
458
  const tempProjectDir = path.join(tempRoot, "project");
500
459
  const tempConfigDir = path.join(tempProjectDir, relativeConfigDir);
501
- const configTypesPath = path.join(relativeConfigDir, "types");
502
460
  await cp(rootDir, tempProjectDir, {
503
461
  recursive: true,
504
462
  errorOnExist: false,
@@ -506,11 +464,15 @@ async function typecheckTypescriptConfig(rootDir, relativeConfigDir) {
506
464
  filter: (source) => {
507
465
  const relative = path.relative(rootDir, source);
508
466
  const first = relative.split(path.sep)[0] ?? "";
509
- return !ignoredTypecheckRootEntries.has(first) && relative !== "bun.lock" && relative !== configTypesPath && !relative.startsWith(`${configTypesPath}${path.sep}`);
467
+ return !ignoredTypecheckRootEntries.has(first) && relative !== "bun.lock";
510
468
  }
511
469
  });
470
+ await prepareConfigDirectory(tempConfigDir, { frozen: true });
512
471
  const tsconfigPath = path.join(tempConfigDir, "tsconfig.json");
513
- await writeGeneratedTypeSupport(tempConfigDir, { tsconfig: !await fileExists(tsconfigPath) });
472
+ if (!await fileExists(tsconfigPath)) {
473
+ await mkdir(tempConfigDir, { recursive: true });
474
+ await Bun.write(tsconfigPath, starterTsconfig);
475
+ }
514
476
  await typecheckTypescriptConfigWithApi(tempConfigDir, tsconfigPath);
515
477
  } finally {
516
478
  await rm(tempRoot, {
@@ -524,28 +486,16 @@ async function typecheckTypescriptConfigWithApi(configDir, tsconfigPath) {
524
486
  const config = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
525
487
  if (config.error) throw new Error(formatTypeScriptDiagnostics(ts, [config.error], configDir));
526
488
  const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, configDir);
527
- const typeRoot = path.join(configDir, "types");
528
489
  const bundledTypeRoots = [];
529
- try {
490
+ if (!await fileExists(path.join(configDir, "node_modules", "@types", "bun", "package.json"))) try {
530
491
  const require = createRequire(import.meta.url);
531
492
  bundledTypeRoots.push(path.dirname(path.dirname(require.resolve("@types/bun/package.json"))));
532
493
  } catch {}
533
- const fileNames = [path.join(configDir, "config.ts"), ...parsed.fileNames.filter((fileName) => {
534
- const relative = path.relative(typeRoot, fileName);
535
- return relative.startsWith("..") || path.isAbsolute(relative);
536
- })];
537
- const program = ts.createProgram(fileNames, {
494
+ const configPath = path.join(configDir, "config.ts");
495
+ const program = ts.createProgram([configPath, ...parsed.fileNames], {
538
496
  ...parsed.options,
539
- typeRoots: [...new Set([
540
- typeRoot,
541
- ...bundledTypeRoots,
542
- ...parsed.options.typeRoots ?? []
543
- ])],
544
- types: [...new Set([
545
- ...parsed.options.types ?? [],
546
- "pipr-sdk",
547
- ...bundledTypeRoots.length ? ["bun"] : []
548
- ])]
497
+ typeRoots: [...new Set([...parsed.options.typeRoots ?? [], ...bundledTypeRoots])],
498
+ types: [...new Set([...parsed.options.types ?? [], ...bundledTypeRoots.length ? ["bun"] : []])]
549
499
  });
550
500
  const diagnostics = [...parsed.errors, ...ts.getPreEmitDiagnostics(program)];
551
501
  if (diagnostics.length > 0) throw new Error(`TypeScript config check failed for ${path.join(configDir, "config.ts")}:\n` + formatTypeScriptDiagnostics(ts, diagnostics, configDir));
@@ -557,26 +507,17 @@ function formatTypeScriptDiagnostics(ts, diagnostics, configDir) {
557
507
  getNewLine: () => "\n"
558
508
  });
559
509
  }
560
- async function sdkStubSource() {
561
- const sourcePath = await sdkSourcePath();
562
- if (sourcePath) return `export * from ${JSON.stringify(pathToFileURL(sourcePath).href)};\n`;
563
- const embedded = embeddedSdkAssets().module;
564
- if (embedded) return embedded;
565
- throw new Error("Unable to locate @usepipr/sdk runtime module");
566
- }
567
- async function sdkSourcePath() {
568
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
569
- const candidates = [
570
- path.resolve(moduleDir, "../../../sdk/src/index.ts"),
571
- path.resolve(moduleDir, "../../sdk/src/index.ts"),
572
- path.resolve(moduleDir, "../../../sdk/dist/index.mjs"),
573
- path.resolve(moduleDir, "../../sdk/dist/index.mjs")
574
- ];
575
- for (const candidate of candidates) if (await fileExists(candidate)) return candidate;
510
+ async function copyConfigDirectory(sourceDir, targetDir) {
511
+ await cp(sourceDir, targetDir, {
512
+ recursive: true,
513
+ errorOnExist: false,
514
+ force: true,
515
+ filter: (source) => !isIgnoredConfigCopyPath(source, sourceDir)
516
+ });
576
517
  }
577
518
  function isIgnoredConfigCopyPath(source, configDir) {
578
519
  const relative = path.relative(configDir, source);
579
- return relative === "node_modules" || relative.startsWith(`node_modules${path.sep}`) || relative === "bun.lock";
520
+ return relative === "node_modules" || relative.startsWith(`node_modules${path.sep}`);
580
521
  }
581
522
  const ignoredTypecheckRootEntries = new Set([
582
523
  ".fallow",
@@ -747,6 +688,8 @@ export default definePipr((pipr) => {
747
688
  options: { thinking: "medium" },
748
689
  });
749
690
 
691
+ pipr.config({ publication: { maxInlineComments: 8 } });
692
+
750
693
  const reviewer = pipr.reviewer({
751
694
  name: "bug-hunter",
752
695
  model: primary,
@@ -765,7 +708,6 @@ export default definePipr((pipr) => {
765
708
  paths: {
766
709
  exclude: ["docs/**", "**/*.md"],
767
710
  },
768
- inlineComments: { max: 8 },
769
711
  timeout: "7m",
770
712
  entrypoints: {
771
713
  changeRequest: ["opened", "updated", "reopened", "ready"],
@@ -913,6 +855,8 @@ export default definePipr((pipr) => {
913
855
  options: { thinking: "high" },
914
856
  });
915
857
 
858
+ pipr.config({ publication: { maxInlineComments: 5 } });
859
+
916
860
  pipr.review({
917
861
  id: "review",
918
862
  model,
@@ -921,7 +865,6 @@ export default definePipr((pipr) => {
921
865
  maintainability, and test coverage.
922
866
  Return only actionable findings that target valid diff ranges.
923
867
  \`,
924
- inlineComments: { max: 5 },
925
868
  timeout: "5m",
926
869
  comment: (result, context) => ({
927
870
  main:
@@ -1547,6 +1490,8 @@ export default definePipr((pipr) => {
1547
1490
  options: { thinking: "medium" },
1548
1491
  });
1549
1492
 
1493
+ pipr.config({ publication: { maxInlineComments: 0 } });
1494
+
1550
1495
  pipr.review({
1551
1496
  id: "pr-briefing",
1552
1497
  model,
@@ -1555,7 +1500,6 @@ export default definePipr((pipr) => {
1555
1500
  classify the PR type, explain review risk, and include a concise file walkthrough.
1556
1501
  Return no inline findings unless there is a concrete blocker.
1557
1502
  \`,
1558
- inlineComments: false,
1559
1503
  comment: (result, context) => [
1560
1504
  "## PR Briefing",
1561
1505
  "",
@@ -1646,18 +1590,6 @@ export default definePipr((pipr) => {
1646
1590
  options: { thinking: "high" },
1647
1591
  });
1648
1592
 
1649
- pipr.checks({
1650
- aggregate: { enabled: true, name: "pipr quality gate" },
1651
- });
1652
-
1653
- pipr.limits({
1654
- timeoutSeconds: 420,
1655
- diffManifest: {
1656
- fullMaxEstimatedTokens: 32000,
1657
- condensedMaxEstimatedTokens: 64000,
1658
- },
1659
- });
1660
-
1661
1593
  pipr.config({
1662
1594
  publication: {
1663
1595
  maxInlineComments: 6,
@@ -1669,6 +1601,16 @@ export default definePipr((pipr) => {
1669
1601
  userReplies: { enabled: true, allowedActors: "write" },
1670
1602
  },
1671
1603
  },
1604
+ checks: {
1605
+ aggregate: { enabled: true, name: "pipr quality gate" },
1606
+ },
1607
+ limits: {
1608
+ timeoutSeconds: 420,
1609
+ diffManifest: {
1610
+ fullMaxEstimatedTokens: 32000,
1611
+ condensedMaxEstimatedTokens: 64000,
1612
+ },
1613
+ },
1672
1614
  });
1673
1615
 
1674
1616
  pipr.review({
@@ -1680,7 +1622,6 @@ export default definePipr((pipr) => {
1680
1622
  If no blocking issue exists, state that no blocking issue exists.
1681
1623
  \`,
1682
1624
  check: { enabled: true, name: "quality gate", required: true },
1683
- inlineComments: { max: 6 },
1684
1625
  comment: (result) => ({
1685
1626
  main: \`## Quality Gate\\n\\n\${result.summary.body}\`,
1686
1627
  inlineFindings: result.inlineFindings,
@@ -1860,7 +1801,9 @@ function isOfficialInitRecipeId(recipe) {
1860
1801
  //#endregion
1861
1802
  //#region src/config/init.ts
1862
1803
  const supportedOfficialInitAdapters = ["github"];
1863
- const defaultWorkflowActionRef = "somus/pipr@v0.1.2";
1804
+ const defaultWorkflowActionRef = "somus/pipr@v0.2.0";
1805
+ const defaultSdkVersion = "0.2.0";
1806
+ const defaultTypesBunVersion = "1.3.14";
1864
1807
  function resolveOfficialInitAdapters(adapters) {
1865
1808
  if (adapters === void 0) return [...supportedOfficialInitAdapters];
1866
1809
  if (adapters.length === 0) return [];
@@ -1880,25 +1823,38 @@ function unsupportedAdapterError(adapter) {
1880
1823
  return /* @__PURE__ */ new Error(`Unsupported pipr init adapter '${adapter}'. Supported adapters: ${supportedOfficialInitAdapters.join(", ")}; use 'none' to skip adapter files.`);
1881
1824
  }
1882
1825
  async function initOfficialMinimalProject(options) {
1883
- const { configDir, relativeConfigDir } = resolveContainedConfigDir(options);
1826
+ const { configDir, relativeConfigDir, projectDir } = resolveContainedConfigDir(options);
1884
1827
  const adapters = resolveOfficialInitAdapters(options.adapters);
1885
1828
  const rootDir = path.resolve(options.rootDir);
1886
- const typeSupport = options.typeSupport ?? "include";
1887
- const targets = (typeSupport === "only" ? await generatedTypeSupportFiles(relativeConfigDir) : await starterFiles(relativeConfigDir, adapters, options.recipe, typeSupport !== "skip")).map((file) => ({
1829
+ const minimal = options.minimal === true;
1830
+ const targets = (await starterFiles(relativeConfigDir, adapters, options.recipe, minimal)).map((file) => ({
1888
1831
  ...file,
1889
1832
  absolutePath: path.join(rootDir, file.relativePath)
1890
1833
  }));
1891
1834
  await assertSafeTargetAncestors(targets, rootDir);
1892
1835
  const existing = await findExistingTargets(targets);
1893
- if (existing.length > 0 && !options.force) {
1894
- if (typeSupport === "only") return {
1895
- configDir,
1896
- ...await writeTargets(targets, existing, { skipExisting: true })
1897
- };
1898
- throw new Error(`Project already contains pipr files: ${existing.join(", ")}. Use --force to replace existing .pipr files.`);
1899
- }
1836
+ if (existing.length > 0 && !options.force) throw new Error(`Project already contains pipr files: ${existing.join(", ")}. Use --force to replace existing .pipr files.`);
1900
1837
  const result = await writeTargets(targets, existing, { skipExisting: false });
1901
- if (typeSupport !== "only") await loadRuntimeProject({
1838
+ if (!minimal) {
1839
+ await assertBunAvailable();
1840
+ const install = Bun.spawn([
1841
+ "bun",
1842
+ "install",
1843
+ "--ignore-scripts"
1844
+ ], {
1845
+ cwd: projectDir,
1846
+ env: process.env,
1847
+ stdout: "pipe",
1848
+ stderr: "pipe"
1849
+ });
1850
+ const [exitCode, stderr] = await Promise.all([install.exited, new Response(install.stderr).text()]);
1851
+ if (exitCode !== 0) throw new Error(`${configDir}: bun install failed (exit ${exitCode}).` + (stderr.trim().length > 0 ? `\n${stderr.trim()}` : ""));
1852
+ if (await Bun.file(path.join(projectDir, "bun.lock")).exists()) {
1853
+ const lockRelative = path.join(relativeConfigDir, "bun.lock");
1854
+ if (!existing.includes(lockRelative) && !result.created.includes(lockRelative)) result.created.push(lockRelative);
1855
+ }
1856
+ }
1857
+ await loadRuntimeProject({
1902
1858
  rootDir: options.rootDir,
1903
1859
  configDir
1904
1860
  });
@@ -1907,7 +1863,7 @@ async function initOfficialMinimalProject(options) {
1907
1863
  ...result
1908
1864
  };
1909
1865
  }
1910
- async function starterFiles(relativeConfigDir, adapters, recipe, includeTypeSupport = true) {
1866
+ async function starterFiles(relativeConfigDir, adapters, recipe, minimal = false) {
1911
1867
  const files = [{
1912
1868
  relativePath: path.join(relativeConfigDir, "config.ts"),
1913
1869
  contents: officialInitRecipeConfigTs(recipe)
@@ -1915,13 +1871,29 @@ async function starterFiles(relativeConfigDir, adapters, recipe, includeTypeSupp
1915
1871
  relativePath: path.join(relativeConfigDir, file.relativePath),
1916
1872
  contents: file.contents
1917
1873
  }))];
1918
- if (includeTypeSupport) files.push(...await generatedTypeSupportFiles(relativeConfigDir));
1874
+ if (!minimal) files.push({
1875
+ relativePath: path.join(relativeConfigDir, "package.json"),
1876
+ contents: starterPackageJson()
1877
+ }, {
1878
+ relativePath: path.join(relativeConfigDir, "tsconfig.json"),
1879
+ contents: starterTsconfig
1880
+ }, {
1881
+ relativePath: path.join(relativeConfigDir, ".gitignore"),
1882
+ contents: "node_modules\n"
1883
+ });
1919
1884
  if (adapters.includes("github")) files.push({
1920
1885
  relativePath: path.join(".github", "workflows", "pipr.yml"),
1921
- contents: starterWorkflow(relativeConfigDir.split(path.sep).join("/"), recipe)
1886
+ contents: starterWorkflow(relativeConfigDir.split(path.sep).join("/"), recipe, minimal)
1922
1887
  });
1923
1888
  return files;
1924
1889
  }
1890
+ function starterPackageJson() {
1891
+ return `${JSON.stringify({
1892
+ private: true,
1893
+ dependencies: { "@usepipr/sdk": process.env.PIPR_INTERNAL_INIT_SDK_VERSION ?? defaultSdkVersion },
1894
+ devDependencies: { "@types/bun": defaultTypesBunVersion }
1895
+ }, null, 2)}\n`;
1896
+ }
1925
1897
  async function writeTargets(targets, existing, options) {
1926
1898
  const created = [];
1927
1899
  const overwritten = [];
@@ -1975,7 +1947,7 @@ async function maybeLstat(filePath) {
1975
1947
  return;
1976
1948
  }
1977
1949
  }
1978
- function starterWorkflow(relativeConfigDir, recipe) {
1950
+ function starterWorkflow(relativeConfigDir, recipe, minimal = false) {
1979
1951
  const lines = [
1980
1952
  "name: pipr",
1981
1953
  "",
@@ -1998,20 +1970,25 @@ function starterWorkflow(relativeConfigDir, recipe) {
1998
1970
  " steps:",
1999
1971
  " - uses: actions/checkout@v6",
2000
1972
  " with:",
2001
- " fetch-depth: 0",
2002
- ` - uses: ${defaultWorkflowActionRef}`,
2003
- " env:",
2004
- ` DEEPSEEK_API_KEY: $${[
2005
- "{{ ",
2006
- "secrets.DEEPSEEK_API_KEY",
2007
- " }}"
2008
- ].join("")}`,
2009
- ` GITHUB_TOKEN: $${[
2010
- "{{ ",
2011
- "github.token",
2012
- " }}"
2013
- ].join("")}`
1973
+ " fetch-depth: 0"
2014
1974
  ];
1975
+ if (!minimal) lines.push(" - uses: actions/cache@v4", " with:", " path: /home/runner/work/_temp/_github_home/.bun/install/cache", ` key: pipr-bun-${[
1976
+ "{{ ",
1977
+ `hashFiles('${relativeConfigDir}/bun.lock')`,
1978
+ " }}"
1979
+ ].join("")}`);
1980
+ lines.push(` - uses: ${defaultWorkflowActionRef}`);
1981
+ lines.push(" env:");
1982
+ lines.push(` DEEPSEEK_API_KEY: $${[
1983
+ "{{ ",
1984
+ "secrets.DEEPSEEK_API_KEY",
1985
+ " }}"
1986
+ ].join("")}`);
1987
+ lines.push(` GITHUB_TOKEN: $${[
1988
+ "{{ ",
1989
+ "github.token",
1990
+ " }}"
1991
+ ].join("")}`);
2015
1992
  for (const secret of officialInitRecipeWorkflowEnvSecrets(recipe)) lines.push(` ${secret.env}: $${[
2016
1993
  "{{ ",
2017
1994
  `secrets.${secret.secret}`,
@@ -2386,44 +2363,452 @@ function createLocalChangeRequestEvent(options) {
2386
2363
  });
2387
2364
  }
2388
2365
  //#endregion
2389
- //#region src/pi/runtime-tools.ts
2390
- const piRuntimeReadToolNames = ["pipr_read_diff", "pipr_read_at_ref"];
2391
- async function preparePiRuntimeReadTools(options) {
2392
- const toolRoot = path.join(options.root, "runtime-tools");
2393
- const baseRoot = path.join(toolRoot, "base");
2394
- await mkdir(baseRoot, { recursive: true });
2395
- const baseRanges = await materializeBaseRangeSnapshots({
2396
- baseRoot,
2397
- manifest: options.request.manifest,
2398
- sourceWorkspace: options.sourceWorkspace,
2399
- maxBytes: options.request.toolResponseMaxBytes
2400
- });
2401
- const data = {
2402
- manifest: options.request.manifest,
2403
- toolResponseMaxBytes: options.request.toolResponseMaxBytes,
2404
- baseRanges
2405
- };
2406
- const dataPath = path.join(toolRoot, "data.json");
2407
- await Bun.write(dataPath, JSON.stringify(data));
2408
- return {
2409
- extensionPath: await piRuntimeToolsExtensionPath(),
2410
- dataPath,
2411
- toolNames: piRuntimeReadToolNames
2412
- };
2366
+ //#region src/commands/grammar.ts
2367
+ const piprCommandPrefix = "@pipr";
2368
+ function firstNonEmptyLine(value) {
2369
+ return value.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2413
2370
  }
2414
- async function piRuntimeToolsExtensionPath() {
2415
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
2416
- const candidates = [
2417
- path.join(moduleDir, "pi", "runtime-tools-extension.mjs"),
2418
- path.join(moduleDir, "runtime-tools-extension.mjs"),
2419
- path.join(moduleDir, "..", "..", "dist", "pi", "runtime-tools-extension.mjs"),
2420
- path.join(moduleDir, "runtime-tools-extension.ts")
2421
- ];
2422
- for (const candidate of candidates) if (await pathExists(candidate)) return candidate;
2423
- throw new Error("Unable to locate pipr runtime tools extension");
2371
+ function isPiprCommandLine(line) {
2372
+ return line === piprCommandPrefix || line.startsWith(`${piprCommandPrefix} `);
2424
2373
  }
2425
- async function pathExists(filePath) {
2426
- return await Bun.file(filePath).exists();
2374
+ function parseCommandPattern(pattern, line) {
2375
+ const patternParts = commandPatternParts(pattern);
2376
+ const validationError = unsupportedCommandRestCaptureError(pattern);
2377
+ if (validationError) return {
2378
+ ok: false,
2379
+ error: validationError
2380
+ };
2381
+ const lineTokens = tokenizeCommandPattern(line);
2382
+ const captures = {};
2383
+ let index = 0;
2384
+ for (const part of patternParts) {
2385
+ if (isOptionalCommandPatternPart(part)) {
2386
+ const nextIndex = parseOptionalPatternPart(part.slice(1, -1), lineTokens, index, captures);
2387
+ if (nextIndex !== void 0) index = nextIndex;
2388
+ continue;
2389
+ }
2390
+ if (isCommandRestCaptureToken(part)) {
2391
+ const value = lineTokens.slice(index).join(" ");
2392
+ if (!value) return {
2393
+ ok: false,
2394
+ error: `Expected '${part}'`
2395
+ };
2396
+ captures[part.slice(1, -4)] = value;
2397
+ index = lineTokens.length;
2398
+ continue;
2399
+ }
2400
+ const nextIndex = parsePatternToken(part, lineTokens, index, captures);
2401
+ if (nextIndex === void 0) return {
2402
+ ok: false,
2403
+ error: `Expected '${part}'`
2404
+ };
2405
+ index = nextIndex;
2406
+ }
2407
+ if (index !== lineTokens.length) return {
2408
+ ok: false,
2409
+ error: `Unexpected argument '${lineTokens[index]}'`
2410
+ };
2411
+ return {
2412
+ ok: true,
2413
+ value: captures
2414
+ };
2415
+ }
2416
+ function commandPatternPrefixMatches(pattern, line) {
2417
+ const patternTokens = commandPatternParts(pattern);
2418
+ if (unsupportedCommandRestCaptureError(pattern)) return false;
2419
+ const lineTokens = tokenizeCommandPattern(line);
2420
+ let index = 0;
2421
+ for (const token of patternTokens) {
2422
+ if (isCommandCaptureToken(token) || isOptionalCommandPatternPart(token)) return true;
2423
+ if (lineTokens[index] !== token) return false;
2424
+ index += 1;
2425
+ }
2426
+ return true;
2427
+ }
2428
+ function parseOptionalPatternPart(pattern, lineTokens, startIndex, captures) {
2429
+ const patternTokens = tokenizeCommandPattern(pattern);
2430
+ if (patternTokens.length === 0 || lineTokens[startIndex] !== patternTokens[0]) return;
2431
+ const snapshot = { ...captures };
2432
+ let index = startIndex;
2433
+ for (const token of patternTokens) {
2434
+ const nextIndex = parsePatternToken(token, lineTokens, index, captures);
2435
+ if (nextIndex === void 0) {
2436
+ for (const key of Object.keys(captures)) delete captures[key];
2437
+ Object.assign(captures, snapshot);
2438
+ return;
2439
+ }
2440
+ index = nextIndex;
2441
+ }
2442
+ return index;
2443
+ }
2444
+ function parsePatternToken(patternToken, lineTokens, index, captures) {
2445
+ if (isCommandCaptureToken(patternToken)) {
2446
+ const value = lineTokens[index];
2447
+ if (!value) return;
2448
+ captures[patternToken.slice(1, -1)] = value;
2449
+ return index + 1;
2450
+ }
2451
+ return lineTokens[index] === patternToken ? index + 1 : void 0;
2452
+ }
2453
+ //#endregion
2454
+ //#region src/action/entry-dispatch.ts
2455
+ const permissionOrder = [
2456
+ "read",
2457
+ "triage",
2458
+ "write",
2459
+ "maintain",
2460
+ "admin"
2461
+ ];
2462
+ const changeRequestActions = [
2463
+ "opened",
2464
+ "updated",
2465
+ "reopened",
2466
+ "ready",
2467
+ "closed"
2468
+ ];
2469
+ function dispatchRuntimeEntry(options) {
2470
+ if (options.kind === "change-request") return {
2471
+ kind: "change-request",
2472
+ tasks: selectRuntimeTasks({
2473
+ plan: options.plan,
2474
+ event: options.event,
2475
+ taskName: options.taskName
2476
+ }),
2477
+ taskName: options.taskName
2478
+ };
2479
+ return resolvePlanCommand(options.plan, options.line);
2480
+ }
2481
+ function selectRuntimeTasks(options) {
2482
+ if (options.taskName) return options.plan.tasks.filter((task) => task.name === options.taskName);
2483
+ return selectChangeRequestTasks(options.plan, options.event);
2484
+ }
2485
+ function selectLocalReviewTasks(plan) {
2486
+ return uniqBy(plan.changeRequestTriggers.map((trigger) => trigger.task), (task) => task.name).filter((task) => task.local !== false);
2487
+ }
2488
+ function selectChangeRequestTasks(plan, event) {
2489
+ if (!changeRequestActions.includes(event.action)) return [];
2490
+ const action = event.action;
2491
+ return uniqBy(plan.changeRequestTriggers.filter((trigger) => trigger.actions.includes(action)).map((trigger) => trigger.task), (task) => task.name);
2492
+ }
2493
+ function selectPlanCommand(plan, line) {
2494
+ let firstInvalid;
2495
+ for (const command of plan.commands) {
2496
+ const parsed = parseCommandPattern(command.pattern, line);
2497
+ if (!parsed.ok) {
2498
+ if (commandPatternPrefixMatches(command.pattern, line) && !firstInvalid) firstInvalid = {
2499
+ kind: "invalid",
2500
+ command,
2501
+ error: parsed.error
2502
+ };
2503
+ continue;
2504
+ }
2505
+ return {
2506
+ kind: "matched",
2507
+ command,
2508
+ commandName: command.pattern.replace(/^@pipr\s+/, "").split(/\s+/)[0] ?? command.pattern,
2509
+ line,
2510
+ arguments: parsed.value
2511
+ };
2512
+ }
2513
+ return firstInvalid;
2514
+ }
2515
+ function resolvePlanCommand(plan, line) {
2516
+ if (!line) return {
2517
+ kind: "ignored",
2518
+ reason: "comment did not contain a command line"
2519
+ };
2520
+ if (!isPiprCommandLine(line)) return {
2521
+ kind: "ignored",
2522
+ reason: "comment did not target pipr"
2523
+ };
2524
+ const selected = selectPlanCommand(plan, line);
2525
+ if (selected?.kind === "matched") return {
2526
+ kind: "matched",
2527
+ invocation: {
2528
+ taskName: selected.command.task.name,
2529
+ commandName: selected.commandName,
2530
+ requiredPermission: selected.command.permission,
2531
+ line: selected.line,
2532
+ pattern: selected.command.pattern,
2533
+ arguments: selected.arguments
2534
+ }
2535
+ };
2536
+ if (selected?.kind === "invalid") return {
2537
+ kind: "invalid",
2538
+ reason: selected.error,
2539
+ requiredPermission: selected.command.permission,
2540
+ body: renderPlanCommandHelp(plan, selected.error)
2541
+ };
2542
+ return {
2543
+ kind: "help",
2544
+ reason: `unknown pipr command '${line}'`,
2545
+ requiredPermission: "read",
2546
+ body: renderPlanCommandHelp(plan, `Unknown command: ${line}`)
2547
+ };
2548
+ }
2549
+ function parsePlanCommandInputs(plan, invocation) {
2550
+ const matchingCommand = plan.commands.find((candidate) => candidate.task.name === invocation.taskName && candidate.pattern === invocation.pattern) ?? plan.commands.find((candidate) => candidate.task.name === invocation.taskName);
2551
+ if (!matchingCommand) return {
2552
+ kind: "invalid",
2553
+ reason: `No command registered for task '${invocation.taskName}'`,
2554
+ requiredPermission: invocation.requiredPermission,
2555
+ body: renderPlanCommandHelp(plan)
2556
+ };
2557
+ try {
2558
+ return {
2559
+ kind: "matched",
2560
+ invocation: {
2561
+ ...invocation,
2562
+ inputs: matchingCommand.parse ? matchingCommand.parse(invocation.arguments) : invocation.arguments
2563
+ }
2564
+ };
2565
+ } catch (error) {
2566
+ const reason = error instanceof Error ? error.message : String(error);
2567
+ return {
2568
+ kind: "invalid",
2569
+ reason,
2570
+ requiredPermission: invocation.requiredPermission,
2571
+ body: renderPlanCommandHelp(plan, reason)
2572
+ };
2573
+ }
2574
+ }
2575
+ function hasRequiredRepositoryPermission(actual, required) {
2576
+ if (actual === "none") return false;
2577
+ return permissionOrder.indexOf(actual) >= permissionOrder.indexOf(required);
2578
+ }
2579
+ function permissionDeniedHelp(plan, required) {
2580
+ return renderPlanCommandHelp(plan, `Permission denied: requires ${required}.`);
2581
+ }
2582
+ function renderPlanCommandHelp(plan, reason) {
2583
+ const lines = ["# pipr commands", ""];
2584
+ if (reason) lines.push(reason, "");
2585
+ for (const command of plan.commands) lines.push(`- ${command.pattern} (${command.permission})`);
2586
+ return lines.join("\n");
2587
+ }
2588
+ //#endregion
2589
+ //#region src/diff/path-filter.ts
2590
+ const matcherOptions = {
2591
+ dot: true,
2592
+ nonegate: true,
2593
+ windows: false
2594
+ };
2595
+ const compiledFilters = /* @__PURE__ */ new WeakMap();
2596
+ function filterDiffManifestByPaths(manifest, filter) {
2597
+ if (!filter) return manifest;
2598
+ return {
2599
+ ...manifest,
2600
+ files: manifest.files.filter((file) => diffFileMatchesPathFilter(file, filter))
2601
+ };
2602
+ }
2603
+ function diffFileMatchesPathFilter(file, filter) {
2604
+ if (!filter) return true;
2605
+ const paths = [...new Set([file.path, file.previousPath].filter((item) => item !== void 0))].map((item) => item.replaceAll("\\", "/").replace(/^\.\/+/, ""));
2606
+ const compiled = readCompiledFilter(filter);
2607
+ const include = compiled.include;
2608
+ const exclude = compiled.exclude;
2609
+ const included = include ? paths.some((filePath) => include.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(filePath) : filePath))) : true;
2610
+ const excluded = exclude ? paths.some((filePath) => exclude.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(filePath) : filePath))) : false;
2611
+ return included && !excluded;
2612
+ }
2613
+ function pathMatchesFilter(filePath, filter) {
2614
+ if (!filter) return true;
2615
+ const normalizedPath = filePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
2616
+ const compiled = readCompiledFilter(filter);
2617
+ if (!(compiled.include ? compiled.include.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(normalizedPath) : normalizedPath)) : true)) return false;
2618
+ return compiled.exclude ? !compiled.exclude.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(normalizedPath) : normalizedPath)) : true;
2619
+ }
2620
+ function readCompiledFilter(filter) {
2621
+ const existing = compiledFilters.get(filter);
2622
+ if (existing) return existing;
2623
+ const compiled = {
2624
+ include: filter.include?.map(compilePattern),
2625
+ exclude: filter.exclude?.map(compilePattern)
2626
+ };
2627
+ compiledFilters.set(filter, compiled);
2628
+ return compiled;
2629
+ }
2630
+ function compilePattern(pattern) {
2631
+ return {
2632
+ matches: picomatch(pattern, matcherOptions),
2633
+ matchBasename: !pattern.includes("/")
2634
+ };
2635
+ }
2636
+ //#endregion
2637
+ //#region src/diff/manifest-projection.ts
2638
+ const defaultDiffManifestPromptLimits = {
2639
+ fullMaxBytes: 128 * 1024,
2640
+ fullMaxEstimatedTokens: 32e3,
2641
+ condensedMaxBytes: 256 * 1024,
2642
+ condensedMaxEstimatedTokens: 64e3,
2643
+ toolResponseMaxBytes: 64 * 1024
2644
+ };
2645
+ function projectDiffManifest(manifest, options) {
2646
+ if (!manifestOptionsHaveEffect(options)) return manifest;
2647
+ const manifestOptions = options ?? {};
2648
+ const scopedManifest = filterDiffManifestByPaths(manifest, manifestOptions.paths);
2649
+ return parseDiffManifest({
2650
+ ...scopedManifest,
2651
+ files: scopedManifest.files.map((file) => ({
2652
+ ...withoutCompressedFileFields(file, manifestOptions.compressed === true),
2653
+ commentableRanges: file.commentableRanges.map((range) => ({
2654
+ ...rangeFieldsForOptions(range, manifestOptions),
2655
+ ...manifestOptions.includePreviews === false ? {} : { preview: truncatePreview(range.preview, manifestOptions.maxPreviewLines) }
2656
+ }))
2657
+ }))
2658
+ });
2659
+ }
2660
+ function cloneDiffManifest(manifest) {
2661
+ return parseDiffManifest(structuredClone(manifest));
2662
+ }
2663
+ function prepareDiffManifestPrompt(manifest, config) {
2664
+ const limits = resolveDiffManifestPromptLimits(config);
2665
+ const full = measureDiffManifestPrompt(manifest);
2666
+ if (fitsLimit(full, limits.fullMaxBytes, limits.fullMaxEstimatedTokens)) return {
2667
+ mode: "full",
2668
+ manifest,
2669
+ metrics: {
2670
+ full,
2671
+ selected: full
2672
+ },
2673
+ limits
2674
+ };
2675
+ const condensedManifest = condenseDiffManifest(manifest);
2676
+ const condensed = measureDiffManifestPrompt(condensedManifest);
2677
+ if (!fitsLimit(condensed, limits.condensedMaxBytes, limits.condensedMaxEstimatedTokens)) throw new Error([
2678
+ "Diff Manifest payload exceeds condensed limit before Pi execution",
2679
+ `selected=${condensed.bytes} bytes/${condensed.estimatedTokens} estimated tokens`,
2680
+ `limit=${limits.condensedMaxBytes} bytes/${limits.condensedMaxEstimatedTokens} estimated tokens`
2681
+ ].join("; "));
2682
+ return {
2683
+ mode: "condensed",
2684
+ manifest: condensedManifest,
2685
+ metrics: {
2686
+ full,
2687
+ selected: condensed
2688
+ },
2689
+ limits
2690
+ };
2691
+ }
2692
+ function condenseDiffManifest(manifest) {
2693
+ return {
2694
+ baseSha: manifest.baseSha,
2695
+ headSha: manifest.headSha,
2696
+ mergeBaseSha: manifest.mergeBaseSha,
2697
+ files: manifest.files.map(condenseDiffManifestFile)
2698
+ };
2699
+ }
2700
+ function measureDiffManifestPrompt(manifest) {
2701
+ const json = JSON.stringify(manifest, null, 2);
2702
+ const bytes = Buffer.byteLength(json, "utf8");
2703
+ return {
2704
+ bytes,
2705
+ estimatedTokens: Math.ceil(bytes / 4)
2706
+ };
2707
+ }
2708
+ function resolveDiffManifestPromptLimits(config) {
2709
+ return {
2710
+ ...defaultDiffManifestPromptLimits,
2711
+ ...Object.fromEntries(Object.entries(config ?? {}).filter((entry) => entry[1] !== void 0))
2712
+ };
2713
+ }
2714
+ function manifestOptionsHaveEffect(options) {
2715
+ return Boolean(options?.compressed || options?.includePreviews === false || options?.maxPreviewLines !== void 0 || options?.paths);
2716
+ }
2717
+ function withoutCompressedFileFields(file, compressed) {
2718
+ if (!compressed) return file;
2719
+ const { signals: _signals, changedSymbols: _changedSymbols, ...rest } = file;
2720
+ return rest;
2721
+ }
2722
+ function withoutCompressedRangeFields(range, compressed) {
2723
+ if (!compressed) return range;
2724
+ const { summary: _summary, ...rest } = range;
2725
+ return rest;
2726
+ }
2727
+ function rangeFieldsForOptions(range, options) {
2728
+ const fields = withoutCompressedRangeFields(range, options.compressed === true);
2729
+ if (options.includePreviews === false) {
2730
+ const { preview: _preview, ...rest } = fields;
2731
+ return rest;
2732
+ }
2733
+ return fields;
2734
+ }
2735
+ function truncatePreview(preview, maxLines) {
2736
+ if (preview === void 0 || maxLines === void 0) return preview;
2737
+ return preview.split("\n").slice(0, maxLines).join("\n");
2738
+ }
2739
+ function condenseDiffManifestFile(file) {
2740
+ return {
2741
+ path: file.path,
2742
+ previousPath: file.previousPath,
2743
+ status: file.status,
2744
+ language: file.language,
2745
+ additions: file.additions,
2746
+ deletions: file.deletions,
2747
+ hunks: file.hunks.map((hunk) => ({
2748
+ hunkIndex: hunk.hunkIndex,
2749
+ header: hunk.header,
2750
+ oldStart: hunk.oldStart,
2751
+ oldLines: hunk.oldLines,
2752
+ newStart: hunk.newStart,
2753
+ newLines: hunk.newLines,
2754
+ contentHash: hunk.contentHash
2755
+ })),
2756
+ commentableRanges: file.commentableRanges.map((range) => ({
2757
+ id: range.id,
2758
+ path: range.path,
2759
+ side: range.side,
2760
+ startLine: range.startLine,
2761
+ endLine: range.endLine,
2762
+ kind: range.kind,
2763
+ hunkIndex: range.hunkIndex,
2764
+ hunkHeader: range.hunkHeader,
2765
+ hunkContentHash: range.hunkContentHash
2766
+ })),
2767
+ excludedReason: file.excludedReason
2768
+ };
2769
+ }
2770
+ function fitsLimit(metrics, maxBytes, maxEstimatedTokens) {
2771
+ return metrics.bytes <= maxBytes && metrics.estimatedTokens <= maxEstimatedTokens;
2772
+ }
2773
+ //#endregion
2774
+ //#region src/pi/runtime-tools.ts
2775
+ const piRuntimeReadToolNames = ["pipr_read_diff", "pipr_read_at_ref"];
2776
+ async function preparePiRuntimeReadTools(options) {
2777
+ const toolRoot = path.join(options.root, "runtime-tools");
2778
+ const baseRoot = path.join(toolRoot, "base");
2779
+ await mkdir(baseRoot, { recursive: true });
2780
+ const baseRanges = await materializeBaseRangeSnapshots({
2781
+ baseRoot,
2782
+ manifest: options.request.manifest,
2783
+ sourceWorkspace: options.sourceWorkspace,
2784
+ maxBytes: options.request.toolResponseMaxBytes
2785
+ });
2786
+ const data = {
2787
+ manifest: options.request.manifest,
2788
+ toolResponseMaxBytes: options.request.toolResponseMaxBytes,
2789
+ baseRanges
2790
+ };
2791
+ const dataPath = path.join(toolRoot, "data.json");
2792
+ await Bun.write(dataPath, JSON.stringify(data));
2793
+ return {
2794
+ extensionPath: await piRuntimeToolsExtensionPath(),
2795
+ dataPath,
2796
+ toolNames: piRuntimeReadToolNames
2797
+ };
2798
+ }
2799
+ async function piRuntimeToolsExtensionPath() {
2800
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
2801
+ const candidates = [
2802
+ path.join(moduleDir, "pi", "runtime-tools-extension.mjs"),
2803
+ path.join(moduleDir, "runtime-tools-extension.mjs"),
2804
+ path.join(moduleDir, "..", "..", "dist", "pi", "runtime-tools-extension.mjs"),
2805
+ path.join(moduleDir, "runtime-tools-extension.ts")
2806
+ ];
2807
+ for (const candidate of candidates) if (await pathExists(candidate)) return candidate;
2808
+ throw new Error("Unable to locate pipr runtime tools extension");
2809
+ }
2810
+ async function pathExists(filePath) {
2811
+ return await Bun.file(filePath).exists();
2427
2812
  }
2428
2813
  async function materializeBaseRangeSnapshots(options) {
2429
2814
  const ranges = {};
@@ -2853,51 +3238,31 @@ function createRuntimeActionLog(options) {
2853
3238
  for (const [key, value] of Object.entries(options.env ?? process.env)) if (sensitiveEnvNamePattern.test(key)) addSecret(secrets, value);
2854
3239
  const debugEnabled = (options.env ?? process.env).ACTIONS_STEP_DEBUG === "true" || (options.env ?? process.env).PIPR_LOG_LEVEL === "debug";
2855
3240
  const sink = options.logSink ?? noopActionLogSink;
2856
- const writer = structuredLogWriter(sink, secrets);
2857
- const logger = pino({
2858
- base: void 0,
2859
- level: debugEnabled ? "debug" : "info",
2860
- timestamp: false,
2861
- messageKey: "event",
2862
- customLevels: { notice: 35 },
2863
- formatters: { level(label) {
2864
- return { level: label };
2865
- } }
2866
- }, writer.stream);
2867
3241
  return {
2868
3242
  debugEnabled,
2869
3243
  writesToSink: options.logSink !== void 0,
2870
3244
  info(event, fields) {
2871
- writer.level = "info";
2872
- logger.info(compactFields(fields, secrets), redact(event, secrets));
3245
+ emitRecord(sink, secrets, "info", event, fields);
2873
3246
  },
2874
3247
  notice(event, fields) {
2875
- writer.level = "notice";
2876
- logger.notice(compactFields(fields, secrets), redact(event, secrets));
3248
+ emitRecord(sink, secrets, "notice", event, fields);
2877
3249
  },
2878
3250
  warning(event, fields) {
2879
- writer.level = "warning";
2880
- logger.warn(compactFields(fields, secrets), redact(event, secrets));
3251
+ emitRecord(sink, secrets, "warning", event, fields);
2881
3252
  },
2882
3253
  error(event, fields) {
2883
- writer.level = "error";
2884
- logger.error(compactFields(fields, secrets), redact(event, secrets));
3254
+ emitRecord(sink, secrets, "error", event, fields);
2885
3255
  },
2886
3256
  debug(event, fields) {
2887
- if (debugEnabled) {
2888
- writer.level = "debug";
2889
- logger.debug(compactFields(fields, secrets), redact(event, secrets));
2890
- }
3257
+ if (debugEnabled) emitRecord(sink, secrets, "debug", event, fields);
2891
3258
  },
2892
3259
  text(level, event, text) {
2893
3260
  if (level === "debug" && !debugEnabled) return;
2894
- const message = `${structuredLine(logger, writer, level, redact(event, secrets))}\n${redact(text, secrets)}`;
2895
- sinkForLevel(sink, level)(message);
3261
+ emitRecord(sink, secrets, level, event, void 0, redact(text, secrets));
2896
3262
  },
2897
3263
  textSnippet(level, event, text, snippetOptions) {
2898
3264
  if (level === "debug" && !debugEnabled) return;
2899
- const message = `${structuredLine(logger, writer, level, redact(event, secrets))}\n${formatTextSnippet(text, secrets, snippetOptions)}`;
2900
- sinkForLevel(sink, level)(message);
3265
+ emitRecord(sink, secrets, level, event, void 0, formatTextSnippet(text, secrets, snippetOptions));
2901
3266
  },
2902
3267
  formatTextSnippet(text, snippetOptions) {
2903
3268
  return formatTextSnippet(text, secrets, snippetOptions);
@@ -2941,93 +3306,21 @@ function compactFields(fields, secrets) {
2941
3306
  function formatTextSnippet(text, secrets, options) {
2942
3307
  return boundedLogSnippet(redact(text, secrets), options);
2943
3308
  }
2944
- function structuredLogWriter(sink, secrets) {
2945
- const writer = {
2946
- level: "info",
2947
- stream: { write(line) {
2948
- sinkForLevel(sink, writer.level)(redact(line.trimEnd(), secrets));
2949
- } }
2950
- };
2951
- return writer;
2952
- }
2953
- function structuredLine(logger, writer, level, event) {
2954
- let line = "";
2955
- const previousStream = writer.stream.write;
2956
- writer.stream.write = (value) => {
2957
- line = value.trimEnd();
2958
- };
2959
- writer.level = level;
2960
- logger[logMethod(level)]({}, event);
2961
- writer.stream.write = previousStream;
2962
- return line;
2963
- }
2964
- function logMethod(level) {
2965
- if (level === "warning") return "warn";
2966
- return level;
2967
- }
2968
- function sinkForLevel(sink, level) {
2969
- if (level === "warning") return sink.warning.bind(sink);
2970
- return sink[level].bind(sink);
3309
+ function emitRecord(sink, secrets, level, event, fields, text) {
3310
+ sink.log({
3311
+ level,
3312
+ event: redact(event, secrets),
3313
+ fields: compactFields(fields, secrets),
3314
+ text
3315
+ });
2971
3316
  }
2972
3317
  const noopActionLogSink = {
2973
- info() {},
2974
- notice() {},
2975
- warning() {},
2976
- error() {},
2977
- debug() {},
3318
+ log() {},
2978
3319
  async group(_name, run) {
2979
3320
  return await run();
2980
3321
  }
2981
3322
  };
2982
3323
  //#endregion
2983
- //#region src/diff/path-filter.ts
2984
- const matcherOptions = {
2985
- dot: true,
2986
- nonegate: true,
2987
- windows: false
2988
- };
2989
- const compiledFilters = /* @__PURE__ */ new WeakMap();
2990
- function filterDiffManifestByPaths(manifest, filter) {
2991
- if (!filter) return manifest;
2992
- return {
2993
- ...manifest,
2994
- files: manifest.files.filter((file) => diffFileMatchesPathFilter(file, filter))
2995
- };
2996
- }
2997
- function diffFileMatchesPathFilter(file, filter) {
2998
- if (!filter) return true;
2999
- const paths = [...new Set([file.path, file.previousPath].filter((item) => item !== void 0))].map((item) => item.replaceAll("\\", "/").replace(/^\.\/+/, ""));
3000
- const compiled = readCompiledFilter(filter);
3001
- const include = compiled.include;
3002
- const exclude = compiled.exclude;
3003
- const included = include ? paths.some((filePath) => include.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(filePath) : filePath))) : true;
3004
- const excluded = exclude ? paths.some((filePath) => exclude.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(filePath) : filePath))) : false;
3005
- return included && !excluded;
3006
- }
3007
- function pathMatchesFilter(filePath, filter) {
3008
- if (!filter) return true;
3009
- const normalizedPath = filePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
3010
- const compiled = readCompiledFilter(filter);
3011
- if (!(compiled.include ? compiled.include.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(normalizedPath) : normalizedPath)) : true)) return false;
3012
- return compiled.exclude ? !compiled.exclude.some((pattern) => pattern.matches(pattern.matchBasename ? path.posix.basename(normalizedPath) : normalizedPath)) : true;
3013
- }
3014
- function readCompiledFilter(filter) {
3015
- const existing = compiledFilters.get(filter);
3016
- if (existing) return existing;
3017
- const compiled = {
3018
- include: filter.include?.map(compilePattern),
3019
- exclude: filter.exclude?.map(compilePattern)
3020
- };
3021
- compiledFilters.set(filter, compiled);
3022
- return compiled;
3023
- }
3024
- function compilePattern(pattern) {
3025
- return {
3026
- matches: picomatch(pattern, matcherOptions),
3027
- matchBasename: !pattern.includes("/")
3028
- };
3029
- }
3030
- //#endregion
3031
3324
  //#region src/review/range-validation.ts
3032
3325
  function assertFindingMatchesRange(finding, range) {
3033
3326
  const reason = findingRangeMismatchReason(finding, range);
@@ -3043,7 +3336,7 @@ function findingRangeMismatchReason(finding, range) {
3043
3336
  }
3044
3337
  //#endregion
3045
3338
  //#region src/review/review.ts
3046
- function validatePrReview(review, manifest, options) {
3339
+ function validateReviewResult(review, manifest, options) {
3047
3340
  if (options.expectedHeadSha && manifest.headSha !== options.expectedHeadSha) throw new Error(`Diff Manifest head SHA '${manifest.headSha}' does not match expected head SHA '${options.expectedHeadSha}'`);
3048
3341
  const ranges = createDiffRangeIndex(manifest);
3049
3342
  const seenFingerprints = /* @__PURE__ */ new Set();
@@ -3111,7 +3404,7 @@ function findingFingerprint(finding) {
3111
3404
  //#endregion
3112
3405
  //#region src/review/agent/agent-prompt.ts
3113
3406
  async function renderAgentPrompt(options) {
3114
- const prompt = await options.agent.definition.prompt(options.input, { ...options.agentRunContext.prompt });
3407
+ const prompt = await renderAgentDefinitionPrompt(options.agent, options.input, options.agentRunContext.prompt);
3115
3408
  const toolMode = options.toolMode ?? "read-only";
3116
3409
  return compact([
3117
3410
  promptSection("Role", "You are pipr's read-only change request agent."),
@@ -3126,6 +3419,9 @@ async function renderAgentPrompt(options) {
3126
3419
  promptSection("Prompt", renderPromptValue(prompt))
3127
3420
  ]).join("\n\n");
3128
3421
  }
3422
+ function renderAgentDefinitionPrompt(agent, input, context) {
3423
+ return agent.definition.prompt(input, { ...context });
3424
+ }
3129
3425
  function promptSection(title, body) {
3130
3426
  if (!body?.trim()) return;
3131
3427
  return `${title}:\n${body}`;
@@ -3142,12 +3438,12 @@ function outputPrompt(schema) {
3142
3438
  return compact([
3143
3439
  `Schema ID: ${schema.id}.`,
3144
3440
  schema.jsonSchema ? `JSON Schema:\n${JSON.stringify(schema.jsonSchema, null, 2)}` : void 0,
3145
- schema.id === prReviewSchemaId ? `Example:\n${JSON.stringify(reviewSchemaExample$1(), null, 2)}` : void 0,
3146
- schema.id === prReviewSchemaId ? "`suggestedFix` is exact replacement code for the selected range. Do not include Markdown fences, prose, or labels in `suggestedFix`." : void 0,
3441
+ schema.id === reviewResultSchemaId ? `Example:\n${JSON.stringify(reviewSchemaExample$1(), null, 2)}` : void 0,
3442
+ schema.id === reviewResultSchemaId ? "`suggestedFix` is exact replacement code for the selected range. Do not include Markdown fences, prose, or labels in `suggestedFix`." : void 0,
3147
3443
  "Return exactly one JSON value matching the schema.",
3148
3444
  "The first non-whitespace character must be { or [ and the last non-whitespace character must be } or ].",
3149
3445
  "Do not include Markdown, code fences, prose, explanations, or leading/trailing text.",
3150
- schema.id === prReviewSchemaId ? "For inlineFindings, use only fields shown in the schema and only exact Diff Manifest commentable ranges. If no exact range applies, omit the finding." : void 0
3446
+ schema.id === reviewResultSchemaId ? "For inlineFindings, use only fields shown in the schema and only exact Diff Manifest commentable ranges. If no exact range applies, omit the finding." : void 0
3151
3447
  ]).join("\n\n");
3152
3448
  }
3153
3449
  function pathScopePrompt(paths) {
@@ -3184,143 +3480,6 @@ function customToolPrompt(agentTools) {
3184
3480
  return ["Custom plugin tools:", ...agentTools.customTools.map((tool) => `${tool.name}: ${tool.description ?? "No description."}`)].join("\n");
3185
3481
  }
3186
3482
  //#endregion
3187
- //#region src/diff/manifest-projection.ts
3188
- const defaultDiffManifestPromptLimits = {
3189
- fullMaxBytes: 128 * 1024,
3190
- fullMaxEstimatedTokens: 32e3,
3191
- condensedMaxBytes: 256 * 1024,
3192
- condensedMaxEstimatedTokens: 64e3,
3193
- toolResponseMaxBytes: 64 * 1024
3194
- };
3195
- function projectDiffManifest(manifest, options) {
3196
- if (!manifestOptionsHaveEffect(options)) return manifest;
3197
- const manifestOptions = options ?? {};
3198
- const scopedManifest = filterDiffManifestByPaths(manifest, manifestOptions.paths);
3199
- return parseDiffManifest({
3200
- ...scopedManifest,
3201
- files: scopedManifest.files.map((file) => ({
3202
- ...withoutCompressedFileFields(file, manifestOptions.compressed === true),
3203
- commentableRanges: file.commentableRanges.map((range) => ({
3204
- ...rangeFieldsForOptions(range, manifestOptions),
3205
- ...manifestOptions.includePreviews === false ? {} : { preview: truncatePreview(range.preview, manifestOptions.maxPreviewLines) }
3206
- }))
3207
- }))
3208
- });
3209
- }
3210
- function cloneDiffManifest(manifest) {
3211
- return parseDiffManifest(structuredClone(manifest));
3212
- }
3213
- function prepareDiffManifestPrompt(manifest, config) {
3214
- const limits = resolveDiffManifestPromptLimits(config);
3215
- const full = measureDiffManifestPrompt(manifest);
3216
- if (fitsLimit(full, limits.fullMaxBytes, limits.fullMaxEstimatedTokens)) return {
3217
- mode: "full",
3218
- manifest,
3219
- metrics: {
3220
- full,
3221
- selected: full
3222
- },
3223
- limits
3224
- };
3225
- const condensedManifest = condenseDiffManifest(manifest);
3226
- const condensed = measureDiffManifestPrompt(condensedManifest);
3227
- if (!fitsLimit(condensed, limits.condensedMaxBytes, limits.condensedMaxEstimatedTokens)) throw new Error([
3228
- "Diff Manifest payload exceeds condensed limit before Pi execution",
3229
- `selected=${condensed.bytes} bytes/${condensed.estimatedTokens} estimated tokens`,
3230
- `limit=${limits.condensedMaxBytes} bytes/${limits.condensedMaxEstimatedTokens} estimated tokens`
3231
- ].join("; "));
3232
- return {
3233
- mode: "condensed",
3234
- manifest: condensedManifest,
3235
- metrics: {
3236
- full,
3237
- selected: condensed
3238
- },
3239
- limits
3240
- };
3241
- }
3242
- function condenseDiffManifest(manifest) {
3243
- return {
3244
- baseSha: manifest.baseSha,
3245
- headSha: manifest.headSha,
3246
- mergeBaseSha: manifest.mergeBaseSha,
3247
- files: manifest.files.map(condenseDiffManifestFile)
3248
- };
3249
- }
3250
- function measureDiffManifestPrompt(manifest) {
3251
- const json = JSON.stringify(manifest, null, 2);
3252
- const bytes = Buffer.byteLength(json, "utf8");
3253
- return {
3254
- bytes,
3255
- estimatedTokens: Math.ceil(bytes / 4)
3256
- };
3257
- }
3258
- function resolveDiffManifestPromptLimits(config) {
3259
- return {
3260
- ...defaultDiffManifestPromptLimits,
3261
- ...Object.fromEntries(Object.entries(config ?? {}).filter((entry) => entry[1] !== void 0))
3262
- };
3263
- }
3264
- function manifestOptionsHaveEffect(options) {
3265
- return Boolean(options?.compressed || options?.includePreviews === false || options?.maxPreviewLines !== void 0 || options?.paths);
3266
- }
3267
- function withoutCompressedFileFields(file, compressed) {
3268
- if (!compressed) return file;
3269
- const { signals: _signals, changedSymbols: _changedSymbols, ...rest } = file;
3270
- return rest;
3271
- }
3272
- function withoutCompressedRangeFields(range, compressed) {
3273
- if (!compressed) return range;
3274
- const { summary: _summary, ...rest } = range;
3275
- return rest;
3276
- }
3277
- function rangeFieldsForOptions(range, options) {
3278
- const fields = withoutCompressedRangeFields(range, options.compressed === true);
3279
- if (options.includePreviews === false) {
3280
- const { preview: _preview, ...rest } = fields;
3281
- return rest;
3282
- }
3283
- return fields;
3284
- }
3285
- function truncatePreview(preview, maxLines) {
3286
- if (preview === void 0 || maxLines === void 0) return preview;
3287
- return preview.split("\n").slice(0, maxLines).join("\n");
3288
- }
3289
- function condenseDiffManifestFile(file) {
3290
- return {
3291
- path: file.path,
3292
- previousPath: file.previousPath,
3293
- status: file.status,
3294
- language: file.language,
3295
- additions: file.additions,
3296
- deletions: file.deletions,
3297
- hunks: file.hunks.map((hunk) => ({
3298
- hunkIndex: hunk.hunkIndex,
3299
- header: hunk.header,
3300
- oldStart: hunk.oldStart,
3301
- oldLines: hunk.oldLines,
3302
- newStart: hunk.newStart,
3303
- newLines: hunk.newLines,
3304
- contentHash: hunk.contentHash
3305
- })),
3306
- commentableRanges: file.commentableRanges.map((range) => ({
3307
- id: range.id,
3308
- path: range.path,
3309
- side: range.side,
3310
- startLine: range.startLine,
3311
- endLine: range.endLine,
3312
- kind: range.kind,
3313
- hunkIndex: range.hunkIndex,
3314
- hunkHeader: range.hunkHeader,
3315
- hunkContentHash: range.hunkContentHash
3316
- })),
3317
- excludedReason: file.excludedReason
3318
- };
3319
- }
3320
- function fitsLimit(metrics, maxBytes, maxEstimatedTokens) {
3321
- return metrics.bytes <= maxBytes && metrics.estimatedTokens <= maxEstimatedTokens;
3322
- }
3323
- //#endregion
3324
3483
  //#region src/review/agent/diff-manifest-context.ts
3325
3484
  function prepareDiffManifestContext(options) {
3326
3485
  const manifest = readReservedInputManifest(options.input);
@@ -3652,9 +3811,9 @@ function parseAgentOutput(output, agent) {
3652
3811
  let lastError = "";
3653
3812
  for (const payload of jsonPayloadCandidates(output)) try {
3654
3813
  const json = JSON.parse(payload);
3655
- if (agent.definition.output.id === prReviewSchemaId) return {
3814
+ if (agent.definition.output.id === reviewResultSchemaId) return {
3656
3815
  ok: true,
3657
- value: parsePrReview(json),
3816
+ value: parseReviewResult(json),
3658
3817
  repairAttempted: false
3659
3818
  };
3660
3819
  return {
@@ -3690,6 +3849,9 @@ function buildRepairPrompt(options) {
3690
3849
  ].join("\n\n");
3691
3850
  }
3692
3851
  //#endregion
3852
+ //#region package.json
3853
+ var version = "0.2.0";
3854
+ //#endregion
3693
3855
  //#region src/review/prior-state.ts
3694
3856
  const mainCommentMarker = "pipr:main-comment";
3695
3857
  const inlineFindingMarkerPrefix = "pipr:finding";
@@ -3732,7 +3894,7 @@ function buildPriorReviewState(options) {
3732
3894
  const prior = priorFindings.get(id);
3733
3895
  if (prior) usedPriorIds.add(prior.id);
3734
3896
  currentFindingIds.add(id);
3735
- nextFindings.set(id, priorFindingRecordSchema.parse({
3897
+ const record = {
3736
3898
  id,
3737
3899
  status: "open",
3738
3900
  path: finding.path,
@@ -3742,29 +3904,30 @@ function buildPriorReviewState(options) {
3742
3904
  endLine: finding.endLine,
3743
3905
  firstSeenHeadSha: prior?.firstSeenHeadSha ?? options.reviewedHeadSha,
3744
3906
  lastSeenHeadSha: options.reviewedHeadSha,
3745
- lastCommentedHeadSha: prior?.lastCommentedHeadSha
3746
- }));
3907
+ ...prior?.lastCommentedHeadSha ? { lastCommentedHeadSha: prior.lastCommentedHeadSha } : {}
3908
+ };
3909
+ nextFindings.set(id, record);
3747
3910
  }
3748
3911
  for (const prior of priorFindings.values()) {
3749
3912
  if (nextFindings.has(prior.id)) continue;
3750
- nextFindings.set(prior.id, priorFindingRecordSchema.parse(prior));
3913
+ nextFindings.set(prior.id, prior);
3751
3914
  }
3752
- return { state: priorReviewStateSchema.parse({
3915
+ return {
3753
3916
  version: 1,
3754
3917
  reviewedHeadSha: options.reviewedHeadSha,
3755
3918
  selectedTasks: options.selectedTasks,
3756
3919
  findings: cappedFindings([...nextFindings.values()], currentFindingIds)
3757
- }) };
3920
+ };
3758
3921
  }
3759
3922
  function resolvePriorFindings(state, findingIds) {
3760
3923
  const resolved = new Set(findingIds);
3761
- return priorReviewStateSchema.parse({
3924
+ return {
3762
3925
  ...state,
3763
3926
  findings: state.findings.map((finding) => ({
3764
3927
  ...finding,
3765
3928
  status: resolved.has(finding.id) ? "resolved" : finding.status
3766
3929
  }))
3767
- });
3930
+ };
3768
3931
  }
3769
3932
  function priorReviewStateForSelectedTasks(state, selectedTasks) {
3770
3933
  if (!state || state.selectedTasks.length !== selectedTasks.length || !state.selectedTasks.every((taskName, index) => taskName === selectedTasks[index])) return;
@@ -3818,26 +3981,26 @@ function renderVerifierResponseMarker(findingId, responseKey) {
3818
3981
  return `<!-- ${verifierResponseMarkerPrefix} id=${findingId} key=${responseKey} -->`;
3819
3982
  }
3820
3983
  function extractInlineFindingMarkerRecords(commentBodies) {
3821
- return commentBodies.flatMap((body) => [parseFindingHeadMarker(firstNonEmptyLine(body), inlineFindingMarkerPrefix)].filter((marker) => marker !== void 0));
3984
+ return extractMarkerRecords(commentBodies, inlineFindingMarkerPrefix);
3822
3985
  }
3823
3986
  function extractInlineFindingMarkers(commentBodies) {
3824
3987
  return new Set(extractInlineFindingMarkerRecords(commentBodies).map((record) => record.marker));
3825
3988
  }
3826
3989
  function extractResolvedFindingMarkerRecords(commentBodies) {
3827
- return commentBodies.flatMap((body) => [parseFindingHeadMarker(firstNonEmptyLine(body), resolvedFindingMarkerPrefix)].filter((marker) => marker !== void 0));
3990
+ return extractMarkerRecords(commentBodies, resolvedFindingMarkerPrefix);
3828
3991
  }
3829
3992
  function applyResolvedFindingMarkers(state, commentBodies) {
3830
3993
  const resolvedMarkers = new Set(extractResolvedFindingMarkerRecords(commentBodies).map((record) => `${record.id}:${record.head}`));
3831
- return priorReviewStateSchema.parse({
3994
+ return {
3832
3995
  ...state,
3833
3996
  findings: state.findings.map((finding) => ({
3834
3997
  ...finding,
3835
3998
  status: finding.lastCommentedHeadSha && resolvedMarkers.has(`${finding.id}:${finding.lastCommentedHeadSha}`) ? "resolved" : finding.status
3836
3999
  }))
3837
- });
4000
+ };
3838
4001
  }
3839
4002
  function extractVerifierResponseMarkers(commentBodies) {
3840
- return new Set(commentBodies.flatMap((body) => [parseFindingHeadMarker(firstNonEmptyLine(body), verifierResponseMarkerPrefix)].filter((marker) => marker !== void 0)).map((record) => record.marker));
4003
+ return new Set(extractMarkerRecords(commentBodies, verifierResponseMarkerPrefix).map((record) => record.marker));
3841
4004
  }
3842
4005
  function isPiprThreadActionReplyBody(body) {
3843
4006
  const parsed = parsePiprMarker(body ? firstNonEmptyLine(body) : void 0);
@@ -3849,13 +4012,17 @@ function applyInlineFindingMarkers(state, commentBodies) {
3849
4012
  const [, , findingId, headSha] = marker.split(":");
3850
4013
  if (findingId && headSha) markerById.set(findingId, headSha);
3851
4014
  }
3852
- return priorReviewStateSchema.parse({
4015
+ return {
3853
4016
  ...state,
3854
- findings: state.findings.map((finding) => ({
3855
- ...finding,
3856
- lastCommentedHeadSha: markerById.get(finding.id)
3857
- }))
3858
- });
4017
+ findings: state.findings.map((finding) => {
4018
+ const headSha = markerById.get(finding.id);
4019
+ const { lastCommentedHeadSha: _lastCommentedHeadSha, ...rest } = finding;
4020
+ return headSha ? {
4021
+ ...rest,
4022
+ lastCommentedHeadSha: headSha
4023
+ } : rest;
4024
+ })
4025
+ };
3859
4026
  }
3860
4027
  function findingIdFor(finding, state) {
3861
4028
  return (state ? matchFindingRecord(state, finding) : void 0)?.id ?? newFindingId(finding);
@@ -3904,8 +4071,11 @@ function parseFindingHeadMarker(comment, prefix) {
3904
4071
  marker: prefix === inlineFindingMarkerPrefix ? inlineFindingMarker(id, head) : prefix === resolvedFindingMarkerPrefix ? `${resolvedFindingMarkerPrefix}:${id}:${head}` : `${verifierResponseMarkerPrefix}:${id}:${head}`
3905
4072
  };
3906
4073
  }
4074
+ function extractMarkerRecords(commentBodies, prefix) {
4075
+ return commentBodies.flatMap((body) => [parseFindingHeadMarker(firstNonEmptyLine(body), prefix)].filter((marker) => marker !== void 0));
4076
+ }
3907
4077
  function encodeReviewState(state) {
3908
- return Buffer$1.from(JSON.stringify(priorReviewStateSchema.parse(state))).toString("base64url");
4078
+ return Buffer$1.from(JSON.stringify(state)).toString("base64url");
3909
4079
  }
3910
4080
  function decodeReviewState(value) {
3911
4081
  if (!value) return;
@@ -3939,143 +4109,8 @@ function hashParts(parts) {
3939
4109
  return new Bun.CryptoHasher("sha256").update(parts.join("\n")).digest("hex").slice(0, 16);
3940
4110
  }
3941
4111
  //#endregion
3942
- //#region src/action/entry-dispatch.ts
3943
- const permissionOrder = [
3944
- "read",
3945
- "triage",
3946
- "write",
3947
- "maintain",
3948
- "admin"
3949
- ];
3950
- const changeRequestActions = [
3951
- "opened",
3952
- "updated",
3953
- "reopened",
3954
- "ready",
3955
- "closed"
3956
- ];
3957
- function dispatchRuntimeEntry(options) {
3958
- if (options.kind === "change-request") return {
3959
- kind: "change-request",
3960
- tasks: selectRuntimeTasks({
3961
- plan: options.plan,
3962
- event: options.event,
3963
- taskName: options.taskName
3964
- }),
3965
- taskName: options.taskName
3966
- };
3967
- return resolvePlanCommand(options.plan, options.line);
3968
- }
3969
- function selectRuntimeTasks(options) {
3970
- if (options.taskName) return options.plan.tasks.filter((task) => task.name === options.taskName);
3971
- return selectChangeRequestTasks(options.plan, options.event);
3972
- }
3973
- function selectLocalReviewTasks(plan) {
3974
- return uniqBy(plan.changeRequestTriggers.map((trigger) => trigger.task), (task) => task.name).filter((task) => task.local !== false);
3975
- }
3976
- function selectChangeRequestTasks(plan, event) {
3977
- if (!changeRequestActions.includes(event.action)) return [];
3978
- const action = event.action;
3979
- return uniqBy(plan.changeRequestTriggers.filter((trigger) => trigger.actions.includes(action)).map((trigger) => trigger.task), (task) => task.name);
3980
- }
3981
- function selectPlanCommand(plan, line) {
3982
- let firstInvalid;
3983
- for (const command of plan.commands) {
3984
- const parsed = parseCommandPattern(command.pattern, line);
3985
- if (!parsed.ok) {
3986
- if (commandPatternPrefixMatches(command.pattern, line) && !firstInvalid) firstInvalid = {
3987
- kind: "invalid",
3988
- command,
3989
- error: parsed.error
3990
- };
3991
- continue;
3992
- }
3993
- return {
3994
- kind: "matched",
3995
- command,
3996
- commandName: command.pattern.replace(/^@pipr\s+/, "").split(/\s+/)[0] ?? command.pattern,
3997
- line,
3998
- arguments: parsed.value
3999
- };
4000
- }
4001
- return firstInvalid;
4002
- }
4003
- function resolvePlanCommand(plan, line) {
4004
- if (!line) return {
4005
- kind: "ignored",
4006
- reason: "comment did not contain a command line"
4007
- };
4008
- if (!isPiprCommandLine(line)) return {
4009
- kind: "ignored",
4010
- reason: "comment did not target pipr"
4011
- };
4012
- const selected = selectPlanCommand(plan, line);
4013
- if (selected?.kind === "matched") return {
4014
- kind: "matched",
4015
- invocation: {
4016
- taskName: selected.command.task.name,
4017
- commandName: selected.commandName,
4018
- requiredPermission: selected.command.permission,
4019
- line: selected.line,
4020
- pattern: selected.command.pattern,
4021
- arguments: selected.arguments
4022
- }
4023
- };
4024
- if (selected?.kind === "invalid") return {
4025
- kind: "invalid",
4026
- reason: selected.error,
4027
- requiredPermission: selected.command.permission,
4028
- body: renderPlanCommandHelp(plan, selected.error)
4029
- };
4030
- return {
4031
- kind: "help",
4032
- reason: `unknown pipr command '${line}'`,
4033
- requiredPermission: "read",
4034
- body: renderPlanCommandHelp(plan, `Unknown command: ${line}`)
4035
- };
4036
- }
4037
- function parsePlanCommandInputs(plan, invocation) {
4038
- const matchingCommand = plan.commands.find((candidate) => candidate.task.name === invocation.taskName && candidate.pattern === invocation.pattern) ?? plan.commands.find((candidate) => candidate.task.name === invocation.taskName);
4039
- if (!matchingCommand) return {
4040
- kind: "invalid",
4041
- reason: `No command registered for task '${invocation.taskName}'`,
4042
- requiredPermission: invocation.requiredPermission,
4043
- body: renderPlanCommandHelp(plan)
4044
- };
4045
- try {
4046
- return {
4047
- kind: "matched",
4048
- invocation: {
4049
- ...invocation,
4050
- inputs: matchingCommand.parse ? matchingCommand.parse(invocation.arguments) : invocation.arguments
4051
- }
4052
- };
4053
- } catch (error) {
4054
- const reason = error instanceof Error ? error.message : String(error);
4055
- return {
4056
- kind: "invalid",
4057
- reason,
4058
- requiredPermission: invocation.requiredPermission,
4059
- body: renderPlanCommandHelp(plan, reason)
4060
- };
4061
- }
4062
- }
4063
- function hasRequiredRepositoryPermission(actual, required) {
4064
- if (actual === "none") return false;
4065
- return permissionOrder.indexOf(actual) >= permissionOrder.indexOf(required);
4066
- }
4067
- function permissionDeniedHelp(plan, required) {
4068
- return renderPlanCommandHelp(plan, `Permission denied: requires ${required}.`);
4069
- }
4070
- function renderPlanCommandHelp(plan, reason) {
4071
- const lines = ["# pipr commands", ""];
4072
- if (reason) lines.push(reason, "");
4073
- for (const command of plan.commands) lines.push(`- ${command.pattern} (${command.permission})`);
4074
- return lines.join("\n");
4075
- }
4076
- //#endregion
4077
4112
  //#region src/review/comment.ts
4078
- const runtimeVersion = "0.1.2";
4113
+ const runtimeVersion = version;
4079
4114
  const inlinePublicationItemSchema = z.strictObject({
4080
4115
  finding: reviewFindingSchema,
4081
4116
  range: commentableRangeSchema,
@@ -4146,7 +4181,7 @@ function buildPublicationPlan(options) {
4146
4181
  findings: options.inlineItems.map((item) => item.finding),
4147
4182
  reviewedHeadSha: options.metadata.reviewedHeadSha,
4148
4183
  selectedTasks: options.metadata.selectedTasks
4149
- }).state;
4184
+ });
4150
4185
  const cappedInlineItems = options.maxInlineComments === void 0 ? options.inlineItems : options.inlineItems.slice(0, options.maxInlineComments);
4151
4186
  const metadata = publicationMetadataSchema.parse({
4152
4187
  ...options.metadata,
@@ -4227,7 +4262,7 @@ function buildCommentPublishingPlan(options) {
4227
4262
  findings: options.validated.validFindings,
4228
4263
  reviewedHeadSha: options.event.change.head.sha,
4229
4264
  selectedTasks: options.metadata.selectedTasks
4230
- }).state;
4265
+ });
4231
4266
  const inlineCommentDrafts = prepareInlinePublicationItems({
4232
4267
  validated: options.validated,
4233
4268
  manifest: options.manifest,
@@ -4469,7 +4504,7 @@ function boundedVerifierText(value) {
4469
4504
  }
4470
4505
  //#endregion
4471
4506
  //#region src/review/task/task-output.ts
4472
- const findingScopeMarker = Symbol("pipr.findingScope");
4507
+ const agentInlineFindingsOutputSchema = z.custom((value) => z.looseObject({ inlineFindings: z.array(reviewFindingSchema) }).safeParse(value).success);
4473
4508
  function createOutputState() {
4474
4509
  return {
4475
4510
  findings: [],
@@ -4491,16 +4526,19 @@ function mergeTaskOutputs(results) {
4491
4526
  }
4492
4527
  function mergeCommentContribution(merged, comment) {
4493
4528
  if (!comment) return;
4494
- if (merged.comment) throw new Error(`ctx.comment(...) may be called once per selected run; received comments from '${merged.comment.taskName}' and '${comment.taskName}'`);
4495
- if (merged.commandResponse) throw new Error("ctx.comment(...) and ctx.command.reply(...) cannot both be called");
4529
+ assertOutputContributionAllowed(merged, "comment", comment.taskName, (existing, next) => `ctx.comment(...) may be called once per selected run; received comments from '${existing}' and '${next}'`);
4496
4530
  merged.comment = comment;
4497
4531
  }
4498
4532
  function mergeCommandResponseContribution(merged, commandResponse) {
4499
4533
  if (!commandResponse) return;
4500
- if (merged.commandResponse) throw new Error(`ctx.command.reply(...) may be called once per selected run; received replies from '${merged.commandResponse.taskName}' and '${commandResponse.taskName}'`);
4501
- if (merged.comment) throw new Error("ctx.comment(...) and ctx.command.reply(...) cannot both be called");
4534
+ assertOutputContributionAllowed(merged, "commandResponse", commandResponse.taskName, (existing, next) => `ctx.command.reply(...) may be called once per selected run; received replies from '${existing}' and '${next}'`);
4502
4535
  merged.commandResponse = commandResponse;
4503
4536
  }
4537
+ function assertOutputContributionAllowed(state, kind, taskName, duplicateMessage) {
4538
+ const existing = kind === "comment" ? state.comment : state.commandResponse;
4539
+ if (existing) throw new Error(duplicateMessage(existing.taskName, taskName));
4540
+ if (kind === "comment" ? state.commandResponse : state.comment) throw new Error("ctx.comment(...) and ctx.command.reply(...) cannot both be called");
4541
+ }
4504
4542
  function createCheckHandle(state) {
4505
4543
  return {
4506
4544
  pass(summary) {
@@ -4532,8 +4570,7 @@ function runtimeTaskCheckResult(taskName, check) {
4532
4570
  };
4533
4571
  }
4534
4572
  function collectComment(state, value, taskName) {
4535
- if (state.commandResponse) throw new Error("ctx.comment(...) and ctx.command.reply(...) cannot both be called");
4536
- if (state.comment) throw new Error(`ctx.comment(...) may be called once per selected run; '${taskName}' called it more than once`);
4573
+ assertOutputContributionAllowed(state, "comment", taskName, () => `ctx.comment(...) may be called once per selected run; '${taskName}' called it more than once`);
4537
4574
  state.comment = {
4538
4575
  taskName,
4539
4576
  value
@@ -4542,8 +4579,7 @@ function collectComment(state, value, taskName) {
4542
4579
  collectInlineFindings(state, value.inlineFindings);
4543
4580
  }
4544
4581
  function collectCommandResponse(state, value, taskName) {
4545
- if (state.comment) throw new Error("ctx.comment(...) and ctx.command.reply(...) cannot both be called");
4546
- if (state.commandResponse) throw new Error(`ctx.command.reply(...) may be called once per selected run; '${taskName}' called it more than once`);
4582
+ assertOutputContributionAllowed(state, "commandResponse", taskName, () => `ctx.command.reply(...) may be called once per selected run; '${taskName}' called it more than once`);
4547
4583
  state.commandResponse = {
4548
4584
  taskName,
4549
4585
  value
@@ -4576,30 +4612,13 @@ function collectInlineFindings(state, findings) {
4576
4612
  const arrayScope = state.findingScopes.get(findings);
4577
4613
  state.findings.push(...findings.map((finding) => ({
4578
4614
  finding,
4579
- paths: finding[findingScopeMarker] ?? arrayScope
4615
+ paths: arrayScope
4580
4616
  })));
4581
4617
  }
4582
4618
  function trackResultFindingScope(state, value, paths) {
4583
- if (!hasInlineFindings(value)) return;
4584
4619
  if (!paths) return;
4585
- state.findingScopes.set(value.inlineFindings, paths);
4586
- for (const finding of value.inlineFindings) {
4587
- if (!isReviewFindingLike(finding)) continue;
4588
- markFindingScope(finding, paths);
4589
- }
4590
- }
4591
- function markFindingScope(finding, paths) {
4592
- Object.defineProperty(finding, findingScopeMarker, {
4593
- value: paths,
4594
- enumerable: true,
4595
- configurable: true
4596
- });
4597
- }
4598
- function hasInlineFindings(value) {
4599
- return typeof value === "object" && value !== null && Array.isArray(value.inlineFindings);
4600
- }
4601
- function isReviewFindingLike(value) {
4602
- return typeof value === "object" && value !== null && typeof value.body === "string" && typeof value.path === "string" && typeof value.rangeId === "string";
4620
+ const parsed = agentInlineFindingsOutputSchema.safeParse(value);
4621
+ if (parsed.success) state.findingScopes.set(parsed.data.inlineFindings, paths);
4603
4622
  }
4604
4623
  function collectedReview(output) {
4605
4624
  return {
@@ -4731,7 +4750,7 @@ async function runTaskRuntime(options) {
4731
4750
  });
4732
4751
  if (commandResponse) return commandResponse;
4733
4752
  assertReviewCommentOutput(output, options.commandInvocation !== void 0);
4734
- const validated = validatePrReview(collectedReview(output), diffManifest, {
4753
+ const validated = validateReviewResult(collectedReview(output), diffManifest, {
4735
4754
  expectedHeadSha: options.event.change.head.sha,
4736
4755
  pathScopeForFinding: (_finding, index) => output.findings[index]?.paths
4737
4756
  });
@@ -4883,7 +4902,7 @@ function createTaskContext(options) {
4883
4902
  options.output.providerModels.push(...result.providerModels);
4884
4903
  if (result.repairAttempted) options.output.repairAttempted = true;
4885
4904
  trackResultFindingScope(options.output, result.value, runOptions?.paths);
4886
- return result.value;
4905
+ return agentOutputForTaskContext(agent, result.value);
4887
4906
  } },
4888
4907
  review: { async prior() {
4889
4908
  return priorReviewForTask(options.priorMainComment, options.priorReviewState);
@@ -4896,6 +4915,9 @@ function createTaskContext(options) {
4896
4915
  };
4897
4916
  return taskContext;
4898
4917
  }
4918
+ function agentOutputForTaskContext(_agent, value) {
4919
+ return value;
4920
+ }
4899
4921
  function resolveTaskSecret(secret, options) {
4900
4922
  if (secret.kind !== "pipr.secret" || typeof secret.name !== "string") throw new Error("ctx.secret(...) requires a pipr.secret reference");
4901
4923
  const value = (options.env ?? process.env)[secret.name];
@@ -6243,105 +6265,31 @@ async function logPhase(log, name, run) {
6243
6265
  throw error;
6244
6266
  }
6245
6267
  }
6246
- function logEventContext(log, event) {
6247
- log.notice("event", {
6248
- platform: event.platform.id,
6249
- eventName: event.eventName,
6250
- action: event.action,
6251
- rawAction: event.rawAction,
6252
- repo: event.repository.slug,
6253
- change: event.change.number,
6254
- base: shortSha(event.change.base.sha),
6255
- head: shortSha(event.change.head.sha),
6256
- fork: event.change.isFork
6257
- });
6258
- }
6259
- function logTrustedRuntime(log, runtime) {
6260
- log.notice("trusted config", {
6261
- source: runtime.settings.source,
6262
- trustedConfigSha: shortSha(runtime.trustedConfigSha),
6263
- trustedConfigHash: runtime.trustedConfigHash.slice(0, 12),
6264
- providers: runtime.settings.config.providers.map((provider) => `${provider.id}:${provider.model}`).join(","),
6265
- tasks: runtime.plan.tasks.length,
6266
- commands: runtime.plan.commands.length
6267
- });
6268
- }
6269
- function addProviderSecrets(log, config, env) {
6270
- for (const provider of config.providers) log.addSecret((env ?? process.env)[provider.apiKeyEnv]);
6271
- }
6272
- //#endregion
6273
- //#region src/action/git-project.ts
6274
- async function loadRuntimeProjectFromGitCommit(options) {
6275
- const configDir = resolveContainedConfigDir(options);
6276
- const files = listConfigFilesAtCommit(options.rootDir, options.commitSha, configDir.gitPath);
6277
- if (files.length === 0) throw new Error(`${configDir.configDir}/config.ts is required at base commit ${options.commitSha}`);
6278
- const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-base-config-"));
6279
- try {
6280
- const hash = new Bun.CryptoHasher("sha256");
6281
- for (const file of files) {
6282
- assertRegularFile(file, options.commitSha);
6283
- const relativePath = relativeGitPath(configDir.gitPath, file.path);
6284
- const targetPath = path.join(tempRoot, configDir.relativeConfigDir, ...relativePath.split("/"));
6285
- const contents = showFileAtCommit(options.rootDir, options.commitSha, file.path);
6286
- hash.update(relativePath);
6287
- hash.update("\0");
6288
- hash.update(contents);
6289
- hash.update("\0");
6290
- await mkdir(path.dirname(targetPath), { recursive: true });
6291
- await Bun.write(targetPath, contents);
6292
- }
6293
- return {
6294
- ...await loadRuntimeProject({
6295
- rootDir: tempRoot,
6296
- configDir: configDir.relativeConfigDir,
6297
- env: options.env,
6298
- requireProviderEnv: false
6299
- }),
6300
- trustedConfigSha: options.commitSha,
6301
- trustedConfigHash: hash.digest("hex")
6302
- };
6303
- } finally {
6304
- await rm(tempRoot, {
6305
- recursive: true,
6306
- force: true
6307
- });
6308
- }
6309
- }
6310
- function listConfigFilesAtCommit(rootDir, commitSha, gitPath) {
6311
- return runGit$1([
6312
- "ls-tree",
6313
- "-r",
6314
- "-z",
6315
- commitSha,
6316
- "--",
6317
- gitPath
6318
- ], rootDir).split("\0").filter(Boolean).map((entry) => parseGitTreeEntry(entry, commitSha));
6319
- }
6320
- function parseGitTreeEntry(entry, commitSha) {
6321
- const separatorIndex = entry.indexOf(" ");
6322
- if (separatorIndex === -1) throw new Error(`Could not parse git tree entry at ${commitSha}: ${entry}`);
6323
- const metadata = entry.slice(0, separatorIndex);
6324
- const filePath = entry.slice(separatorIndex + 1);
6325
- const [mode = ""] = metadata.split(" ");
6326
- if (!mode || !filePath) throw new Error(`Could not parse git tree entry at ${commitSha}: ${entry}`);
6327
- return {
6328
- mode,
6329
- path: filePath
6330
- };
6331
- }
6332
- function assertRegularFile(file, commitSha) {
6333
- if (file.mode !== "100644" && file.mode !== "100755") throw new Error(`${file.path}: only regular config files are supported at ${commitSha}`);
6334
- }
6335
- function relativeGitPath(root, filePath) {
6336
- const relative = root === "." ? filePath : path.posix.relative(root, filePath);
6337
- assertRelativeGitPath(root, filePath, relative);
6338
- return relative;
6339
- }
6340
- function assertRelativeGitPath(root, filePath, relative) {
6341
- if (!relative || relative.startsWith("..") || path.posix.isAbsolute(relative)) throw new Error(`${filePath}: git path escaped configDir ${root}`);
6268
+ function logEventContext(log, event) {
6269
+ log.notice("event", {
6270
+ platform: event.platform.id,
6271
+ eventName: event.eventName,
6272
+ action: event.action,
6273
+ rawAction: event.rawAction,
6274
+ repo: event.repository.slug,
6275
+ change: event.change.number,
6276
+ base: shortSha(event.change.base.sha),
6277
+ head: shortSha(event.change.head.sha),
6278
+ fork: event.change.isFork
6279
+ });
6342
6280
  }
6343
- function showFileAtCommit(rootDir, commitSha, filePath) {
6344
- return runGit$1(["show", `${commitSha}:${filePath}`], rootDir);
6281
+ function logTrustedRuntime(log, runtime) {
6282
+ log.notice("trusted config", {
6283
+ source: runtime.settings.source,
6284
+ trustedConfigSha: shortSha(runtime.trustedConfigSha),
6285
+ trustedConfigHash: runtime.trustedConfigHash.slice(0, 12),
6286
+ providers: runtime.settings.config.providers.map((provider) => `${provider.id}:${provider.model}`).join(","),
6287
+ tasks: runtime.plan.tasks.length,
6288
+ commands: runtime.plan.commands.length
6289
+ });
6290
+ }
6291
+ function addProviderSecrets(log, config, env) {
6292
+ for (const provider of config.providers) log.addSecret((env ?? process.env)[provider.apiKeyEnv]);
6345
6293
  }
6346
6294
  //#endregion
6347
6295
  //#region src/action/runtime-checks.ts
@@ -6601,146 +6549,286 @@ async function runTrustedReviewAndPublish(options) {
6601
6549
  }
6602
6550
  }
6603
6551
  //#endregion
6604
- //#region src/action/commands.ts
6605
- /** Initializes the official minimal `.pipr` project files. */
6606
- async function runInitCommand(options) {
6607
- return await initOfficialMinimalProject({
6552
+ //#region src/action/git-project.ts
6553
+ async function loadRuntimeProjectFromGitCommit(options) {
6554
+ const configDir = resolveContainedConfigDir(options);
6555
+ const files = listConfigFilesAtCommit(options.rootDir, options.commitSha, configDir.gitPath);
6556
+ if (files.length === 0) throw new Error(`${configDir.configDir}/config.ts is required at base commit ${options.commitSha}`);
6557
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-base-config-"));
6558
+ try {
6559
+ const hash = new Bun.CryptoHasher("sha256");
6560
+ for (const file of files) {
6561
+ assertRegularFile(file, options.commitSha);
6562
+ const relativePath = relativeGitPath(configDir.gitPath, file.path);
6563
+ const targetPath = path.join(tempRoot, configDir.relativeConfigDir, ...relativePath.split("/"));
6564
+ const contents = showFileAtCommit(options.rootDir, options.commitSha, file.path);
6565
+ hash.update(relativePath);
6566
+ hash.update("\0");
6567
+ hash.update(contents);
6568
+ hash.update("\0");
6569
+ await mkdir(path.dirname(targetPath), { recursive: true });
6570
+ await Bun.write(targetPath, contents);
6571
+ }
6572
+ return {
6573
+ ...await loadRuntimeProject({
6574
+ rootDir: tempRoot,
6575
+ configDir: configDir.relativeConfigDir,
6576
+ env: options.env,
6577
+ requireProviderEnv: false
6578
+ }),
6579
+ trustedConfigSha: options.commitSha,
6580
+ trustedConfigHash: hash.digest("hex")
6581
+ };
6582
+ } finally {
6583
+ await rm(tempRoot, {
6584
+ recursive: true,
6585
+ force: true
6586
+ });
6587
+ }
6588
+ }
6589
+ function listConfigFilesAtCommit(rootDir, commitSha, gitPath) {
6590
+ return runGit$1([
6591
+ "ls-tree",
6592
+ "-r",
6593
+ "-z",
6594
+ commitSha,
6595
+ "--",
6596
+ gitPath
6597
+ ], rootDir).split("\0").filter(Boolean).map((entry) => parseGitTreeEntry(entry, commitSha)).filter((file) => !isIgnoredGitConfigPath(gitPath, file.path));
6598
+ }
6599
+ function isIgnoredGitConfigPath(gitPath, filePath) {
6600
+ const relative = gitPath === "." ? filePath : path.posix.relative(gitPath, filePath);
6601
+ return relative === "node_modules" || relative.startsWith("node_modules/");
6602
+ }
6603
+ function parseGitTreeEntry(entry, commitSha) {
6604
+ const separatorIndex = entry.indexOf(" ");
6605
+ if (separatorIndex === -1) throw new Error(`Could not parse git tree entry at ${commitSha}: ${entry}`);
6606
+ const metadata = entry.slice(0, separatorIndex);
6607
+ const filePath = entry.slice(separatorIndex + 1);
6608
+ const [mode = ""] = metadata.split(" ");
6609
+ if (!mode || !filePath) throw new Error(`Could not parse git tree entry at ${commitSha}: ${entry}`);
6610
+ return {
6611
+ mode,
6612
+ path: filePath
6613
+ };
6614
+ }
6615
+ function assertRegularFile(file, commitSha) {
6616
+ if (file.mode !== "100644" && file.mode !== "100755") throw new Error(`${file.path}: only regular config files are supported at ${commitSha}`);
6617
+ }
6618
+ function relativeGitPath(root, filePath) {
6619
+ const relative = root === "." ? filePath : path.posix.relative(root, filePath);
6620
+ assertRelativeGitPath(root, filePath, relative);
6621
+ return relative;
6622
+ }
6623
+ function assertRelativeGitPath(root, filePath, relative) {
6624
+ if (!relative || relative.startsWith("..") || path.posix.isAbsolute(relative)) throw new Error(`${filePath}: git path escaped configDir ${root}`);
6625
+ }
6626
+ function showFileAtCommit(rootDir, commitSha, filePath) {
6627
+ return runGit$1(["show", `${commitSha}:${filePath}`], rootDir);
6628
+ }
6629
+ //#endregion
6630
+ //#region src/action/trusted-runtime.ts
6631
+ async function loadTrustedRuntimeForEvent(options, event, log) {
6632
+ const trustedRuntime = await logPhase(log, "load trusted config", async () => loadRuntimeProjectFromGitCommit({
6608
6633
  rootDir: options.rootDir,
6609
6634
  configDir: options.configDir,
6610
- force: options.force,
6611
- adapters: options.adapters,
6612
- recipe: options.recipe,
6613
- typeSupport: options.typeSupport
6635
+ commitSha: event.change.base.sha,
6636
+ env: options.env
6637
+ }));
6638
+ logTrustedRuntime(log, trustedRuntime);
6639
+ return trustedRuntime;
6640
+ }
6641
+ async function prepareTrustedHeadCheckout(options, adapter, config, event, log) {
6642
+ addProviderSecrets(log, config, options.env);
6643
+ assertTrustedActionProviderEnv(options, config);
6644
+ await logPhase(log, "checkout head", async () => {
6645
+ adapter.workspace.ensureHeadCheckout({
6646
+ rootDir: options.rootDir,
6647
+ change: event
6648
+ });
6614
6649
  });
6615
6650
  }
6616
- /** Loads and validates the runtime project configuration. */
6617
- async function runValidateCommand(options) {
6618
- return (await validateProject({
6619
- ...options,
6620
- requireProviderEnv: options.requireProviderEnv ?? false
6621
- })).settings;
6651
+ //#endregion
6652
+ //#region src/action/command-entry.ts
6653
+ async function runIssueCommentActionCommand(options, adapter, log) {
6654
+ const prepared = await prepareIssueCommentCommand(options, adapter, log);
6655
+ if (prepared.kind === "ignored") {
6656
+ log.notice("action ignored", { reason: prepared.reason });
6657
+ return prepared;
6658
+ }
6659
+ return await dispatchIssueCommentCommand(options, adapter, prepared, log);
6622
6660
  }
6623
- /** Returns an inspectable summary of the configured runtime plan. */
6624
- async function runInspectCommand(options) {
6625
- const runtime = await loadRuntimeProject({
6626
- ...options,
6627
- requireProviderEnv: false
6661
+ async function prepareIssueCommentCommand(options, adapter, log) {
6662
+ const comment = await logPhase(log, "parse issue comment", async () => adapter.events.resolveCommandComment({
6663
+ eventPath: options.eventPath,
6664
+ env: options.env ?? process.env,
6665
+ workspace: options.rootDir
6666
+ }));
6667
+ const runnable = runnableIssueCommentCommand(comment, options.dryRun);
6668
+ if (runnable.kind === "ignored") return runnable;
6669
+ const loaded = await logPhase(log, "load change request", async () => adapter.events.loadChangeRequest({
6670
+ repository: comment.repository,
6671
+ changeNumber: comment.changeNumber,
6672
+ workspace: comment.workspace,
6673
+ eventName: comment.eventName,
6674
+ action: comment.action,
6675
+ rawAction: comment.rawAction
6676
+ }));
6677
+ const event = parseChangeRequestEventContext({
6678
+ eventName: loaded.eventName ?? comment.eventName,
6679
+ action: loaded.action ?? comment.action,
6680
+ rawAction: loaded.rawAction ?? comment.rawAction,
6681
+ platform: { id: adapter.id },
6682
+ repository: loaded.repository,
6683
+ change: loaded.change,
6684
+ workspace: loaded.workspace ?? comment.workspace
6685
+ });
6686
+ logEventContext(log, event);
6687
+ const trustedRuntime = await loadTrustedRuntimeForEvent(options, event, log);
6688
+ const resolution = resolvePlanCommand(trustedRuntime.plan, runnable.line);
6689
+ if (resolution.kind === "ignored") return {
6690
+ kind: "ignored",
6691
+ reason: resolution.reason
6692
+ };
6693
+ return {
6694
+ kind: "prepared",
6695
+ comment,
6696
+ line: runnable.line,
6697
+ event,
6698
+ trustedRuntime,
6699
+ resolution
6700
+ };
6701
+ }
6702
+ function runnableIssueCommentCommand(comment, dryRun) {
6703
+ if (!comment.isChangeRequest) return {
6704
+ kind: "ignored",
6705
+ reason: "issue_comment did not target a pull request"
6706
+ };
6707
+ if (comment.action !== "created") return {
6708
+ kind: "ignored",
6709
+ reason: `issue_comment action '${comment.action}' is not supported`
6710
+ };
6711
+ const line = firstNonEmptyLine(comment.body);
6712
+ if (!line || !isPiprCommandLine(line)) return {
6713
+ kind: "ignored",
6714
+ reason: "issue_comment did not target pipr"
6715
+ };
6716
+ return dryRun ? {
6717
+ kind: "ignored",
6718
+ reason: "PIPR_DRY_RUN=1; command dispatch skipped"
6719
+ } : {
6720
+ kind: "runnable",
6721
+ line
6722
+ };
6723
+ }
6724
+ async function dispatchIssueCommentCommand(options, adapter, prepared, log) {
6725
+ const requiredPermission = prepared.resolution.kind === "matched" ? prepared.resolution.invocation.requiredPermission : prepared.resolution.requiredPermission;
6726
+ const permission = await logPhase(log, "check command permission", async () => adapter.permissions.getRepositoryPermission({
6727
+ repository: prepared.comment.repository,
6728
+ actor: prepared.comment.actor
6729
+ }));
6730
+ log.notice("command dispatch", {
6731
+ resolution: prepared.resolution.kind,
6732
+ requiredPermission,
6733
+ actualPermission: permission
6734
+ });
6735
+ if (!hasRequiredRepositoryPermission(permission, requiredPermission)) return {
6736
+ kind: "command-help",
6737
+ event: prepared.event,
6738
+ configSource: prepared.trustedRuntime.settings.source,
6739
+ body: permissionDeniedHelp(prepared.trustedRuntime.plan, requiredPermission),
6740
+ reason: `permission denied for '${prepared.line}'`
6741
+ };
6742
+ if (prepared.resolution.kind === "help" || prepared.resolution.kind === "invalid") return {
6743
+ kind: "command-help",
6744
+ event: prepared.event,
6745
+ configSource: prepared.trustedRuntime.settings.source,
6746
+ body: prepared.resolution.body,
6747
+ reason: prepared.resolution.reason
6748
+ };
6749
+ const parsedResolution = parsePlanCommandInputs(prepared.trustedRuntime.plan, prepared.resolution.invocation);
6750
+ if (parsedResolution.kind === "invalid") return {
6751
+ kind: "command-help",
6752
+ event: prepared.event,
6753
+ configSource: prepared.trustedRuntime.settings.source,
6754
+ body: parsedResolution.body,
6755
+ reason: parsedResolution.reason
6756
+ };
6757
+ if (parsedResolution.kind !== "matched") return {
6758
+ kind: "ignored",
6759
+ reason: "command dispatch did not resolve to a runnable task"
6760
+ };
6761
+ await prepareTrustedHeadCheckout(options, adapter, prepared.trustedRuntime.settings.config, prepared.event, log);
6762
+ const dispatch = dispatchRuntimeEntry({
6763
+ kind: "change-request",
6764
+ plan: prepared.trustedRuntime.plan,
6765
+ event: prepared.event,
6766
+ taskName: parsedResolution.invocation.taskName
6767
+ });
6768
+ return await issueCommentCommandResult({
6769
+ adapter,
6770
+ completed: await runTrustedReviewAndPublish({
6771
+ options,
6772
+ adapter,
6773
+ trustedRuntime: prepared.trustedRuntime,
6774
+ event: prepared.event,
6775
+ taskName: parsedResolution.invocation.taskName,
6776
+ taskInput: parsedResolution.invocation.inputs,
6777
+ selectedTasks: dispatch.kind === "change-request" ? dispatch.tasks : [],
6778
+ commandInvocation: {
6779
+ name: parsedResolution.invocation.commandName,
6780
+ line: parsedResolution.invocation.line,
6781
+ arguments: parsedResolution.invocation.arguments
6782
+ },
6783
+ log
6784
+ }),
6785
+ event: prepared.event,
6786
+ commandName: parsedResolution.invocation.commandName,
6787
+ sourceCommentId: prepared.comment.commentId,
6788
+ configSource: prepared.trustedRuntime.settings.source
6628
6789
  });
6629
- return inspectRuntimePlan(runtime.plan, runtime.settings.source);
6630
6790
  }
6631
- /** Loads the runtime config and pull request event without running review publication. */
6632
- async function runDryRunCommand(options) {
6633
- const runtime = await loadRuntimeProject({
6634
- ...options,
6635
- requireProviderEnv: false
6636
- });
6637
- const event = await createActionHostAdapter(options).events.parseEvent({
6638
- eventPath: options.eventPath,
6639
- env: {
6640
- ...options.env,
6641
- GITHUB_WORKSPACE: options.rootDir,
6642
- GITHUB_EVENT_NAME: "pull_request"
6643
- },
6644
- workspace: options.rootDir
6791
+ async function issueCommentCommandResult(options) {
6792
+ if (options.completed.kind === "skipped") return {
6793
+ kind: "ignored",
6794
+ reason: options.completed.reason
6795
+ };
6796
+ if (options.completed.kind === "command-response") return await publishCommandResponseActionResult({
6797
+ adapter: options.adapter,
6798
+ completed: options.completed,
6799
+ event: options.event,
6800
+ sourceCommentId: options.sourceCommentId,
6801
+ configSource: options.configSource
6645
6802
  });
6646
6803
  return {
6647
- configSource: runtime.settings.source,
6648
- event
6804
+ kind: "review",
6805
+ event: options.event,
6806
+ command: options.commandName,
6807
+ configSource: options.configSource,
6808
+ review: options.completed.review,
6809
+ publication: options.completed.publication
6649
6810
  };
6650
6811
  }
6651
- /** Runs configured change-request tasks against local Git base and head revisions. */
6652
- async function runLocalReviewCommand(options) {
6653
- const log = options.logSink ? createRuntimeActionLog({
6654
- logSink: options.logSink,
6655
- env: options.env
6656
- }) : void 0;
6657
- log?.notice("local review start", {
6658
- root: options.rootDir,
6659
- configDir: options.configDir,
6660
- base: options.baseSha.slice(0, 12),
6661
- head: options.headSha?.slice(0, 12)
6662
- });
6663
- const runtime = await loadRuntimeProject({
6664
- ...options,
6665
- requireProviderEnv: true
6666
- });
6667
- log?.notice("local config loaded", {
6668
- source: runtime.settings.source,
6669
- providers: runtime.settings.config.providers.map((provider) => `${provider.id}:${provider.model}`).join(","),
6670
- tasks: runtime.plan.tasks.length,
6671
- commands: runtime.plan.commands.length
6672
- });
6673
- const selectedTasks = selectLocalReviewTasks(runtime.plan);
6674
- const includeWorkingTree = options.headSha === void 0;
6675
- const headSha = options.headSha ?? runGit$1(["rev-parse", "HEAD"], options.rootDir).trim();
6676
- const event = parseChangeRequestEventContext({ ...createLocalChangeRequestEvent({
6677
- rootDir: options.rootDir,
6678
- baseSha: options.baseSha,
6679
- headSha
6680
- }) });
6681
- if (log) {
6682
- logEventContext(log, event);
6683
- log.notice("local dispatch", {
6684
- selectedTasks: selectedTasks.map((task) => task.name),
6685
- skippedLocalTasks: runtime.plan.tasks.filter((task) => task.local === false).map((task) => task.name),
6686
- diffTarget: includeWorkingTree ? "working-tree" : "head-ref"
6687
- });
6688
- }
6689
- const result = await runTaskRuntime({
6690
- workspace: options.rootDir,
6691
- config: runtime.settings.config,
6692
- event,
6693
- env: options.env,
6694
- plan: runtime.plan,
6695
- selectedTasks,
6696
- emptyTasksReason: "No change-request tasks are configured for local review",
6697
- piExecutable: options.piExecutable,
6698
- diffManifestBuilder: includeWorkingTree ? (diffOptions) => buildDiffManifest({
6699
- ...diffOptions,
6700
- includeWorkingTree: true
6701
- }) : void 0,
6702
- log,
6703
- taskLog: options.taskLog
6704
- });
6705
- if (result.kind === "command-response") throw new Error("command response result is only supported for issue_comment commands");
6706
- log?.notice("local review complete", {
6707
- kind: result.kind,
6708
- taskChecks: result.taskChecks.length,
6709
- validFindings: result.kind === "review" ? result.validated.validFindings.length : void 0,
6710
- droppedFindings: result.kind === "review" ? result.validated.droppedFindings.length : void 0,
6711
- inlineDrafts: result.kind === "review" ? result.inlineCommentDrafts.length : void 0
6712
- });
6713
- return result;
6714
- }
6715
- /** Runs the GitHub Action workflow for pull request and issue-comment events. */
6716
- async function runActionCommand(options) {
6717
- return await runActionCommandWithDependencies(options);
6718
- }
6719
- async function runActionCommandWithDependencies(options) {
6720
- const log = createRuntimeActionLog({
6721
- logSink: options.logSink,
6722
- env: options.env
6723
- });
6724
- return await log.group("pipr action", async () => {
6725
- const eventName = (options.env ?? process.env).GITHUB_EVENT_NAME ?? "pull_request";
6726
- log.notice("action start", {
6727
- eventName,
6728
- dryRun: options.dryRun,
6729
- root: options.rootDir,
6730
- configDir: options.configDir
6731
- });
6732
- const adapter = createActionHostAdapter(options);
6733
- await logPhase(log, "workspace", async () => {
6734
- adapter.workspace.ensureWorkspaceSafeDirectory?.({
6735
- rootDir: options.rootDir,
6736
- env: options.env
6737
- });
6738
- });
6739
- if (eventName === "issue_comment") return await runIssueCommentActionCommand(options, adapter, log);
6740
- if (eventName === "pull_request_review_comment") return await runReviewCommentReplyActionCommand(options, adapter, log);
6741
- return await runPullRequestActionCommand(options, adapter, log);
6812
+ async function publishCommandResponseActionResult(options) {
6813
+ const publishCommandResponse = options.adapter.publication?.publishCommandResponse;
6814
+ if (!publishCommandResponse) throw new Error("command response publication is not available for this code host");
6815
+ const publication = await publishCommandResponse({
6816
+ change: options.event,
6817
+ sourceCommentId: options.sourceCommentId,
6818
+ commandName: options.completed.response.commandName,
6819
+ body: options.completed.response.body
6742
6820
  });
6821
+ return {
6822
+ kind: "command-response",
6823
+ event: options.event,
6824
+ command: options.completed.response.commandName,
6825
+ configSource: options.configSource,
6826
+ response: { body: options.completed.response.body },
6827
+ publication
6828
+ };
6743
6829
  }
6830
+ //#endregion
6831
+ //#region src/action/pull-request-entry.ts
6744
6832
  async function runPullRequestActionCommand(options, adapter, log) {
6745
6833
  const event = await logPhase(log, "parse event", async () => adapter.events.parseEvent({
6746
6834
  eventPath: options.eventPath,
@@ -6789,34 +6877,8 @@ async function runPullRequestActionCommand(options, adapter, log) {
6789
6877
  publication: completed.publication
6790
6878
  };
6791
6879
  }
6792
- async function loadTrustedRuntimeForEvent(options, event, log) {
6793
- const trustedRuntime = await logPhase(log, "load trusted config", async () => loadRuntimeProjectFromGitCommit({
6794
- rootDir: options.rootDir,
6795
- configDir: options.configDir,
6796
- commitSha: event.change.base.sha,
6797
- env: options.env
6798
- }));
6799
- logTrustedRuntime(log, trustedRuntime);
6800
- return trustedRuntime;
6801
- }
6802
- async function prepareTrustedHeadCheckout(options, adapter, config, event, log) {
6803
- addProviderSecrets(log, config, options.env);
6804
- assertTrustedActionProviderEnv(options, config);
6805
- await logPhase(log, "checkout head", async () => {
6806
- adapter.workspace.ensureHeadCheckout({
6807
- rootDir: options.rootDir,
6808
- change: event
6809
- });
6810
- });
6811
- }
6812
- async function runIssueCommentActionCommand(options, adapter, log) {
6813
- const prepared = await prepareIssueCommentCommand(options, adapter, log);
6814
- if (prepared.kind === "ignored") {
6815
- log.notice("action ignored", { reason: prepared.reason });
6816
- return prepared;
6817
- }
6818
- return await dispatchIssueCommentCommand(options, adapter, prepared, log);
6819
- }
6880
+ //#endregion
6881
+ //#region src/action/verifier-entry.ts
6820
6882
  async function runReviewCommentReplyActionCommand(options, adapter, log) {
6821
6883
  const capabilities = reviewCommentReplyDispatchCapabilities(options, adapter);
6822
6884
  if (capabilities.kind === "ignored") {
@@ -6966,201 +7028,173 @@ async function runReviewCommentVerifier(options, adapter, prepared, log) {
6966
7028
  function runnableReviewCommentReply(reply) {
6967
7029
  if (reply.action !== "created") return {
6968
7030
  kind: "ignored",
6969
- reason: `review comment action '${reply.action}' is not supported`
6970
- };
6971
- if (!reply.parentCommentId) return {
6972
- kind: "ignored",
6973
- reason: "review comment was not a reply"
6974
- };
6975
- if (reply.actor === "github-actions[bot]") return {
6976
- kind: "ignored",
6977
- reason: "review comment reply was authored by pipr"
6978
- };
6979
- if (isPiprThreadActionReplyBody(reply.body)) return {
6980
- kind: "ignored",
6981
- reason: "review comment reply was authored by pipr"
6982
- };
6983
- return { kind: "runnable" };
6984
- }
6985
- async function verifierActorAllowed(adapter, event, reply, config) {
6986
- const allowed = config.publication.autoResolve.userReplies.allowedActors;
6987
- if (allowed === "any") return true;
6988
- if (allowed === "author-or-write" && event.change.author?.login === reply.actor) return true;
6989
- return hasRequiredRepositoryPermission(await adapter.permissions.getRepositoryPermission({
6990
- repository: event.repository,
6991
- actor: reply.actor
6992
- }), "write");
6993
- }
6994
- async function prepareIssueCommentCommand(options, adapter, log) {
6995
- const comment = await logPhase(log, "parse issue comment", async () => adapter.events.resolveCommandComment({
6996
- eventPath: options.eventPath,
6997
- env: options.env ?? process.env,
6998
- workspace: options.rootDir
6999
- }));
7000
- const runnable = runnableIssueCommentCommand(comment, options.dryRun);
7001
- if (runnable.kind === "ignored") return runnable;
7002
- const loaded = await logPhase(log, "load change request", async () => adapter.events.loadChangeRequest({
7003
- repository: comment.repository,
7004
- changeNumber: comment.changeNumber,
7005
- workspace: comment.workspace,
7006
- eventName: comment.eventName,
7007
- action: comment.action,
7008
- rawAction: comment.rawAction
7009
- }));
7010
- const event = parseChangeRequestEventContext({
7011
- eventName: loaded.eventName ?? comment.eventName,
7012
- action: loaded.action ?? comment.action,
7013
- rawAction: loaded.rawAction ?? comment.rawAction,
7014
- platform: { id: adapter.id },
7015
- repository: loaded.repository,
7016
- change: loaded.change,
7017
- workspace: loaded.workspace ?? comment.workspace
7018
- });
7019
- logEventContext(log, event);
7020
- const trustedRuntime = await loadTrustedRuntimeForEvent(options, event, log);
7021
- const resolution = resolvePlanCommand(trustedRuntime.plan, runnable.line);
7022
- if (resolution.kind === "ignored") return {
7023
- kind: "ignored",
7024
- reason: resolution.reason
7025
- };
7026
- return {
7027
- kind: "prepared",
7028
- comment,
7029
- line: runnable.line,
7030
- event,
7031
- trustedRuntime,
7032
- resolution
7033
- };
7034
- }
7035
- function runnableIssueCommentCommand(comment, dryRun) {
7036
- if (!comment.isChangeRequest) return {
7037
- kind: "ignored",
7038
- reason: "issue_comment did not target a pull request"
7031
+ reason: `review comment action '${reply.action}' is not supported`
7039
7032
  };
7040
- if (comment.action !== "created") return {
7033
+ if (!reply.parentCommentId) return {
7041
7034
  kind: "ignored",
7042
- reason: `issue_comment action '${comment.action}' is not supported`
7035
+ reason: "review comment was not a reply"
7043
7036
  };
7044
- const line = firstNonEmptyLine(comment.body);
7045
- if (!line || !isPiprCommandLine(line)) return {
7037
+ if (reply.actor === "github-actions[bot]") return {
7046
7038
  kind: "ignored",
7047
- reason: "issue_comment did not target pipr"
7039
+ reason: "review comment reply was authored by pipr"
7048
7040
  };
7049
- return dryRun ? {
7041
+ if (isPiprThreadActionReplyBody(reply.body)) return {
7050
7042
  kind: "ignored",
7051
- reason: "PIPR_DRY_RUN=1; command dispatch skipped"
7052
- } : {
7053
- kind: "runnable",
7054
- line
7043
+ reason: "review comment reply was authored by pipr"
7055
7044
  };
7045
+ return { kind: "runnable" };
7056
7046
  }
7057
- async function dispatchIssueCommentCommand(options, adapter, prepared, log) {
7058
- const requiredPermission = prepared.resolution.kind === "matched" ? prepared.resolution.invocation.requiredPermission : prepared.resolution.requiredPermission;
7059
- const permission = await logPhase(log, "check command permission", async () => adapter.permissions.getRepositoryPermission({
7060
- repository: prepared.comment.repository,
7061
- actor: prepared.comment.actor
7062
- }));
7063
- log.notice("command dispatch", {
7064
- resolution: prepared.resolution.kind,
7065
- requiredPermission,
7066
- actualPermission: permission
7067
- });
7068
- if (!hasRequiredRepositoryPermission(permission, requiredPermission)) return {
7069
- kind: "command-help",
7070
- event: prepared.event,
7071
- configSource: prepared.trustedRuntime.settings.source,
7072
- body: permissionDeniedHelp(prepared.trustedRuntime.plan, requiredPermission),
7073
- reason: `permission denied for '${prepared.line}'`
7074
- };
7075
- if (prepared.resolution.kind === "help" || prepared.resolution.kind === "invalid") return {
7076
- kind: "command-help",
7077
- event: prepared.event,
7078
- configSource: prepared.trustedRuntime.settings.source,
7079
- body: prepared.resolution.body,
7080
- reason: prepared.resolution.reason
7081
- };
7082
- const parsedResolution = parsePlanCommandInputs(prepared.trustedRuntime.plan, prepared.resolution.invocation);
7083
- if (parsedResolution.kind === "invalid") return {
7084
- kind: "command-help",
7085
- event: prepared.event,
7086
- configSource: prepared.trustedRuntime.settings.source,
7087
- body: parsedResolution.body,
7088
- reason: parsedResolution.reason
7089
- };
7090
- if (parsedResolution.kind !== "matched") return {
7091
- kind: "ignored",
7092
- reason: "command dispatch did not resolve to a runnable task"
7093
- };
7094
- await prepareTrustedHeadCheckout(options, adapter, prepared.trustedRuntime.settings.config, prepared.event, log);
7095
- const dispatch = dispatchRuntimeEntry({
7096
- kind: "change-request",
7097
- plan: prepared.trustedRuntime.plan,
7098
- event: prepared.event,
7099
- taskName: parsedResolution.invocation.taskName
7047
+ async function verifierActorAllowed(adapter, event, reply, config) {
7048
+ const allowed = config.publication.autoResolve.userReplies.allowedActors;
7049
+ if (allowed === "any") return true;
7050
+ if (allowed === "author-or-write" && event.change.author?.login === reply.actor) return true;
7051
+ return hasRequiredRepositoryPermission(await adapter.permissions.getRepositoryPermission({
7052
+ repository: event.repository,
7053
+ actor: reply.actor
7054
+ }), "write");
7055
+ }
7056
+ //#endregion
7057
+ //#region src/action/commands.ts
7058
+ /** Initializes the official minimal `.pipr` project files. */
7059
+ async function runInitCommand(options) {
7060
+ return await initOfficialMinimalProject({
7061
+ rootDir: options.rootDir,
7062
+ configDir: options.configDir,
7063
+ force: options.force,
7064
+ adapters: options.adapters,
7065
+ recipe: options.recipe,
7066
+ minimal: options.minimal
7100
7067
  });
7101
- return await issueCommentCommandResult({
7102
- adapter,
7103
- completed: await runTrustedReviewAndPublish({
7104
- options,
7105
- adapter,
7106
- trustedRuntime: prepared.trustedRuntime,
7107
- event: prepared.event,
7108
- taskName: parsedResolution.invocation.taskName,
7109
- taskInput: parsedResolution.invocation.inputs,
7110
- selectedTasks: dispatch.kind === "change-request" ? dispatch.tasks : [],
7111
- commandInvocation: {
7112
- name: parsedResolution.invocation.commandName,
7113
- line: parsedResolution.invocation.line,
7114
- arguments: parsedResolution.invocation.arguments
7115
- },
7116
- log
7117
- }),
7118
- event: prepared.event,
7119
- commandName: parsedResolution.invocation.commandName,
7120
- sourceCommentId: prepared.comment.commentId,
7121
- configSource: prepared.trustedRuntime.settings.source
7068
+ }
7069
+ /** Loads and validates the runtime project configuration. */
7070
+ async function runValidateCommand(options) {
7071
+ return (await validateProject({
7072
+ ...options,
7073
+ requireProviderEnv: options.requireProviderEnv ?? false
7074
+ })).settings;
7075
+ }
7076
+ /** Returns an inspectable summary of the configured runtime plan. */
7077
+ async function runInspectCommand(options) {
7078
+ const runtime = await loadRuntimeProject({
7079
+ ...options,
7080
+ requireProviderEnv: false
7122
7081
  });
7082
+ return inspectRuntimePlan(runtime.plan, runtime.settings.source);
7123
7083
  }
7124
- async function issueCommentCommandResult(options) {
7125
- if (options.completed.kind === "skipped") return {
7126
- kind: "ignored",
7127
- reason: options.completed.reason
7128
- };
7129
- if (options.completed.kind === "command-response") return await publishCommandResponseActionResult({
7130
- adapter: options.adapter,
7131
- completed: options.completed,
7132
- event: options.event,
7133
- sourceCommentId: options.sourceCommentId,
7134
- configSource: options.configSource
7084
+ /** Loads the runtime config and pull request event without running review publication. */
7085
+ async function runDryRunCommand(options) {
7086
+ const runtime = await loadRuntimeProject({
7087
+ ...options,
7088
+ requireProviderEnv: false
7089
+ });
7090
+ const event = await createActionHostAdapter(options).events.parseEvent({
7091
+ eventPath: options.eventPath,
7092
+ env: {
7093
+ ...options.env,
7094
+ GITHUB_WORKSPACE: options.rootDir,
7095
+ GITHUB_EVENT_NAME: "pull_request"
7096
+ },
7097
+ workspace: options.rootDir
7135
7098
  });
7136
7099
  return {
7137
- kind: "review",
7138
- event: options.event,
7139
- command: options.commandName,
7140
- configSource: options.configSource,
7141
- review: options.completed.review,
7142
- publication: options.completed.publication
7100
+ configSource: runtime.settings.source,
7101
+ event
7143
7102
  };
7144
7103
  }
7145
- async function publishCommandResponseActionResult(options) {
7146
- const publishCommandResponse = options.adapter.publication?.publishCommandResponse;
7147
- if (!publishCommandResponse) throw new Error("command response publication is not available for this code host");
7148
- const publication = await publishCommandResponse({
7149
- change: options.event,
7150
- sourceCommentId: options.sourceCommentId,
7151
- commandName: options.completed.response.commandName,
7152
- body: options.completed.response.body
7104
+ /** Runs configured change-request tasks against local Git base and head revisions. */
7105
+ async function runLocalReviewCommand(options) {
7106
+ const log = options.logSink ? createRuntimeActionLog({
7107
+ logSink: options.logSink,
7108
+ env: options.env
7109
+ }) : void 0;
7110
+ log?.notice("local review start", {
7111
+ root: options.rootDir,
7112
+ configDir: options.configDir,
7113
+ base: options.baseSha.slice(0, 12),
7114
+ head: options.headSha?.slice(0, 12)
7115
+ });
7116
+ const runtime = await loadRuntimeProject({
7117
+ ...options,
7118
+ requireProviderEnv: true
7119
+ });
7120
+ log?.notice("local config loaded", {
7121
+ source: runtime.settings.source,
7122
+ providers: runtime.settings.config.providers.map((provider) => `${provider.id}:${provider.model}`).join(","),
7123
+ tasks: runtime.plan.tasks.length,
7124
+ commands: runtime.plan.commands.length
7125
+ });
7126
+ const selectedTasks = selectLocalReviewTasks(runtime.plan);
7127
+ const includeWorkingTree = options.headSha === void 0;
7128
+ const headSha = options.headSha ?? runGit$1(["rev-parse", "HEAD"], options.rootDir).trim();
7129
+ const event = parseChangeRequestEventContext({ ...createLocalChangeRequestEvent({
7130
+ rootDir: options.rootDir,
7131
+ baseSha: options.baseSha,
7132
+ headSha
7133
+ }) });
7134
+ if (log) {
7135
+ logEventContext(log, event);
7136
+ log.notice("local dispatch", {
7137
+ selectedTasks: selectedTasks.map((task) => task.name),
7138
+ skippedLocalTasks: runtime.plan.tasks.filter((task) => task.local === false).map((task) => task.name),
7139
+ diffTarget: includeWorkingTree ? "working-tree" : "head-ref"
7140
+ });
7141
+ }
7142
+ const result = await runTaskRuntime({
7143
+ workspace: options.rootDir,
7144
+ config: runtime.settings.config,
7145
+ event,
7146
+ env: options.env,
7147
+ plan: runtime.plan,
7148
+ selectedTasks,
7149
+ emptyTasksReason: "No change-request tasks are configured for local review",
7150
+ piExecutable: options.piExecutable,
7151
+ diffManifestBuilder: includeWorkingTree ? (diffOptions) => buildDiffManifest({
7152
+ ...diffOptions,
7153
+ includeWorkingTree: true
7154
+ }) : void 0,
7155
+ log,
7156
+ taskLog: options.taskLog
7157
+ });
7158
+ if (result.kind === "command-response") throw new Error("command response result is only supported for issue_comment commands");
7159
+ log?.notice("local review complete", {
7160
+ kind: result.kind,
7161
+ taskChecks: result.taskChecks.length,
7162
+ validFindings: result.kind === "review" ? result.validated.validFindings.length : void 0,
7163
+ droppedFindings: result.kind === "review" ? result.validated.droppedFindings.length : void 0,
7164
+ inlineDrafts: result.kind === "review" ? result.inlineCommentDrafts.length : void 0
7165
+ });
7166
+ return result;
7167
+ }
7168
+ /** Runs the GitHub Action workflow for pull request and issue-comment events. */
7169
+ async function runActionCommand(options) {
7170
+ return await runActionCommandWithDependencies(options);
7171
+ }
7172
+ async function runActionCommandWithDependencies(options) {
7173
+ const log = createRuntimeActionLog({
7174
+ logSink: options.logSink,
7175
+ env: options.env
7176
+ });
7177
+ return await log.group("pipr action", async () => {
7178
+ const eventName = (options.env ?? process.env).GITHUB_EVENT_NAME ?? "pull_request";
7179
+ log.notice("action start", {
7180
+ eventName,
7181
+ dryRun: options.dryRun,
7182
+ root: options.rootDir,
7183
+ configDir: options.configDir
7184
+ });
7185
+ const adapter = createActionHostAdapter(options);
7186
+ await logPhase(log, "workspace", async () => {
7187
+ adapter.workspace.ensureWorkspaceSafeDirectory?.({
7188
+ rootDir: options.rootDir,
7189
+ env: options.env
7190
+ });
7191
+ });
7192
+ if (eventName === "issue_comment") return await runIssueCommentActionCommand(options, adapter, log);
7193
+ if (eventName === "pull_request_review_comment") return await runReviewCommentReplyActionCommand(options, adapter, log);
7194
+ return await runPullRequestActionCommand(options, adapter, log);
7153
7195
  });
7154
- return {
7155
- kind: "command-response",
7156
- event: options.event,
7157
- command: options.completed.response.commandName,
7158
- configSource: options.configSource,
7159
- response: { body: options.completed.response.body },
7160
- publication
7161
- };
7162
7196
  }
7163
7197
  //#endregion
7164
- export { runInspectCommand as a, PublicationError as c, supportedOfficialInitRecipes as d, runInitCommand as i, supportedOfficialInitAdapters as l, runActionCommandWithDependencies as n, runLocalReviewCommand as o, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, listOfficialInitRecipes as u };
7198
+ export { runInspectCommand as a, PublicationError as c, supportedOfficialInitRecipes as d, piBuiltinToolNames as f, piThinkingLevels as h, runInitCommand as i, supportedOfficialInitAdapters as l, piRequiredCliFlags as m, runActionCommandWithDependencies as n, runLocalReviewCommand as o, piReadOnlyToolNames as p, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, listOfficialInitRecipes as u };
7165
7199
 
7166
- //# sourceMappingURL=commands-DjVx1puD.mjs.map
7200
+ //# sourceMappingURL=commands-BajlHf13.mjs.map