reasonix 0.7.12 → 0.10.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.
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  CODE_SYSTEM_PROMPT,
4
4
  codeSystemPrompt
5
- } from "./chunk-5DZMZCCW.js";
5
+ } from "./chunk-WRG56OKI.js";
6
6
  export {
7
7
  CODE_SYSTEM_PROMPT,
8
8
  codeSystemPrompt
9
9
  };
10
- //# sourceMappingURL=prompt-2OABSPAW.js.map
10
+ //# sourceMappingURL=prompt-LJ44NWSU.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
- * user memory (global + project), and the skills index. Drop-in
1627
- * replacement for `applyProjectMemory` at CLI entry points. Stacking
1628
- * order is stable the prefix hash only changes when block *content*
1629
- * changes, not when this helper is called a second time with the same
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.
@@ -3452,13 +3458,27 @@ declare function codeSystemPrompt(rootDir: string): string;
3452
3458
  /** One of the preset bundles (model + harvest + branch combo). */
3453
3459
  type PresetName = "fast" | "smart" | "max";
3454
3460
  /**
3455
- * How `reasonix code` handles model-issued edits:
3456
- * - "review" queue the edit into pendingEdits; user /apply or `y` commits.
3457
- * - "auto" — apply immediately, snapshot for /undo, show a short undo
3458
- * banner so the user can roll back with one keystroke.
3459
- * Persisted so `/mode auto` survives a relaunch. Missing "review".
3461
+ * How `reasonix code` handles model-issued tool calls. Two axes folded
3462
+ * into one enum because users think about "how trusting am I right now?"
3463
+ * as a single dial, not as "writes vs shell" pairs.
3464
+ *
3465
+ * - "review" queue edits into pendingEdits (user /apply or `y` to
3466
+ * commit); shell commands NOT on the read-only allowlist
3467
+ * hit ShellConfirm. Default.
3468
+ * - "auto" — apply edits immediately, snapshot for /undo, show a
3469
+ * short undo banner. Shell still goes through ShellConfirm
3470
+ * for non-allowlisted commands.
3471
+ * - "yolo" — apply edits immediately AND auto-approve every shell
3472
+ * command. No prompts at all. Use when you trust the
3473
+ * current direction and just want to iterate fast; /undo
3474
+ * still rolls back individual edit batches.
3475
+ *
3476
+ * Persisted so `/mode <x>` survives a relaunch. Missing → "review".
3477
+ *
3478
+ * Codex-equivalence note: review ≈ untrusted, auto ≈ on-request,
3479
+ * yolo ≈ never.
3460
3480
  */
3461
- type EditMode = "review" | "auto";
3481
+ type EditMode = "review" | "auto" | "yolo";
3462
3482
  /**
3463
3483
  * reasoning_effort cap for the model. "max" is the agent-class default;
3464
3484
  * "high" is cheaper / faster. Persisted so `/effort high` survives a
package/dist/index.js CHANGED
@@ -3525,6 +3525,111 @@ ${NEGATIVE_CLAIM_RULE}
3525
3525
  ${TUI_FORMATTING_RULES}
3526
3526
 
3527
3527
  The 'task' the parent gave you is the research question. Stay on it.`;
3528
+ var BUILTIN_REVIEW_BODY = `You are running as a code-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 and produce a focused review the parent can hand back to the user.
3529
+
3530
+ How to operate:
3531
+ - Default scope: the current branch's diff vs the default branch. If the user's task names a specific commit range or files, honor that instead.
3532
+ - Discover scope first: \`run_command git status\`, \`git diff --stat\`, \`git log --oneline\` to see what changed. Then \`git diff\` (or \`git diff <base>...HEAD\`) for the actual hunks.
3533
+ - Read the touched files (\`read_file\`) when the diff alone doesn't carry enough context \u2014 function signatures, surrounding invariants, callers.
3534
+ - For "any callers depending on this?" questions: \`search_content\` against the symbol BEFORE asserting impact.
3535
+ - Stay read-only. Never \`run_command git commit\`, never write files, never propose SEARCH/REPLACE blocks. The parent decides whether to act on your findings.
3536
+ - Cap yourself at ~12 tool calls. If the diff is too big to review in one pass, pick the riskiest 2-3 files and say so explicitly.
3537
+
3538
+ What to look for, in priority order:
3539
+ 1. **Correctness bugs** \u2014 off-by-one, null/undefined handling, race conditions, wrong sign / wrong operator, edge cases the code doesn't handle.
3540
+ 2. **Security** \u2014 injection (SQL, shell, path traversal), secrets in code, missing authz checks, unsafe deserialization.
3541
+ 3. **Behavior changes the diff hides** \u2014 renames that miss callers, removed branches that were load-bearing, error-handling that now swallows what used to surface.
3542
+ 4. **Tests** \u2014 does the change have tests for the new behavior? Are existing tests still meaningful, or did the change make them tautological?
3543
+ 5. **Style + consistency** \u2014 only flag deviations that matter (unsafe \`any\`, missing types in TypeScript, inconsistent error shape). Don't pile on cosmetic nits if the substance is clean.
3544
+
3545
+ Your final answer:
3546
+ - Lead with a one-sentence verdict: "ship as-is" / "minor nits, OK to ship after" / "blocking issues, do not ship".
3547
+ - Then a short bulleted list of issues, each with: file:line citation + the problem in one sentence + what to change.
3548
+ - Group by severity if you have more than 4 items: **Blocking**, **Should-fix**, **Nits**.
3549
+ - If everything looks clean, say so plainly. Don't manufacture concerns.
3550
+
3551
+ ${NEGATIVE_CLAIM_RULE}
3552
+
3553
+ ${TUI_FORMATTING_RULES}
3554
+
3555
+ 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.`;
3556
+ 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.
3557
+
3558
+ How to operate:
3559
+ - Default scope: the current branch's diff vs the default branch. If the user names a different range or a directory, honor that.
3560
+ - 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.
3561
+ - 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.
3562
+ - Stay read-only. Never write, never run destructive commands, never propose SEARCH/REPLACE blocks. The parent decides what to act on.
3563
+ - Cap yourself at ~12 tool calls. If the diff is too big, focus on the riskiest 2-3 files and say so explicitly.
3564
+
3565
+ Threat model \u2014 flag with severity:
3566
+
3567
+ **CRITICAL** (do-not-ship):
3568
+ - SQL / NoSQL / shell / template injection \u2014 user input concatenated into a query, command, or template without parameterization.
3569
+ - Path traversal \u2014 user-controlled filenames touching the filesystem without canonicalization + sandbox check.
3570
+ - Authentication / authorization missing \u2014 endpoints / actions that should require a session check but don't.
3571
+ - Hardcoded secrets \u2014 API keys, passwords, signing tokens visible in the diff.
3572
+ - Deserialization of untrusted input \u2014 \`pickle.loads\`, \`yaml.load\` (non-safe), \`eval\`, \`Function()\`, \`unserialize()\`.
3573
+ - Cryptographic mistakes \u2014 homemade crypto, weak hashes (MD5/SHA-1) for passwords, missing IVs, ECB mode, predictable nonces.
3574
+
3575
+ **HIGH**:
3576
+ - XSS \u2014 user input rendered into HTML without escaping (or wrong escaping context).
3577
+ - SSRF \u2014 fetching URLs from user input without an allowlist.
3578
+ - Race conditions in security-relevant code \u2014 TOCTOU on auth/file checks.
3579
+ - Open redirects \u2014 user-controlled URL passed to a redirect helper.
3580
+ - Insufficient logging on security events (login failure, permission denial) \u2014 only flag if the codebase clearly DOES log elsewhere.
3581
+
3582
+ **MEDIUM**:
3583
+ - Verbose error messages leaking internal paths / stack traces / SQL.
3584
+ - Missing rate limiting on a credential / token endpoint.
3585
+ - Cross-origin / cookie-flag issues (missing \`Secure\` / \`HttpOnly\` / \`SameSite\`).
3586
+
3587
+ Things to NOT pile on (out of scope here \u2014 the regular /review covers them):
3588
+ - Style, formatting, naming.
3589
+ - Performance, refactor opportunities, test coverage gaps that aren't security-relevant.
3590
+ - "Should be a constant" / "extract this helper" \u2014 irrelevant to ship-blocking.
3591
+
3592
+ Your final answer:
3593
+ - Lead with a one-sentence verdict: "no security issues found", "minor concerns", or "blocking issues".
3594
+ - 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).
3595
+ - If clean, say so plainly. Don't manufacture findings.
3596
+
3597
+ ${NEGATIVE_CLAIM_RULE}
3598
+
3599
+ ${TUI_FORMATTING_RULES}
3600
+
3601
+ The 'task' the parent gave you names what to review. Stay on it; don't redesign the feature.`;
3602
+ 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.
3603
+
3604
+ How to operate:
3605
+
3606
+ 1. **Detect the test command**.
3607
+ - Look for \`package.json\` \u2192 \`scripts.test\` first (most common: \`npm test\`, \`pnpm test\`, \`yarn test\`).
3608
+ - 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).
3609
+ - If you can't tell, ASK the user for the command \u2014 don't guess. One question, one tool call to confirm.
3610
+
3611
+ 2. **Run it via run_command** (typical timeout 120s, bigger if the suite is large). Capture stdout + stderr.
3612
+
3613
+ 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.
3614
+
3615
+ 4. **Propose fixes**. For each distinct failure:
3616
+ - If the failure is in PRODUCTION code (test catches a real bug) \u2192 propose a SEARCH/REPLACE that fixes the production code.
3617
+ - 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."
3618
+ - 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.
3619
+
3620
+ 5. **Apply + re-run**. After the user accepts the edit blocks, run the test command again. Iterate.
3621
+
3622
+ 6. **Stop conditions**:
3623
+ - All tests pass \u2192 report green, summarize what changed.
3624
+ - 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.
3625
+ - 3+ unrelated failures \u2192 fix one at a time, smallest first, so each pass narrows the surface.
3626
+
3627
+ Don't:
3628
+ - Run \`npm install\` / \`pip install\` / \`cargo update\` without asking \u2014 those mutate lockfiles and have global effects.
3629
+ - 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\`.
3630
+ - Modify the test runner config (vitest.config, jest.config, etc.) to silence failures.
3631
+
3632
+ 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.`;
3528
3633
  var BUILTIN_SKILLS = Object.freeze([
3529
3634
  Object.freeze({
3530
3635
  name: "explore",
@@ -3541,6 +3646,30 @@ var BUILTIN_SKILLS = Object.freeze([
3541
3646
  scope: "builtin",
3542
3647
  path: "(builtin)",
3543
3648
  runAs: "subagent"
3649
+ }),
3650
+ Object.freeze({
3651
+ name: "review",
3652
+ description: "Review the pending changes (current branch diff by default) in an isolated subagent \u2014 flags correctness, security, missing tests, hidden behavior changes; reports verdict + per-issue file:line. Read-only; the parent decides what to act on.",
3653
+ body: BUILTIN_REVIEW_BODY,
3654
+ scope: "builtin",
3655
+ path: "(builtin)",
3656
+ runAs: "subagent"
3657
+ }),
3658
+ Object.freeze({
3659
+ name: "security-review",
3660
+ 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.",
3661
+ body: BUILTIN_SECURITY_REVIEW_BODY,
3662
+ scope: "builtin",
3663
+ path: "(builtin)",
3664
+ runAs: "subagent"
3665
+ }),
3666
+ Object.freeze({
3667
+ name: "test",
3668
+ 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.",
3669
+ body: BUILTIN_TEST_BODY,
3670
+ scope: "builtin",
3671
+ path: "(builtin)",
3672
+ runAs: "inline"
3544
3673
  })
3545
3674
  ]);
3546
3675
 
@@ -3780,6 +3909,40 @@ var MemoryStore = class {
3780
3909
  `, "utf8");
3781
3910
  }
3782
3911
  };
3912
+ function readGlobalReasonixMemory(homeDir = join7(homedir4(), ".reasonix")) {
3913
+ const path = join7(homeDir, "REASONIX.md");
3914
+ if (!existsSync7(path)) return null;
3915
+ let raw;
3916
+ try {
3917
+ raw = readFileSync7(path, "utf8");
3918
+ } catch {
3919
+ return null;
3920
+ }
3921
+ const trimmed = raw.trim();
3922
+ if (!trimmed) return null;
3923
+ const originalChars = trimmed.length;
3924
+ const truncated = originalChars > 8e3;
3925
+ const content = truncated ? `${trimmed.slice(0, 8e3)}
3926
+ \u2026 (truncated ${originalChars - 8e3} chars)` : trimmed;
3927
+ return { path, content, originalChars, truncated };
3928
+ }
3929
+ function applyGlobalReasonixMemory(basePrompt, homeDir) {
3930
+ if (!memoryEnabled()) return basePrompt;
3931
+ const dir = homeDir ?? join7(homedir4(), ".reasonix");
3932
+ const mem = readGlobalReasonixMemory(dir);
3933
+ if (!mem) return basePrompt;
3934
+ return [
3935
+ basePrompt,
3936
+ "",
3937
+ "# Global memory (~/.reasonix/REASONIX.md)",
3938
+ "",
3939
+ "Cross-project notes the user pinned via the `#g` prompt prefix. Treat as authoritative \u2014 same level of trust as project memory.",
3940
+ "",
3941
+ "```",
3942
+ mem.content,
3943
+ "```"
3944
+ ].join("\n");
3945
+ }
3783
3946
  function applyUserMemory(basePrompt, opts = {}) {
3784
3947
  if (!memoryEnabled()) return basePrompt;
3785
3948
  const store = new MemoryStore(opts);
@@ -3815,7 +3978,8 @@ function applyUserMemory(basePrompt, opts = {}) {
3815
3978
  }
3816
3979
  function applyMemoryStack(basePrompt, rootDir) {
3817
3980
  const withProject = applyProjectMemory(basePrompt, rootDir);
3818
- const withMemory = applyUserMemory(withProject, { projectRoot: rootDir });
3981
+ const withGlobal = applyGlobalReasonixMemory(withProject);
3982
+ const withMemory = applyUserMemory(withGlobal, { projectRoot: rootDir });
3819
3983
  return applySkillsIndex(withMemory, { projectRoot: rootDir });
3820
3984
  }
3821
3985
 
@@ -5738,7 +5902,7 @@ function registerShellTools(registry, opts) {
5738
5902
  const snapshot2 = opts.extraAllowed ?? [];
5739
5903
  return () => snapshot2;
5740
5904
  })();
5741
- const allowAll = opts.allowAll ?? false;
5905
+ const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
5742
5906
  registry.register({
5743
5907
  name: "run_command",
5744
5908
  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.",
@@ -5747,7 +5911,7 @@ function registerShellTools(registry, opts) {
5747
5911
  // during planning. Anything that would otherwise trigger a
5748
5912
  // confirmation prompt is treated as "not read-only" and bounced.
5749
5913
  readOnlyCheck: (args) => {
5750
- if (allowAll) return true;
5914
+ if (isAllowAll()) return true;
5751
5915
  const cmd = typeof args?.command === "string" ? args.command.trim() : "";
5752
5916
  if (!cmd) return false;
5753
5917
  return isAllowed(cmd, getExtraAllowed());
@@ -5769,7 +5933,7 @@ function registerShellTools(registry, opts) {
5769
5933
  fn: async (args, ctx) => {
5770
5934
  const cmd = args.command.trim();
5771
5935
  if (!cmd) throw new Error("run_command: empty command");
5772
- if (!allowAll && !isAllowed(cmd, getExtraAllowed())) {
5936
+ if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
5773
5937
  throw new NeedsConfirmationError(cmd);
5774
5938
  }
5775
5939
  const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
@@ -5802,7 +5966,7 @@ function registerShellTools(registry, opts) {
5802
5966
  fn: async (args, ctx) => {
5803
5967
  const cmd = args.command.trim();
5804
5968
  if (!cmd) throw new Error("run_background: empty command");
5805
- if (!allowAll && !isAllowed(cmd, getExtraAllowed())) {
5969
+ if (!isAllowAll() && !isAllowed(cmd, getExtraAllowed())) {
5806
5970
  throw new NeedsConfirmationError(cmd);
5807
5971
  }
5808
5972
  const result = await jobs.start(cmd, {