@usepipr/runtime 0.3.2 → 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.
- package/dist/{commands-4n3t4t5P.d.mts → commands-C6wfO-y7.d.mts} +9 -7
- package/dist/commands-C6wfO-y7.d.mts.map +1 -0
- package/dist/{commands-CQg9QVZt.mjs → commands-DCpIxJkz.mjs} +629 -164
- package/dist/commands-DCpIxJkz.mjs.map +1 -0
- package/dist/index.d.mts +1 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/internal/testing.d.mts +7 -4
- package/dist/internal/testing.d.mts.map +1 -1
- package/dist/internal/testing.mjs +2 -2
- package/dist/pi/runtime-tools-extension.d.mts.map +1 -1
- package/dist/pi/runtime-tools-extension.mjs +2 -1
- package/dist/pi/runtime-tools-extension.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/commands-4n3t4t5P.d.mts.map +0 -1
- package/dist/commands-CQg9QVZt.mjs.map +0 -1
|
@@ -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
|
|
21
|
-
|
|
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
|
-
|
|
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 =
|
|
26
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
577
|
+
const { projectDir, relativeConfigDir } = resolveContainedConfigDir(options);
|
|
431
578
|
const sourceConfigPath = path.join(projectDir, "config.ts");
|
|
432
|
-
if (!await
|
|
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
|
|
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
|
|
469
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
658
|
+
const compilerOptions = {
|
|
498
659
|
...parsed.options,
|
|
499
660
|
skipLibCheck: true,
|
|
500
|
-
typeRoots: [
|
|
501
|
-
types: [
|
|
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
|
-
|
|
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) {
|
|
@@ -2398,6 +2588,14 @@ type CategorizedFinding = {
|
|
|
2398
2588
|
suggestedFix?: string;
|
|
2399
2589
|
};
|
|
2400
2590
|
|
|
2591
|
+
type ReviewSummary = {
|
|
2592
|
+
headline: string;
|
|
2593
|
+
changeSummary: string[];
|
|
2594
|
+
riskLevel: "low" | "medium" | "high";
|
|
2595
|
+
riskSummary: string;
|
|
2596
|
+
reviewerFocus: string[];
|
|
2597
|
+
};
|
|
2598
|
+
|
|
2401
2599
|
const categorizedFindingSchema = z.strictObject({
|
|
2402
2600
|
title: z.string(),
|
|
2403
2601
|
severity: z.enum(["critical", "high", "medium", "low", "nit"]),
|
|
@@ -2420,6 +2618,14 @@ const categorizedFindingSchema = z.strictObject({
|
|
|
2420
2618
|
suggestedFix: z.string().optional(),
|
|
2421
2619
|
});
|
|
2422
2620
|
|
|
2621
|
+
const reviewSummarySchema = z.strictObject({
|
|
2622
|
+
headline: z.string(),
|
|
2623
|
+
changeSummary: z.array(z.string()).min(1).max(4),
|
|
2624
|
+
riskLevel: z.enum(["low", "medium", "high"]),
|
|
2625
|
+
riskSummary: z.string(),
|
|
2626
|
+
reviewerFocus: z.array(z.string()).max(4),
|
|
2627
|
+
});
|
|
2628
|
+
|
|
2423
2629
|
export default definePipr((pipr) => {
|
|
2424
2630
|
const model = pipr.model({
|
|
2425
2631
|
provider: "deepseek",
|
|
@@ -2433,7 +2639,7 @@ export default definePipr((pipr) => {
|
|
|
2433
2639
|
const reviewOutput = pipr.schema({
|
|
2434
2640
|
id: "review/categorized-findings",
|
|
2435
2641
|
schema: z.strictObject({
|
|
2436
|
-
summary:
|
|
2642
|
+
summary: reviewSummarySchema,
|
|
2437
2643
|
findings: z.array(categorizedFindingSchema),
|
|
2438
2644
|
}),
|
|
2439
2645
|
});
|
|
@@ -2449,6 +2655,11 @@ export default definePipr((pipr) => {
|
|
|
2449
2655
|
medium for important follow-up, low for minor actionable improvements,
|
|
2450
2656
|
and nit only for tiny but concrete issues. Include suggestedFix only when
|
|
2451
2657
|
there is an exact replacement for the selected range.
|
|
2658
|
+
|
|
2659
|
+
Make summary maintainer-facing and scannable: one concrete headline, one
|
|
2660
|
+
to four behavior-focused change bullets, a risk level with rationale, and
|
|
2661
|
+
reviewer focus only for useful human follow-up. Put actionable defects in
|
|
2662
|
+
findings, not only in summary.
|
|
2452
2663
|
\`,
|
|
2453
2664
|
output: reviewOutput,
|
|
2454
2665
|
tools: pipr.tools.readOnly,
|
|
@@ -2477,18 +2688,24 @@ export default definePipr((pipr) => {
|
|
|
2477
2688
|
});
|
|
2478
2689
|
await ctx.comment({
|
|
2479
2690
|
main: [
|
|
2480
|
-
|
|
2691
|
+
"## Summary",
|
|
2481
2692
|
"",
|
|
2482
|
-
|
|
2693
|
+
\`**\${result.summary.headline}**\`,
|
|
2483
2694
|
"",
|
|
2484
|
-
|
|
2695
|
+
summaryTable(result.summary, result.findings.length),
|
|
2696
|
+
"",
|
|
2697
|
+
"## What Changed",
|
|
2698
|
+
"",
|
|
2699
|
+
bulletList(result.summary.changeSummary, "No changed behavior summarized."),
|
|
2700
|
+
"",
|
|
2701
|
+
"## Reviewer Focus",
|
|
2485
2702
|
"",
|
|
2486
|
-
"
|
|
2487
|
-
"<summary>Finding rationales</summary>",
|
|
2703
|
+
bulletList(result.summary.reviewerFocus, "No special reviewer focus."),
|
|
2488
2704
|
"",
|
|
2489
|
-
|
|
2705
|
+
"## Findings",
|
|
2490
2706
|
"",
|
|
2491
|
-
|
|
2707
|
+
findingsTable(result.findings),
|
|
2708
|
+
...(result.findings.length > 0 ? ["", findingRationalesBlock(result.findings)] : []),
|
|
2492
2709
|
].join("\\n"),
|
|
2493
2710
|
inlineFindings,
|
|
2494
2711
|
});
|
|
@@ -2499,6 +2716,23 @@ export default definePipr((pipr) => {
|
|
|
2499
2716
|
pipr.command({ pattern: "@pipr review", permission: "write", task });
|
|
2500
2717
|
});
|
|
2501
2718
|
|
|
2719
|
+
function summaryTable(summary: ReviewSummary, findingCount: number): string {
|
|
2720
|
+
return [
|
|
2721
|
+
"| Outcome | Risk | Risk summary |",
|
|
2722
|
+
"| --- | --- | --- |",
|
|
2723
|
+
\`| \${findingOutcome(findingCount)} | \${labelValue(summary.riskLevel)} | \${tableCell(
|
|
2724
|
+
summary.riskSummary,
|
|
2725
|
+
)} |\`,
|
|
2726
|
+
].join("\\n");
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
function findingOutcome(findingCount: number): string {
|
|
2730
|
+
if (findingCount === 0) {
|
|
2731
|
+
return "No findings";
|
|
2732
|
+
}
|
|
2733
|
+
return findingCount === 1 ? "1 finding" : \`\${findingCount} findings\`;
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2502
2736
|
function findingsTable(findings: CategorizedFinding[]): string {
|
|
2503
2737
|
if (findings.length === 0) {
|
|
2504
2738
|
return [
|
|
@@ -2519,22 +2753,48 @@ function findingsTable(findings: CategorizedFinding[]): string {
|
|
|
2519
2753
|
].join("\\n");
|
|
2520
2754
|
}
|
|
2521
2755
|
|
|
2522
|
-
function
|
|
2756
|
+
function findingRationalesBlock(findings: CategorizedFinding[]): string {
|
|
2523
2757
|
if (findings.length === 0) {
|
|
2524
|
-
return "
|
|
2758
|
+
return "";
|
|
2525
2759
|
}
|
|
2526
|
-
return
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2760
|
+
return [
|
|
2761
|
+
"<details>",
|
|
2762
|
+
"<summary>Finding rationales</summary>",
|
|
2763
|
+
"",
|
|
2764
|
+
findings
|
|
2765
|
+
.map((finding, index) =>
|
|
2766
|
+
[
|
|
2767
|
+
\`### \${index + 1}. \${finding.title}\`,
|
|
2768
|
+
"",
|
|
2769
|
+
\`**Severity:** \${labelValue(finding.severity)}\`,
|
|
2770
|
+
\`**Category:** \${labelValue(finding.category)}\`,
|
|
2771
|
+
"",
|
|
2772
|
+
finding.rationale,
|
|
2773
|
+
].join("\\n"),
|
|
2774
|
+
)
|
|
2775
|
+
.join("\\n\\n"),
|
|
2776
|
+
"",
|
|
2777
|
+
"</details>",
|
|
2778
|
+
].join("\\n");
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
function bulletList(items: string[], emptyText: string): string {
|
|
2782
|
+
if (items.length === 0) {
|
|
2783
|
+
return emptyText;
|
|
2784
|
+
}
|
|
2785
|
+
return items.map((item) => \`- \${lineText(item)}\`).join("\\n");
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
function labelValue(value: string): string {
|
|
2789
|
+
return value.replaceAll("-", " ").replace(/^./, (char) => char.toUpperCase());
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
function lineText(value: string): string {
|
|
2793
|
+
return value.replaceAll("\\n", " ").trim();
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
function tableCell(value: string): string {
|
|
2797
|
+
return lineText(value).replaceAll("|", "\\\\|");
|
|
2538
2798
|
}
|
|
2539
2799
|
`
|
|
2540
2800
|
};
|
|
@@ -2552,15 +2812,25 @@ const securitySastRecipe = {
|
|
|
2552
2812
|
configTs: `import { definePipr } from "@usepipr/sdk";
|
|
2553
2813
|
import type { ReviewFinding } from "@usepipr/sdk";
|
|
2554
2814
|
|
|
2815
|
+
type SecuritySummary = {
|
|
2816
|
+
headline: string;
|
|
2817
|
+
riskLevel: "low" | "medium" | "high";
|
|
2818
|
+
riskSummary: string;
|
|
2819
|
+
reviewerFocus: string[];
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
type SecurityRisk = {
|
|
2823
|
+
title: string;
|
|
2824
|
+
category: "auth" | "injection" | "secret" | "crypto" | "data-exposure" | "other";
|
|
2825
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
2826
|
+
rationale: string;
|
|
2827
|
+
finding?: ReviewFinding;
|
|
2828
|
+
};
|
|
2829
|
+
|
|
2555
2830
|
type SecurityReview = {
|
|
2556
|
-
summary:
|
|
2557
|
-
risks:
|
|
2558
|
-
|
|
2559
|
-
category: "auth" | "injection" | "secret" | "crypto" | "data-exposure" | "other";
|
|
2560
|
-
severity: "low" | "medium" | "high" | "critical";
|
|
2561
|
-
rationale: string;
|
|
2562
|
-
finding?: ReviewFinding;
|
|
2563
|
-
}>;
|
|
2831
|
+
summary: SecuritySummary;
|
|
2832
|
+
risks: SecurityRisk[];
|
|
2833
|
+
diagramMermaid?: string;
|
|
2564
2834
|
};
|
|
2565
2835
|
|
|
2566
2836
|
export default definePipr((pipr) => {
|
|
@@ -2578,7 +2848,20 @@ export default definePipr((pipr) => {
|
|
|
2578
2848
|
additionalProperties: false,
|
|
2579
2849
|
required: ["summary", "risks"],
|
|
2580
2850
|
properties: {
|
|
2581
|
-
summary: {
|
|
2851
|
+
summary: {
|
|
2852
|
+
type: "object",
|
|
2853
|
+
additionalProperties: false,
|
|
2854
|
+
required: ["headline", "riskLevel", "riskSummary", "reviewerFocus"],
|
|
2855
|
+
properties: {
|
|
2856
|
+
headline: { type: "string" },
|
|
2857
|
+
riskLevel: { type: "string", enum: ["low", "medium", "high"] },
|
|
2858
|
+
riskSummary: { type: "string" },
|
|
2859
|
+
reviewerFocus: {
|
|
2860
|
+
type: "array",
|
|
2861
|
+
items: { type: "string" },
|
|
2862
|
+
},
|
|
2863
|
+
},
|
|
2864
|
+
},
|
|
2582
2865
|
risks: {
|
|
2583
2866
|
type: "array",
|
|
2584
2867
|
items: {
|
|
@@ -2610,6 +2893,7 @@ export default definePipr((pipr) => {
|
|
|
2610
2893
|
},
|
|
2611
2894
|
},
|
|
2612
2895
|
},
|
|
2896
|
+
diagramMermaid: { type: "string" },
|
|
2613
2897
|
},
|
|
2614
2898
|
},
|
|
2615
2899
|
});
|
|
@@ -2621,6 +2905,11 @@ export default definePipr((pipr) => {
|
|
|
2621
2905
|
Review for exploitable security issues only. Focus on auth bypasses,
|
|
2622
2906
|
injection, unsafe deserialization, secret exposure, cryptography misuse,
|
|
2623
2907
|
authorization gaps, and data exposure. Do not report hypothetical style issues.
|
|
2908
|
+
Make summary maintainer-facing and scannable with a concrete headline,
|
|
2909
|
+
risk level, risk rationale, and only useful security follow-up. Set
|
|
2910
|
+
diagramMermaid only when a high or critical risk has a concrete source-to-sink
|
|
2911
|
+
path and a small Mermaid flowchart clarifies it. Do not include Markdown
|
|
2912
|
+
fences in diagramMermaid.
|
|
2624
2913
|
\`,
|
|
2625
2914
|
output: securityOutput,
|
|
2626
2915
|
tools: pipr.tools.readOnly,
|
|
@@ -2636,14 +2925,40 @@ export default definePipr((pipr) => {
|
|
|
2636
2925
|
async run(ctx) {
|
|
2637
2926
|
const manifest = await ctx.change.diffManifest({ compressed: true });
|
|
2638
2927
|
const result = await ctx.pi.run(security, { manifest });
|
|
2639
|
-
const inlineFindings = result.risks.flatMap((risk) =>
|
|
2640
|
-
|
|
2928
|
+
const inlineFindings: ReviewFinding[] = result.risks.flatMap((risk) =>
|
|
2929
|
+
risk.finding ? [risk.finding] : [],
|
|
2930
|
+
);
|
|
2931
|
+
const hasHighOrCriticalRisk = result.risks.some(isHighOrCriticalRisk);
|
|
2932
|
+
const hasConcreteHighOrCriticalRisk = result.risks.some(
|
|
2933
|
+
(risk) => isHighOrCriticalRisk(risk) && risk.finding,
|
|
2934
|
+
);
|
|
2935
|
+
if (hasHighOrCriticalRisk) {
|
|
2641
2936
|
ctx.check.fail("High or critical security risk found.");
|
|
2642
2937
|
} else {
|
|
2643
2938
|
ctx.check.pass("No high or critical security risks found.");
|
|
2644
2939
|
}
|
|
2645
2940
|
await ctx.comment({
|
|
2646
|
-
main:
|
|
2941
|
+
main: [
|
|
2942
|
+
"## Summary",
|
|
2943
|
+
"",
|
|
2944
|
+
\`**\${result.summary.headline}**\`,
|
|
2945
|
+
"",
|
|
2946
|
+
securityStatusTable(result.summary, result.risks),
|
|
2947
|
+
"",
|
|
2948
|
+
result.summary.riskSummary,
|
|
2949
|
+
"",
|
|
2950
|
+
"## Reviewer Focus",
|
|
2951
|
+
"",
|
|
2952
|
+
bulletList(result.summary.reviewerFocus, "No special security follow-up."),
|
|
2953
|
+
"",
|
|
2954
|
+
"## Security Risks",
|
|
2955
|
+
"",
|
|
2956
|
+
securityRisksTable(result.risks),
|
|
2957
|
+
...(result.risks.length > 0 ? ["", riskRationalesBlock(result.risks)] : []),
|
|
2958
|
+
...(hasConcreteHighOrCriticalRisk && result.diagramMermaid?.trim()
|
|
2959
|
+
? ["", attackPathDiagramBlock(result.diagramMermaid, hasConcreteHighOrCriticalRisk)]
|
|
2960
|
+
: []),
|
|
2961
|
+
].join("\\n"),
|
|
2647
2962
|
inlineFindings,
|
|
2648
2963
|
});
|
|
2649
2964
|
},
|
|
@@ -2652,6 +2967,122 @@ export default definePipr((pipr) => {
|
|
|
2652
2967
|
pipr.on.changeRequest({ actions: ["opened", "updated", "reopened", "ready"], task });
|
|
2653
2968
|
pipr.command({ pattern: "@pipr security", permission: "write", task });
|
|
2654
2969
|
});
|
|
2970
|
+
|
|
2971
|
+
function securityStatusTable(summary: SecuritySummary, risks: SecurityRisk[]): string {
|
|
2972
|
+
return [
|
|
2973
|
+
"| Status | Summary risk | Max severity | Risks |",
|
|
2974
|
+
"| --- | --- | --- | ---: |",
|
|
2975
|
+
\`| \${risks.some(isHighOrCriticalRisk) ? "Fail" : "Pass"} | \${labelValue(
|
|
2976
|
+
summary.riskLevel,
|
|
2977
|
+
)} | \${maxSeverity(risks)} | \${risks.length} |\`,
|
|
2978
|
+
].join("\\n");
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
function securityRisksTable(risks: SecurityRisk[]): string {
|
|
2982
|
+
if (risks.length === 0) {
|
|
2983
|
+
return [
|
|
2984
|
+
"| Severity | Category | Title |",
|
|
2985
|
+
"| --- | --- | --- |",
|
|
2986
|
+
"| - | - | No security risks found. |",
|
|
2987
|
+
].join("\\n");
|
|
2988
|
+
}
|
|
2989
|
+
return [
|
|
2990
|
+
"| Severity | Category | Title |",
|
|
2991
|
+
"| --- | --- | --- |",
|
|
2992
|
+
...risks.map(
|
|
2993
|
+
(risk) =>
|
|
2994
|
+
\`| \${labelValue(risk.severity)} | \${tableCell(risk.category)} | \${tableCell(risk.title)} |\`,
|
|
2995
|
+
),
|
|
2996
|
+
].join("\\n");
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
function riskRationalesBlock(risks: SecurityRisk[]): string {
|
|
3000
|
+
if (risks.length === 0) {
|
|
3001
|
+
return "";
|
|
3002
|
+
}
|
|
3003
|
+
return [
|
|
3004
|
+
"<details>",
|
|
3005
|
+
"<summary>Risk rationales</summary>",
|
|
3006
|
+
"",
|
|
3007
|
+
risks
|
|
3008
|
+
.map((risk, index) =>
|
|
3009
|
+
[
|
|
3010
|
+
\`### \${index + 1}. \${risk.title}\`,
|
|
3011
|
+
"",
|
|
3012
|
+
\`**Severity:** \${labelValue(risk.severity)}\`,
|
|
3013
|
+
\`**Category:** \${labelValue(risk.category)}\`,
|
|
3014
|
+
"",
|
|
3015
|
+
risk.rationale,
|
|
3016
|
+
].join("\\n"),
|
|
3017
|
+
)
|
|
3018
|
+
.join("\\n\\n"),
|
|
3019
|
+
"",
|
|
3020
|
+
"</details>",
|
|
3021
|
+
].join("\\n");
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
function attackPathDiagramBlock(
|
|
3025
|
+
diagramMermaid: string | undefined,
|
|
3026
|
+
hasConcreteHighOrCriticalRisk: boolean,
|
|
3027
|
+
): string {
|
|
3028
|
+
const diagram = diagramMermaid?.trim();
|
|
3029
|
+
if (!diagram || !hasConcreteHighOrCriticalRisk) {
|
|
3030
|
+
return "";
|
|
3031
|
+
}
|
|
3032
|
+
const fence = markdownFenceFor(diagram);
|
|
3033
|
+
return [
|
|
3034
|
+
"<details>",
|
|
3035
|
+
"<summary>Attack path diagram</summary>",
|
|
3036
|
+
"",
|
|
3037
|
+
\`\${fence}mermaid\`,
|
|
3038
|
+
diagram,
|
|
3039
|
+
fence,
|
|
3040
|
+
"",
|
|
3041
|
+
"</details>",
|
|
3042
|
+
].join("\\n");
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
function maxSeverity(risks: SecurityRisk[]): string {
|
|
3046
|
+
const order: SecurityRisk["severity"][] = ["low", "medium", "high", "critical"];
|
|
3047
|
+
const severity = risks.reduce<SecurityRisk["severity"] | undefined>((current, risk) => {
|
|
3048
|
+
if (!current || order.indexOf(risk.severity) > order.indexOf(current)) {
|
|
3049
|
+
return risk.severity;
|
|
3050
|
+
}
|
|
3051
|
+
return current;
|
|
3052
|
+
}, undefined);
|
|
3053
|
+
return severity ? labelValue(severity) : "None";
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
function isHighOrCriticalRisk(risk: SecurityRisk): boolean {
|
|
3057
|
+
return risk.severity === "high" || risk.severity === "critical";
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
function bulletList(items: string[], emptyText: string): string {
|
|
3061
|
+
if (items.length === 0) {
|
|
3062
|
+
return emptyText;
|
|
3063
|
+
}
|
|
3064
|
+
return items.map((item) => \`- \${lineText(item)}\`).join("\\n");
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
function labelValue(value: string): string {
|
|
3068
|
+
return value.replaceAll("-", " ").replace(/^./, (char) => char.toUpperCase());
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
function lineText(value: string): string {
|
|
3072
|
+
return value.replaceAll("\\n", " ").trim();
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function tableCell(value: string): string {
|
|
3076
|
+
return lineText(value).replaceAll("|", "\\\\|");
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function markdownFenceFor(value: string): string {
|
|
3080
|
+
const longestBacktickRun = Math.max(
|
|
3081
|
+
0,
|
|
3082
|
+
...[...value.matchAll(/\`+/g)].map((match) => match[0].length),
|
|
3083
|
+
);
|
|
3084
|
+
return "\`".repeat(Math.max(3, longestBacktickRun + 1));
|
|
3085
|
+
}
|
|
2655
3086
|
`
|
|
2656
3087
|
};
|
|
2657
3088
|
//#endregion
|
|
@@ -2713,9 +3144,8 @@ function isOfficialInitRecipeId(recipe) {
|
|
|
2713
3144
|
//#endregion
|
|
2714
3145
|
//#region src/config/init.ts
|
|
2715
3146
|
const supportedOfficialInitAdapters = ["github"];
|
|
2716
|
-
const defaultWorkflowActionRef = "somus/pipr@v0.3.
|
|
2717
|
-
const defaultSdkVersion = "0.3.
|
|
2718
|
-
const defaultTypesBunVersion = "1.3.14";
|
|
3147
|
+
const defaultWorkflowActionRef = "somus/pipr@v0.3.5";
|
|
3148
|
+
const defaultSdkVersion = "0.3.5";
|
|
2719
3149
|
function resolveOfficialInitAdapters(adapters) {
|
|
2720
3150
|
if (adapters === void 0) return [...supportedOfficialInitAdapters];
|
|
2721
3151
|
if (adapters.length === 0) return [];
|
|
@@ -2803,7 +3233,10 @@ function starterPackageJson() {
|
|
|
2803
3233
|
return `${JSON.stringify({
|
|
2804
3234
|
private: true,
|
|
2805
3235
|
dependencies: { "@usepipr/sdk": process.env.PIPR_INTERNAL_INIT_SDK_VERSION ?? defaultSdkVersion },
|
|
2806
|
-
devDependencies: {
|
|
3236
|
+
devDependencies: {
|
|
3237
|
+
"@types/bun": defaultTypesBunVersion,
|
|
3238
|
+
typescript: defaultTypescriptVersion
|
|
3239
|
+
}
|
|
2807
3240
|
}, null, 2)}\n`;
|
|
2808
3241
|
}
|
|
2809
3242
|
async function writeTargets(targets, existing, options) {
|
|
@@ -3818,7 +4251,7 @@ async function startCustomToolBridge(tools, context) {
|
|
|
3818
4251
|
const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
3819
4252
|
let server;
|
|
3820
4253
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
3821
|
-
const port = 49152 + crypto.getRandomValues(new Uint16Array(1))[0] % 16384;
|
|
4254
|
+
const port = 49152 + crypto.getRandomValues(/* @__PURE__ */ new Uint16Array(1))[0] % 16384;
|
|
3822
4255
|
try {
|
|
3823
4256
|
server = Bun.serve({
|
|
3824
4257
|
hostname: "127.0.0.1",
|
|
@@ -3903,7 +4336,7 @@ const piprJsonSystemPrompt = [
|
|
|
3903
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.",
|
|
3904
4337
|
"When identifying a secret or credential, describe its kind and location without copying the secret value."
|
|
3905
4338
|
].join(" ");
|
|
3906
|
-
const ignoredWorkspacePaths = new Set([
|
|
4339
|
+
const ignoredWorkspacePaths = /* @__PURE__ */ new Set([
|
|
3907
4340
|
".git",
|
|
3908
4341
|
"node_modules",
|
|
3909
4342
|
"dist",
|
|
@@ -4328,6 +4761,10 @@ function findingFingerprint(finding) {
|
|
|
4328
4761
|
return new Bun.CryptoHasher("sha256").update(basis).digest("hex").slice(0, 16);
|
|
4329
4762
|
}
|
|
4330
4763
|
//#endregion
|
|
4764
|
+
//#region src/review/inline-finding-limits.ts
|
|
4765
|
+
const maxInlineFindingBodyCharacters = 700;
|
|
4766
|
+
const maxInlineFindingBodyLines = 4;
|
|
4767
|
+
//#endregion
|
|
4331
4768
|
//#region src/review/agent/agent-prompt.ts
|
|
4332
4769
|
async function renderAgentPrompt(options) {
|
|
4333
4770
|
const prompt = await renderAgentDefinitionPrompt(options.agent, options.input, options.agentRunContext.prompt);
|
|
@@ -4362,20 +4799,18 @@ function toolsPrompt(diffManifest, toolMode) {
|
|
|
4362
4799
|
].join("\n");
|
|
4363
4800
|
}
|
|
4364
4801
|
function outputPrompt(schema) {
|
|
4365
|
-
|
|
4802
|
+
const lines = compact([
|
|
4366
4803
|
`Schema ID: ${schema.id}.`,
|
|
4367
4804
|
schema.jsonSchema ? `JSON Schema:\n${JSON.stringify(schema.jsonSchema, null, 2)}` : void 0,
|
|
4368
|
-
schema.id === reviewResultSchemaId ? `Example:\n${JSON.stringify(reviewSchemaExample$1(), null, 2)}` : void 0,
|
|
4369
|
-
schema.id === reviewResultSchemaId ? "`suggestedFix` is exact replacement code for the selected range. Do not include Markdown fences, prose, or labels in `suggestedFix`." : void 0,
|
|
4370
|
-
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,
|
|
4371
|
-
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,
|
|
4372
|
-
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,
|
|
4373
|
-
schema.id === reviewResultSchemaId ? "Omit `suggestedFix` for broad rewrites, generated docs/pages, uncertain ranges, or changes better described in prose." : void 0,
|
|
4374
4805
|
"Return exactly one JSON value matching the schema.",
|
|
4375
4806
|
"The first non-whitespace character must be { or [ and the last non-whitespace character must be } or ].",
|
|
4376
|
-
"Do not include Markdown, code fences, prose, explanations, or leading/trailing text."
|
|
4377
|
-
|
|
4378
|
-
|
|
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");
|
|
4379
4814
|
}
|
|
4380
4815
|
function reviewPolicyPrompt(schema) {
|
|
4381
4816
|
if (schema.id !== reviewResultSchemaId) return;
|
|
@@ -4383,7 +4818,9 @@ function reviewPolicyPrompt(schema) {
|
|
|
4383
4818
|
"Review only changed behavior.",
|
|
4384
4819
|
"Report only actionable defects, security risks, regressions, or meaningful test gaps.",
|
|
4385
4820
|
"Put each actionable issue in inlineFindings. Do not leave actionable defects or test gaps only in the summary.",
|
|
4386
|
-
"
|
|
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.",
|
|
4387
4824
|
"Omit speculative, style-only, broad refactor, external-fact, and out-of-diff findings.",
|
|
4388
4825
|
"Use read tools when more context is needed. If evidence is insufficient, omit the finding.",
|
|
4389
4826
|
"Emit one inline finding per issue, anchored to the exact Diff Manifest commentable range.",
|
|
@@ -4798,67 +5235,17 @@ function buildRepairPrompt(options) {
|
|
|
4798
5235
|
].join("\n\n");
|
|
4799
5236
|
}
|
|
4800
5237
|
//#endregion
|
|
4801
|
-
//#region package.json
|
|
4802
|
-
var version = "0.3.2";
|
|
4803
|
-
//#endregion
|
|
4804
5238
|
//#region src/review/comment-branding.ts
|
|
4805
|
-
const piprLogoUrl = "https://pipr.run/
|
|
5239
|
+
const piprLogoUrl = "https://pipr.run/images/pipr/pipr-mark.svg";
|
|
4806
5240
|
const piprRepositoryUrl = "https://github.com/somus/pipr";
|
|
4807
5241
|
const mainCommentTitle = `# <img src="${piprLogoUrl}" width="22" height="22" alt=""> Pipr Review`;
|
|
4808
|
-
const mainCommentTitles = new Set([
|
|
5242
|
+
const mainCommentTitles = /* @__PURE__ */ new Set([
|
|
4809
5243
|
"# pipr Review",
|
|
4810
5244
|
"# Pipr Review",
|
|
4811
5245
|
mainCommentTitle
|
|
4812
5246
|
]);
|
|
4813
5247
|
const escapedRepositoryUrl = piprRepositoryUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4814
|
-
const mainCommentAttributionPattern = new RegExp(`^<sub>Review generated by \\[Pipr\\]\\(${escapedRepositoryUrl}\\) for commit \`[^\`]
|
|
4815
|
-
//#endregion
|
|
4816
|
-
//#region src/review/inline-publication-policy.ts
|
|
4817
|
-
const maxSuggestedFixSelectedLines = 12;
|
|
4818
|
-
const maxSuggestedFixReplacementLines = 20;
|
|
4819
|
-
function inlinePublicationDecision(options) {
|
|
4820
|
-
if (options.existing.markers.has(options.marker) || hasExistingInlinePublicationLocation(options.existing.locations, options.location)) return "skip";
|
|
4821
|
-
return "post";
|
|
4822
|
-
}
|
|
4823
|
-
function isPublishableSuggestedFixSelection(selection) {
|
|
4824
|
-
const suggestedLines = normalizedSuggestedFixLines(selection.suggestedFix);
|
|
4825
|
-
const selectedLineCount = selection.endLine - selection.startLine + 1;
|
|
4826
|
-
if (selection.side !== "RIGHT" || selection.kind === "deleted" || selectedLineCount > maxSuggestedFixSelectedLines || suggestedLines.length > maxSuggestedFixReplacementLines) return false;
|
|
4827
|
-
const originalLines = selectedPreviewLines(selection, selectedLineCount);
|
|
4828
|
-
return Boolean(originalLines && !hasUnchangedSelectionEdge(originalLines, suggestedLines) && !suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines));
|
|
4829
|
-
}
|
|
4830
|
-
function hasExistingInlinePublicationLocation(existing, location) {
|
|
4831
|
-
return existing.some((comment) => {
|
|
4832
|
-
if (comment.path !== location.path || comment.commitId !== location.commitId || comment.side !== location.side) return false;
|
|
4833
|
-
return comment.startLine <= location.endLine && location.startLine <= comment.endLine;
|
|
4834
|
-
});
|
|
4835
|
-
}
|
|
4836
|
-
function normalizedSuggestedFixLines(value) {
|
|
4837
|
-
const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
4838
|
-
const withoutFinalNewline = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
|
|
4839
|
-
return withoutFinalNewline.length === 0 ? [] : withoutFinalNewline.split("\n");
|
|
4840
|
-
}
|
|
4841
|
-
function selectedPreviewLines(selection, selectedLineCount) {
|
|
4842
|
-
if (!selection.preview) return;
|
|
4843
|
-
const offset = selection.startLine - selection.rangeStartLine;
|
|
4844
|
-
if (offset < 0) return;
|
|
4845
|
-
const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
4846
|
-
if (offset + selectedLineCount > previewLines.length) return;
|
|
4847
|
-
return previewLines.slice(offset, offset + selectedLineCount);
|
|
4848
|
-
}
|
|
4849
|
-
function suggestionIncludesUnselectedContext(selection, selectedLineCount, suggestedLines) {
|
|
4850
|
-
if (!selection.preview || suggestedLines.length <= selectedLineCount) return false;
|
|
4851
|
-
const offset = selection.startLine - selection.rangeStartLine;
|
|
4852
|
-
if (offset < 0) return false;
|
|
4853
|
-
const previewLines = selection.preview.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
4854
|
-
return [offset > 0 ? previewLines[offset - 1] : void 0, previewLines[offset + selectedLineCount]].filter((line) => Boolean(line?.trim())).some((line) => suggestedLines.includes(line));
|
|
4855
|
-
}
|
|
4856
|
-
function hasUnchangedSelectionEdge(originalLines, suggestedLines) {
|
|
4857
|
-
const firstLineUnchanged = originalLines[0] === suggestedLines[0];
|
|
4858
|
-
const lastLineUnchanged = originalLines.at(-1) === suggestedLines.at(-1);
|
|
4859
|
-
if (originalLines.length === suggestedLines.length || originalLines.length === 1) return firstLineUnchanged || lastLineUnchanged;
|
|
4860
|
-
return firstLineUnchanged && lastLineUnchanged;
|
|
4861
|
-
}
|
|
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>$`);
|
|
4862
5249
|
//#endregion
|
|
4863
5250
|
//#region src/review/prior-state.ts
|
|
4864
5251
|
const mainCommentMarker = "pipr:main-comment";
|
|
@@ -5117,8 +5504,50 @@ function hashParts(parts) {
|
|
|
5117
5504
|
return new Bun.CryptoHasher("sha256").update(parts.join("\n")).digest("hex").slice(0, 16);
|
|
5118
5505
|
}
|
|
5119
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
|
|
5120
5550
|
//#region src/review/comment.ts
|
|
5121
|
-
const runtimeVersion = version;
|
|
5122
5551
|
const inlinePublicationItemSchema = z.strictObject({
|
|
5123
5552
|
finding: reviewFindingSchema,
|
|
5124
5553
|
range: commentableRangeSchema,
|
|
@@ -5165,6 +5594,7 @@ const threadActionSchema = z.strictObject({
|
|
|
5165
5594
|
const threadActionsSchema = z.array(threadActionSchema);
|
|
5166
5595
|
const publicationMetadataSchema = z.strictObject({
|
|
5167
5596
|
runtimeVersion: z.string().min(1),
|
|
5597
|
+
configVersion: z.string().min(1).optional(),
|
|
5168
5598
|
trustedConfigSha: z.string().min(1).optional(),
|
|
5169
5599
|
trustedConfigHash: z.string().min(1).optional(),
|
|
5170
5600
|
reviewedHeadSha: z.string().min(1),
|
|
@@ -5184,8 +5614,6 @@ const publicationPlanSchema = z.strictObject({
|
|
|
5184
5614
|
reviewState: priorReviewStateSchema,
|
|
5185
5615
|
threadActions: threadActionsSchema
|
|
5186
5616
|
});
|
|
5187
|
-
const maxInlineFindingBodyCharacters = 700;
|
|
5188
|
-
const maxInlineFindingBodyLines = 4;
|
|
5189
5617
|
function buildPublicationPlan(options) {
|
|
5190
5618
|
const reviewState = options.reviewState ?? buildPriorReviewState({
|
|
5191
5619
|
findings: options.inlineItems.map((item) => item.finding),
|
|
@@ -5202,7 +5630,7 @@ function buildPublicationPlan(options) {
|
|
|
5202
5630
|
event: options.event,
|
|
5203
5631
|
reviewState,
|
|
5204
5632
|
main: options.main,
|
|
5205
|
-
|
|
5633
|
+
metadata
|
|
5206
5634
|
}),
|
|
5207
5635
|
mainMarker: mainCommentMarker,
|
|
5208
5636
|
changeNumber: options.event.change.number,
|
|
@@ -5256,9 +5684,9 @@ function findingWithPublishableBody(finding) {
|
|
|
5256
5684
|
};
|
|
5257
5685
|
}
|
|
5258
5686
|
function conciseInlineFindingBody(value) {
|
|
5259
|
-
const body = redactPotentialSecrets((value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim().split(/\n{2,}/)[0] ?? value).split("\n").slice(0,
|
|
5260
|
-
if (body.length <=
|
|
5261
|
-
return `${body.slice(0,
|
|
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()}...`;
|
|
5262
5690
|
}
|
|
5263
5691
|
function findingWithPublishableSuggestedFix(finding, range) {
|
|
5264
5692
|
if (!finding.suggestedFix) return finding;
|
|
@@ -5290,10 +5718,19 @@ function renderMainComment(options) {
|
|
|
5290
5718
|
"",
|
|
5291
5719
|
redactPotentialSecrets(options.main),
|
|
5292
5720
|
"",
|
|
5293
|
-
|
|
5721
|
+
renderMainCommentAttribution(options.metadata),
|
|
5294
5722
|
""
|
|
5295
5723
|
].join("\n");
|
|
5296
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
|
+
}
|
|
5297
5734
|
function renderInlineBody(finding, findingId, reviewedHeadSha) {
|
|
5298
5735
|
const findingBody = startsWithStructuredMarkdown(finding.body) ? finding.body : [
|
|
5299
5736
|
"**Issue**",
|
|
@@ -5725,9 +6162,9 @@ function trackResultFindingScope(state, value, paths) {
|
|
|
5725
6162
|
const parsed = agentInlineFindingsOutputSchema.safeParse(value);
|
|
5726
6163
|
if (parsed.success) state.findingScopes.set(parsed.data.inlineFindings, paths);
|
|
5727
6164
|
}
|
|
5728
|
-
function collectedReview(output) {
|
|
6165
|
+
function collectedReview(output, summaryBody) {
|
|
5729
6166
|
return {
|
|
5730
|
-
summary: { body:
|
|
6167
|
+
summary: { body: summaryBody },
|
|
5731
6168
|
inlineFindings: output.findings.map((item) => item.finding)
|
|
5732
6169
|
};
|
|
5733
6170
|
}
|
|
@@ -5768,7 +6205,8 @@ async function runTaskRuntime(options) {
|
|
|
5768
6205
|
reason: options.emptyTasksReason,
|
|
5769
6206
|
taskName: options.taskName,
|
|
5770
6207
|
trustedConfigSha: options.trustedConfigSha,
|
|
5771
|
-
trustedConfigHash: options.trustedConfigHash
|
|
6208
|
+
trustedConfigHash: options.trustedConfigHash,
|
|
6209
|
+
versionCompatibility: options.versionCompatibility
|
|
5772
6210
|
});
|
|
5773
6211
|
}
|
|
5774
6212
|
const selectedTasks = tasks.map((task) => task.name);
|
|
@@ -5863,7 +6301,8 @@ async function runTaskRuntime(options) {
|
|
|
5863
6301
|
});
|
|
5864
6302
|
if (commandResponse) return commandResponse;
|
|
5865
6303
|
assertReviewCommentOutput(output, options.commandInvocation !== void 0);
|
|
5866
|
-
const
|
|
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, {
|
|
5867
6306
|
expectedHeadSha: options.event.change.head.sha,
|
|
5868
6307
|
pathScopeForFinding: (_finding, index) => output.findings[index]?.paths
|
|
5869
6308
|
});
|
|
@@ -5877,7 +6316,7 @@ async function runTaskRuntime(options) {
|
|
|
5877
6316
|
});
|
|
5878
6317
|
const publishing = buildCommentPublishingPlan({
|
|
5879
6318
|
event: options.event,
|
|
5880
|
-
main
|
|
6319
|
+
main,
|
|
5881
6320
|
validated,
|
|
5882
6321
|
manifest: diffManifest,
|
|
5883
6322
|
maxInlineComments: config.publication.maxInlineComments,
|
|
@@ -5885,6 +6324,7 @@ async function runTaskRuntime(options) {
|
|
|
5885
6324
|
threadActions: verifier.threadActions,
|
|
5886
6325
|
metadata: {
|
|
5887
6326
|
runtimeVersion,
|
|
6327
|
+
configVersion: options.versionCompatibility?.configVersion,
|
|
5888
6328
|
trustedConfigSha: options.trustedConfigSha,
|
|
5889
6329
|
trustedConfigHash: options.trustedConfigHash,
|
|
5890
6330
|
reviewedHeadSha: options.event.change.head.sha,
|
|
@@ -6075,6 +6515,7 @@ function skippedTaskRuntimeResult(options) {
|
|
|
6075
6515
|
maxInlineComments: options.config.publication.maxInlineComments,
|
|
6076
6516
|
metadata: {
|
|
6077
6517
|
runtimeVersion,
|
|
6518
|
+
configVersion: options.versionCompatibility?.configVersion,
|
|
6078
6519
|
trustedConfigSha: options.trustedConfigSha,
|
|
6079
6520
|
trustedConfigHash: options.trustedConfigHash,
|
|
6080
6521
|
reviewedHeadSha: options.event.change.head.sha,
|
|
@@ -6143,7 +6584,7 @@ const githubRepositoryPermissionResponseSchema = z.looseObject({
|
|
|
6143
6584
|
permission: z.string().min(1),
|
|
6144
6585
|
role_name: z.string().min(1).optional()
|
|
6145
6586
|
});
|
|
6146
|
-
const permissionLevels = new Set([
|
|
6587
|
+
const permissionLevels = /* @__PURE__ */ new Set([
|
|
6147
6588
|
"read",
|
|
6148
6589
|
"triage",
|
|
6149
6590
|
"write",
|
|
@@ -6802,6 +7243,18 @@ async function loadGitHubPriorMainComment(options) {
|
|
|
6802
7243
|
}), "pipr:main-comment", options.change.change.number, ownerLogin)?.body ?? void 0;
|
|
6803
7244
|
}
|
|
6804
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
|
|
6805
7258
|
//#region src/hosts/github/inline.ts
|
|
6806
7259
|
const githubReviewCommentLocationSchema = z.strictObject({
|
|
6807
7260
|
path: z.string().min(1),
|
|
@@ -7389,6 +7842,10 @@ function logTrustedRuntime(log, runtime) {
|
|
|
7389
7842
|
tasks: runtime.plan.tasks.length,
|
|
7390
7843
|
commands: runtime.plan.commands.length
|
|
7391
7844
|
});
|
|
7845
|
+
logConfigWarnings(log, runtime.settings.warnings);
|
|
7846
|
+
}
|
|
7847
|
+
function logConfigWarnings(log, warnings) {
|
|
7848
|
+
for (const warning of warnings) log.warning("config warning", { warning });
|
|
7392
7849
|
}
|
|
7393
7850
|
function addProviderSecrets(log, config, env) {
|
|
7394
7851
|
for (const provider of config.providers) log.addSecret((env ?? process.env)[provider.apiKeyEnv]);
|
|
@@ -7584,6 +8041,7 @@ async function runTrustedReviewAndPublish(options) {
|
|
|
7584
8041
|
event: options.event,
|
|
7585
8042
|
env: options.options.env,
|
|
7586
8043
|
plan: options.trustedRuntime.plan,
|
|
8044
|
+
versionCompatibility: options.trustedRuntime.versionCompatibility,
|
|
7587
8045
|
taskName: options.taskName,
|
|
7588
8046
|
taskInput: options.taskInput,
|
|
7589
8047
|
commandInvocation: options.commandInvocation,
|
|
@@ -7655,7 +8113,8 @@ async function runTrustedReviewAndPublish(options) {
|
|
|
7655
8113
|
async function loadRuntimeProjectFromGitCommit(options) {
|
|
7656
8114
|
const configDir = resolveContainedConfigDir(options);
|
|
7657
8115
|
const files = listConfigFilesAtCommit(options.rootDir, options.commitSha, configDir.gitPath);
|
|
7658
|
-
|
|
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.`);
|
|
7659
8118
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "pipr-base-config-"));
|
|
7660
8119
|
try {
|
|
7661
8120
|
const hash = new Bun.CryptoHasher("sha256");
|
|
@@ -8193,7 +8652,10 @@ async function runInspectCommand(options) {
|
|
|
8193
8652
|
...options,
|
|
8194
8653
|
requireProviderEnv: false
|
|
8195
8654
|
});
|
|
8196
|
-
return
|
|
8655
|
+
return {
|
|
8656
|
+
...inspectRuntimePlan(runtime.plan, runtime.settings.source),
|
|
8657
|
+
warnings: runtime.settings.warnings
|
|
8658
|
+
};
|
|
8197
8659
|
}
|
|
8198
8660
|
/** Loads the runtime config and pull request event without running review publication. */
|
|
8199
8661
|
async function runDryRunCommand(options) {
|
|
@@ -8212,7 +8674,8 @@ async function runDryRunCommand(options) {
|
|
|
8212
8674
|
});
|
|
8213
8675
|
return {
|
|
8214
8676
|
configSource: runtime.settings.source,
|
|
8215
|
-
event
|
|
8677
|
+
event,
|
|
8678
|
+
warnings: runtime.settings.warnings
|
|
8216
8679
|
};
|
|
8217
8680
|
}
|
|
8218
8681
|
/** Runs configured change-request tasks against local Git base and head revisions. */
|
|
@@ -8237,6 +8700,7 @@ async function runLocalReviewCommand(options) {
|
|
|
8237
8700
|
tasks: runtime.plan.tasks.length,
|
|
8238
8701
|
commands: runtime.plan.commands.length
|
|
8239
8702
|
});
|
|
8703
|
+
if (log) logConfigWarnings(log, runtime.settings.warnings);
|
|
8240
8704
|
const selectedTasks = selectLocalReviewTasks(runtime.plan);
|
|
8241
8705
|
const includeWorkingTree = options.headSha === void 0;
|
|
8242
8706
|
const headSha = options.headSha ?? runGit$1(["rev-parse", "HEAD"], options.rootDir).trim();
|
|
@@ -8259,6 +8723,7 @@ async function runLocalReviewCommand(options) {
|
|
|
8259
8723
|
event,
|
|
8260
8724
|
env: options.env,
|
|
8261
8725
|
plan: runtime.plan,
|
|
8726
|
+
versionCompatibility: runtime.versionCompatibility,
|
|
8262
8727
|
selectedTasks,
|
|
8263
8728
|
emptyTasksReason: "No change-request tasks are configured for local review",
|
|
8264
8729
|
piExecutable: options.piExecutable,
|
|
@@ -8309,6 +8774,6 @@ async function runActionCommandWithDependencies(options) {
|
|
|
8309
8774
|
});
|
|
8310
8775
|
}
|
|
8311
8776
|
//#endregion
|
|
8312
|
-
export { runInspectCommand as a, PublicationError as c,
|
|
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 };
|
|
8313
8778
|
|
|
8314
|
-
//# sourceMappingURL=commands-
|
|
8779
|
+
//# sourceMappingURL=commands-DCpIxJkz.mjs.map
|