fix(minimizer): address PR 2176 review feedback
This commit is contained in:
committed by
can1357
parent
ce7ec45d42
commit
3a73107cce
@@ -344,85 +344,6 @@ pub fn apply_bash_fixups(command: String) -> BashFixupResult {
|
||||
core_apply_bash_fixups(&command).into()
|
||||
}
|
||||
|
||||
/// Inputs for [`apply_shell_minimizer`]: a captured command's text plus the
|
||||
/// minimizer configuration to run against it.
|
||||
#[napi(object)]
|
||||
pub struct ShellMinimizerApplyOptions {
|
||||
/// The command line that produced `captured` (used to select a filter).
|
||||
pub command: String,
|
||||
/// The full captured stdout/stderr to minimize.
|
||||
pub captured: String,
|
||||
/// The command's exit status; omitted is treated as success (`0`).
|
||||
pub exit_code: Option<i32>,
|
||||
/// Minimizer configuration; when omitted the call is a no-op (`null`).
|
||||
pub minimizer: Option<MinimizerOptions>,
|
||||
}
|
||||
|
||||
/// Run the shell-output minimizer over an already-captured command result,
|
||||
/// without spawning a shell.
|
||||
///
|
||||
/// This is the one-shot counterpart to the minimization that
|
||||
/// [`execute_shell`] performs inline: callers that captured a command's output
|
||||
/// elsewhere can pass it here to obtain the same telemetry.
|
||||
///
|
||||
/// Returns [`MinimizerResult`] **only** when the minimizer actually rewrote the
|
||||
/// output (`changed == true`) and retained the original buffer, mirroring the
|
||||
/// persistent-shell path. Returns `null` for every no-op case: when
|
||||
/// `minimizer` is omitted, when the config is disabled, or when the filter
|
||||
/// passes the output through unchanged. A missing `exit_code` is treated as
|
||||
/// success (`0`).
|
||||
///
|
||||
/// Async (returns a Promise): minimization can scan multi-megabyte captured
|
||||
/// output, so the work runs on a blocking pool to avoid stalling the JS event
|
||||
/// loop.
|
||||
#[napi(ts_return_type = "Promise<MinimizerResult | null>")]
|
||||
pub fn apply_shell_minimizer(
|
||||
env: &Env,
|
||||
options: ShellMinimizerApplyOptions,
|
||||
) -> Result<PromiseRaw<'_, Option<MinimizerResult>>> {
|
||||
// Returns a Promise rather than a sync value: minimization can run over a
|
||||
// multi-megabyte capture buffer, and a sync `#[napi]` fn would do that CPU
|
||||
// work on the JS main thread and stall the event loop. Run the whole pass on
|
||||
// a blocking pool, mirroring `execute_shell`.
|
||||
task::future(env, "shell.minimize", async move {
|
||||
napi::tokio::task::spawn_blocking(move || run_shell_minimizer(options))
|
||||
.await
|
||||
.map_err(|err| Error::from_reason(err.to_string()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Pure, blocking core of [`apply_shell_minimizer`], factored out so it can run
|
||||
/// inside `spawn_blocking` and be unit-tested without an N-API `Env`.
|
||||
///
|
||||
/// Mirrors the persistent-shell path (`pi_shell::shell`): surface telemetry
|
||||
/// only when the minimizer actually rewrote the output and kept the original
|
||||
/// buffer. The disabled / passthrough cases report `changed: false` with no
|
||||
/// `original_text`, and yield `None`.
|
||||
fn run_shell_minimizer(options: ShellMinimizerApplyOptions) -> Option<MinimizerResult> {
|
||||
let minimizer = options.minimizer?;
|
||||
let minimizer_options: minimizer::MinimizerOptions = minimizer.into();
|
||||
let config = minimizer::MinimizerConfig::from_options(&minimizer_options);
|
||||
let output = minimizer::apply(
|
||||
&options.command,
|
||||
&options.captured,
|
||||
options.exit_code.unwrap_or(0),
|
||||
&config,
|
||||
);
|
||||
if output.changed
|
||||
&& let Some(original_text) = output.original_text
|
||||
{
|
||||
let output_bytes = u32::try_from(output.text.len()).unwrap_or(u32::MAX);
|
||||
return Some(MinimizerResult {
|
||||
filter: output.filter.to_string(),
|
||||
text: output.text,
|
||||
original_text,
|
||||
input_bytes: u32::try_from(output.input_bytes).unwrap_or(u32::MAX),
|
||||
output_bytes,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
@@ -435,48 +356,6 @@ mod tests {
|
||||
|
||||
use super::CoreShell;
|
||||
|
||||
#[test]
|
||||
fn apply_shell_minimizer_surfaces_rewrite_with_original() {
|
||||
let captured = "diff --git a/file.rs b/file.rs\n@@\n-old\n+new\n";
|
||||
let result = super::run_shell_minimizer(super::ShellMinimizerApplyOptions {
|
||||
command: "git diff".to_string(),
|
||||
captured: captured.to_string(),
|
||||
exit_code: Some(0),
|
||||
minimizer: Some(super::MinimizerOptions { enabled: Some(true), ..Default::default() }),
|
||||
})
|
||||
.expect("an enabled, supported command should surface a rewrite");
|
||||
assert_eq!(result.filter, "git");
|
||||
// A genuine rewrite carries the untouched capture in `original_text`
|
||||
// and a strictly different minimized `text`.
|
||||
assert_eq!(result.original_text, captured);
|
||||
assert_ne!(result.text, result.original_text);
|
||||
assert_eq!(result.input_bytes as usize, captured.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_shell_minimizer_returns_none_when_disabled() {
|
||||
// `enabled: false` keeps the engine in passthrough — no telemetry.
|
||||
assert!(
|
||||
super::run_shell_minimizer(super::ShellMinimizerApplyOptions {
|
||||
command: "git diff".to_string(),
|
||||
captured: "diff --git a/file.rs b/file.rs\n@@\n-old\n+new\n".to_string(),
|
||||
exit_code: Some(0),
|
||||
minimizer: Some(super::MinimizerOptions { enabled: Some(false), ..Default::default() }),
|
||||
})
|
||||
.is_none()
|
||||
);
|
||||
// A missing minimizer handle is also a no-op.
|
||||
assert!(
|
||||
super::run_shell_minimizer(super::ShellMinimizerApplyOptions {
|
||||
command: "git diff".to_string(),
|
||||
captured: "diff --git a/file.rs b/file.rs\n".to_string(),
|
||||
exit_code: Some(0),
|
||||
minimizer: None,
|
||||
})
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
mod child_session_action_tests {
|
||||
use pi_shell::{ChildSessionAction, child_session_action};
|
||||
|
||||
|
||||
@@ -879,6 +879,7 @@ fn aggressive_strip_bodies(input: &str, path: &str) -> Option<String> {
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
match ext {
|
||||
"rs" if contains_rust_raw_string_literal(input) => None,
|
||||
"rs" | "ts" | "tsx" | "js" | "jsx" | "go" => Some(strip_brace_bodies(input)),
|
||||
"py" => Some(strip_python_bodies(input)),
|
||||
_ => None,
|
||||
@@ -926,6 +927,10 @@ fn strip_brace_bodies(input: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
fn contains_rust_raw_string_literal(input: &str) -> bool {
|
||||
input.contains("r#") || input.contains("r\"") || input.contains("br#") || input.contains("br\"")
|
||||
}
|
||||
|
||||
fn brace_delta(line: &str) -> i32 {
|
||||
let mut delta: i32 = 0;
|
||||
let mut in_str: Option<char> = None;
|
||||
@@ -1510,6 +1515,17 @@ mod tests {
|
||||
assert!(!out.text.contains("y * 2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggressive_rust_outline_bails_on_raw_strings() {
|
||||
let cfg = aggressive_cfg();
|
||||
let ctx = ctx_command("cat", "cat src/foo.rs", &cfg);
|
||||
let body =
|
||||
"pub fn shader() -> &'static str {\n r#\"fn main() { println!(\\\"hi\\\"); }\"#\n}\n";
|
||||
let out = filter(&ctx, body, 0);
|
||||
assert!(!out.changed, "raw-string Rust source should fall back to default outline");
|
||||
assert_eq!(out.text, body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brace_in_line_comment_not_counted() {
|
||||
let cfg = aggressive_cfg();
|
||||
|
||||
@@ -70,7 +70,7 @@ fn condense_rustfmt(input: &str, exit_code: i32) -> String {
|
||||
files.iter().filter(|f| seen.insert(**f)).collect()
|
||||
};
|
||||
let total = unique.len();
|
||||
let _ = writeln!(out, "{total} files reformatted ({total} with diffs):");
|
||||
let _ = writeln!(out, "{total} files reformatted:");
|
||||
for file in unique.iter().take(3) {
|
||||
out.push_str(" ");
|
||||
out.push_str(file);
|
||||
|
||||
@@ -133,6 +133,14 @@
|
||||
- Fixed Windows stdio MCP servers launched through PATH shims such as `codegraph.cmd` so bare commands like `codegraph` resolve via `PATHEXT` before spawn ([#2174](https://github.com/can1357/oh-my-pi/issues/2174)).
|
||||
- Fixed compiled-binary extensions failing to load `@oh-my-pi/pi-*` packages when `bun --compile` quietly dropped one of the extra entrypoints (observed on macOS arm64 release builds): the legacy-pi compat shim's package-root override branch returned the bunfs path without checking the target was present, so the rewrite emitted a `file://` URL to a missing module and the #1216 fallback (scoped to the throwing `getResolvedSpecifier` path) never ran. Override targets are now validated against the on-disk filesystem at module init, missing entries are dropped, and resolution falls through to canonical lookup so Bun resolves the import from the extension's own `node_modules` ([#2168](https://github.com/can1357/oh-my-pi/issues/2168)).
|
||||
|
||||
### Added
|
||||
|
||||
- Added opt-in `shellMinimizer.sourceOutlineLevel` and `shellMinimizer.legacyFilters` settings so shell minimization can tune source outlining and selectively fall back to conservative legacy routing.
|
||||
|
||||
### Changed
|
||||
|
||||
- Bash execution now preserves minimized shell output inline while saving the untouched capture as an `artifact://…` footer when shell minimization rewrites a command's output.
|
||||
|
||||
## [15.10.8] - 2026-06-09
|
||||
|
||||
### Added
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function runShellCommand(cmd: ShellCommandArgs): Promise<void> {
|
||||
const settings = await Settings.init({ cwd });
|
||||
const { shell, env: shellEnv } = settings.getShellConfig();
|
||||
const snapshotPath = cmd.noSnapshot || !shell.includes("bash") ? null : await getOrCreateSnapshot(shell, shellEnv);
|
||||
const minimizer = await buildMinimizerOptions(settings.getGroup("shellMinimizer"));
|
||||
const minimizer = buildMinimizerOptions(settings.getGroup("shellMinimizer"));
|
||||
const shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined, minimizer });
|
||||
|
||||
let active = false;
|
||||
|
||||
@@ -2068,8 +2068,9 @@ export const SETTINGS_SCHEMA = {
|
||||
default: 4 * 1024 * 1024,
|
||||
},
|
||||
"shellMinimizer.sourceOutlineLevel": {
|
||||
type: "string",
|
||||
default: undefined,
|
||||
type: "enum",
|
||||
values: ["default", "aggressive"] as const,
|
||||
default: "default",
|
||||
ui: {
|
||||
tab: "editing",
|
||||
label: "Shell Minimizer Source Outline",
|
||||
@@ -3479,7 +3480,7 @@ export interface ShellMinimizerSettings {
|
||||
only: string[];
|
||||
except: string[];
|
||||
maxCaptureBytes: number;
|
||||
sourceOutlineLevel: string | undefined;
|
||||
sourceOutlineLevel: "default" | "aggressive";
|
||||
legacyFilters: boolean | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
|
||||
only: group.only.length > 0 ? group.only : undefined,
|
||||
except: group.except.length > 0 ? group.except : undefined,
|
||||
maxCaptureBytes: group.maxCaptureBytes,
|
||||
sourceOutlineLevel: group.sourceOutlineLevel || undefined,
|
||||
sourceOutlineLevel: group.sourceOutlineLevel === "default" ? undefined : group.sourceOutlineLevel,
|
||||
legacyFilters: group.legacyFilters,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { executeBash } from "@oh-my-pi/pi-coding-agent/exec/bash-executor";
|
||||
import { resetSettingsForTest, Settings, type ShellMinimizerSettings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { buildMinimizerOptions, executeBash } from "@oh-my-pi/pi-coding-agent/exec/bash-executor";
|
||||
import { DEFAULT_MAX_BYTES } from "@oh-my-pi/pi-coding-agent/session/streaming-output";
|
||||
import * as shellSnapshot from "@oh-my-pi/pi-coding-agent/utils/shell-snapshot";
|
||||
import type { Shell } from "@oh-my-pi/pi-natives";
|
||||
@@ -52,6 +52,39 @@ describe("executeBash", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("omits minimizer options when the feature is disabled", () => {
|
||||
const group: ShellMinimizerSettings = {
|
||||
enabled: false,
|
||||
settingsPath: undefined,
|
||||
only: [],
|
||||
except: [],
|
||||
maxCaptureBytes: 4096,
|
||||
sourceOutlineLevel: "default",
|
||||
legacyFilters: undefined,
|
||||
};
|
||||
expect(buildMinimizerOptions(group)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards source outline and legacy filter settings to native minimizer options", () => {
|
||||
const group: ShellMinimizerSettings = {
|
||||
enabled: true,
|
||||
settingsPath: "minimizer.toml",
|
||||
only: ["git"],
|
||||
except: ["docker"],
|
||||
maxCaptureBytes: 1234,
|
||||
sourceOutlineLevel: "aggressive",
|
||||
legacyFilters: true,
|
||||
};
|
||||
expect(buildMinimizerOptions(group)).toEqual({
|
||||
enabled: true,
|
||||
settingsPath: "minimizer.toml",
|
||||
only: ["git"],
|
||||
except: ["docker"],
|
||||
maxCaptureBytes: 1234,
|
||||
sourceOutlineLevel: "aggressive",
|
||||
legacyFilters: true,
|
||||
});
|
||||
});
|
||||
it("returns non-zero exit codes without cancellation", async () => {
|
||||
const result = await executeBash("exit 7", { cwd: tempDir, timeout: 5000 });
|
||||
expect(result.exitCode).toBe(7);
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
- Fixed cross-line grep being a silent no-op on real files: `multiline` set the `(?m)` flag on the regex matcher but never enabled `multi_line` on the `Searcher`, which stayed line-oriented, so any pattern spanning a `\n` returned zero matches with no error.
|
||||
|
||||
### Added
|
||||
|
||||
- Added deterministic shell-output minimization to the native shell pipeline, including opt-in per-command rewrite telemetry surfaced through `executeShell().minimized` for callers that want compact inline output plus a separately persisted original capture.
|
||||
|
||||
## [15.10.5] - 2026-06-08
|
||||
|
||||
### Added
|
||||
|
||||
Vendored
-36
@@ -147,27 +147,6 @@ export declare function __piNativesV15_10_11(): void
|
||||
*/
|
||||
export declare function applyBashFixups(command: string): BashFixupResult
|
||||
|
||||
/**
|
||||
* Run the shell-output minimizer over an already-captured command result,
|
||||
* without spawning a shell.
|
||||
*
|
||||
* This is the one-shot counterpart to the minimization that
|
||||
* [`execute_shell`] performs inline: callers that captured a command's output
|
||||
* elsewhere can pass it here to obtain the same telemetry.
|
||||
*
|
||||
* Returns [`MinimizerResult`] **only** when the minimizer actually rewrote the
|
||||
* output (`changed == true`) and retained the original buffer, mirroring the
|
||||
* persistent-shell path. Returns `null` for every no-op case: when
|
||||
* `minimizer` is omitted, when the config is disabled, or when the filter
|
||||
* passes the output through unchanged. A missing `exit_code` is treated as
|
||||
* success (`0`).
|
||||
*
|
||||
* Async (returns a Promise): minimization can scan multi-megabyte captured
|
||||
* output, so the work runs on a blocking pool to avoid stalling the JS event
|
||||
* loop.
|
||||
*/
|
||||
export declare function applyShellMinimizer(options: ShellMinimizerApplyOptions): Promise<MinimizerResult | null>
|
||||
|
||||
/**
|
||||
* Apply ast-grep rewrite rules to matching files; honors `dryRun` and returns
|
||||
* a promise.
|
||||
@@ -1374,21 +1353,6 @@ export interface ShellExecuteOptions {
|
||||
signal?: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Inputs for [`apply_shell_minimizer`]: a captured command's text plus the
|
||||
* minimizer configuration to run against it.
|
||||
*/
|
||||
export interface ShellMinimizerApplyOptions {
|
||||
/** The command line that produced `captured` (used to select a filter). */
|
||||
command: string
|
||||
/** The full captured stdout/stderr to minimize. */
|
||||
captured: string
|
||||
/** The command's exit status; omitted is treated as success (`0`). */
|
||||
exitCode?: number
|
||||
/** Minimizer configuration; when omitted the call is a no-op (`null`). */
|
||||
minimizer?: MinimizerOptions
|
||||
}
|
||||
|
||||
/** Options for configuring a persistent shell session. */
|
||||
export interface ShellOptions {
|
||||
/** Environment variables to apply once per session. */
|
||||
|
||||
@@ -25,7 +25,6 @@ export const Shell = nativeBindings.Shell;
|
||||
// functions
|
||||
export const __piNativesV15_10_11 = nativeBindings.__piNativesV15_10_11;
|
||||
export const applyBashFixups = nativeBindings.applyBashFixups;
|
||||
export const applyShellMinimizer = nativeBindings.applyShellMinimizer;
|
||||
export const astEdit = nativeBindings.astEdit;
|
||||
export const astGrep = nativeBindings.astGrep;
|
||||
export const astMatch = nativeBindings.astMatch;
|
||||
|
||||
Reference in New Issue
Block a user