@mrciphersmith/keryx 0.3.19 → 0.3.21

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 (3) hide show
  1. package/dist/cli.js +137 -24
  2. package/dist/core.js +101 -20
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9080,6 +9080,24 @@ var init_config = __esm(() => {
9080
9080
  });
9081
9081
 
9082
9082
  // src/security/detect/entropy.ts
9083
+ function isAdjacentLabel(content, start) {
9084
+ const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
9085
+ if (ADJACENT_LABEL.test(before)) {
9086
+ return true;
9087
+ }
9088
+ if (before.trim().length > 0) {
9089
+ return false;
9090
+ }
9091
+ const lineStart = content.lastIndexOf(`
9092
+ `, start - 1);
9093
+ if (lineStart < 0) {
9094
+ return false;
9095
+ }
9096
+ const prevLineStart = content.lastIndexOf(`
9097
+ `, lineStart - 1) + 1;
9098
+ const prevLine = content.slice(prevLineStart, lineStart);
9099
+ return CONTINUATION_LABEL_LINE_RE.test(prevLine);
9100
+ }
9083
9101
  function isHexBlob(value) {
9084
9102
  return HEX_BLOB.test(value) && /[a-f]/i.test(value);
9085
9103
  }
@@ -9091,8 +9109,24 @@ function lastLineOf(before) {
9091
9109
  `);
9092
9110
  return newline < 0 ? before : before.slice(newline + 1);
9093
9111
  }
9112
+ function isPlainCasedWord(letters) {
9113
+ return /^[a-z]+$/.test(letters) || /^[A-Z]+$/.test(letters) || /^[A-Z][a-z]*$/.test(letters);
9114
+ }
9094
9115
  function isSingleTransitionTag(segment) {
9095
- return /^[A-Za-z]+[0-9]+$/.test(segment) || /^[0-9]+[A-Za-z]+$/.test(segment);
9116
+ const letterThenDigit = /^([A-Za-z]+)([0-9]+)$/.exec(segment);
9117
+ if (letterThenDigit !== null) {
9118
+ const letters = letterThenDigit[1];
9119
+ return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
9120
+ }
9121
+ const digitThenLetter = /^([0-9]+)([A-Za-z]+)$/.exec(segment);
9122
+ if (digitThenLetter !== null) {
9123
+ const letters = digitThenLetter[2];
9124
+ return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
9125
+ }
9126
+ return false;
9127
+ }
9128
+ function isSlugHexTail(segment) {
9129
+ return SLUG_HEX_TAIL_RE.test(segment);
9096
9130
  }
9097
9131
  function isWordSlug(value) {
9098
9132
  const segments = value.split(/[-_]/);
@@ -9100,15 +9134,25 @@ function isWordSlug(value) {
9100
9134
  return false;
9101
9135
  }
9102
9136
  let words = 0;
9103
- for (const segment of segments) {
9104
- const isAlpha = /^[A-Za-z]+$/.test(segment);
9137
+ for (let i = 0;i < segments.length; i += 1) {
9138
+ const segment = segments[i];
9105
9139
  const isDigits = /^[0-9]+$/.test(segment);
9106
- if (!isAlpha && !isDigits && !isSingleTransitionTag(segment)) {
9107
- return false;
9140
+ if (isDigits) {
9141
+ continue;
9108
9142
  }
9109
- if (isAlpha && segment.length >= 3) {
9110
- words += 1;
9143
+ if (isPlainCasedWord(segment)) {
9144
+ if (segment.length >= 3) {
9145
+ words += 1;
9146
+ }
9147
+ continue;
9111
9148
  }
9149
+ if (isSingleTransitionTag(segment)) {
9150
+ continue;
9151
+ }
9152
+ if (i === segments.length - 1 && isSlugHexTail(segment)) {
9153
+ continue;
9154
+ }
9155
+ return false;
9112
9156
  }
9113
9157
  return words >= 1;
9114
9158
  }
@@ -9252,6 +9296,36 @@ function secretShapedCandidatesIn(text) {
9252
9296
  function looksSecretShapedIn(text) {
9253
9297
  return secretShapedCandidatesIn(text).length > 0;
9254
9298
  }
9299
+ function labelledPieceQualifies(piece) {
9300
+ if (isAllowShapedValue(piece)) {
9301
+ return { entropy: shannonEntropy(piece) };
9302
+ }
9303
+ if (isHexBlob(piece)) {
9304
+ return { entropy: shannonEntropy(piece) };
9305
+ }
9306
+ if (!/[0-9]/.test(piece) && !/[+=]/.test(piece)) {
9307
+ return null;
9308
+ }
9309
+ const withoutSeparators = piece.replace(/[-_.+]/g, "");
9310
+ const entropy = shannonEntropy(withoutSeparators.length > 0 ? withoutSeparators : piece);
9311
+ if (entropy >= 3.6) {
9312
+ return { entropy };
9313
+ }
9314
+ return null;
9315
+ }
9316
+ function firstQualifyingSlashPiece(head) {
9317
+ let offset = 0;
9318
+ for (const piece of head.split("/")) {
9319
+ if (piece.length > 0) {
9320
+ const found = labelledPieceQualifies(piece);
9321
+ if (found !== null) {
9322
+ return { offset, length: piece.length, entropy: found.entropy };
9323
+ }
9324
+ }
9325
+ offset += piece.length + 1;
9326
+ }
9327
+ return null;
9328
+ }
9255
9329
  function detectEntropy(content) {
9256
9330
  const matches = [];
9257
9331
  const urlSpans = findUrlSpans(content);
@@ -9274,9 +9348,9 @@ function detectEntropy(content) {
9274
9348
  if (urlSpans.some((u) => start < u.end && end > u.start)) {
9275
9349
  continue;
9276
9350
  }
9277
- const { qualifies, entropy } = bareShapeQualifies(value);
9278
- if (qualifies) {
9279
- matches.push(buildMatch(start, end, value, entropy));
9351
+ const found = labelledPieceQualifies(value);
9352
+ if (found !== null) {
9353
+ matches.push(buildMatch(start, end, value, found.entropy));
9280
9354
  }
9281
9355
  }
9282
9356
  TOKEN.lastIndex = 0;
@@ -9291,26 +9365,32 @@ function detectEntropy(content) {
9291
9365
  if (matches.some((existing) => start < existing.end && end > existing.start)) {
9292
9366
  continue;
9293
9367
  }
9294
- const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
9295
- const { qualifies, entropy } = bareShapeQualifies(head);
9296
- if (!qualifies) {
9368
+ if (!isAdjacentLabel(content, start)) {
9297
9369
  continue;
9298
9370
  }
9299
- const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
9300
- if (!SENSITIVE_LABEL.test(before)) {
9371
+ const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
9372
+ if (head.includes("/")) {
9373
+ const piece = firstQualifyingSlashPiece(head);
9374
+ if (piece === null) {
9375
+ continue;
9376
+ }
9377
+ const pieceStart = start + piece.offset;
9378
+ const pieceEnd = pieceStart + piece.length;
9379
+ matches.push(buildMatch(pieceStart, pieceEnd, content.slice(pieceStart, pieceEnd), piece.entropy));
9301
9380
  continue;
9302
9381
  }
9303
- if (isAllowShapedValue(head) && !ADJACENT_LABEL.test(before)) {
9382
+ const found = labelledPieceQualifies(head);
9383
+ if (found === null) {
9304
9384
  continue;
9305
9385
  }
9306
- matches.push(buildMatch(start, end, value, entropy));
9386
+ matches.push(buildMatch(start, end, value, found.entropy));
9307
9387
  }
9308
9388
  return matches.sort((a, b) => a.start - b.start);
9309
9389
  }
9310
- var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), SENSITIVE_LABEL, ADJACENT_LABEL, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
9390
+ var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), FILLER_WORDS = "is|was|set|to|the|value|now", ADJACENT_LABEL, CONTINUATION_LABEL_LINE_RE, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, TAG_MAX_LETTER_RUN = 8, SLUG_HEX_TAIL_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
9311
9391
  var init_entropy = __esm(() => {
9312
- SENSITIVE_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(${caseless(SENSITIVE_LABEL_WORDS)})(?:$|[^A-Za-z0-9]|(?=[A-Z]))`);
9313
- ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
9392
+ ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*` + `(?:\\s+(?:${caseless(FILLER_WORDS)})){0,2}` + `["']?\\s*[:=]?\\s*["']?$`);
9393
+ CONTINUATION_LABEL_LINE_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*\\\\\\s*$`);
9314
9394
  LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
9315
9395
  TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
9316
9396
  TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
@@ -9318,6 +9398,7 @@ var init_entropy = __esm(() => {
9318
9398
  FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
9319
9399
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9320
9400
  INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
9401
+ SLUG_HEX_TAIL_RE = /^[0-9a-f]{10,16}$/i;
9321
9402
  URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
9322
9403
  URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
9323
9404
  });
@@ -18037,7 +18118,7 @@ var package_default;
18037
18118
  var init_package = __esm(() => {
18038
18119
  package_default = {
18039
18120
  name: "@mrciphersmith/keryx",
18040
- version: "0.3.19",
18121
+ version: "0.3.21",
18041
18122
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
18042
18123
  private: false,
18043
18124
  publishConfig: {
@@ -129096,6 +129177,37 @@ function createSystemWebWorkerRunner() {
129096
129177
 
129097
129178
  // src/harness/web/outbound-secret.ts
129098
129179
  init_service4();
129180
+ var HEX32_FRAGMENT = "[0-9a-f]{32}";
129181
+ var SHA40_FRAGMENT = "[0-9a-f]{40}";
129182
+ var GIST_HEX32_PATH_RE = new RegExp(`^/(?:[^/]+/)?(${HEX32_FRAGMENT})/?$`, "i");
129183
+ var GITHUB_COMMIT_OR_BLOB_PATH_RE = new RegExp(`/(?:commit|blob)/(${SHA40_FRAGMENT})(?:/|$)`, "i");
129184
+ var GITLAB_COMMIT_PATH_RE = new RegExp(`/-/commit/(${SHA40_FRAGMENT})(?:/|$)`, "i");
129185
+ function knownPublicIdentifierIn(url) {
129186
+ const host = url.hostname.toLowerCase();
129187
+ if (host === "gist.github.com") {
129188
+ return GIST_HEX32_PATH_RE.exec(url.pathname)?.[1] ?? null;
129189
+ }
129190
+ if (host === "github.com") {
129191
+ return GITHUB_COMMIT_OR_BLOB_PATH_RE.exec(url.pathname)?.[1] ?? null;
129192
+ }
129193
+ if (host === "gitlab.com") {
129194
+ return GITLAB_COMMIT_PATH_RE.exec(url.pathname)?.[1] ?? null;
129195
+ }
129196
+ return null;
129197
+ }
129198
+ function stripKnownPublicIdentifier(text) {
129199
+ let url;
129200
+ try {
129201
+ url = new URL(text);
129202
+ } catch {
129203
+ return text;
129204
+ }
129205
+ const identifier = knownPublicIdentifierIn(url);
129206
+ if (identifier === null) {
129207
+ return text;
129208
+ }
129209
+ return text.replace(identifier, "0".repeat(identifier.length));
129210
+ }
129099
129211
  function safeDecodeComponent(value) {
129100
129212
  try {
129101
129213
  return decodeURIComponent(value);
@@ -129139,13 +129251,14 @@ function anyComponentLooksSecret(text) {
129139
129251
  return false;
129140
129252
  }
129141
129253
  function containsOutboundSecret(text) {
129142
- if (detectSecrets(text).length > 0) {
129254
+ const scanText = stripKnownPublicIdentifier(text);
129255
+ if (detectSecrets(scanText).length > 0) {
129143
129256
  return true;
129144
129257
  }
129145
- if (isEntropyBackendEnabled() && detectEntropy(text).length > 0) {
129258
+ if (isEntropyBackendEnabled() && detectEntropy(scanText).length > 0) {
129146
129259
  return true;
129147
129260
  }
129148
- return anyComponentLooksSecret(text);
129261
+ return anyComponentLooksSecret(scanText);
129149
129262
  }
129150
129263
  async function recordOutboundSecretFinding(cwd, tool, subject) {
129151
129264
  try {
package/dist/core.js CHANGED
@@ -4029,6 +4029,24 @@ var init_config2 = __esm(() => {
4029
4029
  });
4030
4030
 
4031
4031
  // src/security/detect/entropy.ts
4032
+ function isAdjacentLabel(content, start) {
4033
+ const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
4034
+ if (ADJACENT_LABEL.test(before)) {
4035
+ return true;
4036
+ }
4037
+ if (before.trim().length > 0) {
4038
+ return false;
4039
+ }
4040
+ const lineStart = content.lastIndexOf(`
4041
+ `, start - 1);
4042
+ if (lineStart < 0) {
4043
+ return false;
4044
+ }
4045
+ const prevLineStart = content.lastIndexOf(`
4046
+ `, lineStart - 1) + 1;
4047
+ const prevLine = content.slice(prevLineStart, lineStart);
4048
+ return CONTINUATION_LABEL_LINE_RE.test(prevLine);
4049
+ }
4032
4050
  function isHexBlob(value) {
4033
4051
  return HEX_BLOB.test(value) && /[a-f]/i.test(value);
4034
4052
  }
@@ -4040,8 +4058,24 @@ function lastLineOf(before) {
4040
4058
  `);
4041
4059
  return newline < 0 ? before : before.slice(newline + 1);
4042
4060
  }
4061
+ function isPlainCasedWord(letters) {
4062
+ return /^[a-z]+$/.test(letters) || /^[A-Z]+$/.test(letters) || /^[A-Z][a-z]*$/.test(letters);
4063
+ }
4043
4064
  function isSingleTransitionTag(segment) {
4044
- return /^[A-Za-z]+[0-9]+$/.test(segment) || /^[0-9]+[A-Za-z]+$/.test(segment);
4065
+ const letterThenDigit = /^([A-Za-z]+)([0-9]+)$/.exec(segment);
4066
+ if (letterThenDigit !== null) {
4067
+ const letters = letterThenDigit[1];
4068
+ return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
4069
+ }
4070
+ const digitThenLetter = /^([0-9]+)([A-Za-z]+)$/.exec(segment);
4071
+ if (digitThenLetter !== null) {
4072
+ const letters = digitThenLetter[2];
4073
+ return letters.length <= TAG_MAX_LETTER_RUN && isPlainCasedWord(letters);
4074
+ }
4075
+ return false;
4076
+ }
4077
+ function isSlugHexTail(segment) {
4078
+ return SLUG_HEX_TAIL_RE.test(segment);
4045
4079
  }
4046
4080
  function isWordSlug(value) {
4047
4081
  const segments = value.split(/[-_]/);
@@ -4049,15 +4083,25 @@ function isWordSlug(value) {
4049
4083
  return false;
4050
4084
  }
4051
4085
  let words = 0;
4052
- for (const segment of segments) {
4053
- const isAlpha = /^[A-Za-z]+$/.test(segment);
4086
+ for (let i = 0;i < segments.length; i += 1) {
4087
+ const segment = segments[i];
4054
4088
  const isDigits = /^[0-9]+$/.test(segment);
4055
- if (!isAlpha && !isDigits && !isSingleTransitionTag(segment)) {
4056
- return false;
4089
+ if (isDigits) {
4090
+ continue;
4057
4091
  }
4058
- if (isAlpha && segment.length >= 3) {
4059
- words += 1;
4092
+ if (isPlainCasedWord(segment)) {
4093
+ if (segment.length >= 3) {
4094
+ words += 1;
4095
+ }
4096
+ continue;
4060
4097
  }
4098
+ if (isSingleTransitionTag(segment)) {
4099
+ continue;
4100
+ }
4101
+ if (i === segments.length - 1 && isSlugHexTail(segment)) {
4102
+ continue;
4103
+ }
4104
+ return false;
4061
4105
  }
4062
4106
  return words >= 1;
4063
4107
  }
@@ -4201,6 +4245,36 @@ function secretShapedCandidatesIn(text) {
4201
4245
  function looksSecretShapedIn(text) {
4202
4246
  return secretShapedCandidatesIn(text).length > 0;
4203
4247
  }
4248
+ function labelledPieceQualifies(piece) {
4249
+ if (isAllowShapedValue(piece)) {
4250
+ return { entropy: shannonEntropy(piece) };
4251
+ }
4252
+ if (isHexBlob(piece)) {
4253
+ return { entropy: shannonEntropy(piece) };
4254
+ }
4255
+ if (!/[0-9]/.test(piece) && !/[+=]/.test(piece)) {
4256
+ return null;
4257
+ }
4258
+ const withoutSeparators = piece.replace(/[-_.+]/g, "");
4259
+ const entropy = shannonEntropy(withoutSeparators.length > 0 ? withoutSeparators : piece);
4260
+ if (entropy >= 3.6) {
4261
+ return { entropy };
4262
+ }
4263
+ return null;
4264
+ }
4265
+ function firstQualifyingSlashPiece(head) {
4266
+ let offset = 0;
4267
+ for (const piece of head.split("/")) {
4268
+ if (piece.length > 0) {
4269
+ const found = labelledPieceQualifies(piece);
4270
+ if (found !== null) {
4271
+ return { offset, length: piece.length, entropy: found.entropy };
4272
+ }
4273
+ }
4274
+ offset += piece.length + 1;
4275
+ }
4276
+ return null;
4277
+ }
4204
4278
  function detectEntropy(content) {
4205
4279
  const matches = [];
4206
4280
  const urlSpans = findUrlSpans(content);
@@ -4223,9 +4297,9 @@ function detectEntropy(content) {
4223
4297
  if (urlSpans.some((u) => start < u.end && end > u.start)) {
4224
4298
  continue;
4225
4299
  }
4226
- const { qualifies, entropy } = bareShapeQualifies(value);
4227
- if (qualifies) {
4228
- matches.push(buildMatch(start, end, value, entropy));
4300
+ const found = labelledPieceQualifies(value);
4301
+ if (found !== null) {
4302
+ matches.push(buildMatch(start, end, value, found.entropy));
4229
4303
  }
4230
4304
  }
4231
4305
  TOKEN.lastIndex = 0;
@@ -4240,26 +4314,32 @@ function detectEntropy(content) {
4240
4314
  if (matches.some((existing) => start < existing.end && end > existing.start)) {
4241
4315
  continue;
4242
4316
  }
4243
- const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
4244
- const { qualifies, entropy } = bareShapeQualifies(head);
4245
- if (!qualifies) {
4317
+ if (!isAdjacentLabel(content, start)) {
4246
4318
  continue;
4247
4319
  }
4248
- const before = lastLineOf(content.slice(Math.max(0, start - LABEL_WINDOW), start));
4249
- if (!SENSITIVE_LABEL.test(before)) {
4320
+ const head = TOKEN_HEAD.exec(value)?.[0] ?? value;
4321
+ if (head.includes("/")) {
4322
+ const piece = firstQualifyingSlashPiece(head);
4323
+ if (piece === null) {
4324
+ continue;
4325
+ }
4326
+ const pieceStart = start + piece.offset;
4327
+ const pieceEnd = pieceStart + piece.length;
4328
+ matches.push(buildMatch(pieceStart, pieceEnd, content.slice(pieceStart, pieceEnd), piece.entropy));
4250
4329
  continue;
4251
4330
  }
4252
- if (isAllowShapedValue(head) && !ADJACENT_LABEL.test(before)) {
4331
+ const found = labelledPieceQualifies(head);
4332
+ if (found === null) {
4253
4333
  continue;
4254
4334
  }
4255
- matches.push(buildMatch(start, end, value, entropy));
4335
+ matches.push(buildMatch(start, end, value, found.entropy));
4256
4336
  }
4257
4337
  return matches.sort((a, b) => a.start - b.start);
4258
4338
  }
4259
- var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), SENSITIVE_LABEL, ADJACENT_LABEL, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
4339
+ var SENSITIVE_LABEL_WORDS = "key|secret|token|password|passwd|api|credential|auth|bearer", caseless = (words) => words.replace(/[a-z]/g, (ch) => `[${ch}${ch.toUpperCase()}]`), FILLER_WORDS = "is|was|set|to|the|value|now", ADJACENT_LABEL, CONTINUATION_LABEL_LINE_RE, LABEL_ASSIGNMENT_RE, TOKEN, TOKEN_HEAD, LABEL_WINDOW = 40, HEX_BLOB, FULL_GIT_SHA_RE, UUID_RE, INTEGRITY_RE, TAG_MAX_LETTER_RUN = 8, SLUG_HEX_TAIL_RE, URL_SPAN_RE, URL_TRAILING_PUNCTUATION_RE, URL_COMPONENT_MIN_LENGTH = 20;
4260
4340
  var init_entropy = __esm(() => {
4261
- SENSITIVE_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(${caseless(SENSITIVE_LABEL_WORDS)})(?:$|[^A-Za-z0-9]|(?=[A-Z]))`);
4262
- ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*["']?$`);
4341
+ ADJACENT_LABEL = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*` + `(?:\\s+(?:${caseless(FILLER_WORDS)})){0,2}` + `["']?\\s*[:=]?\\s*["']?$`);
4342
+ CONTINUATION_LABEL_LINE_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${caseless(SENSITIVE_LABEL_WORDS)})[A-Za-z0-9_-]*["']?\\s*[:=]?\\s*\\\\\\s*$`);
4263
4343
  LABEL_ASSIGNMENT_RE = new RegExp(`(?:^|[^A-Za-z0-9])(?:${SENSITIVE_LABEL_WORDS})[A-Za-z0-9_-]*"?\\s*=\\s*"?([A-Za-z0-9+/_=-]{6,})`, "gi");
4264
4344
  TOKEN = /[A-Za-z0-9+/=_-]{20,}(?:\.[A-Za-z0-9+/=_-]{6,})*/g;
4265
4345
  TOKEN_HEAD = /^[A-Za-z0-9+/=_-]+/;
@@ -4267,6 +4347,7 @@ var init_entropy = __esm(() => {
4267
4347
  FULL_GIT_SHA_RE = /^[0-9a-f]{40}$/i;
4268
4348
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4269
4349
  INTEGRITY_RE = /^(?:sha256|sha512)-[A-Za-z0-9+/]+=*$/;
4350
+ SLUG_HEX_TAIL_RE = /^[0-9a-f]{10,16}$/i;
4270
4351
  URL_SPAN_RE = /\bhttps?:\/\/[^\s<>"'\\]+/g;
4271
4352
  URL_TRAILING_PUNCTUATION_RE = /[.,;:!?)\]}]$/;
4272
4353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {