reasonix 0.8.0 → 0.11.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.
- package/README.md +4 -0
- package/README.zh-CN.md +721 -0
- package/dist/cli/{chunk-DVBNMXA6.js → chunk-GXABXQMU.js} +143 -4
- package/dist/cli/chunk-GXABXQMU.js.map +1 -0
- package/dist/cli/index.js +2223 -503
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-POARCKKR.js → prompt-FMYQ7IDW.js} +2 -2
- package/dist/index.d.ts +40 -23
- package/dist/index.js +158 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-DVBNMXA6.js.map +0 -1
- /package/dist/cli/{prompt-POARCKKR.js.map → prompt-FMYQ7IDW.js.map} +0 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CODE_SYSTEM_PROMPT,
|
|
4
4
|
codeSystemPrompt
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GXABXQMU.js";
|
|
6
6
|
export {
|
|
7
7
|
CODE_SYSTEM_PROMPT,
|
|
8
8
|
codeSystemPrompt
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=prompt-
|
|
10
|
+
//# sourceMappingURL=prompt-FMYQ7IDW.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1622,12 +1622,12 @@ declare function applyUserMemory(basePrompt: string, opts?: {
|
|
|
1622
1622
|
projectRoot?: string;
|
|
1623
1623
|
}): string;
|
|
1624
1624
|
/**
|
|
1625
|
-
* Compose every lazy-loaded prefix block in one call: REASONIX.md,
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1630
|
-
* filesystem state.
|
|
1625
|
+
* Compose every lazy-loaded prefix block in one call: project REASONIX.md,
|
|
1626
|
+
* global REASONIX.md (`#g` destination), user memory indexes (global +
|
|
1627
|
+
* per-project), and the skills index. Drop-in replacement for
|
|
1628
|
+
* `applyProjectMemory` at CLI entry points. Stacking order is stable —
|
|
1629
|
+
* the prefix hash only changes when block *content* changes, not when
|
|
1630
|
+
* this helper is called a second time with the same filesystem state.
|
|
1631
1631
|
*/
|
|
1632
1632
|
declare function applyMemoryStack(basePrompt: string, rootDir: string): string;
|
|
1633
1633
|
|
|
@@ -2214,8 +2214,14 @@ interface ShellToolsOptions {
|
|
|
2214
2214
|
* When true, skip the allowlist entirely and auto-run every command.
|
|
2215
2215
|
* Off by default — this is an escape hatch for non-interactive use
|
|
2216
2216
|
* (CI, benchmarks) where a human can't be in the loop to confirm.
|
|
2217
|
+
*
|
|
2218
|
+
* Accepts either a static boolean (captured once) or a getter called
|
|
2219
|
+
* on every dispatch. The getter form is what `reasonix code` uses to
|
|
2220
|
+
* wire `editMode === "yolo"` into the registry: flipping the mode
|
|
2221
|
+
* mid-session must take effect on the next tool call without forcing
|
|
2222
|
+
* a re-registration. Static `true` is fine for CI / benchmark code.
|
|
2217
2223
|
*/
|
|
2218
|
-
allowAll?: boolean;
|
|
2224
|
+
allowAll?: boolean | (() => boolean);
|
|
2219
2225
|
/**
|
|
2220
2226
|
* Background-process registry shared between `run_background`,
|
|
2221
2227
|
* `job_output`, `stop_job`, `list_jobs`, and the /jobs /kill slashes.
|
|
@@ -3423,16 +3429,13 @@ declare function restoreSnapshots(snapshots: EditSnapshot[], rootDir: string): A
|
|
|
3423
3429
|
* is the one unavoidable bloat; we trim everything else.
|
|
3424
3430
|
*/
|
|
3425
3431
|
declare const CODE_SYSTEM_PROMPT = "You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, list_directory, directory_tree, search_files, search_content, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell.\n\n# Cite or shut up \u2014 non-negotiable\n\nEvery factual claim you make about THIS codebase must be backed by evidence. Reasonix VALIDATES the citations you write \u2014 broken paths or out-of-range lines render in **red strikethrough with \u274C** in front of the user.\n\n**Positive claims** (a file exists, a function does X, a feature IS implemented) \u2014 append a markdown link to the source:\n\n- \u2705 Correct: `The MCP client supports listResources [listResources](src/mcp/client.ts:142).`\n- \u274C Wrong: `The MCP client supports listResources.` \u2190 no citation, looks authoritative but unverifiable.\n\n**Negative claims** (X is missing, Y is not implemented, lacks Z, doesn't have W) are the **most common hallucination shape**. They feel safe to write because no citation seems possible \u2014 but that's exactly why you must NOT write them on instinct.\n\nIf you are about to write \"X is missing\" or \"Y is not implemented\" \u2014 **STOP**. Call `search_content` for the relevant symbol or term FIRST. Only then:\n\n- If the search returns matches \u2192 you were wrong; correct yourself and cite the matches.\n- If the search returns nothing \u2192 state the absence with the search query as your evidence: `No callers of \\`foo()\\` found (search_content \"foo\").`\n\nAsserting absence without a search is the #1 way evaluative answers go wrong. Treat the urge to write \"missing\" as a red flag in your own reasoning.\n\n# When to propose a plan (submit_plan)\n\nYou have a `submit_plan` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:\n\n- Multi-file refactors or renames.\n- Architecture changes (moving modules, splitting / merging files, new abstractions).\n- Anything where \"undo\" after the fact would be expensive \u2014 migrations, destructive cleanups, API shape changes.\n- When the user's request is ambiguous and multiple reasonable interpretations exist \u2014 propose your reading as a plan and let them confirm.\n\nSkip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.\n\nPlan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an \"Open questions\" section \u2014 the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP \u2014 don't call any more tools, wait for the user's verdict.\n\n**Do NOT use submit_plan to present A/B/C route menus.** The approve/refine/cancel picker has no branch selector \u2014 a menu plan strands the user. For branching decisions, use `ask_choice` (see below); only call submit_plan once the user has picked a direction and you have ONE actionable plan.\n\n# When to ask the user to pick (ask_choice)\n\nYou have an `ask_choice` tool. **If the user is supposed to pick between alternatives, the tool picks \u2014 you don't enumerate the choices as prose.** Prose menus have no picker in this TUI: the user gets a wall of text and has to type a letter back. The tool fires an arrow-key picker that's strictly better.\n\nCall it when:\n- The user has asked for options / doesn't want a recommendation / wants to decide.\n- You've analyzed multiple approaches and the final call is theirs.\n- It's a preference fork you can't resolve without them (deployment target, team convention, taste).\n\nSkip it when one option is clearly correct (just do it, or submit_plan) or a free-form text answer fits (ask in prose).\n\nEach option: short stable id (A/B/C), one-line title, optional summary. `allowCustom: true` when their real answer might not fit. Max 6. A ~1-sentence lead-in before the call is fine (\"I see three directions \u2014 letting you pick\"); don't repeat the options in it. After the call, STOP.\n\n# Plan mode (/plan)\n\nThe user can ALSO enter \"plan mode\" via /plan, which is a stronger, explicit constraint:\n- Write tools (edit_file, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like \"unavailable in plan mode\". Don't retry them.\n- Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work \u2014 use them to investigate.\n- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.\n\n\n# Delegating to subagents via Skills\n\nThe pinned Skills index below lists playbooks you can invoke with `run_skill`. Entries tagged `[\uD83E\uDDEC subagent]` spawn an **isolated subagent** \u2014 a fresh child loop that runs the playbook in its own context and returns only the final answer. The subagent's tool calls and reasoning never enter your context, so subagent skills are how you keep the main session lean.\n\n**When you call `run_skill`, the `name` is ONLY the identifier before the tag** \u2014 e.g. `run_skill({ name: \"explore\", arguments: \"...\" })`, NOT `\"[\uD83E\uDDEC subagent] explore\"` and NOT `\"explore [\uD83E\uDDEC subagent]\"`. The tag is display sugar; the name argument is just the bare identifier.\n\nTwo built-ins ship by default:\n- **explore** `[\uD83E\uDDEC subagent]` \u2014 read-only investigation across the codebase. Use when the user says things like \"find all places that...\", \"how does X work across the project\", \"survey the code for Y\". Pass `arguments` describing the concrete question.\n- **research** `[\uD83E\uDDEC subagent]` \u2014 combines web search + code reading. Use for \"is X supported by lib Y\", \"what's the canonical way to Z\", \"compare our impl to the spec\".\n\nWhen to delegate (call `run_skill` with a subagent skill):\n- The task would otherwise need >5 file reads or searches.\n- You only need the conclusion, not the exploration trail.\n- The work is self-contained (you can describe it in one paragraph).\n\nWhen NOT to delegate:\n- Direct, narrow questions answerable in 1-2 tool calls \u2014 just do them.\n- Anything where you need to track intermediate results yourself (planning, multi-step edits).\n- Anything that requires user interaction (subagents can't submit plans or ask you for clarification).\n\nAlways pass a clear, self-contained `arguments` \u2014 that text is the **only** context the subagent gets.\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\nReasonix runs an **edit gate**. The user's current mode (`review` or `auto`) decides what happens to your writes; you DO NOT see which mode is active, and you SHOULD NOT ask. Write the same way in both cases.\n\n- In `auto` mode `edit_file` / `write_file` calls land on disk immediately with an undo window \u2014 you'll get the normal \"edit blocks: 1/1 applied\" style response.\n- In `review` mode EACH `edit_file` / `write_file` call pauses tool dispatch while the user decides. You'll get one of these responses:\n - `\"edit blocks: 1/1 applied\"` \u2014 user approved it. Continue as normal.\n - `\"User rejected this edit to <path>. Don't retry the same SEARCH/REPLACE\u2026\"` \u2014 user said no to THIS specific edit. Do NOT re-emit the same block, do NOT switch tools to sneak it past the gate (write_file \u2192 edit_file, or text-form SEARCH/REPLACE). Either take a clearly different approach or stop and ask the user what they want instead.\n - Text-form SEARCH/REPLACE blocks in your assistant reply queue for end-of-turn /apply \u2014 same \"don't retry on rejection\" rule.\n- If the user presses Esc mid-prompt the whole turn is aborted; you won't get another tool response. Don't keep spamming tool calls after an abort.\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# Trust what you already know\n\nBefore exploring the filesystem to answer a factual question, check whether the answer is already in context: the user's current message, earlier turns in this conversation (including prior tool results from `remember`), and the pinned memory blocks at the top of this prompt. When the user has stated a fact or you have remembered one, it outranks what the files say \u2014 don't re-derive from code what the user already told you. Explore when you genuinely don't know.\n\n# Exploration\n\n- Skip dependency, build, and VCS directories unless the user explicitly asks. The pinned .gitignore block (if any, below) is your authoritative denylist.\n- Prefer `search_files` over `list_directory` when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees. Note: `search_files` matches file NAMES; for searching file CONTENTS use `search_content`.\n- Available exploration tools: `read_file`, `list_directory`, `directory_tree`, `search_files` (filename match), `search_content` (content grep \u2014 use for \"where is X called\", \"find all references to Y\"), `get_file_info`. Don't call `grep` or other tools that aren't in this list \u2014 they don't exist as functions.\n\n# Path conventions\n\nTwo different rules depending on which tool:\n\n- **Filesystem tools** (`read_file`, `list_directory`, `search_files`, `edit_file`, etc.): paths are sandbox-relative. `/` means the project root, `/src/foo.ts` means `<project>/src/foo.ts`. Both relative (`src/foo.ts`) and POSIX-absolute (`/src/foo.ts`) forms work.\n- **`run_command`**: the command runs in a real OS shell with cwd pinned to the project root. Paths inside the shell command are interpreted by THAT shell, not by us. **Never use leading `/` in run_command arguments** \u2014 Windows treats `/tests` as drive-root `F:\\tests` (non-existent), POSIX shells treat it as filesystem root. Use plain relative paths (`tests`, `./tests`, `src/loop.ts`) instead.\n\n# Foreground vs. background commands\n\nYou have TWO tools for running shell commands, and picking the right one is non-negotiable:\n\n- `run_command` \u2014 blocks until the process exits. Use for: **tests, builds, lints, typechecks, git operations, one-shot scripts**. Anything that naturally returns in under a minute.\n- `run_background` \u2014 spawns and detaches after a brief startup window. Use for: **dev servers, watchers, any command with \"dev\" / \"serve\" / \"watch\" / \"start\" in the name**. Examples: `npm run dev`, `pnpm dev`, `yarn start`, `vite`, `next dev`, `uvicorn app:app --reload`, `flask run`, `python -m http.server`, `cargo watch`, `tsc --watch`, `webpack serve`.\n\n**Never use run_command for a dev server.** It will block for 60s, time out, and the user will see a frozen tool call while the server was actually running fine. Always `run_background`, then `job_output` to peek at the logs when you need to verify something.\n\nAfter `run_background`, tools available to you:\n- `job_output(jobId, tailLines?)` \u2014 read recent logs to verify startup / debug errors.\n- `list_jobs` \u2014 see every job this session (running + exited).\n- `stop_job(jobId)` \u2014 SIGTERM \u2192 SIGKILL after grace. Stop before switching port / config.\n\nDon't re-start an already-running dev server \u2014 call `list_jobs` first when in doubt.\n\n# Scope discipline on \"run it\" / \"start it\" requests\n\nWhen the user's request is to **run / start / launch / serve / boot up** something, your job is ONLY:\n\n1. Start it (`run_background` for dev servers, `run_command` for one-shots).\n2. Verify it came up (read a ready signal via `job_output`, or fetch the URL with `web_fetch` if they want you to confirm).\n3. Report what's running, where (URL / port / pid), and STOP.\n\nDo NOT, in the same turn:\n- Run `tsc` / type-checkers / linters unless the user asked for it.\n- Scan for bugs to \"proactively\" fix. The page rendering is success.\n- Clean up unused imports, dead code, or refactor \"while you're here.\"\n- Edit files to improve anything the user didn't mention.\n\nIf you notice an obvious issue, MENTION it in one sentence and wait for the user to say \"fix it.\" The cost of over-eagerness is real: you burn tokens, make surprise edits the user didn't want, and chain into cascading \"fix the new error I just introduced\" loops. The storm-breaker will cut you off, but the user still sees the mess.\n\n\"It works\" is the end state. Resist the urge to polish.\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 / read / search), do it with tool calls before writing any prose \u2014 silence while exploring is fine.\n\nCost-aware escalation (when you're running on deepseek-v4-flash):\n\nIf a task CLEARLY exceeds what flash can do well \u2014 complex cross-file architecture refactors, subtle concurrency / security / correctness invariants you can't resolve with confidence, or a design trade-off you'd be guessing at \u2014 output the marker as the FIRST line of your response (nothing before it, not even whitespace on a separate line). This aborts the current call and retries this turn on deepseek-v4-pro, one shot.\n\nTwo accepted forms:\n- `<<<NEEDS_PRO>>>` \u2014 bare marker, no rationale.\n- `<<<NEEDS_PRO: <one-sentence reason>>>>` \u2014 preferred. The reason text appears in the user-visible warning (\"\u21E7 flash requested escalation \u2014 <your reason>\"), so they understand WHY a more expensive call is happening. Keep it under ~150 chars, no newlines, no nested `>` characters. Examples: `<<<NEEDS_PRO: cross-file refactor across 6 modules with circular imports>>>` or `<<<NEEDS_PRO: subtle session-token race; flash would likely miss the locking invariant>>>`.\n\nDo NOT emit any other content in the same response when you request escalation. Use this sparingly: normal tasks \u2014 reading files, small edits, clear bug fixes, straightforward feature additions \u2014 stay on flash. Request escalation ONLY when you would otherwise produce a guess or a visibly-mediocre answer. If in doubt, attempt the task on flash first; the system also escalates automatically if you hit 3+ repair / SEARCH-mismatch errors in a single turn (the user sees a typed breakdown).\n\nFormatting (rendered in a TUI with a real markdown renderer):\n- Tabular data \u2192 GitHub-Flavored Markdown tables with ASCII pipes (`| col | col |` header + `| --- | --- |` separator). Never use Unicode box-drawing characters (\u2502 \u2500 \u253C \u250C \u2510 \u2514 \u2518 \u251C \u2524) \u2014 they look intentional but break terminal word-wrap and render as garbled columns at narrow widths.\n- Keep table cells short (one phrase each). If a cell needs a paragraph, use bullets below the table instead.\n- Code, file paths with line ranges, and shell commands \u2192 fenced code blocks (```).\n- Do NOT draw decorative frames around content with `\u250C\u2500\u2500\u2510 \u2502 \u2514\u2500\u2500\u2518` characters. The renderer adds its own borders; extra ASCII art adds noise and shatters at narrow widths.\n- For flow charts and diagrams: a plain bullet list with `\u2192` or `\u2193` between steps. Don't try to draw boxes-and-arrows in ASCII; it never survives word-wrap.\n";
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
* base prompt → REASONIX.md → global MEMORY.md → project MEMORY.md → .gitignore
|
|
3434
|
-
*/
|
|
3435
|
-
declare function codeSystemPrompt(rootDir: string): string;
|
|
3432
|
+
interface CodeSystemPromptOptions {
|
|
3433
|
+
/** True when semantic_search is registered for this run. Adds an
|
|
3434
|
+
* explicit routing fragment so the model picks it for intent-style
|
|
3435
|
+
* queries instead of defaulting to grep. */
|
|
3436
|
+
hasSemanticSearch?: boolean;
|
|
3437
|
+
}
|
|
3438
|
+
declare function codeSystemPrompt(rootDir: string, opts?: CodeSystemPromptOptions): string;
|
|
3436
3439
|
|
|
3437
3440
|
/**
|
|
3438
3441
|
* User-level config storage for the Reasonix CLI.
|
|
@@ -3452,13 +3455,27 @@ declare function codeSystemPrompt(rootDir: string): string;
|
|
|
3452
3455
|
/** One of the preset bundles (model + harvest + branch combo). */
|
|
3453
3456
|
type PresetName = "fast" | "smart" | "max";
|
|
3454
3457
|
/**
|
|
3455
|
-
* How `reasonix code` handles model-issued
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
3459
|
-
*
|
|
3458
|
+
* How `reasonix code` handles model-issued tool calls. Two axes folded
|
|
3459
|
+
* into one enum because users think about "how trusting am I right now?"
|
|
3460
|
+
* as a single dial, not as "writes vs shell" pairs.
|
|
3461
|
+
*
|
|
3462
|
+
* - "review" — queue edits into pendingEdits (user /apply or `y` to
|
|
3463
|
+
* commit); shell commands NOT on the read-only allowlist
|
|
3464
|
+
* hit ShellConfirm. Default.
|
|
3465
|
+
* - "auto" — apply edits immediately, snapshot for /undo, show a
|
|
3466
|
+
* short undo banner. Shell still goes through ShellConfirm
|
|
3467
|
+
* for non-allowlisted commands.
|
|
3468
|
+
* - "yolo" — apply edits immediately AND auto-approve every shell
|
|
3469
|
+
* command. No prompts at all. Use when you trust the
|
|
3470
|
+
* current direction and just want to iterate fast; /undo
|
|
3471
|
+
* still rolls back individual edit batches.
|
|
3472
|
+
*
|
|
3473
|
+
* Persisted so `/mode <x>` survives a relaunch. Missing → "review".
|
|
3474
|
+
*
|
|
3475
|
+
* Codex-equivalence note: review ≈ untrusted, auto ≈ on-request,
|
|
3476
|
+
* yolo ≈ never.
|
|
3460
3477
|
*/
|
|
3461
|
-
type EditMode = "review" | "auto";
|
|
3478
|
+
type EditMode = "review" | "auto" | "yolo";
|
|
3462
3479
|
/**
|
|
3463
3480
|
* reasoning_effort cap for the model. "max" is the agent-class default;
|
|
3464
3481
|
* "high" is cheaper / faster. Persisted so `/effort high` survives a
|
package/dist/index.js
CHANGED
|
@@ -2203,7 +2203,9 @@ var CacheFirstLoop = class {
|
|
|
2203
2203
|
this._proArmedForNextTurn = false;
|
|
2204
2204
|
armedConsumed = true;
|
|
2205
2205
|
}
|
|
2206
|
+
const carryAbort = this._turnAbort.signal.aborted;
|
|
2206
2207
|
this._turnAbort = new AbortController();
|
|
2208
|
+
if (carryAbort) this._turnAbort.abort();
|
|
2207
2209
|
const signal = this._turnAbort.signal;
|
|
2208
2210
|
if (armedConsumed) {
|
|
2209
2211
|
yield {
|
|
@@ -3553,6 +3555,83 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
3553
3555
|
${TUI_FORMATTING_RULES}
|
|
3554
3556
|
|
|
3555
3557
|
The 'task' the parent gave you describes WHAT to review (a branch, a file set, or "the pending changes"). Stay on it; don't redesign the feature.`;
|
|
3558
|
+
var BUILTIN_SECURITY_REVIEW_BODY = `You are running as a security-review subagent. Your job is to inspect the changes the user is about to ship \u2014 usually the current git branch vs its upstream \u2014 through a security lens specifically, and report exploitable issues.
|
|
3559
|
+
|
|
3560
|
+
How to operate:
|
|
3561
|
+
- Default scope: the current branch's diff vs the default branch. If the user names a different range or a directory, honor that.
|
|
3562
|
+
- Discover scope first: \`git status\`, \`git diff --stat\`, \`git diff <base>...HEAD\`. Read touched files (\`read_file\`) when the diff alone doesn't carry security context \u2014 auth checks, input validation, the actual handler that calls into the changed function.
|
|
3563
|
+
- Use \`search_content\` to verify "is this user-controlled input ever sanitized later?" / "are there other call sites that depend on this validation?" before asserting impact.
|
|
3564
|
+
- Stay read-only. Never write, never run destructive commands, never propose SEARCH/REPLACE blocks. The parent decides what to act on.
|
|
3565
|
+
- Cap yourself at ~12 tool calls. If the diff is too big, focus on the riskiest 2-3 files and say so explicitly.
|
|
3566
|
+
|
|
3567
|
+
Threat model \u2014 flag with severity:
|
|
3568
|
+
|
|
3569
|
+
**CRITICAL** (do-not-ship):
|
|
3570
|
+
- SQL / NoSQL / shell / template injection \u2014 user input concatenated into a query, command, or template without parameterization.
|
|
3571
|
+
- Path traversal \u2014 user-controlled filenames touching the filesystem without canonicalization + sandbox check.
|
|
3572
|
+
- Authentication / authorization missing \u2014 endpoints / actions that should require a session check but don't.
|
|
3573
|
+
- Hardcoded secrets \u2014 API keys, passwords, signing tokens visible in the diff.
|
|
3574
|
+
- Deserialization of untrusted input \u2014 \`pickle.loads\`, \`yaml.load\` (non-safe), \`eval\`, \`Function()\`, \`unserialize()\`.
|
|
3575
|
+
- Cryptographic mistakes \u2014 homemade crypto, weak hashes (MD5/SHA-1) for passwords, missing IVs, ECB mode, predictable nonces.
|
|
3576
|
+
|
|
3577
|
+
**HIGH**:
|
|
3578
|
+
- XSS \u2014 user input rendered into HTML without escaping (or wrong escaping context).
|
|
3579
|
+
- SSRF \u2014 fetching URLs from user input without an allowlist.
|
|
3580
|
+
- Race conditions in security-relevant code \u2014 TOCTOU on auth/file checks.
|
|
3581
|
+
- Open redirects \u2014 user-controlled URL passed to a redirect helper.
|
|
3582
|
+
- Insufficient logging on security events (login failure, permission denial) \u2014 only flag if the codebase clearly DOES log elsewhere.
|
|
3583
|
+
|
|
3584
|
+
**MEDIUM**:
|
|
3585
|
+
- Verbose error messages leaking internal paths / stack traces / SQL.
|
|
3586
|
+
- Missing rate limiting on a credential / token endpoint.
|
|
3587
|
+
- Cross-origin / cookie-flag issues (missing \`Secure\` / \`HttpOnly\` / \`SameSite\`).
|
|
3588
|
+
|
|
3589
|
+
Things to NOT pile on (out of scope here \u2014 the regular /review covers them):
|
|
3590
|
+
- Style, formatting, naming.
|
|
3591
|
+
- Performance, refactor opportunities, test coverage gaps that aren't security-relevant.
|
|
3592
|
+
- "Should be a constant" / "extract this helper" \u2014 irrelevant to ship-blocking.
|
|
3593
|
+
|
|
3594
|
+
Your final answer:
|
|
3595
|
+
- Lead with a one-sentence verdict: "no security issues found", "minor concerns", or "blocking issues".
|
|
3596
|
+
- Then a list grouped by severity. Each item: file:line + 1-sentence threat + 1-sentence fix direction (no full SEARCH/REPLACE \u2014 the user / parent agent will write that).
|
|
3597
|
+
- If clean, say so plainly. Don't manufacture findings.
|
|
3598
|
+
|
|
3599
|
+
${NEGATIVE_CLAIM_RULE}
|
|
3600
|
+
|
|
3601
|
+
${TUI_FORMATTING_RULES}
|
|
3602
|
+
|
|
3603
|
+
The 'task' the parent gave you names what to review. Stay on it; don't redesign the feature.`;
|
|
3604
|
+
var BUILTIN_TEST_BODY = `You are running as the parent agent \u2014 this skill is INLINED, not a subagent. The user invoked /test (or asked you to "run the tests and fix failures"). Your job: run the project's test suite, diagnose any failure, propose fixes as SEARCH/REPLACE edit blocks, then re-run. Repeat until green or you hit a wall you should escalate.
|
|
3605
|
+
|
|
3606
|
+
How to operate:
|
|
3607
|
+
|
|
3608
|
+
1. **Detect the test command**.
|
|
3609
|
+
- Look for \`package.json\` \u2192 \`scripts.test\` first (most common: \`npm test\`, \`pnpm test\`, \`yarn test\`).
|
|
3610
|
+
- If no package.json or no test script: try \`pytest\`, \`go test ./...\`, \`cargo test\` based on what files exist (pyproject.toml/requirements.txt \u2192 pytest; go.mod \u2192 go test; Cargo.toml \u2192 cargo test).
|
|
3611
|
+
- If you can't tell, ASK the user for the command \u2014 don't guess. One question, one tool call to confirm.
|
|
3612
|
+
|
|
3613
|
+
2. **Run it via run_command** (typical timeout 120s, bigger if the suite is large). Capture stdout + stderr.
|
|
3614
|
+
|
|
3615
|
+
3. **Read the failures**. Pull out: which test names failed, the actual error/traceback, the file + line that threw. Don't just paraphrase \u2014 locate the exact assertion or stack frame.
|
|
3616
|
+
|
|
3617
|
+
4. **Propose fixes**. For each distinct failure:
|
|
3618
|
+
- If the failure is in PRODUCTION code (test catches a real bug) \u2192 propose a SEARCH/REPLACE that fixes the production code.
|
|
3619
|
+
- If the failure is in TEST code (test is wrong, codebase is right) \u2192 propose a SEARCH/REPLACE that updates the test, AND say so explicitly: "This is a test bug, not a production bug \u2014 updating the assertion."
|
|
3620
|
+
- If the failure is environmental (missing dep, wrong node version, missing fixture file) \u2192 say so and stop. Don't try to install packages or change config without checking with the user.
|
|
3621
|
+
|
|
3622
|
+
5. **Apply + re-run**. After the user accepts the edit blocks, run the test command again. Iterate.
|
|
3623
|
+
|
|
3624
|
+
6. **Stop conditions**:
|
|
3625
|
+
- All tests pass \u2192 report green, summarize what changed.
|
|
3626
|
+
- Same test still failing after 2 fix attempts on the same line \u2192 STOP. Tell the user "I've tried twice, it's still failing \u2014 here's what I think is happening, want me to try a different angle?". Don't loop indefinitely.
|
|
3627
|
+
- 3+ unrelated failures \u2192 fix one at a time, smallest first, so each pass narrows the surface.
|
|
3628
|
+
|
|
3629
|
+
Don't:
|
|
3630
|
+
- Run \`npm install\` / \`pip install\` / \`cargo update\` without asking \u2014 those mutate lockfiles and have global effects.
|
|
3631
|
+
- Disable, skip, or delete failing tests to "make it green". If a test seems wrong, update its assertion with a one-sentence explanation, but never add \`.skip\` / \`it.skip\` / \`@pytest.mark.skip\`.
|
|
3632
|
+
- Modify the test runner config (vitest.config, jest.config, etc.) to silence failures.
|
|
3633
|
+
|
|
3634
|
+
Lead each turn with a one-line status: "\u25B8 running \`npm test\` ..." \u2192 "\u25B8 2 failures in tests/foo.test.ts \u2014 first is \u2026" \u2192 so the user always knows where you are without scrolling tool output.`;
|
|
3556
3635
|
var BUILTIN_SKILLS = Object.freeze([
|
|
3557
3636
|
Object.freeze({
|
|
3558
3637
|
name: "explore",
|
|
@@ -3577,6 +3656,22 @@ var BUILTIN_SKILLS = Object.freeze([
|
|
|
3577
3656
|
scope: "builtin",
|
|
3578
3657
|
path: "(builtin)",
|
|
3579
3658
|
runAs: "subagent"
|
|
3659
|
+
}),
|
|
3660
|
+
Object.freeze({
|
|
3661
|
+
name: "security-review",
|
|
3662
|
+
description: "Security-focused review of the current branch diff in an isolated subagent \u2014 flags injection/authz/secrets/deserialization/path-traversal/crypto issues, severity-tagged. Read-only. Use when shipping changes that touch auth, input parsing, file IO, or external requests.",
|
|
3663
|
+
body: BUILTIN_SECURITY_REVIEW_BODY,
|
|
3664
|
+
scope: "builtin",
|
|
3665
|
+
path: "(builtin)",
|
|
3666
|
+
runAs: "subagent"
|
|
3667
|
+
}),
|
|
3668
|
+
Object.freeze({
|
|
3669
|
+
name: "test",
|
|
3670
|
+
description: "Run the project's test suite, diagnose failures, propose SEARCH/REPLACE fixes, re-run until green (or stop after 2 fix attempts on the same failure). Inlined \u2014 runs in the parent loop so you see the edit blocks and can /apply them. Detects npm/pnpm/yarn/pytest/go/cargo.",
|
|
3671
|
+
body: BUILTIN_TEST_BODY,
|
|
3672
|
+
scope: "builtin",
|
|
3673
|
+
path: "(builtin)",
|
|
3674
|
+
runAs: "inline"
|
|
3580
3675
|
})
|
|
3581
3676
|
]);
|
|
3582
3677
|
|
|
@@ -3816,6 +3911,40 @@ var MemoryStore = class {
|
|
|
3816
3911
|
`, "utf8");
|
|
3817
3912
|
}
|
|
3818
3913
|
};
|
|
3914
|
+
function readGlobalReasonixMemory(homeDir = join7(homedir4(), ".reasonix")) {
|
|
3915
|
+
const path = join7(homeDir, "REASONIX.md");
|
|
3916
|
+
if (!existsSync7(path)) return null;
|
|
3917
|
+
let raw;
|
|
3918
|
+
try {
|
|
3919
|
+
raw = readFileSync7(path, "utf8");
|
|
3920
|
+
} catch {
|
|
3921
|
+
return null;
|
|
3922
|
+
}
|
|
3923
|
+
const trimmed = raw.trim();
|
|
3924
|
+
if (!trimmed) return null;
|
|
3925
|
+
const originalChars = trimmed.length;
|
|
3926
|
+
const truncated = originalChars > 8e3;
|
|
3927
|
+
const content = truncated ? `${trimmed.slice(0, 8e3)}
|
|
3928
|
+
\u2026 (truncated ${originalChars - 8e3} chars)` : trimmed;
|
|
3929
|
+
return { path, content, originalChars, truncated };
|
|
3930
|
+
}
|
|
3931
|
+
function applyGlobalReasonixMemory(basePrompt, homeDir) {
|
|
3932
|
+
if (!memoryEnabled()) return basePrompt;
|
|
3933
|
+
const dir = homeDir ?? join7(homedir4(), ".reasonix");
|
|
3934
|
+
const mem = readGlobalReasonixMemory(dir);
|
|
3935
|
+
if (!mem) return basePrompt;
|
|
3936
|
+
return [
|
|
3937
|
+
basePrompt,
|
|
3938
|
+
"",
|
|
3939
|
+
"# Global memory (~/.reasonix/REASONIX.md)",
|
|
3940
|
+
"",
|
|
3941
|
+
"Cross-project notes the user pinned via the `#g` prompt prefix. Treat as authoritative \u2014 same level of trust as project memory.",
|
|
3942
|
+
"",
|
|
3943
|
+
"```",
|
|
3944
|
+
mem.content,
|
|
3945
|
+
"```"
|
|
3946
|
+
].join("\n");
|
|
3947
|
+
}
|
|
3819
3948
|
function applyUserMemory(basePrompt, opts = {}) {
|
|
3820
3949
|
if (!memoryEnabled()) return basePrompt;
|
|
3821
3950
|
const store = new MemoryStore(opts);
|
|
@@ -3851,7 +3980,8 @@ function applyUserMemory(basePrompt, opts = {}) {
|
|
|
3851
3980
|
}
|
|
3852
3981
|
function applyMemoryStack(basePrompt, rootDir) {
|
|
3853
3982
|
const withProject = applyProjectMemory(basePrompt, rootDir);
|
|
3854
|
-
const
|
|
3983
|
+
const withGlobal = applyGlobalReasonixMemory(withProject);
|
|
3984
|
+
const withMemory = applyUserMemory(withGlobal, { projectRoot: rootDir });
|
|
3855
3985
|
return applySkillsIndex(withMemory, { projectRoot: rootDir });
|
|
3856
3986
|
}
|
|
3857
3987
|
|
|
@@ -4982,7 +5112,11 @@ async function spawnSubagent(opts) {
|
|
|
4982
5112
|
stream: false
|
|
4983
5113
|
});
|
|
4984
5114
|
const onParentAbort = () => childLoop.abort();
|
|
4985
|
-
opts.parentSignal?.
|
|
5115
|
+
if (opts.parentSignal?.aborted) {
|
|
5116
|
+
childLoop.abort();
|
|
5117
|
+
} else {
|
|
5118
|
+
opts.parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
5119
|
+
}
|
|
4986
5120
|
let final = "";
|
|
4987
5121
|
let errorMessage;
|
|
4988
5122
|
let toolIter = 0;
|
|
@@ -5000,7 +5134,11 @@ async function spawnSubagent(opts) {
|
|
|
5000
5134
|
});
|
|
5001
5135
|
}
|
|
5002
5136
|
if (ev.role === "assistant_final") {
|
|
5003
|
-
|
|
5137
|
+
if (ev.forcedSummary) {
|
|
5138
|
+
errorMessage = ev.content?.trim() || "subagent ended without producing an answer";
|
|
5139
|
+
} else {
|
|
5140
|
+
final = ev.content ?? "";
|
|
5141
|
+
}
|
|
5004
5142
|
}
|
|
5005
5143
|
if (ev.role === "error") {
|
|
5006
5144
|
errorMessage = ev.error ?? "subagent error";
|
|
@@ -5774,7 +5912,7 @@ function registerShellTools(registry, opts) {
|
|
|
5774
5912
|
const snapshot2 = opts.extraAllowed ?? [];
|
|
5775
5913
|
return () => snapshot2;
|
|
5776
5914
|
})();
|
|
5777
|
-
const
|
|
5915
|
+
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
5778
5916
|
registry.register({
|
|
5779
5917
|
name: "run_command",
|
|
5780
5918
|
description: "Run a shell command in the project root and return its combined stdout+stderr.\n\nConstraints (read these before the first call):\n\u2022 ONE process per call, NO shell expansion. `&&`, `||`, `|`, `;`, `>`, `<`, `2>&1` are all rejected up-front \u2014 split into separate calls and combine results in reasoning. Example: instead of `grep foo *.ts | wc -l`, use `grep -c foo *.ts`; instead of `cd sub && npm test`, use `npm test --prefix sub` (or whatever --cwd flag the binary accepts).\n\u2022 `cd` DOES NOT PERSIST between calls \u2014 each call spawns a fresh process rooted at the project. If a tool needs a subdirectory, pass it via the tool's own flag (`npm --prefix`, `cargo -C`, `git -C`, `pytest tests/\u2026`), NOT via a preceding `cd`.\n\u2022 Avoid commands with unbounded output (`netstat -ano`, `find /`, etc.) \u2014 they waste tokens. Filter at source: `netstat -ano -p TCP`, `find src -name '*.ts'`, `grep -c`, `wc -l`.\n\nCommon read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
@@ -5783,7 +5921,7 @@ function registerShellTools(registry, opts) {
|
|
|
5783
5921
|
// during planning. Anything that would otherwise trigger a
|
|
5784
5922
|
// confirmation prompt is treated as "not read-only" and bounced.
|
|
5785
5923
|
readOnlyCheck: (args) => {
|
|
5786
|
-
if (
|
|
5924
|
+
if (isAllowAll()) return true;
|
|
5787
5925
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
5788
5926
|
if (!cmd) return false;
|
|
5789
5927
|
return isAllowed(cmd, getExtraAllowed());
|
|
@@ -5805,7 +5943,7 @@ function registerShellTools(registry, opts) {
|
|
|
5805
5943
|
fn: async (args, ctx) => {
|
|
5806
5944
|
const cmd = args.command.trim();
|
|
5807
5945
|
if (!cmd) throw new Error("run_command: empty command");
|
|
5808
|
-
if (!
|
|
5946
|
+
if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
|
|
5809
5947
|
throw new NeedsConfirmationError(cmd);
|
|
5810
5948
|
}
|
|
5811
5949
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
@@ -5838,7 +5976,7 @@ function registerShellTools(registry, opts) {
|
|
|
5838
5976
|
fn: async (args, ctx) => {
|
|
5839
5977
|
const cmd = args.command.trim();
|
|
5840
5978
|
if (!cmd) throw new Error("run_background: empty command");
|
|
5841
|
-
if (!
|
|
5979
|
+
if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
|
|
5842
5980
|
throw new NeedsConfirmationError(cmd);
|
|
5843
5981
|
}
|
|
5844
5982
|
const result = await jobs.start(cmd, {
|
|
@@ -7570,8 +7708,19 @@ ${ESCALATION_CONTRACT}
|
|
|
7570
7708
|
|
|
7571
7709
|
${TUI_FORMATTING_RULES}
|
|
7572
7710
|
`;
|
|
7573
|
-
|
|
7574
|
-
|
|
7711
|
+
var SEMANTIC_SEARCH_ROUTING = `
|
|
7712
|
+
|
|
7713
|
+
# Search routing
|
|
7714
|
+
|
|
7715
|
+
You have BOTH \`semantic_search\` (vector index) and \`search_content\` (literal grep).
|
|
7716
|
+
|
|
7717
|
+
- **Descriptive queries** ("where do we handle X", "which file owns Y", "how does Z work", "find the logic that does \u2026", "the code responsible for \u2026") \u2192 call \`semantic_search\` FIRST. It indexes the project by meaning, so it finds the right file even when your phrasing shares no tokens with the code.
|
|
7718
|
+
- **Exact-token queries** (a specific identifier, regex, or "find every call to foo") \u2192 call \`search_content\`.
|
|
7719
|
+
|
|
7720
|
+
If \`semantic_search\` returns nothing useful (low scores, off-topic), THEN fall back to \`search_content\`. Don't go the other way \u2014 grepping a paraphrased question wastes turns.`;
|
|
7721
|
+
function codeSystemPrompt(rootDir, opts = {}) {
|
|
7722
|
+
const base = opts.hasSemanticSearch ? `${CODE_SYSTEM_PROMPT}${SEMANTIC_SEARCH_ROUTING}` : CODE_SYSTEM_PROMPT;
|
|
7723
|
+
const withMemory = applyMemoryStack(base, rootDir);
|
|
7575
7724
|
const gitignorePath = join9(rootDir, ".gitignore");
|
|
7576
7725
|
if (!existsSync10(gitignorePath)) return withMemory;
|
|
7577
7726
|
let content;
|