@wrongstack/tools 0.287.0 → 0.291.0

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.
Files changed (102) hide show
  1. package/dist/_fetch-guard.d.ts.map +1 -1
  2. package/dist/_redact-command.d.ts.map +1 -1
  3. package/dist/_spawn-stream.d.ts.map +1 -1
  4. package/dist/audit.js +61 -14
  5. package/dist/audit.js.map +3 -3
  6. package/dist/auto-proceed-loop-guard.d.ts +128 -0
  7. package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
  8. package/dist/auto-proceed-loop-guard.js +46 -0
  9. package/dist/auto-proceed-loop-guard.js.map +7 -0
  10. package/dist/bash.d.ts +1 -1
  11. package/dist/bash.d.ts.map +1 -1
  12. package/dist/bash.js +97 -19
  13. package/dist/bash.js.map +3 -3
  14. package/dist/builtin.d.ts +12 -10
  15. package/dist/builtin.d.ts.map +1 -1
  16. package/dist/builtin.js +1766 -864
  17. package/dist/builtin.js.map +3 -3
  18. package/dist/codebase-index/background-indexer.d.ts +1 -1
  19. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  20. package/dist/codebase-index/bm25.d.ts +1 -2
  21. package/dist/codebase-index/bm25.d.ts.map +1 -1
  22. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
  23. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
  24. package/dist/codebase-index/codebase-stats-tool.d.ts +7 -0
  25. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
  26. package/dist/codebase-index/index-service.d.ts +11 -1
  27. package/dist/codebase-index/index-service.d.ts.map +1 -1
  28. package/dist/codebase-index/index.d.ts +2 -1
  29. package/dist/codebase-index/index.d.ts.map +1 -1
  30. package/dist/codebase-index/index.js +888 -254
  31. package/dist/codebase-index/index.js.map +3 -3
  32. package/dist/codebase-index/indexer.d.ts.map +1 -1
  33. package/dist/codebase-index/refs-extractor.d.ts +2 -17
  34. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  35. package/dist/codebase-index/schema.d.ts +43 -1
  36. package/dist/codebase-index/schema.d.ts.map +1 -1
  37. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  38. package/dist/codebase-index/worker-protocol.d.ts +1 -0
  39. package/dist/codebase-index/worker-protocol.d.ts.map +1 -1
  40. package/dist/codebase-index/worker.js +738 -212
  41. package/dist/codebase-index/worker.js.map +3 -3
  42. package/dist/codebase-index/writer.d.ts +60 -1
  43. package/dist/codebase-index/writer.d.ts.map +1 -1
  44. package/dist/e2e.js +477 -0
  45. package/dist/e2e.js.map +7 -0
  46. package/dist/exec.d.ts +1 -1
  47. package/dist/exec.d.ts.map +1 -1
  48. package/dist/exec.js +60 -5
  49. package/dist/exec.js.map +3 -3
  50. package/dist/fetch.js +2 -2
  51. package/dist/fetch.js.map +2 -2
  52. package/dist/format.js +61 -14
  53. package/dist/format.js.map +3 -3
  54. package/dist/glob.js +2 -2
  55. package/dist/glob.js.map +1 -1
  56. package/dist/grep.d.ts.map +1 -1
  57. package/dist/grep.js +2 -2
  58. package/dist/grep.js.map +2 -2
  59. package/dist/index.d.ts +3 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2001 -873
  62. package/dist/index.js.map +3 -3
  63. package/dist/install.js +61 -14
  64. package/dist/install.js.map +3 -3
  65. package/dist/kanban.d.ts +21 -2
  66. package/dist/kanban.d.ts.map +1 -1
  67. package/dist/kanban.js +713 -572
  68. package/dist/kanban.js.map +3 -3
  69. package/dist/languages/detect.d.ts.map +1 -1
  70. package/dist/languages/index.js +61 -14
  71. package/dist/languages/index.js.map +3 -3
  72. package/dist/languages/types.d.ts +7 -0
  73. package/dist/languages/types.d.ts.map +1 -1
  74. package/dist/lint.js +61 -14
  75. package/dist/lint.js.map +3 -3
  76. package/dist/outdated.js +61 -14
  77. package/dist/outdated.js.map +3 -3
  78. package/dist/pack.js +1763 -861
  79. package/dist/pack.js.map +3 -3
  80. package/dist/plan.js +4 -0
  81. package/dist/plan.js.map +2 -2
  82. package/dist/process-registry.js +12 -5
  83. package/dist/process-registry.js.map +2 -2
  84. package/dist/read.d.ts.map +1 -1
  85. package/dist/read.js.map +2 -2
  86. package/dist/search.js +2 -2
  87. package/dist/search.js.map +2 -2
  88. package/dist/session-kanban.d.ts +9 -0
  89. package/dist/session-kanban.d.ts.map +1 -1
  90. package/dist/session-kanban.js +197 -3
  91. package/dist/session-kanban.js.map +2 -2
  92. package/dist/task.js +4 -0
  93. package/dist/task.js.map +2 -2
  94. package/dist/test.js +61 -14
  95. package/dist/test.js.map +3 -3
  96. package/dist/todo.js +4 -0
  97. package/dist/todo.js.map +2 -2
  98. package/dist/tree.js +2 -2
  99. package/dist/tree.js.map +1 -1
  100. package/dist/typecheck.js +61 -14
  101. package/dist/typecheck.js.map +3 -3
  102. package/package.json +13 -5
@@ -1,4 +1,4 @@
1
- import type { FileMeta, IndexStats, Ref, SearchResult, Symbol as IndexSymbol, SymbolKind, SymbolLang } from './schema.js';
1
+ import type { FileMeta, IndexStats, Ref, SearchResult, Symbol as IndexSymbol, SymbolKind, SymbolLang, CodeMapGraph } from './schema.js';
2
2
  /**
3
3
  * Resolve the per-project index directory. By default it lives under the
4
4
  * global project dir (`~/.wrongstack/projects/<hash>/codebase-index`),
@@ -15,6 +15,39 @@ export declare function resolveIndexDir(projectRoot: string, override?: string):
15
15
  export declare function codebaseIndexDirOverride(ctx: {
16
16
  meta?: Record<string, unknown>;
17
17
  }): string | undefined;
18
+ /**
19
+ * Warm-connection pool for IndexStore instances.
20
+ *
21
+ * Each (projectRoot, indexDir) pair gets one persisted IndexStore. Every read
22
+ * operation (search, stats, graph) acquires the store from the pool instead of
23
+ * opening a fresh SQLite connection, re-parsing the schema, and re-preparing
24
+ * statements. The connection stays warm between calls — only warm-up cost is
25
+ * paid once per project per process lifetime.
26
+ *
27
+ * In WAL mode, readers coexist with the writer connection, so a single pooled
28
+ * connection handles both reads and writes safely. The caller is responsible
29
+ * for serializing writes (the host's promise-chain mutex does this); the pool
30
+ * itself has no lock because the worker processes one message at a time.
31
+ */
32
+ export declare class StorePool {
33
+ private readonly stores;
34
+ private key;
35
+ /** Borrow a store. Creates it on first access for this key. */
36
+ acquire(projectRoot: string, opts?: {
37
+ indexDir?: string | undefined;
38
+ }): IndexStore;
39
+ /** Return the store to the pool. The connection stays warm for subsequent
40
+ * operations on the same (projectRoot, indexDir). */
41
+ release(_store: IndexStore): void;
42
+ /** Close every pooled connection and drain the pool. Call on shutdown. */
43
+ closeAll(): void;
44
+ /** Remove one store from the pool. Used by tests that need isolation. */
45
+ evict(projectRoot: string, indexDir?: string): void;
46
+ /** True when the pool holds a connection for the given key. */
47
+ has(projectRoot: string, indexDir?: string): boolean;
48
+ }
49
+ /** Process-wide singleton pool. */
50
+ export declare const indexStorePool: StorePool;
18
51
  export declare class IndexStore {
19
52
  private db;
20
53
  /** Absolute path to this project's index directory. */
@@ -115,6 +148,8 @@ export declare class IndexStore {
115
148
  getMaxSymbolId(): number;
116
149
  getStats(): IndexStats;
117
150
  setLastIndexed(ts: number): void;
151
+ getMetadata(key: string): string | undefined;
152
+ setMetadata(key: string, value: string): void;
118
153
  clearAll(): void;
119
154
  /**
120
155
  * Insert cross-references for a given source symbol id.
@@ -184,6 +219,30 @@ export declare class IndexStore {
184
219
  */
185
220
  findRefsFrom(symbolId: number): Ref[];
186
221
  private sizeBytes;
222
+ /**
223
+ * Derive a monorepo package name from an absolute file path.
224
+ * Handles both `packages/<name>/...` (workspace) and `src/<name>/...` layouts.
225
+ * Returns `undefined` for files outside any recognisable package structure.
226
+ */
227
+ private static derivePackage;
228
+ private static packageFromImport;
229
+ private static resolveRelativeImport;
230
+ /**
231
+ * Package-level graph: each workspace package is a node; edges are derived
232
+ * from cross-package symbol references (a symbol in package A references a
233
+ * symbol resolved in package B). Node metadata includes symbol/file counts.
234
+ */
235
+ getPackageGraph(): CodeMapGraph;
236
+ /**
237
+ * File-level graph for a single package: each file is a node; edges are
238
+ * derived from cross-file symbol references within the package.
239
+ */
240
+ getFileGraph(packageFilter: string): CodeMapGraph;
241
+ /**
242
+ * Symbol-level graph for a single file: each symbol is a node; edges are
243
+ * derived from intra-file and cross-file symbol references (who calls whom).
244
+ */
245
+ getSymbolGraph(fileFilter: string): CodeMapGraph;
187
246
  close(): void;
188
247
  }
189
248
  //# sourceMappingURL=writer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/codebase-index/writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/codebase-index/writer.ts"],"names":[],"mappings":"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"}
package/dist/e2e.js ADDED
@@ -0,0 +1,477 @@
1
+ // src/e2e.ts
2
+ import { open, readdir } from "node:fs/promises";
3
+ import * as path2 from "node:path";
4
+
5
+ // src/_util.ts
6
+ import * as fsp from "node:fs/promises";
7
+ import * as path from "node:path";
8
+ import * as Core from "@wrongstack/core";
9
+ function resolvePath(input, ctx) {
10
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
11
+ }
12
+ function allowedRoots(ctx) {
13
+ return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
14
+ }
15
+ function isInsideAny(target, roots) {
16
+ return roots.some((root) => {
17
+ const rel = path.relative(root, target);
18
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
19
+ });
20
+ }
21
+ function ensureInsideRoot(absPath, ctx) {
22
+ const target = path.resolve(absPath);
23
+ if (ctx.allowOutsideProjectRoot) return target;
24
+ if (isInsideAny(target, allowedRoots(ctx))) return target;
25
+ throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
26
+ }
27
+ function safeResolve(input, ctx) {
28
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
29
+ }
30
+ async function assertRealInsideRoot(absPath, ctx) {
31
+ if (ctx.allowOutsideProjectRoot) return;
32
+ const realRoots = await Promise.all(
33
+ allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
34
+ );
35
+ let probe = absPath;
36
+ for (; ; ) {
37
+ let real;
38
+ try {
39
+ real = await fsp.realpath(probe);
40
+ } catch (err) {
41
+ if (err.code === "ENOENT") {
42
+ const parent = path.dirname(probe);
43
+ if (parent === probe) return;
44
+ probe = parent;
45
+ continue;
46
+ }
47
+ throw err;
48
+ }
49
+ if (isInsideAny(real, realRoots)) return;
50
+ throw new Error(
51
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
52
+ );
53
+ }
54
+ }
55
+ async function safeResolveReal(input, ctx) {
56
+ const abs = safeResolve(input, ctx);
57
+ await assertRealInsideRoot(abs, ctx);
58
+ return abs;
59
+ }
60
+
61
+ // src/e2e.ts
62
+ async function readBoundedText(filePath, maxBytes) {
63
+ let handle;
64
+ try {
65
+ handle = await open(filePath, "r");
66
+ const info = await handle.stat();
67
+ if (!info.isFile() || info.size > maxBytes) return void 0;
68
+ const buffer = Buffer.alloc(info.size);
69
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
70
+ return buffer.subarray(0, bytesRead).toString("utf8");
71
+ } catch {
72
+ return void 0;
73
+ } finally {
74
+ await handle?.close().catch(() => void 0);
75
+ }
76
+ }
77
+ var CONFIG_NAMES = /* @__PURE__ */ new Map([
78
+ ["playwright.config.ts", "playwright"],
79
+ ["playwright.config.js", "playwright"],
80
+ ["playwright.config.mts", "playwright"],
81
+ ["playwright.config.mjs", "playwright"],
82
+ ["playwright.config.cts", "playwright"],
83
+ ["playwright.config.cjs", "playwright"],
84
+ ["cypress.config.ts", "cypress"],
85
+ ["cypress.config.js", "cypress"],
86
+ ["cypress.config.mts", "cypress"],
87
+ ["cypress.config.mjs", "cypress"],
88
+ ["cypress.config.cts", "cypress"],
89
+ ["cypress.config.cjs", "cypress"],
90
+ ["cypress.json", "cypress"]
91
+ ]);
92
+ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
93
+ ".git",
94
+ ".hg",
95
+ ".svn",
96
+ ".wrongstack",
97
+ "node_modules",
98
+ "dist",
99
+ "build",
100
+ "coverage",
101
+ "coverage-html",
102
+ "playwright-report",
103
+ "test-results",
104
+ ".next"
105
+ ]);
106
+ var MAX_SCAN_DIRECTORIES = 4e3;
107
+ var MAX_PACKAGE_BYTES = 512 * 1024;
108
+ var MAX_CONFIG_BYTES = 512 * 1024;
109
+ var MAX_SPEC_SAMPLES = 20;
110
+ function relativePath(root, target) {
111
+ const value = path2.relative(root, target) || ".";
112
+ return value.split(path2.sep).join("/");
113
+ }
114
+ function escapeRegExp(value) {
115
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
116
+ }
117
+ function staticString(source, property) {
118
+ const key = escapeRegExp(property);
119
+ const direct = source.match(
120
+ new RegExp(`(?:['"]?${key}['"]?)\\s*:\\s*(['"\\x60])([^'"\\x60\\r\\n]*)\\1`)
121
+ );
122
+ if (direct?.[2] && !direct[2].includes("${")) return direct[2];
123
+ const sameLineFallback = source.match(
124
+ new RegExp(`(?:['"]?${key}['"]?)\\s*:[^\\r\\n]{0,300}?(['"\\x60])([^'"\\x60\\r\\n]*)\\1`)
125
+ );
126
+ return sameLineFallback?.[2] && !sameLineFallback[2].includes("${") ? sameLineFallback[2] : void 0;
127
+ }
128
+ function staticNumber(source, property) {
129
+ const key = escapeRegExp(property);
130
+ const match = source.match(new RegExp(`(?:['"]?${key}['"]?)\\s*:\\s*(\\d{2,5})\\b`));
131
+ return match?.[1] ? Number.parseInt(match[1], 10) : void 0;
132
+ }
133
+ function serverHints(framework, source) {
134
+ if (framework === "playwright" && /\bwebServer\s*:/.test(source)) {
135
+ const command = staticString(source, "command");
136
+ const url = staticString(source, "url");
137
+ const port = staticNumber(source, "port");
138
+ return [
139
+ {
140
+ kind: "managed",
141
+ ...command ? { command } : {},
142
+ ...url ? { url } : {},
143
+ ...port ? { port } : {},
144
+ source: "static-config",
145
+ incomplete: !command || !url && !port
146
+ }
147
+ ];
148
+ }
149
+ const baseUrl = staticString(source, "baseURL") ?? staticString(source, "baseUrl");
150
+ return baseUrl ? [{ kind: "attach", url: baseUrl, source: "static-config", incomplete: false }] : [];
151
+ }
152
+ function configuredSpecPattern(framework, source) {
153
+ if (framework === "playwright") {
154
+ const testDir = staticString(source, "testDir") ?? "./tests";
155
+ return `${testDir.replace(/\/$/, "")}/**/*.{spec,test}.{js,jsx,ts,tsx,mjs,cjs}`;
156
+ }
157
+ return staticString(source, "specPattern") ?? "cypress/{e2e,integration}/**/*.cy.{js,jsx,ts,tsx,mjs,cjs}";
158
+ }
159
+ async function scanWorkspace(root, maxDepth, signal) {
160
+ const result = {
161
+ configs: [],
162
+ packageFiles: [],
163
+ scannedDirectories: 0,
164
+ truncated: false
165
+ };
166
+ const queue = [{ directory: root, depth: 0 }];
167
+ while (queue.length > 0) {
168
+ signal.throwIfAborted();
169
+ const current = queue.shift();
170
+ if (!current) break;
171
+ result.scannedDirectories += 1;
172
+ if (result.scannedDirectories > MAX_SCAN_DIRECTORIES) {
173
+ result.truncated = true;
174
+ break;
175
+ }
176
+ let entries;
177
+ try {
178
+ entries = await readdir(current.directory, { withFileTypes: true });
179
+ } catch {
180
+ continue;
181
+ }
182
+ for (const entry of entries) {
183
+ signal.throwIfAborted();
184
+ const absolutePath = path2.join(current.directory, entry.name);
185
+ if (entry.isFile()) {
186
+ if (entry.name === "package.json") result.packageFiles.push(absolutePath);
187
+ const framework = CONFIG_NAMES.get(entry.name);
188
+ if (framework) result.configs.push({ framework, absolutePath });
189
+ } else if (entry.isDirectory() && current.depth < maxDepth && !SKIP_DIRECTORIES.has(entry.name) && !entry.name.startsWith("coverage-") && !entry.name.startsWith("cov-")) {
190
+ queue.push({ directory: absolutePath, depth: current.depth + 1 });
191
+ }
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+ async function readPackageInfo(packagePath) {
197
+ try {
198
+ const raw = await readBoundedText(packagePath, MAX_PACKAGE_BYTES);
199
+ if (raw === void 0) return void 0;
200
+ const parsed = JSON.parse(raw);
201
+ const scripts = parsed.scripts && typeof parsed.scripts === "object" ? Object.fromEntries(
202
+ Object.entries(parsed.scripts).filter(
203
+ (entry) => typeof entry[1] === "string"
204
+ )
205
+ ) : {};
206
+ const dependencies = /* @__PURE__ */ new Set();
207
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
208
+ const value = parsed[field];
209
+ if (!value || typeof value !== "object") continue;
210
+ for (const name of Object.keys(value)) dependencies.add(name);
211
+ }
212
+ const declared = typeof parsed.packageManager === "string" ? parsed.packageManager.split("@")[0] ?? "" : "";
213
+ const packageManager = ["pnpm", "npm", "yarn", "bun"].includes(declared) ? declared : void 0;
214
+ return { path: packagePath, scripts, dependencies, packageManager };
215
+ } catch {
216
+ return void 0;
217
+ }
218
+ }
219
+ function frameworkFromPackage(info) {
220
+ const found = /* @__PURE__ */ new Set();
221
+ if (info.dependencies.has("@playwright/test") || info.dependencies.has("playwright")) {
222
+ found.add("playwright");
223
+ }
224
+ if (info.dependencies.has("cypress")) found.add("cypress");
225
+ for (const command of Object.values(info.scripts)) {
226
+ if (/\bplaywright(?:\.cmd)?\b/i.test(command)) found.add("playwright");
227
+ if (/\bcypress(?:\.cmd)?\b/i.test(command)) found.add("cypress");
228
+ }
229
+ return [...found];
230
+ }
231
+ async function detectPackageManager(projectRoot, scanRoot, declared) {
232
+ if (declared) return declared;
233
+ let directory = projectRoot;
234
+ while (true) {
235
+ const names = /* @__PURE__ */ new Set();
236
+ try {
237
+ for (const entry of await readdir(directory)) names.add(entry);
238
+ } catch {
239
+ }
240
+ if (names.has("pnpm-lock.yaml")) return "pnpm";
241
+ if (names.has("yarn.lock")) return "yarn";
242
+ if (names.has("bun.lock") || names.has("bun.lockb")) return "bun";
243
+ if (names.has("package-lock.json") || names.has("npm-shrinkwrap.json")) return "npm";
244
+ if (directory === scanRoot) break;
245
+ const parent = path2.dirname(directory);
246
+ const relativeParent = path2.relative(scanRoot, parent);
247
+ if (parent === directory || relativeParent.startsWith("..") || path2.isAbsolute(relativeParent)) {
248
+ break;
249
+ }
250
+ directory = parent;
251
+ }
252
+ return "npm";
253
+ }
254
+ function matchingScripts(info, framework) {
255
+ if (!info) return [];
256
+ const matcher = framework === "playwright" ? /\bplaywright(?:\.cmd)?\b/i : /\bcypress(?:\.cmd)?\b/i;
257
+ return Object.entries(info.scripts).filter(([, command]) => matcher.test(command)).map(([name]) => name).sort((a, b) => {
258
+ const preferred = ["test:e2e", "e2e", "e2e:test"];
259
+ const ai = preferred.indexOf(a);
260
+ const bi = preferred.indexOf(b);
261
+ if (ai !== -1 || bi !== -1) return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
262
+ return a.localeCompare(b);
263
+ });
264
+ }
265
+ function executionPlan(framework, manager, root, configPath, scripts) {
266
+ const script = scripts[0];
267
+ if (script) {
268
+ return {
269
+ command: manager,
270
+ args: ["run", script],
271
+ cwd: root,
272
+ source: "package-script",
273
+ script
274
+ };
275
+ }
276
+ const runnerArgs = framework === "playwright" ? ["playwright", "test"] : ["cypress", "run"];
277
+ if (configPath) {
278
+ runnerArgs.push(framework === "playwright" ? "--config" : "--config-file", configPath);
279
+ }
280
+ return {
281
+ command: manager === "bun" ? "bunx" : manager,
282
+ args: manager === "bun" ? runnerArgs : manager === "npm" ? ["exec", "--", ...runnerArgs] : ["exec", ...runnerArgs],
283
+ cwd: root,
284
+ source: "framework-default"
285
+ };
286
+ }
287
+ function isSpec(framework, filename) {
288
+ if (framework === "cypress") return /\.cy\.(?:[cm]?[jt]sx?)$/i.test(filename);
289
+ return /\.(?:spec|test)\.(?:[cm]?[jt]sx?)$/i.test(filename);
290
+ }
291
+ async function collectSpecs(root, framework, testDirectory, signal) {
292
+ const roots = testDirectory ? [path2.resolve(root, testDirectory)] : framework === "cypress" ? [path2.join(root, "cypress", "e2e"), path2.join(root, "cypress", "integration")] : [path2.join(root, "tests")];
293
+ const samples = [];
294
+ let count = 0;
295
+ let scanned = 0;
296
+ const queue = [...roots];
297
+ while (queue.length > 0) {
298
+ signal.throwIfAborted();
299
+ const directory = queue.shift();
300
+ if (!directory) break;
301
+ scanned += 1;
302
+ if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
303
+ let entries;
304
+ try {
305
+ entries = await readdir(directory, { withFileTypes: true });
306
+ } catch {
307
+ continue;
308
+ }
309
+ for (const entry of entries) {
310
+ signal.throwIfAborted();
311
+ const target = path2.join(directory, entry.name);
312
+ if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);
313
+ else if (entry.isFile() && isSpec(framework, entry.name)) {
314
+ count += 1;
315
+ if (samples.length < MAX_SPEC_SAMPLES) samples.push(relativePath(root, target));
316
+ }
317
+ }
318
+ }
319
+ return { count, samples, truncated: count > samples.length };
320
+ }
321
+ async function readConfig(configPath) {
322
+ if (!configPath) return "";
323
+ return await readBoundedText(configPath, MAX_CONFIG_BYTES) ?? "";
324
+ }
325
+ function nearestPackage(projectRoot, packagesByDirectory, scanRoot) {
326
+ let directory = projectRoot;
327
+ while (true) {
328
+ const found = packagesByDirectory.get(directory);
329
+ if (found) return found;
330
+ if (directory === scanRoot) return void 0;
331
+ const parent = path2.dirname(directory);
332
+ if (parent === directory || relativePath(scanRoot, parent).startsWith("..")) return void 0;
333
+ directory = parent;
334
+ }
335
+ }
336
+ async function discoverE2EProjects(root, options) {
337
+ const maxDepth = Math.max(0, Math.min(options.maxDepth ?? 5, 8));
338
+ const scan = await scanWorkspace(root, maxDepth, options.signal);
339
+ const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(
340
+ (info) => Boolean(info)
341
+ );
342
+ const packagesByDirectory = new Map(packages.map((info) => [path2.dirname(info.path), info]));
343
+ const candidates = /* @__PURE__ */ new Map();
344
+ for (const config of scan.configs) {
345
+ if (options.framework && options.framework !== "all" && config.framework !== options.framework) {
346
+ continue;
347
+ }
348
+ const projectRoot = path2.dirname(config.absolutePath);
349
+ candidates.set(`${config.framework}:${projectRoot}`, {
350
+ framework: config.framework,
351
+ root: projectRoot,
352
+ configPath: config.absolutePath
353
+ });
354
+ }
355
+ for (const info of packages) {
356
+ const projectRoot = path2.dirname(info.path);
357
+ for (const framework of frameworkFromPackage(info)) {
358
+ if (options.framework && options.framework !== "all" && framework !== options.framework)
359
+ continue;
360
+ const key = `${framework}:${projectRoot}`;
361
+ if (!candidates.has(key)) candidates.set(key, { framework, root: projectRoot });
362
+ }
363
+ }
364
+ const projects = [];
365
+ for (const candidate of candidates.values()) {
366
+ options.signal.throwIfAborted();
367
+ const info = nearestPackage(candidate.root, packagesByDirectory, root);
368
+ const source = await readConfig(candidate.configPath);
369
+ const scripts = matchingScripts(info, candidate.framework);
370
+ const manager = await detectPackageManager(candidate.root, root, info?.packageManager);
371
+ const testDirectory = candidate.framework === "playwright" ? staticString(source, "testDir") : void 0;
372
+ const resolvedTestDirectory = testDirectory ? path2.resolve(candidate.root, testDirectory) : void 0;
373
+ const relativeTestDirectory = resolvedTestDirectory ? path2.relative(root, resolvedTestDirectory) : void 0;
374
+ const unsafeTestDirectory = Boolean(
375
+ relativeTestDirectory && (relativeTestDirectory.startsWith("..") || path2.isAbsolute(relativeTestDirectory))
376
+ );
377
+ const specs = options.includeSpecs === false || unsafeTestDirectory ? { count: 0, samples: [], truncated: false } : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);
378
+ const warnings = [];
379
+ if (!candidate.configPath)
380
+ warnings.push("Framework dependency or script found without a config file.");
381
+ if (candidate.configPath && !source) {
382
+ warnings.push("Config could not be read or exceeded the static inspection limit.");
383
+ }
384
+ if (unsafeTestDirectory) {
385
+ warnings.push(
386
+ "Configured testDir resolves outside the requested discovery root and was not scanned."
387
+ );
388
+ }
389
+ const hints = serverHints(candidate.framework, source);
390
+ if (hints.some((hint) => hint.incomplete)) {
391
+ warnings.push("Configured server contains dynamic values; only static hints are shown.");
392
+ }
393
+ projects.push({
394
+ framework: candidate.framework,
395
+ root: relativePath(root, candidate.root),
396
+ ...candidate.configPath ? { configPath: relativePath(root, candidate.configPath) } : {},
397
+ ...info ? { packagePath: relativePath(root, info.path) } : {},
398
+ packageManager: manager,
399
+ evidence: [
400
+ ...candidate.configPath ? [`config:${relativePath(root, candidate.configPath)}`] : [],
401
+ ...info?.dependencies.has(
402
+ candidate.framework === "playwright" ? "@playwright/test" : "cypress"
403
+ ) ? [`dependency:${candidate.framework === "playwright" ? "@playwright/test" : "cypress"}`] : [],
404
+ ...scripts.map((script) => `script:${script}`)
405
+ ],
406
+ scripts,
407
+ serverHints: hints,
408
+ specPattern: configuredSpecPattern(candidate.framework, source),
409
+ specCount: specs.count,
410
+ specSamples: specs.samples,
411
+ specsTruncated: specs.truncated,
412
+ execution: executionPlan(
413
+ candidate.framework,
414
+ manager,
415
+ relativePath(root, candidate.root),
416
+ candidate.configPath ? relativePath(candidate.root, candidate.configPath) : void 0,
417
+ scripts
418
+ ),
419
+ warnings
420
+ });
421
+ }
422
+ projects.sort((a, b) => a.root.localeCompare(b.root) || a.framework.localeCompare(b.framework));
423
+ return {
424
+ root,
425
+ projects,
426
+ scannedDirectories: scan.scannedDirectories,
427
+ scanTruncated: scan.truncated,
428
+ warnings: scan.truncated ? [`Discovery stopped after ${MAX_SCAN_DIRECTORIES} directories; narrow cwd or maxDepth.`] : []
429
+ };
430
+ }
431
+ var e2ePlanTool = {
432
+ name: "e2e_plan",
433
+ category: "Code Quality",
434
+ description: "Discover Playwright and Cypress projects and preview bounded E2E execution plans without loading configs or starting processes.",
435
+ usageHint: "Use before running browser E2E tests. The result identifies authoritative configs, package scripts, static server hints, specs, and exact argv. This tool never imports project config or executes commands.",
436
+ permission: "auto",
437
+ mutating: false,
438
+ riskTier: "safe",
439
+ capabilities: ["fs.read"],
440
+ icon: "test",
441
+ timeoutMs: 3e4,
442
+ inputSchema: {
443
+ type: "object",
444
+ properties: {
445
+ cwd: { type: "string", description: "Directory inside the project to inspect." },
446
+ framework: {
447
+ type: "string",
448
+ enum: ["all", "playwright", "cypress"],
449
+ description: "Optional framework filter; defaults to all."
450
+ },
451
+ maxDepth: {
452
+ type: "integer",
453
+ minimum: 0,
454
+ maximum: 8,
455
+ description: "Maximum workspace discovery depth; defaults to 5."
456
+ },
457
+ includeSpecs: {
458
+ type: "boolean",
459
+ description: "Count and sample specs; defaults to true."
460
+ }
461
+ }
462
+ },
463
+ async execute(input, ctx, opts) {
464
+ const root = await safeResolveReal(input.cwd ?? ".", ctx);
465
+ return discoverE2EProjects(root, {
466
+ framework: input.framework,
467
+ maxDepth: input.maxDepth,
468
+ includeSpecs: input.includeSpecs,
469
+ signal: opts.signal
470
+ });
471
+ }
472
+ };
473
+ export {
474
+ discoverE2EProjects,
475
+ e2ePlanTool
476
+ };
477
+ //# sourceMappingURL=e2e.js.map