@usepipr/runtime 0.3.3 → 0.3.5

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.
@@ -13,17 +13,74 @@ import { Buffer as Buffer$1 } from "node:buffer";
13
13
  import { spawn } from "node:child_process";
14
14
  import { Octokit } from "@octokit/rest";
15
15
  import { existsSync, mkdirSync } from "node:fs";
16
+ //#region src/config/package-manifest.ts
17
+ function normalizePackageManifest(value) {
18
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
19
+ const manifest = {};
20
+ const rawManifest = value;
21
+ for (const key of ["dependencies", "devDependencies"]) {
22
+ const dependencyMap = rawManifest[key];
23
+ if (dependencyMap === null || typeof dependencyMap !== "object" || Array.isArray(dependencyMap)) continue;
24
+ const entries = Object.entries(dependencyMap).filter((entry) => typeof entry[1] === "string");
25
+ if (entries.length > 0) manifest[key] = Object.fromEntries(entries);
26
+ }
27
+ return manifest;
28
+ }
29
+ //#endregion
30
+ //#region src/config/scaffold-versions.ts
31
+ const defaultTypesBunVersion = "1.3.14";
32
+ const defaultTypescriptVersion = "6.0.3";
33
+ const defaultScaffoldTypescriptSpec = `typescript@${defaultTypescriptVersion}`;
34
+ //#endregion
16
35
  //#region src/config/config-deps.ts
17
- const runtimeProvidedPackages = new Set(["@usepipr/sdk", "@types/bun"]);
36
+ const runtimeProvidedPackages = /* @__PURE__ */ new Set(["@usepipr/sdk", "@types/bun"]);
18
37
  async function installConfigDependencies(configDir, options = {}) {
19
38
  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;
39
+ if (!await Bun.file(packageJsonPath).exists()) return;
40
+ const originalPackageJson = await Bun.file(packageJsonPath).text();
41
+ const manifest = normalizePackageManifest(JSON.parse(originalPackageJson));
42
+ const installablePackages = installableDependencySpecs(manifest);
43
+ if (installablePackages.length === 0) return;
22
44
  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.`);
45
+ const originalBunLock = await Bun.file(bunLockPath).exists() ? await Bun.file(bunLockPath).text() : void 0;
46
+ if (options.frozen && originalBunLock === void 0) throw new Error(`${configDir}: bun.lock is required when .pipr/package.json declares dependencies. Run \`bun install\` in .pipr/ and commit bun.lock.`);
24
47
  await assertBunAvailable();
25
- const args = ["install", "--ignore-scripts"];
26
- if (options.frozen) args.push("--frozen-lockfile");
48
+ const args = configInstallArgs(installablePackages, { frozen: options.frozen === true && !hasRuntimeProvidedDependencies(manifest) });
49
+ await withSanitizedConfigInstallInputs({
50
+ packageJsonPath,
51
+ originalPackageJson,
52
+ bunLockPath,
53
+ originalBunLock
54
+ }, () => runConfigBunInstall(configDir, args));
55
+ }
56
+ function configInstallArgs(installablePackages, options) {
57
+ return [
58
+ "install",
59
+ "--ignore-scripts",
60
+ "--no-save",
61
+ ...options.frozen ? ["--frozen-lockfile"] : [],
62
+ ...isDefaultScaffoldTypescriptInstall(installablePackages) ? ["--no-verify"] : [],
63
+ ...installablePackages.map((dependency) => dependency.spec)
64
+ ];
65
+ }
66
+ function isDefaultScaffoldTypescriptInstall(installablePackages) {
67
+ return installablePackages.length === 1 && installablePackages[0]?.spec === defaultScaffoldTypescriptSpec;
68
+ }
69
+ async function withSanitizedConfigInstallInputs(inputs, install) {
70
+ let wroteInstallInputs = false;
71
+ try {
72
+ await Bun.write(inputs.packageJsonPath, sanitizedPackageJsonForConfigInstall(inputs.originalPackageJson));
73
+ wroteInstallInputs = true;
74
+ if (inputs.originalBunLock !== void 0) await Bun.write(inputs.bunLockPath, sanitizedBunLockForConfigInstall(inputs.originalBunLock));
75
+ await install();
76
+ } finally {
77
+ if (wroteInstallInputs) {
78
+ await Bun.write(inputs.packageJsonPath, inputs.originalPackageJson);
79
+ if (inputs.originalBunLock !== void 0) await Bun.write(inputs.bunLockPath, inputs.originalBunLock);
80
+ }
81
+ }
82
+ }
83
+ async function runConfigBunInstall(configDir, args) {
27
84
  const proc = Bun.spawn(["bun", ...args], {
28
85
  cwd: configDir,
29
86
  env: process.env,
@@ -33,9 +90,43 @@ async function installConfigDependencies(configDir, options = {}) {
33
90
  const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
34
91
  if (exitCode !== 0) throw new Error(`${configDir}: bun install failed (exit ${exitCode}).` + (stderr.trim().length > 0 ? `\n${stderr.trim()}` : ""));
35
92
  }
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));
93
+ function installableDependencySpecs(manifest) {
94
+ return dependencyEntries(manifest).filter(([name]) => !runtimeProvidedPackages.has(name)).map(([name, version]) => ({
95
+ name,
96
+ spec: `${name}@${version}`
97
+ }));
98
+ }
99
+ function hasRuntimeProvidedDependencies(manifest) {
100
+ return dependencyEntries(manifest).some(([name]) => runtimeProvidedPackages.has(name));
101
+ }
102
+ function dependencyEntries(manifest) {
103
+ return Object.entries({
104
+ ...manifest.dependencies ?? {},
105
+ ...manifest.devDependencies ?? {}
106
+ });
107
+ }
108
+ function sanitizedPackageJsonForConfigInstall(packageJson) {
109
+ const value = JSON.parse(packageJson);
110
+ for (const key of ["dependencies", "devDependencies"]) {
111
+ const dependencies = value[key];
112
+ if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
113
+ const sanitized = Object.fromEntries(Object.entries(dependencies).filter(([name]) => !runtimeProvidedPackages.has(name)));
114
+ if (Object.keys(sanitized).length === 0) delete value[key];
115
+ else value[key] = sanitized;
116
+ }
117
+ return `${JSON.stringify(value, null, 2)}\n`;
118
+ }
119
+ function sanitizedBunLockForConfigInstall(lockfile) {
120
+ let sanitized = lockfile;
121
+ for (const packageName of runtimeProvidedPackages) {
122
+ const escapedName = escapeRegExp(packageName);
123
+ sanitized = sanitized.replace(new RegExp(`^\\s*"${escapedName}":\\s*"[^"]+",\\r?\\n`, "gm"), "");
124
+ sanitized = sanitized.replace(new RegExp(`^\\s*"${escapedName}":\\s*\\[[^\\n]*\\],\\r?\\n(?:\\r?\\n)?`, "gm"), "");
125
+ }
126
+ return sanitized;
127
+ }
128
+ function escapeRegExp(value) {
129
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
130
  }
40
131
  async function assertBunAvailable() {
41
132
  try {
@@ -48,9 +139,6 @@ async function assertBunAvailable() {
48
139
  throw new Error("bun is required on PATH to install .pipr/package.json dependencies. Install Bun from https://bun.sh");
49
140
  }
50
141
  }
51
- async function fileExists$1(filePath) {
52
- return await Bun.file(filePath).exists();
53
- }
54
142
  //#endregion
55
143
  //#region src/config/paths.ts
56
144
  function resolveContainedConfigDir(options) {
@@ -365,6 +453,8 @@ async function installTypedSdkStub(configDir) {
365
453
  });
366
454
  await mkdir(sdkRoot, { recursive: true });
367
455
  await Bun.write(path.join(sdkRoot, "package.json"), JSON.stringify({
456
+ name: "@usepipr/sdk",
457
+ version: "0.0.0-pipr-runtime",
368
458
  type: "module",
369
459
  types: "./index.d.ts",
370
460
  exports: { ".": {
@@ -425,23 +515,93 @@ const starterTsconfig = `{
425
515
  }
426
516
  `;
427
517
  //#endregion
518
+ //#region src/shared/semver.ts
519
+ const stableSemverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
520
+ function compareStableSemver(left, right) {
521
+ const leftParts = stableSemverPattern.exec(left);
522
+ const rightParts = stableSemverPattern.exec(right);
523
+ if (!leftParts || !rightParts) throw new Error(`cannot compare non-stable semver versions: ${left}, ${right}`);
524
+ return Number(leftParts[1]) - Number(rightParts[1]) || Number(leftParts[2]) - Number(rightParts[2]) || Number(leftParts[3]) - Number(rightParts[3]);
525
+ }
526
+ //#endregion
527
+ //#region src/shared/version.ts
528
+ const runtimeVersion = "0.3.5";
529
+ //#endregion
530
+ //#region src/config/version-compat.ts
531
+ async function resolveConfigVersionCompatibility(options) {
532
+ const packageJsonPath = path.join(options.configDirPath, "package.json");
533
+ let manifest;
534
+ try {
535
+ manifest = normalizePackageManifest(await Bun.file(packageJsonPath).json());
536
+ } catch (error) {
537
+ if ((error && typeof error === "object" && "code" in error ? error.code : void 0) === "ENOENT") return {
538
+ kind: "unknown",
539
+ runtimeVersion
540
+ };
541
+ throw error;
542
+ }
543
+ const sdkVersion = manifest.dependencies?.["@usepipr/sdk"] ?? manifest.devDependencies?.["@usepipr/sdk"];
544
+ if (!sdkVersion) return {
545
+ kind: "unknown",
546
+ runtimeVersion
547
+ };
548
+ const packageJsonLabel = options.configDir === "." ? "package.json" : `${options.configDir}/package.json`;
549
+ const bunLockLabel = options.configDir === "." ? "bun.lock" : `${options.configDir}/bun.lock`;
550
+ if (!stableSemverPattern.test(sdkVersion)) return {
551
+ kind: "uncomparable",
552
+ runtimeVersion,
553
+ warning: `${packageJsonLabel} declares @usepipr/sdk as ${JSON.stringify(sdkVersion)}; use an exact version to enable Pipr config version checks.`
554
+ };
555
+ if (!stableSemverPattern.test(runtimeVersion)) return {
556
+ kind: "uncomparable",
557
+ runtimeVersion,
558
+ warning: `This Pipr runtime reports version ${runtimeVersion}; skipping Pipr config version checks.`
559
+ };
560
+ const comparison = compareStableSemver(runtimeVersion, sdkVersion);
561
+ if (comparison === 0) return {
562
+ kind: "matched",
563
+ runtimeVersion,
564
+ configVersion: sdkVersion
565
+ };
566
+ if (comparison > 0) return {
567
+ kind: "runtime-newer",
568
+ runtimeVersion,
569
+ configVersion: sdkVersion,
570
+ warning: `${packageJsonLabel} pins @usepipr/sdk ${sdkVersion}, but this Pipr runtime is ${runtimeVersion}. Run \`pipr init --force\` or update ${packageJsonLabel} and ${bunLockLabel} when ready.`
571
+ };
572
+ throw new Error(`${packageJsonLabel} pins @usepipr/sdk ${sdkVersion}, but this Pipr runtime is ${runtimeVersion}. Upgrade Pipr before running this config.`);
573
+ }
574
+ //#endregion
428
575
  //#region src/config/ts-loader.ts
429
576
  async function loadTypescriptConfig(options) {
430
- const { projectDir, relativeConfigDir, configDir } = resolveContainedConfigDir(options);
577
+ const { projectDir, relativeConfigDir } = resolveContainedConfigDir(options);
431
578
  const sourceConfigPath = path.join(projectDir, "config.ts");
432
- if (!await fileExists(sourceConfigPath)) throw new Error(`${configDir}/config.ts is required. Run pipr init to create it.`);
579
+ if (!await Bun.file(sourceConfigPath).exists()) throw new Error(`No Pipr config found at ${sourceConfigPath}.\nRun \`pipr init\` to create one, or pass \`--config-dir <dir>\`.`);
580
+ const versionCompatibility = await resolveConfigVersionCompatibility({
581
+ configDirPath: projectDir,
582
+ configDir: relativeConfigDir
583
+ });
433
584
  if (options.typecheck) await typecheckTypescriptConfig(path.resolve(options.rootDir), relativeConfigDir);
434
585
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-config-"));
435
586
  try {
436
587
  const tempConfigDir = path.join(tempRoot, relativeConfigDir);
437
- await copyConfigDirectory(projectDir, tempConfigDir);
588
+ await cp(projectDir, tempConfigDir, {
589
+ recursive: true,
590
+ errorOnExist: false,
591
+ force: true,
592
+ filter: (source) => {
593
+ const relative = path.relative(projectDir, source);
594
+ return relative !== "node_modules" && !relative.startsWith(`node_modules${path.sep}`);
595
+ }
596
+ });
438
597
  await prepareConfigDirectory(tempConfigDir, { frozen: true });
439
598
  const factory = (await import(`${pathToFileURL(path.join(tempConfigDir, "config.ts")).href}?pipr=${Date.now()}`)).default;
440
599
  if (!isPiprConfigFactory(factory)) throw new Error(`${sourceConfigPath}: default export must be created by definePipr()`);
441
600
  return {
442
601
  plan: buildPiprPlan(factory),
443
602
  source: sourceConfigPath,
444
- tempRoot
603
+ tempRoot,
604
+ versionCompatibility
445
605
  };
446
606
  } finally {
447
607
  await rm(tempRoot, {
@@ -465,13 +625,14 @@ async function typecheckTypescriptConfig(rootDir, relativeConfigDir) {
465
625
  force: true,
466
626
  filter: (source) => {
467
627
  const relative = path.relative(rootDir, source);
468
- const first = relative.split(path.sep)[0] ?? "";
469
- return !ignoredTypecheckRootEntries.has(first) && relative !== "bun.lock";
628
+ const parts = relative.split(path.sep);
629
+ const first = parts[0] ?? "";
630
+ return !ignoredTypecheckRootEntries.has(first) && !parts.includes("node_modules") && relative !== "bun.lock";
470
631
  }
471
632
  });
472
633
  await prepareConfigDirectory(tempConfigDir, { frozen: true });
473
634
  const tsconfigPath = path.join(tempConfigDir, "tsconfig.json");
474
- if (!await fileExists(tsconfigPath)) {
635
+ if (!await Bun.file(tsconfigPath).exists()) {
475
636
  await mkdir(tempConfigDir, { recursive: true });
476
637
  await Bun.write(tsconfigPath, starterTsconfig);
477
638
  }
@@ -484,25 +645,64 @@ async function typecheckTypescriptConfig(rootDir, relativeConfigDir) {
484
645
  }
485
646
  }
486
647
  async function typecheckTypescriptConfigWithApi(configDir, tsconfigPath) {
487
- const ts = await import("typescript");
648
+ const { ts, packageRoot } = await loadTypescriptForConfig(configDir);
488
649
  const config = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
489
650
  if (config.error) throw new Error(formatTypeScriptDiagnostics(ts, [config.error], configDir));
490
651
  const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, configDir);
491
652
  const bundledTypeRoots = [];
492
- if (!await fileExists(path.join(configDir, "node_modules", "@types", "bun", "package.json"))) try {
653
+ if (!await Bun.file(path.join(configDir, "node_modules", "@types", "bun", "package.json")).exists()) try {
493
654
  const require = createRequire(import.meta.url);
494
655
  bundledTypeRoots.push(path.dirname(path.dirname(require.resolve("@types/bun/package.json"))));
495
656
  } catch {}
496
657
  const configPath = path.join(configDir, "config.ts");
497
- const program = ts.createProgram([configPath, ...parsed.fileNames], {
658
+ const compilerOptions = {
498
659
  ...parsed.options,
499
660
  skipLibCheck: true,
500
- typeRoots: [...new Set([...parsed.options.typeRoots ?? [], ...bundledTypeRoots])],
501
- types: [...new Set([...parsed.options.types ?? [], ...bundledTypeRoots.length ? ["bun"] : []])]
502
- });
661
+ typeRoots: [.../* @__PURE__ */ new Set([...parsed.options.typeRoots ?? [], ...bundledTypeRoots])],
662
+ types: [.../* @__PURE__ */ new Set([...parsed.options.types ?? [], ...bundledTypeRoots.length ? ["bun"] : []])]
663
+ };
664
+ const compilerHost = await createTypescriptCompilerHost(ts, compilerOptions, packageRoot);
665
+ const program = ts.createProgram([configPath, ...parsed.fileNames], compilerOptions, compilerHost);
503
666
  const diagnostics = [...parsed.errors, ...ts.getPreEmitDiagnostics(program)];
504
667
  if (diagnostics.length > 0) throw new Error(`TypeScript config check failed for ${path.join(configDir, "config.ts")}:\n` + formatTypeScriptDiagnostics(ts, diagnostics, configDir));
505
668
  }
669
+ async function loadTypescriptForConfig(configDir) {
670
+ const localPackageRoot = path.join(configDir, "node_modules", "typescript");
671
+ const localApiPath = path.join(localPackageRoot, "lib", "typescript.js");
672
+ if (await fileExists(localApiPath)) return {
673
+ ts: typescriptApi(await import(pathToFileURL(localApiPath).href)),
674
+ packageRoot: localPackageRoot
675
+ };
676
+ return {
677
+ ts: typescriptApi(await import("typescript")),
678
+ packageRoot: await runtimeTypescriptPackageRoot()
679
+ };
680
+ }
681
+ function typescriptApi(module) {
682
+ const maybeModule = module;
683
+ if (typeof maybeModule.createProgram === "function") return maybeModule;
684
+ if (typeof maybeModule.default?.createProgram === "function") return maybeModule.default;
685
+ throw new Error("TypeScript module does not expose createProgram");
686
+ }
687
+ async function runtimeTypescriptPackageRoot() {
688
+ try {
689
+ const require = createRequire(import.meta.url);
690
+ const packageRoot = path.dirname(require.resolve("typescript/package.json"));
691
+ return await fileExists(path.join(packageRoot, "lib", "typescript.js")) ? packageRoot : void 0;
692
+ } catch {
693
+ return;
694
+ }
695
+ }
696
+ async function createTypescriptCompilerHost(ts, compilerOptions, packageRoot) {
697
+ const compilerHost = ts.createCompilerHost(compilerOptions, true);
698
+ if (!packageRoot) return compilerHost;
699
+ const libDir = path.join(packageRoot, "lib");
700
+ const defaultLibPath = path.join(libDir, ts.getDefaultLibFileName(compilerOptions));
701
+ if (!await fileExists(defaultLibPath)) return compilerHost;
702
+ compilerHost.getDefaultLibFileName = () => defaultLibPath;
703
+ compilerHost.getDefaultLibLocation = () => libDir;
704
+ return compilerHost;
705
+ }
506
706
  function formatTypeScriptDiagnostics(ts, diagnostics, configDir) {
507
707
  return ts.formatDiagnostics(diagnostics, {
508
708
  getCanonicalFileName: (fileName) => fileName,
@@ -510,19 +710,7 @@ function formatTypeScriptDiagnostics(ts, diagnostics, configDir) {
510
710
  getNewLine: () => "\n"
511
711
  });
512
712
  }
513
- async function copyConfigDirectory(sourceDir, targetDir) {
514
- await cp(sourceDir, targetDir, {
515
- recursive: true,
516
- errorOnExist: false,
517
- force: true,
518
- filter: (source) => !isIgnoredConfigCopyPath(source, sourceDir)
519
- });
520
- }
521
- function isIgnoredConfigCopyPath(source, configDir) {
522
- const relative = path.relative(configDir, source);
523
- return relative === "node_modules" || relative.startsWith(`node_modules${path.sep}`);
524
- }
525
- const ignoredTypecheckRootEntries = new Set([
713
+ const ignoredTypecheckRootEntries = /* @__PURE__ */ new Set([
526
714
  ".fallow",
527
715
  ".git",
528
716
  ".turbo",
@@ -543,8 +731,10 @@ async function loadRuntimeProject(options) {
543
731
  settings: planToRuntimeSettings(loaded.plan, {
544
732
  source: loaded.source,
545
733
  env: options.env,
546
- requireProviderEnv: options.requireProviderEnv
547
- })
734
+ requireProviderEnv: options.requireProviderEnv,
735
+ warnings: [loaded.versionCompatibility.warning].filter((warning) => warning !== void 0)
736
+ }),
737
+ versionCompatibility: loaded.versionCompatibility
548
738
  };
549
739
  }
550
740
  async function validateProject(options) {
@@ -589,7 +779,7 @@ function planToRuntimeSettings(plan, options) {
589
779
  },
590
780
  limits: plan.limits
591
781
  },
592
- warnings: []
782
+ warnings: options.warnings ?? []
593
783
  });
594
784
  }
595
785
  function normalizeAutoResolveConfig(options, defaultProvider) {
@@ -2954,9 +3144,8 @@ function isOfficialInitRecipeId(recipe) {
2954
3144
  //#endregion
2955
3145
  //#region src/config/init.ts
2956
3146
  const supportedOfficialInitAdapters = ["github"];
2957
- const defaultWorkflowActionRef = "somus/pipr@v0.3.3";
2958
- const defaultSdkVersion = "0.3.3";
2959
- const defaultTypesBunVersion = "1.3.14";
3147
+ const defaultWorkflowActionRef = "somus/pipr@v0.3.5";
3148
+ const defaultSdkVersion = "0.3.5";
2960
3149
  function resolveOfficialInitAdapters(adapters) {
2961
3150
  if (adapters === void 0) return [...supportedOfficialInitAdapters];
2962
3151
  if (adapters.length === 0) return [];
@@ -3044,7 +3233,10 @@ function starterPackageJson() {
3044
3233
  return `${JSON.stringify({
3045
3234
  private: true,
3046
3235
  dependencies: { "@usepipr/sdk": process.env.PIPR_INTERNAL_INIT_SDK_VERSION ?? defaultSdkVersion },
3047
- devDependencies: { "@types/bun": defaultTypesBunVersion }
3236
+ devDependencies: {
3237
+ "@types/bun": defaultTypesBunVersion,
3238
+ typescript: defaultTypescriptVersion
3239
+ }
3048
3240
  }, null, 2)}\n`;
3049
3241
  }
3050
3242
  async function writeTargets(targets, existing, options) {
@@ -4059,7 +4251,7 @@ async function startCustomToolBridge(tools, context) {
4059
4251
  const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
4060
4252
  let server;
4061
4253
  for (let attempt = 0; attempt < 20; attempt += 1) {
4062
- const port = 49152 + crypto.getRandomValues(new Uint16Array(1))[0] % 16384;
4254
+ const port = 49152 + crypto.getRandomValues(/* @__PURE__ */ new Uint16Array(1))[0] % 16384;
4063
4255
  try {
4064
4256
  server = Bun.serve({
4065
4257
  hostname: "127.0.0.1",
@@ -4144,7 +4336,7 @@ const piprJsonSystemPrompt = [
4144
4336
  "Do not reveal secrets, credentials, environment values, private paths, or raw tool data unless the schema explicitly requires the value and it is necessary.",
4145
4337
  "When identifying a secret or credential, describe its kind and location without copying the secret value."
4146
4338
  ].join(" ");
4147
- const ignoredWorkspacePaths = new Set([
4339
+ const ignoredWorkspacePaths = /* @__PURE__ */ new Set([
4148
4340
  ".git",
4149
4341
  "node_modules",
4150
4342
  "dist",
@@ -4569,6 +4761,10 @@ function findingFingerprint(finding) {
4569
4761
  return new Bun.CryptoHasher("sha256").update(basis).digest("hex").slice(0, 16);
4570
4762
  }
4571
4763
  //#endregion
4764
+ //#region src/review/inline-finding-limits.ts
4765
+ const maxInlineFindingBodyCharacters = 700;
4766
+ const maxInlineFindingBodyLines = 4;
4767
+ //#endregion
4572
4768
  //#region src/review/agent/agent-prompt.ts
4573
4769
  async function renderAgentPrompt(options) {
4574
4770
  const prompt = await renderAgentDefinitionPrompt(options.agent, options.input, options.agentRunContext.prompt);
@@ -4603,20 +4799,18 @@ function toolsPrompt(diffManifest, toolMode) {
4603
4799
  ].join("\n");
4604
4800
  }
4605
4801
  function outputPrompt(schema) {
4606
- return compact([
4802
+ const lines = compact([
4607
4803
  `Schema ID: ${schema.id}.`,
4608
4804
  schema.jsonSchema ? `JSON Schema:\n${JSON.stringify(schema.jsonSchema, null, 2)}` : void 0,
4609
- schema.id === reviewResultSchemaId ? `Example:\n${JSON.stringify(reviewSchemaExample$1(), null, 2)}` : void 0,
4610
- schema.id === reviewResultSchemaId ? "`suggestedFix` is exact replacement code for the selected range. Do not include Markdown fences, prose, or labels in `suggestedFix`." : void 0,
4611
- schema.id === reviewResultSchemaId ? "GitHub applies `suggestedFix` to the selected `startLine` through `endLine`. Select the smallest contiguous line span that the replacement code should replace." : void 0,
4612
- schema.id === reviewResultSchemaId ? "If a fix changes only part of a line, select that whole line and put the full replacement line in `suggestedFix`. If a fix changes multiple lines, select exactly those original lines and put the full replacement block in `suggestedFix`." : void 0,
4613
- schema.id === reviewResultSchemaId ? "Do not select a larger enclosing block to replace a smaller statement, and do not select one line when the replacement is for a multi-line section. Omit `suggestedFix` if the exact replacement range is uncertain." : void 0,
4614
- schema.id === reviewResultSchemaId ? "Omit `suggestedFix` for broad rewrites, generated docs/pages, uncertain ranges, or changes better described in prose." : void 0,
4615
4805
  "Return exactly one JSON value matching the schema.",
4616
4806
  "The first non-whitespace character must be { or [ and the last non-whitespace character must be } or ].",
4617
- "Do not include Markdown, code fences, prose, explanations, or leading/trailing text.",
4618
- 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
4619
- ]).join("\n\n");
4807
+ "Do not include Markdown, code fences, prose, explanations, or leading/trailing text."
4808
+ ]);
4809
+ if (schema.id === reviewResultSchemaId) {
4810
+ lines.splice(2, 0, `Example:\n${JSON.stringify(reviewSchemaExample$1(), null, 2)}`, "`suggestedFix` is exact replacement code for the selected range. Do not include Markdown fences, prose, or labels in `suggestedFix`.", "GitHub applies `suggestedFix` to the selected `startLine` through `endLine`. Select the smallest contiguous line span that the replacement code should replace.", "If a fix changes only part of a line, select that whole line and put the full replacement line in `suggestedFix`. If a fix changes multiple lines, select exactly those original lines and put the full replacement block in `suggestedFix`.", "Do not select a larger enclosing block to replace a smaller statement, and do not select one line when the replacement is for a multi-line section. Omit `suggestedFix` if the exact replacement range is uncertain.", "Omit `suggestedFix` for broad rewrites, generated docs/pages, uncertain ranges, or changes better described in prose.");
4811
+ lines.push("For inlineFindings, use only fields shown in the schema and only exact Diff Manifest commentable ranges. If no exact range applies, omit the finding.", `For inlineFindings.body, write the exact inline comment body. Use one short paragraph, at most two sentences, and at most 700 characters. Treat 700 as a hard ceiling, not a target; prefer 250-450 characters when possible.`);
4812
+ }
4813
+ return lines.join("\n\n");
4620
4814
  }
4621
4815
  function reviewPolicyPrompt(schema) {
4622
4816
  if (schema.id !== reviewResultSchemaId) return;
@@ -4624,7 +4818,9 @@ function reviewPolicyPrompt(schema) {
4624
4818
  "Review only changed behavior.",
4625
4819
  "Report only actionable defects, security risks, regressions, or meaningful test gaps.",
4626
4820
  "Put each actionable issue in inlineFindings. Do not leave actionable defects or test gaps only in the summary.",
4627
- "Keep each inline finding body to one short paragraph, at most two sentences, and under 700 characters.",
4821
+ "Inline finding bodies are final code-review comments, not analysis notes.",
4822
+ `State the concrete defect and user-visible or runtime impact directly. Keep each body to one short paragraph, at most two sentences, and at most 700 characters.`,
4823
+ "Do not include step-by-step reasoning, broad context, praise, restated diff, alternatives, or code snippets unless they are necessary to identify the defect.",
4628
4824
  "Omit speculative, style-only, broad refactor, external-fact, and out-of-diff findings.",
4629
4825
  "Use read tools when more context is needed. If evidence is insufficient, omit the finding.",
4630
4826
  "Emit one inline finding per issue, anchored to the exact Diff Manifest commentable range.",
@@ -5039,67 +5235,17 @@ function buildRepairPrompt(options) {
5039
5235
  ].join("\n\n");
5040
5236
  }
5041
5237
  //#endregion
5042
- //#region package.json
5043
- var version = "0.3.3";
5044
- //#endregion
5045
5238
  //#region src/review/comment-branding.ts
5046
5239
  const piprLogoUrl = "https://pipr.run/images/pipr/pipr-mark.svg";
5047
5240
  const piprRepositoryUrl = "https://github.com/somus/pipr";
5048
5241
  const mainCommentTitle = `# <img src="${piprLogoUrl}" width="22" height="22" alt=""> Pipr Review`;
5049
- const mainCommentTitles = new Set([
5242
+ const mainCommentTitles = /* @__PURE__ */ new Set([
5050
5243
  "# pipr Review",
5051
5244
  "# Pipr Review",
5052
5245
  mainCommentTitle
5053
5246
  ]);
5054
5247
  const escapedRepositoryUrl = piprRepositoryUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5055
- const mainCommentAttributionPattern = new RegExp(`^<sub>Review generated by \\[Pipr\\]\\(${escapedRepositoryUrl}\\) for commit \`[^\`]+\`\\.</sub>$`);
5056
- //#endregion
5057
- //#region src/review/inline-publication-policy.ts
5058
- const maxSuggestedFixSelectedLines = 12;
5059
- const maxSuggestedFixReplacementLines = 20;
5060
- function inlinePublicationDecision(options) {
5061
- if (options.existing.markers.has(options.marker) || hasExistingInlinePublicationLocation(options.existing.locations, options.location)) return "skip";
5062
- return "post";
5063
- }
5064
- function isPublishableSuggestedFixSelection(selection) {
5065
- const suggestedLines = normalizedSuggestedFixLines(selection.suggestedFix);
5066
- const selectedLineCount = selection.endLine - selection.startLine + 1;
5067
- if (selection.side !== "RIGHT" || selection.kind === "deleted" || selectedLineCount > maxSuggestedFixSelectedLines || suggestedLines.length > maxSuggestedFixReplacementLines) return false;
5068
- const originalLines = selectedPreviewLines(selection, selectedLineCount);
5069
- return Boolean(originalLines && !hasUnchangedSelectionEdge(originalLines, suggestedLines) && !suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines));
5070
- }
5071
- function hasExistingInlinePublicationLocation(existing, location) {
5072
- return existing.some((comment) => {
5073
- if (comment.path !== location.path || comment.commitId !== location.commitId || comment.side !== location.side) return false;
5074
- return comment.startLine <= location.endLine && location.startLine <= comment.endLine;
5075
- });
5076
- }
5077
- function normalizedSuggestedFixLines(value) {
5078
- const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5079
- const withoutFinalNewline = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
5080
- return withoutFinalNewline.length === 0 ? [] : withoutFinalNewline.split("\n");
5081
- }
5082
- function selectedPreviewLines(selection, selectedLineCount) {
5083
- if (!selection.preview) return;
5084
- const offset = selection.startLine - selection.rangeStartLine;
5085
- if (offset < 0) return;
5086
- const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
5087
- if (offset + selectedLineCount > previewLines.length) return;
5088
- return previewLines.slice(offset, offset + selectedLineCount);
5089
- }
5090
- function suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines) {
5091
- if (!selection.preview || suggestedLines.length <= selectedLineCount) return false;
5092
- const offset = selection.startLine - selection.rangeStartLine;
5093
- if (offset < 0) return false;
5094
- const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
5095
- return [offset > 0 ? previewLines[offset - 1] : void 0, previewLines[offset + selectedLineCount]].filter((line) => Boolean(line?.trim())).some((line) => suggestedLines.includes(line));
5096
- }
5097
- function hasUnchangedSelectionEdge(originalLines, suggestedLines) {
5098
- const firstLineUnchanged = originalLines[0] === suggestedLines[0];
5099
- const lastLineUnchanged = originalLines.at(-1) === suggestedLines.at(-1);
5100
- if (originalLines.length === suggestedLines.length || originalLines.length === 1) return firstLineUnchanged || lastLineUnchanged;
5101
- return firstLineUnchanged && lastLineUnchanged;
5102
- }
5248
+ const mainCommentAttributionPattern = new RegExp(`^<sub>Review generated by \\[Pipr\\]\\(${escapedRepositoryUrl}\\) for commit \`[^\`]+\`\\.(?: Config SDK \\d+\\.\\d+\\.\\d+ is behind (?:Pipr \\d+\\.\\d+\\.\\d+|\\[Pipr \\d+\\.\\d+\\.\\d+\\]\\(${escapedRepositoryUrl}/releases/tag/v\\d+\\.\\d+\\.\\d+\\))\\.)?</sub>$`);
5103
5249
  //#endregion
5104
5250
  //#region src/review/prior-state.ts
5105
5251
  const mainCommentMarker = "pipr:main-comment";
@@ -5358,8 +5504,50 @@ function hashParts(parts) {
5358
5504
  return new Bun.CryptoHasher("sha256").update(parts.join("\n")).digest("hex").slice(0, 16);
5359
5505
  }
5360
5506
  //#endregion
5507
+ //#region src/review/suggested-fix-publication-policy.ts
5508
+ const maxSuggestedFixSelectedLines = 12;
5509
+ const maxSuggestedFixReplacementLines = 20;
5510
+ function isPublishableSuggestedFixSelection(selection) {
5511
+ const suggestedLines = normalizedSuggestedFixLines(selection.suggestedFix);
5512
+ const selectedLineCount = selection.endLine - selection.startLine + 1;
5513
+ if (selection.side !== "RIGHT" || selection.kind === "deleted" || selectedLineCount > maxSuggestedFixSelectedLines || suggestedLines.length > maxSuggestedFixReplacementLines) return false;
5514
+ const originalLines = selectedPreviewLines(selection, selectedLineCount);
5515
+ if (!originalLines) return false;
5516
+ const structuralEdgePattern = /^([{}[\]()<>]+)[;,]?$/;
5517
+ const firstOriginalEdge = originalLines[0]?.trim().match(structuralEdgePattern)?.[1];
5518
+ const firstSuggestedEdge = suggestedLines[0]?.trim().match(structuralEdgePattern)?.[1];
5519
+ const lastOriginalEdge = originalLines.at(-1)?.trim().match(structuralEdgePattern)?.[1];
5520
+ const lastSuggestedEdge = suggestedLines.at(-1)?.trim().match(structuralEdgePattern)?.[1];
5521
+ return (firstOriginalEdge === void 0 || firstOriginalEdge === firstSuggestedEdge) && (lastOriginalEdge === void 0 || lastOriginalEdge === lastSuggestedEdge) && !hasUnchangedSelectionEdge(originalLines, suggestedLines) && !suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines);
5522
+ }
5523
+ function normalizedSuggestedFixLines(value) {
5524
+ const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5525
+ const withoutFinalNewline = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
5526
+ return withoutFinalNewline.length === 0 ? [] : withoutFinalNewline.split("\n");
5527
+ }
5528
+ function selectedPreviewLines(selection, selectedLineCount) {
5529
+ if (!selection.preview) return;
5530
+ const offset = selection.startLine - selection.rangeStartLine;
5531
+ if (offset < 0) return;
5532
+ const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
5533
+ if (offset + selectedLineCount > previewLines.length) return;
5534
+ return previewLines.slice(offset, offset + selectedLineCount);
5535
+ }
5536
+ function suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines) {
5537
+ if (!selection.preview || suggestedLines.length <= selectedLineCount) return false;
5538
+ const offset = selection.startLine - selection.rangeStartLine;
5539
+ if (offset < 0) return false;
5540
+ const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
5541
+ return [offset > 0 ? previewLines[offset - 1] : void 0, previewLines[offset + selectedLineCount]].filter((line) => Boolean(line?.trim())).some((line) => suggestedLines.includes(line));
5542
+ }
5543
+ function hasUnchangedSelectionEdge(originalLines, suggestedLines) {
5544
+ const firstLineUnchanged = originalLines[0] === suggestedLines[0];
5545
+ const lastLineUnchanged = originalLines.at(-1) === suggestedLines.at(-1);
5546
+ if (originalLines.length === suggestedLines.length || originalLines.length === 1) return firstLineUnchanged || lastLineUnchanged;
5547
+ return firstLineUnchanged && lastLineUnchanged;
5548
+ }
5549
+ //#endregion
5361
5550
  //#region src/review/comment.ts
5362
- const runtimeVersion = version;
5363
5551
  const inlinePublicationItemSchema = z.strictObject({
5364
5552
  finding: reviewFindingSchema,
5365
5553
  range: commentableRangeSchema,
@@ -5406,6 +5594,7 @@ const threadActionSchema = z.strictObject({
5406
5594
  const threadActionsSchema = z.array(threadActionSchema);
5407
5595
  const publicationMetadataSchema = z.strictObject({
5408
5596
  runtimeVersion: z.string().min(1),
5597
+ configVersion: z.string().min(1).optional(),
5409
5598
  trustedConfigSha: z.string().min(1).optional(),
5410
5599
  trustedConfigHash: z.string().min(1).optional(),
5411
5600
  reviewedHeadSha: z.string().min(1),
@@ -5425,8 +5614,6 @@ const publicationPlanSchema = z.strictObject({
5425
5614
  reviewState: priorReviewStateSchema,
5426
5615
  threadActions: threadActionsSchema
5427
5616
  });
5428
- const maxInlineFindingBodyCharacters = 700;
5429
- const maxInlineFindingBodyLines = 4;
5430
5617
  function buildPublicationPlan(options) {
5431
5618
  const reviewState = options.reviewState ?? buildPriorReviewState({
5432
5619
  findings: options.inlineItems.map((item) => item.finding),
@@ -5443,7 +5630,7 @@ function buildPublicationPlan(options) {
5443
5630
  event: options.event,
5444
5631
  reviewState,
5445
5632
  main: options.main,
5446
- reviewedHeadSha: metadata.reviewedHeadSha
5633
+ metadata
5447
5634
  }),
5448
5635
  mainMarker: mainCommentMarker,
5449
5636
  changeNumber: options.event.change.number,
@@ -5497,9 +5684,9 @@ function findingWithPublishableBody(finding) {
5497
5684
  };
5498
5685
  }
5499
5686
  function conciseInlineFindingBody(value) {
5500
- const body = redactPotentialSecrets((value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim().split(/\n{2,}/)[0] ?? value).split("\n").slice(0, maxInlineFindingBodyLines).join("\n").trim());
5501
- if (body.length <= maxInlineFindingBodyCharacters) return body;
5502
- return `${body.slice(0, maxInlineFindingBodyCharacters).trimEnd()}...`;
5687
+ const body = redactPotentialSecrets((value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim().split(/\n{2,}/)[0] ?? value).split("\n").slice(0, 4).join("\n").trim());
5688
+ if (body.length <= 700) return body;
5689
+ return `${body.slice(0, 700).trimEnd()}...`;
5503
5690
  }
5504
5691
  function findingWithPublishableSuggestedFix(finding, range) {
5505
5692
  if (!finding.suggestedFix) return finding;
@@ -5531,10 +5718,19 @@ function renderMainComment(options) {
5531
5718
  "",
5532
5719
  redactPotentialSecrets(options.main),
5533
5720
  "",
5534
- `<sub>Review generated by [Pipr](${piprRepositoryUrl}) for commit \`${options.reviewedHeadSha.slice(0, 7)}\`.</sub>`,
5721
+ renderMainCommentAttribution(options.metadata),
5535
5722
  ""
5536
5723
  ].join("\n");
5537
5724
  }
5725
+ function renderMainCommentAttribution(metadata) {
5726
+ const configNotice = configVersionNotice(metadata);
5727
+ return `<sub>Review generated by [Pipr](${piprRepositoryUrl}) for commit \`${metadata.reviewedHeadSha.slice(0, 7)}\`.${configNotice}</sub>`;
5728
+ }
5729
+ function configVersionNotice(metadata) {
5730
+ if (!metadata.configVersion || !stableSemverPattern.test(metadata.runtimeVersion) || !stableSemverPattern.test(metadata.configVersion) || compareStableSemver(metadata.runtimeVersion, metadata.configVersion) <= 0) return "";
5731
+ const releaseUrl = `${piprRepositoryUrl}/releases/tag/v${metadata.runtimeVersion}`;
5732
+ return ` Config SDK ${metadata.configVersion} is behind [Pipr ${metadata.runtimeVersion}](${releaseUrl}).`;
5733
+ }
5538
5734
  function renderInlineBody(finding, findingId, reviewedHeadSha) {
5539
5735
  const findingBody = startsWithStructuredMarkdown(finding.body) ? finding.body : [
5540
5736
  "**Issue**",
@@ -5966,9 +6162,9 @@ function trackResultFindingScope(state, value, paths) {
5966
6162
  const parsed = agentInlineFindingsOutputSchema.safeParse(value);
5967
6163
  if (parsed.success) state.findingScopes.set(parsed.data.inlineFindings, paths);
5968
6164
  }
5969
- function collectedReview(output) {
6165
+ function collectedReview(output, summaryBody) {
5970
6166
  return {
5971
- summary: { body: "Review completed." },
6167
+ summary: { body: summaryBody },
5972
6168
  inlineFindings: output.findings.map((item) => item.finding)
5973
6169
  };
5974
6170
  }
@@ -6009,7 +6205,8 @@ async function runTaskRuntime(options) {
6009
6205
  reason: options.emptyTasksReason,
6010
6206
  taskName: options.taskName,
6011
6207
  trustedConfigSha: options.trustedConfigSha,
6012
- trustedConfigHash: options.trustedConfigHash
6208
+ trustedConfigHash: options.trustedConfigHash,
6209
+ versionCompatibility: options.versionCompatibility
6013
6210
  });
6014
6211
  }
6015
6212
  const selectedTasks = tasks.map((task) => task.name);
@@ -6104,7 +6301,8 @@ async function runTaskRuntime(options) {
6104
6301
  });
6105
6302
  if (commandResponse) return commandResponse;
6106
6303
  assertReviewCommentOutput(output, options.commandInvocation !== void 0);
6107
- const validated = validateReviewResult(collectedReview(output), diffManifest, {
6304
+ const main = typeof output.comment.value === "string" ? output.comment.value : output.comment.value.main ?? "Review completed.";
6305
+ const validated = validateReviewResult(collectedReview(output, main), diffManifest, {
6108
6306
  expectedHeadSha: options.event.change.head.sha,
6109
6307
  pathScopeForFinding: (_finding, index) => output.findings[index]?.paths
6110
6308
  });
@@ -6118,7 +6316,7 @@ async function runTaskRuntime(options) {
6118
6316
  });
6119
6317
  const publishing = buildCommentPublishingPlan({
6120
6318
  event: options.event,
6121
- main: typeof output.comment.value === "string" ? output.comment.value : output.comment.value.main ?? "Review completed.",
6319
+ main,
6122
6320
  validated,
6123
6321
  manifest: diffManifest,
6124
6322
  maxInlineComments: config.publication.maxInlineComments,
@@ -6126,6 +6324,7 @@ async function runTaskRuntime(options) {
6126
6324
  threadActions: verifier.threadActions,
6127
6325
  metadata: {
6128
6326
  runtimeVersion,
6327
+ configVersion: options.versionCompatibility?.configVersion,
6129
6328
  trustedConfigSha: options.trustedConfigSha,
6130
6329
  trustedConfigHash: options.trustedConfigHash,
6131
6330
  reviewedHeadSha: options.event.change.head.sha,
@@ -6316,6 +6515,7 @@ function skippedTaskRuntimeResult(options) {
6316
6515
  maxInlineComments: options.config.publication.maxInlineComments,
6317
6516
  metadata: {
6318
6517
  runtimeVersion,
6518
+ configVersion: options.versionCompatibility?.configVersion,
6319
6519
  trustedConfigSha: options.trustedConfigSha,
6320
6520
  trustedConfigHash: options.trustedConfigHash,
6321
6521
  reviewedHeadSha: options.event.change.head.sha,
@@ -6384,7 +6584,7 @@ const githubRepositoryPermissionResponseSchema = z.looseObject({
6384
6584
  permission: z.string().min(1),
6385
6585
  role_name: z.string().min(1).optional()
6386
6586
  });
6387
- const permissionLevels = new Set([
6587
+ const permissionLevels = /* @__PURE__ */ new Set([
6388
6588
  "read",
6389
6589
  "triage",
6390
6590
  "write",
@@ -7043,6 +7243,18 @@ async function loadGitHubPriorMainComment(options) {
7043
7243
  }), "pipr:main-comment", options.change.change.number, ownerLogin)?.body ?? void 0;
7044
7244
  }
7045
7245
  //#endregion
7246
+ //#region src/review/inline-publication-policy.ts
7247
+ function inlinePublicationDecision(options) {
7248
+ if (options.existing.markers.has(options.marker) || hasExistingInlinePublicationLocation(options.existing.locations, options.location)) return "skip";
7249
+ return "post";
7250
+ }
7251
+ function hasExistingInlinePublicationLocation(existing, location) {
7252
+ return existing.some((comment) => {
7253
+ if (comment.path !== location.path || comment.commitId !== location.commitId || comment.side !== location.side) return false;
7254
+ return comment.startLine <= location.endLine && location.startLine <= comment.endLine;
7255
+ });
7256
+ }
7257
+ //#endregion
7046
7258
  //#region src/hosts/github/inline.ts
7047
7259
  const githubReviewCommentLocationSchema = z.strictObject({
7048
7260
  path: z.string().min(1),
@@ -7630,6 +7842,10 @@ function logTrustedRuntime(log, runtime) {
7630
7842
  tasks: runtime.plan.tasks.length,
7631
7843
  commands: runtime.plan.commands.length
7632
7844
  });
7845
+ logConfigWarnings(log, runtime.settings.warnings);
7846
+ }
7847
+ function logConfigWarnings(log, warnings) {
7848
+ for (const warning of warnings) log.warning("config warning", { warning });
7633
7849
  }
7634
7850
  function addProviderSecrets(log, config, env) {
7635
7851
  for (const provider of config.providers) log.addSecret((env ?? process.env)[provider.apiKeyEnv]);
@@ -7825,6 +8041,7 @@ async function runTrustedReviewAndPublish(options) {
7825
8041
  event: options.event,
7826
8042
  env: options.options.env,
7827
8043
  plan: options.trustedRuntime.plan,
8044
+ versionCompatibility: options.trustedRuntime.versionCompatibility,
7828
8045
  taskName: options.taskName,
7829
8046
  taskInput: options.taskInput,
7830
8047
  commandInvocation: options.commandInvocation,
@@ -7896,7 +8113,8 @@ async function runTrustedReviewAndPublish(options) {
7896
8113
  async function loadRuntimeProjectFromGitCommit(options) {
7897
8114
  const configDir = resolveContainedConfigDir(options);
7898
8115
  const files = listConfigFilesAtCommit(options.rootDir, options.commitSha, configDir.gitPath);
7899
- if (files.length === 0) throw new Error(`${configDir.configDir}/config.ts is required at base commit ${options.commitSha}`);
8116
+ const configPath = configDir.gitPath === "." ? "config.ts" : `${configDir.gitPath}/config.ts`;
8117
+ if (files.length === 0 || !files.some((file) => file.path === configPath)) throw new Error(`No Pipr config found at ${configPath} in base commit ${options.commitSha}. Run \`pipr init\` and commit the generated config.`);
7900
8118
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-base-config-"));
7901
8119
  try {
7902
8120
  const hash = new Bun.CryptoHasher("sha256");
@@ -8434,7 +8652,10 @@ async function runInspectCommand(options) {
8434
8652
  ...options,
8435
8653
  requireProviderEnv: false
8436
8654
  });
8437
- return inspectRuntimePlan(runtime.plan, runtime.settings.source);
8655
+ return {
8656
+ ...inspectRuntimePlan(runtime.plan, runtime.settings.source),
8657
+ warnings: runtime.settings.warnings
8658
+ };
8438
8659
  }
8439
8660
  /** Loads the runtime config and pull request event without running review publication. */
8440
8661
  async function runDryRunCommand(options) {
@@ -8453,7 +8674,8 @@ async function runDryRunCommand(options) {
8453
8674
  });
8454
8675
  return {
8455
8676
  configSource: runtime.settings.source,
8456
- event
8677
+ event,
8678
+ warnings: runtime.settings.warnings
8457
8679
  };
8458
8680
  }
8459
8681
  /** Runs configured change-request tasks against local Git base and head revisions. */
@@ -8478,6 +8700,7 @@ async function runLocalReviewCommand(options) {
8478
8700
  tasks: runtime.plan.tasks.length,
8479
8701
  commands: runtime.plan.commands.length
8480
8702
  });
8703
+ if (log) logConfigWarnings(log, runtime.settings.warnings);
8481
8704
  const selectedTasks = selectLocalReviewTasks(runtime.plan);
8482
8705
  const includeWorkingTree = options.headSha === void 0;
8483
8706
  const headSha = options.headSha ?? runGit$1(["rev-parse", "HEAD"], options.rootDir).trim();
@@ -8500,6 +8723,7 @@ async function runLocalReviewCommand(options) {
8500
8723
  event,
8501
8724
  env: options.env,
8502
8725
  plan: runtime.plan,
8726
+ versionCompatibility: runtime.versionCompatibility,
8503
8727
  selectedTasks,
8504
8728
  emptyTasksReason: "No change-request tasks are configured for local review",
8505
8729
  piExecutable: options.piExecutable,
@@ -8550,6 +8774,6 @@ async function runActionCommandWithDependencies(options) {
8550
8774
  });
8551
8775
  }
8552
8776
  //#endregion
8553
- export { runInspectCommand as a, PublicationError as c, listOfficialInitRecipes as d, supportedOfficialInitRecipes as f, piThinkingLevels as g, piRequiredCliFlags as h, runInitCommand as i, isPublishableSuggestedFixSelection as l, piReadOnlyToolNames as m, runActionCommandWithDependencies as n, runLocalReviewCommand as o, piBuiltinToolNames as p, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, supportedOfficialInitAdapters as u };
8777
+ export { piRequiredCliFlags as _, runInspectCommand as a, PublicationError as c, maxInlineFindingBodyLines as d, supportedOfficialInitAdapters as f, piReadOnlyToolNames as g, piBuiltinToolNames as h, runInitCommand as i, isPublishableSuggestedFixSelection as l, supportedOfficialInitRecipes as m, runActionCommandWithDependencies as n, runLocalReviewCommand as o, listOfficialInitRecipes as p, runDryRunCommand as r, runValidateCommand as s, runActionCommand as t, maxInlineFindingBodyCharacters as u, piThinkingLevels as v };
8554
8778
 
8555
- //# sourceMappingURL=commands-C1sVsbEj.mjs.map
8779
+ //# sourceMappingURL=commands-DCpIxJkz.mjs.map