reasonix 0.3.2 → 0.4.3

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.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CODE_SYSTEM_PROMPT,
4
+ codeSystemPrompt
5
+ } from "./chunk-2P2MZLCE.js";
6
+ export {
7
+ CODE_SYSTEM_PROMPT,
8
+ codeSystemPrompt
9
+ };
10
+ //# sourceMappingURL=prompt-MMANQ36Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.ts CHANGED
@@ -433,7 +433,15 @@ declare class ToolRegistry {
433
433
  dispatch(name: string, argumentsRaw: string | Record<string, unknown>): Promise<string>;
434
434
  }
435
435
 
436
- type EventRole = "assistant_delta" | "assistant_final" | "tool" | "done" | "error" | "warning" | "branch_start" | "branch_progress" | "branch_done";
436
+ type EventRole = "assistant_delta" | "assistant_final"
437
+ /**
438
+ * Yielded immediately before a tool is dispatched. Lets the TUI put
439
+ * up a "▸ tool<X> running…" spinner while the tool's Promise is
440
+ * pending — otherwise the UI looks frozen whenever a tool call
441
+ * takes more than a few hundred ms (a big `filesystem_edit_file`
442
+ * is a typical trigger).
443
+ */
444
+ | "tool_start" | "tool" | "done" | "error" | "warning" | "branch_start" | "branch_progress" | "branch_done";
437
445
  interface BranchSummary {
438
446
  budget: number;
439
447
  chosenIndex: number;
@@ -465,6 +473,16 @@ interface LoopEvent {
465
473
  branch?: BranchSummary;
466
474
  branchProgress?: BranchProgress;
467
475
  error?: string;
476
+ /**
477
+ * True on `assistant_final` events emitted by the no-tools fallback
478
+ * when the loop hit its budget, was aborted, or tripped the
479
+ * token-context guard. Consumers that act on assistant text (notably
480
+ * the code-mode edit applier) MUST treat these as display-only —
481
+ * the model is "wrapping up," not proposing new work. Applying
482
+ * SEARCH/REPLACE blocks found in a forced summary caused the
483
+ * "analysis became edits" bug in v0.4.1 and earlier.
484
+ */
485
+ forcedSummary?: boolean;
468
486
  }
469
487
  interface CacheFirstLoopOptions {
470
488
  client: DeepSeekClient;
@@ -567,11 +585,34 @@ declare class CacheFirstLoop {
567
585
  * answer instead of a cliff. Called by the TUI on Esc.
568
586
  */
569
587
  abort(): void;
588
+ /**
589
+ * Drop everything in the log after (and including) the most recent
590
+ * user message. Used by `/retry` so the caller can re-send that
591
+ * message with a fresh turn instead of layering another response on
592
+ * top of the prior exchange. Returns the content of the dropped user
593
+ * message, or `null` if there isn't one yet.
594
+ *
595
+ * Persists by rewriting the session file — otherwise the next
596
+ * launch would rehydrate the old exchange and `/retry` would seem
597
+ * to have done nothing.
598
+ */
599
+ retryLastUser(): string | null;
570
600
  step(userInput: string): AsyncGenerator<LoopEvent>;
571
601
  private forceSummaryAfterIterLimit;
572
602
  run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
573
603
  private assistantMessage;
574
604
  }
605
+ /**
606
+ * R1 occasionally hallucinates tool-call markup as plain text when the
607
+ * real tool channel has been closed — typically our forced-summary
608
+ * path, where `tools: undefined` is supposed to force prose but isn't
609
+ * always respected. The markup isn't parsed by our tool-call path
610
+ * (the API response's structured `tool_calls` field is empty), so
611
+ * it's just noise in the user's view. Strip known envelope shapes.
612
+ *
613
+ * Exported so tests can exercise it against concrete R1 outputs.
614
+ */
615
+ declare function stripHallucinatedToolMarkup(s: string): string;
575
616
  /**
576
617
  * Truncate any tool-role message whose content exceeds the cap. User
577
618
  * and assistant messages are left alone because (a) they're almost
@@ -1238,6 +1279,113 @@ interface SseMcpSpec {
1238
1279
  type McpSpec = StdioMcpSpec | SseMcpSpec;
1239
1280
  declare function parseMcpSpec(input: string): McpSpec;
1240
1281
 
1282
+ /**
1283
+ * Aider-style SEARCH/REPLACE edit blocks.
1284
+ *
1285
+ * The model emits blocks in this exact shape, one or more per response:
1286
+ *
1287
+ * path/to/file.ts
1288
+ * <<<<<<< SEARCH
1289
+ * exact existing lines (whitespace-sensitive)
1290
+ * =======
1291
+ * replacement lines
1292
+ * >>>>>>> REPLACE
1293
+ *
1294
+ * We chose this over unified diffs because:
1295
+ * - Models produce it reliably — no line-number drift.
1296
+ * - It tolerates multi-edit responses without ambiguity over which
1297
+ * hunk belongs to which file.
1298
+ * - Aider has years of evidence that this format works even against
1299
+ * weaker models than DeepSeek R1, so it's a conservative pick.
1300
+ *
1301
+ * The SEARCH text must match the file byte-for-byte. Empty SEARCH is a
1302
+ * sentinel for "create new file" — the REPLACE becomes the whole file.
1303
+ * If SEARCH doesn't match we refuse the edit and surface the failure;
1304
+ * we do NOT guess or fuzzy-match. A wrong silent edit is worse than a
1305
+ * missing one — the user can re-ask with the exact current content.
1306
+ */
1307
+ interface EditBlock {
1308
+ /** Path as written by the model — relative to rootDir, or absolute. */
1309
+ path: string;
1310
+ /** Literal text to match in the target file. Empty → create new file. */
1311
+ search: string;
1312
+ /** Replacement text to write in place of `search`. */
1313
+ replace: string;
1314
+ /** Char offset in the source message where this block started. */
1315
+ offset: number;
1316
+ }
1317
+ type ApplyStatus =
1318
+ /** Edit landed on disk. */
1319
+ "applied"
1320
+ /** New file created (SEARCH was empty and file didn't exist). */
1321
+ | "created"
1322
+ /** File exists but SEARCH block wasn't found in its content. */
1323
+ | "not-found"
1324
+ /** File doesn't exist and SEARCH was non-empty (can't create without content). */
1325
+ | "file-missing"
1326
+ /** Path escapes rootDir — refused on safety grounds. */
1327
+ | "path-escape"
1328
+ /** fs write / read threw. */
1329
+ | "error";
1330
+ interface ApplyResult {
1331
+ path: string;
1332
+ status: ApplyStatus;
1333
+ /** Extra detail (e.g. error message) for logs. */
1334
+ message?: string;
1335
+ }
1336
+ declare function parseEditBlocks(text: string): EditBlock[];
1337
+ declare function applyEditBlock(block: EditBlock, rootDir: string): ApplyResult;
1338
+ declare function applyEditBlocks(blocks: EditBlock[], rootDir: string): ApplyResult[];
1339
+ interface EditSnapshot {
1340
+ /** Path relative to rootDir, as the block named it. */
1341
+ path: string;
1342
+ /**
1343
+ * File content before the edit batch was applied. `null` means the
1344
+ * file didn't exist yet — restoring that means deleting whatever the
1345
+ * edit created.
1346
+ */
1347
+ prevContent: string | null;
1348
+ }
1349
+ /**
1350
+ * Capture the current state of every file an edit batch is about to
1351
+ * touch, so `/undo` can roll back if the user doesn't like the result.
1352
+ * De-duplicates by path because one batch can contain multiple blocks
1353
+ * for the same file, and we only want one "before" snapshot per file.
1354
+ */
1355
+ declare function snapshotBeforeEdits(blocks: EditBlock[], rootDir: string): EditSnapshot[];
1356
+ /**
1357
+ * Restore files to their snapshotted state. Snapshots with
1358
+ * `prevContent === null` were created by the edit, so undo = delete.
1359
+ * Otherwise the prior content is written back, replacing whatever the
1360
+ * edit left behind.
1361
+ */
1362
+ declare function restoreSnapshots(snapshots: EditSnapshot[], rootDir: string): ApplyResult[];
1363
+
1364
+ /**
1365
+ * System prompt used by `reasonix code`. Teaches the model:
1366
+ *
1367
+ * 1. It has a filesystem MCP bridge rooted at the user's CWD.
1368
+ * 2. To modify files it emits SEARCH/REPLACE blocks (not
1369
+ * `write_file` — that would whole-file rewrite and kill diff
1370
+ * reviewability).
1371
+ * 3. Read first, edit second — SEARCH must match byte-for-byte.
1372
+ * 4. Be concise. The user can read a diff faster than prose.
1373
+ *
1374
+ * Kept short on purpose. Long system prompts eat context budget that
1375
+ * the Cache-First Loop is trying to conserve. The SEARCH/REPLACE spec
1376
+ * is the one unavoidable bloat; we trim everything else.
1377
+ */
1378
+ declare const CODE_SYSTEM_PROMPT = "You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.\n\n# When to edit vs. when to explore\n\nOnly propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:\n- analyze, read, explore, describe, or summarize a project\n- explain how something works\n- answer a question about the code\n\nIn those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.\n\nWhen you do propose edits, the user will review them and decide whether to `/apply` or `/discard`. Don't assume they'll accept \u2014 write as if each edit will be audited, because it will.\n\n# Editing files\n\nWhen you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:\n\npath/to/file.ext\n<<<<<<< SEARCH\nexact existing lines from the file, including whitespace\n=======\nthe new lines\n>>>>>>> REPLACE\n\nRules:\n- Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.\n- One edit per block. Multiple blocks in one response are fine.\n- To create a new file, leave SEARCH empty:\n path/to/new.ts\n <<<<<<< SEARCH\n =======\n (whole file content here)\n >>>>>>> REPLACE\n- Do NOT use write_file to change existing files \u2014 the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).\n- Paths are relative to the working directory. Don't use absolute paths.\n\n# Exploration\n\n- Avoid listing or reading inside these common dependency / build directories unless the user explicitly asks about them: node_modules, dist, build, out, .next, .nuxt, .svelte-kit, .git, .venv, venv, __pycache__, target, coverage, .turbo, .cache. They're expensive and usually irrelevant.\n- Prefer search_files / grep over list_directory when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees.\n\n# Style\n\n- Show edits; don't narrate them in prose. \"Here's the fix:\" is enough.\n- One short paragraph explaining *why*, then the blocks.\n- If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.\n";
1379
+ /**
1380
+ * Inject the project's `.gitignore` content into the system prompt as a
1381
+ * "respect this on top of the built-in denylist" hint. We don't parse
1382
+ * the file — we hand it to the model as-is. Truncate long ones so we
1383
+ * don't eat context budget on huge generated ignore lists.
1384
+ *
1385
+ * Missing or unreadable .gitignore → returns the base prompt unchanged.
1386
+ */
1387
+ declare function codeSystemPrompt(rootDir: string): string;
1388
+
1241
1389
  /**
1242
1390
  * User-level config storage for the Reasonix CLI.
1243
1391
  *
@@ -1291,6 +1439,6 @@ declare function redactKey(key: string): string;
1291
1439
 
1292
1440
  /** Reasonix — DeepSeek-native agent framework. Library entry point. */
1293
1441
 
1294
- declare const VERSION = "0.3.2";
1442
+ declare const VERSION = "0.4.3";
1295
1443
 
1296
- export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EventRole, type FlattenDecision, type FlattenOptions, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, bridgeMcpTools, claudeEquivalentCost, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatLoopError, harvest, healLoadedMessages, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, truncateForModel, writeConfig, writeMeta, writeRecord };
1444
+ export { AppendOnlyLog, type ApplyResult, type ApplyStatus, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FlattenDecision, type FlattenOptions, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, applyEditBlock, applyEditBlocks, bridgeMcpTools, claudeEquivalentCost, codeSystemPrompt, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatLoopError, harvest, healLoadedMessages, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseEditBlocks, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, restoreSnapshots, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, truncateForModel, writeConfig, writeMeta, writeRecord };