@nurix/nustack 0.26.8 → 0.27.1

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.
@@ -1,4 +1,5 @@
1
- import { closeSync, openSync, readSync, statSync } from "node:fs";
1
+ import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  export const GATE_BYTES_PER_TOKEN = 4;
4
5
  export const READ_DEFAULT_LINE_LIMIT = 2_000;
@@ -6,6 +7,18 @@ export const READ_LINE_PREFIX_BYTES = 7;
6
7
  export const BINARY_PROBE_BYTES = 8_192;
7
8
  export const GATE_SKIPPED_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "ico", "pdf", "ipynb"];
8
9
  const READ_CHUNK_BYTES = 64 * 1024;
10
+ function realDirectory(directory) {
11
+ try {
12
+ return realpathSync(directory);
13
+ }
14
+ catch {
15
+ return directory;
16
+ }
17
+ }
18
+ export function isExemptPath(realPath, env = process.env) {
19
+ const projects = path.join(realDirectory(env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude")), "projects");
20
+ return realPath.startsWith(`${projects}${path.sep}`) && realPath.split(path.sep).includes("tool-results");
21
+ }
9
22
  export function tokensOfLength(length) {
10
23
  return Math.ceil(length / GATE_BYTES_PER_TOKEN);
11
24
  }
@@ -64,7 +64,7 @@ function headOf(source) {
64
64
  .map((line) => (line.length > HEAD_LINE_MAX_CHARS ? `${line.slice(0, HEAD_LINE_MAX_CHARS)}…` : line));
65
65
  }
66
66
  function waysForward(entries, lineCount, tool) {
67
- const ranged = entries.filter((entry) => entry.endLine !== undefined);
67
+ const ranged = entries.filter((entry) => entry.endLine !== undefined && entry.endLine - entry.startLine + 1 < lineCount);
68
68
  const largest = ranged.reduce((best, entry) => (best === undefined || entry.endLine - entry.startLine > best.endLine - best.startLine ? entry : best), undefined);
69
69
  const example = largest === undefined
70
70
  ? `offset=1 limit=${Math.min(lineCount, 200)}`
@@ -3,7 +3,7 @@ import { closeSync, openSync, readFileSync, readSync, realpathSync } from "node:
3
3
  import path from "node:path";
4
4
  import { performance } from "node:perf_hooks";
5
5
  import { assessBashRead, assessBashReadByCoin, classifyBashRead } from "./bashRead.js";
6
- import { assessFullRead, assessFullReadByCoin, readLeadingLines, regularFileSize, tokensOfLength } from "./estimate.js";
6
+ import { assessFullRead, assessFullReadByCoin, isExemptPath, readLeadingLines, regularFileSize, tokensOfLength } from "./estimate.js";
7
7
  import { assessRedirect, redirectHandler } from "./redirect.js";
8
8
  import { GATE_MIN_THRESHOLD_TOKENS, isCoinSelected, isGateOff, resolveCoin, resolveThreshold } from "./settings.js";
9
9
  export class GateInputError extends Error {
@@ -36,9 +36,11 @@ export async function gateReadCall(call, env = process.env, deps = {}, coin = re
36
36
  const filePath = call.toolInput.file_path;
37
37
  if (typeof filePath !== "string" || filePath === "")
38
38
  throw new GateInputError("tool_input.file_path is missing");
39
- if (call.toolInput.offset != null || call.toolInput.limit != null)
39
+ if (call.toolInput.offset != null || call.toolInput.limit != null || call.toolInput.allow_large === true)
40
40
  return PASS;
41
41
  const resolved = resolvePath(call.cwd, filePath);
42
+ if (isExemptPath(realPathOf(resolved), env))
43
+ return PASS;
42
44
  const redirect = await assessRedirect(resolved, call.cwd, env, deps);
43
45
  const assessment = coin === null
44
46
  ? assessFullRead(resolved, () => resolveThreshold(env).thresholdTokens, GATE_MIN_THRESHOLD_TOKENS)
@@ -55,9 +57,11 @@ export async function gateBashCall(call, env = process.env, coin = resolveCoin(e
55
57
  if (typeof command !== "string")
56
58
  throw new GateInputError("tool_input.command is missing");
57
59
  const read = classifyBashRead(command);
58
- if (read === null)
60
+ if (read === null || read.range.kind !== "whole")
59
61
  return PASS;
60
62
  const resolved = resolvePath(call.cwd, read.path);
63
+ if (isExemptPath(realPathOf(resolved), env))
64
+ return PASS;
61
65
  const assessment = coin === null
62
66
  ? assessBashRead(resolved, read.range, () => resolveThreshold(env).thresholdTokens, GATE_MIN_THRESHOLD_TOKENS, env)
63
67
  : assessBashReadByCoin(resolved, read.range, coinSelects(coin, call, resolved), env);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/nustack",
3
- "version": "0.26.8",
3
+ "version": "0.27.1",
4
4
  "description": "The nustack CLI — bootstrap NuStack services into a repo via the NuStack discovery plane.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -34,10 +34,10 @@
34
34
  "@types/node": "^22.19.20",
35
35
  "typescript": "^5.7.2",
36
36
  "vitest": "^4.1.11",
37
- "@nurix/contracts": "3.0.2",
38
37
  "@nurix/harness": "0.0.0",
39
- "@nurix/nustack.studio": "0.0.0",
40
- "@nurix/nustack.ui": "0.0.0"
38
+ "@nurix/contracts": "3.0.2",
39
+ "@nurix/nustack.ui": "0.0.0",
40
+ "@nurix/nustack.studio": "0.0.0"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public"