

























@@ -1,7 +1,8 @@
1-import { readdirSync, readFileSync } from "node:fs";
1+import { spawnSync } from "node:child_process";
2+import fs from "node:fs";
23import path from "node:path";
34import { fileURLToPath } from "node:url";
4-import { describe, expect, it } from "vitest";
5+import { describe, expect, it, vi } from "vitest";
5667const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
78@@ -69,20 +70,50 @@ const skippedExtensionScanDirs = new Set([
6970]);
70717172function readRepoFile(relativePath: string): string {
72-return readFileSync(path.join(repoRoot, ...relativePath.split("/")), "utf8");
73+return fs.readFileSync(path.join(repoRoot, ...relativePath.split("/")), "utf8");
74+}
75+76+function isScannableTsFile(relativePath: string): boolean {
77+const parts = relativePath.split("/");
78+return (
79+!parts.some((part) => skippedExtensionScanDirs.has(part)) &&
80+relativePath.endsWith(".ts") &&
81+!relativePath.endsWith(".d.ts")
82+);
83+}
84+85+function listGitTsFiles(relativeDir: string): string[] | null {
86+const result = spawnSync("git", ["ls-files", "--", relativeDir], {
87+cwd: repoRoot,
88+encoding: "utf8",
89+stdio: ["ignore", "pipe", "ignore"],
90+});
91+if (result.status !== 0) {
92+return null;
93+}
94+return result.stdout
95+.split("\n")
96+.map((line) => line.trim().replaceAll("\\", "/"))
97+.filter((line) => line.length > 0 && isScannableTsFile(line))
98+.toSorted();
7399}
7410075101function listTsFiles(relativeDir: string): string[] {
102+const gitFiles = listGitTsFiles(relativeDir);
103+if (gitFiles) {
104+return gitFiles;
105+}
106+76107const dir = path.join(repoRoot, ...relativeDir.split("/"));
77-return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
108+return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
78109const relativePath = path.posix.join(relativeDir, entry.name);
79110if (entry.isDirectory()) {
80111if (skippedExtensionScanDirs.has(entry.name)) {
81112return [];
82113}
83114return listTsFiles(relativePath);
84115}
85-if (!entry.isFile() || !entry.name.endsWith(".ts") || entry.name.endsWith(".d.ts")) {
116+if (!entry.isFile() || !isScannableTsFile(relativePath)) {
86117return [];
87118}
88119return [relativePath];
@@ -103,6 +134,20 @@ function collectReplyHistoryBindings(source: string): Set<string> {
103134}
104135105136describe("message turn migration guardrails", () => {
137+it("lists plugin TypeScript files from git without walking extension roots", () => {
138+const readDir = vi.spyOn(fs, "readdirSync");
139+try {
140+const files = listTsFiles("extensions");
141+142+expect(files.length).toBeGreaterThan(0);
143+expect(files.every((file) => file.startsWith("extensions/"))).toBe(true);
144+expect(files.some((file) => file.endsWith(".d.ts"))).toBe(false);
145+expect(readDir).not.toHaveBeenCalled();
146+} finally {
147+readDir.mockRestore();
148+}
149+});
150+106151it("keeps migrated message paths off low-level reply-history helpers", () => {
107152for (const file of migratedMessageTurnFiles) {
108153const source = readRepoFile(file);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。