pi-smart-compact 8.0.8 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/ARCHITECTURE.md +90 -70
  2. package/CHANGELOG.md +45 -2
  3. package/README.md +52 -30
  4. package/SECURITY.md +1 -1
  5. package/dist/app/native-continuity-bridge.d.ts.map +1 -1
  6. package/dist/app/pending-slot.d.ts.map +1 -1
  7. package/dist/app/preflight.d.ts +2 -1
  8. package/dist/app/preflight.d.ts.map +1 -1
  9. package/dist/app/run-context.d.ts +10 -10
  10. package/dist/app/run-context.d.ts.map +1 -1
  11. package/dist/app/run-smart-compact.d.ts +18 -2
  12. package/dist/app/run-smart-compact.d.ts.map +1 -1
  13. package/dist/app/session-run-lock.d.ts.map +1 -1
  14. package/dist/app/smart-compact-input.d.ts +23 -0
  15. package/dist/app/smart-compact-input.d.ts.map +1 -0
  16. package/dist/app/steps/extract.d.ts.map +1 -1
  17. package/dist/app/steps/metrics.d.ts +4 -4
  18. package/dist/app/steps/metrics.d.ts.map +1 -1
  19. package/dist/app/steps/persist.d.ts +2 -3
  20. package/dist/app/steps/persist.d.ts.map +1 -1
  21. package/dist/app/steps/prepare.d.ts +4 -6
  22. package/dist/app/steps/prepare.d.ts.map +1 -1
  23. package/dist/app/steps/recover.d.ts +1 -1
  24. package/dist/app/steps/recover.d.ts.map +1 -1
  25. package/dist/app/steps/state.d.ts.map +1 -1
  26. package/dist/app/steps/synthesize.d.ts.map +1 -1
  27. package/dist/app/steps/verify.d.ts.map +1 -1
  28. package/dist/app/steps/window.d.ts +5 -1
  29. package/dist/app/steps/window.d.ts.map +1 -1
  30. package/dist/constants.d.ts +25 -4
  31. package/dist/constants.d.ts.map +1 -1
  32. package/dist/domain/scrub.d.ts.map +1 -1
  33. package/dist/domain/summary-parse.d.ts +1 -1
  34. package/dist/domain/summary-parse.d.ts.map +1 -1
  35. package/dist/domain/summary-schema.d.ts +1 -1
  36. package/dist/domain/summary-schema.d.ts.map +1 -1
  37. package/dist/domain/tool-semantics.d.ts +15 -9
  38. package/dist/domain/tool-semantics.d.ts.map +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +3105 -1389
  41. package/dist/infra/ai-messages.d.ts +6 -0
  42. package/dist/infra/ai-messages.d.ts.map +1 -1
  43. package/dist/infra/context-graph.d.ts.map +1 -1
  44. package/dist/infra/fs.d.ts +13 -15
  45. package/dist/infra/fs.d.ts.map +1 -1
  46. package/dist/infra/git.d.ts +5 -11
  47. package/dist/infra/git.d.ts.map +1 -1
  48. package/dist/infra/llm-client.d.ts +2 -0
  49. package/dist/infra/llm-client.d.ts.map +1 -1
  50. package/dist/infra/paths.d.ts +4 -2
  51. package/dist/infra/paths.d.ts.map +1 -1
  52. package/dist/infra/session-identity.d.ts +11 -0
  53. package/dist/infra/session-identity.d.ts.map +1 -1
  54. package/dist/infra/synthesis-cache.d.ts.map +1 -1
  55. package/dist/phases/explore.d.ts +2 -1
  56. package/dist/phases/explore.d.ts.map +1 -1
  57. package/dist/phases/synthesize.d.ts +4 -1
  58. package/dist/phases/synthesize.d.ts.map +1 -1
  59. package/dist/phases/verify.d.ts +10 -3
  60. package/dist/phases/verify.d.ts.map +1 -1
  61. package/dist/provider-eval.js +278 -27
  62. package/dist/provider-scenario-eval.js +422 -49
  63. package/dist/telemetry-report.js +278 -27
  64. package/dist/types.d.ts +51 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/ui/overlays.d.ts +2 -1
  67. package/dist/ui/overlays.d.ts.map +1 -1
  68. package/dist/utils/backups.d.ts +31 -0
  69. package/dist/utils/backups.d.ts.map +1 -0
  70. package/dist/utils/cache.d.ts +3 -2
  71. package/dist/utils/cache.d.ts.map +1 -1
  72. package/dist/utils/damage.d.ts.map +1 -1
  73. package/dist/utils/extraction.d.ts +8 -1
  74. package/dist/utils/extraction.d.ts.map +1 -1
  75. package/dist/utils/fingerprint.d.ts +5 -1
  76. package/dist/utils/fingerprint.d.ts.map +1 -1
  77. package/dist/utils/helpers.d.ts +15 -37
  78. package/dist/utils/helpers.d.ts.map +1 -1
  79. package/dist/utils/pruning.d.ts.map +1 -1
  80. package/dist/utils/session-log.d.ts +10 -18
  81. package/dist/utils/session-log.d.ts.map +1 -1
  82. package/dist/utils/state.d.ts +2 -2
  83. package/dist/utils/state.d.ts.map +1 -1
  84. package/dist/utils/tokens.d.ts +2 -0
  85. package/dist/utils/tokens.d.ts.map +1 -1
  86. package/dist/utils/type-guards.d.ts +2 -0
  87. package/dist/utils/type-guards.d.ts.map +1 -1
  88. package/package.json +2 -2
@@ -156,7 +156,7 @@ function formatProviderEvaluation(report) {
156
156
  import fs2 from "fs";
157
157
 
158
158
  // src/constants.ts
159
- var VERSION = "8.0.8";
159
+ var VERSION = "9.1.0";
160
160
  var COMPACT_SYSTEM_PREFIX = "You are an expert conversation summarizer for a coding agent. " + "Produce structured markdown summaries. " + "Follow output format exactly. " + "Use EXACT names \u2014 never paraphrase code identifiers. " + "Trust deterministic extraction data over intuition.";
161
161
  var PROFILES = {
162
162
  light: {
@@ -198,7 +198,7 @@ var DEFAULT_CONFIG = {
198
198
  backupEnabled: true,
199
199
  backupDir: "",
200
200
  minContextPercent: 60,
201
- requireApproval: false,
201
+ requireApproval: true,
202
202
  scrubSecrets: true,
203
203
  scrubPii: false,
204
204
  maxLlmCalls: 8,
@@ -238,6 +238,8 @@ Output EXACTLY this format:
238
238
  - **[Decision]**: [Rationale]
239
239
  ` + `## Files Modified
240
240
  - [Verified list from deterministic extraction]
241
+ ` + `## Files Deleted
242
+ - [Verified deleted paths from deterministic extraction]
241
243
  ` + `## Files Read
242
244
  - [Verified list from deterministic extraction]
243
245
  ` + `## Next Steps
@@ -261,6 +263,7 @@ Rules for Accuracy:
261
263
  ` + `**Summary**: [2-4 sentences: what happened, errors, code changes with paths]
262
264
  ` + `**Decisions**: [comma-separated, or "None"]
263
265
  ` + `**Modified**: [comma-separated paths, or "None"]
266
+ ` + `**Deleted**: [comma-separated paths, or "None"]
264
267
  ` + `**Read**: [comma-separated paths, or "None"]
265
268
  `;
266
269
  var ASSEMBLY_PROMPT_PREFIX = `Merge these topic summaries into ONE coherent summary.
@@ -292,6 +295,8 @@ var ASSEMBLY_PROMPT_PREFIX = `Merge these topic summaries into ONE coherent summ
292
295
  - **[Decision]**: [Rationale]
293
296
  ` + `## Files Modified
294
297
  - [Verified deterministic list]
298
+ ` + `## Files Deleted
299
+ - [Verified deterministic deleted paths]
295
300
  ` + `## Files Read
296
301
  - [Verified deterministic list]
297
302
  ` + `## Next Steps
@@ -307,6 +312,14 @@ var FIVE_MINUTES_MS = 5 * 60 * 1000;
307
312
  var ONE_HOUR_MS = 60 * 60 * 1000;
308
313
  var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
309
314
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
315
+ var ID_PREFIX = {
316
+ PROJECT: "proj-",
317
+ COMPACT_SESSION: "sc-",
318
+ MULTI_TOOL_USE_SYNTHETIC: "mtu_",
319
+ OPEN_LOOP: "loop-",
320
+ DECISION: "decision-",
321
+ ERROR: "error-"
322
+ };
310
323
  var TUNING = {
311
324
  EMA_PREV: 0.7,
312
325
  EMA_SAMPLE: 0.3,
@@ -346,6 +359,7 @@ var TRUNC = {
346
359
  BACKUP_PREVIEW_LINES: 5,
347
360
  FINGERPRINT_SEG: 2
348
361
  };
362
+ var LIKELY_ERROR_RE = /(?:command not found|no such file|permission denied|syntax error|cannot find|module not found|compilation error|build failed|test failed|^FAIL\b|ERROR:)/i;
349
363
  var METRICS_BUFFER_MAX = 200;
350
364
  var RUNTIME_LOG_MAX_BYTES = 5 * 1024 * 1024;
351
365
  var EXPLORER_SYSTEM_PROMPT = `You are a conversation analyst. You have deterministic extraction data and can query the raw conversation using tools.
@@ -421,6 +435,15 @@ function calibrationKey(provider, model) {
421
435
  }
422
436
 
423
437
  // src/utils/type-guards.ts
438
+ function isRecord(value) {
439
+ return typeof value === "object" && value !== null;
440
+ }
441
+ function isTextBlock(c) {
442
+ return isRecord(c) && c.type === "text" && typeof c.text === "string";
443
+ }
444
+ function isToolCallBlock(c) {
445
+ return isRecord(c) && c.type === "toolCall" && typeof c.name === "string" && isRecord(c.arguments);
446
+ }
424
447
  var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
425
448
  var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
426
449
  var KNOWN_MODES = new Set(["balanced", "aggressive", "fast", "thorough"]);
@@ -479,6 +502,74 @@ function isKnownPathReference(ref, knownPaths) {
479
502
  });
480
503
  }
481
504
 
505
+ // src/domain/tool-semantics.ts
506
+ var PATH_KEYS = [
507
+ "path",
508
+ "file_path",
509
+ "filePath",
510
+ "filename",
511
+ "file",
512
+ "target_file",
513
+ "file_uri",
514
+ "absolute_path"
515
+ ];
516
+ var PAYLOAD_KEYS = [
517
+ "content",
518
+ "newText",
519
+ "oldText",
520
+ "new_str",
521
+ "old_str",
522
+ "new_string",
523
+ "old_string",
524
+ "edits",
525
+ "patch",
526
+ "replacement"
527
+ ];
528
+ var COMMAND_KEYS = ["command", "cmd", "script"];
529
+ function hasPresent(args, keys) {
530
+ return keys.some((k) => args[k] != null);
531
+ }
532
+ function extractToolPath(args) {
533
+ if (!args || typeof args !== "object")
534
+ return;
535
+ const a = args;
536
+ for (const k of PATH_KEYS) {
537
+ const v = a[k];
538
+ if (typeof v === "string" && v.length > 0)
539
+ return v;
540
+ }
541
+ return;
542
+ }
543
+ function normalizeToolName(name) {
544
+ if (typeof name !== "string")
545
+ return "";
546
+ return name.replace(/^functions[.:/_-]+/i, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
547
+ }
548
+ function nameHas(name, hints) {
549
+ const words = name.split("_");
550
+ return hints.some((hint) => words.includes(hint));
551
+ }
552
+ function classifyToolOperation(args, toolName) {
553
+ const a = args && typeof args === "object" ? args : {};
554
+ const name = normalizeToolName(toolName);
555
+ const hasPath = extractToolPath(a) !== undefined;
556
+ if (hasPath && hasPresent(a, PAYLOAD_KEYS))
557
+ return "mutate";
558
+ if (hasPresent(a, COMMAND_KEYS))
559
+ return "execute";
560
+ if (hasPath && hasPresent(a, ["text"]) && nameHas(name, ["write", "edit", "patch", "replace", "append", "create", "update", "insert"]))
561
+ return "mutate";
562
+ if (hasPath && nameHas(name, ["delete", "remove", "unlink"]))
563
+ return "delete";
564
+ if (hasPresent(a, ["pattern", "query", "glob"]) || nameHas(name, ["grep", "search", "find", "glob", "rg"]))
565
+ return "search";
566
+ if (nameHas(name, ["list", "ls", "tree"]))
567
+ return "list";
568
+ if (hasPath || nameHas(name, ["read"]))
569
+ return "read";
570
+ return "unknown";
571
+ }
572
+
482
573
  // src/utils/file-ref-detect.ts
483
574
  var CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|rs|py|go|java|rb|cs|cpp|c|h|hpp|swift|kt|scala|php|css|scss|html|json|yaml|yml|toml|md|mdx|sh|sql|tf|ini|env|lock|gradle|xml)$/i;
484
575
  var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
@@ -514,6 +605,8 @@ function classifyHeading(raw) {
514
605
  return "files-modified";
515
606
  if (text.includes("file") && (text.includes("read") || text.includes("viewed")))
516
607
  return "files-read";
608
+ if (text.includes("file") && (text.includes("delet") || text.includes("remov")))
609
+ return "files-deleted";
517
610
  if (text.includes("next step") || text === "next actions")
518
611
  return "next-steps";
519
612
  if (text.includes("critical context") || text === "important context")
@@ -547,6 +640,7 @@ function parseSummary(markdown) {
547
640
  let currentHeading = "";
548
641
  let currentKind = "unknown";
549
642
  let bodyLines = [];
643
+ let fence = null;
550
644
  let started = false;
551
645
  const flush = () => {
552
646
  if (!started)
@@ -560,16 +654,31 @@ function parseSummary(markdown) {
560
654
  sections.push({ kind: currentKind, heading: currentHeading.trim(), body });
561
655
  };
562
656
  for (const line of lines) {
563
- const m = line.match(HEADING_RE);
564
- if (m) {
565
- const kind = classifyHeading(m[2]);
566
- if (m[1].length <= 2 || kind !== "unknown") {
567
- flush();
568
- currentHeading = "## " + m[2].trim();
569
- currentKind = kind;
570
- bodyLines = [];
571
- started = true;
572
- continue;
657
+ const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
658
+ if (fenceMatch) {
659
+ const marker = fenceMatch[1][0];
660
+ const markerLength = fenceMatch[1].length;
661
+ if (!fence) {
662
+ fence = { marker, length: markerLength };
663
+ } else if (marker === fence.marker && markerLength >= fence.length && !fenceMatch[2].trim()) {
664
+ fence = null;
665
+ }
666
+ if (started)
667
+ bodyLines.push(line);
668
+ continue;
669
+ }
670
+ if (!fence) {
671
+ const heading = line.match(HEADING_RE);
672
+ if (heading) {
673
+ const kind = classifyHeading(heading[2]);
674
+ if (heading[1].length <= 2 || kind !== "unknown") {
675
+ flush();
676
+ currentHeading = "## " + heading[2].trim();
677
+ currentKind = kind;
678
+ bodyLines = [];
679
+ started = true;
680
+ continue;
681
+ }
573
682
  }
574
683
  }
575
684
  if (started)
@@ -584,12 +693,68 @@ function findSection(summary, kind) {
584
693
  }
585
694
 
586
695
  // src/utils/extraction.ts
696
+ function nestedToolCallId(wrapperId, messageIndex, toolIndex, nestedId) {
697
+ return typeof nestedId === "string" ? nestedId : wrapperId ? wrapperId + "_" + toolIndex : ID_PREFIX.MULTI_TOOL_USE_SYNTHETIC + messageIndex + "_" + toolIndex;
698
+ }
699
+ function flattenToolCallBlock(b) {
700
+ if (!isToolCallBlock(b))
701
+ return [];
702
+ if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
703
+ return b.arguments.tool_uses.map((u) => {
704
+ const recipient = u?.recipient_name ?? "";
705
+ return {
706
+ name: recipient.replace(/^functions\./, ""),
707
+ id: u?.id ?? undefined,
708
+ arguments: u?.parameters ?? {}
709
+ };
710
+ });
711
+ }
712
+ return [{ name: b.name, id: b.id, arguments: b.arguments }];
713
+ }
714
+ function extractText(content) {
715
+ if (typeof content === "string")
716
+ return content;
717
+ if (!Array.isArray(content))
718
+ return "";
719
+ return content.map((b) => {
720
+ if (typeof b === "string")
721
+ return b;
722
+ if (isTextBlock(b))
723
+ return b.text;
724
+ return "";
725
+ }).join("");
726
+ }
727
+ function buildToolCallIndex(msgs) {
728
+ const idx = new Map;
729
+ for (let i = 0;i < msgs.length; i++) {
730
+ const m = msgs[i];
731
+ if (m.role !== "assistant")
732
+ continue;
733
+ const blocks = Array.isArray(m.content) ? m.content : [];
734
+ for (const b of blocks) {
735
+ if (!isToolCallBlock(b))
736
+ continue;
737
+ if (b.id) {
738
+ idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
739
+ }
740
+ if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
741
+ const nested = flattenToolCallBlock(b);
742
+ for (let t = 0;t < nested.length; t++) {
743
+ const tool = nested[t];
744
+ const id = nestedToolCallId(b.id, i, t, tool.id);
745
+ idx.set(id, { name: tool.name, arguments: tool.arguments, msgIndex: i });
746
+ }
747
+ }
748
+ }
749
+ }
750
+ return idx;
751
+ }
587
752
  var CONSTRAINT_PATTERNS = [
588
753
  { re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: TUNING.CONFIDENCE_HIGH },
589
754
  { re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
590
755
  { re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i, cat: "preference", conf: TUNING.CONFIDENCE_LOW },
591
- { re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|asla|sak\u0131n|sak\u0131nha|bunu yapma|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu, cat: "requirement", conf: TUNING.CONFIDENCE_MEDIUM },
592
- { re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|asla\s+(?:kullanma|yapma|getirme))(?![A-Za-z0-9_])/iu, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
756
+ { re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|sak\u0131nha|asla(?:\s+(?:kullanma|yapma|getirme))?|bunu yapma)(?![A-Za-z0-9_])/iu, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
757
+ { re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu, cat: "requirement", conf: TUNING.CONFIDENCE_MEDIUM },
593
758
  { re: /(?<![A-Za-z0-9_])(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)(?![A-Za-z0-9_])/iu, cat: "preference", conf: TUNING.CONFIDENCE_LOW }
594
759
  ];
595
760
  function isDiagnosticConstraintText(text) {
@@ -656,7 +821,6 @@ function readJsonlTail(target, limit, maxBytes = 512 * 1024) {
656
821
  }
657
822
  return values.slice(-limit);
658
823
  }
659
- var scheduledTailTrims = new Set;
660
824
 
661
825
  // src/infra/clock.ts
662
826
  var systemClock = {
@@ -741,6 +905,35 @@ function assertSuccessful(message) {
741
905
  }
742
906
  return message;
743
907
  }
908
+ async function withProviderDeadline(opts, invoke) {
909
+ if (opts.signal?.aborted)
910
+ throw new Error("LLM request aborted before dispatch");
911
+ const controller = new AbortController;
912
+ const watchdogMs = resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs);
913
+ const abort = Promise.withResolvers();
914
+ const abortFromCaller = () => {
915
+ controller.abort(opts.signal?.reason);
916
+ abort.reject(new Error("LLM request aborted by caller"));
917
+ };
918
+ opts.signal?.addEventListener("abort", abortFromCaller, { once: true });
919
+ const timeout = Promise.withResolvers();
920
+ const timer = setTimeout(() => {
921
+ controller.abort("provider-watchdog");
922
+ timeout.reject(new Error("Provider watchdog stopped generation after " + watchdogMs + "ms"));
923
+ }, watchdogMs);
924
+ if (typeof timer === "object" && "unref" in timer)
925
+ timer.unref();
926
+ try {
927
+ return await Promise.race([
928
+ invoke({ ...opts, signal: controller.signal }),
929
+ abort.promise,
930
+ timeout.promise
931
+ ]);
932
+ } finally {
933
+ clearTimeout(timer);
934
+ opts.signal?.removeEventListener("abort", abortFromCaller);
935
+ }
936
+ }
744
937
  async function completeChatGptCodex(model, body, opts) {
745
938
  const controller = new AbortController;
746
939
  const watchdogMs = resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs);
@@ -784,10 +977,12 @@ async function completeChatGptCodex(model, body, opts) {
784
977
  var rawLlmClient = {
785
978
  complete: async (model, body, originalOpts) => {
786
979
  const opts = withCodexWireLimit(model, originalOpts);
787
- if (isChatGptCodex(model))
788
- return completeChatGptCodex(model, body, opts);
789
- const response = opts.reasoning === undefined ? await (await resolveComplete())(model, body, opts) : await (await resolveCompleteSimple())(model, body, opts);
790
- return assertSuccessful(response);
980
+ return withProviderDeadline(opts, async (bounded) => {
981
+ if (isChatGptCodex(model))
982
+ return completeChatGptCodex(model, body, bounded);
983
+ const response = bounded.reasoning === undefined ? await (await resolveComplete())(model, body, bounded) : await (await resolveCompleteSimple())(model, body, bounded);
984
+ return assertSuccessful(response);
985
+ });
791
986
  }
792
987
  };
793
988
  var defaultLlmClient = rawLlmClient;
@@ -803,14 +998,23 @@ import crypto from "crypto";
803
998
  var SECRET_PATTERNS = [
804
999
  { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
805
1000
  { kind: "aws-access-key", regex: /\bAKIA[0-9A-Z]{16}\b/g },
806
- { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g },
1001
+ { kind: "google-api-key", regex: /\bAIza[0-9A-Za-z_-]{30,}\b/g },
1002
+ { kind: "stripe-key", regex: /\b[rs]k_(?:live|test)_[0-9A-Za-z]{16,}\b/g },
1003
+ { kind: "gitlab-token", regex: /\bglpat-[0-9A-Za-z_-]{20,}\b/g },
1004
+ { kind: "npm-token", regex: /\bnpm_[0-9A-Za-z]{30,}\b/g },
1005
+ { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
807
1006
  { kind: "api-key", regex: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g },
808
- { kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g },
1007
+ { kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
809
1008
  { kind: "jwt", regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
810
- { kind: "bearer-token", regex: /\bBearer\s+[A-Za-z0-9._~+\/-]{24,}/gi, replacement: () => "Bearer [REDACTED:bearer-token]" },
1009
+ { kind: "bearer-token", regex: /\bBearer\s+[A-Za-z0-9._~+\/-]{12,}=*/gi, replacement: () => "Bearer [REDACTED:bearer-token]" },
1010
+ {
1011
+ kind: "connection-password",
1012
+ regex: /\b([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)[^@\s/]+(@)/gi,
1013
+ replacement: (prefix, suffix) => prefix + "[REDACTED:password]" + suffix
1014
+ },
811
1015
  {
812
1016
  kind: "credential",
813
- regex: /\b(api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret)\s*([:=])\s*["']?([A-Za-z0-9._~+\/-]{16,})["']?/gi,
1017
+ regex: /\b((?:[A-Za-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret(?:[_-]?(?:access)?[_-]?key)?|client[_-]?secret)(?:[_-][A-Za-z0-9]+)*)\s*([:=])\s*["']?([^\s"']{16,})["']?/gi,
814
1018
  replacement: (name, separator) => name + separator + "[REDACTED:credential]"
815
1019
  }
816
1020
  ];
@@ -838,6 +1042,42 @@ function mergeFindings(target, findings) {
838
1042
  for (const finding of findings)
839
1043
  target.set(finding.kind, (target.get(finding.kind) ?? 0) + finding.count);
840
1044
  }
1045
+ var SECRET_KEY_NAMES = {
1046
+ api_key: true,
1047
+ apikey: true,
1048
+ access_token: true,
1049
+ auth_token: true,
1050
+ authorization: true,
1051
+ password: true,
1052
+ passwd: true,
1053
+ secret: true,
1054
+ secret_key: true,
1055
+ secret_access_key: true,
1056
+ client_secret: true,
1057
+ private_key: true,
1058
+ database_url: true,
1059
+ connection_string: true,
1060
+ token: true,
1061
+ refresh_token: true,
1062
+ session_token: true,
1063
+ credential: true,
1064
+ credentials: true,
1065
+ cookie: true,
1066
+ set_cookie: true,
1067
+ otp: true,
1068
+ one_time_password: true,
1069
+ pin: true,
1070
+ passcode: true
1071
+ };
1072
+ function normalizeObjectKey(key) {
1073
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1074
+ }
1075
+ function isSecretBearingKey(key) {
1076
+ const normalized = normalizeObjectKey(key);
1077
+ if (SECRET_KEY_NAMES[normalized])
1078
+ return true;
1079
+ return /(?:^|_)(?:api_key|access_token|auth_token|password|passwd|secret_access_key|client_secret|private_key|refresh_token|session_token|one_time_password|passcode)(?:_|$)/.test(normalized);
1080
+ }
841
1081
 
842
1082
  class SecretScrubber {
843
1083
  secretsEnabled;
@@ -867,6 +1107,10 @@ class SecretScrubber {
867
1107
  scrubValue(input) {
868
1108
  const findings = new Map;
869
1109
  const seen = new WeakMap;
1110
+ const recordCredential = () => {
1111
+ findings.set("credential", (findings.get("credential") ?? 0) + 1);
1112
+ this.total++;
1113
+ };
870
1114
  const visit = (value2) => {
871
1115
  if (typeof value2 === "string") {
872
1116
  const result = this.scrubText(value2);
@@ -887,8 +1131,15 @@ class SecretScrubber {
887
1131
  }
888
1132
  const output = {};
889
1133
  seen.set(value2, output);
890
- for (const [key, item] of Object.entries(value2))
891
- output[key] = visit(item);
1134
+ for (const [key, item] of Object.entries(value2)) {
1135
+ const carriesSecret = typeof item === "string" ? item.length > 0 : item != null;
1136
+ if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
1137
+ output[key] = "[REDACTED:credential]";
1138
+ recordCredential();
1139
+ } else {
1140
+ output[key] = visit(item);
1141
+ }
1142
+ }
892
1143
  return output;
893
1144
  };
894
1145
  const value = visit(input);
@@ -1147,8 +1398,8 @@ function readMetricsLog(limit = 100) {
1147
1398
  const fd = fs2.openSync(logPath, "r");
1148
1399
  try {
1149
1400
  const buf = Buffer.alloc(wantBytes);
1150
- fs2.readSync(fd, buf, 0, wantBytes, startPos);
1151
- let text = buf.toString("utf8");
1401
+ const bytesRead = fs2.readSync(fd, buf, 0, wantBytes, startPos);
1402
+ let text = buf.subarray(0, bytesRead).toString("utf8");
1152
1403
  if (startPos > 0) {
1153
1404
  const nl = text.indexOf(`
1154
1405
  `);