mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: add hashline editing and TypeScript LSP
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import { deployBundleHashlineConfig } from "../extensions/hashline-config.ts";
|
||||
|
||||
const EXPECTED_CONFIG = {
|
||||
hashLength: 2,
|
||||
grep: false,
|
||||
replaceText: false,
|
||||
};
|
||||
|
||||
test("hashline config deployment enforces bundle defaults", async () => {
|
||||
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-hashline-"));
|
||||
const targetPath = join(agentDir, "hashline.json");
|
||||
|
||||
try {
|
||||
await writeFile(targetPath, '{"grep":true}\n', "utf8");
|
||||
deployBundleHashlineConfig(agentDir);
|
||||
assert.deepEqual(JSON.parse(await readFile(targetPath, "utf8")), EXPECTED_CONFIG);
|
||||
|
||||
const first = await readFile(targetPath, "utf8");
|
||||
deployBundleHashlineConfig(agentDir);
|
||||
assert.equal(await readFile(targetPath, "utf8"), first);
|
||||
} finally {
|
||||
await rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
import { deployBundleLspConfig, materializeBundleLspConfig } from "../extensions/lsp-config.ts";
|
||||
|
||||
const RUNTIME = {
|
||||
nodeBinary: "/runtime/node",
|
||||
serverCliPath: "/bundle/typescript-language-server/lib/cli.mjs",
|
||||
};
|
||||
|
||||
test("LSP config materializes the bundled TypeScript server command", () => {
|
||||
const source = JSON.stringify({
|
||||
version: 1,
|
||||
servers: [
|
||||
{ id: "typescript-language-server", bin: "typescript-language-server", args: ["--stdio"] },
|
||||
{ id: "jdtls", bin: "jdtls", args: [] },
|
||||
],
|
||||
});
|
||||
const config = JSON.parse(materializeBundleLspConfig(source, RUNTIME));
|
||||
|
||||
assert.deepEqual(config.servers[0], {
|
||||
id: "typescript-language-server",
|
||||
bin: RUNTIME.nodeBinary,
|
||||
args: [RUNTIME.serverCliPath, "--stdio"],
|
||||
});
|
||||
assert.deepEqual(config.servers[1], { id: "jdtls", bin: "jdtls", args: [] });
|
||||
});
|
||||
|
||||
test("LSP config deployment writes the materialized bundle config", async () => {
|
||||
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-lsp-"));
|
||||
try {
|
||||
deployBundleLspConfig(agentDir, RUNTIME);
|
||||
const config = JSON.parse(await readFile(join(agentDir, "lsp.json"), "utf8"));
|
||||
const server = config.servers.find((entry: { id?: string }) => entry.id === "typescript-language-server");
|
||||
|
||||
assert.equal(server.bin, RUNTIME.nodeBinary);
|
||||
assert.deepEqual(server.args, [RUNTIME.serverCliPath, "--stdio"]);
|
||||
assert.deepEqual(server.include, [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.mts",
|
||||
"**/*.cts",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.mjs",
|
||||
"**/*.cjs",
|
||||
]);
|
||||
} finally {
|
||||
await rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -21,13 +21,29 @@ test("search routing narrows files before requesting matching line numbers", ()
|
||||
assert.match(section, /Use read with offset\/limit only for the exact matching region/);
|
||||
});
|
||||
|
||||
test("ctx_execute_file routing warns about its project-root boundary", () => {
|
||||
const section = buildToolRoutingSection(["ctx_execute_file"]);
|
||||
test("context routing analyzes large files without a full read", () => {
|
||||
const section = buildToolRoutingSection(["ctx_execute", "ctx_execute_file"]);
|
||||
|
||||
assert.match(section, /Prefer ctx_execute_file over read when deriving an answer from a large file/);
|
||||
assert.match(section, /ctx_execute_file is confined to the current project root/);
|
||||
assert.match(section, /host permission approval does not bypass this Context Mode boundary/);
|
||||
});
|
||||
|
||||
test("hashline routing uses small reads and fresh anchors for edits", () => {
|
||||
const section = buildToolRoutingSection(["read", "edit"]);
|
||||
|
||||
assert.match(section, /smallest useful offset\/limit range/);
|
||||
assert.match(section, /obtain fresh LINE#HASH anchors with a small read/);
|
||||
assert.match(section, /Reuse fresh anchors returned by successful edits/);
|
||||
});
|
||||
|
||||
test("LSP routing covers navigation and post-edit diagnostics", () => {
|
||||
const section = buildToolRoutingSection(["lsp_definition", "lsp_references", "lsp_diagnostics"]);
|
||||
|
||||
assert.match(section, /Use LSP for symbol definitions, references/);
|
||||
assert.match(section, /post-edit diagnostics/);
|
||||
});
|
||||
|
||||
test("routing includes only guidance for active optional tools", () => {
|
||||
const section = buildToolRoutingSection(["read"]);
|
||||
|
||||
@@ -35,7 +51,8 @@ test("routing includes only guidance for active optional tools", () => {
|
||||
assert.doesNotMatch(section, /For literal search/);
|
||||
assert.doesNotMatch(section, /Use Context Mode/);
|
||||
assert.doesNotMatch(section, /ctx_execute_file is confined/);
|
||||
assert.match(section, /Use read directly/);
|
||||
assert.doesNotMatch(section, /fresh LINE#HASH anchors/);
|
||||
assert.match(section, /Use read only when exact source is needed/);
|
||||
});
|
||||
|
||||
test("routing is appended once", () => {
|
||||
|
||||
Reference in New Issue
Block a user