chore: update tests, lockfiles, and misc cleanups
This commit is contained in:
@@ -56,3 +56,6 @@ pi-*.html
|
||||
|
||||
# Generated files
|
||||
packages/coding-agent/src/internal-urls/docs-index.generated.ts
|
||||
packages/typescript-edit-benchmark/runs/
|
||||
|
||||
all_models_results.json
|
||||
|
||||
@@ -90,30 +90,6 @@
|
||||
"@types/bun": "^1.3",
|
||||
},
|
||||
},
|
||||
"packages/react-edit-benchmark": {
|
||||
"name": "@oh-my-pi/react-edit-benchmark",
|
||||
"version": "0.0.1",
|
||||
"bin": {
|
||||
"react-edit-benchmark": "src/index.ts",
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.29",
|
||||
"@babel/parser": "^7.29",
|
||||
"@babel/traverse": "^7.29",
|
||||
"@babel/types": "^7.29",
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
"@oh-my-pi/pi-coding-agent": "workspace:*",
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
"diff": "^8.0",
|
||||
"prettier": "^3.8",
|
||||
"regexp-tree": "^0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel__generator": "^7.27",
|
||||
"@types/babel__traverse": "^7.28",
|
||||
"@types/bun": "^1.3",
|
||||
},
|
||||
},
|
||||
"packages/stats": {
|
||||
"name": "@oh-my-pi/omp-stats",
|
||||
"version": "13.19.0",
|
||||
@@ -165,6 +141,30 @@
|
||||
"chalk": "^5.6",
|
||||
},
|
||||
},
|
||||
"packages/typescript-edit-benchmark": {
|
||||
"name": "@oh-my-pi/typescript-edit-benchmark",
|
||||
"version": "0.0.1",
|
||||
"bin": {
|
||||
"typescript-edit-benchmark": "src/index.ts",
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.29",
|
||||
"@babel/parser": "^7.29",
|
||||
"@babel/traverse": "^7.29",
|
||||
"@babel/types": "^7.29",
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
"@oh-my-pi/pi-coding-agent": "workspace:*",
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
"diff": "^8.0",
|
||||
"prettier": "^3.8",
|
||||
"regexp-tree": "^0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/babel__generator": "^7.27",
|
||||
"@types/babel__traverse": "^7.28",
|
||||
"@types/bun": "^1.3",
|
||||
},
|
||||
},
|
||||
"packages/utils": {
|
||||
"name": "@oh-my-pi/pi-utils",
|
||||
"version": "13.19.0",
|
||||
@@ -331,10 +331,10 @@
|
||||
|
||||
"@oh-my-pi/pi-utils": ["@oh-my-pi/pi-utils@workspace:packages/utils"],
|
||||
|
||||
"@oh-my-pi/react-edit-benchmark": ["@oh-my-pi/react-edit-benchmark@workspace:packages/react-edit-benchmark"],
|
||||
|
||||
"@oh-my-pi/swarm-extension": ["@oh-my-pi/swarm-extension@workspace:packages/swarm-extension"],
|
||||
|
||||
"@oh-my-pi/typescript-edit-benchmark": ["@oh-my-pi/typescript-edit-benchmark@workspace:packages/typescript-edit-benchmark"],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@
|
||||
"fix:rs": "cargo fmt --all && cargo clippy --fix --allow-dirty --all-targets --no-deps --allow-staged --broken-code --allow-no-vcs",
|
||||
"build:native": "bun --cwd=packages/natives run build:native",
|
||||
"dev:native": "bun --cwd=packages/natives run dev:native",
|
||||
"bench:gen-fixtures": "bun --cwd=packages/react-edit-benchmark run src/generate.ts --react-dir /tmp/react-source --count-per-type 8",
|
||||
"bench:edit": "bun --cwd=packages/react-edit-benchmark run start",
|
||||
"bench:gen-fixtures": "bun --cwd=packages/typescript-edit-benchmark run src/generate.ts --typescript-dir /tmp/typescript-source --count-per-type 8",
|
||||
"bench:edit": "bun --cwd=packages/typescript-edit-benchmark run start",
|
||||
"prepublishOnly": "bun run check",
|
||||
"prepare": "bun --cwd=packages/coding-agent run generate-docs-index",
|
||||
"publish": "bun run prepublishOnly && npm publish -ws --access public",
|
||||
|
||||
@@ -3,14 +3,36 @@
|
||||
## [Unreleased]
|
||||
### Added
|
||||
|
||||
- Autoresearch `init_experiment` options `from_autoresearch_md` (load contract from `autoresearch.md` with only `name`) and `abandon_unlogged_runs` (stamp pending run artifacts abandoned)
|
||||
- Autoresearch `run_experiment` option `force` to override benchmark command equality and the direct-`autoresearch.sh` invocation rule (with warnings when used)
|
||||
- `abandonUnloggedAutoresearchRuns` helper and `abandonedAt` run metadata so abandoned artifacts are ignored as pending runs
|
||||
|
||||
### Changed
|
||||
|
||||
- Autoresearch `log_experiment` refreshes benchmark/scope/constraints from `autoresearch.md` after resolving a pending run (so keep validation matches disk); success output includes a short refresh notice
|
||||
- Autoresearch `log_experiment` `force` also skips ASI requirements and allows keeping a primary-metric regression versus the best kept run in the segment
|
||||
|
||||
### Added
|
||||
|
||||
- Support for LSP diagnostic versioning to track document versions and suppress stale diagnostics
|
||||
- Options parameter to `waitForDiagnostics` for controlling diagnostic freshness validation with `expectedDocumentVersion` and `allowUnversioned` flags
|
||||
|
||||
### Changed
|
||||
|
||||
- `/autoresearch` with no `autoresearch.md` matches `/plan`-style flow: bare command enables mode and waits for the next composer message when off, or disables when already on; non-empty slash args are submitted as the user message only; setup protocol lives in the autoresearch system prompt (`command-initialize.md` removed)
|
||||
- Enabled `versionSupport` in LSP client capabilities to receive diagnostic version information from servers
|
||||
- Diagnostics storage now tracks both diagnostics and their associated document version for freshness validation
|
||||
- Updated `getDiagnosticsForFile` to accept options object instead of positional parameters for better extensibility
|
||||
- Redesigned chunk-mode `read` output to use a single recursive chunk rendering rule with `$XXXX` checksum suffixes and inline large-chunk previews
|
||||
- Normalized copied `#chunk_path$XXXX` selectors across chunk-mode `read` and `edit` inputs so pasted chunk headers resolve without manual cleanup
|
||||
|
||||
### Fixed
|
||||
- `/autoresearch` with no arguments toggles off when mode is already on (same idea as `/plan`), and slash-argument completion no longer offers `off`/`clear` on an empty prefix so Tab after the command does not auto-insert a subcommand.
|
||||
- Fixed chunk-mode edit/read edge cases around zero-width gap splices, stale batch diagnostics, grouped Go receiver rendering, rendered line-count headers, and parse rejection location details.
|
||||
|
||||
### Removed
|
||||
|
||||
- Autoresearch segment fingerprint hashing and `segmentFingerprint` on experiment state / `autoresearch.jsonl` config lines
|
||||
|
||||
## [13.19.0] - 2026-04-05
|
||||
### Added
|
||||
@@ -6635,4 +6657,4 @@ Initial public release.
|
||||
- Git branch display in footer
|
||||
- Message queueing during streaming responses
|
||||
- OAuth integration for Gmail and Google Calendar access
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
export type ExecutionAbortReason = "idle-timeout" | "signal";
|
||||
|
||||
export interface IdleTimeoutWatchdogOptions {
|
||||
timeoutMs?: number;
|
||||
signal?: AbortSignal;
|
||||
hardTimeoutGraceMs: number;
|
||||
onAbort?: (reason: ExecutionAbortReason) => void;
|
||||
}
|
||||
|
||||
export class IdleTimeoutWatchdog {
|
||||
#abortController = new AbortController();
|
||||
#abortReason?: ExecutionAbortReason;
|
||||
#hardTimeoutDeferred = Promise.withResolvers<"hard-timeout">();
|
||||
#hardTimeoutGraceMs: number;
|
||||
#hardTimeoutTimer?: NodeJS.Timeout;
|
||||
#idleTimer?: NodeJS.Timeout;
|
||||
#onAbort?: (reason: ExecutionAbortReason) => void;
|
||||
#signal?: AbortSignal;
|
||||
#signalAbortHandler?: () => void;
|
||||
#timeoutMs?: number;
|
||||
|
||||
constructor(options: IdleTimeoutWatchdogOptions) {
|
||||
this.#timeoutMs = options.timeoutMs;
|
||||
this.#hardTimeoutGraceMs = options.hardTimeoutGraceMs;
|
||||
this.#onAbort = options.onAbort;
|
||||
this.#signal = options.signal;
|
||||
|
||||
if (this.#signal) {
|
||||
if (this.#signal.aborted) {
|
||||
this.#abort("signal");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#signalAbortHandler = () => {
|
||||
this.#abort("signal");
|
||||
};
|
||||
this.#signal.addEventListener("abort", this.#signalAbortHandler, { once: true });
|
||||
}
|
||||
|
||||
this.touch();
|
||||
}
|
||||
|
||||
get abortedBySignal(): boolean {
|
||||
return this.#abortReason === "signal";
|
||||
}
|
||||
|
||||
get hardTimeoutPromise(): Promise<"hard-timeout"> {
|
||||
return this.#hardTimeoutDeferred.promise;
|
||||
}
|
||||
|
||||
get signal(): AbortSignal {
|
||||
return this.#abortController.signal;
|
||||
}
|
||||
|
||||
get timedOut(): boolean {
|
||||
return this.#abortReason === "idle-timeout";
|
||||
}
|
||||
|
||||
touch(): void {
|
||||
if (this.#abortReason || this.#timeoutMs === undefined || this.#timeoutMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#idleTimer) {
|
||||
clearTimeout(this.#idleTimer);
|
||||
}
|
||||
|
||||
this.#idleTimer = setTimeout(() => {
|
||||
this.#abort("idle-timeout");
|
||||
}, this.#timeoutMs);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.#idleTimer) {
|
||||
clearTimeout(this.#idleTimer);
|
||||
this.#idleTimer = undefined;
|
||||
}
|
||||
if (this.#hardTimeoutTimer) {
|
||||
clearTimeout(this.#hardTimeoutTimer);
|
||||
this.#hardTimeoutTimer = undefined;
|
||||
}
|
||||
if (this.#signal && this.#signalAbortHandler) {
|
||||
this.#signal.removeEventListener("abort", this.#signalAbortHandler);
|
||||
this.#signalAbortHandler = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
#abort(reason: ExecutionAbortReason): void {
|
||||
if (this.#abortReason) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#abortReason = reason;
|
||||
|
||||
if (this.#idleTimer) {
|
||||
clearTimeout(this.#idleTimer);
|
||||
this.#idleTimer = undefined;
|
||||
}
|
||||
|
||||
if (!this.#abortController.signal.aborted) {
|
||||
this.#abortController.abort(reason);
|
||||
}
|
||||
|
||||
this.#onAbort?.(reason);
|
||||
this.#armHardTimeout();
|
||||
}
|
||||
|
||||
#armHardTimeout(): void {
|
||||
if (this.#hardTimeoutTimer || this.#hardTimeoutGraceMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#hardTimeoutTimer = setTimeout(() => {
|
||||
this.#hardTimeoutDeferred.resolve("hard-timeout");
|
||||
}, this.#hardTimeoutGraceMs);
|
||||
}
|
||||
}
|
||||
|
||||
export function formatIdleTimeoutMessage(timeoutMs?: number): string {
|
||||
if (timeoutMs === undefined) {
|
||||
return "Command timed out without output";
|
||||
}
|
||||
|
||||
const seconds = Math.max(1, Math.round(timeoutMs / 1000));
|
||||
return `Command timed out after ${seconds} seconds without output`;
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { invalidateFsScanCache } from "@oh-my-pi/pi-natives";
|
||||
import { invalidateChunkTreeCache } from "./chunk-tree";
|
||||
|
||||
/**
|
||||
* Invalidate shared filesystem scan caches after a content write/update.
|
||||
*/
|
||||
export function invalidateFsScanAfterWrite(path: string): void {
|
||||
invalidateFsScanCache(path);
|
||||
invalidateChunkTreeCache(path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -12,6 +14,7 @@ export function invalidateFsScanAfterWrite(path: string): void {
|
||||
*/
|
||||
export function invalidateFsScanAfterDelete(path: string): void {
|
||||
invalidateFsScanCache(path);
|
||||
invalidateChunkTreeCache(path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +25,9 @@ export function invalidateFsScanAfterDelete(path: string): void {
|
||||
*/
|
||||
export function invalidateFsScanAfterRename(oldPath: string, newPath: string): void {
|
||||
invalidateFsScanCache(oldPath);
|
||||
invalidateChunkTreeCache(oldPath);
|
||||
if (newPath !== oldPath) {
|
||||
invalidateFsScanCache(newPath);
|
||||
invalidateChunkTreeCache(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
||||
}
|
||||
|
||||
if (truncation.nextOffset != null) {
|
||||
notice += `. Use offset=${truncation.nextOffset} to continue`;
|
||||
notice += `. Use sel=L${truncation.nextOffset} to continue`;
|
||||
}
|
||||
|
||||
if (truncation.artifactId != null) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Resolve line-display mode for file-like outputs (read, grep, @file mentions).
|
||||
*/
|
||||
|
||||
import { resolveEditMode } from "./edit-mode";
|
||||
|
||||
export interface FileDisplayMode {
|
||||
lineNumbers: boolean;
|
||||
hashLines: boolean;
|
||||
@@ -24,11 +26,7 @@ export interface FileDisplayModeSession {
|
||||
export function resolveFileDisplayMode(session: FileDisplayModeSession): FileDisplayMode {
|
||||
const { settings } = session;
|
||||
const hasEditTool = session.hasEditTool ?? true;
|
||||
const hashLines =
|
||||
hasEditTool &&
|
||||
(settings.get("readHashLines") === true ||
|
||||
settings.get("edit.mode") === "hashline" ||
|
||||
Bun.env.PI_EDIT_VARIANT === "hashline");
|
||||
const hashLines = hasEditTool && resolveEditMode(session) === "hashline" && settings.get("readHashLines") !== false;
|
||||
return {
|
||||
hashLines,
|
||||
lineNumbers: hashLines || settings.get("readLineNumbers") === true,
|
||||
|
||||
@@ -335,7 +335,7 @@ describe("truncation notice formatting", () => {
|
||||
|
||||
test("formatHeadTruncationNotice formats head truncation range", () => {
|
||||
const lineTruncation = truncateHead("l1\nl2\nl3", { maxLines: 2, maxBytes: 100 });
|
||||
expect(formatHeadTruncationNotice(lineTruncation)).toBe("\n\n[Showing lines 1-2 of 3. Use offset=3 to continue]");
|
||||
expect(formatHeadTruncationNotice(lineTruncation)).toBe("\n\n[Showing lines 1-2 of 3. Use sel=L3 to continue]");
|
||||
|
||||
const byteTruncation = truncateHead("12345\nabc\nz", { maxLines: 10, maxBytes: 7 });
|
||||
expect(
|
||||
@@ -343,6 +343,6 @@ describe("truncation notice formatting", () => {
|
||||
startLine: 100,
|
||||
totalFileLines: 500,
|
||||
}),
|
||||
).toBe("\n\n[Showing lines 100-100 of 500. Use offset=101 to continue]");
|
||||
).toBe("\n\n[Showing lines 100-100 of 500. Use sel=L101 to continue]");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -262,7 +262,7 @@ describe("Coding Agent Tools", () => {
|
||||
expect(output).toContain("Line 2");
|
||||
expect(output).toContain("Line 3");
|
||||
// No truncation message since file fits within limits
|
||||
expect(getTextOutput(result)).not.toContain("Use offset=");
|
||||
expect(getTextOutput(result)).not.toContain("Use sel=");
|
||||
expect(result.details?.truncation).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -325,7 +325,7 @@ describe("Coding Agent Tools", () => {
|
||||
expect(output).toContain(`Line ${defaultLimit}`);
|
||||
expect(output).not.toContain(`Line ${defaultLimit + 1}`);
|
||||
expect(output).toContain(
|
||||
`[Showing lines 1-${defaultLimit} of 3500. Use offset=${defaultLimit + 1} to continue]`,
|
||||
`[Showing lines 1-${defaultLimit} of 3500. Use sel=L${defaultLimit + 1} to continue]`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -341,7 +341,7 @@ describe("Coding Agent Tools", () => {
|
||||
expect(output).toContain("Line 1:");
|
||||
// Should show byte limit message
|
||||
expect(output).toMatch(
|
||||
/\[Showing lines 1-\d+ of 1000 \(\d+(\.\d+)?\s*KB limit\)\. Use offset=\d+ to continue\]/,
|
||||
/\[Showing lines 1-\d+ of 1000 \(\d+(\.\d+)?\s*KB limit\)\. Use sel=L\d+ to continue\]/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -350,14 +350,14 @@ describe("Coding Agent Tools", () => {
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
||||
fs.writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-5", { path: testFile, offset: 51 });
|
||||
const result = await readTool.execute("test-call-5", { path: testFile, sel: "L51" });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).not.toContain("Line 50");
|
||||
expect(output).toContain("Line 51");
|
||||
expect(output).toContain("Line 100");
|
||||
// No truncation message since file fits within limits
|
||||
expect(output).not.toContain("Use offset=");
|
||||
expect(output).not.toContain("Use sel=");
|
||||
});
|
||||
|
||||
it("should handle limit parameter", async () => {
|
||||
@@ -365,13 +365,13 @@ describe("Coding Agent Tools", () => {
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`);
|
||||
fs.writeFileSync(testFile, lines.join("\n"));
|
||||
|
||||
const result = await readTool.execute("test-call-6", { path: testFile, limit: 10 });
|
||||
const result = await readTool.execute("test-call-6", { path: testFile, sel: "L1-L10" });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Line 1");
|
||||
expect(output).toContain("Line 10");
|
||||
expect(output).not.toContain("Line 11");
|
||||
expect(output).toContain("[Showing lines 1-10 of 100. Use offset=11 to continue]");
|
||||
expect(output).toContain("[Showing lines 1-10 of 100. Use sel=L11 to continue]");
|
||||
});
|
||||
|
||||
it("should handle offset + limit together", async () => {
|
||||
@@ -381,8 +381,7 @@ describe("Coding Agent Tools", () => {
|
||||
|
||||
const result = await readTool.execute("test-call-7", {
|
||||
path: testFile,
|
||||
offset: 41,
|
||||
limit: 20,
|
||||
sel: "L41-L60",
|
||||
});
|
||||
const output = getTextOutput(result);
|
||||
|
||||
@@ -390,18 +389,18 @@ describe("Coding Agent Tools", () => {
|
||||
expect(output).toContain("Line 41");
|
||||
expect(output).toContain("Line 60");
|
||||
expect(output).not.toContain("Line 61");
|
||||
expect(output).toContain("[Showing lines 41-60 of 100. Use offset=61 to continue]");
|
||||
expect(output).toContain("[Showing lines 41-60 of 100. Use sel=L61 to continue]");
|
||||
});
|
||||
|
||||
it("should show error when offset is beyond file length", async () => {
|
||||
const testFile = path.join(testDir, "short.txt");
|
||||
fs.writeFileSync(testFile, "Line 1\nLine 2\nLine 3");
|
||||
|
||||
const result = await readTool.execute("test-call-8", { path: testFile, offset: 100 });
|
||||
const result = await readTool.execute("test-call-8", { path: testFile, sel: "L100" });
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("Offset 100 is beyond end of file (3 lines total)");
|
||||
expect(output).toContain("Use offset=1 to read from the start, or offset=3 to read the last line.");
|
||||
expect(output).toContain("Line 100 is beyond end of file (3 lines total)");
|
||||
expect(output).toContain("Use sel=L1 to read from the start, or sel=L3 to read the last line.");
|
||||
});
|
||||
|
||||
it("should include truncation details when truncated", async () => {
|
||||
@@ -492,14 +491,14 @@ describe("Coding Agent Tools", () => {
|
||||
|
||||
const result = await readTool.execute("test-call-archive-subpath", {
|
||||
path: `${archivePath}:pkg/README.md`,
|
||||
limit: 2,
|
||||
sel: "L1-L2",
|
||||
});
|
||||
const output = getTextOutput(result);
|
||||
|
||||
expect(output).toContain("# Archive README");
|
||||
expect(output).toContain("Line 2");
|
||||
expect(output).not.toContain("Line 3");
|
||||
expect(output).toContain("Use offset=3");
|
||||
expect(output).toContain("Use sel=L3");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1010,7 +1009,8 @@ function b() {
|
||||
|
||||
const output = getTextOutput(result);
|
||||
expect(output).not.toContain("# example.txt");
|
||||
expect(output).toMatch(/>>\s*2#[ZPMQVRWSNKTXJBYH]{2}:match line/);
|
||||
// PI_EDIT_VARIANT=replace in beforeEach disables hashlines; expect line-number mode
|
||||
expect(output).toMatch(/\b2:match line/);
|
||||
});
|
||||
|
||||
it("should accept wildcard patterns in the path parameter", async () => {
|
||||
@@ -1069,9 +1069,9 @@ function b() {
|
||||
|
||||
const output = getTextOutput(result);
|
||||
expect(output).not.toContain("# context.txt");
|
||||
expect(output).toMatch(/\b1#[ZPMQVRWSNKTXJBYH]{2}:before/);
|
||||
expect(output).toMatch(/>>\s*2#[ZPMQVRWSNKTXJBYH]{2}:match one/);
|
||||
expect(output).toMatch(/\b3#[ZPMQVRWSNKTXJBYH]{2}:after/);
|
||||
expect(output).toMatch(/\b1-before/);
|
||||
expect(output).toMatch(/\b2:match one/);
|
||||
expect(output).toMatch(/\b3-after/);
|
||||
expect(output).toContain("[1 matches limit reached. Use limit=2 for more]");
|
||||
// Ensure second match is not present
|
||||
expect(output).not.toContain("match two");
|
||||
|
||||
@@ -261,7 +261,7 @@ describe("read tool URL handling", () => {
|
||||
content: "<html><body>not really an image</body></html>",
|
||||
});
|
||||
|
||||
const result = await tool.execute("fetch-html-png-path", { path: "https://example.com/foo.png", raw: true });
|
||||
const result = await tool.execute("fetch-html-png-path", { path: "https://example.com/foo.png", sel: "raw" });
|
||||
const imageBlock = result.content.find(content => content.type === "image");
|
||||
const textBlock = result.content.find(content => content.type === "text");
|
||||
|
||||
@@ -603,8 +603,7 @@ describe("read tool URL handling", () => {
|
||||
|
||||
const pagedResult = await tool.execute("fetch-offset-page", {
|
||||
path: pageUrl,
|
||||
offset: 7,
|
||||
limit: 2,
|
||||
sel: "L7-L8",
|
||||
});
|
||||
const pagedText = pagedResult.content.find(content => content.type === "text");
|
||||
expect(pagedText?.type).toBe("text");
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe, expect, it, vi } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { RenderResultOptions } from "@oh-my-pi/pi-agent-core";
|
||||
import { getServersForFile, loadConfig } from "@oh-my-pi/pi-coding-agent/lsp/config";
|
||||
import { renderCall, renderResult } from "@oh-my-pi/pi-coding-agent/lsp/render";
|
||||
import type { CodeAction, SymbolInformation } from "@oh-my-pi/pi-coding-agent/lsp/types";
|
||||
import {
|
||||
applyCodeAction,
|
||||
collectGlobMatches,
|
||||
dedupeWorkspaceSymbols,
|
||||
detectLanguageId,
|
||||
filterWorkspaceSymbols,
|
||||
hasGlobPattern,
|
||||
resolveSymbolColumn,
|
||||
@@ -223,4 +227,30 @@ describe("lsp regressions", () => {
|
||||
expect(normalizedResultText).toContain("occurrence: 2");
|
||||
expect(resultText).not.toContain("\t");
|
||||
});
|
||||
|
||||
it("detects tlaplus files for LSP startup and language ids", async () => {
|
||||
const tempDir = TempDir.createSync("@omp-lsp-tlaplus-");
|
||||
const specPath = path.join(tempDir.path(), "Spec.tla");
|
||||
const aliasPath = path.join(tempDir.path(), "Spec.tlaplus");
|
||||
|
||||
await Bun.write(specPath, "---- MODULE Spec ----\n====\n");
|
||||
|
||||
const whichSpy = vi
|
||||
.spyOn(Bun, "which")
|
||||
.mockImplementation(command => (command === "tlapm_lsp" ? "/usr/local/bin/tlapm_lsp" : null));
|
||||
const existsSpy = vi
|
||||
.spyOn(fs, "existsSync")
|
||||
.mockImplementation(candidate => typeof candidate === "string" && candidate === specPath);
|
||||
|
||||
try {
|
||||
const config = loadConfig(tempDir.path());
|
||||
expect(getServersForFile(config, specPath).map(([name]) => name)).toEqual(["tlaplus"]);
|
||||
expect(whichSpy).toHaveBeenCalledWith("tlapm_lsp");
|
||||
expect(existsSpy).toHaveBeenCalled();
|
||||
expect(detectLanguageId(specPath)).toBe("tlaplus");
|
||||
expect(detectLanguageId(aliasPath)).toBe("tlaplus");
|
||||
} finally {
|
||||
tempDir.removeSync();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user