@dev-tren/mapd 0.21.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/MASTER_PROMPT.md +134 -0
  3. package/README.md +494 -0
  4. package/SETUP.md +108 -0
  5. package/UAT.md +77 -0
  6. package/package.json +56 -0
  7. package/src/adapters/github-app.js +79 -0
  8. package/src/agents/anthropicClient.js +18 -0
  9. package/src/agents/llm.js +196 -0
  10. package/src/agents/modelResolver.js +87 -0
  11. package/src/agents/provider.js +222 -0
  12. package/src/chat/commandRunner.js +86 -0
  13. package/src/chat/commands.js +275 -0
  14. package/src/chat/intent.js +87 -0
  15. package/src/chat/llmIntent.js +118 -0
  16. package/src/chat/repl.js +471 -0
  17. package/src/cli.js +1408 -0
  18. package/src/config/index.js +197 -0
  19. package/src/config/schema.js +119 -0
  20. package/src/core/assist.js +64 -0
  21. package/src/core/audit.js +63 -0
  22. package/src/core/changes.js +110 -0
  23. package/src/core/confidence.js +0 -0
  24. package/src/core/configLint.js +141 -0
  25. package/src/core/diagnose.js +262 -0
  26. package/src/core/docs.js +140 -0
  27. package/src/core/doctor.js +134 -0
  28. package/src/core/envFiles.js +43 -0
  29. package/src/core/events.js +53 -0
  30. package/src/core/evidence.js +212 -0
  31. package/src/core/findingScoring.js +20 -0
  32. package/src/core/fix.js +192 -0
  33. package/src/core/fixApply.js +172 -0
  34. package/src/core/frameworkEntries.js +247 -0
  35. package/src/core/gates.js +209 -0
  36. package/src/core/graph.js +467 -0
  37. package/src/core/grounding.js +235 -0
  38. package/src/core/handoff.js +157 -0
  39. package/src/core/importResolver.js +218 -0
  40. package/src/core/improve.js +226 -0
  41. package/src/core/integrate.js +169 -0
  42. package/src/core/intelligence.js +212 -0
  43. package/src/core/modernize.js +370 -0
  44. package/src/core/parseCache.js +64 -0
  45. package/src/core/parser.js +536 -0
  46. package/src/core/policy.js +65 -0
  47. package/src/core/polyglot.js +333 -0
  48. package/src/core/proc.js +25 -0
  49. package/src/core/reachability.js +543 -0
  50. package/src/core/regression.js +193 -0
  51. package/src/core/resolution.js +92 -0
  52. package/src/core/retry.js +61 -0
  53. package/src/core/review.js +219 -0
  54. package/src/core/score.js +338 -0
  55. package/src/core/security.js +0 -0
  56. package/src/core/session.js +143 -0
  57. package/src/core/solutions.js +254 -0
  58. package/src/core/staleness.js +45 -0
  59. package/src/core/testGuidance.js +226 -0
  60. package/src/core/theme.js +50 -0
  61. package/src/core/trace.js +151 -0
  62. package/src/core/verify.js +123 -0
  63. package/src/core/view.js +221 -0
  64. package/src/core/viewServer.js +88 -0
  65. package/src/core/watch.js +76 -0
  66. package/src/core/workspace.js +115 -0
  67. package/src/mcp/server.js +48 -0
  68. package/src/mcp/tools.js +423 -0
  69. package/src/server.js +84 -0
@@ -0,0 +1,333 @@
1
+ /**
2
+ * polyglot.js — heuristic parser adapters for languages Map'd has no full AST
3
+ * adapter for yet (Python, Go, Rust, Ruby, Java, PHP).
4
+ *
5
+ * HONESTY CONTRACT (the whole point of this module):
6
+ * - Every node produced here is marked `parserKind: "heuristic"`. Full-AST
7
+ * JS/TS nodes never carry that mark. Downstream consumers treat the two
8
+ * differently on purpose: confidence discounts heuristic files, reachability
9
+ * never asserts "orphan" about them, and diagnose discloses the count.
10
+ * - Extraction is line/regex-based and deliberately conservative: it may MISS
11
+ * functions or imports (disclosed as a limit), but every import edge it
12
+ * emits still goes through existence-checked resolution — a specifier only
13
+ * resolves if the target file is really in the project. No fabricated edges.
14
+ * - The kill switch is `.mapdrc` `mapping.polyglot: false` — you can tell
15
+ * Map'd to stop mapping these languages entirely, and they revert to the
16
+ * old honestly-reported "unsupported" bucket.
17
+ *
18
+ * Adding a language = one entry in EXTRACTORS + one resolver case below.
19
+ */
20
+
21
+ import fs from "node:fs";
22
+ import path from "node:path";
23
+
24
+ /** extension -> language id. Everything here is heuristic-tier. */
25
+ export const POLYGLOT_EXTENSIONS = Object.freeze({
26
+ ".py": "python",
27
+ ".go": "go",
28
+ ".rs": "rust",
29
+ ".rb": "ruby",
30
+ ".java": "java",
31
+ ".php": "php",
32
+ });
33
+
34
+ /** Languages recognized but NOT heuristically parsed — stay in the honest "unsupported" bucket. */
35
+ export const KNOWN_UNSUPPORTED_EXTENSIONS = new Set([".c", ".h", ".cpp", ".hpp", ".cc", ".cs", ".kt", ".kts", ".swift", ".scala", ".ex", ".exs", ".dart", ".lua", ".pl", ".m"]);
36
+
37
+ const stripBom = (s) => (s.charCodeAt(0) === 0xfeff ? s.slice(1) : s);
38
+
39
+ /** Identifier fragment that tolerates unicode word characters (Python/Ruby allow them). */
40
+ const ID = "[\\p{L}_][\\p{L}\\p{N}_]*";
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // per-language extraction — each returns { functions, imports, exports, entryHints }
44
+ // imports: [{ source, names }] where `source` keeps enough language context
45
+ // for resolvePolyglotImport to do existence-checked resolution.
46
+ // ---------------------------------------------------------------------------
47
+
48
+ function extractPython(lines) {
49
+ const functions = [], imports = [], exports = [], entryHints = [];
50
+ const defRe = new RegExp(`^(\\s*)(?:async\\s+)?def\\s+(${ID})\\s*\\(`, "u");
51
+ const classRe = new RegExp(`^(\\s*)class\\s+(${ID})\\b`, "u");
52
+ for (let i = 0; i < lines.length; i++) {
53
+ const line = lines[i];
54
+ let m;
55
+ if ((m = defRe.exec(line))) {
56
+ const topLevel = m[1].length === 0;
57
+ functions.push({ name: m[2], line: i + 1, exported: topLevel && !m[2].startsWith("_"), async: /^\s*async\s/.test(line) });
58
+ if (topLevel && !m[2].startsWith("_")) exports.push(m[2]);
59
+ } else if ((m = classRe.exec(line))) {
60
+ if (m[1].length === 0 && !m[2].startsWith("_")) exports.push(m[2]);
61
+ } else if ((m = /^\s*from\s+(\.*[\w.]*)\s+import\s+(.+)/u.exec(line))) {
62
+ const names = m[2].split(",").map((n) => n.trim().split(/\s+as\s+/)[0]).filter((n) => /^[\w*]+$/u.test(n));
63
+ imports.push({ source: m[1], names });
64
+ } else if ((m = /^\s*import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/u.exec(line))) {
65
+ for (const mod of m[1].split(",").map((s) => s.trim().split(/\s+as\s+/)[0])) imports.push({ source: mod, names: [mod.split(".")[0]] });
66
+ } else if (/^if\s+__name__\s*==\s*["']__main__["']/.test(line)) {
67
+ entryHints.push({ kind: "python-main-guard", detail: `if __name__ == "__main__" at line ${i + 1}` });
68
+ }
69
+ }
70
+ return { functions, imports, exports, entryHints };
71
+ }
72
+
73
+ function extractGo(lines) {
74
+ const functions = [], imports = [], exports = [], entryHints = [];
75
+ let isMainPackage = false, hasMainFn = false, inImportBlock = false;
76
+ const fnRe = /^func\s+(?:\([^)]*\)\s+)?([A-Za-z_]\w*)\s*[([]/;
77
+ for (let i = 0; i < lines.length; i++) {
78
+ const line = lines[i];
79
+ let m;
80
+ if (/^package\s+main\b/.test(line)) isMainPackage = true;
81
+ if (inImportBlock) {
82
+ if (/^\s*\)/.test(line)) { inImportBlock = false; continue; }
83
+ if ((m = /^\s*(?:\w+\s+)?"([^"]+)"/.exec(line))) imports.push({ source: m[1], names: [m[1].split("/").pop()] });
84
+ continue;
85
+ }
86
+ if (/^import\s*\(/.test(line)) { inImportBlock = true; continue; }
87
+ if ((m = /^import\s+(?:\w+\s+)?"([^"]+)"/.exec(line))) imports.push({ source: m[1], names: [m[1].split("/").pop()] });
88
+ if ((m = fnRe.exec(line))) {
89
+ const exported = /^[A-Z]/.test(m[1]);
90
+ functions.push({ name: m[1], line: i + 1, exported, async: false });
91
+ if (exported) exports.push(m[1]);
92
+ if (m[1] === "main") hasMainFn = true;
93
+ }
94
+ }
95
+ if (isMainPackage && hasMainFn) entryHints.push({ kind: "go-main", detail: "package main with func main()" });
96
+ return { functions, imports, exports, entryHints };
97
+ }
98
+
99
+ function extractRust(lines, relFile) {
100
+ const functions = [], imports = [], exports = [], entryHints = [];
101
+ const fnRe = /^\s*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
102
+ for (let i = 0; i < lines.length; i++) {
103
+ const line = lines[i];
104
+ let m;
105
+ if ((m = /^\s*(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)\s*;/.exec(line))) {
106
+ imports.push({ source: `mod:${m[1]}`, names: [m[1]] });
107
+ } else if ((m = /^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+((?:crate|super|self)(?:::[A-Za-z_]\w*)+)/.exec(line))) {
108
+ imports.push({ source: m[1], names: [m[1].split("::").pop()] });
109
+ } else if ((m = fnRe.exec(line))) {
110
+ const exported = !!m[1];
111
+ functions.push({ name: m[2], line: i + 1, exported, async: /\basync\s+fn\b/.test(line) });
112
+ if (exported) exports.push(m[2]);
113
+ if (m[2] === "main" && /(^|\/)main\.rs$/.test(relFile)) entryHints.push({ kind: "rust-main", detail: `fn main() in ${path.posix.basename(relFile)}` });
114
+ }
115
+ }
116
+ return { functions, imports, exports, entryHints };
117
+ }
118
+
119
+ function extractRuby(lines) {
120
+ const functions = [], imports = [], exports = [], entryHints = [];
121
+ const defRe = new RegExp(`^\\s*def\\s+(?:self\\.)?(${ID}[?!=]?)`, "u");
122
+ for (let i = 0; i < lines.length; i++) {
123
+ const line = lines[i];
124
+ let m;
125
+ if ((m = /^\s*require_relative\s+["']([^"']+)["']/.exec(line))) {
126
+ imports.push({ source: `./${m[1]}`, names: [] });
127
+ } else if ((m = /^\s*require\s+["']([^"']+)["']/.exec(line))) {
128
+ imports.push({ source: m[1], names: [] });
129
+ } else if ((m = defRe.exec(line))) {
130
+ functions.push({ name: m[1], line: i + 1, exported: true, async: false });
131
+ exports.push(m[1]);
132
+ } else if ((m = new RegExp(`^\\s*(?:class|module)\\s+(${ID})`, "u").exec(line))) {
133
+ exports.push(m[1]);
134
+ } else if (/^if\s+__FILE__\s*==\s*\$(?:0|PROGRAM_NAME)/.test(line)) {
135
+ entryHints.push({ kind: "ruby-main-guard", detail: `__FILE__ == $0 guard at line ${i + 1}` });
136
+ }
137
+ }
138
+ return { functions, imports, exports: [...new Set(exports)], entryHints };
139
+ }
140
+
141
+ function extractJava(lines) {
142
+ const functions = [], imports = [], exports = [], entryHints = [];
143
+ const methodRe = /^\s*(?:public|protected|private)\s+(?:static\s+)?(?:final\s+)?[\w<>[\],\s.]+?\s+(\w+)\s*\(/;
144
+ const typeRe = /^\s*(?:public\s+)?(?:final\s+|abstract\s+)?(?:class|interface|enum|record)\s+(\w+)/;
145
+ for (let i = 0; i < lines.length; i++) {
146
+ const line = lines[i];
147
+ let m;
148
+ if ((m = /^\s*import\s+(?:static\s+)?([\w.]+)\s*;/.exec(line))) imports.push({ source: m[1], names: [m[1].split(".").pop()] });
149
+ else if ((m = typeRe.exec(line))) exports.push(m[1]);
150
+ else if ((m = methodRe.exec(line)) && !/^(if|for|while|switch|catch|return|new)$/.test(m[1])) {
151
+ const exported = /^\s*public\b/.test(line);
152
+ functions.push({ name: m[1], line: i + 1, exported, async: false });
153
+ if (exported) exports.push(m[1]);
154
+ if (/public\s+static\s+void\s+main\s*\(/.test(line)) entryHints.push({ kind: "java-main", detail: `public static void main at line ${i + 1}` });
155
+ }
156
+ }
157
+ return { functions, imports, exports: [...new Set(exports)], entryHints };
158
+ }
159
+
160
+ function extractPhp(lines) {
161
+ const functions = [], imports = [], exports = [], entryHints = [];
162
+ const fnRe = new RegExp(`^\\s*(?:public\\s+|protected\\s+|private\\s+)?(?:static\\s+)?function\\s+(${ID})\\s*\\(`, "u");
163
+ for (let i = 0; i < lines.length; i++) {
164
+ const line = lines[i];
165
+ let m;
166
+ if ((m = /\b(?:require|include)(?:_once)?\s*\(?\s*(?:__DIR__\s*\.\s*)?["']\/?([^"']+\.php)["']/.exec(line))) {
167
+ imports.push({ source: line.includes("__DIR__") ? `./${m[1].replace(/^\//, "")}` : m[1], names: [] });
168
+ } else if ((m = fnRe.exec(line))) {
169
+ const exported = !/^\s*(?:private|protected)\b/.test(line);
170
+ functions.push({ name: m[1], line: i + 1, exported, async: false });
171
+ if (exported) exports.push(m[1]);
172
+ } else if ((m = new RegExp(`^\\s*(?:abstract\\s+|final\\s+)?(?:class|interface|trait)\\s+(${ID})`, "u").exec(line))) {
173
+ exports.push(m[1]);
174
+ }
175
+ }
176
+ return { functions, imports, exports: [...new Set(exports)], entryHints };
177
+ }
178
+
179
+ const EXTRACTORS = { python: extractPython, go: extractGo, rust: extractRust, ruby: extractRuby, java: extractJava, php: extractPhp };
180
+
181
+ /**
182
+ * Parse one non-JS file heuristically into the same FileNode shape
183
+ * parseJsFile produces, plus `parserKind: "heuristic"` and `entryHints`.
184
+ * Any read/extract failure degrades to an honest unparsed node — never throws.
185
+ */
186
+ export function parsePolyglotFile(absPath, relPath) {
187
+ const lang = POLYGLOT_EXTENSIONS[path.extname(relPath).toLowerCase()];
188
+ const node = {
189
+ file: relPath, lang: lang ?? "unknown", parsed: false, parseErrors: 0,
190
+ parserKind: "heuristic",
191
+ functions: [], imports: [], exports: [], loc: 0,
192
+ moduleType: null, varCount: 0, decoratorRoutes: [], entryHints: [],
193
+ };
194
+ let code;
195
+ try { code = stripBom(fs.readFileSync(absPath, "utf8")); } catch { return node; }
196
+ const lines = code.split(/\r?\n/);
197
+ node.loc = lines.length;
198
+ const extractor = EXTRACTORS[lang];
199
+ if (!extractor) return node;
200
+ try {
201
+ const { functions, imports, exports, entryHints } = extractor(lines, relPath);
202
+ // a shebang is a verified executable marker in any language
203
+ if (lines[0]?.startsWith("#!")) entryHints.push({ kind: "shebang", detail: lines[0].slice(0, 60) });
204
+ node.functions = functions.map((f) => ({ name: f.name, exported: f.exported, async: f.async, loc: 1, params: 0, calls: [] }));
205
+ node.imports = imports;
206
+ node.exports = exports;
207
+ node.entryHints = entryHints;
208
+ node.parsed = true;
209
+ } catch {
210
+ // extraction bug on exotic input: recorded honestly as unparsed, never a crash
211
+ }
212
+ return node;
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // import resolution — existence-checked against the project fileSet, mirroring
217
+ // importResolver.js's contract: {internal} | {external} | {unresolved}.
218
+ // ---------------------------------------------------------------------------
219
+
220
+ const tryCandidates = (fileSet, candidates) => {
221
+ for (const c of candidates) {
222
+ const norm = path.posix.normalize(c);
223
+ if (fileSet.has(norm)) return norm;
224
+ }
225
+ return null;
226
+ };
227
+
228
+ function resolvePython(fromDir, spec, fileSet) {
229
+ // relative: leading dots climb from the importing file's package
230
+ const dots = /^(\.+)/.exec(spec)?.[1].length ?? 0;
231
+ const modPath = spec.slice(dots).split(".").filter(Boolean).join("/");
232
+ const bases = dots > 0
233
+ ? [path.posix.join(fromDir, "../".repeat(dots - 1), modPath)]
234
+ : [modPath, path.posix.join(fromDir, modPath)]; // absolute: project root first, then sibling package
235
+ for (const base of bases.map((b) => path.posix.normalize(b))) {
236
+ const hit = tryCandidates(fileSet, base === "." || base === "" ? [] : [`${base}.py`, `${base}/__init__.py`]);
237
+ if (hit) return { internal: hit };
238
+ if ((base === "." || base === "") && dots > 0) {
239
+ // `from . import x` — the package itself
240
+ const init = tryCandidates(fileSet, [path.posix.join(fromDir, "__init__.py")]);
241
+ if (init) return { internal: init };
242
+ }
243
+ }
244
+ return dots > 0 ? { unresolved: spec } : { external: spec };
245
+ }
246
+
247
+ function resolveGo(spec, fileSet, dirIndex) {
248
+ // Go imports are package (directory) paths. We resolve by longest real
249
+ // directory suffix match and link to that directory's .go files.
250
+ const parts = spec.split("/");
251
+ for (let i = 0; i < parts.length; i++) {
252
+ const suffix = parts.slice(i).join("/");
253
+ const files = dirIndex.get(suffix);
254
+ if (files?.length) return { internalMany: files };
255
+ }
256
+ return { external: spec };
257
+ }
258
+
259
+ function resolveRust(fromDir, spec, fileSet) {
260
+ if (spec.startsWith("mod:")) {
261
+ const name = spec.slice(4);
262
+ const hit = tryCandidates(fileSet, [path.posix.join(fromDir, `${name}.rs`), path.posix.join(fromDir, name, "mod.rs")]);
263
+ return hit ? { internal: hit } : { unresolved: spec };
264
+ }
265
+ const segs = spec.split("::").slice(1); // drop crate/super/self
266
+ const roots = spec.startsWith("crate") ? ["src", "."] : [fromDir];
267
+ for (const root of roots) {
268
+ for (let take = segs.length; take >= 1; take--) {
269
+ const base = path.posix.join(root, ...segs.slice(0, take));
270
+ const hit = tryCandidates(fileSet, [`${base}.rs`, `${base}/mod.rs`]);
271
+ if (hit) return { internal: hit };
272
+ }
273
+ }
274
+ return { unresolved: spec };
275
+ }
276
+
277
+ function resolveRuby(fromDir, spec, fileSet) {
278
+ if (spec.startsWith("./") || spec.startsWith("../")) {
279
+ const base = path.posix.join(fromDir, spec);
280
+ const hit = tryCandidates(fileSet, [base.endsWith(".rb") ? base : `${base}.rb`]);
281
+ return hit ? { internal: hit } : { unresolved: spec };
282
+ }
283
+ const hit = tryCandidates(fileSet, [`lib/${spec}.rb`, `${spec}.rb`]);
284
+ return hit ? { internal: hit } : { external: spec };
285
+ }
286
+
287
+ function resolveJava(spec, fileSet) {
288
+ // import a.b.C -> any project file whose path ends a/b/C.java
289
+ const suffix = `${spec.split(".").join("/")}.java`;
290
+ for (const f of fileSet) if (f === suffix || f.endsWith(`/${suffix}`)) return { internal: f };
291
+ return { external: spec };
292
+ }
293
+
294
+ function resolvePhp(fromDir, spec, fileSet) {
295
+ const base = spec.startsWith("./") || spec.startsWith("../") ? path.posix.join(fromDir, spec) : spec;
296
+ const hit = tryCandidates(fileSet, [base, path.posix.join(fromDir, spec)]);
297
+ return hit ? { internal: hit } : { unresolved: spec };
298
+ }
299
+
300
+ /**
301
+ * Build the polyglot resolver once per graph. `dirIndex` (dir suffix -> .go
302
+ * files) makes Go package resolution O(1) per import.
303
+ * resolve(fileNode, spec) -> {internal} | {internalMany} | {external} | {unresolved}
304
+ */
305
+ export function createPolyglotResolver(fileSet) {
306
+ const goDirIndex = new Map();
307
+ for (const f of fileSet) {
308
+ if (!f.endsWith(".go")) continue;
309
+ const dir = path.posix.dirname(f);
310
+ // index every suffix of the directory path: "a/b/c" -> "a/b/c", "b/c", "c"
311
+ const parts = dir === "." ? [] : dir.split("/");
312
+ for (let i = 0; i < parts.length; i++) {
313
+ const suffix = parts.slice(i).join("/");
314
+ if (!goDirIndex.has(suffix)) goDirIndex.set(suffix, []);
315
+ goDirIndex.get(suffix).push(f);
316
+ }
317
+ }
318
+
319
+ return {
320
+ resolve(fileNode, spec) {
321
+ const fromDir = path.posix.dirname(fileNode.file.split(path.sep).join("/"));
322
+ switch (fileNode.lang) {
323
+ case "python": return resolvePython(fromDir, spec, fileSet);
324
+ case "go": return resolveGo(spec, fileSet, goDirIndex);
325
+ case "rust": return resolveRust(fromDir, spec, fileSet);
326
+ case "ruby": return resolveRuby(fromDir, spec, fileSet);
327
+ case "java": return resolveJava(spec, fileSet);
328
+ case "php": return resolvePhp(fromDir, spec, fileSet);
329
+ default: return { unresolved: spec };
330
+ }
331
+ },
332
+ };
333
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * proc.js — spawn package-manager commands the same way on every OS.
3
+ *
4
+ * On Windows, npm/npx/pnpm/yarn/corepack are `.cmd` shims, and Node ≥20
5
+ * refuses to spawn a `.cmd` without a shell (CVE-2024-27980) — every spawn
6
+ * failed with ENOENT, which silently failed the fix engine's test gate and
7
+ * chat's dev commands on Windows. Running through the shell reintroduces
8
+ * command-line parsing, so any argument containing a cmd.exe metacharacter is
9
+ * REFUSED (throws) rather than escaped: an escape bug would be an injection.
10
+ *
11
+ * Everywhere else, and for any other binary, this is a pass-through.
12
+ */
13
+
14
+ const CMD_SHIMS = new Set(["npm", "npx", "pnpm", "yarn", "corepack"]);
15
+ const CMD_META = /[&|<>^%!"\r\n`]/;
16
+
17
+ /** Returns { file, args, options } to hand to spawn/execFile/execFileSync. */
18
+ export function platformCommand(cmd, args = [], options = {}, platform = process.platform) {
19
+ if (platform !== "win32" || !CMD_SHIMS.has(cmd)) return { file: cmd, args, options };
20
+ for (const a of args) {
21
+ if (CMD_META.test(String(a))) throw new Error(`refusing to pass ${JSON.stringify(a)} to ${cmd} through the Windows shell`);
22
+ }
23
+ const quoted = args.map((a) => (/\s/.test(a) ? `"${a}"` : String(a)));
24
+ return { file: `${cmd}.cmd`, args: quoted, options: { ...options, shell: true, windowsHide: true } };
25
+ }