claude-nomad 0.63.0 → 0.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.63.1](https://github.com/funkadelic/claude-nomad/compare/v0.63.0...v0.63.1) (2026-07-31)
4
+
5
+
6
+ ### Fixed
7
+
8
+ * **push:** correct gitleaks column offsets in recovery prompts ([#478](https://github.com/funkadelic/claude-nomad/issues/478)) ([e230993](https://github.com/funkadelic/claude-nomad/commit/e2309932456f5646694685dd0c3b6b6e1007059f))
9
+
3
10
  ## [0.63.0](https://github.com/funkadelic/claude-nomad/compare/v0.62.6...v0.63.0) (2026-07-31)
4
11
 
5
12
  ### What's new
package/dist/nomad.mjs CHANGED
@@ -5658,6 +5658,93 @@ function dropSessionFromStaged(sid, map) {
5658
5658
  init_push_gitleaks_scan();
5659
5659
  init_utils();
5660
5660
 
5661
+ // src/commands.push.recovery.span-align.ts
5662
+ var REDACTED_TOKEN = "REDACTED";
5663
+ var SHIFT_WINDOW = [0, -1, 1, -2, 2];
5664
+ var ENTROPY_TOLERANCE = 1e-4;
5665
+ function byteAt(buf, at) {
5666
+ return at >= 0 && at < buf.length ? buf[at] : void 0;
5667
+ }
5668
+ function isWordByte(byte) {
5669
+ if (byte === void 0) return false;
5670
+ return byte >= 48 && byte <= 57 || byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte === 95;
5671
+ }
5672
+ function splitsToken(buf, at) {
5673
+ return isWordByte(byteAt(buf, at - 1)) && isWordByte(byteAt(buf, at));
5674
+ }
5675
+ function shannonEntropy(text) {
5676
+ if (text.length === 0) return 0;
5677
+ const counts = /* @__PURE__ */ new Map();
5678
+ for (const ch of text) counts.set(ch, (counts.get(ch) ?? 0) + 1);
5679
+ let bits = 0;
5680
+ for (const n of counts.values()) {
5681
+ const p = n / text.length;
5682
+ bits -= p * Math.log2(p);
5683
+ }
5684
+ return bits;
5685
+ }
5686
+ function matchTemplate(finding) {
5687
+ if (finding.Match.length === 0) return null;
5688
+ const idx = finding.Match.indexOf(REDACTED_TOKEN);
5689
+ if (idx < 0) return { pre: "", post: "", redacted: false };
5690
+ return {
5691
+ pre: finding.Match.slice(0, idx),
5692
+ post: finding.Match.slice(idx + REDACTED_TOKEN.length),
5693
+ redacted: true
5694
+ };
5695
+ }
5696
+ function fitsTemplate(span, finding, template) {
5697
+ if (!template.redacted) return span === finding.Match;
5698
+ return span.startsWith(template.pre) && span.endsWith(template.post);
5699
+ }
5700
+ function candidateAt(finding, buf, template, shift) {
5701
+ const start = finding.StartColumn - 1 + shift;
5702
+ const end = start + (finding.EndColumn - finding.StartColumn + 1);
5703
+ if (start < 0 || end > buf.length) return null;
5704
+ const span = buf.subarray(start, end).toString("utf8");
5705
+ if (!fitsTemplate(span, finding, template)) return null;
5706
+ const valueStart = start + Buffer.byteLength(template.pre, "utf8");
5707
+ const valueEnd = end - Buffer.byteLength(template.post, "utf8");
5708
+ const value = buf.subarray(valueStart, valueEnd).toString("utf8");
5709
+ if (value.length === 0) return null;
5710
+ if (splitsToken(buf, start) || splitsToken(buf, end)) return null;
5711
+ return { start, end, valueStart, value, exact: template.redacted };
5712
+ }
5713
+ function locateSecretLiteral(finding, buf) {
5714
+ const secret = finding.Secret;
5715
+ if (typeof secret !== "string" || secret.length === 0 || secret === REDACTED_TOKEN) return null;
5716
+ const needle = Buffer.from(secret, "utf8");
5717
+ const hint = finding.StartColumn - 1;
5718
+ let best = null;
5719
+ for (let at = buf.indexOf(needle); at >= 0; at = buf.indexOf(needle, at + 1)) {
5720
+ if (best === null || Math.abs(at - hint) < Math.abs(best - hint)) best = at;
5721
+ }
5722
+ if (best === null) return null;
5723
+ return { start: best, end: best + needle.length, valueStart: best, value: secret, exact: true };
5724
+ }
5725
+ function breakTie(hits, finding) {
5726
+ const reported = finding.Entropy;
5727
+ if (typeof reported !== "number" || !Number.isFinite(reported) || reported <= 0) return null;
5728
+ const agreeing = hits.filter(
5729
+ (h2) => h2.exact && Math.abs(shannonEntropy(h2.value) - reported) <= ENTROPY_TOLERANCE
5730
+ );
5731
+ return agreeing.length === 1 ? agreeing[0] : null;
5732
+ }
5733
+ function alignFindingSpan(finding, buf) {
5734
+ const literal = locateSecretLiteral(finding, buf);
5735
+ if (literal !== null) return literal;
5736
+ if (typeof finding.EndLine === "number" && finding.EndLine > finding.StartLine) return null;
5737
+ const template = matchTemplate(finding);
5738
+ if (template === null) return null;
5739
+ const hits = [];
5740
+ for (const shift of SHIFT_WINDOW) {
5741
+ const candidate = candidateAt(finding, buf, template, shift);
5742
+ if (candidate !== null) hits.push(candidate);
5743
+ }
5744
+ if (hits.length === 1) return hits[0];
5745
+ return breakTie(hits, finding);
5746
+ }
5747
+
5661
5748
  // src/commands.push.recovery.secret-shape.ts
5662
5749
  var VALUE_HEAD = 4;
5663
5750
  var VALUE_TAIL = 4;
@@ -5665,7 +5752,7 @@ var MIN_ELIDE_LEN = 16;
5665
5752
  var FRAGMENT_SEP = "...";
5666
5753
  var NEAR_WINDOW = 40;
5667
5754
  var VALUE_PAD = 16;
5668
- var REDACTED_TOKEN = "REDACTED";
5755
+ var SIBLING_PAD = 2;
5669
5756
  var CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
5670
5757
  function classifyCharset(value) {
5671
5758
  if (/^[0-9a-fA-F]+$/.test(value)) return "hex";
@@ -5691,26 +5778,23 @@ function withoutValue(near, value) {
5691
5778
  const idx = near.indexOf(value);
5692
5779
  return idx < 0 ? near : emptyToNull(near.slice(0, idx));
5693
5780
  }
5694
- function alignRedactedMatch(finding, span, lead) {
5781
+ function templateLead(finding) {
5695
5782
  const idx = finding.Match.indexOf(REDACTED_TOKEN);
5696
- if (idx < 0) return null;
5697
- const pre = finding.Match.slice(0, idx);
5698
- const post = finding.Match.slice(idx + REDACTED_TOKEN.length);
5699
- if (span !== finding.Match && span.startsWith(pre) && span.endsWith(post) && span.length > pre.length + post.length) {
5700
- return {
5701
- value: span.slice(pre.length, span.length - post.length),
5702
- near: emptyToNull(lead + pre),
5703
- exact: true
5704
- };
5705
- }
5706
- return { value: null, near: emptyToNull(lead + pre), exact: false };
5783
+ return idx > 0 ? finding.Match.slice(0, idx) : null;
5784
+ }
5785
+ function siblingBounds(sibling, buf) {
5786
+ const aligned = alignFindingSpan(sibling, buf);
5787
+ if (aligned !== null) return { start: aligned.start, end: aligned.end };
5788
+ const reportedStart = sibling.StartColumn - 1;
5789
+ if (sibling.EndColumn <= reportedStart) return { start: 0, end: 0 };
5790
+ const start = Math.max(0, Math.min(reportedStart, buf.length) - SIBLING_PAD);
5791
+ return { start, end: Math.max(start, Math.min(sibling.EndColumn + SIBLING_PAD, buf.length)) };
5707
5792
  }
5708
5793
  function maskSiblingSpans(buf, siblings, ownStart, ownEnd) {
5709
5794
  if (siblings.length === 0) return buf;
5710
5795
  const copy = Buffer.from(buf);
5711
5796
  for (const sibling of siblings) {
5712
- const start = Math.max(1, Math.min(sibling.StartColumn, copy.length + 1)) - 1;
5713
- const end = Math.max(start, Math.min(sibling.EndColumn, copy.length));
5797
+ const { start, end } = siblingBounds(sibling, buf);
5714
5798
  const headEnd = Math.min(end, ownStart);
5715
5799
  if (start < headEnd) copy.fill(0, start, headEnd);
5716
5800
  const tailStart = Math.max(start, ownEnd);
@@ -5720,43 +5804,27 @@ function maskSiblingSpans(buf, siblings, ownStart, ownEnd) {
5720
5804
  }
5721
5805
  function resolveSecretContext(finding, readLine, siblings = []) {
5722
5806
  const resolved = resolveSpanContext(finding, readLine, siblings);
5723
- const ownStripped = withoutValue(resolved.near, resolved.value);
5724
- const multiLine = typeof finding.EndLine === "number" && finding.EndLine > finding.StartLine;
5725
5807
  return {
5726
5808
  value: resolved.value,
5727
- near: ownStripped,
5728
- exact: resolved.exact && !multiLine
5809
+ near: withoutValue(resolved.near, resolved.value),
5810
+ exact: resolved.exact
5729
5811
  };
5730
5812
  }
5813
+ function resolveWithoutLine(finding) {
5814
+ if (finding.Match.includes(REDACTED_TOKEN)) {
5815
+ return { value: null, near: templateLead(finding), exact: false };
5816
+ }
5817
+ return { value: emptyToNull(finding.Match), near: null, exact: false };
5818
+ }
5731
5819
  function resolveSpanContext(finding, readLine, siblings = []) {
5732
5820
  const raw = readLine(finding.File, finding.StartLine);
5733
- let span;
5734
- let lead;
5735
- if (raw !== null) {
5736
- const buf = Buffer.from(raw, "utf8");
5737
- const len = buf.length;
5738
- const startCol = Math.max(1, Math.min(finding.StartColumn, len + 1));
5739
- const endCol = Math.max(startCol, Math.min(finding.EndColumn, len));
5740
- span = buf.subarray(startCol - 1, endCol).toString("utf8");
5741
- lead = maskSiblingSpans(buf, siblings, startCol - 1, endCol).subarray(0, startCol - 1).toString("utf8");
5742
- } else {
5743
- span = finding.Match;
5744
- lead = "";
5745
- }
5746
- const aligned = alignRedactedMatch(finding, span, lead);
5747
- if (aligned !== null) return aligned;
5748
- if (typeof finding.Secret === "string" && finding.Secret.length > 0 && span.includes(finding.Secret)) {
5749
- return {
5750
- value: finding.Secret,
5751
- near: emptyToNull(lead + span.slice(0, span.indexOf(finding.Secret))),
5752
- exact: true
5753
- };
5754
- }
5755
- return {
5756
- value: span.length > 0 ? span : null,
5757
- near: emptyToNull(lead),
5758
- exact: false
5759
- };
5821
+ if (raw === null) return resolveWithoutLine(finding);
5822
+ const unresolved = { value: null, near: templateLead(finding), exact: false };
5823
+ const buf = Buffer.from(raw, "utf8");
5824
+ const aligned = alignFindingSpan(finding, buf);
5825
+ if (aligned === null) return unresolved;
5826
+ const near = maskSiblingSpans(buf, siblings, aligned.start, aligned.end).subarray(0, aligned.valueStart).toString("utf8");
5827
+ return { value: aligned.value, near: emptyToNull(near), exact: aligned.exact };
5760
5828
  }
5761
5829
  function formatNear(text) {
5762
5830
  const stripped = elideTokenRuns(text.replace(CONTROL_CHARS, ""));
@@ -9439,7 +9507,7 @@ function parseSyncArgs(argv) {
9439
9507
  // package.json
9440
9508
  var package_default = {
9441
9509
  name: "claude-nomad",
9442
- version: "0.63.0",
9510
+ version: "0.63.1",
9443
9511
  type: "module",
9444
9512
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
9445
9513
  keywords: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.63.0",
3
+ "version": "0.63.1",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [