reasonix 0.37.0 → 0.38.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 (74) hide show
  1. package/README.md +1 -0
  2. package/README.zh-CN.md +1 -0
  3. package/dist/cli/{chat-7257YAPG.js → chat-FPEYKTMI.js} +13 -14
  4. package/dist/cli/{chunk-T52GAWPP.js → chunk-3VTV4WAH.js} +2 -2
  5. package/dist/cli/{chunk-MSKUP6PD.js → chunk-4PNXH2MH.js} +910 -659
  6. package/dist/cli/chunk-4PNXH2MH.js.map +1 -0
  7. package/dist/cli/{chunk-YER7WCHF.js → chunk-A63QT566.js} +24 -10
  8. package/dist/cli/chunk-A63QT566.js.map +1 -0
  9. package/dist/cli/{chunk-4Q3GRJIU.js → chunk-AATCLE5N.js} +2 -2
  10. package/dist/cli/{chunk-BHLHOS5Y.js → chunk-BW2HWSYH.js} +315 -5
  11. package/dist/cli/chunk-BW2HWSYH.js.map +1 -0
  12. package/dist/cli/{chunk-ZJR4QLXB.js → chunk-FB46F6H4.js} +2 -2
  13. package/dist/cli/{chunk-GKZJXYMY.js → chunk-FYKZB6TX.js} +415 -11
  14. package/dist/cli/chunk-FYKZB6TX.js.map +1 -0
  15. package/dist/cli/{chunk-XQIFIB3U.js → chunk-JOFZ6AW5.js} +2 -2
  16. package/dist/cli/{chunk-JGZKTAOH.js → chunk-LMNAMITH.js} +2 -2
  17. package/dist/cli/{chunk-S4GF3HPO.js → chunk-LY352GTC.js} +6 -4
  18. package/dist/cli/chunk-LY352GTC.js.map +1 -0
  19. package/dist/cli/{chunk-VF57YX2M.js → chunk-NYP2DDDV.js} +40 -1
  20. package/dist/cli/chunk-NYP2DDDV.js.map +1 -0
  21. package/dist/cli/{chunk-JULZ7JTO.js → chunk-T5U5JO7Q.js} +11 -8
  22. package/dist/cli/chunk-T5U5JO7Q.js.map +1 -0
  23. package/dist/cli/{chunk-SEFXUF24.js → chunk-YJKLNYCP.js} +113 -24
  24. package/dist/cli/chunk-YJKLNYCP.js.map +1 -0
  25. package/dist/cli/{code-64EG5IU2.js → code-GTE65OUT.js} +23 -17
  26. package/dist/cli/{code-64EG5IU2.js.map → code-GTE65OUT.js.map} +1 -1
  27. package/dist/cli/{commands-FE2UDFBC.js → commands-R4JWISND.js} +3 -4
  28. package/dist/cli/{commands-FE2UDFBC.js.map → commands-R4JWISND.js.map} +1 -1
  29. package/dist/cli/{commit-3IAGB22T.js → commit-TQ4DMUNS.js} +2 -3
  30. package/dist/cli/{commit-3IAGB22T.js.map → commit-TQ4DMUNS.js.map} +1 -1
  31. package/dist/cli/{doctor-BW5HSQDW.js → doctor-GGK2JKTA.js} +6 -7
  32. package/dist/cli/index.js +24 -25
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/{mcp-2RDEQST6.js → mcp-M7I23TQ7.js} +2 -3
  35. package/dist/cli/{mcp-2RDEQST6.js.map → mcp-M7I23TQ7.js.map} +1 -1
  36. package/dist/cli/{mcp-browse-VM5GLRBQ.js → mcp-browse-TWO7RYT4.js} +2 -3
  37. package/dist/cli/{mcp-browse-VM5GLRBQ.js.map → mcp-browse-TWO7RYT4.js.map} +1 -1
  38. package/dist/cli/{prompt-KGIUONO3.js → prompt-ODPFOKSH.js} +2 -2
  39. package/dist/cli/{replay-D7RT2DR7.js → replay-R3QRXPI2.js} +13 -9
  40. package/dist/cli/replay-R3QRXPI2.js.map +1 -0
  41. package/dist/cli/{run-RWCOA32G.js → run-WGSPYYOJ.js} +7 -8
  42. package/dist/cli/{run-RWCOA32G.js.map → run-WGSPYYOJ.js.map} +1 -1
  43. package/dist/cli/{server-6ZW4TQUP.js → server-IZPWQYG3.js} +8 -9
  44. package/dist/cli/{server-6ZW4TQUP.js.map → server-IZPWQYG3.js.map} +1 -1
  45. package/dist/cli/{sessions-5ISNWFMU.js → sessions-E4UH5JYL.js} +7 -8
  46. package/dist/cli/{sessions-5ISNWFMU.js.map → sessions-E4UH5JYL.js.map} +1 -1
  47. package/dist/cli/{setup-HJG23NKJ.js → setup-FTZNN3TZ.js} +60 -15
  48. package/dist/cli/setup-FTZNN3TZ.js.map +1 -0
  49. package/dist/cli/{version-BXAN7Q4V.js → version-MDVCFTKA.js} +7 -8
  50. package/dist/cli/{version-BXAN7Q4V.js.map → version-MDVCFTKA.js.map} +1 -1
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.js +568 -40
  53. package/dist/index.js.map +1 -1
  54. package/package.json +1 -1
  55. package/dist/cli/chunk-BHLHOS5Y.js.map +0 -1
  56. package/dist/cli/chunk-GKZJXYMY.js.map +0 -1
  57. package/dist/cli/chunk-JULZ7JTO.js.map +0 -1
  58. package/dist/cli/chunk-MSKUP6PD.js.map +0 -1
  59. package/dist/cli/chunk-S4GF3HPO.js.map +0 -1
  60. package/dist/cli/chunk-SEFXUF24.js.map +0 -1
  61. package/dist/cli/chunk-VF57YX2M.js.map +0 -1
  62. package/dist/cli/chunk-WUI3P4RA.js +0 -319
  63. package/dist/cli/chunk-WUI3P4RA.js.map +0 -1
  64. package/dist/cli/chunk-YER7WCHF.js.map +0 -1
  65. package/dist/cli/replay-D7RT2DR7.js.map +0 -1
  66. package/dist/cli/setup-HJG23NKJ.js.map +0 -1
  67. /package/dist/cli/{chat-7257YAPG.js.map → chat-FPEYKTMI.js.map} +0 -0
  68. /package/dist/cli/{chunk-T52GAWPP.js.map → chunk-3VTV4WAH.js.map} +0 -0
  69. /package/dist/cli/{chunk-4Q3GRJIU.js.map → chunk-AATCLE5N.js.map} +0 -0
  70. /package/dist/cli/{chunk-ZJR4QLXB.js.map → chunk-FB46F6H4.js.map} +0 -0
  71. /package/dist/cli/{chunk-XQIFIB3U.js.map → chunk-JOFZ6AW5.js.map} +0 -0
  72. /package/dist/cli/{chunk-JGZKTAOH.js.map → chunk-LMNAMITH.js.map} +0 -0
  73. /package/dist/cli/{doctor-BW5HSQDW.js.map → doctor-GGK2JKTA.js.map} +0 -0
  74. /package/dist/cli/{prompt-KGIUONO3.js.map → prompt-ODPFOKSH.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  bootstrapSemanticSearchInCodeMode
4
- } from "./chunk-4Q3GRJIU.js";
4
+ } from "./chunk-AATCLE5N.js";
5
5
  import {
6
6
  chatCommand
7
- } from "./chunk-MSKUP6PD.js";
7
+ } from "./chunk-4PNXH2MH.js";
8
8
  import "./chunk-BQNUJJN7.js";
9
9
  import {
10
10
  preflightStdioSpec
@@ -20,29 +20,29 @@ import {
20
20
  registerMemoryTools,
21
21
  registerPlanTool,
22
22
  registerTodoTool
23
- } from "./chunk-SEFXUF24.js";
23
+ } from "./chunk-YJKLNYCP.js";
24
24
  import {
25
25
  parseMcpSpec
26
26
  } from "./chunk-UNMYFZPZ.js";
27
27
  import "./chunk-XJLZ4HKU.js";
28
28
  import "./chunk-XHQIK7B6.js";
29
- import "./chunk-YER7WCHF.js";
30
- import "./chunk-T52GAWPP.js";
31
- import "./chunk-ZJR4QLXB.js";
29
+ import "./chunk-A63QT566.js";
30
+ import "./chunk-3VTV4WAH.js";
31
+ import "./chunk-FB46F6H4.js";
32
32
  import "./chunk-MHDNZXJJ.js";
33
- import "./chunk-JULZ7JTO.js";
33
+ import "./chunk-T5U5JO7Q.js";
34
34
  import "./chunk-DAEAAVDF.js";
35
35
  import "./chunk-KMWKGPFZ.js";
36
36
  import "./chunk-3Q3C4W66.js";
37
37
  import "./chunk-4DCHFFEY.js";
38
38
  import "./chunk-WJ3YX4PZ.js";
39
- import "./chunk-VF57YX2M.js";
39
+ import "./chunk-NYP2DDDV.js";
40
40
  import "./chunk-SOZE7V7V.js";
41
41
  import "./chunk-6NMWJSES.js";
42
42
  import {
43
43
  JobRegistry,
44
44
  registerShellTools
45
- } from "./chunk-S4GF3HPO.js";
45
+ } from "./chunk-LY352GTC.js";
46
46
  import {
47
47
  SkillStore,
48
48
  detectForeignAgentPlatform
@@ -50,21 +50,22 @@ import {
50
50
  import {
51
51
  MCP_CATALOG
52
52
  } from "./chunk-FM57FNPJ.js";
53
- import "./chunk-XQIFIB3U.js";
54
- import "./chunk-JGZKTAOH.js";
53
+ import "./chunk-JOFZ6AW5.js";
54
+ import "./chunk-LMNAMITH.js";
55
55
  import "./chunk-5X7LZJDE.js";
56
56
  import {
57
57
  sanitizeName
58
58
  } from "./chunk-6CXT5JRM.js";
59
- import "./chunk-GKZJXYMY.js";
59
+ import {
60
+ t
61
+ } from "./chunk-FYKZB6TX.js";
60
62
  import {
61
63
  defaultConfigPath,
62
64
  loadEditMode,
63
65
  loadProjectShellAllowed,
64
66
  readConfig,
65
67
  writeConfig
66
- } from "./chunk-BHLHOS5Y.js";
67
- import "./chunk-WUI3P4RA.js";
68
+ } from "./chunk-BW2HWSYH.js";
68
69
  import "./chunk-ZTLZO42A.js";
69
70
  import "./chunk-ORM6PK57.js";
70
71
  import "./chunk-CRPQUBP6.js";
@@ -343,7 +344,7 @@ function quoteIfNeeded(s) {
343
344
  // src/cli/commands/code.tsx
344
345
  async function codeCommand(opts = {}) {
345
346
  markPhase("code_command_enter");
346
- const { codeSystemPrompt } = await import("./prompt-KGIUONO3.js");
347
+ const { codeSystemPrompt } = await import("./prompt-ODPFOKSH.js");
347
348
  const rootDir = resolve(opts.dir ?? process.cwd());
348
349
  const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
349
350
  const tools = new ToolRegistry();
@@ -382,7 +383,12 @@ async function codeCommand(opts = {}) {
382
383
  semantic.enabled ? "semantic_bootstrap_done_enabled" : "semantic_bootstrap_done_skipped"
383
384
  );
384
385
  process.stderr.write(
385
- `\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)${semantic.enabled ? " \xB7 semantic_search on" : ""}
386
+ `${t("startup.codeRooted", {
387
+ rootDir,
388
+ session: session ?? t("startup.ephemeral"),
389
+ tools: tools.size,
390
+ semantic: semantic.enabled ? t("startup.semanticOn") : ""
391
+ })}
386
392
  `
387
393
  );
388
394
  const foreign = detectForeignAgentPlatform(rootDir);
@@ -441,4 +447,4 @@ async function codeCommand(opts = {}) {
441
447
  export {
442
448
  codeCommand
443
449
  };
444
- //# sourceMappingURL=code-64EG5IU2.js.map
450
+ //# sourceMappingURL=code-GTE65OUT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/code.tsx","../../src/tools/scaffold.ts"],"sourcesContent":["/**\n * `reasonix code [dir]` — opinionated wrapper around `reasonix chat` for\n * code-editing workflows.\n *\n * What it does differently from plain chat:\n * - Registers native filesystem tools rooted at the given directory\n * (CWD by default). No subprocess, no `npx install` step, R1-\n * friendly schemas. Replaced the old `@modelcontextprotocol/server-filesystem`\n * subprocess in 0.4.9 because its `edit_file` argv shape was the\n * biggest driver of R1 DSML hallucinations.\n * - Uses a coding-focused system prompt (src/code/prompt.ts) that\n * teaches the model to propose edits as SEARCH/REPLACE blocks.\n * - Defaults to the `smart` preset (reasoner + harvest) because\n * coding tasks pay back R1 thinking.\n * - Scopes its session to the directory so projects don't share\n * conversation history.\n * - Hooks `codeMode` into the TUI so assistant replies get parsed\n * for SEARCH/REPLACE blocks and applied on disk after each turn.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename, resolve } from \"node:path\";\nimport { loadEditMode, loadProjectShellAllowed, readConfig } from \"../../config.js\";\nimport { bootstrapSemanticSearchInCodeMode } from \"../../index/semantic/tool.js\";\nimport { detectForeignAgentPlatform } from \"../../memory/project.js\";\nimport { sanitizeName } from \"../../memory/session.js\";\nimport { ToolRegistry } from \"../../tools.js\";\nimport { registerChoiceTool } from \"../../tools/choice.js\";\nimport { registerFilesystemTools } from \"../../tools/filesystem.js\";\nimport { JobRegistry } from \"../../tools/jobs.js\";\nimport { registerMemoryTools } from \"../../tools/memory.js\";\nimport { registerPlanTool } from \"../../tools/plan.js\";\nimport { registerScaffoldTools } from \"../../tools/scaffold.js\";\nimport { registerShellTools } from \"../../tools/shell.js\";\nimport { registerTodoTool } from \"../../tools/todo.js\";\nimport { markPhase } from \"../startup-profile.js\";\nimport { chatCommand } from \"./chat.js\";\n\nexport interface CodeOptions {\n /** Directory to root the filesystem tools at. Defaults to process.cwd(). */\n dir?: string;\n /** Override the default `smart` model. */\n model?: string;\n /** Disable session persistence. */\n noSession?: boolean;\n /** Transcript file for replay/diff. */\n transcript?: string;\n /** Skip the session picker — always resume prior messages. */\n forceResume?: boolean;\n /** Skip the session picker — always wipe prior messages and start fresh. */\n forceNew?: boolean;\n /**\n * Soft USD spend cap. Off by default. Same semantics as `chat`:\n * warns at 80%, refuses next turn at 100%. Mid-session adjustable\n * via `/budget <usd>` slash command.\n */\n budgetUsd?: number;\n /** Suppress the auto-launched embedded web dashboard. */\n noDashboard?: boolean;\n /** Inline string appended to the code system prompt after the generated base prompt. */\n systemAppend?: string;\n /** Path to a UTF-8 text file whose contents are appended to the code system prompt. */\n systemAppendFile?: string;\n /** Default true. Pass false (CLI: `--no-alt-screen`) to keep chat output in shell scrollback. */\n altScreen?: boolean;\n /** Default true. Pass false (CLI: `--no-mouse`) to keep terminal-native drag-select unmodified. */\n mouse?: boolean;\n}\n\nexport async function codeCommand(opts: CodeOptions = {}): Promise<void> {\n markPhase(\"code_command_enter\");\n const { codeSystemPrompt } = await import(\"../../code/prompt.js\");\n const rootDir = resolve(opts.dir ?? process.cwd());\n // Per-directory session so switching projects doesn't mix histories.\n // `code-<sanitized-basename>` fits the session name rules without\n // truncating most project names.\n const session = opts.noSession ? undefined : `code-${sanitizeName(basename(rootDir))}`;\n\n // Native filesystem tools. No subprocess, ~50-200 ms faster per call\n // than the MCP server was, and `edit_file` takes a flat SEARCH/REPLACE\n // shape instead of the `string=\"false\"` JSON-in-string array that\n // triggered R1's DSML hallucinations all through 0.4.x.\n const tools = new ToolRegistry();\n // Background-process registry shared between the shell tools and the\n // TUI's /jobs + /kill slashes + exit cleanup. One per `reasonix code`\n // run — orphan prevention on SIGINT / process exit kills everything\n // it owns, so dev servers don't outlive the Reasonix process.\n const jobs = new JobRegistry();\n // Bundled re-registration so `/cwd <path>` can swap every rootDir-\n // dependent tool atomically. ToolRegistry.register is keyed by name\n // and overwrites in-place, so re-calling these against the existing\n // registry replaces the closures cleanly without disturbing tool\n // specs (names/descriptions/params don't reference rootDir, so the\n // prefix cache survives).\n const registerRootedTools = (root: string): void => {\n registerFilesystemTools(tools, { rootDir: root });\n registerShellTools(tools, {\n rootDir: root,\n // Per-project \"always allow\" list persisted from prior ShellConfirm\n // choices; merged on top of the built-in allowlist in shell.ts.\n // GETTER form — re-read every dispatch so a prefix the user adds\n // via ShellConfirm mid-session takes effect on the next shell call\n // instead of waiting for `/new` or a relaunch.\n extraAllowed: () => loadProjectShellAllowed(root),\n // `yolo` edit-mode disables shell confirmations entirely. Re-read\n // from config on each dispatch so /mode yolo (or Shift+Tab cycling\n // through to it) flips the gate live without forcing a relaunch.\n allowAll: () => loadEditMode() === \"yolo\",\n jobs,\n });\n // `remember` / `forget` / `recall_memory` — cross-session user memory.\n // Project scope hashes off rootDir so switching projects gets a fresh\n // per-project memory store; the global scope is shared across runs.\n registerMemoryTools(tools, { projectRoot: root });\n };\n // Async tail to `registerRootedTools`. Kept separate because the FS /\n // shell / memory re-registration above is sync and must happen before\n // the next tool dispatch, while semantic-index probing reads disk and\n // can race ahead in the background. On `/cwd`, App.tsx fires this\n // after the sync swap and surfaces the result via postInfo.\n const reBootstrapSemantic = async (root: string): Promise<{ enabled: boolean }> => {\n const result = await bootstrapSemanticSearchInCodeMode(tools, root);\n if (!result.enabled) tools.unregister(\"semantic_search\");\n return result;\n };\n registerRootedTools(rootDir);\n // `submit_plan` is always in the spec list so the prefix cache stays\n // stable across plan-mode toggles (Pillar 1). The tool itself is a\n // no-op outside plan mode and throws `PlanProposedError` when the\n // user has `/plan`-enabled the session.\n registerPlanTool(tools);\n // `ask_choice` — branching primitive. Independent of plan mode: the\n // model uses it to put a 2–4 way choice in front of the user\n // (strategy, style, library pick) without trying to squeeze the\n // menu into a submit_plan body. Keeping it always-registered\n // preserves the prefix cache across plan-mode toggles.\n registerChoiceTool(tools);\n // `todo_write` — lightweight in-session task tracker, no approval gate.\n // Independent of plan mode (readOnly=true so it stays callable in /plan).\n registerTodoTool(tools);\n // `create_skill` / `add_mcp_server` — let the model scaffold from chat.\n // Both writes go through the same paths the wizard / `/skill new` use,\n // so the on-disk shape stays one source of truth. New servers take\n // effect on next launch (no live client churn).\n registerScaffoldTools(tools, { projectRoot: rootDir });\n // `run_skill` is intentionally NOT registered here — App.tsx wires it\n // up with the subagent runner attached, so `runAs: subagent` skills\n // can spawn isolated child loops. Doing it here would mean the App's\n // re-registration would shadow the no-runner version, which works\n // (last write wins) but obscures the wiring.\n\n // Bootstrap semantic_search. Silent: registers the tool when an\n // on-disk index already exists, skips entirely otherwise. Setup\n // happens via the explicit `reasonix index` command — never\n // by surprise on launch.\n markPhase(\"semantic_bootstrap_start\");\n const semantic = await reBootstrapSemantic(rootDir);\n markPhase(\n semantic.enabled ? \"semantic_bootstrap_done_enabled\" : \"semantic_bootstrap_done_skipped\",\n );\n\n process.stderr.write(\n `▸ reasonix code: rooted at ${rootDir}, session \"${session ?? \"(ephemeral)\"}\" · ${tools.size} native tool(s)${\n semantic.enabled ? \" · semantic_search on\" : \"\"\n }\\n`,\n );\n\n const foreign = detectForeignAgentPlatform(rootDir);\n if (foreign) {\n process.stderr.write(\n `⚠ workspace contains another agent platform's files (${foreign.join(\", \")}). Reasonix Code may read them as project content; relaunch with --dir <your-project> if that's not what you want.\\n`,\n );\n }\n\n // Belt-and-suspenders cleanup: even though spawn(detached:false)\n // should tie child processes to the parent's lifetime, Windows cmd.exe\n // wrappers occasionally leak. We DON'T install SIGINT/SIGTERM\n // handlers here — that overrode Node's default \"exit on Ctrl+C\" with\n // a silent no-op, which made Ctrl+C feel broken in the TUI. App.tsx\n // owns the SIGINT path now (it shows the quit-armed banner and calls\n // exit() on confirmation); this 'exit' hook just guarantees the job\n // registry is drained on the way out, regardless of which exit path\n // fired.\n process.once(\"exit\", () => {\n void jobs.shutdown();\n });\n\n let systemAppendFileContents: string | undefined;\n if (opts.systemAppend !== undefined && opts.systemAppend.trim().length === 0) {\n process.stderr.write(\"--system-append is empty — no prompt text will be appended\\n\");\n }\n if (opts.systemAppendFile) {\n const filePath = resolve(opts.systemAppendFile);\n try {\n systemAppendFileContents = readFileSync(filePath, \"utf8\");\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n process.stderr.write(\n `Error: cannot read --system-append-file \"${filePath}\": ${e.code ? `[${e.code}] ` : \"\"}${e.message}\\n`,\n );\n process.exit(1);\n }\n }\n\n await chatCommand({\n model: opts.model ?? \"deepseek-v4-flash\",\n budgetUsd: opts.budgetUsd,\n system: codeSystemPrompt(rootDir, {\n hasSemanticSearch: semantic.enabled,\n systemAppend: opts.systemAppend,\n systemAppendFile: systemAppendFileContents,\n modelId: opts.model ?? \"deepseek-v4-flash\",\n }),\n transcript: opts.transcript,\n session,\n seedTools: tools,\n codeMode: {\n rootDir,\n jobs,\n reregisterTools: registerRootedTools,\n reBootstrapSemantic,\n },\n mcp: readConfig().mcp,\n forceResume: opts.forceResume,\n forceNew: opts.forceNew,\n noDashboard: opts.noDashboard,\n altScreen: opts.altScreen,\n mouse: opts.mouse,\n });\n}\n","/** Agent-facing tools for scaffolding skills + MCP servers from chat. Persists via the same paths the wizard / `/skill new` use. */\n\nimport { defaultConfigPath, readConfig, writeConfig } from \"../config.js\";\nimport { MCP_CATALOG } from \"../mcp/catalog.js\";\nimport { preflightStdioSpec } from \"../mcp/preflight.js\";\nimport { type McpSpec, parseMcpSpec } from \"../mcp/spec.js\";\nimport { SkillStore } from \"../skills.js\";\nimport type { ToolRegistry } from \"../tools.js\";\n\nexport interface ScaffoldToolsOptions {\n homeDir?: string;\n projectRoot?: string;\n /** Override config path — tests point this at a tmp file. */\n configPath?: string;\n}\n\nconst VALID_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;\nconst VALID_SERVER_NAME = /^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/;\nconst VALID_TOOL_NAME = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;\n\nexport function registerScaffoldTools(\n registry: ToolRegistry,\n opts: ScaffoldToolsOptions = {},\n): ToolRegistry {\n const configPath = opts.configPath ?? defaultConfigPath();\n\n registry.register({\n name: \"create_skill\",\n description:\n 'Scaffold a new skill (`SKILL.md` in `.reasonix/skills/<name>.md`) the user can invoke later via `/skill <name>`. Use this when the user asks the agent to add a playbook, automate a recurring workflow, or capture a multi-step recipe as a named skill. The frontmatter is filled from the structured args here (description / allowed_tools / run_as / model) so the model never has to write raw YAML. Use `run_as: \"subagent\"` for read-and-synthesize playbooks where only the final answer should come back; default `\"inline\"` appends the body to the parent log so the user sees the steps. Refuses to overwrite an existing skill — pick a different name or ask the user to delete the old one.',\n parameters: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description:\n \"Skill identifier — letters/digits/`_`/`-`/`.`, 1–64 chars. Becomes the `name` frontmatter and the `<name>.md` filename.\",\n },\n description: {\n type: \"string\",\n description:\n 'One-line summary shown in the pinned skills index. Lead with the verb (\"Run X and …\") so the parent agent can scan it.',\n },\n body: {\n type: \"string\",\n description:\n \"Markdown body of the skill — the playbook the model follows when invoked. Plain prose + bullets; reference tools by name.\",\n },\n scope: {\n type: \"string\",\n enum: [\"project\", \"global\"],\n description:\n \"`project` = `.reasonix/skills/` under the workspace (default, requires `reasonix code`); `global` = `~/.reasonix/skills/` shared across all repos.\",\n },\n allowed_tools: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Optional whitelist of tool names the subagent registry is scoped to (only meaningful for `run_as: subagent`). Common values: `read_file`, `search_content`, `directory_tree`, `run_command`. Omit to give the subagent the full inherited toolset.\",\n },\n run_as: {\n type: \"string\",\n enum: [\"inline\", \"subagent\"],\n description:\n \"`inline` (default) appends the body to the parent log as a tool result. `subagent` spawns an isolated child loop and only the final answer comes back — use for read-and-synthesize playbooks (explore, research, review).\",\n },\n model: {\n type: \"string\",\n enum: [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n description:\n \"Subagent model override (only meaningful for `run_as: subagent`). Default is the same as `spawn_subagent` — `deepseek-v4-flash`. Set to `deepseek-v4-pro` only when the playbook empirically needs the stronger model.\",\n },\n },\n required: [\"name\", \"description\", \"body\"],\n },\n fn: async (args: {\n name?: unknown;\n description?: unknown;\n body?: unknown;\n scope?: unknown;\n allowed_tools?: unknown;\n run_as?: unknown;\n model?: unknown;\n }) => {\n const name = typeof args.name === \"string\" ? args.name.trim() : \"\";\n if (!VALID_SKILL_NAME.test(name)) {\n return JSON.stringify({\n error: `invalid skill name: ${JSON.stringify(name)} — use letters, digits, _, -, .`,\n });\n }\n const description =\n typeof args.description === \"string\" ? args.description.trim().replace(/\\n+/g, \" \") : \"\";\n if (!description) {\n return JSON.stringify({\n error: \"create_skill requires a non-empty 'description'\",\n });\n }\n const body = typeof args.body === \"string\" ? args.body : \"\";\n if (!body.trim()) {\n return JSON.stringify({ error: \"create_skill requires a non-empty 'body'\" });\n }\n const scope: \"project\" | \"global\" =\n args.scope === \"global\" ? \"global\" : opts.projectRoot ? \"project\" : \"global\";\n const runAs: \"inline\" | \"subagent\" = args.run_as === \"subagent\" ? \"subagent\" : \"inline\";\n const allowedTools = parseAllowedTools(args.allowed_tools);\n if (allowedTools && \"error\" in allowedTools) {\n return JSON.stringify({ error: allowedTools.error });\n }\n const model =\n typeof args.model === \"string\" && args.model.startsWith(\"deepseek-\")\n ? args.model\n : undefined;\n\n const content = serializeSkill({\n name,\n description,\n runAs,\n allowedTools: allowedTools ?? undefined,\n model,\n body,\n });\n\n const store = new SkillStore({\n homeDir: opts.homeDir,\n projectRoot: opts.projectRoot,\n });\n const result = store.createWithContent(name, scope, content);\n if (\"error\" in result) {\n return JSON.stringify({ error: result.error });\n }\n return JSON.stringify({\n success: true,\n path: result.path,\n scope,\n name,\n run_as: runAs,\n });\n },\n });\n\n registry.register({\n name: \"add_mcp_server\",\n description:\n 'Register a new MCP server in the user\\'s Reasonix config (`mcp` array). Takes effect on the next session — does NOT spawn the server now. Use stdio for local commands (npx packages, local binaries), `sse` or `streamable-http` for remote endpoints. Pass `from_catalog: \"<name>\"` (e.g. `\"filesystem\"`, `\"memory\"`, `\"github\"`) to auto-fill `command` + `args` from the bundled catalog — the user still has to supply user-args (filesystem: a sandbox dir; github: GITHUB_PERSONAL_ACCESS_TOKEN in env). Refuses to add a server whose name collides with an existing entry.',\n parameters: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description:\n \"Server name — used as the namespace prefix on every tool the server exposes. Letters/digits/`_`/`-`, must start with a letter or `_`.\",\n },\n transport: {\n type: \"string\",\n enum: [\"stdio\", \"sse\", \"streamable-http\"],\n description:\n \"`stdio` = spawn a local command and pipe MCP over stdin/stdout. `sse` = HTTP+SSE remote. `streamable-http` = Streamable HTTP remote. Required unless `from_catalog` is set.\",\n },\n command: {\n type: \"string\",\n description:\n 'Argv[0] for stdio servers — typically `npx` or a binary path. Required when `transport: \"stdio\"` (and no `from_catalog`).',\n },\n args: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n 'Remaining argv for stdio servers — e.g. `[\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/path/to/dir\"]`. The dir at the tail is enforced to exist by the preflight check.',\n },\n url: {\n type: \"string\",\n description:\n \"Endpoint URL for `sse` / `streamable-http` transports. Must be `http://` or `https://`.\",\n },\n from_catalog: {\n type: \"string\",\n description:\n \"Optional shortcut — name out of the bundled catalog (`filesystem`, `memory`, `github`, `puppeteer`, `everything`). When set, fills `command` + `args` from the catalog entry; you still supply `name` (defaults to the catalog name) and any user-args via `args`.\",\n },\n },\n required: [\"name\"],\n },\n fn: async (args: {\n name?: unknown;\n transport?: unknown;\n command?: unknown;\n args?: unknown;\n url?: unknown;\n from_catalog?: unknown;\n }) => {\n const name = typeof args.name === \"string\" ? args.name.trim() : \"\";\n if (!VALID_SERVER_NAME.test(name)) {\n return JSON.stringify({\n error: `invalid server name: ${JSON.stringify(name)} — must match [a-zA-Z_][a-zA-Z0-9_-]*`,\n });\n }\n\n const specStr = buildSpecString({\n name,\n transport: typeof args.transport === \"string\" ? args.transport : undefined,\n command: typeof args.command === \"string\" ? args.command : undefined,\n argv: Array.isArray(args.args)\n ? (args.args.filter((a) => typeof a === \"string\") as string[])\n : undefined,\n url: typeof args.url === \"string\" ? args.url : undefined,\n fromCatalog: typeof args.from_catalog === \"string\" ? args.from_catalog : undefined,\n });\n if (\"error\" in specStr) {\n return JSON.stringify({ error: specStr.error });\n }\n\n let parsed: McpSpec;\n try {\n parsed = parseMcpSpec(specStr.spec);\n } catch (err) {\n return JSON.stringify({ error: (err as Error).message });\n }\n if (parsed.transport === \"stdio\") {\n try {\n preflightStdioSpec(parsed);\n } catch (err) {\n return JSON.stringify({ error: (err as Error).message });\n }\n }\n\n const cfg = readConfig(configPath);\n const existing = cfg.mcp ?? [];\n const collision = existing.find((s) => parseSpecName(s) === name);\n if (collision) {\n return JSON.stringify({\n error: `MCP server ${JSON.stringify(name)} already registered: ${collision}`,\n });\n }\n cfg.mcp = [...existing, specStr.spec];\n writeConfig(cfg, configPath);\n return JSON.stringify({\n success: true,\n name,\n transport: parsed.transport,\n spec: specStr.spec,\n config_path: configPath,\n active_on_next_launch: true,\n });\n },\n });\n\n return registry;\n}\n\ninterface SerializeSkillArgs {\n name: string;\n description: string;\n runAs: \"inline\" | \"subagent\";\n allowedTools?: readonly string[];\n model?: string;\n body: string;\n}\n\nexport function serializeSkill(args: SerializeSkillArgs): string {\n const lines: string[] = [\"---\", `name: ${args.name}`, `description: ${args.description}`];\n if (args.runAs === \"subagent\") {\n lines.push(\"runAs: subagent\");\n }\n if (args.allowedTools && args.allowedTools.length > 0) {\n lines.push(`allowed-tools: ${args.allowedTools.join(\", \")}`);\n }\n if (args.model) {\n lines.push(`model: ${args.model}`);\n }\n lines.push(\"---\", \"\");\n return `${lines.join(\"\\n\")}\\n${args.body.trim()}\\n`;\n}\n\nfunction parseAllowedTools(raw: unknown): readonly string[] | { error: string } | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!Array.isArray(raw)) {\n return { error: \"'allowed_tools' must be an array of tool-name strings\" };\n }\n const out: string[] = [];\n for (const v of raw) {\n if (typeof v !== \"string\") {\n return { error: \"'allowed_tools' entries must be strings\" };\n }\n const trimmed = v.trim();\n if (!trimmed) continue;\n if (!VALID_TOOL_NAME.test(trimmed)) {\n return { error: `invalid tool name in allowed_tools: ${JSON.stringify(trimmed)}` };\n }\n out.push(trimmed);\n }\n return out.length > 0 ? out : undefined;\n}\n\ninterface BuildSpecInput {\n name: string;\n transport?: string;\n command?: string;\n argv?: string[];\n url?: string;\n fromCatalog?: string;\n}\n\nfunction buildSpecString(input: BuildSpecInput): { spec: string } | { error: string } {\n if (input.fromCatalog) {\n const entry = MCP_CATALOG.find((e) => e.name === input.fromCatalog);\n if (!entry) {\n const known = MCP_CATALOG.map((e) => e.name).join(\", \");\n return {\n error: `unknown catalog entry: ${JSON.stringify(input.fromCatalog)} — known: ${known}`,\n };\n }\n const userArgs = input.argv ?? [];\n if (entry.userArgs && userArgs.length === 0) {\n return {\n error: `catalog entry \"${entry.name}\" needs ${entry.userArgs} — pass it via the 'args' parameter`,\n };\n }\n const tail = userArgs.map(quoteIfNeeded).join(\" \");\n const body = `npx -y ${entry.package}${tail ? ` ${tail}` : \"\"}`;\n return { spec: `${input.name}=${body}` };\n }\n\n const transport = input.transport;\n if (!transport) {\n return { error: \"add_mcp_server requires 'transport' (or 'from_catalog')\" };\n }\n if (transport === \"stdio\") {\n if (!input.command || !input.command.trim()) {\n return { error: \"stdio transport requires 'command'\" };\n }\n const tail = (input.argv ?? []).map(quoteIfNeeded).join(\" \");\n const body = `${quoteIfNeeded(input.command.trim())}${tail ? ` ${tail}` : \"\"}`;\n return { spec: `${input.name}=${body}` };\n }\n if (transport === \"sse\" || transport === \"streamable-http\") {\n if (!input.url || !/^https?:\\/\\//i.test(input.url)) {\n return { error: `${transport} transport requires an http(s):// 'url'` };\n }\n const prefix = transport === \"streamable-http\" ? \"streamable+\" : \"\";\n return { spec: `${input.name}=${prefix}${input.url.trim()}` };\n }\n return { error: `unknown transport: ${JSON.stringify(transport)}` };\n}\n\nfunction parseSpecName(spec: string): string | null {\n const m = spec.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*)=/);\n return m ? (m[1] ?? null) : null;\n}\n\nfunction quoteIfNeeded(s: string): string {\n return /\\s|\"/.test(s) ? `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"` : s;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/code.tsx","../../src/tools/scaffold.ts"],"sourcesContent":["/**\n * `reasonix code [dir]` — opinionated wrapper around `reasonix chat` for\n * code-editing workflows.\n *\n * What it does differently from plain chat:\n * - Registers native filesystem tools rooted at the given directory\n * (CWD by default). No subprocess, no `npx install` step, R1-\n * friendly schemas. Replaced the old `@modelcontextprotocol/server-filesystem`\n * subprocess in 0.4.9 because its `edit_file` argv shape was the\n * biggest driver of R1 DSML hallucinations.\n * - Uses a coding-focused system prompt (src/code/prompt.ts) that\n * teaches the model to propose edits as SEARCH/REPLACE blocks.\n * - Defaults to the `smart` preset (reasoner + harvest) because\n * coding tasks pay back R1 thinking.\n * - Scopes its session to the directory so projects don't share\n * conversation history.\n * - Hooks `codeMode` into the TUI so assistant replies get parsed\n * for SEARCH/REPLACE blocks and applied on disk after each turn.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename, resolve } from \"node:path\";\nimport { loadEditMode, loadProjectShellAllowed, readConfig } from \"../../config.js\";\nimport { t } from \"../../i18n/index.js\";\nimport { bootstrapSemanticSearchInCodeMode } from \"../../index/semantic/tool.js\";\nimport { detectForeignAgentPlatform } from \"../../memory/project.js\";\nimport { sanitizeName } from \"../../memory/session.js\";\nimport { ToolRegistry } from \"../../tools.js\";\nimport { registerChoiceTool } from \"../../tools/choice.js\";\nimport { registerFilesystemTools } from \"../../tools/filesystem.js\";\nimport { JobRegistry } from \"../../tools/jobs.js\";\nimport { registerMemoryTools } from \"../../tools/memory.js\";\nimport { registerPlanTool } from \"../../tools/plan.js\";\nimport { registerScaffoldTools } from \"../../tools/scaffold.js\";\nimport { registerShellTools } from \"../../tools/shell.js\";\nimport { registerTodoTool } from \"../../tools/todo.js\";\nimport { markPhase } from \"../startup-profile.js\";\nimport { chatCommand } from \"./chat.js\";\n\nexport interface CodeOptions {\n /** Directory to root the filesystem tools at. Defaults to process.cwd(). */\n dir?: string;\n /** Override the default `smart` model. */\n model?: string;\n /** Disable session persistence. */\n noSession?: boolean;\n /** Transcript file for replay/diff. */\n transcript?: string;\n /** Skip the session picker — always resume prior messages. */\n forceResume?: boolean;\n /** Skip the session picker — always wipe prior messages and start fresh. */\n forceNew?: boolean;\n /**\n * Soft USD spend cap. Off by default. Same semantics as `chat`:\n * warns at 80%, refuses next turn at 100%. Mid-session adjustable\n * via `/budget <usd>` slash command.\n */\n budgetUsd?: number;\n /** Suppress the auto-launched embedded web dashboard. */\n noDashboard?: boolean;\n /** Inline string appended to the code system prompt after the generated base prompt. */\n systemAppend?: string;\n /** Path to a UTF-8 text file whose contents are appended to the code system prompt. */\n systemAppendFile?: string;\n /** Default true. Pass false (CLI: `--no-alt-screen`) to keep chat output in shell scrollback. */\n altScreen?: boolean;\n /** Default true. Pass false (CLI: `--no-mouse`) to keep terminal-native drag-select unmodified. */\n mouse?: boolean;\n}\n\nexport async function codeCommand(opts: CodeOptions = {}): Promise<void> {\n markPhase(\"code_command_enter\");\n const { codeSystemPrompt } = await import(\"../../code/prompt.js\");\n const rootDir = resolve(opts.dir ?? process.cwd());\n // Per-directory session so switching projects doesn't mix histories.\n // `code-<sanitized-basename>` fits the session name rules without\n // truncating most project names.\n const session = opts.noSession ? undefined : `code-${sanitizeName(basename(rootDir))}`;\n\n // Native filesystem tools. No subprocess, ~50-200 ms faster per call\n // than the MCP server was, and `edit_file` takes a flat SEARCH/REPLACE\n // shape instead of the `string=\"false\"` JSON-in-string array that\n // triggered R1's DSML hallucinations all through 0.4.x.\n const tools = new ToolRegistry();\n // Background-process registry shared between the shell tools and the\n // TUI's /jobs + /kill slashes + exit cleanup. One per `reasonix code`\n // run — orphan prevention on SIGINT / process exit kills everything\n // it owns, so dev servers don't outlive the Reasonix process.\n const jobs = new JobRegistry();\n // Bundled re-registration so `/cwd <path>` can swap every rootDir-\n // dependent tool atomically. ToolRegistry.register is keyed by name\n // and overwrites in-place, so re-calling these against the existing\n // registry replaces the closures cleanly without disturbing tool\n // specs (names/descriptions/params don't reference rootDir, so the\n // prefix cache survives).\n const registerRootedTools = (root: string): void => {\n registerFilesystemTools(tools, { rootDir: root });\n registerShellTools(tools, {\n rootDir: root,\n // Per-project \"always allow\" list persisted from prior ShellConfirm\n // choices; merged on top of the built-in allowlist in shell.ts.\n // GETTER form — re-read every dispatch so a prefix the user adds\n // via ShellConfirm mid-session takes effect on the next shell call\n // instead of waiting for `/new` or a relaunch.\n extraAllowed: () => loadProjectShellAllowed(root),\n // `yolo` edit-mode disables shell confirmations entirely. Re-read\n // from config on each dispatch so /mode yolo (or Shift+Tab cycling\n // through to it) flips the gate live without forcing a relaunch.\n allowAll: () => loadEditMode() === \"yolo\",\n jobs,\n });\n // `remember` / `forget` / `recall_memory` — cross-session user memory.\n // Project scope hashes off rootDir so switching projects gets a fresh\n // per-project memory store; the global scope is shared across runs.\n registerMemoryTools(tools, { projectRoot: root });\n };\n // Async tail to `registerRootedTools`. Kept separate because the FS /\n // shell / memory re-registration above is sync and must happen before\n // the next tool dispatch, while semantic-index probing reads disk and\n // can race ahead in the background. On `/cwd`, App.tsx fires this\n // after the sync swap and surfaces the result via postInfo.\n const reBootstrapSemantic = async (root: string): Promise<{ enabled: boolean }> => {\n const result = await bootstrapSemanticSearchInCodeMode(tools, root);\n if (!result.enabled) tools.unregister(\"semantic_search\");\n return result;\n };\n registerRootedTools(rootDir);\n // `submit_plan` is always in the spec list so the prefix cache stays\n // stable across plan-mode toggles (Pillar 1). The tool itself is a\n // no-op outside plan mode and throws `PlanProposedError` when the\n // user has `/plan`-enabled the session.\n registerPlanTool(tools);\n // `ask_choice` — branching primitive. Independent of plan mode: the\n // model uses it to put a 2–4 way choice in front of the user\n // (strategy, style, library pick) without trying to squeeze the\n // menu into a submit_plan body. Keeping it always-registered\n // preserves the prefix cache across plan-mode toggles.\n registerChoiceTool(tools);\n // `todo_write` — lightweight in-session task tracker, no approval gate.\n // Independent of plan mode (readOnly=true so it stays callable in /plan).\n registerTodoTool(tools);\n // `create_skill` / `add_mcp_server` — let the model scaffold from chat.\n // Both writes go through the same paths the wizard / `/skill new` use,\n // so the on-disk shape stays one source of truth. New servers take\n // effect on next launch (no live client churn).\n registerScaffoldTools(tools, { projectRoot: rootDir });\n // `run_skill` is intentionally NOT registered here — App.tsx wires it\n // up with the subagent runner attached, so `runAs: subagent` skills\n // can spawn isolated child loops. Doing it here would mean the App's\n // re-registration would shadow the no-runner version, which works\n // (last write wins) but obscures the wiring.\n\n // Bootstrap semantic_search. Silent: registers the tool when an\n // on-disk index already exists, skips entirely otherwise. Setup\n // happens via the explicit `reasonix index` command — never\n // by surprise on launch.\n markPhase(\"semantic_bootstrap_start\");\n const semantic = await reBootstrapSemantic(rootDir);\n markPhase(\n semantic.enabled ? \"semantic_bootstrap_done_enabled\" : \"semantic_bootstrap_done_skipped\",\n );\n\n process.stderr.write(\n `${t(\"startup.codeRooted\", {\n rootDir,\n session: session ?? t(\"startup.ephemeral\"),\n tools: tools.size,\n semantic: semantic.enabled ? t(\"startup.semanticOn\") : \"\",\n })}\\n`,\n );\n\n const foreign = detectForeignAgentPlatform(rootDir);\n if (foreign) {\n process.stderr.write(\n `⚠ workspace contains another agent platform's files (${foreign.join(\", \")}). Reasonix Code may read them as project content; relaunch with --dir <your-project> if that's not what you want.\\n`,\n );\n }\n\n // Belt-and-suspenders cleanup: even though spawn(detached:false)\n // should tie child processes to the parent's lifetime, Windows cmd.exe\n // wrappers occasionally leak. We DON'T install SIGINT/SIGTERM\n // handlers here — that overrode Node's default \"exit on Ctrl+C\" with\n // a silent no-op, which made Ctrl+C feel broken in the TUI. App.tsx\n // owns the SIGINT path now (it shows the quit-armed banner and calls\n // exit() on confirmation); this 'exit' hook just guarantees the job\n // registry is drained on the way out, regardless of which exit path\n // fired.\n process.once(\"exit\", () => {\n void jobs.shutdown();\n });\n\n let systemAppendFileContents: string | undefined;\n if (opts.systemAppend !== undefined && opts.systemAppend.trim().length === 0) {\n process.stderr.write(\"--system-append is empty — no prompt text will be appended\\n\");\n }\n if (opts.systemAppendFile) {\n const filePath = resolve(opts.systemAppendFile);\n try {\n systemAppendFileContents = readFileSync(filePath, \"utf8\");\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n process.stderr.write(\n `Error: cannot read --system-append-file \"${filePath}\": ${e.code ? `[${e.code}] ` : \"\"}${e.message}\\n`,\n );\n process.exit(1);\n }\n }\n\n await chatCommand({\n model: opts.model ?? \"deepseek-v4-flash\",\n budgetUsd: opts.budgetUsd,\n system: codeSystemPrompt(rootDir, {\n hasSemanticSearch: semantic.enabled,\n systemAppend: opts.systemAppend,\n systemAppendFile: systemAppendFileContents,\n modelId: opts.model ?? \"deepseek-v4-flash\",\n }),\n transcript: opts.transcript,\n session,\n seedTools: tools,\n codeMode: {\n rootDir,\n jobs,\n reregisterTools: registerRootedTools,\n reBootstrapSemantic,\n },\n mcp: readConfig().mcp,\n forceResume: opts.forceResume,\n forceNew: opts.forceNew,\n noDashboard: opts.noDashboard,\n altScreen: opts.altScreen,\n mouse: opts.mouse,\n });\n}\n","/** Agent-facing tools for scaffolding skills + MCP servers from chat. Persists via the same paths the wizard / `/skill new` use. */\n\nimport { defaultConfigPath, readConfig, writeConfig } from \"../config.js\";\nimport { MCP_CATALOG } from \"../mcp/catalog.js\";\nimport { preflightStdioSpec } from \"../mcp/preflight.js\";\nimport { type McpSpec, parseMcpSpec } from \"../mcp/spec.js\";\nimport { SkillStore } from \"../skills.js\";\nimport type { ToolRegistry } from \"../tools.js\";\n\nexport interface ScaffoldToolsOptions {\n homeDir?: string;\n projectRoot?: string;\n /** Override config path — tests point this at a tmp file. */\n configPath?: string;\n}\n\nconst VALID_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;\nconst VALID_SERVER_NAME = /^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/;\nconst VALID_TOOL_NAME = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;\n\nexport function registerScaffoldTools(\n registry: ToolRegistry,\n opts: ScaffoldToolsOptions = {},\n): ToolRegistry {\n const configPath = opts.configPath ?? defaultConfigPath();\n\n registry.register({\n name: \"create_skill\",\n description:\n 'Scaffold a new skill (`SKILL.md` in `.reasonix/skills/<name>.md`) the user can invoke later via `/skill <name>`. Use this when the user asks the agent to add a playbook, automate a recurring workflow, or capture a multi-step recipe as a named skill. The frontmatter is filled from the structured args here (description / allowed_tools / run_as / model) so the model never has to write raw YAML. Use `run_as: \"subagent\"` for read-and-synthesize playbooks where only the final answer should come back; default `\"inline\"` appends the body to the parent log so the user sees the steps. Refuses to overwrite an existing skill — pick a different name or ask the user to delete the old one.',\n parameters: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description:\n \"Skill identifier — letters/digits/`_`/`-`/`.`, 1–64 chars. Becomes the `name` frontmatter and the `<name>.md` filename.\",\n },\n description: {\n type: \"string\",\n description:\n 'One-line summary shown in the pinned skills index. Lead with the verb (\"Run X and …\") so the parent agent can scan it.',\n },\n body: {\n type: \"string\",\n description:\n \"Markdown body of the skill — the playbook the model follows when invoked. Plain prose + bullets; reference tools by name.\",\n },\n scope: {\n type: \"string\",\n enum: [\"project\", \"global\"],\n description:\n \"`project` = `.reasonix/skills/` under the workspace (default, requires `reasonix code`); `global` = `~/.reasonix/skills/` shared across all repos.\",\n },\n allowed_tools: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"Optional whitelist of tool names the subagent registry is scoped to (only meaningful for `run_as: subagent`). Common values: `read_file`, `search_content`, `directory_tree`, `run_command`. Omit to give the subagent the full inherited toolset.\",\n },\n run_as: {\n type: \"string\",\n enum: [\"inline\", \"subagent\"],\n description:\n \"`inline` (default) appends the body to the parent log as a tool result. `subagent` spawns an isolated child loop and only the final answer comes back — use for read-and-synthesize playbooks (explore, research, review).\",\n },\n model: {\n type: \"string\",\n enum: [\"deepseek-v4-flash\", \"deepseek-v4-pro\"],\n description:\n \"Subagent model override (only meaningful for `run_as: subagent`). Default is the same as `spawn_subagent` — `deepseek-v4-flash`. Set to `deepseek-v4-pro` only when the playbook empirically needs the stronger model.\",\n },\n },\n required: [\"name\", \"description\", \"body\"],\n },\n fn: async (args: {\n name?: unknown;\n description?: unknown;\n body?: unknown;\n scope?: unknown;\n allowed_tools?: unknown;\n run_as?: unknown;\n model?: unknown;\n }) => {\n const name = typeof args.name === \"string\" ? args.name.trim() : \"\";\n if (!VALID_SKILL_NAME.test(name)) {\n return JSON.stringify({\n error: `invalid skill name: ${JSON.stringify(name)} — use letters, digits, _, -, .`,\n });\n }\n const description =\n typeof args.description === \"string\" ? args.description.trim().replace(/\\n+/g, \" \") : \"\";\n if (!description) {\n return JSON.stringify({\n error: \"create_skill requires a non-empty 'description'\",\n });\n }\n const body = typeof args.body === \"string\" ? args.body : \"\";\n if (!body.trim()) {\n return JSON.stringify({ error: \"create_skill requires a non-empty 'body'\" });\n }\n const scope: \"project\" | \"global\" =\n args.scope === \"global\" ? \"global\" : opts.projectRoot ? \"project\" : \"global\";\n const runAs: \"inline\" | \"subagent\" = args.run_as === \"subagent\" ? \"subagent\" : \"inline\";\n const allowedTools = parseAllowedTools(args.allowed_tools);\n if (allowedTools && \"error\" in allowedTools) {\n return JSON.stringify({ error: allowedTools.error });\n }\n const model =\n typeof args.model === \"string\" && args.model.startsWith(\"deepseek-\")\n ? args.model\n : undefined;\n\n const content = serializeSkill({\n name,\n description,\n runAs,\n allowedTools: allowedTools ?? undefined,\n model,\n body,\n });\n\n const store = new SkillStore({\n homeDir: opts.homeDir,\n projectRoot: opts.projectRoot,\n });\n const result = store.createWithContent(name, scope, content);\n if (\"error\" in result) {\n return JSON.stringify({ error: result.error });\n }\n return JSON.stringify({\n success: true,\n path: result.path,\n scope,\n name,\n run_as: runAs,\n });\n },\n });\n\n registry.register({\n name: \"add_mcp_server\",\n description:\n 'Register a new MCP server in the user\\'s Reasonix config (`mcp` array). Takes effect on the next session — does NOT spawn the server now. Use stdio for local commands (npx packages, local binaries), `sse` or `streamable-http` for remote endpoints. Pass `from_catalog: \"<name>\"` (e.g. `\"filesystem\"`, `\"memory\"`, `\"github\"`) to auto-fill `command` + `args` from the bundled catalog — the user still has to supply user-args (filesystem: a sandbox dir; github: GITHUB_PERSONAL_ACCESS_TOKEN in env). Refuses to add a server whose name collides with an existing entry.',\n parameters: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description:\n \"Server name — used as the namespace prefix on every tool the server exposes. Letters/digits/`_`/`-`, must start with a letter or `_`.\",\n },\n transport: {\n type: \"string\",\n enum: [\"stdio\", \"sse\", \"streamable-http\"],\n description:\n \"`stdio` = spawn a local command and pipe MCP over stdin/stdout. `sse` = HTTP+SSE remote. `streamable-http` = Streamable HTTP remote. Required unless `from_catalog` is set.\",\n },\n command: {\n type: \"string\",\n description:\n 'Argv[0] for stdio servers — typically `npx` or a binary path. Required when `transport: \"stdio\"` (and no `from_catalog`).',\n },\n args: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n 'Remaining argv for stdio servers — e.g. `[\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/path/to/dir\"]`. The dir at the tail is enforced to exist by the preflight check.',\n },\n url: {\n type: \"string\",\n description:\n \"Endpoint URL for `sse` / `streamable-http` transports. Must be `http://` or `https://`.\",\n },\n from_catalog: {\n type: \"string\",\n description:\n \"Optional shortcut — name out of the bundled catalog (`filesystem`, `memory`, `github`, `puppeteer`, `everything`). When set, fills `command` + `args` from the catalog entry; you still supply `name` (defaults to the catalog name) and any user-args via `args`.\",\n },\n },\n required: [\"name\"],\n },\n fn: async (args: {\n name?: unknown;\n transport?: unknown;\n command?: unknown;\n args?: unknown;\n url?: unknown;\n from_catalog?: unknown;\n }) => {\n const name = typeof args.name === \"string\" ? args.name.trim() : \"\";\n if (!VALID_SERVER_NAME.test(name)) {\n return JSON.stringify({\n error: `invalid server name: ${JSON.stringify(name)} — must match [a-zA-Z_][a-zA-Z0-9_-]*`,\n });\n }\n\n const specStr = buildSpecString({\n name,\n transport: typeof args.transport === \"string\" ? args.transport : undefined,\n command: typeof args.command === \"string\" ? args.command : undefined,\n argv: Array.isArray(args.args)\n ? (args.args.filter((a) => typeof a === \"string\") as string[])\n : undefined,\n url: typeof args.url === \"string\" ? args.url : undefined,\n fromCatalog: typeof args.from_catalog === \"string\" ? args.from_catalog : undefined,\n });\n if (\"error\" in specStr) {\n return JSON.stringify({ error: specStr.error });\n }\n\n let parsed: McpSpec;\n try {\n parsed = parseMcpSpec(specStr.spec);\n } catch (err) {\n return JSON.stringify({ error: (err as Error).message });\n }\n if (parsed.transport === \"stdio\") {\n try {\n preflightStdioSpec(parsed);\n } catch (err) {\n return JSON.stringify({ error: (err as Error).message });\n }\n }\n\n const cfg = readConfig(configPath);\n const existing = cfg.mcp ?? [];\n const collision = existing.find((s) => parseSpecName(s) === name);\n if (collision) {\n return JSON.stringify({\n error: `MCP server ${JSON.stringify(name)} already registered: ${collision}`,\n });\n }\n cfg.mcp = [...existing, specStr.spec];\n writeConfig(cfg, configPath);\n return JSON.stringify({\n success: true,\n name,\n transport: parsed.transport,\n spec: specStr.spec,\n config_path: configPath,\n active_on_next_launch: true,\n });\n },\n });\n\n return registry;\n}\n\ninterface SerializeSkillArgs {\n name: string;\n description: string;\n runAs: \"inline\" | \"subagent\";\n allowedTools?: readonly string[];\n model?: string;\n body: string;\n}\n\nexport function serializeSkill(args: SerializeSkillArgs): string {\n const lines: string[] = [\"---\", `name: ${args.name}`, `description: ${args.description}`];\n if (args.runAs === \"subagent\") {\n lines.push(\"runAs: subagent\");\n }\n if (args.allowedTools && args.allowedTools.length > 0) {\n lines.push(`allowed-tools: ${args.allowedTools.join(\", \")}`);\n }\n if (args.model) {\n lines.push(`model: ${args.model}`);\n }\n lines.push(\"---\", \"\");\n return `${lines.join(\"\\n\")}\\n${args.body.trim()}\\n`;\n}\n\nfunction parseAllowedTools(raw: unknown): readonly string[] | { error: string } | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!Array.isArray(raw)) {\n return { error: \"'allowed_tools' must be an array of tool-name strings\" };\n }\n const out: string[] = [];\n for (const v of raw) {\n if (typeof v !== \"string\") {\n return { error: \"'allowed_tools' entries must be strings\" };\n }\n const trimmed = v.trim();\n if (!trimmed) continue;\n if (!VALID_TOOL_NAME.test(trimmed)) {\n return { error: `invalid tool name in allowed_tools: ${JSON.stringify(trimmed)}` };\n }\n out.push(trimmed);\n }\n return out.length > 0 ? out : undefined;\n}\n\ninterface BuildSpecInput {\n name: string;\n transport?: string;\n command?: string;\n argv?: string[];\n url?: string;\n fromCatalog?: string;\n}\n\nfunction buildSpecString(input: BuildSpecInput): { spec: string } | { error: string } {\n if (input.fromCatalog) {\n const entry = MCP_CATALOG.find((e) => e.name === input.fromCatalog);\n if (!entry) {\n const known = MCP_CATALOG.map((e) => e.name).join(\", \");\n return {\n error: `unknown catalog entry: ${JSON.stringify(input.fromCatalog)} — known: ${known}`,\n };\n }\n const userArgs = input.argv ?? [];\n if (entry.userArgs && userArgs.length === 0) {\n return {\n error: `catalog entry \"${entry.name}\" needs ${entry.userArgs} — pass it via the 'args' parameter`,\n };\n }\n const tail = userArgs.map(quoteIfNeeded).join(\" \");\n const body = `npx -y ${entry.package}${tail ? ` ${tail}` : \"\"}`;\n return { spec: `${input.name}=${body}` };\n }\n\n const transport = input.transport;\n if (!transport) {\n return { error: \"add_mcp_server requires 'transport' (or 'from_catalog')\" };\n }\n if (transport === \"stdio\") {\n if (!input.command || !input.command.trim()) {\n return { error: \"stdio transport requires 'command'\" };\n }\n const tail = (input.argv ?? []).map(quoteIfNeeded).join(\" \");\n const body = `${quoteIfNeeded(input.command.trim())}${tail ? ` ${tail}` : \"\"}`;\n return { spec: `${input.name}=${body}` };\n }\n if (transport === \"sse\" || transport === \"streamable-http\") {\n if (!input.url || !/^https?:\\/\\//i.test(input.url)) {\n return { error: `${transport} transport requires an http(s):// 'url'` };\n }\n const prefix = transport === \"streamable-http\" ? \"streamable+\" : \"\";\n return { spec: `${input.name}=${prefix}${input.url.trim()}` };\n }\n return { error: `unknown transport: ${JSON.stringify(transport)}` };\n}\n\nfunction parseSpecName(spec: string): string | null {\n const m = spec.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*)=/);\n return m ? (m[1] ?? null) : null;\n}\n\nfunction quoteIfNeeded(s: string): string {\n return /\\s|\"/.test(s) ? `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"` : s;\n}\n"],"mappings":"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","names":[]}
@@ -4,13 +4,12 @@ import {
4
4
  checkOllamaStatus,
5
5
  pullOllamaModel,
6
6
  startOllamaDaemon
7
- } from "./chunk-XQIFIB3U.js";
7
+ } from "./chunk-JOFZ6AW5.js";
8
8
  import "./chunk-5X7LZJDE.js";
9
9
  import {
10
10
  loadIndexConfig,
11
11
  resolveSemanticEmbeddingConfig
12
- } from "./chunk-BHLHOS5Y.js";
13
- import "./chunk-WUI3P4RA.js";
12
+ } from "./chunk-BW2HWSYH.js";
14
13
 
15
14
  // src/cli/commands/index.ts
16
15
  import { resolve } from "path";
@@ -350,4 +349,4 @@ function makeTtyWriter() {
350
349
  export {
351
350
  indexCommand
352
351
  };
353
- //# sourceMappingURL=commands-FE2UDFBC.js.map
352
+ //# sourceMappingURL=commands-R4JWISND.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/index.ts","../../src/index/semantic/i18n.ts","../../src/index/semantic/preflight.ts"],"sourcesContent":["/** `reasonix index` — progress writes go to stderr so stdout stays pipeable. */\n\nimport { resolve } from \"node:path\";\nimport { loadIndexConfig, resolveSemanticEmbeddingConfig } from \"../../config.js\";\nimport { buildIndex } from \"../../index/semantic/builder.js\";\nimport type { BuildProgress, BuildResult, SkipBuckets } from \"../../index/semantic/builder.js\";\nimport { t } from \"../../index/semantic/i18n.js\";\nimport { semanticPreflight } from \"../../index/semantic/preflight.js\";\n\nexport interface IndexCommandOptions {\n rebuild?: boolean;\n model?: string;\n dir?: string;\n ollamaUrl?: string;\n yes?: boolean;\n}\n\nexport async function indexCommand(opts: IndexCommandOptions = {}): Promise<void> {\n const root = resolve(opts.dir ?? process.cwd());\n const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;\n const resolved = resolveSemanticEmbeddingConfig();\n const embedding =\n resolved.provider === \"ollama\"\n ? {\n ...resolved,\n model: opts.model ?? resolved.model,\n baseUrl: opts.ollamaUrl ?? resolved.baseUrl,\n }\n : {\n ...resolved,\n model: opts.model ?? resolved.model,\n };\n\n const preflightOk = await semanticPreflight(embedding, {\n interactive: tty && !opts.yes,\n yesToAll: opts.yes ?? false,\n });\n if (!preflightOk) process.exit(1);\n\n const writer = makeProgressWriter(tty);\n\n const t0 = Date.now();\n let result: BuildResult;\n try {\n result = await buildIndex(root, {\n ...embedding,\n rebuild: opts.rebuild,\n indexConfig: loadIndexConfig(),\n onProgress: (p) => writer.update(p),\n });\n } catch (err) {\n writer.clear();\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(t(\"indexFailed\", { msg }));\n process.exit(1);\n }\n writer.clear();\n\n const seconds = ((Date.now() - t0) / 1000).toFixed(1);\n const successKey = result.chunksSkipped > 0 ? \"indexSuccessWithSkips\" : \"indexSuccess\";\n process.stderr.write(\n t(successKey, {\n scanned: result.filesScanned,\n changed: result.filesChanged,\n added: result.chunksAdded,\n removed: result.chunksRemoved,\n skipped: result.chunksSkipped,\n seconds,\n }),\n );\n const breakdown = renderSkipBreakdown(result.skipBuckets);\n if (breakdown) process.stderr.write(`${breakdown}\\n`);\n if (result.filesChanged === 0 && !opts.rebuild) {\n process.stderr.write(t(\"indexNothingToDo\"));\n }\n}\n\nfunction renderSkipBreakdown(buckets: SkipBuckets): string {\n const total = Object.values(buckets).reduce((a, b) => a + b, 0);\n if (total === 0) return \"\";\n const parts: string[] = [];\n if (buckets.gitignore) parts.push(`gitignore: ${buckets.gitignore}`);\n if (buckets.pattern) parts.push(`pattern: ${buckets.pattern}`);\n if (buckets.defaultDir) parts.push(`defaultDir: ${buckets.defaultDir}`);\n if (buckets.defaultFile) parts.push(`defaultFile: ${buckets.defaultFile}`);\n if (buckets.binaryExt) parts.push(`binaryExt: ${buckets.binaryExt}`);\n if (buckets.binaryContent) parts.push(`binaryContent: ${buckets.binaryContent}`);\n if (buckets.tooLarge) parts.push(`tooLarge: ${buckets.tooLarge}`);\n if (buckets.readError) parts.push(`readError: ${buckets.readError}`);\n return ` · skipped ${total} files (${parts.join(\", \")})`;\n}\n\ninterface ProgressWriter {\n update(p: BuildProgress): void;\n clear(): void;\n}\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst SPINNER_INTERVAL_MS = 120;\n\nfunction makeProgressWriter(tty: boolean): ProgressWriter {\n if (!tty) return makeNonTtyWriter();\n return makeTtyWriter();\n}\n\nfunction makeNonTtyWriter(): ProgressWriter {\n let lastPhase: BuildProgress[\"phase\"] | null = null;\n let lastChunks = 0;\n return {\n update(p) {\n if (p.phase !== lastPhase) {\n lastPhase = p.phase;\n if (p.phase === \"scan\") {\n process.stderr.write(t(\"progressScanLine\"));\n } else if (p.phase === \"embed\") {\n process.stderr.write(\n t(\"progressEmbedLine\", {\n total: p.chunksTotal ?? 0,\n files: p.filesChanged ?? 0,\n }),\n );\n }\n }\n if (p.phase === \"embed\" && p.chunksDone !== undefined && p.chunksDone - lastChunks >= 50) {\n lastChunks = p.chunksDone;\n process.stderr.write(\n t(\"progressEmbedHeartbeat\", {\n done: p.chunksDone,\n total: p.chunksTotal ?? \"?\",\n }),\n );\n }\n },\n clear() {\n /* non-TTY keeps its accumulated lines */\n },\n };\n}\n\nfunction makeTtyWriter(): ProgressWriter {\n let status = t(\"progressStarting\");\n let lastLineLen = 0;\n let frameIdx = 0;\n const startTs = Date.now();\n\n const repaint = () => {\n const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];\n frameIdx++;\n const elapsed = ((Date.now() - startTs) / 1000).toFixed(1);\n const line = `${frame} ${status} ${elapsed}s`;\n const padded = line + \" \".repeat(Math.max(0, lastLineLen - line.length));\n process.stderr.write(`\\r${padded}`);\n lastLineLen = line.length;\n };\n\n repaint();\n const interval = setInterval(repaint, SPINNER_INTERVAL_MS);\n\n return {\n update(p) {\n if (p.phase === \"scan\") {\n status = t(\"progressScan\", { files: p.filesScanned ?? 0 });\n } else if (p.phase === \"embed\") {\n const done = p.chunksDone ?? 0;\n const total = p.chunksTotal ?? 0;\n const pct = total > 0 ? ((done / total) * 100).toFixed(0) : \"0\";\n status = t(\"progressEmbed\", { done, total, pct });\n }\n repaint();\n },\n clear() {\n clearInterval(interval);\n if (lastLineLen > 0) {\n process.stderr.write(`\\r${\" \".repeat(lastLineLen)}\\r`);\n lastLineLen = 0;\n }\n },\n };\n}\n","/** EN+ZH for semantic-search prompts only; tool descriptions stay English to preserve prompt-cache. */\n\nexport type Locale = \"en\" | \"zh\";\n\nlet cachedLocale: Locale | null = null;\n\nexport function detectLocale(): Locale {\n if (cachedLocale) return cachedLocale;\n const override = (process.env.REASONIX_LANG ?? \"\").toLowerCase();\n if (override === \"zh\" || override === \"en\") {\n cachedLocale = override;\n return cachedLocale;\n }\n const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? \"\";\n if (/^zh[-_]/i.test(env)) {\n cachedLocale = \"zh\";\n return \"zh\";\n }\n try {\n const sys = new Intl.DateTimeFormat().resolvedOptions().locale ?? \"\";\n if (/^zh[-_]/i.test(sys)) {\n cachedLocale = \"zh\";\n return \"zh\";\n }\n } catch {\n /* ignore — fall through to default */\n }\n cachedLocale = \"en\";\n return \"en\";\n}\n\n/** Reset the cached locale. Tests use this; production never needs it. */\nexport function resetLocaleCache(): void {\n cachedLocale = null;\n}\n\n/** Falls back to English so partial dictionary updates never show \"[missing]\". */\nexport function t(key: keyof typeof EN, vars: Record<string, string | number> = {}): string {\n const loc = detectLocale();\n const dict = loc === \"zh\" ? ZH : EN;\n const tpl = dict[key] ?? EN[key];\n return tpl.replace(/\\{(\\w+)\\}/g, (_m, name) => {\n const v = vars[name];\n return v === undefined ? `{${name}}` : String(v);\n });\n}\n\nconst EN = {\n // ── preflight ─────────────────────────────────────────────────────\n ollamaNotFound:\n \"✗ `ollama` not found on PATH.\\n Install from https://ollama.com (one-time, ~150 MB), then retry.\\n\",\n daemonNotReachableHint:\n \"✗ Ollama daemon not reachable. Run `ollama serve` and retry, or pass --yes to start it automatically.\\n\",\n daemonStartConfirm: \"Ollama daemon isn't running. Start `ollama serve` now?\",\n daemonAbortStart: \"✗ aborted — start `ollama serve` yourself and retry.\\n\",\n daemonStarting: \"▸ starting `ollama serve`…\\n\",\n daemonStartTimeout:\n \"✗ daemon didn't come up within 15s. Try `ollama serve` in a separate terminal and retry.\\n\",\n daemonReady: \"✓ daemon up{pid}\\n\",\n modelNotPulledHint:\n '✗ embedding model \"{model}\" not pulled. Run `ollama pull {model}` and retry, or pass --yes to pull it automatically.\\n',\n modelPullConfirm:\n 'Embedding model \"{model}\" isn\\'t pulled yet. Pull it now? (~274 MB for nomic-embed-text)',\n modelAbortPull: \"✗ aborted — pull the model yourself and retry.\\n\",\n modelPulling: \"▸ pulling {model}…\\n\",\n modelPullFailed: \"✗ `ollama pull {model}` failed (exit {code}).\\n\",\n modelPulled: \"✓ {model} pulled\\n\",\n\n // ── progress ─────────────────────────────────────────────────────\n // The TTY-mode progress writer paints `<spinner> <status> <elapsed>s`\n // every 120ms. The status itself comes from one of these keys based\n // on the current phase. {files}, {done}, {total}, {pct} are\n // substituted by the writer.\n progressStarting: \"starting…\",\n progressScan: \"scanning project · {files} files\",\n progressEmbed: \"embedding {done}/{total} chunks · {pct}%\",\n progressEmbedHeartbeat: \" {done}/{total}\\n\",\n progressScanLine: \"scanning files…\\n\",\n progressEmbedLine: \"embedding {total} chunks across {files} files…\\n\",\n // Final result line after a successful build.\n indexSuccess:\n \"✓ indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed) in {seconds}s\\n\",\n indexSuccessWithSkips:\n \"✓ indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed, {skipped} skipped due to embed errors) in {seconds}s\\n\",\n indexNothingToDo: \" (nothing to do — re-run with --rebuild to force a full rebuild)\\n\",\n indexFailed: \"✗ index failed: {msg}\\n\",\n\n // ── /semantic slash ──────────────────────────────────────────────\n slashHeader: \"semantic_search status\",\n slashEnabled: \"✓ enabled — index built, tool registered.\",\n slashEnabledDetail: \" index size: {chunks} chunks across {files} files\",\n slashEnabledHowto: \" the model will call semantic_search automatically when it fits.\",\n slashIndexMissing: \"✗ no index built yet for this project.\",\n slashHowToBuild: \" to enable, exit Reasonix and run in your shell:\\n reasonix index\",\n slashOllamaMissing: \" prerequisite: install Ollama from https://ollama.com\",\n slashDaemonDown:\n \" Ollama is installed but the daemon isn't running. start it with: ollama serve\",\n slashIndexInfo:\n \" what semantic_search does: cross-language code understanding via local embeddings.\\n better than grep when you describe WHAT something does, not WHICH token to find.\",\n} as const;\n\nconst ZH: Partial<Record<keyof typeof EN, string>> = {\n ollamaNotFound:\n \"✗ 未找到 `ollama`。\\n 请访问 https://ollama.com 安装(一次性,约 150 MB),然后重试。\\n\",\n daemonNotReachableHint:\n \"✗ Ollama 守护进程未启动。请运行 `ollama serve` 后重试,或加 --yes 让我自动启动。\\n\",\n daemonStartConfirm: \"Ollama 守护进程未运行。现在启动 `ollama serve` 吗?\",\n daemonAbortStart: \"✗ 已取消——请自行运行 `ollama serve` 后重试。\\n\",\n daemonStarting: \"▸ 正在启动 `ollama serve`…\\n\",\n daemonStartTimeout: \"✗ 15 秒内守护进程未就绪。请在另一个终端运行 `ollama serve` 后重试。\\n\",\n daemonReady: \"✓ 守护进程已启动{pid}\\n\",\n modelNotPulledHint:\n '✗ 嵌入模型 \"{model}\" 未下载。请运行 `ollama pull {model}` 后重试,或加 --yes 让我自动下载。\\n',\n modelPullConfirm: '嵌入模型 \"{model}\" 还未下载。现在下载吗?(nomic-embed-text 约 274 MB)',\n modelAbortPull: \"✗ 已取消——请自行下载模型后重试。\\n\",\n modelPulling: \"▸ 正在下载 {model}…\\n\",\n modelPullFailed: \"✗ `ollama pull {model}` 失败(退出码 {code})。\\n\",\n modelPulled: \"✓ {model} 下载完成\\n\",\n\n progressStarting: \"正在启动…\",\n progressScan: \"扫描项目 · 已扫描 {files} 个文件\",\n progressEmbed: \"正在向量化 {done}/{total} 个片段 · {pct}%\",\n progressEmbedHeartbeat: \" {done}/{total}\\n\",\n progressScanLine: \"正在扫描文件…\\n\",\n progressEmbedLine: \"正在向量化 {total} 个片段(涉及 {files} 个文件)…\\n\",\n indexSuccess:\n \"✓ 已建立索引:扫描 {scanned} 个文件({changed} 个有变化,新增 {added} 个片段,移除 {removed} 个过期);耗时 {seconds}s\\n\",\n indexSuccessWithSkips:\n \"✓ 已建立索引:扫描 {scanned} 个文件({changed} 个有变化,新增 {added} 个片段,移除 {removed} 个过期,跳过 {skipped} 个嵌入失败的片段);耗时 {seconds}s\\n\",\n indexNothingToDo: \" (没有变化——加 --rebuild 强制重建)\\n\",\n indexFailed: \"✗ 建立索引失败:{msg}\\n\",\n\n slashHeader: \"semantic_search 状态\",\n slashEnabled: \"✓ 已启用——索引已建好,工具已注册。\",\n slashEnabledDetail: \" 索引规模:{chunks} 个片段,{files} 个文件\",\n slashEnabledHowto: \" 模型在合适的时候会自动调用 semantic_search。\",\n slashIndexMissing: \"✗ 当前项目还没有索引。\",\n slashHowToBuild: \" 启用方式:退出 Reasonix,在终端运行:\\n reasonix index\",\n slashOllamaMissing: \" 前置依赖:从 https://ollama.com 安装 Ollama\",\n slashDaemonDown: \" 已装 Ollama 但守护进程未启动,请运行:ollama serve\",\n slashIndexInfo:\n ' semantic_search 用本地 embedding 做跨语言代码理解。\\n 当你描述\"做什么\"而不是具体 token 时,比 grep 更好。',\n};\n","import { stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport type { ResolvedEmbeddingConfig } from \"../../config.js\";\nimport { t } from \"./i18n.js\";\nimport { checkOllamaStatus, pullOllamaModel, startOllamaDaemon } from \"./ollama-launcher.js\";\n\nexport interface PreflightOptions {\n model: string;\n baseUrl?: string | undefined;\n interactive: boolean;\n yesToAll: boolean;\n log?: (line: string) => void;\n}\n\nexport async function ollamaPreflight(opts: PreflightOptions): Promise<boolean> {\n const log = opts.log ?? ((line: string) => process.stderr.write(line));\n const status = await checkOllamaStatus(opts.model, opts.baseUrl);\n\n if (!status.binaryFound) {\n log(t(\"ollamaNotFound\"));\n return false;\n }\n\n if (!status.daemonRunning) {\n if (!opts.interactive && !opts.yesToAll) {\n log(t(\"daemonNotReachableHint\"));\n return false;\n }\n const ok = opts.yesToAll || (await confirm(t(\"daemonStartConfirm\"), true));\n if (!ok) {\n log(t(\"daemonAbortStart\"));\n return false;\n }\n log(t(\"daemonStarting\"));\n const started = await startOllamaDaemon({ baseUrl: opts.baseUrl, timeoutMs: 15_000 });\n if (!started.ready) {\n log(t(\"daemonStartTimeout\"));\n return false;\n }\n log(t(\"daemonReady\", { pid: started.pid ? ` (pid ${started.pid})` : \"\" }));\n }\n\n const after = status.daemonRunning ? status : await checkOllamaStatus(opts.model, opts.baseUrl);\n\n if (!after.modelPulled) {\n if (!opts.interactive && !opts.yesToAll) {\n log(t(\"modelNotPulledHint\", { model: opts.model }));\n return false;\n }\n const ok = opts.yesToAll || (await confirm(t(\"modelPullConfirm\", { model: opts.model }), true));\n if (!ok) {\n log(t(\"modelAbortPull\"));\n return false;\n }\n log(t(\"modelPulling\", { model: opts.model }));\n const ESC = String.fromCharCode(0x1b);\n const ANSI_CSI = new RegExp(`${ESC}\\\\[[0-9;]*[A-Za-z]`, \"g\");\n const code = await pullOllamaModel(opts.model, {\n onLine: (line) => {\n const cleaned = line.replace(ANSI_CSI, \"\").trim();\n if (cleaned.length === 0) return;\n log(` ${cleaned}\\n`);\n },\n });\n if (code !== 0) {\n log(t(\"modelPullFailed\", { model: opts.model, code }));\n return false;\n }\n log(t(\"modelPulled\", { model: opts.model }));\n }\n\n return true;\n}\n\nexport async function semanticPreflight(\n config: ResolvedEmbeddingConfig,\n opts: Omit<PreflightOptions, \"model\" | \"baseUrl\">,\n): Promise<boolean> {\n if (config.provider === \"openai-compat\") return true;\n return await ollamaPreflight({\n ...opts,\n model: config.model,\n baseUrl: config.baseUrl,\n });\n}\n\nexport async function confirm(question: string, defaultYes: boolean): Promise<boolean> {\n const suffix = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();\n if (raw === \"\") return defaultYes;\n return raw === \"y\" || raw === \"yes\";\n } finally {\n rl.close();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/index.ts","../../src/index/semantic/i18n.ts","../../src/index/semantic/preflight.ts"],"sourcesContent":["/** `reasonix index` — progress writes go to stderr so stdout stays pipeable. */\n\nimport { resolve } from \"node:path\";\nimport { loadIndexConfig, resolveSemanticEmbeddingConfig } from \"../../config.js\";\nimport { buildIndex } from \"../../index/semantic/builder.js\";\nimport type { BuildProgress, BuildResult, SkipBuckets } from \"../../index/semantic/builder.js\";\nimport { t } from \"../../index/semantic/i18n.js\";\nimport { semanticPreflight } from \"../../index/semantic/preflight.js\";\n\nexport interface IndexCommandOptions {\n rebuild?: boolean;\n model?: string;\n dir?: string;\n ollamaUrl?: string;\n yes?: boolean;\n}\n\nexport async function indexCommand(opts: IndexCommandOptions = {}): Promise<void> {\n const root = resolve(opts.dir ?? process.cwd());\n const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;\n const resolved = resolveSemanticEmbeddingConfig();\n const embedding =\n resolved.provider === \"ollama\"\n ? {\n ...resolved,\n model: opts.model ?? resolved.model,\n baseUrl: opts.ollamaUrl ?? resolved.baseUrl,\n }\n : {\n ...resolved,\n model: opts.model ?? resolved.model,\n };\n\n const preflightOk = await semanticPreflight(embedding, {\n interactive: tty && !opts.yes,\n yesToAll: opts.yes ?? false,\n });\n if (!preflightOk) process.exit(1);\n\n const writer = makeProgressWriter(tty);\n\n const t0 = Date.now();\n let result: BuildResult;\n try {\n result = await buildIndex(root, {\n ...embedding,\n rebuild: opts.rebuild,\n indexConfig: loadIndexConfig(),\n onProgress: (p) => writer.update(p),\n });\n } catch (err) {\n writer.clear();\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(t(\"indexFailed\", { msg }));\n process.exit(1);\n }\n writer.clear();\n\n const seconds = ((Date.now() - t0) / 1000).toFixed(1);\n const successKey = result.chunksSkipped > 0 ? \"indexSuccessWithSkips\" : \"indexSuccess\";\n process.stderr.write(\n t(successKey, {\n scanned: result.filesScanned,\n changed: result.filesChanged,\n added: result.chunksAdded,\n removed: result.chunksRemoved,\n skipped: result.chunksSkipped,\n seconds,\n }),\n );\n const breakdown = renderSkipBreakdown(result.skipBuckets);\n if (breakdown) process.stderr.write(`${breakdown}\\n`);\n if (result.filesChanged === 0 && !opts.rebuild) {\n process.stderr.write(t(\"indexNothingToDo\"));\n }\n}\n\nfunction renderSkipBreakdown(buckets: SkipBuckets): string {\n const total = Object.values(buckets).reduce((a, b) => a + b, 0);\n if (total === 0) return \"\";\n const parts: string[] = [];\n if (buckets.gitignore) parts.push(`gitignore: ${buckets.gitignore}`);\n if (buckets.pattern) parts.push(`pattern: ${buckets.pattern}`);\n if (buckets.defaultDir) parts.push(`defaultDir: ${buckets.defaultDir}`);\n if (buckets.defaultFile) parts.push(`defaultFile: ${buckets.defaultFile}`);\n if (buckets.binaryExt) parts.push(`binaryExt: ${buckets.binaryExt}`);\n if (buckets.binaryContent) parts.push(`binaryContent: ${buckets.binaryContent}`);\n if (buckets.tooLarge) parts.push(`tooLarge: ${buckets.tooLarge}`);\n if (buckets.readError) parts.push(`readError: ${buckets.readError}`);\n return ` · skipped ${total} files (${parts.join(\", \")})`;\n}\n\ninterface ProgressWriter {\n update(p: BuildProgress): void;\n clear(): void;\n}\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst SPINNER_INTERVAL_MS = 120;\n\nfunction makeProgressWriter(tty: boolean): ProgressWriter {\n if (!tty) return makeNonTtyWriter();\n return makeTtyWriter();\n}\n\nfunction makeNonTtyWriter(): ProgressWriter {\n let lastPhase: BuildProgress[\"phase\"] | null = null;\n let lastChunks = 0;\n return {\n update(p) {\n if (p.phase !== lastPhase) {\n lastPhase = p.phase;\n if (p.phase === \"scan\") {\n process.stderr.write(t(\"progressScanLine\"));\n } else if (p.phase === \"embed\") {\n process.stderr.write(\n t(\"progressEmbedLine\", {\n total: p.chunksTotal ?? 0,\n files: p.filesChanged ?? 0,\n }),\n );\n }\n }\n if (p.phase === \"embed\" && p.chunksDone !== undefined && p.chunksDone - lastChunks >= 50) {\n lastChunks = p.chunksDone;\n process.stderr.write(\n t(\"progressEmbedHeartbeat\", {\n done: p.chunksDone,\n total: p.chunksTotal ?? \"?\",\n }),\n );\n }\n },\n clear() {\n /* non-TTY keeps its accumulated lines */\n },\n };\n}\n\nfunction makeTtyWriter(): ProgressWriter {\n let status = t(\"progressStarting\");\n let lastLineLen = 0;\n let frameIdx = 0;\n const startTs = Date.now();\n\n const repaint = () => {\n const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];\n frameIdx++;\n const elapsed = ((Date.now() - startTs) / 1000).toFixed(1);\n const line = `${frame} ${status} ${elapsed}s`;\n const padded = line + \" \".repeat(Math.max(0, lastLineLen - line.length));\n process.stderr.write(`\\r${padded}`);\n lastLineLen = line.length;\n };\n\n repaint();\n const interval = setInterval(repaint, SPINNER_INTERVAL_MS);\n\n return {\n update(p) {\n if (p.phase === \"scan\") {\n status = t(\"progressScan\", { files: p.filesScanned ?? 0 });\n } else if (p.phase === \"embed\") {\n const done = p.chunksDone ?? 0;\n const total = p.chunksTotal ?? 0;\n const pct = total > 0 ? ((done / total) * 100).toFixed(0) : \"0\";\n status = t(\"progressEmbed\", { done, total, pct });\n }\n repaint();\n },\n clear() {\n clearInterval(interval);\n if (lastLineLen > 0) {\n process.stderr.write(`\\r${\" \".repeat(lastLineLen)}\\r`);\n lastLineLen = 0;\n }\n },\n };\n}\n","/** EN+ZH for semantic-search prompts only; tool descriptions stay English to preserve prompt-cache. */\n\nexport type Locale = \"en\" | \"zh\";\n\nlet cachedLocale: Locale | null = null;\n\nexport function detectLocale(): Locale {\n if (cachedLocale) return cachedLocale;\n const override = (process.env.REASONIX_LANG ?? \"\").toLowerCase();\n if (override === \"zh\" || override === \"en\") {\n cachedLocale = override;\n return cachedLocale;\n }\n const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? \"\";\n if (/^zh[-_]/i.test(env)) {\n cachedLocale = \"zh\";\n return \"zh\";\n }\n try {\n const sys = new Intl.DateTimeFormat().resolvedOptions().locale ?? \"\";\n if (/^zh[-_]/i.test(sys)) {\n cachedLocale = \"zh\";\n return \"zh\";\n }\n } catch {\n /* ignore — fall through to default */\n }\n cachedLocale = \"en\";\n return \"en\";\n}\n\n/** Reset the cached locale. Tests use this; production never needs it. */\nexport function resetLocaleCache(): void {\n cachedLocale = null;\n}\n\n/** Falls back to English so partial dictionary updates never show \"[missing]\". */\nexport function t(key: keyof typeof EN, vars: Record<string, string | number> = {}): string {\n const loc = detectLocale();\n const dict = loc === \"zh\" ? ZH : EN;\n const tpl = dict[key] ?? EN[key];\n return tpl.replace(/\\{(\\w+)\\}/g, (_m, name) => {\n const v = vars[name];\n return v === undefined ? `{${name}}` : String(v);\n });\n}\n\nconst EN = {\n // ── preflight ─────────────────────────────────────────────────────\n ollamaNotFound:\n \"✗ `ollama` not found on PATH.\\n Install from https://ollama.com (one-time, ~150 MB), then retry.\\n\",\n daemonNotReachableHint:\n \"✗ Ollama daemon not reachable. Run `ollama serve` and retry, or pass --yes to start it automatically.\\n\",\n daemonStartConfirm: \"Ollama daemon isn't running. Start `ollama serve` now?\",\n daemonAbortStart: \"✗ aborted — start `ollama serve` yourself and retry.\\n\",\n daemonStarting: \"▸ starting `ollama serve`…\\n\",\n daemonStartTimeout:\n \"✗ daemon didn't come up within 15s. Try `ollama serve` in a separate terminal and retry.\\n\",\n daemonReady: \"✓ daemon up{pid}\\n\",\n modelNotPulledHint:\n '✗ embedding model \"{model}\" not pulled. Run `ollama pull {model}` and retry, or pass --yes to pull it automatically.\\n',\n modelPullConfirm:\n 'Embedding model \"{model}\" isn\\'t pulled yet. Pull it now? (~274 MB for nomic-embed-text)',\n modelAbortPull: \"✗ aborted — pull the model yourself and retry.\\n\",\n modelPulling: \"▸ pulling {model}…\\n\",\n modelPullFailed: \"✗ `ollama pull {model}` failed (exit {code}).\\n\",\n modelPulled: \"✓ {model} pulled\\n\",\n\n // ── progress ─────────────────────────────────────────────────────\n // The TTY-mode progress writer paints `<spinner> <status> <elapsed>s`\n // every 120ms. The status itself comes from one of these keys based\n // on the current phase. {files}, {done}, {total}, {pct} are\n // substituted by the writer.\n progressStarting: \"starting…\",\n progressScan: \"scanning project · {files} files\",\n progressEmbed: \"embedding {done}/{total} chunks · {pct}%\",\n progressEmbedHeartbeat: \" {done}/{total}\\n\",\n progressScanLine: \"scanning files…\\n\",\n progressEmbedLine: \"embedding {total} chunks across {files} files…\\n\",\n // Final result line after a successful build.\n indexSuccess:\n \"✓ indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed) in {seconds}s\\n\",\n indexSuccessWithSkips:\n \"✓ indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed, {skipped} skipped due to embed errors) in {seconds}s\\n\",\n indexNothingToDo: \" (nothing to do — re-run with --rebuild to force a full rebuild)\\n\",\n indexFailed: \"✗ index failed: {msg}\\n\",\n\n // ── /semantic slash ──────────────────────────────────────────────\n slashHeader: \"semantic_search status\",\n slashEnabled: \"✓ enabled — index built, tool registered.\",\n slashEnabledDetail: \" index size: {chunks} chunks across {files} files\",\n slashEnabledHowto: \" the model will call semantic_search automatically when it fits.\",\n slashIndexMissing: \"✗ no index built yet for this project.\",\n slashHowToBuild: \" to enable, exit Reasonix and run in your shell:\\n reasonix index\",\n slashOllamaMissing: \" prerequisite: install Ollama from https://ollama.com\",\n slashDaemonDown:\n \" Ollama is installed but the daemon isn't running. start it with: ollama serve\",\n slashIndexInfo:\n \" what semantic_search does: cross-language code understanding via local embeddings.\\n better than grep when you describe WHAT something does, not WHICH token to find.\",\n} as const;\n\nconst ZH: Partial<Record<keyof typeof EN, string>> = {\n ollamaNotFound:\n \"✗ 未找到 `ollama`。\\n 请访问 https://ollama.com 安装(一次性,约 150 MB),然后重试。\\n\",\n daemonNotReachableHint:\n \"✗ Ollama 守护进程未启动。请运行 `ollama serve` 后重试,或加 --yes 让我自动启动。\\n\",\n daemonStartConfirm: \"Ollama 守护进程未运行。现在启动 `ollama serve` 吗?\",\n daemonAbortStart: \"✗ 已取消——请自行运行 `ollama serve` 后重试。\\n\",\n daemonStarting: \"▸ 正在启动 `ollama serve`…\\n\",\n daemonStartTimeout: \"✗ 15 秒内守护进程未就绪。请在另一个终端运行 `ollama serve` 后重试。\\n\",\n daemonReady: \"✓ 守护进程已启动{pid}\\n\",\n modelNotPulledHint:\n '✗ 嵌入模型 \"{model}\" 未下载。请运行 `ollama pull {model}` 后重试,或加 --yes 让我自动下载。\\n',\n modelPullConfirm: '嵌入模型 \"{model}\" 还未下载。现在下载吗?(nomic-embed-text 约 274 MB)',\n modelAbortPull: \"✗ 已取消——请自行下载模型后重试。\\n\",\n modelPulling: \"▸ 正在下载 {model}…\\n\",\n modelPullFailed: \"✗ `ollama pull {model}` 失败(退出码 {code})。\\n\",\n modelPulled: \"✓ {model} 下载完成\\n\",\n\n progressStarting: \"正在启动…\",\n progressScan: \"扫描项目 · 已扫描 {files} 个文件\",\n progressEmbed: \"正在向量化 {done}/{total} 个片段 · {pct}%\",\n progressEmbedHeartbeat: \" {done}/{total}\\n\",\n progressScanLine: \"正在扫描文件…\\n\",\n progressEmbedLine: \"正在向量化 {total} 个片段(涉及 {files} 个文件)…\\n\",\n indexSuccess:\n \"✓ 已建立索引:扫描 {scanned} 个文件({changed} 个有变化,新增 {added} 个片段,移除 {removed} 个过期);耗时 {seconds}s\\n\",\n indexSuccessWithSkips:\n \"✓ 已建立索引:扫描 {scanned} 个文件({changed} 个有变化,新增 {added} 个片段,移除 {removed} 个过期,跳过 {skipped} 个嵌入失败的片段);耗时 {seconds}s\\n\",\n indexNothingToDo: \" (没有变化——加 --rebuild 强制重建)\\n\",\n indexFailed: \"✗ 建立索引失败:{msg}\\n\",\n\n slashHeader: \"semantic_search 状态\",\n slashEnabled: \"✓ 已启用——索引已建好,工具已注册。\",\n slashEnabledDetail: \" 索引规模:{chunks} 个片段,{files} 个文件\",\n slashEnabledHowto: \" 模型在合适的时候会自动调用 semantic_search。\",\n slashIndexMissing: \"✗ 当前项目还没有索引。\",\n slashHowToBuild: \" 启用方式:退出 Reasonix,在终端运行:\\n reasonix index\",\n slashOllamaMissing: \" 前置依赖:从 https://ollama.com 安装 Ollama\",\n slashDaemonDown: \" 已装 Ollama 但守护进程未启动,请运行:ollama serve\",\n slashIndexInfo:\n ' semantic_search 用本地 embedding 做跨语言代码理解。\\n 当你描述\"做什么\"而不是具体 token 时,比 grep 更好。',\n};\n","import { stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport type { ResolvedEmbeddingConfig } from \"../../config.js\";\nimport { t } from \"./i18n.js\";\nimport { checkOllamaStatus, pullOllamaModel, startOllamaDaemon } from \"./ollama-launcher.js\";\n\nexport interface PreflightOptions {\n model: string;\n baseUrl?: string | undefined;\n interactive: boolean;\n yesToAll: boolean;\n log?: (line: string) => void;\n}\n\nexport async function ollamaPreflight(opts: PreflightOptions): Promise<boolean> {\n const log = opts.log ?? ((line: string) => process.stderr.write(line));\n const status = await checkOllamaStatus(opts.model, opts.baseUrl);\n\n if (!status.binaryFound) {\n log(t(\"ollamaNotFound\"));\n return false;\n }\n\n if (!status.daemonRunning) {\n if (!opts.interactive && !opts.yesToAll) {\n log(t(\"daemonNotReachableHint\"));\n return false;\n }\n const ok = opts.yesToAll || (await confirm(t(\"daemonStartConfirm\"), true));\n if (!ok) {\n log(t(\"daemonAbortStart\"));\n return false;\n }\n log(t(\"daemonStarting\"));\n const started = await startOllamaDaemon({ baseUrl: opts.baseUrl, timeoutMs: 15_000 });\n if (!started.ready) {\n log(t(\"daemonStartTimeout\"));\n return false;\n }\n log(t(\"daemonReady\", { pid: started.pid ? ` (pid ${started.pid})` : \"\" }));\n }\n\n const after = status.daemonRunning ? status : await checkOllamaStatus(opts.model, opts.baseUrl);\n\n if (!after.modelPulled) {\n if (!opts.interactive && !opts.yesToAll) {\n log(t(\"modelNotPulledHint\", { model: opts.model }));\n return false;\n }\n const ok = opts.yesToAll || (await confirm(t(\"modelPullConfirm\", { model: opts.model }), true));\n if (!ok) {\n log(t(\"modelAbortPull\"));\n return false;\n }\n log(t(\"modelPulling\", { model: opts.model }));\n const ESC = String.fromCharCode(0x1b);\n const ANSI_CSI = new RegExp(`${ESC}\\\\[[0-9;]*[A-Za-z]`, \"g\");\n const code = await pullOllamaModel(opts.model, {\n onLine: (line) => {\n const cleaned = line.replace(ANSI_CSI, \"\").trim();\n if (cleaned.length === 0) return;\n log(` ${cleaned}\\n`);\n },\n });\n if (code !== 0) {\n log(t(\"modelPullFailed\", { model: opts.model, code }));\n return false;\n }\n log(t(\"modelPulled\", { model: opts.model }));\n }\n\n return true;\n}\n\nexport async function semanticPreflight(\n config: ResolvedEmbeddingConfig,\n opts: Omit<PreflightOptions, \"model\" | \"baseUrl\">,\n): Promise<boolean> {\n if (config.provider === \"openai-compat\") return true;\n return await ollamaPreflight({\n ...opts,\n model: config.model,\n baseUrl: config.baseUrl,\n });\n}\n\nexport async function confirm(question: string, defaultYes: boolean): Promise<boolean> {\n const suffix = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();\n if (raw === \"\") return defaultYes;\n return raw === \"y\" || raw === \"yes\";\n } finally {\n rl.close();\n }\n}\n"],"mappings":"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","names":[]}
@@ -8,8 +8,7 @@ import {
8
8
  import {
9
9
  loadApiKey,
10
10
  loadBaseUrl
11
- } from "./chunk-BHLHOS5Y.js";
12
- import "./chunk-WUI3P4RA.js";
11
+ } from "./chunk-BW2HWSYH.js";
13
12
 
14
13
  // src/cli/commands/commit.ts
15
14
  import { spawn, spawnSync } from "child_process";
@@ -287,4 +286,4 @@ async function commitCommand(opts = {}) {
287
286
  export {
288
287
  commitCommand
289
288
  };
290
- //# sourceMappingURL=commit-3IAGB22T.js.map
289
+ //# sourceMappingURL=commit-TQ4DMUNS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/commit.ts"],"sourcesContent":["/** Drafts via diff + recent log (style mimicry); commit uses `-F -` so multi-line bodies survive shell quoting. */\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { DeepSeekClient } from \"../../client.js\";\nimport { loadApiKey, loadBaseUrl } from \"../../config.js\";\nimport { loadDotenv } from \"../../env.js\";\n\nexport interface CommitOptions {\n /** Override the default model (deepseek-v4-flash). */\n model?: string;\n /** Skip the confirmation step — useful in scripts where the diff has been pre-reviewed. */\n yes?: boolean;\n}\n\nconst DEFAULT_MODEL = \"deepseek-v4-flash\";\nconst DIFF_BYTE_CAP = 80 * 1024;\nconst LOG_COUNT = 10;\n\nconst SYSTEM_PROMPT = `You draft git commit messages.\n\nOutput ONLY the commit message — no preamble, no \\`\\`\\` fences, no \"Here's a commit message:\" lead-in. The first line of your output IS the commit subject.\n\nMatch the project's existing style:\n- Look at the recent commits provided. Mirror their voice, conventional-commit prefix usage (or absence), tense, length, body structure.\n- If recent commits use a \"type(scope): summary\" prefix, use it. If they don't, don't invent one.\n- Subject line: one line, ≤72 chars, imperative mood, no trailing period.\n- Body (optional): explain WHY when the diff isn't self-evident. Wrap at ~72 chars. Skip the body for trivial changes — repeating the subject in the body is noise.\n\nThe diff is the source of truth for what changed; describe THAT, not your guesses about the broader project. If the diff includes a deletion you can't explain from the surrounding context, name it but don't speculate about why.\n\nNo emojis unless the recent commits use them.\nNo co-author trailers, no \"Generated with X\" footers.`;\n\nfunction runGit(\n args: string[],\n opts: { input?: string } = {},\n): { stdout: string; stderr: string; status: number | null } {\n const result = spawnSync(\"git\", args, {\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n input: opts.input,\n maxBuffer: 32 * 1024 * 1024,\n });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n status: result.status,\n };\n}\n\nfunction dieIfNotGitRepo(): void {\n const r = runGit([\"rev-parse\", \"--is-inside-work-tree\"]);\n if (r.status !== 0) {\n process.stderr.write(\"reasonix commit: not inside a git repository.\\n\");\n process.exit(1);\n }\n}\n\ninterface DiffResult {\n diff: string;\n source: \"staged\" | \"working-tree\";\n truncated: boolean;\n}\n\nfunction readDiff(): DiffResult | null {\n const staged = runGit([\"diff\", \"--staged\", \"--no-color\"]);\n if (staged.status !== 0) {\n process.stderr.write(`reasonix commit: git diff --staged failed: ${staged.stderr.trim()}\\n`);\n process.exit(1);\n }\n if (staged.stdout.trim().length > 0) {\n return capDiff(staged.stdout, \"staged\");\n }\n const wt = runGit([\"diff\", \"--no-color\"]);\n if (wt.stdout.trim().length === 0) {\n return null;\n }\n return capDiff(wt.stdout, \"working-tree\");\n}\n\nfunction capDiff(raw: string, source: \"staged\" | \"working-tree\"): DiffResult {\n if (raw.length <= DIFF_BYTE_CAP) {\n return { diff: raw, source, truncated: false };\n }\n const head = raw.slice(0, Math.floor(DIFF_BYTE_CAP * 0.7));\n const tail = raw.slice(-Math.floor(DIFF_BYTE_CAP * 0.3));\n return {\n diff: `${head}\\n\\n[… ${raw.length - DIFF_BYTE_CAP} bytes of diff truncated …]\\n\\n${tail}`,\n source,\n truncated: true,\n };\n}\n\nfunction readRecentCommits(): string {\n const r = runGit([\"log\", `-${LOG_COUNT}`, \"--no-merges\", \"--format=%s%n%b%n---END---\"]);\n if (r.status !== 0) {\n // Repo may not have any commits yet (initial commit case). Don't\n // fail — let the model work from the diff alone.\n return \"\";\n }\n return r.stdout.trim();\n}\n\nasync function draftMessage(\n client: DeepSeekClient,\n model: string,\n diff: DiffResult,\n recentCommits: string,\n): Promise<string> {\n const userParts: string[] = [];\n if (recentCommits) {\n userParts.push(`Recent commits (style reference):\\n\\n${recentCommits}`);\n }\n if (diff.source === \"working-tree\") {\n userParts.push(\n \"(NOTE: diff is from the working tree, not the staging area — nothing is staged yet. The user will stage selectively after seeing the draft.)\",\n );\n }\n userParts.push(`Diff to summarize:\\n\\n${diff.diff}`);\n\n const resp = await client.chat({\n model,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: userParts.join(\"\\n\\n\") },\n ],\n temperature: 0.2,\n });\n return stripCodeFences(resp.content.trim());\n}\n\nfunction stripCodeFences(s: string): string {\n // Some models still wrap output in ``` despite the system prompt\n // telling them not to. Strip a single leading + trailing fence pair\n // if present. Only operates on a wrapping pair — internal fences\n // (a code block inside the body) stay.\n const trimmed = s.trim();\n const fenceOpen = /^```[a-zA-Z]*\\n/;\n const fenceClose = /\\n?```$/;\n if (fenceOpen.test(trimmed) && fenceClose.test(trimmed)) {\n return trimmed.replace(fenceOpen, \"\").replace(fenceClose, \"\").trim();\n }\n return trimmed;\n}\n\nfunction printDraft(message: string): void {\n const sep = \"─\".repeat(60);\n process.stdout.write(`\\n${sep}\\n${message}\\n${sep}\\n\\n`);\n}\n\nasync function promptChoice(): Promise<\"accept\" | \"regen\" | \"edit\" | \"cancel\"> {\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const answer = await rl.question(\"[a]ccept / [r]egenerate / [e]dit / [c]ancel: \");\n const k = answer.trim().toLowerCase();\n if (k === \"\" || k === \"a\" || k === \"y\" || k === \"yes\") return \"accept\";\n if (k === \"r\" || k === \"regen\" || k === \"regenerate\") return \"regen\";\n if (k === \"e\" || k === \"edit\") return \"edit\";\n return \"cancel\";\n } finally {\n rl.close();\n }\n}\n\nfunction editInExternal(initial: string): string | null {\n const editor = process.env.GIT_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;\n if (!editor) {\n process.stderr.write(\n \"reasonix commit: no $EDITOR / $VISUAL / $GIT_EDITOR set — can't open editor. Pick [a]ccept and `git commit --amend` afterwards.\\n\",\n );\n return null;\n }\n const dir = mkdtempSync(join(tmpdir(), \"reasonix-commit-\"));\n const path = join(dir, \"COMMIT_EDITMSG\");\n writeFileSync(path, initial, \"utf8\");\n // spawnSync with shell:true is required so $EDITOR strings like\n // `code --wait` work — they're shell command lines, not argv tuples.\n // The trust boundary is the user's own env var; matches how git\n // itself launches editors.\n const result = spawnSync(`${editor} \"${path}\"`, {\n stdio: \"inherit\",\n shell: true,\n });\n if (result.status !== 0) {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n process.stderr.write(\n `reasonix commit: editor exited ${result.status} — keeping prior draft.\\n`,\n );\n return null;\n }\n let edited: string;\n try {\n edited = readFileSync(path, \"utf8\");\n } catch {\n return null;\n } finally {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n }\n // Strip git's standard `# …` comment lines, even though we didn't\n // emit any — a user habituated to `git commit` may add `#`-prefixed\n // notes by reflex.\n const cleaned = edited\n .split(/\\r?\\n/)\n .filter((line) => !/^\\s*#/.test(line))\n .join(\"\\n\")\n .trim();\n return cleaned || null;\n}\n\nfunction commitWithMessage(message: string): void {\n // -F - reads the message from stdin, sidestepping shell quoting and\n // letting multi-line bodies through cleanly. Inherit stdio so the\n // user sees git's own confirmation / pre-commit hook output.\n const child = spawn(\"git\", [\"commit\", \"-F\", \"-\"], {\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n });\n child.stdin.write(message);\n child.stdin.end();\n child.on(\"close\", (code) => {\n if (code !== 0) {\n process.stderr.write(`reasonix commit: git commit exited ${code}.\\n`);\n process.exit(code ?? 1);\n }\n });\n}\n\nexport async function commitCommand(opts: CommitOptions = {}): Promise<void> {\n loadDotenv();\n dieIfNotGitRepo();\n\n const apiKey = loadApiKey() ?? process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n process.stderr.write(\n \"reasonix commit: DEEPSEEK_API_KEY not set. Run `reasonix setup` to save one, or export it.\\n\",\n );\n process.exit(1);\n }\n\n const diff = readDiff();\n if (!diff) {\n process.stderr.write(\n \"reasonix commit: no staged changes and working tree is clean — nothing to commit.\\n\",\n );\n process.exit(1);\n }\n if (diff.source === \"working-tree\") {\n process.stderr.write(\n \"reasonix commit: nothing staged — drafting from working-tree diff. Stage your changes and re-run, or use the draft as a starting point.\\n\",\n );\n }\n if (diff.truncated) {\n process.stderr.write(\n \"reasonix commit: diff exceeded 80KB; head + tail sent to the model. Large diffs often produce vague drafts — consider committing in smaller chunks.\\n\",\n );\n }\n\n const client = new DeepSeekClient({ apiKey, baseUrl: loadBaseUrl() });\n const model = opts.model ?? DEFAULT_MODEL;\n const recentCommits = readRecentCommits();\n\n let message = \"\";\n let firstPass = true;\n while (true) {\n if (firstPass) {\n process.stdout.write(\"Drafting commit message…\\n\");\n } else {\n process.stdout.write(\"Regenerating…\\n\");\n }\n firstPass = false;\n try {\n message = await draftMessage(client, model, diff, recentCommits);\n } catch (err) {\n process.stderr.write(`reasonix commit: model call failed — ${(err as Error).message}\\n`);\n process.exit(1);\n }\n if (!message) {\n process.stderr.write(\"reasonix commit: model returned an empty draft. Try again.\\n\");\n process.exit(1);\n }\n printDraft(message);\n\n if (opts.yes) break;\n if (diff.source === \"working-tree\") {\n // Refuse to commit a working-tree-derived draft — the staging\n // area is empty so `git commit` would fail anyway. Print the\n // draft so the user can copy it; exit 0 because we did our job.\n process.stdout.write(\n \"(no staged changes — draft printed above for you to copy. Stage with `git add` and re-run to commit.)\\n\",\n );\n return;\n }\n\n const choice = await promptChoice();\n if (choice === \"accept\") break;\n if (choice === \"cancel\") {\n process.stderr.write(\"commit cancelled.\\n\");\n return;\n }\n if (choice === \"edit\") {\n const edited = editInExternal(message);\n if (edited) {\n message = edited;\n printDraft(message);\n // Re-prompt: the user may want to edit again, accept, etc.\n const next = await promptChoice();\n if (next === \"accept\") break;\n if (next === \"cancel\") {\n process.stderr.write(\"commit cancelled.\\n\");\n return;\n }\n // next is \"regen\" or another \"edit\" — fall through to the\n // loop top to re-draft (regen) or land back at this branch.\n }\n // editor returned no edit — loop top will regen by default.\n }\n // Anything else (regen, or unsuccessful edit) → loop top redraws.\n }\n\n commitWithMessage(message);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/commit.ts"],"sourcesContent":["/** Drafts via diff + recent log (style mimicry); commit uses `-F -` so multi-line bodies survive shell quoting. */\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stdin, stdout } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { DeepSeekClient } from \"../../client.js\";\nimport { loadApiKey, loadBaseUrl } from \"../../config.js\";\nimport { loadDotenv } from \"../../env.js\";\n\nexport interface CommitOptions {\n /** Override the default model (deepseek-v4-flash). */\n model?: string;\n /** Skip the confirmation step — useful in scripts where the diff has been pre-reviewed. */\n yes?: boolean;\n}\n\nconst DEFAULT_MODEL = \"deepseek-v4-flash\";\nconst DIFF_BYTE_CAP = 80 * 1024;\nconst LOG_COUNT = 10;\n\nconst SYSTEM_PROMPT = `You draft git commit messages.\n\nOutput ONLY the commit message — no preamble, no \\`\\`\\` fences, no \"Here's a commit message:\" lead-in. The first line of your output IS the commit subject.\n\nMatch the project's existing style:\n- Look at the recent commits provided. Mirror their voice, conventional-commit prefix usage (or absence), tense, length, body structure.\n- If recent commits use a \"type(scope): summary\" prefix, use it. If they don't, don't invent one.\n- Subject line: one line, ≤72 chars, imperative mood, no trailing period.\n- Body (optional): explain WHY when the diff isn't self-evident. Wrap at ~72 chars. Skip the body for trivial changes — repeating the subject in the body is noise.\n\nThe diff is the source of truth for what changed; describe THAT, not your guesses about the broader project. If the diff includes a deletion you can't explain from the surrounding context, name it but don't speculate about why.\n\nNo emojis unless the recent commits use them.\nNo co-author trailers, no \"Generated with X\" footers.`;\n\nfunction runGit(\n args: string[],\n opts: { input?: string } = {},\n): { stdout: string; stderr: string; status: number | null } {\n const result = spawnSync(\"git\", args, {\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n input: opts.input,\n maxBuffer: 32 * 1024 * 1024,\n });\n return {\n stdout: result.stdout ?? \"\",\n stderr: result.stderr ?? \"\",\n status: result.status,\n };\n}\n\nfunction dieIfNotGitRepo(): void {\n const r = runGit([\"rev-parse\", \"--is-inside-work-tree\"]);\n if (r.status !== 0) {\n process.stderr.write(\"reasonix commit: not inside a git repository.\\n\");\n process.exit(1);\n }\n}\n\ninterface DiffResult {\n diff: string;\n source: \"staged\" | \"working-tree\";\n truncated: boolean;\n}\n\nfunction readDiff(): DiffResult | null {\n const staged = runGit([\"diff\", \"--staged\", \"--no-color\"]);\n if (staged.status !== 0) {\n process.stderr.write(`reasonix commit: git diff --staged failed: ${staged.stderr.trim()}\\n`);\n process.exit(1);\n }\n if (staged.stdout.trim().length > 0) {\n return capDiff(staged.stdout, \"staged\");\n }\n const wt = runGit([\"diff\", \"--no-color\"]);\n if (wt.stdout.trim().length === 0) {\n return null;\n }\n return capDiff(wt.stdout, \"working-tree\");\n}\n\nfunction capDiff(raw: string, source: \"staged\" | \"working-tree\"): DiffResult {\n if (raw.length <= DIFF_BYTE_CAP) {\n return { diff: raw, source, truncated: false };\n }\n const head = raw.slice(0, Math.floor(DIFF_BYTE_CAP * 0.7));\n const tail = raw.slice(-Math.floor(DIFF_BYTE_CAP * 0.3));\n return {\n diff: `${head}\\n\\n[… ${raw.length - DIFF_BYTE_CAP} bytes of diff truncated …]\\n\\n${tail}`,\n source,\n truncated: true,\n };\n}\n\nfunction readRecentCommits(): string {\n const r = runGit([\"log\", `-${LOG_COUNT}`, \"--no-merges\", \"--format=%s%n%b%n---END---\"]);\n if (r.status !== 0) {\n // Repo may not have any commits yet (initial commit case). Don't\n // fail — let the model work from the diff alone.\n return \"\";\n }\n return r.stdout.trim();\n}\n\nasync function draftMessage(\n client: DeepSeekClient,\n model: string,\n diff: DiffResult,\n recentCommits: string,\n): Promise<string> {\n const userParts: string[] = [];\n if (recentCommits) {\n userParts.push(`Recent commits (style reference):\\n\\n${recentCommits}`);\n }\n if (diff.source === \"working-tree\") {\n userParts.push(\n \"(NOTE: diff is from the working tree, not the staging area — nothing is staged yet. The user will stage selectively after seeing the draft.)\",\n );\n }\n userParts.push(`Diff to summarize:\\n\\n${diff.diff}`);\n\n const resp = await client.chat({\n model,\n messages: [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: userParts.join(\"\\n\\n\") },\n ],\n temperature: 0.2,\n });\n return stripCodeFences(resp.content.trim());\n}\n\nfunction stripCodeFences(s: string): string {\n // Some models still wrap output in ``` despite the system prompt\n // telling them not to. Strip a single leading + trailing fence pair\n // if present. Only operates on a wrapping pair — internal fences\n // (a code block inside the body) stay.\n const trimmed = s.trim();\n const fenceOpen = /^```[a-zA-Z]*\\n/;\n const fenceClose = /\\n?```$/;\n if (fenceOpen.test(trimmed) && fenceClose.test(trimmed)) {\n return trimmed.replace(fenceOpen, \"\").replace(fenceClose, \"\").trim();\n }\n return trimmed;\n}\n\nfunction printDraft(message: string): void {\n const sep = \"─\".repeat(60);\n process.stdout.write(`\\n${sep}\\n${message}\\n${sep}\\n\\n`);\n}\n\nasync function promptChoice(): Promise<\"accept\" | \"regen\" | \"edit\" | \"cancel\"> {\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n const answer = await rl.question(\"[a]ccept / [r]egenerate / [e]dit / [c]ancel: \");\n const k = answer.trim().toLowerCase();\n if (k === \"\" || k === \"a\" || k === \"y\" || k === \"yes\") return \"accept\";\n if (k === \"r\" || k === \"regen\" || k === \"regenerate\") return \"regen\";\n if (k === \"e\" || k === \"edit\") return \"edit\";\n return \"cancel\";\n } finally {\n rl.close();\n }\n}\n\nfunction editInExternal(initial: string): string | null {\n const editor = process.env.GIT_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;\n if (!editor) {\n process.stderr.write(\n \"reasonix commit: no $EDITOR / $VISUAL / $GIT_EDITOR set — can't open editor. Pick [a]ccept and `git commit --amend` afterwards.\\n\",\n );\n return null;\n }\n const dir = mkdtempSync(join(tmpdir(), \"reasonix-commit-\"));\n const path = join(dir, \"COMMIT_EDITMSG\");\n writeFileSync(path, initial, \"utf8\");\n // spawnSync with shell:true is required so $EDITOR strings like\n // `code --wait` work — they're shell command lines, not argv tuples.\n // The trust boundary is the user's own env var; matches how git\n // itself launches editors.\n const result = spawnSync(`${editor} \"${path}\"`, {\n stdio: \"inherit\",\n shell: true,\n });\n if (result.status !== 0) {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n process.stderr.write(\n `reasonix commit: editor exited ${result.status} — keeping prior draft.\\n`,\n );\n return null;\n }\n let edited: string;\n try {\n edited = readFileSync(path, \"utf8\");\n } catch {\n return null;\n } finally {\n try {\n unlinkSync(path);\n } catch {\n /* ignore */\n }\n }\n // Strip git's standard `# …` comment lines, even though we didn't\n // emit any — a user habituated to `git commit` may add `#`-prefixed\n // notes by reflex.\n const cleaned = edited\n .split(/\\r?\\n/)\n .filter((line) => !/^\\s*#/.test(line))\n .join(\"\\n\")\n .trim();\n return cleaned || null;\n}\n\nfunction commitWithMessage(message: string): void {\n // -F - reads the message from stdin, sidestepping shell quoting and\n // letting multi-line bodies through cleanly. Inherit stdio so the\n // user sees git's own confirmation / pre-commit hook output.\n const child = spawn(\"git\", [\"commit\", \"-F\", \"-\"], {\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n });\n child.stdin.write(message);\n child.stdin.end();\n child.on(\"close\", (code) => {\n if (code !== 0) {\n process.stderr.write(`reasonix commit: git commit exited ${code}.\\n`);\n process.exit(code ?? 1);\n }\n });\n}\n\nexport async function commitCommand(opts: CommitOptions = {}): Promise<void> {\n loadDotenv();\n dieIfNotGitRepo();\n\n const apiKey = loadApiKey() ?? process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n process.stderr.write(\n \"reasonix commit: DEEPSEEK_API_KEY not set. Run `reasonix setup` to save one, or export it.\\n\",\n );\n process.exit(1);\n }\n\n const diff = readDiff();\n if (!diff) {\n process.stderr.write(\n \"reasonix commit: no staged changes and working tree is clean — nothing to commit.\\n\",\n );\n process.exit(1);\n }\n if (diff.source === \"working-tree\") {\n process.stderr.write(\n \"reasonix commit: nothing staged — drafting from working-tree diff. Stage your changes and re-run, or use the draft as a starting point.\\n\",\n );\n }\n if (diff.truncated) {\n process.stderr.write(\n \"reasonix commit: diff exceeded 80KB; head + tail sent to the model. Large diffs often produce vague drafts — consider committing in smaller chunks.\\n\",\n );\n }\n\n const client = new DeepSeekClient({ apiKey, baseUrl: loadBaseUrl() });\n const model = opts.model ?? DEFAULT_MODEL;\n const recentCommits = readRecentCommits();\n\n let message = \"\";\n let firstPass = true;\n while (true) {\n if (firstPass) {\n process.stdout.write(\"Drafting commit message…\\n\");\n } else {\n process.stdout.write(\"Regenerating…\\n\");\n }\n firstPass = false;\n try {\n message = await draftMessage(client, model, diff, recentCommits);\n } catch (err) {\n process.stderr.write(`reasonix commit: model call failed — ${(err as Error).message}\\n`);\n process.exit(1);\n }\n if (!message) {\n process.stderr.write(\"reasonix commit: model returned an empty draft. Try again.\\n\");\n process.exit(1);\n }\n printDraft(message);\n\n if (opts.yes) break;\n if (diff.source === \"working-tree\") {\n // Refuse to commit a working-tree-derived draft — the staging\n // area is empty so `git commit` would fail anyway. Print the\n // draft so the user can copy it; exit 0 because we did our job.\n process.stdout.write(\n \"(no staged changes — draft printed above for you to copy. Stage with `git add` and re-run to commit.)\\n\",\n );\n return;\n }\n\n const choice = await promptChoice();\n if (choice === \"accept\") break;\n if (choice === \"cancel\") {\n process.stderr.write(\"commit cancelled.\\n\");\n return;\n }\n if (choice === \"edit\") {\n const edited = editInExternal(message);\n if (edited) {\n message = edited;\n printDraft(message);\n // Re-prompt: the user may want to edit again, accept, etc.\n const next = await promptChoice();\n if (next === \"accept\") break;\n if (next === \"cancel\") {\n process.stderr.write(\"commit cancelled.\\n\");\n return;\n }\n // next is \"regen\" or another \"edit\" — fall through to the\n // loop top to re-draft (regen) or land back at this branch.\n }\n // editor returned no edit — loop top will regen by default.\n }\n // Anything else (regen, or unsuccessful edit) → loop top redraws.\n }\n\n commitWithMessage(message);\n}\n"],"mappings":"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","names":[]}
@@ -2,20 +2,19 @@
2
2
  import {
3
3
  doctorCommand,
4
4
  runDoctorChecks
5
- } from "./chunk-JULZ7JTO.js";
5
+ } from "./chunk-T5U5JO7Q.js";
6
6
  import "./chunk-DAEAAVDF.js";
7
7
  import "./chunk-KMWKGPFZ.js";
8
8
  import "./chunk-3Q3C4W66.js";
9
- import "./chunk-XQIFIB3U.js";
10
- import "./chunk-JGZKTAOH.js";
9
+ import "./chunk-JOFZ6AW5.js";
10
+ import "./chunk-LMNAMITH.js";
11
11
  import "./chunk-5X7LZJDE.js";
12
12
  import "./chunk-6CXT5JRM.js";
13
- import "./chunk-GKZJXYMY.js";
14
- import "./chunk-BHLHOS5Y.js";
15
- import "./chunk-WUI3P4RA.js";
13
+ import "./chunk-FYKZB6TX.js";
14
+ import "./chunk-BW2HWSYH.js";
16
15
  import "./chunk-CRPQUBP6.js";
17
16
  export {
18
17
  doctorCommand,
19
18
  runDoctorChecks
20
19
  };
21
- //# sourceMappingURL=doctor-BW5HSQDW.js.map
20
+ //# sourceMappingURL=doctor-GGK2JKTA.js.map