@node9/proxy 2.41.0 → 2.41.2

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.
@@ -869,12 +869,12 @@ function judgeInlineCode(language, code, isProtected) {
869
869
  category
870
870
  };
871
871
  }
872
- const path41 = protectedLiteral(text, isProtected);
873
- if (path41) {
872
+ const path42 = protectedLiteral(text, isProtected);
873
+ if (path42) {
874
874
  return {
875
875
  outcome: "dangerous",
876
876
  what: WHAT["reads-protected"],
877
- evidence: path41.slice(0, 60),
877
+ evidence: path42.slice(0, 60),
878
878
  category: "reads-protected"
879
879
  };
880
880
  }
@@ -1072,6 +1072,66 @@ function foldCommandWords(reading) {
1072
1072
  function commandReadingsImpl(command) {
1073
1073
  return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
1074
1074
  }
1075
+ function readsStdinAsCode(interp, rest) {
1076
+ const ok = (w) => {
1077
+ if (w === null) return false;
1078
+ if (w === "-") return true;
1079
+ if (/^python/.test(interp))
1080
+ return ["-u", "-B", "-E", "-I", "-s", "-q", "-O", "-OO"].includes(w) || /^-[WX]\S/.test(w);
1081
+ if (interp === "perl" || interp === "ruby") return w === "-w" || w === "-W";
1082
+ return false;
1083
+ };
1084
+ return rest.every(ok);
1085
+ }
1086
+ function interpreterHeredocSpans(f, at) {
1087
+ const stmts = f?.Stmts || [];
1088
+ const candidates = [];
1089
+ let unsafe = false;
1090
+ syntax.Walk(f, (node) => {
1091
+ if (!node || unsafe) return false;
1092
+ const n = node;
1093
+ const t = syntax.NodeType(n);
1094
+ if (t === "FuncDecl") {
1095
+ unsafe = true;
1096
+ return false;
1097
+ }
1098
+ if (t !== "CallExpr") return true;
1099
+ const words = (n.Args || []).map((a) => resolveWordLiteral(a));
1100
+ const head = unwrapCommandHead(words);
1101
+ const raw = words[head];
1102
+ if (raw === null || raw === void 0) {
1103
+ unsafe = true;
1104
+ return false;
1105
+ }
1106
+ const name = raw.split("/").pop()?.toLowerCase() ?? "";
1107
+ if (raw.includes("/") && !STDIN_CODE_INTERPRETERS.test(name)) unsafe = true;
1108
+ if (RUNS_TEXT_ELSEWHERE.has(name)) unsafe = true;
1109
+ return !unsafe;
1110
+ });
1111
+ if (unsafe) return [];
1112
+ for (const st of stmts) {
1113
+ if (!st?.Cmd || syntax.NodeType(st.Cmd) !== "CallExpr") continue;
1114
+ const words = (st.Cmd.Args || []).map((a) => resolveWordLiteral(a));
1115
+ const name = (words[0] ?? "").toLowerCase();
1116
+ if (!STDIN_CODE_INTERPRETERS.test(name)) continue;
1117
+ if (!readsStdinAsCode(name, words.slice(1))) continue;
1118
+ candidates.push(st);
1119
+ }
1120
+ const spans = [];
1121
+ for (const st of candidates) {
1122
+ const heredocs = (st.Redirs || []).filter((r2) => r2?.Hdoc);
1123
+ const stdin = heredocs.filter((r2) => r2.N == null || r2.N.Value === "0");
1124
+ const others = heredocs.length - stdin.length;
1125
+ if (stdin.length !== 1 || others > 0) continue;
1126
+ const r = stdin[0];
1127
+ if (heredocText(r.Hdoc) === null) continue;
1128
+ const hs = at(r.Hdoc.Pos().Offset());
1129
+ const he = at(r.Hdoc.End().Offset());
1130
+ if (hs < 0 || he < 0 || he <= hs) continue;
1131
+ spans.push([hs, he]);
1132
+ }
1133
+ return spans;
1134
+ }
1075
1135
  function normalizeCommandForPolicyImpl(command) {
1076
1136
  const f = parseShared(command);
1077
1137
  if (f === PARSE_FAIL) return { posix: command, separator: command, folded: command };
@@ -1082,6 +1142,10 @@ function normalizeCommandForPolicyImpl(command) {
1082
1142
  const rewrites = [];
1083
1143
  const quoteOnlyRewrites = [];
1084
1144
  const msgSpans = /* @__PURE__ */ new Set();
1145
+ for (const [hs, he] of interpreterHeredocSpans(f, at)) {
1146
+ strips.push([hs, he]);
1147
+ msgSpans.add(`${hs}:${he}`);
1148
+ }
1085
1149
  syntax.Walk(f, (node) => {
1086
1150
  if (!node) return false;
1087
1151
  const n = node;
@@ -1135,7 +1199,12 @@ function normalizeCommandForPolicyImpl(command) {
1135
1199
  });
1136
1200
  const stripEdits = strips.map(([s, e]) => [s, e, '""']);
1137
1201
  const apply = (extra) => {
1138
- const edits = [...stripEdits, ...extra];
1202
+ const all = [...stripEdits, ...extra];
1203
+ const edits = all.filter(
1204
+ (e, i) => !all.some(
1205
+ (o, k) => k !== i && o[0] <= e[0] && e[1] <= o[1] && (o[0] < e[0] || e[1] < o[1] || k < i)
1206
+ )
1207
+ );
1139
1208
  if (edits.length === 0) return command;
1140
1209
  edits.sort((a, b) => b[0] - a[0]);
1141
1210
  let out = command;
@@ -2950,9 +3019,9 @@ function ruleServerMatches(rule, serverLabel) {
2950
3019
  if (rule.notServer && label2 && matchesPattern(label2, rule.notServer)) return false;
2951
3020
  return true;
2952
3021
  }
2953
- function getNestedValue(obj, path41) {
3022
+ function getNestedValue(obj, path42) {
2954
3023
  if (!obj || typeof obj !== "object") return null;
2955
- const segments2 = path41.split(".");
3024
+ const segments2 = path42.split(".");
2956
3025
  for (const seg of segments2) {
2957
3026
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
2958
3027
  }
@@ -3909,8 +3978,8 @@ function collectFileAccesses(tool, args, context = {}, inspection = {}) {
3909
3978
  }
3910
3979
  return out;
3911
3980
  }
3912
- function within(path41, root) {
3913
- return path41 === root || path41.startsWith(root === "/" ? "/" : root + "/");
3981
+ function within(path42, root) {
3982
+ return path42 === root || path42.startsWith(root === "/" ? "/" : root + "/");
3914
3983
  }
3915
3984
  function hits(access, paths, directories, context) {
3916
3985
  const home = context.homeDirs?.[0] ?? "~";
@@ -5882,9 +5951,9 @@ function bareToolName(toolName) {
5882
5951
  const parts = toolName.split("__");
5883
5952
  return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
5884
5953
  }
5885
- function valuesAt(args, path41) {
5954
+ function valuesAt(args, path42) {
5886
5955
  let cursors = [args];
5887
- for (const rawSegment of path41.split(".")) {
5956
+ for (const rawSegment of path42.split(".")) {
5888
5957
  const isArray = rawSegment.endsWith("[]");
5889
5958
  const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
5890
5959
  const next = [];
@@ -5915,8 +5984,8 @@ function extractToolDestinations(toolName, args) {
5915
5984
  try {
5916
5985
  const out = [];
5917
5986
  const paths = DESTINATION_ARGS.get(bareToolName(toolName)) ?? [];
5918
- for (const path41 of paths) {
5919
- for (const value of valuesAt(args, path41)) {
5987
+ for (const path42 of paths) {
5988
+ for (const value of valuesAt(args, path42)) {
5920
5989
  const host2 = hostOf(value);
5921
5990
  if (host2) out.push({ host: host2, binary: toolName, raw: value });
5922
5991
  }
@@ -5942,8 +6011,8 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
5942
6011
  try {
5943
6012
  const paths = DESTINATION_ARGS.get(bareToolName(toolName));
5944
6013
  if (!paths) return null;
5945
- for (const path41 of paths) {
5946
- for (const value of valuesAt(args, path41)) {
6014
+ for (const path42 of paths) {
6015
+ for (const value of valuesAt(args, path42)) {
5947
6016
  const host2 = hostOf(value);
5948
6017
  if (!host2) continue;
5949
6018
  const m = classifySsrf(host2);
@@ -5952,7 +6021,7 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
5952
6021
  const decision = ssrfFloorDecision(m, written, opts);
5953
6022
  if (decision === null) continue;
5954
6023
  const reason = decision === "review" ? ssrfDisguisedReason(m, written) : ssrfReason(m, host2);
5955
- return { ...m, argPath: path41, host: host2, reason, decision };
6024
+ return { ...m, argPath: path42, host: host2, reason, decision };
5956
6025
  }
5957
6026
  }
5958
6027
  return null;
@@ -7520,7 +7589,7 @@ function matchCanaryArgs(args, values) {
7520
7589
  return null;
7521
7590
  }
7522
7591
  }
7523
- var MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, finance_default, identity_default, publishing_default, ACTION_TEMPLATES, ACTION_CHECKS, GAP, prog, httpTo, httpWrite, HTTP_WRITE, HTTP_WRITE_ANY, NOT_STRIPE_TEST, postTo, STRIPE_OBJECTS, APP_SHIELD_SPECS, APP_SHIELDS, APP_SHIELD_NAMES, APP_SERVER_GLOB, GENERAL_ACTION_RULES, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, LEVELS, MODE_VALUES, SOFT, STRICT, BLOCK_ALL, LEVEL_CHECKS, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, INTERPRETER_LINE, PROGRAM_DIR, ACCESS_VIEW_CACHE_MAX, accessViewCache, MAX_READING_PAYLOADS, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, CURL_SOURCE_FLAGS, CURL_VALUE_LETTERS, WGET_VALUE_LETTERS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, MAX_PAYLOAD_NESTING, payloadNesting, MAX_INLINE_CALLS, INLINE_EXEC_FALLBACK, UNREADABLE_SOURCE, SCRIPT_RUNNERS, SCRIPT_RUNNER_VALUE_FLAGS, MAX_SCRIPT_OPERANDS, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, exempt, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, SECRET_VIEW_LIMITS, IBAN_LENGTH, IBAN_BBAN_SHAPE, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, CF_ODD, CF_EVEN, SPANISH_LETTERS, HETU_CHARS, MAX, UNTRUSTED_TOOLS, SIGNALS, MENTION_QUOTE_RE, MENTION_WORD_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, CIPHER_KEY_NAME_RE, CIPHER_NAME_PARTS, CIPHER_KEY_VALUE_RE, LARAVEL_KEY_VALUE_RE, SQL_PASSWORD_VALUE_RE, GRAFANA_KEY_VALUE_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DB_URL_PASSWORD_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, MCP_DESTINATION_KEYS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_CC14_RE, PII_CC13_RE, PII_CC19_RE, CARD_PATTERNS, PII_ROUTING_RE, ROUTING_LABEL_RE, ROUTING_LABEL_WINDOW, NATIONAL_ID_SHAPES, NATIONAL_ID_LABEL_RE, NATIONAL_ID_LABEL_WINDOW, NATIONAL_ID_GUARD_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, IBAN_DESTINATION_KEYS, WHOLE_IBAN_RE, JSON_ESCAPE_RE, JSON_ESCAPES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, LOOP_MAX_RECORDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION;
7592
+ var MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, finance_default, identity_default, publishing_default, ACTION_TEMPLATES, ACTION_CHECKS, GAP, prog, httpTo, httpWrite, HTTP_WRITE, HTTP_WRITE_ANY, NOT_STRIPE_TEST, postTo, STRIPE_OBJECTS, APP_SHIELD_SPECS, APP_SHIELDS, APP_SHIELD_NAMES, APP_SERVER_GLOB, GENERAL_ACTION_RULES, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, LEVELS, MODE_VALUES, SOFT, STRICT, BLOCK_ALL, LEVEL_CHECKS, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, MAX_INLINE_CODE, WHAT, OUTSIDE, pyImport, either, PYTHON, NODE, PERL, RUBY, PHP, TABLES, STRING_LITERAL, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, INTERPRETER_LINE, PROGRAM_DIR, ACCESS_VIEW_CACHE_MAX, accessViewCache, MAX_READING_PAYLOADS, STDIN_CODE_INTERPRETERS, RUNS_TEXT_ELSEWHERE, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, CURL_SOURCE_FLAGS, CURL_VALUE_LETTERS, WGET_VALUE_LETTERS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, MAX_PAYLOAD_NESTING, payloadNesting, MAX_INLINE_CALLS, INLINE_EXEC_FALLBACK, UNREADABLE_SOURCE, SCRIPT_RUNNERS, SCRIPT_RUNNER_VALUE_FLAGS, MAX_SCRIPT_OPERANDS, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, exempt, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, SECRET_VIEW_LIMITS, IBAN_LENGTH, IBAN_BBAN_SHAPE, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, CF_ODD, CF_EVEN, SPANISH_LETTERS, HETU_CHARS, MAX, UNTRUSTED_TOOLS, SIGNALS, MENTION_QUOTE_RE, MENTION_WORD_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, CIPHER_KEY_NAME_RE, CIPHER_NAME_PARTS, CIPHER_KEY_VALUE_RE, LARAVEL_KEY_VALUE_RE, SQL_PASSWORD_VALUE_RE, GRAFANA_KEY_VALUE_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DB_URL_PASSWORD_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, MCP_DESTINATION_KEYS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_CC14_RE, PII_CC13_RE, PII_CC19_RE, CARD_PATTERNS, PII_ROUTING_RE, ROUTING_LABEL_RE, ROUTING_LABEL_WINDOW, NATIONAL_ID_SHAPES, NATIONAL_ID_LABEL_RE, NATIONAL_ID_LABEL_WINDOW, NATIONAL_ID_GUARD_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, IBAN_DESTINATION_KEYS, WHOLE_IBAN_RE, JSON_ESCAPE_RE, JSON_ESCAPES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, LOOP_MAX_RECORDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION;
7524
7593
  var init_dist = __esm({
7525
7594
  "packages/policy-engine/dist/index.mjs"() {
7526
7595
  "use strict";
@@ -10196,6 +10265,37 @@ var init_dist = __esm({
10196
10265
  ACCESS_VIEW_CACHE_MAX = 1e3;
10197
10266
  accessViewCache = /* @__PURE__ */ new Map();
10198
10267
  MAX_READING_PAYLOADS = 8;
10268
+ STDIN_CODE_INTERPRETERS = /^(python[0-9.]*|node|nodejs|perl|ruby|php|lua)$/;
10269
+ RUNS_TEXT_ELSEWHERE = /* @__PURE__ */ new Set([
10270
+ ...SHELL_INTERPRETERS,
10271
+ "csh",
10272
+ "tcsh",
10273
+ "ash",
10274
+ "mksh",
10275
+ "busybox",
10276
+ "eval",
10277
+ "source",
10278
+ ".",
10279
+ "exec",
10280
+ "xargs",
10281
+ "ssh",
10282
+ "scp",
10283
+ "docker",
10284
+ "podman",
10285
+ "kubectl",
10286
+ "su",
10287
+ "nsenter",
10288
+ "at",
10289
+ "batch",
10290
+ "crontab",
10291
+ "flock",
10292
+ "psql",
10293
+ "mysql",
10294
+ "mongo",
10295
+ "mongosh",
10296
+ "redis-cli",
10297
+ "sqlite3"
10298
+ ]);
10199
10299
  defaultExpansionOps = null;
10200
10300
  VARIABLE_READERS = /* @__PURE__ */ new Set([
10201
10301
  "read",
@@ -15203,7 +15303,7 @@ zoo`.split("\n");
15203
15303
  { view: "separators-stripped", decoder: "separators", stripped: true }
15204
15304
  ];
15205
15305
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
15206
- CANONICAL_EXTRACTOR_VERSION = "canonical-v30";
15306
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v31";
15207
15307
  }
15208
15308
  });
15209
15309
 
@@ -15873,87 +15973,69 @@ var init_dlp = __esm({
15873
15973
  }
15874
15974
  });
15875
15975
 
15976
+ // src/credential-files.ts
15977
+ import path5 from "path";
15978
+ function homeCredentialFiles(home) {
15979
+ const f = (parts, description, score, kind = "credential") => ({
15980
+ full: path5.join(home, ...parts),
15981
+ label: "~/" + parts.join("/"),
15982
+ description,
15983
+ score,
15984
+ kind
15985
+ });
15986
+ return [
15987
+ f([".ssh", "id_rsa"], "RSA private key: grants SSH access to your servers", 20),
15988
+ f([".ssh", "id_ed25519"], "Ed25519 private key: grants SSH access to your servers", 20),
15989
+ f([".ssh", "id_ecdsa"], "ECDSA private key: grants SSH access to your servers", 20),
15990
+ f([".ssh", "id_dsa"], "DSA private key: grants SSH access to your servers", 20),
15991
+ f([".aws", "credentials"], "AWS access keys: full cloud account access", 20),
15992
+ f([".aws", "config"], "AWS configuration: account and region settings", 5, "config"),
15993
+ f([".config", "gcloud", "credentials.db"], "Google Cloud credentials", 15),
15994
+ f(
15995
+ [".config", "gcloud", "application_default_credentials.json"],
15996
+ "Google Cloud application default credentials",
15997
+ 15
15998
+ ),
15999
+ f([".docker", "config.json"], "Docker registry auth tokens", 10),
16000
+ f([".netrc"], "FTP/HTTP credentials in plain text", 15),
16001
+ f([".npmrc"], "npm auth token: can publish packages as you", 10),
16002
+ f([".node9", "credentials.json"], "Node9 cloud API key", 10)
16003
+ ];
16004
+ }
16005
+ var init_credential_files = __esm({
16006
+ "src/credential-files.ts"() {
16007
+ "use strict";
16008
+ }
16009
+ });
16010
+
15876
16011
  // src/cli/commands/blast.ts
15877
16012
  import chalk from "chalk";
15878
16013
  import fs5 from "fs";
15879
- import path5 from "path";
16014
+ import path6 from "path";
15880
16015
  import os4 from "os";
15881
16016
  function buildSensitivePaths(home, cwd) {
16017
+ const homeFiles = homeCredentialFiles(home).map(({ full, label: label2, description, score }) => ({
16018
+ full,
16019
+ label: label2,
16020
+ description,
16021
+ score
16022
+ }));
15882
16023
  return [
16024
+ ...homeFiles,
15883
16025
  {
15884
- full: path5.join(home, ".ssh", "id_rsa"),
15885
- label: "~/.ssh/id_rsa",
15886
- description: "RSA private key \u2014 grants SSH access to your servers",
15887
- score: 20
15888
- },
15889
- {
15890
- full: path5.join(home, ".ssh", "id_ed25519"),
15891
- label: "~/.ssh/id_ed25519",
15892
- description: "Ed25519 private key \u2014 grants SSH access to your servers",
15893
- score: 20
15894
- },
15895
- {
15896
- full: path5.join(home, ".ssh", "id_ecdsa"),
15897
- label: "~/.ssh/id_ecdsa",
15898
- description: "ECDSA private key \u2014 grants SSH access to your servers",
15899
- score: 20
15900
- },
15901
- {
15902
- full: path5.join(home, ".aws", "credentials"),
15903
- label: "~/.aws/credentials",
15904
- description: "AWS access keys \u2014 full cloud account access",
15905
- score: 20
15906
- },
15907
- {
15908
- full: path5.join(home, ".aws", "config"),
15909
- label: "~/.aws/config",
15910
- description: "AWS configuration \u2014 account and region settings",
15911
- score: 5
15912
- },
15913
- {
15914
- full: path5.join(home, ".config", "gcloud", "credentials.db"),
15915
- label: "~/.config/gcloud/credentials.db",
15916
- description: "Google Cloud credentials",
15917
- score: 15
15918
- },
15919
- {
15920
- full: path5.join(home, ".docker", "config.json"),
15921
- label: "~/.docker/config.json",
15922
- description: "Docker registry auth tokens",
15923
- score: 10
15924
- },
15925
- {
15926
- full: path5.join(home, ".netrc"),
15927
- label: "~/.netrc",
15928
- description: "FTP/HTTP credentials in plain text",
15929
- score: 15
15930
- },
15931
- {
15932
- full: path5.join(home, ".npmrc"),
15933
- label: "~/.npmrc",
15934
- description: "npm auth token \u2014 can publish packages as you",
15935
- score: 10
15936
- },
15937
- {
15938
- full: path5.join(home, ".node9", "credentials.json"),
15939
- label: "~/.node9/credentials.json",
15940
- description: "Node9 cloud API key",
15941
- score: 10
15942
- },
15943
- {
15944
- full: path5.join(cwd, ".env"),
16026
+ full: path6.join(cwd, ".env"),
15945
16027
  label: ".env (current folder)",
15946
- description: "App secrets \u2014 database passwords, API keys",
16028
+ description: "App secrets: database passwords, API keys",
15947
16029
  score: 20
15948
16030
  },
15949
16031
  {
15950
- full: path5.join(cwd, ".env.local"),
16032
+ full: path6.join(cwd, ".env.local"),
15951
16033
  label: ".env.local (current folder)",
15952
- description: "Local overrides \u2014 often contains real credentials",
16034
+ description: "Local overrides: often contains real credentials",
15953
16035
  score: 15
15954
16036
  },
15955
16037
  {
15956
- full: path5.join(cwd, ".env.production"),
16038
+ full: path6.join(cwd, ".env.production"),
15957
16039
  label: ".env.production (current folder)",
15958
16040
  description: "Production secrets",
15959
16041
  score: 20
@@ -15995,6 +16077,7 @@ var init_blast = __esm({
15995
16077
  "src/cli/commands/blast.ts"() {
15996
16078
  "use strict";
15997
16079
  init_dlp();
16080
+ init_credential_files();
15998
16081
  }
15999
16082
  });
16000
16083
 
@@ -16154,7 +16237,7 @@ var init_decision_record = __esm({
16154
16237
  // src/auth/daemon.ts
16155
16238
  import fs6 from "fs";
16156
16239
  import http from "http";
16157
- import path6 from "path";
16240
+ import path7 from "path";
16158
16241
  import os5 from "os";
16159
16242
  async function checkStatePredicates(predicates) {
16160
16243
  if (predicates.length === 0) return {};
@@ -16181,7 +16264,7 @@ function resolveDaemonPort(env = process.env) {
16181
16264
  function getInternalToken() {
16182
16265
  if (forecastRun()) return null;
16183
16266
  try {
16184
- const pidFile = path6.join(os5.homedir(), ".node9", "daemon.pid");
16267
+ const pidFile = path7.join(os5.homedir(), ".node9", "daemon.pid");
16185
16268
  if (!fs6.existsSync(pidFile)) return null;
16186
16269
  const data = JSON.parse(fs6.readFileSync(pidFile, "utf-8"));
16187
16270
  process.kill(data.pid, 0);
@@ -16192,7 +16275,7 @@ function getInternalToken() {
16192
16275
  }
16193
16276
  function isDaemonRunning() {
16194
16277
  if (forecastRun()) return false;
16195
- const pidFile = path6.join(os5.homedir(), ".node9", "daemon.pid");
16278
+ const pidFile = path7.join(os5.homedir(), ".node9", "daemon.pid");
16196
16279
  if (fs6.existsSync(pidFile)) {
16197
16280
  let pid;
16198
16281
  let port;
@@ -16554,7 +16637,7 @@ var init_daemon = __esm({
16554
16637
  "use strict";
16555
16638
  init_forecast_context();
16556
16639
  init_decision_record();
16557
- ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path6.join(os5.tmpdir(), "node9-activity.sock");
16640
+ ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path7.join(os5.tmpdir(), "node9-activity.sock");
16558
16641
  DEFAULT_DAEMON_PORT = 7391;
16559
16642
  DAEMON_PORT = resolveDaemonPort();
16560
16643
  DAEMON_HOST = "127.0.0.1";
@@ -16565,8 +16648,8 @@ var init_daemon = __esm({
16565
16648
  import { z } from "zod";
16566
16649
  function formatIssues(issues) {
16567
16650
  const lines = issues.map((issue) => {
16568
- const path41 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
16569
- return ` \u2022 ${path41}: ${issue.message}`;
16651
+ const path42 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
16652
+ return ` \u2022 ${path42}: ${issue.message}`;
16570
16653
  });
16571
16654
  return `Invalid config:
16572
16655
  ${lines.join("\n")}`;
@@ -16579,9 +16662,9 @@ function prunePaths(root, paths) {
16579
16662
  const yi = y[y.length - 1];
16580
16663
  return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
16581
16664
  });
16582
- for (const path41 of ordered) {
16665
+ for (const path42 of ordered) {
16583
16666
  let cur = root;
16584
- for (const key of path41.slice(0, -1)) {
16667
+ for (const key of path42.slice(0, -1)) {
16585
16668
  if (cur === null || typeof cur !== "object") {
16586
16669
  cur = void 0;
16587
16670
  break;
@@ -16589,7 +16672,7 @@ function prunePaths(root, paths) {
16589
16672
  cur = cur[key];
16590
16673
  }
16591
16674
  if (cur === null || typeof cur !== "object") continue;
16592
- const last = path41[path41.length - 1];
16675
+ const last = path42[path42.length - 1];
16593
16676
  if (Array.isArray(cur)) {
16594
16677
  const i = Number(last);
16595
16678
  if (Number.isInteger(i) && i >= 0 && i < cur.length) {
@@ -16625,7 +16708,7 @@ function sanitizeConfig(raw) {
16625
16708
  return issue.keys.map((k) => [...at, k]);
16626
16709
  }
16627
16710
  return [at.slice(0, -level || void 0)];
16628
- }).filter((path41) => path41.length > 0);
16711
+ }).filter((path42) => path42.length > 0);
16629
16712
  if (paths.length === 0 || !prunePaths(working, paths)) break;
16630
16713
  }
16631
16714
  if (ConfigFileSchema.safeParse(working).success) break;
@@ -16923,7 +17006,7 @@ var init_build = __esm({
16923
17006
 
16924
17007
  // src/shields.ts
16925
17008
  import fs7 from "fs";
16926
- import path7 from "path";
17009
+ import path8 from "path";
16927
17010
  import os6 from "os";
16928
17011
  function loadUserShields() {
16929
17012
  const result = {};
@@ -16940,7 +17023,7 @@ function loadUserShields() {
16940
17023
  return result;
16941
17024
  }
16942
17025
  for (const file2 of entries) {
16943
- const filePath = path7.join(USER_SHIELDS_DIR, file2);
17026
+ const filePath = path8.join(USER_SHIELDS_DIR, file2);
16944
17027
  try {
16945
17028
  const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
16946
17029
  const v = validateShieldDefinition(raw);
@@ -17007,9 +17090,9 @@ var init_shields = __esm({
17007
17090
  init_dist();
17008
17091
  init_build();
17009
17092
  init_dist();
17010
- USER_SHIELDS_DIR = path7.join(os6.homedir(), ".node9", "shields");
17093
+ USER_SHIELDS_DIR = path8.join(os6.homedir(), ".node9", "shields");
17011
17094
  SHIELDS = buildSHIELDS();
17012
- SHIELDS_STATE_FILE = path7.join(os6.homedir(), ".node9", "shields.json");
17095
+ SHIELDS_STATE_FILE = path8.join(os6.homedir(), ".node9", "shields.json");
17013
17096
  }
17014
17097
  });
17015
17098
 
@@ -17143,10 +17226,10 @@ var init_managed = __esm({
17143
17226
 
17144
17227
  // src/auth/trusted-hosts.ts
17145
17228
  import fs8 from "fs";
17146
- import path8 from "path";
17229
+ import path9 from "path";
17147
17230
  import os7 from "os";
17148
17231
  function getTrustedHostsPath() {
17149
- return path8.join(os7.homedir(), ".node9", "trusted-hosts.json");
17232
+ return path9.join(os7.homedir(), ".node9", "trusted-hosts.json");
17150
17233
  }
17151
17234
  function readTrustedHosts() {
17152
17235
  try {
@@ -17379,7 +17462,7 @@ var init_api_url = __esm({
17379
17462
 
17380
17463
  // src/config/index.ts
17381
17464
  import fs9 from "fs";
17382
- import path9 from "path";
17465
+ import path10 from "path";
17383
17466
  import os8 from "os";
17384
17467
  function sanitizeSsrfAllow(entries, source) {
17385
17468
  const kept = [];
@@ -17403,7 +17486,7 @@ function noteRejectedApiUrl(raw) {
17403
17486
  rejectedApiUrlsSeen.add(key);
17404
17487
  try {
17405
17488
  fs9.appendFileSync(
17406
- path9.join(os8.homedir(), ".node9", "hook-debug.log"),
17489
+ path10.join(os8.homedir(), ".node9", "hook-debug.log"),
17407
17490
  `[${(/* @__PURE__ */ new Date()).toISOString()}] REFUSED apiUrl, using the default instead: ${String(raw).slice(0, 200).replace(/[\r\n]+/g, " ")}
17408
17491
  `
17409
17492
  );
@@ -17420,7 +17503,7 @@ function getCredentials() {
17420
17503
  };
17421
17504
  }
17422
17505
  try {
17423
- const credPath = path9.join(os8.homedir(), ".node9", "credentials.json");
17506
+ const credPath = path10.join(os8.homedir(), ".node9", "credentials.json");
17424
17507
  if (fs9.existsSync(credPath)) {
17425
17508
  const creds = JSON.parse(fs9.readFileSync(credPath, "utf-8"));
17426
17509
  const profileName = process.env.NODE9_PROFILE || "default";
@@ -17469,7 +17552,7 @@ function readRulesCacheResilient(cacheFile) {
17469
17552
  }
17470
17553
  }
17471
17554
  if (existed || sawReadError) {
17472
- const backup = path9.join(path9.dirname(cacheFile), "rules-cache.last-good.json");
17555
+ const backup = path10.join(path10.dirname(cacheFile), "rules-cache.last-good.json");
17473
17556
  if (backup !== cacheFile) {
17474
17557
  try {
17475
17558
  const raw = JSON.parse(fs9.readFileSync(backup, "utf-8"));
@@ -17493,7 +17576,7 @@ function logCacheReadIssue(cacheFile, kind) {
17493
17576
  cacheReadLastLoggedAt = now;
17494
17577
  try {
17495
17578
  fs9.appendFileSync(
17496
- path9.join(os8.homedir(), ".node9", "hook-debug.log"),
17579
+ path10.join(os8.homedir(), ".node9", "hook-debug.log"),
17497
17580
  `[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
17498
17581
  `
17499
17582
  );
@@ -17510,8 +17593,8 @@ function catalogSettingsFrom(settings, policy) {
17510
17593
  }
17511
17594
  function getConfig(cwd) {
17512
17595
  if (!cwd && cachedConfig) return cachedConfig;
17513
- const globalPath = path9.join(os8.homedir(), ".node9", "config.json");
17514
- const projectPath = path9.join(cwd ?? process.cwd(), "node9.config.json");
17596
+ const globalPath = path10.join(os8.homedir(), ".node9", "config.json");
17597
+ const projectPath = path10.join(cwd ?? process.cwd(), "node9.config.json");
17515
17598
  const globalConfig = tryLoadConfig(globalPath);
17516
17599
  const projectConfig = tryLoadConfig(projectPath, { project: true });
17517
17600
  const mergedSettings = {
@@ -17756,7 +17839,7 @@ function getConfig(cwd) {
17756
17839
  let cloudMandatesEnforcement = false;
17757
17840
  let cloudMandatesAppPerm = false;
17758
17841
  {
17759
- const cacheFile = path9.join(os8.homedir(), ".node9", "rules-cache.json");
17842
+ const cacheFile = path10.join(os8.homedir(), ".node9", "rules-cache.json");
17760
17843
  try {
17761
17844
  const raw = readRulesCacheResilient(cacheFile);
17762
17845
  if (Array.isArray(raw.rules) && raw.rules.length > 0) {
@@ -17949,13 +18032,13 @@ function getConfig(cwd) {
17949
18032
  }
17950
18033
  if (Array.isArray(mc.jailPaths)) {
17951
18034
  for (const jp of mc.jailPaths) {
17952
- const path41 = typeof jp?.path === "string" ? jp.path.trim() : "";
17953
- if (!path41) continue;
18035
+ const path42 = typeof jp?.path === "string" ? jp.path.trim() : "";
18036
+ if (!path42) continue;
17954
18037
  const verdict = jp?.verdict === "review" ? "review" : "block";
17955
- for (const r of pathRules(path41, verdict, "org-managed jail")) {
18038
+ for (const r of pathRules(path42, verdict, "org-managed jail")) {
17956
18039
  mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
17957
18040
  }
17958
- mergedPolicy.managedJailPaths.push({ path: path41, verdict });
18041
+ mergedPolicy.managedJailPaths.push({ path: path42, verdict });
17959
18042
  }
17960
18043
  }
17961
18044
  if (Array.isArray(mc.trustedHosts)) {
@@ -18675,7 +18758,7 @@ var init_cost_copilot = __esm({
18675
18758
 
18676
18759
  // src/session-files.ts
18677
18760
  import * as fs10 from "fs";
18678
- import * as path10 from "path";
18761
+ import * as path11 from "path";
18679
18762
  function listSessionFiles(dir2, maxDepth = 6) {
18680
18763
  const out = [];
18681
18764
  const walk2 = (d, rel, depth) => {
@@ -18687,8 +18770,8 @@ function listSessionFiles(dir2, maxDepth = 6) {
18687
18770
  return;
18688
18771
  }
18689
18772
  for (const e of entries) {
18690
- const childRel = rel ? path10.join(rel, e.name) : e.name;
18691
- if (e.isDirectory()) walk2(path10.join(d, e.name), childRel, depth + 1);
18773
+ const childRel = rel ? path11.join(rel, e.name) : e.name;
18774
+ if (e.isDirectory()) walk2(path11.join(d, e.name), childRel, depth + 1);
18692
18775
  else if (e.name.endsWith(".jsonl")) out.push(childRel);
18693
18776
  }
18694
18777
  };
@@ -18696,7 +18779,7 @@ function listSessionFiles(dir2, maxDepth = 6) {
18696
18779
  return out;
18697
18780
  }
18698
18781
  function sessionIdOf(relPath) {
18699
- return path10.basename(relPath).replace(/\.jsonl$/, "");
18782
+ return path11.basename(relPath).replace(/\.jsonl$/, "");
18700
18783
  }
18701
18784
  var init_session_files = __esm({
18702
18785
  "src/session-files.ts"() {
@@ -18802,9 +18885,9 @@ var init_decision = __esm({
18802
18885
  // src/shields/jail.ts
18803
18886
  import fs11 from "fs";
18804
18887
  import os9 from "os";
18805
- import path11 from "path";
18888
+ import path12 from "path";
18806
18889
  function jailStorePath() {
18807
- return path11.join(os9.homedir(), ".node9", "jail-paths.json");
18890
+ return path12.join(os9.homedir(), ".node9", "jail-paths.json");
18808
18891
  }
18809
18892
  function readJailPaths() {
18810
18893
  let text;
@@ -18849,9 +18932,9 @@ var init_jail = __esm({
18849
18932
  // src/canary/registry.ts
18850
18933
  import fs12 from "fs";
18851
18934
  import os10 from "os";
18852
- import path12 from "path";
18935
+ import path13 from "path";
18853
18936
  function canaryStorePath() {
18854
- return path12.join(os10.homedir(), ".node9", "canaries.json");
18937
+ return path13.join(os10.homedir(), ".node9", "canaries.json");
18855
18938
  }
18856
18939
  function isRecord(x) {
18857
18940
  if (!x || typeof x !== "object") return false;
@@ -18916,7 +18999,7 @@ var init_scan_watermark = __esm({
18916
18999
  // src/cli/aggregate/report-audit.ts
18917
19000
  import fs13 from "fs";
18918
19001
  import os11 from "os";
18919
- import path13 from "path";
19002
+ import path14 from "path";
18920
19003
  function buildTestTimestamps(allEntries) {
18921
19004
  const testTs = /* @__PURE__ */ new Set();
18922
19005
  for (const e of allEntries) {
@@ -19052,7 +19135,7 @@ function freezeClaudeCost(acc) {
19052
19135
  };
19053
19136
  }
19054
19137
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
19055
- const projPath = path13.join(projectsDir, proj);
19138
+ const projPath = path14.join(projectsDir, proj);
19056
19139
  let files;
19057
19140
  try {
19058
19141
  const stat = fs13.statSync(projPath);
@@ -19063,7 +19146,7 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
19063
19146
  }
19064
19147
  const startMs = start.getTime();
19065
19148
  for (const file2 of files) {
19066
- const filePath = path13.join(projPath, file2);
19149
+ const filePath = path14.join(projPath, file2);
19067
19150
  try {
19068
19151
  if (fs13.statSync(filePath).mtimeMs < startMs) continue;
19069
19152
  } catch {
@@ -19302,7 +19385,7 @@ function listGeminiSessionFiles(geminiTmpDir) {
19302
19385
  return out;
19303
19386
  }
19304
19387
  for (const proj of dirs) {
19305
- const chatsDir = path13.join(geminiTmpDir, proj, "chats");
19388
+ const chatsDir = path14.join(geminiTmpDir, proj, "chats");
19306
19389
  let files;
19307
19390
  try {
19308
19391
  if (!fs13.statSync(chatsDir).isDirectory()) continue;
@@ -19312,7 +19395,7 @@ function listGeminiSessionFiles(geminiTmpDir) {
19312
19395
  }
19313
19396
  for (const f of files) {
19314
19397
  if (!f.endsWith(".jsonl")) continue;
19315
- out.push({ projectKey: proj, file: path13.join(chatsDir, f) });
19398
+ out.push({ projectKey: proj, file: path14.join(chatsDir, f) });
19316
19399
  }
19317
19400
  }
19318
19401
  return out;
@@ -19353,10 +19436,10 @@ function dimensionOfBlock(checkedBy, ruleName) {
19353
19436
  }
19354
19437
  function aggregateReportFromAudit(period, opts = {}) {
19355
19438
  const now = opts.now ?? /* @__PURE__ */ new Date();
19356
- const auditLogPath2 = opts.auditLogPath ?? path13.join(os11.homedir(), ".node9", "audit.log");
19357
- const claudeProjectsDir = opts.claudeProjectsDir ?? path13.join(os11.homedir(), ".claude", "projects");
19439
+ const auditLogPath2 = opts.auditLogPath ?? path14.join(os11.homedir(), ".node9", "audit.log");
19440
+ const claudeProjectsDir = opts.claudeProjectsDir ?? path14.join(os11.homedir(), ".claude", "projects");
19358
19441
  const codexSessionsDir2 = opts.codexSessionsDir ?? codexSessionsDir();
19359
- const geminiTmpDir = opts.geminiTmpDir ?? path13.join(os11.homedir(), ".gemini", "tmp");
19442
+ const geminiTmpDir = opts.geminiTmpDir ?? path14.join(os11.homedir(), ".gemini", "tmp");
19360
19443
  const hasAuditFile = fs13.existsSync(auditLogPath2);
19361
19444
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath2);
19362
19445
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
@@ -19756,10 +19839,10 @@ var init_mcp_cmd = __esm({
19756
19839
 
19757
19840
  // src/daemon/mcp-tools.ts
19758
19841
  import fs14 from "fs";
19759
- import path14 from "path";
19842
+ import path15 from "path";
19760
19843
  import os12 from "os";
19761
19844
  function getMcpToolsFile() {
19762
- return path14.join(os12.homedir(), ".node9", "mcp-tools.json");
19845
+ return path15.join(os12.homedir(), ".node9", "mcp-tools.json");
19763
19846
  }
19764
19847
  function readMcpToolsConfig() {
19765
19848
  try {
@@ -19781,9 +19864,9 @@ var init_mcp_tools = __esm({
19781
19864
  // src/apps/state.ts
19782
19865
  import fs15 from "fs";
19783
19866
  import os13 from "os";
19784
- import path15 from "path";
19867
+ import path16 from "path";
19785
19868
  function appsFilePath(home = os13.homedir()) {
19786
- return path15.join(home, ".node9", "apps.json");
19869
+ return path16.join(home, ".node9", "apps.json");
19787
19870
  }
19788
19871
  function emptyFile() {
19789
19872
  return { version: 1, apps: /* @__PURE__ */ Object.create(null), links: /* @__PURE__ */ Object.create(null) };
@@ -19896,7 +19979,7 @@ var init_state = __esm({
19896
19979
  });
19897
19980
 
19898
19981
  // src/context-sniper.ts
19899
- import path16 from "path";
19982
+ import path17 from "path";
19900
19983
  function smartTruncate(str, maxLen = 500) {
19901
19984
  if (str.length <= maxLen) return str;
19902
19985
  const edge = Math.floor(maxLen / 2) - 3;
@@ -19948,7 +20031,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
19948
20031
  intent = "EDIT";
19949
20032
  if (obj.file_path) {
19950
20033
  editFilePath = String(obj.file_path);
19951
- editFileName = path16.basename(editFilePath);
20034
+ editFileName = path17.basename(editFilePath);
19952
20035
  }
19953
20036
  const result = extractContext(String(obj.new_string), matchedWord);
19954
20037
  contextSnippet = result.snippet;
@@ -20005,7 +20088,7 @@ var init_context_sniper = __esm({
20005
20088
 
20006
20089
  // src/ui/native.ts
20007
20090
  import { spawn } from "child_process";
20008
- import path17 from "path";
20091
+ import path18 from "path";
20009
20092
  function resolveNativeDecision(opts) {
20010
20093
  const { code, output, elapsedMs, locked } = opts;
20011
20094
  if (locked) return "deny";
@@ -20033,7 +20116,7 @@ function formatArgs(args, matchedField, matchedWord) {
20033
20116
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
20034
20117
  const obj = parsed;
20035
20118
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
20036
- const file2 = obj.file_path ? path17.basename(String(obj.file_path)) : "file";
20119
+ const file2 = obj.file_path ? path18.basename(String(obj.file_path)) : "file";
20037
20120
  const oldPreview = smartTruncate(String(obj.old_string), 120);
20038
20121
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
20039
20122
  return {
@@ -20250,16 +20333,16 @@ var init_native = __esm({
20250
20333
  // src/policy/file-access.ts
20251
20334
  import fs16 from "fs";
20252
20335
  import os14 from "os";
20253
- import path18 from "path";
20336
+ import path19 from "path";
20254
20337
  function absoluteSpelling(p, cwd, home) {
20255
20338
  if (p === "~" || p.startsWith("~/")) p = home + p.slice(1);
20256
- return path18.isAbsolute(p) ? p : `${cwd}/${p}`;
20339
+ return path19.isAbsolute(p) ? p : `${cwd}/${p}`;
20257
20340
  }
20258
20341
  function physical(p, followFinal, links = 0) {
20259
20342
  if (links > 40) throw new Error("Too many symbolic links");
20260
20343
  if (!followFinal) {
20261
- const parent = physical(path18.dirname(p), true, links);
20262
- return { path: `${parent.path}/${path18.basename(p)}`, missing: parent.missing };
20344
+ const parent = physical(path19.dirname(p), true, links);
20345
+ return { path: `${parent.path}/${path19.basename(p)}`, missing: parent.missing };
20263
20346
  }
20264
20347
  try {
20265
20348
  return { path: fs16.realpathSync.native(p), missing: false };
@@ -20270,7 +20353,7 @@ function physical(p, followFinal, links = 0) {
20270
20353
  if (fs16.lstatSync(p).isSymbolicLink()) {
20271
20354
  const target = fs16.readlinkSync(p);
20272
20355
  return physical(
20273
- path18.isAbsolute(target) ? target : `${path18.dirname(p)}/${target}`,
20356
+ path19.isAbsolute(target) ? target : `${path19.dirname(p)}/${target}`,
20274
20357
  true,
20275
20358
  links + 1
20276
20359
  );
@@ -20279,10 +20362,10 @@ function physical(p, followFinal, links = 0) {
20279
20362
  if (!["ENOENT", "ENOTDIR"].includes(linkError.code ?? ""))
20280
20363
  throw linkError;
20281
20364
  }
20282
- const parent = path18.dirname(p);
20365
+ const parent = path19.dirname(p);
20283
20366
  if (parent === p) return { path: p, missing: true };
20284
20367
  const resolved = physical(parent, true, links);
20285
- return { path: `${resolved.path}/${path18.basename(p)}`, missing: true };
20368
+ return { path: `${resolved.path}/${path19.basename(p)}`, missing: true };
20286
20369
  }
20287
20370
  }
20288
20371
  function prepareFileAccesses(tool, args, cwd = process.cwd(), inspection = {}, home = os14.homedir()) {
@@ -20372,7 +20455,7 @@ var init_file_access = __esm({
20372
20455
  // src/auth/session-script.ts
20373
20456
  import fs17 from "fs";
20374
20457
  import os15 from "os";
20375
- import path19 from "path";
20458
+ import path20 from "path";
20376
20459
  function readScriptFile(resolved) {
20377
20460
  let fd;
20378
20461
  try {
@@ -20401,7 +20484,7 @@ function readScriptFile(resolved) {
20401
20484
  }
20402
20485
  function absolute(p, cwd, home) {
20403
20486
  const spelled = p === "~" || p.startsWith("~/") ? home + p.slice(1) : p;
20404
- return path19.resolve(cwd, spelled);
20487
+ return path20.resolve(cwd, spelled);
20405
20488
  }
20406
20489
  async function resolveSessionScript(toolName, args, cwd, layers, deps, home = os15.homedir()) {
20407
20490
  try {
@@ -20468,7 +20551,7 @@ var init_session_script = __esm({
20468
20551
 
20469
20552
  // src/utils/git-remotes.ts
20470
20553
  import fs18 from "fs";
20471
- import path20 from "path";
20554
+ import path21 from "path";
20472
20555
  function readSmallFile(p, max) {
20473
20556
  let fd;
20474
20557
  try {
@@ -20500,20 +20583,20 @@ function gitRemoteHost(url) {
20500
20583
  return null;
20501
20584
  }
20502
20585
  function findGitConfig(dir2, walkUp = true) {
20503
- let cur = path20.resolve(dir2);
20586
+ let cur = path21.resolve(dir2);
20504
20587
  for (let i = 0; i < (walkUp ? 64 : 1); i++) {
20505
- const dotGit = path20.join(cur, ".git");
20506
- const direct = readSmallFile(path20.join(dotGit, "config"), 1e6);
20588
+ const dotGit = path21.join(cur, ".git");
20589
+ const direct = readSmallFile(path21.join(dotGit, "config"), 1e6);
20507
20590
  if (direct !== null) return direct;
20508
20591
  const pointer = readSmallFile(dotGit, 4096);
20509
20592
  const m = pointer && /^gitdir:\s*(.+?)\s*$/m.exec(pointer);
20510
20593
  if (m) {
20511
- let gitDir = path20.resolve(cur, m[1]);
20512
- const common = readSmallFile(path20.join(gitDir, "commondir"), 4096);
20513
- if (common !== null) gitDir = path20.resolve(gitDir, common.trim());
20514
- return readSmallFile(path20.join(gitDir, "config"), 1e6);
20594
+ let gitDir = path21.resolve(cur, m[1]);
20595
+ const common = readSmallFile(path21.join(gitDir, "commondir"), 4096);
20596
+ if (common !== null) gitDir = path21.resolve(gitDir, common.trim());
20597
+ return readSmallFile(path21.join(gitDir, "config"), 1e6);
20515
20598
  }
20516
- const parent = path20.dirname(cur);
20599
+ const parent = path21.dirname(cur);
20517
20600
  if (parent === cur) return null;
20518
20601
  cur = parent;
20519
20602
  }
@@ -20522,12 +20605,12 @@ function findGitConfig(dir2, walkUp = true) {
20522
20605
  function readGitRemotes(dir2, opts = {}) {
20523
20606
  if (!dir2) return [];
20524
20607
  const walkUp = opts.walkUp !== false;
20525
- const key = `${walkUp ? "up" : "at"}:${path20.resolve(dir2)}`;
20608
+ const key = `${walkUp ? "up" : "at"}:${path21.resolve(dir2)}`;
20526
20609
  const hit = cache.get(key);
20527
20610
  if (hit) return hit;
20528
20611
  const out = [];
20529
20612
  try {
20530
- const cfg = findGitConfig(path20.resolve(dir2), walkUp);
20613
+ const cfg = findGitConfig(path21.resolve(dir2), walkUp);
20531
20614
  if (cfg !== null) {
20532
20615
  let current = null;
20533
20616
  for (const line of cfg.split(/\r?\n/)) {
@@ -20563,17 +20646,17 @@ var init_git_remotes = __esm({
20563
20646
 
20564
20647
  // src/exposure/known-hosts.ts
20565
20648
  import fs19 from "fs";
20566
- import path21 from "path";
20649
+ import path22 from "path";
20567
20650
  function manifestRoot(dir2) {
20568
- let cur = path21.resolve(dir2);
20651
+ let cur = path22.resolve(dir2);
20569
20652
  for (let i = 0; i < 64; i++) {
20570
20653
  for (const [m] of ECOSYSTEMS) {
20571
20654
  try {
20572
- if (fs19.existsSync(path21.join(cur, m))) return cur;
20655
+ if (fs19.existsSync(path22.join(cur, m))) return cur;
20573
20656
  } catch {
20574
20657
  }
20575
20658
  }
20576
- const parent = path21.dirname(cur);
20659
+ const parent = path22.dirname(cur);
20577
20660
  if (parent === cur) return null;
20578
20661
  cur = parent;
20579
20662
  }
@@ -20582,7 +20665,7 @@ function manifestRoot(dir2) {
20582
20665
  function knownProjectHosts(dir2) {
20583
20666
  const empty = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
20584
20667
  if (!dir2) return empty;
20585
- const key = path21.resolve(dir2);
20668
+ const key = path22.resolve(dir2);
20586
20669
  const hit = cache2.get(key);
20587
20670
  if (hit) return hit;
20588
20671
  const out = { hosts: /* @__PURE__ */ new Set(), repos: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Map() };
@@ -20595,7 +20678,7 @@ function knownProjectHosts(dir2) {
20595
20678
  const root = manifestRoot(key);
20596
20679
  if (root !== null) {
20597
20680
  for (const [m, registry] of ECOSYSTEMS) {
20598
- if (fs19.existsSync(path21.join(root, m))) out.hosts.add(registry);
20681
+ if (fs19.existsSync(path22.join(root, m))) out.hosts.add(registry);
20599
20682
  }
20600
20683
  try {
20601
20684
  if (fs19.readdirSync(root).some((f) => f.endsWith(".gemspec")))
@@ -20627,7 +20710,7 @@ var init_known_hosts = __esm({
20627
20710
  // src/exposure/writes.ts
20628
20711
  import fs20 from "fs";
20629
20712
  import os16 from "os";
20630
- import path22 from "path";
20713
+ import path23 from "path";
20631
20714
  function writtenContent(args) {
20632
20715
  if (!args || typeof args !== "object" || Array.isArray(args)) return [];
20633
20716
  const a = args;
@@ -20675,7 +20758,7 @@ function judgeWritesAfterExposure(opts) {
20675
20758
  seen.add(p);
20676
20759
  if (p === "/dev" || p.startsWith("/dev/")) continue;
20677
20760
  if (root !== null && isInside(root, p)) {
20678
- if (isAgentInstructionPath(p, root) && (path22.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
20761
+ if (isAgentInstructionPath(p, root) && (path23.basename(p) !== "package.json" || packageScriptsTouched(opts.args, p, opts.shellCommand)))
20679
20762
  holds.push({ path: p, why: "agent-instruction" });
20680
20763
  continue;
20681
20764
  }
@@ -20717,13 +20800,13 @@ var init_exposure_store = __esm({
20717
20800
  import crypto3 from "crypto";
20718
20801
  import fs21 from "fs";
20719
20802
  import os17 from "os";
20720
- import path23 from "path";
20803
+ import path24 from "path";
20721
20804
  function dir() {
20722
- return path23.join(os17.homedir(), ".node9", "exposure-sessions");
20805
+ return path24.join(os17.homedir(), ".node9", "exposure-sessions");
20723
20806
  }
20724
20807
  function file(sessionId) {
20725
20808
  const id = crypto3.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
20726
- return path23.join(dir(), id);
20809
+ return path24.join(dir(), id);
20727
20810
  }
20728
20811
  function maybeSessionExposed(sessionId) {
20729
20812
  if (!sessionId) return false;
@@ -20847,10 +20930,10 @@ var init_roles = __esm({
20847
20930
 
20848
20931
  // src/auth/trusted-recipients.ts
20849
20932
  import fs23 from "fs";
20850
- import path24 from "path";
20933
+ import path25 from "path";
20851
20934
  import os18 from "os";
20852
20935
  function getTrustedRecipientsPath() {
20853
- return path24.join(os18.homedir(), ".node9", "trusted-recipients.json");
20936
+ return path25.join(os18.homedir(), ".node9", "trusted-recipients.json");
20854
20937
  }
20855
20938
  function readTrustedRecipients() {
20856
20939
  try {
@@ -20863,7 +20946,7 @@ function readTrustedRecipients() {
20863
20946
  }
20864
20947
  function writeTrustedRecipients(entries) {
20865
20948
  const filePath = getTrustedRecipientsPath();
20866
- fs23.mkdirSync(path24.dirname(filePath), { recursive: true });
20949
+ fs23.mkdirSync(path25.dirname(filePath), { recursive: true });
20867
20950
  const tmp = filePath + ".node9-tmp";
20868
20951
  fs23.writeFileSync(tmp, JSON.stringify({ entries }, null, 2), { mode: 384 });
20869
20952
  fs23.renameSync(tmp, filePath);
@@ -20903,10 +20986,10 @@ var init_trusted_recipients = __esm({
20903
20986
 
20904
20987
  // src/utils/atomic-write.ts
20905
20988
  import fs24 from "fs";
20906
- import path25 from "path";
20989
+ import path26 from "path";
20907
20990
  import { randomUUID } from "crypto";
20908
20991
  function atomicWriteSync(filePath, data, options) {
20909
- const dir2 = path25.dirname(filePath);
20992
+ const dir2 = path26.dirname(filePath);
20910
20993
  if (!fs24.existsSync(dir2)) fs24.mkdirSync(dir2, { recursive: true });
20911
20994
  const tmpPath = `${filePath}.${randomUUID()}.tmp`;
20912
20995
  try {
@@ -20937,13 +21020,13 @@ var init_atomic_write = __esm({
20937
21020
  // src/supply-chain/osv-index.ts
20938
21021
  import fs25 from "fs";
20939
21022
  import os19 from "os";
20940
- import path26 from "path";
21023
+ import path27 from "path";
20941
21024
  import { createHash as createHash4 } from "crypto";
20942
21025
  function osvRoot() {
20943
- return path26.join(os19.homedir(), ".node9", "osv");
21026
+ return path27.join(os19.homedir(), ".node9", "osv");
20944
21027
  }
20945
21028
  function ecoDir(eco) {
20946
- return path26.join(osvRoot(), eco);
21029
+ return path27.join(osvRoot(), eco);
20947
21030
  }
20948
21031
  function indexKey(eco, name) {
20949
21032
  switch (eco) {
@@ -20962,11 +21045,11 @@ function shardOf(key) {
20962
21045
  return createHash4("sha1").update(key).digest("hex").slice(0, 2);
20963
21046
  }
20964
21047
  function shardPath(eco, shard) {
20965
- return path26.join(ecoDir(eco), "shards", `${shard}.json`);
21048
+ return path27.join(ecoDir(eco), "shards", `${shard}.json`);
20966
21049
  }
20967
21050
  function readMeta(eco) {
20968
21051
  try {
20969
- const m = JSON.parse(fs25.readFileSync(path26.join(ecoDir(eco), "meta.json"), "utf8"));
21052
+ const m = JSON.parse(fs25.readFileSync(path27.join(ecoDir(eco), "meta.json"), "utf8"));
20970
21053
  if (typeof m?.syncedAt !== "string" || typeof m?.lastModifiedMs !== "number") return null;
20971
21054
  return m;
20972
21055
  } catch {
@@ -21358,14 +21441,14 @@ var init_registry2 = __esm({
21358
21441
 
21359
21442
  // src/supply-chain/local-resolve.ts
21360
21443
  import fs26 from "fs";
21361
- import path27 from "path";
21444
+ import path28 from "path";
21362
21445
  function resolveInstalled(name, cwd) {
21363
- if (!cwd || !path27.isAbsolute(cwd)) return null;
21446
+ if (!cwd || !path28.isAbsolute(cwd)) return null;
21364
21447
  if (!/^(?:@[^/\\]+\/)?[^/\\]+$/.test(name)) return null;
21365
- let dir2 = path27.resolve(cwd);
21448
+ let dir2 = path28.resolve(cwd);
21366
21449
  for (let i = 0; i < MAX_LEVELS; i++) {
21367
- const pkgDir = path27.join(dir2, "node_modules", ...name.split("/"));
21368
- const manifest = path27.join(pkgDir, "package.json");
21450
+ const pkgDir = path28.join(dir2, "node_modules", ...name.split("/"));
21451
+ const manifest = path28.join(pkgDir, "package.json");
21369
21452
  try {
21370
21453
  const raw = fs26.readFileSync(manifest, "utf8");
21371
21454
  const parsed = JSON.parse(raw);
@@ -21375,7 +21458,7 @@ function resolveInstalled(name, cwd) {
21375
21458
  return null;
21376
21459
  } catch {
21377
21460
  }
21378
- const parent = path27.dirname(dir2);
21461
+ const parent = path28.dirname(dir2);
21379
21462
  if (parent === dir2) break;
21380
21463
  dir2 = parent;
21381
21464
  }
@@ -21392,7 +21475,7 @@ var init_local_resolve = __esm({
21392
21475
 
21393
21476
  // src/supply-chain/check.ts
21394
21477
  import picomatch2 from "picomatch";
21395
- import path28 from "path";
21478
+ import path29 from "path";
21396
21479
  function commandOf(toolName, args) {
21397
21480
  if (!isBashTool(toolName) || !args || typeof args !== "object") return null;
21398
21481
  const a = args;
@@ -21494,7 +21577,7 @@ function checkInstalled(p, version, misses) {
21494
21577
  }
21495
21578
  async function checkOne(p, cfg, misses, networkCapped, cwd) {
21496
21579
  if (p.ecosystem === "npm" && LOCAL_FIRST_MANAGERS.has(p.manager) && p.localCwd !== null && (p.raw === p.name || p.version)) {
21497
- const localCwd = cwd && path28.isAbsolute(cwd) ? (p.localCwd ?? []).reduce((dir2, step) => path28.resolve(dir2, step), cwd) : void 0;
21580
+ const localCwd = cwd && path29.isAbsolute(cwd) ? (p.localCwd ?? []).reduce((dir2, step) => path29.resolve(dir2, step), cwd) : void 0;
21498
21581
  const installed = resolveInstalled(p.name, localCwd);
21499
21582
  if (installed && (!p.version || p.version === installed.version))
21500
21583
  return checkInstalled(p, installed.version, misses);
@@ -21664,14 +21747,14 @@ var init_check = __esm({
21664
21747
 
21665
21748
  // src/utils/provenance.ts
21666
21749
  import fs27 from "fs";
21667
- import path29 from "path";
21750
+ import path30 from "path";
21668
21751
  import os20 from "os";
21669
21752
  function findInPath(cmd) {
21670
- if (path29.posix.isAbsolute(cmd)) return cmd;
21753
+ if (path30.posix.isAbsolute(cmd)) return cmd;
21671
21754
  const pathEnv = process.env.PATH ?? "";
21672
- for (const dir2 of pathEnv.split(path29.delimiter)) {
21755
+ for (const dir2 of pathEnv.split(path30.delimiter)) {
21673
21756
  if (!dir2) continue;
21674
- const full = path29.join(dir2, cmd);
21757
+ const full = path30.join(dir2, cmd);
21675
21758
  try {
21676
21759
  fs27.accessSync(full, fs27.constants.X_OK);
21677
21760
  return full;
@@ -21702,7 +21785,7 @@ function _classifyPath(resolved, cwd) {
21702
21785
  }
21703
21786
  function checkProvenance(cmd, cwd) {
21704
21787
  const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
21705
- if (path29.posix.isAbsolute(bare)) {
21788
+ if (path30.posix.isAbsolute(bare)) {
21706
21789
  const early = _classifyPath(bare, cwd);
21707
21790
  if (early.trustLevel === "suspect") {
21708
21791
  return { resolvedPath: bare, ...early };
@@ -21752,11 +21835,11 @@ var init_provenance2 = __esm({
21752
21835
  SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
21753
21836
  MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
21754
21837
  USER_PREFIXES = [
21755
- path29.join(os20.homedir(), "bin"),
21756
- path29.join(os20.homedir(), ".local", "bin"),
21757
- path29.join(os20.homedir(), ".cargo", "bin"),
21758
- path29.join(os20.homedir(), ".npm-global", "bin"),
21759
- path29.join(os20.homedir(), ".volta", "bin")
21838
+ path30.join(os20.homedir(), "bin"),
21839
+ path30.join(os20.homedir(), ".local", "bin"),
21840
+ path30.join(os20.homedir(), ".cargo", "bin"),
21841
+ path30.join(os20.homedir(), ".npm-global", "bin"),
21842
+ path30.join(os20.homedir(), ".volta", "bin")
21760
21843
  ];
21761
21844
  SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
21762
21845
  }
@@ -21811,7 +21894,7 @@ var init_policy = __esm({
21811
21894
 
21812
21895
  // src/auth/state.ts
21813
21896
  import fs28 from "fs";
21814
- import path30 from "path";
21897
+ import path31 from "path";
21815
21898
  import os21 from "os";
21816
21899
  function extractCommandPattern(toolName, args) {
21817
21900
  const lower2 = toolName.toLowerCase();
@@ -21889,7 +21972,7 @@ function writeTrustSession(toolName, durationMs, args) {
21889
21972
  }
21890
21973
  function getPersistentDecision(toolName) {
21891
21974
  try {
21892
- const file2 = path30.join(os21.homedir(), ".node9", "decisions.json");
21975
+ const file2 = path31.join(os21.homedir(), ".node9", "decisions.json");
21893
21976
  if (!fs28.existsSync(file2)) return null;
21894
21977
  const decisions = JSON.parse(fs28.readFileSync(file2, "utf-8"));
21895
21978
  const d = decisions[toolName];
@@ -21905,8 +21988,8 @@ var init_state2 = __esm({
21905
21988
  init_policy();
21906
21989
  init_atomic_write();
21907
21990
  init_forecast_context();
21908
- PAUSED_FILE = path30.join(os21.homedir(), ".node9", "PAUSED");
21909
- TRUST_FILE = path30.join(os21.homedir(), ".node9", "trust.json");
21991
+ PAUSED_FILE = path31.join(os21.homedir(), ".node9", "PAUSED");
21992
+ TRUST_FILE = path31.join(os21.homedir(), ".node9", "trust.json");
21910
21993
  }
21911
21994
  });
21912
21995
 
@@ -21957,7 +22040,7 @@ var init_read_capped = __esm({
21957
22040
  // src/auth/cloud.ts
21958
22041
  import fs30 from "fs";
21959
22042
  import os22 from "os";
21960
- import path31 from "path";
22043
+ import path32 from "path";
21961
22044
  function maskArgsForCloud(args) {
21962
22045
  if (args === void 0 || args === null) return args;
21963
22046
  try {
@@ -21990,7 +22073,7 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
21990
22073
  let ciContext;
21991
22074
  if (process.env.CI) {
21992
22075
  try {
21993
- const ciContextPath = path31.join(os22.homedir(), ".node9", "ci-context.json");
22076
+ const ciContextPath = path32.join(os22.homedir(), ".node9", "ci-context.json");
21994
22077
  const ci = readCappedText(ciContextPath, 1e4);
21995
22078
  if (!ci) throw new Error("ci-context.json unreadable");
21996
22079
  if (ci.truncated) throw new Error("ci-context.json exceeds 10 KB");
@@ -22137,10 +22220,10 @@ var init_cloud = __esm({
22137
22220
 
22138
22221
  // src/loop-detector.ts
22139
22222
  import fs31 from "fs";
22140
- import path32 from "path";
22223
+ import path33 from "path";
22141
22224
  import os23 from "os";
22142
22225
  function loopStateFile() {
22143
- return path32.join(os23.homedir(), ".node9", "loop-state.json");
22226
+ return path33.join(os23.homedir(), ".node9", "loop-state.json");
22144
22227
  }
22145
22228
  function readState() {
22146
22229
  try {
@@ -22154,7 +22237,7 @@ function readState() {
22154
22237
  }
22155
22238
  }
22156
22239
  function writeState(records) {
22157
- const dir2 = path32.dirname(loopStateFile());
22240
+ const dir2 = path33.dirname(loopStateFile());
22158
22241
  if (!fs31.existsSync(dir2)) fs31.mkdirSync(dir2, { recursive: true });
22159
22242
  const tmpPath = `${loopStateFile()}.${os23.hostname()}.${process.pid}.tmp`;
22160
22243
  fs31.writeFileSync(tmpPath, JSON.stringify(records));
@@ -23954,17 +24037,17 @@ var init_forecast = __esm({
23954
24037
 
23955
24038
  // src/version.ts
23956
24039
  import fs32 from "fs";
23957
- import path33 from "path";
24040
+ import path34 from "path";
23958
24041
  function readVersion() {
23959
24042
  let dir2 = __dirname;
23960
24043
  for (let i = 0; i < 5; i++) {
23961
- const p = path33.join(dir2, "package.json");
24044
+ const p = path34.join(dir2, "package.json");
23962
24045
  try {
23963
24046
  const pkg = JSON.parse(fs32.readFileSync(p, "utf-8"));
23964
24047
  if (pkg.name === "@node9/proxy" && typeof pkg.version === "string") return pkg.version;
23965
24048
  } catch {
23966
24049
  }
23967
- const parent = path33.dirname(dir2);
24050
+ const parent = path34.dirname(dir2);
23968
24051
  if (parent === dir2) break;
23969
24052
  dir2 = parent;
23970
24053
  }
@@ -23981,10 +24064,10 @@ var init_version = __esm({
23981
24064
  // src/cli/commands/scan-cache.ts
23982
24065
  import fs33 from "fs";
23983
24066
  import os25 from "os";
23984
- import path34 from "path";
24067
+ import path35 from "path";
23985
24068
  import { createHash as createHash5 } from "crypto";
23986
24069
  function scanCacheRoot() {
23987
- return path34.join(os25.homedir(), ".node9", "scan-cache");
24070
+ return path35.join(os25.homedir(), ".node9", "scan-cache");
23988
24071
  }
23989
24072
  function configHashOf(config) {
23990
24073
  const h = createHash5("sha256");
@@ -23992,7 +24075,7 @@ function configHashOf(config) {
23992
24075
  h.update(JSON.stringify(config.policySource));
23993
24076
  h.update(JSON.stringify(config.policy));
23994
24077
  for (const name of SIDECAR_FILES) {
23995
- const st = fileStamp(path34.join(os25.homedir(), ".node9", name));
24078
+ const st = fileStamp(path35.join(os25.homedir(), ".node9", name));
23996
24079
  h.update(`|${name}:${st ? `${st.size}:${st.mtimeMs}` : "absent"}`);
23997
24080
  }
23998
24081
  return h.digest("hex").slice(0, 16);
@@ -24005,17 +24088,16 @@ function fileStamp(filePath) {
24005
24088
  return null;
24006
24089
  }
24007
24090
  }
24008
- function windowKey(startDate) {
24009
- return startDate ? startDate.toISOString().slice(0, 10) : "all";
24010
- }
24011
- var SCAN_CACHE_VERSION, KEPT_WINDOWS, SIDECAR_FILES, ScanCache;
24091
+ var SCAN_CACHE_VERSION, FLUSH_EVERY_FILES, FLUSH_EVERY_MS, activeCaches, SIDECAR_FILES, ScanCache;
24012
24092
  var init_scan_cache = __esm({
24013
24093
  "src/cli/commands/scan-cache.ts"() {
24014
24094
  "use strict";
24015
24095
  init_dist();
24016
24096
  init_version();
24017
- SCAN_CACHE_VERSION = 1;
24018
- KEPT_WINDOWS = 3;
24097
+ SCAN_CACHE_VERSION = 3;
24098
+ FLUSH_EVERY_FILES = 25;
24099
+ FLUSH_EVERY_MS = 1e4;
24100
+ activeCaches = /* @__PURE__ */ new Set();
24019
24101
  SIDECAR_FILES = [
24020
24102
  "canaries.json",
24021
24103
  "shields.json",
@@ -24024,8 +24106,8 @@ var init_scan_cache = __esm({
24024
24106
  "trusted-hosts.json",
24025
24107
  "trusted-recipients.json",
24026
24108
  "rules-cache.json",
24027
- path34.join("osv", "npm", "meta.json"),
24028
- path34.join("osv", "PyPI", "meta.json")
24109
+ path35.join("osv", "npm", "meta.json"),
24110
+ path35.join("osv", "PyPI", "meta.json")
24029
24111
  ];
24030
24112
  ScanCache = class {
24031
24113
  root;
@@ -24035,10 +24117,13 @@ var init_scan_cache = __esm({
24035
24117
  /** Counters for the run, so a caller can say what was reused. */
24036
24118
  hits = 0;
24037
24119
  misses = 0;
24120
+ pendingPuts = 0;
24121
+ lastFlush = Date.now();
24038
24122
  constructor(opts) {
24039
24123
  this.root = opts.root ?? scanCacheRoot();
24040
24124
  this.enabled = opts.enabled ?? process.env.NODE9_SCAN_CACHE !== "0";
24041
24125
  this.index = this.load(opts.configHash);
24126
+ activeCaches.add(this);
24042
24127
  }
24043
24128
  load(configHash) {
24044
24129
  const fresh = {
@@ -24051,7 +24136,7 @@ var init_scan_cache = __esm({
24051
24136
  if (!this.enabled) return fresh;
24052
24137
  try {
24053
24138
  const raw = JSON.parse(
24054
- fs33.readFileSync(path34.join(this.root, "index.json"), "utf8")
24139
+ fs33.readFileSync(path35.join(this.root, "index.json"), "utf8")
24055
24140
  );
24056
24141
  if (raw.cacheVersion !== SCAN_CACHE_VERSION || raw.engineVersion !== CANONICAL_EXTRACTOR_VERSION || raw.proxyVersion !== PROXY_VERSION || raw.configHash !== configHash || typeof raw.files !== "object") {
24057
24142
  this.dropAll();
@@ -24068,15 +24153,19 @@ var init_scan_cache = __esm({
24068
24153
  } catch {
24069
24154
  }
24070
24155
  }
24071
- /** The cached result for an unchanged file in this window, judged under
24156
+ /** The cached whole-file result for an unchanged file, judged under
24072
24157
  * configurations that have not changed either, or null. */
24073
- get(filePath, stamp, window, hasher) {
24158
+ get(filePath, stamp, hasher, judgeFrom = null) {
24074
24159
  if (!this.enabled) return null;
24075
- const e = this.index.files[`${filePath}|${window}`];
24160
+ const e = this.index.files[filePath];
24076
24161
  if (!e || e.size !== stamp.size || e.mtimeMs !== stamp.mtimeMs) {
24077
24162
  this.misses++;
24078
24163
  return null;
24079
24164
  }
24165
+ if (e.judgedFrom && (!judgeFrom || judgeFrom.getTime() < Date.parse(e.judgedFrom))) {
24166
+ this.misses++;
24167
+ return null;
24168
+ }
24080
24169
  for (const [cwd, hash2] of Object.entries(e.configHashes ?? {})) {
24081
24170
  if (hasher && hasher.configHashFor(cwd) !== hash2) {
24082
24171
  this.misses++;
@@ -24084,67 +24173,78 @@ var init_scan_cache = __esm({
24084
24173
  }
24085
24174
  }
24086
24175
  try {
24087
- const r = JSON.parse(fs33.readFileSync(path34.join(this.root, e.result), "utf8"));
24176
+ const stored = JSON.parse(fs33.readFileSync(path35.join(this.root, e.result), "utf8"));
24177
+ if ((stored.judgedFrom ?? null) !== (e.judgedFrom ?? null) || !stored.result) {
24178
+ this.misses++;
24179
+ return null;
24180
+ }
24088
24181
  this.hits++;
24089
- return r;
24182
+ return stored.result;
24090
24183
  } catch {
24091
24184
  this.misses++;
24092
24185
  return null;
24093
24186
  }
24094
24187
  }
24095
- put(filePath, stamp, window, agent, result, hasher) {
24188
+ put(filePath, stamp, agent, result, hasher, judgeFrom = null) {
24096
24189
  if (!this.enabled) return;
24097
- const name = createHash5("sha256").update(`${agent}|${filePath}|${window}`).digest("hex").slice(0, 40);
24098
- const rel = path34.join("files", name.slice(0, 2), `${name}.json`);
24190
+ const name = createHash5("sha256").update(`${agent}|${filePath}`).digest("hex").slice(0, 40);
24191
+ const rel = path35.join("files", name.slice(0, 2), `${name}.json`);
24099
24192
  const configHashes = {};
24100
24193
  if (hasher)
24101
24194
  for (const cwd of result.configCwds ?? []) configHashes[cwd] = hasher.configHashFor(cwd);
24102
24195
  try {
24103
- const abs = path34.join(this.root, rel);
24104
- fs33.mkdirSync(path34.dirname(abs), { recursive: true, mode: 448 });
24105
- fs33.writeFileSync(abs, JSON.stringify(result), { mode: 384 });
24106
- this.index.files[`${filePath}|${window}`] = {
24196
+ const abs = path35.join(this.root, rel);
24197
+ fs33.mkdirSync(path35.dirname(abs), { recursive: true, mode: 448 });
24198
+ fs33.writeFileSync(
24199
+ abs,
24200
+ JSON.stringify({ judgedFrom: judgeFrom ? judgeFrom.toISOString() : null, result }),
24201
+ { mode: 384 }
24202
+ );
24203
+ this.index.files[filePath] = {
24107
24204
  ...stamp,
24108
- window,
24109
24205
  agent,
24110
24206
  result: rel,
24111
24207
  configHashes,
24112
- at: Date.now()
24208
+ at: Date.now(),
24209
+ ...judgeFrom && { judgedFrom: judgeFrom.toISOString() }
24113
24210
  };
24114
24211
  this.dirty = true;
24212
+ this.pendingPuts++;
24213
+ if (this.pendingPuts >= FLUSH_EVERY_FILES || Date.now() - this.lastFlush > FLUSH_EVERY_MS)
24214
+ this.flush();
24115
24215
  } catch {
24116
24216
  }
24117
24217
  }
24118
- /** Drop the entries and files of every window but the most recent few: a
24119
- * daily `--days 30` moves the window every day and would otherwise leave
24120
- * one orphan set per day. */
24218
+ /** Drop the entries of transcripts that no longer exist, and their files. */
24121
24219
  prune() {
24122
- const byWindow = /* @__PURE__ */ new Map();
24123
- for (const e of Object.values(this.index.files))
24124
- byWindow.set(e.window, Math.max(byWindow.get(e.window) ?? 0, e.at ?? 0));
24125
- const keep = new Set(
24126
- [...byWindow.entries()].sort((a, b) => b[1] - a[1]).slice(0, KEPT_WINDOWS).map(([w]) => w)
24127
- );
24128
24220
  for (const [key, e] of Object.entries(this.index.files)) {
24129
- if (keep.has(e.window)) continue;
24221
+ if (fs33.existsSync(key)) continue;
24130
24222
  try {
24131
- fs33.rmSync(path34.join(this.root, e.result), { force: true });
24223
+ fs33.rmSync(path35.join(this.root, e.result), { force: true });
24132
24224
  } catch {
24133
24225
  }
24134
24226
  delete this.index.files[key];
24135
24227
  this.dirty = true;
24136
24228
  }
24137
24229
  }
24138
- /** Write the index once per run. */
24230
+ /** The final flush of a run: the cache leaves the interrupt registry, so a
24231
+ * long-lived daemon does not keep every index it ever opened. */
24232
+ close() {
24233
+ this.flush();
24234
+ activeCaches.delete(this);
24235
+ }
24236
+ /** Write the index: every few files, on an interrupt, and at the end. */
24139
24237
  flush() {
24140
24238
  if (!this.enabled) return;
24141
- this.prune();
24239
+ if (this.pendingPuts === 0) this.prune();
24240
+ this.pendingPuts = 0;
24241
+ this.lastFlush = Date.now();
24142
24242
  if (!this.dirty) return;
24143
24243
  try {
24144
24244
  fs33.mkdirSync(this.root, { recursive: true, mode: 448 });
24145
- const tmp = path34.join(this.root, `index.${process.pid}.tmp`);
24245
+ const tmp = path35.join(this.root, `index.${process.pid}.tmp`);
24146
24246
  fs33.writeFileSync(tmp, JSON.stringify(this.index), { mode: 384 });
24147
- fs33.renameSync(tmp, path34.join(this.root, "index.json"));
24247
+ fs33.renameSync(tmp, path35.join(this.root, "index.json"));
24148
24248
  this.dirty = false;
24149
24249
  } catch {
24150
24250
  }
@@ -24156,7 +24256,7 @@ var init_scan_cache = __esm({
24156
24256
  // src/cli/commands/scan-judge.ts
24157
24257
  import fs34 from "fs";
24158
24258
  import os26 from "os";
24159
- import path35 from "path";
24259
+ import path36 from "path";
24160
24260
  function findingDedupKey(f) {
24161
24261
  return `${f.source.rule.name ?? "<unnamed>"}|${preview(f.input)}|${f.project}|${f.repo ?? ""}`;
24162
24262
  }
@@ -24234,6 +24334,7 @@ var init_scan_judge = __esm({
24234
24334
  init_roles();
24235
24335
  init_scan_cache();
24236
24336
  init_dlp();
24337
+ init_scan();
24237
24338
  PREVIEW_LEN = 120;
24238
24339
  HistoryJudge = class {
24239
24340
  configs = /* @__PURE__ */ new Map();
@@ -24254,20 +24355,20 @@ var init_scan_judge = __esm({
24254
24355
  * file, so worktrees count), home-relative; undefined when none. Cached per
24255
24356
  * directory: history repeats the same few cwds thousands of times. */
24256
24357
  repoOf(cwd) {
24257
- if (!cwd || !path35.isAbsolute(cwd)) return void 0;
24358
+ if (!cwd || !path36.isAbsolute(cwd)) return void 0;
24258
24359
  if (this.repos.has(cwd)) return this.repos.get(cwd);
24259
24360
  let dir2 = cwd;
24260
24361
  let found;
24261
24362
  for (; ; ) {
24262
24363
  try {
24263
- if (fs34.existsSync(path35.join(dir2, ".git"))) {
24364
+ if (fs34.existsSync(path36.join(dir2, ".git"))) {
24264
24365
  found = dir2;
24265
24366
  break;
24266
24367
  }
24267
24368
  } catch {
24268
24369
  break;
24269
24370
  }
24270
- const parent = path35.dirname(dir2);
24371
+ const parent = path36.dirname(dir2);
24271
24372
  if (parent === dir2) break;
24272
24373
  dir2 = parent;
24273
24374
  }
@@ -24346,7 +24447,7 @@ var init_scan_judge = __esm({
24346
24447
  agent: call.agent,
24347
24448
  ...where
24348
24449
  };
24349
- const k = `${finding.patternName}|${finding.redactedSample}|${call.project}`;
24450
+ const k = `${finding.patternName}|${finding.redactedSample}|${call.project}|${dayKey(call.timestamp || void 0)}`;
24350
24451
  if (!dedup.dlpKeys.has(k)) {
24351
24452
  dedup.dlpKeys.add(k);
24352
24453
  result.dlpFindings.push(finding);
@@ -24356,7 +24457,7 @@ var init_scan_judge = __esm({
24356
24457
  const m = scanArgs(call.scrubbed);
24357
24458
  if (m) {
24358
24459
  dlpMatched = true;
24359
- const k = `${m.patternName}|${m.redactedSample}|${call.project}`;
24460
+ const k = `${m.patternName}|${m.redactedSample}|${call.project}|${dayKey(call.timestamp || void 0)}`;
24360
24461
  if (!dedup.dlpKeys.has(k)) {
24361
24462
  dedup.dlpKeys.add(k);
24362
24463
  result.dlpFindings.push({
@@ -24374,7 +24475,7 @@ var init_scan_judge = __esm({
24374
24475
  }
24375
24476
  if (verdict.decision !== "allow" && verdict.layer !== "dlp" && verdict.layer !== "canary") {
24376
24477
  const source = ruleSourceFor(verdict, call.toolName, config);
24377
- const k = `${source.rule.name ?? "<unnamed>"}|${preview(call.input)}|${call.project}|${repo ?? ""}`;
24478
+ const k = `${source.rule.name ?? "<unnamed>"}|${preview(call.input)}|${call.project}|${repo ?? ""}|${dayKey(call.timestamp || void 0)}`;
24378
24479
  if (!dedup.findingsKeys.has(k)) {
24379
24480
  dedup.findingsKeys.add(k);
24380
24481
  result.findings.push({
@@ -24434,7 +24535,7 @@ var init_scan_summary = __esm({
24434
24535
 
24435
24536
  // src/agent-wiring.ts
24436
24537
  import fs35 from "fs";
24437
- import path36 from "path";
24538
+ import path37 from "path";
24438
24539
  import * as yaml from "yaml";
24439
24540
  import { parse as parseToml } from "smol-toml";
24440
24541
  var exists, ck, lg, AGENT_SPECS;
@@ -24456,55 +24557,55 @@ var init_agent_wiring = __esm({
24456
24557
  id: "claude",
24457
24558
  label: "Claude Code",
24458
24559
  setupCommand: "node9 agents add claude",
24459
- hookFile: (h) => path36.join(h, ".claude", "settings.json"),
24560
+ hookFile: (h) => path37.join(h, ".claude", "settings.json"),
24460
24561
  hookFormat: "matcher",
24461
24562
  // UserPromptSubmit is prompt DLP. setup.ts has written it for Claude since
24462
24563
  // that shipped; the spec must name it too, or status/doctor never show the
24463
24564
  // row and heal (which repairs via setupAgent) has no signal it is missing.
24464
24565
  hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
24465
- mcpFile: (h) => path36.join(h, ".claude.json"),
24466
- present: (h) => exists(path36.join(h, ".claude", "settings.json")) || exists(path36.join(h, ".claude.json"))
24566
+ mcpFile: (h) => path37.join(h, ".claude.json"),
24567
+ present: (h) => exists(path37.join(h, ".claude", "settings.json")) || exists(path37.join(h, ".claude.json"))
24467
24568
  },
24468
24569
  {
24469
24570
  id: "gemini",
24470
24571
  label: "Gemini CLI",
24471
24572
  setupCommand: "node9 agents add gemini",
24472
- hookFile: (h) => path36.join(h, ".gemini", "settings.json"),
24573
+ hookFile: (h) => path37.join(h, ".gemini", "settings.json"),
24473
24574
  hookFormat: "matcher",
24474
24575
  hookEvents: [ck("BeforeTool"), lg("AfterTool")],
24475
- mcpFile: (h) => path36.join(h, ".gemini", "settings.json"),
24476
- present: (h) => exists(path36.join(h, ".gemini", "settings.json"))
24576
+ mcpFile: (h) => path37.join(h, ".gemini", "settings.json"),
24577
+ present: (h) => exists(path37.join(h, ".gemini", "settings.json"))
24477
24578
  },
24478
24579
  {
24479
24580
  id: "codex",
24480
24581
  label: "Codex",
24481
24582
  setupCommand: "node9 agents add codex",
24482
- hookFile: (h) => path36.join(h, ".codex", "hooks.json"),
24583
+ hookFile: (h) => path37.join(h, ".codex", "hooks.json"),
24483
24584
  hookFormat: "matcher",
24484
24585
  hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
24485
- mcpFile: (h) => path36.join(h, ".codex", "config.toml"),
24586
+ mcpFile: (h) => path37.join(h, ".codex", "config.toml"),
24486
24587
  mcpFormat: "toml",
24487
- present: (h) => exists(path36.join(h, ".codex"))
24588
+ present: (h) => exists(path37.join(h, ".codex"))
24488
24589
  },
24489
24590
  {
24490
24591
  id: "antigravity",
24491
24592
  label: "Antigravity",
24492
24593
  setupCommand: "node9 agents add antigravity",
24493
- hookFile: (h) => path36.join(h, ".gemini", "config", "hooks.json"),
24594
+ hookFile: (h) => path37.join(h, ".gemini", "config", "hooks.json"),
24494
24595
  hookFormat: "matcher",
24495
24596
  hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
24496
- mcpFile: (h) => path36.join(h, ".gemini", "config", "mcp_config.json"),
24497
- present: (h) => exists(path36.join(h, ".gemini", "config", "hooks.json")) || exists(path36.join(h, ".gemini", "antigravity-cli")) || exists(path36.join(h, ".gemini", "antigravity-ide"))
24597
+ mcpFile: (h) => path37.join(h, ".gemini", "config", "mcp_config.json"),
24598
+ present: (h) => exists(path37.join(h, ".gemini", "config", "hooks.json")) || exists(path37.join(h, ".gemini", "antigravity-cli")) || exists(path37.join(h, ".gemini", "antigravity-ide"))
24498
24599
  },
24499
24600
  {
24500
24601
  id: "copilot",
24501
24602
  label: "GitHub Copilot",
24502
24603
  setupCommand: "node9 agents add copilot",
24503
- hookFile: (h) => path36.join(h, ".copilot", "hooks", "node9.json"),
24604
+ hookFile: (h) => path37.join(h, ".copilot", "hooks", "node9.json"),
24504
24605
  hookFormat: "flat",
24505
24606
  hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
24506
- mcpFile: (h) => path36.join(h, ".copilot", "mcp-config.json"),
24507
- present: (h) => exists(path36.join(h, ".copilot"))
24607
+ mcpFile: (h) => path37.join(h, ".copilot", "mcp-config.json"),
24608
+ present: (h) => exists(path37.join(h, ".copilot"))
24508
24609
  },
24509
24610
  {
24510
24611
  id: "cursor",
@@ -24513,8 +24614,8 @@ var init_agent_wiring = __esm({
24513
24614
  // MCP-only — no hook file (see note above).
24514
24615
  hookFormat: "flat",
24515
24616
  hookEvents: [],
24516
- mcpFile: (h) => path36.join(h, ".cursor", "mcp.json"),
24517
- present: (h) => exists(path36.join(h, ".cursor", "mcp.json"))
24617
+ mcpFile: (h) => path37.join(h, ".cursor", "mcp.json"),
24618
+ present: (h) => exists(path37.join(h, ".cursor", "mcp.json"))
24518
24619
  },
24519
24620
  {
24520
24621
  id: "hermes",
@@ -24535,8 +24636,8 @@ var init_agent_wiring = __esm({
24535
24636
  setupCommand: "node9 agents add opencode",
24536
24637
  hookFormat: "flat",
24537
24638
  hookEvents: [],
24538
- shimFile: (h) => path36.join(opencodeConfigDir(h), "plugins", "node9.js"),
24539
- present: (h) => exists(opencodeConfigDir(h)) || exists(path36.join(opencodeConfigDir(h), "plugins", "node9.js"))
24639
+ shimFile: (h) => path37.join(opencodeConfigDir(h), "plugins", "node9.js"),
24640
+ present: (h) => exists(opencodeConfigDir(h)) || exists(path37.join(opencodeConfigDir(h), "plugins", "node9.js"))
24540
24641
  },
24541
24642
  {
24542
24643
  id: "pi",
@@ -24544,8 +24645,8 @@ var init_agent_wiring = __esm({
24544
24645
  setupCommand: "node9 agents add pi",
24545
24646
  hookFormat: "flat",
24546
24647
  hookEvents: [],
24547
- shimFile: (h) => path36.join(h, ".pi", "agent", "extensions", "node9.js"),
24548
- present: (h) => exists(path36.join(h, ".pi", "agent")) || exists(path36.join(h, ".pi", "agent", "extensions", "node9.js"))
24648
+ shimFile: (h) => path37.join(h, ".pi", "agent", "extensions", "node9.js"),
24649
+ present: (h) => exists(path37.join(h, ".pi", "agent")) || exists(path37.join(h, ".pi", "agent", "extensions", "node9.js"))
24549
24650
  }
24550
24651
  ];
24551
24652
  }
@@ -24596,14 +24697,14 @@ var init_codex_trust = __esm({
24596
24697
  });
24597
24698
 
24598
24699
  // src/daemon/hook-baseline.ts
24599
- import path37 from "path";
24700
+ import path38 from "path";
24600
24701
  import os27 from "os";
24601
24702
  var BASELINE_FILE, NOTIFIED_FILE;
24602
24703
  var init_hook_baseline = __esm({
24603
24704
  "src/daemon/hook-baseline.ts"() {
24604
24705
  "use strict";
24605
- BASELINE_FILE = path37.join(os27.homedir(), ".node9", "hooks-baseline.json");
24606
- NOTIFIED_FILE = path37.join(os27.homedir(), ".node9", "hook-heal-notified.json");
24706
+ BASELINE_FILE = path38.join(os27.homedir(), ".node9", "hooks-baseline.json");
24707
+ NOTIFIED_FILE = path38.join(os27.homedir(), ".node9", "hook-heal-notified.json");
24607
24708
  }
24608
24709
  });
24609
24710
 
@@ -24622,7 +24723,7 @@ var init_setup_pi_shim = __esm({
24622
24723
  });
24623
24724
 
24624
24725
  // src/setup.ts
24625
- import path38 from "path";
24726
+ import path39 from "path";
24626
24727
  import os28 from "os";
24627
24728
  import chalk3 from "chalk";
24628
24729
  import { confirm as rawConfirm } from "@inquirer/prompts";
@@ -24630,16 +24731,16 @@ import { parse as parseToml5, stringify as stringifyToml2 } from "smol-toml";
24630
24731
  import * as yaml2 from "yaml";
24631
24732
  function opencodeConfigDir(home = os28.homedir()) {
24632
24733
  const xdg = process.env.XDG_CONFIG_HOME;
24633
- const base2 = xdg && path38.isAbsolute(xdg) ? xdg : path38.join(home, ".config");
24634
- return path38.join(base2, "opencode");
24734
+ const base2 = xdg && path39.isAbsolute(xdg) ? xdg : path39.join(home, ".config");
24735
+ return path39.join(base2, "opencode");
24635
24736
  }
24636
24737
  function hermesHomeDir(homeDir = os28.homedir()) {
24637
24738
  const env = process.env.HERMES_HOME?.trim();
24638
- if (env && path38.isAbsolute(env)) return env;
24639
- return path38.join(homeDir, ".hermes");
24739
+ if (env && path39.isAbsolute(env)) return env;
24740
+ return path39.join(homeDir, ".hermes");
24640
24741
  }
24641
24742
  function hermesConfigPath(homeDir = os28.homedir()) {
24642
- return path38.join(hermesHomeDir(homeDir), HERMES_CONFIG_FILENAME);
24743
+ return path39.join(hermesHomeDir(homeDir), HERMES_CONFIG_FILENAME);
24643
24744
  }
24644
24745
  var HERMES_CONFIG_FILENAME;
24645
24746
  var init_setup = __esm({
@@ -24685,15 +24786,66 @@ var init_scan_history = __esm({
24685
24786
  // src/cli/commands/scan.ts
24686
24787
  import chalk5 from "chalk";
24687
24788
  import fs36 from "fs";
24688
- import path39 from "path";
24789
+ import path40 from "path";
24689
24790
  import os29 from "os";
24690
24791
  import stringWidth2 from "string-width";
24691
- function countMcpCall(result, toolName) {
24792
+ function dayKey(ts) {
24793
+ if (!ts) return "unknown";
24794
+ if (typeof ts === "string" && /^\d{4}-\d{2}-\d{2}$/.test(ts)) return ts;
24795
+ const d = typeof ts === "string" ? new Date(ts) : ts;
24796
+ if (Number.isNaN(d.getTime())) return "unknown";
24797
+ const m = String(d.getMonth() + 1).padStart(2, "0");
24798
+ const day = String(d.getDate()).padStart(2, "0");
24799
+ return `${d.getFullYear()}-${m}-${day}`;
24800
+ }
24801
+ function dayOf(result, ts, event = true) {
24802
+ result.days ??= {};
24803
+ const key = dayKey(ts);
24804
+ let d = result.days[key];
24805
+ if (!d) {
24806
+ d = {
24807
+ events: 0,
24808
+ toolCalls: 0,
24809
+ bashCalls: 0,
24810
+ mcpCalls: 0,
24811
+ mcpByServer: {},
24812
+ costUSD: 0,
24813
+ unknownInputs: {}
24814
+ };
24815
+ result.days[key] = d;
24816
+ }
24817
+ if (!event) return d;
24818
+ d.events++;
24819
+ if (ts) {
24820
+ if (!d.first || ts < d.first) d.first = ts;
24821
+ if (!d.last || ts > d.last) d.last = ts;
24822
+ }
24823
+ return d;
24824
+ }
24825
+ function addUnknown(day, unknown) {
24826
+ for (const u of unknown) day.unknownInputs[u] = (day.unknownInputs[u] ?? 0) + 1;
24827
+ }
24828
+ function countMcpCall(result, toolName, day) {
24692
24829
  const m = /^mcp__(.+?)__/.exec(toolName);
24693
24830
  if (!m) return;
24694
24831
  result.mcpCalls = (result.mcpCalls ?? 0) + 1;
24695
24832
  result.mcpByServer ??= {};
24696
24833
  result.mcpByServer[m[1]] = (result.mcpByServer[m[1]] ?? 0) + 1;
24834
+ if (day) {
24835
+ day.mcpCalls++;
24836
+ day.mcpByServer[m[1]] = (day.mcpByServer[m[1]] ?? 0) + 1;
24837
+ }
24838
+ }
24839
+ function judgeFromFor(startDate) {
24840
+ return startDate ? new Date(startDate.getTime() - STATE_LOOKBACK_MS) : null;
24841
+ }
24842
+ function beforeJudged(judgeFrom, ts) {
24843
+ if (!judgeFrom || !ts) return false;
24844
+ const t = Date.parse(ts);
24845
+ return Number.isFinite(t) && t < judgeFrom.getTime();
24846
+ }
24847
+ function fileMayHoldWindow(stamp, startDate) {
24848
+ return !startDate || !stamp || stamp.mtimeMs >= startDate.getTime();
24697
24849
  }
24698
24850
  function claudeModelPrice2(model) {
24699
24851
  const t = pricingFor(model);
@@ -24722,8 +24874,8 @@ function preview2(input, max) {
24722
24874
  function emptyScanDedup() {
24723
24875
  return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
24724
24876
  }
24725
- function dlpKey(patternName, redactedSample, projLabel) {
24726
- return `${patternName}|${redactedSample}|${projLabel}`;
24877
+ function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
24878
+ return `${patternName}|${redactedSample}|${projLabel}|${dayKey(timestamp2 || void 0)}`;
24727
24879
  }
24728
24880
  function safeCanaryScanValues() {
24729
24881
  try {
@@ -24742,7 +24894,7 @@ function recordCanaries(scanned, toolName, timestamp2, projLabel, sessionId, age
24742
24894
  const v = pool.find((x) => x.id === hit.id);
24743
24895
  if (v) matched.push(v.value);
24744
24896
  pool = pool.filter((x) => x.id !== hit.id);
24745
- const key = `canary|${hit.id}|${sessionId}|${agent}`;
24897
+ const key = `canary|${hit.id}|${sessionId}|${agent}|${dayKey(timestamp2 || void 0)}`;
24746
24898
  const existing = dedup.canaryIndex.get(key);
24747
24899
  if (existing) {
24748
24900
  existing.count++;
@@ -24843,13 +24995,12 @@ function geminiChatFiles(chatsDir) {
24843
24995
  return true;
24844
24996
  });
24845
24997
  }
24846
- async function processClaudeFile(file2, projPath, projLabel, judge, startDate, result, dedup, canaryVals, onLine) {
24847
- let sessionCounted = false;
24998
+ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, result, dedup, canaryVals, onLine) {
24848
24999
  const sessionId = sessionIdOf(file2);
24849
25000
  const session = { sessionId, costUSD: 0, toolCalls: 0 };
24850
25001
  let raw;
24851
25002
  try {
24852
- raw = fs36.readFileSync(path39.join(projPath, file2), "utf-8");
25003
+ raw = fs36.readFileSync(path40.join(projPath, file2), "utf-8");
24853
25004
  } catch {
24854
25005
  return false;
24855
25006
  }
@@ -24868,13 +25019,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24868
25019
  continue;
24869
25020
  }
24870
25021
  if (entry.type !== "assistant" && entry.type !== "user") continue;
24871
- if (startDate && entry.timestamp) {
24872
- if (new Date(entry.timestamp) < startDate) continue;
24873
- }
24874
- if (!sessionCounted) {
24875
- sessionCounted = true;
24876
- result.sessions++;
24877
- }
25022
+ if (beforeJudged(judgeFrom, entry.timestamp)) continue;
25023
+ const day = dayOf(result, entry.timestamp);
25024
+ if (result.sessions === 0) result.sessions = 1;
24878
25025
  if (entry.timestamp) {
24879
25026
  if (!result.firstDate || entry.timestamp < result.firstDate)
24880
25027
  result.firstDate = entry.timestamp;
@@ -24898,7 +25045,12 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24898
25045
  );
24899
25046
  const dlpMatch = scanArgs(scrubDecoys({ text }, decoysHere0));
24900
25047
  if (dlpMatch) {
24901
- const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
25048
+ const k = dlpKey(
25049
+ dlpMatch.patternName,
25050
+ dlpMatch.redactedSample,
25051
+ projLabel,
25052
+ entry.timestamp ?? ""
25053
+ );
24902
25054
  if (!dedup.dlpKeys.has(k)) {
24903
25055
  dedup.dlpKeys.add(k);
24904
25056
  result.dlpFindings.push({
@@ -24917,7 +25069,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24917
25069
  if (block.type !== "tool_result") continue;
24918
25070
  const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
24919
25071
  if (filePath) {
24920
- const ext = path39.extname(filePath).toLowerCase();
25072
+ const ext = path40.extname(filePath).toLowerCase();
24921
25073
  if (CODE_EXTENSIONS.has(ext)) continue;
24922
25074
  }
24923
25075
  const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
@@ -24941,7 +25093,12 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24941
25093
  if (dlpMatch) {
24942
25094
  if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
24943
25095
  judge.noteOutputSecret(sessionId, dlpMatch.patternName, entry.timestamp ?? "");
24944
- const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
25096
+ const k = dlpKey(
25097
+ dlpMatch.patternName,
25098
+ dlpMatch.redactedSample,
25099
+ projLabel,
25100
+ entry.timestamp ?? ""
25101
+ );
24945
25102
  if (!dedup.dlpKeys.has(k)) {
24946
25103
  dedup.dlpKeys.add(k);
24947
25104
  result.dlpFindings.push({
@@ -24967,6 +25124,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24967
25124
  const rowCost = (usage2.input_tokens ?? 0) * p.i + (usage2.output_tokens ?? 0) * p.o + (usage2.cache_creation_input_tokens ?? 0) * p.cw + (usage2.cache_read_input_tokens ?? 0) * p.cr;
24968
25125
  result.totalCostUSD += rowCost;
24969
25126
  session.costUSD += rowCost;
25127
+ day.costUSD += rowCost;
24970
25128
  }
24971
25129
  }
24972
25130
  const content = entry.message?.content;
@@ -24975,8 +25133,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24975
25133
  if (block.type !== "tool_use") continue;
24976
25134
  result.totalToolCalls++;
24977
25135
  session.toolCalls++;
25136
+ day.toolCalls++;
24978
25137
  const toolName = block.name ?? "";
24979
- countMcpCall(result, toolName);
25138
+ countMcpCall(result, toolName, day);
24980
25139
  const toolNameLower = toolName.toLowerCase();
24981
25140
  const input = block.input ?? {};
24982
25141
  if (block.id && typeof input.file_path === "string") {
@@ -24986,6 +25145,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
24986
25145
  sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
24987
25146
  if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
24988
25147
  result.bashCalls++;
25148
+ day.bashCalls++;
24989
25149
  }
24990
25150
  if (firstEditTs === null && (toolNameLower === "edit" || toolNameLower === "write" || toolNameLower === "write_file" || toolNameLower === "edit_file" || toolNameLower === "multiedit")) {
24991
25151
  firstEditTs = entry.timestamp ?? null;
@@ -25017,6 +25177,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
25017
25177
  result,
25018
25178
  dedup
25019
25179
  );
25180
+ addUnknown(day, judged.verdict.unknown);
25020
25181
  if (judged.dlpMatched && firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
25021
25182
  }
25022
25183
  }
@@ -25027,8 +25188,8 @@ async function processClaudeFile(file2, projPath, projLabel, judge, startDate, r
25027
25188
  result.perSession.push(session);
25028
25189
  return true;
25029
25190
  }
25030
- async function processClaudeProjectAsync(proj, projectsDir, judge, cache4, window, startDate, result, dedup, canaryVals, onProgress, onLine) {
25031
- const projPath = path39.join(projectsDir, proj);
25191
+ async function processClaudeProjectAsync(proj, projectsDir, judge, cache4, startDate, result, dedup, canaryVals, onProgress, onLine) {
25192
+ const projPath = path40.join(projectsDir, proj);
25032
25193
  try {
25033
25194
  if (!fs36.statSync(projPath).isDirectory()) return;
25034
25195
  } catch {
@@ -25042,13 +25203,15 @@ async function processClaudeProjectAsync(proj, projectsDir, judge, cache4, windo
25042
25203
  return;
25043
25204
  }
25044
25205
  for (const file2 of files) {
25045
- const filePath = path39.join(projPath, file2);
25206
+ const filePath = path40.join(projPath, file2);
25207
+ const stamp = fileStamp(filePath);
25208
+ if (!fileMayHoldWindow(stamp, startDate)) continue;
25046
25209
  result.filesScanned++;
25047
25210
  onProgress?.(result.filesScanned);
25048
- const stamp = fileStamp(filePath);
25049
- const cached = stamp ? cache4.get(filePath, stamp, window, judge) : null;
25211
+ const judgeFrom = judgeFromFor(startDate);
25212
+ const cached = stamp ? cache4.get(filePath, stamp, judge, judgeFrom) : null;
25050
25213
  if (cached) {
25051
- mergeFileScan(result, cached, dedup);
25214
+ mergeFileScan(result, cached, dedup, startDate);
25052
25215
  continue;
25053
25216
  }
25054
25217
  const fileResult = emptyScan();
@@ -25058,14 +25221,15 @@ async function processClaudeProjectAsync(proj, projectsDir, judge, cache4, windo
25058
25221
  projPath,
25059
25222
  projLabel,
25060
25223
  judge,
25061
- startDate,
25224
+ judgeFrom,
25062
25225
  fileResult,
25063
25226
  fileDedup,
25064
25227
  canaryVals,
25065
25228
  onLine
25066
25229
  );
25067
- if (read2 && stamp) cache4.put(filePath, stamp, window, "claude", fileResult, judge);
25068
- mergeFileScan(result, fileResult, dedup);
25230
+ if (read2 && stamp) cache4.put(filePath, stamp, "claude", fileResult, judge, judgeFrom);
25231
+ mergeFileScan(result, fileResult, dedup, startDate);
25232
+ await yieldTick();
25069
25233
  await yieldTick();
25070
25234
  }
25071
25235
  }
@@ -25104,40 +25268,71 @@ function openScanCache(judge, opts) {
25104
25268
  enabled: opts.cache !== false && process.env.NODE9_SCAN_CACHE !== "0"
25105
25269
  });
25106
25270
  }
25107
- function mergeFileScan(result, file2, dedup) {
25108
- result.sessions += file2.sessions;
25109
- result.totalToolCalls += file2.totalToolCalls;
25110
- result.bashCalls += file2.bashCalls;
25111
- result.totalCostUSD += file2.totalCostUSD;
25112
- result.sessionsWithEarlySecrets += file2.sessionsWithEarlySecrets;
25113
- result.perSession.push(...file2.perSession);
25114
- result.loopFindings.push(...file2.loopFindings);
25115
- if (file2.firstDate && (!result.firstDate || file2.firstDate < result.firstDate))
25116
- result.firstDate = file2.firstDate;
25117
- if (file2.lastDate && (!result.lastDate || file2.lastDate > result.lastDate))
25118
- result.lastDate = file2.lastDate;
25119
- if (file2.mcpCalls) result.mcpCalls = (result.mcpCalls ?? 0) + file2.mcpCalls;
25120
- for (const [server, n] of Object.entries(file2.mcpByServer ?? {})) {
25121
- result.mcpByServer ??= {};
25122
- result.mcpByServer[server] = (result.mcpByServer[server] ?? 0) + n;
25123
- }
25124
- for (const [what, n] of Object.entries(file2.unknownInputs ?? {})) {
25125
- result.unknownInputs ??= {};
25126
- result.unknownInputs[what] = (result.unknownInputs[what] ?? 0) + n;
25271
+ function mergeFileScan(result, file2, dedup, startDate = null) {
25272
+ const startKey = startDate ? dayKey(startDate) : null;
25273
+ const dayIn = (key) => !startKey || key === "unknown" || key >= startKey;
25274
+ const tsIn = (ts) => dayIn(dayKey(ts || void 0));
25275
+ const days = Object.entries(file2.days ?? {}).filter(([k]) => dayIn(k));
25276
+ const inWindow = !startKey || days.some(([, d]) => d.events > 0 || d.toolCalls > 0);
25277
+ if (!startKey) {
25278
+ result.sessions += file2.sessions;
25279
+ result.totalToolCalls += file2.totalToolCalls;
25280
+ result.bashCalls += file2.bashCalls;
25281
+ result.totalCostUSD += file2.totalCostUSD;
25282
+ result.sessionsWithEarlySecrets += file2.sessionsWithEarlySecrets;
25283
+ if (file2.firstDate && (!result.firstDate || file2.firstDate < result.firstDate))
25284
+ result.firstDate = file2.firstDate;
25285
+ if (file2.lastDate && (!result.lastDate || file2.lastDate > result.lastDate))
25286
+ result.lastDate = file2.lastDate;
25287
+ if (file2.mcpCalls) result.mcpCalls = (result.mcpCalls ?? 0) + file2.mcpCalls;
25288
+ for (const [server, n] of Object.entries(file2.mcpByServer ?? {})) {
25289
+ result.mcpByServer ??= {};
25290
+ result.mcpByServer[server] = (result.mcpByServer[server] ?? 0) + n;
25291
+ }
25292
+ for (const [what, n] of Object.entries(file2.unknownInputs ?? {})) {
25293
+ result.unknownInputs ??= {};
25294
+ result.unknownInputs[what] = (result.unknownInputs[what] ?? 0) + n;
25295
+ }
25296
+ } else {
25297
+ if (inWindow) {
25298
+ result.sessions += Math.min(1, file2.sessions);
25299
+ result.sessionsWithEarlySecrets += file2.sessionsWithEarlySecrets;
25300
+ }
25301
+ for (const [, d] of days) {
25302
+ result.totalToolCalls += d.toolCalls;
25303
+ result.bashCalls += d.bashCalls;
25304
+ result.totalCostUSD += d.costUSD;
25305
+ if (d.mcpCalls) result.mcpCalls = (result.mcpCalls ?? 0) + d.mcpCalls;
25306
+ for (const [server, n] of Object.entries(d.mcpByServer)) {
25307
+ result.mcpByServer ??= {};
25308
+ result.mcpByServer[server] = (result.mcpByServer[server] ?? 0) + n;
25309
+ }
25310
+ for (const [what, n] of Object.entries(d.unknownInputs)) {
25311
+ result.unknownInputs ??= {};
25312
+ result.unknownInputs[what] = (result.unknownInputs[what] ?? 0) + n;
25313
+ }
25314
+ if (d.first && (!result.firstDate || d.first < result.firstDate)) result.firstDate = d.first;
25315
+ if (d.last && (!result.lastDate || d.last > result.lastDate)) result.lastDate = d.last;
25316
+ }
25127
25317
  }
25318
+ if (inWindow) result.perSession.push(...file2.perSession);
25319
+ result.loopFindings.push(...file2.loopFindings.filter((l) => tsIn(l.timestamp)));
25128
25320
  for (const f of file2.findings) {
25321
+ if (!tsIn(f.timestamp)) continue;
25129
25322
  const k = findingDedupKey(f);
25130
25323
  if (dedup.findingsKeys.has(k)) continue;
25131
25324
  dedup.findingsKeys.add(k);
25132
25325
  result.findings.push(f);
25133
25326
  }
25134
25327
  for (const d of file2.dlpFindings) {
25328
+ if (!tsIn(d.timestamp)) continue;
25135
25329
  const k = dlpDedupKey(d);
25136
25330
  if (dedup.dlpKeys.has(k)) continue;
25137
25331
  dedup.dlpKeys.add(k);
25138
25332
  result.dlpFindings.push(d);
25139
25333
  }
25140
25334
  for (const c of file2.canaryFindings) {
25335
+ if (!tsIn(c.timestamp)) continue;
25141
25336
  const k = `canary|${c.canaryId}|${c.sessionId}|${c.agent}`;
25142
25337
  const existing = dedup.canaryIndex.get(k);
25143
25338
  if (existing) {
@@ -25151,7 +25346,7 @@ function mergeFileScan(result, file2, dedup) {
25151
25346
  }
25152
25347
  }
25153
25348
  async function scanClaudeHistoryAsync(startDate, onProgress, onLine, opts = {}) {
25154
- const projectsDir = path39.join(os29.homedir(), ".claude", "projects");
25349
+ const projectsDir = path40.join(os29.homedir(), ".claude", "projects");
25155
25350
  const result = emptyClaudeScan();
25156
25351
  if (!fs36.existsSync(projectsDir)) return result;
25157
25352
  let projDirs;
@@ -25162,7 +25357,6 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine, opts = {})
25162
25357
  }
25163
25358
  const judge = new HistoryJudge();
25164
25359
  const cache4 = openScanCache(judge, opts);
25165
- const window = windowKey(startDate);
25166
25360
  const dedup = emptyScanDedup();
25167
25361
  const canaryVals = safeCanaryScanValues();
25168
25362
  for (const proj of projDirs) {
@@ -25171,7 +25365,6 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine, opts = {})
25171
25365
  projectsDir,
25172
25366
  judge,
25173
25367
  cache4,
25174
- window,
25175
25368
  startDate,
25176
25369
  result,
25177
25370
  dedup,
@@ -25180,13 +25373,13 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine, opts = {})
25180
25373
  onLine
25181
25374
  );
25182
25375
  }
25183
- cache4.flush();
25376
+ cache4.close();
25184
25377
  result.policy = judge.policy(result.unknownInputs ?? {});
25185
25378
  return result;
25186
25379
  }
25187
25380
  async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25188
25381
  const canaryVals = safeCanaryScanValues();
25189
- const tmpDir = path39.join(os29.homedir(), ".gemini", "tmp");
25382
+ const tmpDir = path40.join(os29.homedir(), ".gemini", "tmp");
25190
25383
  const result = {
25191
25384
  filesScanned: 0,
25192
25385
  sessions: 0,
@@ -25212,9 +25405,8 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25212
25405
  }
25213
25406
  const judge = new HistoryJudge();
25214
25407
  const cache4 = openScanCache(judge, opts);
25215
- const window = windowKey(startDate);
25216
25408
  for (const slug2 of slugDirs) {
25217
- const slugPath = path39.join(tmpDir, slug2);
25409
+ const slugPath = path40.join(tmpDir, slug2);
25218
25410
  try {
25219
25411
  if (!fs36.statSync(slugPath).isDirectory()) continue;
25220
25412
  } catch {
@@ -25223,11 +25415,11 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25223
25415
  let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
25224
25416
  try {
25225
25417
  projLabel = stripTerminalEscapes(
25226
- fs36.readFileSync(path39.join(slugPath, ".project_root"), "utf-8").trim()
25418
+ fs36.readFileSync(path40.join(slugPath, ".project_root"), "utf-8").trim()
25227
25419
  ).replace(os29.homedir(), "~").slice(0, 40);
25228
25420
  } catch {
25229
25421
  }
25230
- const chatsDir = path39.join(slugPath, "chats");
25422
+ const chatsDir = path40.join(slugPath, "chats");
25231
25423
  if (!fs36.existsSync(chatsDir)) continue;
25232
25424
  let chatFiles;
25233
25425
  try {
@@ -25237,20 +25429,21 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25237
25429
  }
25238
25430
  for (const chatFile of chatFiles) {
25239
25431
  const sessionId = chatFile.replace(/\.jsonl?$/, "");
25432
+ const filePath = path40.join(chatsDir, chatFile);
25433
+ const stamp = fileStamp(filePath);
25434
+ if (!fileMayHoldWindow(stamp, startDate)) continue;
25240
25435
  result.filesScanned++;
25241
25436
  onProgress?.(result.filesScanned);
25242
- const filePath = path39.join(chatsDir, chatFile);
25243
- const stamp = fileStamp(filePath);
25244
- const cached = stamp ? cache4.get(filePath, stamp, window, judge) : null;
25437
+ const cached = stamp ? cache4.get(filePath, stamp, judge, judgeFromFor(startDate)) : null;
25245
25438
  if (cached) {
25246
- mergeFileScan(result, cached, dedup);
25439
+ mergeFileScan(result, cached, dedup, startDate);
25247
25440
  continue;
25248
25441
  }
25249
25442
  const fileResult = emptyScan();
25250
25443
  const fileDedup = emptyScanDedup();
25251
25444
  let raw;
25252
25445
  try {
25253
- raw = fs36.readFileSync(path39.join(chatsDir, chatFile), "utf-8");
25446
+ raw = fs36.readFileSync(path40.join(chatsDir, chatFile), "utf-8");
25254
25447
  } catch {
25255
25448
  continue;
25256
25449
  }
@@ -25272,7 +25465,6 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25272
25465
  } catch {
25273
25466
  continue;
25274
25467
  }
25275
- let sessionCounted = false;
25276
25468
  for (const msg of session.messages ?? []) {
25277
25469
  onLine?.();
25278
25470
  if (msg.type === "user") {
@@ -25292,7 +25484,12 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25292
25484
  );
25293
25485
  const dlpMatch = scanArgs(scrubDecoys({ text }, decoysHere3));
25294
25486
  if (dlpMatch) {
25295
- const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
25487
+ const k = dlpKey(
25488
+ dlpMatch.patternName,
25489
+ dlpMatch.redactedSample,
25490
+ projLabel,
25491
+ msg.timestamp ?? ""
25492
+ );
25296
25493
  if (!fileDedup.dlpKeys.has(k)) {
25297
25494
  fileDedup.dlpKeys.add(k);
25298
25495
  fileResult.dlpFindings.push({
@@ -25310,11 +25507,9 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25310
25507
  continue;
25311
25508
  }
25312
25509
  if (msg.type !== "gemini") continue;
25313
- if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
25314
- if (!sessionCounted) {
25315
- sessionCounted = true;
25316
- fileResult.sessions++;
25317
- }
25510
+ if (beforeJudged(judgeFromFor(startDate), msg.timestamp)) continue;
25511
+ const day = dayOf(fileResult, msg.timestamp);
25512
+ if (fileResult.sessions === 0) fileResult.sessions = 1;
25318
25513
  if (msg.timestamp) {
25319
25514
  if (!fileResult.firstDate || msg.timestamp < fileResult.firstDate)
25320
25515
  fileResult.firstDate = msg.timestamp;
@@ -25327,18 +25522,22 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25327
25522
  const p = geminiModelPrice(model);
25328
25523
  if (p) {
25329
25524
  const nonCached = Math.max(0, tokens.input - tokens.cached);
25330
- fileResult.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
25525
+ const cost = nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
25526
+ fileResult.totalCostUSD += cost;
25527
+ day.costUSD += cost;
25331
25528
  }
25332
25529
  }
25333
25530
  for (const tc of msg.toolCalls ?? []) {
25334
25531
  fileResult.totalToolCalls++;
25532
+ day.toolCalls++;
25335
25533
  const toolName = tc.name ?? "";
25336
- countMcpCall(fileResult, toolName);
25534
+ countMcpCall(fileResult, toolName, day);
25337
25535
  const toolNameLower = toolName.toLowerCase();
25338
25536
  const input = tc.args ?? {};
25339
25537
  sessionCalls.push({ toolName, input, timestamp: msg.timestamp ?? "" });
25340
25538
  if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
25341
25539
  fileResult.bashCalls++;
25540
+ day.bashCalls++;
25342
25541
  }
25343
25542
  const rawCmd = String(input.command ?? "").trimStart();
25344
25543
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
@@ -25367,15 +25566,16 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
25367
25566
  fileResult,
25368
25567
  fileDedup
25369
25568
  );
25370
- void judged;
25569
+ addUnknown(day, judged.verdict.unknown);
25371
25570
  }
25372
25571
  }
25373
25572
  fileResult.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "gemini"));
25374
- if (stamp) cache4.put(filePath, stamp, window, "gemini", fileResult, judge);
25375
- mergeFileScan(result, fileResult, dedup);
25573
+ if (stamp) cache4.put(filePath, stamp, "gemini", fileResult, judge, judgeFromFor(startDate));
25574
+ mergeFileScan(result, fileResult, dedup, startDate);
25575
+ await yieldTick();
25376
25576
  }
25377
25577
  }
25378
- cache4.flush();
25578
+ cache4.close();
25379
25579
  result.policy = judge.policy(result.unknownInputs ?? {});
25380
25580
  return result;
25381
25581
  }
@@ -25400,14 +25600,14 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25400
25600
  const jsonlFiles = listCodexSessionFiles();
25401
25601
  const judge = new HistoryJudge();
25402
25602
  const cache4 = openScanCache(judge, opts);
25403
- const window = windowKey(startDate);
25404
25603
  for (const filePath of jsonlFiles) {
25604
+ const stamp = fileStamp(filePath);
25605
+ if (!fileMayHoldWindow(stamp, startDate)) continue;
25405
25606
  result.filesScanned++;
25406
25607
  onProgress?.(result.filesScanned);
25407
- const stamp = fileStamp(filePath);
25408
- const cached = stamp ? cache4.get(filePath, stamp, window, judge) : null;
25608
+ const cached = stamp ? cache4.get(filePath, stamp, judge, judgeFromFor(startDate)) : null;
25409
25609
  if (cached) {
25410
- mergeFileScan(result, cached, dedup);
25610
+ mergeFileScan(result, cached, dedup, startDate);
25411
25611
  continue;
25412
25612
  }
25413
25613
  const fileResult = emptyScan();
@@ -25422,7 +25622,6 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25422
25622
  let startTime = "";
25423
25623
  let projLabel = "";
25424
25624
  let sessionCwd;
25425
- let sessionCounted = false;
25426
25625
  const sessionCalls = [];
25427
25626
  for (const line of lines) {
25428
25627
  if (!line.trim()) continue;
@@ -25439,7 +25638,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25439
25638
  startTime = String(payload["timestamp"] ?? "");
25440
25639
  const cwd = String(payload["cwd"] ?? "");
25441
25640
  projLabel = stripTerminalEscapes(cwd.replace(os29.homedir(), "~")).slice(0, 40);
25442
- if (path39.isAbsolute(cwd)) sessionCwd = cwd;
25641
+ if (path40.isAbsolute(cwd)) sessionCwd = cwd;
25443
25642
  continue;
25444
25643
  }
25445
25644
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
@@ -25458,7 +25657,12 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25458
25657
  );
25459
25658
  const dlpMatch = scanArgs(scrubDecoys({ text }, decoysHere9));
25460
25659
  if (dlpMatch) {
25461
- const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
25660
+ const k = dlpKey(
25661
+ dlpMatch.patternName,
25662
+ dlpMatch.redactedSample,
25663
+ projLabel,
25664
+ entry.timestamp ?? startTime
25665
+ );
25462
25666
  if (!fileDedup.dlpKeys.has(k)) {
25463
25667
  fileDedup.dlpKeys.add(k);
25464
25668
  fileResult.dlpFindings.push({
@@ -25478,18 +25682,18 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25478
25682
  if (entry.type !== "response_item") continue;
25479
25683
  if (payload["type"] !== "function_call") continue;
25480
25684
  const ts = startTime;
25481
- if (startDate && ts && new Date(ts) < startDate) continue;
25482
- if (!sessionCounted) {
25483
- sessionCounted = true;
25484
- fileResult.sessions++;
25485
- }
25685
+ const callTs = typeof entry.timestamp === "string" ? entry.timestamp : ts;
25686
+ if (beforeJudged(judgeFromFor(startDate), callTs || void 0)) continue;
25687
+ const day = dayOf(fileResult, callTs || void 0);
25688
+ if (fileResult.sessions === 0) fileResult.sessions = 1;
25486
25689
  if (ts) {
25487
25690
  if (!fileResult.firstDate || ts < fileResult.firstDate) fileResult.firstDate = ts;
25488
25691
  if (!fileResult.lastDate || ts > fileResult.lastDate) fileResult.lastDate = ts;
25489
25692
  }
25490
25693
  fileResult.totalToolCalls++;
25694
+ day.toolCalls++;
25491
25695
  const toolName = String(payload["name"] ?? "");
25492
- countMcpCall(fileResult, toolName);
25696
+ countMcpCall(fileResult, toolName, day);
25493
25697
  const toolNameLower = toolName.toLowerCase();
25494
25698
  let input = {};
25495
25699
  try {
@@ -25502,6 +25706,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25502
25706
  sessionCalls.push({ toolName, input, timestamp: ts });
25503
25707
  if (toolNameLower === "exec_command" || toolNameLower === "shell") {
25504
25708
  fileResult.bashCalls++;
25709
+ day.bashCalls++;
25505
25710
  }
25506
25711
  const rawCmd = String(input["command"] ?? "").trimStart();
25507
25712
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
@@ -25525,7 +25730,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25525
25730
  // The window filter uses the session start (ts); the gate's clock
25526
25731
  // (loop window, taint expiry) is the call's own time when the line
25527
25732
  // carries one.
25528
- timestamp: typeof entry.timestamp === "string" ? entry.timestamp : ts,
25733
+ timestamp: callTs,
25529
25734
  sessionId,
25530
25735
  project: projLabel,
25531
25736
  cwd: sessionCwd
@@ -25533,20 +25738,23 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
25533
25738
  fileResult,
25534
25739
  fileDedup
25535
25740
  );
25536
- void judged;
25741
+ addUnknown(day, judged.verdict.unknown);
25537
25742
  }
25538
- for (const event of codexUsageInWindow(parseCodexUsage(lines), startDate)) {
25743
+ for (const event of parseCodexUsage(lines).events) {
25744
+ if (beforeJudged(judgeFromFor(startDate), event.timestamp || void 0)) continue;
25539
25745
  fileResult.totalCostUSD += event.costUSD;
25746
+ dayOf(fileResult, event.timestamp || void 0, false).costUSD += event.costUSD;
25540
25747
  }
25541
25748
  fileResult.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
25542
- if (stamp) cache4.put(filePath, stamp, window, "codex", fileResult, judge);
25543
- mergeFileScan(result, fileResult, dedup);
25749
+ if (stamp) cache4.put(filePath, stamp, "codex", fileResult, judge, judgeFromFor(startDate));
25750
+ mergeFileScan(result, fileResult, dedup, startDate);
25751
+ await yieldTick();
25544
25752
  }
25545
- cache4.flush();
25753
+ cache4.close();
25546
25754
  result.policy = judge.policy(result.unknownInputs ?? {});
25547
25755
  return result;
25548
25756
  }
25549
- var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
25757
+ var toolInspectionMap, STATE_LOOKBACK_MS, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
25550
25758
  var init_scan = __esm({
25551
25759
  "src/cli/commands/scan.ts"() {
25552
25760
  "use strict";
@@ -25576,6 +25784,7 @@ var init_scan = __esm({
25576
25784
  init_scan_history();
25577
25785
  init_safe_text();
25578
25786
  toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
25787
+ STATE_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
25579
25788
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
25580
25789
  ".ts",
25581
25790
  ".tsx",
@@ -25629,10 +25838,10 @@ var init_scan = __esm({
25629
25838
  // src/tui/dashboard/data.ts
25630
25839
  import fs37 from "fs";
25631
25840
  import os30 from "os";
25632
- import path40 from "path";
25841
+ import path41 from "path";
25633
25842
  import http2 from "http";
25634
25843
  function auditLogPath() {
25635
- return path40.join(os30.homedir(), ".node9", "audit.log");
25844
+ return path41.join(os30.homedir(), ".node9", "audit.log");
25636
25845
  }
25637
25846
  function readAuditEntriesAsync(chunkSize = 1e3, customPath) {
25638
25847
  return new Promise((resolve) => {
@@ -25779,9 +25988,9 @@ function shortenPath(p) {
25779
25988
  return p.startsWith(home) ? p.replace(home, "~") : p;
25780
25989
  }
25781
25990
  async function loadReportAuditAsync(period) {
25782
- const claudeProjectsDir = path40.join(os30.homedir(), ".claude", "projects");
25991
+ const claudeProjectsDir = path41.join(os30.homedir(), ".claude", "projects");
25783
25992
  const codexSessionsDir2 = codexSessionsDir();
25784
- const geminiTmpDir = path40.join(os30.homedir(), ".gemini", "tmp");
25993
+ const geminiTmpDir = path41.join(os30.homedir(), ".gemini", "tmp");
25785
25994
  const { start, end } = getDateRange(period, /* @__PURE__ */ new Date());
25786
25995
  const entries = await readAuditEntriesAsync();
25787
25996
  void ensurePricingLoaded();
@@ -26893,8 +27102,8 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-run
26893
27102
  function TopToolsProjects({ audit }) {
26894
27103
  const data = audit?.data;
26895
27104
  const tools = data ? [...data.toolMap.entries()].sort(([, a], [, b]) => b.calls - a.calls).slice(0, ROW_LIMIT) : [];
26896
- const projects = data ? [...data.cost.byProject.entries()].map(([path41, r]) => ({
26897
- name: basenameOf(path41),
27105
+ const projects = data ? [...data.cost.byProject.entries()].map(([path42, r]) => ({
27106
+ name: basenameOf(path42),
26898
27107
  cost: r.cost,
26899
27108
  tokens: r.inputTokens + r.outputTokens
26900
27109
  })).sort((a, b) => b.cost - a.cost).slice(0, ROW_LIMIT) : [];
@@ -27331,8 +27540,8 @@ function pickTopLoopFile(loops) {
27331
27540
  map.set(k, (map.get(k) ?? 0) + (l.count ?? 0));
27332
27541
  }
27333
27542
  if (map.size === 0) return void 0;
27334
- const [path41, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
27335
- return { path: path41, count };
27543
+ const [path42, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
27544
+ return { path: path42, count };
27336
27545
  }
27337
27546
  var EMPTY_FILTERED_SCAN;
27338
27547
  var init_derive = __esm({