pruneguard 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,13 @@
1
1
  //#region src-js/runtime.d.ts
2
+ type ResolutionSource = "env" | "platform-package" | "dev" | "path";
3
+ type ResolutionInfo = {
4
+ binaryPath: string;
5
+ source: ResolutionSource;
6
+ platformPackage?: string;
7
+ schemaPath?: string;
8
+ version?: string;
9
+ platform?: string;
10
+ };
2
11
  type CommandResult = {
3
12
  args: string[];
4
13
  cwd?: string;
@@ -14,12 +23,14 @@ declare class PruneguardExecutionError extends Error {
14
23
  stderr?: string;
15
24
  binaryPath?: string;
16
25
  args?: string[];
26
+ resolutionSource?: ResolutionSource;
17
27
  constructor(code: PruneguardExecutionError["code"], message: string, details?: {
18
28
  exitCode?: number;
19
29
  stdout?: string;
20
30
  stderr?: string;
21
31
  binaryPath?: string;
22
32
  args?: string[];
33
+ resolutionSource?: ResolutionSource;
23
34
  });
24
35
  }
25
36
  //#endregion
@@ -113,6 +124,8 @@ type AnalysisReport = {
113
124
  }>;
114
125
  suggestion?: string;
115
126
  ruleName?: string;
127
+ primaryActionKind?: string;
128
+ actionKinds?: string[];
116
129
  }>;
117
130
  entrypoints: Array<{
118
131
  path: string;
@@ -164,6 +177,12 @@ type AnalysisReport = {
164
177
  cacheEntriesRead: number;
165
178
  cacheEntriesWritten: number;
166
179
  affectedScopeIncomplete: boolean;
180
+ executionMode?: "oneshot" | "daemon";
181
+ indexWarm?: boolean;
182
+ indexAgeMs?: number;
183
+ reusedGraphNodes?: number;
184
+ reusedGraphEdges?: number;
185
+ watcherLagMs?: number;
167
186
  };
168
187
  };
169
188
  type MigrationOutput = {
@@ -197,8 +216,156 @@ type ExplainReport = {
197
216
  focusFiltered: boolean;
198
217
  };
199
218
  type PruneguardConfig = Record<string, unknown>;
219
+ type ReviewOptions = {
220
+ cwd?: string;
221
+ config?: string;
222
+ profile?: Profile;
223
+ baseRef?: string;
224
+ noCache?: boolean;
225
+ noBaseline?: boolean;
226
+ };
227
+ type ReviewReport = {
228
+ baseRef?: string;
229
+ changedFiles: string[];
230
+ newFindings: AnalysisReport["findings"];
231
+ blockingFindings: AnalysisReport["findings"];
232
+ advisoryFindings: AnalysisReport["findings"];
233
+ trust: {
234
+ fullScope: boolean;
235
+ baselineApplied: boolean;
236
+ unresolvedPressure: number;
237
+ confidenceCounts: {
238
+ high: number;
239
+ medium: number;
240
+ low: number;
241
+ };
242
+ };
243
+ recommendations: string[];
244
+ proposedActions?: Array<{
245
+ id: string;
246
+ kind: string;
247
+ targets: string[];
248
+ why: string;
249
+ preconditions: string[];
250
+ steps: Array<{
251
+ description: string;
252
+ file?: string;
253
+ action?: string;
254
+ }>;
255
+ verification: string[];
256
+ risk: "low" | "medium" | "high";
257
+ confidence: "high" | "medium" | "low";
258
+ }>;
259
+ executionMode?: "oneshot" | "daemon";
260
+ latencyMs?: number;
261
+ };
262
+ type SafeDeleteOptions = {
263
+ cwd?: string;
264
+ config?: string;
265
+ profile?: Profile;
266
+ targets: string[];
267
+ noCache?: boolean;
268
+ };
269
+ type SafeDeleteReport = {
270
+ targets: string[];
271
+ safe: Array<{
272
+ target: string;
273
+ confidence?: "high" | "medium" | "low";
274
+ reasons: string[];
275
+ }>;
276
+ needsReview: Array<{
277
+ target: string;
278
+ confidence?: "high" | "medium" | "low";
279
+ reasons: string[];
280
+ }>;
281
+ blocked: Array<{
282
+ target: string;
283
+ confidence?: "high" | "medium" | "low";
284
+ reasons: string[];
285
+ }>;
286
+ deletionOrder: string[];
287
+ evidence: Array<{
288
+ kind: string;
289
+ file?: string;
290
+ line?: number;
291
+ description: string;
292
+ }>;
293
+ };
294
+ type FixPlanOptions = {
295
+ cwd?: string;
296
+ config?: string;
297
+ profile?: Profile;
298
+ targets: string[];
299
+ noCache?: boolean;
300
+ };
301
+ type SuggestRulesOptions = {
302
+ cwd?: string;
303
+ config?: string;
304
+ profile?: Profile;
305
+ noCache?: boolean;
306
+ };
307
+ type SuggestRulesReport = {
308
+ suggestedRules: Array<{
309
+ kind: string;
310
+ name: string;
311
+ description: string;
312
+ configFragment: Record<string, unknown>;
313
+ confidence: "high" | "medium" | "low";
314
+ evidence?: string[];
315
+ }>;
316
+ tags?: Array<{
317
+ name: string;
318
+ glob: string;
319
+ rationale: string;
320
+ }>;
321
+ ownershipHints?: Array<{
322
+ pathGlob: string;
323
+ suggestedOwner: string;
324
+ crossTeamEdges: number;
325
+ rationale: string;
326
+ }>;
327
+ hotspots?: Array<{
328
+ file: string;
329
+ crossPackageImports: number;
330
+ crossOwnerImports: number;
331
+ incomingEdges: number;
332
+ outgoingEdges: number;
333
+ suggestion: string;
334
+ }>;
335
+ rationale?: string[];
336
+ };
337
+ type FixPlanReport = {
338
+ query: string[];
339
+ matchedFindings: AnalysisReport["findings"];
340
+ actions: Array<{
341
+ id: string;
342
+ kind: string;
343
+ targets: string[];
344
+ why: string;
345
+ preconditions: string[];
346
+ steps: Array<{
347
+ description: string;
348
+ file?: string;
349
+ action?: string;
350
+ }>;
351
+ verification: string[];
352
+ risk: "low" | "medium" | "high";
353
+ confidence: "high" | "medium" | "low";
354
+ }>;
355
+ blockedBy: string[];
356
+ verificationSteps: string[];
357
+ riskLevel: "low" | "medium" | "high";
358
+ confidence: "high" | "medium" | "low";
359
+ };
360
+ type DaemonStatusReport = {
361
+ running: boolean;
362
+ pid?: number;
363
+ port?: number;
364
+ version?: string;
365
+ startedAt?: string;
366
+ projectRoot?: string;
367
+ };
200
368
  declare function scan(options?: ScanOptions): Promise<AnalysisReport>;
201
- /** @experimental */
202
369
  declare function scanDot(options?: ScanOptions): Promise<string>;
203
370
  declare function impact(options: ImpactOptions): Promise<ImpactReport>;
204
371
  declare function explain(options: ExplainOptions): Promise<ExplainReport>;
@@ -208,21 +375,27 @@ declare function loadConfig(options?: {
208
375
  }): Promise<PruneguardConfig>;
209
376
  declare function schemaPath(): string;
210
377
  declare function binaryPath(): string;
378
+ declare function resolutionInfo(): ResolutionInfo;
211
379
  declare function run(args: string[], options?: {
212
380
  cwd?: string;
213
381
  }): Promise<CommandResult>;
214
382
  declare function debugResolve(options: DebugResolveOptions): Promise<string>;
215
383
  declare function debugEntrypoints(options?: DebugEntrypointsOptions): Promise<string[]>;
216
- /** @experimental */
384
+ declare function review(options?: ReviewOptions): Promise<ReviewReport>;
385
+ declare function safeDelete(options: SafeDeleteOptions): Promise<SafeDeleteReport>;
386
+ declare function fixPlan(options: FixPlanOptions): Promise<FixPlanReport>;
387
+ declare function suggestRules(options?: SuggestRulesOptions): Promise<SuggestRulesReport>;
217
388
  declare function migrateKnip(options?: {
218
389
  cwd?: string;
219
390
  file?: string;
220
391
  }): Promise<MigrationOutput>;
221
- /** @experimental */
222
392
  declare function migrateDepcruise(options?: {
223
393
  cwd?: string;
224
394
  file?: string;
225
395
  node?: boolean;
226
396
  }): Promise<MigrationOutput>;
397
+ declare function daemonStatus(options?: {
398
+ cwd?: string;
399
+ }): Promise<DaemonStatusReport>;
227
400
  //#endregion
228
- export { AnalysisReport, type CommandResult, DebugEntrypointsOptions, DebugResolveOptions, ExplainOptions, ExplainReport, ImpactOptions, ImpactReport, MigrationOutput, Profile, PruneguardConfig, PruneguardExecutionError, ScanOptions, binaryPath, debugEntrypoints, debugResolve, explain, impact, loadConfig, migrateDepcruise, migrateKnip, run, scan, scanDot, schemaPath };
401
+ export { AnalysisReport, type CommandResult, DaemonStatusReport, DebugEntrypointsOptions, DebugResolveOptions, ExplainOptions, ExplainReport, FixPlanOptions, FixPlanReport, ImpactOptions, ImpactReport, MigrationOutput, Profile, PruneguardConfig, PruneguardExecutionError, type ResolutionInfo, type ResolutionSource, ReviewOptions, ReviewReport, SafeDeleteOptions, SafeDeleteReport, ScanOptions, SuggestRulesOptions, SuggestRulesReport, binaryPath, daemonStatus, debugEntrypoints, debugResolve, explain, fixPlan, impact, loadConfig, migrateDepcruise, migrateKnip, resolutionInfo, review, run, safeDelete, scan, scanDot, schemaPath, suggestRules };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as binaryPath$1, r as run$1, t as PruneguardExecutionError } from "./runtime-BxlGT_W-.mjs";
1
+ import { i as run$1, n as binaryPath$1, r as resolutionInfo$1, t as PruneguardExecutionError } from "./runtime-BSHQsTbN.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  //#region src-js/index.ts
4
4
  function parseJson(result) {
@@ -44,7 +44,6 @@ async function scan(options = {}) {
44
44
  if (result.exitCode !== 0 && result.exitCode !== 1) requireSuccess(result);
45
45
  return parseJson(result);
46
46
  }
47
- /** @experimental */
48
47
  async function scanDot(options = {}) {
49
48
  const args = [
50
49
  "--format",
@@ -93,6 +92,9 @@ function schemaPath() {
93
92
  function binaryPath() {
94
93
  return binaryPath$1();
95
94
  }
95
+ function resolutionInfo() {
96
+ return resolutionInfo$1();
97
+ }
96
98
  function run(args, options) {
97
99
  return run$1(args, options);
98
100
  }
@@ -113,7 +115,49 @@ async function debugEntrypoints(options = {}) {
113
115
  requireSuccess(result);
114
116
  return result.stdout.trimEnd().split("\n").filter(Boolean);
115
117
  }
116
- /** @experimental */
118
+ async function review(options = {}) {
119
+ const args = [
120
+ "--format",
121
+ "json",
122
+ "--severity",
123
+ "info"
124
+ ];
125
+ pushGlobalFlags(args, options);
126
+ if (options.baseRef) args.push("--changed-since", options.baseRef);
127
+ if (options.noCache) args.push("--no-cache");
128
+ if (options.noBaseline) args.push("--no-baseline");
129
+ args.push("review");
130
+ const result = await run$1(args, { cwd: options.cwd });
131
+ if (result.exitCode !== 0 && result.exitCode !== 1) requireSuccess(result);
132
+ return parseJson(result);
133
+ }
134
+ async function safeDelete(options) {
135
+ const args = ["--format", "json"];
136
+ pushGlobalFlags(args, options);
137
+ if (options.noCache) args.push("--no-cache");
138
+ args.push("safe-delete", ...options.targets);
139
+ const result = await run$1(args, { cwd: options.cwd });
140
+ if (result.exitCode !== 0 && result.exitCode !== 1) requireSuccess(result);
141
+ return parseJson(result);
142
+ }
143
+ async function fixPlan(options) {
144
+ const args = ["--format", "json"];
145
+ pushGlobalFlags(args, options);
146
+ if (options.noCache) args.push("--no-cache");
147
+ args.push("fix-plan", ...options.targets);
148
+ const result = await run$1(args, { cwd: options.cwd });
149
+ requireSuccess(result);
150
+ return parseJson(result);
151
+ }
152
+ async function suggestRules(options = {}) {
153
+ const args = ["--format", "json"];
154
+ pushGlobalFlags(args, options);
155
+ if (options.noCache) args.push("--no-cache");
156
+ args.push("suggest-rules");
157
+ const result = await run$1(args, { cwd: options.cwd });
158
+ requireSuccess(result);
159
+ return parseJson(result);
160
+ }
117
161
  async function migrateKnip(options = {}) {
118
162
  const args = [
119
163
  "--format",
@@ -126,7 +170,6 @@ async function migrateKnip(options = {}) {
126
170
  requireSuccess(result);
127
171
  return parseJson(result);
128
172
  }
129
- /** @experimental */
130
173
  async function migrateDepcruise(options = {}) {
131
174
  const args = [
132
175
  "--format",
@@ -140,5 +183,21 @@ async function migrateDepcruise(options = {}) {
140
183
  requireSuccess(result);
141
184
  return parseJson(result);
142
185
  }
186
+ async function daemonStatus(options) {
187
+ const result = await run$1(["daemon", "status"], { cwd: options?.cwd });
188
+ if (result.exitCode === 1 && result.stdout.includes("no running daemon")) return { running: false };
189
+ if (result.exitCode !== 0) return { running: false };
190
+ const lines = result.stdout.trim().split("\n");
191
+ const report = { running: true };
192
+ for (const line of lines) {
193
+ const [key, ...rest] = line.split(": ");
194
+ const value = rest.join(": ").trim();
195
+ if (key === "pid") report.pid = parseInt(value, 10);
196
+ else if (key === "port") report.port = parseInt(value, 10);
197
+ else if (key === "version") report.version = value;
198
+ else if (key === "started_at") report.startedAt = value;
199
+ }
200
+ return report;
201
+ }
143
202
  //#endregion
144
- export { PruneguardExecutionError, binaryPath, debugEntrypoints, debugResolve, explain, impact, loadConfig, migrateDepcruise, migrateKnip, run, scan, scanDot, schemaPath };
203
+ export { PruneguardExecutionError, binaryPath, daemonStatus, debugEntrypoints, debugResolve, explain, fixPlan, impact, loadConfig, migrateDepcruise, migrateKnip, resolutionInfo, review, run, safeDelete, scan, scanDot, schemaPath, suggestRules };
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { execFileSync, spawn } from "node:child_process";
4
- import { existsSync } from "node:fs";
4
+ import { accessSync, constants, existsSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
6
  //#region src-js/runtime.ts
7
7
  const require = createRequire(import.meta.url);
@@ -13,6 +13,7 @@ var PruneguardExecutionError = class extends Error {
13
13
  stderr;
14
14
  binaryPath;
15
15
  args;
16
+ resolutionSource;
16
17
  constructor(code, message, details) {
17
18
  super(message);
18
19
  this.name = "PruneguardExecutionError";
@@ -22,6 +23,7 @@ var PruneguardExecutionError = class extends Error {
22
23
  this.stderr = details?.stderr;
23
24
  this.binaryPath = details?.binaryPath;
24
25
  this.args = details?.args;
26
+ this.resolutionSource = details?.resolutionSource;
25
27
  }
26
28
  };
27
29
  const PLATFORM_PACKAGES = {
@@ -40,7 +42,10 @@ function findPlatformBinary() {
40
42
  if (!candidates) return void 0;
41
43
  for (const pkg of candidates) try {
42
44
  const binPath = join(dirname(require.resolve(`${pkg}/package.json`)), "bin", exeName());
43
- if (existsSync(binPath)) return binPath;
45
+ if (existsSync(binPath)) return {
46
+ path: binPath,
47
+ packageName: pkg
48
+ };
44
49
  } catch {
45
50
  continue;
46
51
  }
@@ -63,38 +68,94 @@ function findPathBinary() {
63
68
  } catch {}
64
69
  }
65
70
  let cachedBinaryPath;
71
+ let cachedResolutionSource;
72
+ let cachedPlatformPackage;
73
+ function validateExecutable(binPath, source) {
74
+ if (!existsSync(binPath)) throw new PruneguardExecutionError("PRUNEGUARD_BINARY_NOT_FOUND", `[${source}] Binary does not exist: ${binPath}. Run "pruneguard debug runtime" for diagnostics.`, {
75
+ binaryPath: binPath,
76
+ resolutionSource: source
77
+ });
78
+ if (process.platform !== "win32") try {
79
+ accessSync(binPath, constants.X_OK);
80
+ } catch {
81
+ throw new PruneguardExecutionError("PRUNEGUARD_BINARY_NOT_FOUND", `[${source}] Binary exists but is not executable: ${binPath}. Try: chmod +x "${binPath}"`, {
82
+ binaryPath: binPath,
83
+ resolutionSource: source
84
+ });
85
+ }
86
+ }
66
87
  function binaryPath(options) {
67
88
  if (cachedBinaryPath) return cachedBinaryPath;
68
89
  const envPath = process.env.PRUNEGUARD_BINARY;
69
90
  if (envPath) {
70
- if (!existsSync(envPath)) throw new PruneguardExecutionError("PRUNEGUARD_BINARY_NOT_FOUND", `PRUNEGUARD_BINARY points to ${envPath} but the file does not exist`, { binaryPath: envPath });
91
+ validateExecutable(envPath, "env");
71
92
  cachedBinaryPath = envPath;
93
+ cachedResolutionSource = "env";
72
94
  return envPath;
73
95
  }
74
96
  const platformBin = findPlatformBinary();
75
97
  if (platformBin) {
76
- cachedBinaryPath = platformBin;
77
- return platformBin;
98
+ validateExecutable(platformBin.path, "platform-package");
99
+ cachedBinaryPath = platformBin.path;
100
+ cachedResolutionSource = "platform-package";
101
+ cachedPlatformPackage = platformBin.packageName;
102
+ return platformBin.path;
78
103
  }
79
104
  const devBin = findDevBinary();
80
105
  if (devBin) {
106
+ validateExecutable(devBin, "dev");
81
107
  cachedBinaryPath = devBin;
108
+ cachedResolutionSource = "dev";
82
109
  return devBin;
83
110
  }
84
111
  if (options?.allowPathFallback) {
85
112
  const pathBin = findPathBinary();
86
113
  if (pathBin) {
114
+ validateExecutable(pathBin, "path");
87
115
  cachedBinaryPath = pathBin;
116
+ cachedResolutionSource = "path";
88
117
  return pathBin;
89
118
  }
90
119
  }
91
- throw new PruneguardExecutionError("PRUNEGUARD_BINARY_NOT_FOUND", "Could not find the pruneguard binary. Install a platform-specific package or set PRUNEGUARD_BINARY.");
120
+ const expectedPkgs = PLATFORM_PACKAGES[`${process.platform}-${process.arch === "arm64" ? "arm64" : "x64"}`];
121
+ const tried = [
122
+ "env(PRUNEGUARD_BINARY)",
123
+ "platform-package",
124
+ "dev(cargo build)"
125
+ ];
126
+ if (options?.allowPathFallback) tried.push("PATH");
127
+ const pkgHint = expectedPkgs?.length ? `\n Expected platform package: ${expectedPkgs.join(" or ")}` : "";
128
+ throw new PruneguardExecutionError("PRUNEGUARD_BINARY_NOT_FOUND", `Could not find the pruneguard binary for ${`${process.platform}-${process.arch}`}.\n Tried: ${tried.join(", ")}${pkgHint}\n Fix: npm install pruneguard (or set PRUNEGUARD_BINARY)\n Debug: npx pruneguard debug runtime`);
129
+ }
130
+ function resolutionInfo() {
131
+ return {
132
+ binaryPath: binaryPath(),
133
+ source: cachedResolutionSource,
134
+ ...cachedPlatformPackage ? { platformPackage: cachedPlatformPackage } : {},
135
+ schemaPath: join(dirname(fileURLToPath(import.meta.url)), "..", "configuration_schema.json"),
136
+ platform: `${process.platform}-${process.arch}`
137
+ };
138
+ }
139
+ /**
140
+ * Returns `true` if the current process is running in a CI environment.
141
+ *
142
+ * Checks the common `CI` env var used by GitHub Actions, GitLab CI,
143
+ * CircleCI, Travis CI, Jenkins, and most other CI providers.
144
+ */
145
+ function isCI() {
146
+ const ci = process.env.CI;
147
+ return ci !== void 0 && ci !== "" && ci !== "0" && ci.toLowerCase() !== "false";
92
148
  }
93
149
  function run(args, options) {
94
150
  const binary = binaryPath();
95
151
  const start = performance.now();
152
+ const finalArgs = args.some((arg) => arg === "--daemon" || arg.startsWith("--daemon=")) ? args : [
153
+ "--daemon",
154
+ options?.daemon ?? (isCI() ? "off" : "auto"),
155
+ ...args
156
+ ];
96
157
  return new Promise((resolve, reject) => {
97
- const child = spawn(binary, args, {
158
+ const child = spawn(binary, finalArgs, {
98
159
  cwd: options?.cwd,
99
160
  stdio: [
100
161
  "ignore",
@@ -113,7 +174,8 @@ function run(args, options) {
113
174
  child.on("error", (err) => {
114
175
  reject(new PruneguardExecutionError("PRUNEGUARD_EXECUTION_FAILED", `Failed to spawn pruneguard: ${err.message}`, {
115
176
  binaryPath: binary,
116
- args
177
+ args,
178
+ resolutionSource: cachedResolutionSource
117
179
  }));
118
180
  });
119
181
  child.on("close", (exitCode) => {
@@ -129,4 +191,4 @@ function run(args, options) {
129
191
  });
130
192
  }
131
193
  //#endregion
132
- export { binaryPath as n, run as r, PruneguardExecutionError as t };
194
+ export { run as i, binaryPath as n, resolutionInfo as r, PruneguardExecutionError as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruneguard",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Repo truth engine for JS/TS monorepos",
5
5
  "keywords": [
6
6
  "javascript",
@@ -27,6 +27,8 @@
27
27
  "files": [
28
28
  "configuration_schema.json",
29
29
  "report_schema.json",
30
+ "review_report_schema.json",
31
+ "safe_delete_report_schema.json",
30
32
  "dist",
31
33
  "README.md",
32
34
  "bin/pruneguard"
@@ -41,14 +43,14 @@
41
43
  }
42
44
  },
43
45
  "optionalDependencies": {
44
- "@pruneguard/cli-darwin-arm64": "0.2.1",
45
- "@pruneguard/cli-darwin-x64": "0.2.1",
46
- "@pruneguard/cli-linux-arm64-gnu": "0.2.1",
47
- "@pruneguard/cli-linux-arm64-musl": "0.2.1",
48
- "@pruneguard/cli-linux-x64-gnu": "0.2.1",
49
- "@pruneguard/cli-linux-x64-musl": "0.2.1",
50
- "@pruneguard/cli-win32-arm64-msvc": "0.2.1",
51
- "@pruneguard/cli-win32-x64-msvc": "0.2.1"
46
+ "@pruneguard/cli-darwin-arm64": "0.3.1",
47
+ "@pruneguard/cli-darwin-x64": "0.3.1",
48
+ "@pruneguard/cli-linux-arm64-gnu": "0.3.1",
49
+ "@pruneguard/cli-linux-arm64-musl": "0.3.1",
50
+ "@pruneguard/cli-linux-x64-gnu": "0.3.1",
51
+ "@pruneguard/cli-linux-x64-musl": "0.3.1",
52
+ "@pruneguard/cli-win32-arm64-msvc": "0.3.1",
53
+ "@pruneguard/cli-win32-x64-msvc": "0.3.1"
52
54
  },
53
55
  "engines": {
54
56
  "node": ">=18.0.0"