reasonix 0.4.16 → 0.4.19

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.
package/README.md CHANGED
@@ -143,11 +143,75 @@ worse than visible rejections.
143
143
  inline clip isn't enough).
144
144
  - `/think` — see the model's full R1 reasoning for the last turn
145
145
  (reasoner preset only).
146
+ - `/memory` — show the project's `REASONIX.md` (see below).
147
+ - `/plan` — toggle read-only plan mode (see below).
146
148
  - `/undo` — roll back the last applied edit batch.
147
149
  - `/new` — start fresh in the same directory without losing the
148
150
  session file.
149
151
  - Drop `--no-session` for an ephemeral session that doesn't persist.
150
152
 
153
+ ### Plan mode — review before executing
154
+
155
+ For anything bigger than a typo, the model is encouraged to propose a
156
+ markdown plan first. You'll see a picker with **Approve / Refine /
157
+ Cancel**:
158
+
159
+ ```
160
+ reasonix code › 把 auth 从 JWT 迁移到 session cookies
161
+
162
+ ▸ plan submitted — awaiting your review
163
+ ────────────────────────────────────────
164
+ ## Summary
165
+ Swap JWT middleware for session cookies, keep user table intact.
166
+
167
+ ## Files
168
+ - src/auth/middleware.ts — replace `verifyJwt` with `readSession`
169
+ - src/auth/session.ts — new file, in-memory store + signed cookie
170
+ - src/routes/login.ts — return Set-Cookie instead of a token
171
+ - tests/auth/*.test.ts — update fixtures
172
+
173
+ ## Risks
174
+ - Existing logged-in users get logged out (no migration).
175
+ - Session store is in-memory; restart clears sessions.
176
+ ────────────────────────────────────────
177
+ ▸ Approve and implement
178
+ Refine — explore more
179
+ Cancel
180
+ ```
181
+
182
+ **Approve** exits plan mode and the model starts executing.
183
+ **Refine** keeps the model exploring and asking for an updated plan.
184
+ **Cancel** drops it and asks you what you actually want.
185
+
186
+ Small fixes skip this — the model goes straight to `edit_file`.
187
+
188
+ **Force it** with `/plan` — enters an explicit read-only phase where
189
+ the model *must* submit a plan before any edit or non-allowlisted
190
+ shell call will execute. Use it for high-stakes changes where you
191
+ want to audit the plan before the model touches disk. `/plan off` or
192
+ picker Approve/Cancel exits.
193
+
194
+ ### Project memory — `REASONIX.md`
195
+
196
+ Drop a `REASONIX.md` in the project root and its contents are pinned
197
+ into the immutable-prefix system prompt every time you launch
198
+ `reasonix` in that directory. Good for house conventions, domain
199
+ glossary, or things the model keeps forgetting:
200
+
201
+ ```bash
202
+ cat > REASONIX.md <<'EOF'
203
+ # Notes for Reasonix
204
+ - Use snake_case for new Python modules; legacy camelCase modules keep their style.
205
+ - `cargo check` is in the auto-run allowlist; full `cargo test` needs confirmation.
206
+ - The `api/` dir mirrors `backend/` — keep schemas in sync.
207
+ EOF
208
+ ```
209
+
210
+ Re-launch (or `/new`) to pick it up; the prefix is hashed once per
211
+ session to keep the DeepSeek cache warm. `/memory` prints what's
212
+ currently pinned. `REASONIX_MEMORY=off` disables the lookup for CI /
213
+ offline repro.
214
+
151
215
  ```bash
152
216
  npx reasonix code src/ # narrower sandbox (only src/ is writable)
153
217
  npx reasonix code --no-session # ephemeral — nothing saved to disk
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/code/prompt.ts
4
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5
+ import { join as join2 } from "path";
6
+
7
+ // src/project-memory.ts
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { join } from "path";
10
+ var PROJECT_MEMORY_FILE = "REASONIX.md";
11
+ var PROJECT_MEMORY_MAX_CHARS = 8e3;
12
+ function readProjectMemory(rootDir) {
13
+ const path = join(rootDir, PROJECT_MEMORY_FILE);
14
+ if (!existsSync(path)) return null;
15
+ let raw;
16
+ try {
17
+ raw = readFileSync(path, "utf8");
18
+ } catch {
19
+ return null;
20
+ }
21
+ const trimmed = raw.trim();
22
+ if (!trimmed) return null;
23
+ const originalChars = trimmed.length;
24
+ const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
25
+ const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
26
+ \u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
27
+ return { path, content, originalChars, truncated };
28
+ }
29
+ function memoryEnabled() {
30
+ const env = process.env.REASONIX_MEMORY;
31
+ if (env === "off" || env === "false" || env === "0") return false;
32
+ return true;
33
+ }
34
+ function applyProjectMemory(basePrompt, rootDir) {
35
+ if (!memoryEnabled()) return basePrompt;
36
+ const mem = readProjectMemory(rootDir);
37
+ if (!mem) return basePrompt;
38
+ return `${basePrompt}
39
+
40
+ # Project memory (REASONIX.md)
41
+
42
+ The user pinned these notes about this project \u2014 treat them as authoritative context for every turn:
43
+
44
+ \`\`\`
45
+ ${mem.content}
46
+ \`\`\`
47
+ `;
48
+ }
49
+
50
+ // src/code/prompt.ts
51
+ var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.
52
+
53
+ # When to propose a plan (submit_plan)
54
+
55
+ You have a \`submit_plan\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:
56
+
57
+ - Multi-file refactors or renames.
58
+ - Architecture changes (moving modules, splitting / merging files, new abstractions).
59
+ - Anything where "undo" after the fact would be expensive \u2014 migrations, destructive cleanups, API shape changes.
60
+ - When the user's request is ambiguous and multiple reasonable interpretations exist \u2014 propose your reading as a plan and let them confirm.
61
+
62
+ Skip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.
63
+
64
+ Plan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an "Open questions" or "\u5F85\u786E\u8BA4" section \u2014 the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP \u2014 don't call any more tools, wait for the user's verdict.
65
+
66
+ # Plan mode (/plan)
67
+
68
+ The user can ALSO enter "plan mode" via /plan, which is a stronger, explicit constraint:
69
+ - Write tools (edit_file, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
70
+ - Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted shell (git status/log/diff, ls, cat, grep, cargo check, npm test) still work \u2014 use them to investigate.
71
+ - You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
72
+
73
+
74
+ # When to edit vs. when to explore
75
+
76
+ Only propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:
77
+ - analyze, read, explore, describe, or summarize a project
78
+ - explain how something works
79
+ - answer a question about the code
80
+
81
+ In those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.
82
+
83
+ When you do propose edits, the user will review them and decide whether to \`/apply\` or \`/discard\`. Don't assume they'll accept \u2014 write as if each edit will be audited, because it will.
84
+
85
+ # Editing files
86
+
87
+ When you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:
88
+
89
+ path/to/file.ext
90
+ <<<<<<< SEARCH
91
+ exact existing lines from the file, including whitespace
92
+ =======
93
+ the new lines
94
+ >>>>>>> REPLACE
95
+
96
+ Rules:
97
+ - Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.
98
+ - One edit per block. Multiple blocks in one response are fine.
99
+ - To create a new file, leave SEARCH empty:
100
+ path/to/new.ts
101
+ <<<<<<< SEARCH
102
+ =======
103
+ (whole file content here)
104
+ >>>>>>> REPLACE
105
+ - Do NOT use write_file to change existing files \u2014 the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).
106
+ - Paths are relative to the working directory. Don't use absolute paths.
107
+
108
+ # Exploration
109
+
110
+ - Avoid listing or reading inside these common dependency / build directories unless the user explicitly asks about them: node_modules, dist, build, out, .next, .nuxt, .svelte-kit, .git, .venv, venv, __pycache__, target, coverage, .turbo, .cache. They're expensive and usually irrelevant.
111
+ - Prefer search_files / grep over list_directory when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees.
112
+
113
+ # Style
114
+
115
+ - Show edits; don't narrate them in prose. "Here's the fix:" is enough.
116
+ - One short paragraph explaining *why*, then the blocks.
117
+ - If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
118
+ `;
119
+ function codeSystemPrompt(rootDir) {
120
+ const withMemory = applyProjectMemory(CODE_SYSTEM_PROMPT, rootDir);
121
+ const gitignorePath = join2(rootDir, ".gitignore");
122
+ if (!existsSync2(gitignorePath)) return withMemory;
123
+ let content;
124
+ try {
125
+ content = readFileSync2(gitignorePath, "utf8");
126
+ } catch {
127
+ return withMemory;
128
+ }
129
+ const MAX = 2e3;
130
+ const truncated = content.length > MAX ? `${content.slice(0, MAX)}
131
+ \u2026 (truncated ${content.length - MAX} chars)` : content;
132
+ return `${withMemory}
133
+
134
+ # Project .gitignore
135
+
136
+ The user's repo ships this .gitignore \u2014 treat every pattern as "don't traverse or edit inside these paths unless explicitly asked":
137
+
138
+ \`\`\`
139
+ ${truncated}
140
+ \`\`\`
141
+ `;
142
+ }
143
+
144
+ export {
145
+ PROJECT_MEMORY_FILE,
146
+ readProjectMemory,
147
+ memoryEnabled,
148
+ applyProjectMemory,
149
+ CODE_SYSTEM_PROMPT,
150
+ codeSystemPrompt
151
+ };
152
+ //# sourceMappingURL=chunk-HNEWBEWZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/code/prompt.ts","../../src/project-memory.ts"],"sourcesContent":["/**\n * System prompt used by `reasonix code`. Teaches the model:\n *\n * 1. It has a filesystem MCP bridge rooted at the user's CWD.\n * 2. To modify files it emits SEARCH/REPLACE blocks (not\n * `write_file` — that would whole-file rewrite and kill diff\n * reviewability).\n * 3. Read first, edit second — SEARCH must match byte-for-byte.\n * 4. Be concise. The user can read a diff faster than prose.\n *\n * Kept short on purpose. Long system prompts eat context budget that\n * the Cache-First Loop is trying to conserve. The SEARCH/REPLACE spec\n * is the one unavoidable bloat; we trim everything else.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { applyProjectMemory } from \"../project-memory.js\";\n\nexport const CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.\n\n# When to propose a plan (submit_plan)\n\nYou have a \\`submit_plan\\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:\n\n- Multi-file refactors or renames.\n- Architecture changes (moving modules, splitting / merging files, new abstractions).\n- Anything where \"undo\" after the fact would be expensive — migrations, destructive cleanups, API shape changes.\n- When the user's request is ambiguous and multiple reasonable interpretations exist — propose your reading as a plan and let them confirm.\n\nSkip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.\n\nPlan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an \"Open questions\" or \"待确认\" section — the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP — don't call any more tools, wait for the user's verdict.\n\n# Plan mode (/plan)\n\nThe user can ALSO enter \"plan mode\" via /plan, which is a stronger, explicit constraint:\n- Write tools (edit_file, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch — you'll get a tool result like \"unavailable in plan mode\". Don't retry them.\n- Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted shell (git status/log/diff, ls, cat, grep, cargo check, npm test) still work — use them to investigate.\n- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.\n\n\n# When to edit vs. when to explore\n\nOnly propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:\n- analyze, read, explore, describe, or summarize a project\n- explain how something works\n- answer a question about the code\n\nIn those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.\n\nWhen you do propose edits, the user will review them and decide whether to \\`/apply\\` or \\`/discard\\`. Don't assume they'll accept — write as if each edit will be audited, because it will.\n\n# Editing files\n\nWhen you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:\n\npath/to/file.ext\n<<<<<<< SEARCH\nexact existing lines from the file, including whitespace\n=======\nthe new lines\n>>>>>>> REPLACE\n\nRules:\n- Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.\n- One edit per block. Multiple blocks in one response are fine.\n- To create a new file, leave SEARCH empty:\n path/to/new.ts\n <<<<<<< SEARCH\n =======\n (whole file content here)\n >>>>>>> REPLACE\n- Do NOT use write_file to change existing files — the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).\n- Paths are relative to the working directory. Don't use absolute paths.\n\n# Exploration\n\n- Avoid listing or reading inside these common dependency / build directories unless the user explicitly asks about them: node_modules, dist, build, out, .next, .nuxt, .svelte-kit, .git, .venv, venv, __pycache__, target, coverage, .turbo, .cache. They're expensive and usually irrelevant.\n- Prefer search_files / grep over list_directory when you know roughly what you're looking for — it saves context and avoids enumerating huge trees.\n\n# Style\n\n- Show edits; don't narrate them in prose. \"Here's the fix:\" is enough.\n- One short paragraph explaining *why*, then the blocks.\n- If you need to explore first (list / grep / read), do it with tool calls before writing any prose — silence while exploring is fine.\n`;\n\n/**\n * Inject the project's `.gitignore` content into the system prompt as a\n * \"respect this on top of the built-in denylist\" hint. We don't parse\n * the file — we hand it to the model as-is. Truncate long ones so we\n * don't eat context budget on huge generated ignore lists.\n *\n * Stacking order (stable for cache prefix):\n * base prompt → project memory (REASONIX.md) → .gitignore block\n */\nexport function codeSystemPrompt(rootDir: string): string {\n const withMemory = applyProjectMemory(CODE_SYSTEM_PROMPT, rootDir);\n const gitignorePath = join(rootDir, \".gitignore\");\n if (!existsSync(gitignorePath)) return withMemory;\n let content: string;\n try {\n content = readFileSync(gitignorePath, \"utf8\");\n } catch {\n return withMemory;\n }\n const MAX = 2000;\n const truncated =\n content.length > MAX\n ? `${content.slice(0, MAX)}\\n… (truncated ${content.length - MAX} chars)`\n : content;\n return `${withMemory}\n\n# Project .gitignore\n\nThe user's repo ships this .gitignore — treat every pattern as \"don't traverse or edit inside these paths unless explicitly asked\":\n\n\\`\\`\\`\n${truncated}\n\\`\\`\\`\n`;\n}\n","/**\n * Project memory — a user-authored `REASONIX.md` in the project root\n * that gets pinned into the immutable-prefix system prompt.\n *\n * Design notes:\n *\n * - The file lands in `ImmutablePrefix.system`, so the whole memory\n * block is hashed into the cache prefix fingerprint. Editing the\n * file invalidates the prefix; unchanged memory across sessions\n * keeps the DeepSeek prefix cache warm. That matches Pillar 1 —\n * memory is a deliberate, stable prefix, not per-turn drift.\n * - Only one source: the working-root `REASONIX.md`. No parent walk,\n * no `~/.reasonix/REASONIX.md`, no CLAUDE.md fallback. User-global\n * memory can come later; for v1 one file == one mental model.\n * - Truncated at 8 000 chars (≈ 2k tokens). `.gitignore` gets 2 000\n * because it's a constraint dump; memory gets more headroom because\n * it's deliberate instructions.\n * - Opt-out via `REASONIX_MEMORY=off|false|0`. No CLI flag — memory\n * is a file, `rm REASONIX.md` is the other opt-out.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport const PROJECT_MEMORY_FILE = \"REASONIX.md\";\nexport const PROJECT_MEMORY_MAX_CHARS = 8000;\n\nexport interface ProjectMemory {\n /** Absolute path the memory was read from. */\n path: string;\n /** Post-truncation content (may include a \"… (truncated N chars)\" marker). */\n content: string;\n /** Original byte length before truncation. */\n originalChars: number;\n /** True iff `originalChars > PROJECT_MEMORY_MAX_CHARS`. */\n truncated: boolean;\n}\n\n/**\n * Read `REASONIX.md` from `rootDir`. Returns `null` when the file is\n * missing, unreadable, or empty (whitespace-only counts as empty — an\n * empty memory file shouldn't perturb the cache prefix).\n */\nexport function readProjectMemory(rootDir: string): ProjectMemory | null {\n const path = join(rootDir, PROJECT_MEMORY_FILE);\n if (!existsSync(path)) return null;\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n const trimmed = raw.trim();\n if (!trimmed) return null;\n const originalChars = trimmed.length;\n const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;\n const content = truncated\n ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}\\n… (truncated ${\n originalChars - PROJECT_MEMORY_MAX_CHARS\n } chars)`\n : trimmed;\n return { path, content, originalChars, truncated };\n}\n\n/**\n * Resolve whether project memory should be read. Default: on.\n * `REASONIX_MEMORY=off|false|0` turns it off (CI, reproducing issues,\n * intentional offline runs).\n */\nexport function memoryEnabled(): boolean {\n const env = process.env.REASONIX_MEMORY;\n if (env === \"off\" || env === \"false\" || env === \"0\") return false;\n return true;\n}\n\n/**\n * Return `basePrompt` with the project's `REASONIX.md` appended as a\n * \"Project memory\" section. No-op when the file is absent, empty, or\n * memory is disabled via env.\n *\n * The appended block is deterministic — identical input ⇒ identical\n * output — so every session that opens against the same memory file\n * gets the same prefix hash.\n */\nexport function applyProjectMemory(basePrompt: string, rootDir: string): string {\n if (!memoryEnabled()) return basePrompt;\n const mem = readProjectMemory(rootDir);\n if (!mem) return basePrompt;\n return `${basePrompt}\n\n# Project memory (REASONIX.md)\n\nThe user pinned these notes about this project — treat them as authoritative context for every turn:\n\n\\`\\`\\`\n${mem.content}\n\\`\\`\\`\n`;\n}\n"],"mappings":";;;AAeA,SAAS,cAAAA,aAAY,gBAAAC,qBAAoB;AACzC,SAAS,QAAAC,aAAY;;;ACKrB,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AAEd,IAAM,sBAAsB;AAC5B,IAAM,2BAA2B;AAkBjC,SAAS,kBAAkB,SAAuC;AACvE,QAAM,OAAO,KAAK,SAAS,mBAAmB;AAC9C,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,MAAM,MAAM;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,YAAY,gBAAgB;AAClC,QAAM,UAAU,YACZ,GAAG,QAAQ,MAAM,GAAG,wBAAwB,CAAC;AAAA,oBAC3C,gBAAgB,wBAClB,YACA;AACJ,SAAO,EAAE,MAAM,SAAS,eAAe,UAAU;AACnD;AAOO,SAAS,gBAAyB;AACvC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,QAAQ,SAAS,QAAQ,WAAW,QAAQ,IAAK,QAAO;AAC5D,SAAO;AACT;AAWO,SAAS,mBAAmB,YAAoB,SAAyB;AAC9E,MAAI,CAAC,cAAc,EAAG,QAAO;AAC7B,QAAM,MAAM,kBAAkB,OAAO;AACrC,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,GAAG,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB,IAAI,OAAO;AAAA;AAAA;AAGb;;;AD/EO,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8E3B,SAAS,iBAAiB,SAAyB;AACxD,QAAM,aAAa,mBAAmB,oBAAoB,OAAO;AACjE,QAAM,gBAAgBC,MAAK,SAAS,YAAY;AAChD,MAAI,CAACC,YAAW,aAAa,EAAG,QAAO;AACvC,MAAI;AACJ,MAAI;AACF,cAAUC,cAAa,eAAe,MAAM;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,MAAM;AACZ,QAAM,YACJ,QAAQ,SAAS,MACb,GAAG,QAAQ,MAAM,GAAG,GAAG,CAAC;AAAA,oBAAkB,QAAQ,SAAS,GAAG,YAC9D;AACN,SAAO,GAAG,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB,SAAS;AAAA;AAAA;AAGX;","names":["existsSync","readFileSync","join","join","existsSync","readFileSync"]}